@mmerterden/multi-agent-pipeline 17.6.0 → 19.0.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 (272) hide show
  1. package/CHANGELOG.md +310 -0
  2. package/README.md +76 -18
  3. package/README.tr.md +55 -16
  4. package/docs/adr/0002-instruction-driven-flag.md +1 -0
  5. package/docs/adr/0005-lazy-phase-docs.md +11 -1
  6. package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +1 -0
  7. package/docs/adr/0010-own-code-graph.md +1 -0
  8. package/docs/adr/0011-dormant-ci.md +25 -1
  9. package/docs/adr/0014-six-phase-consolidation.md +134 -0
  10. package/docs/adr/README.md +2 -1
  11. package/docs/architecture.md +37 -38
  12. package/docs/best-practices.md +1 -1
  13. package/docs/ecosystem.md +37 -26
  14. package/docs/engineering.md +1 -1
  15. package/docs/facts.json +45 -0
  16. package/docs/features.md +54 -53
  17. package/docs/performance.md +5 -5
  18. package/docs/recovery-guide.md +9 -9
  19. package/docs/server-readiness.md +188 -0
  20. package/docs/token-budget-history.md +3 -1
  21. package/index.js +18 -3
  22. package/install/_codex-agents.mjs +1 -1
  23. package/install/_common.mjs +42 -17
  24. package/install/_dev-only-files.mjs +8 -0
  25. package/install/_unattended-profile.mjs +113 -0
  26. package/install/index.mjs +48 -0
  27. package/install/templates/claude-hooks.json +1 -1
  28. package/install/templates/codex-instructions.md +1 -1
  29. package/install/templates/copilot-instructions.md +28 -28
  30. package/manifest.json +1065 -0
  31. package/package.json +6 -3
  32. package/pipeline/agents/dev-critic.md +3 -3
  33. package/pipeline/commands/figma-to-swiftui.md +1 -1
  34. package/pipeline/commands/multi-agent/SKILL.md +8 -8
  35. package/pipeline/commands/multi-agent/analysis/SKILL.md +9 -9
  36. package/pipeline/commands/multi-agent/autopilot/SKILL.md +7 -7
  37. package/pipeline/commands/multi-agent/channels/SKILL.md +15 -15
  38. package/pipeline/commands/multi-agent/diff-explain/SKILL.md +6 -6
  39. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
  40. package/pipeline/commands/multi-agent/graph/SKILL.md +1 -1
  41. package/pipeline/commands/multi-agent/help/SKILL.md +62 -62
  42. package/pipeline/commands/multi-agent/language/SKILL.md +2 -2
  43. package/pipeline/commands/multi-agent/local/SKILL.md +11 -11
  44. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +13 -13
  45. package/pipeline/commands/multi-agent/log/SKILL.md +2 -2
  46. package/pipeline/commands/multi-agent/manual-test/SKILL.md +9 -9
  47. package/pipeline/commands/multi-agent/model/SKILL.md +69 -0
  48. package/pipeline/commands/multi-agent/refactor/SKILL.md +3 -3
  49. package/pipeline/commands/multi-agent/resume/SKILL.md +4 -4
  50. package/pipeline/commands/multi-agent/resume-local/SKILL.md +19 -17
  51. package/pipeline/commands/multi-agent/review/SKILL.md +1 -1
  52. package/pipeline/commands/multi-agent/route-off/SKILL.md +36 -0
  53. package/pipeline/commands/multi-agent/route-on/SKILL.md +74 -0
  54. package/pipeline/commands/multi-agent/route-status/SKILL.md +56 -0
  55. package/pipeline/commands/multi-agent/setup/SKILL.md +2 -2
  56. package/pipeline/commands/multi-agent/status/SKILL.md +54 -23
  57. package/pipeline/commands/multi-agent/steer/SKILL.md +2 -2
  58. package/pipeline/commands/multi-agent/sync/SKILL.md +12 -13
  59. package/pipeline/commands/multi-agent/test/SKILL.md +1 -1
  60. package/pipeline/lib/_jira-auth.sh +8 -0
  61. package/pipeline/lib/analysis-jira-write.sh +32 -0
  62. package/pipeline/lib/ask-choice.sh +13 -2
  63. package/pipeline/lib/autopilot-state.sh +8 -0
  64. package/pipeline/lib/credential-inventory.sh +1 -1
  65. package/pipeline/lib/fatal.mjs +129 -0
  66. package/pipeline/lib/fetch-fortify.sh +1 -1
  67. package/pipeline/lib/figma-mcp-refresh.sh +18 -0
  68. package/pipeline/lib/figma-screenshot.sh +18 -0
  69. package/pipeline/lib/invoked-directly.mjs +43 -0
  70. package/pipeline/lib/jira-publish.sh +42 -0
  71. package/pipeline/lib/md2confluence-v3.py +47 -0
  72. package/pipeline/lib/model-rung.sh +142 -0
  73. package/pipeline/lib/outbound-gate.mjs +175 -0
  74. package/pipeline/lib/phase-schema.mjs +88 -0
  75. package/pipeline/lib/plan-todos.sh +32 -11
  76. package/pipeline/lib/post-pr-review.sh +77 -8
  77. package/pipeline/lib/repo-hygiene.sh +8 -3
  78. package/pipeline/lib/require-jq.sh +40 -0
  79. package/pipeline/lib/route-state.sh +161 -0
  80. package/pipeline/lib/run-paths.sh +335 -0
  81. package/pipeline/multi-agent-refs/_account-picker.md +1 -1
  82. package/pipeline/multi-agent-refs/_dev-context.md +1 -1
  83. package/pipeline/multi-agent-refs/_input-parser.md +1 -1
  84. package/pipeline/multi-agent-refs/analysis/evidence.md +0 -9
  85. package/pipeline/multi-agent-refs/analysis/intake.md +1 -1
  86. package/pipeline/multi-agent-refs/analysis/locked.md +21 -22
  87. package/pipeline/multi-agent-refs/analysis/render.md +1 -1
  88. package/pipeline/multi-agent-refs/analysis/synthesis.md +12 -6
  89. package/pipeline/multi-agent-refs/android-guide.md +1 -1
  90. package/pipeline/multi-agent-refs/audit-guide.md +13 -13
  91. package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
  92. package/pipeline/multi-agent-refs/channels/jira.md +3 -3
  93. package/pipeline/multi-agent-refs/channels/pr.md +4 -4
  94. package/pipeline/multi-agent-refs/channels/wiki.md +1 -1
  95. package/pipeline/multi-agent-refs/component-dispatch.md +3 -3
  96. package/pipeline/multi-agent-refs/cross-cli-contract.md +31 -6
  97. package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +74 -4
  98. package/pipeline/multi-agent-refs/features/code-graph.md +5 -5
  99. package/pipeline/multi-agent-refs/features/cost-analysis.md +93 -0
  100. package/pipeline/multi-agent-refs/features/design-conformance.md +1 -1
  101. package/pipeline/multi-agent-refs/features/dev-critic.md +3 -3
  102. package/pipeline/multi-agent-refs/features/doctor.md +47 -2
  103. package/pipeline/multi-agent-refs/features/external-context-injection.md +3 -3
  104. package/pipeline/multi-agent-refs/features/maturity-followup.md +3 -3
  105. package/pipeline/multi-agent-refs/features/model-fallback.md +5 -5
  106. package/pipeline/multi-agent-refs/features/plan-todos.md +1 -1
  107. package/pipeline/multi-agent-refs/features/repo-map.md +1 -1
  108. package/pipeline/multi-agent-refs/features/review-delta.md +3 -3
  109. package/pipeline/multi-agent-refs/features/review-multi-repo.md +1 -1
  110. package/pipeline/multi-agent-refs/features/scope-check.md +4 -4
  111. package/pipeline/multi-agent-refs/features/skill-conformance.md +2 -2
  112. package/pipeline/multi-agent-refs/features/stack-skill-routing.md +1 -1
  113. package/pipeline/multi-agent-refs/features/verify-by-test.md +4 -4
  114. package/pipeline/multi-agent-refs/features/verify.md +83 -0
  115. package/pipeline/multi-agent-refs/features/visual-evidence.md +19 -19
  116. package/pipeline/multi-agent-refs/features/worktree-finalize.md +6 -6
  117. package/pipeline/multi-agent-refs/issue-jira-triad.md +10 -10
  118. package/pipeline/multi-agent-refs/knowledge.md +11 -11
  119. package/pipeline/multi-agent-refs/multi-repo-integration-build.md +13 -13
  120. package/pipeline/multi-agent-refs/payload-contracts.md +8 -8
  121. package/pipeline/multi-agent-refs/phases/log-format.md +10 -10
  122. package/pipeline/multi-agent-refs/phases/modes.md +30 -30
  123. package/pipeline/multi-agent-refs/phases/operations.md +21 -10
  124. package/pipeline/multi-agent-refs/phases/phase-0-init.md +25 -25
  125. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +599 -0
  126. package/pipeline/multi-agent-refs/phases/{phase-3-dev.md → phase-2-dev.md} +129 -49
  127. package/pipeline/multi-agent-refs/phases/{phase-4-review.md → phase-3-review.md} +225 -107
  128. package/pipeline/multi-agent-refs/phases/{phase-6-commit.md → phase-4-commit.md} +23 -23
  129. package/pipeline/multi-agent-refs/phases/{phase-7-report.md → phase-5-report.md} +29 -29
  130. package/pipeline/multi-agent-refs/phases.md +44 -48
  131. package/pipeline/multi-agent-refs/picker-contract.md +1 -1
  132. package/pipeline/multi-agent-refs/progress-contract.md +6 -6
  133. package/pipeline/multi-agent-refs/readiness-review.md +1 -1
  134. package/pipeline/multi-agent-refs/rules.md +7 -7
  135. package/pipeline/multi-agent-refs/swiftui-guide.md +2 -2
  136. package/pipeline/multi-agent-refs/tracker-contract.md +31 -32
  137. package/pipeline/multi-agent-refs/unattended-contract.md +129 -0
  138. package/pipeline/multi-agent-refs/wiki-capture.md +14 -14
  139. package/pipeline/preferences-template.json +9 -1
  140. package/pipeline/rules/outside-the-pipeline.md +1 -1
  141. package/pipeline/schemas/agent-state.schema.json +50 -50
  142. package/pipeline/schemas/analysis-output.schema.json +2 -2
  143. package/pipeline/schemas/autopilot-config.schema.json +1 -1
  144. package/pipeline/schemas/code-graph.schema.json +1 -1
  145. package/pipeline/schemas/criteria-manifest.schema.json +1 -1
  146. package/pipeline/schemas/dev-critic-output.schema.json +1 -1
  147. package/pipeline/schemas/diff-risk.schema.json +1 -1
  148. package/pipeline/schemas/migrations/prefs-2.4.0-to-2.5.0.mjs +2 -2
  149. package/pipeline/schemas/migrations/prefs-2.6.0-to-2.7.0.mjs +31 -0
  150. package/pipeline/schemas/migrations/state-2.1.0-to-2.2.0.mjs +129 -0
  151. package/pipeline/schemas/phases.json +105 -0
  152. package/pipeline/schemas/plan-todos.schema.json +5 -5
  153. package/pipeline/schemas/planning-output.schema.json +1 -1
  154. package/pipeline/schemas/prefs.schema.json +100 -56
  155. package/pipeline/schemas/reviewer-output.schema.json +3 -3
  156. package/pipeline/schemas/route-config.schema.json +74 -0
  157. package/pipeline/schemas/scope-check.schema.json +1 -1
  158. package/pipeline/schemas/test-gap.schema.json +1 -1
  159. package/pipeline/schemas/token-budget.json +12 -18
  160. package/pipeline/schemas/triage-output.schema.json +6 -6
  161. package/pipeline/scripts/README.md +3 -3
  162. package/pipeline/scripts/_code-graph.mjs +2 -2
  163. package/pipeline/scripts/_run-paths.mjs +372 -0
  164. package/pipeline/scripts/_smoke-root.sh +1 -1
  165. package/pipeline/scripts/aggregate-metrics.mjs +65 -65
  166. package/pipeline/scripts/autopilot-arming.mjs +2 -1
  167. package/pipeline/scripts/autopilot-intake.mjs +2 -1
  168. package/pipeline/scripts/autopilot-runner.mjs +206 -2
  169. package/pipeline/scripts/build-references.mjs +2 -1
  170. package/pipeline/scripts/build-stack-plugins.mjs +10 -2
  171. package/pipeline/scripts/capture-evidence.sh +7 -2
  172. package/pipeline/scripts/capture-flush.sh +8 -8
  173. package/pipeline/scripts/capture-resume.sh +3 -3
  174. package/pipeline/scripts/classify-plan-safety.mjs +3 -2
  175. package/pipeline/scripts/cost-analyze.mjs +600 -0
  176. package/pipeline/scripts/cost-budget-check.mjs +4 -12
  177. package/pipeline/scripts/council-view.mjs +2 -1
  178. package/pipeline/scripts/crush-json.mjs +2 -1
  179. package/pipeline/scripts/diff-explain.mjs +7 -10
  180. package/pipeline/scripts/diff-risk-score.mjs +2 -1
  181. package/pipeline/scripts/doctor.mjs +140 -6
  182. package/pipeline/scripts/evidence-gate.mjs +9 -3
  183. package/pipeline/scripts/feedback-send.mjs +12 -2
  184. package/pipeline/scripts/gc-abandoned.sh +32 -16
  185. package/pipeline/scripts/gc-tmp.sh +1 -1
  186. package/pipeline/scripts/gc-worktrees.sh +12 -5
  187. package/pipeline/scripts/gen-facts.mjs +175 -0
  188. package/pipeline/scripts/gen-mode-dispatch.mjs +32 -37
  189. package/pipeline/scripts/gen-ref-toc.mjs +1 -1
  190. package/pipeline/scripts/github-ssh-setup.sh +64 -7
  191. package/pipeline/scripts/graph-mermaid.mjs +4 -2
  192. package/pipeline/scripts/graph-report.mjs +1 -1
  193. package/pipeline/scripts/jira-attach.sh +1 -1
  194. package/pipeline/scripts/keychain-save.sh +101 -30
  195. package/pipeline/scripts/learn-from-transcripts.mjs +3 -2
  196. package/pipeline/scripts/learning-curve.mjs +36 -31
  197. package/pipeline/scripts/log-metric.sh +17 -4
  198. package/pipeline/scripts/make-manifest.mjs +199 -0
  199. package/pipeline/scripts/memory-save.sh +1 -1
  200. package/pipeline/scripts/migrate-prefs.mjs +24 -6
  201. package/pipeline/scripts/migrate-state.mjs +94 -4
  202. package/pipeline/scripts/phase-banner.sh +26 -22
  203. package/pipeline/scripts/phase-tracker.sh +48 -10
  204. package/pipeline/scripts/plan-coverage-gate.mjs +8 -4
  205. package/pipeline/scripts/pre-commit-check.sh +7 -0
  206. package/pipeline/scripts/pre-push-check.sh +7 -0
  207. package/pipeline/scripts/purge.sh +23 -6
  208. package/pipeline/scripts/render-agent-log-cost.sh +10 -3
  209. package/pipeline/scripts/render-cost-summary.sh +9 -2
  210. package/pipeline/scripts/render-work-summary.sh +14 -7
  211. package/pipeline/scripts/review-file-filter.mjs +5 -3
  212. package/pipeline/scripts/review-scope.mjs +2 -1
  213. package/pipeline/scripts/routine-registry.mjs +2 -1
  214. package/pipeline/scripts/run-aggregator.mjs +26 -20
  215. package/pipeline/scripts/run-metrics.mjs +4 -2
  216. package/pipeline/scripts/runs-index.mjs +353 -0
  217. package/pipeline/scripts/scorecard-snapshot.mjs +178 -0
  218. package/pipeline/scripts/search-logs.sh +18 -0
  219. package/pipeline/scripts/smoke-cross-cli-behavior.sh +6 -6
  220. package/pipeline/scripts/smoke-schema-validation.sh +26 -7
  221. package/pipeline/scripts/test-gap-scan.mjs +2 -1
  222. package/pipeline/scripts/test-integrity-gate.mjs +2 -1
  223. package/pipeline/scripts/token-budget-report.mjs +13 -2
  224. package/pipeline/scripts/triage-memory.mjs +2 -2
  225. package/pipeline/scripts/update-issue-progress.sh +56 -7
  226. package/pipeline/scripts/usage-report.mjs +12 -1
  227. package/pipeline/scripts/validate-analysis-doc.mjs +75 -18
  228. package/pipeline/scripts/validate-code-graph.mjs +6 -3
  229. package/pipeline/scripts/validate-complaint-doc.mjs +2 -1
  230. package/pipeline/scripts/validate-diff-risk.mjs +6 -3
  231. package/pipeline/scripts/validate-planning.mjs +1 -1
  232. package/pipeline/scripts/validate-reviewer.mjs +1 -1
  233. package/pipeline/scripts/validate-state.mjs +45 -5
  234. package/pipeline/scripts/validate-test-gap.mjs +6 -3
  235. package/pipeline/scripts/validate-triage.mjs +6 -4
  236. package/pipeline/scripts/verify-citations.mjs +4 -2
  237. package/pipeline/scripts/verify.mjs +327 -0
  238. package/pipeline/scripts/worktree-finalize.sh +18 -9
  239. package/pipeline/scripts/write-state.mjs +154 -15
  240. package/pipeline/skills/.skill-manifest.json +37 -21
  241. package/pipeline/skills/.skills-index.json +104 -5
  242. package/pipeline/skills/shared/README.md +15 -6
  243. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +2 -2
  244. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +2 -2
  245. package/pipeline/skills/shared/core/multi-agent/SKILL.md +69 -71
  246. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +3 -3
  247. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +14 -14
  248. package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +5 -5
  249. package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +1 -1
  250. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +25 -23
  251. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +2 -2
  252. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +2 -2
  253. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +8 -8
  254. package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +6 -6
  255. package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +71 -0
  256. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +3 -3
  257. package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +1 -1
  258. package/pipeline/skills/shared/core/multi-agent-resume-local/SKILL.md +7 -7
  259. package/pipeline/skills/shared/core/multi-agent-route-off/SKILL.md +39 -0
  260. package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +76 -0
  261. package/pipeline/skills/shared/core/multi-agent-route-status/SKILL.md +59 -0
  262. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +1 -1
  263. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +35 -11
  264. package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +2 -2
  265. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +6 -5
  266. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/package_app.sh +4 -1
  267. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/setup_dev_signing.sh +4 -1
  268. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/sign-and-notarize.sh +2 -1
  269. package/pipeline/skills/skills-index.md +13 -4
  270. package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +0 -263
  271. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +0 -344
  272. package/pipeline/multi-agent-refs/phases/phase-5-test.md +0 -182
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @file phase-schema.mjs - read a phase number that may have been written
3
+ * under either phase vocabulary.
4
+ *
5
+ * metrics.jsonl is append-only and v19.0.0 renumbered the phases, so the same
6
+ * field means two different things depending on when the line was written:
7
+ * `phase: 3` is Dev in a pre-v19 row and Review in a post-v19 one. Rows from
8
+ * v19.0.0 on carry `phaseSchema: 2`; rows without the field are generation 1.
9
+ *
10
+ * Every aggregator that compares a phase number to a literal, or prints a
11
+ * phase name, goes through here. Comparing directly is the bug this module
12
+ * exists to prevent - it does not throw, it silently attributes work to the
13
+ * wrong phase and the roll-up still looks plausible.
14
+ *
15
+ * @module pipeline/lib/phase-schema
16
+ */
17
+
18
+ import { readFileSync } from "node:fs";
19
+
20
+ const CONTRACT = JSON.parse(
21
+ readFileSync(new URL("../schemas/phases.json", import.meta.url), "utf8"),
22
+ );
23
+
24
+ export const CURRENT_SCHEMA = CONTRACT.phaseSchema;
25
+
26
+ /** Generation 1 number -> current number, derived from each phase's `was`. */
27
+ const GEN1_TO_CURRENT = new Map();
28
+ for (const p of CONTRACT.phases) {
29
+ for (const old of p.was) GEN1_TO_CURRENT.set(Number(old), p.id);
30
+ }
31
+
32
+ const CURRENT_NAMES = new Map(CONTRACT.phases.map((p) => [p.id, p.name]));
33
+ const GEN1_NAMES = new Map(
34
+ Object.entries(CONTRACT.legacyPhaseNames)
35
+ .filter(([k]) => /^\d+$/.test(k))
36
+ .map(([k, v]) => [Number(k), v]),
37
+ );
38
+
39
+ /**
40
+ * Which vocabulary a metrics row was written under.
41
+ *
42
+ * @param {object} row - a parsed metrics.jsonl line
43
+ * @returns {number} - 1 or 2
44
+ */
45
+ export function schemaOf(row) {
46
+ const s = Number(row?.phaseSchema);
47
+ return Number.isInteger(s) && s > 0 ? s : 1;
48
+ }
49
+
50
+ /**
51
+ * A row's phase expressed in the CURRENT vocabulary, so rows from both
52
+ * generations can be counted together.
53
+ *
54
+ * @param {string|number} phase
55
+ * @param {number} schema - from schemaOf()
56
+ * @returns {number|null} - null when the value is not a phase number at all
57
+ */
58
+ export function toCurrentPhase(phase, schema) {
59
+ const n = Number(phase);
60
+ if (!Number.isInteger(n)) return null;
61
+ if (schema >= CURRENT_SCHEMA) return CURRENT_NAMES.has(n) ? n : null;
62
+ return GEN1_TO_CURRENT.has(n) ? GEN1_TO_CURRENT.get(n) : null;
63
+ }
64
+
65
+ /**
66
+ * The name the phase number carried WHEN IT WAS WRITTEN. A historical row is
67
+ * labelled with its own vocabulary's name; relabelling it with today's name
68
+ * would make the archive disagree with the log it came from.
69
+ *
70
+ * @param {string|number} phase
71
+ * @param {number} schema
72
+ * @returns {string}
73
+ */
74
+ export function phaseNameAsWritten(phase, schema) {
75
+ const n = Number(phase);
76
+ const table = schema >= CURRENT_SCHEMA ? CURRENT_NAMES : GEN1_NAMES;
77
+ return table.get(n) ?? `Phase ${phase}`;
78
+ }
79
+
80
+ /**
81
+ * Convenience for the common shape: a row in, its current-vocabulary phase out.
82
+ *
83
+ * @param {object} row
84
+ * @returns {number|null}
85
+ */
86
+ export function rowPhase(row) {
87
+ return toCurrentPhase(row?.phase, schemaOf(row));
88
+ }
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env bash
2
2
  #
3
- # plan-todos.sh - manage the Phase 2 plan as a live Todo list.
3
+ # plan-todos.sh - manage the Phase 1 plan as a live Todo list.
4
4
  #
5
- # The Phase 2 plan is broken into a live, reviewable Todo list that updates
5
+ # The Phase 1 plan is broken into a live, reviewable Todo list that updates
6
6
  # step-by-step as Phase 3 works through it.
7
7
  #
8
8
  # State lives in `agent-state.json` under `.plan.todos[]` per
9
9
  # pipeline/schemas/plan-todos.schema.json. Phase 2 (Planning) emits the
10
10
  # initial plan; Phase 3 (Dev) iterates step-by-step; Phase 4 (Review)
11
- # inspects completion; Phase 7 (Report) renders the rollup.
11
+ # inspects completion; Phase 5 (Report) renders the rollup.
12
12
  #
13
13
  # Subcommands:
14
14
  # init <task-id> [title] Initialize with empty todos[] (Phase 2 usually pipes the JSON instead)
@@ -19,7 +19,7 @@
19
19
  # skip <task-id> <todo-id> <reason> Mark skipped (counts as satisfied for dependents)
20
20
  # next <task-id> Print next pending todo (deps-respecting); empty if none
21
21
  # list <task-id> Render Markdown checklist
22
- # status <task-id> Print one-line summary: "3/7 done, 1 in progress"
22
+ # status <task-id> Print one-line summary: "2/5 done, 1 in progress"
23
23
  # show <task-id> Print the full plan JSON
24
24
  #
25
25
  # Exit codes:
@@ -30,6 +30,31 @@
30
30
 
31
31
  set -euo pipefail
32
32
 
33
+ # jq is not optional on this path. Without the guard below a missing binary
34
+ # renders as EMPTY DATA and the work continues on it; see lib/require-jq.sh.
35
+ for _rq in "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/require-jq.sh" \
36
+ "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/../lib" 2>/dev/null && pwd)/require-jq.sh" \
37
+ "$HOME/.claude/lib/require-jq.sh" \
38
+ "$HOME/.copilot/lib/require-jq.sh" \
39
+ "$HOME/.codex/lib/require-jq.sh"; do
40
+ [ -f "$_rq" ] || continue
41
+ # shellcheck source=/dev/null
42
+ . "$_rq" && break
43
+ done
44
+ unset _rq
45
+ if ! command -v ma_require_jq >/dev/null 2>&1; then
46
+ # The helper itself is missing, which is an install problem, not a jq one.
47
+ ma_require_jq() { command -v jq >/dev/null 2>&1 || { echo "jq not found - cannot ${1:-continue}." >&2; return 1; }; }
48
+ fi
49
+ ma_require_jq "read or write the plan" || exit 3
50
+
51
+ # Run-state path resolution: pipeline/lib/run-paths.sh owns the two layouts
52
+ # (nested <root>/<project>/<id>/ and flat <root>/<id>/) and every id spelling.
53
+ _MA_RP_HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
54
+ # shellcheck source=/dev/null
55
+ . "$_MA_RP_HERE/run-paths.sh" 2>/dev/null || . "$HOME/.claude/lib/run-paths.sh"
56
+
57
+
33
58
  CMD="${1:-}"
34
59
  shift || true
35
60
 
@@ -49,12 +74,8 @@ resolve_state() {
49
74
  # directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/) - not the
50
75
  # ~/.claude/projects/*/state/ path this used to glob, which no writer in
51
76
  # the pipeline ever populates.
52
- for candidate in "$HOME"/.claude/logs/multi-agent/*/"$task_id"/agent-state.json; do
53
- if [ -f "$candidate" ]; then
54
- found="$candidate"
55
- break
56
- fi
57
- done
77
+ # This globbed ONLY the nested layout; flat-layout runs resolved to nothing.
78
+ found="$(ma_resolve_run_file "$task_id" agent-state.json 2>/dev/null || true)"
58
79
  if [ -z "$found" ]; then
59
80
  echo "plan-todos: agent-state.json not found for task=$task_id" >&2
60
81
  exit 1
@@ -96,7 +117,7 @@ do_set() {
96
117
  fi
97
118
  # A planning-output document is accepted directly and converted here.
98
119
  #
99
- # The conversion used to live as a jq blob inside phase-2-planning.md, which
120
+ # The conversion used to live as a jq blob inside phase-1-plan.md, which
100
121
  # made the mapping from `tasks[]` to `todos[]` a thing two files defined - and
101
122
  # the phase doc was the copy nothing tested. Accepting both shapes costs four
102
123
  # lines and removes the second definition.
@@ -34,6 +34,65 @@
34
34
 
35
35
  set -euo pipefail
36
36
 
37
+ # jq is not optional on this path. Without the guard below a missing binary
38
+ # renders as EMPTY DATA and the work continues on it; see lib/require-jq.sh.
39
+ for _rq in "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/require-jq.sh" \
40
+ "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/../lib" 2>/dev/null && pwd)/require-jq.sh" \
41
+ "$HOME/.claude/lib/require-jq.sh" \
42
+ "$HOME/.copilot/lib/require-jq.sh" \
43
+ "$HOME/.codex/lib/require-jq.sh"; do
44
+ [ -f "$_rq" ] || continue
45
+ # shellcheck source=/dev/null
46
+ . "$_rq" && break
47
+ done
48
+ unset _rq
49
+ if ! command -v ma_require_jq >/dev/null 2>&1; then
50
+ # The helper itself is missing, which is an install problem, not a jq one.
51
+ ma_require_jq() { command -v jq >/dev/null 2>&1 || { echo "jq not found - cannot ${1:-continue}." >&2; return 1; }; }
52
+ fi
53
+ ma_require_jq "parse the review findings" || exit 3
54
+
55
+ # Outbound leak gate. Every byte below is composed at runtime out of command
56
+ # output, error text and file excerpts, any of which can carry a token that was
57
+ # on this machine a second earlier - and once it is in a comment it is in
58
+ # someone else's database. The repo's own leak scanner looks at FILES IN THE
59
+ # REPO and never sees this text. See lib/outbound-gate.mjs.
60
+ ma_outbound_gate() {
61
+ local body_file="$1" og=""
62
+ for c in "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/outbound-gate.mjs" \
63
+ "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/../lib" 2>/dev/null && pwd)/outbound-gate.mjs" \
64
+ "$HOME/.claude/lib/outbound-gate.mjs" \
65
+ "$HOME/.copilot/lib/outbound-gate.mjs" \
66
+ "$HOME/.codex/lib/outbound-gate.mjs"; do
67
+ [ -f "$c" ] && { og="$c"; break; }
68
+ done
69
+ # Missing gate is NOT an open door: refusing to publish beats publishing
70
+ # unchecked, and the only way this file is absent is a broken install.
71
+ if [ -z "$og" ]; then
72
+ echo "outbound-gate.mjs not found - refusing to publish unchecked text." >&2
73
+ return 7
74
+ fi
75
+ node "$og" --file "$body_file"
76
+ }
77
+
78
+ # Same gate, for the bodies this file keeps in a variable rather than a file.
79
+ ma_outbound_gate_text() {
80
+ local tmp
81
+ tmp=$(mktemp) || return 7
82
+ printf '%s' "$1" > "$tmp"
83
+ ma_outbound_gate "$tmp"
84
+ local rc=$?
85
+ rm -f "$tmp"
86
+ return $rc
87
+ }
88
+
89
+ # Run-state path resolution: pipeline/lib/run-paths.sh owns the two layouts
90
+ # (nested <root>/<project>/<id>/ and flat <root>/<id>/) and every id spelling.
91
+ _MA_RP_HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
92
+ # shellcheck source=/dev/null
93
+ . "$_MA_RP_HERE/run-paths.sh" 2>/dev/null || . "$HOME/.claude/lib/run-paths.sh"
94
+
95
+
37
96
  # Basic-auth via a curl config fed through process substitution so the
38
97
  # credential never appears in argv (argv is visible to `ps` / process audit).
39
98
  bb_auth_cfg() { printf 'user = "%s:%s"\n' "$1" "$2"; }
@@ -49,13 +108,10 @@ fi
49
108
  # per-project directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/)
50
109
  # - not the ~/.claude/projects/*/state/ path this used to glob, which no
51
110
  # writer in the pipeline ever populates.
52
- AGENT_STATE=""
53
- for candidate in "$HOME"/.claude/logs/multi-agent/*/"$TASK_ID"/agent-state.json; do
54
- if [ -f "$candidate" ]; then
55
- AGENT_STATE="$candidate"
56
- break
57
- fi
58
- done
111
+ # This globbed ONLY the nested layout. phase-tracker.sh and most Phase 0 paths
112
+ # write flat, so on a real machine the majority of runs resolved to nothing and
113
+ # this script reported "no agent-state.json" for a run whose state existed.
114
+ AGENT_STATE="$(ma_resolve_run_file "$TASK_ID" agent-state.json 2>/dev/null || true)"
59
115
 
60
116
  if [ -z "$AGENT_STATE" ]; then
61
117
  echo "post-pr-review: agent-state.json not found for task=$TASK_ID" >&2
@@ -271,6 +327,13 @@ post_github() {
271
327
  continue
272
328
  fi
273
329
 
330
+ # A finding's body quotes the diff and the reviewer's prose, so it is
331
+ # the most likely of the three to carry something from the machine.
332
+ if ! ma_outbound_gate_text "$body"; then
333
+ echo "post-pr-review: outbound gate refused the comment for $path:$line - not posted" >&2
334
+ continue
335
+ fi
336
+
274
337
  local inline_ok=0
275
338
  if [ -n "$head_sha" ] && [ "$line" -gt 0 ] 2>/dev/null; then
276
339
  if gh api -X POST "/repos/$org_repo/pulls/$PR_NUM/comments" \
@@ -290,7 +353,13 @@ post_github() {
290
353
 
291
354
  case "$DECISION" in
292
355
  approve) gh pr review "$PR_NUM" --repo "$org_repo" --approve ;;
293
- needs_work) gh pr review "$PR_NUM" --repo "$org_repo" --request-changes --body "$rc_body" ;;
356
+ needs_work)
357
+ if ! ma_outbound_gate_text "$rc_body"; then
358
+ echo "post-pr-review: outbound gate refused the request-changes body; no verdict posted" >&2
359
+ return 7
360
+ fi
361
+ gh pr review "$PR_NUM" --repo "$org_repo" --request-changes --body "$rc_body"
362
+ ;;
294
363
  esac
295
364
  }
296
365
 
@@ -141,11 +141,16 @@ ma_hygiene_local_gitignore() {
141
141
  # rmdir, never rm -rf: it fails harmlessly when anything is left, which is the
142
142
  # behaviour we want. An empty .worktrees/ and .pipeline/evidence/ survive a
143
143
  # clean run today and read as leftovers to anyone looking at the checkout.
144
+ #
145
+ # "Harmlessly" is only true while the CALLER does not run under `set -e`, and
146
+ # this file is sourced, so that is the caller's choice and not ours. Every
147
+ # rmdir here is therefore explicitly forgiven: a non-empty directory is the
148
+ # ordinary case, and it must not abort a teardown half-way.
144
149
  ma_hygiene_prune_empty() {
145
150
  local root d
146
151
  root=$(ma_hygiene_safe_root "${1:-}") || return 0
147
152
  for d in "$root/.pipeline/evidence" "$root/.pipeline" "$root/.worktrees/.archive" "$root/.worktrees" "$root/.multi-agent/refs"; do
148
- [ -d "$d" ] && rmdir "$d" 2>/dev/null
153
+ [ -d "$d" ] && { rmdir "$d" 2>/dev/null || true; }
149
154
  done
150
155
 
151
156
  # .multi-agent survives the loop above because it still holds the .gitignore
@@ -156,8 +161,8 @@ ma_hygiene_prune_empty() {
156
161
  local leftover
157
162
  leftover=$(ls -A "$root/.multi-agent" 2>/dev/null)
158
163
  if [ "$leftover" = ".gitignore" ]; then
159
- rm -f "$root/.multi-agent/.gitignore" 2>/dev/null
160
- rmdir "$root/.multi-agent" 2>/dev/null
164
+ rm -f "$root/.multi-agent/.gitignore" 2>/dev/null || true
165
+ rmdir "$root/.multi-agent" 2>/dev/null || true
161
166
  fi
162
167
  fi
163
168
  return 0
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bash
2
+ # require-jq.sh - one place to refuse when `jq` is missing.
3
+ #
4
+ # 78 shell files in this repo call `jq`. 46 of them called it with no check at
5
+ # all, and the README described that as helpers which "skip in silence" - which
6
+ # is the defect stated as if it were the design. A bare `jq` on a machine
7
+ # without it does not skip. It writes "command not found" to stderr, produces
8
+ # an empty string on stdout, and the caller carries on with that empty string
9
+ # as if it were the answer.
10
+ #
11
+ # The nine production paths are where that matters:
12
+ #
13
+ # autopilot-state.sh an unreadable queue reads as an EMPTY queue, so the
14
+ # runner concludes there is no work and goes quiet
15
+ # jira-publish.sh posts a comment built from empty fields
16
+ # post-pr-review.sh publishes a review whose findings did not parse
17
+ # update-issue-progress.sh same, on an issue
18
+ # _jira-auth.sh no credential, and the failure surfaces later as 401
19
+ # figma-mcp-refresh.sh / figma-screenshot.sh silent no-op on a design fetch
20
+ # plan-todos.sh an empty plan looks like a plan with no work in it
21
+ # search-logs.sh "no results" instead of "could not search"
22
+ #
23
+ # Every one of those is the same shape: absence of a tool rendered as absence
24
+ # of data. Refusing is the only honest answer, and the exit code is distinct
25
+ # so a caller can tell "jq missing" from "the work failed".
26
+ #
27
+ # Usage:
28
+ # . "$(dirname "$0")/require-jq.sh"
29
+ # ma_require_jq "read the autopilot queue" || exit 3
30
+ #
31
+ # It returns rather than exits: this file is SOURCED, and a sourced file that
32
+ # exits kills the caller's shell in ways the caller did not write down.
33
+
34
+ # shellcheck disable=SC2329 # sourced by other scripts, not called here
35
+ ma_require_jq() {
36
+ command -v jq >/dev/null 2>&1 && return 0
37
+ printf 'jq not found - cannot %s.\n' "${1:-continue}" >&2
38
+ printf ' install it: brew install jq\n' >&2
39
+ return 1
40
+ }
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env bash
2
+ # route-state.sh - read and write prefs.global.modelRouting.
3
+ #
4
+ # Backs /multi-agent:route-on, route-off and route-status. Ships disabled and
5
+ # stays that way until someone turns it on; while `enabled` is false nothing in
6
+ # this file changes one dispatch.
7
+ #
8
+ # The `off` contract is borrowed from autopilot-off and for the same reason: it
9
+ # clears the ON STATE, never the rules. Turning routing back on must not re-ask
10
+ # for a configuration the user already gave.
11
+ #
12
+ # Usage:
13
+ # route-state.sh status print the current policy
14
+ # route-state.sh on [--strategy=S] [--scope=a,b]
15
+ # route-state.sh off
16
+ # route-state.sh set-rules <file.json> replace rules[] from a JSON array
17
+ #
18
+ # Exit codes:
19
+ # 0 - done
20
+ # 2 - bad usage
21
+ # 3 - preferences missing/unparseable, or a scope value this refuses
22
+
23
+ set -uo pipefail
24
+
25
+ PREFS="${MULTI_AGENT_PREFS:-$HOME/.claude/multi-agent-preferences.json}"
26
+
27
+ # The scope values this accepts, spelled once. `host-session` is NOT here and
28
+ # must never be added: it would mean rewriting the host's base URL, which routes
29
+ # the user's whole session - work that has nothing to do with this pipeline
30
+ # included - through a third layer, breaks the subscription's auth model, and
31
+ # silently changes which model answers. The schema encodes the same list so a
32
+ # hand-edited preferences file is rejected too, rather than only this entry point.
33
+ ALLOWED_SCOPE="subagent bulk-read research"
34
+
35
+ die() { echo "route-state: $1" >&2; exit "${2:-3}"; }
36
+
37
+ # A missing `jq` must not look like routing being off. Every read below would
38
+ # come back empty, `route-status` would report disabled, and a user with rules
39
+ # armed would be told there are none.
40
+ command -v jq >/dev/null 2>&1 || die "jq not found - cannot read or write preferences (install: brew install jq)"
41
+ [ -f "$PREFS" ] || die "preferences not found at $PREFS - run /multi-agent:setup first"
42
+ jq -e . "$PREFS" >/dev/null 2>&1 || die "$PREFS is not valid JSON - refusing to write over it"
43
+
44
+ ACTION="${1:-status}"; shift 2>/dev/null || true
45
+
46
+ write_prefs() {
47
+ local filter="$1"; shift
48
+ local tmp
49
+ tmp=$(mktemp) || die "mktemp failed"
50
+ if ! jq "$@" "$filter" "$PREFS" > "$tmp"; then rm -f "$tmp"; die "jq failed - preferences left untouched"; fi
51
+ jq -e . "$tmp" >/dev/null 2>&1 || { rm -f "$tmp"; die "produced invalid JSON - preferences left untouched"; }
52
+ mv "$tmp" "$PREFS"
53
+ }
54
+
55
+ case "$ACTION" in
56
+ status)
57
+ ENABLED=$(jq -r '.global.modelRouting.enabled // false' "$PREFS")
58
+ STRATEGY=$(jq -r '.global.modelRouting.strategy // "manual"' "$PREFS")
59
+ SCOPE=$(jq -r '(.global.modelRouting.scope // ["subagent"]) | join(", ")' "$PREFS")
60
+ NRULES=$(jq -r '(.global.modelRouting.rules // []) | length' "$PREFS")
61
+ CEIL=$(jq -r '.global.modelRouting.budgetCeilingUsd // "none"' "$PREFS")
62
+ REC=$(jq -r '.global.modelRouting.recordDecisions // true' "$PREFS")
63
+
64
+ echo "routing: $ENABLED"
65
+ echo "strategy: $STRATEGY"
66
+ echo "scope: $SCOPE"
67
+ echo "rules: $NRULES"
68
+ echo "ceiling: $CEIL"
69
+ echo "decisions: $REC"
70
+ echo ""
71
+
72
+ if [ "$ENABLED" != "true" ]; then
73
+ echo "Disabled. Rules are stored but read by nothing; dispatch is unchanged."
74
+ [ "$NRULES" -gt 0 ] && echo "route-on turns these $NRULES rule(s) back on without re-asking."
75
+ exit 0
76
+ fi
77
+
78
+ if [ "$NRULES" -eq 0 ]; then
79
+ echo "Armed with no rules. Nothing matches, so nothing is routed - this is a"
80
+ echo "configuration state, not an error."
81
+ else
82
+ jq -r '(.global.modelRouting.rules // [])[] |
83
+ " when " + ([.when | to_entries[] | "\(.key)=\(.value)"] | join(" ")) +
84
+ " -> " + (.prefer | join(" > "))' "$PREFS"
85
+ fi
86
+
87
+ echo ""
88
+ # The limit is printed every time rather than documented once, because the
89
+ # question it answers ("routing is on, why is the reviewer still on Opus")
90
+ # otherwise arrives days later as a bug report.
91
+ echo "Honest limit: on Claude Code a subagent cannot be sent to a non-Anthropic"
92
+ echo "model - subagent dispatch belongs to the host. Phase 1/2/3 personas stay"
93
+ echo "inside the Anthropic ladder whatever the rules say. External providers apply"
94
+ echo "only where this pipeline makes the call itself (bulk-read, research)."
95
+ ;;
96
+
97
+ on)
98
+ STRATEGY="manual"; SCOPE_ARG=""
99
+ for arg in "$@"; do
100
+ case "$arg" in
101
+ --strategy=*) STRATEGY="${arg#*=}" ;;
102
+ --scope=*) SCOPE_ARG="${arg#*=}" ;;
103
+ *) die "unknown option: $arg" 2 ;;
104
+ esac
105
+ done
106
+ case "$STRATEGY" in
107
+ manual|task-fit|cost-ceiling) ;;
108
+ *) die "strategy must be manual, task-fit or cost-ceiling (got '$STRATEGY')" 2 ;;
109
+ esac
110
+
111
+ if [ -n "$SCOPE_ARG" ]; then
112
+ SCOPE_JSON="[]"
113
+ IFS=',' read -r -a parts <<< "$SCOPE_ARG"
114
+ for one in "${parts[@]}"; do
115
+ one="${one// /}"
116
+ # shellcheck disable=SC2076
117
+ case " $ALLOWED_SCOPE " in
118
+ *" $one "*) ;;
119
+ *) die "scope '$one' is not allowed (allowed: $ALLOWED_SCOPE). 'host-session' is refused by design: it would route the whole session, not this pipeline's calls." ;;
120
+ esac
121
+ SCOPE_JSON=$(jq -c --arg s "$one" '. + [$s]' <<< "$SCOPE_JSON")
122
+ done
123
+ write_prefs '
124
+ .global.modelRouting //= {}
125
+ | .global.modelRouting.enabled = true
126
+ | .global.modelRouting.strategy = $st
127
+ | .global.modelRouting.scope = ($sc | fromjson)
128
+ ' --arg st "$STRATEGY" --arg sc "$SCOPE_JSON"
129
+ else
130
+ write_prefs '
131
+ .global.modelRouting //= {}
132
+ | .global.modelRouting.enabled = true
133
+ | .global.modelRouting.strategy = $st
134
+ | .global.modelRouting.scope //= ["subagent"]
135
+ ' --arg st "$STRATEGY"
136
+ fi
137
+ echo "routing enabled (strategy: $STRATEGY)"
138
+ exec "$0" status
139
+ ;;
140
+
141
+ off)
142
+ # Rules survive on purpose. Clearing them here would make route-off a
143
+ # destructive action wearing the name of a toggle.
144
+ write_prefs '.global.modelRouting //= {} | .global.modelRouting.enabled = false'
145
+ echo "routing disabled. Rules kept - route-on restores this configuration as it is."
146
+ ;;
147
+
148
+ set-rules)
149
+ FILE="${1:-}"
150
+ [ -n "$FILE" ] || die "set-rules needs a JSON file containing an array of rules" 2
151
+ [ -f "$FILE" ] || die "no such file: $FILE" 2
152
+ jq -e 'type == "array"' "$FILE" >/dev/null 2>&1 || die "$FILE must contain a JSON ARRAY of rules" 2
153
+ write_prefs '.global.modelRouting //= {} | .global.modelRouting.rules = $r' --slurpfile _ignore /dev/null --argjson r "$(cat "$FILE")"
154
+ echo "rules replaced: $(jq -r 'length' "$FILE")"
155
+ ;;
156
+
157
+ *)
158
+ echo "usage: route-state.sh [status|on|off|set-rules <file>]" >&2
159
+ exit 2
160
+ ;;
161
+ esac