@mmerterden/multi-agent-pipeline 14.1.0 → 14.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -16,6 +16,62 @@ Internal file-layout changes that don't affect the slash-command surface are sti
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [14.1.1] - 2026-07-30
20
+
21
+ Three defects that all shared one shape: a name written in one place and read in
22
+ another, so the mechanism looked implemented, ran without error, and did nothing.
23
+
24
+ - **Phase 0 wrote the base-ref field its own exit gate does not read.** `/multi-agent:dev`
25
+ recorded `"baseRefFreshness"` while `phase0-exit-gate.mjs` requires `baseFetchStatus`
26
+ with one of `fresh | cached-stale | local-branch | aborted`. The value vocabulary was
27
+ already correct - only the field name differed - so every dev-mode run failed its own
28
+ exit gate with `baseFetchStatus="<unset>"` even when all four Phase 0 pickers had
29
+ actually run. A gate that always fails is as useless as one that never fails. The
30
+ canonical name appeared in five places and the wrong one in exactly one: the dev
31
+ command's own doc. `smoke-phase-0-multi-repo.sh` now asserts that the name the exit
32
+ gate reads equals the name the phase doc documents, and that no shipped file names an
33
+ alternative, with a planted-line probe proving the detector can fire.
34
+ - **Branch memory never populated.** Phase 0 Step 3 read
35
+ `prefs.global.recentBranches[{projectKey}]` while its own step 7 wrote the legacy
36
+ `prefs.projects[].branches`, which `prefs.schema.json` marks pre-v2.1.0. Both spots
37
+ also described a `{name, lastUsed}` entry the schema rejects (`branch` is required and
38
+ `additionalProperties` is false), so a literal implementation would have failed prefs
39
+ validation and the dedup - which keys on `branch` - would have accumulated a duplicate
40
+ every run. The "reused from last run" picker option could therefore never appear.
41
+ `migrate-prefs.mjs` carries stranded legacy entries into the canonical LRU, stamped
42
+ with the migration time because the legacy field never recorded a real one and an epoch
43
+ stamp would be pruned by the TTL on first read; `count: 0` marks them seeded rather
44
+ than observed.
45
+ - **The sync skill hardcoded the author's git identity.** `git config user.name`,
46
+ `user.email` and `gh auth switch --user` carried literal values in the plugin-publish
47
+ block, while the same file's other two publish blocks used `{identity.name}` and
48
+ `{owner}`. Since the file ships to every installation, a downstream user's
49
+ plugins-repo commits would have been attributed to someone else and their `gh` account
50
+ switched under them - and it contradicted the pipeline's own rule that the git author
51
+ is always the user's identity.
52
+
53
+ Leak-gate coverage, which is why the third defect had gone unnoticed:
54
+
55
+ - `smoke-personal-data.sh` only ever scanned `pipeline/`, but the package also publishes
56
+ `install/`, `docs/`, `index.js`, `install.js`, `README.md` and `CHANGELOG.md`, and every
57
+ tracked file is public regardless of what npm ships. `CHANGELOG.md` was additionally in
58
+ the `--exclude` list. A second pass now scans every tracked file outside `pipeline/`,
59
+ driven by `git ls-files` so the covered set stays exactly "what is public" with no
60
+ second list to maintain. `LICENSE`, `package.json` and `CODE_OF_CONDUCT.md` are exempt,
61
+ because a package must name its author and a code of conduct must give a real contact.
62
+ - Added patterns for the author's own name, personal email and `gh auth switch` account -
63
+ none were checked before, which is precisely how a literal identity survived in a
64
+ shipped command - plus the employer's abbreviation where it is used as a symbol or
65
+ workspace prefix, bounded so ordinary words that merely contain those letters
66
+ (`HEALTHY`, `RHYTHM`) do not match.
67
+ - `--exclude-dir` for `.git`, `node_modules`, `.worktrees`, `.next` and `DerivedData`.
68
+ Without it, `--root` mode scanned `.git/logs`, so auditing any checkout was guaranteed
69
+ to "fail" on commit metadata no consumer receives, burying the real findings.
70
+ - Five leaks removed from public files: a real corporate email in `CHANGELOG-archive.md`,
71
+ a corporate toolkit name in both changelogs, the author's website in `docs/adr/0008`
72
+ and `docs/internal/`, and corporate symbol/repo/task literals in `docs/internal/`.
73
+ These are gone from HEAD; git history still contains them.
74
+
19
75
  ## [14.1.0] - 2026-07-29
