@mmerterden/multi-agent-pipeline 20.24.0 → 20.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +6 -5
  3. package/README.tr.md +6 -5
  4. package/docs/architecture.md +3 -3
  5. package/docs/ecosystem.md +5 -5
  6. package/docs/facts.json +7 -6
  7. package/docs/features.md +9 -4
  8. package/install/catalog-history.json +1 -1
  9. package/install/templates/claude-hooks.json +1 -1
  10. package/install/templates/codex-hooks.json +18 -0
  11. package/manifest.json +89 -82
  12. package/package.json +1 -1
  13. package/pipeline/agents/task-clarifier.md +1 -1
  14. package/pipeline/commands/multi-agent/deps/SKILL.md +61 -0
  15. package/pipeline/commands/multi-agent/model/SKILL.md +2 -2
  16. package/pipeline/commands/multi-agent/postmortem/SKILL.md +18 -6
  17. package/pipeline/commands/multi-agent/review/SKILL.md +5 -5
  18. package/pipeline/commands/multi-agent/route-on/SKILL.md +1 -1
  19. package/pipeline/commands/multi-agent/setup/SKILL.md +7 -5
  20. package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
  21. package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +2 -3
  22. package/pipeline/contract/fixtures/runs-failed.json +1 -1
  23. package/pipeline/contract/fixtures/runs-old-schema.json +1 -1
  24. package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
  25. package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
  26. package/pipeline/contract/fixtures/runs-running.json +1 -1
  27. package/pipeline/lib/credential-inventory.sh +5 -5
  28. package/pipeline/lib/fetch-crashlytics.sh +6 -8
  29. package/pipeline/lib/model-rung.sh +1 -1
  30. package/pipeline/lib/plan-todos.sh +21 -4
  31. package/pipeline/lib/post-pr-review.sh +4 -6
  32. package/pipeline/lib/route-state.sh +1 -1
  33. package/pipeline/lib/shadow-git.sh +4 -5
  34. package/pipeline/multi-agent-refs/cross-cli-contract.md +8 -9
  35. package/pipeline/multi-agent-refs/features/cost-analysis.md +4 -0
  36. package/pipeline/multi-agent-refs/features/dep-bump.md +176 -0
  37. package/pipeline/multi-agent-refs/features/model-fallback.md +17 -12
  38. package/pipeline/multi-agent-refs/help/en.md +1 -0
  39. package/pipeline/multi-agent-refs/help/tr.md +1 -0
  40. package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
  41. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +2 -0
  42. package/pipeline/multi-agent-refs/phases/phase-3-review.md +5 -5
  43. package/pipeline/multi-agent-refs/picker-contract.md +1 -1
  44. package/pipeline/multi-agent-refs/tracker-contract.md +6 -6
  45. package/pipeline/schemas/agent-state.schema.json +5 -0
  46. package/pipeline/schemas/phases.json +4 -0
  47. package/pipeline/schemas/plan-todos.schema.json +4 -4
  48. package/pipeline/schemas/prefs.schema.json +27 -4
  49. package/pipeline/scripts/_code-graph.mjs +2 -2
  50. package/pipeline/scripts/_transcript-hosts.mjs +406 -0
  51. package/pipeline/scripts/build-stack-plugins.mjs +3 -3
  52. package/pipeline/scripts/capture-flush.sh +7 -7
  53. package/pipeline/scripts/code-graph-rules/android.json +1 -1
  54. package/pipeline/scripts/cost-analyze.mjs +26 -19
  55. package/pipeline/scripts/cost-table.json +35 -19
  56. package/pipeline/scripts/dep-bump.mjs +1556 -0
  57. package/pipeline/scripts/edit-syntax-gate.mjs +151 -29
  58. package/pipeline/scripts/edit-syntax-gate.sh +9 -6
  59. package/pipeline/scripts/gc-abandoned.sh +2 -2
  60. package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
  61. package/pipeline/scripts/git-path.mjs +1 -2
  62. package/pipeline/scripts/host-hooks.mjs +265 -0
  63. package/pipeline/scripts/jira-attach.sh +1 -1
  64. package/pipeline/scripts/keychain.py +3 -3
  65. package/pipeline/scripts/output-quality-check.sh +1 -1
  66. package/pipeline/scripts/phase-tracker.sh +5 -6
  67. package/pipeline/scripts/phase0-exit-gate.mjs +3 -3
  68. package/pipeline/scripts/pre-push-check.sh +3 -6
  69. package/pipeline/scripts/transcript-index.mjs +423 -242
  70. package/pipeline/scripts/uninstall.mjs +90 -4
  71. package/pipeline/scripts/update-issue-progress.sh +4 -6
  72. package/pipeline/scripts/usage-report.mjs +2 -2
  73. package/pipeline/skills/.skill-manifest.json +14 -10
  74. package/pipeline/skills/.skills-index.json +12 -1
  75. package/pipeline/skills/shared/README.md +4 -3
  76. package/pipeline/skills/shared/core/multi-agent/SKILL.md +6 -6
  77. package/pipeline/skills/shared/core/multi-agent-deps/SKILL.md +60 -0
  78. package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +2 -2
  79. package/pipeline/skills/shared/core/multi-agent-postmortem/SKILL.md +10 -7
  80. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +5 -5
  81. package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +1 -1
  82. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +7 -5
  83. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +3 -3
  84. package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +2 -3
  85. package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
  86. package/pipeline/skills/skills-index.md +2 -1
@@ -0,0 +1,61 @@
1
+ ---
2
+ description: "Bump one dependency, fix only what the bump broke, review, open a PR; rolls back past the fix round cap. SwiftPM, Gradle, npm, pip. Use when a library needs updating."
3
+ description-tr: "Tek bağımlılığı yükseltir, yalnızca yükseltmenin kırdığını düzeltir, review edip PR açar; düzeltme turu sınırında geri alır. SwiftPM, Gradle, npm, pip. Bir kütüphaneyi güncellemek için kullanılır."
4
+ argument-hint: "[package[@version]] [--major] - no package opens a picker over the outdated candidates; --major allows a breaking bump"
5
+ parameters: [{"name":"package","kind":"text"},{"name":"--major","kind":"flag"}]
6
+ gui: form
7
+ destructive: false
8
+ confirm: none
9
+ ---
10
+
11
+ # multi-agent deps - bump one dependency and fix what it broke
12
+
13
+ > **Pickers follow** `$HOME/.claude/multi-agent-refs/picker-contract.md`: never a one-option call, and branch on the option selected, not on its text.
14
+
15
+ **Input**: $ARGUMENTS
16
+
17
+ > **Language (read FIRST)**: read `prefs.global.outputLanguage` and render every conversational line in it. `AskUserQuestion` renders `question`, option `label`s and `description`s in `outputLanguage`; only `header` stays English (<=12-char chip). Commit messages and branch names stay English. Full contract: `$HOME/.claude/multi-agent-refs/rules.md` "Language Application".
18
+
19
+ The normal pipeline with a fixed task. Read `$HOME/.claude/multi-agent-refs/features/dep-bump.md` first: it is the contract for every step below (run shape, scope rule, rollback). `dep-bump.mjs` does every deterministic step; never edit a version string or a lockfile by hand.
20
+
21
+ ## Steps
22
+
23
+ 1. **Repo and settings.** Resolve the repo as `/multi-agent` does for a free-text task (`_account-picker.md`, `_repo-picker.md`). Then:
24
+ ```bash
25
+ node "$HOME/.claude/scripts/dep-bump.mjs" config # depBump.maxFixRounds, depBump.queryRegistry
26
+ node "$HOME/.claude/scripts/dep-bump.mjs" detect --dir "$REPO"
27
+ ```
28
+ No ecosystem detected: say so and stop.
29
+
30
+ 2. **Candidates.**
31
+ ```bash
32
+ node "$HOME/.claude/scripts/dep-bump.mjs" list --dir "$REPO" [--registry] # --registry when queryRegistry is true
33
+ ```
34
+ Show `registry.note` and every per-package `note`: an offline listing is reported as offline, never as "nothing outdated".
35
+ - `package@version` given: use it. `package` alone: its `latestInMajor`, else `latest`. Not in the list: stop with the manifests that were read.
36
+ - No package: a single-select picker over the candidates with a newer version, non-breaking first, each option `name current -> target (risk)`. Two or more options per the picker contract; one candidate is confirmed with a yes/no, none means report "nothing newer found" with the registry note.
37
+
38
+ 3. **Major gate.** Risk `major` or `unknown`, or a prerelease target (`prerelease: true`), needs `--major` in the input or an explicit confirmation naming the versions (`Bump anyway` / `Take {latestInMajor} instead` when one exists / `Cancel`). An autopilot run without `--major` takes `latestInMajor` or stops.
39
+
40
+ 4. **Run the pipeline** with `state.mode = "deps"`, `state.taskType = "chore"`, the free-text task `chore(deps): bump {name} from {current} to {target}` and the branch `chore/deps-{name}-{target}` (kebab rules from Phase 0 Step 4). Phase 0 intake, worktree and tracker run as in `/multi-agent`. Then:
41
+ - **Phase 1**: the fixed plan from the feature doc, with the changelog excerpt as evidence:
42
+ ```bash
43
+ node "$HOME/.claude/scripts/dep-bump.mjs" notes --dir "$WORKTREE" --ecosystem E --name N --from A --to B
44
+ ```
45
+ - **Phase 2**: apply first, then the exit gate as written:
46
+ ```bash
47
+ node "$HOME/.claude/scripts/dep-bump.mjs" apply --dir "$WORKTREE" --ecosystem E --name N --to B [--allow-major]
48
+ ```
49
+ Exit 1: report `error` and `lock.output` (or `dirty`: the worktree had uncommitted changes), stop with nothing to commit. Persist the output to `state.depBump`. Green: no dev agent, on to Phase 3. Red: fix rounds, at most `depBump.maxFixRounds`, each dispatched with the failing log tail, `surface` output, the changelog excerpt and the scope rule (feature doc section 4); after each round:
50
+ ```bash
51
+ node "$HOME/.claude/scripts/dep-bump.mjs" scope --dir "$WORKTREE" --log "$GATE_LOG" # the round's build/test log
52
+ ```
53
+ Only the surface recorded by `apply` is in scope. A file listed in `newImporters` with `cited: null` started importing the dependency without an error line behind it: revert it before the next round.
54
+ - **Cap reached and still red** (or still out of scope): `dep-bump.mjs rollback --dir "$WORKTREE"` (restores the manifest, lockfile and fix files; anything under `kept` is reported, not discarded), set `state.depBump.rolledBack = true`, and end with the report from feature doc section 5. No commit, no push, no PR.
55
+ - **Phase 3**: standard review. Each `outOfScope` file without a cited error line, and each `newImporters` entry with `cited: null`, is a blocking finding.
56
+ - **Phase 4**: commit with `commitMessage` from `apply` (plus ` [{jiraId}]` when present). PR body per the feature doc; `Ref:` or `Related:`, never `Closes`, `Fixes` or `Resolves`, and never close an issue.
57
+ - **Phase 5**: channels as in every mode.
58
+
59
+ ## Report
60
+
61
+ In `outputLanguage`, at most 10 lines: the dependency, from and to, risk, lockfile command and result, rounds taken against the cap, files changed (in scope / justified), review verdict, and the PR URL or the rollback reason.
@@ -52,8 +52,8 @@ because it does not mean the same thing on all three:
52
52
 
53
53
  | Host | What `off` does | Why |
54
54
  |---|---|---|
55
- | Claude Code | every `preferredModel: fable` persona (architects, reviewer 1, triage) starts on `opus` | the only host where the rung is Fable 5 |
56
- | Copilot CLI | nothing | Fable 5 is not offered there; its personas never sat on this rung |
55
+ | Claude Code | every `preferredModel: fable` persona (architects, reviewer 1, triage) starts on `opus` | the only host where the rung is Fable 5.1 |
56
+ | Copilot CLI | nothing | Fable is not offered there; its personas never sat on this rung |
57
57
  | Codex CLI | nothing, deliberately | the `fable` rung there means `gpt-5.6 @ xhigh`, a different model on a different account - switching it from a knob named after an Anthropic model would surprise a Codex user |
58
58
 
59
59
  So on two of the three hosts this command is a **status report**, not a switch,
@@ -12,18 +12,30 @@ confirm: none
12
12
 
13
13
  **Input**: $ARGUMENTS
14
14
 
15
- Explains what a run actually did, read from the Claude Code session transcripts
16
- it ran in, not from what the run log says it decided. The evidence comes from a
15
+ Explains what a run actually did, read from the session transcripts it ran in,
16
+ not from what the run log says it decided. The evidence comes from a
17
17
  deterministic indexer; the model only interprets it.
18
18
 
19
+ The indexer reads all three hosts and detects the format per file:
20
+
21
+ | Host | Session files |
22
+ |------|---------------|
23
+ | Claude Code | `~/.claude/projects/<dir>/<session>.jsonl` plus its subagent files |
24
+ | Copilot CLI | `~/.copilot/session-state/<session>/events.jsonl` (cited as `<first 8 of session>/events.jsonl`) |
25
+ | Codex | `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` (cited as `rollout-<first 8 of session>.jsonl`) |
26
+
27
+ Copilot CLI reports tokens per session, so its turns carry output tokens only
28
+ and the totals come from the session's shutdown record; Codex reports them per
29
+ request.
30
+
19
31
  **Read-only.** Nothing is sent anywhere: no issue comment, no PR, no channel.
20
32
  The one write is the report itself, saved under the run's log directory.
21
33
 
22
34
  ## Steps
23
35
 
24
36
  1. **Resolve the target.**
25
- - A path ending in `.jsonl`: index that file (subagent files beside it are
26
- included). There is no run directory, so nothing is saved unless the user
37
+ - A path ending in `.jsonl` (any of the three hosts): index that file
38
+ (Claude Code subagent files beside it are included). There is no run directory, so nothing is saved unless the user
27
39
  names one.
28
40
  - A task id or `#N`: locate the run and its transcripts:
29
41
  ```bash
@@ -31,8 +43,8 @@ The one write is the report itself, saved under the run's log directory.
31
43
  ```
32
44
  `matchedBy: "session"` means the run recorded its session id and the match
33
45
  is exact. `"time-window"` means it did not (attended runs record none), so
34
- the files are the sessions that ran in the run's worktree or checkout while
35
- it was active; say that this match is a heuristic. Exit 3 with a `reason`:
46
+ the files are the sessions, on any host, that ran in the run's worktree or
47
+ checkout while it was active; say that this match is a heuristic. Exit 3 with a `reason`:
36
48
  report the reason, and offer to take a transcript path instead.
37
49
  - No argument: take the most recent run from
38
50
  `node "$HOME/.claude/scripts/runs-index.mjs" --json` (latest `startedAt`)
@@ -247,14 +247,14 @@ off: Agent 1 is dropped, the panel is Agent 2 + Agent 3, triage runs on
247
247
  Never dispatch a Fable reviewer or a Fable triage on the model list below alone.
248
248
 
249
249
  **Claude Code (3 in parallel while the fable rung is on, 2 while it is off):**
250
- - Agent 1: `claude-fable-5` → security + architecture (only when `REVIEW_RUNG` is `fable`)
251
- - Agent 2: `claude-opus-5` → edge cases, alternate perspective
252
- - Agent 3: `claude-sonnet-5` → general quality
250
+ - Agent 1: `claude-fable-5-1` → security + architecture (only when `REVIEW_RUNG` is `fable`)
251
+ - Agent 2: `claude-opus-5-5` → edge cases, alternate perspective
252
+ - Agent 3: `claude-sonnet-5-5` → general quality
253
253
 
254
254
  **Copilot CLI (3 in parallel):**
255
- - Agent 1: `claude-opus-5` → security + architecture (Fable 5 is not offered on Copilot CLI)
255
+ - Agent 1: `claude-opus-4.7` → security + architecture (Fable is not offered on Copilot CLI)
256
256
  - Agent 2: `gpt-5.4` → edge cases, alternate perspective
257
- - Agent 3: `claude-sonnet-5` → general quality
257
+ - Agent 3: `claude-sonnet-4.6` → general quality
258
258
 
259
259
  **Codex CLI (3 in parallel, single vendor):**
260
260
  - Agent 1: `gpt-5.6` @ `xhigh` → security + architecture
@@ -30,7 +30,7 @@ honoured only at `bulk-read` and `delegate` and only while `--free` is on.
30
30
 
31
31
  **Rung names are the contract; model ids are not.** `opus` is a rung here and a
32
32
  model id in `cost-table.json`, and the second can change without anyone editing
33
- a rule. Writing `claude-opus-5` into a rule pins a decision to a string that will
33
+ a rule. Writing `claude-opus-5-5` into a rule pins a decision to a string that will
34
34
  go stale.
35
35
 
36
36
  Set rules with:
@@ -429,14 +429,16 @@ To set up multi-agent on a new machine:
429
429
 
430
430
  All tokens are optional in the sense that every service can be answered with Skip - but the ASKING is not optional: the Step 3 sequential loop still walks every missing service one by one (token → author → host). Phase 0 re-asks at runtime only for tokens the user skipped here.
431
431
 
432
- ### Step 8 - Enforcement hooks (optional, Claude Code)
432
+ ### Step 8 - Enforcement hooks (optional)
433
433
 
434
- Offer to merge `$HOME/.claude/templates/claude-hooks.json`: three `PreToolUse` gates that block on a non-zero exit (secret scan, agent-guard, read-size) plus four hooks that block nothing (`PreCompact`, `SessionEnd`, `SessionStart`, `PostToolUse`). What each does: `$HOME/.claude/multi-agent-refs/picker-contract.md`.
434
+ On Claude Code, offer to merge `$HOME/.claude/templates/claude-hooks.json`: three blocking `PreToolUse` gates (secret scan, agent-guard, read-size) plus four non-blocking hooks (`PreCompact`, `SessionEnd`, `SessionStart`, `PostToolUse`). What each does: `$HOME/.claude/multi-agent-refs/picker-contract.md`.
435
435
 
436
436
  - Ask (picker): "Install the pipeline's hooks into `~/.claude/settings.json`?" Default Yes.
437
- - On Yes, deep-merge EVERY event in the template's `hooks` object, not `PreToolUse` alone - merging one event silently drops the capture hooks, and a run killed before Phase 5 then loses its findings exactly as it did before they existed. Preserve existing hooks; never duplicate a matcher already calling the same script.
438
- - Say what the merge does NOT cover: only the three gates need no run-specific arguments, so only they are hookable; the rest are phase-enforced.
439
- - Say what it does not turn on: the read-size gate is inert until `prefs.global.bulkRead.mode` is set. Recommend `observe` first. Why, and the Phase 3 exemption: `$HOME/.claude/multi-agent-refs/picker-contract.md`.
437
+ - On Yes, deep-merge EVERY event in the template's `hooks` object (merging `PreToolUse` alone drops the capture hooks); keep existing hooks, never duplicate a matcher calling the same script.
438
+ - Say only the three gates are hookable (no run-specific arguments); the rest are phase-enforced.
439
+ - Say the read-size gate stays inert until `prefs.global.bulkRead.mode` is set; recommend `observe` first (why, and the Phase 3 exemption: `picker-contract.md`).
440
+
441
+ Codex: offer only the syntax gate, then run `node "$HOME/.codex/scripts/host-hooks.mjs" install --host codex`. Copilot CLI: offer no hook (it discards `postToolUse` output as of 1.0.45).
440
442
 
441
443
  ### Step 9 - Default stack plugin enablement
442
444
 
@@ -73,7 +73,7 @@ Run every step automatically:
73
73
  ```
74
74
  Step 0: DOCTOR doctor.mjs - exit 2 or 4 stops the sync
75
75
  Step 1.5: DETECT Compare timestamps, find stale targets
76
- Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 65 sub-command skills)
76
+ Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 66 sub-command skills)
77
77
  Step 2b: CODEX Claude Code -> Codex CLI (1 router skill + sub-command specs as refs + agent TOML)
78
78
  Step 3: REPO Claude Code -> pipeline repo (genericized, personal data scrub, bash -n on all sh)
79
79
  Step 3c: PLUGINS pipeline shared/external -> multi-agent-plugins marketplace (rebuild knowledge/,
@@ -241,12 +241,12 @@ same specs as reference files rather than as peer skills, via Step 2b - see
241
241
  |-------------|-------------|
242
242
  | `~/.claude/commands/multi-agent/{cmd}/SKILL.md` | `~/.copilot/skills/multi-agent-{cmd}/SKILL.md` |
243
243
 
244
- **65 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
244
+ **66 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
245
245
 
246
246
  ```
247
247
  analysis, analysis-jira, analysis-resolve, autopilot, autopilot-off, autopilot-on,
248
248
  autopilot-status, bug-bash, build-optimize, channels, complaint-analysis, create-jira,
249
- design-check, diff-explain, doctor, estimate, feedback, forget, garbage-collect, graph, help,
249
+ deps, design-check, diff-explain, doctor, estimate, feedback, forget, garbage-collect, graph, help,
250
250
  ios-coding-standard, issue, jira, kill, language, log,
251
251
  manual-test, model, postmortem, prune-logs, prune-prompts, purge, refactor, research, resume, review, review-analysis, review-issue, review-jira, route-off, route-on, route-status,
252
252
  routines, save, scaffold, scan, scenario-audit, search, security-review, serve, setup, stack, status, steer, store-ready, sync, test,
@@ -11,10 +11,9 @@ allowed-tools: Agent, Bash, Read, Write, Edit, Glob, Grep, TaskCreate, TaskUpdat
11
11
 
12
12
  # /multi-agent:testflight-validation - iOS alias for store-ready
13
13
 
14
- This command is an alias. The implementation it used to carry was merged into
14
+ This command is an alias. The implementation lives in
15
15
  `/multi-agent:store-ready`, which runs the same three gates on iOS and adds the
16
- Android side, so there is one flow to maintain instead of two that had already
17
- started to drift.
16
+ Android side, so there is one flow to maintain instead of two that drift.
18
17
 
19
18
  The name is kept because it is the one people reach for when the target is
20
19
  TestFlight, and removing a command is a breaking change to the slash-command
@@ -85,7 +85,7 @@
85
85
  "out": 1020,
86
86
  "cached": 2700
87
87
  },
88
- "estUsd": 0.0449,
88
+ "estUsd": 0.0353,
89
89
  "unlockedWrites": 0,
90
90
  "runType": "development",
91
91
  "title": null,
@@ -63,7 +63,7 @@
63
63
  "out": 680,
64
64
  "cached": 1800
65
65
  },
66
- "estUsd": 0.0299,
66
+ "estUsd": 0.0236,
67
67
  "unlockedWrites": 0
68
68
  }
69
69
  ]
@@ -106,7 +106,7 @@
106
106
  "out": 2040,
107
107
  "cached": 5400
108
108
  },
109
- "estUsd": 0.0897,
109
+ "estUsd": 0.0707,
110
110
  "unlockedWrites": 0,
111
111
  "runType": "development",
112
112
  "title": null,
@@ -111,7 +111,7 @@
111
111
  "out": 2040,
112
112
  "cached": 5400
113
113
  },
114
- "estUsd": 0.0897,
114
+ "estUsd": 0.0707,
115
115
  "unlockedWrites": 0,
116
116
  "runType": "development",
117
117
  "title": null,
@@ -96,7 +96,7 @@
96
96
  "out": 680,
97
97
  "cached": 1800
98
98
  },
99
- "estUsd": 0.0299,
99
+ "estUsd": 0.0236,
100
100
  "unlockedWrites": 0,
101
101
  "runType": "autopilot",
102
102
  "title": "Fix the crash on the login screen",
@@ -3,11 +3,11 @@
3
3
  #
4
4
  # WHY THIS EXISTS
5
5
  #
6
- # A run asked the user to paste a Crashlytics stack trace by hand while a Firebase
7
- # service-account JSON sat in the Keychain, mapped as `firebase`, fully valid. Nothing
8
- # was broken - the pipeline simply never asked itself "do I already hold a credential
9
- # that answers this?" before asking the user. From the user's side that is worse than a
10
- # failure: they had supplied the key precisely so this would not happen.
6
+ # A run that asks the user to paste a Crashlytics stack trace by hand while a valid
7
+ # Firebase service-account JSON sits in the Keychain, mapped as `firebase`, has failed
8
+ # even though nothing is broken. From the user's side that is worse than a failure: they
9
+ # supplied the key precisely so this would not happen. So the pipeline asks itself "do I
10
+ # already hold a credential that answers this?" before it asks the user.
11
11
  #
12
12
  # One command, so there is no excuse for asking blind. Run it before any question that
13
13
  # requests data an external system holds, and let the answer shape the question:
@@ -214,15 +214,13 @@ if [ -z "$SA_JSON_RAW" ]; then
214
214
  fi
215
215
 
216
216
  # The credential store returns the JSON as stored - encoding is its internal
217
- # business, not this script's. The base64 attempt that used to live here was
218
- # guesswork that never fired: what actually broke the fetcher was the store
219
- # returning bare hex for any multi-line value, which is neither base64-of-JSON
220
- # nor JSON, so both branches failed and the failure surfaced three layers later
221
- # as a token-exchange error.
217
+ # business, not this script's, so nothing here guesses at a decoding. A guess
218
+ # that misfires (bare hex read as base64, say) fails three layers later as a
219
+ # token-exchange error instead of here.
222
220
  #
223
- # With no decode step there is nothing to stage on disk, so the temp file and its
224
- # cleanup traps went with it: the service-account JSON now only ever exists in a
225
- # shell variable, which is one fewer place a GCP private key can be left behind.
221
+ # With no decode step there is nothing to stage on disk, so there is no temp file
222
+ # and no cleanup trap: the service-account JSON only ever exists in a shell
223
+ # variable, which is one fewer place a GCP private key can be left behind.
226
224
  SA_JSON="$SA_JSON_RAW"
227
225
 
228
226
  # Verify project_id from SA JSON matches the URL - protects against pasting a
@@ -69,7 +69,7 @@ host_note() {
69
69
  echo "Claude Code: the fable rung is Fable 5. This switch is live here."
70
70
  ;;
71
71
  copilot)
72
- echo "Copilot CLI: Fable 5 is not offered; its personas never sat on this rung."
72
+ echo "Copilot CLI: Fable is not offered; its personas never sat on this rung."
73
73
  echo "This setting is recorded but changes nothing on this host."
74
74
  ;;
75
75
  codex)
@@ -13,6 +13,7 @@
13
13
  # Subcommands:
14
14
  # init <task-id> [title] Initialize with empty todos[] (Phase 2 usually pipes the JSON instead)
15
15
  # set <task-id> <plan-json> Replace the whole plan from a JSON blob on stdin or arg
16
+ # (exit 2 when a dep names no todo id exactly)
16
17
  # start <task-id> <todo-id> Mark in_progress, set startedAt; refuses if deps unsatisfied
17
18
  # or the todo awaits re-plan confirmation
18
19
  # complete <task-id> <todo-id> [notes] [--no-tests <reason>]
