@mmerterden/multi-agent-pipeline 18.0.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 (209) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/README.md +34 -18
  3. package/README.tr.md +14 -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/0014-six-phase-consolidation.md +134 -0
  9. package/docs/adr/README.md +2 -1
  10. package/docs/architecture.md +37 -38
  11. package/docs/best-practices.md +1 -1
  12. package/docs/ecosystem.md +37 -26
  13. package/docs/engineering.md +1 -1
  14. package/docs/facts.json +45 -0
  15. package/docs/features.md +54 -53
  16. package/docs/performance.md +5 -5
  17. package/docs/recovery-guide.md +9 -9
  18. package/docs/token-budget-history.md +3 -1
  19. package/index.js +2 -2
  20. package/install/_codex-agents.mjs +1 -1
  21. package/install/templates/claude-hooks.json +1 -1
  22. package/install/templates/codex-instructions.md +1 -1
  23. package/install/templates/copilot-instructions.md +28 -28
  24. package/manifest.json +209 -193
  25. package/package.json +2 -2
  26. package/pipeline/agents/dev-critic.md +3 -3
  27. package/pipeline/commands/figma-to-swiftui.md +1 -1
  28. package/pipeline/commands/multi-agent/SKILL.md +8 -8
  29. package/pipeline/commands/multi-agent/analysis/SKILL.md +9 -9
  30. package/pipeline/commands/multi-agent/autopilot/SKILL.md +7 -7
  31. package/pipeline/commands/multi-agent/channels/SKILL.md +15 -15
  32. package/pipeline/commands/multi-agent/diff-explain/SKILL.md +6 -6
  33. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
  34. package/pipeline/commands/multi-agent/graph/SKILL.md +1 -1
  35. package/pipeline/commands/multi-agent/help/SKILL.md +62 -62
  36. package/pipeline/commands/multi-agent/language/SKILL.md +2 -2
  37. package/pipeline/commands/multi-agent/local/SKILL.md +11 -11
  38. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +13 -13
  39. package/pipeline/commands/multi-agent/log/SKILL.md +2 -2
  40. package/pipeline/commands/multi-agent/manual-test/SKILL.md +9 -9
  41. package/pipeline/commands/multi-agent/model/SKILL.md +69 -0
  42. package/pipeline/commands/multi-agent/refactor/SKILL.md +3 -3
  43. package/pipeline/commands/multi-agent/resume/SKILL.md +4 -4
  44. package/pipeline/commands/multi-agent/resume-local/SKILL.md +19 -17
  45. package/pipeline/commands/multi-agent/review/SKILL.md +1 -1
  46. package/pipeline/commands/multi-agent/route-off/SKILL.md +36 -0
  47. package/pipeline/commands/multi-agent/route-on/SKILL.md +74 -0
  48. package/pipeline/commands/multi-agent/route-status/SKILL.md +56 -0
  49. package/pipeline/commands/multi-agent/setup/SKILL.md +2 -2
  50. package/pipeline/commands/multi-agent/status/SKILL.md +5 -5
  51. package/pipeline/commands/multi-agent/steer/SKILL.md +2 -2
  52. package/pipeline/commands/multi-agent/sync/SKILL.md +12 -13
  53. package/pipeline/commands/multi-agent/test/SKILL.md +1 -1
  54. package/pipeline/lib/credential-inventory.sh +1 -1
  55. package/pipeline/lib/fetch-fortify.sh +1 -1
  56. package/pipeline/lib/model-rung.sh +142 -0
  57. package/pipeline/lib/phase-schema.mjs +88 -0
  58. package/pipeline/lib/plan-todos.sh +5 -5
  59. package/pipeline/lib/route-state.sh +161 -0
  60. package/pipeline/lib/run-paths.sh +2 -2
  61. package/pipeline/multi-agent-refs/_account-picker.md +1 -1
  62. package/pipeline/multi-agent-refs/_dev-context.md +1 -1
  63. package/pipeline/multi-agent-refs/_input-parser.md +1 -1
  64. package/pipeline/multi-agent-refs/analysis/evidence.md +0 -9
  65. package/pipeline/multi-agent-refs/analysis/intake.md +1 -1
  66. package/pipeline/multi-agent-refs/analysis/locked.md +21 -22
  67. package/pipeline/multi-agent-refs/analysis/render.md +1 -1
  68. package/pipeline/multi-agent-refs/analysis/synthesis.md +12 -6
  69. package/pipeline/multi-agent-refs/android-guide.md +1 -1
  70. package/pipeline/multi-agent-refs/audit-guide.md +13 -13
  71. package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
  72. package/pipeline/multi-agent-refs/channels/jira.md +3 -3
  73. package/pipeline/multi-agent-refs/channels/pr.md +4 -4
  74. package/pipeline/multi-agent-refs/channels/wiki.md +1 -1
  75. package/pipeline/multi-agent-refs/component-dispatch.md +3 -3
  76. package/pipeline/multi-agent-refs/cross-cli-contract.md +31 -6
  77. package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +4 -4
  78. package/pipeline/multi-agent-refs/features/code-graph.md +5 -5
  79. package/pipeline/multi-agent-refs/features/design-conformance.md +1 -1
  80. package/pipeline/multi-agent-refs/features/dev-critic.md +3 -3
  81. package/pipeline/multi-agent-refs/features/doctor.md +2 -2
  82. package/pipeline/multi-agent-refs/features/external-context-injection.md +3 -3
  83. package/pipeline/multi-agent-refs/features/maturity-followup.md +3 -3
  84. package/pipeline/multi-agent-refs/features/model-fallback.md +5 -5
  85. package/pipeline/multi-agent-refs/features/plan-todos.md +1 -1
  86. package/pipeline/multi-agent-refs/features/repo-map.md +1 -1
  87. package/pipeline/multi-agent-refs/features/review-delta.md +3 -3
  88. package/pipeline/multi-agent-refs/features/review-multi-repo.md +1 -1
  89. package/pipeline/multi-agent-refs/features/scope-check.md +4 -4
  90. package/pipeline/multi-agent-refs/features/skill-conformance.md +2 -2
  91. package/pipeline/multi-agent-refs/features/stack-skill-routing.md +1 -1
  92. package/pipeline/multi-agent-refs/features/verify-by-test.md +4 -4
  93. package/pipeline/multi-agent-refs/features/visual-evidence.md +19 -19
  94. package/pipeline/multi-agent-refs/features/worktree-finalize.md +6 -6
  95. package/pipeline/multi-agent-refs/issue-jira-triad.md +10 -10
  96. package/pipeline/multi-agent-refs/knowledge.md +11 -11
  97. package/pipeline/multi-agent-refs/multi-repo-integration-build.md +13 -13
  98. package/pipeline/multi-agent-refs/payload-contracts.md +8 -8
  99. package/pipeline/multi-agent-refs/phases/log-format.md +10 -10
  100. package/pipeline/multi-agent-refs/phases/modes.md +30 -30
  101. package/pipeline/multi-agent-refs/phases/operations.md +8 -8
  102. package/pipeline/multi-agent-refs/phases/phase-0-init.md +24 -24
  103. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +599 -0
  104. package/pipeline/multi-agent-refs/phases/{phase-3-dev.md → phase-2-dev.md} +129 -49
  105. package/pipeline/multi-agent-refs/phases/{phase-4-review.md → phase-3-review.md} +225 -107
  106. package/pipeline/multi-agent-refs/phases/{phase-6-commit.md → phase-4-commit.md} +23 -23
  107. package/pipeline/multi-agent-refs/phases/{phase-7-report.md → phase-5-report.md} +29 -29
  108. package/pipeline/multi-agent-refs/phases.md +44 -48
  109. package/pipeline/multi-agent-refs/picker-contract.md +1 -1
  110. package/pipeline/multi-agent-refs/progress-contract.md +6 -6
  111. package/pipeline/multi-agent-refs/readiness-review.md +1 -1
  112. package/pipeline/multi-agent-refs/rules.md +7 -7
  113. package/pipeline/multi-agent-refs/swiftui-guide.md +2 -2
  114. package/pipeline/multi-agent-refs/tracker-contract.md +31 -32
  115. package/pipeline/multi-agent-refs/wiki-capture.md +14 -14
  116. package/pipeline/preferences-template.json +9 -1
  117. package/pipeline/rules/outside-the-pipeline.md +1 -1
  118. package/pipeline/schemas/agent-state.schema.json +50 -50
  119. package/pipeline/schemas/analysis-output.schema.json +2 -2
  120. package/pipeline/schemas/autopilot-config.schema.json +1 -1
  121. package/pipeline/schemas/code-graph.schema.json +1 -1
  122. package/pipeline/schemas/criteria-manifest.schema.json +1 -1
  123. package/pipeline/schemas/dev-critic-output.schema.json +1 -1
  124. package/pipeline/schemas/diff-risk.schema.json +1 -1
  125. package/pipeline/schemas/migrations/prefs-2.4.0-to-2.5.0.mjs +2 -2
  126. package/pipeline/schemas/migrations/prefs-2.6.0-to-2.7.0.mjs +31 -0
  127. package/pipeline/schemas/migrations/state-2.1.0-to-2.2.0.mjs +129 -0
  128. package/pipeline/schemas/phases.json +105 -0
  129. package/pipeline/schemas/plan-todos.schema.json +5 -5
  130. package/pipeline/schemas/planning-output.schema.json +1 -1
  131. package/pipeline/schemas/prefs.schema.json +100 -56
  132. package/pipeline/schemas/reviewer-output.schema.json +3 -3
  133. package/pipeline/schemas/route-config.schema.json +74 -0
  134. package/pipeline/schemas/scope-check.schema.json +1 -1
  135. package/pipeline/schemas/test-gap.schema.json +1 -1
  136. package/pipeline/schemas/token-budget.json +12 -18
  137. package/pipeline/schemas/triage-output.schema.json +6 -6
  138. package/pipeline/scripts/README.md +3 -3
  139. package/pipeline/scripts/_code-graph.mjs +2 -2
  140. package/pipeline/scripts/_run-paths.mjs +2 -2
  141. package/pipeline/scripts/_smoke-root.sh +1 -1
  142. package/pipeline/scripts/aggregate-metrics.mjs +1 -1
  143. package/pipeline/scripts/capture-flush.sh +8 -8
  144. package/pipeline/scripts/capture-resume.sh +3 -3
  145. package/pipeline/scripts/classify-plan-safety.mjs +1 -1
  146. package/pipeline/scripts/diff-explain.mjs +1 -1
  147. package/pipeline/scripts/doctor.mjs +2 -2
  148. package/pipeline/scripts/gc-abandoned.sh +3 -3
  149. package/pipeline/scripts/gc-tmp.sh +1 -1
  150. package/pipeline/scripts/gc-worktrees.sh +1 -1
  151. package/pipeline/scripts/gen-facts.mjs +175 -0
  152. package/pipeline/scripts/gen-mode-dispatch.mjs +32 -37
  153. package/pipeline/scripts/gen-ref-toc.mjs +1 -1
  154. package/pipeline/scripts/graph-report.mjs +1 -1
  155. package/pipeline/scripts/jira-attach.sh +1 -1
  156. package/pipeline/scripts/learn-from-transcripts.mjs +1 -1
  157. package/pipeline/scripts/learning-curve.mjs +2 -2
  158. package/pipeline/scripts/log-metric.sh +17 -4
  159. package/pipeline/scripts/memory-save.sh +1 -1
  160. package/pipeline/scripts/migrate-prefs.mjs +22 -5
  161. package/pipeline/scripts/phase-banner.sh +20 -20
  162. package/pipeline/scripts/phase-tracker.sh +7 -7
  163. package/pipeline/scripts/plan-coverage-gate.mjs +2 -2
  164. package/pipeline/scripts/render-agent-log-cost.sh +1 -1
  165. package/pipeline/scripts/render-work-summary.sh +3 -3
  166. package/pipeline/scripts/review-file-filter.mjs +1 -1
  167. package/pipeline/scripts/run-aggregator.mjs +13 -6
  168. package/pipeline/scripts/run-metrics.mjs +1 -1
  169. package/pipeline/scripts/runs-index.mjs +11 -1
  170. package/pipeline/scripts/smoke-cross-cli-behavior.sh +6 -6
  171. package/pipeline/scripts/smoke-schema-validation.sh +26 -7
  172. package/pipeline/scripts/token-budget-report.mjs +13 -2
  173. package/pipeline/scripts/triage-memory.mjs +2 -2
  174. package/pipeline/scripts/validate-analysis-doc.mjs +73 -17
  175. package/pipeline/scripts/validate-planning.mjs +1 -1
  176. package/pipeline/scripts/validate-reviewer.mjs +1 -1
  177. package/pipeline/scripts/validate-state.mjs +45 -5
  178. package/pipeline/scripts/validate-triage.mjs +3 -3
  179. package/pipeline/scripts/worktree-finalize.sh +5 -5
  180. package/pipeline/skills/.skill-manifest.json +37 -21
  181. package/pipeline/skills/.skills-index.json +49 -5
  182. package/pipeline/skills/shared/README.md +10 -6
  183. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +2 -2
  184. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +2 -2
  185. package/pipeline/skills/shared/core/multi-agent/SKILL.md +69 -71
  186. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +3 -3
  187. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +14 -14
  188. package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +5 -5
  189. package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +1 -1
  190. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +25 -23
  191. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +2 -2
  192. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +2 -2
  193. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +8 -8
  194. package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +6 -6
  195. package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +71 -0
  196. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +3 -3
  197. package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +1 -1
  198. package/pipeline/skills/shared/core/multi-agent-resume-local/SKILL.md +7 -7
  199. package/pipeline/skills/shared/core/multi-agent-route-off/SKILL.md +39 -0
  200. package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +76 -0
  201. package/pipeline/skills/shared/core/multi-agent-route-status/SKILL.md +59 -0
  202. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +1 -1
  203. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +5 -5
  204. package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +2 -2
  205. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +6 -5
  206. package/pipeline/skills/skills-index.md +8 -4
  207. package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +0 -263
  208. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +0 -344
  209. package/pipeline/multi-agent-refs/phases/phase-5-test.md +0 -182