20
76
 
21
77
  Two things the pipeline was supposed to do and did not: use the skills a project's
@@ -2650,7 +2706,7 @@ Analysis open-question resolver + repo hygiene hardening.
2650
2706
  blockquotes), humanizer punctuation policy, no Figma access (Locked 30 - design-gap
2651
2707
  rows get only Defer + a re-run recommendation), no auto-commit. Command inventory
2652
2708
  33 -> 34; `/multi-agent:analysis` Phase 5 report now suggests the resolver when
2653
- Section 20 has open rows. Pattern ported from the ai-mobile-toolkit resolver skills.
2709
+ Section 20 has open rows. Pattern ported from a private stack toolkit's resolver skills.
2654
2710
  - **Dead references removed.** `analysis.md` Reusable refs no longer points at a
2655
2711
  non-existent `fetch-wiki.sh` (the wiki fetch chain is inline: clone -> gh api ->
2656
2712
  WebFetch); `refs/features/external-context-injection.md` figma row routed to the real
package/README.md CHANGED
@@ -62,11 +62,11 @@ The discipline behind all of this - bounded loops, evidence gates, token-budgete
62
62
  | Autopilot | `/multi-agent:autopilot "task"` | All 8 phases, no confirmations |
63
63
  | Dev | `/multi-agent:dev "task"` | Init → Dev → Review → Test → Commit → Report |
64
64
  | Local | `/multi-agent:local "task"` | Full pipeline, current branch (no worktree) |
65
- | Finish | `/multi-agent:ship` | Run the review→test→commit→report tail over local work |
65
+ | Ship | `/multi-agent:ship` | Run the review→test→commit→report tail over local work |
66
66
  | Audit | `/multi-agent:design-check` | Mock-mode vs Figma conformance, local-only |
67
67
  | Audit | `/multi-agent:testflight-validation` | Pre-submission gates for a TestFlight build: static archive audit → Apple's `altool --validate-app` → Review-Guidelines check. Validates only, never uploads |
68
68
 
69
- Helpers: `setup`, `status`, `resume #N`, `review`, `test`, `channels`, `stack`, `update`, `sync`, `refactor`, `jira`, `issue`, `analysis`, `create-jira`, `save`, `routines`, `forget`. 43 commands in all - full list: `/multi-agent:help`.
69
+ Helpers: `setup`, `status`, `resume #N`, `review`, `test`, `channels`, `stack`, `update`, `sync`, `refactor`, `jira`, `issue`, `analysis`, `create-jira`, `save`, `routines`, `forget`. 44 commands in all - full list: `/multi-agent:help`.
70
70
 
71
71
  ## Stacks
72
72
 
@@ -80,12 +80,12 @@ This enables the matching plugin (+ the shared `ai-common` plugin) in the repo's
80
80
 
81
81
  ## Tool support
82
82
 
83
- The pipeline runs natively on **Claude Code**, **Copilot CLI** and **Codex CLI** - all three install from the same `pipeline/` source and get the same 43 commands.
83
+ The pipeline runs natively on **Claude Code**, **Copilot CLI** and **Codex CLI** - all three install from the same `pipeline/` source and get the same 44 commands.
84
84
 
85
85
  | Tool | Flag | What it installs |
86
86
  |---|---|---|
87
87
  | Claude Code | `--claude` (default) | slash commands + skills + agents + `PreToolUse` secret-scan hook |
88
- | Copilot CLI | `--copilot` | instructions + 43 sub-command skills + scripts |
88
+ | Copilot CLI | `--copilot` | instructions + 44 sub-command skills + scripts |
89
89
  | Codex CLI | `--codex` | one router skill + 43 specs as refs + 8 agent TOML + `AGENTS.md` block + `codex mcp add` |
