---
name: fluent-version-manage
description: Manage Fluent Commerce module versions. Read, bump, sync, and tag versions across POM files, module.json, and CHANGELOG.md. Ensures all version references stay consistent. Triggers on "version status", "bump version", "tag release", "version sync", "what version", "prepare release".
user-invocable: true
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
argument-hint: [status|bump|sync|tag] [--level patch|minor|major] [--module-path <path>] [--dry-run]
cursor-tier: manual
planning-gate: advisory
---

# Version Management

Manage the full version lifecycle for Fluent Commerce extension modules: read current versions from all source-of-truth files, bump with semver policy, update CHANGELOG.md, synchronize mismatches, and create git release tags.

**ADD Phase:** Packaging & Versioning (Phase 5)

## Pre-flight: Feedback Check

Before starting, check for past learnings from this skill:

1. Check if `accounts/<PROFILE>/feedback/fluent-version-manage.jsonl` exists
2. If yes, read last 20 records
3. Filter to: FAILURE or USER_CORRECTED outcomes, confidence = CONFIRMED or PROMOTED
4. Extract top 3 relevant learnings — use as internal guidance during execution
5. Do NOT show raw feedback to the user — silently adjust approach based on past issues

## Planning Gate

### Pre-flight: Plan Verification

Before proceeding, check for an existing approved plan:

1. **Search** `accounts/<PROFILE>/features/*/status.json` for an entry with `plan: "APPROVED"` that covers this version change, OR check `accounts/<PROFILE>/tasks/` for a task plan with `Status: APPROVED`
2. **If multi-artifact work** (version bump as part of feature delivery): STOP. Invoke `/fluent-feature-plan` first — this skill cannot be used for multi-artifact work without a feature plan
3. **If approved plan found:** Skip to implementation, referencing the plan's §17 (Deployment) section
4. **If version-only work with no plan:** Continue to the Planning Gate below to write a plan for this skill
5. **`status` (read-only) and `sync --dry-run`** are always exempt from planning

**Before bumping versions, write a plan using the template from `PLAN_TEMPLATE.md` in the `fluent-feature-plan` skill.** `status` (read-only) and `sync --dry-run` are exempt.

**Version-manage specific emphasis — ensure these are covered:**

1. **Business Context (Section 1)** — what change drives this release
2. **Scope (Section 2)** — current version → new version, bump type (patch/minor/major) with rationale
3. **Files (Section 6)** — all pom.xml, module.json, package.json, CHANGELOG.md to modify
4. **Detailed Design (Section 5)** — CHANGELOG entries to move from [Unreleased] to new version heading
5. **Impacted retailers (Section 4.8)** — which environments will receive this version

**Write the plan to:** `accounts/<PROFILE>/tasks/<YYYY-MM-DD>-version-manage-<slug>.md`. Set `Status: PENDING`.

Present the full plan content to the user and wait for approval before modifying any version files. On approval, update the file to `Status: APPROVED`. If the user says "just do it", proceed directly (still write the file for audit trail).

## Ownership Boundary

This skill owns:

- Reading version state across all module files
- Bumping versions with semver policy (patch / minor / major)
- Synchronizing version mismatches across POM files and module.json
- Updating CHANGELOG.md during version bumps (moving `[Unreleased]` to a dated heading)
- Creating annotated git tags for releases
- Git commit of version changes (optional)

This skill does **not** own:

- Maven builds or test execution -> `/fluent-build`
- Module deployment -> `/fluent-module-deploy`
- Structural validation -> `/fluent-module-validate`
- CHANGELOG content authoring -> developer responsibility (this skill only transforms headings)

### Delegation from `/fluent-build`

The existing `/fluent-build` skill has a "Version Bump" section (step 1 of 4). This skill extracts and expands that functionality. After implementation, `/fluent-build` should delegate its version bump step here:

```
/fluent-version-manage bump --level patch
```

| Capability | `/fluent-build` (current) | `/fluent-version-manage` (this skill) |
|-----------|--------------------------|--------------------------------------|
| Read current version | Implicit (before bump) | Explicit `status` command with JSON output |
| Bump version | Patch only, inline | patch / minor / major with `--dry-run` |
| Sync check | None | `sync` command detects and fixes mismatches |
| CHANGELOG update | None | Moves `[Unreleased]` to dated version heading |
| Git tag | None | `tag` command with annotated tags |
| Git commit | None | Optional `--commit` flag |
| SNAPSHOT handling | None | Strips `-SNAPSHOT` suffix during bump |