@@ -13,7 +13,7 @@
13
13
  //
14
14
  // Section level is DERIVED, not assumed. These files are not consistent: some
15
15
  // open at `##` and section at `###` (keychain.md), others open at `###` and
16
- // section at `##` (phases/phase-3-dev.md). The rule is the shallowest heading
16
+ // section at `##` (phases/phase-2-dev.md). The rule is the shallowest heading
17
17
  // depth that occurs more than once - a depth used exactly once is a title, not
18
18
  // a section level.
19
19
  //
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * @file graph-report.mjs - GRAPH_REPORT.md, the human-and-model readable summary.
5
5
  *
6
- * Phase 1 reads this before deciding how wide to explore, and Phase 7 ships it
6
+ * Phase 1 reads this before deciding how wide to explore, and Phase 5 ships it
7
7
  * next to the knowledge base. Sections are ordered by how much they narrow a
8
8
  * search: hubs first (the types everything touches), then the module map, then
9
9
  * the long tail that is usually safe to ignore.
@@ -5,7 +5,7 @@
5
5
  #
6
6
  # Two callers need this and they used to carry their own curl: the issue
7
7
  # generator (`generate-issue.md` step 12) and visual evidence (Phase 3 / Phase 5
8
- # captures rendered into the Phase 7 Jira comment). Two copies of an upload is
8
+ # captures rendered into the Phase 5 Jira comment). Two copies of an upload is
9
9
  # two places for the `X-Atlassian-Token: no-check` header to go missing, which
