@mmerterden/multi-agent-pipeline 14.0.0 → 14.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -16,6 +16,91 @@ Internal file-layout changes that don't affect the slash-command surface are sti
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [14.1.0] - 2026-07-29
20
+
21
+ Two things the pipeline was supposed to do and did not: use the skills a project's
22
+ own toolkit says apply, and clean up after itself.
23
+
24
+ ### Added
25
+
26
+ - **Phase 3 asks the stack toolkit which of its skills govern the task.** Each
27
+ `ai-<platform>-engineering-toolkit` already ships an `index` skill holding a
28
+ 30-plus row intent-to-skill table, maintained beside the skills it points at.
29
+ Phase 3 dispatched to that plugin for exactly one case (`taskType == "component"`),
30
+ so `bugfix` / `feature` / `refactor` / `chore` had no skill dispatch at all:
31
+ whichever skills the host surfaced by description match were the ones used, and
32
+ nothing recorded or required any of them. That was the dev-side half of the gap
33
+ v14.0.0 closed on the review side - review asked "was this built to the rules it
34
+ was supposed to follow" while nobody had chosen any rules.
35
+
36
+ The routing table is NOT copied into this repo. A second copy would drift the
37
+ moment the plugin shipped a skill, and the copy here would be the stale one, so
38
+ the pipeline asks rather than knows. `smoke-stack-skill-routing.sh` check 5 fails
39
+ the build if a routing table appears in a shipped file (verified against a planted
40
+ 6-row table). Routed skills land in `telemetry.skillCalls[]` with
41
+ `routedBy: "<toolkit>:index@<version>"`, so Phase 4 conformance can hold the run to
42
+ what its own toolkit chose. An absent or disabled toolkit is a recorded no-op, not
43
+ a halt - a backend repo has no toolkit and must still run.
44
+ Contract: `refs/features/stack-skill-routing.md`.
45
+
46
+ - **Phase 6 removes a task's worktree once its PR is open** (`worktree-finalize.sh`,
47
+ gated by `prefs.global.settings.worktreeAutoRemoveOnPr`, default **true**). It
48
+ salvages `agent-state.json`, `phase-tracker.json`, `triage-output.json`,
49
+ `.pipeline/`, `.build.log`, `.test.log` and `.review-diff.txt` into the log dir
50
+ first, because Phase 7's triage-memory ingest, the learnings-ledger distill,
51
+ `render-work-summary.sh`, `:resume`, `:status` and `:log` all read them - and the
52
+ first three are `[ -f ]`-guarded, so a removal without salvage would have degraded
53
+ silently rather than failing.
54
+
55
+ It keeps the branch and **does not check it out**. `git worktree remove` leaves the
56
+ branch as an ordinary local branch, so nothing is lost, while a checkout would move
57
+ the user's HEAD and could collide with their own uncommitted work on another
58
+ branch. Phase 5 removes-then-checks-out on purpose because it is a test handoff;
59
+ this is not. Verified end to end: HEAD stays put, the user's uncommitted file
60
+ survives, the branch is still checkoutable on demand.
61
+
62
+ Every destructive path is gated and each skips with a reason rather than failing:
63
+ real uncommitted changes, an unpushed HEAD, `--local` mode, a cwd inside the tree,
64
+ an unregistered path. `--force` appears nowhere. Contract:
65
+ `refs/features/worktree-finalize.md`. New state: `worktreeRemovedAt`,
66
+ `artifactsPath`.
67
+
68
+ - Gates: `smoke-stack-skill-routing.sh` (12 checks) and `smoke-worktree-finalize.sh`
69
+ (28 checks, exercising real git repos rather than grepping the doc).
70
+
71
+ ### Changed
72
+
73
+ - `render-work-summary.sh` gained a log-dir fallback for state and tracker files.
74
+ It resolved them only from the worktree, so it exited 2 and the entire Work Summary
75
+ vanished from the PR body and the Jira comment. Its sibling
76
+ `render-agent-log-cost.sh` has had that fallback all along.
77
+ - `:resume`, `:status` and `:log` no longer treat a missing worktree as a broken run
78
+ when `worktreeRemovedAt` is set: state is read from `artifactsPath`, and resume asks
79
+ before moving the user's HEAD.
80
+ - `gc-worktrees.sh` and `/multi-agent:garbage-collect` both claimed the finishing
81
+ command owned worktree removal. That is now true rather than aspirational.
82
+ - Token budget 52200 -> 52700, after compressing 414 tokens out of Phase 3 and
83
+ Phase 6 first, per the discipline recorded in `token-budget.json`.
84
+
85
+ ### Fixed
86
+
87
+ - **Every finding the test-integrity gate produced was unattributable.** It read
88
+ `f.file` from the diff-risk report, which declares and emits `path`, so each
89
+ finding carried `file: undefined` and read "Test file 'undefined' shrinks". That
90
+ is useless to the developer and rejected by `reviewer-output.schema.json`, which
91
+ requires `file` with `minLength: 1` - on the one gate that deliberately has no
92
+ opt-out. It stayed invisible because the smoke's own fixtures used `"file"`, the
93
+ key the bug read: the fixture matched the bug instead of the schema. Fixtures
94
+ corrected, and a new assertion checks the finding names a real path (verified by
95
+ reintroducing the bug).
96
+
97
+ - **The command inventories kept a renamed command alive.** `finish` survived the
98
+ v14.0.0 rename to `ship` in all three inventory lists, because
99
+ `smoke-command-inventory.sh` only proved nothing was MISSING and `ship` appears
100
+ elsewhere in those files' prose. Added check 4b: every name in an inventory must be
101
+ a command that exists in the tree (verified against a planted `ghostcmd`).
102
+
103
+
19
104
  ## [14.0.0] - 2026-07-29
20
105
 
21
106
  The `--dev` family reviews its own work, and review now checks the code against the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "14.0.0",
3
+ "version": "14.1.0",
4
4
  "description": "8-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI and Codex CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -15,7 +15,7 @@ Show the task's detailed agent-log.md report.
15
15
  1. **Parse task ID** - extract the `#N` form from the argument.
16
16
  - No argument → find the most recent (highest-ID) worktree.
17
17
 
18
- 2. **Find the worktree** - search the known repos:
18
+ 2. **Find the worktree** - search the known repos. A task whose worktree was removed after its PR (Phase 6 finalize) is found under `$HOME/.claude/logs/multi-agent/<project>/<task-id>/` instead; the `agent-log.md` was always there, and `artifacts/agent-state.json` holds the state. Look there before reporting "task not found":
19
19
  ```bash
20
20
  find ~/my-ios-app/.worktrees/ ~/my-figma-app/.worktrees/ ~/my-ui-components/.worktrees/ -name "agent-state.json" -maxdepth 2 2>/dev/null
21
21
  ```
@@ -19,6 +19,7 @@ Resume a paused or failed task from the last successful phase.
19
19
  2. **Read + validate state** - parse `agent-state.json`:
20
20
  - Validate first: `node $HOME/.claude/scripts/validate-state.mjs <state-file>` (resume-safety check, tolerant of legacy shapes). On non-zero exit, do NOT guess a phase - surface the errors and stop with `ERR: agent-state.json is unsafe to resume; inspect it or 'kill #N' and restart.`
21
21
  - Confirm the worktree (`worktreePath` / `projects[].worktreePath`) exists and is usable; if missing or locked, run the Phase 0 "Worktree stale-lock heal" before continuing.
22
+ - **Unless `state.worktreeRemovedAt` is set.** Then the worktree was removed on purpose by Phase 6 once the PR opened, the branch is still local, and the artefacts live under `state.artifactsPath`. Do NOT heal or recreate it: read state from `artifactsPath`, and if the remaining work needs a checkout (a Phase 7 pause needs none), ask before moving the user's HEAD - they may be mid-work on another branch, which is exactly why the removal did not check the branch out.
22
23
  - `currentPhase` - last completed phase
23
24
  - `status` - `paused` | `failed` | `in_progress`
24
25
  - `haltReason` - if set, show it so the user knows why the run stopped; clear it on successful re-entry
@@ -14,7 +14,7 @@ Show every active and completed task as a table.
14
14
  - `~/my-figma-app/.worktrees/`
15
15
  - `~/my-ui-components/.worktrees/`