## Handoff Protocol

### Signals emitted by this skill

| Signal | Condition | Example |
|--------|-----------|---------|
| `-> READY: <path>` | Version bumped | `-> READY: Version bumped 0.0.29 -> 0.0.30 in pom.xml + module.json` |
| `-> NEXT: /fluent-<skill>` | Ready for build | `-> NEXT: /fluent-build` |
| `-> SKIP: <reason>` | Status/dry-run (exempt from planning) | `-> SKIP: Version status check only — no changes made` |

### Error codes

| Code | Condition | Recovery |
|------|-----------|----------|
| `PLAN_REQUIRED` | Version bump attempted without approved plan | Run `/fluent-feature-plan` first |
| `VALIDATION_FAILED` | Invalid version format or conflict | Fix version string and retry |

## When to Use

- "What version is this module?" -> `status`
- "Are all version files consistent?" -> `status`
- Module code changed and needs a new version before redeploy -> `bump`
- Version mismatch detected by `/fluent-module-validate` -> `sync`
- After code is finalized and ready for release -> `bump --commit --tag`
- Tag an already-bumped version for release -> `tag`
- Preparing a release candidate -> `bump --level minor --dry-run` (preview first)

## Required Inputs

| Parameter | Required | Default | Description |
|-----------|----------|---------|-------------|
| `command` | Yes | -- | One of: `status`, `bump`, `sync`, `tag` |
| `--level` | For `bump` | `patch` | Semver level: `patch`, `minor`, `major` |
| `--module-path` | No | Auto-detect | Path to module root (directory containing `resources/module.json` and `plugins/`) |
| `--dry-run` | No | `false` | Show what would change without modifying files |
| `--changelog-entry` | For `bump` | Auto-generate | CHANGELOG entry text for the new version |
| `--commit` | No | `false` | Git commit the version changes after bump |
| `--tag` | No | `false` | Also create git tag after bump (implies version is final) |
| `--profile` | No | Active profile | For module path auto-detection under `accounts/<PROFILE>/SOURCE/backend/` |

## Version Source-of-Truth Files

Every Fluent extension module has up to 7+ files that must carry the same version string. The canonical source of truth is `resources/module.json` because that is what Fluent Commerce reads during module installation.

| # | File | Field / Location | Extraction Pattern |
|---|------|-----------------|-------------------|
| 1 | `resources/module.json` | `"version": "x.y.z"` | JSON parse -> `.version` |
| 2 | `plugins/pom.xml` | `<version>` under `<project>` | Regex: `<project>[^]*?<version>([^<]+)</version>` (first `<version>` after `<project>` that is NOT inside `<parent>`) |
| 3 | `plugins/rules/<alias>/pom.xml` | `<version>` under `<parent>` | Regex: `<parent>[^]*?<version>([^<]+)</version>` |
| 4 | `plugins/types/<alias>/pom.xml` | `<version>` under `<parent>` | Regex: `<parent>[^]*?<version>([^<]+)</version>` |
| 5 | `plugins/util/<alias>/pom.xml` | `<version>` under `<parent>` | Regex: `<parent>[^]*?<version>([^<]+)</version>` |
| 6 | `CHANGELOG.md` (if exists) | `## [x.y.z]` heading | Regex: `^## \[(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?)\]` |
| 7 | `package.json` (if exists) | `"version": "x.y.z"` | JSON parse -> `.version` |

### POM Version Extraction Details

POM files use XML without a full parser. Use these regex strategies:

**Parent POM (`plugins/pom.xml`) -- project-level version:**

The parent POM typically has this structure:

```xml
<project ...>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.fluentcommerce</groupId>
  <artifactId>fc-module-my-extensions</artifactId>
  <version>1.2.3</version>          <!-- THIS is the project version -->
  <packaging>pom</packaging>
  ...
</project>
```

Extraction: Read the file content. Find `<version>` that appears as a direct child of `<project>` but NOT inside a `<parent>` block.