90
90
 
91
91
  Filter skills by stack with `--platform=ios\|android\|all`.
@@ -10,7 +10,7 @@ Two pressures collided during the v7.x line:
10
10
 
11
11
  1. **Installer monolith.** `install.js` reached 1246 LOC by v7.9.1. It mixed flag parsing, three platform installers (Claude / Copilot / six third-party adapters), telemetry, a dev-only file exclusion list, and a 200-line static generator for `copilot-instructions.md`. Reasoning about install behaviour required holding the whole file in your head; reviewers asked for documentation we could not produce because every change touched untyped, intertwined sections.
12
12
 
13
- 2. **Secret leaks at deploy time.** During the v7.9.1 production deploy of `mmerterden.dev`, a `vercel deploy --token=vcp_...` invocation failed. The Vercel CLI printed the failed argv verbatim in its retry hint. That leaked the deploy token into the conversation transcript and forced a token rotation. A second incident in the same release window - `git -c user.email=...` overriding the repo-local config - pushed seven commits with the wrong author identity, which Vercel's contributor gate then blocked. Both classes of failure share a root cause: privileged values flowed through argv when the codebase had no audited boundary between provider tools and the orchestration layer.
13
+ 2. **Secret leaks at deploy time.** During the v7.9.1 production deploy of the project website, a `vercel deploy --token=vcp_...` invocation failed. The Vercel CLI printed the failed argv verbatim in its retry hint. That leaked the deploy token into the conversation transcript and forced a token rotation. A second incident in the same release window - `git -c user.email=...` overriding the repo-local config - pushed seven commits with the wrong author identity, which Vercel's contributor gate then blocked. Both classes of failure share a root cause: privileged values flowed through argv when the codebase had no audited boundary between provider tools and the orchestration layer.
14
14
 
15
15
  We needed an architectural answer that prevented both classes of failure from recurring without expanding scope into "rewrite everything as TypeScript" - the project's zero-dependency philosophy (ADR-4) is a hard constraint.
16
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "14.1.0",
3
+ "version": "14.1.1",
4
4
  "description": "8-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI and Codex CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -91,9 +91,13 @@ The agent CANNOT make these Phase 0 decisions automatically; it suggests, then w
91
91
  3. Cancel
92
92
  Confirm? [1/2/3]
93
93
  ```
94
- - User picks `2` → log warning + record `"baseRefFreshness": "stale"` in
94
+ - User picks `2` → log warning + record `"baseFetchStatus": "cached-stale"` in
95
95
  `agent-state.json`, proceed from local ref. Phase 6 push needs network anyway,
96
96
  so re-prompt there if still unreachable.
97
+ - Option `1` (fetch succeeded) records `"fresh"`; a local-branch base records
98
+ `"local-branch"`; option `3` records `"aborted"`. The field name and this
99
+ four-value vocabulary are what `phase0-exit-gate.mjs` reads, so a run that writes
100
+ anything else cannot close Phase 0.
97
101
 
98
102
  Always show what was **observed** next to what was **classified**. The previous
99
103
  wording asserted `Detected: <host> unreachable (VPN/DNS)` for every failure mode,
@@ -245,10 +245,15 @@ node "$HOME/multi-agent-pipeline/pipeline/scripts/build-stack-plugins.mjs"
245
245
  ```bash
246
246
  cd "$PLUGINS_REPO"
247
247
  if ! git diff --quiet; then
248
- git config user.name "Mert Erden"; git config user.email "mmerterden@gmail.com"
248
+ # Identity resolves from prefs.global.identities[] routed by platformIdentityRouting,
249
+ # exactly as Phase 6 does. Never hardcode a name, email or gh account here: this file
250
+ # ships to every installation, so a literal identity attributes a downstream user's
251
+ # plugins-repo commits to someone else and switches their gh account out from under them.
252
+ git config user.name "{identity.name}"
253
+ git config user.email "{identity.email}"
249
254
  git add -A