10
10
  # fails as a 403 that reads like an auth problem.
11
11
  #
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * WHY THIS EXISTS
6
6
  *
7
- * Phase 7 asks a subagent to read the run log and propose a few memory entries.
7
+ * Phase 5 asks a subagent to read the run log and propose a few memory entries.
8
8
  * That works for what the model noticed, and it is structurally blind to what it
9
9
  * did not: the same wrong path tried in three sessions, the command that fails
10
10
  * every time until its `--flag` variant is used, the directory that never has
@@ -87,9 +87,9 @@ for (const e of events) {
87
87
  }
88
88
  if (e.ts && (!t.firstTs || e.ts < t.firstTs)) t.firstTs = e.ts;
89
89
  const d = e.details || {};
90
- // phase-4-review.md's actual review.completed emission (raw_count=/
90
+ // phase-3-review.md's actual review.completed emission (raw_count=/
91
91
  // accepted=/deferred=/rejected=/approved=/duration_ms=) carries no
92
- // review_cycles field - that name only appears on Phase 7's task.completed
92
+ // review_cycles field - that name only appears on Phase 5's task.completed
93
93
  // summary event, which this never matched. Phase 4 re-runs (and logs its
94
94
  // own review.completed) once per rework cycle, so the cycle count for a
95
95
  // task is how many review.completed events share its task_id - the same
@@ -6,11 +6,19 @@
6
6
  # {
7
7
  # "ts": "<ISO 8601 timestamp>",
8
8
  # "task_id": "<jira id, github issue, or run id>",
9
- # "phase": "<0..7>",
9
+ # "phase": "<0..5>",
10
10
  # "event": "<event name>",
11
- # "details": { ...arbitrary key/value pairs... }
11
+ # "details": { ...arbitrary key/value pairs... },
12
+ # "phaseSchema": 2
12
13
  # }
13
14
  #
15
+ # phaseSchema names the phase vocabulary the line was written under, because
16
+ # this file is append-only and v19.0.0 renumbered the phases. A line with
17
+ # phase 3 means Dev under schema 1 and Review under schema 2. Lines with no
18
+ # phaseSchema field predate v19.0.0 and are schema 1. Aggregators pick their
19
+ # name table from this field; without it the two vocabularies are
20
+ # indistinguishable and every historical roll-up is quietly wrong.
21
+ #
14
22
  # Usage:
15
23
  # log-metric.sh <task_id> <phase> <event> [key=value ...]
16
24
  #
@@ -18,7 +26,7 @@
18
26
  # log-metric.sh PROJ-1234 4 review.completed raw_count=8 accepted=3 deferred=2 rejected=3 approved=true duration_ms=42000
19
27
  # log-metric.sh PROJ-1234 4 triage.edge_case case=high-rejection-rate
