---
name: eck:update-project
version: "0.7.4"
description: "Post-upgrade root document merge — section-level update of CLAUDE.md, project-constants.md, and project-constitution.md after a package version bump."
disable-model-invocation: false
---

# Update Project

Quick post-upgrade command for **root documents only**: CLAUDE.md, project-constants.md, project-constitution.md. Uses deterministic section-level merge via `template-cli merge-sections` so new sections are added and user customizations are preserved.

> **Scope:** This skill updates root documents. Run `/eck:pull-from-global-catalog` afterward to update all skills, agents, primitives, and hooks.

---

## Visual Framework

Reference: `output:visual-framework` primitive

Follow these conventions for all terminal output displayed to the user via the Bash tool.

### Output Mode Awareness

Before producing output, read the user's preferred output mode:

```bash
cat $ECK_HOME/user-preferences.json 2>/dev/null
```

Extract the `outputMode` field and adapt verbosity:

- **guided** — Add explanatory prose after key steps. Describe what is happening and why.
- **standard** (default) — Balanced. Show steps with brief context for non-obvious decisions.
- **concise** — Steps and summary box only. No prose, no explanations.

If the file is missing or `outputMode` is absent, use **standard**.

### ANSI Colors

Declare these variables at the top of **every** Bash tool call (shell state does not persist between calls):

```bash
source $ECK_HOME/colors.sh
```

Every color sequence **must** end with `${RESET}`. No exceptions.

### Stage Banners

Mark major stage transitions with a banner. One blank line before and after.

```bash
echo ""
printf '%b\n' "${ORANGE}${BOLD}STAGE NAME${RESET}"
echo ""
```

### Status Symbols

| Symbol | Meaning          | Color       |
| ------ | ---------------- | ----------- |
| `[*]`  | Active / running | `${CYAN}`   |
| `[x]`  | Completed        | `${GREEN}`  |
| `[!]`  | Error            | `${RED}`    |
| `[-]`  | Skipped          | `${DIM}`    |
| `[>]`  | In progress      | `${YELLOW}` |
| `[ ]`  | Pending          | `${DIM}`    |

```bash
printf '%b\n' "${GREEN}[x]${RESET} Step completed"
printf '%b\n' "${RED}[!]${RESET} Something failed"
```

No emoji in output. Use only these bracket symbols for status.

### Summary Boxes

Display at checkpoints and at the end of the skill. One blank line before and after.

```bash
echo ""
printf '%b\n' "${ORANGE}${BOLD}Title${RESET}"
echo ""
printf '%b\n' "Label: Value"
echo ""
```

### Error Format

```bash
printf '%b\n' "${RED}[!]${RESET} Error message here"
printf '%b\n' "    ${DIM}Recovery guidance here.${RESET}"
```

### Spacing Rules

- One blank line before and after stage banners and summary boxes.
- No blank lines within step lists.
- Maximum output line width: 70 characters.

---

## Task Registration

| Stage | Subject                       | Active Form                  | Statusline |
| ----- | ----------------------------- | ---------------------------- | ---------- |
| 1     | Stage 1: Pre-Flight           | Verifying prerequisites      | Upg (1/8)  |
| 2     | Stage 2: Backup               | Creating backup              | Upg (2/8)  |
| 3     | Stage 3: Root Merge           | Merging root documents       | Upg (3/8)  |
| 4     | Stage 4: Docs Audit           | Auditing documentation       | Upg (4/8)  |
| 5     | Stage 5: Docs Migrate         | Migrating docs architecture  | Upg (5/8)  |
| 6     | Stage 6: Feature Data Migrate | Seeding features.json        | Upg (6/8)  |
| 7     | Stage 7: Confluence Pull      | Pulling Confluence-only docs | Upg (7/8)  |
| 8     | Stage 8: Finalize             | Finalizing upgrade           | Upg (8/8)  |

## Statusline Stage Updates

