# Changelog

All notable changes to `@mmerterden/multi-agent-pipeline` are documented here.

Format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Versioning Policy

- **Major (X.0.0)** — breaking changes to the slash-command surface (renamed command, removed option, changed default that flips a reasonable workflow).
- **Minor (x.Y.0)** — new actions, new modes, new phase behaviors that are additive and opt-in.
- **Patch (x.y.Z)** — bug fixes, doc clarifications, new safety-net rules that refine existing behavior.

Internal file-layout changes that don't affect the slash-command surface are still flagged with `refactor!` commits but do **not** force a major bump. Version `2.0.0` marks the first "public-stable" API surface.

---

## [Unreleased]

### Fixed

- **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.
- **The commit is now made by `pipeline/scripts/website-deploy-commit.sh`, not by three lines of prose.** It reads the clone's own `user.name` / `user.email` and writes only on a mismatch (the website clone is usually already right, and overwriting it with the caller's identity is the defect), commits only when something is staged, reads the author back off the commit with `git log -1 --format=%ae` and halts before pushing on a mismatch - setting `git config` proves nothing, since an exported `GIT_AUTHOR_EMAIL` outranks it - then waits for a Ready production build instead of treating the push as the deploy. Exit codes separate the cases that need different responses: `1` wrong author and nothing pushed, `3` pushed but never built. The identity itself still resolves from `prefs.global.identities[]` routed by `platformIdentityRouting`, so no literal address enters a file that installs on every machine.
- **`multi-agent-refs/website-deploy.md`** carries the failure signature, the API call that names the reason the CLI hides, the empty-commit recovery that needs no history rewrite, and the live-site verification notes. The sync doc keeps three lines and a pointer, which is also what kept it inside its token ceiling.

### Added

- **`smoke-website-deploy-identity.sh` (20 assertions).** It runs the script against throwaway repos with real remotes rather than grepping the doc: the happy path lands and pushes, an exported `GIT_AUTHOR_EMAIL` halts with the commit still local, an unchanged tree makes no empty commit, a matching config is preserved while a wrong name is corrected, and missing or non-repository arguments exit 2. Wiring is asserted separately, since a correct script nothing calls is its own failure mode.

## [16.10.1] - 2026-08-26

### Fixed

- **`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.

- **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.

## [16.10.0] - 2026-08-26

Three debts the last few releases kept naming, closed.

### Added

- **`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.

### Changed