20
28
  # log-metric.sh PROJ-1234 3 rework.started iteration=2 accepted_blocking=2
21
- # log-metric.sh #316 7 task.completed phases=8 review_cycles=2 lang=tr
29
+ # log-metric.sh #316 5 task.completed phases=6 review_cycles=2 lang=tr
22
30
  #
23
31
  # Atomic append: each call uses `>> "$file"` which the kernel guarantees
24
32
  # atomic for writes ≤ PIPE_BUF (4 KB on macOS/Linux). One JSONL line is well
@@ -64,8 +72,13 @@ for kv in "$@"; do
64
72
  done
65
73
  DETAILS="${DETAILS}}"
66
74
 
75
+ # Read from the phase contract rather than hardcoding: the next renumbering
76
+ # bumps one JSON field instead of hunting for a literal in a shell script.
77
+ PHASE_SCHEMA="$(node -e 'const c=require("'"$(cd "$(dirname "$0")" && pwd)"'/../schemas/phases.json"); process.stdout.write(String(c.phaseSchema))' 2>/dev/null)"
78
+ [ -n "$PHASE_SCHEMA" ] || PHASE_SCHEMA=2
79
+
67
80
  TS="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
68
- LINE="{\"ts\":\"${TS}\",\"task_id\":\"${TASK_ID}\",\"phase\":\"${PHASE}\",\"event\":\"${EVENT}\",\"details\":${DETAILS}}"
81
+ LINE="{\"ts\":\"${TS}\",\"task_id\":\"${TASK_ID}\",\"phase\":\"${PHASE}\",\"event\":\"${EVENT}\",\"details\":${DETAILS},\"phaseSchema\":${PHASE_SCHEMA}}"
69
82
 
70
83
  # Atomic append (one line, well under PIPE_BUF).
71
84
  echo "$LINE" >> "$METRICS_FILE" 2>/dev/null || {
@@ -2,7 +2,7 @@
2
2
  # memory-save.sh - v6.2.E
3
3
  #
4
4
  # Writes a single per-repo memory file and updates the MEMORY.md index.
5
- # Called by Phase 7's synthesis step (or any user skill that wants to
5
+ # Called by Phase 5's synthesis step (or any user skill that wants to
6
6
  # persist a finding). Opt-in via prefs.global.perRepoMemory.
7
7
  #
8
8
  # Usage:
@@ -18,9 +18,26 @@ import { runMain } from "../lib/fatal.mjs";
18
18
 
19
19
  const DEFAULT_FILE = path.join(os.homedir(), ".claude", "multi-agent-preferences.json");
20
20
 
21
- // Single source of truth for the migration target version. Referenced by the
22
- // migrate() bump chain AND every user-facing message, so they cannot drift.
23
- const TARGET_VERSION = "2.6.0";
21
+ // The migration target, read from the schema rather than written here. It was
22
+ // a literal, and a literal is how it came to say 2.6.0 while the schema had
23
+ // moved to 2.7.0 - the runner then reported every current file as needing a
24
+ // migration it had already had. Referenced by the migrate() bump chain AND
25
+ // every user-facing message, so they cannot drift from each other either.
26
+ const TARGET_VERSION = (() => {
27
+ const schemaPath = path.join(
28
+ path.dirname(fileURLToPath(import.meta.url)),
29
+ "..",
30
+ "schemas",
31
+ "prefs.schema.json",
32
+ );
33
+ const sv = JSON.parse(fs.readFileSync(schemaPath, "utf-8"))?.properties?.schemaVersion;
34
+ const v = sv?.const ?? sv?.enum?.at(-1);
35
+ if (!v) {
36
+ console.error("migrate-prefs: prefs.schema.json declares no schemaVersion");
37
+ process.exit(1);
38
+ }
39
+ return v;
40
+ })();
24
41
 
25
42
  const USAGE = "Usage: migrate-prefs.mjs [--dry-run] [--file <path>]";
26
43
 
@@ -327,7 +344,7 @@ function migrate(prefs) {
327
344
  out.global.routines = [];
328
345
  changes.push("added empty routines (v2.4.0 user-defined /multi-agent:save routines)");
329
346
  }
330
- // v2.5.0 / v14.0.0: Phase 4 Review entered the --dev phase sets and Step 1.78
347
+ // v2.5.0 / v14.0.0: Phase 3 Review entered the --dev phase sets and Step 1.78
331
348
  // (criteria resolution) landed. Default false because only Swift plus the two
332
349
  // store-compliance catalogs ship a scoped registry today, so blocking on a
333
350
  // coverage gap would stop every Kotlin/Python/Node/ObjC run from day one. The
@@ -488,7 +505,7 @@ function migrateReportChannels(out, changes) {
488
505
  if (typeof g.autopilotReportTimeoutSeconds !== "number") {
489
506
  g.autopilotReportTimeoutSeconds = 1800;
490
507
  changes.push(
491
- "added autopilotReportTimeoutSeconds: 1800 (30 min) - Phase 7 always-pause timeout",
508
+ "added autopilotReportTimeoutSeconds: 1800 (30 min) - Phase 5 always-pause timeout",
492
509
  );
493
510
  }
494
511
 
@@ -12,9 +12,9 @@
12
12
  # phase-banner.sh label <phase> (print canonical name)
13
13
  #
14
14
  # Examples:
15
- # phase-banner.sh start 4 "Review" "deterministic gates + parallel + triage"
16
- # phase-banner.sh start 4 auto # resolves to "Review" (en) or "İnceleme" (tr)
17
- # PHASE_LANG=tr phase-banner.sh start 4 auto # "Faz 4: İnceleme"
15
+ # phase-banner.sh start 3 "Review" "parallel + triage + user test"
16
+ # phase-banner.sh start 3 auto # resolves to "Review" (en) or "İnceleme" (tr)
17
+ # PHASE_LANG=tr phase-banner.sh start 3 auto # "Faz 3: İnceleme"
18
18
  #
19
19
  # Status values for `end`: done | failed | skipped
20
20
  #
@@ -32,28 +32,28 @@ set -uo pipefail
32
32
 
33
33
  PHASE_LANG="${PHASE_LANG:-en}"
34
34
 
35
- # Canonical phase label table. Keep synced with
36
- # pipeline/multi-agent-refs/phases.md and the table in
37
- # pipeline/skills/shared/multi-agent/SKILL.md.
35
+ # Canonical phase label table. The English half is checked against
36
+ # pipeline/schemas/phases.json by smoke-phase-contract.sh, so it cannot drift
37
+ # from the contract in silence the way it did through the eight-phase era. It
38
+ # stays a case statement rather than a jq read because this runs on every
39
+ # banner and the pipeline's zero-dependency rule (ADR-0004) applies to the hot
40
+ # path. The Turkish half has no contract entry - labels are translations, not
41
+ # identifiers - and is kept beside it.
38
42
  phase_label() {
39
43
  local phase="$1" lang="${2:-en}"
40
44
  case "$lang:$phase" in
41
45
  en:0) echo "Init" ;;
42
- en:1) echo "Analysis" ;;
43
- en:2) echo "Planning" ;;
44
- en:3) echo "Dev" ;;
45
- en:4) echo "Review" ;;
46
- en:5) echo "Test" ;;
47
- en:6) echo "Commit & PR" ;;
48
- en:7) echo "Report" ;;
46
+ en:1) echo "Plan" ;;
47
+ en:2) echo "Dev" ;;
48
+ en:3) echo "Review" ;;
49
+ en:4) echo "Commit & PR" ;;
50
+ en:5) echo "Report" ;;
49
51
  tr:0) echo "Başlangıç" ;;