16
16
 
17
- 2. **Scan worktrees** - read `agent-state.json` in each worktree dir:
17
+ 2. **Scan worktrees** - read `agent-state.json` in each worktree dir. **Also scan the log dir**, because a task whose PR is open has no worktree any more (Phase 6 removes it and salvages its state): `find $HOME/.claude/logs/multi-agent -maxdepth 4 -name agent-state.json -path '*/artifacts/*'`. (`-maxdepth 4`, not 3: Phase 6 always passes `--project`, so the salvaged copy lands at `<project>/<task-id>/artifacts/agent-state.json`, which a depth-3 scan can never reach.) Merge both sets by `taskId`, preferring the worktree copy when both exist, and render a finalized task with its `worktreeRemovedAt` rather than omitting it - a task that shipped should not vanish from status.
18
18
  ```bash
19
19
  find {repo}/.worktrees/ -name "agent-state.json" -maxdepth 2
20
20
  ```
@@ -490,10 +490,10 @@ same 43 specs as reference files rather than as peer skills, via Step 2b - see
490
490
 
491
491
  ```
492
492
  analysis, analysis-resolve, autopilot, build-optimize, channels, create-jira, design-check, dev,
493
- dev-autopilot, dev-local, dev-local-autopilot, diff-explain, finish, forget, garbage-collect,
493
+ dev-autopilot, dev-local, dev-local-autopilot, diff-explain, forget, garbage-collect,
494
494
  help, ios-coding-standard, issue, jira, kill, language, local,
495
495
  local-autopilot, log, manual-test, prune-logs, purge, refactor, resume, review, review-issue, review-jira,
496
- routines, save, scan, search, setup, stack, status, sync, test, testflight-validation, uninstall, update
496
+ routines, save, scan, search, setup, ship, stack, status, sync, test, testflight-validation, uninstall, update
497
497
  ```
498
498
 
499
499
  **NOT synced**: `$HOME/.claude/multi-agent-refs/*` - lazy-load references, Claude Code specific
@@ -0,0 +1,72 @@
1
+ # Stack skill routing - letting the toolkit plugin choose its own skills
2
+
3
+ > **TLDR** - When a stack toolkit plugin is enabled, Phase 3 asks that plugin's own `index` skill which of its skills apply to this task, loads them before writing code, and records each into `state.telemetry.skillCalls[]`. The routing table lives in the plugin; the pipeline copies none of it.
4
+
5
+ ## Why this exists
6
+
7
+ Phase 3 dispatched to the toolkit plugin for exactly one case, `taskType === "component"` (see `component-dispatch.md`). Every other task - `bugfix`, `feature`, `refactor`, `chore` - had no skill dispatch at all: whichever skills the host happened to surface by description match were the ones that got used, and nothing recorded or required any of them.
8
+
9
+ That is the dev-side half of the gap `features/skill-conformance.md` closes on the review side. Review now asks "was this built to the rules it was supposed to follow"; without this step, the answer for a non-component task was "there were no declared rules, because nobody chose any".
10
+
11
+ The fix is not a routing table in the pipeline. Each `ai-<platform>-engineering-toolkit` already ships one: an `index` skill whose description says *"Load this first when unsure which skill applies"*, holding a 30-plus row intent-to-skill map maintained alongside the skills it points at. A second copy in this repo would drift the moment the plugin shipped a new skill, and the pipeline's copy would be the stale one.
12
+
13
+ So the pipeline's job is to **ask**, not to know.
14
+
15
+ ## When it runs
16
+
17
+ Phase 3 pre-flight, before any code is written, for **every** `taskType`. Component tasks keep their dedicated dispatch in `component-dispatch.md`; this step runs in addition, because the reference skills (architecture, naming, file placement, tokens) apply to a component build too.
18
+
19
+ ## Resolution
20
+
21
+ Platform comes from the same mapping component dispatch uses, so the two cannot disagree:
22
+
23
+ | `state.platform` / detected stack | Toolkit |
24
+ |---|---|
25
+ | ios, swift | `ai-ios-engineering-toolkit` |
26
+ | android, kotlin | `ai-android-engineering-toolkit` |
27
+ | anything else | no toolkit - step is a recorded no-op |
28
+
29
+ The toolkit is enabled per repo (`.claude/settings.local.json` / `~/.claude/settings.json` `enabledPlugins`). **Not enabled is not an error here**, unlike component dispatch: a backend or web repo legitimately has no toolkit, and halting would make the pipeline unusable outside mobile. Record the no-op and continue.
30
+
31
+ Two marketplaces may ship the same toolkit name (a public one and a corporate one). Resolve whichever is enabled and record its **name and version** in the ledger entry, because the routing table and the skill set differ between versions - a finding that cites a skill has to be traceable to the version that defined it.
32
+
33
+ ## The call
34
+
35
+ ```text
36
+ Skill(<toolkit>:index, args: "<task title + one-line intent>")
37
+ ```
38
+
39
+ The index returns which `reference/` and `workflow/` skills apply. Load each via the Skill tool before writing code. A typical task pulls one workflow skill plus one or more reference skills.
40
+
41
+ Emit one progress line per loaded skill per `progress-contract.md`, so the user can see which standards the run bound itself to rather than inferring it afterwards.
42
+
43
+ ## Recording - what makes this checkable
44
+
45
+ Append one `state.telemetry.skillCalls[]` entry per skill actually loaded:
46
+
47
+ ```json
48
+ {"skill": "ai-ios-engineering-toolkit:reference/architecture", "phase": 3,
49
+ "targetFiles": ["Domains/Checkin/Sources/CheckinScene.swift"],
50
+ "routedBy": "ai-ios-engineering-toolkit:index@0.13.0", "timestamp": "<ISO-8601>"}
51
+ ```
52
+
53
+ `routedBy` names the index and version that chose it. That is the difference between "the model happened to read a skill" and "the toolkit said this skill governs this task".
54
+
55
+ What Phase 4 actually does with it, precisely: Step 1.78 lists these entries in the manifest under `ledger.routedByToolkit`, so a reviewer and the Phase 7 report can see which skills the project's own toolkit selected. It does **not** give them extra weight in the coverage maths. The deterministic resolver stays primary because an unrecorded load and no load are indistinguishable in state, and no `routedBy` tag changes that - the tag says who chose the skill, not that the code honoured it.
56
+
57
+ ## Failure modes, and why none of them halt
58
+
59
+ | Situation | Behaviour |
60
+ |---|---|
61
+ | No toolkit for this stack | recorded no-op, continue |
62
+ | Toolkit not enabled in this repo | recorded no-op, continue (component dispatch still halts for its own case) |
63
+ | `index` resolves but routes to a skill that does not exist in this version | record the miss with the version, load the rest, continue. A stale row in a plugin's table must not stop a run |
64
+ | `index` itself does not resolve | record and fall back to the host's own description matching, which is the pre-v14.1.0 behaviour - no worse than before |
65
+
66
+ Nothing here blocks Phase 3. What is downstream is visibility, not enforcement: routed skills appear in the manifest's `ledger.routedByToolkit`, and a task that recorded nothing shows up as `ledgerSource: derived` with its coverage gap stated. Enforcement over rule IDs is the registry's job (`features/skill-conformance.md`), not this step's.
67
+
68
+ ## What this deliberately does NOT do
69
+
70
+ - It does not decide which skills apply. Copying the plugin's routing into this repo would put the authoritative table in the wrong place and guarantee drift.
71
+ - It does not fail a run for a missing skill. The pipeline's contract is to ask and record, not to require that a third-party plugin be complete.
72
+ - It does not replace `component-dispatch.md`. That path owns the component build itself, including the `figma-validate` pre-check and the halt-on-incomplete-state rule.
@@ -0,0 +1,66 @@
1
+ # Worktree finalize - removing a task's worktree once its PR is open
2
+
3
+ > **TLDR** - Phase 6 step 9. Once the PR exists the worktree is dead weight, so it is removed: artefacts are salvaged into the log dir first, the branch is kept and deliberately NOT checked out, and every destructive path is gated. Gated by `prefs.global.settings.worktreeAutoRemoveOnPr` (default **true**). Script: `worktree-finalize.sh`.
4
+
5
+ ## Why it exists
6
+
7
+ A finished task's worktree is a full second checkout that nobody needs after the PR is open, and removing it is the step people forget. `.worktrees/` then accumulates copies of the repo until `/multi-agent:kill` or `:garbage-collect` is run by hand.
8
+
9
+ Removing it at PR-open is only safe because of the salvage, so the two are one step and not two.
10
+
11
+ ## What it will not do
12
+
13
+ **No `git checkout` of the task branch.** `git worktree remove` leaves the branch as an ordinary local branch: the ref, its commits, and the ability to `git checkout <branch>` later all survive untouched. Checking it out here would move the user's HEAD out from under them and can collide with their own uncommitted work on another branch. Phase 5 removes-then-checks-out on purpose, because it is handing the branch over for manual testing; this step is not.
14
+
15
+ **No `git branch -D`.** The branch is the deliverable.
16
+
17
+ **No `--force`, ever.** `git worktree remove` refusing is a safety feature. The clean-tree check runs before it, so a refusal at that point means something unexpected (a lock, a submodule, permissions) and forcing past unexpected dirt is how work gets lost.
18
+
19
+ ## Preconditions - each one skips with a reason, none is an error
20
+
21
+ | Condition | Why it blocks |
22
+ |---|---|
23
+ | `worktreePath == projectRoot` (`--local` mode) | there is no worktree; removing it would delete the user's checkout |
24
+ | cwd is inside the worktree | a shell left on a deleted inode is worse than a leftover directory, and Phase 6 legitimately `cd`s into the worktree earlier |
25
+ | not a registered worktree of the project root | a mistyped path must not delete an unrelated directory |
26
+ | real uncommitted changes | never discarded; see the artefact carve-out below |
27
+ | HEAD not on the remote | removing a worktree whose commits exist nowhere else is data loss, not cleanup |
28
+
29
+ Exit codes: `0` removed, `3` skipped with a reason (report and continue to Phase 7), `1` usage error.
30
+
31
+ ### The artefact carve-out, and why `--untracked-files=no` is wrong
32
+
33
+ The pipeline's own artefacts live inside the worktree and are untracked, so a raw `git status --porcelain` is never empty at PR-open. Left unhandled the removal would never fire and the feature would look implemented while doing nothing.
34
+
35
+ So exactly these paths are forgiven, and nothing else:
36
+
37
+ ```
38
+ agent-state.json phase-tracker.json triage-output.json
39
+ .review-diff.txt .build.log .test.log .pipeline/
40
+ ```
41
+
42
+ Suppressing all untracked files instead (`--untracked-files=no`) would have been shorter and wrong: a source file the developer created but never `git add`ed is invisible to it, and that file would be destroyed silently.
43
+
44
+ ## Salvage
45
+
46
+ Copied into `$HOME/.claude/logs/multi-agent/<project>/<task-id>/artifacts/` before removal, each only if present - a task that never reached Phase 4 has no triage output and that is not an error.
47
+
48
+ This is why the removal is safe:
49
+
50
+ | Consumer | Reads | Without salvage |
51
+ |---|---|---|
52
+ | Phase 7 triage-memory ingest | `triage-output.json` | `[ -f ]`-guarded, so it degrades **silently**: the triage corpus and learnings ledger stop being fed and no error appears |
53
+ | Phase 7 learnings-ledger distill | same file | same silent degradation |
54
+ | `render-work-summary.sh` | `agent-state.json`, `phase-tracker.json` | exits 2, so the Work Summary vanishes from the PR body and the Jira comment |
55
+ | `:resume` | `agent-state.json` | cannot continue a Phase 7 pause |
56
+ | `:status`, `:log` | `agent-state.json` | the task becomes invisible |
57
+
58
+ `state.worktreeRemovedAt` and `state.artifactsPath` record the outcome. The timestamp is what tells a reader that a worktree-less task was finished-and-tidied rather than killed - without it, a missing worktree is indistinguishable from a broken run.
59
+
60
+ ## Multi-repo
61
+
62
+ Run serially per repo, and only **after** `update_sibling_links`: that function issues an update per PR and the loop `cd`s per repo, so removing repo 1's worktree mid-loop breaks repos 2..N.
63
+
64
+ ## Interaction with the existing removal sites
65
+
66
+ `gc-worktrees.sh` and `/multi-agent:garbage-collect` never touch a registered, healthy worktree - they sweep orphans. Both name this step as the owner of finishing-a-task removal, which is now true rather than aspirational.
@@ -8,7 +8,7 @@ Per Locked decision 30, Phase 3 Dev consumes the analysis document as the sole d
8
8
 
9
9
  Pre-flight steps (run in order, abort on failure).
10
10
 
11
- **Steps 1, 2, 3, 5 and 6 apply only when Phase 1 ran.** In the `--dev` family (`state.onlyDevelop === true`) there is no analysis document by design, so those steps are recorded as `not-applicable (no Phase 1 in this mode)` and skipped - an unconditional abort here would make every fast mode impossible, which is the contradiction the modes have always carried in practice. Steps 4, 7 and 8 apply in every mode.
11
+ **Steps 1, 2, 3, 5 and 6 apply only when Phase 1 ran.** In the `--dev` family (`state.onlyDevelop === true`) there is no analysis doc by design, so they are recorded `not-applicable (no Phase 1 in this mode)` and skipped - an unconditional abort there would make every fast mode impossible. Steps 4, 7, 8 and 9 apply in every mode.
12
12
 
13
13
  1. **Analysis document presence** (Phase 1 modes only): locate `analysis/<feature-slug>-<platform>.md` for the active platform.
14
14
  - Path resolution: `state.run.repoPath` + `/analysis/<feature>-<platform>.md`
@@ -28,7 +28,7 @@ Pre-flight steps (run in order, abort on failure).
28
28
 
29
29
  6. **Conventions handoff**: read `analysis Section 13.1 Concept Table` (Pass B output with footnotes). Persist concept-to-realization mapping into `state.dev.conventions[<concept>]`. Phase 3 implementation uses these names verbatim (e.g., if Section 13.1 says "State holder: PassengerFlightViewModel", Phase 3 names the class exactly `PassengerFlightViewModel`).
30
30
 
31
- 7. **MCP forbidden**: any attempt to call `mcp__claude_ai_Figma__*` in Phase 3 is a violation. The smoke gate `smoke-no-mcp-in-dev-phases.sh` (see CHANGELOG [9.0.0]) checks `state.telemetry.mcpCalls[]` after run and fails if Phase 3 contributed any entry.
31
+ 7. **MCP forbidden**: calling `mcp__claude_ai_Figma__*` in Phase 3 is a violation. `smoke-no-mcp-in-dev-phases.sh` reads `state.telemetry.mcpCalls[]` after the run and fails if Phase 3 contributed an entry.
32
32
 
33
33
  8. **Criteria ledger (required, every mode)**: the moment this phase consults a skill, a marketplace plugin skill, a stack guide or a module `CLAUDE.md` in order to write code, append an entry to `state.telemetry.skillCalls[]`:
34
34
 
@@ -36,9 +36,9 @@ Pre-flight steps (run in order, abort on failure).
36
36
  {"skill": "ios-coding-standard", "phase": 3, "targetFiles": ["Sources/Login/LoginViewModel.swift"], "timestamp": "<ISO-8601>"}
37
37
  ```
38
38
 
39
- `targetFiles` is required: a skill applied to the wrong files still reads as "applied" without it, so coverage could not be attributed. Append at the moment of consultation, not retrospectively at the end of the phase.
39
+ `targetFiles` is required - without it a skill applied to the wrong files still reads as "applied". Append at the moment of consultation, not at the end of the phase. Phase 4 Step 1.78 treats this as self-report only and resolves criteria independently; it is the one signal separating "applied to the wrong files" from "never opened".
40
40
 
41
- This is a **self-report**, and Phase 4 treats it as such. Step 1.78 resolves the criteria independently and defaults `ledger.source` to `derived`; a skill named here that the resolver cannot bind to a changed file is flagged rather than believed. The record still earns its keep, because it is the only signal that separates "this skill was applied to the wrong files" from "this skill was never opened" - and because a phase that has to name what it followed tends to follow something.
41
+ 9. **Stack skill routing (every `taskType`, when a stack toolkit plugin is enabled)**: ask the enabled `ai-<platform>-engineering-toolkit`'s own `index` skill which skills govern this task, load them BEFORE writing code, and record each into `state.telemetry.skillCalls[]` with `routedBy: "<toolkit>:index@<version>"`. The routing table stays in the plugin - a copy here would be the stale one. No toolkit, or none enabled, is a recorded no-op, not a halt. Contract: [`features/stack-skill-routing.md`]($HOME/.claude/multi-agent-refs/features/stack-skill-routing.md).
42
42
 
43
43
  The analysis document is the SOLE design source in Phase 3. Variant choices, padding values, color tokens, copy strings, accessibility identifiers, and test method names all come from the rendered Pass B cells. If something is missing in the analysis doc, the fix is to re-run `/multi-agent:analysis`, not to fetch from Figma.
44
44
 
@@ -89,9 +89,20 @@ Branch **deterministically**, no implicit fallback. Read `agent-state.json` and
89
89
  - In mixed mode (some local, some remote), only prompt for the remote-backed repos; local ones auto-skip.
90
90
  - No -> Phase 7
91
91
  - Yes -> Create PR with technical description (see below)
92
- 9. **Issue body update** (GitHub Issue only): if the issue body has a `### Pull Requests` section and/or a `### Progress` table, fill in the PR URL(s) (one row per submodule - e.g. `- **common:** {url}`, `- **uicomponents:** {url}`) and flip the Implementation / Testing / Code Connect flags from Pending to Done using whatever marker the template uses (match in place, do NOT introduce new markers). Apply with `gh issue edit {issueNo} --body "{updated body}"` and preserve every other section unchanged.
93
- 10. **NEVER close or resolve the issue** - neither GitHub Issue nor Jira. Issues require team review (4 approvals) before closing. Only post a comment with commit/PR URLs.
94
- 11. Log: "Phase 6: Commit {sha} - PR #{number}"
92
+ 9. **Worktree finalize (gated by `settings.worktreeAutoRemoveOnPr`, default true)**: run **from the project root** - step 3 leaves the shell inside the worktree and the script refuses there.
93
+
94
+ ```bash
95
+ cd "$PROJECT_ROOT"
96
+ FIN=$(bash $HOME/.claude/scripts/worktree-finalize.sh --json \
97
+ --worktree "$WT_PATH" --project-root "$PROJECT_ROOT" \
98
+ --task-id "$TASK_ID" --project "$PROJECT" --branch "$BRANCH")
99
+ ```
100
+
101
+ The script salvages before removing, keeps the branch, and does **not** check it out - the user's HEAD and uncommitted work stay put. On success it stamps `worktreeRemovedAt`, `artifactsPath` and `worktreePath: null` into the **salvaged** `agent-state.json` itself. Do NOT write those via `write-state.mjs "$STATE_FILE"`: in single-repo mode that path went away with the worktree, so the write fails, the fields land nowhere, and Phase 7 reads a dead path and silently skips the triage ingest. After a removal re-point `STATE_FILE` at `$(jq -r .artifactsPath <<< "$FIN")/agent-state.json`. Exit 3 is a safe skip (uncommitted changes, unpushed or detached HEAD, `--local`, cwd inside the tree, preference off): report and continue. Never `--force`. Contract: [`features/worktree-finalize.md`]($HOME/.claude/multi-agent-refs/features/worktree-finalize.md).
102
+
103
+ 10. **Issue body update** (GitHub Issue only): if the issue body has a `### Pull Requests` section and/or a `### Progress` table, fill in the PR URL(s) (one row per submodule - e.g. `- **common:** {url}`, `- **uicomponents:** {url}`) and flip the Implementation / Testing / Code Connect flags from Pending to Done using whatever marker the template uses (match in place, do NOT introduce new markers). Apply with `gh issue edit {issueNo} --body "{updated body}"` and preserve every other section unchanged.
104
+ 11. **NEVER close or resolve the issue** - neither GitHub Issue nor Jira. Issues require team review (4 approvals) before closing. Only post a comment with commit/PR URLs.
105
+ 12. Log: "Phase 6: Commit {sha} - PR #{number}, worktree {removed|kept: <reason>}"
95
106
 
96
107
  #### Step 3 - PR Description (technical detail for reviewers)
97
108
 
@@ -294,11 +305,13 @@ done
294
305
  update_sibling_links "${PR_URLS[@]}"
295
306
  ```
296
307
 
308
+ **Then finalize each repo's worktree**, serially and only now - `update_sibling_links` updates one PR per repo and the loop `cd`s per repo, so removing repo 1's worktree mid-loop breaks 2..N. Run `worktree-finalize.sh` once per repo with that repo's `--worktree` / `--project-root` / `--project`; each skips or removes independently.
309
+
297
310
  `update_sibling_links` does an UPDATE call per PR (Bitbucket: PUT with `version+1`; GitHub: `gh pr edit --body-file`). Idempotent: re-running with the same set is a no-op.
298
311
 
299
312
  ##### GitHub issue body - list all PRs
300
313
 
301
- Step 9's "Issue body update" extends to multi-repo: instead of a single `### Pull Requests` line, write one row per repo:
314
+ Step 10's "Issue body update" extends to multi-repo: instead of a single `### Pull Requests` line, write one row per repo:
302
315
 
303
316
  ```
304
317
  ### Pull Requests
@@ -212,7 +212,10 @@ This is independent of the channels-side `reportContent.costSummary` (which gate
212
212
  **Triage memory ingest (mandatory):** after Phase 4 produces a final triage output, persist the accepted/deferred/rejected rows into the per-repo triage corpus so Phase 1 enrichment and Phase 4 prior-art lookup can recall them on future tasks. Idempotent - re-running on the same task writes 0 rows.
213
213
 
214
214
  ```bash
215
- TRIAGE_PATH="$WORKTREE/triage-output.json"
215
+ # Salvaged copy first: Phase 6 removes the worktree once the PR is open, and this
216
+ # reader is `[ -f ]`-guarded, so a wrong path degrades SILENTLY.
217
+ TRIAGE_PATH="$(jq -r '.artifactsPath // empty' "$STATE_FILE" 2>/dev/null)/triage-output.json"
218
+ [ -f "$TRIAGE_PATH" ] || TRIAGE_PATH="$WORKTREE/triage-output.json"
216
219
  if [ -f "$TRIAGE_PATH" ]; then
217
220
  node $HOME/.claude/scripts/triage-memory.mjs ingest \
218
221
  --triage "$TRIAGE_PATH" \
@@ -29,7 +29,8 @@
29
29
  "repoScanDays": 90,
30
30
  "identityRoutingEnabled": false,
31
31
  "multiRepoEnabled": false,
32
- "pushMustSucceed": true
32
+ "pushMustSucceed": true,
33
+ "worktreeAutoRemoveOnPr": true
33
34
  },
34
35
  "defaultJiraKey": null,
35
36
  "reportChannels": {
@@ -162,6 +162,10 @@
162
162
  "timestamp": {
163
163
  "type": "string",
164
164
  "description": "ISO-8601 time of the consultation."
165
+ },
166
+ "routedBy": {
167
+ "type": "string",
168
+ "description": "Set when a stack toolkit's own index skill chose this skill, as '<toolkit>:index@<version>'. Recorded so a finding can be traced to the index version that selected it - the skill set differs between plugin versions. Phase 4 Step 1.78 surfaces these separately in the manifest ledger; it does NOT grant them extra trust, because the resolver stays primary either way."
165
169
  }
166
170
  }
167
171
  }
@@ -685,6 +689,15 @@
685
689
  }
686
690
  }
687
691
  }
692
+ },
693
+ "worktreeRemovedAt": {
694
+ "type": ["string", "null"],
695
+ "format": "date-time",
696
+ "description": "Set when Phase 6 removed the worktree after opening the PR. Its presence is what tells :resume, :status and :log that a worktree-less task is finished-and-tidied rather than broken - without it a missing worktree is indistinguishable from a killed run."
697
+ },
698
+ "artifactsPath": {
699
+ "type": ["string", "null"],
700
+ "description": "Directory the worktree's artefacts were salvaged into before removal (agent-state, phase-tracker, triage-output, .pipeline/, build+test logs, review diff). Phase 7 and :resume read from here when worktreePath is gone."
688
701
  }
689
702
  }
690
703
  }