@mmerterden/multi-agent-pipeline 16.20.0 → 16.23.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 (51) hide show
  1. package/CHANGELOG.md +231 -102
  2. package/README.md +6 -8
  3. package/README.tr.md +6 -8
  4. package/docs/architecture.md +3 -3
  5. package/docs/ecosystem.md +5 -5
  6. package/docs/features.md +1 -0
  7. package/install/templates/claude-hooks.json +12 -1
  8. package/install/templates/copilot-instructions.md +17 -2
  9. package/package.json +1 -1
  10. package/pipeline/agents/bulk-reader.md +57 -0
  11. package/pipeline/commands/multi-agent/SKILL.md +0 -5
  12. package/pipeline/commands/multi-agent/help/SKILL.md +0 -10
  13. package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +2 -2
  14. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -1
  15. package/pipeline/commands/multi-agent/resume-local/SKILL.md +2 -2
  16. package/pipeline/commands/multi-agent/setup/SKILL.md +7 -5
  17. package/pipeline/commands/multi-agent/sync/SKILL.md +13 -13
  18. package/pipeline/multi-agent-refs/cross-cli-contract.md +10 -12
  19. package/pipeline/multi-agent-refs/phases/modes.md +1 -1
  20. package/pipeline/multi-agent-refs/phases/phase-0-init.md +7 -2
  21. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +1 -1
  22. package/pipeline/multi-agent-refs/phases/phase-7-report.md +8 -1
  23. package/pipeline/multi-agent-refs/picker-contract.md +1 -1
  24. package/pipeline/multi-agent-refs/tracker-contract.md +46 -0
  25. package/pipeline/schemas/agent-state.schema.json +1 -1
  26. package/pipeline/schemas/bulk-read-output.schema.json +52 -0
  27. package/pipeline/schemas/prefs.schema.json +74 -19
  28. package/pipeline/schemas/token-budget.json +3 -3
  29. package/pipeline/scripts/bulk-read.sh +277 -0
  30. package/pipeline/scripts/check-read-size.py +335 -0
  31. package/pipeline/scripts/check-read-size.sh +86 -0
  32. package/pipeline/scripts/phase-tracker.sh +245 -3
  33. package/pipeline/scripts/pre-commit-check.sh +1 -0
  34. package/pipeline/scripts/uninstall.mjs +1 -0
  35. package/pipeline/skills/.skill-manifest.json +8 -24
  36. package/pipeline/skills/.skills-index.json +2 -46
  37. package/pipeline/skills/shared/README.md +4 -8
  38. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +0 -8
  39. package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +1 -1
  40. package/pipeline/skills/shared/core/multi-agent-resume-local/SKILL.md +1 -1
  41. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +12 -12
  42. package/pipeline/skills/shared/external/backlog/SKILL.md +10 -6
  43. package/pipeline/skills/skills-index.md +2 -6
  44. package/pipeline/commands/multi-agent/dev/SKILL.md +0 -17
  45. package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +0 -23
  46. package/pipeline/commands/multi-agent/dev-local/SKILL.md +0 -17
  47. package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +0 -21
  48. package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +0 -19
  49. package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +0 -25
  50. package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +0 -19
  51. package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +0 -23
package/CHANGELOG.md CHANGED
@@ -16,6 +16,65 @@ Internal file-layout changes that don't affect the slash-command surface are sti
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [16.23.0] - 2026-09-09
20
+
21
+ ### Fixed
22
+
23
+ - **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.
24
+
25
+ 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.
26
+
27
+ - **`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.
28
+
29
+ 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.
30
+
31
+ - **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.
32
+
33
+ ### Added
34
+
35
+ - **`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.
36
+
37
+ - **`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.
38
+
39
+ ### Removed
40
+
41
+ - **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.
42
+
43
+ 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.
44
+
45
+ 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.
46
+
47
+ - **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.
48
+
49
+ `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.
50
+
51
+ 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.
52
+
53
+ 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.
54
+
55
+ ## [16.21.0] - 2026-09-08
56
+
57
+ ### Added
58
+
59
+ - **A read-size gate, and a cheap worker to send an oversized read to.** `offload-ref.sh` took half the machinery bill in 15.10.0 - the build log, the diff, the test output become a pointer plus a tail. The other half was never looked at, because nothing could look at it: only a `PreToolUse` hook sees a tool call before it runs. A phase that reads six 900-line files pays for 5,400 lines at that phase's own rung, and the part it needed was a handful of symbols.
60
+
61
+ `check-read-size.sh` (+ `check-read-size.py`, the decision core) is the third hook in `install/templates/claude-hooks.json` and the first on the READ side. `bulk-read.sh` is where a blocked read goes instead: the file reaches a haiku-rung worker over the `bulk-reader` persona, and what comes back is a structured summary with LINE NUMBERS, so the caller's next step is a bounded `Read(offset:limit:)` around the region that matters - which is cheap, exact, and passes the gate. The full text is parked under `.multi-agent/refs/`, the same place and the same promise `offload-ref.sh` makes.
62
+
63
+ **`observe` is a real mode, not a debug flag.** It decides and logs, blocks nothing, and it is what `bulkRead.mode` should be set to first: a gate switched straight to `enforce` has no baseline to be compared against, and "we cut tokens" becomes a claim nothing can check. The saving is measured from `metrics.jsonl` (`read.observed` / `read.blocked` / `bulk_read.delegated`), not asserted here - this entry deliberately quotes no percentage.
64
+
65
+ **Phase 3 is exempt, and that is load-bearing.** Claude Code's `Edit` requires the same file to have been `Read` first, so a gate that blocks reads while code is being changed blocks the change. So does any file the run itself has touched (`phases[n].files[]`). The gate is for the phases that read to UNDERSTAND. A draft of the decision core read `run.phase` and `dev.editSet` - neither of which `agent-state.schema.json` declares - so the exemption could never have engaged and development would have been blocked by a hook whose message read like advice; `smoke-bulk-read.sh` now asserts the field names against the schema so it cannot come back quietly.
66
+
67
+ **A ceiling, because delegation is not free either** (`bulkRead.maxBytes`, default 1 MB). Past some size the worker's own input bill approaches the read it replaced, and a file large enough to strain its window comes back truncated - a partial summary presented as a whole one is the one thing this feature must never produce. Over the ceiling the caller is told to narrow first (grep, then a bounded read). The check runs BEFORE the file is hashed or parked, so the guard is not theatre.
68
+
69
+ **The command parser is per-tool, because the flags are not shared.** `cat -n` NUMBERS LINES rather than taking a count, and reading its next token as one swallowed the filename, left no operand, and let `cat -n <bigfile>` through the gate entirely. `sed -n '1,900p'` looked bounded because it carried a flag, when the range in its script IS the bound. An unparseable count reads as UNBOUNDED, never as 1. A `tail -f` is a stream and passes, because blocking it would be a false positive with no cheap alternative to offer.
70
+
71
+ Off by default (`bulkRead.mode: "off"`), so merging the hook block is not itself a behaviour change. The worker degrades rather than inventing: no CLI, no auth, a timeout, or a non-JSON answer prints why and tells the caller to do a bounded read. A fabricated summary of a file nobody read is the one outcome worse than paying full price for the file.
72
+
73
+ ### Changed
74
+
75
+ - **`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.
76
+ - **`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.
77
+
19
78
  ## [16.20.0] - 2026-09-07
20
79
 
21
80
  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.