50
- tr:1) echo "Analiz" ;;
51
- tr:2) echo "Planlama" ;;
52
- tr:3) echo "Geliştirme" ;;
53
- tr:4) echo "İnceleme" ;;
54
- tr:5) echo "Kullanıcı Testi" ;;
55
- tr:6) echo "Commit & PR" ;;
56
- tr:7) echo "Rapor" ;;
52
+ tr:1) echo "Plan" ;;
53
+ tr:2) echo "Geliştirme" ;;
54
+ tr:3) echo "İnceleme" ;;
55
+ tr:4) echo "Commit & PR" ;;
56
+ tr:5) echo "Rapor" ;;
57
57
  *) echo "" ;;
58
58
  esac
59
59
  }
@@ -43,7 +43,7 @@
43
43
  #
44
44
  # Examples:
45
45
  # phase-tracker.sh init "TASK-123"
46
- # for p in 0:Init 1:Analysis 2:Planning 3:Dev 4:Review 5:Test 6:Commit 7:Report; do
46
+ # for p in 0:Init 1:Plan 2:Dev 3:Review 4:Commit 5:Report; do
47
47
  # phase-tracker.sh add "${p%%:*}" "${p#*:}"
48
48
  # done
49
49
  # phase-tracker.sh update 0 in_progress