```bash
# Extract project version from parent POM (skip any <parent> block version)
node -e "
const fs = require('fs');
const xml = fs.readFileSync(process.argv[1], 'utf8');
// Remove parent block to avoid matching parent version
const noParent = xml.replace(/<parent>[\s\S]*?<\/parent>/, '');
const m = noParent.match(/<version>([^<]+)<\/version>/);
console.log(m ? m[1] : 'NOT_FOUND');
" "plugins/pom.xml"
```

**Child POM (`plugins/rules/<alias>/pom.xml`) -- parent reference version:**

```xml
<project ...>
  <parent>
    <groupId>com.fluentcommerce</groupId>
    <artifactId>fc-module-my-extensions</artifactId>
    <version>1.2.3</version>          <!-- THIS inherits from parent -->
  </parent>
  <artifactId>my-extensions-rules</artifactId>
  <!-- No <version> here = inherits from parent -->
</project>
```

Extraction: Read the `<version>` inside the `<parent>` block.

```bash
# Extract parent version from child POM
node -e "
const fs = require('fs');
const xml = fs.readFileSync(process.argv[1], 'utf8');
const parentBlock = xml.match(/<parent>([\s\S]*?)<\/parent>/);
if (!parentBlock) { console.log('NO_PARENT_BLOCK'); process.exit(0); }
const m = parentBlock[1].match(/<version>([^<]+)<\/version>/);
console.log(m ? m[1] : 'NOT_FOUND');
" "plugins/rules/my-extensions/pom.xml"
```

**Important:** Some child POMs also declare a project-level `<version>` that overrides parent inheritance. If present, both the parent reference AND the project version must be updated.

### Discovering Child POM Aliases

The module alias (subdirectory name under `rules/`, `types/`, `util/`) varies per module. Discover dynamically:

```bash
# List all child POMs under plugins/
node -e "
const fs = require('fs');
const path = require('path');
const root = process.argv[1];
const dirs = ['rules', 'types', 'util'];
const results = [];
for (const d of dirs) {
  const dir = path.join(root, 'plugins', d);
  if (!fs.existsSync(dir)) continue;
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    if (entry.isDirectory()) {
      const pom = path.join(dir, entry.name, 'pom.xml');
      if (fs.existsSync(pom)) results.push({ type: d, alias: entry.name, pom });
    }
  }
}
console.log(JSON.stringify(results, null, 2));
" "<MODULE_ROOT>"
```

---

## Commands

### 1. `status` -- Read Current Versions

Read version strings from every source-of-truth file, check consistency, and report CHANGELOG state.

**Algorithm:**

```
1. Locate module root (auto-detect or --module-path)
2. Read resources/module.json -> extract "version"
3. Read plugins/pom.xml -> extract project <version>
4. Glob for plugins/{rules,types,util}/*/pom.xml -> extract parent <version> from each
5. Check for CHANGELOG.md -> scan for [Unreleased] section and latest [x.y.z] heading
6. Check for package.json -> extract "version" if present
7. Compare all extracted versions
8. Output structured report
```

**Output schema:**

```json
{
  "command": "status",
  "modulePath": "accounts/<PROFILE>/SOURCE/backend/fluentcommerce-fc-module-my-extensions",
  "moduleName": "fluent-commerce/fc-module-my-extensions",
  "currentVersion": "1.2.3",
  "consistent": true,
  "files": {
    "resources/module.json": { "version": "1.2.3", "field": "version" },
    "plugins/pom.xml": { "version": "1.2.3", "field": "project.version" },
    "plugins/rules/my-extensions/pom.xml": { "version": "1.2.3", "field": "parent.version" },
    "plugins/types/my-extensions/pom.xml": { "version": "1.2.3", "field": "parent.version" },
    "plugins/util/my-extensions/pom.xml": { "version": "1.2.3", "field": "parent.version" }
  },
  "changelog": {
    "exists": true,
    "hasUnreleased": true,
    "unreleasedEntries": 3,
    "latestRelease": "1.2.2",
    "latestReleaseDate": "2026-02-20"
  },
  "packageJson": {
    "exists": false,
    "version": null
  },
  "snapshot": false
}
```