@@ -151,7 +210,6 @@ A review of the three repositories together (pipeline, toolkit MCP, plugin marke
151
210
 
152
211
  - **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`.
153
212
 
154
-
155
213
  ### Changed
156
214
 
157
215
  - **`/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.
@@ -173,22 +231,21 @@ A review of the three repositories together (pipeline, toolkit MCP, plugin marke
173
231
 
174
232
  ### Added
175
233
 
176
- - **`/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`.
234
+ - **`/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`.
177
235
  - **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.
178
236
  - **`_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.
179
237
  - **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).
180
238
  - **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.
181
- - **`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.
239
+ - **`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.
182
240
 
183
241
  ### Fixed
184
242
 
185
243
  - **`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.
186
244
  - **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.
187
245
  - **`--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.
188
- - **`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.
246
+ - **`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.
189
247
  - **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.
190
248
 
191
-
192
249
  ## [16.12.0] - 2026-08-27
193
250
 
194
251
  ### Added
@@ -217,14 +274,13 @@ A review of the three repositories together (pipeline, toolkit MCP, plugin marke
217
274
  - 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.
218
275
  - `smoke-validate-analysis-doc.sh` replaces four section-number pairing assertions with the `AS-NN` contract in both directions.
219
276
 
220
-
221
277
  ## [16.11.0] - 2026-08-27
222
278
 
223
279
  ### Changed
224
280
 
225
281
  - **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.
226
282
  - **`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.
227
- - **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.
283
+ - **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.
228
284
 
229
285
  ### Fixed
230
286
 
@@ -246,7 +302,6 @@ A review of the three repositories together (pipeline, toolkit MCP, plugin marke
246
302
  - 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.
247
303
  - `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.
248
304
 
249
-
250
305
  ### Fixed
251
306
 
252
307
  - **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.
@@ -261,7 +316,7 @@ A review of the three repositories together (pipeline, toolkit MCP, plugin marke
261
316
 
262
317
  ### Fixed
263
318
 
264
- - **`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.
319
+ - **`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.
265
320
 
266
321
  - **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.
267
322
 
@@ -271,7 +326,7 @@ Three debts the last few releases kept naming, closed.
271
326
 
272
327
  ### Added
273
328
 
274
- - **`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.
329
+ - **`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.
275
330
 
276
331
  ### Changed
277
332
 
@@ -286,7 +341,7 @@ Three debts the last few releases kept naming, closed.
286
341
 
287
342
  ### Added
288
343
 
289
- - **`/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.
344
+ - **`/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.
290
345
 
291
346
  ### Fixed
292
347
 
@@ -446,7 +501,7 @@ Four ideas taken from github/spec-kit, obra/superpowers, karpathy/llm-council an
446
501
  platform's promised files enter one denominator instead of the first one
447
502
  standing in for the run.
448
503
  - **A plan with zero steps passed.** `todos: []` reported `0/0 steps accounted
449
- for` and exited 0, so a Phase 2 that produced nothing - or a state whose todos
504
+ for` and exited 0, so a Phase 2 that produced nothing - or a state whose todos
450
505
  were cleared - read as a fully delivered plan. An empty plan is now exit 2,
451
506
  the same as a missing one.
452
507
  - **A malformed reviewer dispatch vanished.** A reviewer whose `findings` came
@@ -690,10 +745,12 @@ Found by reviewing v16.0.0 after it shipped. Four defects, three of them mine fr
690
745
  ## [15.21.0] - 2026-08-23
691
746
 
692
747
  ### Added
748
+
693
749
  - **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.
694
750
  - 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.
695
751
 
696
752
  ### Changed
753
+
697
754
  - **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.
698
755
  - **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.
699
756
  - `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.
@@ -702,45 +759,54 @@ Found by reviewing v16.0.0 after it shipped. Four defects, three of them mine fr
702
759
  ## [15.20.0] - 2026-08-23
703
760
 
704
761
  ### Added
762
+
705
763
  - **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.
706
764
  - **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`.
707
765
  - **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.
708
766
 
709
767
  ### Fixed
768
+
710
769
  - 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.
711
770
 
712
771
  ## [15.19.0] - 2026-08-23
713
772
 
714
773
  ### Added
774
+
715
775
  - **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.
716
776
  - **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.
717
777
  - **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.
718
778
  - **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.
719
779
 
720
780
  ### Fixed
781
+
721
782
  - **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.
722
783
 
723
784
  ### Changed
785
+
724
786
  - 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.
725
787
  - 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.
726
788
 
727
789
  ## [15.18.0] - 2026-08-22
728
790
 
729
791
  ### Added
792
+
730
793
  - **`/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.
731
794
  - 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.
732
795
  - 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.
733
796
 
734
797
  ### Changed
798
+
735
799
  - 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.
736
800
  - 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.
737
801
 
738
802
  ## [15.17.0] - 2026-08-22
739
803
 
740
804
  ### Fixed
805
+
741
806
  - **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.
742
807
 
743
808
  ### Changed
809
+
744
810
  - **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.
745
811
  - 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).
746
812
  - `analysis-output.schema.json` gains `docStatus`, `docPath[]` and `openQuestions[]` - the fields the two pre-flights branch on.
@@ -749,18 +815,21 @@ Found by reviewing v16.0.0 after it shipped. Four defects, three of them mine fr
749
815
  ## [15.16.1] - 2026-08-22
750
816
 
751
817
  ### Fixed
818
+
752
819
  - **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.
753
820
  - 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.
754
821
 
755
822
  ## [15.16.0] - 2026-08-22
756
823
 
757
824
  ### Added
825
+
758
826
  - **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.
759
827
  - 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.
760
828
 
761
829
  - **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.
762
830
 
763
831
  ### Changed
832
+
764
833
  - `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.
765
834
  - 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.
766
835
  - `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.
@@ -768,30 +837,36 @@ Found by reviewing v16.0.0 after it shipped. Four defects, three of them mine fr
768
837
  ## [15.15.0] - 2026-08-22
769
838
 
770
839
  ### Added
840
+
771
841
  - **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.
772
842
  - **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.
773
843
  - `/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.
774
844
  - **`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.
775
845
 
776
846
  ### Fixed
847
+
777
848
  - 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.
778
849
 
779
850
  ### Changed
851
+
780
852
  - `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.
781
853
 
782
854
  ## [15.14.0] - 2026-08-22
783
855
 
784
856
  ### Added
857
+
785
858
  - **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.
786
859
  - **`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.
787
860
 
788
861
  ### Fixed
862
+
789
863
  - **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.
790
- - **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.
864
+ - **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.
791
865
  - **`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`.
792
866
  - 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`.
793
867
 
794
868
  ### Changed
869
+
795
870
  - `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".
796
871
  - **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.
797
872
  - **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.
@@ -803,126 +878,151 @@ Found by reviewing v16.0.0 after it shipped. Four defects, three of them mine fr
803
878
  ## [15.13.0] - 2026-08-22
804
879
 
805
880
  ### Fixed
881
+
806
882
  - **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.
807
883
 
808
884
  ### Added
885
+
809
886
  - **`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.
810
887
 
811
888
  ### Changed
889
+
812
890
  - 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.
813
891
 
814
892
  ## [15.12.2] - 2026-08-22
815
893
 
816
894
  ### Fixed
895
+
817
896
  - **`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.
818
897
  - **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.
819
898
 
820
899
  ## [15.12.1] - 2026-08-22
821
900
 
822
901
  ### Fixed
823
- - **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.
824
-
825
902
 
903
+ - **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.
826
904
 
827
905
  ## [15.12.0] - 2026-08-22
828
906
 
829
907
  ### Changed
908
+
830
909
  - **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.
831
910
  - **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.
832
911
  - Declared minimums move to `v3.0.0+`; 2.x only ever existed under the old package name.
833
912
 
834
913
  ### Migration
914
+
835
915
  - `@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.
836
916
  - A hand-registered client that the pipeline installer does not manage needs `<cli> mcp remove dev-toolkit` once.
837
917
 
838
918
  ### Fixed
919
+
839
920
  - **`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.
840
921
 
841
922
  ## [15.11.0] - 2026-08-21
842
923
 
843
924
  ### Changed
925
+
844
926
  - **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.
845
927
 
846
928
  ### Fixed
929
+
847
930
  - **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.
848
931
 
849
932
  ### Tests
933
+
850
934
  - `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.
851
935
 
852
936
  ## [15.10.1] - 2026-08-21
853
937
 
854
938
  ### Fixed
939
+
855
940
  - **`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.
856
941
  - **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`.
857
942
 
858
943
  ## [15.10.0] - 2026-08-21
859
944
 
860
945
  ### Added
946
+
861
947
  - **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.
862
948
  - **`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.
863
949
  - **`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.
864
950
  - **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.
865
951
 
866
952
  ### Security
953
+
867
954
  - **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.
868
955
 
869
956
  ### Changed
957
+
870
958
  - `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.
871
959
  - 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.
872
960
  - `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.
873
961
  - 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.
874
962
 
875
963
  ### Tests
964
+
876
965
  - `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`.
877
966
 
878
967
  ## [15.9.1] - 2026-08-20
879
968
 
880
969
  ### Changed
970
+
881
971
  - **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.
882
972
 
883
973
  ## [15.9.0] - 2026-08-20
884
974
 
885
975
  ### Fixed
976
+
886
977
  - **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`.
887
978
 
888
979
  ### Changed
980
+
889
981
  - **`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.
890
982
  - **`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.
891
983
  - Stale version tables refreshed: `SECURITY.md` supported-versions moves to the 15.x line; `ROADMAP.md` "Current Release" becomes a rolling "Recent Releases".
892
984
 
893
985
  ### Companion
986
+
894
987
  - **`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.
895
988
 
896
989
  ## [15.8.1] - 2026-08-19
897
990
 
898
991
  ### Fixed
992
+
899
993
  - **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.
900
994
 
901
995
  ## [15.8.0] - 2026-08-19
902
996
 
903
997
  ### Added
998
+
904
999
  - **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.
905
1000
 
906
1001
  ## [15.7.0] - 2026-08-19
907
1002
 
908
1003
  ### Added
1004
+
909
1005
  - **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.
910
1006
  - **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`.
911
1007
  - **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.
912
1008
 
913
1009
  ### Changed
1010
+
914
1011
  - **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.
915
1012
  - **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.
916
1013
 
917
1014
  ### Fixed
1015
+
918
1016
  - **build-stack-plugins.mjs dead import**: unused `APPLE_ONLY` import removed (superseded by the `STACK_ONLY` routing table).
919
1017
 
920
1018
  ## [15.6.1] - 2026-08-19
921
1019
 
922
1020
  ### Changed
1021
+
923
1022
  - **`/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.
924
1023
 
925
1024
  ### Fixed
1025
+
926
1026
  - **`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.
927
1027
  - **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.
928
1028
  - **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).
@@ -931,28 +1031,34 @@ Found by reviewing v16.0.0 after it shipped. Four defects, three of them mine fr
931
1031
  ## [15.6.0] - 2026-08-18
932
1032
 
933
1033
  ### Fixed
1034
+
934
1035
  - **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.
935
1036
 
936
1037
  ### Added
1038
+
937
1039
  - **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`.
938
1040
 
939
1041
  ## [15.5.0] - 2026-08-18
940
1042
 
941
1043
  ### Added
1044
+
942
1045
  - **`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.
943
1046
 
944
1047
  ### Fixed
1048
+
945
1049
  - **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).
946
1050
  - **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.
947
1051
 
948
1052
  ## [15.4.0] - 2026-08-18
949
1053
 
950
1054
  ### Added
1055
+
951
1056
  - **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.
952
1057
  - **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.
953
1058
  - **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.
954
1059
 
955
1060
  ### Fixed
1061
+
956
1062
  - 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.
957
1063
 
958
1064
  ## [15.3.2] - 2026-08-17
@@ -1030,7 +1136,7 @@ places at once.
1030
1136
  delivered a different stack than the repo asked for. Repo settings are now read
1031
1137
  first, user-global second, `--platform` last.
1032
1138
  - **Copilot's skipped-stack prune could delete user-authored skills.** The prune
1033
- was derived from catalog *names*, so a user's own `~/.copilot/skills/<name>/`
1139
+ was derived from catalog _names_, so a user's own `~/.copilot/skills/<name>/`
1034
1140
  sharing a catalog name was removed with no prompt. It is now scoped to the
1035
1141
  previous delivery manifest (or, pre-manifest, to dirs byte-identical to the
1036
1142
  catalog) - the same contract `uninstall` already used.
@@ -1131,7 +1237,6 @@ places at once.
1131
1237
  were already at (or 193 bytes past) their limit; the always-on surface itself
1132
1238
  shrank with the local copy gone.
1133
1239
 
1134
-
1135
1240
  ### Added
1136
1241
 
1137
1242
  - **`/multi-agent:complaint-analysis`** — customer-complaint / customer-reported-error
@@ -1183,7 +1288,7 @@ places at once.
1183
1288
  `gzip` preserves the source file's mtime by default, so rotating log
1184
1289
  content already older than `KEEP_DAYS` produced a `.gz` that inherited
1185
1290
  that old mtime - and the very next line's retention sweep (`find -mtime
1186
- +$KEEP_DAYS -delete`) deleted it in the same run, before it was ever read.
1291
+ +$KEEP_DAYS -delete`) deleted it in the same run, before it was ever read.
1187
1292
  The archive's mtime is now reset to rotation time, so retention counts
1188
1293
  from when it was archived, not from the age of the content inside it.
1189
1294
  - The mode banner claimed pickers stay English when they didn't - language +
@@ -1204,26 +1309,26 @@ places at once.
1204
1309
 
1205
1310
  - **`testflight-validation` merged into a new `/multi-agent:store-ready`, and the
1206
1311
  Android side brought to parity.** The iOS archive audit existed twice with
1207
- identical arguments - `ios_app_store_audit({archive_path, rules: "all"})` in the
1312
+ identical arguments - `ios_app_store_audit({archive_path, rules: "all"})` in the
1208
1313
  command's Gate 1 and again inside `sim-test.md`'s `store-ready` scenario. Two
1209
1314
  copies of one call is how the second door grew with no Gate 2, no Gate 3 and no
1210
1315
  Android equivalent. There is now one implementation.
1211
1316
  Gates are symmetric per platform, because the failure modes are: a malformed
1212
1317
  package, a package the store itself refuses, and a policy a human enforces.
1213
1318
 
1214
- | Gate | iOS | Android |
1215
- |---|---|---|
1216
- | 1 Static | `ios_app_store_audit`, 18 rules | `android_apk_audit` + `google-play-compliance`, 21 rules |
1217
- | 2 Authoritative | `altool --validate-app` | `SKIPPED` |
1218
- | 3 Policy | `app-store-review` vs source | `play-store-review` vs source |
1319
+ | Gate | iOS | Android |
1320
+ | --------------- | ------------------------------- | -------------------------------------------------------- |
1321
+ | 1 Static | `ios_app_store_audit`, 18 rules | `android_apk_audit` + `google-play-compliance`, 21 rules |
1322
+ | 2 Authoritative | `altool --validate-app` | `SKIPPED` |
1323
+ | 3 Policy | `app-store-review` vs source | `play-store-review` vs source |
1219
1324
 
1220
1325
  Gate 2's asymmetry is reported as an asymmetry rather than smoothed over: Play's
1221
1326
  authoritative check exists only server-side, through a Publishing API draft edit,
1222
1327
  and the pipeline ships no client for it. An Android run therefore clears at most
1223
1328
  2 of 3 and never prints `passed`. `bundletool validate` is Gate 1 and is not
1224
1329
  promoted to fill the hole.
1225
- The running-app sweep became Step A rather than a separate errand - a build can
1226
- be structurally perfect and still ship an unreadable screen - and it degrades to
1330
+ The running-app sweep became Step A rather than a separate errand - a build can
1331
+ be structurally perfect and still ship an unreadable screen - and it degrades to
1227
1332
  `SKIPPED (no booted device)` instead of halting the package validation.
1228
1333
 
1229
1334
  Nothing was removed. `testflight-validation` is a thin iOS-pinned alias (removing
@@ -1254,7 +1359,7 @@ places at once.
1254
1359
  - **Copilot's `purge` could not see a single worktree.** It discovered them with
1255
1360
  `find {repo}/.worktrees/ -name "agent-state.json"`, and no worktree carries that
1256
1361
  file: Phase 0 writes state to `$HOME/.claude/logs/multi-agent/{project}/{task-id}/`.
1257
- Verified on a repo with two live task worktrees - the marker scan returns zero
1362
+ Verified on a repo with two live task worktrees - the marker scan returns zero
1258
1363
  while `purge.sh`'s own directory enumeration finds both. So the skill reported
1259
1364
  "nothing to purge" as success with real worktrees on disk. The Claude Code command
1260
1365
  had already been fixed by delegating to `purge.sh`; the Copilot mirror never
@@ -1277,7 +1382,7 @@ places at once.
1277
1382
  `.worktrees/PROJ-*/agent-log.md` and `agent-state.json`; Phase 0 has written both
1278
1383
  to `$HOME/.claude/logs/multi-agent/{project}/{task-id}/` since the layout moved.
1279
1384
  Against the current tree the scan matches zero files, so the op reported "logs
1280
- cleared, deleted 0" as success while every real log stayed on disk - worse than a
1385
+ cleared, deleted 0" as success while every real log stayed on disk - worse than a
1281
1386
  missing command, because it reads as done. There was also no `clear-logs` command
1282
1387
  directory, so the `/multi-agent:clear-logs` entry in the Copilot help pointed at a
1283
1388
  slash command that was never installed.
@@ -1291,7 +1396,7 @@ places at once.
1291
1396
  reaching either fell through to the general sweep and was reported as the scenario
1292
1397
  asked for. Neither can be built symmetrically today: biometric has `ios_biometric`
1293
1398
  and no Android counterpart, launch timing has `android_launch_time` and no iOS
1294
- counterpart, and the file auto-detects platform - so each would work on one
1399
+ counterpart, and the file auto-detects platform - so each would work on one
1295
1400
  platform and silently do nothing on the other. Both rows are withdrawn with the
1296
1401
  reason recorded in place, rather than shipped as half-features or left advertised.
1297
1402
  Advertised scenarios and implemented sections now match one to one.
@@ -1388,7 +1493,7 @@ own toolkit says apply, and clean up after itself.
1388
1493
  so `bugfix` / `feature` / `refactor` / `chore` had no skill dispatch at all:
1389
1494
  whichever skills the host surfaced by description match were the ones used, and
1390
1495
  nothing recorded or required any of them. That was the dev-side half of the gap
1391
- v14.0.0 closed on the review side - review asked "was this built to the rules it
1496
+ v14.0.0 closed on the review side - review asked "was this built to the rules it
1392
1497
  was supposed to follow" while nobody had chosen any rules.
1393
1498
 
1394
1499
  The routing table is NOT copied into this repo. A second copy would drift the
@@ -1398,7 +1503,7 @@ own toolkit says apply, and clean up after itself.
1398
1503
  6-row table). Routed skills land in `telemetry.skillCalls[]` with
1399
1504
  `routedBy: "<toolkit>:index@<version>"`, so Phase 4 conformance can hold the run to
1400
1505
  what its own toolkit chose. An absent or disabled toolkit is a recorded no-op, not
1401
- a halt - a backend repo has no toolkit and must still run.
1506
+ a halt - a backend repo has no toolkit and must still run.
1402
1507
  Contract: `refs/features/stack-skill-routing.md`.
1403
1508
 
1404
1509
  - **Phase 6 removes a task's worktree once its PR is open** (`worktree-finalize.sh`,
@@ -1406,7 +1511,7 @@ own toolkit says apply, and clean up after itself.
1406
1511
  salvages `agent-state.json`, `phase-tracker.json`, `triage-output.json`,
1407
1512
  `.pipeline/`, `.build.log`, `.test.log` and `.review-diff.txt` into the log dir
1408
1513
  first, because Phase 7's triage-memory ingest, the learnings-ledger distill,
1409
- `render-work-summary.sh`, `:resume`, `:status` and `:log` all read them - and the
1514
+ `render-work-summary.sh`, `:resume`, `:status` and `:log` all read them - and the
1410
1515
  first three are `[ -f ]`-guarded, so a removal without salvage would have degraded
1411
1516
  silently rather than failing.
1412
1517
 
@@ -1446,7 +1551,7 @@ own toolkit says apply, and clean up after itself.
1446
1551
  `f.file` from the diff-risk report, which declares and emits `path`, so each
1447
1552
  finding carried `file: undefined` and read "Test file 'undefined' shrinks". That
1448
1553
  is useless to the developer and rejected by `reviewer-output.schema.json`, which
1449
- requires `file` with `minLength: 1` - on the one gate that deliberately has no
1554
+ requires `file` with `minLength: 1` - on the one gate that deliberately has no
1450
1555
  opt-out. It stayed invisible because the smoke's own fixtures used `"file"`, the
1451
1556
  key the bug read: the fixture matched the bug instead of the schema. Fixtures
1452
1557
  corrected, and a new assertion checks the finding names a real path (verified by
@@ -1458,14 +1563,13 @@ own toolkit says apply, and clean up after itself.
1458
1563
  elsewhere in those files' prose. Added check 4b: every name in an inventory must be
1459
1564
  a command that exists in the tree (verified against a planted `ghostcmd`).
1460
1565
 
1461
-
1462
1566
  ## [14.0.0] - 2026-07-29
1463
1567
 
1464
1568
  The `--dev` family reviews its own work, and review now checks the code against the
1465
1569
  criteria it was supposed to be built to instead of general good taste.
1466
1570
 
1467
1571
  Major because a command was renamed: `/multi-agent:finish` is now `/multi-agent:ship`.
1468
- No alias is kept - `installCommands()` replaces the command tree wholesale, so a clean
1572
+ No alias is kept - `installCommands()` replaces the command tree wholesale, so a clean
1469
1573
  cut leaves nothing half-migrated.
1470
1574
 
1471
1575
  ### Added
@@ -1478,7 +1582,7 @@ cut leaves nothing half-migrated.
1478
1582
  without them, while review judges work that now exists and has no substitute. Accepted
1479
1583
  blocking findings return to Phase 3 under the existing 3-iteration hard kill; the
1480
1584
  autopilot variants auto-fix without prompting and halt on the existing rework-storm
1481
- circuit breaker rather than committing. No new machinery for either - both were already
1585
+ circuit breaker rather than committing. No new machinery for either - both were already
1482
1586
  built and became reachable the moment Phase 4 entered the phase set.
1483
1587
  - **Phase 4 Step 1.78, criteria resolution** (`skill-conformance.mjs`, zero LLM). Resolves
1484
1588
  which rule registries apply to this diff, scoped to its languages and paths, and writes
@@ -1493,7 +1597,7 @@ cut leaves nothing half-migrated.
1493
1597
  - **Every registry declares its own `scope`** (`languages`, `paths`, `excludePaths`,
1494
1598
  `notCovered`), and per-rule `scope` narrows it further. Measured before this landed: only
1495
1599
  2 of the iOS registry's 99 rules carried any applicability field, and the rest wrote their
1496
- scope as English prose inside `mechanism` - so an Objective-C or UIKit diff would have
1600
+ scope as English prose inside `mechanism` - so an Objective-C or UIKit diff would have
1497
1601
  collected all 99 SwiftUI-shaped rules, manufacturing findings and burying the real ones.
1498
1602
  - `references/rules.yml` for `apple-archive-compliance` (18 rules) and
1499
1603
  `google-play-compliance` (21), converted from their existing SKILL.md tables with IDs and
@@ -1502,7 +1606,7 @@ cut leaves nothing half-migrated.
1502
1606
  - **Exception-marker audit**: expired, reason-less, expiry-less, or unknown-ID
1503
1607
  `standard:exception(...)` markers become rule-ID-bearing findings. The marker template is
1504
1608
  read from the registry, never hardcoded, so a registry with different comment syntax works.
1505
- - `state.telemetry.skillCalls[]` - Phase 3 records each skill, plugin skill and guide it
1609
+ - `state.telemetry.skillCalls[]` - Phase 3 records each skill, plugin skill and guide it
1506
1610
  consulted, with the files it applied them to.
1507
1611
  - `prefs.global.skillConformance.blockOnCoverageGap` (default **false**) and
1508
1612
  `prefs.global.ship.autoFix`, the latter referenced by the tail command's spec since it
@@ -1522,13 +1626,13 @@ cut leaves nothing half-migrated.
1522
1626
  - **Reviewers cite rule IDs.** `reviewer-output.schema.json` 1.0.0 -> 1.1.0 adds `ruleId`,
1523
1627
  `criteriaSource` and the per-rule `conformance[]` array; `triage-output.schema.json`
1524
1628
  3.2.0 -> 3.3.0 carries both through triage. `code-reviewer.md` gains a `${CRITERIA}`
1525
- injection slot - the phase doc had claimed for some time that "skills are injected into
1629
+ injection slot - the phase doc had claimed for some time that "skills are injected into
1526
1630
  reviewer prompt context" while the agent definition had no slot for them, which is exactly
1527
1631
  why review could not cite a rule.
1528
1632
  - `validate-reviewer.mjs` enforces the checklist with `--criteria`: a selected ID with no
1529
1633
  verdict, a verdict for an ID that was never selected, a `conformant` row with no file
1530
1634
  evidence, and a `violated` row with no matching finding all fail. Without this the field
1531
- would be decoration - the validator is hand-written and does not apply
1635
+ would be decoration - the validator is hand-written and does not apply
1532
1636
  `additionalProperties`, so any array at all would have passed.
1533
1637
  - `${CRITERIA}` lives in the shared cacheable prefix, identical for every reviewer.
1534
1638
  Subsetting it per reviewer would invalidate the prefix for the whole panel and re-bill the
@@ -1550,7 +1654,7 @@ cut leaves nothing half-migrated.
1550
1654
 
1551
1655
  - **`ios-coding-standard` in the repo was a version behind the installed copy** (95 rules
1552
1656
  v1.0.0 vs 99 rules v1.1.0). Because `installSkills()` copies the repo over the
1553
- destination, the next `/multi-agent:update` would have downgraded a user's registry -
1657
+ destination, the next `/multi-agent:update` would have downgraded a user's registry -
1554
1658
  harmless while nothing read it, a correctness regression the moment Phase 4 blocks on rule
1555
1659
  IDs. Resynced to 99 rules.
1556
1660
  - `modules/*.yml` overlays and `references/EXAMPLES.md` are deliberately NOT shipped: they
@@ -1576,8 +1680,8 @@ gates are named per item.
1576
1680
 
1577
1681
  - **The Tier 2 Figma PAT could not be found on any migrated install.** `migrate-prefs.mjs`
1578
1682
  consolidated `keychainMapping.figma_pat` into `.figma` and deleted the old key, but the
1579
- setup wizard kept *writing* `figma_pat` (so the next migration deleted the mapping it
1580
- had just created), both Tier 2 fetchers kept *reading* it, and the failure text told the
1683
+ setup wizard kept _writing_ `figma_pat` (so the next migration deleted the mapping it
1684
+ had just created), both Tier 2 fetchers kept _reading_ it, and the failure text told the
1581
1685
  user to map the one key guaranteed not to survive. Tier 2 reported `missing-token` while
1582
1686
  a valid PAT sat under the new name. Claude Code's MCP tier masked it; Copilot and Codex,
1583
1687
  which serve no Figma MCP tools at all, fell straight to Tier 3. The lookup now lives once
@@ -1636,7 +1740,7 @@ gates are named per item.
1636
1740
  written. Copilot now rewrites the trees it owns, and installs `rules/` as well, closing
1637
1741
  the same silent-nothing fallback Codex's installer already documented.
1638
1742
  - **`phase-tracker.sh` never reclaimed a stale lock on Linux.** It tried `stat -f %m`
1639
- before `stat -c %Y`, and on GNU coreutils `stat -f` is a *valid* flag (`--file-system`,
1743
+ before `stat -c %Y`, and on GNU coreutils `stat -f` is a _valid_ flag (`--file-system`,
1640
1744
  where `%m` is the mount point): it succeeds, returns something like `/`, the `||` never
1641
1745
  fires, and the age arithmetic runs on a path. Every tracker call then spun the full ~5s
1642
1746
  bound and fell open with a warning. Every other `stat` call site in the repo already had
@@ -1651,7 +1755,7 @@ gates are named per item.
1651
1755
  the previous generation. Now `claude-opus-5` and `claude-sonnet-5`, with the rung/ID split
1652
1756
  stated in the fallback contract. Gate: a generation guard in `smoke-model-fallback.sh`.
1653
1757
  - **`rules/pipeline-output-formatting.md` did not exist.** Two shipped skills named it as
1654
- the PR-body contract to follow. The install-layout smoke had been *reporting* paths that
1758
+ the PR-body contract to follow. The install-layout smoke had been _reporting_ paths that
1655
1759
  exist on no host as a note rather than failing on them, which is how it survived; that
1656
1760
  claim is now strict.
1657
1761
  - **A pre-migration preferences file failed validation before the migration could fix it.**
@@ -1708,7 +1812,7 @@ gates are named per item.
1708
1812
 
1709
1813
  - **Every fetcher died on a Copilot-only or Codex-only install.** Eleven runtime scripts
1710
1814
  loaded `credential-store-resolver.sh` as `. "$HOME/.claude/lib/..." || . <next> || {
1711
- error }`, all of them under `set -e`. Sourcing a file that does not exist aborts the
1815
+ error }`, all of them under `set -e`. Sourcing a file that does not exist aborts the
1712
1816
  shell outright - `||` included - so on a host without `~/.claude/lib` the chain reached
1713
1817
  neither its later candidates nor its error branch: bare exit 1, no message. Reordering
1714
1818
  does not help, because whichever candidate is absent aborts at that point. All eleven
@@ -1799,7 +1903,7 @@ gates are named per item.
1799
1903
  `--dev` skips the LLM phases, not Phase 0's pickers.
1800
1904
  - **An unreachable external source is announced instead of absorbed.** Exit code `3`
1801
1905
  used to mean "mark it failed and continue", so an expired token and a VPN-off remote
1802
- both reached the analysis phase as *no data*, indistinguishable from a ticket that
1906
+ both reached the analysis phase as _no data_, indistinguishable from a ticket that
1803
1907
  referenced nothing. The run then planned from a partial picture and reported success.
1804
1908
  `external-context-injection.md` now classifies the stderr and surfaces a decision -
1805
1909
  refresh the credential, connect the VPN and retry, supply a current URL, or continue
@@ -1821,7 +1925,7 @@ gates are named per item.
1821
1925
  `api.figma.com` returned 403 for a healthy token and would have sent the user to
1822
1926
  regenerate something that worked. It is now reported as not-probeable, with liveness
1823
1927
  left to `figma-mcp-refresh.sh`, which owns the grant.
1824
- - A failed `curl` wrote `000` through `write-out` *and* triggered the `|| echo "000"`
1928
+ - A failed `curl` wrote `000` through `write-out` _and_ triggered the `|| echo "000"`
1825
1929
  fallback, producing `000000` and a bogus verdict for what was simply a closed VPN.
1826
1930
  Any non-three-digit status now reads as `probe-error` rather than being dressed up as
1827
1931
  a service verdict.
@@ -1858,7 +1962,7 @@ gates are named per item.
1858
1962
  ### Fixed
1859
1963
 
1860
1964
  - **`build-stack-plugins.mjs` never propagated content edits.** It copied a skill only
1861
- when the skill *set* changed, so editing a routed skill in `shared/external` reached
1965
+ when the skill _set_ changed, so editing a routed skill in `shared/external` reached
1862
1966
  no plugin while the generator reported "all plugins up to date" - which read as
1863
1967
  confirmation. This falsified the single-authoring-source guarantee the whole
1864
1968
  `shared/external` design rests on. Found by converting banned punctuation in
@@ -1890,7 +1994,7 @@ The iOS coding standard reaches all three hosts, and works outside the pipeline.
1890
1994
  The 95-rule registry lived as a pipeline-local routine. Two consequences, both
1891
1995
  measured: only Claude Code could reach it, because `local-only` commands are
1892
1996
  deliberately never synced (verified absent on Copilot CLI and Codex CLI), and it
1893
- applied only when the routine was invoked explicitly - nothing pulled the rules in
1997
+ applied only when the routine was invoked explicitly - nothing pulled the rules in
1894
1998
  while Swift was being written. The plugin's own `reference/code-style` skill is 154
1895
1999
  lines of prose with zero rule IDs, so the registry was not reachable that way either.
1896
2000
 
@@ -1934,7 +2038,6 @@ lines of prose with zero rule IDs, so the registry was not reachable that way ei
1934
2038
  predates this change and `tools/validate.py` warns on it; routing `knowledge/` behind
1935
2039
  the plugin's `index` skill is the fix and is separate work.
1936
2040
 
1937
-
1938
2041
  ## [13.1.0] - 2026-07-27
1939
2042
 
1940
2043
  Six gates, from one branch that spent half its commits on rework.
@@ -1944,7 +2047,7 @@ chain, measured from the run's own artefacts: Phase 0 reported `completed` havin
1944
2047
  written only `tracker-state.json`, so `agent-state.json` and its `taskType` never
1945
2048
  existed, so Phase 3's component dispatch could not fire. The stack plugin already
1946
2049
  ships `create-screen`, `figma-validate` (7 criteria including design-token compliance
1947
- and Code Connect strategy) and `figma-review` (14-item checklist) - none of them ran.
2050
+ and Code Connect strategy) and `figma-review` (14-item checklist) - none of them ran.
1948
2051
  Padding came out 16 where the frame said `Spacing/12`. Three of six commits were fixes,
1949
2052
  the last a full sheet rebuild.
1950
2053
 
@@ -1961,7 +2064,7 @@ invokes a skill that was already written but never called.
1961
2064
  gate is testable without reproducing a run.
1962
2065
  - **Phase 4 Step 2.8, visual conformance gate.** Runs `figma-review` and
1963
2066
  `/multi-agent:design-check` for component/screen work, with the coverage gate, and
1964
- asserts Code Connect was **published** rather than merely written - a
2067
+ asserts Code Connect was **published** rather than merely written - a
1965
2068
  `*.figma.swift` on disk with "Not published" in Figma is a binding that exists for
1966
2069
  nobody. `design-check` had been a command with no phase invoking it, so the only
1967
2070
  thing standing between a build and visual drift was the user opening the app.
@@ -1976,14 +2079,14 @@ invokes a skill that was already written but never called.
1976
2079
 
1977
2080
  - **Component dispatch halts instead of degrading.** The contract used to send an
1978
2081
  incomplete-state component task down the generic TDD path while the next sentence
1979
- said "never silently skip the Figma work" - taking the generic path *is* skipping
2082
+ said "never silently skip the Figma work" - taking the generic path _is_ skipping
1980
2083
  it. That wording authorised the exact failure above.
1981
2084
  - **Dispatch routes on scope, not just platform.** A screen and a component are
1982
2085
  different jobs and the plugin ships a skill for each; routing a screen to the
1983
2086
  component skill is why one run produced entities and a mapper but left the screen
1984
2087
  half-wired. `figma-validate` now runs before the create skill.
1985
2088
  - **Phase 1 captures spacing by token name, per atom.** Phase 3 is forbidden from
1986
- calling Figma, so a pixel number - or a missing entry - is unrecoverable later. A
2089
+ calling Figma, so a pixel number - or a missing entry - is unrecoverable later. A
1987
2090
  UI frame with no spacing entries is a capture failure, not an empty frame.
1988
2091
  - **Phase 3 records that generated trees are not editable.** A mock fixture went into
1989
2092
  the generated tree; the fix moved it to the custom tree and registered the scenario
@@ -1993,7 +2096,6 @@ invokes a skill that was already written but never called.
1993
2096
  Planning by design, so there is no phase that turns a document into a plan. The doc
1994
2097
  becomes context for one pass and work lands in whatever order it was read.
1995
2098
 
1996
-
1997
2099
  ## [13.0.0] - 2026-07-27
1998
2100
 
1999
2101
  > **Why major and not minor.** The additions here are additive, but two defaults
@@ -2003,7 +2105,6 @@ invokes a skill that was already written but never called.
2003
2105
  > comparing Codex on skill directories would read a correct install as drift).
2004
2106
  > Per the versioning policy that is a changed default, not a new option.
2005
2107
 
2006
-
2007
2108
  Codex CLI becomes a third supported host, and a new pre-submission validation command.
2008
2109
 
2009
2110
  ### Added
@@ -2014,19 +2115,19 @@ Codex CLI becomes a third supported host, and a new pre-submission validation co
2014
2115
  with per-agent model and reasoning effort, hooks, MCP and a plugin marketplace,
2015
2116
  so it earns the same treatment as the other two rather than an adapter.
2016
2117
  `install/codex.mjs` writes `~/.codex/{skills/multi-agent,multi-agent-refs,
2017
- agents/*.toml,prompts/multi-agent.md,scripts,lib,schemas,rules}` plus a managed
2118
+ agents/*.toml,prompts/multi-agent.md,scripts,lib,schemas,rules}` plus a managed
2018
2119
  span in `~/.codex/AGENTS.md`, and registers the dev-toolkit MCP server through
2019
2120
  `codex mcp add` rather than hand-merging TOML that Codex owns.
2020
2121
  - **`/multi-agent:testflight-validation`** (command 43). Three gates, each seeing
2021
2122
  what the others structurally cannot: the static 18-rule archive audit, Apple's
2022
2123
  own `altool --validate-app`, and a Review-Guidelines check against repo evidence.
2023
- ITMS codes are mapped to the rule each implies. It validates only - never
2024
- `--upload-app` - so a validation run cannot ship a build by accident.
2124
+ ITMS codes are mapped to the rule each implies. It validates only - never
2125
+ `--upload-app` - so a validation run cannot ship a build by accident.
2025
2126
  - **App Store Connect credentials in `/multi-agent:setup`**, inside the Step 1
2026
2127
  discovery / Step 2 mapping / Step 3b onboarding flow alongside Jira and
2027
2128
  Bitbucket, not as a late add-on: a user who already has one in their keychain
2028
2129
  gets it mapped automatically. Tier 1 is an API key, tier 2 an Apple ID plus an
2029
- app-specific password - which matters because creating an API key needs an
2130
+ app-specific password - which matters because creating an API key needs an
2030
2131
  Admin or App Manager role many developers on a corporate team do not have.
2031
2132
 
2032
2133
  ### Changed
@@ -2042,8 +2143,8 @@ Codex CLI becomes a third supported host, and a new pre-submission validation co
2042
2143
  gpt-5.6 @ medium, triage at max) with two measured constraints written into the
2043
2144
  contract: a `spawn_agent` that sets `model` without `fork_turns: "none"`
2044
2145
  **silently inherits the parent model**, collapsing the panel onto one
2045
- perspective; and 4 concurrency slots *including the orchestrator* make three
2046
- reviewers the ceiling. Single-vendor caveat recorded - consensus among three
2146
+ perspective; and 4 concurrency slots _including the orchestrator_ make three
2147
+ reviewers the ceiling. Single-vendor caveat recorded - consensus among three
2047
2148
  OpenAI models is weaker evidence than the same consensus on a two-vendor host.
2048
2149
  - `tracker-contract.md` gains the `codex` visual channel: the native `update_plan`
2049
2150
  tool, with its no-parallel-call and no-plan-mode caveats.
@@ -2063,7 +2164,7 @@ Codex CLI becomes a third supported host, and a new pre-submission validation co
2063
2164
  - **The Phase 0 branch-collision probe read a failed probe as "no collision".**
2064
2165
  With `2>/dev/null` and an empty-output test, an auth or network failure was
2065
2166
  indistinguishable from "the ref does not exist", so the run created a branch
2066
- that already existed on the remote - surfacing as a rejected push at Phase 6,
2167
+ that already existed on the remote - surfacing as a rejected push at Phase 6,
2067
2168
  far from its cause. Exit codes are now distinguished (0 exists, 2 free, anything
2068
2169
  else unknown-and-recorded).
2069
2170
  - **`/multi-agent:update` deleted the Codex prompt on every run**, pruning
@@ -2076,14 +2177,14 @@ Codex CLI becomes a third supported host, and a new pre-submission validation co
2076
2177
  (added in v12.11.0) holds the banned characters as its own assertion list and
2077
2178
  was never allowlisted.
2078
2179
  - **Half-English pickers on Turkish runs: six shipped files contradicted the
2079
- canonical language matrix.** `rules.md` is unambiguous - `AskUserQuestion`
2180
+ canonical language matrix.** `rules.md` is unambiguous - `AskUserQuestion`
2080
2181
  `question` and `options[].description` render in `outputLanguage`, only `label`,
2081
2182
  `header` and host chrome are pinned to English. But `/multi-agent:setup` Step 0
2082
2183
  claimed `promptLanguage` governs "interactive pickers and prompts ... Picker UI is
2083
2184
  always English", `/multi-agent:language` claimed "confirmation prompts ... are
2084
2185
  authored in English. Only the assistant's free-form replies follow
2085
2186
  `outputLanguage`", and four more files said variations of the same. Those are the
2086
- two commands a user goes to *configure* this, so the model followed whichever
2187
+ two commands a user goes to _configure_ this, so the model followed whichever
2087
2188
  canonical-looking doc it read first and gate questions came out English. All six
2088
2189
  corrected to describe `promptLanguage`'s real scope: the button and chip chrome,
2089
2190
  never the question a user reads.
@@ -2093,7 +2194,7 @@ Codex CLI becomes a third supported host, and a new pre-submission validation co
2093
2194
  - New `smoke-language-matrix.sh`: asserts `rules.md` still pins the per-field
2094
2195
  matrix, then greps every shipped command / skill / ref for the seven phrasings
2095
2196
  that actually shipped and contradicted it. Two authoritative docs giving opposite
2096
- answers is a spec with two answers, not a wording nit - the gate found a sixth
2197
+ answers is a spec with two answers, not a wording nit - the gate found a sixth
2097
2198
  violation (`_input-parser.md`) that the manual sweep had missed.
2098
2199
 
2099
2200
  ### Gates
@@ -2105,7 +2206,7 @@ Codex CLI becomes a third supported host, and a new pre-submission validation co
2105
2206
  - `smoke-install-layout.sh` extended to three targets, including a check that every
2106
2207
  concrete `$HOME/.codex` reference **resolves on disk**. The earlier "no `.claude`
2107
2208
  references remain" check was blind to a wrongly-rewritten path, which is how
2108
- `commands/multi-agent.md` became `multi-agent-refs/commands.md` - well-formed,
2209
+ `commands/multi-agent.md` became `multi-agent-refs/commands.md` - well-formed,
2109
2210
  pointing at nothing. A miss is classified by whether the Claude counterpart
2110
2211
  exists, so broken-by-rewrite fails and broken-upstream is only reported.
2111
2212
  - New `test/codex-install.test.mjs` (24 assertions) locks the path-rewrite map,
@@ -2138,11 +2239,11 @@ bug that produced it.
2138
2239
 
2139
2240
  **All three encodings**, because a title can arrive in any of them:
2140
2241
 
2141
- | | ampersand | less-than | em-dash |
2142
- |---|---|---|---|
2143
- | named | `&amp;` | `&lt;` | `&mdash;` |
2144
- | decimal | `&#38;` | `&#60;` | `&#8212;` |
2145
- | hex | `&#x26;` | `&#x3C;` | `&#x2014;` |
2242
+ | | ampersand | less-than | em-dash |
2243
+ | ------- | --------- | --------- | ---------- |
2244
+ | named | `&amp;` | `&lt;` | `&mdash;` |
2245
+ | decimal | `&#38;` | `&#60;` | `&#8212;` |
2246
+ | hex | `&#x26;` | `&#x3C;` | `&#x2014;` |
2146
2247
 
2147
2248
  The first version of this fix handled only the named column, which is the same bug
2148
2249
  fixed for one spelling out of three.
@@ -2360,7 +2461,7 @@ entry, so the count would have under-reported by 100+ files.
2360
2461
  ### Four gates that were tested, green, and never ran
2361
2462
 
2362
2463
  Every gate in this repo checks its own behaviour. Nothing checked that a gate is
2363
- *reachable*. Four were not - each with a passing unit test and a passing smoke,
2464
+ _reachable_. Four were not - each with a passing unit test and a passing smoke,
2364
2465
  and no reference from `pipeline/commands/`, `multi-agent-refs/`, `agents/`,
2365
2466
  `skills/` or `install/`:
2366
2467
 
@@ -2521,7 +2622,7 @@ while fixing them.
2521
2622
  Two threads. The design-check command gains a scenario inventory, a coverage
2522
2623
  gate and MCP-currency gating, and the refactor/sync pair learns to research,
2523
2624
  audit and ship the companion dev-toolkit MCP server. Alongside that, a defect
2524
- sweep found eight gates and features that exited 0 while doing nothing - six
2625
+ sweep found eight gates and features that exited 0 while doing nothing - six
2525
2626
  of them guarded by a smoke test whose fixture had the wrong shape, so the suite
2526
2627
  stayed green the whole time.
2527
2628
 
@@ -2553,14 +2654,14 @@ Requires `@mmerterden/dev-toolkit-mcp` >= v2.9.0 for the App Store audit path.
2553
2654
  `smoke-personal-data.sh --root`, keeping one pattern list.
2554
2655
  - **shellcheck moves from `error` to `warning` severity.** `error` returns zero
2555
2656
  findings across all 175 scripts, so the gate could not catch this repo's
2556
- actual defect class - SC2046 is a warning. Noisy codes are excluded with a
2657
+ actual defect class - SC2046 is a warning. Noisy codes are excluded with a
2557
2658
  per-code rationale. Four real findings surfaced and are fixed, including two
2558
2659
  unguarded `cd` calls in `smoke-shadow-git.sh`, one preceding a relative
2559
2660
  `rm -rf`.
2560
2661
  - **`credential-store.sh` resolves logical keys through
2561
2662
  `prefs.global.keychainMapping`.** `get github` searched the backend for a
2562
2663
  credential literally named "github" and returned empty with exit 1 -
2563
- indistinguishable from "no such credential" - because the entry is named by
2664
+ indistinguishable from "no such credential" - because the entry is named by
2564
2665
  the mapping. Applied in `get`/`set`/`delete`, falling back to the logical key
2565
2666
  when no mapping exists.
2566
2667
 
@@ -2770,6 +2871,7 @@ all project specifics live in per-project `figma-config` / prefs.
2770
2871
  ## [12.2.0] - 2026-07-21
2771
2872
 
2772
2873
  ### Added
2874
+
2773
2875
  - **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).
2774
2876
  - 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.
2775
2877
  - **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.
@@ -2778,6 +2880,7 @@ all project specifics live in per-project `figma-config` / prefs.
2778
2880
  ## [12.1.1] - 2026-07-21
2779
2881
 
2780
2882
  ### Changed
2883
+
2781
2884
  - **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.
2782
2885
 
2783
2886
  ---
@@ -2785,20 +2888,25 @@ all project specifics live in per-project `figma-config` / prefs.
2785
2888
  ## [12.1.0] - 2026-07-21
2786
2889
 
2787
2890
  ### Security
2891
+
2788
2892
  - **`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`).
2789
2893
  - **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.)
2790
2894
  - **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.
2791
2895
 
2792
2896
  ### Changed
2897
+
2793
2898
  - **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.
2794
2899
 
2795
2900
  ### Refactored
2901
+
2796
2902
  - **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.
2797
2903
 
2798
2904
  ### Added
2905
+
2799
2906
  - **`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.
2800
2907
 
2801
2908
  ### Docs
2909
+
2802
2910
  - 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/`.
2803
2911
 
2804
2912
  ---
@@ -2918,7 +3026,7 @@ research, bug hunt, category scoring, upstream-drift check).
2918
3026
  - **False-success family.** `multi-repo-pipeline.sh` no longer prints
2919
3027
  "committed/pushed" on failure; `repo-cache.sh` keeps the previous cache when a
2920
3028
  refresh fails instead of serving an empty file for the TTL; `review-watch.sh
2921
- --watch` survives transient gh failures and its cursor can no longer move
3029
+ --watch` survives transient gh failures and its cursor can no longer move
2922
3030
  backwards.
2923
3031
  - **`issue-fetcher.sh`** classifies bare `#316` / `316` correctly (repo picker
2924
3032
  path instead of a malformed `//issues/` URL).
@@ -2928,7 +3036,7 @@ research, bug hunt, category scoring, upstream-drift check).
2928
3036
  code, ballooning snapshots with DerivedData), and `restore --files` removes
2929
3037
  files created after the snapshot instead of keeping half the change.
2930
3038
  - **macOS bash 3.2 portability**: `${URL,,}` in `fetch-swagger.sh`, `date -r
2931
- <file>` in `audit-log-rotate.sh` (which also rotates atomically now).
3039
+ <file>` in `audit-log-rotate.sh` (which also rotates atomically now).
2932
3040
  - **Glob translation** in `triage-memory.mjs` / `test-gap-scan.mjs`: `**` no
2933
3041
  longer breaks after the `*` pass (nested-path filters match again).
2934
3042
  - **`migrate-prefs.mjs`** errors on unknown flags (a `--dry-rnu` typo used to
@@ -3024,7 +3132,7 @@ Self-audit patch (found by running `/multi-agent:refactor` on the pipeline itsel
3024
3132
  lists it in the PRESERVED summary.
3025
3133
  - **Fix (gate regression): `lint-skills` scoped to `pipeline/skills`.** The
3026
3134
  v11.3.0 command-layout migration made command files match `find pipeline -name
3027
- SKILL.md`; slash commands have no `name:` frontmatter, so the skill linter
3135
+ SKILL.md`; slash commands have no `name:` frontmatter, so the skill linter
3028
3136
  started erroring on all 37 of them (latent because CI is billing-paused and
3029
3137
  releases were published manually). The linter now only checks actual skills.
3030
3138
  - **Fix: `.skills-index.json` is idempotent** - dropped the `generatedAt`
@@ -3035,7 +3143,7 @@ Self-audit patch (found by running `/multi-agent:refactor` on the pipeline itsel
3035
3143
  - **Fix: install "dev-only excluded" count** is now the real file count (the
3036
3144
  `fixtures/` directory was under-counted as one entry).
3037
3145
  - **Security (supply-chain):** `release.yml` publishes with `npm publish
3038
- --provenance` (`id-token: write`); all GitHub Actions pinned to commit SHAs;
3146
+ --provenance` (`id-token: write`); all GitHub Actions pinned to commit SHAs;
3039
3147
  added `.github/dependabot.yml` (actions + npm dev deps); `credential-store.sh`
3040
3148
  escapes single quotes in the Windows PowerShell paths.
3041
3149
  - **New: `eval-mine-corpus.mjs`** turns recorded triage decisions
@@ -3147,7 +3255,7 @@ command is a breaking surface change).
3147
3255
  skeleton (Task/Story: Detailed Description, Scope, Acceptance Criteria, Test
3148
3256
  Scenarios; Bug: Detailed Description, Steps to Reproduce, Expected/Actual
3149
3257
  Result, Environment). Conditional sections (Design Reference, API Contract /
3150
- Swagger, Screenshots, Notes) render only when their trigger is present - no
3258
+ Swagger, Screenshots, Notes) render only when their trigger is present - no
3151
3259
  empty placeholder headings, nothing invented.
3152
3260
  - **Test Scenarios pulled from Jira convention.** Detects linked Xray/Zephyr test
3153
3261
  issues or a dominant test-scenario heading style and reuses it; falls back to an
@@ -3290,7 +3398,7 @@ Live tracker UX: per-tile cost, a real "currently doing X" line, and tracker con
3290
3398
  history (elapsed, tokens, USD) survives, Phase 5 is closed with a Result
3291
3399
  meta, the Claude Code TaskList is rebuilt from state, and one line
3292
3400
  summarizes the inherited history (`Continuing <id>: phases 0-3 finished
3293
- earlier (12m, 38.4k tok, ~$0.74)`). `add` is idempotent to make this safe.
3401
+ earlier (12m, 38.4k tok, ~$0.74)`). `add` is idempotent to make this safe.
3294
3402
  - **`/multi-agent:resume` rebuilds the phase tiles** from `tracker-state.json`
3295
3403
  (the contract documented it; the command now actually does it).
3296
3404
  - **Phase 0 + 7 token forwarding**: the clarifier call and the Phase 7 report
@@ -3591,7 +3699,7 @@ Generic SwiftUI interaction coverage for the figma-to-swiftui pipeline. Three
3591
3699
  new cross-cutting integration skills (navigation, overlays, bottom sheets) plus
3592
3700
  a reconcile-and-extend workflow, all native-SwiftUI-first with an optional
3593
3701
  per-project `ui.*` config hook so the same capabilities work on any SwiftUI
3594
- codebase - no app-specific coupling. Phase 3D dev detection and Phase 4 review
3702
+ codebase - no app-specific coupling. Phase 3D dev detection and Phase 4 review
3595
3703
  both consume them.
3596
3704
 
3597
3705
  ### Added
@@ -3609,12 +3717,12 @@ both consume them.
3609
3717
  component against current Figma (drift-heal) + additively extend it for a
3610
3718
  need, behind a mandatory human gate; distinct from `figma-to-swiftui`
3611
3719
  (build new), `figma-mend` (rebuild), and `figma-fix` (review bug).
3612
- - **`animated-gradient-border` UI pattern** (`figma-ui-patterns/patterns/`) -
3720
+ - **`animated-gradient-border` UI pattern** (`figma-ui-patterns/patterns/`) -
3613
3721
  self-contained native-SwiftUI recipe (angular-gradient stroke + Reduce-Motion
3614
3722
  gate), no external dependency.
3615
- - **`ui` block in `figma-project-config.schema.json`** - optional
3723
+ - **`ui` block in `figma-project-config.schema.json`** - optional
3616
3724
  `navigationSystem` / `overlaySystem` / `sheetSystem` (`mode: native | custom`
3617
- + type names). Absent → native SwiftUI.
3725
+ - type names). Absent → native SwiftUI.
3618
3726
 
3619
3727
  ### Changed
3620
3728
 
@@ -3650,7 +3758,7 @@ stale worktrees, and validates state before resume.
3650
3758
  Opus. This also resolves the prior CLAUDE.md-vs-phase-doc inconsistency
3651
3759
  (CLAUDE.md already documented Opus for these stages).
3652
3760
  - **Model fallback ladder is now `opus -> sonnet`** (was `fable -> opus ->
3653
- sonnet`). `modelFallback.fallbackModel` default is `sonnet`. The
3761
+ sonnet`). `modelFallback.fallbackModel` default is `sonnet`. The
3654
3762
  `premiumTierUntil` date gate is kept as a generic mechanism for any future
3655
3763
  plan-window-limited premium tier. `model-fallback.md` bumped to v10.1.0 and
3656
3764
  `smoke-model-fallback.sh` updated to assert the new ladder + Opus personas.
@@ -3728,12 +3836,12 @@ token ledger agree on what an input token is.
3728
3836
  - **Prompt-cache reuse ratio in metrics.** `aggregate-metrics.mjs` now sums
3729
3837
  `tokens_cached` per model and reports `cache_ratio = cached / (in + cached)`
3730
3838
  - the share of input tokens served from the host prompt cache - per model and
3731
- overall, in all three output modes (json / markdown / text). This is the
3732
- single number that says whether cache-friendly prompt structuring is paying
3733
- off. Backward-compatible: a phase that omits `tokens_cached` reads as 0%.
3839
+ overall, in all three output modes (json / markdown / text). This is the
3840
+ single number that says whether cache-friendly prompt structuring is paying
3841
+ off. Backward-compatible: a phase that omits `tokens_cached` reads as 0%.
3734
3842
  - **PR review iteration counter derived from the PR itself.** On a `needs_work`
3735
3843
  post, the iteration number is re-derived as `max("iteration #N" already on the
3736
- PR) + 1` instead of trusting agent-state. Standalone `/multi-agent:review`
3844
+ PR) + 1` instead of trusting agent-state. Standalone `/multi-agent:review`
3737
3845
  runs use a fresh task id each time, so the PR comments are the only reliable
3738
3846
  cross-run source of truth.
3739
3847
 
@@ -3743,7 +3851,7 @@ token ledger agree on what an input token is.
3743
3851
  `render-agent-log-cost.sh` treated `tokens_in` as cache-inclusive and
3744
3852
  subtracted the cached count (`fresh = in - min(cached, in)`), while the new
3745
3853
  `aggregate-metrics.mjs` treated `tokens_in` as cache-exclusive (`total =
3746
- in + cached`). Fed real data with high cache reuse (`cached > in`), the
3854
+ in + cached`). Fed real data with high cache reuse (`cached > in`), the
3747
3855
  renderer collapsed `fresh` to 0 and underpriced the row. Standardized on the
3748
3856
  **cache-exclusive** convention that matches the host usage report
3749
3857
  (`input_tokens` and `cache_read_input_tokens` are disjoint): the renderer no
@@ -3786,7 +3894,7 @@ reproduced locally by putting GNU coreutils' gnubin ahead of PATH.
3786
3894
  `search-logs.sh` (since-filter, scoring, JSON/TSV output) and
3787
3895
  `repo-cache.sh` TTLs. Probe order flipped to GNU-first (`stat -c` first;
3788
3896
  BSD rejects `-c`, so the fallback chain is safe both ways). `date -r
3789
- <epoch>` display calls gained a GNU `date -d @` fallback.
3897
+ <epoch>` display calls gained a GNU `date -d @` fallback.
3790
3898
  - **Hardcoded maintainer layout `$HOME/multi-agent-pipeline` removed** from
3791
3899
  `smoke-schema-validation.sh` (preferences-template path) and
3792
3900
  `smoke-pat-audit.sh` (.gitignore audit-log check) - both now derive the
@@ -4138,10 +4246,12 @@ Disk hygiene + correctness hardening, all gate-backed.
4138
4246
  Toward proven (not just designed): measure the features instead of asserting them, and ship the evidence-collection harness.
4139
4247
 
4140
4248
  ### Added
4249
+
4141
4250
  - **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.
4142
4251
  - **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.
4143
4252
 
4144
4253
  ### Notes
4254
+
4145
4255
  - 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.
4146
4256
  - 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.
4147
4257
 
@@ -4150,6 +4260,7 @@ Toward proven (not just designed): measure the features instead of asserting the
4150
4260
  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.
4151
4261
 
4152
4262
  ### Added
4263
+
4153
4264
  - **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`.
4154
4265
  - **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.
4155
4266
  - **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`.
@@ -4157,9 +4268,11 @@ Closes the structural gaps the adversarial review surfaced: the deterministic ga
4157
4268
  - **Reviewer-count contract checks** in `smoke-cross-cli-behavior.sh`: locks Claude=2 / Copilot=3 + the documented adapter-platform reviewer set against drift.
4158
4269
 
4159
4270
  ### Fixed
4271
+
4160
4272
  - 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.
4161
4273
 
4162
4274
  ### Notes (stated honestly)
4275
+
4163
4276
  - The adapter platforms still have no `PreToolUse` equivalent, so their gates are workflow-enforced (run as steps) rather than OS-blocked.
4164
4277
  - Pinned adapter models depend on the user's subscription; swap them in `REVIEWER_MODELS` / the Antigravity dropdown if a model is unavailable.
4165
4278
  - 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.
@@ -4169,12 +4282,14 @@ Closes the structural gaps the adversarial review surfaced: the deterministic ga
4169
4282
  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.
4170
4283
 
4171
4284
  ### Fixed
4285
+
4172
4286
  - **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.
4173
4287
  - **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.
4174
4288
  - **`learnings-ledger forget` rewrite is now atomic** (temp file + rename) so a crash or concurrent reader never sees a half-written ledger.
4175
4289
  - **`from-triage` scope for a top-level file** is now the filename itself, not a stray `./*` glob.
4176
4290
 
4177
4291
  ### Changed
4292
+
4178
4293
  - **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.
4179
4294
 
4180
4295
  ## [9.3.3] - 2026-05-30
@@ -4182,68 +4297,82 @@ Second round of review-driven fixes - the lower-severity findings left open in 9
4182
4297
  A 4-agent adversarial review of the v9.3.x work surfaced real defects in the features just shipped; this release fixes them.
4183
4298
 
4184
4299
  ### Fixed
4300
+
4185
4301
  - **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`)
4186
4302
  - **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`)
4187
4303
  - **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`)
4188
4304
  - **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`)
4189
4305
 
4190
4306
  ### Security
4307
+
4191
4308
  - **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)
4192
4309
 
4193
4310
  ### Changed
4311
+
4194
4312
  - 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).
4195
4313
 
4196
4314
  ## [9.3.2] - 2026-05-30
4197
4315
 
4198
4316
  ### Fixed
4317
+
4199
4318
  - **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`.
4200
4319
  - `evidence-gate.mjs` made executable to match its sibling `.mjs` scripts.
4201
4320
 
4202
4321
  ### Changed
4322
+
4203
4323
  - README "What's new" refreshed to v9.3.x (was stale at v8.8.1).
4204
4324
 
4205
4325
  ## [9.3.1] - 2026-05-30
4206
4326
 
4207
4327
  ### Fixed
4328
+
4208
4329
  - **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.
4209
4330
 
4210
4331
  ### Added
4332
+
4211
4333
  - **`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`.
4212
4334
 
4213
4335
  ## [9.3.0] - 2026-05-30
4214
4336
 
4215
4337
  ### Added
4338
+
4216
4339
  - **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`.
4217
4340
  - **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`.
4218
4341
  - **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`.
4219
4342
  - **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`.
4220
4343
 
4221
4344
  ### Changed
4345
+
4222
4346
  - **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.
4223
4347
  - **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.
4224
4348
  - Uninstall header and package description refreshed to the current 5-platform set (Cursor / Antigravity / VS Code Copilot Chat), replacing stale Windsurf/Cline references.
4225
4349
 
4226
4350
  ### Fixed
4351
+
4227
4352
  - 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.
4228
4353
 
4229
4354
  ## [9.2.0] - 2026-05-30
4230
4355
 
4231
4356
  ### Added
4357
+
4232
4358
  - **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`).
4233
4359
  - **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).
4234
4360
  - **Proactive token-budget cap** (`prefs.global.costBudget` + `cost-budget-check.mjs`): prices the phase-tracker accumulators live and warns/halts before spend runs away.
4235
4361
  - **Eval harness** expanded from 2 to 7 golden tasks across all stacks and every triage bucket.
4236
4362
 
4237
4363
  ### Changed
4364
+
4238
4365
  - **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.
4239
4366
  - **Command/skill instruction files are English** throughout (token efficiency + model comprehension); `outputLanguage` still governs all runtime user-facing text.
4240
4367
  - Phase 5 (User Test) now runs only in interactive worktree-backed modes (`dev`, `full`); every autopilot/local variant skips it.
4241
4368
  - Analysis->plan contract field names aligned across schema, validator, and phase docs; the "no MCP outside analysis" gate made enforceable (telemetry recorded + checked).
4242
4369
 
4243
4370
  ### Fixed
4371
+
4244
4372
  - 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).
4245
4373
 
4246
4374
  ### Removed
4375
+
4247
4376
  - Dead `--windsurf` / `--cline` / `--continue` / `--zed` install flags (the adapters were dropped in 8.5.4; only the advertising lingered).
4248
4377
 
4249
4378
  ## [9.1.1] - 2026-05-16
@@ -4257,7 +4386,7 @@ A 4-agent adversarial review of the v9.3.x work surfaced real defects in the fea
4257
4386
  - `pipeline/commands/multi-agent/channels.md`: 103 chars
4258
4387
  - `pipeline/skills/shared/core/multi-agent/SKILL.md`: 97 chars
4259
4388
  - `pipeline/commands/multi-agent/setup.md`: 79 chars
4260
- - Mapping: `U+2014` -> ` - `, `U+2013` -> `-`, `U+2026` -> `...`, `U+201C/D` -> `"`, `U+2018/9` -> `'`, `U+00A7` -> `section`.
4389
+ - Mapping: `U+2014` -> `-`, `U+2013` -> `-`, `U+2026` -> `...`, `U+201C/D` -> `"`, `U+2018/9` -> `'`, `U+00A7` -> `section`.
4261
4390
 
4262
4391
  ### Excluded from sweep
4263
4392
 
@@ -4301,13 +4430,13 @@ A 4-agent adversarial review of the v9.3.x work surfaced real defects in the fea
4301
4430
 
4302
4431
  Six measurable improvements on the hot path:
4303
4432
 
4304
- | Fix | Before | After | Speedup |
4305
- |---|---|---|---|
4306
- | `smoke-personal-data.sh` pattern alternation (single grep vs 22 invocations) | 3.348s | 1.545s | 2.2x |
4307
- | `smoke-no-token-prompt.sh` multi-`-e` (single grep vs 7x7=49 invocations) | 0.485s | 0.014s | 35x |
4308
- | `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 |
4309
- | `issue-fetcher.sh` python3 batch (single inline vs 7 separate calls per fetch) | 0.806s / 3 iter | 0.136s / 3 iter | 5.9x |
4310
- | `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 |
4433
+ | Fix | Before | After | Speedup |
4434
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------- |
4435
+ | `smoke-personal-data.sh` pattern alternation (single grep vs 22 invocations) | 3.348s | 1.545s | 2.2x |
4436
+ | `smoke-no-token-prompt.sh` multi-`-e` (single grep vs 7x7=49 invocations) | 0.485s | 0.014s | 35x |
4437
+ | `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 |
4438
+ | `issue-fetcher.sh` python3 batch (single inline vs 7 separate calls per fetch) | 0.806s / 3 iter | 0.136s / 3 iter | 5.9x |
4439
+ | `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 |
4311
4440
  | `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 |
4312
4441
 
4313
4442
  ### Verified