250
255
  git commit -m "chore: rebuild stack plugins from pipeline shared/external"
251
- gh auth switch --user mmerterden 2>/dev/null || true
256
+ gh auth switch --user {owner} 2>/dev/null || true
252
257
  git push origin main
253
258
  fi
254
259
  ```
@@ -73,7 +73,7 @@ Used for: input parsing, branch naming, commit messages.
73
73
  |---|---|---|---|
74
74
  | Project picked (Step 2) | `global.recentProjects` | `[{path, label, count, lastUsed}]` | 20 |
75
75
  | Multi-repo group picked or saved (Step 2) | `global.recentGroups` | `[{label?, repos[], count, lastUsed}]` | 10 |
76
- | Branch picked (Step 3) | `global.recentBranches[{projectKey}]` | `[{name, lastUsed}]` (TTL `settings.branchTtlDays`, default 15d) | implicit (TTL prunes) |
76
+ | Branch picked (Step 3) | `global.recentBranches[{projectKey}]` | `[{branch, lastUsed, count?}]` (TTL `settings.branchTtlDays`, default 15d) | 10 (TTL also prunes) |
77
77
  | Service ping (any external API call) | `global.serviceStatus[{service}]` | `{ok, checkedAt, reason?}` (TTL `settings.serviceStatusCacheSeconds`, default 300s) | n/a |
78
78
  | Git identity routed (Step 6a) | `projects[{name}].lastIdentity` | int (index into `global.identities`) | n/a |
79
79
 
@@ -261,7 +261,10 @@ Scan `$HOME` (maxdepth 2) for project markers (`.xcodeproj`, `Package.swift`, `b
261
261
  header: "Base branch"
262
262
  options: origin/develop (Recommended, reused from last run) | origin/main | release/8.4.0 | Other