When `consistent` is `false`, `currentVersion` is set to the `resources/module.json` version (canonical) and a `mismatches` array is added:

```json
{
  "consistent": false,
  "currentVersion": "1.2.3",
  "mismatches": [
    { "file": "plugins/rules/my-extensions/pom.xml", "expected": "1.2.3", "actual": "1.2.2" }
  ]
}
```

**Console output format:**

```
VERSION STATUS
===========
Module: fluent-commerce/fc-module-my-extensions
Path:   accounts/MY_PROFILE/SOURCE/backend/fluentcommerce-fc-module-my-extensions
Version: 1.2.3

FILES
-----
[OK]   resources/module.json          1.2.3
[OK]   plugins/pom.xml                1.2.3
[OK]   plugins/rules/my-extensions    1.2.3
[OK]   plugins/types/my-extensions    1.2.3
[OK]   plugins/util/my-extensions     1.2.3

CHANGELOG
---------
[Unreleased]: 3 entries pending
Latest release: [1.2.2] - 2026-02-20

Result: ALL VERSIONS CONSISTENT
```

### 2. `bump` -- Increment Version

Increment the version across all source-of-truth files using semver policy.

**Algorithm:**

```
1. Run status internally to get current state
2. If versions are inconsistent:
   -> ABORT with error: "Version mismatch detected. Run /fluent-version-manage sync first."
3. Parse current version: MAJOR.MINOR.PATCH[-SNAPSHOT]
4. Strip -SNAPSHOT suffix if present
5. Calculate new version based on --level:
   - patch: MAJOR.MINOR.(PATCH+1)
   - minor: MAJOR.(MINOR+1).0
   - major: (MAJOR+1).0.0
6. If --dry-run:
   -> Print "Would update X.Y.Z -> A.B.C in N files" and STOP
7. Update each file:
   a. resources/module.json:
      - Read JSON, set .version = newVersion, write back
   b. plugins/pom.xml:
      - Replace project <version>OLD</version> with <version>NEW</version>
      - Avoid replacing <version> inside <parent> or <dependency> blocks
   c. Each child POM (rules/types/util):
      - Replace <version>OLD</version> inside <parent> block
      - If child also has project-level <version>, replace that too
   d. package.json (if exists):
      - Read JSON, set .version = newVersion, write back
8. Transform CHANGELOG.md (see CHANGELOG Transformation Rules below)
9. Verify: re-run status to confirm all files now show new version
10. If --commit:
    -> git add <all changed files>
    -> git commit -m "chore: bump version to <newVersion>"
11. If --tag:
    -> git tag -a "v<newVersion>" -m "Release <newVersion>"
12. Report: list of files changed with old -> new version
```

**POM update -- regex replacement:**

For the parent POM (`plugins/pom.xml`), the replacement must target only the project-level version:

```bash
# Update parent POM project version
node -e "
const fs = require('fs');
const file = process.argv[1];
const oldVer = process.argv[2];
const newVer = process.argv[3];
let xml = fs.readFileSync(file, 'utf8');
// Replace version outside of <parent> block
// Strategy: split on <parent>...</parent>, replace in non-parent sections
const parts = xml.split(/(<parent>[\s\S]*?<\/parent>)/);
for (let i = 0; i < parts.length; i++) {
  if (!parts[i].startsWith('<parent>')) {
    parts[i] = parts[i].replace(
      '<version>' + oldVer + '</version>',
      '<version>' + newVer + '</version>'
    );
  }
}
fs.writeFileSync(file, parts.join(''));
console.log('Updated ' + file);
" "plugins/pom.xml" "1.2.3" "1.2.4"
```

For child POMs, update the version inside the `<parent>` block:

```bash
# Update child POM parent version reference
node -e "
const fs = require('fs');
const file = process.argv[1];
const oldVer = process.argv[2];
const newVer = process.argv[3];
let xml = fs.readFileSync(file, 'utf8');
xml = xml.replace(
  /(<parent>[\s\S]*?<version>)(\Q${oldVer}\E)(<\/version>[\s\S]*?<\/parent>)/,
  function(match, pre, ver, post) {
    return pre + newVer + post;
  }
);
// Also update project-level version if present (outside parent block)
const parts = xml.split(/(<parent>[\s\S]*?<\/parent>)/);
for (let i = 0; i < parts.length; i++) {
  if (!parts[i].startsWith('<parent>')) {
    parts[i] = parts[i].replace(
      '<version>' + oldVer + '</version>',
      '<version>' + newVer + '</version>'
    );
  }
}
fs.writeFileSync(file, parts.join(''));
console.log('Updated ' + file);
" "plugins/rules/my-extensions/pom.xml" "1.2.3" "1.2.4"
```

**Dry-run output example:**

```
VERSION BUMP (DRY RUN)
======================
Module: fluent-commerce/fc-module-my-extensions
Current: 1.2.3
New:     1.2.4 (patch)

Files that would be updated:
  resources/module.json               1.2.3 -> 1.2.4
  plugins/pom.xml                     1.2.3 -> 1.2.4
  plugins/rules/my-extensions/pom.xml 1.2.3 -> 1.2.4
  plugins/types/my-extensions/pom.xml 1.2.3 -> 1.2.4
  plugins/util/my-extensions/pom.xml  1.2.3 -> 1.2.4
  CHANGELOG.md                        [Unreleased] -> [1.2.4] - 2026-02-23

No files were modified (dry run).
```

### 3. `sync` -- Fix Version Mismatches

Detect and repair version inconsistencies using `resources/module.json` as the canonical source.

**Algorithm:**

```
1. Run status internally
2. If consistent: print "All versions already in sync at X.Y.Z" and STOP
3. Determine canonical version:
   a. If resources/module.json exists and has a version -> use it (preferred)
   b. Else use the highest semver found across all files
4. If --dry-run:
   -> Print "Would sync N files to version X.Y.Z" and STOP
5. Update every file that has a different version to the canonical version
   (same update logic as bump, but only touching mismatched files)
6. Re-run status to confirm consistency
7. Report: files changed with old -> canonical version
```

**Console output:**

```
VERSION SYNC
============
Canonical version: 1.2.3 (from resources/module.json)

Changes:
  [FIXED] plugins/rules/my-extensions/pom.xml   1.2.2 -> 1.2.3
  [FIXED] plugins/util/my-extensions/pom.xml     1.2.1 -> 1.2.3
  [OK]    resources/module.json                   1.2.3 (canonical)
  [OK]    plugins/pom.xml                         1.2.3
  [OK]    plugins/types/my-extensions/pom.xml     1.2.3

Result: 2 files synced to 1.2.3
```

### 4. `tag` -- Create Git Release Tag

Create an annotated git tag for the current version.

**Algorithm:**

```
1. Run status internally to get current version
2. If versions are inconsistent:
   -> ABORT: "Version mismatch. Run /fluent-version-manage sync first."
3. Construct tag name: "v" + currentVersion (e.g., "v1.2.3")
4. Check if tag already exists:
   -> git tag -l "v1.2.3"
   -> If exists, ABORT: "Tag v1.2.3 already exists."
5. If --dry-run:
   -> Print "Would create tag v1.2.3" and STOP
6. Check for uncommitted changes to version files:
   -> git status --porcelain -- resources/module.json plugins/ CHANGELOG.md package.json
   -> If dirty, WARN: "Uncommitted version file changes. Consider committing first."
7. Create annotated tag:
   -> git tag -a "v1.2.3" -m "Release 1.2.3"
8. Report: tag name and instructions to push
```

**Console output:**

```
GIT TAG
=======
Module: fluent-commerce/fc-module-my-extensions
Version: 1.2.3
Tag: v1.2.3

Tag created successfully.

To push the tag to remote:
  git push origin v1.2.3
```

---

## CHANGELOG Transformation Rules

The `bump` command transforms CHANGELOG.md by moving the `[Unreleased]` section contents under a new dated version heading.

**Preview mode:** Before applying CHANGELOG transforms, use `--dry-run` to preview what will change:
- Show which entries will move from `[Unreleased]` to the new version heading
- Show the new version heading format
- Do not modify any files

This prevents accidental CHANGELOG corruption when version numbers are wrong.

### Expected CHANGELOG Format (Keep a Changelog)

```markdown
# Changelog

## [Unreleased]

### Added
- New CancelOrder rule for order cancellation workflow
- Webhook retry logic with exponential backoff

### Fixed
- Status transition guard in FulfilmentComplete rule

## [1.2.3] - 2026-02-20

### Added
- Initial webhook support

## [1.2.2] - 2026-02-15
...
```

### Dry-Run Preview

Before transforming the CHANGELOG, **always show a preview** of what will change:

```
/fluent-version-manage bump --level patch --dry-run
```

In dry-run mode:
1. Read CHANGELOG.md and extract the `[Unreleased]` section
2. Show the user what the new version heading will look like
3. Show what content will move from `[Unreleased]` to the new heading
4. Do NOT write any changes
5. Wait for user confirmation before applying

This prevents accidental CHANGELOG corruption, especially when the `[Unreleased]` section is empty or the format doesn't match expectations.

### Transformation Algorithm

```
1. Read CHANGELOG.md
2. Find the [Unreleased] heading: regex ^## \[Unreleased\]\s*$
3. Find the next version heading (or end of file):
   regex ^## \[\d+\.\d+\.\d+
4. Extract everything between [Unreleased] heading and next heading
5. If extracted content is empty (only whitespace):
   -> WARN: "No unreleased entries found. CHANGELOG heading will be empty."
6. Replace:
   BEFORE:
     ## [Unreleased]
     <entries>
     ## [1.2.3] - 2026-02-20

   AFTER:
     ## [Unreleased]

     ## [1.2.4] - 2026-02-23
     <entries>
     ## [1.2.3] - 2026-02-20
7. The date format is YYYY-MM-DD (ISO 8601 date)
8. Write updated CHANGELOG.md
```

### Before/After Example

**Before (`bump --level patch` on 2026-02-23):**

```markdown
# Changelog

## [Unreleased]

### Added
- New CancelOrder rule for order cancellation workflow
- Webhook retry logic with exponential backoff

### Fixed
- Status transition guard in FulfilmentComplete rule

## [1.2.3] - 2026-02-20

### Added
- Initial webhook support
```

**After:**

```markdown
# Changelog

## [Unreleased]

## [1.2.4] - 2026-02-23

### Added
- New CancelOrder rule for order cancellation workflow
- Webhook retry logic with exponential backoff

### Fixed
- Status transition guard in FulfilmentComplete rule

## [1.2.3] - 2026-02-20

### Added
- Initial webhook support
```

---

## Validation Rules

| Rule | Enforcement | Recovery |
|------|------------|----------|
| Version must match semver: `^\d+\.\d+\.\d+(-SNAPSHOT)?$` | All commands | Report invalid format and the file containing it |
| SNAPSHOT suffix is stripped during bump | `bump` | `1.2.3-SNAPSHOT` becomes `1.2.4` (not `1.2.3-SNAPSHOT+1`) |
| Bump refuses on inconsistent versions | `bump` | Run `sync` first, then retry bump |
| Bump warns on uncommitted changes to version files | `bump` (without `--dry-run`) | Commit or stash first, or acknowledge the warning |
| Tag refuses if tag already exists | `tag` | Use a different version or delete the existing tag manually |
| Tag warns on uncommitted version file changes | `tag` | Commit first |
| Module.json must exist for any command | All commands | Cannot operate without a module manifest |

### SNAPSHOT Handling

SNAPSHOT versions are common during active development in Maven-based projects:

| Scenario | Input | `--level patch` | `--level minor` | `--level major` |
|----------|-------|-----------------|-----------------|-----------------|
| Release version | `1.2.3` | `1.2.4` | `1.3.0` | `2.0.0` |
| SNAPSHOT version | `1.2.3-SNAPSHOT` | `1.2.3` | `1.3.0` | `2.0.0` |

For SNAPSHOT input, `patch` bumps to the release version (strips suffix without incrementing). This follows the convention that SNAPSHOT represents a pre-release of that version.

---

## Module Path Auto-Detection

When `--module-path` is not provided, search for the module root automatically:

```
1. Search accounts/<PROFILE>/SOURCE/backend/ recursively for resources/module.json
2. For each found module.json:
   a. Verify plugins/ directory exists alongside it (or one level up)
   b. Read module name for display
3. If exactly 1 module found -> use it
4. If multiple found -> list them and ask user to choose:
   "Found N modules under accounts/<PROFILE>/SOURCE/backend/:
    [1] fluent-commerce/fc-module-my-extensions (accounts/MY_PROFILE/SOURCE/backend/fluentcommerce-fc-module-my-extensions)
    [2] fluent-commerce/fc-module-my-returns (accounts/MY_PROFILE/SOURCE/backend/fluentcommerce-fc-module-my-returns)
    Which module? Specify with --module-path <path>"
5. If none found -> ERROR: "No module found. Provide --module-path explicitly."
```

**Handle double-nested directories:** If a directory under SOURCE/backend contains only one subdirectory with a similar name and no direct `module.json` at the outer level, check the inner directory. Record this for the user.

---

## Execution Flow (All Commands)

```
1. AUTO-DETECT module path:
   a. If --module-path provided, verify it exists
   b. Else search accounts/<PROFILE>/SOURCE/backend/ for resources/module.json
   c. Validate: resources/module.json must exist at resolved path

2. DISCOVER version files:
   a. Read resources/module.json for module name and version
   b. Read plugins/pom.xml for project version
   c. Glob plugins/{rules,types,util}/*/pom.xml for child POM versions
   d. Check for CHANGELOG.md at module root
   e. Check for package.json at module root

3. EXECUTE command:
   - status: collect and compare all versions, report
   - bump: validate consistency, increment, update files, transform CHANGELOG
   - sync: detect mismatches, update to canonical
   - tag: validate consistency, check existing tags, create annotated tag

4. VERIFY (for mutating commands):
   Re-run the status check to confirm all files are now consistent

5. REPORT:
   Print structured output with all changes made
```

---

## Integration with Other Skills

| Skill | Integration Point |
|-------|-------------------|
| `/fluent-build` | Delegates version bump to this skill. This skill's `status` output feeds build's pre-check. |
| `/fluent-module-validate` | Version consistency check (check ID `versions`) maps to this skill's `status` command. Validation report `summary.status: NEEDS_FIXES` with a version failure should trigger `sync`. |
| `/fluent-custom-code` | Reads module version from `source-map.json`. If version drift is detected, recommend `bump` or `sync`. |
| `/fluent-pre-deploy-check` | Gate 2.4 (Version bumped) calls `status` to compare local vs deployed version. |
| `/fluent-session` | Version bumps and tags are tracked as session changes. |
| `/fluent-scope-decompose` | Task type `VERSION` maps to this skill. |

### Consuming Cached Artifacts

Before running, check if `/fluent-custom-code` artifacts exist:

```
accounts/<PROFILE>/analysis/code/source-map.json
```

If found, read `modules[].version` and `modules[].versionSource` to pre-populate version information without re-scanning POM files. Fall back to direct file reads if artifacts are missing or stale.

---

## Edge Cases

| Scenario | Behavior |
|----------|----------|
| **SNAPSHOT version** | `bump --level patch` strips SNAPSHOT without incrementing (1.2.3-SNAPSHOT -> 1.2.3). Minor and major still increment normally. |
| **Multiple modules found** | List all discovered modules and require `--module-path` to disambiguate. Never auto-select when ambiguous. |
| **No CHANGELOG.md** | `status` reports `changelog.exists: false`. `bump` skips CHANGELOG transformation with a warning: "No CHANGELOG.md found -- skipping changelog update." |
| **Empty [Unreleased] section** | `bump` still creates the version heading but warns: "No unreleased entries. Version heading will have no content." |
| **No [Unreleased] heading in CHANGELOG** | `bump` prepends a new version heading at the top of the changelog (after the `# Changelog` title) and warns: "No [Unreleased] section found -- added version heading at top." |
| **CHANGELOG has no title line** | Treat the entire file as changelog content. Insert `## [Unreleased]` and `## [x.y.z]` at the top. |
| **Windows path handling** | Use forward slashes in all POM `<module>` references and report paths. Use `path.join()` for filesystem operations but normalize to forward slashes for display. |
| **Child POM has both parent and project version** | Update BOTH the `<parent><version>` and the project-level `<version>` in the same file. |
| **module.json missing** | ABORT all commands. Module.json is required as the canonical version source. |
| **package.json version out of sync** | `sync` updates package.json to match module.json. `status` reports the mismatch. |
| **Git not initialized** | `--commit` and `--tag` fail gracefully: "Not a git repository. Skipping git operations." |
| **Tag already exists** | `tag` aborts with clear message. `bump --tag` also aborts the tag step (but version files are already updated). |
| **Uncommitted changes** | `bump` warns but proceeds (version bump is the intended commit). `tag` warns more strongly (tagging uncommitted state is risky). |
| **Read-only files** | Report which files could not be written and suggest checking file permissions or read-only flags. |