- **The telemetry self-registration path no longer needs `jq`.** It read preferences through `jq` behind a `command -v jq` guard, so a machine without jq silently never registered and never reported - which in the panel is indistinguishable from nobody using the pipeline. Reads now go through `node`, a declared engine (>=20.11), so the path cannot be skipped for a missing tool. Thirteen other shell helpers still use jq; the install now prints one note when it is absent, naming what degrades (cost summaries, the tracker's JSON reads, convention extraction, skill signing) instead of leaving each to fail quietly. Both READMEs gained a prerequisites section.
- **The phase-doc token budget has room again.** The token-telemetry instructions were repeated verbatim in four phase docs while the canonical text already lived in `progress-contract.md`; the phases now point at it. Total goes 57599 -> 57059 against a 57600 ceiling, so headroom moves from 1 token to 541. The ceiling was not raised - the space came back from text that had more than its share, the same rule 16.7.1 followed for `help`.

### Fixed

- Six phase docs cited `progress-contract.md#token-telemetry-forwarding-v83`; the heading anchors as `#token-telemetry-forwarding`, so none of those links resolved.

## [16.9.0] - 2026-08-25

### Added

- **`/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.

### Fixed

- The Phase 7 telemetry block still called reporting "optional, opt-in" after 16.8.0 made it on by default. It now states the default, both ways to silence it, and what is actually sent - command, duration, tokens, outcome - along with what is not: no repository name, path, prompt or diff. Same class of staleness as 16.7.1 and 16.8.1: the behaviour moved and the sentence describing it did not.

## [16.8.1] - 2026-08-25

### Fixed

- **`/multi-agent:help` still described the analysis command as it behaved before 16.6.0.** The entry read "per-platform v3 doc (23-section Full / 7-section Lite)" and said nothing about the standard being asked at intake, the corporate profile, stack being optional, or References being built from the evidence record - the word "profile" appeared nowhere in the file. Two releases had added the behaviour and neither had updated the sentence a user reads to learn what the command does.
- **The Copilot and Codex help never listed the analysis family at all.** `multi-agent-analysis` and `multi-agent-analysis-resolve` were absent in both the colon and the dash form, so a user on those hosts could not discover them from help. This predates the recent work; it surfaced while auditing the entry above.

Both fixes fit under the file's grandfathered token ceiling rather than raising it: the new text was trimmed where it carried the least, which is the same rule the 16.7.1 entry followed.

## [16.8.0] - 2026-08-25

Usage reporting answers the question it was built for, and stops carrying what it never should have.

### Changed

- **`usageLog.enabled` now defaults to `true`.** Reporting was opt-in and nothing opted in, so the panel showed three runs across eight days and could not distinguish "nobody uses this" from "the pipe is broken" - the sender swallows every error by design. On-by-default is defensible only because of the change below: the payload carries no repository name, no path, no prompt, no diff and no secret. `usageLog.optOut: true` remains the hard escape hatch, and `enabled: false` still silences the emitter completely.
- **The event carries the command that ran.** `commandOf()` returned `state.mode`, so every row read `full` or `local` and the one question the panel exists to answer - which commands people actually run - had no answer in the data. It now sends the invoked command; `mode` continues to ship separately.

### Fixed

- **The repository name is no longer sent.** `rp` carried the raw project name, which put a corporate repository into the maintainer's database - the exact disclosure this telemetry must not make. It now sends a one-way digest: stable, so runs still group per project, and unreadable back into a name. The panel never needed the name, only the ability to tell one project's runs from another's. The row already stored was redacted in place.

## [16.7.1] - 2026-08-25

### Fixed

- **The two commands 16.7.0 added were not listed anywhere a user looks.** `/multi-agent:review-analysis` and `/multi-agent:feedback` shipped without an entry in `/multi-agent:help` (either language), in either README, or in the Copilot and Codex help variant - so the release published two commands nobody could discover. Every gate was green through seven rounds and none of them caught it, because no gate asks whether a new command was documented; a command a user cannot find is close to a command that does not exist.
- Making room for those entries took `help/SKILL.md` past its grandfathered token ceiling (8586 against 8500). The ceiling is governed by a list that only ratchets down, so raising it was not available - and should not have been. The new lines were cut first, and the rest came from the `store-ready` entry, which spent four lines per language on detail that belongs in its own command doc. A help listing is one line per command. Space for the new commands was taken back from an entry that had more than its share rather than borrowed from the budget.

## [16.7.0] - 2026-08-25

Two commands: one that reviews the document instead of the diff, one that lets a user say something went wrong.

### Added

- **`/multi-agent:review-analysis`** reviews a written analysis the way `/multi-agent:review` reviews a diff. It resolves the document from a local path, a Confluence page or a Jira issue, runs the deterministic gates FIRST and reports their output verbatim (`validate-analysis-doc.mjs`, and `build-references.mjs --check` when a state JSON is available), then runs the parallel model review and triage. Findings cite `Locked <n>` where a rule applies, because "I would have written this differently" gives an author nothing to act on while "Locked 34: this Confluence page is in the evidence record but not in Section 21" gives them a fix and a reason; anything with no rule behind it is marked as judgement rather than dressed up as a violation. The verdict states what was NOT checked - without a state file the references coverage claim is exactly the one nobody can verify from the document alone. It never edits the reviewed document: that belongs to its author, and a reviewer who rewrites it has removed the choice to disagree. `/multi-agent:analysis-resolve` remains the command that folds answers back in.
- **`/multi-agent:feedback "<message>"`** sends one message to the maintainer. **Only the text the user types is sent** - plus the pipeline version, the host CLI and a timestamp, which a report is useless without. No logs, no repo names, no branch names, no file paths, no diffs. That limit is deliberate: this package installs from a public registry, so an automatic log attachment would take a corporate user's internal identifiers off their machine and into someone else's database. A person can paste the one line that matters; a script cannot know which line that is. The exact payload is printed before anything is sent and nothing leaves the machine without a confirmation, autopilot included - a message to a person is never fired unattended. Auth reuses the usage ingest token, so nothing new is onboarded; `usageLog.optOut` does not silence it, because telemetry is passive collection while this is a deliberate act, and silently dropping something somebody chose to send is worse than not offering the command. A send failure is reported rather than swallowed: the person is waiting to hear whether their message went.

### Changed

- **The analysis context-budget gate measures the refs the command declares, not a directory glob.** `multi-agent-refs/analysis/` also holds sibling-command refs - `resolve.md` belongs to `:analysis-resolve`, `review.md` to `:review-analysis` - and an analysis run loads neither, so the glob billed every run for files it never reads. The ceiling goes back to 145000 (from the 155000 v16.6.0 set): with the measurement corrected the real per-run cost is 139672, and that raise had been compensating for the glob rather than for anything the tree costs. A ceiling raised to fit a wrong number stops being a budget. Adding a ref to the analysis command now counts automatically; adding one for a sibling command does not.
- The command surface is 53. Both new commands carry their `shared/core` counterpart for Copilot and Codex, and the canonical inventory in `cross-cli-contract.md` and both sync skills lists them - `smoke-command-inventory.sh` fails on any of those going stale, and did, which is how the gap was found rather than shipped.

## [16.6.0] - 2026-08-25

Two analysis standards, one evidence record, and a references section that is built rather than remembered.

### Added

- **`/multi-agent:analysis` asks which standard the document follows** (Locked 32). Phase 0 Step 1b offers `global` - the 23-section development handoff, unchanged - and `corporate`, a requirements document whose spine is `IG -> UC -> FG`: business requirements, use cases with actor, precondition, main flow and step-bound alternative flows, functional requirements each beginning "Sistem,", service details, and three cross matrices that prove the chain closes. Part A is the requirement document, Part B is the technical analysis, Part C is the development analysis. Both profiles read the same `state.analysisSpec.evidence.*`: intake, fetching, repo evidence and convention extraction are shared, so the projections cannot drift into two products. One run emits one profile - rendering both would produce two documents about the same feature and leave the next reader to guess which is current.
- **`pipeline/multi-agent-refs/analysis-template-corporate.md`**, the corporate projection, plus the corporate backbone rule (Locked 33): Part A and the footer render even with zero evidence, carrying `N/A` when a section is genuinely out of scope and `EKLENECEK` when evidence is expected but missing. A requirements document has to let a reader tell "we considered hardware needs and there are none" from "nobody looked", which the global profile's omission rule deliberately cannot express. Every `EKLENECEK` owes a Risks and Open Questions row naming what is missing and who can answer it; one without a row fails the dispatch gate. Missing inputs never halt the run - the gap is written down and raised, not waited on.
- **The traceability matrix is cross-checked, not just required.** In the corporate profile every `IG`, `UC` and `FG` id defined in the document must appear in the matrix, and every id in the matrix must be defined somewhere else; both directions block dispatch. A matrix that merely exists is not the claim worth making, because every downstream reader trusts it instead of re-deriving the chain, and a requirement quietly missing from it is invisible exactly where it matters. The remaining consistency rules (an IG realised by a use case, an FG naming a source that exists, a cancelled requirement struck through everywhere) stay renderer obligations and are written as instructions rather than as guarantees.
- **`pipeline/scripts/build-references.mjs`** builds Section 21 from the evidence record instead of leaving it to the model (Locked 34). Each row carries a precision anchor - Figma node id, Confluence `pageId` plus page version, the commit sha a repo was read at, the Swagger spec version - because a reference with no anchor points at a moving target six weeks later. Each row carries an access cell, so a declared source that could not be fetched is listed as unreachable rather than dropped; a silently dropped source reads to the next person as a source that never existed. Statements the user made in conversation that no fetched source carries are recorded verbatim as free-text rows with the decision they settled. A coverage gate blocks dispatch when a consumed source is missing from the table and when a listed row has no evidence behind it: an invented reference is worse than an absent one, because a reader will follow it.
- **Two preference keys, declared in the schema rather than only in prose.** `global.analysisProfiles` narrows which standards the Step 1b picker offers (listing one auto-resolves the step), and `global.analysisProfile.corporate` carries the corporate profile's deployment bindings: `confluenceSpaceKey`, `confluenceParentPageId`, `titleFormat` and `titlePrefix`. A corporate analysis always lands in the same tree, so the Phase 3.5 destination prompt is skipped when all four are set and falls back to asking when any is missing. The names are deliberately generic, so no organisation's space, page or tooling names live in the repo, and the key set is closed - `global` is `additionalProperties: false`, so a documented-but-undeclared key would have failed `validate-prefs.mjs` for anyone who set it.
- **Stack selection is optional** (Locked 35). `No platform yet` is a real answer: evidence is still fetched, everything that does not need a target repository renders in full, and only the development layer plus the Pass B projection are skipped, with an open-question row recording why. The output is a single file at `~/Desktop/multiAgentAnalysis/<feature-name>/<feature>.md`: without a repo the usual repo-relative `analysis/` path has nothing to be relative to, and the current working directory is never written to, since for a repo-less run it is arbitrary and creating a folder wherever the command happened to be invoked is the kind of surprise that costs a tool its trust. Desktop rather than a hidden directory because the document is a deliverable meant to be opened and handed over, and `Analysis` because a folder named after the command that produced it is guessable; the Phase 3.5 picker shows the resolved path and takes an override. A requirements document is useful before anyone has decided which repository will hold the code, and refusing to produce one until that decision exists inverts the order the work actually happens in.

### Changed

- **The context-budget gate measures what one analysis run pays, not what the tree weighs.** With two profile templates on disk and exactly one loaded per run, summing both would bill every run for a file it never reads - and would push the project toward deleting a template to satisfy a number that was measuring the wrong thing. The gate is now shared refs plus the largest template; the pinned ceiling moves 145000 -> 155000, and the current cost is 146139 bytes.
- `validate-analysis-doc.mjs` reads `profile` from the front-matter and applies the matching contract. Three global-profile checks - the Section 3 flow-chart warning, the bare `N/A` placeholder warning, and the missing-`BR-` warning - no longer fire on a corporate document, where Section 3 is Business Requirements, `N/A` is required behaviour, and the spine is `IG`/`UC`/`FG`. The Test Plan check matches on title rather than number, so it covers both profiles, and is waived only for the stack-optional render where the development layer is legitimately absent. `platform: none` is a known platform value.

### Fixed

- `analysis-template.md` cited the References-at-the-bottom rule as Locked 20; Locked 20 is the localization mode and the References rule is Locked 21.

## [16.5.0] - 2026-08-25

Staying current stops being something a user has to be told to do.

### Changed

- **`updateCheck.autoUpdate` now defaults to `true`.** A newer version is installed before the run starts, in interactive modes and autopilot alike, instead of asking once per `ttlHours`. The old default made "be current" opt-in, which in practice meant a maintainer telling people to run a command - and since v15.14 the Supported Version Gate already halts any install below the `required` floor, so the real choice was never "update or not", it was "update, or be stopped and told to update". `autoUpdate: false` restores the question; `updateCheck.enabled: false` silences the advisory check (neither disables the required floor, whose only override remains `MULTI_AGENT_ALLOW_OUTDATED=1`). Docs already loaded finish the current run on the old version; the update takes full effect on the next run.
- **The MCP server is registered as `@latest`.** The registration was the bare package name, so `npx` reused any cached copy: a machine that had cached toolkit 3.0.0 kept starting 3.0.0 after 3.1.0 was published and tagged `latest`. The new tool existed on the registry and in the docs, and in no running server. Measured, not assumed - the npx cache held exactly `3.0.0` while `latest` was `3.1.0`. The cost is a registry round-trip when the server starts, and an offline start now depends on what npx can resolve rather than on any cached version being present: a stale server is a silent wrong answer, a failed start is a loud one.
- **Registration is rewritten on every install and update, not skipped when present.** The installer treated "already registered" as done and left the stored args alone, so an existing install would have kept the bare spec forever and the `@latest` change would have reached new installs only. The current entry is now removed before the add, exactly as the pre-rename entry already was. A host that refuses the remove is reported as possibly-stale with the manual command, rather than counted as a clean registration.

### Fixed

- Two comments in `install/_mcp-register.mjs` and one in `smoke-npm-scope-pinning.sh` still said 83 tools / `MCP_SERVER_PACKAGE`; the toolkit serves 84 and the registration builds from `MCP_SERVER_SPEC`.

## [16.4.0] - 2026-08-24

Two default changes are worth reading before upgrading. `/multi-agent:analysis`
no longer overwrites a Jira issue's description - it posts a comment unless you
explicitly choose the description, which is then backed up first. And Phase 6
gained a blocking gate: a plan step that never reached a terminal status stops
the commit instead of surfacing in the Phase 7 report afterwards. Both are
corrections to behaviour that lost work quietly; neither changes a command name
or an option.

Four ideas taken from github/spec-kit, obra/superpowers, karpathy/llm-council and yamadashy/repomix after auditing all five candidate repos against this pipeline at source level. Most of what those projects do the pipeline already had (spec-kit's constitution is `analysis/locked.md`, its `/analyze` is `validate-analysis-doc.mjs`, repomix's packed digest is `repo-map.mjs`, its offload pattern is Phase 4 Step 1.9, its secretlint pass is Gate 4 before any reviewer runs, serena's memories are the learnings ledger). These are the four gaps that were real.

### Fixed

- **The triage model could recognise its own findings.** On Claude Code the reviewers are Fable + Sonnet and triage is Fable; on Copilot CLI the reviewers include Opus and triage is Opus. Step 3.2 handed that model a list labelled "Reviewer 1 + Reviewer 2", and the Step 2.5 rebuttal round showed each reviewer "the OTHER reviewers'" findings by attribution - so the judge was marking its own homework, and the word "anonym" appeared nowhere in the pipeline. `scripts/anonymize-findings.mjs` now strips every identity key, relabels findings `Source A/B/C`, and orders them with a PRNG seeded from `taskId:iteration` after a stable content sort, so reviewer completion order cannot leak through position and `/multi-agent:resume` reproduces the same input. The label map is written to a separate file and never enters a prompt. Pattern source: llm-council `backend/council.py` stage 2; the delta is deliberate, since llm-council anonymizes only its peer-ranking step and lets its chairman see names - our triage is chairman AND panel member.
- **An unfinished plan could reach commit.** Phase 4 answers whether the diff is correct, Step 1.45 covers the planned tests, and the criteria manifest's denominator is rule IDs. Nothing covered the plan's own steps: the `[done]` / `[pending]` rollup is rendered in Phase 7, after the commit. `scripts/plan-coverage-gate.mjs` runs as Phase 6 Step 0a and fails when a step never reached a terminal status, when a skip or failure carries no reason, or when an analysis Section 14 row tagged `Add new` names a file that is not in the tree. Pattern source: spec-kit `converge` - current state rather than a diff, and a clean run stays quiet. Not copied: spec-kit appends remediation tasks to `tasks.md`; rewriting an approved plan is a Phase 2 decision, not a gate's.

### Added

- **Test baseline (`prefs.global.testBaseline`, default off).** Phase 4 Gate 3 had no way to tell an inherited red suite from one this run broke, so it blocked on someone else's bug or the dev agent "fixed" tests it never touched (`shadow-git.sh init` snapshots files, not test results). Phase 0 Step 7.6 now runs the same command Gate 3 uses, time-capped, and records `state.baseline.tests` with one of three statuses: `green`, `red` with the failing set, `red` with an empty set plus the log path when the output cannot be parsed into names, or `unknown`. Gate 3 subtracts a known failing set, refuses to either pass or silently block on an unparseable red, and leaves today's behaviour untouched when there is no baseline. Pattern source: superpowers `using-git-worktrees` Step 3, extended from ask-the-user to a stored set.
- **`smoke-triage-anonymity.sh` (17 assertions), `smoke-plan-coverage.sh` (15), `smoke-test-baseline.sh` (13).** Each proves the behaviour, not the prose: identity keys really are stripped, no model name survives in the payload, the same seed reproduces the order and a different seed does not, reviewer order does not change finding order, per-reviewer metrics degrade to `unavailable` instead of inventing a zero, every unaccounted todo shape is caught, Reuse/Modify rows and template placeholders are not counted against the tree, and no doc collapses baseline `unknown` into `green`.

### Changed

- **`reviewIterations[].reviewers` is typed.** It was `{"type": "array"}` with no item schema, so nothing said a reviewer entry names its model. Now `model`, `findings[]` and `roundCount` are declared, with nothing required: a run written before this shape still validates and surfaces as `unknown` rather than being folded into a named model.
- **`run-metrics.mjs` reports signal-to-noise per reviewer.** `acceptedRatio` pooled every reviewer together, so "which model is worth dispatching" had no answer. With the anonymization map present, accepted findings are attributed back per model; without it the raw counts still land and `perReviewerAttribution` says `unavailable`. Pattern source: llm-council `calculate_aggregate_rankings`.
- **`smoke-gate-wiring.sh` matches the property, not the section title.** It grepped for the literal heading "3.0 Merge the deterministic findings in" and reported a wiring break when that section was retitled, while the wiring was intact. It now checks that 3.0 merges, that 3.1 reads the merged count, and that anonymization precedes the merge - relabelling gate findings as reviewer findings would cost triage the difference between a fact and a raw signal.
- **`total_max_tokens` 56600 -> 57600.** Three new phase-doc contracts cost about 1000 tokens after trimming the drafts by 500. Every individual phase stays inside its own max and `phase-0-init` is back under its warn line; only the aggregate needed the same incremental bump the last three feature commits made.

### Fixed (same release, found by reviewing the four items above)

- **`run-metrics` understated a reviewer on a mixed-map run.** `attributed` was a
  single global flag, so an iteration with no label map still added its findings
  to the per-reviewer denominator while being unable to contribute a numerator. A
  resumed run that upgraded mid-flight read as noise the reviewer was never
  credited for. The denominator is now the attributable raw count,
  `rawAttributable` is reported next to `rawFindings`,
  `mappedIterations` says how many iterations carried a map, and
  `acceptedUnattributed` counts every accepted finding that resolves to no
  reviewer - a deterministic-gate finding, which carries no `foundBy` by design,
  or anything from an iteration that had no map - so
  `acceptedAll === sum(perReviewer.accepted) + acceptedUnattributed` holds.
- **`anonymizationMap` was referenced but never declared.** Phase 4 Step 3.0
  writes it and `run-metrics.mjs` reads it, yet it appeared nowhere in
  `agent-state.schema.json` - it validated only because the iteration object
  allows extra keys. That is the same declared-but-undeclared shape the typed
  `reviewers` entry was added to close. Now declared with `seed` and
  `labelToModel`.
- **`anonymize-findings.mjs` documented an exit code it could not reach.** The
  header promised `64 usage`, but the TTY guard was lost when the file was
  rewritten, so a bare invocation blocked on stdin forever and 64 was
  unreachable. Restored, and verified through a pty rather than a pipe.
- **`plan-coverage-gate.mjs` could skip a real Section 14 row.** A header-row
  filter matched `^(dosya|file)\b` against the PATH column, so a promised file
  under a `File/` directory left the denominator silently. The filter was
  redundant anyway - the header's tag cell already fails the `Add new` test - so
  it is gone, with a fixture row proving it.
- **Two gate assertions claimed more than their code.**
  `smoke-triage-anonymity.sh` said "no model name appears anywhere in the
  payload" when the guarantee is structural (no identity FIELD survives; free
  text is deliberately not scrubbed, because that would mangle a finding about
  `ClaudeService.swift`), and the limit is now stated in both the gate and the
  script header. `smoke-gate-wiring.sh` matched a section title instead of the
  property and reported a wiring break on a retitled but intact section.

### Fixed (review round 2)

- **The Section 14 file check was wired to a variable nothing sets.** Phase 6
  passed `${ANALYSIS_DOC:+--analysis "$ANALYSIS_DOC"}`, and `ANALYSIS_DOC` is
  defined nowhere in the pipeline - so in practice that half of the gate never
  ran. The real location is `state.analysis.docPath[]`, an array with one entry
  per platform (Phase 1 Step 4), and `--analysis` is now repeatable so every
  platform's promised files enter one denominator instead of the first one
  standing in for the run.
- **A plan with zero steps passed.** `todos: []` reported `0/0 steps accounted
  for` and exited 0, so a Phase 2 that produced nothing - or a state whose todos
  were cleared - read as a fully delivered plan. An empty plan is now exit 2,
  the same as a missing one.
- **A malformed reviewer dispatch vanished.** A reviewer whose `findings` came
  back as an object contributed nothing and said nothing, so triage adjudicated
  a smaller panel than actually ran. The anonymizer now warns on stderr and
  records the loss in the map under `malformed`, named by label.
- **A residual that anonymization does not remove is written down.** With two
  reviewers the label set is `{Source A, Source B}` and the triage model is one
  of them, so it keeps a 50% prior on which findings are its own. The value is
  that nothing tells it; llm-council's four-model council has a stronger version
  of the same property.

### Not adopted, with reasons

- **tree-sitter symbol extraction (repomix `--compress`).** `repo-map.mjs` extracts declarations by regex and tree-sitter would be more accurate, but `package.json` has `"dependencies": {}` and that zero-runtime-dependency property is deliberate. If accuracy is wanted, LSP is the compatible route.
- **serena-style symbol-level reuse detection.** Locked 11's `Reuse existing X (file:line)` rows rest on regex and would benefit, but it means rewriting the Phase 1b collector, and serena was only read at README level. Separate work.
- **Per-task `verification` field (superpowers).** Not a failure class, overlaps the coverage gate above, and touches schema plus plan approval plus the Phase 3 evidence chain - the widest blast radius of the five candidates.
- **spec-kit `extensions.yml` hooks.** A HookExecutor layer above the existing `prefs` flags, for one maintainer.

### Fixed - Jira channel

- **`/multi-agent:analysis` overwrote the Jira issue description.** The Jira output target was a bare `PUT /rest/api/2/issue/{key}` on the `description` field: one write, no read first, no backup, no preview. An issue whose description held the reporter's own requirement text lost it silently - the picker asked which issue, never what would happen to it. Publishing now goes through `lib/jira-publish.sh`, and the destination question has three answers with **Comment pre-selected** (a comment cannot destroy anything, and it is what an unattended run gets). The description path reads the current value first, writes it to `~/.claude/logs/multi-agent/jira-backups/<KEY>-description-<stamp>.txt` and reports the path, appends below a `----` rule by default, and exits 3 rather than replacing a non-empty field unless the user's explicit "Description - replace" answer supplies `--confirm-overwrite`. The body is escaped by `jira-wiki-escape.mjs` on both paths, and the bearer token is passed through a curl `-K` config so it never reaches argv.

- **A Jira comment showed a smiley nobody typed.** The body referenced the Swift selector `login(source:input:)`, whose trailing `:)` Jira's wiki renderer turns into an emoticon image at render time. `channels/jira.md` had carried an "Emoticon escaping (required)" table with all 21 sequences for releases, and its own text said the table "is applied by the model, by hand" - so on a long body it was skipped, silently. The escape is now `pipeline/scripts/jira-wiki-escape.mjs`: it backslash-escapes each documented sequence, skips `{code}` / `{noformat}` blocks, reaches inside `{{monospace}}` (Jira parses emoticons there too), and is idempotent. `--check` is the inverse, for a pre-POST assertion. Wired into all nine Jira comment/description write sites (channels adapter, analysis description PUT, generate-issue create, wiki-to-Jira triad, readiness review, create-jira).

### Added - Jira channel

- **`smoke-jira-publish.sh`** - 20 assertions over the safe-write contract, driven by a curl stub so nothing touches the network: comment is the default target, the description path GETs before it writes, the backup exists even on the refused path, a blind replace is refused, a confirmed one writes only the new body, an empty description needs no ceremony, `--dry-run` sends nothing, and the token never appears in argv. It also holds `analysis/render.md` to the contract, so the bare description PUT cannot come back in prose.

### Changed - Jira channel

- **`smoke-channel-glyphs.sh` section 3 tests behavior instead of prose.** It used to grep `channels/jira.md` for the escaping table and pass - the table was present, and nothing applied it. It now runs the program: the exact selector that caused the bug, the monospace case, the code-block skip, idempotency, both `--check` exit codes, and a table-to-program tie that reads the 21 sequences out of the doc rather than retyping them, so the doc and the program cannot drift apart in either direction. It also asserts every known Jira write site routes through the escape, which is how a new write site that skips it gets caught.
- **`pipeline/scripts/README.md` counts recounted from the filesystem** - the header said 148 shell + 45 `.mjs` and the smoke heading said 118 files; the tree holds 186 shell, 58 `.mjs`, 151 smokes.

## [16.3.0] - 2026-08-24

Three defects the 16.2.0 review named but did not close, plus one it caused.

### Fixed

- **A section body ran past its own section.** `sectionBody` closed on the next heading at the same depth, so a sub-section that is the last of its parent had no terminator and swallowed everything up to the next sibling. In practice 13.1 ran into Section 14 and the Locked 24 check audited the Files-to-Add rows as concept-table rows, failing a correct document with three errors. It closes on any shallower heading now. Found by running a realistic template-conforming document through the gate, which is something the 16.2.0 fixtures never did: every one of them happened to put the concept table last.

### Added

- **`smoke-state-keys-declared.sh`** - every `state` root a phase doc names must be declared in `agent-state.schema.json`. 16.2.0 fixed `lastAnalysisDigest` by adding a sentence telling Phase 1 to persist it, which is the same shape as the bug it fixed: a contract asserted in prose with nothing checking it. This checks the half a machine can. The twenty roots the schema never declared are held in an explicit grandfather list that may shrink and never grow, and the gate fails if a grandfathered key gets declared and stays on the list.
- **`smoke-doc-model-claims.sh`** - a model named in a README phase line must be the model the persona file declares. Both READMEs credited Phase 1 to Opus for several releases while `explorer.md` had said `sonnet` all along. 16.2.1 fixed the two lines; this fixes the class. Proven against the original regression by putting it back.

### Known

- Both new gates read declarations, not behaviour. Nothing verifies that Phase 1 actually writes the digest at runtime, or that a Turkish document comes out clearer for the humanizer rules. Those are model instructions, and a gate over prose can only check that the instruction is present and self-consistent.

## [16.2.2] - 2026-08-24

### Fixed

- **The companion MCP server's old name survived in the docs.** 16.1.3 corrected one line in `docs/architecture.md` and called the rename done; `docs/ecosystem.md` still carried it 22 more times, including its section heading, its mermaid nodes and its boundary discussion. Fixing the instance is not fixing the class. `docs/internal/GENERICITY-REVIEW.md` keeps the old name on purpose: it is a dated pre-v10.7.0 snapshot, and editing a record to match today falsifies it.
- `docs/ecosystem.md` advertised 80 tools against 83 served, and declared a `≥ v2.9.0` minimum where `cross-cli-contract.md` requires v3.0.0.

## [16.2.1] - 2026-08-24

### Fixed

- **Both READMEs credited Phase 1 to Opus.** The explorer persona has run on `sonnet` since it was retuned, with a rationale in its own frontmatter saying sonnet matches opus on file inventory and dependency graphs at a fraction of the cost. The phase doc carried the same leftover in an `explorer/Opus` reference. A model name in a README is the kind of claim nobody re-derives, so it stays wrong until someone reads the persona file.

## [16.2.0] - 2026-08-23

The analysis subsystem said things its gate did not enforce. `locked.md` carries 31 decisions; four of them ended with the sentence "fails the dispatch gate", and the dispatch gate implemented none of the four. This release makes three of them true and pulls the fourth back to what actually happens.

### Fixed

- **The humanizer punctuation check flagged text the humanizer is documented to leave alone.** `analysis/render.md` exempts front-matter, fenced code, table rows and URLs; the validator split the whole file and scanned every line, so an em-dash inside a URL or a Swift snippet was a hard ERROR no humanizer pass could clear. Scope now mirrors the pass it enforces. The prose body is still checked, proven by a negative control rather than assumed.
- **An orphan code fence hid Section 16 from the model.** The Section 15 scaffold closed early, leaving 15.7 outside it, and the stray closing fence then opened a block that swallowed the Locked 31 dispatch rule, the Section 16 heading, its instructions and its entire scaffold. Sixty-three fences in a file that can only be even. Section 16's contract had been arriving as a code sample.
- **Phase 3's staleness check could not fire.** Step 3 compared `state.run.lastAnalysisDigest` against the document front-matter, and nothing in the pipeline ever wrote that key; the state schema did not declare it either. Every run took the absent branch and reported fresh. Phase 1 now persists it, plus a `base_commit` anchor, and the schema declares both containers.
- **The template attributed the Files-to-Add tag rule to Locked 15** (new assets default to SVG). It is Locked 16; the schema already had it right.
- **`filesToAdd` was typed `["object", "null"]` under a description reading "Never null."** Now `object`, matching its own sentence and its presence in `required`.
- The variant-coverage check anchored on a heading numbered literally `6.X`, while Locked 2 requires numbering to re-flow. Renumber the section and the check stopped running, silently. It now keys on the heading text.
- "23 main sections + 3 footer" counted the footers twice, and Lite mode advertised "7 main + 1 footer" above a list of eight numbers. Corrected across seven surfaces, both languages.

### Added

- **The dispatch gate enforces Locked 16, 24 and 31.** Untagged Files-to-Add rows, concept-table rows that state no evidence, and business rules with no Section 15 scenario are now errors rather than sentences. The traceability check previously warned when a `BR-` id appeared fewer than twice anywhere in the document; it now resolves the actual chain, from the rule's definition to a test that covers it.
- **Section 15 must exist in Full mode.** It is what Phase 3 reads as the RED input, so a document without it hands development an empty test matrix - and the gate used to accept that with exit 0. A rule defined with no unit sub-table fails too. The omission table allows exactly one exception, a backend-only run with no contract testing planned, and the validator knows the platform, so that case warns instead of blocking.
- **Section 3 diagrams are checked.** A rendered section with no mermaid block is an error; an absent section is a warning, because the template's omission condition (single screen, single service, simple rule) is real and a blanket requirement would be wrong. The 3.3 state-machine sub-section gained the trigger it never had.
- **The humanizer knows what language it is writing.** It was 135 lines of English AI-slop patterns with no notion of `outputLanguage`, applied to Turkish analysis documents that stakeholders read in Confluence. Turkish fails differently: nominalization chains, clauses piled up before a sentence-final verb, calqued English structure. Those are now named, with the diacritic rule moved beside them instead of living only in the caller's doc.
- **The three tones the pipeline passes are defined.** `technical-explanatory`, `formal-stakeholder` and `informal-technical` appeared at seven call sites across two refs and in none of the skill; all three silently collapsed to the same default, so the stakeholder page and the ticket comment came out identical. `smoke-humanizer-contract.sh` harvests the tone names from the call sites and fails when one has no definition.
- **Section 14's tag reaches development.** Phase 2 carries it onto the todo as `sourceTag`; Phase 3 binds the existing file on a `Reuse` step instead of writing a new one, and Phase 4 checks the diff against it. The analysis had already settled that decision and the implementer was re-making it.
- **Repo drift is detectable.** `base_commit` in the front-matter lets Phase 3 diff the repo against the commit the spec was written from. This is the case a digest cannot see: a reused document keeps a matching `evidence_digest` precisely because its evidence inputs did not change, while the code underneath it moved. Recomputing the digest would mean re-running Phase 1b and 1c, which is why the anchor is a commit.
- The analysis ref tree is measured: 132138 bytes across seven files, and nothing had ever put a number on the largest reference the pipeline ships. Unlike `rules/`, this loads once per analysis run rather than once per session, so the ceiling exists to make growth visible, not to force a cut.
- `smoke-validate-analysis-doc.sh` grew from 13 assertions to 26, every one planted-and-proven: the bad document is fed in and the error text matched, with a negative control wherever a check could pass by never firing.

### Known

- **The template prescribes two shapes for the Section 13.1 concept table and does not say which wins.** The scaffold uses explicit `Confidence` and `Evidence` columns; the Pass B footnote grammar puts the same facts in a `^[key confidence: source]` suffix, and its examples reuse the scaffold's own row names. Both carry the evidence, so the Locked 24 check accepts either and fails only a row carrying neither. Choosing one shape is a design decision for a follow-up, not something a gate should settle by rejecting half the template.
- The state schema declares `additionalProperties: false` while the phase docs write about twenty roots it does not define (`dev`, `plan`, `analysisSpec`, `taskType`, `maturity` and others). This release adds the two it touches, `analysis` and `run`. Nothing validates a state instance against the schema today, so the drift is latent rather than active.

### Changed

- **Locked 18 no longer claims a gate it cannot have.** URL-only Figma references in Section 5 are a review finding, not a validator error: whether an attachment upload succeeded is known at the Confluence dispatch step, not by reading the markdown.
- Phase doc aggregate 56350 -> 56600. The baseline had nine tokens of headroom, so no addition of any size could fit; compression came first and took the four new blocks from 380 tokens to 214.

## [16.1.3] - 2026-08-23

A dead-code and stale-name sweep. No dead symbols: every function and constant added this week is called from somewhere. Five stale names and one lie.

### Fixed

- **The uninstall preview described the wrong MCP entry, and claimed to remove one it now keeps.** The dry-run text named `dev-toolkit` - the pre-v15.12.0 name - and listed it as removed, while a current install registers `multi-agent-toolkit` and 16.1.0 made that one survive. The preview is the last thing a user reads before confirming a destructive action, so being wrong there is worse than being wrong in a comment. Both host lines now say which entry goes and which is kept, and that `lib/` keeps the credential reader.
- `refactor`'s research ref was still `dev-toolkit-research.md`, two releases after the server was renamed. Renamed to `toolkit-research.md` with both SKILL trees updated. The file was never dangling - just carrying a name that stopped being true.
- `docs/architecture.md` listed `dev-toolkit-mcp` as a sync target; the repo is `multi-agent-toolkit-mcp`. The plugins repo README and two site comments named the old server too.
- The MCP registration header still said "80 tools"; it serves 83.

### Added

- **Both READMEs and `docs/features.md` describe what 16.1.0 actually shipped.** They had been checked for stale `--dev` references and found clean, which is not the same as being current: the release's headline capability - the install being usable in an ordinary session - appeared in none of them. Now it does, in English and Turkish, including the part that constrains it: reads are ordinary work, writes route through the pipeline commands that carry the rules making them safe.

## [16.1.2] - 2026-08-23

The rule reached all three hosts in 16.1.1. It only WORKED on one.

### Fixed

- **Rules were installed with a byte copy, so every path in them dangled on two hosts.** Each CLI keeps its trees under its own root, and both installers carry a path-rewrite table that names `rules` explicitly - but `installRules` (Copilot) and `installTree` (Codex) used a plain `copyDir`, so the rewrite never ran on that tree. Result: all 13 rules told a Copilot or Codex session to run `~/.claude/lib/credential-store.sh`, a tree those installs never create. This is the same dangling-path class the Copilot rewrite was written to eliminate for skills; rules were named in the doctrine and left out of the code. It bites hardest on the one rule whose entire job is resolving that path. Both installers route rules through `copyTreeRewritten` now: `~/.copilot/lib/...` on Copilot, `~/.codex/lib/...` on Codex, unchanged on Claude Code. Twelve pre-existing rules were fixed along with the new one.
- `outside-the-pipeline.md` says what to do when the refs tree is absent. Copilot installs no `multi-agent-refs/` by design - the doctrine routes those references to `~/.claude` when Claude Code shares the machine - so a Copilot-only install was being sent to a file that is not there with no explanation. The rule now states that the summary it carries IS the contract in that case.

### Changed

- `smoke-context-budget.sh` asserts both installers put rules through the rewriter. Verified by reverting the Copilot fix and watching it go red. Shipping a rule to a host is not the same as shipping a rule that works there, and only the second one is worth the bytes it costs every session.

## [16.1.1] - 2026-08-23

Found by reviewing 16.1.0 after it shipped, and by the `/mcp` reconnect confirming the fix worked.

### Fixed

- **Copilot installs a rules tree and never mentions it.** `copilot-instructions.md` names `skills/`, `scripts/` and `agents/` but has zero references to `rules/` - so all 13 files, 31874 bytes, land in `~/.copilot/rules/` with nothing telling the agent they exist. That predates this release; 16.1.0 simply added a fourteenth unread file to the pile. Codex got its pointer in 16.1.0 and Claude Code has always had the CLAUDE.md "Modular Rules" table; Copilot now has one too, naming which rule matches which kind of work.
- `smoke-context-budget.sh` asserts reachability alongside size. A tree paid for by every session and pointed at by nothing is worse than one that is read: same cost, no return. Verified by removing the pointer and watching the gate go red.

### Verified

- The MCP scope fix works end to end: `/mcp` reports `Reconnected to multi-agent-toolkit` and all 83 tools are present. 16.1.0 could only show the server serving them when run directly.

## [16.1.0] - 2026-08-23

Installing the pipeline set up three capabilities that only worked inside `/multi-agent`. A plain session had the tokens, the plugins and the MCP server on disk and no way to know it.

### Added

- **`rules/outside-the-pipeline.md`** - the always-loaded pointer that makes the install usable without a run. Services (resolve the onboarded token, read freely, route writes to the commands that carry the safety rules), stack skills (ask each enabled toolkit's own `index`), and the toolkit MCP. 1412 bytes; the detail lives in `multi-agent-refs/outside-the-pipeline.md` and loads on demand. `rules/` already carried the proof this works: `figma-pipeline.md` is why Figma was reachable outside a run and the other 15 mapped services were not.
- **A budget for `rules/`.** It was the one always-loaded tree nothing measured - 30462 bytes across 12 files, every one loaded into every session whether the pipeline is used or not. `globs:` does not gate this: `code-style.md` declares `**/*.swift` and still loads in a repo with no Swift, so size is the only lever. Total ceiling 33500, and a 2048-byte cap on a capability-announcing rule so it stays a pointer rather than becoming a catalog. Both pinned in `context-budget-gate.test.mjs`; the cap caught this release's own rule at 2476 bytes and sent the detail to a ref.
- **`smoke-npm-scope-pinning.sh`** - one root cause broke three things this release, so it is now a gate rather than a lesson.

### Fixed

- **The toolkit MCP had been failing to start, and the error named nothing useful.** `/mcp` reported `CONNECTION_CLOSED`. The registration was `npx -y @scope/pkg`, and a `@scope:registry=` line in the user's `.npmrc` outranks everything that is not itself scope-specific - so npm looked in GitHub Packages, the package was not there, and the process never launched. Measured: `npm view @mmerterden/multi-agent-toolkit-mcp version` returns 404, the same command with `--@mmerterden:registry=https://registry.npmjs.org` returns 3.0.0. The server was never broken; run directly it serves 83 tools. The registration now derives the scope from the package name and pins it, so a fork under a different scope pins its own.
- **Uninstall deleted the only way to read the tokens it promised to preserve.** `lib/` was in the removal set, `credential-store.sh` lives there, and the closing message then advised running it. `--all-data` went further and removed `global.keychainMapping`, leaving the surviving tokens under names the user never chose and no longer had a record of. Both are preserved now; everything else in `lib/` and in the preferences file still goes, which is what separates keeping the right thing from stopping the clean.
- **The `multi-agent-toolkit` MCP registration survives uninstall.** Its device, accessibility and store-audit tools are useful with no pipeline at all - the same reason the tokens stay. The retired `dev-toolkit` name is still deregistered: it points at a registration nothing maintains, which is litter, not preservation.
- **Phase 3 skill routing knew two toolkits out of six.** The stack table mapped `ios` and `android` and sent everything else to "no toolkit", so a React or backend repo was told it had none while its plugin sat enabled with a routing table inside it. Routing now reads the effective `enabledPlugins` - the set `/multi-agent:stack` already writes - which cannot go stale when a seventh toolkit ships. The doc's own stale sentence ("a backend or web repo legitimately has no toolkit") went with it.

### Changed

- Routing is one definition with two callers: Phase 3 and the always-on rule both read the enabled set and defer to each plugin's `index`. Only the `telemetry.skillCalls[]` recording is run-specific. `smoke-stack-skill-routing.sh` asserts the rule keeps no toolkit table of its own - a second copy is the one that goes stale.
- That gate previously required the contract to name `ai-ios-toolkit` and `ai-android-toolkit`, which pinned in place the exact two-row table that was the bug. It now asserts the resolution source instead of a list of names.
- Codex's `AGENTS.md` gained the same pointer (it had zero mentions of the credential path; Copilot had one line, Claude none).

## [16.0.1] - 2026-08-23

Found by reviewing v16.0.0 after it shipped. Four defects, three of them mine from that release.

### Fixed

- **`/multi-agent:update` could not download the release, and said the opposite.** Step 3 ran `npm pack --registry "$REG"` under a comment claiming "an ambient .npmrc must not reroute the scope elsewhere". `--registry` does **not** override a scope mapping: a user-level `@scope:registry=https://npm.pkg.github.com` line wins, npm fails with `notarget`, and `--silent` hid the reason. Measured on the maintainer's own machine - exit 1, no tarball. The scope is now pinned with `--@<scope>:registry`, the failure prints the diagnosis and the `npm config get` command that confirms it, and the tarball name comes from npm's own output instead of a glob that would have extracted a stale tgz left in the temp dir. This mattered more than usual because v16.0.0 published a `required` floor whose remedy is exactly this command.
- **`analyst.evidence` and `analyst.signals` shipped declared-but-inert in v15.22.0.** Nothing read them. They passed `smoke-prefs-consumed.sh` because the words "evidence" and "signals" saturate the very file that should have consumed them. Phase 1d now names `prefs.global.analyst.evidence[]`, `.signals[]` and `.webSignals` where it dispatches, and states that a source absent from its array is off rather than unreachable.
- **`priorArtEnrichment.topN` never reached the script it configures.** `triage-memory.mjs query` reads `memoryRecall.maxResults` (5) unless `--top` is passed, so a user setting `topN: 3` got 5 and no signal. Phase 4 passes `--top` now.

### Changed

- **`smoke-prefs-consumed.sh` was reporting green off its own prose.** The gate lists formerly-inert settings in its header to document them, and its own file sat in the search path, so `contextOffload.minLines`, `learningsLedger.maxBriefEntries`, `testGap.scanTree` and `.promoteSeverity` were all satisfied by the sentence describing them as broken. It excludes itself now, the way the portability and personal-data scanners already did.
- **A second rule, for the blind spot that let two of the above ship.** A new setting must name its full path - `parent.leaf`, or the shell form `prefs_parent_leaf`, which is the stronger signal since it can only come from resolving that exact key. Bare-leaf matching survives for 32 grandfathered settings that are legitimately referenced by leaf name in prose. The list is a ratchet enforced mechanically, not by promise: an entry that has since been wired fails the gate until it is deleted.
- Underscore is a word character to both `grep -w` and `\w`, so a bare-leaf search never sees `${prefs_testGap_scanTree:-false}`. Three genuinely wired settings looked dead the moment the gate stopped searching itself; recognising the shell form is what tells a real defect from that artefact.
- The scan is one filesystem walk matched in memory instead of two recursive greps per setting across 944 files: 2 minutes back down to 1.5 seconds.

## [16.0.0] - 2026-08-23

### Removed

- **`--dev` and the four `dev-*` commands.** They existed only to express one boolean - skip Analysis and Planning - and made the user learn four command names to say it. Depth is now a question the run asks itself.
- **"Fast plus unattended" as a combination.** This is the part that is a behaviour change rather than a rename, so it is stated plainly: `dev-autopilot` and `dev-local-autopilot` have no equivalent. Autopilot may not ask questions, so something has to choose the depth, and an unattended run is the worst place to drop analysis and planning - nobody is watching to notice what the shortcut lost. A cron job or script on those names now picks: stay unattended and pay for the full pipeline, or stay fast and have a person present.
- All four names ship as **redirect stubs** for one minor release. A stub prints where to go and runs no phase; the files are deleted in the next minor. Same two-step the `delete` → `uninstall` rename used in v12.0.0.

### Added

- **Phase 0 Step 7.5, the depth picker.** Full or Short, asked once, recommended from the `taskType` Step 7 already computed: `bugfix` / `chore` recommend Short, `feature` / `refactor` / `component` recommend Full. `/multi-agent` and `/multi-agent:local` ask it; both autopilot entries and analysis mode do not.
- The question **names the caveat before the choice, not after**. When the intake carried an analysis document or a Figma reference, Short is the option that skips the only two phases that would turn that document into a task breakdown, and the user reads that inside the question.
- **`smoke-pipeline-surface.sh`** - four entries, four stubs, no live `--dev` anywhere, the depth step passes `ASK_CHOICE_DEFAULT`, help lists only commands that exist, and the four downstream readers of `onlyDevelop` still read it. Both of its scanners carry a planted-probe self-test, so a green run means "scanned and clean" rather than "the pattern matched nothing".
- **`smoke-analysis-mode.sh`** - 41 checks over the analysis mode: its phase set writes no code and runs no test, Phase 1 produces the document the pre-flights block on, both pre-flights read a status instead of aborting, the humanizer is invoked rather than approximated by a punctuation grep, the validator runs in every mode and fails closed, the open-question walk is reachable from Phase 2 and Phase 4, and Locked 30 still holds (no Figma access after the analysis phase, with a probe proving the detector can fire).

### Changed

- **`state.onlyDevelop` is untouched.** The whole point of routing the picker into the existing key is that nothing downstream had to change: Phase 2's pre-flight, Phase 3's model selection, `agent-state.schema.json` and `usage-report.mjs` all keep working, and only who sets the key is different. `smoke-pipeline-surface.sh` asserts all four still read it.
- **Tracker tiles flip late instead of being pre-marked.** The tracker boots at Step -1 and the depth answer arrives at Step 7.5, so a Short run registers Phases 1 and 2 like any other and flips them to `skipped` when it learns. Pre-marking is forbidden by the ordering contract and produces a visually scrambled tile stack; this is the pattern the contract already prescribed for exactly this case.
- `gen-mode-dispatch.mjs` no longer knows the four dev modes, and `smoke-mode-dispatch-drift.sh` asserts it **rejects** them - a generator that still answers `--mode=dev` is an invitation to restore the command.
- `smoke-dev-mode-review.sh` → **`smoke-short-run-review.sh`**. Its concern survives the rename intact: the fast path reviews its own work. Analysis and planning shape work that has not happened yet, so an already-scoped task can skip them; review judges work that now exists and has no substitute.
- Command inventory reads **51 files, 47 live commands**, and both numbers are derived. `smoke-command-inventory.sh` counts stubs by the H1 title rather than anywhere in the body, because `help` legitimately lists the retired names under a "Removed in v16.0.0" heading and counting it would report one stub too many with a straight face.
- Three gates (`smoke-command-inventory`, `smoke-generate-issue`, `smoke-review-readiness`) were each asserting the exact inventory header string. Two now check only that it leads with the derived count: one string asserted in three places means a legitimate change has to be made three times, and the third gets forgotten.
- Help, modes, the tracker contract, both phase docs, the dispatcher, the cross-CLI contract, both READMEs, `docs/features.md` and `docs/architecture.md` all describe depth as a question. CHANGELOGs, `docs/internal/`, ROADMAP release entries and prefs migrations keep their `--dev` mentions: they record what was true when they were written, and rewriting that is how a changelog stops being evidence.

### Fixed

- **Fourteen files still pointed readers at the retired command names.** The first sweep of this release chased the `--dev` flag and missed the class beside it: a file can drop the flag and still say "run `/multi-agent:dev`", which now only prints a redirect. Both READMEs, the Copilot instruction template, the mode-comparison tables in `local-autopilot` (both trees), the Copilot orchestrator's phase-set list, `ios-coding-standard`, `resume-local`, `store-ready`, `rules.md`, Locked 30 and 31, the analysis template, `render.md`, the complaint template and command, and the complaint schema all named a command that no longer dispatches. `smoke-pipeline-surface.sh` gained check 5b for the class, with a planted probe and a one-line-of-context rule so a wrapped removal notice is not misread as a fresh pointer.
- **The Copilot orchestrator's Phase 0 contract said "8 sequential interactive steps" and stopped there**, so a Copilot run had no instruction to ask the depth question at all. It now names Step 7.5 and who is exempt from it.
- `update`'s sample output still claimed 205 skills and 228 scripts; the real numbers are 208 and 245.
- The three generated indexes (`skills-index.md`, `.skills-index.json`, `shared/README.md`) were carrying the old command descriptions and skill counts; regenerated.
- **`local` mode's Phase 5 was documented two ways.** `modes.md` said the local pipeline keeps the interactive test prompt; `gen-mode-dispatch.mjs --mode=local`, which is byte-equality-enforced against the command file, drops Phase 5 entirely. The generator is right - the gate hands the user a change checked out of a worktree, and local has no worktree - so both `modes.md` rows now say so.
- **`cross-cli-contract.md` section 7 claimed Claude Code was macOS-only.** It has not been for some time, and the assumption it invited - that a Windows user necessarily arrives through Copilot or Codex - produces wrong conclusions about which code paths need to be portable.
- The phase-4 telemetry block named as compression debt in v15.22.0 was collapsed to an `emit()` helper.

## [15.22.0] - 2026-08-23

### Added

- **`ai-analyst-toolkit`, the second always-on plugin.** Analysis work needs facts the repo cannot supply: whether a dependency has a known upstream bug, what a release actually changed, whether anyone outside this team has reported the same symptom. Five skills answer those, and the plugin is enabled everywhere alongside `ai-common-toolkit` because none of the questions are stack-specific.
- **Two tiers, kept apart on purpose.** EVIDENCE (`evidence-github`, `evidence-registry`) has a citable identity and may feed a specification body, cited `GitHub:<owner>/<repo>#<n>` or `Release:<pkg>@<version>`. SIGNAL (`signal-community`) is advisory and may only reach Section 20 Risks. A specification is reviewable because every claim in it can be checked; a forum post is one person's experience, and letting it into a requirements section makes the checkable and the anecdotal look identical.
- **Access reflects what these sources actually allow in 2026.** GitHub goes through the already-authenticated `gh` CLI. npm, PyPI, Maven Central and Swift Package Index are keyless HTTP. Stack Overflow and Hacker News are keyless too, with HN search through `hn.algolia.com` because the official Firebase API has no search endpoint. Reddit closed self-service app registration in late 2025 and X discontinued its free tier for new developers, so both are reachable only through the host CLI's own web search: `prefs.global.analyst.webSignals`, off by default, best-effort, and exempt from cross-CLI parity for the same reason Figma component work is.
- **Phase 1d in the analysis engine**, plus `evidence.outside[]` / `evidence.signals[]` in `analysis-spec.schema.json`. Signal is deliberately excluded from the Locked 27 evidence digest: it changes by the hour, so including it would produce a new digest every run and permanently invalidate the cache the digest exists to serve. The consequence is stated where it matters - a cache hit reuses yesterday's signal rows, and `--no-cache` is how you refresh them.
- **Four other callers, all optional.** `refactor` Step 0 routes its GitHub / X / Reddit research through the skills instead of ad-hoc searching. The shared readiness review asks whether a reported bug is already open upstream, which changes the comment rather than the score. `complaint-analysis` can corroborate a complaint beyond one device. Phase 4 triage defers a finding that blames a third-party library, with its citation, instead of sending Phase 3 to fix code that is not ours.
- **`prefs.global.analyst`** (`evidence[]`, `signals[]`, `webSignals`) and **`prefs.global.analysisPhase`** (`mode`, `forceFull`, `commitDoc`). The second block had been referenced by three phase docs since v15.17.0 without ever being declared.

### Fixed

- **`analysisPhase.forceFull` was declared-but-inert.** `smoke-prefs-consumed.sh` caught it the moment the schema landed: nothing read the key. Phase 1 Step 4 now names both it and `mode` in the when-table - one decides whether a document is produced, the other how deep it goes.
- **`evidence.documents[]` had nowhere to land.** v15.20.0 added `fetch-document.sh` and a `document` link type, but `analysis-spec.schema.json` is `additionalProperties: false` and had no bucket for the result, and the Phase 1 fan-out table had no row for the type. Both exist now, and a document whose text could not be extracted stays in the list with `fetched: false` so Section 21 still cites it - dropping it would make the analysis read as if the file was never supplied. `standards.kind` accepts `document` too.
- **Copilot and Codex silently lost every `ai-common-toolkit` skill whenever a repo named its stack.** `pluginsToDeliver()` derived the delivery set from `enabledPlugins` and kept only what it found there, so a `.claude/settings.json` listing `ai-ios-toolkit` alone dropped all 32 common-toolkit external skills (humanizer, firebase, council, search-first, the accessibility audit and five more) from both copy hosts. Claude Code loads them from the marketplace, so the loss was invisible on the one host with a plugin loader and total on the two without. The installer now unions an exported `ALWAYS_ON` set into every selection, and `smoke-install-layout.sh` imports that same constant instead of restating the enabled list - a hardcoded copy of it is what let the gate stay green through the whole regression.
- **The document fetch was invisible in the progress contract.** `by-type` and the deep-fetch line now carry `document`, with a `degraded` outcome for the found-but-unreadable case (a PDF on a machine with no `pdftotext`).

### Changed

- `smoke-stack-skill-routing.sh` gained check 7: the always-on toolkits must be wired in every mechanism that carries them. "Always enabled" is not one switch - the routing table, the plugin builder, the installer (Copilot and Codex have no plugin loader, so a plugin missing there is simply absent) and the two SKILL files each fail silently on their own.
- Description-surface ceiling 82600 -> 83400 and the paired literal in `context-budget-gate.test.mjs`. All three new descriptions were trimmed toward the average first; the average held at 320 against its 420 ceiling, which is this gate's own signal that the tree grew rather than that anything is padded.
- Phase-doc aggregate 55800 -> 55900. Compression came first and three times, taking the new prose from 220 tokens to 110; the Phase 1d contract itself never entered this budget, because it lives in `multi-agent-refs/analysis/evidence.md`.

## [15.21.0] - 2026-08-23

### Added
- **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.
- 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.

### Changed
- **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.
- **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.
- `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.
- Phase-doc token budget: total 55500 -> 55800. Compression came first and twice, 430 tokens down to 250, by collapsing the derived-versus-asked explanation and leaving the walk itself in the ref that Phase 4 and `analysis-resolve` already mount.

## [15.20.0] - 2026-08-23

### Added
- **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.
- **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`.
- **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.

### Fixed
- 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.

## [15.19.0] - 2026-08-23

### Added
- **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.
- **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.
- **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.
- **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.

### Fixed
- **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.

### Changed
- 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.
- 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.

## [15.18.0] - 2026-08-22

### Added
- **`/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.
- 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.
- 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.

### Changed
- 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.
- 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.

## [15.17.0] - 2026-08-22

### Fixed
- **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.

### Changed
- **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.
- 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).
- `analysis-output.schema.json` gains `docStatus`, `docPath[]` and `openQuestions[]` - the fields the two pre-flights branch on.
- Phase-doc token budget: total 54400 -> 54900. Compression came first and twice: the new step went from 745 tokens to 497 (its history moved to this entry, the per-ref descriptions moved into the refs' own headers), and the front-matter parse contract, spelled out identically in both pre-flights, now lives in `phase-3-dev.md` with `phase-2-planning.md` pointing at it. The 17.4k-token engine itself left the budget entirely by moving to refs.

## [15.16.1] - 2026-08-22

### Fixed
- **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.
- 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.

## [15.16.0] - 2026-08-22

### Added
- **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.
- 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.

- **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.

### Changed
- `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.
- 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.
- `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.

## [15.15.0] - 2026-08-22

### Added
- **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.
- **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.
- `/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.
- **`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.

### Fixed
- 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.

### Changed
- `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.

## [15.14.0] - 2026-08-22

### Added
- **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.
- **`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.

### Fixed
- **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.
- **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.
- **`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`.
- 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`.

### Changed
- `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".
- **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.
- **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.
- Phase-doc token budget: total 54050 -> 54400. Compression came first and took the new Step 0.6 prose from 469 tokens to 337, by moving the rationale, the exemption list and the `npm dist-tag add` recipe into `multi-agent-refs/rules.md` "Supported Version Gate" (loaded by 25 commands, outside this budget) and into the script header, leaving the phase doc with the call, the decision table and the halt.
- `smoke-update-check.sh` grew from 12 to 27 assertions, covering the floor, the clamp, the legacy cache, the wrapper's three exit paths and the doc wiring on both sides.
- **`smoke-context-links.sh`: the extractor has a behaviour test now.** A 250-line deterministic classifier that every Phase 0 deep fetch dispatches on had no test of its own, so a regression in any pattern would only ever surface as "the ticket had a link and nothing was enriched", with nothing red to point at. 32 assertions over every emitted type, both Fortify entry shapes, dedup, and the two things that must NOT match: prose containing the words, and a bare non-doc URL.
- `sync/SKILL.md` lost a "Special inputs" table that repeated the argument table from the top of the same file, minus one row.

## [15.13.0] - 2026-08-22

### Fixed
- **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.

### Added
- **`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.

### Changed
- 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.

## [15.12.2] - 2026-08-22

### Fixed
- **`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.
- **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.

## [15.12.1] - 2026-08-22

### Fixed
- **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.



## [15.12.0] - 2026-08-22

### Changed
- **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.
- **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.
- Declared minimums move to `v3.0.0+`; 2.x only ever existed under the old package name.

### Migration
- `@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.
- A hand-registered client that the pipeline installer does not manage needs `<cli> mcp remove dev-toolkit` once.

### Fixed
- **`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.

## [15.11.0] - 2026-08-21

### Changed
- **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.

### Fixed
- **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.

### Tests
- `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.

## [15.10.1] - 2026-08-21

### Fixed
- **`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.
- **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`.

## [15.10.0] - 2026-08-21

### Added
- **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.
- **`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.
- **`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.
- **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.

### Security
- **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.

### Changed
- `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.
- 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.
- `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.
- 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.

### Tests
- `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`.

## [15.9.1] - 2026-08-20

### Changed
- **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.

## [15.9.0] - 2026-08-20

### Fixed
- **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`.

### Changed
- **`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.
- **`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.
- Stale version tables refreshed: `SECURITY.md` supported-versions moves to the 15.x line; `ROADMAP.md` "Current Release" becomes a rolling "Recent Releases".

### Companion
- **`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.

## [15.8.1] - 2026-08-19

### Fixed
- **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.

## [15.8.0] - 2026-08-19

### Added
- **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.

## [15.7.0] - 2026-08-19

### Added
- **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.
- **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`.
- **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.

### Changed
- **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.
- **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.

### Fixed
- **build-stack-plugins.mjs dead import**: unused `APPLE_ONLY` import removed (superseded by the `STACK_ONLY` routing table).

## [15.6.1] - 2026-08-19

### Changed
- **`/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.

### Fixed
- **`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.
- **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.
- **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).
- **Version and user fields stop reporting null**: the reporter reads the installer's `~/.claude/.pipeline-version` marker (installed trees have no adjacent `package.json`) and falls back to `identity.name` when `identity.username` is absent.

## [15.6.0] - 2026-08-18

### Fixed
- **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.

### Added
- **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`.

## [15.5.0] - 2026-08-18

### Added
- **`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.

### Fixed
- **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).
- **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.

## [15.4.0] - 2026-08-18

### Added
- **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.
- **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.
- **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.

### Fixed
- 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.

## [15.3.2] - 2026-08-17

### Fixed

- `local` and `local-autopilot` mode docs pointed at the retired `commands/multi-agent.md` dispatcher spelling; they now reference `commands/multi-agent/SKILL.md`, and the Codex installer rewrites the directory-layout spelling to the router skill (dedicated rewrite rule, smoke guard for the new corruption shape, unit tests).
- The sync spec copies (command tree + Copilot shared-core mirror) disagreed on the Claude source list and file mapping; both now carry `multi-agent-refs/` and `lib/` and the same mapping rows.
- The dispatcher entry doc now declares the TaskCreate ordering rule and is scanned by `smoke-tasklist-ordering.sh`, closing the one mode (full interactive) the inventory missed.
- `check-md-links.mjs` resolves `$HOME/.claude/commands/` links against the repo source, so a future layout move cannot leave dangling dispatcher references silently.
- Install-layout fingerprint fixture regenerated for `usage-report.mjs` and the relocated reference files.

### Changed

- refactor Step 0c (dev-toolkit research) and setup Step 3b (App Store Connect onboarding) moved to on-demand references under `multi-agent-refs/`, keeping both skills inside their token budgets.

## [15.3.1] - 2026-08-15

### Changed

- Documentation and wording clarifications.

## [15.3.0] - 2026-08-15

### Added

- `/multi-agent:setup` gains an optional one-time operational-token onboarding
  step (opt-in). The token is stored in the OS credential store only, never in a
  file, prefs value, git, or synced tree.

## [15.2.0] - 2026-08-15

### Added

- `/multi-agent:update` auto-configures the optional operational integration when
  its token is already onboarded, resolving it from the credential store; it never
  fabricates or ships a secret, so a machine without one is unaffected.

## [15.1.0] - 2026-08-14

### Added

- Optional, opt-in per-run operational reporting (`usageLog`), off by default and
  a no-op unless configured.
- `/multi-agent:refactor` Step 0d (band F) reads a local run-diagnostics ledger to
  rank recurring failures as prioritized improvement areas - offline, no auth.

### Changed

- Reconciled the Copilot shared-core `multi-agent-refactor` skill with the Claude
  command version, ending the prose drift that had accumulated between the two.

## [15.0.1] - 2026-08-13

Post-15.0.0 audit release: three blocking defects in the 15.0.0 surface, two
data-loss paths, and the doc drift that let a command count go stale in 17
places at once.

### Fixed

- **The 15.0.0 plugin-only migration never ran on an upgrade.** Its prune was
  gated on `.external-skills-manifest.json`, which no released version wrote on
  Claude Code, so every machine upgrading from 14.x kept the whole ~151-dir
  external catalog (the duplicated per-session descriptions and plugin-shadowing
  copies ADR-0009 exists to remove), and `uninstall --claude` refused to clean it
  for the same reason. Pre-manifest installs are now migrated by proof rather
  than by name: a skill dir byte-identical to the shipped catalog is provably
  pipeline-delivered and is removed, a dir that differs is kept and reported,
  and the new `install --prune-external` flag removes those too (still never a
  dir carrying `local-only: true`). Gate: `smoke-claude-external-migration.sh`.
- **The cross-host stack filter read a file nothing writes.** `pluginsToDeliver`
  looked only at `~/.claude/settings.json` while `/multi-agent:stack` writes the
  invoking repo's `.claude/settings.json`, so a stack selection either delivered
  the entire catalog to Copilot/Codex (logged as if a filter had run) or, worse,
  delivered a different stack than the repo asked for. Repo settings are now read
  first, user-global second, `--platform` last.
- **Copilot's skipped-stack prune could delete user-authored skills.** The prune
  was derived from catalog *names*, so a user's own `~/.copilot/skills/<name>/`
  sharing a catalog name was removed with no prompt. It is now scoped to the
  previous delivery manifest (or, pre-manifest, to dirs byte-identical to the
  catalog) - the same contract `uninstall` already used.
- **Manifest-driven removal had no path containment.** A name carrying `..` or a
  path separator in `.external-skills-manifest.json` resolved outside the skills
  directory before reaching a recursive delete.
- **`validate-complaint-doc.mjs` rejected reports that follow the template.**
  Section 1's coverage table cites `C-NN` ids in its last column and was read as
  verdict-less triage rows, and verdict matching was positional-substring, so a
  summary cell mentioning `core-data` classified the row as `core`. Triage rows
  are now scoped to the Triage section with the id in the first cell, and verdict
  tokens match cell-exact. The smoke fixture carries both Section 1 tables so the
  gate is exercised against real template output.
- **`/multi-agent:stack` with no arguments wiped the repo's stack selection.**
  The implementation block resolved an empty arg list to "common only" and then
  applied the write rule that sets every other stack toolkit to `false`. Zero
  args now exits into status mode, and the picker's `frontend (web)` label maps
  back to its canonical arg.
- `parse-complaints.sh` returns the documented exit 4 when `--file` / `--format`
  is passed without a value instead of dying on `set -u`, and redacts all-letter
  PNRs that follow a PNR-ish keyword.
- `migrate-prefs.mjs`: the schema-unreadable fallback set now includes `2.5.0`,
  so a 14.x prefs file no longer throws `unknown schemaVersion` on an install
  tree whose `schemas/` cannot be read.
- `check-derived-drift.mjs`: an acknowledged drift pin no longer silences a
  cache-only resolution, which had let the gate exit 0 while nothing
  authoritative was consulted.
- `match-skills.mjs` gains the `~/.codex/multi-agent-refs/skills` index
  candidate, so dynamic skill loading works on Codex instead of exiting 1.

### Changed

- `smoke-command-inventory.sh` now derives the command count for `README.md`,
  `README.tr.md`, `docs/architecture.md` and `docs/ecosystem.md` as well. Those
  four files held 17 of the 20 "50 commands" sites and none was gated, so adding
  `complaint-analysis` desynced all of them at once.
- Doc corrections: autopilot / local modes document 7 phases, not 8 (the
  interactive Phase 5 test gate is dropped in every autopilot and local variant,
  per `gen-mode-dispatch.mjs`) - README EN+TR and `tracker-contract.md`; the
  Node badge matches `engines` (20.11+); the publish diagrams say public npm
  rather than GitHub Packages; skill / schema / fixture counts match the tree;
  `ecosystem.md` drops the retracted "frozen `figma-*` fallback" claim; ROADMAP
  catches up four majors; ADR-0007 records Codex's return as a native target.
- `SECURITY.md` ships in the package, so the README link resolves on npmjs and
  in an installed tree. Install help documents `--dry-run`, `--index-only` and
  `--prune-external`.

### Removed

- Dead pre-v15 code: `copyExternalSkillsFiltered`, `classifyExternalSkill` and
  its prefix tables (superseded by the routing table), `existingSkillNames`
  (superseded by `pipelineOwnedSkillNames`, and a hazard if called), and an
  unconsumed `MCP_SERVER_NAME` re-export. Six internals no longer export a
  surface nothing imports.

## [15.0.0] - 2026-08-13

### Changed (BREAKING)

- **Claude Code stack skills are plugin-only** (ADR-0009). The installer no longer
  copies `shared/core` + `shared/external` into `~/.claude/skills`; the
  `multi-agent-plugins` marketplace is Claude Code's only stack-skill source,
  namespaced (`ai-ios-toolkit:<skill>`). Exactly two pipeline-owned compliance
  catalogs stay local (`PIPELINE_LOCAL_SKILLS`). Old installs are migrated
  manifest-scoped (`.external-skills-manifest.json`); user-authored dirs survive.
- **Marketplace plugin names lose the `engineering` infix**:
  `ai-<stack>-engineering-toolkit` -> `ai-<stack>-toolkit`. `enabledPlugins`
  keys must migrate; `/multi-agent:stack` deletes the retired keys.
- **`/multi-agent:ship` -> `/multi-agent:resume-local`** (the tail command opens a
  PR but merges nothing, so "ship" oversold it). Prefs `global.ship` ->
  `global.resumeLocal` via schema 2.6.0 (step migration + monolith, autoFix value
  carried across the finish -> ship -> resume-local chain).

### Added

- `/multi-agent:stack` **multi-select**: several stacks per call (`ios backend`),
  a native multi-select picker with no args, `web` alias, always-on
  `ai-common-toolkit`, legacy-key cleanup, and a Copilot/Codex refresh offer.
- Every marketplace plugin ships **`ai-<stack>-toolkit:help`**: a language-aware
  catalog rendered live from `plugin.json` (drift-proof by construction).
- `pipeline/scripts/_stack-routing.mjs`: the single routing table shared by
  `build-stack-plugins.mjs`, the installers and the skills index.
- Skills index entries carry `plugin` + `invokeAs`; `match-skills.mjs` returns
  plugin-namespaced names. `skill-conformance.mjs` probes the marketplace
  checkout and version-named plugin cache as skills roots.

### Changed

- Copilot CLI and Codex CLI local copies are **filtered to the enabled stacks**
  (`pluginsToDeliver` + `partitionExternalSkillsByPlugins`); Codex previously
  received the full catalog unconditionally and now also writes a delivery
  manifest. One `/multi-agent:stack` selection governs all three hosts.
- Prompt-context skill tables (phase 1/2/4), `humanizer` invocations,
  `build-optimize` dispatches and the store-review references are
  plugin-namespaced.
- `DESC_CEILING` 82000 -> 82600 and phase-doc total 52750 -> 53100: the
  namespaced tables and the new command descriptions did not fit ceilings that
  were already at (or 193 bytes past) their limit; the always-on surface itself
  shrank with the local copy gone.


### Added

- **`/multi-agent:complaint-analysis`** — customer-complaint / customer-reported-error
  triage as a new one-shot ops command (51st command). Ingests complaints from
  free-text paste, csv/xlsx/txt/json exports (`pipeline/lib/parse-complaints.sh`,
  with built-in PII redaction: email / phone / card / national-id / PNR shapes),
  Jira issues, or Confluence URLs; fetches Graylog evidence per
  trxId/conversationId via the existing `fetch-graylog.sh`; correlates read-only
  against the selected client + BFF repos (user-confirmed layer tagging:
  ios / android / web / mobile-bff / web-bff); and classifies each complaint as
  `client:<layer>` / `bff:<layer>` (root cause + citations + a fix plan grounded
  in the existing architecture + a ready-to-run dev prompt), `core` (routing
  recommendation to the backend core team, never a fix analysis), or
  `insufficient-evidence`. Report dispatches to Local (default) / Confluence /
  Jira behind a deterministic gate (`scripts/validate-complaint-doc.mjs`:
  sections, verdict tokens, per-core routing entries, humanizer punctuation,
  redaction-leak scan; smoke: `smoke-validate-complaint-doc.sh`). State contract:
  `schemas/complaint-analysis-spec.schema.json`. Graylog-as-primary-evidence is a
  documented, command-scoped exception to the advisory-only rule in
  `features/external-context-injection.md`. `DESC_CEILING` raised 81000 -> 82000
  (gate + pinned test together): the two new ~430-byte descriptions did not fit
  in the ~25 bytes of headroom the surface had left; the average stays at 319
  against the 420 ceiling.

## [14.2.2] - 2026-08-04

### Added

- **`localization-reuse-map` skill** added to `shared/external` — a per-screen
  localization-reuse map that ties new/legacy/CMS translation keys together,
  with follow-up fixes for placeholder rendering, snapshot staleness, and the
  three gates its initial commit left red.
- **`docs/ecosystem.md`** — a detailed diagram of how this repo, the
  `multi-agent-plugins` marketplace, and `dev-toolkit-mcp` compose at install
  time and run time. `docs/architecture.md` cross-links it.
- **Turkish README (`README.tr.md`)**, cross-linked from `README.md`.
- Jira intake now surfaces the **parent issue's description** as a
  confirmable candidate when the child issue's own description is empty.
- `payload-contracts` reference doc, consolidating the required-reading list
  for Phase 6 and 7.

### Fixed

- Corrected stale 44/43-command counts to the real 49 across README,
  `cross-cli-contract.md`, and both sync/update `SKILL.md` copies; fixed a
  hardcoded "42 command specs" log line in `install/codex.mjs` to count the
  actual source tree instead.
- **`audit-log-rotate.sh` could delete the archive it had just created.**
  `gzip` preserves the source file's mtime by default, so rotating log
  content already older than `KEEP_DAYS` produced a `.gz` that inherited
  that old mtime - and the very next line's retention sweep (`find -mtime
  +$KEEP_DAYS -delete`) deleted it in the same run, before it was ever read.
  The archive's mtime is now reset to rotation time, so retention counts
  from when it was archived, not from the age of the content inside it.
- The mode banner claimed pickers stay English when they didn't - language +
  dialect resolution fixed.
- The cost tracker now reads the same state file `phase-tracker.sh` writes.
- Jira conversion table pipes are now escaped; Intake warnings render at h2.
- Copilot mode skills carried a stale review claim and missing payload
  pointers - corrected.
- 18 cross-script drift and edge-case bugs resolved from code review.
- Markup dialect is now chosen per surface for channel posts; the Jira table
  heading-conversion bug fixed.
- `localization-reuse-map`: empty-cell placeholder is now a single dash; a
  stale snapshot no longer reads as an unauthored key.

## [14.2.1] - 2026-07-30

### Changed

- **`testflight-validation` merged into a new `/multi-agent:store-ready`, and the
  Android side brought to parity.** The iOS archive audit existed twice with
  identical arguments  -  `ios_app_store_audit({archive_path, rules: "all"})` in the
  command's Gate 1 and again inside `sim-test.md`'s `store-ready` scenario. Two
  copies of one call is how the second door grew with no Gate 2, no Gate 3 and no
  Android equivalent. There is now one implementation.
  Gates are symmetric per platform, because the failure modes are: a malformed
  package, a package the store itself refuses, and a policy a human enforces.

  | Gate | iOS | Android |
  |---|---|---|
  | 1 Static | `ios_app_store_audit`, 18 rules | `android_apk_audit` + `google-play-compliance`, 21 rules |
  | 2 Authoritative | `altool --validate-app` | `SKIPPED` |
  | 3 Policy | `app-store-review` vs source | `play-store-review` vs source |

  Gate 2's asymmetry is reported as an asymmetry rather than smoothed over: Play's
  authoritative check exists only server-side, through a Publishing API draft edit,
  and the pipeline ships no client for it. An Android run therefore clears at most
  2 of 3 and never prints `passed`. `bundletool validate` is Gate 1 and is not
  promoted to fill the hole.
  The running-app sweep became Step A rather than a separate errand  -  a build can
  be structurally perfect and still ship an unreadable screen  -  and it degrades to
  `SKIPPED (no booted device)` instead of halting the package validation.

  Nothing was removed. `testflight-validation` is a thin iOS-pinned alias (removing
  a command is a breaking change to the slash-command surface), and
  `test "store-ready"` still resolves, now as a hand-off. `sim-test.md` keeps only
  the pointer, so a dark-mode run no longer loads altool and credential-tier prose
  it never needs.

### Added

- **Four fixed-scenario `test-*` commands.** `/multi-agent:test-dark-mode`,
  `:test-accessibility`, `:test-dynamic-type` and `:test-screenshots [locale]` pin a
  scenario the quoted-tag form made you remember and quote. Typing `test-` now
  autocompletes the matrix list instead of returning a single entry whose tags live
  only in the help text. The scenario-tag form is unchanged and not deprecated -
  each command is an alias for it, delegating to the same `sim-test.md`, so there is
  one implementation and no forked logic. Same pattern the mode aliases already use
  (`:dev-autopilot` = `--dev autopilot`).
  `screenshot <lang>` became `test-screenshots [locale]` rather than
  `test-screenshot-tr`: the locale is a parameter, and freezing one language into a
  command name buys one command per language. `store-ready` deliberately got no
  alias - it takes an optional archive path, and its 18-rule audit is the same one
  `/multi-agent:testflight-validation` runs as its first gate, so the alias would
  have advertised a second door onto a duplicate.

### Fixed

- **Copilot's `purge` could not see a single worktree.** It discovered them with
  `find {repo}/.worktrees/ -name "agent-state.json"`, and no worktree carries that
  file: Phase 0 writes state to `$HOME/.claude/logs/multi-agent/{project}/{task-id}/`.
  Verified on a repo with two live task worktrees  -  the marker scan returns zero
  while `purge.sh`'s own directory enumeration finds both. So the skill reported
  "nothing to purge" as success with real worktrees on disk. The Claude Code command
  had already been fixed by delegating to `purge.sh`; the Copilot mirror never
  received that fix, which is the second time a repaired Claude-side surface left its
  Copilot counterpart behind in this release.
  It now delegates to the script and explicitly forbids re-introducing a
  marker-file scan.
- **The stale log location survived in four more places** after `clear-logs` was
  redirected. `shared/core/multi-agent` told Phase 0 to create `agent-log.md` and
  `agent-state.json` inside `.worktrees/PROJ-{id}/`, told `:resume` to look for state
  there, and printed that path as the report location; `phase-0-init` said "create log
  dir" without naming one, 49 lines above the line that does. All four now name
  `$HOME/.claude/logs/multi-agent/{project}/{task-id}/`, the path the tracker,
  `prune-logs.sh` and `:resume` actually read.
  `status` and `log` keep their worktree scan as a legacy fallback: it finds nothing
  on a current tree, but it is harmless and would still serve an old one.

- **`clear-logs` scanned a path nothing writes to, and help advertised it as a
  slash command that does not exist.** It looked for
  `.worktrees/PROJ-*/agent-log.md` and `agent-state.json`; Phase 0 has written both
  to `$HOME/.claude/logs/multi-agent/{project}/{task-id}/` since the layout moved.
  Against the current tree the scan matches zero files, so the op reported "logs
  cleared, deleted 0" as success while every real log stayed on disk  -  worse than a
  missing command, because it reads as done. There was also no `clear-logs` command
  directory, so the `/multi-agent:clear-logs` entry in the Copilot help pointed at a
  slash command that was never installed.
  The name is kept as a redirect to `prune-logs` rather than deleted, so an existing
  invocation lands somewhere correct. `prune-logs` and `garbage-collect` now appear
  in the Copilot help, matching the Claude Code side. This is the `finish` → `ship`
  class of drift the inventory gate's own comment describes, in the files that gate
  does not parse.
- **`sim-test.md` advertised two scenarios it never implemented.** `"biometric"` and
  `"performance"` sat in the activation block with no implementation section, so
  reaching either fell through to the general sweep and was reported as the scenario
  asked for. Neither can be built symmetrically today: biometric has `ios_biometric`
  and no Android counterpart, launch timing has `android_launch_time` and no iOS
  counterpart, and the file auto-detects platform  -  so each would work on one
  platform and silently do nothing on the other. Both rows are withdrawn with the
  reason recorded in place, rather than shipped as half-features or left advertised.
  Advertised scenarios and implemented sections now match one to one.
- **`help` never documented two shipped commands.** `testflight-validation` and
  `ios-coding-standard` existed in the tree, installed correctly, and appeared in no
  block of `help/SKILL.md` in either language - `ios-coding-standard` was reachable
  only because it also sat in `prefs.global.routines`, i.e. it read as a personal
  routine rather than a first-party command. Both are now in the EN and TR Post-Hoc
  sections. A command absent from help is a command nobody runs.
- **Rule count drifted between the two language blocks.** The TR block advertised a
  17-rule iOS store audit against the EN block's 18 and `ios_app_store_audit`'s
  actual 18. The TR reader was being given a number no code produces.

### Changed

- **`DESC_CEILING` 78000 -> 81000**, with the paired pin in
  `test/context-budget-gate.test.mjs` moved in the same commit, as that test
  requires. Not a bump to silence a red gate: at 78000 the surface had **16 bytes**
  of headroom, so any new command failed regardless of how tersely it was described,
  while the gate's own average check sat at 320 against its 420 ceiling - the signal
  that the tree grew rather than that descriptions are padded. The alternative was
  shaving routing text off eight unrelated `shared/external` skills to fund four
  commands, which trades a real capability for a cosmetic number. Both the gate and
  the test now carry the ceiling's history inline so the next raise has to argue for
  itself. Cost is honest: ~470 always-on tokens per run.

## [14.1.1] - 2026-07-30

Three defects that all shared one shape: a name written in one place and read in
another, so the mechanism looked implemented, ran without error, and did nothing.

- **Phase 0 wrote the base-ref field its own exit gate does not read.** `/multi-agent:dev`
  recorded `"baseRefFreshness"` while `phase0-exit-gate.mjs` requires `baseFetchStatus`
  with one of `fresh | cached-stale | local-branch | aborted`. The value vocabulary was
  already correct - only the field name differed - so every dev-mode run failed its own
  exit gate with `baseFetchStatus="<unset>"` even when all four Phase 0 pickers had
  actually run. A gate that always fails is as useless as one that never fails. The
  canonical name appeared in five places and the wrong one in exactly one: the dev
  command's own doc. `smoke-phase-0-multi-repo.sh` now asserts that the name the exit
  gate reads equals the name the phase doc documents, and that no shipped file names an
  alternative, with a planted-line probe proving the detector can fire.
- **Branch memory never populated.** Phase 0 Step 3 read
  `prefs.global.recentBranches[{projectKey}]` while its own step 7 wrote the legacy
  `prefs.projects[].branches`, which `prefs.schema.json` marks pre-v2.1.0. Both spots
  also described a `{name, lastUsed}` entry the schema rejects (`branch` is required and
  `additionalProperties` is false), so a literal implementation would have failed prefs
  validation and the dedup - which keys on `branch` - would have accumulated a duplicate
  every run. The "reused from last run" picker option could therefore never appear.
  `migrate-prefs.mjs` carries stranded legacy entries into the canonical LRU, stamped
  with the migration time because the legacy field never recorded a real one and an epoch
  stamp would be pruned by the TTL on first read; `count: 0` marks them seeded rather
  than observed.
- **The sync skill hardcoded the author's git identity.** `git config user.name`,
  `user.email` and `gh auth switch --user` carried literal values in the plugin-publish
  block, while the same file's other two publish blocks used `{identity.name}` and
  `{owner}`. Since the file ships to every installation, a downstream user's
  plugins-repo commits would have been attributed to someone else and their `gh` account
  switched under them - and it contradicted the pipeline's own rule that the git author
  is always the user's identity.

Leak-gate coverage, which is why the third defect had gone unnoticed:

- `smoke-personal-data.sh` only ever scanned `pipeline/`, but the package also publishes
  `install/`, `docs/`, `index.js`, `install.js`, `README.md` and `CHANGELOG.md`, and every
  tracked file is public regardless of what npm ships. `CHANGELOG.md` was additionally in
  the `--exclude` list. A second pass now scans every tracked file outside `pipeline/`,
  driven by `git ls-files` so the covered set stays exactly "what is public" with no
  second list to maintain. `LICENSE`, `package.json` and `CODE_OF_CONDUCT.md` are exempt,
  because a package must name its author and a code of conduct must give a real contact.
- Added patterns for the author's own name, personal email and `gh auth switch` account -
  none were checked before, which is precisely how a literal identity survived in a
  shipped command - plus the employer's abbreviation where it is used as a symbol or
  workspace prefix, bounded so ordinary words that merely contain those letters
  (`HEALTHY`, `RHYTHM`) do not match.
- `--exclude-dir` for `.git`, `node_modules`, `.worktrees`, `.next` and `DerivedData`.
  Without it, `--root` mode scanned `.git/logs`, so auditing any checkout was guaranteed
  to "fail" on commit metadata no consumer receives, burying the real findings.
- Five leaks removed from public files: a real corporate email in `CHANGELOG-archive.md`,
  a corporate toolkit name in both changelogs, the author's website in `docs/adr/0008`
  and `docs/internal/`, and corporate symbol/repo/task literals in `docs/internal/`.
  These are gone from HEAD; git history still contains them.

## [14.1.0] - 2026-07-29

Two things the pipeline was supposed to do and did not: use the skills a project's
own toolkit says apply, and clean up after itself.

### Added

- **Phase 3 asks the stack toolkit which of its skills govern the task.** Each
  `ai-<platform>-engineering-toolkit` already ships an `index` skill holding a
  30-plus row intent-to-skill table, maintained beside the skills it points at.
  Phase 3 dispatched to that plugin for exactly one case (`taskType == "component"`),
  so `bugfix` / `feature` / `refactor` / `chore` had no skill dispatch at all:
  whichever skills the host surfaced by description match were the ones used, and
  nothing recorded or required any of them. That was the dev-side half of the gap
  v14.0.0 closed on the review side  -  review asked "was this built to the rules it
  was supposed to follow" while nobody had chosen any rules.

  The routing table is NOT copied into this repo. A second copy would drift the
  moment the plugin shipped a skill, and the copy here would be the stale one, so
  the pipeline asks rather than knows. `smoke-stack-skill-routing.sh` check 5 fails
  the build if a routing table appears in a shipped file (verified against a planted
  6-row table). Routed skills land in `telemetry.skillCalls[]` with
  `routedBy: "<toolkit>:index@<version>"`, so Phase 4 conformance can hold the run to
  what its own toolkit chose. An absent or disabled toolkit is a recorded no-op, not
  a halt  -  a backend repo has no toolkit and must still run.
  Contract: `refs/features/stack-skill-routing.md`.

- **Phase 6 removes a task's worktree once its PR is open** (`worktree-finalize.sh`,
  gated by `prefs.global.settings.worktreeAutoRemoveOnPr`, default **true**). It
  salvages `agent-state.json`, `phase-tracker.json`, `triage-output.json`,
  `.pipeline/`, `.build.log`, `.test.log` and `.review-diff.txt` into the log dir
  first, because Phase 7's triage-memory ingest, the learnings-ledger distill,
  `render-work-summary.sh`, `:resume`, `:status` and `:log` all read them  -  and the
  first three are `[ -f ]`-guarded, so a removal without salvage would have degraded
  silently rather than failing.

  It keeps the branch and **does not check it out**. `git worktree remove` leaves the
  branch as an ordinary local branch, so nothing is lost, while a checkout would move
  the user's HEAD and could collide with their own uncommitted work on another
  branch. Phase 5 removes-then-checks-out on purpose because it is a test handoff;
  this is not. Verified end to end: HEAD stays put, the user's uncommitted file
  survives, the branch is still checkoutable on demand.

  Every destructive path is gated and each skips with a reason rather than failing:
  real uncommitted changes, an unpushed HEAD, `--local` mode, a cwd inside the tree,
  an unregistered path. `--force` appears nowhere. Contract:
  `refs/features/worktree-finalize.md`. New state: `worktreeRemovedAt`,
  `artifactsPath`.

- Gates: `smoke-stack-skill-routing.sh` (12 checks) and `smoke-worktree-finalize.sh`
  (28 checks, exercising real git repos rather than grepping the doc).

### Changed

- `render-work-summary.sh` gained a log-dir fallback for state and tracker files.
  It resolved them only from the worktree, so it exited 2 and the entire Work Summary
  vanished from the PR body and the Jira comment. Its sibling
  `render-agent-log-cost.sh` has had that fallback all along.
- `:resume`, `:status` and `:log` no longer treat a missing worktree as a broken run
  when `worktreeRemovedAt` is set: state is read from `artifactsPath`, and resume asks
  before moving the user's HEAD.
- `gc-worktrees.sh` and `/multi-agent:garbage-collect` both claimed the finishing
  command owned worktree removal. That is now true rather than aspirational.
- Token budget 52200 -> 52700, after compressing 414 tokens out of Phase 3 and
  Phase 6 first, per the discipline recorded in `token-budget.json`.

### Fixed

- **Every finding the test-integrity gate produced was unattributable.** It read
  `f.file` from the diff-risk report, which declares and emits `path`, so each
  finding carried `file: undefined` and read "Test file 'undefined' shrinks". That
  is useless to the developer and rejected by `reviewer-output.schema.json`, which
  requires `file` with `minLength: 1`  -  on the one gate that deliberately has no
  opt-out. It stayed invisible because the smoke's own fixtures used `"file"`, the
  key the bug read: the fixture matched the bug instead of the schema. Fixtures
  corrected, and a new assertion checks the finding names a real path (verified by
  reintroducing the bug).

- **The command inventories kept a renamed command alive.** `finish` survived the
  v14.0.0 rename to `ship` in all three inventory lists, because
  `smoke-command-inventory.sh` only proved nothing was MISSING and `ship` appears
  elsewhere in those files' prose. Added check 4b: every name in an inventory must be
  a command that exists in the tree (verified against a planted `ghostcmd`).


## [14.0.0] - 2026-07-29

The `--dev` family reviews its own work, and review now checks the code against the
criteria it was supposed to be built to instead of general good taste.

Major because a command was renamed: `/multi-agent:finish` is now `/multi-agent:ship`.
No alias is kept  -  `installCommands()` replaces the command tree wholesale, so a clean
cut leaves nothing half-migrated.

### Added

- **Phase 4 Review runs in `dev`, `dev-autopilot`, `dev-local` and `dev-local-autopilot`.**
  Phase sets become `0/3/4/5/6/7` for `dev` and `0/3/4/6/7` for the three variants, in
  `gen-mode-dispatch.mjs` (the generator is the source of truth; the tracker sections are
  regenerated from it, never hand-edited). Analysis and planning are still skipped: they
  shape work that has not happened yet, so a task the user has already scoped can do
  without them, while review judges work that now exists and has no substitute. Accepted
  blocking findings return to Phase 3 under the existing 3-iteration hard kill; the
  autopilot variants auto-fix without prompting and halt on the existing rework-storm
  circuit breaker rather than committing. No new machinery for either  -  both were already
  built and became reachable the moment Phase 4 entered the phase set.
- **Phase 4 Step 1.78, criteria resolution** (`skill-conformance.mjs`, zero LLM). Resolves
  which rule registries apply to this diff, scoped to its languages and paths, and writes
  `criteria-manifest.json` BEFORE the reviewers run. That file is the denominator: reviewers
  return one verdict per selected rule ID, so "did it apply this completely" is answerable
  rather than inferred. A reviewer that opened nothing and one that checked everything
  produce identical `findings` arrays, which is why the checklist exists.
- **Registry discovery is declared, never sniffed by name.** A skill opts in with
  `standards-registry: <path>` in its frontmatter, so the pipeline names no stack-specific
  skill and a future UIKit, Objective-C, Kotlin or backend registry drops in with zero
  pipeline change.
- **Every registry declares its own `scope`** (`languages`, `paths`, `excludePaths`,
  `notCovered`), and per-rule `scope` narrows it further. Measured before this landed: only
  2 of the iOS registry's 99 rules carried any applicability field, and the rest wrote their
  scope as English prose inside `mechanism`  -  so an Objective-C or UIKit diff would have
  collected all 99 SwiftUI-shaped rules, manufacturing findings and burying the real ones.
- `references/rules.yml` for `apple-archive-compliance` (18 rules) and
  `google-play-compliance` (21), converted from their existing SKILL.md tables with IDs and
  severities preserved. Registry count goes 1 -> 3, so discovery is genuinely
  capability-based rather than one skill with extra steps.
- **Exception-marker audit**: expired, reason-less, expiry-less, or unknown-ID
  `standard:exception(...)` markers become rule-ID-bearing findings. The marker template is
  read from the registry, never hardcoded, so a registry with different comment syntax works.
- `state.telemetry.skillCalls[]`  -  Phase 3 records each skill, plugin skill and guide it
  consulted, with the files it applied them to.
- `prefs.global.skillConformance.blockOnCoverageGap` (default **false**) and
  `prefs.global.ship.autoFix`, the latter referenced by the tail command's spec since it
  shipped but never actually declared. Prefs schema 2.4.0 -> 2.5.0 with a migration that
  carries any existing `finish.autoFix` value across the rename.
- Gates: `smoke-dev-mode-review.sh`, `smoke-skill-conformance.sh` (26 checks),
  `smoke-skills-root-resolution.sh`. `smoke-subagent-validators.sh` grew 10 checks for the
  conformance contract; `smoke-mode-dispatch-drift.sh` grew a step that asserts the negative,
  because its per-phase `grep -Fq` could not catch an ABSENT phase.

### Changed

- **`/multi-agent:finish` -> `/multi-agent:ship`.** "Finish" never said what it did. The
  command takes work already sitting on a branch through review, a build+test gate, PR and
  report; `ship` says that. With review now inside the dev modes, its remaining job is work
  that had no pipeline run behind it, rather than a patch for a mode that skipped review.
- **Reviewers cite rule IDs.** `reviewer-output.schema.json` 1.0.0 -> 1.1.0 adds `ruleId`,
  `criteriaSource` and the per-rule `conformance[]` array; `triage-output.schema.json`
  3.2.0 -> 3.3.0 carries both through triage. `code-reviewer.md` gains a `${CRITERIA}`
  injection slot  -  the phase doc had claimed for some time that "skills are injected into
  reviewer prompt context" while the agent definition had no slot for them, which is exactly
  why review could not cite a rule.
- `validate-reviewer.mjs` enforces the checklist with `--criteria`: a selected ID with no
  verdict, a verdict for an ID that was never selected, a `conformant` row with no file
  evidence, and a `violated` row with no matching finding all fail. Without this the field
  would be decoration  -  the validator is hand-written and does not apply
  `additionalProperties`, so any array at all would have passed.
- `${CRITERIA}` lives in the shared cacheable prefix, identical for every reviewer.
  Subsetting it per reviewer would invalidate the prefix for the whole panel and re-bill the
  largest block in the phase.
- Phase 4 no longer transcribes SwiftUI interaction and accessibility rules inline; they
  resolve from the registry that declares SwiftUI scope, so criteria and severities live in
  one place. This removed the drift the release exists to close, and reclaimed budget.
- Phase 3's pre-flight no longer aborts unconditionally on a missing analysis document.
  Steps that read that document are recorded `not-applicable (no Phase 1 in this mode)`,
  which is what the `--dev` family always needed and never had.
- `migrate-prefs.mjs` derives its migratable-version set instead of enumerating one
  `else if` per version. The old chain had to be extended by hand whenever
  `TARGET_VERSION` moved, and forgetting made the migrator throw `unknown schemaVersion`
  on the exact version it had just been released to migrate from.
- Token budget 51500 -> 52200, after 820 tokens were compressed first, per the note in
  `token-budget.json`.

### Fixed

- **`ios-coding-standard` in the repo was a version behind the installed copy** (95 rules
  v1.0.0 vs 99 rules v1.1.0). Because `installSkills()` copies the repo over the
  destination, the next `/multi-agent:update` would have downgraded a user's registry  - 
  harmless while nothing read it, a correctness regression the moment Phase 4 blocks on rule
  IDs. Resynced to 99 rules.
- `modules/*.yml` overlays and `references/EXAMPLES.md` are deliberately NOT shipped: they
  quote real module paths, symbol names and call-site counts from one codebase, so a shipped
  overlay would bind another project's dialect slots to the wrong dialect. `modules/_TEMPLATE.yml`
  carries the shape instead, and the skill states that an unbound slot disables its rules
  rather than defaulting silently.
- `tracker-contract.md` contradicted itself on skipped phases: one section said to
  `TaskCreate` every phase "even if it will be skipped in this mode", another said omitted
  phases get no `TaskCreate` at all. The mode files and the generator follow the second, so
  the example now matches it.
- Three docs disagreed on whether `--dev` runs Phase 5. They now agree with the generator.
- The dispatcher's modular-loading table had no row for `dev` or `dev-autopilot`.

## [13.6.0] - 2026-07-28

A review pass across the pipeline, the plugin marketplace and the companion MCP server.
Every gate was green before it started: each defect below was invisible to the suite,
and most were invisible because a gate asserted the wrong half of the contract. New
gates are named per item.

### Fixed

- **The Tier 2 Figma PAT could not be found on any migrated install.** `migrate-prefs.mjs`
  consolidated `keychainMapping.figma_pat` into `.figma` and deleted the old key, but the
  setup wizard kept *writing* `figma_pat` (so the next migration deleted the mapping it
  had just created), both Tier 2 fetchers kept *reading* it, and the failure text told the
  user to map the one key guaranteed not to survive. Tier 2 reported `missing-token` while
  a valid PAT sat under the new name. Claude Code's MCP tier masked it; Copilot and Codex,
  which serve no Figma MCP tools at all, fell straight to Tier 3. The lookup now lives once
  in `lib/figma-token.sh` (canonical `figma`, legacy `figma_pat` as a fallback) rather than
  duplicated per fetcher, which is what let the two copies drift from the migration.
  Gate: `smoke-credential-key-alignment.sh`.
- **Claude Code installs registered no MCP server**, so every skill that reaches for a
  `dev-toolkit` tool - `design-check`, the `ios_*` / `android_*` calls, the archive audits -
  had nothing to call. Codex had registration from day one and Copilot gained it later;
  Claude Code was last and easiest to miss, because a maintainer who registered it by hand
  once sees a working tree forever. `--scope user` is not optional: `claude mcp add`
  defaults to project scope, binding the server to whichever directory the installer ran
  in. Uninstall now deregisters it too. The gate that existed to catch this iterated
  `codex copilot`, so it structurally could not; the host list is now derived from the
  installers on disk.
- **`@mmerterden/dev-toolkit-mcp` was not on the public npm registry**, while all three
  installers register `npx -y @mmerterden/dev-toolkit-mcp`, which resolves from it. GitHub
  Packages answers 401 to unauthenticated reads even for public packages, so pointing npx
  there is not an alternative. Registration never failed - it only writes host config - so
  the break surfaced later as an E404 on first tool use, and only for users whose `~/.npmrc`
  lacked a scope redirect. Published; `publishConfig` now matches the pipeline's.
  Gate: `smoke-mcp-package-resolvable.sh`.
- **157 instruction paths resolved only from a repo checkout.** `node pipeline/scripts/x.mjs`
  works when cwd is this repo's root, which is never true during a run: the pipeline works
  inside a worktree of the user's project. The class covered 85 script invocations (bare,
  `node`-prefixed, env-prefixed, `&&`-chained), 16 skill references, 12 rules references,
  and 4 files invoking `"$REPO_ROOT/pipeline/scripts/..."` where `$REPO_ROOT` was never
  assigned anywhere in the pipeline. Gate: `smoke-install-relative-paths.sh`, which
  enumerates the whole class rather than the instances.
- **`dynamicSkillLoading` had never worked on an installed tree.** Three breaks stacked: a
  full install did not ship `.skills-index.json` (while the schema said it always did),
  `match-skills.mjs` resolved its default index to `$HOME/pipeline/skills/...` from
  `~/.claude/scripts/`, and the runtime contract in the orchestrator invoked a
  repo-relative path. Its own smoke passed `--index` explicitly and ran from the repo, so
  it never exercised either failure. The index now ships on every install for all three
  hosts via one shared helper.
- **Two compliance skills were unreachable on Codex.** `apple-archive-compliance` and
  `google-play-compliance` live in `skills/shared/core`, and Codex's installer copied only
  `shared/external`. `smoke-compliance-skills.sh` asserted both were wired to four
  consumers - reading the source tree, with no host awareness - so it passed while the host
  had nothing to load. Gate: cross-host reachability parity in `smoke-install-layout.sh`.
- **A plugin skill whose name a pipeline skill also owns was dropped on the copy hosts.**
  Claude Code reaches both because its loader namespaces plugin skills; Copilot and Codex
  copy flat, so the iOS toolkit's `architecture` (that stack's structural rules) was
  silently unreachable behind the pipeline's generic ADR skill, as was its `backlog` and
  the common toolkit's `index`. Clashes are now plugin-prefixed, the flat analogue of what
  the loader does, so the pipeline keeps the bare name and nothing is lost.
- **Plugin skills were frozen after their first install.** `skipNames` was a snapshot of
  the destination, so a plugin skill matched its own previous copy and was skipped forever,
  outliving every upstream fix in it. It is now the pipeline-owned set derived from the
  source tree, and each plugin skill dir is wiped before refill so a file dropped upstream
  does not linger.
- **Copilot skills referenced trees Copilot does not install.** Its skills were copied
  byte-for-byte, so 15 references pointed at `~/.claude/scripts` and `~/.claude/lib` -
  absent on a Copilot-only machine. Codex has had this rewrite since its installer was
  written. Copilot now rewrites the trees it owns, and installs `rules/` as well, closing
  the same silent-nothing fallback Codex's installer already documented.
- **`phase-tracker.sh` never reclaimed a stale lock on Linux.** It tried `stat -f %m`
  before `stat -c %Y`, and on GNU coreutils `stat -f` is a *valid* flag (`--file-system`,
  where `%m` is the mount point): it succeeds, returns something like `/`, the `||` never
  fires, and the age arithmetic runs on a path. Every tracker call then spun the full ~5s
  bound and fell open with a warning. Every other `stat` call site in the repo already had
  the order right.
- **The portability gate accepted any file containing `||` anywhere in it**, which is every
  shell script in the repo, so an unguarded BSD/GNU construct shipped green - the
  `phase-tracker` bug above rode through that hole for releases. The guard is now checked
  on the hit's own line, plus a dedicated `stat` ordering rule.
- **Model IDs were a generation behind.** 21 references dispatched `claude-opus-4-8` and
  `claude-sonnet-4-6` on the opus/sonnet rungs. Both are still valid upstream, so nothing
  errored - Phase 4's reviewer panel, the dev phase and the whole fallback ladder simply ran
  the previous generation. Now `claude-opus-5` and `claude-sonnet-5`, with the rung/ID split
  stated in the fallback contract. Gate: a generation guard in `smoke-model-fallback.sh`.
- **`rules/pipeline-output-formatting.md` did not exist.** Two shipped skills named it as
  the PR-body contract to follow. The install-layout smoke had been *reporting* paths that
  exist on no host as a note rather than failing on them, which is how it survived; that
  claim is now strict.
- **A pre-migration preferences file failed validation before the migration could fix it.**
  `firebase_sa`, `firebase_project` and `servicePatMap.figma_pat` are deleted by
  `migrate-prefs.mjs` but were undeclared under `additionalProperties: false`. Declared as
  deprecated tolerance slots, and the deprecated/deleted sets are now checked against each
  other in both halves of the schema.
- `state.figmaAccess` was a BLOCKING pipeline-wide contract that the state schema never
  declared. Declared, including the new `tier1Unavailable` cause.
- Four credential-store corrections: the resolver overwrote a pre-set `CRED_STORE` while
  documenting it as an override; `doctor` advertised an env-var fallback that `get` does not
  implement; `resolve_key` read `keychainMapping` with python3 only, so on a Windows box
  without it the mapping was skipped in silence and a present token read as missing (node
  is now the second reader, and node is guaranteed wherever this installs); and macOS
  `list` read the item LABEL rather than the SERVICE attribute `get` looks up.
- `lint-mcp-refs.mjs` printed one checkmark for two different claims - "the server name is
  recognised" and "its tools were verified to exist" - so Figma and XcodeBuildMCP read as
  verified when nothing had probed them. Verified and declared are now distinguished.
- 15 orphan `<name>.md` files from a pre-directory install layout sat in a real
  `~/.copilot/skills`, shadowing the proper directory form. Pruned, on both copy hosts.

### Changed

- Figma Tier 1 availability is now host-aware. Absent tools (the normal case on Copilot and
  Codex, where the installer registers only `dev-toolkit`) record
  `figmaAccess.tier1Unavailable = "host"` and fall straight to Tier 2 - no probe, no
  re-auth retry, and no "recreate the MCP token" question the user cannot act on. Tools
  present but failing auth is `"auth"`, where the retry does apply. On those two hosts a
  mapped `figma` PAT is the primary path, not a fallback.
- Phase-doc token budgets recalibrated. The path correction above cost 196 tokens of pure
  correctness; 149 were reclaimed first by compression (Phase 1's Figma tier table now
  points at the Phase 0 probe that already resolved it; Phase 4's Codex constraints point
  at the always-loaded AGENTS.md block). Five warn lines had been permanently amber, which
  makes the amber tier useless as a signal - every warn reset to the documented
  current+10%, the four maxes the new warn would have collided with to current+25%,
  aggregate 51000 -> 51500.
- The install summary told users to register the Claude Code MCP server by hand. It no
  longer does, because the installer does it.

## [13.5.1] - 2026-07-28

### Fixed

- `ios-coding-standard`'s local lint runner defaulted its scratch root to
  `~/.claude/local/...`. On a Copilot- or Codex-only machine that made the script create
  a stray `.claude` directory belonging to a host that is not installed, just to hold
  lint output. It now picks whichever host tree exists and falls back to `$TMPDIR`.
  Found by the Codex sync verification, which flags any `$HOME/.claude/...` reference
  that reaches the Codex tree.

## [13.5.0] - 2026-07-28

### Fixed

- **Every fetcher died on a Copilot-only or Codex-only install.** Eleven runtime scripts
  loaded `credential-store-resolver.sh` as `. "$HOME/.claude/lib/..." || . <next> || {
  error }`, all of them under `set -e`. Sourcing a file that does not exist aborts the
  shell outright - `||` included - so on a host without `~/.claude/lib` the chain reached
  neither its later candidates nor its error branch: bare exit 1, no message. Reordering
  does not help, because whichever candidate is absent aborts at that point. All eleven
  now check `[ -f ]` before sourcing and decide on `$CRED_STORE`, and the resolver ends
  with `resolve_credential_store || :` so sourcing it can never trip a caller's `set -e`.
- The resolver only knew about `~/.claude` and `~/.copilot`. A Codex-only install could
  not locate the credential helper at all, while the file sat in `~/.codex/lib`.
- **Copilot CLI registered no MCP server.** Codex had registration from the day its
  installer was written; Copilot never did, so it carried all 257 pipeline skills and
  could not call one of the 80 dev-toolkit tools those skills depend on. Both hosts now
  go through the shared `install/_mcp-register.mjs`, which also treats Copilot's
  `already exists` (exit 1, unlike Codex's replace-and-exit-0) as the desired end state
  rather than reporting a working registration as a failed install. Uninstall deregisters
  on both.
- **`figma-mcp-refresh.sh` called macOS `security` directly**, so silent Figma MCP token
  renewal - the path that keeps every Figma consumer on Tier 1 - never worked on Linux or
  Windows, and surfaced as an unexplained expired-token prompt. It now goes through
  `credential-store.sh` with the `set <key> -` stdin form, which keeps the secret off
  argv on every platform, the property the old `security -i` call was preserving.
- **`phase-tracker.sh` hashed with a bare `shasum`** - a perl script absent from slim
  Linux images. Where it was missing, OTel trace and span ids came back empty: valid
  JSON, useless telemetry, no error. Same for `post-pr-review.sh`, where an empty
  fingerprint made every finding look new and re-posted the whole review each iteration.
- `audit-log.sh` fell back to printing `unhashed:<url>` when no hasher was available,
  writing the plaintext remote - credentials included, if the URL carried any - into the
  audit log that hashing exists to protect. It emits a marker instead.
- `smoke-write-state.sh` counted an honest exit-2 lock timeout as data loss, because it
  read the final state without looking at exit codes. That made it fail once inside a
  fully loaded suite run while passing 12/12 in isolation - a flake that read as a
  data-loss bug and would eventually have been "fixed" by lowering the writer count,
  removing the regression detection the test exists for. It now asserts the real
  invariant: no writer reports success while losing its update.
- `smoke-token-preflight.sh` asserted the literal `security add-generic-password`, which
  pinned the implementation to macOS. It now asserts the portable route and fails if a
  direct platform keychain call comes back.
- `multi-agent-refs/rules.md` pointed at `~/.claude/scripts/vercel-deploy.sh`; the
  wrapper installs to `lib/` with the other shell libraries.

### Added

- **`smoke-shell-portability.sh`** (8 assertions) over every shell file that ships to a
  user tree: single-platform hashing without a fallback, unguarded BSD/GNU-divergent
  constructs, hardcoded absolute home paths, direct platform keychain calls, and the
  `.`-chain-under-`set -e` trap above. It found the `post-pr-review.sh` hasher and the
  `figma-mcp-refresh.sh` keychain call on its first run.
- `smoke-install-layout.sh --update-fixture` regenerates the layout fixture from the tree
  the gate itself installs, probe plugin included. The documented hand-rolled recipe
  omitted it, so following it baked a three-file shortfall into the fixture.

## [13.4.0] - 2026-07-28

### Added

- **`credential-inventory.sh`** - one command that answers "what can I reach right now?"
  A run asked the user to paste a Crashlytics stack trace by hand, offering the manual
  path as the fastest route, while a valid Firebase service-account JSON sat in the
  Keychain mapped as `firebase` and resolving fine. Nothing had failed: the pipeline
  simply never asked itself whether it already held a credential that answered the
  question. The tool reports `present` / `mapped-but-missing` / `unmapped` per logical
  key plus what each one unlocks, and `keychain.md` Rule 2 now binds every question that
  requests external data to its output - ask for the pointer (the issue URL), never for
  the payload. Values are never printed.
- `--probe` verifies that configured credentials actually answer, because "the key is in
  the Keychain" and "the service replies" are different claims that the user fixes in
  different ways. The verdicts stay separate on purpose: `auth-rejected` (refresh the
  token), `unreachable` (on a corporate host, almost always the VPN),
  `no-host-configured` (a token with nowhere to point), `well-formed` (valid shape,
  liveness needs more input), `not-probeable`. Collapsing them into one "failed" bucket
  is what led the user to three different wrong actions. Only configured credentials are
  probed - an unmapped key is a capability the user chose not to enable.
- `smoke-credential-awareness.sh` (32 assertions) and `test/phase0-exit-gate.test.mjs`
  (20 cases) lock all of the above, including two secret-leak checks and the rule that
  the credential reaches curl through a stdin config rather than argv, where any `ps`
  could read it.
- `smoke-install-layout.sh --update-fixture` regenerates the layout fixture from the
  tree the gate itself installs. The documented hand-rolled recipe omitted the seeded
  probe plugin, so following it baked a three-file shortfall into the fixture and the
  next run failed on a drift that was really a bad regen.

### Fixed

- **The Phase 0 exit gate now asserts that the pickers ran.** It checked `taskType` and
  the Figma access tier only, so a run that skipped the project and branch pickers and
  developed straight on the local checkout passed cleanly - which is exactly what a
  reported run did with a Jira ID. The gate now also requires `baseBranch`,
  a `baseFetchStatus` from the known set, and a worktree path distinct from the project
  root. Branch selection is marked non-skippable in `phase-0-init.md`, in every mode:
  `--dev` skips the LLM phases, not Phase 0's pickers.
- **An unreachable external source is announced instead of absorbed.** Exit code `3`
  used to mean "mark it failed and continue", so an expired token and a VPN-off remote
  both reached the analysis phase as *no data*, indistinguishable from a ticket that
  referenced nothing. The run then planned from a partial picture and reported success.
  `external-context-injection.md` now classifies the stderr and surfaces a decision -
  refresh the credential, connect the VPN and retry, supply a current URL, or continue
  without - records `userDecision` in state, and requires autopilot to report what it
  skipped rather than hide it. Retry is a real branch, not a label.
- The base-branch and fetch-failure pickers were still written as ASCII numbered menus
  in `phase-0-init.md`, against the picker contract's native-widget rule. Both are now
  expressed as picker options, and the fetch-failure question names the corporate host
  when the remote points at one - a host that will not resolve is almost always the VPN,
  and saying so is the difference between a five-second fix and a run built on a stale ref.
- `credential-inventory.sh --json` returned empty arrays while the table mode looked
  correct: `python3 - <<EOF` takes its script from stdin, so the piped payload never
  reached `sys.stdin`. Caught by the new gate on its first run.
- The probe read hosts from `global.<service>Host`; the real path is
  `global.hosts.<service>`, so every self-hosted service reported
  `no-host-configured`. Found by running the probe against the real preferences instead
  of trusting the field name.
- `figma_mcp` is an OAuth token for the MCP server, not a REST PAT: probing it against
  `api.figma.com` returned 403 for a healthy token and would have sent the user to
  regenerate something that worked. It is now reported as not-probeable, with liveness
  left to `figma-mcp-refresh.sh`, which owns the grant.
- A failed `curl` wrote `000` through `write-out` *and* triggered the `|| echo "000"`
  fallback, producing `000000` and a bogus verdict for what was simply a closed VPN.
  Any non-three-digit status now reads as `probe-error` rather than being dressed up as
  a service verdict.
- `multi-agent-refs/rules.md` pointed at `~/.claude/scripts/vercel-deploy.sh`; the
  wrapper installs to `lib/` with the other shell libraries. It had been reported as a
  dangling reference for several releases.
- `/multi-agent:setup` no longer describes an unreachable discovery source as skipped
  "silently". Setup is where the user is configuring things, so a source that could not
  be reached is precisely what they need told, with the classification that decides
  their next move.

## [13.3.0] - 2026-07-28

### Added

- **Copilot CLI and Codex CLI now carry the stack plugin's authored skills.** Claude Code
  loads `multi-agent-plugins` natively; the other two hosts could not, and the contract
  papered over it by claiming Copilot kept standalone `figma-*` copies as a "frozen
  fallback". `install/copilot.mjs` pruned exactly those directories, so Copilot had
  **zero** plugin-authored skills: `create-screen`, `figma-validate`, `figma-review`,
  `component`, `state` and `navigation` were all absent, and a component task on Copilot
  had nothing to dispatch to. New shared `install/_plugin-skills.mjs` delivers the
  `index` / `reference` / `workflow` / `tools` groups to both hosts - as skills on
  Copilot, as reference files on Codex, where the skills block truncates silently.
- Plugin selection reads `enabledPlugins` from `~/.claude/settings.json` rather than
  copying every stack plugin. A flat copy of all five is last-write-wins, and `fix-bug`
  plus `branch-and-pr` exist in four of them while `component`, `state` and
  `create-component` exist in three - an iOS repo could have ended up running the
  Android `create-component`. The `--platform` flag is now only the fallback for a
  machine with no Claude Code settings to read.
- `test/stack-content-sync.test.mjs` - three cases locking content propagation,
  content-only version bumps, and no-op idempotence in the stack-plugin generator.

### Fixed

- **`build-stack-plugins.mjs` never propagated content edits.** It copied a skill only
  when the skill *set* changed, so editing a routed skill in `shared/external` reached
  no plugin while the generator reported "all plugins up to date" - which read as
  confirmation. This falsified the single-authoring-source guarantee the whole
  `shared/external` design rests on. Found by converting banned punctuation in
  `ios-coding-standard` and seeing the plugin copy keep all 188 banned characters with a
  differing `rules.yml`. Fixing it also surfaced 37 stale skill descriptions across four
  plugins, still missing the "Use when ..." routing clause that decides whether a skill
  is matched at all: those plugins had been serving worse descriptions than the source
  for as long as the routing clause existed.
- `build-stack-plugins.mjs` now accepts `--key=value` as well as `--key value`. The `=`
  form fell through to the default, so a run aimed at a throwaway checkout silently
  retargeted the user's real marketplace and reported success.
- `smoke-install-layout.sh` asserts plugin-skill parity against a seeded probe plugin,
  so the check cannot pass vacuously in an isolated `HOME` the way it did on first write.

### Changed

- `cross-cli-contract.md` §1.1 replaced the false "frozen fallback" claim with a
  per-host delivery table: native plugin load on Claude Code, installer-delivered skills
  on Copilot CLI, installer-delivered refs on Codex CLI. Parity is asserted on the
  reference set, not the skill set, because Codex ships one router skill by design.
- `codex-instructions.md` gained a "the skills are on disk, not in the list" section
  with an `ls` / `grep` / `head` discovery recipe: only 5 of Codex's skill entries render
  descriptions, so the 193 delivered refs are reachable but not description-matched.

## [13.2.0] - 2026-07-28

The iOS coding standard reaches all three hosts, and works outside the pipeline.

The 95-rule registry lived as a pipeline-local routine. Two consequences, both
measured: only Claude Code could reach it, because `local-only` commands are
deliberately never synced (verified absent on Copilot CLI and Codex CLI), and it
applied only when the routine was invoked explicitly  -  nothing pulled the rules in
while Swift was being written. The plugin's own `reference/code-style` skill is 154
lines of prose with zero rule IDs, so the registry was not reachable that way either.

### Added

- **`ios-coding-standard` skill**, authored once in `pipeline/skills/shared/external`
  and routed into the iOS stack plugin's `knowledge/` by the existing generator. That
  one source now lands as a skill on Claude Code and Copilot CLI (description-matched,
  so the rules are present while code is being written) and ships inside the plugin for
  Codex. `references/` carries the registry, the teaching doc, a SwiftLint config for
  the mechanically-enforceable subset, and a module-scoped lint runner with a baseline
  mode.
- **`/multi-agent:ios-coding-standard`** promoted out of `local-only` into the command
  inventory (43 → 44), with its Copilot twin, so the module-wide audit procedure exists
  on every host. It reads its rules from the skill rather than carrying a copy.

### Changed

- The registry was generalised before promotion: company hostnames, an internal URL
  scheme, internal model and endpoint names, and repo-specific module paths were
  replaced. `smoke-personal-data.sh` is clean across all six promoted files. Rule
  bodies describe shapes (a mapper doing arithmetic, a logger interpolating a token)
  rather than named modules, so the registry applies unchanged to any SwiftUI codebase;
  a project layers its own vocabulary through a `modules/<Module>.yml` overlay.
- Four over-long skill descriptions trimmed (`ios-simulator` 839→578,
  `swift-api-design-guidelines` 738→468, `swiftlint` 725→473, and the three new ones).
  The always-on description surface was at 78,079 of 78,000 bytes before this change;
  adding a 44th command and a new skill needed 859 bytes it did not have. Each trim
  removed duplication between a description's "covers" enumeration and its trigger
  clause, which is where the routing value actually is. Surface is now 77,734.

### Notes

- **Codex lists plugin skills by name only.** Measured: all 76 plugin-provided skills
  render with an empty description in Codex's skills block, while the 5 system skills
  render theirs. So on Codex a plugin skill is invoked explicitly rather than matched
  on description. That is the reason the registry is authored in `shared/external`
  instead of hand-placed in the plugin: the pipeline install path gives Claude Code and
  Copilot CLI a description-matched skill, and the plugin copy covers Codex.
- The iOS plugin now declares 143 skills and Codex surfaces about half. That overflow
  predates this change and `tools/validate.py` warns on it; routing `knowledge/` behind
  the plugin's `index` skill is the fix and is separate work.


## [13.1.0] - 2026-07-27

Six gates, from one branch that spent half its commits on rework.

A Figma-driven screen task was built through the generic development path. The cause
chain, measured from the run's own artefacts: Phase 0 reported `completed` having
written only `tracker-state.json`, so `agent-state.json` and its `taskType` never
existed, so Phase 3's component dispatch could not fire. The stack plugin already
ships `create-screen`, `figma-validate` (7 criteria including design-token compliance
and Code Connect strategy) and `figma-review` (14-item checklist)  -  none of them ran.
Padding came out 16 where the frame said `Spacing/12`. Three of six commits were fixes,
the last a full sheet rebuild.

Nothing here is a new capability. Every gate enforces a rule that already existed or
invokes a skill that was already written but never called.

### Added

- **`phase0-exit-gate.mjs`, blocking.** Phase 0 may not be marked completed until
  `agent-state.json` exists with a `taskType`, and a Figma reference forces
  `taskType: "component"` plus a recorded `figmaAccess.tier`. A phase that reports
  success without its output is worse than one that fails: every later phase then
  reasons from a field that is not there. The evaluator is pure and exported, so the
  gate is testable without reproducing a run.
- **Phase 4 Step 2.8, visual conformance gate.** Runs `figma-review` and
  `/multi-agent:design-check` for component/screen work, with the coverage gate, and
  asserts Code Connect was **published** rather than merely written  -  a
  `*.figma.swift` on disk with "Not published" in Figma is a binding that exists for
  nobody. `design-check` had been a command with no phase invoking it, so the only
  thing standing between a build and visual drift was the user opening the app.
- **Phase 1 Step 1.45, reuse discovery, blocking.** Search for an existing wrapper,
  entity, mapper or screen before proposing a new one. One run wrote a repository over
  an endpoint a sibling domain already wrapped **with its country parameter**, called
  the generated method without it, and re-invented an entity the module already had.
  "Copy X and rename it" is the reuse answer, not a hint.
- **`smoke-component-dispatch-gates.sh`** (12 assertions) pins all six.

### Changed

- **Component dispatch halts instead of degrading.** The contract used to send an
  incomplete-state component task down the generic TDD path while the next sentence
  said "never silently skip the Figma work"  -  taking the generic path *is* skipping
  it. That wording authorised the exact failure above.
- **Dispatch routes on scope, not just platform.** A screen and a component are
  different jobs and the plugin ships a skill for each; routing a screen to the
  component skill is why one run produced entities and a mapper but left the screen
  half-wired. `figma-validate` now runs before the create skill.
- **Phase 1 captures spacing by token name, per atom.** Phase 3 is forbidden from
  calling Figma, so a pixel number  -  or a missing entry  -  is unrecoverable later. A
  UI frame with no spacing entries is a capture failure, not an empty frame.
- **Phase 3 records that generated trees are not editable.** A mock fixture went into
  the generated tree; the fix moved it to the custom tree and registered the scenario
  in the generated index. Same content, wrong side of the generator, and the Debug menu
  never showed it.
- **Fast modes warn when handed an analysis document.** `--dev` skips Analysis and
  Planning by design, so there is no phase that turns a document into a plan. The doc
  becomes context for one pass and work lands in whatever order it was read.


## [13.0.0] - 2026-07-27

> **Why major and not minor.** The additions here are additive, but two defaults
> changed in ways a reasonable workflow would notice: `install --all` now writes a
> third tree (`~/.codex`) and registers an MCP server via `codex mcp add`, and the
> cross-CLI contract changed shape (the parity axis is per-host now, so an auditor
> comparing Codex on skill directories would read a correct install as drift).
> Per the versioning policy that is a changed default, not a new option.


Codex CLI becomes a third supported host, and a new pre-submission validation command.

### Added

- **Codex CLI as a first-class target** (`install --codex`, included in `--all`).
  The v9.7.0 adapter was deleted in v10.7.0 because Codex was then a degraded
  consumer with no sub-agent fan-out. Codex 0.145 has skills, parallel sub-agents
  with per-agent model and reasoning effort, hooks, MCP and a plugin marketplace,
  so it earns the same treatment as the other two rather than an adapter.
  `install/codex.mjs` writes `~/.codex/{skills/multi-agent,multi-agent-refs,
  agents/*.toml,prompts/multi-agent.md,scripts,lib,schemas,rules}` plus a managed
  span in `~/.codex/AGENTS.md`, and registers the dev-toolkit MCP server through
  `codex mcp add` rather than hand-merging TOML that Codex owns.
- **`/multi-agent:testflight-validation`** (command 43). Three gates, each seeing
  what the others structurally cannot: the static 18-rule archive audit, Apple's
  own `altool --validate-app`, and a Review-Guidelines check against repo evidence.
  ITMS codes are mapped to the rule each implies. It validates only  -  never
  `--upload-app`  -  so a validation run cannot ship a build by accident.
- **App Store Connect credentials in `/multi-agent:setup`**, inside the Step 1
  discovery / Step 2 mapping / Step 3b onboarding flow alongside Jira and
  Bitbucket, not as a late add-on: a user who already has one in their keychain
  gets it mapped automatically. Tier 1 is an API key, tier 2 an Apple ID plus an
  app-specific password  -  which matters because creating an API key needs an
  Admin or App Manager role many developers on a corporate team do not have.

### Changed

- **Codex takes the Claude Code thin-dispatcher shape, and has to.** Measured on
  Codex 0.145: installing one plugin declaring 142 skills took the assembled
  skills block from 11 skills / 4,710 bytes to 83 / 22,111, surfacing only **75 of
  142** and **evicting an unrelated user-scope skill**. Shipping the 43
  sub-commands as peer skills would silently lose pipeline commands next to any
  stack toolkit. The pipeline contributes exactly one skill on Codex and keeps the
  specs as refs; `smoke-install-layout.sh` fails if a second one appears.
- Phase 4 reviewer matrix gains a Codex column (gpt-5.6 @ xhigh / gpt-5.4 /
  gpt-5.6 @ medium, triage at max) with two measured constraints written into the
  contract: a `spawn_agent` that sets `model` without `fork_turns: "none"`
  **silently inherits the parent model**, collapsing the panel onto one
  perspective; and 4 concurrency slots *including the orchestrator* make three
  reviewers the ceiling. Single-vendor caveat recorded  -  consensus among three
  OpenAI models is weaker evidence than the same consensus on a two-vendor host.
- `tracker-contract.md` gains the `codex` visual channel: the native `update_plan`
  tool, with its no-parallel-call and no-plan-mode caveats.
- Cross-CLI contract retitled for three hosts; parity axis for Codex is the **ref
  set**, not the skill set, because comparing skill directories would demand the
  layout that breaks it.

### Fixed

- **The remote reachability gate blamed the network for every failure.** Any
  non-zero `git ls-remote` exit was reported as `unreachable (VPN/DNS)`, so a
  missing-credential error that returns in under a second sent the user to enable
  a VPN that could not help, and offered a stale-base fallback for a cause that
  had nothing to do with staleness. Failures are now classified from stderr
  (credential / network / wrong remote / unknown), the observed line is printed
  next to the classification, and only the network case offers the cached ref.
- **The Phase 0 branch-collision probe read a failed probe as "no collision".**
  With `2>/dev/null` and an empty-output test, an auth or network failure was
  indistinguishable from "the ref does not exist", so the run created a branch
  that already existed on the remote  -  surfacing as a rejected push at Phase 6,
  far from its cause. Exit codes are now distinguished (0 exists, 2 free, anything
  else unknown-and-recorded).
- **`/multi-agent:update` deleted the Codex prompt on every run**, pruning
  `~/.codex/prompts/multi-agent.md` as a retired v9.7.0 adapter leftover.
- **Uninstall could delete trailing user content in `~/.codex/AGENTS.md`.**
  `stripManagedBlock` matched only the Copilot end marker, so on the Codex marker
  it fell through to a heading-bounded fallback that returns nothing when the
  trailing content has no top-level heading. Verified against the pre-fix path.
- `smoke-own-punctuation.sh` failed on HEAD: `test/tracker-title-entities.test.mjs`
  (added in v12.11.0) holds the banned characters as its own assertion list and
  was never allowlisted.
- **Half-English pickers on Turkish runs: six shipped files contradicted the
  canonical language matrix.** `rules.md` is unambiguous  -  `AskUserQuestion`
  `question` and `options[].description` render in `outputLanguage`, only `label`,
  `header` and host chrome are pinned to English. But `/multi-agent:setup` Step 0
  claimed `promptLanguage` governs "interactive pickers and prompts ... Picker UI is
  always English", `/multi-agent:language` claimed "confirmation prompts ... are
  authored in English. Only the assistant's free-form replies follow
  `outputLanguage`", and four more files said variations of the same. Those are the
  two commands a user goes to *configure* this, so the model followed whichever
  canonical-looking doc it read first and gate questions came out English. All six
  corrected to describe `promptLanguage`'s real scope: the button and chip chrome,
  never the question a user reads.

### Gates (continued)

- New `smoke-language-matrix.sh`: asserts `rules.md` still pins the per-field
  matrix, then greps every shipped command / skill / ref for the seven phrasings
  that actually shipped and contradicted it. Two authoritative docs giving opposite
  answers is a spec with two answers, not a wording nit  -  the gate found a sixth
  violation (`_input-parser.md`) that the manual sweep had missed.

### Gates

- New `smoke-codex-install.sh` (37 assertions): install/uninstall round-trip into a
  HOME seeded with user content, asserting every artifact lands, every rewritten
  `$HOME/.codex` path resolves, generated agent TOML parses, and user content is
  byte-identical afterwards.
- `smoke-install-layout.sh` extended to three targets, including a check that every
  concrete `$HOME/.codex` reference **resolves on disk**. The earlier "no `.claude`
  references remain" check was blind to a wrongly-rewritten path, which is how
  `commands/multi-agent.md` became `multi-agent-refs/commands.md`  -  well-formed,
  pointing at nothing. A miss is classified by whether the Claude counterpart
  exists, so broken-by-rewrite fails and broken-upstream is only reported.
- New `test/codex-install.test.mjs` (24 assertions) locks the path-rewrite map,
  the frontmatter transform, the persona tier map, and managed-block trailing
  content, including regressions for both rewrite defects above.
- `MULTI_AGENT_SKIP_MCP_REGISTER=1` makes an install hermetic, so the layout
  fingerprint no longer depends on whether `codex` is on PATH.

## [12.11.0] - 2026-07-26

### Tracker tile titles rendered HTML entities

A live run showed this in the TaskList widget:

```
Phase 1: Build &amp; Launch
Phase 3: Drive &amp; Compare
```

`rules.md` already forbids entities in "titles, commit messages, task subjects, or
body text" because nothing downstream decodes them. A tile title IS a task subject,
so the rule covered this exactly. `output-quality-check.sh` enforced it over the PR
body and the Jira comment and nowhere else, so the one surface where the rule was
actually broken was the one surface nothing inspected. The gap was in the check's
coverage, not in the rule.

`phase-tracker.sh add` is the single funnel for every tile title, so it decodes
there, and warns on stderr: decoding silently would fix the display and hide the
bug that produced it.

**All three encodings**, because a title can arrive in any of them:

| | ampersand | less-than | em-dash |
|---|---|---|---|
| named | `&amp;` | `&lt;` | `&mdash;` |
| decimal | `&#38;` | `&#60;` | `&#8212;` |
| hex | `&#x26;` | `&#x3C;` | `&#x2014;` |

The first version of this fix handled only the named column, which is the same bug
fixed for one spelling out of three.

Two subtleties, each pinned by a test:

- **Nesting.** `&amp;lt;` means the literal text `&lt;`, not `<`. One
  left-to-right pass gets this right for free, because a global replace never
  rescans its own output. An ordered-sed version needed `&amp;` decoded last for
  the same effect, and that ordering was a latent trap.
- **Fancy punctuation degrades to ASCII.** `&mdash;` becomes `-`, never an
  em-dash. This repo bans em/en-dash and ellipsis in shipped text and gates it in
  the scorecard, so decoding them faithfully would make this function INJECT what
  another gate rejects. Applies to the numeric spellings too, or the named form
  would be safe while `&#8212;` still injected.

An unknown named entity is left alone, so a title legitimately containing `&foo;`
survives.

`output-quality-check.sh` gains a tracker-title backstop covering all three
encodings: an entity reaching the stored state means the funnel was bypassed.

The decoder is fed through a QUOTED HEREDOC rather than `node -e '...'`. Inside a
single-quoted shell string every apostrophe in the JS terminates the string early -
including, at one point, the apostrophe in the comment explaining the problem.
shellcheck flags it (SC2140) and the first version worked only by accident of where
the quotes fell.

12 tests. 306 unit total.

## [12.10.0] - 2026-07-26

Two pieces of abandoned residue, and a gate that had inverted.

### `~/.multi-agent/` is pruned on install

The "shared runtime" the Cursor / Antigravity / VS Code Copilot Chat adapters
needed, so their emitted agents could reach the gate scripts by absolute path.
Those adapters were deleted in v10.7.0 along with `installSharedRuntime`,
`_base.mjs`, `rewriteScriptRefs` and `smoke-shared-runtime.sh` - but not the tree
they had written. 222 files (174 scripts, 23 lib, 24 schemas) sat frozen at
whatever the last adapter-era install produced.

It is worse than dead weight because it is indistinguishable from a live install
when you look at it: same directory names, same file names. Its
`schemas/migrations/` is missing `prefs-2.3.0-to-2.4.0.mjs`, so reading it gives a
migration chain three versions short and a reasonable conclusion that the chain
itself is stale.

`ABANDONED_TREES` now takes `root: "home"` entries for trees beside `~/.claude`
rather than inside it, and 253 files (this plus the 31 in `~/.claude/eval`) are
removed on the next install.

### The live-prefs gate was rejecting the only correct state

`smoke-schema-validation.sh` step 7 read:

```bash
if [ "$LVER" = "2.1.0" ]; then pass "live prefs already v2.1.0"
elif ... else fail "live prefs has unknown schemaVersion: $LVER"
```

`migrate-prefs.mjs` `TARGET_VERSION` had since moved to 2.4.0, which inverted the
gate: a prefs file left behind at 2.1.0 passed as "already current", and a file
correctly migrated to 2.4.0 fell through to the else arm and FAILED as "unknown".
The gate rejected the fully-migrated state it exists to encourage.

It now reads the target from `migrate-prefs.mjs` and the accepted set from the
schema's own `schemaVersion` enum, so it cannot disagree with either again, and a
known-but-older version reports how far behind it is (`v2.1.0 -> v2.4.0`) instead
of reading as current. This is a CONSUMER smoke (`/multi-agent:update` runs it), so
it resolves both paths through `SMOKE_DIR`, which is `pipeline/scripts` in the repo
and `~/.claude/scripts` in an install.

### Dead-file sweep

Swept refs, schemas, agents, libs and scripts for files nothing references. No
orphans. The two candidates were both false positives from the sweep's own exclude
flags: `design-check-config.schema.json` is referenced as the instance filename by
the design-check command, and `count-lib.sh` is sourced by two siblings inside
`pipeline/lib/`, which the sweep had excluded. Recorded here so the next sweep does
not re-flag them.

## [12.9.0] - 2026-07-26

Context engineering follow-through, and a guard that was blocking safe commands.

### Phase docs load their conditional sections on demand

The two biggest phase docs carried the UNION of every branch, so a run paid for
paths it never took. Both were over their own warn thresholds.

- `phase-0-init.md` Step 1b (URL Enrichment, 9.6 kB) applies only when the task
  input carries URLs. A bare Jira ID, a GitHub issue number or a free-text task
  never needs it. Moved to `multi-agent-refs/features/url-enrichment.md` behind a
  761-byte stub that states the run condition. Phase 0: 11701 -> 9469 tokens.
- `phase-4-review.md` Multi-Repo Mode (3.0 kB) applies only when
  `state.projects[].length > 1`. Moved to `features/review-multi-repo.md`.
  Phase 4: 12070 -> 11048 tokens.

Total phase-doc budget 50955 -> 48047 tokens, and both docs are now under warn
rather than over. `smoke-url-enrichment.sh` follows the content to the ref and
additionally asserts the phase doc still POINTS at it: a JIT reference nothing
loads is worse than the inline copy it replaced.

### The always-on description surface is budgeted

Progressive disclosure defers reference BODIES, but a skill or command
`description` sits in the host's listing for the whole session whether or not the
skill is ever invoked. That is a second fixed load, it was LARGER than the
budgeted one (76918 bytes across 236 descriptions), and nothing capped it.
`smoke-context-budget.sh` now gates the total and the per-description average
(the total can stay under budget while every description bloats), reports the
whole pre-task cost as one number, and `test/context-budget-gate.test.mjs` pins
all three ceilings as literals.

### No prose block loads twice

`smoke-context-duplication.sh` hashes normalised prose blocks across the files
that can co-load in one run. 12.7.0 removed three sections the orchestrator had
inlined from `multi-agent-refs/` - one already stale, naming a path the code no
longer used - found by hand with nothing to prevent a recurrence.

Scope mattered more than the algorithm. A first draft scanned the command and
skill trees too and reported 10+ duplicates, every one legitimate: the
`commands/multi-agent/X` vs `skills/shared/core/multi-agent-X` pairs are the
cross-CLI parity contract, which `cross-cli-contract.md` REQUIRES byte-identical,
and the mode commands repeat prose but only one loads per run. A gate that fires
on deliberate design trains people to ignore it. Narrowed to the ref tree plus the
orchestrator, it found one real case: three pickers each restating the Language
matrix that `rules.md` already carries on every run - and the copies had already
drifted, two saying "Commit/PR/Jira/Wiki" and one "Commit/PR/Jira". Replaced with
a one-line pointer, 678 bytes reclaimed.

### agent-guard stopped blocking safe pushes

The force-push detector ended in `-\S*f\S*`, matching any flag containing an `f`:

```
git pull --ff-only && git push origin main    -> BLOCKED
git push --follow-tags origin main             -> BLOCKED
```

Neither rewrites history, and `--follow-tags` is the normal way to push a release
tag with its commit, so the guard blocked a routine release step. False positives
are not free: people learn to route around the hook, which loses more safety than
the guard buys. The short-flag arm is now `-[A-Za-z]*f[A-Za-z]*` (a single dash
followed only by letters, so `-f`/`-fq` still match and no `--long-flag` can), and
force detection is scoped to the `git push` segment so a neighbouring command's
`-f` no longer implicates it (`rm -f stale.log && git push origin main`).

Fail-closed behaviour is unchanged and now asserted: an untokenizable command, and
a bare force-push whose target branch cannot be determined, still block.
`test/agent-guard.test.mjs` covers 30 cases across both directions.

### Also

- Trees an older installer created and no current one manages are pruned on
  install (`pruneAbandonedTrees`). `~/.claude/eval/` held 31 files that no
  installer wrote and no uninstall removed - wipe-before-copy only protects trees
  still being written, so an abandoned one persists forever.
- shellcheck in CI scans `git ls-files '*.sh'` instead of `find pipeline`, so a
  shell script added outside `pipeline/` is no longer silently unchecked.

## [12.8.0] - 2026-07-26

12.7.0 fixed nine gates that reported success without checking anything. This
release is the level above that: gates that check correctly and never run, and a
shipped surface that could not work where it was shipped to.

### 117 maintainer smokes stopped shipping to users

`pipeline/scripts/` was copied wholesale into `~/.claude/scripts/`, so every
repository CI gate landed on every user's machine. Measured from a real install:
of 117 installed smoke scripts, **100 failed** and 17 passed only by accident of
not needing the repo. They resolve the repo root as `dirname($0)/../..`, which in
an install layout is `$HOME`, and then read `package.json` / `install.js` /
`node_modules`. What a user saw:

```
$ bash ~/.claude/scripts/smoke-install-layout.sh
FAIL: install.js missing
```

A green repo gate, run in the wrong place, indistinguishable from a broken
installation. Worse, `/multi-agent:update` actively instructed users to run
`smoke-cross-cli-behavior.sh`, which reported "3 failed" for the same reason.

The default for `smoke-*.sh` is now EXCLUDE, in both the install and the tarball.
A smoke ships only when a command actually invokes it - `CONSUMER_SMOKES` in
`install/_dev-only-files.mjs`, currently the two that `/multi-agent:update` runs.
Both now resolve paths through the new `pipeline/scripts/_smoke-root.sh`, which
handles the repo and install layouts, so `smoke-cross-cli-behavior.sh` reports
20/20 from an install instead of 17/20.

Also withheld: `run-smokes.mjs`, `eval-*.mjs`, `pipeline/eval/**`, `scorecard.mjs`,
the linters, `validate-schemas.mjs`, and the fixture corpora - all of them need
devDependencies or the repo tree. Install drops from 201 files to 73; the tarball
from 977 to 753.

`smoke-consumer-smoke-surface.sh` holds four invariants: the allowlist equals what
commands invoke (drift either way fails), every consumer smoke uses the shared
resolver, no maintainer smoke survives the install filter, and none survives into
the tarball. `smoke-pack-contents.sh` now evaluates `files[]` the way npm does
(last matching pattern wins, so a broad negation may be followed by a narrower
re-inclusion - verified against npm directly) and reports an inert negation that
guards nothing. `smoke-install-leak-gate.sh` step 4 previously asserted the
OPPOSITE contract, naming three "essential" smokes that had to install; it now
derives the expected set from `CONSUMER_SMOKES`.

Fixed along the way: both installers computed the excluded-file count by summing
`countFiles()` over the exclusion array, which returns 0 for every non-literal
entry, so the count would have under-reported by 100+ files.

### Four gates that were tested, green, and never ran

Every gate in this repo checks its own behaviour. Nothing checked that a gate is
*reachable*. Four were not - each with a passing unit test and a passing smoke,
and no reference from `pipeline/commands/`, `multi-agent-refs/`, `agents/`,
`skills/` or `install/`:

- **`test-integrity-gate.mjs`** - the anti-reward-hacking control for a suite made
  green by deleting tests. Now Phase 4 Step 1.76, emitting blocking findings that
  merge into the reviewer findings at Step 3.0 (before the no-findings
  short-circuit, which would otherwise skip triage entirely on a diff whose only
  finding is a deleted test). No opt-out: a run that can switch off its own
  anti-reward-hacking control cannot be trusted to report a pass.
- **`review-scope.mjs`** - the reviewer-count cost gate. Now Phase 4 Step 1.77,
  behind `prefs.global.reviewScopeGate` (default true). Every diff had been paying
  for the full reviewer set.
- **`audit-log.sh`** - the PAT-lookup audit trail. `.gitignore` carried defensive
  globs for it and `/multi-agent:prune-logs` documented managing it, but
  `credential-store.sh` never called it, so the trail was always empty. Now
  written on every `get`, on both the success and the miss path, through the
  Python delegate and the raw-shell path. Since the trail now actually grows,
  `audit-log.sh` self-rotates past `AUDIT_ROTATE_AT_BYTES` (1 MB) rather than
  depending on a launchd/cron install nothing ever told a user to set up.
- **`write-state.mjs`** - the atomic `agent-state.json` writer with an advisory
  lock, written for exactly the multi-repo race the phase docs create by sharing
  one state file across worktrees. `operations.md` now documents it as the
  required mechanism, with the exit codes a caller must handle, and
  `phase-0-init.md` points at it where the shared file is introduced.

Phase 4 Step 1.75 now scores the diff **once without `--top`**: both new gates
need every scored file (a shrinking test file ranked 20th; whether any file is
high-stakes), and the top-5 prompt hint is derived from that report instead of a
second git walk.

`smoke-gate-wiring.sh` sweeps every runtime script for a reference from the
instruction surface, with maintainer tooling listed explicitly and by reason
rather than being the silent default for anything unreferenced. It also pins the
four wirings individually, and asserts the test-integrity gate is fed the full
report rather than the truncated hint. It found two more scripts on the first run
(`review-watch.sh`, `audit-log-rotate.sh`), both correctly user-launched.

### Removed: the v9.1.0 legacy-v2 analysis escape hatch

Triple-dead and still documented in three files. Its own text said "escape hatch
removed in v9.2.0" - this is 12.8.0. The `prefs.global.legacyV2AnalysisAllowed` it
depended on was never added to `prefs.schema.json`, and `global` is
`additionalProperties: false`, so a user who configured it as documented produced
a preferences file that fails validation. `template_version < v3` now simply
aborts. The reclaimed budget paid for the two new phase-4 steps, so the fixed
per-run context ceiling did not move.

### Gate honesty

- **The context ceiling can no longer be bypassed.** It was
  `${CONTEXT_BUDGET_CEILING:-60000}` - the only env-overridable threshold in the
  suite, so `CONTEXT_BUDGET_CEILING=999999` passed while measuring nothing. It is
  a literal now, pinned by `test/context-budget-gate.test.mjs` (the companion to
  `test/coverage-gate.test.mjs`, which already protects the coverage floors from
  the same move). Headroom was 3.5% when this was written - precisely when raising
  the number looks attractive. The test also prints current headroom so it shrinks
  visibly instead of silently.
- **The coverage claim names its denominator.** "coverage floors held" reads as a
  repo-wide figure; c8 instruments JavaScript, and this repo carries ~30k lines of
  shell and Python that no coverage number covers at all. The metric is now
  "coverage floors held (JavaScript only)" and states the excluded size, matching
  what the scorecard's UNMEASURED section already does for whole categories.

### Also

- `smoke-consumer-smoke-surface.sh` and `smoke-gate-wiring.sh` added to `ci-lite`;
  the consumer-surface gate also runs in `release`, since a publish is the only
  moment tarball contents become permanent.
- `test/dev-only-files.test.mjs` (20 tests) covers the exclusion predicate,
  including that a hypothetical new smoke is withheld by default and that runtime
  scripts the phases invoke are not.

## [12.7.0] - 2026-07-26

A refactor pass whose theme turned out to be one recurring defect: gates that
reported success without checking anything. Nine of them, plus a data-loss bug found
while fixing them.

### Fixed

- **The GitHub Actions security audit had never run anywhere.** `smoke-workflow-audit.sh`
  reported "0 passed, 0 failed, 2 skipped" on the workstation and on the runner, and
  `ci-lite.yml` justified not installing zizmor and actionlint by saying to run it
  locally "where the tools exist" - where they were equally absent. Run for the first
  time it found nine issues: a high-severity cache-poisoning path restoring a
  lockfile-keyed dependency cache into the job that signs and publishes the tarball,
  four checkouts persisting their credential, four jobs on the default token scope.
  All nine fixed across all three repos. The smoke is strict by default now; a
  missing linter fails, and the local escape hatch is ignored when `CI` is set.
- **A suite that asserted nothing reported success.** `run-smokes` only read exit
  codes, and three suites were exiting 0 having checked nothing - including the gate
  for the no-MCP-outside-analysis rule, which printed "no run to check" on every
  clean clone and every CI runner. It self-tests against eight fixtures now. The
  runner counts assertions and fails a suite that made none; the four output shapes
  it recognises were surveyed across all suites rather than assumed.
- **`smoke-plugin-validate.sh` ran zero checks here** while appearing as a green CI
  step. Removed; the real check lives in multi-agent-plugins, which gained its own CI.
- **The drift check read a mirror and called it the authority.** It resolved the
  upstream version from the installed plugin cache, only as fresh as the last
  marketplace update, so it reported "up to date" for a derivation four releases
  behind. Resolution is local clone, then repo API, then cache; a cache-only answer
  reports "unverified" rather than passing. `check-derived-drift.mjs` makes the order
  testable - seven tests, including a clone whose two manifests disagree.
- **`write-state.mjs` lost a concurrent write.** The lock was created empty with its
  PID written on the next line, so a writer arriving in that window read an
  unparseable PID, called the lock stale, and deleted a live one. Both writers then
  did their own read-modify-write and one update vanished. Reproduced three times in
  fifteen runs under load, invisible on an idle machine, which is why the smoke read
  as flaky. The lock is created atomically with its PID via `link()`, an unreadable
  PID no longer counts as stale, and the smoke went 10 checks to 12 with the race
  pinned directly.
- **Coverage was measured against the wrong denominator.** `.c8rc.json` lacked `all`,
  reporting 83% against a real 33% over its own include set. Floors are 72/68/85.
- **Two HIGH advisories** pinned by the lockfile, closed.
- **One skill routed into two stack plugins**: the backend pattern carried a bare
  `architecture` alternative that also matched `android-architecture` and
  `swift-architecture`, so a Python/Node plugin shipped Compose and SwiftUI guidance.
- **`engines.node` claimed `>=20.0.0`** while the code uses `import.meta.dirname`,
  which arrived in 20.11. A user on 20.0 through 20.10 would have broken.
- **The orchestrator skill named a log path the code had stopped using**, in one of
  three sections it had duplicated from loadable references while citing them zero
  times.

### Added

- **`npm run scorecard`** gates the measurable half of a review score against locked
  thresholds and names the four categories no script can score instead of giving them
  a number. Nine measured metrics: coverage floors, zero high or critical advisories,
  the fixed context budget, the workflow audit, the punctuation rule, a routing clause
  on every skill description, licence and changelog presence, the zero-assertion check
  still wired, derived skills current with upstream. Its own first version trusted an
  exit code that meant two different things and reported an unconfigured machine as
  passing.
- **`smoke-context-budget.sh`** pins the bytes every run pays before it starts.
- **`smoke-own-punctuation.sh`** enforces the locked punctuation rule on our own tree,
  in both the literal and the escaped encoding - `prefs.schema.json` carried 50
  escaped em-dashes that the first version of the gate could not see.
- **`check-derived-drift.mjs`**, with `upstreamVersionSource` and `upstreamLocalClone`
  in the prefs schema.
- **`eslint-plugin-n`** and **`publint`**, the latter having sat behind
  `npx --no-install` and never executed.

### Changed

- **Fixed per-run context: 67150 -> 57831 bytes.** Three duplicated sections moved
  out of the orchestrator skill to the references it now cites.
- **A routing clause in all 193 skill descriptions**, 93 of which had none. The
  linter fails on a missing one instead of warning.
- **`prettier` is enforced.** It was configured with nothing running it, so 583 files
  failed the declared style. Scope is code and config; markdown is excluded on
  purpose and the reason is in the workflow.
- **The punctuation rule applied to our own files**: 719 em-dashes across 77 files.
- `--ignore-scripts` on the four CI `npm ci` lines; `pipefail` in 13 lib scripts.
- eslint 10.8, prettier 3.9.6, c8 12.

## [12.6.0] - 2026-07-25

Two threads. The design-check command gains a scenario inventory, a coverage
gate and MCP-currency gating, and the refactor/sync pair learns to research,
audit and ship the companion dev-toolkit MCP server. Alongside that, a defect
sweep found eight gates and features that exited 0 while doing nothing -  six
of them guarded by a smoke test whose fixture had the wrong shape, so the suite
stayed green the whole time.

Requires `@mmerterden/dev-toolkit-mcp` >= v2.9.0 for the App Store audit path.

### Fixed

- **Eight gates and features that reported success while doing nothing.** Each
  exited 0 without doing its job, and in six cases the guarding smoke test
  hand-wrote a fixture with the wrong shape, so the suite stayed green.
  `npx @mmerterden/multi-agent-pipeline uninstall` was a silent no-op (the
  `isMainModule` guard compared `import.meta.url` to `argv[1]`, which is
  `index.js` under the bin dispatcher). `cost-budget-check.mjs` probed
  `.worktrees/<id>/phase-tracker.json` while `phase-tracker.sh` writes
  `~/.claude/logs/multi-agent/<id>/tracker-state.json`, so the cost ceiling
  could never fire; it also now prices `tokens_cached`. `classify-plan-safety.mjs`
  required `score >= 50` while its heaviest rule is 35, so no single heavy
  signal could trip the autopilot pause its own contract promises.
  `triage-memory.mjs` and `learnings-ledger.mjs` derived the repo slug from
  `--show-toplevel`, which inside a worktree is the task id, sharding the
  per-repo store one directory per task; they now use `--git-common-dir`.
  `state-2.0.0-to-2.1.0.mjs` emitted keys `agent-state.schema.json` rejects
  under `additionalProperties: false`. `memory-load.sh` read `preferences.json`
  while the installer only writes `multi-agent-preferences.json`, so per-repo
  memory was dead in production. `smoke-cross-cli-behavior.sh` had unquoted
  command substitution (SC2046) that made two gates pass while violations were
  present. The CI leak gate in all three workflows grepped 5 of the 23
  forbidden patterns and was `release.yml`'s only PII gate; all three now call
  `smoke-personal-data.sh --root`, keeping one pattern list.
- **shellcheck moves from `error` to `warning` severity.** `error` returns zero
  findings across all 175 scripts, so the gate could not catch this repo's
  actual defect class -  SC2046 is a warning. Noisy codes are excluded with a
  per-code rationale. Four real findings surfaced and are fixed, including two
  unguarded `cd` calls in `smoke-shadow-git.sh`, one preceding a relative
  `rm -rf`.
- **`credential-store.sh` resolves logical keys through
  `prefs.global.keychainMapping`.** `get github` searched the backend for a
  credential literally named "github" and returned empty with exit 1 -
  indistinguishable from "no such credential" -  because the entry is named by
  the mapping. Applied in `get`/`set`/`delete`, falling back to the logical key
  when no mapping exists.

### Changed

- **`ios_app_store_audit` catalog grows to 18 rules**, requiring
  `@mmerterden/dev-toolkit-mcp` >= v2.9.0. Adds `sdk-floor` (ITMS-90725, the
  iOS 26 / Xcode 26 build floor in force since 2026-04-28). `embedded-sdk`'s
  missing framework privacy manifest moves from a `5.1.1` WARNING to
  `ITMS-91061` ERROR, an enforced rejection since 2025-02-12, so archives that
  previously passed with a warning now fail. The rule count and the declared
  minimum are updated across all nine files that asserted them.

### Added

- **`/multi-agent:refactor` band E (Step 0c)**: the companion dev-toolkit MCP
  server is now in scope. The step researches current MCP practice (protocol
  revisions and SDK releases, host-client conventions, peer servers, the
  platform tooling it wraps, field practice) and audits the toolkit repo
  against it: syntax, stdout hygiene (stdout carries the JSON-RPC frames),
  advertised tool counts vs reality, `files[]` coverage, dependency freshness.
  Findings land in the merged Step 4 plan as band-E items, applied in that repo
  and shipped by sync. New focus filter: `/multi-agent:refactor dev-toolkit`.
- **`/multi-agent:sync` Step 3d**: ships the dev-toolkit MCP server when it
  moved (dirty tree, unpushed commits, or an untagged version). Seven ship
  gates run before anything leaves the machine, including a `tools/list`
  stdio handshake that must answer with a non-zero tool count, an
  advertised-count match against README and `package.json`, an
  `npm pack --dry-run` check that every runtime `tools/*/` directory is inside
  `files[]`, and a version-contract check against the minimums pipeline skills
  declare. Publish goes to the registry from that repo's `publishConfig`
  through a throwaway `--userconfig` built from the `npm` logical Keychain key.
  Outside autopilot / `release` it asks first; `/multi-agent:sync dev-toolkit`
  runs only this step.
- **`global.devToolkit` preference** (schema + template): `enabled`, `label`,
  `localPath`, `mcpServerName`, `packageName`, `registry`, `repoUrl`. Both
  commands fall back to auto-detecting the repo from the `mcpServers`
  registration when `localPath` is absent, and skip silently when nothing
  resolves. No path is ever hardcoded.

- **`smoke-command-inventory.sh`**: the command count is now DERIVED from
  `pipeline/commands/multi-agent/*/` and the prose must agree with the tree
  (contract header, both sync surfaces, every command listed on all three, and a
  `shared/core` skill counterpart per command). `smoke-generate-issue.sh` and
  `smoke-review-readiness.sh` stopped asserting the literal `41` and now compare
  against the derived count too.
- **`lint-mcp-refs.mjs`**: every `mcp__<server>__<tool>` reference in
  `commands/`, `skills/` and `multi-agent-refs/` must name a known registered
  MCP server, and when the companion toolkit resolves locally the referenced
  `dev-toolkit` tools are checked against its live `tools/list`. Wired into
  `npm test` and `npm run test:quick`.
- **`validate-prefs.mjs`**: validates `preferences-template.json` against
  `prefs.schema.json` (hard failure) and the local preferences file (advisory,
  `--strict-live` to enforce). Dev-only (ajv), excluded from the published
  package so the runtime stays zero-dep.
- `design-check` is registered in the command inventory: contract list +
  category row, both sync inventories, and the Copilot mirror. Inventory is 42
  commands.

### Fixed

- **Every `mcp__dev_toolkit__*` reference is now `mcp__multi-agent-toolkit__*`** (55
  lines across 10 files). A host composes MCP tool names as
  `mcp__<registered-server-name>__<tool>` and the server registers as
  `dev-toolkit`, so the snake_case form named tools that do not exist. The worst
  case was silent: `design-check`'s `allowed-tools` allowlist matched nothing, so
  the skill was denied the tools it needs, and `smoke-compliance-skills.sh`
  asserted the wrong spelling and kept the gate green.
- `prefs.schema.json` accepted neither the template it describes nor a real
  preferences file. Now declared: `global.modelFallback`, the `_*Template`
  documentation blocks, nullable `defaultJiraKey`, `keychainMapping.figma_pat` /
  `figma_user` / `claude_oauth_token` / `claude_oauth_token_fallback` (all four
  resolved by shipped flows), project-level `componentDevWorkflow`, and a
  `defaultReviewers` ceiling that fits real reviewer groups (10 -> 40).
- `global.derivedSkillSources` is now declared in `prefs.schema.json`. It was
  used by refactor Step 0b and shipped in the preferences template, but the
  strict (`additionalProperties: false`) global block rejected it.
- `/multi-agent:sync` Step 3d prefers the toolkit repo's own gate script
  (`npm run gates`) over the inline gate list, so the definition lives in the
  repo being shipped and the two cannot drift apart.

---

## [12.5.0] - 2026-07-24

Repo learning loop: repeated runs on the same repo produce better and cheaper
output over time. Plus a round of worktree hardening.

### Added

- **Freshness gate** for the triage corpus: `triage-memory` stamps each stored
  row with the file's git `file_sha`, and queries annotate `stale=true` when
  the file changed since the lesson was recorded, so dead lessons stop
  resurfacing in later runs.
- **Causal diagnosis** on lessons: `learnings-ledger` rows carry an optional
  `diagnosis` (the verbal root-cause "why", per Reflexion), and Phase 4's
  lesson-memory loop now records it. `brief` renders it as `(why: ...)` so the
  reason, not just the outcome, re-enters Phase 1 on the next run.
- **`learning-curve.mjs`** (+ smoke): a time-bucketed trend over
  `metrics.jsonl` (first-pass clean rate, review cycles, rework per task,
  tokens per task, cache ratio) that shows whether a repo's runs are improving.
- **`prompt-assembly.md`**: stable-prefix prompt-cache guidance, referenced
  from `phases.md`, so lazy-loaded phase docs stay cache-friendly.

### Changed

- Worktree handling hardened: `.worktrees/` is now a first-class member of the
  traversal-prune skip set (`node_modules`, `Pods`, `.build`, `DerivedData`,
  `.next`) across every tree walker (`repo-map`, `repo-cache`,
  `extract-conventions`, `shadow-git`), so no walker descends into a worktree
  checkout. Phase 0 gains an explicit residue-guard + traversal-prune contract;
  Phase 5 heals stale worktree admin state before recreating a worktree.
- `eval-mine-corpus` now reads the corpus from the per-repo memory dir it is
  actually written to (was a path that never received data).
- Phase-doc total token budget recalibrated 50000 -> 51000 for the new
  contracts (prose compressed first; every per-phase max still passes).

### Fixed

- `gc-worktrees.sh` anchors the repo on the main worktree and resolves paths on
  both sides, so orphan detection no longer misfires from inside a worktree.

## [12.4.0] - 2026-07-23

User-defined routines: manage your own recurring, project-specific jobs as
first-class `/multi-agent` commands.

### Added

- **`/multi-agent:save`** turns a recurring job into a reusable
  `/multi-agent:<name>` command. It leads with the work actually executed in
  the current session (distilling repeatable jobs from what was just done),
  then adds named procedures found in `~/.claude/CLAUDE.md`, and offers them in
  a multi-select picker: pick one, pick several to combine into one routine
  (steps concatenated in order), or describe a new one via free text.
- **`/multi-agent:routines`** lists saved routines with what each does, in
  `outputLanguage`. **`/multi-agent:forget`** removes one (guarded: never
  touches a shipped command).
- Backed by `routine-registry.mjs` (add/list/remove/get; name validation,
  collision + shipped-command guards, atomic prefs write). Saved routines are
  `local-only: true` command dirs + a `prefs.global.routines` registry entry:
  preserved across `/multi-agent:update` by the install snapshot/restore,
  rejected from the public repo by the sync backstops, and never counted in the
  command inventory.
- Registered in `/multi-agent:help` (EN + TR "Routines" section with a dynamic
  listing of saved routines).

### Changed

- Command inventory 38 -> 41 (`save`, `routines`, `forget`); contract, both sync
  docs, help EN/TR, and the count smokes updated; 3 Copilot twins added.
- prefs schema `global.routines` + `schemaVersion` 2.4.0; new
  `prefs-2.3.0-to-2.4.0.mjs` migration (idempotent, adds `routines: []`).

## [12.3.0] - 2026-07-23

`/multi-agent:analysis` overhaul: the feature-spec doc is now a development
handoff that both an AI implementer and a human reviewer can act on, bound by
one shared-ID traceability spine, with a deterministic pre-dispatch gate.
Researched against 2025-2026 spec-for-AI best practices. Repo stays generic;
all project specifics live in per-project `figma-config` / prefs.

### Added

- **Figma Dev Mode annotations as authoritative copy.** `evidence.figma[]` gains
  `annotations[]`; new `pipeline/lib/fetch-figma-annotations.sh` (Tier-2, token
  off argv, config-driven `TR:/EN:` parser). The annotation is the copy; the
  visible text layer is a placeholder. Never invent copy (blank beats a guess).
- **Localization ownership model.** `figma-config.localization`
  (`in-repo` | `externally-owned`, `authoringPipeline`, `locales`, `baseLanguage`,
  `keyPattern`). Section 10 renders a per-locale grid (in-repo) or key + status +
  copy-source + base value with per-locale values deferred to the owning system
  (externally-owned); new key = addition, existing = review-gated.
- **Business rules + rule-driven tests (Locked 31, the traceability spine).**
  New Section 4.4 `BR-<slug>-NN` rules -> Given/When/Then acceptance criteria ->
  Section 15.1 unit-test scenarios (happy/boundary/error/empty) with Swift
  Testing (iOS) + JUnit5/MockK/Turbine (Android). One shared-ID vocabulary binds
  rules -> tests -> UI flows -> a11y ids -> tokens -> Figma nodes.
- **Optional UI-test scenarios (Section 15.6)** and **optional VoiceOver/TalkBack
  walkthrough (Section 16.2)** via Phase 0 Step 5a opt-ins (`ui_tests`,
  `a11y_depth`), recorded in front-matter and enforced by the validator.
- **Layout & Scroll (Section 5.4):** auto-layout/padding/gap/sizing/position
  tokens plus explicit scroll container / sticky / content insets / safe area /
  keyboard avoidance (not inferable from a static frame) + Figma MCP capture
  order. **Dark Mode (Section 7.4):** semantic light/dark token pairs, elevation,
  contrast recheck. **Accessibility (16.1):** per-element table + WCAG 2.2 refs.
- **Deterministic pre-dispatch gate:** new `pipeline/scripts/validate-analysis-doc.mjs`
  checks the emitted doc (front-matter completeness, never-omitted sections,
  humanizer punctuation, Full-mode BR traceability, opt-in section presence);
  wired as a BLOCKING Phase 4 gate. Turns prose "dispatch gate" claims into a
  real, model-independent check. `smoke-validate-analysis-doc.sh` (10 cases).

### Changed / Fixed

- Section 11 analytics events cite their triggering story / business rule.
- Platform coverage clarified: the analysis renders exactly the platforms whose
  repos are provided (iOS only -> iOS doc, both -> both).
- Trim: Section 19 Alternatives + 22 Glossary default-drop; Section 18 Rollout
  slimmed to a checklist.
- Fixed pre-existing template bugs: figma evidence schema reconciled with the
  captured fields, locale-set `it`/`zh` mismatch, four undeclared `prefs.projects`
  keys, duplicate Section 13.6 heading, off-by-one Locked-decisions summary, and
  the Lite-mode Locked-31 contradiction (rule-to-test half is Full-mode only).
- Purged all airline/corporate example terms from the analysis surface (neutral
  `UserProfile` domain); personal-data leak gate: 0 leaking.

## [12.2.0] - 2026-07-21

### Added
- **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).
  - 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.
  - **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.
  - Config surface: `graylog` added to `keychainMapping` + `hosts` in `schemas/prefs.schema.json` and `preferences-template.json`; keychain doc row (non-critical service). Extraction branch added to `lib/context-link-extractor.sh`. Phase-0 deep-fetch step (`state.graylogContext`) + Phase-1 dispatch row + external-context-injection dispatch row. `multi-agent:setup` prompts for the Graylog host (like the Jira host) and discovers the token key. Offline smoke coverage in `scripts/smoke-fetchers-offline.sh`.

## [12.1.1] - 2026-07-21

### Changed
- **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.

---

## [12.1.0] - 2026-07-21

### Security
- **`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`).
- **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.)
- **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.

### Changed
- **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.

### Refactored
- **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.

### Added
- **`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.

### Docs
- 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/`.

---

## [12.0.0] - 2026-07-20

Command rename (breaking), a full-cleanup uninstall mode, outputLanguage-aware
picker descriptions, worktree residue cleanup, and cleanup-command hardening.

### Breaking

- **`/multi-agent:delete` renamed to `/multi-agent:uninstall`.** The old name is
  gone (no alias). The Copilot twin is now `multi-agent-uninstall`. Command
  count stays 38; the canonical inventory, sync list, help, and cross-cli
  contract were updated. Per the versioning policy a renamed command forces a
  major bump.

### Added

- **`uninstall --all-data` full-cleanup mode.** Standard uninstall still keeps
  settings + logs; `--all-data` additionally removes
  `multi-agent-preferences.json` and the entire `~/.claude/logs/multi-agent/`
  tree (task logs, state, metrics, audit). Tokens, `CLAUDE.md`, and the
  user-owned `rules/` are preserved in both modes. The slash command asks a
  scope picker (Standard vs Full cleanup) before the double confirmation.
- **outputLanguage-aware slash-command descriptions.** Every command ships an
  English `description` plus a `description-tr`; when `outputLanguage=tr`, the
  installer and the `/multi-agent:language` toggle swap the installed
  `~/.claude` descriptions to Turkish (reversible via a `description-en`
  sidecar). Repo and npm files stay English; `/multi-agent:sync` normalizes and
  gates the round trip so localized text can never leak back into the repo.
  New `localize-commands.mjs` + `smoke-description-tr.sh`.
- **`/multi-agent:garbage-collect` worktree phase.** New `gc-worktrees.sh`
  (dry-run by default) prunes stale worktree admin entries, removes orphan
  `.worktrees/*` dirs no longer registered as worktrees, unstages gitlink
  ("Subproject commit") residue from a pre-guard `git add -A`, and ensures the
  `.worktrees/` `info/exclude` guard. Registered worktrees are never touched.
- **Worktree residue guard on creation.** Phase 0 (single- and multi-repo) and
  the Copilot core skill now append `.worktrees/` to the clone-local
  `.git/info/exclude` before every `worktree add`, so a blanket `git add -A`
  can no longer record `.worktrees/{id}` gitlinks into shared branches.
- **update-check reaches npx-only installs.** The installer stamps
  `~/.claude/.pipeline-version` (and the Copilot equivalent); `update-check.sh`
  falls back to it when no repo clone is present, so users who never cloned the
  repo still see the "update available" prompt. Uninstall removes the marker.

### Changed / hardened

- **`/multi-agent:purge` now runs a real backend** (`purge.sh`): dry-run by
  default, every `rm` target resolved strictly inside `<repo>/.worktrees/`,
  refuses `/`, `$HOME`, and the project root; main worktree and
  main/master/develop always preserved. `--delete-remote` gates remote-branch
  deletion behind `--yes`.
- **`prune-logs.sh` / `gc-tmp.sh` hardened**: safe id/path validation, the
  audit trail + metrics corpus never removed, containment guards on deletion
  roots. New/expanded smokes: `smoke-purge.sh`, plus prune-logs and gc-tmp
  coverage.

## [11.5.0] - 2026-07-16

Full-project refactor round: 20 verified defect fixes (install lifecycle, shell
libs, fetchers, gate scripts), an install-time performance overhaul, and new
supply-chain / validation gates. Driven by a 4-band analysis (best-practices
research, bug hunt, category scoring, upstream-drift check).

### Fixed - data loss and destructive paths

- **Install no longer wipes user agent files.** `installAgents` wiped the whole
  `~/.claude/agents` / `~/.copilot/agents` dir on every install/update, deleting
  user-authored subagents. It now removes only pipeline-owned files (list derived
  from `pipeline/agents/` so it cannot go stale).
- **Plain install after `--link` no longer destroys the dev checkout.** Every
  wipe/copy target now goes through a symlink guard (`ensureRealDir`); a `--link`
  re-install just re-links instead of wiping through the symlink into the repo.
- **Unguarded `rm -rf` paths locked down.** `shadow-git.sh` validates task ids
  (`prune ..` refused); `multi-repo-pipeline.sh teardown` refuses `/`, `$HOME`,
  the project root, and their ancestors after path resolution.
- **`update-issue-progress.sh` no longer eats issue bodies.** The Progress-table
  rewrite is now bounded at the legend comment, the next heading, or EOF; bodies
  without the legend comment keep all trailing sections.
- **Copilot instructions merge is now marker-bounded.** An explicit end marker
  preserves user content appended after the pipeline section across install,
  update, and uninstall (previously sliced to EOF).
- **Uninstall actually removes what install creates.** Agent files (dir-vs-file
  bug meant none were ever removed), `multi-agent-refs/`, `schemas/`, `lib/`,
  and the compliance core skills are now covered on both targets; token and
  preferences guarantees unchanged.

### Fixed - security

- **Pre-commit secret-scan hook fired never.** The PreToolUse matcher used
  permission-rule syntax that can never match the tool name; matcher is now
  `Bash` with a fast stdin filter in `pre-commit-check.sh`, and existing installs
  migrate the dead entry in place.
- **Secrets off argv.** OAuth refresh grants, bearer/PAT headers, Basic auth,
  and keychain writes across nine call sites (fetchers, repo-cache, issue-fetcher,
  figma-mcp-refresh, credential-store, keychain-save) moved to `curl -K <(...)`
  configs and stdin so tokens never appear in `ps`.
- **Crashlytics service-account key hygiene.** The decoded Firebase SA JSON now
  lives in a `mktemp` file created under `umask 077` with trap cleanup instead of
  a predictable world-readable `/tmp` name.
- **Telemetry honesty.** The "anonymous" install ping no longer sends
  `githubUser`, `gitEmail`, or `hostname`.

### Fixed - correctness

- **`fetch-confluence.sh` worked never.** An env-prefix-on-assignment bug meant
  the URL parser always saw empty input; every invocation failed. Parsing now
  goes through argv and the fetcher honors its documented exit codes.
- **`diff-risk-score.mjs` default invocation** resolved the diff base after
  using it (`null...HEAD` fatal, gate fail-open); base now resolves first.
- **`evidence-gate.mjs`** no longer flags passing logs containing "0 tests
  failed" as failures.
- **`grep -c || echo 0` double-output idiom** fixed at ~20 sites via a shared
  `count-lib.sh` helper (bogus `* PR:` bullets in Jira/Confluence bodies, silently
  zeroed convention counters in `extract-conventions.sh`).
- **False-success family.** `multi-repo-pipeline.sh` no longer prints
  "committed/pushed" on failure; `repo-cache.sh` keeps the previous cache when a
  refresh fails instead of serving an empty file for the TTL; `review-watch.sh
  --watch` survives transient gh failures and its cursor can no longer move
  backwards.
- **`issue-fetcher.sh`** classifies bare `#316` / `316` correctly (repo picker
  path instead of a malformed `//issues/` URL).
- **`phase-tracker.sh`** read-modify-write is now lock-protected; parallel
  Phase 4 reviewers no longer lose token/cost deltas.
- **Shadow-git**: forced excludes now land in `info/exclude` (they were dead
  code, ballooning snapshots with DerivedData), and `restore --files` removes
  files created after the snapshot instead of keeping half the change.
- **macOS bash 3.2 portability**: `${URL,,}` in `fetch-swagger.sh`, `date -r
  <file>` in `audit-log-rotate.sh` (which also rotates atomically now).
- **Glob translation** in `triage-memory.mjs` / `test-gap-scan.mjs`: `**` no
  longer breaks after the `*` pass (nested-path filters match again).
- **`migrate-prefs.mjs`** errors on unknown flags (a `--dry-rnu` typo used to
  perform a real write) and reports the correct target version.
- Minor hardening: `write-state.mjs` lock release on early exits,
  `post-pr-review.sh` / `plan-todos.sh` null-safe jq, `account-resolver.sh`
  JSON escaping, `scan-skills.sh --json` spurious empty finding removed.

### Performance

- **Install: ~94s -> ~6s.** `scan-skills.sh` rewritten from per-file grep loops
  to combined single-pass scans (identical findings output, verified by diff);
  `countFiles` no longer shells out to `find | wc -l`.

### Added - gates and tooling

- **OIDC trusted publishing.** `release.yml` publishes tokenless (no more
  `NPM_TOKEN`); one-time npmjs.com trusted-publisher setup required before the
  next release (see workflow header). Release gates now also run the layout
  smoke, the install leak audit, and the new pack-contents smoke.
- **New smokes**: `smoke-plugin-validate.sh` (`claude plugin validate --strict`),
  `smoke-workflow-audit.sh` (zizmor + actionlint, offline, skip-if-missing),
  `smoke-pack-contents.sh` (packed-payload deny-list derived from package.json
  negations at runtime + file-count band), `smoke-fetchers-offline.sh`
  (44 offline assertions covering every fetcher's parse/credential/network
  stages - the class of hole that let fetch-confluence ship dead).
- **`check-md-links.mjs` now covers `pipeline/**`** (700+ skill/command files,
  `$HOME/.claude/multi-agent-refs/` links validated against the repo source).
- **`lint-skills.mjs` spec-conformance warnings** (agent-skills name pattern,
  routing-clause descriptions, body size budget).
- **Coverage floor**: c8 `--check-coverage` at the measured baseline (lines 80).
- **DevEx**: `test:quick` script, `index.js --version`, unknown commands list
  valid ones, smoke-runner env knobs documented in CONTRIBUTING, `cache: npm` +
  strict `npm ci` in all workflows.
- **Real-module tests**: `test/index.test.mjs` suites now import the actual
  install helpers instead of inlined copies (the dead hook shipped because the
  copy-based tests could not fail); new temp-HOME integration tests for the full
  install -> update -> uninstall lifecycle and the JS gate scripts. Unit tests
  145 -> 197.

### Docs

- ROADMAP moved to the 11.x line, SECURITY.md supported-versions table updated
  (11.x active), README Scorecard badge, dead `files[]` negations removed,
  internal working notes moved to `docs/internal/`.

## [11.4.0] - 2026-07-10

Review PR-picker, two readiness-review commands, and durable local-only wrappers.

- **`/multi-agent:review` no-URL PR picker.** With no argument (interactive), review
  now lists open GitHub + Bitbucket PRs in one labeled list and multi-selects which
  to review (per-PR inline comments + approve/needs-work), instead of silently
  defaulting to the local branch diff. Autopilot / no open PRs falls back to the
  local diff; explicit URL/`#N`/branch still work. The Copilot twin was brought to
  parity (input-aware + PR posting).
- **New `/multi-agent:review-jira` + `/multi-agent:review-issue`.** Grade whether a
  Jira issue / GitHub issue is ready for the pipeline (scope, acceptance criteria,
  repro, design reference, API contract, stack signal, dependencies) by reusing the
  Phase 0 maturity engine (`issue-fetcher.sh`) plus a readiness rubric, then post
  the gaps back as a comment on the item (confirmed; autopilot auto-posts). Read-only
  on code: no worktree, no branch, no creation. Command count 36 -> 38.
- **Corporate alias wrappers survive updates.** `install/claude.mjs` now preserves
  command dirs whose SKILL.md is `local-only: true` across the namespace wipe, so
  personal `multi-agent:<name>` aliases that delegate to a private marketplace plugin
  are no longer destroyed on every install/update (they were, before). Covered by a
  new `smoke-wrapper-preservation.sh`. (The wrappers themselves are local-only and
  never ship in this package.)

## [11.3.2] - 2026-07-10

Command rename + website refresh.

- **Renamed `/multi-agent:generate` -> `/multi-agent:create-jira`** (Copilot skill
  `multi-agent-generate` -> `multi-agent-create-jira`). The command is the
  consolidated Jira-issue creator that asks the type (Task / Bug / Story); the
  new name says what it does. Command dir, Copilot skill, dispatcher rows, help
  (EN + TR), the command inventory in the cross-CLI contract + both sync copies,
  the skill index + manifest, and the contract smoke were all updated; command
  count stays 36. The internal ref (`generate-issue.md`) keeps its filename.
- **Website:** corrected the `multi-agent-plugins` skill count (205 -> 227, the
  real sum across the five toolkits) and bumped the pipeline card to this version.
- Also published v11.3.1 to GitHub Packages (both registries were on npmjs only).

## [11.3.1] - 2026-07-10

Self-audit patch (found by running `/multi-agent:refactor` on the pipeline itself): a data-loss fix, a latent gate regression, supply-chain hardening, and a few correctness fixes.

- **Fix (data-loss): uninstall no longer deletes `~/.claude/rules/`.** Install
  treats `rules/` as user-owned (write-if-missing, never overwritten), but the
  uninstaller recursively removed the whole tree, destroying personal rules
  (including the git-attribution rule). Uninstall now preserves `rules/`, and
  lists it in the PRESERVED summary.
- **Fix (gate regression): `lint-skills` scoped to `pipeline/skills`.** The
  v11.3.0 command-layout migration made command files match `find pipeline -name
  SKILL.md`; slash commands have no `name:` frontmatter, so the skill linter
  started erroring on all 37 of them (latent because CI is billing-paused and
  releases were published manually). The linter now only checks actual skills.
- **Fix: `.skills-index.json` is idempotent** - dropped the `generatedAt`
  timestamp so regenerating an unchanged skill set produces no diff.
- **Fix: uninstall strips the Copilot pipeline block regardless of file size** -
  removed the 50 KB fallback cap that orphaned the block in large
  `copilot-instructions.md` files.
- **Fix: install "dev-only excluded" count** is now the real file count (the
  `fixtures/` directory was under-counted as one entry).
- **Security (supply-chain):** `release.yml` publishes with `npm publish
  --provenance` (`id-token: write`); all GitHub Actions pinned to commit SHAs;
  added `.github/dependabot.yml` (actions + npm dev deps); `credential-store.sh`
  escapes single quotes in the Windows PowerShell paths.
- **New: `eval-mine-corpus.mjs`** turns recorded triage decisions
  (`triage-corpus.jsonl`) into review-gated candidate triage-eval fixtures.
- **Tests:** added node unit tests for the Phase 4 diff-risk gates
  (`test/phase4-gates.test.mjs`) so they run in the release lane, not only the
  bash smoke; `lint-skills` now warns (non-fatal) on terse skill descriptions.

## [11.3.0] - 2026-07-10

Single-source skill management, an upgraded `refactor` command, and two new Phase 4 diff-risk gates.

- **Component/Figma work is plugin-only.** The pipeline no longer bundles the
  `figma-ios` / `figma-android` / `figma-common` skill trees. Phase 3 dispatches
  component and Figma-to-code work to the per-stack marketplace plugins
  (`ai-ios-toolkit` / `ai-android-toolkit`) via the Skill
  tool, so component skills live in one place. The 3-tier Figma design-access
  fallback still governs the analysis phase.
- **Command layout migrated to `<name>/SKILL.md`.** Each subcommand is now its own
  directory with a `SKILL.md`, and the dispatcher is `commands/multi-agent/SKILL.md`.
  This restores subcommand registration in the `/` picker on current Claude Code
  (the older flat `commands/multi-agent/<name>.md` files stopped registering). The
  slash surface (`/multi-agent:<name>`) is unchanged.
- **Refs are non-invocable.** The 46 reference docs + internal pickers moved out of
  `commands/` into `multi-agent-refs/`, so Claude Code no longer registers them as
  `/multi-agent:refs:*` slash commands. Commands read them by absolute path.
- **`refactor` upgraded.** `/multi-agent:refactor` now (A) researches field
  best-practices from GitHub + X/Twitter + Reddit + web and produces an adapted
  plan fitted to the repo's stack, (B) runs an explicit bug hunt (real defects with
  file:line + failure scenario, verified before reporting), and (C) checks upstream
  drift of derived skills, configured via `global.derivedSkillSources` in local
  preferences (never synced, so it can reference private sources). All four bands
  (best-practice / bug / improvement / drift) merge into one prioritized plan.
- **New: Phase 4 diff-risk gates.** `test-integrity-gate.mjs` is an
  anti-reward-hacking gate that turns removed/weakened tests into a blocking review
  finding, and `review-scope.mjs` scales the reviewer count to diff complexity
  (trivial diffs get a single reviewer, high-stakes or large churn gets the full
  panel). Both are deterministic pure functions of the diff-risk JSON, covered by
  `smoke-phase4-gates.sh`.
- **Cleanup + hardening.** Removed stale flat/`refs/` paths from the Copilot core
  skills and `copilot-instructions`, pruned the orphaned `figma-to-component`
  install dir, broadened the personal-data leak detector to catch private
  marketplace/plugin names, and refreshed the docs (README, features, architecture,
  Figma pipeline) to the plugin-dispatch model.

## [11.2.0] - 2026-07-07

Skill mining + an install-safety fix.

- **Fix (data-loss): `install.js` no longer clobbers user-owned rules.** `rules/`
  is authored/evolved locally and is deliberately never synced back to the repo
  (privacy). The installer used to `wipe + copy` it on every update, which
  reverted local edits (including the git attribution rule). Rules now install
  write-if-missing: new baseline rules are added, existing local rules are
  preserved untouched. New `copyDir({ skipExisting })` option backs this.
- **Two skills mined from the corporate iOS toolkit and genericized into
  `ai-common-toolkit` (v0.1.2):**
  - `skill-creator` — the house rules for authoring skills (description-first
    discovery, lean SKILL.md as an index, progressive disclosure, reference vs
    workflow vs tool layers, no-prefix naming, grow-from-failure), with a
    template, pre-ship checklist, good/bad examples, and a multi-architect audit
    script. All corporate references scrubbed.
  - `backlog` — a deferred-work registry where every deferred job lands with its
    full spec + origin + unblock condition (deferred -> ready -> in-progress ->
    done, never deleted), so deferral never means loss.

## [11.1.0] - 2026-07-07

Harness-hardening pass: four techniques adapted from studying cross-harness agent
operating systems, all additive and opt-in.

- **Guard hooks (`agent-guard.sh`)** — a new PreToolUse Bash gate that turns two
  prompt-level rules into deterministic, OS-enforced blocks: AI/assistant
  attribution in a commit message, and force-push to a protected branch
  (main/master/develop). Fail-open on any internal error, never executes the
  inspected command (decision core is `agent-guard.py`, shlex-tokenized only),
  no network, no secret output. Wired into `install/templates/claude-hooks.json`
  alongside the existing secret scan; `multi-agent:setup` offers to merge it.
  Backed by `smoke-agent-guard.sh` (19 behavioral cases incl. injection safety).
- **Config-hygiene gate (`scan-agent-config.sh`)** — a dependency-free, read-only
  audit of the shipped config surface (hook templates, preferences template,
  agent defs, MCP helper scripts) for hardcoded secrets, permission-bypass flags,
  eval-bearing hook commands, blanket `Bash(*)` allows, and pipe-to-shell /
  unpinned-npx install patterns. Runs in the release VERIFY step; blocks on any
  HIGH finding. Backed by `smoke-config-hygiene.sh` (real surface clean +
  planted-bad detection).
- **Autopilot circuit-breaker** — `refs/features/autopilot-circuit-breaker.md`
  defines the sanctioned autopilot pause: halt and hand back to the user on a
  no-progress stall, an identical repeated failure, a rework storm, cost drift
  past the `costBudget` ceiling, or a merge/rebase conflict. Continuing
  unattended off the happy path is the less safe choice.
- **Three technique skills** added to `ai-common-toolkit`
  (v0.1.1): `council` (multi-voice adversarial decision), `search-first`
  (research-before-coding with an adopt/extend/compose/build matrix), and
  `agent-introspection-debugging` (capture -> diagnose -> contained-recovery ->
  report, with an honesty guard against fake auto-heal claims).

## [11.0.0] - 2026-07-07

Breaking: the two issue creators `generate-task` and `generate-bug` are merged
into a single `generate` command. The command surface shrinks from 37 to 36
commands, so this is a major bump per the versioning policy (a renamed/removed
command is a breaking surface change).

- **New `/multi-agent:generate`** asks the issue type (Task / Bug / Story) at the
  start of every run, then mines the project's recent same-type issues for
  conventions and drafts the issue.
- **Standard template baseline with auto-sizing.** Each type has a fixed section
  skeleton (Task/Story: Detailed Description, Scope, Acceptance Criteria, Test
  Scenarios; Bug: Detailed Description, Steps to Reproduce, Expected/Actual
  Result, Environment). Conditional sections (Design Reference, API Contract /
  Swagger, Screenshots, Notes) render only when their trigger is present  -  no
  empty placeholder headings, nothing invented.
- **Test Scenarios pulled from Jira convention.** Detects linked Xray/Zephyr test
  issues or a dominant test-scenario heading style and reuses it; falls back to an
  AC-derived skeleton the user edits.
- **Swagger / API Contract section.** When a Swagger/OpenAPI URL or contract is
  given, fetches only the referenced endpoints (method + path + key fields) and
  links the spec. No full-spec crawl; failures degrade to link-only.
- **Removed** `generate-task` and `generate-bug` on both CLIs (Claude Code +
  Copilot) and the plugin skill set. The shared `refs/generate-issue.md` flow is
  now 12 steps with a dedicated type-selection step; the hard approval gate is
  unchanged.

## [10.12.0] - 2026-07-06

Two trust upgrades: every external channel post now shows a plan-mode-style
preview + approval gate before anything is written (autopilot posts directly),
and Phase 0 gains a proactive token expiry pre-flight with silent Figma MCP
renewal via OAuth refresh grant.

### Added

- **Channels Step 6 body preview + approval gate** (dispatch → Step 7,
  summary → Step 8) - interactive runs render
  every selected channel's final body (PR description, Jira comment,
  Confluence page, Wiki, Board move, GitHub issue comment) and ask
  Approve & post / Edit (loop) / Skip channels / Cancel before dispatch.
  Bypassed only by autopilot and `--dry-run`. What is posted is byte-identical
  to the approved preview; regeneration after approval re-runs the gate.
  Phase 7 Step 1.5 (issue comment + progress flags) routes through the same
  gate. Contract: `smoke-channels-approval-gate.sh`.
- **Phase 0 Step 0.7 token expiry pre-flight** - probes every token the run
  will need (jira/github/bitbucket/confluence/figma/figma_mcp) with one cheap
  call each (cached via `serviceStatus`, TTL 300s) and resolves expiry AT INIT
  instead of on the first mid-run 401. Figma MCP is the critical path: silent
  renewal first, then an init-time question (Regenerate now via script / Save
  Flow / Continue degraded). Autopilot never prompts - silent renewal or
  logged degrade. Contract: `smoke-token-preflight.sh`.
- **`lib/figma-mcp-refresh.sh`** - silent Figma MCP (`figu_`) renewal via
  OAuth refresh grant: reads `<key>_Refresh` from the Keychain + client
  credentials from the `.figma-oauth.json` next to
  `prefs.global.tokenScripts.figma_mcp`, saves the new access (and rotated
  refresh) token back. Status lines only - token values never printed.
  Live-verified end-to-end (renewed + mcp.figma.com probe 200).
- **`prefs.global.tokenScripts`** (schema + template) - maps service ids to
  user-owned token generation scripts; personal paths stay in the local
  preferences file, never in synced command files.
- **keychain.md Figma MCP lifecycle section** - refresh-entry convention,
  silent-renewal-first rule, generation-script escalation, init cross-ref.

### Fixed

- Copilot parity catch-ups the 10.11.0 sweep missed: `multi-agent-sync/SKILL.md`
  inventory 35 -> 37 + list, `multi-agent-help/SKILL.md` generator entries
  (EN + TR), `multi-agent-channels/SKILL.md` flow now includes the approval
  gate (steps 6-8). `smoke-generate-issue.sh` now asserts the SKILL-side
  counts too.
- `figma-mcp-refresh.sh` form fields sent via `--data-urlencode` (robust to
  special characters in tokens); re-verified live.

## [10.11.0] - 2026-07-06

Jira issue generators: `/multi-agent:generate-task` and `/multi-agent:generate-bug` create standards-compliant Jira issues by learning the target project's own conventions before drafting, with a hard preview + approval gate before anything is created.

### Added

- **`/multi-agent:generate-task` + `/multi-agent:generate-bug`** - one-shot
  Jira issue creators (no worktree, no commits, no pipeline chaining). Inputs:
  optional free-text description + optional Figma URL; both asked
  interactively when missing.
- **Convention mining** - samples the project's 30 most recent same-type
  issues (JQL) and adopts the team's summary prefix pattern, dominant
  description heading set, top labels/components, and priority norm.
  createmeta discovery surfaces required custom fields (allowedValues become
  picker options) and detects Sprint/Epic/Team fields generically by
  `schema.custom` id.
- **Active-sprint placement** - Agile REST board + `sprint?state=active`
  detection; the user chooses active sprint vs backlog. Sprint assignment
  happens post-create via `POST /rest/agile/1.0/sprint/{id}/issue`
  (field-id-agnostic). No board / no active sprint degrades to backlog with
  an explicit note.
- **Hard approval gate** - a full draft preview (every field + complete
  description) renders before create; Approve / Edit (loop) / Cancel. The
  gate has no bypass: no autopilot variant exists for these commands.
  Genuinely unknown fields (component, epic, priority, labels, assignee,
  sprint, required customs, missing Bug repro/environment) are asked, never
  invented.
- **Figma context (optional)** - the 3-tier chain (MCP -> REST PAT -> user
  screenshot) resolves frame name + node id for a Design Reference section,
  a remote link on the created issue, and an opt-in screenshot attachment.
  Standalone command, so MCP use is allowed (analysis-class); Figma failures
  never block issue creation.
- **Language rule** - issue summary + description follow
  `prefs.global.outputLanguage` (intentional exception to the
  external-payloads-English default; the issue is authored for the user's
  team). Documented in both command files and the shared ref.
- **Shared flow ref** `refs/generate-issue.md` - single 11-step contract
  consumed by both commands via profile blocks (`ISSUE_TYPE`, default
  sections, JQL filter); UTF-8 verbatim POST path (`jq --rawfile` +
  `--data-binary @file`) and humanizer pass reused from the Jira channel
  adapter.
- **Smoke gate** `smoke-generate-issue.sh` - 66 assertions: file/frontmatter
  presence, profile declarations, approval-gate markers, endpoint markers,
  UTF-8 path, section vocabulary, 37-command count consistency across
  dispatcher/contract/sync/help, forbidden-string scan.

### Changed

- Command inventory 35 -> 37 across `cross-cli-contract.md`, `sync.md`,
  dispatcher routing/loading tables, `help.md` (EN + TR), README.

## [10.10.0] - 2026-07-03

Live tracker UX: per-tile cost, a real "currently doing X" line, and tracker continuity across the local-test handover. Shaped by a market sweep of 2026 agent-UX practice (transparency-first live status; per-step cost visibility).

### Added

- **`phase-tracker.sh now <N> "<text>"`** - dedicated live current-action
  line for the active phase (quiet: writes `meta.Now`, never renders, 60-char
  cap). The bordered card shows it as `Now: <text>` under the active tile.
- **`phase-tracker.sh cost <N>|total`** - single pricing implementation
  (cost-table.json rates, cached tokens at the discounted cacheRead rate,
  floored to cents; `-` when unpriceable). The completion narration line,
  the card, and finish's summary all reuse it - no more inline USD math.
- **Per-tile est. USD + cached footer on the bash card.** Tiles render
  `<elapsed> · <tok> tok · ~$<usd>` when the phase has a priced model; the
  footer adds total USD and cached tokens. Render survives a missing cost
  table. Tiles now sort by numeric phase id so continuation sets stay ordered.
- **Progress-line mirror rule** (tracker-contract "Active phase" +
  progress-contract cross-link): every normal-tier `→ verb object` line is
  mirrored to the active phase - `TaskUpdate activeForm` on Claude Code,
  `now` elsewhere - so the tracker always answers "what is it doing right
  now". Throttled: canonical lines only, dedupe, no renders.
- **Completion tile suffix (Claude Code):** on phase completion the tile
  subject gains the spend (`Phase 3: Dev · ~35k tok · ~$0.58`); subject edits
  do not reorder tiles.
- **Tracker continuity across the local-test handover.** Phase 5 and
  `/multi-agent:manual-test` mark the waiting state
  (`now 5 "awaiting local test (user)"`); `/multi-agent:finish` now runs
  load-or-continue instead of an unconditional `init` - prior phase 0-3
  history (elapsed, tokens, USD) survives, Phase 5 is closed with a Result
  meta, the Claude Code TaskList is rebuilt from state, and one line
  summarizes the inherited history (`Continuing <id>: phases 0-3 finished
  earlier (12m, 38.4k tok, ~$0.74)`). `add` is idempotent to make this safe.
- **`/multi-agent:resume` rebuilds the phase tiles** from `tracker-state.json`
  (the contract documented it; the command now actually does it).
- **Phase 0 + 7 token forwarding**: the clarifier call and the Phase 7 report
  compose call forward tokens to the tracker, completing all 8 tiles.

### Fixed

- Tracker doc drift: the `tokens` action's 4th `[cached]` arg and the `model`
  action are now documented in the contract and the script usage text.

---

## [10.9.0] - 2026-07-03

### Added

- **Update check at run start (Phase 0 Step 0.6, opt-out via
  `prefs.global.updateCheck`).** Once per `ttlHours` window (default 24h,
  cached, 3s-bounded curl straight to the npm registry - never `npm view`),
  the pipeline compares the installed version against `dist-tags.latest`.
  Newer version found: interactive modes ask ONE question ("Update now?" ->
  yes runs the `/multi-agent:update` flow and the run continues; the update
  takes full effect next session); autopilot logs one line and never asks
  (zero-interaction contract). `autoUpdate: true` skips the question and
  updates before the worktree is created. Offline, ahead-of-registry, and
  failed checks are silent - the step never blocks. New
  `pipeline/scripts/update-check.sh` + `smoke-update-check.sh` (13 assertions).
- **Design fidelity contract (Phase 3, BLOCKING on Figma-referenced tasks).**
  The analysis doc's captured frames are the 1:1 reference for every UI line:
  a Code Connect-mapped component must be used verbatim (no sound-alikes,
  missing modifiers go into `+Modifiers`, never forks), unmapped UI becomes a
  NEW component under the standard architecture, and inter-component spacing
  comes from the design's measured values mapped to tokens - never invented.
  Missing design data halts with a re-run-analysis instruction; Figma MCP
  stays forbidden outside analysis.
- **`docs/engineering.md`** - version-free catalog of the discipline layer
  (bounded loops, evidence gates, token-budgeted phase docs, immutable tests,
  fresh-context handoffs, memory hedges, install hygiene), linked from README.

### Changed

- **Docs drop inline version markers.** `docs/features.md` headers and bullets
  no longer carry `(vX.Y.Z)` tags - the docs describe the current state;
  history lives in this CHANGELOG. Same principle applied to the website copy.
- **Phase-doc token budgets recalibrated** (`token-budget.json`, total
  46500 -> 50000) after the verify-by-test, update-check, immutable-test, and
  design-fidelity contracts landed - Step 3.7 prose was compressed to a
  pointer into `refs/features/verify-by-test.md` before recalibration.

---

## [10.8.0] - 2026-07-02

Three additive loop-quality features, all sourced from the 2026 agentic-loop research sweep (Anthropic long-running-agent harness guidance + adversarial-review findings): empirical verification of blocking findings, an immutable-test contract, and structured phase-boundary handoffs.

### Added

- **Verify-by-test triage (Phase 4 Step 3.7, opt-in via `prefs.global.verifyByTest`).**
  Accepted blocking findings are no longer judgment-final: one verifier agent
  (default Sonnet, capped at `maxFindings`=3) writes a minimal repro test per
  finding and runs ONLY that test. Test fails as predicted -> finding confirmed
  and the repro test is handed to Phase 3 as the rework RED test
  (`state.reviewIterations[-1].verifyByTest.redTests[]`). Test passes under
  `evidence-gate.mjs` -> finding downgraded to `deferred` (never `rejected`).
  Compile error / timeout -> `inconclusive`, judgment verdict stands. The whole
  step is timeout-bounded and never blocks the pipeline. Schema
  `triage-output` v3.2.0 adds the optional per-finding `verification` block;
  `validate-triage.mjs` validates it. New feature doc
  `refs/features/verify-by-test.md` + `smoke-verify-by-test.sh` (20 assertions).
- **Immutable-test rule + `test_lines_removed` diff-risk signal.** New rule in
  `refs/rules.md` and the Phase 3 GREEN step: deleting, renaming, or weakening
  an existing test to reach green is a violation; a test changes only when the
  task changes the spec it encodes, named in the commit body. Deterministic
  backstop: `diff-risk-score.mjs` v1.1.0 emits `test_lines_removed` (w=3.0)
  when a test-classified file removes more lines than it adds; wired into the
  Phase 4 Step 1.75 signal table, `diff-risk.schema.json` v1.1.0, and
  `validate-diff-risk.mjs`. New fixture `diff-risk-test-removal.diff` +
  3 new `smoke-diff-risk.sh` assertions.
- **Structured handoff blocks (fresh-context re-entry discipline).** The
  phase-boundary checkpoint in `refs/phases/operations.md` now appends a
  structured `## Handoff` block (Done / Remaining / Decisions / Open findings /
  Next) to `agent-log.md` at every phase transition - written by the
  orchestrator from state it already holds, no LLM call, ~15 lines. The
  post-`/compact` re-grounding and `resume.md` Step 3 read the latest handoff
  FIRST (state wins on mismatch), falling back to per-phase findings for
  pre-v10.8 logs. Documented in `log-format.md`; guarded by
  `smoke-handoff-contract.sh` (13 assertions).

- **Module review guides in `/multi-agent:review` (Step 2b).** When a changed
  file's module carries its own convention file (`CLAUDE.md`, `*-CLAUDE.md`,
  `AGENTS.md` below repo root), the review discovers it deterministically from
  the diff paths (capped at 5, truncation logged), injects it into every
  reviewer prompt, and scopes each guide to files under its own directory.
  Works with a local checkout or via provider API in PR mode.

### Fixed

- **`validate-triage.mjs` reviewer enum accepts `fable` and `gpt`.** The runtime
  validator still rejected the schema-v3.1.0 reviewer values restored in
  v10.7.4 (`fable` is Reviewer 1 on Claude Code, `gpt` on Copilot CLI), so any
  accepted finding attributed to them failed the 3.2.1 gate. Enum now matches
  `triage-output.schema.json`.

---

## [10.7.4] - 2026-07-02

Deep-consistency sweep: the v10.6.0 Fable restore and the v10.7.0 adapter removal are now reflected on every surface, and the test suite is green again end to end.

### Fixed

- **`cost-table.json` regains the `fable` price row** (`claude-fable-5`, $10/$50 per MTok, cache-read $1) — the v10.6.0 restore had left every architect/Reviewer-1/triage dispatch unpriced ("USD unavailable") in the cost ledger. `cost-budget-check.mjs` and `prefs.schema.json` `costBudget.pricingModel` now default to `fable` (conservative upper bound); `triage-output` / `reviewer-output` schemas and the telemetry enums (`log-format`, `progress-contract`) accept `fable` (+ `gpt` as reviewer source).
- **`uninstall` legacy adapter cleanup actually works again.** The four `--cursor` / `--copilot-chat` / `--antigravity` / `--codex` blocks imported adapter modules deleted in v10.7.0, so every run silently no-opped. They now perform inline cleanup (multi-agent-* files + managed-marker blocks) with user files untouched; verified by a round-trip test.
- **`smoke-install-layout` fixture regenerated** (167 scripts; it was stale at 174 since the v10.7.0 deletions and failed `npm test` + CI). `smoke-readme-counts.sh` deleted — it asserted a README counts table that the v10.7.1 concise-README rewrite intentionally removed. The 10.7.1 changelog entry no longer spells the corporate Jira key it genericized, so the tarball leak gate passes.
- **`multi-agent-sync` skill command inventory reconciled** — it still listed the v7-era 26 commands; now the canonical 35 (incl. `finish`), matching `sync.md` and `cross-cli-contract.md`.

### Changed

- **Fable-restore consistency sweep.** Every doc that still said "Opus triage" / "Opus top tier" now says Fable where Claude Code dispatches Fable (Phase 1/2 headers, phase-4-review tables + metrics, help EN+TR, review command + skill, orchestration SKILL, features/architecture/performance docs, examples, CLAUDE.md template). The Copilot CLI reviewer trio stays explicitly pinned at GPT-5.4 + Opus + Sonnet (Fable 5 is not offered there) — now stated in `model-fallback.md`, which also drops its stale version-tagged title. The stray `claude-opus-4.6` / `claude-sonnet-4.6` ids are normalized to `claude-opus-4-8` / `claude-sonnet-4-6`.
- **Adapter-era residue swept**: `index.js` help no longer advertises removed install flags; `package.json` description/keywords say Claude Code + Copilot CLI only; `.gitignore` drops the dead `sync-adapters.mjs` note; `tracker-contract.md` drops the Cursor detection branch; ADR-0007 is marked Superseded and `GENERICITY-REVIEW.md` carries a pre-v10.7.0 banner; ROADMAP's "Current Release" jumps from the stale 10.1.0 (which still claimed Fable retired) to 10.7.x and dead future items are resolved.
- **Plugin-model docs**: the Copilot instructions template and `scripts/README` no longer reference the deleted `stack-swap.sh` session-start mechanic; `features.md` "Stack Swap" section rewritten around marketplace-plugin enablement; `architecture.md` counts refreshed (39 commands, 8 personas, 17 schemas, 100+ smokes, 41 figma + 38 core + 143 external skills); `help` stack args include `mobile`.

## [10.7.3] - 2026-07-02

### Changed

- **Zero external-tool residue.** Genericized every remaining editor/tool citation
  in the pipeline's own files (Windsurf Cascade / Cursor Plan Mode / Cursor Bugbot /
  Cline / Devin) across schemas, lib comments, refs, and the task-clarifier persona —
  feature behavior is unchanged, only the third-party naming is dropped. (Vendored
  `shared/external` knowledge and the `~/.codex` prune path in `update` are unaffected.)
- **README** gained a **Tokens & integrations** table (keychain-mapping model + the
  services the pipeline talks to: Jira, GitHub, Bitbucket, Confluence, Figma, Fortify,
  Firebase, Jenkins, npm) and an explicit **Platform support** section (macOS / Linux /
  Windows, keychain backends).

## [10.7.2] - 2026-07-02

### Changed

- Swept the last adapter residue from the phase-tracker docs: the "Visual channel"
  CLI list is now **Copilot CLI / plain shell** (Cursor / Windsurf / Cline dropped)
  across every mode command + `tracker-contract` + `phases`. Design-lineage
  citations ("Pattern source: Windsurf Cascade / Cursor Plan Mode") are kept —
  they credit the plan-todos pattern's inspiration, not a supported runtime.

## [10.7.1] - 2026-07-02

Sweep residual adapter references left after the v10.7.0 removal.

### Changed

- Cleaned lingering Cursor / Antigravity / Codex / Copilot Chat mentions from
  `picker-contract.md` (now Claude Code + Copilot CLI only), `delete` command +
  skill, `shared/README`, `ask-choice.sh`, and the skills index. Deleted the
  now-broken `smoke-delete-flow.sh` (it installed adapter targets that no longer
  exist). `uninstall.mjs` keeps its legacy adapter-file cleanup for users who
  still have old adapter installs.

## [10.7.0] - 2026-07-02

The pipeline targets **Claude Code + Copilot CLI only**. The Cursor / Antigravity / VS Code Copilot Chat / OpenAI Codex CLI adapters are removed.

### Removed

- **All non-native adapters deleted.** `pipeline/adapters/` (Cursor, Antigravity,
  Copilot Chat, Codex + orchestration variants), `pipeline/scripts/sync-adapters.mjs`,
  `install/_adapters.mjs`, and the `smoke-adapters.sh` / `smoke-sync-adapters.sh` /
  `smoke-shared-runtime.sh` smokes are gone.
- Installer flags `--cursor` / `--copilot-chat` / `--antigravity` / `--codex` /
  `--all-tools` removed; `install` now dispatches only Claude Code + Copilot CLI.
- `multi-agent:sync` drops the Codex (Step 2a) and per-project adapter (Step 2b)
  steps; the `--no-adapters` flag is retired. README, `phase-4-review`, and
  `setup` adapter documentation removed.

### Changed

- **`multi-agent:update` prunes the retired global Codex adapter prompt**
  (`~/.codex/prompts/multi-agent.md`). Per-project adapter files (`.cursor/`,
  `.agent/`, `.github/copilot-instructions.md`) are left untouched — they live in
  user repos; the `uninstall` command can still remove them selectively.

## [10.6.0] - 2026-07-02

Fable 5 restored as the top model tier; `stack-swap` fully removed; setup gains a default stack step.

### Added

- **`multi-agent:setup` Step 9 — default stack plugin enablement.** First-run
  setup detects the project stack from markers (`.xcodeproj`/`Package.swift` → iOS,
  `build.gradle` → Android, `package.json`+react → Frontend, `requirements.txt`/
  `pyproject.toml` → Backend) and enables the matching marketplace plugin plus the
  always-on `ai-common-toolkit`. **No clear marker → default iOS.** So a
  fresh install works out of the box and a repo at any org gets its correct stack.

### Changed

- **Fable 5 is the top model tier again.** Architect (`ios/android/backend-architect`)
  and Reviewer-1 (`code-reviewer`) personas declare `preferredModel: fable`. The tier
  ladder is now `fable → opus → sonnet → haiku`: **if Fable 5 is unavailable, the first
  fallback is Opus 4.8**, applied per-dispatch with no file edits. Security personas keep
  opus. Reverses the 2026-06 "fable retired" state now that `claude-fable-5` is available.

### Removed

- **`stack-swap.sh` + `smoke-stack-swap.sh` deleted.** The SessionStart skill-dir-swap
  mechanic is gone entirely; stack selection is marketplace-plugin enablement
  (`/multi-agent:stack`). No dead code or legacy hook remains.

### Fixed

- Genericized a leaked corporate Jira project key (replaced with `PROJ`/`{JIRA_KEY}`)
  in the `finish` command + skill so the personal-data gate passes.

## [10.5.0] - 2026-07-02

Stack skills move from the local `stack-swap` mechanic to versioned marketplace plugins.

### Added

- **`build-stack-plugins.mjs`** — rebuilds each stack plugin's `knowledge/`
  layer in the `{owner}/multi-agent-plugins` marketplace from the pipeline's
  authoring source (`pipeline/skills/shared/external/`), regenerates each
  `plugin.json` `skills[]`, and **bumps the patch version of any plugin whose
  skill set changed**. Idempotent (`--dry-run` supported); a no-op run bumps
  nothing. Cross-stack skills (accessibility audit, humanizer, Firebase) route
  to `ai-common-toolkit`; Apple/Xcode-only skills stay in the iOS
  plugin. This is the version-based-management backbone: the pipeline is the
  single authoring source, the marketplace is a derived, versioned artifact.
- **`multi-agent:sync` Step 3c (PLUGINS)** — runs the generator, then commits +
  pushes the plugins repo when a plugin version was bumped.

### Changed

- **`/multi-agent:stack`** — selecting a stack no longer moves skill directories
  via `stack-swap.sh`. It now enables the matching marketplace plugin(s) (stack
  toolkit + `ai-common-toolkit`) in the current repo's
  `.claude/settings.json` `enabledPlugins`, disabling the stack toolkits that
  don't apply. Declarative, per-repo, versioned. No SessionStart hook.
- **`/multi-agent:update`** — added a step that refreshes the plugin marketplace
  (`claude marketplace update multi-agent-plugins`) so the latest published
  plugin versions are picked up alongside the pipeline update.

### Deprecated

- **`stack-swap.sh`** — no longer wired to any SessionStart hook. Retained for
  manual/offline use only; stack selection is now plugin enablement.

## [10.4.0] - 2026-07-02

New `finish` command: run the pipeline tail over work you already did locally.

### Added

- **`/multi-agent:finish`** (`multi-agent-finish` on Copilot CLI). Takes the
  current branch's local work (diff vs base + working tree) and runs the
  pipeline **tail** in one command — Phase 4 Review → Phase 5 Build+Test success
  gate → Phase 6 Commit/push/PR → Phase 7 technical analysis + a Jira comment
  with test scenarios — **without re-developing** (phases 1-3 skipped). Works
  after `dev-local`/`local` (which skip Review + Test) or after hand-coding /
  hand-testing. Interactive and `autopilot` modes. Reuses the existing
  `phase-4-review` / `phase-5-test` / `phase-6-commit` / `phase-7-report` /
  `channels` contracts; distinct from `resume` (tracked task) and `review`/
  `channels` (single-step). If the repo has no tests it reports "no tests
  present" — never fabricates results.
- Command inventory 34 → 35 (`cross-cli-contract` §1, README counts, `help.md`
  EN+TR, `sync.md` synced-command list); cross-refs added in `dev-local`/`resume`.

## [10.3.0] - 2026-07-02

Android (Jetpack Compose) parity for the interaction skills shipped in 10.2.0,
plus two fixes from a review pass. The cross-cutting integration adapters are
now genuinely dual-platform, honoring the figma-common "dispatched by both iOS
and Android" contract.

### Added

- **Android (Jetpack Compose) sections** in `figma-navigation`, `figma-overlays`,
  and `figma-bottom-sheets`. Same emit-intent / caller-owns rules and the same
  `figma-config` `ui.*` hooks; native-first per platform (Navigation Compose;
  Snackbar / AlertDialog / Dialog + state-driven loading; `ModalBottomSheet` +
  `rememberModalBottomSheetState`). The Android orchestrator uses the Compose
  section, the iOS orchestrator the SwiftUI section. `figma-evolve-component`
  wording made platform-neutral (SwiftUI or Compose).

### Fixed

- **`animated-gradient-border` reference snippet** now animates the
  `AngularGradient(angle:)` on a static shape instead of `rotationEffect` on the
  stroked rounded-rect (which spun the whole shape — glitchy corners).
- **Genericized the sheet corner-radius example token** (`.Radius.n12` →
  `.CornerRadius.radius12`) so no app-specific token leaks into the generic
  pipeline.

## [10.2.0] - 2026-07-02

Generic SwiftUI interaction coverage for the figma-to-swiftui pipeline. Three
new cross-cutting integration skills (navigation, overlays, bottom sheets) plus
a reconcile-and-extend workflow, all native-SwiftUI-first with an optional
per-project `ui.*` config hook so the same capabilities work on any SwiftUI
codebase  -  no app-specific coupling. Phase 3D dev detection and Phase 4 review
both consume them.

### Added

- **`figma-navigation` / `figma-overlays` / `figma-bottom-sheets` skills**
  (`pipeline/skills/figma-common/`). Cross-cutting integration adapters, same
  shape as `figma-form-integration` / `figma-price-integration`: a component
  emits a typed intent / takes a caller-owned binding and never routes, presents
  an app-level overlay, or owns the sheet surface. Native SwiftUI by default
  (`NavigationStack`/`navigationDestination`, `.alert`/`.confirmationDialog`/
  `.sheet(item:)` + ref-counted loading, `.sheet`+`presentationDetents`); a
  project's own system is used only when `figma-config` declares
  `ui.navigationSystem` / `ui.overlaySystem` / `ui.sheetSystem`.
- **`figma-evolve-component` workflow.** Reconcile an already-implemented
  component against current Figma (drift-heal) + additively extend it for a
  need, behind a mandatory human gate; distinct from `figma-to-swiftui`
  (build new), `figma-mend` (rebuild), and `figma-fix` (review bug).
- **`animated-gradient-border` UI pattern** (`figma-ui-patterns/patterns/`)  -
  self-contained native-SwiftUI recipe (angular-gradient stroke + Reduce-Motion
  gate), no external dependency.
- **`ui` block in `figma-project-config.schema.json`**  -  optional
  `navigationSystem` / `overlaySystem` / `sheetSystem` (`mode: native | custom`
  + type names). Absent → native SwiftUI.

### Changed

- **Phase 3D pattern detection (§1.5.4).** Detects navigation / overlay /
  bottom-sheet affordances and dispatches the matching integration skill
  (gate-driven, per `orchestrator-discipline` Rule 4).
- **Phase 4B view (§4.7.4)** references the new skills; **`figma-to-swiftui`
  accessibility reference** enriched with the VoiceOver-minimalism decision tree
  (annotate only what a vision-impaired user needs; state-necessity tree;
  anti-patterns).
- **Phase 4 review** (`phase-4-review.md`): Step 1.5 gains iOS interaction +
  accessibility-minimalism checks (self-routing/self-presenting components,
  `AnyView` through an overlay center, un-ref-counted loading, magic-number
  detents, a11y over-annotation), and the new skills are injected as reviewer
  reference context for SwiftUI UI diffs. Generic across SwiftUI projects.
- **`figma-to-component` SKILL.md** documents the complementary integration
  skills; **`orchestrator-discipline` Rule 4** and **`cross-cli-contract`**
  updated (figma-common 27 → 31, total figma skills 37 → 41).

## [10.1.0] - 2026-06-25

Fable 5 retired (no longer available): the pipeline's top intelligence tier is
now Opus. Plus per-task cost visibility on by default, four Phase 1/4 token
economy measures, and a stability pass that bounds dispatch timeouts, heals
stale worktrees, and validates state before resume.

### Changed

- **Fable 5 -> Opus across all routing.** The five heavy agent personas
  (`code-reviewer`, `security-auditor`, `ios/android/backend-architect`) now
  declare `preferredModel: opus` instead of `fable`; Phase 1 Analysis, Phase 2
  Planning, Phase 4 Reviewer 1 + triage, and the `--dev` dev model all run on
  Opus. This also resolves the prior CLAUDE.md-vs-phase-doc inconsistency
  (CLAUDE.md already documented Opus for these stages).
- **Model fallback ladder is now `opus -> sonnet`** (was `fable -> opus ->
  sonnet`). `modelFallback.fallbackModel` default is `sonnet`. The
  `premiumTierUntil` date gate is kept as a generic mechanism for any future
  plan-window-limited premium tier. `model-fallback.md` bumped to v10.1.0 and
  `smoke-model-fallback.sh` updated to assert the new ladder + Opus personas.
- **`costBudget` defaults on in warn mode.** The proactive per-task cost ceiling
  (`cost-budget-check.mjs`) now ships enabled (`onExceed: "warn"`, `maxUsd: 5`,
  priced at the conservative opus rate) so runaway spend is visible live. Warn
  never halts; set `onExceed: "halt"` to make the ceiling blocking, or
  `enabled: false` to opt out.

### Added

- **Phase 4 context economy (Step 1.9).** Reviewer and triage prompts now build
  a byte-identical shared prefix (full diff + analysis summary + plan) so, when
  the host supports prompt caching, reviewer 2/3 and triage read it at the
  discounted cache-read rate instead of re-billing the diff as fresh input.
- **Single-repo diff cap.** Diffs exceeding the Phase 4 token allowance are
  truncated with a `file://...review-diff.txt` pointer and a
  `review.diff_truncated` metric - the single-repo equivalent of the existing
  multi-repo 80% cap.
- **Phase 1 light Explore tier.** Small `bugfix`/`chore` tasks (single named
  file or a referenced crash/stack frame) scan only the named area + direct
  callers instead of a full-repo "very thorough" pass.
- **Triage prior-art injection cap.** Merged prior-art is capped at the 8
  highest-similarity hits so a many-finding review does not inflate the triage
  prompt.

### Fixed

- **Parallel dispatch timeouts.** Phase 1 Explore and Phase 4 reviewer dispatches
  are now bounded by a wall-clock budget (`EXPLORE_TIMEOUT_SECONDS` /
  `REVIEWER_TIMEOUT_SECONDS`, default 180). A stalled or dead agent is dropped and
  the phase proceeds with the dispatches that returned, instead of hanging
  indefinitely waiting on the slowest one.
- **Worktree stale-lock heal.** Phase 0 runs `worktree prune` + `unlock` before
  every `worktree add`, ending the `already exists` wedge a run killed mid-add
  left behind.
- **Resume safety validation.** New `validate-state.mjs` checks `agent-state.json`
  is safe to re-enter (parseable + `currentPhase` in range + well-formed
  `phases`, tolerant of legacy shapes) before resume picks a phase, instead of
  silently re-entering on stale state.
- **Model fallback floor.** The ladder now walks `opus -> sonnet -> haiku`
  (`floorModel`, absent defaults to `haiku`) so two simultaneously-unavailable
  tiers degrade instead of hard-halting the phase.
- **Visible halts + lock tuning.** Hard halts surface to stderr + tracker even in
  autopilot (no more silent stops); `write-state` lock acquire window is
  env-tunable (default 15s) with the stale window cut 60s -> 30s; external curls
  gain `--connect-timeout 5`; `phase-tracker` gains a concurrent-safe
  `MULTI_AGENT_STRICT_TASK_ID` mode; the retired `fable` price entry is removed.

---

## [10.0.6] - 2026-06-23

Release-plumbing patch so the stable line reaches npmjs. No behavior change to
the slash-command surface.

### Fixed

- **Regenerated the stale `install-layout.tsv` fixture.** The v10.0.5 line added
  `smoke-metrics-cache-ratio.sh` without refreshing the layout fingerprint, so
  `smoke-install-layout.sh` failed (install writes 173 scripts per target, the
  fixture still claimed 172). The fixture now records 173 for `.claude/scripts`
  and `.copilot/scripts`; this was the only gate blocking a clean release.

---

## [10.0.5] - 2026-06-19

Telemetry backbone for prompt-cache reuse, plus a self-deriving PR review
iteration counter. Ships with a consistency fix so the two consumers of the
token ledger agree on what an input token is.

### Added

- **Prompt-cache reuse ratio in metrics.** `aggregate-metrics.mjs` now sums
  `tokens_cached` per model and reports `cache_ratio = cached / (in + cached)`
  - the share of input tokens served from the host prompt cache - per model and
  overall, in all three output modes (json / markdown / text). This is the
  single number that says whether cache-friendly prompt structuring is paying
  off. Backward-compatible: a phase that omits `tokens_cached` reads as 0%.
- **PR review iteration counter derived from the PR itself.** On a `needs_work`
  post, the iteration number is re-derived as `max("iteration #N" already on the
  PR) + 1` instead of trusting agent-state. Standalone `/multi-agent:review`
  runs use a fresh task id each time, so the PR comments are the only reliable
  cross-run source of truth.

### Fixed

- **Token-count convention mismatch between the two ledger consumers.**
  `render-agent-log-cost.sh` treated `tokens_in` as cache-inclusive and
  subtracted the cached count (`fresh = in - min(cached, in)`), while the new
  `aggregate-metrics.mjs` treated `tokens_in` as cache-exclusive (`total =
  in + cached`). Fed real data with high cache reuse (`cached > in`), the
  renderer collapsed `fresh` to 0 and underpriced the row. Standardized on the
  **cache-exclusive** convention that matches the host usage report
  (`input_tokens` and `cache_read_input_tokens` are disjoint): the renderer no
  longer clamps or subtracts, and `log-format.md` now documents the disjoint
  contract that both consumers rely on.
- **PR iteration scan could be inflated by a human comment.** The
  `max("iteration #N")` scan now matches only inside the Multi-Agent Review
  footer, so a reviewer who writes "iteration #99" in prose cannot jump the
  counter.
- **README smoke-suite count.** Bumped 107 -> 108 to match the actual suite
  count after the cache-ratio smoke landed.

## [10.0.4] - 2026-06-12

Final two clean-runner failures (ubuntu matrix) - full 5-job matrix green is
the target state.

### Fixed

- **macOS-only project-slug glob.** `plan-todos.sh`, `post-pr-review.sh`, and
  `update-issue-progress.sh` resolved task state by globbing
  `~/.claude/projects/-Users-*` - Linux slugs start with `-home-`, so state
  resolution silently found nothing there. All three now glob `projects/*`.
- **`smoke-bitbucket-contract.sh` stubbed only macOS Keychain.** The smoke
  PATH-shadows `security`, but on Linux `credential-store.sh get` routes
  through `secret-tool`, so the mock token stayed empty and every parse
  assertion came back blank. A `secret-tool` stub now covers the Linux path.

## [10.0.3] - 2026-06-12

Cross-platform portability fixes - the v10.0.2 SMOKE_NO_BAIL run surfaced
every remaining clean-runner failure; all nine are fixed. Linux behavior was
reproduced locally by putting GNU coreutils' gnubin ahead of PATH.

### Fixed

- **`stat -f %m` is not a safe BSD probe.** GNU stat also accepts `-f` (as
  "filesystem status") and SUCCEEDS, printing a mount point - so the
  `|| stat -c %Y` fallback never ran and mtimes became `/` on Linux, breaking
  `search-logs.sh` (since-filter, scoring, JSON/TSV output) and
  `repo-cache.sh` TTLs. Probe order flipped to GNU-first (`stat -c` first;
  BSD rejects `-c`, so the fallback chain is safe both ways). `date -r
  <epoch>` display calls gained a GNU `date -d @` fallback.
- **Hardcoded maintainer layout `$HOME/multi-agent-pipeline` removed** from
  `smoke-schema-validation.sh` (preferences-template path) and
  `smoke-pat-audit.sh` (.gitignore audit-log check) - both now derive the
  repo root from the script location; the template check skips gracefully on
  installed-tree execution (which also un-cascades `smoke-install-leak-gate`
  step 5).
- **`smoke-lib-scripts.sh` fixture bare repos init with `-b main`** - on
  runners without `init.defaultBranch=main` the bare HEAD pointed at master
  and `prepare` ended detached instead of on the task branch.
- **`vercel-deploy.sh` rejects `--token` argv BEFORE the CLI-presence
  check** - the refusal is a security guarantee and must not depend on the
  vercel CLI being installed.
- **`smoke-install-layout.sh` fingerprint pinned to `LC_ALL=C sort`** -
  runner locales order dot-prefixed paths differently, producing
  same-content/different-order fingerprint mismatches on macOS runners;
  fixture regenerated under the pinned locale.

## [10.0.2] - 2026-06-12

Second round of cross-platform CI repairs (first real clean-runner exercise
of the full smoke suite).

### Fixed

- **Windows: `New-StoredCredential` not recognized.** `ps_run` preferred
  `pwsh` (PowerShell 7), but the CredentialManager binary module targets .NET
  Framework and installs into Windows PowerShell's module path - `ps_run`
  now prefers `powershell.exe` and falls back to `pwsh`.
- **Linux: headless keyring unlock.** `gnome-keyring-daemon --unlock` spawns
  SystemPrompter, which exits 1 on ubuntu-24.04 runners and never creates the
  `login` collection. The round-trip now pre-seeds an unencrypted default
  keyring file (libsecret CI recipe) and starts the daemon on it.
- **test.yml smoke suite ran against a missing install.** Install-dependent
  smokes (cross-cli-behavior etc.) verify `~/.claude` / `~/.copilot`; a clean
  runner has neither, so the full matrix could never go green off the
  maintainer machine. The workflow now runs `node install.js --all` first and
  executes the suite with `SMOKE_NO_BAIL=1` + a 600s per-suite timeout so one
  CI run surfaces every remaining environment-dependent failure.

## [10.0.1] - 2026-06-12

CI matrix repairs - the cross-platform `test.yml` jobs had been failing on
runner-environment drift since before v10.0.0; all three root causes fixed.

### Fixed

- **Windows: `credential-store.sh: line 137: USER: unbound variable`.** Git
  Bash on Windows runners does not export `USER`; under `set -u` the Windows
  `set` backend crashed before reaching Credential Manager. Now falls back
  `${USER:-${USERNAME:-claude}}` (same default the macOS backend already used).
- **Linux: `secret-tool: Object does not exist .../collection/login`.**
  Persisting `gnome-keyring-daemon` env across workflow steps via
  `$GITHUB_ENV` stopped working on ubuntu-24.04 runners (the default `login`
  collection never materializes, `GNOME_KEYRING_CONTROL` lands empty). The
  round-trip now runs inside a single `dbus-run-session` with an inline
  unlock.
- **macOS: `pip3 install --user jsonschema` -> `externally-managed-environment`.**
  macos-latest runners ship PEP 668-managed Pythons; the install step now
  falls back to `--break-system-packages`.

## [10.0.0] - 2026-06-12

Quality major: a 10-category self-audit (73.5/100) plus a competitive sweep
(GitHub / Reddit / HN / X) drove hardening across CI, tests, installer,
adapters, and the phase contracts. No slash command was renamed or removed;
the major bump covers the behavioral defaults below.

### Breaking

- **`npm test` smoke loop replaced by `pipeline/scripts/run-smokes.mjs`.**
  Per-suite timeout (default 180s, `SMOKE_TIMEOUT_SECONDS` override), substring
  filters (`node pipeline/scripts/run-smokes.mjs adapters keychain`),
  `SMOKE_NO_BAIL=1` run-all mode, and a pass/fail summary. A hung smoke can no
  longer block `npm test` or CI indefinitely.
- **`publishConfig` corrected to `registry.npmjs.org` (`access: public`).** It
  pointed at `npm.pkg.github.com`, so a plain `npm publish` targeted the wrong
  registry - npmjs had been stuck at 9.8.0 while 9.9.0-9.10.2 shipped only as
  git tags. New `release.yml` publishes on `v*` tags after gating on
  tag==package.json version and a matching CHANGELOG entry.
- **CHANGELOG split.** Entries v2.0.0-v8.13.0 moved to `CHANGELOG-archive.md`
  (repo-only, excluded from the npm tarball); CHANGELOG.md shrinks 310KB -> 40KB.
  `smoke-changelog-version.sh` gates top-entry==package.json drift.
- **Phase 1 / 2 / 4 structured outputs now pass deterministic validator gates.**
  `validate-analysis/planning/reviewer/triage.mjs` accept a file argument and run
  inside the phases (fails CLOSED: one self-correction rework, then halt with a
  recovery hint). Previously the validators only ran in `npm test`.
  `smoke-validator-gates.sh` (23 assertions).

### Added

- **Skill frontmatter linter** (`lint-skills.mjs`, wired into `npm test` +
  ci-lite + release.yml): every SKILL.md must carry a frontmatter block with
  `name` + `description`; `user-invocable`/`platform` value checks. First run
  found and fixed 16 figma-common SKILL.md with no frontmatter at all and 15
  external skills missing `name:`; `.skill-manifest.json` re-signed (217).
- **Installer `--dry-run`** - same code path (guards live in `install/_common.mjs`
  primitives), prints `[dry-run] would ...` per operation, writes nothing.
  Unknown `--flags` now exit 1 with the supported list instead of being
  silently ignored. Both covered by new unit tests (23 total).
- **Phase 3 Step 3.6 code-simplifier pass** - one Sonnet subagent shrinks the
  working diff (comment bloat, unrelated rewrites, dead code, over-abstraction)
  before Phase 4; safe edits only, build+tests re-verified, wholesale revert if
  anything breaks, tokens in the cost ledger.
- **Phase 4 lesson memory loop** - each fix/rework round appends a one-line
  root-cause lesson per resolved blocking/important finding to the existing
  learnings ledger.
- **Phase 2 cross-artifact consistency gate** - plan is checked against the
  analysis doc (requirement -> task mapping, no orphan tasks, open questions
  carried) before approval. `smoke-community-gates.sh` (19 assertions) covers
  all three. Sourced from the June 2026 competitive sweep - see ROADMAP for
  the adopted/declined ledger.
- **CI hardening**: lint is blocking on ci-lite (was warn-only),
  `npm audit --audit-level=moderate` gates both workflows, shellcheck
  (severity=error) gates all pipeline shell scripts, skill lint step added.
- **Docs drift gates**: `smoke-md-links.sh` + `check-md-links.mjs` (internal
  markdown link checker, code spans excluded), `smoke-changelog-version.sh`.

### Changed

- **Adapter family deduplicated.** Shared install/uninstall flow extracted into
  `pipeline/adapters/_base.mjs` (skill collection sources, managed-block
  removal, rules walking, digest rendering); cursor / copilot-chat /
  antigravity / codex + both orchestration modules thinned by ~490 lines with
  byte-identical installed output (`smoke-adapters.sh` 33 assertions green).
- **README restructured**: Quick Start now precedes "What's new"; the release
  wall condensed to the latest three entries (CHANGELOG remains canonical);
  `--dry-run` documented; smoke count 103 -> 107.
- **ROADMAP**: new "v10.x candidates" table from the competitive sweep
  (autonomous loop mode, mid-run steering, watchdog + review debt,
  constitution artifact, evidence receipts, marketplace distribution,
  cross-session backlog, pre-flight quota check) plus a declined list.
- Help text aligned to "8-phase" (index.js said 9-phase; phases are 0-7).

### Fixed

- `brace-expansion` moderate DoS advisory (GHSA-jxxr-4gwj-5jf2) via
  `npm audit fix` - 0 vulnerabilities.
- `smoke-keychain.sh` SC1087 unbraced variable in a grep class (shellcheck).
- `eslint.config.js` missing `Response` global (5 no-undef errors in
  install-telemetry tests under blocking lint).
- Stale `fixtures/install-layout.tsv` (claimed 86 command files; 87 on disk
  since v9.9.0) regenerated.

## [9.10.2] - 2026-06-11

Live per-phase token narration (user-reported gap: tiles showed duration only).

- **tracker-contract section 5 now MANDATES a completion narration line.** The
  native TaskList widget on Claude Code shows name/status/duration - it has no
  per-phase token field, so token telemetry written via `phase-tracker.sh tokens`
  was invisible until the Phase 7 Cost Breakdown. On every phase transition to
  `completed`, the orchestrator now prints one chat line in `outputLanguage`:
  `Phase <N> <name> done - ~<in> in / ~<out> out tokens (<model>, ~$<usd>)`,
  priced from `cost-table.json` (floor-to-cents, same math as the cost renderer).
  Zero-token phases print `(no LLM calls)`; counts are content-size estimates
  prefixed with `~` (the orchestrator does not receive its own usage metering) -
  the state file + Phase 7 breakdown stay authoritative.
- `smoke-tracker-tokens-invocation.sh` extended to assert the contract carries
  the narration mandate.

## [9.10.1] - 2026-06-11

Model fallback contract for fable personas.

- **New `refs/features/model-fallback.md`.** Fable access can be plan-window-limited
  or quota-limited; the contract defines a deterministic tier ladder
  (`fable -> opus -> sonnet`) applied via the existing `PHASE_MODEL_OVERRIDE` /
  `CLAUDE_CODE_SUBAGENT_MODEL` per-dispatch override - persona files are never
  edited at runtime. Three triggers, checked in order: (1) **date gate** -
  `prefs.global.modelFallback.premiumTierUntil` (ISO date); past it, fable
  personas dispatch on `fallbackModel` with a one-line WARN (autopilot never
  asks); (2) **dispatch error** - a failed fable dispatch retries once on
  `fallbackModel` instead of aborting the phase; (3) **budget ceiling** - after
  a `cost-budget-check.mjs` exit-11 pause, fable downgrades for the rest of the
  run. Every fallback logs a `model_fallback` metric + agent-log line.
- **Prefs knob** `global.modelFallback` added to `preferences-template.json`
  (`enabled: true`, `premiumTierUntil: null`, `fallbackModel: "opus"`,
  `onDispatchError: true`). Wired at Phase 0 Step 0 (date gate) and Phase 4
  (per-dispatch); `phase-4-review.md` model-override paragraph also fixed
  (still said `preferredModel: opus` after v9.10.0).
- **New gate `smoke-model-fallback.sh`** (10 assertions): contract doc + three
  triggers + template defaults + phase wiring + fable personas intact.
  Smoke suites 102 -> 103.

## [9.10.0] - 2026-06-11

Claude Fable 5 adoption on the Claude Code side.

- **Heavy personas route to Fable.** `code-reviewer`, `security-auditor`, `ios-architect`,
  `android-architect`, and `backend-architect` move from `model: opus` to `model: fable`
  (`claude-fable-5` - the tier above Opus 4.8). `explorer` / `dev-critic` stay on sonnet,
  `task-clarifier` on haiku. `smoke-agent-model-routing.sh` enum extended to
  `fable|opus|sonnet|haiku`; modelRationale lines refreshed.
- **Model-assignment labels updated** across the Claude-side docs: Phase 1 Analysis and
  Phase 2 Planning headings, Phase 4 reviewer pair (Claude Code now dispatches
  Fable + Sonnet; Copilot CLI keeps GPT-5.4 + Opus + Sonnet), Phase 4 triage
  (Opus triage -> Fable triage), and the `--dev` fast-mode dev model in
  `dev.md` / `dev-autopilot.md` / `dev-local.md` / `dev-local-autopilot.md` / `help.md`
  (EN + TR). Adapter-platform picker labels (VS Code / Antigravity / Codex) untouched.
- **Cost ledger: `fable` pricing added + stale `opus` entry fixed.** `cost-table.json`
  gains `fable` ($10 in / $50 out / $1 cache-read per MTok, `claude-fable-5`) and the
  `opus` entry is corrected from the 4.7-era $15/$75 to the actual Opus 4.8 pricing
  ($5/$25/$0.50, `claude-opus-4-8`) - every prior cost report priced opus phases 3x too
  high. Cost smokes re-baselined to the corrected math (`smoke-agent-log-cost`,
  `smoke-cost-budget`).

## [9.9.0] - 2026-06-10

Analysis open-question resolver + repo hygiene hardening.

- **New command `/multi-agent:analysis-resolve`** (+ dash-form `multi-agent-analysis-resolve`
  skill). Walks the Section 20 Risks and Open Questions of an analysis v3 document one
  row at a time: up to 3 source-labeled candidates per row (`From evidence` / `From repo`
  / `AI reasoned`), the pick merges into the target body section, the doc saves after
  every answer. Stop tokens (`stop` / `pause` / `dur` / `kes`), immediate follow-up
  insertion, optional verbatim-match sibling propagation across per-platform files,
  changelog bump on finalize. Inherits the analysis Locked decisions: citation
  discipline, forward-looking voice (repo answers demote to `> Legacy reference:`
  blockquotes), humanizer punctuation policy, no Figma access (Locked 30 - design-gap
  rows get only Defer + a re-run recommendation), no auto-commit. Command inventory
  33 -> 34; `/multi-agent:analysis` Phase 5 report now suggests the resolver when
  Section 20 has open rows. Pattern ported from a private stack toolkit's resolver skills.
- **Dead references removed.** `analysis.md` Reusable refs no longer points at a
  non-existent `fetch-wiki.sh` (the wiki fetch chain is inline: clone -> gh api ->
  WebFetch); `refs/features/external-context-injection.md` figma row routed to the real
  3-tier chain instead of a non-existent `fetch-figma.sh`; `channels.md` board adapter
  no longer links a missing `refs/channels/board.md` (behaviors documented inline).
- **Bitbucket credential hardening.** `post-pr-review.sh` Basic auth moved off
  `curl -u user:token` argv (visible to `ps` / process audit) onto a curl config fed
  through process substitution (`-K <(...)`).
- **First offline test coverage for the two biggest untested libs.**
  `smoke-extract-conventions.sh` (8 assertions: throwaway iOS fixture repo, 12-field
  output shape, confidence enum, evidenceFiles cap, error paths) plus a new
  `conventions-output.schema.json` contract for the Phase 1c extractor output.
  `smoke-md2confluence.sh` (9 assertions: front-matter parsing, TR punctuation gate
  with diacritic preservation, storage-XML rendering for headings / tables / code).
  Smoke suites 100 -> 102, JSON schemas 15 -> 16.
- **Stale-version cleanup.** `multi-agent-analysis/SKILL.md` rewritten from the
  v8.8.1-era "fixed 7-section" description to the v3 contract (23-section Full /
  7-section Lite, Pass A/B render, Phase 3.5 output picker); `help.md` analysis line
  updated in both languages; README at-a-glance counts re-synced to the filesystem
  (external catalog 133 -> 143 and total SKILL.md 206 -> 217 had drifted in v9.8.0);
  ROADMAP current-release pointer moved off 8.4.1.

## [9.8.0] - 2026-06-03

Interactive credential-expiry handling and analysis improvements.

- **Token expiry now asks, never silently skips.** When a credential resolves but
  the service rejects it (401/403), the pipeline surfaces an Expired-token decision
  instead of silently dropping the source or falling to a lower tier: `Regenerate`
  (replace in place) / `Use a different token` / `Skip and continue` (which halts
  only when the token is structurally required for the input). The question asks a
  choice; the replacement value still enters through the clipboard Save Flow, never
  chat, so `smoke-no-token-prompt.sh` stays green. `refs/keychain.md` Rule 1 reframed
  to distinguish "never prompt for a token value" (kept) from "may ask a decision"
  (new). `setup.md` Token Save Flow Step A split into missing vs expired branches.
- **Figma access tiers stop silently degrading.** A dead Figma MCP token (after one
  re-auth retry) now asks `Recreate the MCP token` vs `Continue with Figma PAT`
  instead of silently dropping to the PAT. A 401/403 on the Figma PAT runs the same
  Expired-token decision before falling to the user-screenshot tier. Mirrored in the
  `refs/rules.md` Tier table.
- **Picker step narration.** New breadcrumb contract in `refs/picker-contract.md`:
  every picker step prints a localized `Step <i>/<n>: <what this step decides>`
  narrator line (auto-resolved steps included), so the native picker shows, step by
  step, what it is doing. Wired into the account / repo / dev-context pickers and the
  analysis Phase 0 chain.
- **Analysis picker questions follow `outputLanguage`.** Fixed `_account-picker.md`,
  which hardcoded English prompt strings and contradicted the canonical Language
  Application matrix; its questions now render in `outputLanguage` (`label` / `header`
  stay English per the UI contract). This was the source of the half-English picker
  on Turkish runs.
- **Analysis reuses already-built components via Code Connect.** New Phase 1b.1 builds
  a Code Connect index from existing `*.figma.swift` / `*.figma.kt` bindings
  (`{fileKey, nodeId -> component, path}`). figma-to-swiftui already built and bound
  the components, so this index is the source of truth for "what already exists":
  matched design nodes emit `reuse` rows, only unmatched nodes become new components.
  Empty index falls back to the prior `uiComponents` heuristic. A scope note clarifies
  the command is a development analysis, not a screen-anatomy spec.

## [9.7.0] - 2026-06-02

OpenAI Codex CLI support - the 6th supported AI surface.

- **New `--codex` adapter (`pipeline/adapters/codex.mjs`).** Codex CLI is a
  global-config tool: its custom prompts (slash commands) and MCP servers live
  under `~/.codex/`, and it has no per-project prompt directory. So unlike the
  Cursor / Antigravity / Copilot Chat per-project adapters, the Codex adapter
  installs globally and writes Codex's real surfaces: `~/.codex/prompts/multi-agent.md`
  (the `/multi-agent` slash command), `~/.codex/AGENTS.md` (marker-wrapped skill
  index), and a marker-wrapped `[mcp_servers.dev-toolkit]` block in
  `~/.codex/config.toml` (TOML managed block that preserves the user's existing
  config). Codex has no subagent fan-out, so the Phase 4 parallel review degrades
  to a sequential adversarial two-pass (encoded in the prompt).
- **Wiring.** `--codex` flag in `install/index.mjs` + `install/_adapters.mjs`
  (and `--all-tools`), uninstall support in `pipeline/scripts/uninstall.mjs`,
  `REVIEWER_MODELS.codex` (GPT-5.5-Codex primary + Claude Opus 4.8 cross-model)
  in `_base.mjs`, and a global one-shot pass in `sync-adapters.mjs` (fires when
  `~/.codex` exists). Sync Step 2a documents the global flow.
- **Gate.** `smoke-adapters.sh` test 2d covers the Codex round-trip (prompt +
  AGENTS.md + config.toml MCP merge preserving user TOML, then uninstall
  restoring the user's config). Adapters smoke 28 -> 33 assertions.
- Platforms 5 -> 6. No new slash command (Codex is a consumer, not a command),
  so the Cross-CLI command inventory stays 33.

## [9.6.0] - 2026-05-31

Disk hygiene + correctness hardening, all gate-backed.

- **Two new maintenance commands.** `/multi-agent:garbage-collect` (`gc-tmp.sh`)
  sweeps leftover `/tmp` scratch from past runs (picker state, review diffs,
  `issue-progress-*`, `channels-*`, `context-*`, analysis drafts); `--older-than`
  spares in-flight scratch. `/multi-agent:prune-logs` (`prune-logs.sh`) deletes
  per-task log dirs under `~/.claude/logs/multi-agent` with
  `--older-than`/`--project`/`--task` filters and ALWAYS preserves the audit
  trail + metrics corpus + `.counter`. Both are dry-run by default and confirm
  before deleting (purge-style). New smokes: `smoke-gc-tmp` (13), `smoke-prune-logs`
  (15). Command inventory 31 -> 33; help catalog now points at a real log cleaner
  (the dangling `clear-logs` reference had no backing command).
- **Intent guard hardened against adversarial input.** The 26-case corpus was
  non-adversarial, so the green eval hid real misclassifications: `fix the bug?`
  read as a question (work silently skipped), `can you explain how to add X` read
  as a task (worktree for nothing), and TR mid-sentence interrogatives
  (`nasil refactor ederim`) missed. `classify-intent.sh` now lets a leading
  imperative beat a trailing `?`, keeps a polite-but-conceptual phrasing a
  question, and detects TR SOV interrogatives mid-sentence. 8 adversarial EN+TR
  cases added; eval-intent safe 100% (34/34), exact 97.1%.
- **Schema conformance test (ajv).** The hand-rolled `validate-*.mjs` validators
  were a parallel re-implementation of the `.schema.json` contracts that nothing
  cross-checked. A new ajv-backed test (43 cases) validates the eval fixtures
  against the real schemas. It immediately caught two drifts, now fixed:
  `reviewer-output` forbade the `reviewer` label the merged Phase 4 array carries;
  `agent-state` modeled `buildStatus` as a string-only enum and `reviewIterations`
  as flat counts while `phase-3-dev.md` writes `buildStatus={ok,attempts,lastError}`
  and the iteration record carries `reviewers`/`triage`/`validatorResult` (which
  `run-metrics.mjs` reads). `ajv`/`ajv-formats` added as devDependencies (runtime
  deps stay zero).
- **Correctness fixes.** `run-metrics.mjs` degrades instead of crashing on a
  partial/null `agent-state.json` (a documented use case); `eval-intent.mjs` fails
  loudly on an invalid `--min` instead of silently disabling the gate (NaN
  threshold); `audit-log.sh` coerces a non-bool `success` arg so the JSONL line
  always parses; `keychain-save.sh` prints usage instead of crashing on no args.
- **CI re-armed** on push + PR to main (ci-lite + the macOS/Windows test matrix),
  and dead code removed (`runReviewerValidator`, unused imports/vars). Counts:
  commands 31 -> 33, smoke suites 98 -> 100, SKILL.md 204 -> 206.

## [9.5.0] - 2026-05-30

Toward proven (not just designed): measure the features instead of asserting them, and ship the evidence-collection harness.

### Added
- **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.
- **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.

### Notes
- 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.
- 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.

## [9.4.0] - 2026-05-30

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.

### Added
- **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`.
- **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.
- **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`.
- **Golden-task fixture 08** (`08-ios-auth-consensus-unverified`) exercising the consensus block through the eval harness (both reviewers approve on a security surface -> `unverified`). Golden tasks 7 -> 8.
- **Reviewer-count contract checks** in `smoke-cross-cli-behavior.sh`: locks Claude=2 / Copilot=3 + the documented adapter-platform reviewer set against drift.

### Fixed
- 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.

### Notes (stated honestly)
- The adapter platforms still have no `PreToolUse` equivalent, so their gates are workflow-enforced (run as steps) rather than OS-blocked.
- Pinned adapter models depend on the user's subscription; swap them in `REVIEWER_MODELS` / the Antigravity dropdown if a model is unavailable.
- 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.

## [9.3.4] - 2026-05-30

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.

### Fixed
- **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.
- **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.
- **`learnings-ledger forget` rewrite is now atomic** (temp file + rename) so a crash or concurrent reader never sees a half-written ledger.
- **`from-triage` scope for a top-level file** is now the filename itself, not a stray `./*` glob.

### Changed
- **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.

## [9.3.3] - 2026-05-30

A 4-agent adversarial review of the v9.3.x work surfaced real defects in the features just shipped; this release fixes them.

### Fixed
- **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`)
- **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`)
- **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`)
- **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`)

### Security
- **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)

### Changed
- 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).

## [9.3.2] - 2026-05-30

### Fixed
- **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`.
- `evidence-gate.mjs` made executable to match its sibling `.mjs` scripts.

### Changed
- README "What's new" refreshed to v9.3.x (was stale at v8.8.1).

## [9.3.1] - 2026-05-30

### Fixed
- **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.

### Added
- **`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`.

## [9.3.0] - 2026-05-30

### Added
- **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`.
- **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`.
- **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`.
- **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`.

### Changed
- **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.
- **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.
- Uninstall header and package description refreshed to the current 5-platform set (Cursor / Antigravity / VS Code Copilot Chat), replacing stale Windsurf/Cline references.

### Fixed
- 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.

## [9.2.0] - 2026-05-30

### Added
- **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`).
- **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).
- **Proactive token-budget cap** (`prefs.global.costBudget` + `cost-budget-check.mjs`): prices the phase-tracker accumulators live and warns/halts before spend runs away.
- **Eval harness** expanded from 2 to 7 golden tasks across all stacks and every triage bucket.

### Changed
- **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.
- **Command/skill instruction files are English** throughout (token efficiency + model comprehension); `outputLanguage` still governs all runtime user-facing text.
- Phase 5 (User Test) now runs only in interactive worktree-backed modes (`dev`, `full`); every autopilot/local variant skips it.
- Analysis->plan contract field names aligned across schema, validator, and phase docs; the "no MCP outside analysis" gate made enforceable (telemetry recorded + checked).

### Fixed
- 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).

### Removed
- Dead `--windsurf` / `--cline` / `--continue` / `--zed` install flags (the adapters were dropped in 8.5.4; only the advertising lingered).

## [9.1.1] - 2026-05-16

**Patch** - Pipeline-wide humanizer punctuation sweep. 558 files, 5660 character replacements. Pre-existing em-dash / en-dash / ellipsis / curly quotes / section sign codepoints (U+2013, U+2014, U+2026, U+201C, U+201D, U+2018, U+2019, U+00A7) replaced with ASCII equivalents per Locked decision 7 (humanizer punctuation policy is non-negotiable).

### Changed

- 558 pipeline files swept for banned characters. Top changes by file:
  - `pipeline/skills/skills-index.md`: 214 chars
  - `pipeline/commands/multi-agent/channels.md`: 103 chars
  - `pipeline/skills/shared/core/multi-agent/SKILL.md`: 97 chars
  - `pipeline/commands/multi-agent/setup.md`: 79 chars
- Mapping: `U+2014` -> ` - `, `U+2013` -> `-`, `U+2026` -> `...`, `U+201C/D` -> `"`, `U+2018/9` -> `'`, `U+00A7` -> `section`.

### Excluded from sweep

- `CHANGELOG.md`: historical version blocks preserved as audit trail (humanizer policy applies to new content, not retroactive history rewrite).
- `pipeline/lib/md2confluence-v3.py`: PUNCT_MAP intentionally uses Unicode escapes (`—` etc) for replacement source; replacing the keys would break the converter.
- `pipeline/scripts/smoke-personal-data.sh`: pattern literals contain U+00A7 references for matching purposes.

### Verified

- Punctuation gate: 0 files with banned characters remaining (was 558 files, 5389 lines).
- Smoke gate `smoke-personal-data.sh`: 22 patterns clean, 0 leaks.
- `bash -n` clean on sampled scripts.
- `python3 -m py_compile` clean on `md2confluence-v3.py`.

### Migration notes

- No behavior change. Pure cosmetic / typographic consistency.
- Downstream consumers reading these docs see ASCII-only prose now. Code blocks, URLs, and front-matter YAML were already ASCII.

---

## [9.1.0] - 2026-05-16

**Minor** - Consolidation release. Locked decision category index (5 groups), Lite mode scoring relaxed from AND to 2/3, legacy v2 analysis soft-skip flag (`MULTI_AGENT_LEGACY_V2_ANALYSIS=allow`), dead helper cleanup, 31-command contract drift fix, broader PII smoke gate, and 6 measurable performance improvements across the hot path.

### Added

- `pipeline/commands/multi-agent/analysis.md` "Locked decisions Index by category": 30 decisions grouped into Governance, Citation and Evidence, Output Format and Structure, Design Source and Pipeline Architecture, UI/Variant/Test Coverage. Browse-friendly navigation; canonical numbering unchanged.
- Lite mode 2/3 scoring (Locked 25): each of three signals (spec lines < 100, figma frames <= 1, repo direct-match >= 8) scores 1 point; `liteModeAuto = (score >= 2)`. Previous AND-threshold (v8.12.0..v9.0.x) forced small features into Full mode when one signal was marginal. Explicit `--lite` / `--full` flags still win over scoring.
- Legacy v2 analysis soft-skip (Locked 30): Phase 2 and Phase 3 Pre-flight degrade `template_version: v2` analysis docs to warning mode when `prefs.global.legacyV2AnalysisAllowed == true` OR env `MULTI_AGENT_LEGACY_V2_ANALYSIS=allow`. Standards binding enforcement and Pass B footnote check drop to warning level; v3-specific section coverage checks skip. Escape hatch removed in v9.2.0.
- `pipeline/scripts/smoke-personal-data.sh`: 7 new patterns covering airline-specific brand literals, page ID ranges, and Keychain alias forms. Now 22 patterns total. Exclude list extended for `confluence-page-ids.example.json` and CHANGELOG historical references.
- `pipeline/skills/figma-ios/figma-to-component/scripts/confluence-page-ids.example.json`: placeholder schema reference file (5 generic entries) replacing the 91-entry corporate page ID inventory which moved to project-local storage.

### Changed

- 9 HIGH severity PII redactions across pipeline + Copilot skills (airline brand names, corporate page IDs, Keychain alias literals, accountId examples). Pipeline source and Copilot mirror aligned.
- `pipeline/commands/multi-agent/refs/cross-cli-contract.md` Section 1: "31 commands" enumeration drift fix - added `delete`, `diff-explain`, `language` to the visible list (they existed as files but were missing from the doc enumeration).
- 5 dead helper functions removed (~70 LOC total): `top_suffix()` from `extract-conventions.sh`, `auth_attempt()` from `fetch-fortify.sh`, `now_ts()` and `download_one()` from `figma-screenshot.sh`, `jq_get()` from `multi-repo-pipeline.sh`. All were defined but never called (inline duplication at the call site).

### Performance

Six measurable improvements on the hot path:

| Fix | Before | After | Speedup |
|---|---|---|---|
| `smoke-personal-data.sh` pattern alternation (single grep vs 22 invocations) | 3.348s | 1.545s | 2.2x |
| `smoke-no-token-prompt.sh` multi-`-e` (single grep vs 7x7=49 invocations) | 0.485s | 0.014s | 35x |
| `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 |
| `issue-fetcher.sh` python3 batch (single inline vs 7 separate calls per fetch) | 0.806s / 3 iter | 0.136s / 3 iter | 5.9x |
| `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 |
| `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 |

### Verified

- `bash -n` clean on 9 modified shell scripts.
- `python3 -m py_compile` clean on `md2confluence-v3.py`.
- Smoke gate `smoke-personal-data.sh`: 22 patterns clean, 0 leaks.
- Smoke gate `smoke-no-mcp-in-dev-phases.sh`: behavior unchanged, mock fixtures pass.
- Pre/post smoke output byte-identical (behavior parity preserved despite faster path).

### Migration notes

- Existing v2 analyses still abort in default mode (v9.0.x behavior unchanged). Set `prefs.global.legacyV2AnalysisAllowed = true` or env `MULTI_AGENT_LEGACY_V2_ANALYSIS=allow` to opt into the soft-skip. Plan to regenerate v3 docs before v9.2.0.
- Lite mode now activates on 2 of 3 signals; existing analyses that previously rendered Full may now render Lite. To force Full, pass `--full` to `/multi-agent:analysis`.
- `cross-cli-contract.md` "31 commands" enumeration now matches reality. Smoke gate behavior unchanged - this was a documentation drift only.

---

## [9.0.0] - 2026-05-16

**Major** - Pipeline-wide design source contract: analysis document is the sole design source for Phase 2 through Phase 7. Figma MCP / REST calls are forbidden outside `/multi-agent:analysis` Phase 1. Phase 2 Planning and Phase 3 Dev gain BLOCKING pre-flight checks that abort the run when `analysis/<feature>-<platform>.md` is missing or below template `v3`. Locked decision 30 codifies the rule; smoke gate `smoke-no-mcp-in-dev-phases.sh` enforces it by reading `state.telemetry.mcpCalls[]` and failing any entry with `phase >= 2`.

This is a major bump because the pre-flight is hard - existing dev mode invocations that previously ran without an analysis document will now abort. The mitigation path is to run `/multi-agent:analysis "<feature>"` first.

### Why

Re-fetching Figma during Phase 2+ duplicates analysis work, burns MCP tokens, splits the design source of truth, and breaks the analysis -> dev contract. The user-facing pain in v8.x was Phase 3 silently calling MCP again, producing variants that drifted from the analysis Section 13.1 concept table. v9.0.0 binds Phase 2+ to the analysis document plus repo Code Connect mappings only.

### Added

- `pipeline/commands/multi-agent/refs/phases/phase-3-dev.md`: new "Phase 3 Pre-flight (BLOCKING, v9.0.0)" section (7 steps): analysis document presence, YAML front-matter parse, evidence digest cache validation, Code Connect mapping lookup, standards binding citation, conventions handoff (Pass B concept-to-realization), MCP forbidden gate.
- `pipeline/commands/multi-agent/refs/phases/phase-2-planning.md`: matching "Phase 2 Pre-flight (BLOCKING, v9.0.0)" section (5 steps): analysis presence, front-matter parse, section coverage check (required sections per Locked 2), task seed from analysis Section 14, MCP forbidden gate.
- `pipeline/rules/figma-pipeline.md`: new "MUST: No MCP outside analysis phase (BLOCKING, pipeline-wide)" section with phase access matrix, halt condition, rationale, and verification gate reference.
- `pipeline/commands/multi-agent/refs/rules.md`: new "Figma Access by Phase (pipeline-wide BLOCKING, v9.0.0)" matrix under existing "Figma Access Tier" section.
- `pipeline/scripts/smoke-no-mcp-in-dev-phases.sh`: smoke gate reading `state.telemetry.mcpCalls[]`. Fails when any entry has `phase >= 2`.
- `pipeline/commands/multi-agent/analysis.md`: Locked decisions 29 (variant usage explicit) and 30 (analysis self-contained, pipeline-wide MCP forbidden). Total Locked count now 30.

### Changed

- `pipeline/commands/multi-agent/refs/phases/phase-3-dev.md`: removed the 80-line legacy "MUST: Figma access 3-tier fallback chain" block. The 3-tier chain is exclusive to Phase 1 of `/multi-agent:analysis`; Phase 3 Dev now reads design context from the analysis document only. About 12 MCP / Figma fetch call sites cleaned up.
- `pipeline/commands/multi-agent/dev-local.md`: drift fix. Phase 4 (Review) references removed; header normalized to "5-phase fast pipeline (no worktree)" covering Phase 0, 3, 5, 6, 7 only.
- `pipeline/commands/multi-agent/refs/phases/modes.md`: Tablo 1 drift fix. Phase 5 (Test) cells now distinguish interactive prompt (normal, local, --dev, --dev-local) from skip (autopilot, local-autopilot, dev-autopilot, dev-local-autopilot).

### Migration notes

- Existing v2 / v8.x analyses are not v3 and will fail the pre-flight `template_version` check. Re-run `/multi-agent:analysis "<feature>"` to regenerate.
- If your dev mode invocation does not have an analysis document yet, run `/multi-agent:analysis` first. The pipeline now treats `/multi-agent:analysis` as a prerequisite for Phase 2+ work.
- Smoke gate runs after every pipeline invocation. CI invocations should add `bash pipeline/scripts/smoke-no-mcp-in-dev-phases.sh` to their post-run checks.

### Verified

- Personal-data smoke: 15 patterns clean, 0 leaks.
- Humanizer punctuation: zero hits in new content.
- `bash -n pipeline/scripts/smoke-no-mcp-in-dev-phases.sh`: zero output.
- Mock state fixtures: gate passes on `phase: 1` MCP entry, fails on `phase: 3` MCP entry.

---

---

Entries for v2.0.0 - v8.13.0 live in [CHANGELOG-archive.md](./CHANGELOG-archive.md) (repo only, not shipped in the npm tarball).