263
263
  ```
264
- 7. User picks → store as `baseBranch`. Save to `prefs.projects[{project}].branches` (dedup, max 10).
264
+ 7. User picks → store as `baseBranch`, and append `{branch, lastUsed, count?}` to
265
+ `prefs.global.recentBranches[{projectKey}]` (dedup by `branch`, cap 10) - what the TTL
266
+ filter below reads. Key is `branch`, not `name`. Never the legacy
267
+ `projects[{project}].branches`.
265
268
 
266
269
  **MUST: this step is not skippable (BLOCKING).** The only legitimate skip is rule 4
267
270
  above - `baseBranch` already supplied in the input. Everything else asks. A run once
@@ -277,7 +280,7 @@ which still writes the fields - it does not leave them unset.
277
280
 
278
281
  **TTL filter for recent branches**:
279
282
 
280
- - `prefs.global.recentBranches[{projectKey}][]` carries `{name, lastUsed}`. Filter to those whose `lastUsed` is within `settings.branchTtlDays` (default 15).
283
+ - `prefs.global.recentBranches[{projectKey}][]` carries `{branch, lastUsed, count?}`. Filter to those whose `lastUsed` is within `settings.branchTtlDays` (default 15).
281
284
  - Stale entries (>TTL) are pruned in-place during the read - keeps the picker uncluttered without a separate cleanup pass.
282
285
  - The filtered "Recent" list precedes the fresh `git branch -r` list; cap at 5 visible recent entries.
283
286
 
@@ -36,6 +36,6 @@
36
36
  "warn_tokens": 5600
37
37
  }
38
38
  },
39
- "total_max_tokens": 52700,
40
- "note": "Token estimate = ceil(chars / 4). Per-phase budget rule: warn = current+10% (rounded to nearest 50), max = current+25%. Gives ~6 edit cycles of headroom before warn trips - intentionally quiet under normal maintenance, loud when a phase grows unusually. Only the active phase is loaded (lazy). Recalibrated at v10.0.0 after the validator/consistency/simplifier/lesson gate contracts landed in phases 1-4. Recalibrated again at v10.9.0 after the verify-by-test (Phase 4 Step 3.7), update-check (Phase 0 Step 0.6), immutable-test (Phase 3 GREEN) and redTests re-entry contracts landed - Step 3.7 prose was compressed to a pointer into refs/features/verify-by-test.md before the recalibration. Total bumped 50000 -> 51000 at v12.5.0 after the worktree residue/traversal-prune contract (Phase 0 + Phase 5 heal) and the Reflexion causal-diagnosis contract (Phase 4 lesson memory) landed; the prose was compressed first (161 tokens reclaimed) and every per-phase max still passes - only the aggregate needed room. Recalibrated again at v13.6.0 after the install-relative path correction: an instruction that names `pipeline/scripts/x` resolves only from a repo checkout, and a run happens in the user's worktree, so 157 references across these docs moved to `$HOME/.claude/...` at +5 bytes each - 196 tokens of pure correctness cost. Same discipline as before: prose was compressed FIRST (149 tokens reclaimed, by pointing Phase 1's Figma tier table at the Phase 0 probe that already resolved it and Phase 4's Codex constraints at the always-loaded AGENTS.md block), and only then were the budgets moved. Five warn lines had been permanently amber, which makes the amber tier useless as a signal, so every warn was reset to the documented current+10% and the four maxes that the new warn would have collided with were reset to current+25%. Aggregate 51000 -> 51500. Total bumped 51500 -> 52200 at v14.0.0 after Phase 4 Review entered the four --dev mode phase sets and the criteria-resolution contract (Step 1.78) landed. Same discipline as every prior bump: prose was compressed FIRST, 820 tokens reclaimed, before the number moved. Two of those compressions are structural rather than cosmetic - the hardcoded SwiftUI interaction list in Step 1.5 and the SwiftUI convention paragraph in Step 2.8 were transcriptions of rules that now live in a scoped registry, so keeping them here would have re-created the drift this release exists to remove, and the third moved the Step 1.78 full contract into refs/features/skill-conformance.md leaving a pointer. What remains is contract text that cannot be inferred: the manifest's four consumer-visible parts, the conformance checklist the reviewers must return, and the fail-closed semantics. Every per-phase max still passes (phase-4 12405/14750); only the aggregate needed room. Total bumped 52200 -> 52700 at v14.1.0 after two more contracts landed: stack skill routing (Phase 3 pre-flight step 9) and worktree finalize (Phase 6 step 9). Compression came first, as always, and twice: 224 tokens out of Phase 3 by pointing its criteria-ledger and routing steps at their feature files instead of restating them, and 190 out of Phase 6 by moving the finalize contract into refs/features/worktree-finalize.md and leaving the invocation plus the exit-3 semantics. Both new contracts follow the pattern the earlier ones set: the phase doc carries the call and the decision, the feature file carries the reasoning, and the feature files are outside this budget because it loops only the eight phase-N-* keys. Every per-phase max still passes (phase-3 7677/8950, phase-6 5223/6150 and both under warn); only the aggregate needed room."
39
+ "total_max_tokens": 52750,
40
+ "note": "Token estimate = ceil(chars / 4). Per-phase budget rule: warn = current+10% (rounded to nearest 50), max = current+25%. Gives ~6 edit cycles of headroom before warn trips - intentionally quiet under normal maintenance, loud when a phase grows unusually. Only the active phase is loaded (lazy). Recalibrated at v10.0.0 after the validator/consistency/simplifier/lesson gate contracts landed in phases 1-4. Recalibrated again at v10.9.0 after the verify-by-test (Phase 4 Step 3.7), update-check (Phase 0 Step 0.6), immutable-test (Phase 3 GREEN) and redTests re-entry contracts landed - Step 3.7 prose was compressed to a pointer into refs/features/verify-by-test.md before the recalibration. Total bumped 50000 -> 51000 at v12.5.0 after the worktree residue/traversal-prune contract (Phase 0 + Phase 5 heal) and the Reflexion causal-diagnosis contract (Phase 4 lesson memory) landed; the prose was compressed first (161 tokens reclaimed) and every per-phase max still passes - only the aggregate needed room. Recalibrated again at v13.6.0 after the install-relative path correction: an instruction that names `pipeline/scripts/x` resolves only from a repo checkout, and a run happens in the user's worktree, so 157 references across these docs moved to `$HOME/.claude/...` at +5 bytes each - 196 tokens of pure correctness cost. Same discipline as before: prose was compressed FIRST (149 tokens reclaimed, by pointing Phase 1's Figma tier table at the Phase 0 probe that already resolved it and Phase 4's Codex constraints at the always-loaded AGENTS.md block), and only then were the budgets moved. Five warn lines had been permanently amber, which makes the amber tier useless as a signal, so every warn was reset to the documented current+10% and the four maxes that the new warn would have collided with were reset to current+25%. Aggregate 51000 -> 51500. Total bumped 51500 -> 52200 at v14.0.0 after Phase 4 Review entered the four --dev mode phase sets and the criteria-resolution contract (Step 1.78) landed. Same discipline as every prior bump: prose was compressed FIRST, 820 tokens reclaimed, before the number moved. Two of those compressions are structural rather than cosmetic - the hardcoded SwiftUI interaction list in Step 1.5 and the SwiftUI convention paragraph in Step 2.8 were transcriptions of rules that now live in a scoped registry, so keeping them here would have re-created the drift this release exists to remove, and the third moved the Step 1.78 full contract into refs/features/skill-conformance.md leaving a pointer. What remains is contract text that cannot be inferred: the manifest's four consumer-visible parts, the conformance checklist the reviewers must return, and the fail-closed semantics. Every per-phase max still passes (phase-4 12405/14750); only the aggregate needed room. Total bumped 52200 -> 52700 at v14.1.0 after two more contracts landed: stack skill routing (Phase 3 pre-flight step 9) and worktree finalize (Phase 6 step 9). Compression came first, as always, and twice: 224 tokens out of Phase 3 by pointing its criteria-ledger and routing steps at their feature files instead of restating them, and 190 out of Phase 6 by moving the finalize contract into refs/features/worktree-finalize.md and leaving the invocation plus the exit-3 semantics. Both new contracts follow the pattern the earlier ones set: the phase doc carries the call and the decision, the feature file carries the reasoning, and the feature files are outside this budget because it loops only the eight phase-N-* keys. Every per-phase max still passes (phase-3 7677/8950, phase-6 5223/6150 and both under warn); only the aggregate needed room. Total bumped 52700 -> 52750 for the Phase 0 Step 3 branch-persistence correction: the step wrote the legacy `projects[].branches` while the TTL filter two sections below read `global.recentBranches`, and both spots named a `{name, lastUsed}` shape the schema rejects (`branch` required, `additionalProperties: false`), so the recent-branch picker option could never populate and a literal implementation would have failed prefs validation. Naming the right target, the right key and the legacy field to avoid costs 41 tokens over the one line it replaces. Compression came first and was applied three times to the replacement text itself, from 120 tokens down to 66, by moving the rationale out of the phase doc entirely: the reasoning now lives where it is enforced, in the migrate-prefs carry-forward comment and the smoke-pref-migration f7 block, leaving the phase doc with only the instruction. 50 was the smallest step that clears it; phase-0-init sits at 10893/12400, far under its own max, so this is purely an aggregate ceiling."
41
41
  }
@@ -33,6 +33,7 @@ import {
33
33
  statSync,
34
34
  } from "node:fs";
35
35
  import { join } from "node:path";
36
+ import { spawnSync } from "node:child_process";
36
37
 
37
38
  const args = process.argv.slice(2);
38
39
  // Accepts both `--key value` and `--key=value`. The `=` form used to fall through to
@@ -251,6 +252,26 @@ for (const [plugin, want] of Object.entries(desired)) {
251
252
  pj.skills = newSkills;
252
253
  pj.version = newV;
253
254
  writeFileSync(pjPath, `${JSON.stringify(pj, null, 2)}\n`);
255
+
256
+ // The marketplace README carries a per-plugin table of version + skill count,
257
+ // and that table is what a human reads to know what is published. Bumping
258
+ // plugin.json without it drifted five of the five rows, silently, because the
259
+ // drift is only visible if someone runs the checker. `tools/bump.py` owns the
260
+ // table's format, so call it rather than reproducing the row layout here - a
261
+ // second formatter would be the one that rots.
262
+ const bumpTool = join(PLUGINS_REPO, "tools", "bump.py");
263
+ if (existsSync(bumpTool)) {
264
+ const r = spawnSync("python3", [bumpTool, plugin, "--set", newV], {
265
+ cwd: PLUGINS_REPO,
266
+ encoding: "utf8",
267
+ });
268
+ if (r.status !== 0) {
269
+ console.warn(
270
+ ` WARN ${plugin}: plugin.json is ${newV} but the README table was not synced ` +
271
+ `(bump.py exit ${r.status ?? "n/a"}). Run \`tools/bump.py --check\` in ${PLUGINS_REPO}.`,
272
+ );
273
+ }
274
+ }
254
275
  }