---

## Example: Full `bump` Walkthrough

**Starting state:**

```
resources/module.json        -> "version": "0.0.29"
plugins/pom.xml              -> <version>0.0.29</version>
plugins/rules/hm/pom.xml    -> <parent><version>0.0.29</version></parent>
plugins/types/hm/pom.xml    -> <parent><version>0.0.29</version></parent>
plugins/util/hm/pom.xml     -> <parent><version>0.0.29</version></parent>
CHANGELOG.md                 -> ## [Unreleased] with 2 entries
```

**Command:** `/fluent-version-manage bump --level minor --commit`

**Steps executed:**

1. Status check: all files at `0.0.29` -- consistent
2. Calculate: `0.0.29` -> `0.1.0` (minor bump)
3. Update `resources/module.json`: `"version": "0.1.0"`
4. Update `plugins/pom.xml`: `<version>0.1.0</version>`
5. Update 3 child POMs: `<parent><version>0.1.0</version></parent>`
6. Transform CHANGELOG.md: `[Unreleased]` entries moved under `[0.1.0] - 2026-02-23`
7. Verify: re-read all files, confirm `0.1.0` everywhere
8. Git commit: `git add resources/module.json plugins/pom.xml plugins/rules/hm/pom.xml plugins/types/hm/pom.xml plugins/util/hm/pom.xml CHANGELOG.md && git commit -m "chore: bump version to 0.1.0"`

**Output:**

```
VERSION BUMP
============
Module: fluent-commerce/fc-module-my-extensions
Old version: 0.0.29
New version: 0.1.0 (minor)

Updated files:
  [OK] resources/module.json               0.0.29 -> 0.1.0
  [OK] plugins/pom.xml                     0.0.29 -> 0.1.0
  [OK] plugins/rules/hm/pom.xml            0.0.29 -> 0.1.0
  [OK] plugins/types/hm/pom.xml            0.0.29 -> 0.1.0
  [OK] plugins/util/hm/pom.xml             0.0.29 -> 0.1.0
  [OK] CHANGELOG.md                        [Unreleased] -> [0.1.0] - 2026-02-23

Verification: ALL CONSISTENT at 0.1.0

Git commit: abc1234 "chore: bump version to 0.1.0"
```

## Post-Execution: Feedback Capture

After completing this skill (whether success or failure), write a feedback record:

1. **Classify outcome:** `SUCCESS` (completed cleanly), `PARTIAL_SUCCESS` (completed after fixing issues), `FAILURE` (could not complete), `BLOCKED` (prereq missing), `USER_CORRECTED` (user overrode approach)
2. **Build record:** Create a `feedback-record-v1` JSON object with:
   - `skill`: `"fluent-version-manage"`
   - `feature`: feature slug if applicable, null otherwise
   - `phases`: trace of each phase with PASS/FAIL/SKIP
   - `learnings`: any gotchas discovered during execution
   - `userCorrections`: any corrections the user provided
3. **Redact secrets:** Strip tokens, passwords, API keys. Keep business identifiers.
4. **Append** one JSON line to `accounts/<PROFILE>/feedback/fluent-version-manage.jsonl`
5. **Update** `accounts/<PROFILE>/feedback/index.json` counters (create with `mkdir -p` if missing)

**Skip capture if:** The execution was trivially simple (e.g., user asked a question, no actual write operation happened).

## Known Pitfalls
<!-- feedback-promoted: managed section, updated by feedback loop -->

_(No promoted learnings yet.)_

<!-- end feedback-promoted -->