At the **start** of each stage, update the statusline:

```bash
bash $ECK_HOME/update-stage.sh "Upg (N/8)"
```

At **skill completion** (success or error), reset:

```bash
bash $ECK_HOME/update-stage.sh
```

---

## Usage

```
/eck:update-project
```

No arguments required. Reads version info from `.claude/project-catalog.yaml` and `$ECK_HOME/global-catalog.yaml`.

---

## Stage 1: Pre-Flight

### Inputs

- `.claude/project-catalog.yaml` in current project
- `$ECK_HOME/global-catalog.yaml`

### Activities

1. Update statusline: `bash $ECK_HOME/update-stage.sh "Upg (1/8)"`
2. Verify `.claude/project-catalog.yaml` exists
   - If missing, display error and suggest `/eck:migrate-project`, then exit
3. Read project `template_version` from `.claude/project-catalog.yaml`
4. Read global `template_version` from `$ECK_HOME/global-catalog.yaml`
5. Compare versions:
   - If project version >= global version: display "Already up to date" and exit
   - If project version > global version: display "Project version newer than global — no downgrade" and exit
6. Compute `needs_docs_migration` flag:
   ```
   needs_docs_migration = (project_version < DOCS_FIRST_VERSION) AND (global_version >= DOCS_FIRST_VERSION)
   ```
   This flag controls whether Stages 4 and 5 run. Carry it forward through all subsequent stages.
7. Compute `needs_feature_migration` flag:
   ```
   needs_feature_migration = (project_version < FEAT74_VERSION) AND (global_version >= FEAT74_VERSION)
                             AND (.claude/features.json does NOT exist)
   ```
   Where `FEAT74_VERSION = "0.7.4"`. The `.claude/features.json` existence check makes this idempotent — a partially completed migration will not re-run.
   This flag controls whether Stage 6 runs. Carry it forward through all subsequent stages.
8. Display version context:
   ```bash
   printf '%b\n' "${CYAN}[*]${RESET} Upgrade: ${projVer} -> ${globalVer}"
   ```

### Outputs

- Project version and global version identified
- Confirmation that upgrade is needed
- `needs_docs_migration` flag computed
- `needs_feature_migration` flag computed

### Exit Criteria

- Project version confirmed less than global version
- Both catalog files readable
- `needs_docs_migration` and `needs_feature_migration` flags set

---

## Stage 2: Backup

### Inputs

- Root documents: `CLAUDE.md`, `.claude/project-constants.md`, `.claude/project-constitution.md`

### Activities

1. Update statusline: `bash $ECK_HOME/update-stage.sh "Upg (2/8)"`
2. Create backup directory: `.claude-backup-upgrade-{YYYY-MM-DD}` in project root
3. Copy the three root documents into the backup directory
4. If `needs_docs_migration=true`: generate a frontmatter manifest for all `docs/**/*.md` files and write it to the backup directory as `docs-frontmatter-manifest.json`. The manifest records the current frontmatter state of each file (path, keys present) so it can serve as a rollback reference.
5. Display backup path:
   ```bash
   printf '%b\n' "${GREEN}[x]${RESET} Backup: .claude-backup-upgrade-YYYY-MM-DD/"
   ```

### Outputs

- Backup of root documents at `.claude-backup-upgrade-{date}/`
- If `needs_docs_migration=true`: `docs-frontmatter-manifest.json` in the backup directory

### Exit Criteria

- Backup directory exists with all root documents
- Backup path recorded for rollback reference

---

## Stage 3: Root Document Merge

### Inputs

- Template root documents from `$ECK_HOME/`
- Deployed root documents in the project
- `.claude/project-constants.md` for placeholder values

### Activities

1. Update statusline: `bash $ECK_HOME/update-stage.sh "Upg (3/8)"`
2. Locate the template-cli binary:
   ```bash
   TEMPLATE_CLI="$(npm root -g)/@evolvconsulting/evolv-coder-kit/bin/template-cli.js"
   ```