@@ -141,7 +141,7 @@ need_jq() {
141
141
  # Live usage ping (best-effort). Emits one per-phase update to the private
142
142
  # dashboard via usage-report.mjs, always status=running so per-phase updates
143
143
  # never fold the run into rollup counters - the terminal fold comes only from
144
- # the Phase 7 / halt emit that reads the real run status. The emitter no-ops
144
+ # the Phase 5 / halt emit that reads the real run status. The emitter no-ops
145
145
  # unless prefs.global.usageLog.enabled; detached so it never blocks a boundary.
146
146
  usage_live_ping() {
147
147
  # Smoke runs exercise this script's state handling, never the live dashboard -
@@ -461,7 +461,7 @@ save_state() {
461
461
 
462
462
  # --- read-modify-write lock ---------------------------------------------------
463
463
  # tmp+rename makes each save atomic, but two concurrent invocations (e.g. two
464
- # parallel Phase 4 reviewers reporting `tokens`) both load the same state and
464
+ # parallel Phase 3 reviewers reporting `tokens`) both load the same state and
465
465
  # the second save silently drops the first delta. Guard every load->save
466
466
  # sequence with a portable mkdir spinlock (macOS bash 3.2: no flock builtin).
467
467
  # The wait is bounded and the lock FAILS OPEN with a warning so a stuck lock
@@ -612,7 +612,7 @@ tracker_next_hint() {
612
612
  # Not every session carries the task tools: Claude Code provides them by
613
613
  # default only up to Opus 4.7 / Sonnet 4.6, a default that landed in
614
614
  # v2.1.268. Naming the fallback on the same line is what keeps a newer model
615
- # from advancing eight phases in silence.
615
+ # from advancing six phases in silence.
616
616
  # `subjects` now carries Phase 2's plan steps as indented rows, and
617
617
  # update_plan takes the whole list anyway, so re-reading it is what puts
618
618
  # those steps on the Codex plan without a second mechanism. Codex has no
@@ -643,7 +643,7 @@ format_span() {
643
643
  }
644
644
 
645
645
  # The end-of-run report: what the pipeline spent, phase by phase, and what it
646
- # has to say about phases it could not price. Printed by Phase 7 next to the
646
+ # has to say about phases it could not price. Printed by Phase 5 next to the
647
647
  # work summary, which covers what actually changed on disk.
648
648
  report() {
649
649
  need_jq
@@ -800,7 +800,7 @@ subjects() {
800
800
  #
801
801
  # Creation order is the ONLY ordering the native widget has - it renders by the
802
802
  # order tiles were made, not by any id inside them - so a plan that arrives at
803
- # Phase 2 cannot simply be appended: its rows would land after Phase 7. The
803
+ # Phase 2 cannot simply be appended: its rows would land after Phase 5. The
804
804
  # answer is a rebuild at that one boundary, which is the same thing `:resume`
805
805
  # already does for a different reason.
806
806
  tiles_script() {
@@ -811,7 +811,7 @@ tiles_script() {
811
811
  has_subs=$(echo "$state" | jq '[.phases[]?.subs[]?] | length')
812
812
 
813
813
  # A list carrying sub-steps has to be rebuilt whole, so the rebuild wins over a
814
- # narrowed batch: appending to it would land the new rows after Phase 7.
814
+ # narrowed batch: appending to it would land the new rows after Phase 5.
815
815
  [ "${has_subs:-0}" -gt 0 ] && new_only=""
816
816
 
817
817
  if [ -n "$new_only" ]; then
@@ -4,7 +4,7 @@
4
4
  // Phase 4 answers "is what changed correct" and Step 1.45 answers "did every
5
5
  // planned TEST land". Nothing answered "did every planned TASK land". The
6
6
  // criteria manifest's denominator is rule IDs, not plan steps, and the only
7
- // place a step's status surfaced was render-work-summary.sh at Phase 7 - a
7
+ // place a step's status surfaced was render-work-summary.sh at Phase 5 - a
8
8
  // report, printed after the commit. So a plan with seven steps could ship five
9
9
  // and read as done.
10
10
  //
@@ -116,7 +116,7 @@ if (isMain) {
116
116
  "",
117
117
  "Fails when a plan step never reached a terminal status, when a skip or",
118
118
  "failure carries no reason, or when an analysis Section 14 `Add new` file",
119
- "is missing from the tree. Run before Phase 6 commit.",
119
+ "is missing from the tree. Run before Phase 4 commit.",
120
120
  "",
121
121
  ].join("\n"),
122
122
  );
@@ -7,7 +7,7 @@
7
7
  #
8
8
  # This is the agent-log counterpart to render-cost-summary.sh (which
9
9
  # targets PR/Jira channel bodies). The agent-log version is rendered
10
- # unconditionally on every Phase 7 run; the channels version is opt-in
10
+ # unconditionally on every Phase 5 run; the channels version is opt-in
11
11
  # via prefs.global.reportContent.costSummary.
12
12
  #
13
13
  # Usage:
@@ -2,7 +2,7 @@
2
2
  # render-work-summary.sh - v7.1.0
3
3
  #
4
4
  # Reads agent-state.json + phase-tracker.json + git diff, emits a concise
5
- # "### Work Summary" markdown block for Phase 7 channels dispatch. Intended
5
+ # "### Work Summary" markdown block for Phase 5 channels dispatch. Intended
6
6
  # as an executive-summary companion to the existing Normal Analysis /
7
7
  # Technical Details / Test Scenarios sections - gives Jira/Confluence
8
8
  # readers and PR reviewers a single-screen answer to "what actually
@@ -85,7 +85,7 @@ if [ -n "$WORKTREE" ]; then
85
85
  [ -f "$WORKTREE/triage-output.json" ] && TRIAGE_FILE="$WORKTREE/triage-output.json"
86
86
  fi
87
87
 
88
- # Fall back to the salvaged artefacts when the worktree is gone. Phase 6 removes it
88
+ # Fall back to the salvaged artefacts when the worktree is gone. Phase 4 removes it
89
89
  # once the PR is open (worktree-finalize), and this script used to resolve state
90
90
  # ONLY from the worktree - so it exited 2 and the whole Work Summary silently
91
91
  # disappeared from the PR body and the Jira comment. render-agent-log-cost.sh has
@@ -203,7 +203,7 @@ total_add=0; total_del=0
203
203
  # repository check. Testing -d "$WORKTREE/.git" would be wrong regardless: in a
204
204
  # linked worktree .git is a file, not a directory.
205
205
  # Prefer the worktree; fall back to the project root with the branch by name. A
206
- # finalized task (Phase 6 removed the worktree after the PR) has no worktree but
206
+ # finalized task (Phase 4 removed the worktree after the PR) has no worktree but
207
207
  # the branch is still local, so without this the Changed-files section vanished
208
208
  # from the PR body and the Jira comment even though the data was right there.
209
209
  DIFF_IN=""; DIFF_TIP=""
@@ -4,7 +4,7 @@
4
4
  * @file review-file-filter.mjs - decide what the reviewers are asked to read.
5
5
  *
6
6
  * Phase 4 has a size cap and no exclusion list. When the diff exceeds the
7
- * budget the cap truncates the LARGEST files first (`phase-4-review.md`, Step
7
+ * budget the cap truncates the LARGEST files first (`phase-3-review.md`, Step
8
8
  * 1.9), so a regenerated lockfile or a snapshot dump does not merely waste
9
9
  * tokens - it is the thing that survives while real code is cut. The cheapest
10
10
  * fix is to decide what is worth reading before the cap decides what fits.
@@ -39,6 +39,7 @@ import { join, dirname } from "node:path";
39
39
  import { fileURLToPath } from "node:url";
40
40
  import { costUsd } from "./_cost.mjs";
41
41
  import { resolveRunFile, canonicalRunDir } from "./_run-paths.mjs";
42
+ import { toCurrentPhase, CURRENT_SCHEMA } from "../lib/phase-schema.mjs";
42
43
 
43
44
  const __dirname = dirname(fileURLToPath(import.meta.url));
44
45
 
@@ -157,16 +158,22 @@ function computeCost(modelKey, tokensIn, tokensOut, costTable) {
157
158
  }
158
159
 
159
160
  /**
160
- * Heuristic: phase 1/2 use Opus (per personas + dev-mode docs); phase 3 uses
161
- * Sonnet by default unless the run is a Short pipeline (Opus). Phase 4 review
162
- * is multi-model - span events carry per-reviewer model when OTel is on.
161
+ * Heuristic: Plan (1) uses Opus per personas + dev-mode docs; Dev (2) uses
162
+ * Sonnet by default unless the run is a Short pipeline (Opus); Review (3)
163
+ * triage is Opus and the reviewer breakdown comes from spans.
163
164
  *
164
165
  * This is a fallback when phase-tracker doesn't record the model used. Live
165
166
  * runs that emit OTel spans get per-call model attribution from the spans.
167
+ *
168
+ * The phase number is normalised first. The old body compared against the
169
+ * literals "1", "2" and "4", which under the six-phase contract are Plan, Dev
170
+ * and Commit - so a pre-v19 row and a post-v19 row would have been priced
171
+ * against different phases with no error anywhere.
166
172
  */
167
- function inferModelForPhase(phaseId) {
168
- if (phaseId === "1" || phaseId === "2") return "opus";
169
- if (phaseId === "4") return "opus"; // triage is opus; reviewer breakdown comes from spans
173
+ function inferModelForPhase(phaseId, schema = CURRENT_SCHEMA) {
174
+ const p = toCurrentPhase(phaseId, schema);
175
+ if (p === 1) return "opus";
176
+ if (p === 3) return "opus";
170
177
  return "sonnet";
171
178
  }
172
179
 
@@ -24,7 +24,7 @@
24
24
  * consensusVerdict - unanimous-* / split / unverified
25
25
  * buildPassed - per-repo build outcome
26
26
  * diff.filesTouched / locAdded / - size of the change, from state.diffRisk
27
- * locRemoved / maxScore (Phase 4 Step 1.75); null when the
27
+ * locRemoved / maxScore (Phase 3 Step 1.75); null when the
28
28
  * run predates the persisted totals
29
29
  * reviewDelta.stillPresentFinal / - cross-round classification of the
30
30
  * resolvedTotal / tripped last iteration (state.reviewIterations[].delta)
@@ -42,6 +42,16 @@ import { listRuns, logsRoot, resolveRunDir, taskIdVariants } from "./_run-paths.
42
42
  import { runMain } from "../lib/fatal.mjs";
43
43
  import { invokedDirectly } from "../lib/invoked-directly.mjs";
44
44
 
45
+ /**
46
+ * The phase from which a run counts as "waiting on you", read from the phase
47
+ * contract rather than written here. This threshold moved once already (it was
48
+ * 6 under the eight-phase contract, it is 4 under six) and nothing connected it
49
+ * to the renumbering, so it would have silently regrouped every run.
50
+ */
51
+ const PHASE_WAITING_FROM = JSON.parse(
52
+ readFileSync(new URL("../schemas/phases.json", import.meta.url), "utf8"),
53
+ ).thresholds.waitingFromPhase;
54
+
45
55
  const GROUPS = {
46
56
  waiting: "Waiting on you",
47
57
  stopped: "Stopped mid-development",
@@ -166,7 +176,7 @@ function groupOf(state) {
166
176
  if (status === "awaiting_input" || status === "awaiting-user-test-main-checkout")
167
177
  return "waiting";
168
178
  if (prUrl) return "waiting";
169
- if (Number.isFinite(phase) && phase >= 6) return "waiting";
179
+ if (Number.isFinite(phase) && phase >= PHASE_WAITING_FROM) return "waiting";
170
180
  if (Number.isFinite(phase) && phase === 0) return "question";
171
181
  return "stopped";
172
182
  }
@@ -204,21 +204,21 @@ echo "→ reviewer-count contract (Claude=3, Copilot=3, Codex=3)"
204
204
  # a broken installation. _smoke-root.sh handles both layouts.
205
205
  # shellcheck source=pipeline/scripts/_smoke-root.sh
206
206
  . "$(dirname "${BASH_SOURCE[0]}")/_smoke-root.sh"
207
- P4="${MA_REFS:+$MA_REFS/phases/phase-4-review.md}"
207
+ P4="${MA_REFS:+$MA_REFS/phases/phase-3-review.md}"
208
208
  REVSCHEMA="${MA_SCHEMAS:+$MA_SCHEMAS/reviewer-output.schema.json}"
209
209
  TRSCHEMA="${MA_SCHEMAS:+$MA_SCHEMAS/triage-output.schema.json}"
210
210
 
211
211
  if [ -z "$P4" ] || [ ! -f "$P4" ]; then
212
- echo " ↷ SKIP: phase-4-review.md not present in this $MA_LAYOUT layout"
212
+ echo " ↷ SKIP: phase-3-review.md not present in this $MA_LAYOUT layout"
213
213
  # Two independent statements, because they can drift apart: the count sentence is
214
214
  # the contract, and the matrix is what a reader dispatches from. Three regexes over
215
215
  # overlapping prose used to stand in for this and let the count sentence 300 lines
216
216
  # further down go stale for a whole release without failing.
217
217
  elif grep -qF "Claude Code 3, Copilot CLI 3, Codex CLI 3" "$P4" \
218
218
  && grep -qE '^\| Reviewer 3 .*\|.*\|.*\|.*\|' "$P4"; then
219
- pass "phase-4-review declares Claude=3 / Copilot=3 / Codex=3 reviewers, and the matrix has all three host columns"
219
+ pass "phase-3-review declares Claude=3 / Copilot=3 / Codex=3 reviewers, and the matrix has all three host columns"
220
220
  else
221
- fail "phase-4-review does not declare the CLI-aware reviewer count for all three hosts"
221
+ fail "phase-3-review does not declare the CLI-aware reviewer count for all three hosts"
222
222
  fi
223
223
 
224
224
  # The two Codex constraints are silent-failure shaped, so the contract has to name
@@ -226,9 +226,9 @@ fi
226
226
  # 4-slot ceiling (orchestrator included) is why the count is 3 and not more.
227
227
  if [ -n "$P4" ] && [ -f "$P4" ]; then
228
228
  if grep -q 'fork_turns' "$P4" && grep -qiE "concurrency|slots" "$P4"; then
229
- pass "phase-4-review documents the fork_turns override rule + the concurrency ceiling"
229
+ pass "phase-3-review documents the fork_turns override rule + the concurrency ceiling"
230
230
  else
231
- fail "phase-4-review must document fork_turns and the Codex concurrency ceiling"
231
+ fail "phase-3-review must document fork_turns and the Codex concurrency ceiling"
232
232
  fi
233
233
  fi
234
234
 
@@ -24,6 +24,26 @@ TEMPLATE="$SMOKE_DIR/../preferences-template.json"
24
24
  [ -f "$TEMPLATE" ] || TEMPLATE="$HOME/multi-agent-pipeline/pipeline/preferences-template.json"
25
25
  LIVE_PREFS="$HOME/.claude/multi-agent-preferences.json"
26
26
 
27
+ # The migration target, derived the way migrate-prefs.mjs derives it: the last
28
+ # entry of the schema's schemaVersion enum, read from the schema that sits
29
+ # beside the migrator being checked. It used to be grepped as a
30
+ # `TARGET_VERSION = "x.y.z"` literal out of the migrator, and when that literal
31
+ # was replaced by the schema read - precisely because a literal had drifted a
32
+ # minor behind - the grep started matching nothing and both checks that depend
33
+ # on it reported "no reference point". A gate that reads the source of truth
34
+ # cannot go stale against it; a gate that reads a transcription of it can.
35
+ migration_target() {
36
+ local schema="$1/../schemas/prefs.schema.json"
37
+ [ -f "$schema" ] || schema="$PREFS_SCHEMA"
38
+ [ -f "$schema" ] || return 1
39
+ node -e "
40
+ const sv = JSON.parse(require('fs').readFileSync('$schema','utf8'))?.properties?.schemaVersion;
41
+ const v = sv?.const ?? sv?.enum?.at(-1);
42
+ if (!v) process.exit(1);
43
+ process.stdout.write(v);
44
+ " 2>/dev/null
45
+ }
46
+
27
47
  # ──────────────────────────────────────────────────────────────────────────
28
48
  echo "→ 1. Schema files parse as JSON"
29
49
  for f in "$PREFS_SCHEMA" "$STATE_SCHEMA"; do
@@ -145,10 +165,9 @@ if [ -f "$TEMPLATE" ]; then
145
165
  # every old entry in the migrator's accepted set became load-bearing purely to
146
166
  # rescue the template this gate was holding back. A template behind the target
147
167
  # is a defect, not the expected shape.
148
- TEMPLATE_TARGET=$(grep -oE 'TARGET_VERSION = "[0-9.]+"' "$SMOKE_DIR/migrate-prefs.mjs" 2>/dev/null \
149
- | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
168
+ TEMPLATE_TARGET=$(migration_target "$SMOKE_DIR")
150
169
  if [ -z "$TEMPLATE_TARGET" ]; then
151
- fail "cannot read TARGET_VERSION from migrate-prefs.mjs - the check has no reference point"
170
+ fail "cannot derive the migration target from prefs.schema.json - the check has no reference point"
152
171
  elif [ "$TVER" = "$TEMPLATE_TARGET" ]; then
153
172
  pass "template schemaVersion: $TVER (at the migration target)"
154
173
  else
@@ -201,17 +220,17 @@ if [ -f "$LIVE_PREFS" ]; then
201
220
  # correctly migrated to 2.4.0 hit the else arm and FAILED as "unknown". The
202
221
  # gate was rejecting the only fully-migrated state it exists to encourage.
203
222
  #
204
- # Reading the target from the migrator, and the accepted set from the schema
205
- # enum, means this can never disagree with them again.
223
+ # Reading the target and the accepted set from the same schema enum the
224
+ # migrator reads means this can never disagree with it again.
206
225
  MIGRATOR="$SMOKE_DIR/migrate-prefs.mjs"
207
- TARGET=$(grep -oE 'TARGET_VERSION = "[0-9.]+"' "$MIGRATOR" 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
226
+ TARGET=$(migration_target "$(dirname "$MIGRATOR")")
208
227
  KNOWN=$(node -e "
209
228
  const s = require('$PREFS_SCHEMA');
210
229
  process.stdout.write((s.properties.schemaVersion.enum || []).join(' '));
211
230
  " 2>/dev/null)
212
231
 
213
232
  if [ -z "$TARGET" ]; then
214
- fail "cannot read TARGET_VERSION from migrate-prefs.mjs - the check has no reference point"
233
+ fail "cannot derive the migration target from prefs.schema.json - the check has no reference point"
215
234
  elif [ "$LVER" = "$TARGET" ]; then
216
235
  pass "live prefs at the migration target (v$TARGET)"
217
236
  elif [ "$LVER" = "none" ]; then
@@ -21,6 +21,7 @@
21
21
  import fs from "node:fs";
22
22
  import os from "node:os";
23
23
  import path from "node:path";
24
+ import { rowPhase } from "../lib/phase-schema.mjs";
24
25
 
25
26
  const args = parseArgs(process.argv.slice(2));
26
27
  const METRICS =
@@ -64,7 +65,12 @@ for (const e of events) {
64
65
  models: new Set(),
65
66
  });
66
67
  const t = byTask.get(e.task_id);
67
- t.phases.add(e.phase);
68
+ // Normalised to the current vocabulary before it goes in the set. This log
69
+ // is append-only across the v19.0.0 renumbering, so the raw field carries
70
+ // two different meanings and a set of raw values cannot be compared to
71
+ // anything.
72
+ const cp = rowPhase(e);
73
+ if (cp !== null) t.phases.add(cp);
68
74
  const d = e.details || {};
69
75
  if (typeof d.tokens_in === "number") t.tokens_in += d.tokens_in;
70
76
  if (typeof d.tokens_out === "number") t.tokens_out += d.tokens_out;
@@ -113,7 +119,12 @@ for (const [taskId, t] of byTask) {
113
119
  summary.totalTokensOut += t.tokens_out;
114
120
  summary.totalUsd += usd;
115
121
  const total = t.tokens_in + t.tokens_out;
116
- const isMulti = t.phases.has("4") && t.premium_calls > 5; // heuristic
122
+ // Heuristic: a task that reached Review with many premium calls is a
123
+ // multi-repo run. Keyed on Review (3), not on the literal "4" this line used
124
+ // to carry - under the six-phase contract 4 is Commit, and every run reaches
125
+ // Commit, so the old literal would have classified nearly everything as
126
+ // multi-repo and applied the wrong ceiling.
127
+ const isMulti = t.phases.has(3) && t.premium_calls > 5;
117
128
  const tokenLimit = isMulti ? limits.perTaskTokensMultiRepo : limits.perTaskTokens;
118
129
  const usdLimit = isMulti ? limits.perTaskUsdMultiRepo : limits.perTaskUsd;
119
130
  if (total > tokenLimit)
@@ -3,8 +3,8 @@
3
3
  /**
4
4
  * @file triage-memory.mjs - v8.3.0
5
5
  *
6
- * Lightweight, zero-dep persistence layer for past Phase 4 triage findings.
7
- * Used by Phase 7 to ingest results, by Phase 1/4 to look up prior art when
6
+ * Lightweight, zero-dep persistence layer for past Phase 3 triage findings.
7
+ * Used by Phase 5 to ingest results, by Phase 1/4 to look up prior art when
8
8
  * a new task overlaps with a previous one, and by /multi-agent:search.
9
9
  *
10
10
  * Storage: append-only JSONL, one file per repo: