@mmerterden/multi-agent-pipeline 16.21.0 → 16.24.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +230 -103
  2. package/README.md +5 -7
  3. package/README.tr.md +5 -7
  4. package/docs/architecture.md +3 -3
  5. package/docs/ecosystem.md +5 -5
  6. package/install/templates/copilot-instructions.md +17 -2
  7. package/package.json +1 -1
  8. package/pipeline/commands/multi-agent/SKILL.md +0 -5
  9. package/pipeline/commands/multi-agent/help/SKILL.md +0 -10
  10. package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +2 -2
  11. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -1
  12. package/pipeline/commands/multi-agent/resume-local/SKILL.md +2 -2
  13. package/pipeline/commands/multi-agent/sync/SKILL.md +13 -13
  14. package/pipeline/multi-agent-refs/channels/jira.md +7 -0
  15. package/pipeline/multi-agent-refs/channels/pr.md +16 -3
  16. package/pipeline/multi-agent-refs/cross-cli-contract.md +10 -12
  17. package/pipeline/multi-agent-refs/features/visual-evidence.md +172 -0
  18. package/pipeline/multi-agent-refs/generate-issue.md +3 -3
  19. package/pipeline/multi-agent-refs/phases/modes.md +1 -1
  20. package/pipeline/multi-agent-refs/phases/phase-0-init.md +9 -2
  21. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +5 -1
  22. package/pipeline/multi-agent-refs/phases/phase-5-test.md +1 -0
  23. package/pipeline/multi-agent-refs/phases/phase-6-commit.md +1 -1
  24. package/pipeline/multi-agent-refs/phases/phase-7-report.md +8 -1
  25. package/pipeline/multi-agent-refs/tracker-contract.md +46 -0
  26. package/pipeline/schemas/agent-state.schema.json +269 -42
  27. package/pipeline/schemas/prefs.schema.json +45 -19
  28. package/pipeline/schemas/token-budget.json +3 -3
  29. package/pipeline/scripts/capture-evidence.sh +187 -0
  30. package/pipeline/scripts/jira-attach.sh +85 -0
  31. package/pipeline/scripts/phase-tracker.sh +245 -3
  32. package/pipeline/skills/.skill-manifest.json +8 -24
  33. package/pipeline/skills/.skills-index.json +2 -46
  34. package/pipeline/skills/shared/README.md +4 -8
  35. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +0 -8
  36. package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +1 -1
  37. package/pipeline/skills/shared/core/multi-agent-resume-local/SKILL.md +1 -1
  38. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +12 -12
  39. package/pipeline/skills/skills-index.md +2 -6
  40. package/pipeline/commands/multi-agent/dev/SKILL.md +0 -17
  41. package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +0 -23
  42. package/pipeline/commands/multi-agent/dev-local/SKILL.md +0 -17
  43. package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +0 -21
  44. package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +0 -19
  45. package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +0 -25
  46. package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +0 -19
  47. package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +0 -23
package/CHANGELOG.md CHANGED
@@ -16,6 +16,64 @@ Internal file-layout changes that don't affect the slash-command surface are sti
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [16.24.0] - 2026-09-09
20
+
21
+ ### Added
22
+
23
+ - **A UI fix now carries its picture.** Three changed files in a diff do not tell a reviewer what was wrong, and they do not tell a tester what to look for. When `taskType` is `bugfix` and a UI file changed, the run produces a before/after pair; when the work is design (a `component` task, a Figma reference, or a UI file that did not exist before), it produces the after. The verdict comes from `taskType` plus the changed-file list, mechanically, not from the model's reading of the task - the UI-file test is per stack and written down.
24
+
25
+ **Before is the reporter's screenshot, or nothing.** Rebuilding the pre-fix state to photograph it costs a second build and a second launch on every UI bug, to recreate evidence the reporter usually attached already. So Phase 0 keeps the issue's own image attachments and that is the whole of it; no image on the ticket is a recorded gap, not a search.
26
+
27
+ **After is captured in Phase 3, not Phase 5.** Phase 5 is the natural home - the simulator is already up - and it is dropped by every `autopilot` and `--local` entry, so a capture living only there produces nothing for exactly the runs nobody watched. It happens instead right after the build+test gate goes green, which is in every mode's phase set. `capture-evidence.sh` owns the naming, cleans the status bar (two shots of one screen otherwise differ by the clock, which makes every "after" look like a change) and downscales to 1242px.
28
+
29
+ - **The flow video, in three tiers, so no repo is excluded by not having a test target.** Tier 1 runs the repo's own XCUITest/Espresso target when it can be seeded with test data - the most faithful record, and it is a test that already runs in CI. Tier 2 drives the flow with `agent_run_steps` and records with `ios_record_video` / `android_record_screen`, needing nothing but a launchable build. Tier 3 is the honest skip for a library or a backend, with the reason recorded. The flow itself comes from the ticket when someone wrote one there, and from the Phase 5 test scenarios otherwise: human-written beats generated, the same rule the before follows.
30
+
31
+ - **Where they land.** The artefacts are Jira attachments. The Phase 7 comment renders them _inside_ its existing sections rather than adding a fourth - the section order is fixed and the contract says so - with the before/after thumbnails under the work summary and the flow video under the scenario it demonstrates. The PR body gains a `visuals` section that names the filenames and the ticket, deliberately not the URLs: a Jira attachment URL is auth-gated and renders as a broken image for anyone reading the PR outside a Jira session.
32
+
33
+ - **Degrading quality is allowed; dropping the artefact is not.** Jira's ceiling is an instance setting, so it is a preference (`visualEvidence.maxAttachmentMb`, default 10). Over it, a video re-encodes to 720p and a screenshot to JPEG q80; still over, the video becomes a four-frame contact sheet that says why. Phase 6 Step 3 blocks when a required artefact is neither attached nor explained - the gate is against silence, not against an honest "the ticket carries no image".
34
+
35
+ - **`smoke-visual-evidence.sh`** - 32 checks, most of them about stated absence being as required as stated presence: both scripts refuse bad input rather than guessing, the size ladder never returns an empty path, all four phases act, both renderers know where pictures go, the schemas declare the fields, and no ref re-implements the attachment POST.
36
+
37
+ ### Changed
38
+
39
+ - **One Jira uploader instead of two.** `generate-issue.md` carried its own attachment `curl`; visual evidence would have been the second copy, and two copies is two places for `X-Atlassian-Token: no-check` to go missing - which fails as a 403 that reads like an auth problem. Both now call `jira-attach.sh`, which resolves host and token from prefs, keeps the token out of argv, and prints `<filename>\t<url>` per file.
40
+
41
+ ## [16.23.0] - 2026-09-09
42
+
43
+ ### Fixed
44
+
45
+ - **The progress signal reached stdout and stopped there.** `phase-tracker.sh render` prints a bordered card at every phase boundary and it was always correct on disk. It was also invisible: the hosts collapse tool output into `Ran 8 shell commands`, so the card the contract calls "the last tool result in the chat" has not been in the chat for a while. A run showed its pickers, went quiet for forty minutes, and finished without ever drawing its shape.
46
+
47
+ Each host has a widget and now the tracker names its calls instead of hoping they are remembered. `phase-tracker.sh tiles` reads the phase set already registered by `add` and prints the registration calls for the host it runs from: a `TaskCreate` list on Claude Code, one `update_plan` payload on Codex, and on Copilot CLI - which has no task widget at all - the instruction to reprint the card inside the reply, plus the card. Every `update` then prints a `-- NEXT (required) --` block carrying that host's mirror call. Phase 0 Step −1 calls `tiles` right after the `add` loop; the Copilot instructions template does the same.
48
+
49
+ - **`phase-tracker.sh tokens` was documented, linted for, and never called.** Not once: no run in the local log tree had a `tokens_in`, `tokens_out` or `model` field on any phase, going back weeks. Every phase priced at `-`, every required completion line printed without its numbers, and every gate stayed green - because `smoke-tracker-tokens-invocation.sh` lints that the phase docs _mention_ the call, which is a claim about prose, not about a run. `payload-contracts.md` had already written that limitation down.
50
+
51
+ The gate moved to where the call would have landed: `update <N> completed` now **exits 3** for phases 1-4 when nothing was recorded, prints the two commands that clear it, and leaves the phase exactly as it was - record, then re-run, nothing lost. A phase that genuinely ran no LLM call completes with `--no-llm`, which is recorded so the report can tell "nothing" from "unknown". Phases 0 and 5-7 are orchestration and are not gated, and `skipped` never is, because Short skips 1 and 2 by design.
52
+
53
+ - **A full model id silently cost nothing.** `cost-table.json` is keyed by family (`opus`, `sonnet`, `gpt-5.6`) while the name an agent has at hand is `claude-opus-5`, and the mismatch priced the phase at `-` with no complaint anywhere. `model` now resolves the family and says so out loud, listing the priced names, when it cannot.
54
+
55
+ ### Added
56
+
57
+ - **`phase-tracker.sh report` - the closing report the run never printed.** Per phase: status, elapsed, tokens in/out, model and USD; then the run total, and an explicit list of any phase it could not price rather than a silent gap. Phase 7 prints it beside `render-work-summary.sh` (scope delivered, changed files with line counts, PR and commit links) so the end of a run answers both questions at once - what the pipeline spent, and what it changed. `format_span` gives the total an hours bucket; the per-phase card keeps minutes.
58
+
59
+ - **`smoke-tracker-widget.sh`** - 31 checks over the parts that were failing quietly: `tiles` emits the right call for each of the three hosts and starts at phase 0, the accounting gate fires and is recoverable, `--no-llm` is honoured and recorded, a full model id resolves to a priced family, every `update` carries its host's mirror call, the report prices what it can and names what it cannot, and the phase docs actually make these calls.
60
+
61
+ ### Removed
62
+
63
+ - **The four `dev` redirect stubs are gone from the command surface.** `16.0.0` turned pipeline depth into a question the run asks at Phase 0 Step 7.5 and left `multi-agent-dev`, `multi-agent-dev-autopilot`, `multi-agent-dev-local` and `multi-agent-dev-local-autopilot` behind as stubs that printed a redirect and nothing else. Each one said, in its own body, that it would be deleted in the next minor. Twenty-one minors later they were still in the picker: typing `/multi-agent:` still offered `dev`, still described it as removed, and the description was the only thing it did.
64
+
65
+ Deleted from every surface that carries them - Claude Code commands, the shared skill tree, the Copilot dash-command skills, the Codex refs mirror - along with the generated indexes (`.skills-index.json`, `skills-index.md`, `shared/README.md`, `.skill-manifest.json`, now 207 skills rather than 211) and the migration lines in `multi-agent-help` and the Copilot instructions template. `smoke-pipeline-surface.sh` check 5b, the gate that fails when a shipped file points a reader at a retired name, loses the four exemptions that existed only because the stubs and their generated index rows were allowed to name themselves; it is strictly tighter now.
66
+
67
+ Nothing about the surface changes for anyone who has already migrated: the break shipped in `16.0.0`, this is the tombstone coming down. `/multi-agent` and `/multi-agent:local` still ask Full or Short, and Short is still what the removed flag used to mean.
68
+
69
+ - **The four names themselves, everywhere they were still readable.** Deleting the directories left the names sitting in prose, and prose is what a user acts on. Gone from the dispatcher routing table (the `--dev` / `dev-*` redirect row), from `modes.md` and `phase-3-dev.md`, from `resume-local` and `local-autopilot` (which described themselves in terms of a command that no longer exists), and from the `ios-coding-standard` picker description, which had been telling readers to hand their remediation plan to `dev/dev-local` - a description is the one piece of a command everybody reads.
70
+
71
+ `examples/03-dev-mode-fast-path.md` was the worst of them and no gate could see it: `smoke-pipeline-surface.sh` check 2 scanned `pipeline/`, `docs/`, `install/` and `test/`, never `examples/`, so a whole worked example kept teaching `--dev` fourteen minors after the flag was removed - and contradicted itself while doing it, listing Phase 4 as skipped when Review has not been skippable since `14.0.0`. It is now `examples/03-short-run-fast-path.md`, written against the depth question, with the phase list and the transcript corrected. The `--dev` invocations in examples 01 and 02 and in the bug-report issue template went with it.
72
+
73
+ With nothing left to detect, the deprecation-era gates came out too: `smoke-pipeline-surface.sh` loses check 2 (the `--dev` flag scan), check 5b (the retired-name scan) and both planted probes, and `smoke-mode-dispatch-drift.sh` loses the two loops that named the four modes - the generator's unknown-mode guard already covers them. The gate is 203 lines down to 98 and every remaining check asserts something that can still be false.
74
+
75
+ Left alone on purpose: `CHANGELOG.md`, `CHANGELOG-archive.md`, `ROADMAP.md`, `docs/internal/` and the calibration log in `token-budget.json` are history, and rewriting them would misrepresent what shipped when. So is the runtime label `"dev"` that `state.onlyDevelop` still produces for the cost report and the component-dispatch payload: that is a run shape, not a command name, and renaming it would break the plugin contract and orphan every log already written.
76
+
19
77
  ## [16.21.0] - 2026-09-08
20
78
 
21
79
  ### Added
@@ -39,7 +97,6 @@ Internal file-layout changes that don't affect the slash-command surface are sti
39
97
  - **`multi-agent:setup` Step 8 and the picker-contract stop miscounting the hooks.** Both said two gates ship, and the setup step called the secret scan "the only deterministic gate that is OS-enforceable as a hook" - untrue since `agent-guard.sh` landed. Three ship now, and both places say so, along with which side of the run each one inspects.
40
98
  - **`smoke-gate-hooks.sh` covers all three gates** rather than two, and asserts the shipped defaults are safe (the read gate off, phase 3 exempt) - a read gate that shipped switched on would have been a breaking change disguised as a merge.
41
99
 
42
-
43
100
  ## [16.20.0] - 2026-09-07
44
101
 
45
102
  A comparison against `msitarzewski/agency-agents`, a 150k-star persona catalog, found nothing to copy wholesale and three things to fix: the personas were never registered, the circuit-breaker was prose, and a review finding had no identity from one round to the next. This release gives findings that identity and builds the loop controls, the handoff records and the evidence rules on top of it.
@@ -175,7 +232,6 @@ A review of the three repositories together (pipeline, toolkit MCP, plugin marke
175
232
 
176
233
  - **Usage reporting only ever reported runs that finished.** The reporter was called from Phase 7, and Phase 7 is the phase a run is least likely to reach: across the whole local log history, exactly one run got there. So the admin dashboard read zero of everything while the table held real rows, and the question it exists to answer - who is using this - had no answer. Phase 0's exit gate now reports the run as started, on the same upsert key, so Phase 7 replaces that row with the final state and nothing is double-counted. The read side of the same bug is fixed in the dashboard: its rollup folded only `terminal` rows, and an unfinished run was scored `fail`.
177
234
 
178
-
179
235
  ### Changed
180
236
 
181
237
  - **`/multi-agent:help` now covers the plugins and the toolkit MCP.** It named both in passing - a `refactor` line, a `sync` line, a `design-check` footnote - and never said what they are or how to call one. The catalog gained a Plugins & tools block in both languages: how a stack skill is invoked, which plugins are always on, and the MCP's 83 tools grouped by what they drive (device control, web, design comparison, store and accessibility audits), with the note that its registration survives uninstall. Paid for by compressing the `design-check` entry, which was restating its own contract in fifteen lines: 353 tokens reclaimed before a byte was added, so the file sits within its 8500 ceiling. The tool count that went in with it was wrong and is now absent: `rules/outside-the-pipeline.md` said 83 while the server serves 84, and the pipeline cannot check either number because it does not vendor the toolkit. Both places now say 80+, which stays true; the exact count lives in the toolkit's own README, where a gate boots the server and compares `tools/list` against it.
@@ -197,22 +253,21 @@ A review of the three repositories together (pipeline, toolkit MCP, plugin marke
197
253
 
198
254
  ### Added
199
255
 
200
- - **`/multi-agent:graph` - a deterministic code graph, built without an LLM.** Phase 1 re-scanned the repo with grep and read on every task and defaulted to the "very thorough" tier on feature work; Phase 7 wrote `architecture.md` from one task's window and let it age for 90 days. Both wanted the same answer: where does this live, and what depends on it. `graph-build.mjs` extracts symbols, imports and references by regex over comment-stripped source into `~/.claude/knowledge/<project>/code-graph.json`; `graph-query.mjs` runs a token-budgeted traversal, `graph-affected.mjs` walks it backwards for impact, `graph-report.mjs` renders `GRAPH_REPORT.md`, and `validate-code-graph.mjs` fails a build whose edges point at missing nodes. Zero runtime dependencies, zero API cost, read-only on the repo. Design reference: `Graphify-Labs/graphify`; the reasoning, the trade and the numbers are in `docs/adr/0010-own-code-graph.md`.
256
+ - **`/multi-agent:graph` - a deterministic code graph, built without an LLM.** Phase 1 re-scanned the repo with grep and read on every task and defaulted to the "very thorough" tier on feature work; Phase 7 wrote `architecture.md` from one task's window and let it age for 90 days. Both wanted the same answer: where does this live, and what depends on it. `graph-build.mjs` extracts symbols, imports and references by regex over comment-stripped source into `~/.claude/knowledge/<project>/code-graph.json`; `graph-query.mjs` runs a token-budgeted traversal, `graph-affected.mjs` walks it backwards for impact, `graph-report.mjs` renders `GRAPH_REPORT.md`, and `validate-code-graph.mjs` fails a build whose edges point at missing nodes. Zero runtime dependencies, zero API cost, read-only on the repo. Design reference: `Graphify-Labs/graphify`; the reasoning, the trade and the numbers are in `docs/adr/0010-own-code-graph.md`.
201
257
  - **Phase 1 Step 2.6 and Phase 7 graph refresh, behind `prefs.global.codeGraph.enabled` (default `false`).** When on, Phase 1 queries the graph and hands Explore a ranked starting set instead of a full scan, and Phase 7 rebuilds the graph after the branch changed code. Off, the pipeline behaves exactly as before. Measured on a 4,300-file Swift app at a fixed 30k retrieval budget: 80.4% coverage at 18,465 tokens against 66.0% at 24,555 for grep-and-read. The win is entirely in domain-word searches (63.3% vs 32.0% at under half the cost); when the task already names an exact type, `grep -lw` is still slightly better and slightly cheaper, and the command says so.
202
258
  - **`_path-match.mjs`.** The glob matcher `test-gap-scan.mjs` carried inline is now shared with the graph engine, so the two cannot drift apart on what a repo excludes.
203
259
  - **Code-graph rules for Android, Node and Python.** Four stacks now build: measured on a 4,229-file Kotlin app (25,234 nodes, 70,897 edges), this repo (126 sources) and a 43-file Python service, each validated and each hub list checked by hand rather than assumed correct. Two engine defects surfaced doing it, both of which iOS had been getting away with. Nesting was read from the pattern's match column, so `public final class Foo` counted as nested because the match starts at `class` seven columns in; it now reads the line's indentation. And the import pass read a fully stripped body, which blanks a JavaScript module specifier because the specifier IS a string literal - every import edge in that stack would have vanished with nothing to notice it. iOS was re-gated after both and scored identically (80.4% at 18,467 tokens).
204
260
  - **A nested declaration is a node but never a reference target.** Kotlin sealed cases and Python inner classes are named after the concept they model - `Icon`, `Color`, `Success`, `Disabled` - and each is declared exactly once, so the ambiguity rule never caught them. Every Kotlin file that merely mentioned Compose's `Color` gained an edge to one app's nested case: 4,072 false edges out of 74,969, and four of the top eight architectural hubs were wrong. They stay findable by name through their `defines` edge.
205
- - **`prefs.global.modelFallback.fableEnabled` (default `true`) - the fable rung as a switch, not only as a fallback.** Every other trigger in the fallback contract reacts to something going wrong; this one asserts up front that a rung is not in play, so there is no dispatch attempt and no error to recover from. `false` starts `ios/android/backend-architect`, `code-reviewer` and triage on `opus`. Claude Code only: Copilot CLI does not offer Fable 5, and on Codex CLI the `fable` rung means `gpt-5.6 @ xhigh`, a different vendor's model on a different account, which the switch deliberately leaves alone. Turning it off also collapses the Phase 4 Claude Code panel to two reviewers, because Reviewer 1 lands on the `opus` Reviewer 2 already holds and one model dispatched twice is not cross-model review; `consensus.reviewerCount` records `2`. Set `costBudget.priceAt` to `opus` alongside it, or the estimate keeps pricing at a rung nothing runs on and trips the ceiling early.
261
+ - **`prefs.global.modelFallback.fableEnabled` (default `true`) - the fable rung as a switch, not only as a fallback.** Every other trigger in the fallback contract reacts to something going wrong; this one asserts up front that a rung is not in play, so there is no dispatch attempt and no error to recover from. `false` starts `ios/android/backend-architect`, `code-reviewer` and triage on `opus`. Claude Code only: Copilot CLI does not offer Fable 5, and on Codex CLI the `fable` rung means `gpt-5.6 @ xhigh`, a different vendor's model on a different account, which the switch deliberately leaves alone. Turning it off also collapses the Phase 4 Claude Code panel to two reviewers, because Reviewer 1 lands on the `opus` Reviewer 2 already holds and one model dispatched twice is not cross-model review; `consensus.reviewerCount` records `2`. Set `costBudget.priceAt` to `opus` alongside it, or the estimate keeps pricing at a rung nothing runs on and trips the ceiling early.
206
262
 
207
263
  ### Fixed
208
264
 
209
265
  - **`derivedSkillSources` could not say which repo held the derived copies.** `localPath` reads as relative to the current repo, so both entries pointed at the UPSTREAM tree and stayed wrong through a plugin rename: the drift check was comparing a path that did not exist. Added `localRepo`, corrected both entries, and re-derived their skill lists from the tree instead of from memory.
210
266
  - **Two `excludePathGlobs` shapes were silently inert.** A pattern with a wildcard and no `**` fell through to a literal `endsWith` / `includes` test against the pattern text, which can never be true: nothing ends with the characters `*.d.ts`. So `*.d.ts` in the node rules and `*.egg-info/` in the python rules excluded nothing, and generated typings and build residue reached every consumer of those rules, `test-gap-scan.mjs` included. Both shapes now translate to an anchored regex, and eight glob cases are pinned by the smoke.
211
267
  - **`--all-data` left the knowledge base behind.** Full uninstall removed the log root and the per-repo memory root but never `~/.claude/knowledge/`, which holds `architecture.md`, `patterns.md`, `gotchas.md`, `decisions.md` and now `code-graph.json`. That is prose about a private codebase plus every symbol name in it, and it is the data a user most likely means by "remove everything". A default uninstall still keeps all of it, and memory written inside a repo (`$PROJECT_ROOT/.multi-agent/memory/`) is still never touched by either mode: it lives in the user's working tree and may be committed.
212
- - **`modelFallback.floorModel` could not be set.** The fallback contract has documented it since the two-step ladder landed, but it was absent from `prefs.schema.json`, whose `modelFallback` object forbids extra keys - so a prefs file that followed the documented example failed validation. Declared, defaulted to `haiku`, and added to the template.
268
+ - **`modelFallback.floorModel` could not be set.** The fallback contract has documented it since the two-step ladder landed, but it was absent from `prefs.schema.json`, whose `modelFallback` object forbids extra keys - so a prefs file that followed the documented example failed validation. Declared, defaulted to `haiku`, and added to the template.
213
269
  - **The consensus rule still claimed two reviewers on Claude Code.** v16.12.0 added the third reviewer and updated the matrix but not the `reviewerCount` line 300 lines below it. `smoke-cross-cli-behavior.sh` reads the matrix, so it never noticed. The line now says what it should have said all along: the count is the reviewers that actually dispatched, not the configured maximum.
214
270
 
215
-
216
271
  ## [16.12.0] - 2026-08-27
217
272
 
218
273
  ### Added
@@ -241,14 +296,13 @@ A review of the three repositories together (pipeline, toolkit MCP, plugin marke
241
296
  - The analysis ref ceiling moves 148000 -> 154000 across two raises in one session. Everything that could live outside the count already does: the scan in `figma-screenshot.sh`, the reviewer rubric in `analysis/review.md` which a subagent loads and the orchestrator does not, and all four new gates in `validate-analysis-doc.mjs`. The tree was measured for reclaimable duplication first and had none.
242
297
  - `smoke-validate-analysis-doc.sh` replaces four section-number pairing assertions with the `AS-NN` contract in both directions.
243
298
 
244
-
245
299
  ## [16.11.0] - 2026-08-27
246
300
 
247
301
  ### Changed
248
302
 
249
303
  - **Picker option labels now follow `outputLanguage`.** Only the `AskUserQuestion` `header` chip stays English, because it is capped at 12 characters and most Turkish equivalents overflow it. A Turkish run used to render a Turkish question with English buttons, which reads as a half-translated dialogue rather than a contract. `rules.md` carries the new per-field matrix; `picker-contract.md` carries what the caller now owns: branch on which option was picked, never on its rendered text, and pass `default` / `ASK_CHOICE_DEFAULT` as a 1-based index. The host's own **Other** row is injected in English and no run can localize it.
250
304
  - **`Frontend` is `Web` across the pipeline** - the platform picker, the stack command, the schemas, the conventions tables, the phase docs and `frontend-guide.md`, now `web-guide.md`. The published `ai-frontend-toolkit` plugin id, the `frontendRepos` / `frontendRoots` prefs keys and the `frontend` stack alias all keep working: `web` is canonical and `frontend` still resolves, so nothing written before this release stops loading. `webRepos` / `webRoots` are the new spelling; the old pair is documented as deprecated rather than removed.
251
- - **A repo-less analysis run keeps its channel split.** It used to flatten everything into one document because there were no repo conventions to project onto. But a phone screen and a browser screen carry different requirements whether or not a repository exists; only the *projection* half of the split needed conventions. Channels are now derived from the evidence (`intake.md` Step 3 carries the signal table) and one document is emitted per channel. `mobile` stays one channel rather than iOS plus Android, since without conventions nothing tells the two apart.
305
+ - **A repo-less analysis run keeps its channel split.** It used to flatten everything into one document because there were no repo conventions to project onto. But a phone screen and a browser screen carry different requirements whether or not a repository exists; only the _projection_ half of the split needed conventions. Channels are now derived from the evidence (`intake.md` Step 3 carries the signal table) and one document is emitted per channel. `mobile` stays one channel rather than iOS plus Android, since without conventions nothing tells the two apart.
252
306
 
253
307
  ### Fixed
254
308
 
@@ -270,7 +324,6 @@ A review of the three repositories together (pipeline, toolkit MCP, plugin marke
270
324
  - The analysis ref ceiling moves 145000 -> 148000. Unlike the v16.6.0 raise this one is not compensating for a measurement error: the tree gained three features and the number is right. About 1.4 kB of new prose was trimmed back before the ceiling was touched.
271
325
  - `smoke-language-matrix.sh` and `smoke-language-axis.sh` now assert the new rule in both directions - label follows `outputLanguage`, header stays English - and three more phrasings were added to the contradiction list after one slipped past the old set.
272
326
 
273
-
274
327
  ### Fixed
275
328
 
276
329
  - **The website sync committed under whatever identity the run carried, and the site silently stopped updating.** Step 4 ran a bare `git commit`, so the commit took the active account's address. The deploy platform builds only a commit whose author is a contributor on the project; any other author is accepted by the push and then never built - the deployment is created, reports `readyState: BLOCKED` (rendered by the CLI as `UNKNOWN` with a 0ms build), and the live site keeps serving the previous version. v16.4.0 and v16.5.0 were both pushed that way, neither was ever built, and both syncs reported the website as done.
@@ -285,7 +338,7 @@ A review of the three repositories together (pipeline, toolkit MCP, plugin marke
285
338
 
286
339
  ### Fixed
287
340
 
288
- - **`write-state.mjs` could delete a live lock and lose a writer's update.** The stale-lock reclaim deleted by path: between judging a lock stale and unlinking it, the holder can release and a third writer can acquire a fresh one, so the unlink removed a *live* lock and two writers then held it. It is the same failure the PID-window comment in that file already describes, at a different point in the acquire loop, and it survived because it only reproduces under load - `smoke-write-state.sh` failed inside a full gate run and passed 12/12 when run alone. Reclaim is now by identity: the inode and mtime judged stale must still be the file at that path, otherwise it belongs to somebody else and is left alone. Twelve runs under artificial load are clean, which is evidence and not proof - a race cannot be proven absent.
341
+ - **`write-state.mjs` could delete a live lock and lose a writer's update.** The stale-lock reclaim deleted by path: between judging a lock stale and unlinking it, the holder can release and a third writer can acquire a fresh one, so the unlink removed a _live_ lock and two writers then held it. It is the same failure the PID-window comment in that file already describes, at a different point in the acquire loop, and it survived because it only reproduces under load - `smoke-write-state.sh` failed inside a full gate run and passed 12/12 when run alone. Reclaim is now by identity: the inode and mtime judged stale must still be the file at that path, otherwise it belongs to somebody else and is left alone. Twelve runs under artificial load are clean, which is evidence and not proof - a race cannot be proven absent.
289
342
 
290
343
  - **Counts that had drifted from the tree.** `skills-index.md` and `skills/shared/README.md` still said 208 skills against 210 on disk; both are generated, so they were regenerated rather than hand-edited. `/multi-agent:update` quoted "245 scripts, 208 skills" for what an install lays down; it is 263 and 210. A comment in `smoke-command-inventory.sh` used "51 commands" as its example, which is the kind of number that goes stale the moment a command lands - it now says what it means without pinning a figure.
291
344
 
@@ -295,7 +348,7 @@ Three debts the last few releases kept naming, closed.
295
348
 
296
349
  ### Added
297
350
 
298
- - **`smoke-help-sync.sh` pairs each command's own description with the line `/multi-agent:help` shows for it.** Three releases running shipped the same defect: 16.7.1 (two commands absent from help entirely), 16.8.1 (the analysis entry describing behaviour from two releases earlier), 16.9.0 (a telemetry block still saying "optional, opt-in" after the default flipped). Every other gate was green through all three, because none of them asked whether the sentence a user reads still matches what the command does. The gate hashes both sides and fails when one moved without the other. Its limit is written into the file: it cannot tell whether either text is *correct* - a maintainer who edits both to say something equally wrong still passes. What it forces is that the pair gets looked at together, which is exactly what did not happen those three times.
351
+ - **`smoke-help-sync.sh` pairs each command's own description with the line `/multi-agent:help` shows for it.** Three releases running shipped the same defect: 16.7.1 (two commands absent from help entirely), 16.8.1 (the analysis entry describing behaviour from two releases earlier), 16.9.0 (a telemetry block still saying "optional, opt-in" after the default flipped). Every other gate was green through all three, because none of them asked whether the sentence a user reads still matches what the command does. The gate hashes both sides and fails when one moved without the other. Its limit is written into the file: it cannot tell whether either text is _correct_ - a maintainer who edits both to say something equally wrong still passes. What it forces is that the pair gets looked at together, which is exactly what did not happen those three times.
299
352
 
300
353
  ### Changed
301
354
 
@@ -310,7 +363,7 @@ Three debts the last few releases kept naming, closed.
310
363
 
311
364
  ### Added
312
365
 
313
- - **`/multi-agent:analysis` emits a run record.** It is a pipeline of its own now - its own phases, its own gates, its own report - but telemetry was only wired into the dev pipeline's Phase 7, so analysis runs were invisible. The panel showed dev work only, and the command people reach for *before* writing any code did not exist in the usage data. Phase 5 now writes its own `agent-state.json` through `write-state.mjs` (atomic, lock-guarded, so concurrent runs cannot wipe each other) and calls the reporter. `mode` carries the profile, which is what tells one analysis run from another: a `global` run and a `corporate` run are different work. `project` is `null` on a stack-optional run - there is no repository to name - and is digested before it leaves the machine either way. The reporter stays best-effort: a failure there never touches the run, whose summary is already printed.
366
+ - **`/multi-agent:analysis` emits a run record.** It is a pipeline of its own now - its own phases, its own gates, its own report - but telemetry was only wired into the dev pipeline's Phase 7, so analysis runs were invisible. The panel showed dev work only, and the command people reach for _before_ writing any code did not exist in the usage data. Phase 5 now writes its own `agent-state.json` through `write-state.mjs` (atomic, lock-guarded, so concurrent runs cannot wipe each other) and calls the reporter. `mode` carries the profile, which is what tells one analysis run from another: a `global` run and a `corporate` run are different work. `project` is `null` on a stack-optional run - there is no repository to name - and is digested before it leaves the machine either way. The reporter stays best-effort: a failure there never touches the run, whose summary is already printed.
314
367
 
315
368
  ### Fixed
316
369
 
@@ -470,7 +523,7 @@ Four ideas taken from github/spec-kit, obra/superpowers, karpathy/llm-council an
470
523
  platform's promised files enter one denominator instead of the first one
471
524
  standing in for the run.
472
525
  - **A plan with zero steps passed.** `todos: []` reported `0/0 steps accounted
473
- for` and exited 0, so a Phase 2 that produced nothing - or a state whose todos
526
+ for` and exited 0, so a Phase 2 that produced nothing - or a state whose todos
474
527
  were cleared - read as a fully delivered plan. An empty plan is now exit 2,
475
528
  the same as a missing one.
476
529
  - **A malformed reviewer dispatch vanished.** A reviewer whose `findings` came
@@ -714,10 +767,12 @@ Found by reviewing v16.0.0 after it shipped. Four defects, three of them mine fr
714
767
  ## [15.21.0] - 2026-08-23
715
768
 
716
769
  ### Added
770
+
717
771
  - **A confirmation pass that shows what was derived and asks only what was not.** Phase 2 Step 0.9 runs before planning: the platform set, the seven convention groups, the existing components, the localization keys and the analytics events all came out of the repos in Phase 1, so they are shown for confirmation rather than asked. Only Section 20 rows are asked, through the resolve engine, one row with at most three source-labeled candidates plus Defer - and never an invented one. It sits here rather than in Phase 4 because Phase 4 runs after development, where an answer arrives too late to change anything.
718
772
  - A corrected derived value rewrites its Pass B footnote as `^[user-override: resolved <date>]` (Locked 24), so where a value came from stays traceable even after a human overrode it.
719
773
 
720
774
  ### Changed
775
+
721
776
  - **The analysis stopped asking for the platform.** Every repo selected in Phase 0 already carries a stack tag from the project scan, so the platform set is the distinct tags of the selected repos. It is derived and shown in the breadcrumb; the question survives only as a fallback for an untaggable repo or a user who wants fewer platforms than the repos imply.
722
777
  - **The analysis stopped asking for repos.** That was the third place asking the same thing - Phase 0 Step 2 selects projects, `_dev-context.md` adds editable siblings, and analysis re-derived its own list on top. Repos now come from Phase 0, and the platform-to-repo mapping falls out of the stack tags.
723
778
  - `prefs.projects[<key>].frontendRepos[]` moved into `_dev-context.md`. Only the analysis command read it, which meant the pipeline's own dev-context picker could never offer a frontend repo - they are rarely submodules, so submodule detection never finds them.
@@ -726,45 +781,54 @@ Found by reviewing v16.0.0 after it shipped. Four defects, three of them mine fr
726
781
  ## [15.20.0] - 2026-08-23
727
782
 
728
783
  ### Added
784
+
729
785
  - **The analysis can read a Word file now.** Nothing in the pipeline handled `.docx`, which is the format most feature specs actually arrive in - a spec had to be pasted or re-typed into Confluence before the analysis could cite it. `pipeline/lib/fetch-document.sh` handles `.docx`, `.pdf`, `.md` and `.txt`, as a local path or a URL, and the extractor gained a `document` type that recognises both. It is distinct from `generic-doc`, which is an HTML page: this one is a file that must be converted before it can be read.
730
786
  - **No new dependency for the common case.** A `.docx` is a zip, so `word/document.xml` is parsed with the python3 standard library the lib layer already requires; the same code path runs on macOS, Linux and Windows. Requiring pandoc or libreoffice would have been the wrong dependency for a tool that must work everywhere. PDF has no stdlib path: `pdftotext` is used when present and its absence is a soft skip (exit 6, `converter-not-available`), which on Windows is the normal case, not an error. The converter binary is configurable via `DOCUMENT_PDFTOTEXT`, which is also how the smoke exercises the degrade path without breaking `PATH`.
731
787
  - **The analysis test plan is now the TDD RED input.** Phase 3's pre-flight read the concept table and even claimed test method names come from the analysis, while nothing read Section 15 - so RED invented its own tests and the carefully written test matrix never reached development. Step 5b loads it into `state.dev.testPlan[]` and RED writes those rows. Phase 4 step 1.45 then cross-checks every planned row against a real test: missing is `important`, present-but-asserting-something-else is `blocking`. That is what makes "analysis quality is output quality" a finding rather than a slogan.
732
788
 
733
789
  ### Fixed
790
+
734
791
  - The local-document matcher also matched the path inside a URL, so `https://x/api.pdf` produced a phantom local file `//x/api.pdf`. Matches overlapping a URL span are skipped; the smoke asserts a document URL yields exactly one entry.
735
792
 
736
793
  ## [15.19.0] - 2026-08-23
737
794
 
738
795
  ### Added
796
+
739
797
  - **Business rules are written in EARS now.** Section 4.4 rule statements were free prose, so Locked 31's "two readers must not disagree on pass/fail" was carried only by the acceptance criterion, not by the rule it came from. EARS (Easy Approach to Requirements Syntax, IEEE RE'09, five patterns) fixes the clause order and the keyword set, which is what removes the ambiguity. EARS states the rule, Gherkin still states how you check it - Locked 13 widens, it does not change.
740
798
  - **Section 15.7, manual test scenarios.** The pipeline already emitted this format as the Jira test-scenario comment from `resume-local`; the analysis had no place for it, so the document a QA engineer needs was the one thing the spec did not carry. Defined once, read by both. Every MT row carries its `BR-` id, and the validator enforces it: a scenario nobody can trace to a rule is a scenario nobody can tell is stale.
741
799
  - **Section 6.X is the whole variant axis, not the part this screen used.** New Phase 1b.2 walks each Code Connect-bound instance to its main component and reads `componentPropertyDefinitions`, so "used subset" finally has a set to be a subset of. Locked 29 promised this table for four releases with no step that could produce it. It has to happen in Phase 1: Locked 30 forbids Figma access afterwards, so an axis missed here is missed for the run.
742
800
  - **Three layer headings**: `Bölüm A - Analiz` (what to build), `B - Teknik Analiz` (what is true), `C - Geliştirme Analizi` (how to build it), with a boundary rule - remove the row, what becomes unclear - and the corollary that A carries no technology name and C no business rationale. Additive `#` headings only: section numbers are referenced in 172 places including two validators, so nothing renumbers, and Confluence gains a two-level table of contents for free.
743
801
 
744
802
  ### Fixed
803
+
745
804
  - **A layer heading could have satisfied a required section.** `validate-analysis-doc.mjs` matched required sections by substring over every `#{1,3}` heading, so a layer named `Bölüm B - Mimari ve Teknik` would have reported the architecture requirement as met with Section 13 absent. The matcher now only reads numbered section headings. Caught while adding the headings, not after shipping them.
746
805
 
747
806
  ### Changed
807
+
748
808
  - UI test scenarios default ON for `taskType == component` and for any task carrying a Figma reference. They were opt-in everywhere, so UI work started with UI tests switched off.
749
809
  - The doc validator gained the 15.7 and 6.X rules; `smoke-validate-analysis-doc.sh` grew from 10 to 13 assertions, one per new contract, each planted-and-proven rather than asserted.
750
810
 
751
811
  ## [15.18.0] - 2026-08-22
752
812
 
753
813
  ### Added
814
+
754
815
  - **`/multi-agent:analysis` is a pipeline mode now, not a command standing beside the pipeline.** It runs on the same 8-phase machinery - tracker tiles, `:resume`, the cost ledger, the channels report - with four phases reinterpreted the way `--dev` reinterprets Phase 3: Phase 3 and Phase 5 skip, Phase 4 reviews the document instead of a diff, Phase 6 publishes instead of committing. Phase set 0/1/2/4/6/7, generated by `gen-mode-dispatch.mjs --mode=analysis` and drift-checked like every other mode.
755
816
  - Phase 4 in analysis mode asks reviewers one question: could an implementer build the right thing from this document alone? A finding is anything that would force them to guess. The Section 20 walk runs there too, and a deferred row reports `review_blocking` rather than quietly staying open.
756
817
  - No `local` or `autopilot` variant, on purpose: worktree isolation buys nothing when no code is written, and the intake, the convention preview and the open-question walk are interactive by nature.
757
818
 
758
819
  ### Changed
820
+
759
821
  - The engine keeps moving out of commands and into `multi-agent-refs/analysis/`: `intake.md` and `resolve.md` join `locked/evidence/synthesis/render`. `analysis-resolve` and pipeline Phase 4 now mount the same resolution walk instead of describing it twice, and `analysis/SKILL.md` fits under the 6000 hard cap that applied once its grace entry was retired.
760
822
  - Phase-doc token budget: total 54900 -> 55250. Compression came first and three times, twice on the new prose and once on old: both mode branches shrank by pointing at the refs that hold the actual walks, and the front-matter parse contract stopped being spelled out identically in two pre-flights.
761
823
 
762
824
  ## [15.17.0] - 2026-08-22
763
825
 
764
826
  ### Fixed
827
+
765
828
  - **The full pipeline demanded a document nothing produced.** Phase 2 and Phase 3 pre-flights have BLOCKED on `analysis/<feature-slug>-<platform>.md` since v9.0.0, and Phase 1 never wrote it - its output was `analysis.json`, a different artefact. So a full run either aborted at Phase 2 telling the user to go run `/multi-agent:analysis` by hand, or the model quietly ignored its own BLOCKING contract. Phase 1 Step 4 now produces the document, and both pre-flights read `state.analysis.docStatus` instead of guessing from the filesystem: `produced` / `reused` continue, `not-applicable` is a legitimate skip (bugfix or chore with no Figma reference), and only a contract breach aborts. Neither phase sends the user to another command any more, because producing the file is Phase 1's job.
766
829
 
767
830
  ### Changed
831
+
768
832
  - **The analysis engine moved out of the command and into on-demand refs.** `multi-agent-refs/analysis/{locked,evidence,synthesis,render}.md` now carry the 31 Locked decisions, the evidence gathering, the two-pass synthesis and the render/publish flow. `/multi-agent:analysis` keeps them as its contract and Phase 1 loads the same four files, so there is one engine with two entry points rather than a command the pipeline cannot reach. Side effect worth naming: `analysis/SKILL.md` went from 18081 to 5974 tokens and its lint grace entry (ceiling 18500) is retired - the grace list only ratchets down.
769
833
  - Whether the document is produced is decided from signals Phase 0 already computed, so no new question: `feature` / `refactor` / `component` always, `bugfix` / `chore` only with a Figma reference. An existing document whose `evidence_digest` still matches is reused rather than regenerated (Locked 27).
770
834
  - `analysis-output.schema.json` gains `docStatus`, `docPath[]` and `openQuestions[]` - the fields the two pre-flights branch on.
@@ -773,18 +837,21 @@ Found by reviewing v16.0.0 after it shipped. Four defects, three of them mine fr
773
837
  ## [15.16.1] - 2026-08-22
774
838
 
775
839
  ### Fixed
840
+
776
841
  - **The humanizer punctuation check never ran on macOS.** Two SKILL files told the agent to verify the emitted document with `grep -P '[\x{2013}...]'`. BSD grep has no `-P`, so on the pipeline's primary platform the command errored out and "returns zero matches" was trivially true - the policy's only mechanical check was a shipped no-op. Both now call the deterministic Node validator that already implements the same policy (`validate-analysis-doc.mjs`, `validate-complaint-doc.mjs`), which behaves identically on macOS, Linux and Windows.
777
842
  - The repo already banned `grep -P` (`smoke-shell-portability.sh`, "no guarded form"), and that gate was green the whole time: it only scanned `*.sh`. A markdown instruction file is executed too - an agent reads `grep -P ...` and runs it verbatim - so the scanner now covers `commands/`, `multi-agent-refs/` and `skills/` markdown as well. It distinguishes a prohibition from an invocation, so a line that forbids the construct still passes. Adding it immediately surfaced a second instance in `complaint-analysis/SKILL.md` that no one had noticed, which is the argument for the gate.
778
843
 
779
844
  ## [15.16.0] - 2026-08-22
780
845
 
781
846
  ### Added
847
+
782
848
  - **More than one Firebase project per team.** `keychainMapping.firebase` held exactly one service-account key, which is wrong for the normal case: a legacy app next to its redesign, or staging next to production, each with its own key. A crash URL from the project you did not pick failed the `project_id` check and reported it as a configuration error, which it was, but not the one the message suggested. `global.firebase.accounts[]` maps `projectId` to a keychain key, `fetch-crashlytics.sh` reads the projectId out of the console URL and picks the matching account, and the single slot stays the fallback so a one-project setup needs no config at all. `/multi-agent:setup` now loops the Firebase pass (`Add another Firebase project? [y/N]`), reading `project_id` from each decoded JSON rather than asking for it.
783
849
  - A `project_id` mismatch now names the key it used and prints the `accounts[]` entry to add. With several projects in play, "project mismatch" alone does not say whether the URL is wrong or the mapping is incomplete.
784
850
 
785
851
  - **Jira project keys are discovered instead of recalled.** The token is saved and the host is known by the time setup asks for a project key, so it now asks Jira: one search for issues the person assigned or reported, most-recently-updated first, and the distinct project keys become a picker. A corporate instance has thousands of projects and a typed key is a typo that routes branches and new issues at the wrong board. The free-text prompt stays as the fallback for no-VPN and fresh accounts, and the per-repo mapping offers the discovered keys rather than asking for them again.
786
852
 
787
853
  ### Changed
854
+
788
855
  - `setup/SKILL.md` lost its third copy of the service-ID table and its second copy of the `keychainMapping` shape; both live in `refs/keychain.md`, which the flow already cites. What stayed is the column nothing else had: where to generate each token. The Firebase host-exemption note also stopped being stated twice, two paragraphs apart.
789
856
  - The App Store Connect tier reasoning moved into the ref that owns that flow, and `refs/keychain.md` gained the four App Store Connect standard key names so it is now the complete answer for every service setup points at.
790
857
  - `smoke-url-enrichment.sh` follows the type label to where it now lives and adds eight assertions: five for the multi-account contract (including that the single-slot fallback survives) and three for Jira discovery and the completeness of the key-name reference.
@@ -792,30 +859,36 @@ Found by reviewing v16.0.0 after it shipped. Four defects, three of them mine fr
792
859
  ## [15.15.0] - 2026-08-22
793
860
 
794
861
  ### Added
862
+
795
863
  - **Graylog has two instances now, because it always did.** Test and production are separate Graylog deployments, and a trx id minted by a tester does not exist in production - so searching production alone answered "no logs" for a complaint that was fully logged one host over, and that answer was indistinguishable from a genuine miss. `hosts.graylogTest` and the optional `keychainMapping.graylog_test` (which falls back to the production key, correct for shared-token deployments) make the second instance addressable. `fetch-graylog.sh --env auto` is the new default: production first, test when production returns nothing or is unreachable. `--env prod` / `--env test` pin one.
796
864
  - **The payload names the instance that answered** (`source.environment`, `source.searchedEnvironments[]`), and `/multi-agent:complaint-analysis` now has to cite it. A production complaint corroborated only by test logs is `insufficient-evidence`, not a confirmed `bff` fault, and the old payload gave the triage no way to tell those apart.
797
865
  - `/multi-agent:setup` asks for the two things the previous release added a consumer for but no collector: the Graylog test host plus its optional separate token, and `fortify.versionIds`. `versionIds` shipped in 15.14.0 as the only way to resolve an instance-id-only Fortify ticket, and nothing asked for it, so that path silently no-opped for everyone.
798
866
  - **`smoke-graylog-environments.sh`**, 15 assertions over the resolution rules that are easy to get subtly wrong: auto stops at production when production answers, falls back on empty AND on unreachable, degrades (never blocks) when both are down, treats a pinned environment with no host as exit 6 rather than silently searching the other one, and lets a 401 on one instance fall through instead of masking a working answer from the other.
799
867
 
800
868
  ### Fixed
869
+
801
870
  - A pinned `--env` with no configured host now exits 6 naming the exact pref, instead of falling back to whichever host happened to be set. Attaching test logs to a production complaint is worse than attaching none.
802
871
 
803
872
  ### Changed
873
+
804
874
  - `fetch-graylog.sh` resolves host and token per environment instead of once at the top, and reads prefs through one helper rather than three near-identical inline python blocks.
805
875
 
806
876
  ## [15.14.0] - 2026-08-22
807
877
 
808
878
  ### Added
879
+
809
880
  - **A release can now be required, not just available.** `/multi-agent:update` stays exactly what it was, and most releases change nothing: the run-start check keeps reading `dist-tags.latest` and keeps asking politely. What is new is a second tag. `npm dist-tag add <pkg>@<version> required` names the oldest version a user may run, and an install below that floor is not behind, it is wrong - it would produce work against a contract that no longer holds, which then has to be redone. Below the floor, Phase 0 Step 0.6 halts: it runs the update flow and stops, and the user re-issues the command on the new version. It does not continue on the freshly updated install, because this run's phase docs, refs and scripts were already loaded from the old one, and that is the drift the floor exists to prevent. Interactive and autopilot behave identically - there is nothing to decide.
810
881
  - **`require-supported-version.sh`** turns the signal into an exit code for shell callers: 0 = proceed, 3 = halt, with `force|<local>|<latest>|<required>` on stdout and a human block on stderr. It shares `update-check.sh`'s cache, so a second command inside the TTL window costs no network call.
811
882
 
812
883
  ### Fixed
884
+
813
885
  - **The Firebase Crashlytics fetcher called an endpoint that does not exist.** It built the app reference by hand as `<platform>:<bundle>` and asked for `/v1alpha/projects/<p>/apps/<ref>/issues/<issueId>`. The appId is opaque (`1:1234567890:ios:abcdef`) and cannot be derived from a bundle, and v1alpha has no get-issue-by-id route, so every fetch failed - and the failure was reported as `api-not-enabled`, which sent anyone debugging it to look at Google's API allowlist instead of at the URL. It now resolves the real appId through the Firebase Management API (`iosApps` / `androidApps`, matching `bundleId` / `packageName`), then reads `reports/topIssues` for the summary and metrics and `events?filter.issue.id=<id>` for the newest event. The payload gains what that event carries and the old shape could not: the full `stackTrace[]`, the breadcrumb and log timeline with screen names, session and occurrence counts, and the console URI. A multi-app project with no bundle match exits 3 as `app-not-found` rather than picking a neighbouring app.
814
- - **Fortify findings that arrive without a URL are no longer invisible.** A scanner-to-tracker bridge writes the instance id and the `file:line` into the ticket and never writes the SSC link - it knows the id, and the person reading the ticket never needed the URL. The extractor was URL-only, so those tickets produced an empty `contextLinks[]`, Phase 0 skipped the deep fetch, and Phase 4's Fortify gate reported `n/a` on a ticket that exists *because of* a security finding. It now also matches a labelled `Fortify Instance ID` / `issue instance id` / `fortify id` and emits a URL-less entry, the same shape graylog trx ids already used. `fetch-fortify.sh --instance-id <id>` resolves the project version by asking each id in `prefs.global.fortify.versionIds` in order. To stay off prose, the id must be at least 16 characters and contain a digit, and the bare label `instance id` is deliberately not matched.
886
+ - **Fortify findings that arrive without a URL are no longer invisible.** A scanner-to-tracker bridge writes the instance id and the `file:line` into the ticket and never writes the SSC link - it knows the id, and the person reading the ticket never needed the URL. The extractor was URL-only, so those tickets produced an empty `contextLinks[]`, Phase 0 skipped the deep fetch, and Phase 4's Fortify gate reported `n/a` on a ticket that exists _because of_ a security finding. It now also matches a labelled `Fortify Instance ID` / `issue instance id` / `fortify id` and emits a URL-less entry, the same shape graylog trx ids already used. `fetch-fortify.sh --instance-id <id>` resolves the project version by asking each id in `prefs.global.fortify.versionIds` in order. To stay off prose, the id must be at least 16 characters and contain a digit, and the bare label `instance id` is deliberately not matched.
815
887
  - **`prefs.global.fortify` did not exist.** Phase 4 Gate 5 documented `fortify.alwaysCheck` as its opt-in from the day it shipped, but `global` is closed to additional properties and the schema had no `fortify` object, so setting it failed validation - the gate could only ever run off a referenced URL. The object now exists with `alwaysCheck` and `versionIds`.
816
888
  - Fortify URLs of the form `/ssc/html/ssc/version/<id>` and `#/version/<id>` now yield a version id. Only the API shape `/projectVersions/<id>` was matched before, so the UI links people actually paste parsed to `projectId: null`.
817
889
 
818
890
  ### Changed
891
+
819
892
  - `update-check.sh` reads both tags in one call and now uses the abbreviated packument (60 kB instead of 250 kB for the same answer). Its own contract is unchanged and deliberately so: it still always exits 0, still says nothing when the registry is unreachable, and still emits `<local>|<latest>` for a plain update. The floor appends a third field, `force`, which a `cut -f1`/`-f2` reader ignores. The cache file grows a third field too; a two-field cache written by an older install still reads, and its missing floor means "unknown", never "none".
820
893
  - **Fail-open, on purpose.** Offline, a blocked registry, an undeterminable local version, a `required` tag published above `latest`, or no tag at all: every one of these exits 0. A version gate that bricks the pipeline on a flaky network is worse than the drift it guards against.
821
894
  - **Not opt-out.** `updateCheck.enabled: false` silences the advisory "update available" prompt, which is what it always meant; it does not lift a floor. The single override is the env var `MULTI_AGENT_ALLOW_OUTDATED=1`, which exits 0 with a warning and is logged in the run record, so a broken release cannot strand someone mid-incident. Exempt commands - `update`, `setup`, `uninstall`, `help`, `status`, `log`, `search`, `routines`, `forget`, `language` - are the remedy or cannot depend on a contract.
@@ -827,126 +900,151 @@ Found by reviewing v16.0.0 after it shipped. Four defects, three of them mine fr
827
900
  ## [15.13.0] - 2026-08-22
828
901
 
829
902
  ### Fixed
903
+
830
904
  - **Three settings that were declared and did nothing are now wired up.** `learningsLedger.maxBriefEntries` had a default of 20 while both phase docs hardcoded `--max 20`, so raising it changed nothing. `testGap.scanTree` and `testGap.promoteSeverity` were declared in the schema AND implemented as `--scan-tree` / `--severity-promote` in the scanner, with nothing in between reading the pref and passing the flag: the plumbing existed at both ends and the middle was missing. A user who set any of the three got the default back with no error and no warning, and the schema told them they had done it right.
831
905
 
832
906
  ### Added
907
+
833
908
  - **`smoke-prefs-consumed.sh`: every setting the schema declares must be read by something.** This class has now shipped five times - the two `contextOffload` fields fixed in 15.11.0 and the three above - which is enough to gate rather than to keep catching by hand. The check walks the nested schema and asserts each leaf key is mentioned as a whole word somewhere outside `schemas/`. Deliberately loose: it asks "did anyone wire this up", not "is the wiring correct", because a stricter rule would have to understand shell, JS and markdown, and a gate that is wrong is worse than one that is broad. `keychainMapping.*` is exempt with a reason - those are resolved dynamically, so the literal name never appears in code by design. Verified by planting a setting nothing reads and watching the gate go red.
834
909
 
835
910
  ### Changed
911
+
836
912
  - Phase-doc token budget: total 53950 -> 54050. The 94 tokens are the wiring itself, not prose - two `--max` substitutions and a three-line flag block. Compression came first and twice: the rationale moved into the new gate's header, where it is enforced rather than described, and a `--severity-promote` table row was dropped because the invocation above it now shows the flag and names the pref that triggers it.
837
913
 
838
914
  ## [15.12.2] - 2026-08-22
839
915
 
840
916
  ### Fixed
917
+
841
918
  - **`grep -P` is banned in shell, and the ban is enforced.** Unlike the other divergences the portability gate tracks, this one has no guarded form: BSD grep has no `-P` at all, so it exits 2 with "invalid option", and the `2>/dev/null` that nearly every check carries turns that error into an empty result, which reads as "found nothing". A check written that way passes on every input including the one it was meant to catch. Two gates in this repo did exactly that, were handed a file with the defect deliberately planted in it, and reported a clean tree. The scan covers every shell file under `scripts/` and `lib/`, not only the shipped ones, because the two that broke were smoke gates that never ship - and a gate that cannot fail is worse than no gate, since it is trusted. The check opens by proving its own detector fires on a planted invocation.
842
919
  - **Always-loaded context is back under budget with its designed headroom.** The fixed per-run load had reached 60000 of a 60000 ceiling, so the next edit to either tracked file would have broken the gate. The cause was the pattern the gate exists to catch: `core/multi-agent/SKILL.md` carried a full transcription of the Phase 0 contract, restating all eight steps that `refs/phases/phase-0-init.md` already defines, while its own text said the transcription "does not replace the contract, read the ref". Its citations into that ref had also drifted - `L221` now points at the token pre-check, not the branch picker it claimed. Every rule was verified present in the ref before cutting (per-repo branch picker, shared branch name with per-repo collision, per-repo identity, serial per-repo worktrees). What stays in the always-loaded file is the part that enforces rather than describes: the blocking exit gate and the credential-inventory rule. 2422 bytes reclaimed, load now 57576 of 60000. The ceiling was not raised.
843
920
 
844
921
  ## [15.12.1] - 2026-08-22
845
922
 
846
923
  ### Fixed
847
- - **The pre-push gate no longer runs inside the push.** Git opens the connection to the remote before the hook fires, so the six-minute chain 15.12.0 wired in idled that connection until the server dropped it: the first two attempts to push the rename died on a broken pipe with every gate green. The hook is now verify-only and instant - it checks a stamp keyed to the exact tree (HEAD plus a hash of the working tree) and refuses the push when there is none. `npm run gate` produces the stamp. Refusing is the point: no stamp means nothing has verified this tree, and the reason this file exists is that nothing else will.
848
-
849
924
 
925
+ - **The pre-push gate no longer runs inside the push.** Git opens the connection to the remote before the hook fires, so the six-minute chain 15.12.0 wired in idled that connection until the server dropped it: the first two attempts to push the rename died on a broken pipe with every gate green. The hook is now verify-only and instant - it checks a stamp keyed to the exact tree (HEAD plus a hash of the working tree) and refuses the push when there is none. `npm run gate` produces the stamp. Refusing is the point: no stamp means nothing has verified this tree, and the reason this file exists is that nothing else will.
850
926
 
851
927
  ## [15.12.0] - 2026-08-22
852
928
 
853
929
  ### Changed
930
+
854
931
  - **The companion MCP server is now `@mmerterden/multi-agent-toolkit-mcp` (v3.0.0), registered as `multi-agent-toolkit`.** The old name read as internal scaffolding; the server is standalone (three runtime dependencies, 83 tools, no coupling to any orchestrator) and the name now says which family it belongs to. The MCP tool namespace moves with it: every `mcp__dev-toolkit__*` reference across 24 files is now `mcp__multi-agent-toolkit__*`, because a host derives the tool prefix from the server name and the old prefix would have addressed a server that no longer answers.
855
932
  - **Existing registrations are migrated, not duplicated.** A host keys its registration by name, so a rename does not upgrade an entry in place: without a migration an install ends up with both `dev-toolkit` (pointing at the now-frozen 2.26.0) and `multi-agent-toolkit`, two servers advertising the same 83 tools with the host choosing between them. The installer removes the legacy entry before adding the new one, and `uninstall` clears both names - "removes the pipeline's footprint" has to mean the footprint it ever had.
856
933
  - Declared minimums move to `v3.0.0+`; 2.x only ever existed under the old package name.
857
934
 
858
935
  ### Migration
936
+
859
937
  - `@mmerterden/dev-toolkit-mcp` stays published at 2.26.0 and is deprecated with a pointer. Nothing is unpublished, so a pinned consumer keeps resolving. The toolkit also keeps `dev-toolkit-mcp` as a second `bin` alias.
860
938
  - A hand-registered client that the pipeline installer does not manage needs `<cli> mcp remove dev-toolkit` once.
861
939
 
862
940
  ### Fixed
941
+
863
942
  - **`pre-push-check.sh` ran three of the eleven gate steps.** It was missing both linters, three of the four evals, `validate-prefs` and `scorecard`, and drove the smoke suites through the bare `for f in smoke-*.sh` loop that `run-smokes.mjs` was written to replace - the loop that cannot tell a passing suite from one that exited 0 having asserted nothing. It now runs `npm test`, defined once in `package.json` so the hook cannot drift behind the chain, plus eslint and the personal-data scan. Because a full run takes about six minutes and git has already opened the connection to the remote by the time the hook fires, the verdict is cached against the exact tree that produced it (HEAD plus a hash of the working tree): re-pushing an unchanged tree is instant, one edited byte re-runs everything. Without that, the first push after the fix died on a broken pipe with every gate green. Verified by planting a defect that only the previously-missing steps catch: the old subset reported 434 unit tests passing and a clean tree; the new gate blocked the push. The hook's header also claimed the repo has no CI, which stopped being true some time ago.
864
943
 
865
944
  ## [15.11.0] - 2026-08-21
866
945
 
867
946
  ### Changed
947
+
868
948
  - **Nothing the pipeline posts carries a decorative glyph any more.** The work summary rendered task marks and a phase strip in checkmarks and hourglasses, the PR review emitter prefixed every finding with a coloured dot and signed it with a robot, and the GitHub issue Progress table was three traffic lights. All of it now reads in words: `[done]` / `[pending]` for tasks, `done · active · failed · skipped · pending` for the phase strip, `done` / `partial` / `pending` for the issue flags, and the severity is the bold label it always was. `channels/jira.md` had banned decorative glyphs in a comment body for releases while the renderers filled it with them; the rule and the emitters now agree, and `smoke-channel-glyphs.sh` holds them to it. The pipeline's own terminal output is deliberately out of scope: a `✓` in a console summary is a UI affordance, not a document somebody reads later.
869
949
 
870
950
  ### Fixed
951
+
871
952
  - **Jira no longer manufactures smileys the pipeline never typed.** Comments are posted as Jira wiki markup, and Jira's renderer converts `:)` `:D` `;)` and, far more easily hit in technical prose, `(x)` `(/)` `(!)` `(i)` `(y)` `(+)` `(on)` `(*)` into emoticon images at render time. Nothing escaped them. `channels/jira.md` now carries the escaping table and orders it after the markdown conversion and before the POST. This was never something the humanizer could fix: the text is legitimate, and `(x)` in a comparison table renders correctly on GitHub and Confluence - only the Jira conversion knows the target parser.
872
953
 
873
954
  ### Tests
955
+
874
956
  - `smoke-channel-glyphs.sh` (8 checks) and a glyph assertion in `smoke-work-summary.sh`. Both detect with node's `\p{Extended_Pictographic}` rather than `grep -P`: the first draft of the gate used a PCRE class, reported a clean tree with a checkmark deliberately planted in an emitter, and passed. Under `bash` on a stock macOS `grep` is BSD grep, which has no `-P` at all - it exits 2 with "invalid option", the `2>/dev/null` swallowed the message, and an empty result read as "no glyphs". The gate now opens by proving its own detector fires on a planted glyph before it trusts any verdict, and both gates were re-checked by planting one and watching them go red.
875
957
 
876
958
  ## [15.10.1] - 2026-08-21
877
959
 
878
960
  ### Fixed
961
+
879
962
  - **`uninstall --all-data` now removes the per-repo memory root.** `~/.claude/memory/multi-agent/` holds the learnings ledger and triage corpus: durable knowledge written in prose about the repos it was collected from, which on a corporate checkout is concrete information about the codebase. It survived even the full-cleanup mode, so "removes everything but your tokens" was not true in the one place a user is most likely to mean it. The default run still keeps it, alongside settings and logs, and both halves of that promise are now tested.
880
963
  - **Offloaded tool payloads are reclaimable.** v15.10.0 added `offload-ref.sh` without a way to clean up after it. In worktree modes the payloads die with the worktree, but the `--local` modes write into the real checkout, and because `.multi-agent/refs/` is gitignored the files never appear in `git status` and nothing reclaims them. New `gc-refs.sh`, dispatched as a third phase of `/multi-agent:garbage-collect`, sweeps them with the same contract as the /tmp sweeper: dry-run until `--yes`, a root guard that refuses `/` and `$HOME`, a grace window so a sweep cannot pull a ref out from under a running phase, and node-id matching so a file the user put in that directory survives. `--all` sweeps every checkout under `$HOME`.
881
964
 
882
965
  ## [15.10.0] - 2026-08-21
883
966
 
884
967
  ### Added
968
+
885
969
  - **Per-repo memory now recalls by relevance, not by recency.** Both memory stores ranked by something that was not relevance: `triage-memory.mjs query` scored a raw token overlap with no IDF, so a word present in every row ("view", "test", "error") counted as much as the one word that identified the bug, and `learnings-ledger.mjs brief` did not rank at all - it replayed the newest 20 entries. Past a few hundred rows both degrade the same way: the injected context stops being about the task in hand. New `pipeline/scripts/_retrieval.mjs` owns the arithmetic for both (the way `_cost.mjs` owns pricing): field-weighted BM25, exponential recency, and Reciprocal Rank Fusion with per-ranking damping so recency separates comparably relevant rows without ever promoting an unrelated one. The tokenizer indexes identifiers whole and split (`KeychainStore` is reachable from "keychain") and folds regular plurals, which is what lets a query phrased as prose reach a row that names a symbol. Zero dependencies: no embedding service, no vector store, no second model call. `prefs.global.memoryRecall.strategy: "legacy"` restores the old behaviour in one flag.
886
970
  - **`learnings-ledger.mjs profile` and drill-down pointers.** Durable knowledge is now two blocks instead of one, because relevance and prompt-cache reuse pull against each other. `profile` emits a task-INDEPENDENT `<repo-profile>` ordered by confidence, then kind, then statement - byte-stable across runs, so it belongs at the head of a phase prompt where an unchanged prefix is served from cache and grows into an asset as a repo is learned. `brief --task` emits `<task-relevant-memory>`, ranked against the task, and goes after the task text where a per-run difference costs nothing. Every rendered line ends with an `L:<id>` pointer instead of spelling out its evidence; `learnings-ledger.mjs show --id` and `triage-memory.mjs show --id` return the full row. `multi-agent-refs/prompt-assembly.md` carries the placement contract.
887
971
  - **`offload-ref.sh`: bulky tool payloads become a pointer plus a tail.** Phase 3 already teed its build output to a file, but nothing decided how much of that file reached the model, so in practice all of it did. The filter parks the full text at `.multi-agent/refs/<node_id>.md` (content-addressed, gitignored) and prints a `[[ref:<node_id>]]` stub with the last lines - where a failing build's error already is. The evidence gate keeps reading the whole log, so what counts as a verified pass is unchanged; only what reaches the prompt shrinks. Wired into Phase 3 builds and Phase 4 test output, opt-in via `prefs.global.contextOffload.enabled`, and a pass-through when off, so the pipe is always safe to write.
888
972
  - **Recall precision is measured.** Phase 1 and Phase 4 emit `memory.injected` / `memory.hit`, and `learning-curve.mjs` trends the ratio alongside the existing KPIs. Without the pair, a ranking change that injects the right rows and one that injects noise are indistinguishable from outside: both return five hits and exit 0. `smoke-learnings-ledger.sh` holds the emitter and the consumer to the same event names, so renaming one side fails a gate instead of silently emptying the column.
889
973
 
890
974
  ### Security
975
+
891
976
  - **Entry text cannot forge the boundary of the block it is injected into.** `from-triage` builds ledger statements out of a finding's own words, which are model output, so a statement is untrusted content placed inside a structure the reader parses. Statements are now stored as one line (the schema always said "in one line"; nothing enforced it), and the block delimiters are neutralised at render time in both the ledger blocks and `<repo-memory>`. Angle brackets that are not delimiters are untouched, so `Array<String>` still reads as itself.
892
977
 
893
978
  ### Changed
979
+
894
980
  - `memory-load.sh` takes optional task text and ranks the MEMORY.md pointers against it. The previous `head -30` was a truncation, not a summary - the thirty-first pointer was invisible however precisely it matched, so the block got less useful the longer a repo was worked on. With no task text the index order is unchanged.
895
981
  - Phase 4 prior-art lookup and the rejected-preference brief are both ranked against the findings under triage; a finding whose wording matches nothing now returns nothing instead of the three newest rows.
896
982
  - `prefs.global.contextOffload.minLines` and `tailLines` are read by `offload-ref.sh`. They shipped in the schema and were honoured by nothing, so a user who set `tailLines: 50` got 20 with no way to tell; config that does nothing documents a control that is not there. An explicit flag still beats the pref, and a non-numeric or zero value falls back to the shipped default.
897
983
  - Phase-doc token budget: total 53350 -> 53950. The new prose was compressed twice first (1168 tokens down to 580) by keeping the reasoning in `prompt-assembly.md` and the `offload-ref.sh` header, both outside the budget. Phase 3 and Phase 4 are left amber on their warn lines on purpose - that is the signal that those two docs are next for structural compression rather than another bump.
898
984
 
899
985
  ### Tests
986
+
900
987
  - `test/retrieval.test.mjs` (20 unit assertions on the ranking primitives), `pipeline/scripts/eval-recall.mjs` + `pipeline/eval/recall-cases.json` (8 end-to-end recall cases against a deliberately noisy corpus, each reporting what the pre-ranking scorer would have returned), `smoke-offload-ref.sh`, and new coverage in `smoke-learnings-ledger.sh` and `smoke-per-repo-memory.sh`.
901
988
 
902
989
  ## [15.9.1] - 2026-08-20
903
990
 
904
991
  ### Changed
992
+
905
993
  - **Telemetry logs the GitHub account name, never the git `identity.name`.** The reporter resolved the run's user to `identity.username || identity.name`, and since prefs identities carried no `username`, it fell back to `identity.name` - which can be a full corporate title/brand string, landing verbatim in the usage store. It now resolves to the identity's GitHub username, then the active `gh` account login resolved live, then null; the git `identity.name` is no longer a fallback. Self-registration (`/multi-agent:update` step 5b) resolves the same way.
906
994
 
907
995
  ## [15.9.0] - 2026-08-20
908
996
 
909
997
  ### Fixed
998
+
910
999
  - **Telemetry emitter and run scripts: 21 verified defects from a refactor bug hunt.** The emitter now reads `usageLog.optOut` as a hard block, refuses non-TLS endpoints so the write-only token never travels in cleartext, resolves the credential store and version marker across all host trees (Copilot/Codex-only installs), prices each phase at its own model rate instead of opus-for-all, keeps hyphenated MCP server names, drops plugins mapped to `false`, and gates before touching the keychain. `phase-tracker.sh` uses a per-process temp file so the fail-open lock cannot publish a torn state, honors `$TRACKER_FILE` on init, and builds OTEL attrs with jq. `build-stack-plugins.mjs` aborts on a flag given without a value and reports content-only changes in `--dry-run`; `localize-commands.mjs` is Windows- and CRLF-safe; `account-resolver.sh`, `channels-multi-repo.sh` and `figma-mcp-refresh.sh` gaps closed. Covered by `test/usage-report.test.mjs`.
911
1000
 
912
1001
  ### Changed
1002
+
913
1003
  - **`purge` and `uninstall` are no longer model-auto-invocable** (`disable-model-invocation: true`): the two irreversible, full-data-loss commands run only on an explicit user request.
914
1004
  - **`humanizer` skill (v1.1.0):** a self-critique pass re-verifies the rewrite against the original (meaning preserved, nothing invented, patterns actually gone); trailing-participle and connective-padding patterns added.
915
1005
  - Stale version tables refreshed: `SECURITY.md` supported-versions moves to the 15.x line; `ROADMAP.md` "Current Release" becomes a rolling "Recent Releases".
916
1006
 
917
1007
  ### Companion
1008
+
918
1009
  - **`dev-toolkit-mcp` v2.26.0** (shipped alongside): CallTool boundary now validates arguments against each tool's inputSchema (lenient-but-safe), closing the command-injection class where a string reached a numeric shell interpolation; every caller-derived path is single-quoted; a new gate backstops it. Backward-compatible, 83 tools unchanged.
919
1010
 
920
1011
  ## [15.8.1] - 2026-08-19
921
1012
 
922
1013
  ### Fixed
1014
+
923
1015
  - **Self-registration follows the endpoint redirect**: the default reporting host answers `/register` with a 308 to the canonical domain; the update step's curl now passes `-L`, so the token actually arrives instead of the redirect page. Without it, v15.8.0's self-registration silently reported "registration unreachable" on every machine.
924
1016
 
925
1017
  ## [15.8.0] - 2026-08-19
926
1018
 
927
1019
  ### Added
1020
+
928
1021
  - **Operational reporting self-registers on update**: when no ingest token is onboarded, `/multi-agent:update` requests a per-machine write-only token from the reporting endpoint's `/register` route, stores it only in the OS credential store, and enables `usageLog`. Registration failing (offline, endpoint down, ingest disabled) leaves reporting off with a one-line notice - never an error. Hard opt-out via `usageLog.optOut: true` blocks both the registration and the auto-enable; setup Step 2.7 (admin-issued token) still takes precedence. The emitter's payload is unchanged: coarse run metadata only, never prompts, code, diffs, or paths.
929
1022
 
930
1023
  ## [15.7.0] - 2026-08-19
931
1024
 
932
1025
  ### Added
1026
+
933
1027
  - **Setup walks missing credentials one by one**: Step 3 gained a strict sequential onboarding loop - fixed service order, one Token Save Flow prompt per service (token -> author -> host), per-service skips that never abort the loop, and `figma` / `figma_mcp` as distinct passes. The discovery summary can no longer end setup.
934
1028
  - **Per-repo Jira project keys in setup**: the Jira pass closes with an optional multi-select mapping (repo -> project key) written to `prefs.projects[{slug}].jiraProjectKeys`; per-repo keys resolve before `global.defaultJiraKey` everywhere a key is needed. Re-open with `/multi-agent:setup jira-keys`.
935
1029
  - **Figma MCP mode question**: the `figma_mcp` pass opens with Remote / Local. Remote generates the `figu_` OAuth token for the current user (Dynamic Client Registration + PKCE; `prefs.global.figmaMcp.remoteGeneratorScript` drives the flow when set); Local wires the PAT-based `@anthropic-ai/figma-mcp` server (`localGeneratorScript` supported). Prefs schema: new `global.figmaMcp` block plus `supabase_access` / `supabase_service_role` keychainMapping keys.
936
1030
 
937
1031
  ### Changed
1032
+
938
1033
  - **Help spells out the --dev pipeline**: the Pipeline section now carries the dev chain (Phases 1-2 skipped, Review never skipped, Opus dev). Stale notes claiming Test or Review are skipped in --dev were corrected in both languages.
939
1034
  - **Setup Step 5 (Repo Discovery) split to a reference**: the full contract moved to `multi-agent-refs/setup/repo-discovery.md`; the SKILL keeps the opt-in prompt and a summary.
940
1035
 
941
1036
  ### Fixed
1037
+
942
1038
  - **build-stack-plugins.mjs dead import**: unused `APPLE_ONLY` import removed (superseded by the `STACK_ONLY` routing table).
943
1039
 
944
1040
  ## [15.6.1] - 2026-08-19
945
1041
 
946
1042
  ### Changed
1043
+
947
1044
  - **`/multi-agent:update` installs from npm, not from a git clone**: the registry is the single update channel - latest published release resolved with a direct registry read (never `npm view`'s cache), downloaded via `npm pack` with the registry pinned, installed with `install.js --all`, changes rendered from the packaged CHANGELOG, smokes run from the tarball. A pipeline repo clone is now purely a maintainer workspace (synced by `/multi-agent:sync`); consumers need no git access at all, so collaborator grants on the private repo can stay read-only or be dropped.
948
1045
 
949
1046
  ### Fixed
1047
+
950
1048
  - **`node --test` runs stop pinging the live dashboard**: the tracker-entities suite calls `phase-tracker.sh init` outside run-smokes' `MULTI_AGENT_SMOKE` guard, so every test run left a phantom "probe" row on the timeline. The suite now sets the flag itself, and `usage-report.mjs` refuses to emit under it as the last line of defense for any caller.
951
1049
  - **Usage report reads the tracker as it is actually written**: `tracker-state.json` stores `phases` as an array, but the reporter iterated it with `Object.entries`, so dashboard phase ids were array indexes - every phase after a skipped one was mislabeled (Commit id "6" reported as Faz 5). Failed-phase error tags carried the same wrong ids.
952
1050
  - **Run duration and terminal timestamp resolve from the tracker**: nothing stamps `state.finishedAt`, so every run reported `du=null` and a terminal emit was stamped with the reporter's wall clock (wrong for backfills). Both now fall back to the tracker's phase span (earliest start to latest completion).
@@ -955,28 +1053,34 @@ Found by reviewing v16.0.0 after it shipped. Four defects, three of them mine fr
955
1053
  ## [15.6.0] - 2026-08-18
956
1054
 
957
1055
  ### Fixed
1056
+
958
1057
  - **The heart goes platform-blind** (platform-dynamic audit, 15 findings): Phase 3's RED run, target resolution and build verification become `case "$STACK"` arms (gradle/pytest/npm beside xcodebuild, with the Gradle build-lock decision stated); Phase 4 Gate 1 matches its own stack-generic Gates 2-3; Phase 2 dispatches the platform's architect agent; Phase 5's device-check table gains the Android MCP tools; the figma-config schema stops claiming SwiftUI as universal; wiki scope gains a `platform` value.
959
1058
 
960
1059
  ### Added
1060
+
961
1061
  - **Per-stack routing hatches**: `STACK_ONLY` lists in `_stack-routing.mjs` so a stack-only skill (ktlint, hilt-di, ...) routes with one list entry instead of a regex widening - and `--check-routing` now FAILS on unrouted skills. `lint-skills` accepts `platform: backend|frontend`.
962
1062
 
963
1063
  ## [15.5.0] - 2026-08-18
964
1064
 
965
1065
  ### Added
1066
+
966
1067
  - **`sharedUtilities` census bucket** - the bind-don't-rebuild inventory: formatter families, validation rule types + per-module facades and design-token namespaces living outside screen slices, counted with samples; Phase 3 treats a non-empty bucket as binding.
967
1068
 
968
1069
  ### Fixed
1070
+
969
1071
  - **Smoke runs never touch the live dashboard** - run-smokes exports `MULTI_AGENT_SMOKE=1` and phase-tracker's live ping returns under it (test gates were leaving phantom "running" rows on the timeline).
970
1072
  - **Routing resolves the multi-agent-plugins toolkit first** - the public toolkit family is the pipeline's standard companion; a corporate variant is the fallback, not the default.
971
1073
 
972
1074
  ## [15.4.0] - 2026-08-18
973
1075
 
974
1076
  ### Added
1077
+
975
1078
  - **Per-project `testPolicy`** (`tdd` | `tests-after` | `none`): Phase 0 resolves it after project selection for every input type, asking once via a native picker when absent (autopilot defaults to `tdd` and notes it). Phase 3 gates the TDD cycle on it; Phase 5 skips the gap scan under `none` and runs only pre-existing targets. Prefs schema + template carry the field.
976
1079
  - **Ordered stack-toolkit routing candidates**: the ios/android rows resolve the corporate `-engineering-` variant before its public derivation, and a full probe miss records every candidate tried. The conventions census learns a `CrossDomains` root; screen-creation tasks bind the routed toolkit's `create-screen` workflow.
977
1080
  - **ios-module-structure 0.3.0 / ios-coding-standard 1.2.0** (authoring source): the settled architecture becomes the standard - UnitDialect slot, unit vocabulary roles, STRUCT-18..21 with checker support, UNIT-01..03 / SAFE-03 / MOD-08; the pre-conversion spellings are named residue.
978
1081
 
979
1082
  ### Fixed
1083
+
980
1084
  - The test-policy step resolves for Jira-ID and URL inputs, not only free-text; STRUCT-18 reports an unbound contracts root as disabled coverage instead of passing silently.
981
1085
 
982
1086
  ## [15.3.2] - 2026-08-17
@@ -1054,7 +1158,7 @@ places at once.
1054
1158
  delivered a different stack than the repo asked for. Repo settings are now read
1055
1159
  first, user-global second, `--platform` last.
1056
1160
  - **Copilot's skipped-stack prune could delete user-authored skills.** The prune
1057
- was derived from catalog *names*, so a user's own `~/.copilot/skills/<name>/`
1161
+ was derived from catalog _names_, so a user's own `~/.copilot/skills/<name>/`
1058
1162
  sharing a catalog name was removed with no prompt. It is now scoped to the
1059
1163
  previous delivery manifest (or, pre-manifest, to dirs byte-identical to the
1060
1164
  catalog) - the same contract `uninstall` already used.
@@ -1155,7 +1259,6 @@ places at once.
1155
1259
  were already at (or 193 bytes past) their limit; the always-on surface itself
1156
1260
  shrank with the local copy gone.
1157
1261
 
1158
-
1159
1262
  ### Added
1160
1263
 
1161
1264
  - **`/multi-agent:complaint-analysis`** — customer-complaint / customer-reported-error
@@ -1207,7 +1310,7 @@ places at once.
1207
1310
  `gzip` preserves the source file's mtime by default, so rotating log
1208
1311
  content already older than `KEEP_DAYS` produced a `.gz` that inherited
1209
1312
  that old mtime - and the very next line's retention sweep (`find -mtime
1210
- +$KEEP_DAYS -delete`) deleted it in the same run, before it was ever read.
1313
+ +$KEEP_DAYS -delete`) deleted it in the same run, before it was ever read.
1211
1314
  The archive's mtime is now reset to rotation time, so retention counts
1212
1315
  from when it was archived, not from the age of the content inside it.
1213
1316
  - The mode banner claimed pickers stay English when they didn't - language +
@@ -1228,26 +1331,26 @@ places at once.
1228
1331
 
1229
1332
  - **`testflight-validation` merged into a new `/multi-agent:store-ready`, and the
1230
1333
  Android side brought to parity.** The iOS archive audit existed twice with
1231
- identical arguments - `ios_app_store_audit({archive_path, rules: "all"})` in the
1334
+ identical arguments - `ios_app_store_audit({archive_path, rules: "all"})` in the
1232
1335
  command's Gate 1 and again inside `sim-test.md`'s `store-ready` scenario. Two
1233
1336
  copies of one call is how the second door grew with no Gate 2, no Gate 3 and no
1234
1337
  Android equivalent. There is now one implementation.
1235
1338
  Gates are symmetric per platform, because the failure modes are: a malformed
1236
1339
  package, a package the store itself refuses, and a policy a human enforces.
1237
1340
 
1238
- | Gate | iOS | Android |
1239
- |---|---|---|
1240
- | 1 Static | `ios_app_store_audit`, 18 rules | `android_apk_audit` + `google-play-compliance`, 21 rules |
1241
- | 2 Authoritative | `altool --validate-app` | `SKIPPED` |
1242
- | 3 Policy | `app-store-review` vs source | `play-store-review` vs source |
1341
+ | Gate | iOS | Android |
1342
+ | --------------- | ------------------------------- | -------------------------------------------------------- |
1343
+ | 1 Static | `ios_app_store_audit`, 18 rules | `android_apk_audit` + `google-play-compliance`, 21 rules |
1344
+ | 2 Authoritative | `altool --validate-app` | `SKIPPED` |
1345
+ | 3 Policy | `app-store-review` vs source | `play-store-review` vs source |
1243
1346
 
1244
1347
  Gate 2's asymmetry is reported as an asymmetry rather than smoothed over: Play's
1245
1348
  authoritative check exists only server-side, through a Publishing API draft edit,
1246
1349
  and the pipeline ships no client for it. An Android run therefore clears at most
1247
1350
  2 of 3 and never prints `passed`. `bundletool validate` is Gate 1 and is not
1248
1351
  promoted to fill the hole.
1249
- The running-app sweep became Step A rather than a separate errand - a build can
1250
- be structurally perfect and still ship an unreadable screen - and it degrades to
1352
+ The running-app sweep became Step A rather than a separate errand - a build can
1353
+ be structurally perfect and still ship an unreadable screen - and it degrades to
1251
1354
  `SKIPPED (no booted device)` instead of halting the package validation.
1252
1355
 
1253
1356
  Nothing was removed. `testflight-validation` is a thin iOS-pinned alias (removing
@@ -1278,7 +1381,7 @@ places at once.
1278
1381
  - **Copilot's `purge` could not see a single worktree.** It discovered them with
1279
1382
  `find {repo}/.worktrees/ -name "agent-state.json"`, and no worktree carries that
1280
1383
  file: Phase 0 writes state to `$HOME/.claude/logs/multi-agent/{project}/{task-id}/`.
1281
- Verified on a repo with two live task worktrees - the marker scan returns zero
1384
+ Verified on a repo with two live task worktrees - the marker scan returns zero
1282
1385
  while `purge.sh`'s own directory enumeration finds both. So the skill reported
1283
1386
  "nothing to purge" as success with real worktrees on disk. The Claude Code command
1284
1387
  had already been fixed by delegating to `purge.sh`; the Copilot mirror never
@@ -1301,7 +1404,7 @@ places at once.
1301
1404
  `.worktrees/PROJ-*/agent-log.md` and `agent-state.json`; Phase 0 has written both
1302
1405
  to `$HOME/.claude/logs/multi-agent/{project}/{task-id}/` since the layout moved.
1303
1406
  Against the current tree the scan matches zero files, so the op reported "logs
1304
- cleared, deleted 0" as success while every real log stayed on disk - worse than a
1407
+ cleared, deleted 0" as success while every real log stayed on disk - worse than a
1305
1408
  missing command, because it reads as done. There was also no `clear-logs` command
1306
1409
  directory, so the `/multi-agent:clear-logs` entry in the Copilot help pointed at a
1307
1410
  slash command that was never installed.
@@ -1315,7 +1418,7 @@ places at once.
1315
1418
  reaching either fell through to the general sweep and was reported as the scenario
1316
1419
  asked for. Neither can be built symmetrically today: biometric has `ios_biometric`
1317
1420
  and no Android counterpart, launch timing has `android_launch_time` and no iOS
1318
- counterpart, and the file auto-detects platform - so each would work on one
1421
+ counterpart, and the file auto-detects platform - so each would work on one
1319
1422
  platform and silently do nothing on the other. Both rows are withdrawn with the
1320
1423
  reason recorded in place, rather than shipped as half-features or left advertised.
1321
1424
  Advertised scenarios and implemented sections now match one to one.
@@ -1412,7 +1515,7 @@ own toolkit says apply, and clean up after itself.
1412
1515
  so `bugfix` / `feature` / `refactor` / `chore` had no skill dispatch at all:
1413
1516
  whichever skills the host surfaced by description match were the ones used, and
1414
1517
  nothing recorded or required any of them. That was the dev-side half of the gap
1415
- v14.0.0 closed on the review side - review asked "was this built to the rules it
1518
+ v14.0.0 closed on the review side - review asked "was this built to the rules it
1416
1519
  was supposed to follow" while nobody had chosen any rules.
1417
1520
 
1418
1521
  The routing table is NOT copied into this repo. A second copy would drift the
@@ -1422,7 +1525,7 @@ own toolkit says apply, and clean up after itself.
1422
1525
  6-row table). Routed skills land in `telemetry.skillCalls[]` with
1423
1526
  `routedBy: "<toolkit>:index@<version>"`, so Phase 4 conformance can hold the run to
1424
1527
  what its own toolkit chose. An absent or disabled toolkit is a recorded no-op, not
1425
- a halt - a backend repo has no toolkit and must still run.
1528
+ a halt - a backend repo has no toolkit and must still run.
1426
1529
  Contract: `refs/features/stack-skill-routing.md`.
1427
1530
 
1428
1531
  - **Phase 6 removes a task's worktree once its PR is open** (`worktree-finalize.sh`,
@@ -1430,7 +1533,7 @@ own toolkit says apply, and clean up after itself.
1430
1533
  salvages `agent-state.json`, `phase-tracker.json`, `triage-output.json`,
1431
1534
  `.pipeline/`, `.build.log`, `.test.log` and `.review-diff.txt` into the log dir
1432
1535
  first, because Phase 7's triage-memory ingest, the learnings-ledger distill,
1433
- `render-work-summary.sh`, `:resume`, `:status` and `:log` all read them - and the
1536
+ `render-work-summary.sh`, `:resume`, `:status` and `:log` all read them - and the
1434
1537
  first three are `[ -f ]`-guarded, so a removal without salvage would have degraded
1435
1538
  silently rather than failing.
1436
1539
 
@@ -1470,7 +1573,7 @@ own toolkit says apply, and clean up after itself.
1470
1573
  `f.file` from the diff-risk report, which declares and emits `path`, so each
1471
1574
  finding carried `file: undefined` and read "Test file 'undefined' shrinks". That
1472
1575
  is useless to the developer and rejected by `reviewer-output.schema.json`, which
1473
- requires `file` with `minLength: 1` - on the one gate that deliberately has no
1576
+ requires `file` with `minLength: 1` - on the one gate that deliberately has no
1474
1577
  opt-out. It stayed invisible because the smoke's own fixtures used `"file"`, the
1475
1578
  key the bug read: the fixture matched the bug instead of the schema. Fixtures
1476
1579
  corrected, and a new assertion checks the finding names a real path (verified by
@@ -1482,14 +1585,13 @@ own toolkit says apply, and clean up after itself.
1482
1585
  elsewhere in those files' prose. Added check 4b: every name in an inventory must be
1483
1586
  a command that exists in the tree (verified against a planted `ghostcmd`).
1484
1587
 
1485
-
1486
1588
  ## [14.0.0] - 2026-07-29
1487
1589
 
1488
1590
  The `--dev` family reviews its own work, and review now checks the code against the
1489
1591
  criteria it was supposed to be built to instead of general good taste.
1490
1592
 
1491
1593
  Major because a command was renamed: `/multi-agent:finish` is now `/multi-agent:ship`.
1492
- No alias is kept - `installCommands()` replaces the command tree wholesale, so a clean
1594
+ No alias is kept - `installCommands()` replaces the command tree wholesale, so a clean
1493
1595
  cut leaves nothing half-migrated.
1494
1596
 
1495
1597
  ### Added
@@ -1502,7 +1604,7 @@ cut leaves nothing half-migrated.
1502
1604
  without them, while review judges work that now exists and has no substitute. Accepted
1503
1605
  blocking findings return to Phase 3 under the existing 3-iteration hard kill; the
1504
1606
  autopilot variants auto-fix without prompting and halt on the existing rework-storm
1505
- circuit breaker rather than committing. No new machinery for either - both were already
1607
+ circuit breaker rather than committing. No new machinery for either - both were already
1506
1608
  built and became reachable the moment Phase 4 entered the phase set.
1507
1609
  - **Phase 4 Step 1.78, criteria resolution** (`skill-conformance.mjs`, zero LLM). Resolves
1508
1610
  which rule registries apply to this diff, scoped to its languages and paths, and writes
@@ -1517,7 +1619,7 @@ cut leaves nothing half-migrated.
1517
1619
  - **Every registry declares its own `scope`** (`languages`, `paths`, `excludePaths`,
1518
1620
  `notCovered`), and per-rule `scope` narrows it further. Measured before this landed: only
1519
1621
  2 of the iOS registry's 99 rules carried any applicability field, and the rest wrote their
1520
- scope as English prose inside `mechanism` - so an Objective-C or UIKit diff would have
1622
+ scope as English prose inside `mechanism` - so an Objective-C or UIKit diff would have
1521
1623
  collected all 99 SwiftUI-shaped rules, manufacturing findings and burying the real ones.
1522
1624
  - `references/rules.yml` for `apple-archive-compliance` (18 rules) and
1523
1625
  `google-play-compliance` (21), converted from their existing SKILL.md tables with IDs and
@@ -1526,7 +1628,7 @@ cut leaves nothing half-migrated.
1526
1628
  - **Exception-marker audit**: expired, reason-less, expiry-less, or unknown-ID
1527
1629
  `standard:exception(...)` markers become rule-ID-bearing findings. The marker template is
1528
1630
  read from the registry, never hardcoded, so a registry with different comment syntax works.
1529
- - `state.telemetry.skillCalls[]` - Phase 3 records each skill, plugin skill and guide it
1631
+ - `state.telemetry.skillCalls[]` - Phase 3 records each skill, plugin skill and guide it
1530
1632
  consulted, with the files it applied them to.
1531
1633
  - `prefs.global.skillConformance.blockOnCoverageGap` (default **false**) and
1532
1634
  `prefs.global.ship.autoFix`, the latter referenced by the tail command's spec since it
@@ -1546,13 +1648,13 @@ cut leaves nothing half-migrated.
1546
1648
  - **Reviewers cite rule IDs.** `reviewer-output.schema.json` 1.0.0 -> 1.1.0 adds `ruleId`,
1547
1649
  `criteriaSource` and the per-rule `conformance[]` array; `triage-output.schema.json`
1548
1650
  3.2.0 -> 3.3.0 carries both through triage. `code-reviewer.md` gains a `${CRITERIA}`
1549
- injection slot - the phase doc had claimed for some time that "skills are injected into
1651
+ injection slot - the phase doc had claimed for some time that "skills are injected into
1550
1652
  reviewer prompt context" while the agent definition had no slot for them, which is exactly
1551
1653
  why review could not cite a rule.
1552
1654
  - `validate-reviewer.mjs` enforces the checklist with `--criteria`: a selected ID with no
1553
1655
  verdict, a verdict for an ID that was never selected, a `conformant` row with no file
1554
1656
  evidence, and a `violated` row with no matching finding all fail. Without this the field
1555
- would be decoration - the validator is hand-written and does not apply
1657
+ would be decoration - the validator is hand-written and does not apply
1556
1658
  `additionalProperties`, so any array at all would have passed.
1557
1659
  - `${CRITERIA}` lives in the shared cacheable prefix, identical for every reviewer.
1558
1660
  Subsetting it per reviewer would invalidate the prefix for the whole panel and re-bill the
@@ -1574,7 +1676,7 @@ cut leaves nothing half-migrated.
1574
1676
 
1575
1677
  - **`ios-coding-standard` in the repo was a version behind the installed copy** (95 rules
1576
1678
  v1.0.0 vs 99 rules v1.1.0). Because `installSkills()` copies the repo over the
1577
- destination, the next `/multi-agent:update` would have downgraded a user's registry -
1679
+ destination, the next `/multi-agent:update` would have downgraded a user's registry -
1578
1680
  harmless while nothing read it, a correctness regression the moment Phase 4 blocks on rule
1579
1681
  IDs. Resynced to 99 rules.
1580
1682
  - `modules/*.yml` overlays and `references/EXAMPLES.md` are deliberately NOT shipped: they
@@ -1600,8 +1702,8 @@ gates are named per item.
1600
1702
 
1601
1703
  - **The Tier 2 Figma PAT could not be found on any migrated install.** `migrate-prefs.mjs`
1602
1704
  consolidated `keychainMapping.figma_pat` into `.figma` and deleted the old key, but the
1603
- setup wizard kept *writing* `figma_pat` (so the next migration deleted the mapping it
1604
- had just created), both Tier 2 fetchers kept *reading* it, and the failure text told the
1705
+ setup wizard kept _writing_ `figma_pat` (so the next migration deleted the mapping it
1706
+ had just created), both Tier 2 fetchers kept _reading_ it, and the failure text told the
1605
1707
  user to map the one key guaranteed not to survive. Tier 2 reported `missing-token` while
1606
1708
  a valid PAT sat under the new name. Claude Code's MCP tier masked it; Copilot and Codex,
1607
1709
  which serve no Figma MCP tools at all, fell straight to Tier 3. The lookup now lives once
@@ -1660,7 +1762,7 @@ gates are named per item.
1660
1762
  written. Copilot now rewrites the trees it owns, and installs `rules/` as well, closing
1661
1763
  the same silent-nothing fallback Codex's installer already documented.
1662
1764
  - **`phase-tracker.sh` never reclaimed a stale lock on Linux.** It tried `stat -f %m`
1663
- before `stat -c %Y`, and on GNU coreutils `stat -f` is a *valid* flag (`--file-system`,
1765
+ before `stat -c %Y`, and on GNU coreutils `stat -f` is a _valid_ flag (`--file-system`,
1664
1766
  where `%m` is the mount point): it succeeds, returns something like `/`, the `||` never
1665
1767
  fires, and the age arithmetic runs on a path. Every tracker call then spun the full ~5s
1666
1768
  bound and fell open with a warning. Every other `stat` call site in the repo already had
@@ -1675,7 +1777,7 @@ gates are named per item.
1675
1777
  the previous generation. Now `claude-opus-5` and `claude-sonnet-5`, with the rung/ID split
1676
1778
  stated in the fallback contract. Gate: a generation guard in `smoke-model-fallback.sh`.
1677
1779
  - **`rules/pipeline-output-formatting.md` did not exist.** Two shipped skills named it as
1678
- the PR-body contract to follow. The install-layout smoke had been *reporting* paths that
1780
+ the PR-body contract to follow. The install-layout smoke had been _reporting_ paths that
1679
1781
  exist on no host as a note rather than failing on them, which is how it survived; that
1680
1782
  claim is now strict.
1681
1783
  - **A pre-migration preferences file failed validation before the migration could fix it.**
@@ -1732,7 +1834,7 @@ gates are named per item.
1732
1834
 
1733
1835
  - **Every fetcher died on a Copilot-only or Codex-only install.** Eleven runtime scripts
1734
1836
  loaded `credential-store-resolver.sh` as `. "$HOME/.claude/lib/..." || . <next> || {
1735
- error }`, all of them under `set -e`. Sourcing a file that does not exist aborts the
1837
+ error }`, all of them under `set -e`. Sourcing a file that does not exist aborts the
1736
1838
  shell outright - `||` included - so on a host without `~/.claude/lib` the chain reached
1737
1839
  neither its later candidates nor its error branch: bare exit 1, no message. Reordering
1738
1840
  does not help, because whichever candidate is absent aborts at that point. All eleven
@@ -1823,7 +1925,7 @@ gates are named per item.
1823
1925
  `--dev` skips the LLM phases, not Phase 0's pickers.
1824
1926
  - **An unreachable external source is announced instead of absorbed.** Exit code `3`
1825
1927
  used to mean "mark it failed and continue", so an expired token and a VPN-off remote
1826
- both reached the analysis phase as *no data*, indistinguishable from a ticket that
1928
+ both reached the analysis phase as _no data_, indistinguishable from a ticket that
1827
1929
  referenced nothing. The run then planned from a partial picture and reported success.
1828
1930
  `external-context-injection.md` now classifies the stderr and surfaces a decision -
1829
1931
  refresh the credential, connect the VPN and retry, supply a current URL, or continue
@@ -1845,7 +1947,7 @@ gates are named per item.
1845
1947
  `api.figma.com` returned 403 for a healthy token and would have sent the user to
1846
1948
  regenerate something that worked. It is now reported as not-probeable, with liveness
1847
1949
  left to `figma-mcp-refresh.sh`, which owns the grant.
1848
- - A failed `curl` wrote `000` through `write-out` *and* triggered the `|| echo "000"`
1950
+ - A failed `curl` wrote `000` through `write-out` _and_ triggered the `|| echo "000"`
1849
1951
  fallback, producing `000000` and a bogus verdict for what was simply a closed VPN.
1850
1952
  Any non-three-digit status now reads as `probe-error` rather than being dressed up as
1851
1953
  a service verdict.
@@ -1882,7 +1984,7 @@ gates are named per item.
1882
1984
  ### Fixed
1883
1985
 
1884
1986
  - **`build-stack-plugins.mjs` never propagated content edits.** It copied a skill only
1885
- when the skill *set* changed, so editing a routed skill in `shared/external` reached
1987
+ when the skill _set_ changed, so editing a routed skill in `shared/external` reached
1886
1988
  no plugin while the generator reported "all plugins up to date" - which read as
1887
1989
  confirmation. This falsified the single-authoring-source guarantee the whole
1888
1990
  `shared/external` design rests on. Found by converting banned punctuation in
@@ -1914,7 +2016,7 @@ The iOS coding standard reaches all three hosts, and works outside the pipeline.
1914
2016
  The 95-rule registry lived as a pipeline-local routine. Two consequences, both
1915
2017
  measured: only Claude Code could reach it, because `local-only` commands are
1916
2018
  deliberately never synced (verified absent on Copilot CLI and Codex CLI), and it
1917
- applied only when the routine was invoked explicitly - nothing pulled the rules in
2019
+ applied only when the routine was invoked explicitly - nothing pulled the rules in
1918
2020
  while Swift was being written. The plugin's own `reference/code-style` skill is 154
1919
2021
  lines of prose with zero rule IDs, so the registry was not reachable that way either.
1920
2022
 
@@ -1958,7 +2060,6 @@ lines of prose with zero rule IDs, so the registry was not reachable that way ei
1958
2060
  predates this change and `tools/validate.py` warns on it; routing `knowledge/` behind
1959
2061
  the plugin's `index` skill is the fix and is separate work.
1960
2062
 
1961
-
1962
2063
  ## [13.1.0] - 2026-07-27
1963
2064
 
1964
2065
  Six gates, from one branch that spent half its commits on rework.
@@ -1968,7 +2069,7 @@ chain, measured from the run's own artefacts: Phase 0 reported `completed` havin
1968
2069
  written only `tracker-state.json`, so `agent-state.json` and its `taskType` never
1969
2070
  existed, so Phase 3's component dispatch could not fire. The stack plugin already
1970
2071
  ships `create-screen`, `figma-validate` (7 criteria including design-token compliance
1971
- and Code Connect strategy) and `figma-review` (14-item checklist) - none of them ran.
2072
+ and Code Connect strategy) and `figma-review` (14-item checklist) - none of them ran.
1972
2073
  Padding came out 16 where the frame said `Spacing/12`. Three of six commits were fixes,
1973
2074
  the last a full sheet rebuild.
1974
2075
 
@@ -1985,7 +2086,7 @@ invokes a skill that was already written but never called.
1985
2086
  gate is testable without reproducing a run.
1986
2087
  - **Phase 4 Step 2.8, visual conformance gate.** Runs `figma-review` and
1987
2088
  `/multi-agent:design-check` for component/screen work, with the coverage gate, and
1988
- asserts Code Connect was **published** rather than merely written - a
2089
+ asserts Code Connect was **published** rather than merely written - a
1989
2090
  `*.figma.swift` on disk with "Not published" in Figma is a binding that exists for
1990
2091
  nobody. `design-check` had been a command with no phase invoking it, so the only
1991
2092
  thing standing between a build and visual drift was the user opening the app.
@@ -2000,14 +2101,14 @@ invokes a skill that was already written but never called.
2000
2101
 
2001
2102
  - **Component dispatch halts instead of degrading.** The contract used to send an
2002
2103
  incomplete-state component task down the generic TDD path while the next sentence
2003
- said "never silently skip the Figma work" - taking the generic path *is* skipping
2104
+ said "never silently skip the Figma work" - taking the generic path _is_ skipping
2004
2105
  it. That wording authorised the exact failure above.
2005
2106
  - **Dispatch routes on scope, not just platform.** A screen and a component are
2006
2107
  different jobs and the plugin ships a skill for each; routing a screen to the
2007
2108
  component skill is why one run produced entities and a mapper but left the screen
2008
2109
  half-wired. `figma-validate` now runs before the create skill.
2009
2110
  - **Phase 1 captures spacing by token name, per atom.** Phase 3 is forbidden from
2010
- calling Figma, so a pixel number - or a missing entry - is unrecoverable later. A
2111
+ calling Figma, so a pixel number - or a missing entry - is unrecoverable later. A
2011
2112
  UI frame with no spacing entries is a capture failure, not an empty frame.
2012
2113
  - **Phase 3 records that generated trees are not editable.** A mock fixture went into
2013
2114
  the generated tree; the fix moved it to the custom tree and registered the scenario
@@ -2017,7 +2118,6 @@ invokes a skill that was already written but never called.
2017
2118
  Planning by design, so there is no phase that turns a document into a plan. The doc
2018
2119
  becomes context for one pass and work lands in whatever order it was read.
2019
2120
 
2020
-
2021
2121
  ## [13.0.0] - 2026-07-27
2022
2122
 
2023
2123
  > **Why major and not minor.** The additions here are additive, but two defaults
@@ -2027,7 +2127,6 @@ invokes a skill that was already written but never called.
2027
2127
  > comparing Codex on skill directories would read a correct install as drift).
2028
2128
  > Per the versioning policy that is a changed default, not a new option.
2029
2129
 
2030
-
2031
2130
  Codex CLI becomes a third supported host, and a new pre-submission validation command.
2032
2131
 
2033
2132
  ### Added
@@ -2038,19 +2137,19 @@ Codex CLI becomes a third supported host, and a new pre-submission validation co
2038
2137
  with per-agent model and reasoning effort, hooks, MCP and a plugin marketplace,
2039
2138
  so it earns the same treatment as the other two rather than an adapter.
2040
2139
  `install/codex.mjs` writes `~/.codex/{skills/multi-agent,multi-agent-refs,
2041
- agents/*.toml,prompts/multi-agent.md,scripts,lib,schemas,rules}` plus a managed
2140
+ agents/*.toml,prompts/multi-agent.md,scripts,lib,schemas,rules}` plus a managed
2042
2141
  span in `~/.codex/AGENTS.md`, and registers the dev-toolkit MCP server through
2043
2142
  `codex mcp add` rather than hand-merging TOML that Codex owns.
2044
2143
  - **`/multi-agent:testflight-validation`** (command 43). Three gates, each seeing
2045
2144
  what the others structurally cannot: the static 18-rule archive audit, Apple's
2046
2145
  own `altool --validate-app`, and a Review-Guidelines check against repo evidence.
2047
- ITMS codes are mapped to the rule each implies. It validates only - never
2048
- `--upload-app` - so a validation run cannot ship a build by accident.
2146
+ ITMS codes are mapped to the rule each implies. It validates only - never
2147
+ `--upload-app` - so a validation run cannot ship a build by accident.
2049
2148
  - **App Store Connect credentials in `/multi-agent:setup`**, inside the Step 1
2050
2149
  discovery / Step 2 mapping / Step 3b onboarding flow alongside Jira and
2051
2150
  Bitbucket, not as a late add-on: a user who already has one in their keychain
2052
2151
  gets it mapped automatically. Tier 1 is an API key, tier 2 an Apple ID plus an
2053
- app-specific password - which matters because creating an API key needs an
2152
+ app-specific password - which matters because creating an API key needs an
2054
2153
  Admin or App Manager role many developers on a corporate team do not have.
2055
2154
 
2056
2155
  ### Changed
@@ -2066,8 +2165,8 @@ Codex CLI becomes a third supported host, and a new pre-submission validation co
2066
2165
  gpt-5.6 @ medium, triage at max) with two measured constraints written into the
2067
2166
  contract: a `spawn_agent` that sets `model` without `fork_turns: "none"`
2068
2167
  **silently inherits the parent model**, collapsing the panel onto one
2069
- perspective; and 4 concurrency slots *including the orchestrator* make three
2070
- reviewers the ceiling. Single-vendor caveat recorded - consensus among three
2168
+ perspective; and 4 concurrency slots _including the orchestrator_ make three
2169
+ reviewers the ceiling. Single-vendor caveat recorded - consensus among three
2071
2170
  OpenAI models is weaker evidence than the same consensus on a two-vendor host.
2072
2171
  - `tracker-contract.md` gains the `codex` visual channel: the native `update_plan`
2073
2172
  tool, with its no-parallel-call and no-plan-mode caveats.
@@ -2087,7 +2186,7 @@ Codex CLI becomes a third supported host, and a new pre-submission validation co
2087
2186
  - **The Phase 0 branch-collision probe read a failed probe as "no collision".**
2088
2187
  With `2>/dev/null` and an empty-output test, an auth or network failure was
2089
2188
  indistinguishable from "the ref does not exist", so the run created a branch
2090
- that already existed on the remote - surfacing as a rejected push at Phase 6,
2189
+ that already existed on the remote - surfacing as a rejected push at Phase 6,
2091
2190
  far from its cause. Exit codes are now distinguished (0 exists, 2 free, anything
2092
2191
  else unknown-and-recorded).
2093
2192
  - **`/multi-agent:update` deleted the Codex prompt on every run**, pruning
@@ -2100,14 +2199,14 @@ Codex CLI becomes a third supported host, and a new pre-submission validation co
2100
2199
  (added in v12.11.0) holds the banned characters as its own assertion list and
2101
2200
  was never allowlisted.
2102
2201
  - **Half-English pickers on Turkish runs: six shipped files contradicted the
2103
- canonical language matrix.** `rules.md` is unambiguous - `AskUserQuestion`
2202
+ canonical language matrix.** `rules.md` is unambiguous - `AskUserQuestion`
2104
2203
  `question` and `options[].description` render in `outputLanguage`, only `label`,
2105
2204
  `header` and host chrome are pinned to English. But `/multi-agent:setup` Step 0
2106
2205
  claimed `promptLanguage` governs "interactive pickers and prompts ... Picker UI is
2107
2206
  always English", `/multi-agent:language` claimed "confirmation prompts ... are
2108
2207
  authored in English. Only the assistant's free-form replies follow
2109
2208
  `outputLanguage`", and four more files said variations of the same. Those are the
2110
- two commands a user goes to *configure* this, so the model followed whichever
2209
+ two commands a user goes to _configure_ this, so the model followed whichever
2111
2210
  canonical-looking doc it read first and gate questions came out English. All six
2112
2211
  corrected to describe `promptLanguage`'s real scope: the button and chip chrome,
2113
2212
  never the question a user reads.
@@ -2117,7 +2216,7 @@ Codex CLI becomes a third supported host, and a new pre-submission validation co
2117
2216
  - New `smoke-language-matrix.sh`: asserts `rules.md` still pins the per-field
2118
2217
  matrix, then greps every shipped command / skill / ref for the seven phrasings
2119
2218
  that actually shipped and contradicted it. Two authoritative docs giving opposite
2120
- answers is a spec with two answers, not a wording nit - the gate found a sixth
2219
+ answers is a spec with two answers, not a wording nit - the gate found a sixth
2121
2220
  violation (`_input-parser.md`) that the manual sweep had missed.
2122
2221
 
2123
2222
  ### Gates
@@ -2129,7 +2228,7 @@ Codex CLI becomes a third supported host, and a new pre-submission validation co
2129
2228
  - `smoke-install-layout.sh` extended to three targets, including a check that every
2130
2229
  concrete `$HOME/.codex` reference **resolves on disk**. The earlier "no `.claude`
2131
2230
  references remain" check was blind to a wrongly-rewritten path, which is how
2132
- `commands/multi-agent.md` became `multi-agent-refs/commands.md` - well-formed,
2231
+ `commands/multi-agent.md` became `multi-agent-refs/commands.md` - well-formed,
2133
2232
  pointing at nothing. A miss is classified by whether the Claude counterpart
2134
2233
  exists, so broken-by-rewrite fails and broken-upstream is only reported.
2135
2234
  - New `test/codex-install.test.mjs` (24 assertions) locks the path-rewrite map,
@@ -2162,11 +2261,11 @@ bug that produced it.
2162
2261
 
2163
2262
  **All three encodings**, because a title can arrive in any of them:
2164
2263
 
2165
- | | ampersand | less-than | em-dash |
2166
- |---|---|---|---|
2167
- | named | `&amp;` | `&lt;` | `&mdash;` |
2168
- | decimal | `&#38;` | `&#60;` | `&#8212;` |
2169
- | hex | `&#x26;` | `&#x3C;` | `&#x2014;` |
2264
+ | | ampersand | less-than | em-dash |
2265
+ | ------- | --------- | --------- | ---------- |
2266
+ | named | `&amp;` | `&lt;` | `&mdash;` |
2267
+ | decimal | `&#38;` | `&#60;` | `&#8212;` |
2268
+ | hex | `&#x26;` | `&#x3C;` | `&#x2014;` |
2170
2269
 
2171
2270
  The first version of this fix handled only the named column, which is the same bug
2172
2271
  fixed for one spelling out of three.
@@ -2384,7 +2483,7 @@ entry, so the count would have under-reported by 100+ files.
2384
2483
  ### Four gates that were tested, green, and never ran
2385
2484
 
2386
2485
  Every gate in this repo checks its own behaviour. Nothing checked that a gate is
2387
- *reachable*. Four were not - each with a passing unit test and a passing smoke,
2486
+ _reachable_. Four were not - each with a passing unit test and a passing smoke,
2388
2487
  and no reference from `pipeline/commands/`, `multi-agent-refs/`, `agents/`,
2389
2488
  `skills/` or `install/`:
2390
2489
 
@@ -2545,7 +2644,7 @@ while fixing them.
2545
2644
  Two threads. The design-check command gains a scenario inventory, a coverage
2546
2645
  gate and MCP-currency gating, and the refactor/sync pair learns to research,
2547
2646
  audit and ship the companion dev-toolkit MCP server. Alongside that, a defect
2548
- sweep found eight gates and features that exited 0 while doing nothing - six
2647
+ sweep found eight gates and features that exited 0 while doing nothing - six
2549
2648
  of them guarded by a smoke test whose fixture had the wrong shape, so the suite
2550
2649
  stayed green the whole time.
2551
2650
 
@@ -2577,14 +2676,14 @@ Requires `@mmerterden/dev-toolkit-mcp` >= v2.9.0 for the App Store audit path.
2577
2676
  `smoke-personal-data.sh --root`, keeping one pattern list.
2578
2677
  - **shellcheck moves from `error` to `warning` severity.** `error` returns zero
2579
2678
  findings across all 175 scripts, so the gate could not catch this repo's
2580
- actual defect class - SC2046 is a warning. Noisy codes are excluded with a
2679
+ actual defect class - SC2046 is a warning. Noisy codes are excluded with a
2581
2680
  per-code rationale. Four real findings surfaced and are fixed, including two
2582
2681
  unguarded `cd` calls in `smoke-shadow-git.sh`, one preceding a relative
2583
2682
  `rm -rf`.
2584
2683
  - **`credential-store.sh` resolves logical keys through
2585
2684
  `prefs.global.keychainMapping`.** `get github` searched the backend for a
2586
2685
  credential literally named "github" and returned empty with exit 1 -
2587
- indistinguishable from "no such credential" - because the entry is named by
2686
+ indistinguishable from "no such credential" - because the entry is named by
2588
2687
  the mapping. Applied in `get`/`set`/`delete`, falling back to the logical key
2589
2688
  when no mapping exists.
2590
2689
 
@@ -2794,6 +2893,7 @@ all project specifics live in per-project `figma-config` / prefs.
2794
2893
  ## [12.2.0] - 2026-07-21
2795
2894
 
2796
2895
  ### Added
2896
+
2797
2897
  - **Graylog log-fetch integration** — when a task's ticket/issue carries a transaction id (`trx`/`trxId`/`transaction id`) and/or a conversation id (`conversationId`/`convId`/`X-conversationId`), the pipeline fetches the matching Graylog log messages and injects them as advisory diagnostic context into Phase 1 analysis. Mirrors the Crashlytics analysis-context adapter (advisory only, no Phase-4 gate).
2798
2898
  - New `lib/fetch-graylog.sh` adapter (mirrors the Fortify hosted-service shape): host from `prefs.global.hosts.graylog` (+ `GRAYLOG_HOST_OVERRIDE`), token via `prefs.global.keychainMapping.graylog` → `${USER}_Graylog_Access_Token` through the credential-store resolver. Graylog PAT auth is HTTP Basic `<token>:token` delivered only via a `curl -K` process-substitution config (never on argv). Universal/relative search by full-text OR of the ids, range + limit configurable via env.
2799
2899
  - **Non-blocking by design:** any network/VPN failure degrades to an empty normalized result and exit 0 — a log fetch never blocks a run. Exit codes: 0 ok/degraded, 2 missing-token, 3 genuine auth rejection, 4 usage, 6 host-not-configured.
@@ -2802,6 +2902,7 @@ all project specifics live in per-project `figma-config` / prefs.
2802
2902
  ## [12.1.1] - 2026-07-21
2803
2903
 
2804
2904
  ### Changed
2905
+
2805
2906
  - **Channel outputs no longer carry decorative/emotive emoji or smileys.** Jira/issue comments, Confluence & Wiki pages, and PR bodies are plain technical prose. Added an explicit no-emoji hard-rule to each channel template (`multi-agent-refs/channels/{jira,issue-comment,confluence,wiki}.md`) and an emoji-stripping pattern to the humanizer (`shared/external/humanizer`). Functional status/severity marks a fixed template defines (`✅/⏳` phase ticks, `🔴/🟡` severity labels) are unaffected.
2806
2907
 
2807
2908
  ---
@@ -2809,20 +2910,25 @@ all project specifics live in per-project `figma-config` / prefs.
2809
2910
  ## [12.1.0] - 2026-07-21
2810
2911
 
2811
2912
  ### Security
2913
+
2812
2914
  - **`agent-guard.sh` is now wired by the default installer** (was opt-in via the setup template). A plain `install` now OS-enforces the two load-bearing git gates on every `Bash` tool call: no AI/assistant attribution in commit messages, and no force-push to a protected branch (`main`/`master`/`develop`).
2813
2915
  - **Force-push protection now fails CLOSED.** A detected force-push that can't be tokenized, or a bare force-push while the current branch can't be confirmed non-protected, is blocked rather than allowed. (The general guard stays fail-open; only the data-loss path is fail-closed.)
2814
2916
  - **Multi-repo clone hardening.** `cmd_prepare` disables git's `ext::`/`fd::` remote helpers (`protocol.ext.allow=never`, `protocol.fd.allow=never`) and ends options with `--`, closing an arbitrary-command-execution / option-injection vector on repo URLs sourced from `.gitmodules` suggestions. Local/file/https/ssh clones are unaffected.
2815
2917
 
2816
2918
  ### Changed
2919
+
2817
2920
  - **Node floor raised to 20** (`.nvmrc`, `engines.node` → `>=20.0.0`). Node 18 was EOL and untested by CI; the matrix already covers 20/22.
2818
2921
 
2819
2922
  ### Refactored
2923
+
2820
2924
  - **Token-cost pricing is now a single source.** The per-Mtok cost formula, previously copy-pasted across ~6 bash/mjs scripts, lives in `cost-lib.sh` (bash) + `_cost.mjs` (Node), both reading the shared `cost-table.json`. Zero runtime dependencies preserved.
2821
2925
 
2822
2926
  ### Added
2927
+
2823
2928
  - **`smoke-source-parity.sh`** — guards feature-set drift between the two source trees (`commands/multi-agent/<name>` vs `skills/shared/core/multi-agent-<name>`); fails if a feature exists in one tree but not the other.
2824
2929
 
2825
2930
  ### Docs
2931
+
2826
2932
  - Refreshed post-v12 doc drift: `SECURITY.md` + `ROADMAP.md` support/version markers → 12.x; `docs/features.md` smoke-suite count `10` → `100+`; ADR index now lists 0008 and 0008 carries a v10.7.0 amendment (the `_adapters.mjs` module was removed); `CONTRIBUTING.md` coverage claim clarified (local-only) and the stale phase-docs path corrected to `pipeline/multi-agent-refs/phases/`.
2827
2933
 
2828
2934
  ---
@@ -2942,7 +3048,7 @@ research, bug hunt, category scoring, upstream-drift check).
2942
3048
  - **False-success family.** `multi-repo-pipeline.sh` no longer prints
2943
3049
  "committed/pushed" on failure; `repo-cache.sh` keeps the previous cache when a
2944
3050
  refresh fails instead of serving an empty file for the TTL; `review-watch.sh
2945
- --watch` survives transient gh failures and its cursor can no longer move
3051
+ --watch` survives transient gh failures and its cursor can no longer move
2946
3052
  backwards.
2947
3053
  - **`issue-fetcher.sh`** classifies bare `#316` / `316` correctly (repo picker
2948
3054
  path instead of a malformed `//issues/` URL).
@@ -2952,7 +3058,7 @@ research, bug hunt, category scoring, upstream-drift check).
2952
3058
  code, ballooning snapshots with DerivedData), and `restore --files` removes
2953
3059
  files created after the snapshot instead of keeping half the change.
2954
3060
  - **macOS bash 3.2 portability**: `${URL,,}` in `fetch-swagger.sh`, `date -r
2955
- <file>` in `audit-log-rotate.sh` (which also rotates atomically now).
3061
+ <file>` in `audit-log-rotate.sh` (which also rotates atomically now).
2956
3062
  - **Glob translation** in `triage-memory.mjs` / `test-gap-scan.mjs`: `**` no
2957
3063
  longer breaks after the `*` pass (nested-path filters match again).
2958
3064
  - **`migrate-prefs.mjs`** errors on unknown flags (a `--dry-rnu` typo used to
@@ -3048,7 +3154,7 @@ Self-audit patch (found by running `/multi-agent:refactor` on the pipeline itsel
3048
3154
  lists it in the PRESERVED summary.
3049
3155
  - **Fix (gate regression): `lint-skills` scoped to `pipeline/skills`.** The
3050
3156
  v11.3.0 command-layout migration made command files match `find pipeline -name
3051
- SKILL.md`; slash commands have no `name:` frontmatter, so the skill linter
3157
+ SKILL.md`; slash commands have no `name:` frontmatter, so the skill linter
3052
3158
  started erroring on all 37 of them (latent because CI is billing-paused and
3053
3159
  releases were published manually). The linter now only checks actual skills.
3054
3160
  - **Fix: `.skills-index.json` is idempotent** - dropped the `generatedAt`
@@ -3059,7 +3165,7 @@ Self-audit patch (found by running `/multi-agent:refactor` on the pipeline itsel
3059
3165
  - **Fix: install "dev-only excluded" count** is now the real file count (the
3060
3166
  `fixtures/` directory was under-counted as one entry).
3061
3167
  - **Security (supply-chain):** `release.yml` publishes with `npm publish
3062
- --provenance` (`id-token: write`); all GitHub Actions pinned to commit SHAs;
3168
+ --provenance` (`id-token: write`); all GitHub Actions pinned to commit SHAs;
3063
3169
  added `.github/dependabot.yml` (actions + npm dev deps); `credential-store.sh`
3064
3170
  escapes single quotes in the Windows PowerShell paths.
3065
3171
  - **New: `eval-mine-corpus.mjs`** turns recorded triage decisions
@@ -3171,7 +3277,7 @@ command is a breaking surface change).
3171
3277
  skeleton (Task/Story: Detailed Description, Scope, Acceptance Criteria, Test
3172
3278
  Scenarios; Bug: Detailed Description, Steps to Reproduce, Expected/Actual
3173
3279
  Result, Environment). Conditional sections (Design Reference, API Contract /
3174
- Swagger, Screenshots, Notes) render only when their trigger is present - no
3280
+ Swagger, Screenshots, Notes) render only when their trigger is present - no
3175
3281
  empty placeholder headings, nothing invented.
3176
3282
  - **Test Scenarios pulled from Jira convention.** Detects linked Xray/Zephyr test
3177
3283
  issues or a dominant test-scenario heading style and reuses it; falls back to an
@@ -3314,7 +3420,7 @@ Live tracker UX: per-tile cost, a real "currently doing X" line, and tracker con
3314
3420
  history (elapsed, tokens, USD) survives, Phase 5 is closed with a Result
3315
3421
  meta, the Claude Code TaskList is rebuilt from state, and one line
3316
3422
  summarizes the inherited history (`Continuing <id>: phases 0-3 finished
3317
- earlier (12m, 38.4k tok, ~$0.74)`). `add` is idempotent to make this safe.
3423
+ earlier (12m, 38.4k tok, ~$0.74)`). `add` is idempotent to make this safe.
3318
3424
  - **`/multi-agent:resume` rebuilds the phase tiles** from `tracker-state.json`
3319
3425
  (the contract documented it; the command now actually does it).
3320
3426
  - **Phase 0 + 7 token forwarding**: the clarifier call and the Phase 7 report
@@ -3615,7 +3721,7 @@ Generic SwiftUI interaction coverage for the figma-to-swiftui pipeline. Three
3615
3721
  new cross-cutting integration skills (navigation, overlays, bottom sheets) plus
3616
3722
  a reconcile-and-extend workflow, all native-SwiftUI-first with an optional
3617
3723
  per-project `ui.*` config hook so the same capabilities work on any SwiftUI
3618
- codebase - no app-specific coupling. Phase 3D dev detection and Phase 4 review
3724
+ codebase - no app-specific coupling. Phase 3D dev detection and Phase 4 review
3619
3725
  both consume them.
3620
3726
 
3621
3727
  ### Added
@@ -3633,12 +3739,12 @@ both consume them.
3633
3739
  component against current Figma (drift-heal) + additively extend it for a
3634
3740
  need, behind a mandatory human gate; distinct from `figma-to-swiftui`
3635
3741
  (build new), `figma-mend` (rebuild), and `figma-fix` (review bug).
3636
- - **`animated-gradient-border` UI pattern** (`figma-ui-patterns/patterns/`) -
3742
+ - **`animated-gradient-border` UI pattern** (`figma-ui-patterns/patterns/`) -
3637
3743
  self-contained native-SwiftUI recipe (angular-gradient stroke + Reduce-Motion
3638
3744
  gate), no external dependency.
3639
- - **`ui` block in `figma-project-config.schema.json`** - optional
3745
+ - **`ui` block in `figma-project-config.schema.json`** - optional
3640
3746
  `navigationSystem` / `overlaySystem` / `sheetSystem` (`mode: native | custom`
3641
- + type names). Absent → native SwiftUI.
3747
+ - type names). Absent → native SwiftUI.
3642
3748
 
3643
3749
  ### Changed
3644
3750
 
@@ -3674,7 +3780,7 @@ stale worktrees, and validates state before resume.
3674
3780
  Opus. This also resolves the prior CLAUDE.md-vs-phase-doc inconsistency
3675
3781
  (CLAUDE.md already documented Opus for these stages).
3676
3782
  - **Model fallback ladder is now `opus -> sonnet`** (was `fable -> opus ->
3677
- sonnet`). `modelFallback.fallbackModel` default is `sonnet`. The
3783
+ sonnet`). `modelFallback.fallbackModel` default is `sonnet`. The
3678
3784
  `premiumTierUntil` date gate is kept as a generic mechanism for any future
3679
3785
  plan-window-limited premium tier. `model-fallback.md` bumped to v10.1.0 and
3680
3786
  `smoke-model-fallback.sh` updated to assert the new ladder + Opus personas.
@@ -3752,12 +3858,12 @@ token ledger agree on what an input token is.
3752
3858
  - **Prompt-cache reuse ratio in metrics.** `aggregate-metrics.mjs` now sums
3753
3859
  `tokens_cached` per model and reports `cache_ratio = cached / (in + cached)`
3754
3860
  - the share of input tokens served from the host prompt cache - per model and
3755
- overall, in all three output modes (json / markdown / text). This is the
3756
- single number that says whether cache-friendly prompt structuring is paying
3757
- off. Backward-compatible: a phase that omits `tokens_cached` reads as 0%.
3861
+ overall, in all three output modes (json / markdown / text). This is the
3862
+ single number that says whether cache-friendly prompt structuring is paying
3863
+ off. Backward-compatible: a phase that omits `tokens_cached` reads as 0%.
3758
3864
  - **PR review iteration counter derived from the PR itself.** On a `needs_work`
3759
3865
  post, the iteration number is re-derived as `max("iteration #N" already on the
3760
- PR) + 1` instead of trusting agent-state. Standalone `/multi-agent:review`
3866
+ PR) + 1` instead of trusting agent-state. Standalone `/multi-agent:review`
3761
3867
  runs use a fresh task id each time, so the PR comments are the only reliable
3762
3868
  cross-run source of truth.
3763
3869
 
@@ -3767,7 +3873,7 @@ token ledger agree on what an input token is.
3767
3873
  `render-agent-log-cost.sh` treated `tokens_in` as cache-inclusive and
3768
3874
  subtracted the cached count (`fresh = in - min(cached, in)`), while the new
3769
3875
  `aggregate-metrics.mjs` treated `tokens_in` as cache-exclusive (`total =
3770
- in + cached`). Fed real data with high cache reuse (`cached > in`), the
3876
+ in + cached`). Fed real data with high cache reuse (`cached > in`), the
3771
3877
  renderer collapsed `fresh` to 0 and underpriced the row. Standardized on the
3772
3878
  **cache-exclusive** convention that matches the host usage report
3773
3879
  (`input_tokens` and `cache_read_input_tokens` are disjoint): the renderer no
@@ -3810,7 +3916,7 @@ reproduced locally by putting GNU coreutils' gnubin ahead of PATH.
3810
3916
  `search-logs.sh` (since-filter, scoring, JSON/TSV output) and
3811
3917
  `repo-cache.sh` TTLs. Probe order flipped to GNU-first (`stat -c` first;
3812
3918
  BSD rejects `-c`, so the fallback chain is safe both ways). `date -r
3813
- <epoch>` display calls gained a GNU `date -d @` fallback.
3919
+ <epoch>` display calls gained a GNU `date -d @` fallback.
3814
3920
  - **Hardcoded maintainer layout `$HOME/multi-agent-pipeline` removed** from
3815
3921
  `smoke-schema-validation.sh` (preferences-template path) and
3816
3922
  `smoke-pat-audit.sh` (.gitignore audit-log check) - both now derive the
@@ -4162,10 +4268,12 @@ Disk hygiene + correctness hardening, all gate-backed.
4162
4268
  Toward proven (not just designed): measure the features instead of asserting them, and ship the evidence-collection harness.
4163
4269
 
4164
4270
  ### Added
4271
+
4165
4272
  - **Measured intent-guard accuracy** (`eval-intent.mjs` + `pipeline/eval/intent-cases.json`). 26 labeled EN+TR cases run through `classify-intent.sh`; the gate uses operationally-safe accuracy (the only dangerous errors are a task read as a question -> work skipped, or a question read as a task -> a spurious worktree; `ambiguous` proceeds as a task so it is safe for task cases). Currently 100% safe / 96.2% exact. Wired into `npm test`. Turns the heuristic into a number with a regression set.
4166
4273
  - **Per-run outcome metrics** (`run-metrics.mjs` + fixture + `smoke-run-metrics.sh`). Parses an `agent-state.json` into the numbers that answer "did this run go well": review iterations (rework loops), first-pass-clean, reviewer signal-to-noise (accepted / raw findings), consensus verdict, build outcome. Phase 7 emits it; accumulating the output across real runs is the real-world validation corpus that golden tasks + benchmarks only approximate.
4167
4274
 
4168
4275
  ### Notes
4276
+
4169
4277
  - These address the honest "measure, don't assume" gap from the self-review: the intent guard and review signal are now quantified, and the harness exists to turn real runs into evidence. The remaining step (running real tasks + a public benchmark) is the user's, and cannot be fabricated.
4170
4278
  - CI auto-run stays disabled in `test.yml` (the maintainer paused it for GitHub Actions billing); re-enabling the push/PR triggers is a billing decision, not changed here.
4171
4279
 
@@ -4174,6 +4282,7 @@ Toward proven (not just designed): measure the features instead of asserting the
4174
4282
  Closes the structural gaps the adversarial review surfaced: the deterministic gates now actually RUN on the three adapter platforms, and the multi-model review is restored there using each platform's real model lineup.
4175
4283
 
4176
4284
  ### Added
4285
+
4177
4286
  - **Shared runtime so gates execute on Cursor / Antigravity / VS Code Copilot Chat.** The gate scripts + lib + schemas are installed once to `~/.multi-agent/` (dev-only / PII files excluded) and the emitted agents/commands/workflow reference them by absolute path (`installSharedRuntime` / `rewriteScriptRefs` in `_base.mjs`). Previously the emitted agents referenced `pipeline/scripts/...` which did not exist in the consumer project, so the deterministic gates could not run there at all. Uninstall removes the runtime. Enforced by `smoke-shared-runtime.sh`.
4178
4287
  - **Cross-vendor 2-model review on the adapter platforms.** A second reviewer agent (`ma-code-reviewer-x`) is emitted pinned to a different vendor, using each platform's actual model lineup (researched mid-2026, centralized in `_base.mjs#REVIEWER_MODELS`): Cursor `inherit` + `gpt-5.5`; VS Code Copilot Chat `Claude Opus 4.8` + `GPT-5.5`; Antigravity documents a `Gemini 3 Pro` + `Claude Opus 4.6` pair (its models are dropdown-selected, not file-pinned). Restores the cross-model diversity that was Claude-Code / Copilot-CLI-only.
4179
4288
  - **Recommended PreToolUse hooks template** (`install/templates/claude-hooks.json`) wiring the secret scan as a HARD pre-commit gate on Claude Code; `multi-agent:setup` Step 8 offers to merge it. The secret scan is the one gate that is OS-hookable (no run-specific args); the others are phase-invoked by contract. Enforced by `smoke-gate-hooks.sh`.
@@ -4181,9 +4290,11 @@ Closes the structural gaps the adversarial review surfaced: the deterministic ga
4181
4290
  - **Reviewer-count contract checks** in `smoke-cross-cli-behavior.sh`: locks Claude=2 / Copilot=3 + the documented adapter-platform reviewer set against drift.
4182
4291
 
4183
4292
  ### Fixed
4293
+
4184
4294
  - VS Code Copilot Chat agents emitted `model: inherit`, which is not a valid Copilot model (there is no `inherit` keyword; omitting `model` inherits the picker). Normal personas now omit `model`; the two reviewers pin a picker label.
4185
4295
 
4186
4296
  ### Notes (stated honestly)
4297
+
4187
4298
  - The adapter platforms still have no `PreToolUse` equivalent, so their gates are workflow-enforced (run as steps) rather than OS-blocked.
4188
4299
  - Pinned adapter models depend on the user's subscription; swap them in `REVIEWER_MODELS` / the Antigravity dropdown if a model is unavailable.
4189
4300
  - Old PII-bearing versions (9.3.0-9.3.3) remain in the private registry: GitHub Packages does not support `npm deprecate` (E400), and deleting versions / rewriting git history are irreversible and were left to an explicit owner decision. The package + repo are private, so this is not a public exposure.
@@ -4193,12 +4304,14 @@ Closes the structural gaps the adversarial review surfaced: the deterministic ga
4193
4304
  Second round of review-driven fixes - the lower-severity findings left open in 9.3.3, plus an honesty correction on the multi-platform claim.
4194
4305
 
4195
4306
  ### Fixed
4307
+
4196
4308
  - **Arg parsers dropped values starting with `--`.** `learnings-ledger.mjs` and `evidence-gate.mjs` now accept the `--key=value` form, so a statement / pattern that begins with `--` (e.g. `--statement="-- prefer let"`) is preserved instead of silently failing.
4197
4309
  - **Secret scan skipped filenames with spaces.** `pre-commit-check.sh` now iterates staged files NUL-delimited (`git diff --name-only -z`), closing a false-negative where a secret in `my file.txt` went unscanned.
4198
4310
  - **`learnings-ledger forget` rewrite is now atomic** (temp file + rename) so a crash or concurrent reader never sees a half-written ledger.
4199
4311
  - **`from-triage` scope for a top-level file** is now the filename itself, not a stray `./*` glob.
4200
4312
 
4201
4313
  ### Changed
4314
+
4202
4315
  - **Honest multi-platform claim.** The README and the adapter-emitted orchestration commands now state plainly that Claude Code + Copilot CLI run the pipeline natively (gate scripts installed), while Cursor / Antigravity / VS Code Copilot Chat receive the workflow + subagents + MCP but run the deterministic gates as ADVISORY (the gate scripts are not copied into those projects). Making those gates execute on the three adapter platforms is tracked work (needs a uniform script-path resolution + per-platform testing), not yet shipped. Stale `cursor.mjs` header ("26 commands can't run there") corrected.
4203
4316
 
4204
4317
  ## [9.3.3] - 2026-05-30
@@ -4206,68 +4319,82 @@ Second round of review-driven fixes - the lower-severity findings left open in 9
4206
4319
  A 4-agent adversarial review of the v9.3.x work surfaced real defects in the features just shipped; this release fixes them.
4207
4320
 
4208
4321
  ### Fixed
4322
+
4209
4323
  - **evidence-gate was bypassable.** A failing build log that also contained the word "SUCCESS" (cached-step note, banner) passed because success and failure were weighed equally. Failure markers are now DECISIVE (a definitive failure marker fails the claim regardless of success text), success markers were narrowed (dropped the generic `\bSUCCESS\b`), and caller-supplied `--success/--failure-pattern` are length-capped + compiled in a try/catch so a bad pattern is a clean usage error, not a crash. (`evidence-gate.mjs`, `smoke-evidence-gate.sh`)
4210
4324
  - **intent-guard misclassified questions as tasks.** "does it support offline mode", "should we enable caching" were read as tasks (the imperative check beat the interrogative) and would spin up a worktree. A strong question signal (interrogative lead / trailing `?` / TR particle) now wins over a bare imperative verb; an explicit polite request ("can you split this file") stays a task. (`classify-intent.sh`, `smoke-intent-guard.sh`)
4211
4325
  - **consensus block was decorative.** `validate-triage.mjs` validated the v3.1.0 consensus block structurally but never cross-checked it: `unanimous-block` + `approved:true`, `unanimous-pass` + an accepted blocker, and a single-reviewer "unanimous" verdict now all fail validation. (`smoke-phase4-triage.sh`)
4212
4326
  - **A malformed `tokens_cached` poisoned the whole tokens call.** `log-metric.sh` now sanitizes a non-integer cached count to 0 before forwarding, so the valid in/out counts still land. (`smoke-agent-log-cost.sh`)
4213
4327
 
4214
4328
  ### Security
4329
+
4215
4330
  - **Stopped real maintainer/employer identifiers from shipping in the npm tarball.** The dev-only figma substitution map (a scrub table that by design holds real upstream values), the two personal-data scanners, and two internal planning docs were excluded from the package via negated `files` entries; stray corporate hosts / repo names / a private Jira key in CHANGELOG + docs examples were genericized. The leak gate now scans the published npm tarball (not just the install tree), closing the hole that let these ship in 9.3.0-9.3.2. The repo/package are private, so this was not a public exposure. (`smoke-install-leak-gate.sh`, `.npmignore`, `package.json` files)
4216
4331
 
4217
4332
  ### Changed
4333
+
4218
4334
  - README "What's new" refreshed to v9.3.3; em-dashes removed from README and the `MANDATORY` keyword removed from `install/templates/copilot-instructions.md` (project style rules).
4219
4335
 
4220
4336
  ## [9.3.2] - 2026-05-30
4221
4337
 
4222
4338
  ### Fixed
4339
+
4223
4340
  - **Cost-ledger cache pricing is now wired end-to-end.** v9.3.0 added `cacheReadPerMtok` pricing + a cache-reads line to `render-agent-log-cost.sh`, but nothing fed `tokens_cached` to the tracker, so the feature was dormant. `phase-tracker.sh tokens` now accepts an optional 4th `cached` arg (defaults to 0, fully back-compatible), `log-metric.sh` forwards `tokens_cached=` into it, and the Phase 4 telemetry doc documents passing the host's `cache_read_input_tokens`. Verified end-to-end in `smoke-agent-log-cost.sh`.
4224
4341
  - `evidence-gate.mjs` made executable to match its sibling `.mjs` scripts.
4225
4342
 
4226
4343
  ### Changed
4344
+
4227
4345
  - README "What's new" refreshed to v9.3.x (was stale at v8.8.1).
4228
4346
 
4229
4347
  ## [9.3.1] - 2026-05-30
4230
4348
 
4231
4349
  ### Fixed
4350
+
4232
4351
  - **Learnings ledger no longer auto-suppresses rejected BLOCKING findings.** `learnings-ledger.mjs from-triage` previously distilled every rejected finding into a durable "do not re-flag" preference regardless of severity; a single wrong rejection of a blocking issue could permanently silence that class on future runs. Blocking-severity rejections are now skipped (reported as `skippedBlocking`); only lower-severity rejections become durable preferences, and they are recorded at `low` confidence.
4233
4352
 
4234
4353
  ### Added
4354
+
4235
4355
  - **`learnings-ledger.mjs forget`** subcommand to remove a bad or stale ledger entry by statement substring and/or kind (the one non-append operation), so a wrong learning can be cleared instead of persisting forever. Enforced by `smoke-learnings-ledger.sh`.
4236
4356
 
4237
4357
  ## [9.3.0] - 2026-05-30
4238
4358
 
4239
4359
  ### Added
4360
+
4240
4361
  - **Review consensus surfacing (anti-correlation).** Phase 4 triage now records an optional `consensus` block (triage-output schema v3.1.0): `reviewerCount`, a `verdict` (`unanimous-pass` / `unanimous-block` / `split` / `unverified`), and `disagreements[]`. Unanimous agreement among same-base-model reviewers on a judgment-heavy surface (security, auth, concurrency, money, migration) is marked `unverified` and surfaced to the user instead of being trusted as a pass. Disagreements are shown at the Step 4 checkpoint and written to the agent-log "Review Consensus" section. Validated by `validate-triage.mjs` + new fixtures in `smoke-phase4-triage.sh`.
4241
4362
  - **Persistent learnings ledger** (`pipeline/scripts/learnings-ledger.mjs`, schema `learnings-ledger.schema.json`). A per-repo, append-only store of durable architectural facts, conventions, and explicitly rejected review preferences, stored next to the triage corpus. A compact `<repo-learnings>` brief is injected into Phase 1 analysis and Phase 4 triage so agents stop re-discovering structure and reviewers stop re-flagging rejected feedback (the most-cited cold-boot-amnesia complaint). Phase 7 distills each run's rejected findings into the ledger. On by default via `prefs.global.learningsLedger`; per-repo isolated. Enforced by `smoke-learnings-ledger.sh`.
4242
4363
  - **Default-FAIL evidence gate** (`pipeline/scripts/evidence-gate.mjs`). A build/test/review "passed" claim is only trusted when a substantiating log artifact exists and shows success; the gate fails CLOSED on missing, empty, or contradicting evidence. Wired into Phase 3 (build), Phase 4 Stage 1 gates (build + test), and Phase 6 (commit). Enforced by `smoke-evidence-gate.sh`.
4243
4364
  - **Conceptual-vs-edit intent guard** (`pipeline/lib/classify-intent.sh`). A deterministic, language-aware (EN + TR) classifier runs on free-text input at Phase 0; a question is answered in place instead of spinning up a branch/worktree. On by default via `prefs.global.intentGuard`. Enforced by `smoke-intent-guard.sh`.
4244
4365
 
4245
4366
  ### Changed
4367
+
4246
4368
  - **Secret pre-commit gate** (`pre-commit-check.sh`) extended beyond pattern matching: high-signal provider-token prefixes (GitHub PAT, Slack, Google API key, Stripe, npm, GitLab), JWT detection, and a Shannon-entropy scan that catches custom/unknown secrets while exempting lockfiles, integrity hashes, source maps, and snapshots.
4247
4369
  - **Per-phase cost ledger** (`render-agent-log-cost.sh`) now prices prompt-cache reads at the discounted `cacheReadPerMtok` rate (cost-table schema 1.1.0; backward-compatible, defaults to 0 cached), appends a "Top cost driver" line so the report shows where spend went, and surfaces a cache-reads line when the tracker recorded cache hits.
4248
4370
  - Uninstall header and package description refreshed to the current 5-platform set (Cursor / Antigravity / VS Code Copilot Chat), replacing stale Windsurf/Cline references.
4249
4371
 
4250
4372
  ### Fixed
4373
+
4251
4374
  - Cursor uninstall left an empty `.cursor/commands/` directory behind, and the `.cursor` parent-empty cleanup ran before orchestration teardown so the parent was never reclaimed. Both now clean up after the orchestration uninstall.
4252
4375
 
4253
4376
  ## [9.2.0] - 2026-05-30
4254
4377
 
4255
4378
  ### Added
4379
+
4256
4380
  - **Full-pipeline orchestration on three more platforms** (previously knowledge-layer only). Cursor (`.cursor/agents/ma-*.md` subagents + `.cursor/commands/multi-agent.md` + `.cursor/mcp.json`), Antigravity (`.agent/workflows/multi-agent.md` + `.agent/rules/` + `AGENTS.md` + `.agent/mcp_config.json`), and VS Code Copilot Chat (`.github/agents/ma-*.agent.md` + `.github/prompts/multi-agent.prompt.md` + `.vscode/mcp.json`). Each adapter transforms the pipeline personas into the platform's subagent/agent format and registers the dev-toolkit MCP server. Install with `--cursor` / `--antigravity` / `--copilot-chat` (or `--all-tools`).
4257
4381
  - **Picker contract** (`refs/picker-contract.md`) + `pipeline/lib/ask-choice.sh`: a cross-platform single-choice abstraction so confirmations degrade gracefully where there is no native `AskUserQuestion` (numbered-menu fallback; `ASK_CHOICE_DEFAULT` for autopilot/CI).
4258
4382
  - **Proactive token-budget cap** (`prefs.global.costBudget` + `cost-budget-check.mjs`): prices the phase-tracker accumulators live and warns/halts before spend runs away.
4259
4383
  - **Eval harness** expanded from 2 to 7 golden tasks across all stacks and every triage bucket.
4260
4384
 
4261
4385
  ### Changed
4386
+
4262
4387
  - **Confirmations are now native pickers** instead of typed keywords (`AskUserQuestion` on Claude Code, degrading per the picker contract elsewhere). Removed the typed `y/N` / `onayla`/`iptal` prompts.
4263
4388
  - **Command/skill instruction files are English** throughout (token efficiency + model comprehension); `outputLanguage` still governs all runtime user-facing text.
4264
4389
  - Phase 5 (User Test) now runs only in interactive worktree-backed modes (`dev`, `full`); every autopilot/local variant skips it.
4265
4390
  - Analysis->plan contract field names aligned across schema, validator, and phase docs; the "no MCP outside analysis" gate made enforceable (telemetry recorded + checked).
4266
4391
 
4267
4392
  ### Fixed
4393
+
4268
4394
  - Command-injection vectors in `diff-explain.mjs` and `figma-screenshot.sh`; `review-watch` cursor loss (re-reviewed PRs forever); `diff-risk` / `classify-plan-safety` / `match-skills` logic defects; `write-state` stale-lock deadlock; several pre-existing test failures (mode-dispatch drift, README/install-layout counts, token budgets).
4269
4395
 
4270
4396
  ### Removed
4397
+
4271
4398
  - Dead `--windsurf` / `--cline` / `--continue` / `--zed` install flags (the adapters were dropped in 8.5.4; only the advertising lingered).
4272
4399
 
4273
4400
  ## [9.1.1] - 2026-05-16
@@ -4281,7 +4408,7 @@ A 4-agent adversarial review of the v9.3.x work surfaced real defects in the fea
4281
4408
  - `pipeline/commands/multi-agent/channels.md`: 103 chars
4282
4409
  - `pipeline/skills/shared/core/multi-agent/SKILL.md`: 97 chars
4283
4410
  - `pipeline/commands/multi-agent/setup.md`: 79 chars
4284
- - Mapping: `U+2014` -> ` - `, `U+2013` -> `-`, `U+2026` -> `...`, `U+201C/D` -> `"`, `U+2018/9` -> `'`, `U+00A7` -> `section`.
4411
+ - Mapping: `U+2014` -> `-`, `U+2013` -> `-`, `U+2026` -> `...`, `U+201C/D` -> `"`, `U+2018/9` -> `'`, `U+00A7` -> `section`.
4285
4412
 
4286
4413
  ### Excluded from sweep
4287
4414
 
@@ -4325,13 +4452,13 @@ A 4-agent adversarial review of the v9.3.x work surfaced real defects in the fea
4325
4452
 
4326
4453
  Six measurable improvements on the hot path:
4327
4454
 
4328
- | Fix | Before | After | Speedup |
4329
- |---|---|---|---|
4330
- | `smoke-personal-data.sh` pattern alternation (single grep vs 22 invocations) | 3.348s | 1.545s | 2.2x |
4331
- | `smoke-no-token-prompt.sh` multi-`-e` (single grep vs 7x7=49 invocations) | 0.485s | 0.014s | 35x |
4332
- | `phase-tracker.sh` render batched jq (U+001F separator preserves empty fields) | ~7 jq calls per phase + ~3 per sub | 1 batch + 1 per active sub/meta | ~120 subprocess azalma per render |
4333
- | `issue-fetcher.sh` python3 batch (single inline vs 7 separate calls per fetch) | 0.806s / 3 iter | 0.136s / 3 iter | 5.9x |
4334
- | `md2confluence-v3.py` HTTP retry wrapper (3-attempt exponential backoff on 5xx / 429) + `ThreadPoolExecutor(max_workers=4)` paralel attachment | sequential N x ~1.5s | parallel ~max(individual) | up to 4x on multi-screenshot pages, plus transient-error resilience |
4455
+ | Fix | Before | After | Speedup |
4456
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------- |
4457
+ | `smoke-personal-data.sh` pattern alternation (single grep vs 22 invocations) | 3.348s | 1.545s | 2.2x |
4458
+ | `smoke-no-token-prompt.sh` multi-`-e` (single grep vs 7x7=49 invocations) | 0.485s | 0.014s | 35x |
4459
+ | `phase-tracker.sh` render batched jq (U+001F separator preserves empty fields) | ~7 jq calls per phase + ~3 per sub | 1 batch + 1 per active sub/meta | ~120 subprocess azalma per render |
4460
+ | `issue-fetcher.sh` python3 batch (single inline vs 7 separate calls per fetch) | 0.806s / 3 iter | 0.136s / 3 iter | 5.9x |
4461
+ | `md2confluence-v3.py` HTTP retry wrapper (3-attempt exponential backoff on 5xx / 429) + `ThreadPoolExecutor(max_workers=4)` paralel attachment | sequential N x ~1.5s | parallel ~max(individual) | up to 4x on multi-screenshot pages, plus transient-error resilience |
4335
4462
  | `extract-conventions.sh` env override `EXTRACT_CONV_EXTRA_ROOTS` + auto-add `.gitmodules` paths, bucket timeout 30s -> 10s, `xargs basename` -> `awk -F/ '{print $NF}'` (8 callsites) | scan roots too narrow on monorepos with submodules; per-bucket 30s budget | submodule paths auto-detected, faster fail | resolves "confidence: none" on submodule-heavy repos; ~100-300ms saved per bucket |
4336
4463
 
4337
4464
  ### Verified