@@ -84,10 +85,9 @@ resolve_state() {
84
85
  local task_id="$1"
85
86
  local found=""
86
87
  # multi-repo-pipeline.sh writes agent-state.json under a per-project
87
- # directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/) - not the
88
- # ~/.claude/projects/*/state/ path this used to glob, which no writer in
89
- # the pipeline ever populates.
90
- # This globbed ONLY the nested layout; flat-layout runs resolved to nothing.
88
+ # directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/), other
89
+ # writers write it flat; no writer uses ~/.claude/projects/*/state/.
90
+ # ma_resolve_run_file checks both layouts.
91
91
  found="$(ma_resolve_run_file "$task_id" agent-state.json 2>/dev/null || true)"
92
92
  if [ -z "$found" ]; then
93
93
  echo "plan-todos: agent-state.json not found for task=$task_id" >&2
@@ -155,6 +155,23 @@ do_set() {
155
155
  echo "plan-todos: every todo must be an object with string .id and .task fields" >&2
156
156
  exit 2
157
157
  fi
158
+ # Every dependency must name a todo exactly. Ids are compared as written, so
159
+ # `t1` against a todo `T1` would leave the dependent blocked for good; the
160
+ # error names the id that differs only by case.
161
+ local bad_deps
162
+ bad_deps=$(jq -r '
163
+ [.todos[].id] as $ids
164
+ | [.todos[] | .id as $owner | (.deps // [])[]
165
+ | select((type != "string") or (. as $d | $ids | index($d) | not))
166
+ | . as $d
167
+ | ($ids | map(select(($d | type) == "string" and ascii_downcase == ($d | ascii_downcase)))) as $near
168
+ | "\($owner) depends on \($d | tojson), which is no todo id"
169
+ + (if ($near | length) > 0 then " (did you mean \($near | map(tojson) | join(", "))?)" else "" end)]
170
+ | .[]' <<<"$plan_blob" 2>/dev/null)
171
+ if [ -n "$bad_deps" ]; then
172
+ printf 'plan-todos: %s\n' "$bad_deps" >&2
173
+ exit 2
174
+ fi
158
175
  local state_path; state_path=$(resolve_state "$task_id")
159
176
  local now; now=$(iso_now)
160
177
  local new
@@ -107,12 +107,10 @@ if [ -z "$TASK_ID" ]; then
107
107
  fi
108
108
 
109
109
  # Resolve agent-state.json. multi-repo-pipeline.sh writes it under a
110
- # per-project directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/)
111
- # - not the ~/.claude/projects/*/state/ path this used to glob, which no
112
- # writer in the pipeline ever populates.
113
- # This globbed ONLY the nested layout. phase-tracker.sh and most Phase 0 paths
114
- # write flat, so on a real machine the majority of runs resolved to nothing and
115
- # this script reported "no agent-state.json" for a run whose state existed.
110
+ # per-project directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/),
111
+ # while phase-tracker.sh and most Phase 0 paths write it flat; no writer uses
112
+ # ~/.claude/projects/*/state/. ma_resolve_run_file checks both layouts, so a
113
+ # run whose state exists is always found.
116
114
  AGENT_STATE="$(ma_resolve_run_file "$TASK_ID" agent-state.json 2>/dev/null || true)"
117
115
 
118
116
  if [ -z "$AGENT_STATE" ]; then
@@ -77,7 +77,7 @@ free_savings() {
77
77
  since=$(( $(date +%s) - 7 * 86400 ))
78
78
  jq -rs --argjson since "$since" --slurpfile ct "$COST_TABLE" '
79
79
  map(select((.ts | sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601? // 0) >= $since))
80
- | ($ct[0].prices.haiku // {inPerMtok: 1, outPerMtok: 5}) as $h
80
+ | ($ct[0].prices.haiku // {inPerMtok: 0.5, outPerMtok: 2.5}) as $h
81
81
  | {
82
82
  n: length,
83
83
  ok: (map(select(.ok and (.cached | not))) | length),
@@ -227,11 +227,10 @@ do_prune() {
227
227
  printf 'shadow-git: pruned shadow repo for %s\n' "$task_id" >&2
228
228
  else
229
229
  [ ! -d "$sd/.git" ] && { err "no shadow for $task_id"; exit 1; }
230
- # Retention never touches a real work-tree: this used to run
231
- # `reset --hard` with --work-tree=$(pwd), silently rewriting whatever
232
- # directory prune happened to be invoked from, and it kept the OLDEST
233
- # commit inside the window while discarding every newer one - backwards
234
- # for a "keep the last N days" policy. Every git call below is
230
+ # Retention never touches a real work-tree: a `reset --hard` with
231
+ # --work-tree=$(pwd) would silently rewrite whatever directory prune is
232
+ # invoked from. It keeps every commit inside the window and drops the
233
+ # older ones, as a "keep the last N days" policy requires. Every git call below is
235
234
  # git-dir-only plumbing (log/rev-parse/commit-tree/update-ref/gc - no
236
235
  # checkout, no reset, no clean); the one --work-tree `sg` still wants
237
236
  # points at $sd itself, which prune already owns.
@@ -1,7 +1,7 @@
1
1
  # Cross-CLI Contract (Claude Code · Copilot CLI · Codex CLI)
2
2
 
3
3
  <!-- toc -->
4
- - [1. Command Inventory (65 commands)](#1-command-inventory-65-commands)
4
+ - [1. Command Inventory (66 commands)](#1-command-inventory-66-commands)
5
5
  - [2. Canonical Placeholder Vocabulary](#2-canonical-placeholder-vocabulary)
6
6
  - [2.6 Intentional structural divergence - thin dispatcher vs inlined orchestrator](#26-intentional-structural-divergence---thin-dispatcher-vs-inlined-orchestrator)
7
7
  - [2.7 One command, three different meanings: `/multi-agent:model`](#27-one-command-three-different-meanings-multi-agentmodel)
@@ -20,12 +20,12 @@
20
20
 
21
21
  ---
22
22
 
23
- ## 1. Command Inventory (65 commands)
23
+ ## 1. Command Inventory (66 commands)
24
24
 
25
25
  ```
26
26
  analysis, analysis-jira, analysis-resolve, autopilot, autopilot-off,
27
27
  autopilot-on, autopilot-status, bug-bash, build-optimize, channels, complaint-analysis,
28
- create-jira, design-check, diff-explain, doctor, estimate, feedback, forget,
28
+ create-jira, deps, design-check, diff-explain, doctor, estimate, feedback, forget,
29
29
  garbage-collect, graph, help, ios-coding-standard, issue, jira, kill,
30
30
  language, log, manual-test, model, postmortem, prune-logs,
31
31
  prune-prompts, purge, refactor, research, resume, review,
@@ -40,7 +40,7 @@ Categories:
40
40
 
41
41
  - **Interactive pickers** (single-purpose, not modes): `jira`, `issue`
42
42
  - **Issue generator** (one-shot, no worktree, asks type Task/Bug/Story, hard approval gate before create): `create-jira`
43
- - **Pipeline entries**: `autopilot` (plus the bare `/multi-agent` in the dispatcher). The phase set is a property of the command, and every mode runs its whole set.
43
+ - **Pipeline entries**: `autopilot`, `deps` (plus the bare `/multi-agent` in the dispatcher). The phase set is a property of the command, and every mode runs its whole set.
44
44
  - **Tail path** (the pipeline tail over work already on a branch, no run behind it): `resume`
45
45
  - **Ops commands** (one-shot, no worktree): `status`, `log`, `kill`, `steer`, `purge`, `uninstall`, `resume`, `review`, `review-jira`, `review-issue`, `analysis`, `analysis-resolve`, `research`, `scaffold`, `complaint-analysis`, `bug-bash`, `build-optimize`, `channels`, `scan`, `search`, `diff-explain`, `garbage-collect`, `graph`, `postmortem`, `prune-logs`, `prune-prompts`, `serve`
46
46
  - **Local audits** (worktree only to build; no commit, push, PR or channels): `design-check`, `testflight-validation`, `ios-coding-standard`. `testflight-validation` additionally never invokes `altool --upload-app` - a validation run must not be able to ship a build by accident.
@@ -262,8 +262,8 @@ that quietly no-ops is worse than one that says it cannot act.
262
262
 
263
263
  | Host | What `model on|off` does | Why |
264
264
  |---|---|---|
265
- | Claude Code | live switch: flips `modelFallback.fableEnabled` and realigns `costBudget.pricingModel` | the only host where the `fable` rung is Fable 5 |
266
- | Copilot CLI | writes the preference, changes no dispatch | Fable 5 is not offered there; its personas never sat on this rung |
265
+ | Claude Code | live switch: flips `modelFallback.fableEnabled` and realigns `costBudget.pricingModel` | the only host where the `fable` rung is Fable 5.1 |
266
+ | Copilot CLI | writes the preference, changes no dispatch | Fable is not offered there; its personas never sat on this rung |
267
267
  | Codex CLI | writes the preference, changes no dispatch, **deliberately** | the `fable` rung on Codex means `gpt-5.6 @ xhigh` - a different model on a different account. A knob named after an Anthropic model must not silently retune a Codex run |
268
268
 
269
269
  `model-rung.sh` prints which of the three it is on every invocation. The rule it
@@ -440,9 +440,8 @@ what keeps a secret off argv and what writes the audit entry.
440
440
  something that is not a timestamp.
441
441
  - `sed -E`, never `-r`; `sed -i ''` when editing in place.
442
442
 
443
- Clipboard callers may use `pbpaste` / `pbcopy` directly; the three-way
444
- `command -v` guard they used to carry was for hosts this package no longer
445
- installs on.
443
+ Clipboard callers may use `pbpaste` / `pbcopy` directly; a `command -v` guard
444
+ for other clipboard tools would serve hosts this package does not install on.
446
445
 
447
446
  ---
448
447
 
@@ -46,6 +46,10 @@ discovered later:
46
46
  - A host with no such transcripts (Copilot, Codex) reports `UNMEASURED`, never
47
47
  zero. Zero would read as "you spent nothing" when it means "I could not see".
48
48
 
49
+ A record is priced by its exact wire ID: a rung's `modelId`, a Copilot CLI
50
+ `copilotModelId` at its own model's rate from the table's `models` map, or a
51
+ previous-generation ID in the table's `legacy` map at that ID's own rate. A
52
+ dated snapshot (`<id>-<date>`) prices as the longest known ID it extends.
49
53
  Records whose model is not in `cost-table.json` are COUNTED as unpriced and left
50
54
  out of the total. Folding them in at zero would make an unknown model look free,
51
55
  which is the direction that hurts.