3. For each root document, run the section-level merge:

#### CLAUDE.md

```bash
node "$TEMPLATE_CLI" merge-sections \
  $ECK_HOME/CLAUDE.md.project.template \
  CLAUDE.md \
  --strategy markdown \
  --values .claude/project-constants.md \
  --output CLAUDE.md
```

Display: sections added, sections preserved.

#### project-constants.md

```bash
node "$TEMPLATE_CLI" merge-sections \
  $ECK_HOME/project-constants.md \
  .claude/project-constants.md \
  --strategy constants \
  --output .claude/project-constants.md
```

Display: sections added, rows added. If new rows were added, list the new constant names for the user to fill in.

#### project-constitution.md

```bash
node "$TEMPLATE_CLI" merge-sections \
  $ECK_HOME/project-constitution.md \
  .claude/project-constitution.md \
  --strategy markdown \
  --values .claude/project-constants.md \
  --output .claude/project-constitution.md
```

Display: sections added, sections preserved.

4. If any merge command fails, display error and stop. The backup from Stage 2 can be used for rollback.

### Outputs

- All three root documents merged with section-level precision
- New sections added, user customizations preserved

### Exit Criteria

- All three merge commands completed successfully
- No template placeholders remaining in merged output (except in project-constants.md which holds placeholder definitions)

---

## Stage 4: Docs Audit

**Conditional:** Skip entirely if `needs_docs_migration=false`. Display `${DIM}[-]${RESET} Stage 4: Docs Audit — skipped (not needed)` and proceed to Stage 5.

### Inputs

- `.claude/project-constants.md` (for `DOC_PLATFORM` value)
- All `docs/**/*.md` files in the project

### Activities

1. Update statusline: `bash $ECK_HOME/update-stage.sh "Upg (4/8)"`
2. Read `DOC_PLATFORM` from `.claude/project-constants.md`
3. Glob all `docs/**/*.md` files via `docs/local:doc-list`
4. For each file, read frontmatter via `docs/local:doc-read` and classify as one of:
   - `complete` — frontmatter present with all required keys
   - `missing_frontmatter` — no frontmatter block at all
   - `partial_frontmatter` — frontmatter block present but missing required keys
5. If `DOC_PLATFORM=Confluence`:
   - Run `core/ops:mcp-preflight` to verify MCP availability
   - Run `confluence-official:page-search` to list all Confluence pages for this project
   - Cross-reference Confluence pages against local files and classify each as:
     - `confluence_only` — exists in Confluence, no matching local file
     - `both` — exists in both Confluence and locally
     - `local_only` — exists locally, not in Confluence
6. Display structured audit report:
   ```bash
   echo ""
   printf '%b\n' "${ORANGE}${BOLD}Docs Audit Report${RESET}"
   echo ""
   printf '%b\n' "Local files:          N total"
   printf '%b\n' "  complete:           N"
   printf '%b\n' "  missing_frontmatter: N"
   printf '%b\n' "  partial_frontmatter: N"
   # If DOC_PLATFORM=Confluence:
   printf '%b\n' "Confluence pages:     N total"
   printf '%b\n' "  confluence_only:    N"
   printf '%b\n' "  both:               N"
   printf '%b\n' "  local_only:         N"
   echo ""
   ```
7. Ask user confirmation before proceeding:
   - If user aborts: clean exit with no changes made, reset statusline

### Outputs

- Classification of all `docs/**/*.md` files by frontmatter completeness
- If `DOC_PLATFORM=Confluence`: classification of pages by location (confluence_only, both, local_only)
- User confirmation to proceed

### Exit Criteria

- All files classified
- User confirmed continuation (or clean abort)

---

## Stage 5: Docs Migrate