255
276
  report.push({
256
277
  plugin,
@@ -270,6 +270,36 @@ function migrate(prefs) {
270
270
  out.global.recentBranches = {};
271
271
  changes.push("added empty recentBranches");
272
272
  }
273
+ // Carry the legacy per-project branch list into the canonical LRU. Phase 0 Step 3
274
+ // read global.recentBranches while its own step 7 wrote projects[].branches, so
275
+ // every branch a user ever picked landed in the field nothing reads and the
276
+ // "reused from last run" picker option stayed empty. Seeding here makes that
277
+ // history usable instead of stranding it. Idempotent: an existing entry for the
278
+ // same branch is never duplicated or overwritten.
279
+ //
280
+ // `lastUsed` is stamped with the migration time, not the real pick time, which the
281
+ // legacy field never recorded. Stamping the true unknown (epoch) would put every
282
+ // seeded entry outside settings.branchTtlDays and the TTL filter would prune it on
283
+ // the first read, making this carry-forward a no-op. `count: 0` marks the entry as
284
+ // seeded rather than observed, and the normal TTL retires anything the user does not
285
+ // actually pick again within the window.
286
+ const seededAt = new Date().toISOString();
287
+ for (const [projectKey, project] of Object.entries(out.projects ?? {})) {
288
+ if (!Array.isArray(project?.branches) || project.branches.length === 0) continue;
289
+ const lru = (out.global.recentBranches[projectKey] ??= []);
290
+ const known = new Set(lru.map((e) => e?.branch));
291
+ let seeded = 0;
292
+ for (const branch of project.branches) {
293
+ if (typeof branch !== "string" || !branch || known.has(branch)) continue;
294
+ if (lru.length >= 10) break;
295
+ lru.push({ branch, lastUsed: seededAt, count: 0 });
296
+ known.add(branch);
297
+ seeded += 1;
298
+ }
299
+ if (seeded > 0) {
300
+ changes.push(`seeded ${seeded} recentBranches entr${seeded === 1 ? "y" : "ies"} for ${projectKey} from legacy projects[].branches`);
301
+ }
302
+ }
273
303
  if (!Array.isArray(out.global.recentGroups)) {
274
304
  out.global.recentGroups = [];
275
305
  changes.push("added empty recentGroups");
@@ -857,15 +857,6 @@
857
857
  "triggerPaths": [],
858
858
  "relativePath": "shared/core/multi-agent-diff-explain/SKILL.md"
859
859
  },
860
- {
861
- "name": "multi-agent-ship",
862
- "description": "Continue already-done LOCAL work through the pipeline tail: Review → Build+Test → Commit/PR → Report (technical analysis + Jira test-scenario comment). No dev phase. Use when local work is already done and only review, build, commit and reporting remain.",
863
- "platform": null,
864
- "group": "core",
865
- "triggerKeywords": [],
866
- "triggerPaths": [],
867
- "relativePath": "shared/core/multi-agent-ship/SKILL.md"
868
- },
869
860
  {
870
861
  "name": "multi-agent-forget",
871
862
  "description": "Remove a saved /multi-agent routine (created by /multi-agent:save): deletes its local-only command and its registry entry. Asks which one and confirms. Use when a saved routine is no longer wanted and should be removed.",
@@ -1082,6 +1073,15 @@
1082
1073
  "triggerPaths": [],
1083
1074
  "relativePath": "shared/core/multi-agent-setup/SKILL.md"
1084
1075
  },
1076
+ {
1077
+ "name": "multi-agent-ship",
1078
+ "description": "Continue already-done LOCAL work through the pipeline tail: Review → Build+Test → Commit/PR → Report (technical analysis + Jira test-scenario comment). No dev phase. Use when local work is already done and only review, build, commit and reporting remain.",
1079
+ "platform": null,
1080
+ "group": "core",
1081
+ "triggerKeywords": [],
1082
+ "triggerPaths": [],
1083
+ "relativePath": "shared/core/multi-agent-ship/SKILL.md"
1084
+ },
1085
1085
  {
1086
1086
  "name": "multi-agent-stack",
1087
1087
  "description": "Select the active stack for this repo by enabling the matching marketplace plugin(s) in .claude/settings.json (ios/android/mobile/backend/frontend/fullstack/all). Use when a repo's stack changed or the wrong plugins are enabled for it.",
@@ -102,7 +102,6 @@
102
102
  | core | `multi-agent-dev-local` | - | Fast mode + local - Init → Dev(Opus) → Review → Commit → Report, no worktree. Use when a change should be developed and reviewed on the cu |
103
103
  | core | `multi-agent-dev-local-autopilot` | - | Fastest + local - Dev(Opus) + autopilot, no worktree, zero interaction. Use when a change should be developed on the current branch with n |
104
104
  | core | `multi-agent-diff-explain` | - | Map Phase 4 triage findings to branch diff lines. Read-only post-hoc command, used after review to answer 'which finding lines up with which |
105
- | core | `multi-agent-ship` | - | Continue already-done LOCAL work through the pipeline tail: Review → Build+Test → Commit/PR → Report (technical analysis + Jira test-scenari |
106
105
  | core | `multi-agent-forget` | - | Remove a saved /multi-agent routine (created by /multi-agent:save): deletes its local-only command and its registry entry. Asks which one an |
107
106
  | core | `multi-agent-garbage-collect` | - | Sweep leftover /tmp scratch (picker state, review diffs, channel payloads, analysis drafts) from past runs. Dry-run first; confirms before d |
108
107
  | core | `multi-agent-help` | - | Multi-agent pipeline usage guide - renders in EN or TR per prefs.global.outputLanguage (falls back to promptLanguage for backward compatib |
@@ -127,6 +126,7 @@
127
126
  | core | `multi-agent-scan` | - | Skill security scan: walks local skill directories against a tiered pattern catalog. Use when local skill directories need checking for unsa |
128
127
  | core | `multi-agent-search` | - | Log search across every agent-log.md with smart ranking and filters. Optional --semantic flag queries the per-repo triage corpus. Use when s |
129
128
  | core | `multi-agent-setup` | - | First-run setup wizard: keychain token discovery, Git Identity onboarding, and pipeline preparation. Use when the pipeline is being set up f |
129
+ | core | `multi-agent-ship` | - | Continue already-done LOCAL work through the pipeline tail: Review → Build+Test → Commit/PR → Report (technical analysis + Jira test-scenari |
130
130
  | core | `multi-agent-stack` | - | Select the active stack for this repo by enabling the matching marketplace plugin(s) in .claude/settings.json (ios/android/mobile/backend/fr |
131
131
  | core | `multi-agent-status` | - | Show every multi-agent task's ID, phase, branch, and status. Use when asked what is running, or for an overview of every task. |
132
132
  | core | `multi-agent-sync` | - | One-shot sync of the entire multi-agent ecosystem: Claude Code, Copilot CLI, pipeline repo, website, and the dev-toolkit MCP server. Use whe |