**Conditional:** Skip entirely if `needs_docs_migration=false`. Display `${DIM}[-]${RESET} Stage 5: Docs Migrate — skipped (not needed)` and proceed to Stage 6.

### Inputs

- Audit results from Stage 4
- `DOC_PLATFORM` value read in Stage 4
- All `docs/**/*.md` files classified as `missing_frontmatter` or `partial_frontmatter`

### Activities

1. Update statusline: `bash $ECK_HOME/update-stage.sh "Upg (5/8)"`

#### 5a. Constants Migration

- Read current `DOC_PLATFORM` value from `.claude/project-constants.md`
- If `DOC_PLATFORM=Confluence`: set `CONFLUENCE_ENABLED=true` and retain all Confluence-related constants
- If `DOC_PLATFORM=Local`: set `CONFLUENCE_ENABLED=false` and remove the Confluence constants section
- Remove the `DOC_PLATFORM` row from `project-constants.md` (superseded by `CONFLUENCE_ENABLED`)
- Note: Stage 3's `merge-sections` already added the `CONFLUENCE_ENABLED` row — this step sets its value

#### 5b. Frontmatter Migration

For each file classified as `missing_frontmatter` or `partial_frontmatter`:

1. Infer document `type` from path and filename using these rules (first match wins):
   - Filename is `ARCHITECTURE.md` → `architecture`
   - Filename is `FRD.md` → `specfeat`
   - Path contains `adrs/` → `adr`
   - Filename is `RISK.md` → `riskassessment`
   - Filename is `RESEARCH.md` → `research`
   - Filename matches `*-api-*` → `specapi`
   - Filename matches `*-data-*` → `specdata`
   - Fallback → `specfeat`
2. Set `status: accepted` if `status` key is absent
3. Set `date` from `git log --format=%as -1 -- <filepath>`, or today's date if no git history
4. If `CONFLUENCE_ENABLED=true`: add an empty `confluence:` block to frontmatter
5. Write updated frontmatter via `docs/local:doc-update`

#### 5c. Display Results

```bash
echo ""
printf '%b\n' "${ORANGE}${BOLD}Docs Migration Results${RESET}"
echo ""
printf '%b\n' "Constants updated:    CONFLUENCE_ENABLED=${value}"
printf '%b\n' "DOC_PLATFORM removed: yes"
printf '%b\n' "Files updated:        N"
printf '%b\n' "  frontmatter added:  N"
printf '%b\n' "  frontmatter patched: N"
echo ""
```

### Outputs

- `project-constants.md` updated: `CONFLUENCE_ENABLED` set, `DOC_PLATFORM` removed
- All `missing_frontmatter` and `partial_frontmatter` files updated with complete frontmatter

### Exit Criteria

- Constants migration complete
- All targeted files have complete frontmatter
- Results displayed

---

## Stage 6: Feature Data Migrate

**Conditional:** Skip entirely if `needs_feature_migration=false`. Display `${DIM}[-]${RESET} Stage 6: Feature Data Migrate — skipped (not needed)` and proceed to Stage 7.

### Inputs

- `.claude/lifecycle.json` in current project (may not exist if project has no features yet)
- `.claude/project-constants.md` (for `TRACKER_TYPE` and `PROJECT_KEY`)
- `docs/` directory (for folder rename)
- `.gitignore` in project root

### Activities

1. Update statusline: `bash $ECK_HOME/update-stage.sh "Upg (6/8)"`

#### 6a. Guard Check

Check whether `.claude/features.json` already exists:

```bash
if [ -f .claude/features.json ]; then
  printf '%b\n' "${DIM}[-]${RESET} Stage 6: Feature Data Migrate — skipped (features.json exists)"
  # proceed to Stage 7
fi
```

If it does exist, skip the entire stage. This makes the stage idempotent.

#### 6b. Seed features.json

If `.claude/lifecycle.json` does NOT exist (no features have been registered yet):

- Create a minimal empty `features.json`:
  ```json
  {
    "schema_version": 1,
    "project_key": "{PROJECT_KEY}",
    "next_id": 1,
    "features": {}
  }
  ```
  Where `<PROJECT_KEY>` is read from `.claude/project-constants.md`.
- Skip to step 6d (re-key lifecycle.json is a no-op if empty; still write a v5 empty file).

If `.claude/lifecycle.json` exists and contains features (keyed by slug in v4 format):

1. Read all feature entries from `lifecycle.json`. Each entry is keyed by slug and contains at minimum: `display_name`, `created_at`, and optionally `issue_number`, `description`, `priority`, `updated_at`.
2. Sort entries by `created_at` ascending (earliest first) to determine assignment order.
3. Read `TRACKER_TYPE` from `.claude/project-constants.md`.
4. Read `<PROJECT_KEY>` from `.claude/project-constants.md` (fall back to `FEAT` if absent).
5. For each entry in sorted order, assign a sequential ID starting at 1:
   - `id` = `<PROJECT_KEY>-{N}` (e.g. `ECK-1`, `ECK-2`, or `FEAT-1` if no key)
   - `slug` = the current lifecycle.json key
   - `display_name` = entry `display_name`
   - `description` = entry `description` (null if absent)
   - `priority` = entry `priority` (null if absent)
   - `created_at` = entry `created_at`
   - `updated_at` = entry `updated_at` (fall back to `created_at` if absent)
   - `remote` = if `issue_number` is present and `TRACKER_TYPE != none`:
     ```json
     { "tracker": "<TRACKER_TYPE>", "issue_number": {issue_number} }
     ```
     Otherwise `null`.
   - `archived_at` = null
6. Write `.claude/features.json`:
   ```json
   {
     "schema_version": 1,
     "project_key": "{PROJECT_KEY}",
     "next_id": {max_assigned_id + 1},
     "features": { "{KEY}-1": { ... }, "{KEY}-2": { ... } }
   }
   ```
7. Display progress:
   ```bash
   printf '%b\n' "${GREEN}[x]${RESET} Seeded features.json: N features"
   ```

#### 6c. Re-key lifecycle.json (v4 → v5)

The v5 lifecycle.json format uses `<KEY>-N` keys instead of slug keys. Trigger the update-lifecycle.js migration by running a read-write cycle:

```bash
node $ECK_HOME/update-lifecycle.js list 2>&1
```

`update-lifecycle.js` detects that `features.json` now exists and automatically re-keys all slug-keyed entries to their corresponding `<KEY>-N` IDs when reading the file. The re-keyed content is written back on the next mutating operation.

If `update-lifecycle.js` is not available (older installation), perform the re-key manually:

1. Read `lifecycle.json` (slug-keyed v4 format)
2. For each slug key, look up the matching `<KEY>-N` ID in `features.json` by matching `slug` field
3. Write a new `lifecycle.json` with `<KEY>-N` keys, preserving all other fields
4. Display:
   ```bash
   printf '%b\n' "${GREEN}[x]${RESET} Re-keyed lifecycle.json: N entries"
   ```

#### 6d. Migrate feature doc folders

Migrate all feature doc folders to the canonical `docs/features/<KEY>-N-{slug}/` layout. This handles every legacy pattern:

**Scan locations:** `docs/` root and `docs/features/` for directories matching any of:

| Legacy Pattern                     | Example                        | Match Strategy                             |
| ---------------------------------- | ------------------------------ | ------------------------------------------ |
| `docs/{slug}/`                     | `docs/user-auth/`              | Match slug in `features.json`              |
| `docs/FEAT-{N}-{slug}/`            | `docs/FEAT-73-local-first/`    | Match by issue number or old ID            |
| `docs/FEAT-<NNN>-{slug}/`          | `docs/FEAT-073-local-first/`   | Match by issue number (zero-padded legacy) |
| `docs/<KEY>-{N}-{slug}/`           | `docs/ECK-1-user-auth/`        | Already has ID — verify correct            |
| `docs/features/{slug}/`            | `docs/features/user-auth/`     | Match slug in `features.json`              |
| `docs/features/FEAT-{N}-{slug}/`   | `docs/features/FEAT-73-auth/`  | Match by issue number or old ID            |
| `docs/features/FEAT-<NNN>-{slug}/` | `docs/features/FEAT-073-auth/` | Match by issue number (zero-padded legacy) |
| `docs/features/<KEY>-{N}-{slug}/`  | `docs/features/ECK-1-auth/`    | Already canonical — verify or skip         |

**For each matched folder:**

1. **Resolve the feature** — try these in order:
   - Extract `<KEY>-N` from folder name → exact lookup in `features.json`
   - Extract `FEAT-N` or `FEAT-NNN` from folder name → lookup by old ID or issue number
   - Extract `{slug}` from folder name → slug match in `features.json`
   - If no match found → log warning, skip

2. **Compute canonical path:** `docs/features/<KEY>-N-{slug}/` using the resolved feature's `id` and `slug` from `features.json`

3. **Move if needed** (skip if already at canonical path):
   - If in a git repo: `git mv {old-path} docs/features/<KEY>-N-{slug}/`
   - Otherwise: `mkdir -p docs/features/ && mv {old-path} docs/features/<KEY>-N-{slug}/`

4. **Update artifact paths** in lifecycle registry — for each step that has a `gate_artifact` referencing the old path, update it to the new path:

   ```
   docs/FEAT-73-auth/DESIGN-REVIEW.md → docs/features/ECK-73-auth/DESIGN-REVIEW.md
   ```

5. **Display:**
   ```bash
   printf '%b\n' "${GREEN}[x]${RESET} Migrated: {old-path} -> docs/features/{KEY}-N-{slug}/"
   ```

**Unmatched folders:**

```bash
printf '%b\n' "${YELLOW}[!]${RESET} No match for {old-path} — skipped"
printf '%b\n' "    ${DIM}Register this feature with manage-features.js, then re-run${RESET}"
```

**Skip directories** that are not feature folders: `docs/adrs/`, `docs/guides/`, `docs/project/`, `docs/features/` itself, and any directory containing only non-lifecycle files (no FRD.md, DESIGN.md, TASKS.md, etc.).

#### 6e. Update .gitignore

Read `TRACKER_TYPE` from `.claude/project-constants.md`:

- If `TRACKER_TYPE = none`:
  - Remove the line `.claude/features.json` from `.gitignore` if present (features.json is the source of truth and must be committed when there is no remote tracker)
- If `TRACKER_TYPE != none` (GitHub, JIRA, Linear, etc.):
  - Add `.claude/features.json` to `.gitignore` if not already present (features are derived from the remote tracker)
- Always: add `.claude/features.lock` to `.gitignore` if not already present (lock file must never be committed)

#### 6f. Display Results

```bash
echo ""
printf '%b\n' "${ORANGE}${BOLD}Feature Data Migration Results${RESET}"
echo ""
printf '%b\n' "features.json seeded:    N features"
printf '%b\n' "lifecycle.json re-keyed: N entries"
printf '%b\n' "Docs folders renamed:    N"
printf '%b\n' "Docs folders skipped:    N (no match)"
printf '%b\n' ".gitignore updated:      yes|no"
echo ""
```

### Outputs

- `.claude/features.json` created and populated with `<KEY>-N` IDs
- `.claude/lifecycle.json` re-keyed from slug format (v4) to `<KEY>-N` format (v5+)
- Feature doc folders migrated to `docs/features/<KEY>-N-{slug}/` (from any legacy location/format)
- Lifecycle registry `gate_artifact` paths updated to match new folder locations
- `.gitignore` updated with `features.json` and `features.lock` rules

### Exit Criteria

- `features.json` exists and is valid
- All lifecycle entries have `<KEY>-N` keys
- All feature doc folders are under `docs/features/` with canonical naming
- Lifecycle artifact paths reference current folder locations
- `.gitignore` updated

### Rollback

If the migration fails partway through:

1. Remove `.claude/features.json` if it was created: `rm -f .claude/features.json`
2. Restore `.claude/lifecycle.json` from the backup created in Stage 2: `cp .claude-backup-upgrade-YYYY-MM-DD/.claude/lifecycle.json .claude/lifecycle.json`
3. For any renamed docs folders, restore with: `git mv docs/features/<KEY>-N-{slug}/ {original-path}` (reverse the rename)
4. Re-run `/eck:update-project` after resolving the root cause. The `needs_feature_migration` guard in Stage 1 will re-trigger Stage 6 since `features.json` was removed.

---

## Stage 7: Confluence Pull

**Conditional:** Skip if any of the following:

- `DOC_PLATFORM` was not `Confluence`
- Zero `confluence_only` pages found in Stage 4
- MCP unavailable (preflight failed in Stage 4)

Display `${DIM}[-]${RESET} Stage 7: Confluence Pull — skipped ({reason})` and proceed to Stage 8.

### Inputs

- `confluence_only` page list from Stage 4 audit
- MCP connection established in Stage 4

### Activities

1. Update statusline: `bash $ECK_HOME/update-stage.sh "Upg (7/8)"`
2. Display the list of `confluence_only` pages with index numbers
3. Ask user which pages to pull (default: all). Accept comma-separated index list or "all"
4. For each selected page:
   - Read page content via `confluence-official:page-read`
   - Convert Confluence storage format to Markdown
   - Build frontmatter with `type` inferred by title (same rules as Stage 5b), `status: accepted`, `date` from Confluence last-modified, and `confluence.page_id` set to the Confluence page ID
   - Write to `docs/migrated/<slugified-title>.md` via `docs/local:doc-create`
5. Display results:
   ```bash
   echo ""
   printf '%b\n' "${ORANGE}${BOLD}Confluence Pull Results${RESET}"
   echo ""
   printf '%b\n' "Pages pulled:   N"
   printf '%b\n' "Pages skipped:  N"
   printf '%b\n' "Output dir:     docs/migrated/"
   echo ""
   ```

### Outputs

- Confluence-only pages written as Markdown to `docs/migrated/`
- Each file has complete frontmatter including `confluence.page_id`

### Exit Criteria

- All selected pages written to `docs/migrated/`
- Results displayed

---

## Stage 8: Finalize

### Inputs

- Results from Stages 3–7
- Backup path from Stage 2
- Migration stats from Stages 4–5 (if `needs_docs_migration=true`)
- Feature migration stats from Stage 6 (if `needs_feature_migration=true`)
- Confluence pull stats from Stage 7 (if applicable)

### Activities

1. Update statusline: `bash $ECK_HOME/update-stage.sh "Upg (8/8)"`
2. **MUST** update `.claude/project-catalog.yaml`:
   1. Set `template_version` to match global catalog version
   2. Add `sync_history` entry:
      ```yaml
      sync_history:
        - date: "YYYY-MM-DD"
          from_version: "old_version"
          to_version: "new_version"
          scope: "root-documents"
      ```
      If `needs_docs_migration=true`, add a second entry:
      ```yaml
      - date: "YYYY-MM-DD"
        from_version: "old_version"
        to_version: "new_version"
        scope: "docs-first-migration"
      ```
      If `needs_feature_migration=true`, add an additional entry:
      ```yaml
      - date: "YYYY-MM-DD"
        from_version: "old_version"
        to_version: "new_version"
        scope: "feat74-feature-data-migration"
      ```
3. **MUST** update `$ECK_HOME/registry.yaml` (if it exists):
   - **Reference:** `ops:registry-update` primitive for the canonical write procedure
   - If registry does not exist, log warning and skip registry update.
   - Set `audit_status: "pending"` (template version changed, needs re-audit)
   - Set `last_modified_at` to current ISO-8601 date
   - Set `doc_platform` registry value to `"local"` or `"local+confluence"` based on `CONFLUENCE_ENABLED`
   - Append `sync_history` entry:
     ```yaml
     - date: "YYYY-MM-DD"
       action: "update"
       performed_by: "/eck:update-project"
       summary: "Root document merge from {old_version} to {new_version}"
     ```
   - Recompute `project_summary` per the `ops:registry-update` primitive
   - Atomic write: write to `registry.yaml.tmp`, then rename to `registry.yaml`
4. Display final summary box (include migration stats if `needs_docs_migration=true` or `needs_feature_migration=true`):

```bash
echo ""
printf '%b\n' "${ORANGE}${BOLD}Upgrade Complete (root documents)${RESET}"
echo ""
printf '%b\n' "Version: old_ver -> new_ver"
printf '%b\n' "CLAUDE.md:               N added, N preserved"
printf '%b\n' "project-constants.md:    N added, N rows"
printf '%b\n' "project-constitution.md: N added, N preserved"
# If needs_docs_migration=true:
printf '%b\n' "Docs frontmatter:        N files updated"
printf '%b\n' "Confluence pages pulled: N (docs/migrated/)"
# If needs_feature_migration=true:
printf '%b\n' "Features seeded:         N (.claude/features.json)"
printf '%b\n' "Docs folders renamed:    N"
echo ""
printf '%b\n' "${YELLOW}[>]${RESET} Next: /eck:pull-from-global-catalog"
printf '%b\n' "    Update all skills, agents, primitives, and hooks."
# If CONFLUENCE_ENABLED=true:
printf '%b\n' "${YELLOW}[>]${RESET} Then: /confluence-reconcile"
printf '%b\n' "    Reconcile local docs with Confluence."
echo ""
printf '%b\n' "Backup: .claude-backup-upgrade-YYYY-MM-DD/"
echo ""
```

5. Display rollback instructions:

   ```bash
   printf '%b\n' "${DIM}To rollback: cp -r .claude-backup-upgrade-YYYY-MM-DD/.claude/ .claude/${RESET}"
   ```

6. Reset statusline: `bash $ECK_HOME/update-stage.sh`

### Outputs

- Updated `project-catalog.yaml` with new `template_version`
- Updated registry entry (if exists) including `doc_platform` value
- Summary displayed to user with next-step guidance

### Exit Criteria

- `template_version` in project catalog matches global catalog
- Statusline reset

---

## Error Handling

Reference: `output:error-handler` primitive

| Error                            | Recovery                                                              |
| -------------------------------- | --------------------------------------------------------------------- |
| No `project-catalog.yaml`        | Exit with suggestion: `/eck:migrate-project`                          |
| Global catalog missing           | Exit with suggestion: `npx @evolvconsulting/evolv-coder-kit --global` |
| Backup creation fails            | Abort upgrade, display error                                          |
| Merge command fails              | Stop, show error, point to backup for rollback                        |
| Version downgrade attempted      | Refuse with explanation                                               |
| lifecycle.json parse error       | Log error, write empty `features.json`, warn user to seed manually    |
| Docs folder rename fails         | Log warning per folder, continue with remaining folders               |
| `git mv` fails (not a git repo)  | Fall back to plain `mv`                                               |
| No <KEY>-N match for docs folder | Log warning and skip that folder; proceed with others                 |

---

## Related Documents

- `/eck:pull-from-global-catalog` — Full granular update of all components (skills, agents, primitives, hooks)
- `/eck:audit-current-project` — Health check and drift detection
- `/eck:migrate-project` — Onboard unmanaged projects
- `global-catalog.yaml` — Canonical component registry
