canon-ai 3.4.0 → 3.6.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.
@@ -357,7 +357,7 @@ var WORKTREES_ROOT = process.env.CANON_WORKTREES_ROOT ? path.resolve(REPO_ROOT,
357
357
  var STALL_TIMEOUT_MS = Number(process.env.PIPELINE_STALL_TIMEOUT_MS) || 10 * 60 * 1e3;
358
358
  var STALL_KILL_GRACE_MS = 3e3;
359
359
  var LEGACY_FALLBACK_ENV_VARS = [
360
- { old: "CLAUDE_MODEL", replacement: "CLAUDE_MODEL_SPEC / _PLAN / _REVIEW (still honored as fallback for those three; not applied to qa)" },
360
+ { old: "CLAUDE_MODEL", replacement: "CLAUDE_MODEL_LIGHT / CLAUDE_MODEL_STRONG (still honored as a fallback for every Claude phase, including qa)" },
361
361
  { old: "CODEX_MODEL_DEFAULT", replacement: "CODEX_MODEL_MINI (still honored as fallback)" },
362
362
  { old: "CODEX_MODEL_DELICATE", replacement: "CODEX_MODEL_FULL (still honored as fallback)" }
363
363
  ];
@@ -416,15 +416,6 @@ function resolveProjectName() {
416
416
  }
417
417
  var config = {
418
418
  projectName: resolveProjectName(),
419
- claudeBudget: process.env.CLAUDE_BUDGET ?? null,
420
- claudeModelSpec: process.env.CLAUDE_MODEL_SPEC ?? process.env.CLAUDE_MODEL ?? "opus",
421
- claudeModelPlan: process.env.CLAUDE_MODEL_PLAN ?? process.env.CLAUDE_MODEL ?? "sonnet",
422
- claudeModelReview: process.env.CLAUDE_MODEL_REVIEW ?? process.env.CLAUDE_MODEL ?? "sonnet",
423
- claudeModelReviewLarge: process.env.CLAUDE_MODEL_REVIEW_LARGE ?? process.env.CLAUDE_MODEL ?? "opus",
424
- claudeModelQa: process.env.CLAUDE_MODEL_QA ?? process.env.CLAUDE_MODEL ?? "sonnet",
425
- codexModelMini: process.env.CODEX_MODEL_MINI ?? process.env.CODEX_MODEL_DEFAULT ?? "gpt-6-luna",
426
- codexModelFull: process.env.CODEX_MODEL_FULL ?? process.env.CODEX_MODEL_DELICATE ?? "gpt-6-sol",
427
- maxReviewLoops: parseMaxReviewLoops(process.env.MAX_REVIEW_LOOPS),
428
419
  maxContextBytes: Number.parseInt(process.env.MAX_CONTEXT_BYTES ?? String(64 * 1024), 10)
429
420
  };
430
421
 
@@ -1595,7 +1586,9 @@ function parseNameStatusOutput(raw) {
1595
1586
  return [...paths].sort();
1596
1587
  }
1597
1588
  function getAffectedFiles(baseRef, cwd) {
1598
- return getPathsInRange(`${baseRef}...HEAD`, cwd) ?? [];
1589
+ const result = gitSafeAtRaw(cwd, "diff", `${baseRef}...HEAD`, "--name-status", "-M", "-z");
1590
+ if (!result.ok) return { ok: false, stderr: result.stderr };
1591
+ return { ok: true, files: parseNameStatusOutput(result.stdout) };
1599
1592
  }
1600
1593
  function getPathsInRange(rangeExpr, cwd) {
1601
1594
  const result = gitSafeAtRaw(cwd, "diff", rangeExpr, "--name-status", "-M", "-z");
@@ -1733,7 +1726,37 @@ function codexMatrix(config3) {
1733
1726
  }
1734
1727
  };
1735
1728
  }
1736
- function claudeModelFor(config3, phase) {
1729
+ var CLAUDE_CELLS = {
1730
+ spec: {
1731
+ XS: { tier: "light", effort: "medium" },
1732
+ S: { tier: "light", effort: "medium" },
1733
+ M: { tier: "strong", effort: "medium" },
1734
+ L: { tier: "strong", effort: "medium" },
1735
+ XL: { tier: "strong", effort: "high" }
1736
+ },
1737
+ plan: {
1738
+ XS: { tier: "light", effort: "medium" },
1739
+ S: { tier: "light", effort: "medium" },
1740
+ M: { tier: "strong", effort: "medium" },
1741
+ L: { tier: "strong", effort: "medium" },
1742
+ XL: { tier: "strong", effort: "high" }
1743
+ },
1744
+ code_review: {
1745
+ XS: { tier: "light", effort: "medium" },
1746
+ S: { tier: "light", effort: "medium" },
1747
+ M: { tier: "strong", effort: "medium" },
1748
+ L: { tier: "strong", effort: "medium" },
1749
+ XL: { tier: "strong", effort: "high" }
1750
+ },
1751
+ qa: {
1752
+ XS: { tier: "light", effort: "medium" },
1753
+ S: { tier: "light", effort: "medium" },
1754
+ M: { tier: "light", effort: "medium" },
1755
+ L: { tier: "light", effort: "medium" },
1756
+ XL: { tier: "light", effort: "medium" }
1757
+ }
1758
+ };
1759
+ function claudePinFor(phase, size, config3) {
1737
1760
  switch (phase) {
1738
1761
  case "spec":
1739
1762
  return config3.claudeModelSpec;
@@ -1741,48 +1764,36 @@ function claudeModelFor(config3, phase) {
1741
1764
  return config3.claudeModelPlan;
1742
1765
  case "qa":
1743
1766
  return config3.claudeModelQa;
1744
- // code_review is size-keyed (see codeReviewMatrix in claudeMatrix); not
1745
- // resolved through this helper. spec_review, implement, human_review
1746
- // aren't Claude phases; fall back to the spec model so resumed Claude
1747
- // sessions survive accidental use.
1748
- default:
1749
- return config3.claudeModelSpec;
1767
+ case "code_review":
1768
+ return size === "XL" ? config3.claudeModelReviewLarge : config3.claudeModelReview;
1750
1769
  }
1751
1770
  }
1771
+ function resolveClaudeModel(phase, size, config3) {
1772
+ const pin = claudePinFor(phase, size, config3);
1773
+ if (pin !== null) return pin;
1774
+ const tier = CLAUDE_CELLS[phase][size].tier;
1775
+ const tierModel = tier === "light" ? config3.claudeModelLight : config3.claudeModelStrong;
1776
+ if (tierModel !== null) return tierModel;
1777
+ if (config3.claudeModelLegacy !== null) return config3.claudeModelLegacy;
1778
+ return tier === "light" ? "sonnet" : "opus";
1779
+ }
1752
1780
  function claudeMatrix(config3) {
1753
- const buildHigh = (phase, xlEffort = "xhigh") => {
1754
- const model = claudeModelFor(config3, phase);
1755
- return {
1756
- XS: { model, effort: "medium" },
1757
- S: { model, effort: "medium" },
1758
- M: { model, effort: "high" },
1759
- L: { model, effort: "high" },
1760
- XL: { model, effort: xlEffort }
1761
- };
1762
- };
1763
- const buildMedium = (phase) => {
1764
- const model = claudeModelFor(config3, phase);
1765
- return {
1766
- XS: { model, effort: "medium" },
1767
- S: { model, effort: "medium" },
1768
- M: { model, effort: "medium" },
1769
- L: { model, effort: "high" },
1770
- XL: { model, effort: "high" }
1771
- };
1772
- };
1773
- const codeReviewMatrix = () => ({
1774
- XS: { model: config3.claudeModelReview, effort: "medium" },
1775
- S: { model: config3.claudeModelReview, effort: "medium" },
1776
- M: { model: config3.claudeModelReview, effort: "high" },
1777
- L: { model: config3.claudeModelReview, effort: "high" },
1778
- XL: { model: config3.claudeModelReviewLarge, effort: "xhigh" }
1781
+ const cell = (phase, size) => ({
1782
+ model: resolveClaudeModel(phase, size, config3),
1783
+ effort: CLAUDE_CELLS[phase][size].effort
1784
+ });
1785
+ const row = (phase) => ({
1786
+ XS: cell(phase, "XS"),
1787
+ S: cell(phase, "S"),
1788
+ M: cell(phase, "M"),
1789
+ L: cell(phase, "L"),
1790
+ XL: cell(phase, "XL")
1779
1791
  });
1780
1792
  return {
1781
- spec: buildHigh("spec"),
1782
- plan: buildHigh("plan", "high"),
1783
- // sonnet doesn't support xhigh
1784
- code_review: codeReviewMatrix(),
1785
- qa: buildMedium("qa")
1793
+ spec: row("spec"),
1794
+ plan: row("plan"),
1795
+ code_review: row("code_review"),
1796
+ qa: row("qa")
1786
1797
  };
1787
1798
  }
1788
1799
  function getPipelinePolicy(tasks, config3) {
@@ -1808,11 +1819,14 @@ function getPipelinePolicy(tasks, config3) {
1808
1819
 
1809
1820
  // src/orchestrator/policy.ts
1810
1821
  var config2 = {
1811
- claudeModelSpec: process.env.CLAUDE_MODEL_SPEC ?? process.env.CLAUDE_MODEL ?? "opus",
1812
- claudeModelPlan: process.env.CLAUDE_MODEL_PLAN ?? process.env.CLAUDE_MODEL ?? "sonnet",
1813
- claudeModelReview: process.env.CLAUDE_MODEL_REVIEW ?? process.env.CLAUDE_MODEL ?? "sonnet",
1814
- claudeModelReviewLarge: process.env.CLAUDE_MODEL_REVIEW_LARGE ?? process.env.CLAUDE_MODEL ?? "opus",
1815
- claudeModelQa: process.env.CLAUDE_MODEL_QA ?? process.env.CLAUDE_MODEL ?? "sonnet",
1822
+ claudeModelSpec: process.env.CLAUDE_MODEL_SPEC ?? null,
1823
+ claudeModelPlan: process.env.CLAUDE_MODEL_PLAN ?? null,
1824
+ claudeModelReview: process.env.CLAUDE_MODEL_REVIEW ?? null,
1825
+ claudeModelReviewLarge: process.env.CLAUDE_MODEL_REVIEW_LARGE ?? null,
1826
+ claudeModelQa: process.env.CLAUDE_MODEL_QA ?? null,
1827
+ claudeModelLight: process.env.CLAUDE_MODEL_LIGHT || null,
1828
+ claudeModelStrong: process.env.CLAUDE_MODEL_STRONG || null,
1829
+ claudeModelLegacy: process.env.CLAUDE_MODEL ?? null,
1816
1830
  codexModelMini: process.env.CODEX_MODEL_MINI ?? process.env.CODEX_MODEL_DEFAULT ?? "gpt-6-luna",
1817
1831
  codexModelFull: process.env.CODEX_MODEL_FULL ?? process.env.CODEX_MODEL_DELICATE ?? "gpt-6-sol",
1818
1832
  maxReviewLoops: parseMaxReviewLoops(process.env.MAX_REVIEW_LOOPS),
@@ -1825,6 +1839,9 @@ function policyConfig() {
1825
1839
  claudeModelReview: config2.claudeModelReview,
1826
1840
  claudeModelReviewLarge: config2.claudeModelReviewLarge,
1827
1841
  claudeModelQa: config2.claudeModelQa,
1842
+ claudeModelLight: config2.claudeModelLight,
1843
+ claudeModelStrong: config2.claudeModelStrong,
1844
+ claudeModelLegacy: config2.claudeModelLegacy,
1828
1845
  codexModelMini: config2.codexModelMini,
1829
1846
  codexModelFull: config2.codexModelFull,
1830
1847
  maxReviewLoops: config2.maxReviewLoops,
@@ -1894,6 +1911,7 @@ var CLAUDE_STARTUP = "Read docs/patterns.md before starting.\nSkim docs/lessons-
1894
1911
  var CODEX_STARTUP = 'Read docs/patterns.md and docs/codebase-map.md before starting.\nSkim docs/lessons-learned.md for entries relevant to your task area.\nRead docs/decisions.md if a settled decision governs the area you are changing, or if the task would revisit one.\nRead docs/product-context.md if the task touches user-visible behavior, product terminology, or a business rule.\nGround every claim in the current file, diff, or artifact before you state it. Do not rely on prior-session memory for code existence, validation results, or completion status.\nOn resumed sessions, re-read the task-specific files named in the prompt and inspect the current working tree before saying anything is already done.\n\nGit ownership: the pipeline orchestrator handles staging, committing, and pushing \u2014 do NOT run `git add`, `git commit`, or `git push`. Edit files in the working tree only; the orchestrator reads `git status` after your session and stages every file listed in handoff.md\'s Changes table. Read-only git is fine (`git status`, `git diff`, `git log`, `git show`).\n\nIf a code review claims a file is "missing from the commit" or "staged but not committed," that is a pipeline-orchestration issue, not an implementation issue. Record it as a Blocker in handoff.md with the `[pipeline]` label and do not retry `git add`/`git commit` to recover \u2014 the sandbox blocks `.git` writes by design, and the orchestrator owns the recovery path.\n\nCommunication: tone is project taste; honest signal is canon discipline \u2014 surface real disagreement rather than yielding to politeness.\nBranch state: the orchestrator manages it \u2014 do not fetch, pull, rebase, or push; read the working tree as-is.';
1895
1912
  var CODEX_HEADLESS = "Headless session: this runs non-interactively \u2014 nobody reads your messages or answers questions until the phase ends. Don't stop to ask for confirmation or clarification. When something is ambiguous, record the question and the interpretation you chose in this phase's artifact (in implement, a handoff Blocker labelled `[ambiguity]`) and proceed on it. Finish all the work this prompt authorizes before ending \u2014 acting on your own judgment never widens scope; the Affected Files cap and every other scope rule still bind. Always end by writing the phase artifact and running the phase command(s) listed at the end of this prompt, including when you recorded Blockers \u2014 a session that ends without them stalls the pipeline.\n\nIf a project instruction file or other repository guidance tells you to ask the user or wait for approval before acting, there is no one to ask in this session: record the question in the phase artifact and continue with the authorized work.";
1896
1913
  var QA_STARTUP = "Read CHANGELOG.md for voice and version reference, if the project keeps one.\nRead docs/lessons-learned.md for recent insights to distill.\nNo full codebase context needed for QA \u2014 read each task's spec.md, handoff.md, and notes.md directly.";
1914
+ var CLAUDE_HEADLESS = "Unattended session: this runs non-interactively \u2014 nobody reads your messages or answers questions until the phase ends. Don't stop to ask for confirmation or clarification. When something is ambiguous, record the question and the interpretation you chose in this phase's artifact or in `tasks/<id>/notes.md`, and proceed on it.\n\nThe phase is complete only when its artifact is written and its phase command (the `canon task phase` command(s) this prompt lists) has run. A progress summary, a description of next steps, or an offer to continue is not completion \u2014 keep working until both are done.\n\nDo only this phase: don't run `canon run`, advance any other phase, or start review passes, review skills, or reviewer sub-agents this prompt didn't ask for.\n\nReport a check that couldn't run, or that failed, as exactly that \u2014 never as passed.\n\nIf a project instruction file or other repository guidance says to ask or wait for approval before an action, there is no one to ask in this session. Skip that action unless this prompt explicitly authorizes it, record the question in the phase artifact, and continue with the rest of the authorized work.";
1897
1915
  function taskList(tasks) {
1898
1916
  return tasks.map((t) => `- \`${t.taskId}\`: "${t.title}" \u2192 tasks/${t.taskId}/`).join("\n");
1899
1917
  }
@@ -2116,7 +2134,10 @@ async function runClaude(prompt, interactive, resumeId, model, effort, budget, m
2116
2134
  };
2117
2135
  }
2118
2136
  const attempt = async (useResumeId) => {
2119
- const effectivePrompt = useResumeId ? toResumePrompt(prompt) : prompt;
2137
+ const renderedPrompt = useResumeId ? toResumePrompt(prompt) : prompt;
2138
+ const effectivePrompt = `${CLAUDE_HEADLESS}
2139
+
2140
+ ${renderedPrompt}`;
2120
2141
  const args = [
2121
2142
  "-p",
2122
2143
  effectivePrompt,
@@ -4716,7 +4737,7 @@ function renderTemplate(template, view) {
4716
4737
  }
4717
4738
 
4718
4739
  // src/orchestrator/prompts/templates/code-review-foreman.md
4719
- var code_review_foreman_default = "You are the synthesis foreman for the code review phase for {{taskScope}} for {{projectName}}.\n\n{{{startup}}}\n\n## Code-Review Rules of Thumb (Foreman)\n\n- **Reviewer diffs against the task baseline, not `main`, on release branches**: on a shared release branch ahead of `main`, always diff against the task's baseline \u2014 diffing against `main` attributes unrelated work to the task.\n- **Use `git -C <absolute-path>` for every worktree git op, not `cd` + git**: when operating across REPO_ROOT and a task worktree, `git -C /absolute/path` avoids silent cwd reversion between tool calls.\n- **Don't infer one git invariant from another**: `git status --porcelain` empty \u2260 origin matches HEAD; `origin/<branch>` exists \u2260 origin matches HEAD; PR exists \u2260 PR is in the expected state. Do the actual check directly.\n- **A cross-cutting invariant belongs in one shared helper, not patched per call site**: when the same rule must hold at multiple enforcement points, implement it once. The tell: findings come back round after round as the same bug class at a new location. At \u22653 sites, extract the shared helper and route all sites through it.\n\nYour job is to synthesize three review inputs: the anchored Claude lens, the cold-Claude lens, and the pre-obtained cold-Codex findings injected below. You spawn the Claude lenses as isolated sub-agents, collect their findings, adjudicate all three inputs using the spec (which you hold and the cold lenses do not), then write one `review.md` and set the verdict. Do not run `codex` yourself.\n\nTasks:\n{{{taskLines}}}\n\n{{#isRound1}}\nThis is Round 1, the initial code review.\n{{/isRound1}}\n{{^isRound1}}\nThis is Round {{roundN}}: re-review after iteration {{priorIteration}}. All three lenses run again this round. Direct the anchored lens to read the Iteration {{priorIteration}} section of `handoff.md` that addresses review round {{priorIteration}}.\n{{#tightenLine}}\n{{{tightenLine}}}\n{{/tightenLine}}\n{{/isRound1}}\n\n**Scope:** {{{scopeWord}}} \u2014 base `{{{scopeBase}}}` \u2014 reason: {{{scopeReason}}}\n\n{{#hasDiff}}\nTask diff against {{{baseBranch}}}:\n\n```diff\n{{{diffContent}}}\n```\n{{#diffTruncated}}\n> Diff truncated at 50 000 bytes. Give the Claude lenses the visible diff first; for the omitted remainder, direct them to inspect only the changed files named in the handoff Changes table. Do not give the cold-Claude lens spec, AC, or canon-doc context.\n{{/diffTruncated}}\n{{/hasDiff}}\n{{^hasDiff}}\nRetrieve the task diff with `git diff {{{baseBranch}}}...HEAD`.\n{{/hasDiff}}\n{{#isDeltaScope}}\n\n{{#hasDeltaDiff}}\nDelta diff (`{{{deltaBase}}}..HEAD`) \u2014 give this delta to the cold-Claude lens:\n\n```diff\n{{{deltaDiffContent}}}\n```\n{{#deltaDiffTruncated}}\n> Delta diff truncated at 50 000 bytes. Give the cold-Claude lens the visible delta first; for the remainder, direct it to `git diff {{{deltaBase}}}..HEAD`.\n{{/deltaDiffTruncated}}\n{{/hasDeltaDiff}}\n{{^hasDeltaDiff}}\nThe delta diff could not be injected. Retrieve it with `git diff {{{deltaBase}}}..HEAD` and give it to the cold-Claude lens before it reviews.\n{{/hasDeltaDiff}}\n{{/isDeltaScope}}\n\n## Injected Cold-Codex Findings\n\n{{#hasColdCodexFindings}}\nThe orchestrator ran `codex review` before spawning you. Its findings are reproduced below. These are unanchored: Codex reviewed adversarially without the spec as a checklist. Treat them as the third lens input. Do not re-run Codex; synthesize these findings alongside the Claude lens outputs.\n{{#isDeltaScope}}\nFor this round, cold-Codex reviewed only `{{{deltaBase}}}..HEAD`.\n{{/isDeltaScope}}\n{{^isDeltaScope}}\nFor this round, cold-Codex reviewed the full task diff against `{{{baseBranch}}}`.\n{{/isDeltaScope}}\n\n{{{coldCodexFindings}}}\n{{/hasColdCodexFindings}}\n{{^hasColdCodexFindings}}\nNo cold-Codex findings were provided to this prompt. In production code_review, the orchestrator must obtain that artifact before foreman synthesis; do not treat a missing cold-Codex lens as approval evidence.\n{{/hasColdCodexFindings}}\n\n{{#hasOutOfScopeFiles}}\n## Full-Send Scope Judgment\n\nThe task changed these files outside the spec's `### Affected Files` table:\n\n{{{outOfScopeFilesList}}}\n\nJudge each file against the owning task's ACs and record its outcome and reason in `review.md`:\n\n1. **Appropriate scope expansion:** If needed to meet the ACs and consistent with the spec's intent, append an `## Amendment (full-send scope)` section to the owning task's `spec.md` (use exactly that heading; a bare `## Amendment` is reserved for human reroute amendments). Include an exact `### Affected Files` heading and table row naming the file, plus a one-line reason that calls it a full-send scope expansion.\n2. **Spec miss:** If the spec should have listed the file, append the same `## Amendment (full-send scope)` section and exact `### Affected Files` table row, with a one-line reason that calls it a spec miss.\n3. **Should not have been changed:** Do not amend the spec. Write a finding requiring the file to be reverted or removed, and set the verdict to `changes_requested`.\n\nFor a bundle, amend the member spec whose ACs the file serves. The orchestrator checks after code review completes: every listed file must be in a member's Affected Files or a `changes_requested` verdict must route it back to implement. The legacy `needs_re_review` verdict routes the same way. A `spec_gap` verdict blocks for human triage and names any files still outside Affected Files; blessing that verdict does not amend the spec. An approved review with unjudged files auto-blocks.\n\n{{/hasOutOfScopeFiles}}\n## Foreman Protocol\n\n### 1. Spawn Claude Lenses In Parallel\n\nSpawn both Claude lenses with the sub-agent tool (`Agent`, called `Task` in older harnesses) in a single message so they run concurrently, and run them **in the foreground** (`run_in_background: false`) so the call returns their findings. Do not spawn them in the background and do not end your turn to wait for them: a turn that ends while a lens is still running ends the code review with no `review.md` and no verdict, and the phase is retried from scratch. Your turn ends only after step 5 below has run.\n\n**Anchored lens** (`subagent_type: code-review-anchored`)\n{{#isDeltaScope}}\n- Retrieve the full task diff with `git diff {{{baseBranch}}}...HEAD` and give it that full diff, not the delta, plus `spec.md`, `handoff.md`, and prior `review.md`.\n{{/isDeltaScope}}\n{{^isDeltaScope}}\n- Give it the full diff, `spec.md`, `handoff.md`, and prior `review.md` if this is a re-review.\n{{/isDeltaScope}}\n- It applies canon's anchored Stage 1 / Stage 2 code-review charter.\n- It returns structured findings to you. It must not write `review.md` or run `canon task phase`.\n\n**Cold-Claude lens** (`subagent_type: code-review-cold`)\n{{#isDeltaScope}}\n- This round is delta-scoped. Give it the delta (`{{{deltaBase}}}..HEAD`) and the delta base ref only.\n{{/isDeltaScope}}\n{{^isDeltaScope}}\n- Give it the full diff and base ref only.\n{{/isDeltaScope}}\n- Do not give it `spec.md`, ACs, handoff rationale, canon docs, known risks, or your anchored-lens prompt.\n- If it needs to inspect files for truncated diff context, constrain it to changed files only and preserve the spec-blind framing.\n- Separately, its sibling-site sweep may search other code files for guards, checks, and invariants that need the same treatment. Keep the search spec-blind.\n- It returns structured findings to you. It must not write `review.md` or run `canon task phase`.\n\nThe injected cold-Codex findings above are the third lens input. Do not spawn a Codex agent or shell out to Codex yourself. Do not let a Claude lens see another lens's output.\n\n### 2. Adjudicate\n\nUse the three lens inputs and the spec. Do not perform a new full diff review for novel bugs; your role is synthesis and adjudication.\n\nThe lenses are instructed to over-report \u2014 to surface low-confidence and low-severity findings rather than self-censor. Filtering is **your** job, not theirs: a quiet lens output is a bug in the lens, not a clean diff. Rank surviving findings by confidence \xD7 severity. A low-confidence, low-severity finding is a nit or gets dismissed; it does not by itself drive `changes_requested`. Do not discard a finding merely because a lens marked it low-confidence \u2014 verify it against the spec/diff first, then rank.\n\n1. Dedup: if 2+ lenses flagged the same behavior, collapse it to one finding and record \"flagged by N lenses.\" A finding flagged by 2+ lenses is higher-confidence regardless of any lens's self-tag. Cross-model agreement \u2014 the same behavior flagged by cold-Claude and cold-Codex \u2014 must not be dismissed as spec-intended without explicit spec evidence cited in `review.md`.\n2. Keep the two reconciliation checks separate:\n - Does it hold against the code? For cold findings (cold-Claude and cold-Codex), verify each against the diff/code. Codex P-levels are claims to check, not verdicts. A finding that does not hold gets recorded as `Dismissed (cold-Claude): <finding> - <reason>` or `Dismissed (cold-Codex): <finding> - <reason>`.\n - Is it in spec scope? Apply this only to anchored-lens findings as part of the Stage 1 / Stage 2 charter.\n - Forbidden: do not dismiss a verified cold-Claude or cold-Codex finding merely for being off-AC or out of spec scope. A real bug caught by a cold lens is still a bug even if no AC named it.\n3. Altitude classification: every surviving finding is either:\n - `code-bug`: the implementation is wrong or test integrity is compromised.\n - `spec-gap`: the implementation may match the written spec, but the spec is missing, wrong, or too ambiguous for the implementer to fix.\n\n### 3. Choose Verdict\n\n- Any `code-bug` finding -> `changes_requested`.\n- Any `spec-gap` finding and no code-bugs -> `spec_gap`.\n- Optional nits or cleanup without blocking findings -> `approved_with_nits`.\n- No surviving findings -> `approved`.\n\nTest-integrity findings are always code-bugs.\n\n### 4. Write `review.md`\n\nFor each task, write `tasks/<id>/review.md`.\n\nRound 1 fills the existing template structure directly \u2014 do **not** wrap it in a `## Round 1` section; the `## Stage 1` and `## Stage 2` headings stay at H2. Re-review appends a new `## Round {{roundN}}` section near the bottom (with `### Stage 1` / `### Stage 2` sub-headings), preserving earlier rounds.\n\nEvery round \u2014 the Round 1 body and each `## Round N` section \u2014 includes one scope line near the top: **Scope:** {{{scopeWord}}} \u2014 base `{{{scopeBase}}}` \u2014 reason: {{{scopeReason}}}. The numbered cold-Codex archive header is the machine-readable record.\n\nInclude:\n- Stage 1: anchored lens validation gate result and AC table.\n- Stage 2 / Findings: surviving findings with altitude (`code-bug` or `spec-gap`), source lens, and file:line.\n- Dismissed Cold Findings: every dropped cold finding plus the reason, including `Dismissed (cold-Claude): ...` and `Dismissed (cold-Codex): ...` entries where applicable.\n- Final Verdict: check exactly one verdict checkbox, including `Spec gap` when applicable.\n\n### 5. Set Phase Verdict\n\nRun one command per task with the actual verdict:\n{{{phaseCommands}}}\n";
4740
+ var code_review_foreman_default = "You are the synthesis foreman for the code review phase for {{taskScope}} for {{projectName}}.\n\n{{{startup}}}\n\n## Code-Review Rules of Thumb (Foreman)\n\n- **Reviewer diffs against the task baseline, not `main`, on release branches**: on a shared release branch ahead of `main`, always diff against the task's baseline \u2014 diffing against `main` attributes unrelated work to the task.\n- **Use `git -C <absolute-path>` for every worktree git op, not `cd` + git**: when operating across REPO_ROOT and a task worktree, `git -C /absolute/path` avoids silent cwd reversion between tool calls.\n- **Don't infer one git invariant from another**: `git status --porcelain` empty \u2260 origin matches HEAD; `origin/<branch>` exists \u2260 origin matches HEAD; PR exists \u2260 PR is in the expected state. Do the actual check directly.\n- **A cross-cutting invariant belongs in one shared helper, not patched per call site**: when the same rule must hold at multiple enforcement points, implement it once. The tell: findings come back round after round as the same bug class at a new location. At \u22653 sites, extract the shared helper and route all sites through it.\n\nYour job is to synthesize three review inputs: the anchored Claude lens, the cold-Claude lens, and the pre-obtained cold-Codex findings injected below. You spawn the Claude lenses as isolated sub-agents, collect their findings, adjudicate all three inputs using the spec (which you hold and the cold lenses do not), then write one `review.md` and set the verdict. Do not run `codex` yourself.\n\nTasks:\n{{{taskLines}}}\n\n{{#isRound1}}\nThis is Round 1, the initial code review.\n{{/isRound1}}\n{{^isRound1}}\nThis is Round {{roundN}}: re-review after iteration {{priorIteration}}. All three lenses run again this round. Direct the anchored lens to read the Iteration {{priorIteration}} section of `handoff.md` that addresses review round {{priorIteration}}.\n{{#tightenLine}}\n{{{tightenLine}}}\n{{/tightenLine}}\n{{/isRound1}}\n\n**Scope:** {{{scopeWord}}} \u2014 base `{{{scopeBase}}}` \u2014 reason: {{{scopeReason}}}\n\n{{#hasDiff}}\nTask diff against {{{baseBranch}}}:\n\n```diff\n{{{diffContent}}}\n```\n{{#diffTruncated}}\n> Diff truncated at 50 000 bytes. Give the Claude lenses the visible diff first; for the omitted remainder, direct them to inspect only the changed files named in the handoff Changes table. Do not give the cold-Claude lens spec, AC, or canon-doc context.\n{{/diffTruncated}}\n{{/hasDiff}}\n{{^hasDiff}}\nRetrieve the task diff with `git diff {{{baseBranch}}}...HEAD`.\n{{/hasDiff}}\n{{#isDeltaScope}}\n\n{{#hasDeltaDiff}}\nDelta diff (`{{{deltaBase}}}..HEAD`) \u2014 give this delta to the cold-Claude lens:\n\n```diff\n{{{deltaDiffContent}}}\n```\n{{#deltaDiffTruncated}}\n> Delta diff truncated at 50 000 bytes. Give the cold-Claude lens the visible delta first; for the remainder, direct it to `git diff {{{deltaBase}}}..HEAD`.\n{{/deltaDiffTruncated}}\n{{/hasDeltaDiff}}\n{{^hasDeltaDiff}}\nThe delta diff could not be injected. Retrieve it with `git diff {{{deltaBase}}}..HEAD` and give it to the cold-Claude lens before it reviews.\n{{/hasDeltaDiff}}\n{{/isDeltaScope}}\n\n## Injected Cold-Codex Findings\n\n{{#hasColdCodexFindings}}\nThe orchestrator ran `codex review` before spawning you. Its findings are reproduced below. These are unanchored: Codex reviewed adversarially without the spec as a checklist. Treat them as the third lens input. Do not re-run Codex; synthesize these findings alongside the Claude lens outputs.\n{{#isDeltaScope}}\nFor this round, cold-Codex reviewed only `{{{deltaBase}}}..HEAD`.\n{{/isDeltaScope}}\n{{^isDeltaScope}}\nFor this round, cold-Codex reviewed the full task diff against `{{{baseBranch}}}`.\n{{/isDeltaScope}}\n\n{{{coldCodexFindings}}}\n{{/hasColdCodexFindings}}\n{{^hasColdCodexFindings}}\nNo cold-Codex findings were provided to this prompt. In production code_review, the orchestrator must obtain that artifact before foreman synthesis; do not treat a missing cold-Codex lens as approval evidence.\n{{/hasColdCodexFindings}}\n\n{{#hasOutOfScopeFiles}}\n## Full-Send Scope Judgment\n\nThe task changed these files outside the spec's `### Affected Files` table:\n\n{{{outOfScopeFilesList}}}\n\nJudge each file against the owning task's ACs and record its outcome and reason in `review.md`:\n\n1. **Appropriate scope expansion:** If needed to meet the ACs and consistent with the spec's intent, append an `## Amendment (full-send scope)` section to the owning task's `spec.md` (use exactly that heading; a bare `## Amendment` is reserved for human reroute amendments). Include an exact `### Affected Files` heading and table row naming the file, plus a one-line reason that calls it a full-send scope expansion.\n2. **Spec miss:** If the spec should have listed the file, append the same `## Amendment (full-send scope)` section and exact `### Affected Files` table row, with a one-line reason that calls it a spec miss.\n3. **Should not have been changed:** Do not amend the spec. Write a finding requiring the file to be reverted or removed, and set the verdict to `changes_requested`.\n\nFor a bundle, amend the member spec whose ACs the file serves. The orchestrator checks after code review completes: every listed file must be in a member's Affected Files or a `changes_requested` verdict must route it back to implement. The legacy `needs_re_review` verdict routes the same way. A `spec_gap` verdict blocks for human triage and names any files still outside Affected Files; blessing that verdict does not amend the spec. An approved review with unjudged files auto-blocks.\n\n{{/hasOutOfScopeFiles}}\n## Foreman Protocol\n\n### 1. Spawn Claude Lenses In Parallel\n\nSpawn both Claude lenses with the sub-agent tool (`Agent`, called `Task` in older harnesses) in a single message so they run concurrently, and run them **in the foreground** (`run_in_background: false`) so the call returns their findings. Do not spawn them in the background and do not end your turn to wait for them: a turn that ends while a lens is still running ends the code review with no `review.md` and no verdict, and the phase is retried from scratch. Your turn ends only after step 5 below has run.\n\n**Anchored lens** (`subagent_type: code-review-anchored`)\n{{#isDeltaScope}}\n- Retrieve the full task diff with `git diff {{{baseBranch}}}...HEAD` and give it that full diff, not the delta, plus `spec.md`, `handoff.md`, and prior `review.md`.\n{{/isDeltaScope}}\n{{^isDeltaScope}}\n- Give it the full diff, `spec.md`, `handoff.md`, and prior `review.md` if this is a re-review.\n{{/isDeltaScope}}\n- It applies canon's anchored Stage 1 / Stage 2 code-review charter.\n- It returns structured findings to you. It must not write `review.md` or run `canon task phase`.\n\n**Cold-Claude lens** (`subagent_type: code-review-cold`)\n{{#isDeltaScope}}\n- This round is delta-scoped. Give it the delta (`{{{deltaBase}}}..HEAD`) and the delta base ref only.\n{{/isDeltaScope}}\n{{^isDeltaScope}}\n- Give it the full diff and base ref only.\n{{/isDeltaScope}}\n- Do not give it `spec.md`, ACs, handoff rationale, canon docs, known risks, or your anchored-lens prompt.\n- If it needs to inspect files for truncated diff context, constrain it to changed files only and preserve the spec-blind framing.\n- Separately, its sibling-site sweep may search other code files for guards, checks, and invariants that need the same treatment. Keep the search spec-blind.\n- It returns structured findings to you. It must not write `review.md` or run `canon task phase`.\n\nThe injected cold-Codex findings above are the third lens input. Do not spawn a Codex agent or shell out to Codex yourself. Do not let a Claude lens see another lens's output.\n\nSpawn exactly the two Claude lenses above each round, plus at most one re-spawn of a lens whose return is missing. Spawn no other reviewers or review agents, run no review skills, and never substitute your own review for a lens's return.\n\nA lens return is valid when it follows that lens's charter return format. This includes a clean empty form (`STAGE_2_FINDINGS: (none)` or `COLD_FINDINGS: (none)`) and the anchored Stage 1 fail form (`STAGE_1: fail`, no Stage 2 findings, `OVERALL_SIGNAL: changes_requested`). A return is missing if absent or outside the charter format. Re-spawn a missing lens once with the same inputs. If still missing, do not approve or guess at its findings: it is not approval evidence. Write `review.md` recording which lens was missing and what came back, check no verdict box, then run the `code_review blocked` commands in step 5 for every task. Other lenses' findings may be recorded but not adjudicated into a verdict.\n\n### 2. Adjudicate\n\nUse the three lens inputs and the spec. Do not perform a new full diff review for novel bugs; your role is synthesis and adjudication.\n\nThe lenses are instructed to over-report \u2014 to surface low-confidence and low-severity findings rather than self-censor. Filtering is **your** job, not theirs. A clean return in the charter's empty form is a valid result; only a missing return (step 1) is a lens failure. Rank surviving findings by confidence \xD7 severity. A low-confidence, low-severity finding is a nit or gets dismissed; it does not by itself drive `changes_requested`. Do not discard a finding merely because a lens marked it low-confidence \u2014 verify it against the spec/diff first, then rank.\n\n1. Dedup: if 2+ lenses flagged the same behavior, collapse it to one finding and record \"flagged by N lenses.\" A finding flagged by 2+ lenses is higher-confidence regardless of any lens's self-tag. Cross-model agreement \u2014 the same behavior flagged by cold-Claude and cold-Codex \u2014 must not be dismissed as spec-intended without explicit spec evidence cited in `review.md`.\n2. Keep the two reconciliation checks separate:\n - Does it hold against the code? For cold findings (cold-Claude and cold-Codex), verify each against the diff/code. Codex P-levels are claims to check, not verdicts. A finding that does not hold gets recorded as `Dismissed (cold-Claude): <finding> - <reason>` or `Dismissed (cold-Codex): <finding> - <reason>`.\n - Is it in spec scope? Apply this only to anchored-lens findings as part of the Stage 1 / Stage 2 charter.\n - Forbidden: do not dismiss a verified cold-Claude or cold-Codex finding merely for being off-AC or out of spec scope. A real bug caught by a cold lens is still a bug even if no AC named it.\n3. Altitude classification: every surviving finding is either:\n - `code-bug`: the implementation is wrong or test integrity is compromised.\n - `spec-gap`: the implementation may match the written spec, but the spec is missing, wrong, or too ambiguous for the implementer to fix.\n\n### 3. Choose Verdict\n\n- Any `code-bug` finding -> `changes_requested`.\n- Any `spec-gap` finding and no code-bugs -> `spec_gap`.\n- Optional nits or cleanup without blocking findings -> `approved_with_nits`.\n- No surviving findings -> `approved`.\n\nTest-integrity findings are always code-bugs.\n\n### 4. Write `review.md`\n\nFor each task, write `tasks/<id>/review.md`.\n\nRound 1 fills the existing template structure directly \u2014 do **not** wrap it in a `## Round 1` section; the `## Stage 1` and `## Stage 2` headings stay at H2. Re-review appends a new `## Round {{roundN}}` section near the bottom (with `### Stage 1` / `### Stage 2` sub-headings), preserving earlier rounds.\n\nEvery round \u2014 the Round 1 body and each `## Round N` section \u2014 includes one scope line near the top: **Scope:** {{{scopeWord}}} \u2014 base `{{{scopeBase}}}` \u2014 reason: {{{scopeReason}}}. The numbered cold-Codex archive header is the machine-readable record.\n\nInclude:\n- Stage 1: anchored lens validation gate result and AC table.\n- Stage 2 / Findings: surviving findings with altitude (`code-bug` or `spec-gap`), source lens, and file:line.\n- Dismissed Cold Findings: every dropped cold finding plus the reason, including `Dismissed (cold-Claude): ...` and `Dismissed (cold-Codex): ...` entries where applicable.\n- Final Verdict: check exactly one verdict checkbox, including `Spec gap` when applicable (except the missing-lens stop in step 1, which checks none).\n\n### 5. Set Phase Verdict\n\nRun one command per task with the actual verdict:\n{{{phaseCommands}}}\n\nIf a lens was still missing after its re-spawn (step 1), write `review.md` first, then run these instead, with no verdict \u2014 this stops the run for the human:\n{{{blockedCommands}}}\n";
4720
4741
 
4721
4742
  // src/orchestrator/prompts/templates/implement.md
4722
4743
  var implement_default = 'You are implementing {{taskScope}} for {{projectName}}.\n\n{{{stateHeader}}}\n{{{startup}}}\n{{{risksBlock}}}{{{pitfallsBlock}}}{{{contextBlock}}}\n{{{affectedFilesBlock}}}\nTasks to implement:\n{{{taskLines}}}{{#isBundle}}\nThese tasks are related \u2014 implement them together. Consider shared code paths and cross-task interactions.{{/isBundle}}\n\nGrounding rule: before you write handoff.md, re-open the files you changed and verify the current diff against the spec. Do not treat a previous session\'s memory as proof that the work is already in place.\n\n**Spec ACs are binding. Plan approach is guidance.**\n- Every Acceptance Criterion in spec.md MUST be met \u2014 these are non-negotiable.\n- If you find a better implementation approach than what\'s in the plan, use it. Document every deviation in handoff.md under "Deviations" with specific rationale.\n- You may NOT silently drop an AC, skip a required validation check, or omit a spec requirement.\n- If an AC is infeasible as written, document it in Blockers \u2014 do not silently skip.\n- If an AC is ambiguous enough that two reasonable implementations exist, document your interpretation in handoff.md under Blockers with label `[ambiguity]` \u2014 do not silently guess. Claude will evaluate whether the interpretation was correct.\n\n## Implementation Rules\n\n**Safe-First Rules** \u2014 always applicable regardless of stack:\n1. For storage, reload, sync, or data-affecting flows: ship the safer guarded behavior first.\n2. Behavior that reloads the app, replaces local state, or dismisses user work must be gated by explicit user action.\n3. Prefer shared types over duplicating signatures.\n\n**Scope Discipline** \u2014 always applicable; the spec is the contract:\n1. **Affected Files is the scope cap.** If satisfying an AC genuinely requires editing files outside the spec\'s *Affected Files* table, do not make that edit. Record the gap in `handoff.md` under *Blockers* \u2014 the handoff is how it reaches a human \u2014 then finish the remaining in-scope work, the handoff, and the phase command. Do not silently expand scope. New helpers, fixtures, and test files introduced during implementation count against this cap too.\n2. **No unauthorized new abstractions.** Do not introduce new top-level modules, services, packages, or routing layers that the spec did not authorize. Minor refactors within an authorized file are fine; new abstractions are an architecture decision and belong in the spec.\n3. **No incidental dependency changes.** Do not add, remove, upgrade, or downgrade dependencies (or their pinned versions) unless the spec explicitly requests it.\n\n**Lint & Type Safety Policy** \u2014 always applicable:\n1. **Suppressing a lint or type error is a last resort**, not a convenience escape hatch. Never add a suppression without a same-line justification explaining *why the rule is wrong for this specific case*.\n2. **`any` / dynamic typing**: When the shape is truly unknown at the boundary, type as `unknown` and narrow explicitly.\n\n**Bug/Flake-Fix Red-First Checkpoint** \u2014 applicable when a spec\'s ACs include a red-first regression test:\nWrite the test and run it against the pre-fix code first; confirm it fails *for the reason the spec states*, then apply the fix and confirm it passes. Report the red run (command + observed failure) in handoff.md. If the test cannot be made to fail on the pre-fix code for the stated reason, do not implement a fix on a premise you could not reproduce \u2014 record a `[wrong-premise]` Blocker in handoff.md instead, then finish the remaining in-scope work, the handoff, and the phase command. If the spec instead uses the environment-bound-and-impractical escape, run its named deterministic alternative before fixing if it is executable in your sandbox (e.g. an integration fixture) and report the outcome in handoff.md the same way; if it is not executable (e.g. a documented manual repro), state that in handoff.md instead \u2014 never report an outcome for a run that did not happen.\n\n**Parsing Structured Input** \u2014 always applicable when implementing a parser for author-facing structured input:\nParse cell-by-cell with explicit rejection, not a permissive whole-string regex. Anchor each cell to exactly one expected shape and reject malformed cells with a specific reason at the parse boundary.\n\nRun ALL applicable validation checks before writing handoff. See "Validation Required" in each spec.md. The universal change-type \u2192 check-category matrix:\n\n| Change Type | Required Check Categories |\n|---|---|\n| Most changes | Linting, type checking, unit tests |\n| Docs references | Docs references |\n| Routes / config / build | Full build |\n| UI / interaction changes | End-to-end tests |\n| Content / SEO / metadata | Prerender / sitemap / feed regeneration |\n| Schema / migration | Migration runner + manual review |\n| Cross-platform | Subset of the above on each platform |\n\nFor which command runs each category: see `docs/architecture.md` \xA7Validation (project command bindings). Required checks must be recorded as Pass or Fail; do not mark a required check N/A unless the spec explicitly removed it.\n\n**Test flakiness in your sandbox.** Validation suites \u2014 especially E2E or integration tests \u2014 can hit transient failures (timing races, environment quirks, network jitter) that have nothing to do with the code in your spec\'s Affected Files. **If a failure is in a test / file outside your Affected Files table, do NOT fix it.** Note the observed test name, file, line, and a one-line repro hint in handoff.md \u2192 Blockers (or "Validation Outcomes" Notes column with status `Fail \u2013 unrelated`), then continue. `Fail \u2013 unrelated` is only valid for failures in files outside your Affected Files; a failure in a file you changed is yours to fix. Scope discipline > fixing adjacent bugs you spot during validation. The reviewer/operator will decide whether to triage the unrelated failure separately.\n\nFor each task, write tasks/<id>/handoff.md using the template. The Validation Outcomes table must have no Fail results EXCEPT for unrelated-flake rows clearly labeled in the Notes column.\nAppend to tasks/<id>/notes.md for any surprising codebase behavior (prefix: [implement]).\n\nWhen done, run:\n{{{phaseCommands}}}\n';
@@ -4728,25 +4749,25 @@ var implement_reroute_default = "A human wrote a spec amendment and rerouted {{t
4728
4749
  var implement_revisions_default = '{{{iterBanner}}}\n\n{{{stateHeader}}}\n{{{startup}}}\n\n{{{affectedFilesBlock}}}\n\n{{#hasReviewFindings}}\nYour prior iteration shipped; the reviewer (Claude) appended findings to `review.md` as `## Round {{priorRound}}`. If you\'re resuming the prior session, the full task framing (spec, plan, repo conventions) is already in context \u2014 skip the re-read. If your context is cold, re-read `tasks/<id>/spec.md` and `tasks/<id>/plan.md` before addressing findings.\n\nTasks with new review feedback:\n{{{reviewLines}}}\n\nFor each task:\n1. Read the most recent `## Round {{priorRound}}` section of `tasks/<id>/review.md`. That is the entire scope of this iteration.\n2. Address every `correctness bug`, `risk/guardrail`, and `spec gap` finding from that round (blocking). `optional cleanup/nit` is at your discretion{{#tightenLine}}{{{tightenLine}}}{{/tightenLine}}\n3. Re-run only the validation checks affected by your changes (typically lint, type-check, plus whatever the diff touches).\n4. **APPEND** to `tasks/<id>/handoff.md` a new section `{{{handoffAppend}}}` (the template\'s "On revision rounds" comment shows the shape). Do NOT rewrite the file from scratch \u2014 earlier iterations stay as the cumulative record. Include only the delta: findings addressed, AC deltas, re-run validation results.\n{{/hasReviewFindings}}\n{{#hasPreflightFindings}}\nYour prior iteration was rejected by the orchestrator\'s pre-flight gate **before any Claude review ran**. The rejection details are recorded in `review.md` under `## Validation Gate` / `## Pre-Flight Rejection`.\n\nTasks with pre-flight rejection feedback:\n{{{reviewLines}}}\n\nFor each task:\n1. Read the pre-flight block in `tasks/<id>/review.md` and follow **whichever framing it carries**:\n - **"Fix the handoff"** items \u2192 fix `handoff.md` (Validation Outcomes rows, AC Coverage table, Changes table).\n - **"Fix the code"** items \u2192 a required check failed on a file you changed. Fix the regression, re-run the check, and update the handoff.\n - Both framings may be present \u2014 address all items from both before resubmitting.\n2. **APPEND** to `tasks/<id>/handoff.md` a new section `{{{handoffAppend}}}`. Include the delta: which items you addressed and how.\n{{/hasPreflightFindings}}\n\nSpec ACs remain binding. If the review identifies a dropped AC, restore it.\n\n**Iteration rules:**\n\n- **Reverting a file**: For a byte-perfect revert to the task baseline, use `git show origin/<base-branch>:<path>` (read-only git, always allowed) and write the output to the file.\n - *Perfect revert* (file no longer in `git diff base...HEAD`): delete it from all prior iteration Changes tables in `handoff.md`.\n - *Imperfect revert* (trailing newline or other residual remains): add it to the current iteration\'s Changes table with "Reverted to original (describe residual diff)".\n- **Referencing deleted (or not-yet-created) files in artifacts**: `docs-refs-check` flags a backtick path-ref to a file that does not exist. Referencing deleted paths in the handoff Changes-table first column must use `[path](path)` markdown-link form only \u2014 backtick form fails both checks.\n- **Rerouted / revised tasks \u2014 the pre-flight diff is cumulative**: the verifier checks the union of all Changes tables against `git diff <base>...HEAD`. Before submitting, run `git diff <base>...HEAD --name-only` and confirm every listed path is covered by at least one Changes-table row across ALL iterations.\n- **Bug/flake-fix red-first checkpoint**: if this round adds or modifies the spec\'s red-first regression test, write and run the test **before** making this round\'s fix edits and confirm it fails *for the reason the spec states*; then apply the fix and confirm it passes. Report the red run in the handoff delta. If it cannot be made to fail for that reason, do not implement around an unconfirmed premise \u2014 record a `[wrong-premise]` Blocker in handoff.md instead, then finish the remaining in-scope work, the handoff, and the phase command. If the spec instead uses the environment-bound-and-impractical escape, run its named deterministic alternative if it is executable in your sandbox and report the outcome in the handoff delta; if it is not executable (e.g. a documented manual repro), state that instead \u2014 never report an outcome for a run that did not happen.\n\nAppend to `tasks/<id>/notes.md` for new pitfalls found (prefix: `[implement-revision]`).\n\nWhen done, run:\n{{{phaseCommands}}}\n';
4729
4750
 
4730
4751
  // src/orchestrator/prompts/templates/plan-reroute.md
4731
- var plan_reroute_default = "You are updating the implementation plan for {{taskScope}} for {{projectName}} after a human reroute.\n\n{{{startup}}}\n\nA human amended the spec and rerouted the task after a completed implementation round. Codex has reviewed the amendment. Your job is to **append** a reroute plan section to `plan.md`; do not rewrite or remove existing plan content.\n\n{{{roundBanner}}}Amendment review verdicts:\n{{{verdictLines}}}\n\nFor each task \u2014 EXCEPT tasks whose line above marks them EXEMPT (those have no amendment; skip every step below for them and append NO Reroute Plan section to their plan.md):\n1. Read `tasks/<id>/spec.md` from your current directory, including the amendment for the round listed above.\n2. Read `tasks/<id>/plan.md` to understand the prior plan.\n3. Read `tasks/<id>/handoff.md` to understand what Codex previously shipped.\n4. Read `tasks/<id>/spec-review.md` for the latest reroute amendment review and any nits to incorporate.\n5. Append a new section to `tasks/<id>/plan.md`:\n - Round 1: `## Reroute Plan`\n - Round N >= 2: `## Reroute Plan Round N`\n6. Plan only the delta from the amendment. Reference specific files, functions, and existing patterns. Acknowledge prior plan steps that still apply without re-planning them.\n\nDo **not** rewrite or remove existing sections from `plan.md`. The appended reroute plan is what implement-reroute reads as its delta guide.\n\nWhen done, run:\n{{{phaseCommands}}}\n\n<!-- per-round append shape:\n## Reroute Plan [Round N]\n### Delta\n- ...ordered steps for the amendment delta only...\n-->\n";
4752
+ var plan_reroute_default = "You are updating the implementation plan for {{taskScope}} for {{projectName}} after a human reroute.\n\n{{{startup}}}\n\nA human amended the spec and rerouted the task after a completed implementation round. Codex has reviewed the amendment. Your job is to **append** a reroute plan section to `plan.md`; do not rewrite or remove existing plan content.\n\n{{{roundBanner}}}Amendment review verdicts:\n{{{verdictLines}}}\n\nFor each task \u2014 EXCEPT tasks whose line above marks them EXEMPT (those have no amendment; skip every step below for them and append NO Reroute Plan section to their plan.md):\n1. Read `tasks/<id>/spec.md` from your current directory, including the amendment for the round listed above.\n2. Read `tasks/<id>/plan.md` to understand the prior plan.\n3. Read `tasks/<id>/handoff.md` to understand what Codex previously shipped.\n4. Read `tasks/<id>/spec-review.md` for the latest reroute amendment review and any nits to incorporate.\n5. Append a new section to `tasks/<id>/plan.md`:\n - Round 1: `## Reroute Plan`\n - Round N >= 2: `## Reroute Plan Round N`\n6. Plan only the delta from the amendment. Reference specific files, functions, and existing patterns. Acknowledge prior plan steps that still apply without re-planning them.\n7. {{{feasibilityCheck}}}\n\nDo **not** rewrite or remove existing sections from `plan.md`. The appended reroute plan is what implement-reroute reads as its delta guide.\n\nWhen done, run:\n{{{phaseCommands}}}\n\n<!-- per-round append shape:\n## Reroute Plan [Round N]\n### Delta\n- Feasibility: ...\n- ...ordered steps for the amendment delta only...\n-->\n";
4732
4753
 
4733
4754
  // src/orchestrator/prompts/templates/plan.md
4734
- var plan_default = "You are writing implementation plans for {{taskScope}} for {{projectName}}.\n\n{{{startup}}}\n\n{{{verdictLines}}}\n\nFor each task, read tasks/<id>/spec.md and tasks/<id>/spec-review.md. Address any `changes_requested` items before writing the plan. If the verdict is `approved_with_nits`, incorporate the nits into the plan \u2014 they don't require spec changes but should inform implementation decisions.\n\nWrite tasks/<id>/plan.md for each task with ordered implementation steps. Reference specific files, existing patterns, and code examples from the codebase. Codex implements directly from this plan.\n\nIf you encounter spec gaps, append to tasks/<id>/notes.md (prefix: [plan]).\n\nWhen done, run:\n{{{phaseCommands}}}\n";
4755
+ var plan_default = "You are writing implementation plans for {{taskScope}} for {{projectName}}.\n\n{{{startup}}}\n\n{{{verdictLines}}}\n\nFor each task, read tasks/<id>/spec.md and tasks/<id>/spec-review.md. Address any `changes_requested` items before writing the plan. If the verdict is `approved_with_nits`, incorporate the nits into the plan \u2014 they don't require spec changes but should inform implementation decisions.\n\nWrite tasks/<id>/plan.md for each task with ordered implementation steps. Reference specific files, existing patterns, and code examples from the codebase. Codex implements directly from this plan.\n\n{{{feasibilityCheck}}}\n\nIf you encounter spec gaps, append to tasks/<id>/notes.md (prefix: [plan]).\n\nWhen done, run:\n{{{phaseCommands}}}\n";
4735
4756
 
4736
4757
  // src/orchestrator/prompts/templates/qa.md
4737
4758
  var qa_default = 'You are writing QA summaries for {{taskScope}} for {{projectName}}.\n\n{{{startup}}}\n\nTasks:\n{{{taskLines}}}\n\nFor each task:\n1. **Use the Write tool** to create tasks/<id>/done.md \u2014 plain-English summary for the human. Include: what changed, files changed, how to test, test results, human verification required, decisions made, open questions.\n \u26A0\uFE0F CRITICAL: Use the `Write` tool \u2014 do NOT simply output the done.md content as text in your response. Content in your chat reply does not get saved to disk. The pipeline validates that done.md contains real content (not the template) before advancing. Write the file.\n2. Read the latest `## Validation Outcomes` table in `tasks/<id>/handoff.md`, including any later iteration `### Re-run validation` tables. If any check\'s latest result is `human_pending`, include a **Human Verification Required** section in done.md that lists each pending check and its Notes. If none remain, write `None.` in that section. Do not hide `human_pending` checks inside the generic Test Results table.\n - If the human chooses to waive or defer a pending check later, the waiver line in done.md must begin with `Acknowledged:`. The `human_review` gate only treats that explicit prefix as a waiver.\n - Preserve `deferred_by_spec` rows in Test Results with the spec citation from Notes; do not translate them to `Pass`.\n3. Include a **Proposed Changelog** section in done.md \u2014 the changelog **entry text only**. Do **NOT** include a version number, a "Proposed version" line, or a bump tier (Patch/Minor/Major): the version is decided at the release step (`/canon-changelog` + human), never by QA.\n - **Canon release rules (non-negotiable)**: (1) Agents do not bump versions or land changelog edits without explicit scope authorization. (2) The QA step proposes a draft changelog entry text only \u2014 not the version number. (3) Changelog + version bump are committed separately from code changes (when a project versions its releases). (4) No major versioning surprises: if a task introduces a breaking change the spec didn\'t flag, raise it before shipping.\n - Read `docs/decisions.md` \xA7"Versioning and release policy" for this project\'s changelog **scope** (which changes warrant an entry). The bump tier / SemVer interpretation is the release step\'s call, not QA\'s \u2014 do not propose one.\n - If CHANGELOG.md exists, read the top of it (the most recent version section) to calibrate on scope and voice.\n - Apply the "would a user notice" test to every candidate bullet (or the project\'s equivalent scope test): if a candidate falls outside the project\'s defined changelog scope, omit it. If a task is entirely out of scope, say so explicitly ("no user-facing change \u2014 omit from changelog") rather than inventing a bullet.\n - Implementation mechanics belong in the "What Changed" section above \u2014 not in the proposed changelog.\n The human finalizes the changelog entry.\n4. **For single tasks only \u2014 use the Write tool** to create `tasks/<id>/pr-body.md` \u2014 the outward-facing PR body draft for `--pr`. Write it as if a human wrote it after doing the work.\n {{#prTemplate}}\n The repo has this PR template. Fill every section with specifics from what shipped. Keep the headings; replace every placeholder:\n\n {{{prTemplate}}}\n {{/prTemplate}}\n {{^prTemplate}}\n No PR template found. Use this structure:\n\n ## Summary\n 1\u20133 bullets: what changed and why.\n\n ## Changes\n Key files or areas touched, described for a reviewer.\n\n ## How to Test\n Steps a reviewer can follow to verify the change.\n\n ## Notes for Reviewer\n Any context, caveats, or follow-up items.\n {{/prTemplate}}\n \u26A0\uFE0F Write as the human engineer who did the work \u2014 not as the AI or tool that produced it (Claude, Codex, canon, an LLM). \u2705 e.g. "Fix the pagination off-by-one that dropped the last row." \u274C e.g. "\u{1F916} Generated with Claude Code."\n Skip this step entirely for bundle tasks \u2014 per-task bodies are not combined for bundle PRs.\n\nAfter writing all done.md files:\n- Read tasks/<id>/notes.md for each task. For each insight, ask: "would this have changed how a *different* task was approached?" If yes, **append** one new entry for *this* task to docs/lessons-learned.md. If no, the detail stays in notes.md only. Append-only: never edit, prune, promote, reorganize, or delete existing entries \u2014 not this task\'s earlier entries, and never another task\'s. Promoting entries into permanent docs (patterns.md / decisions.md) and pruning the buffer is a **human-initiated, human-approved** action \u2014 never perform it during QA, and no entry count ever triggers it. (See docs/lessons-learned.md \u2192 "How to use this doc".)\n- Add a `## Quality Log` section to done.md with the five judgment cells the pipeline cannot derive from status.json. Do **not** edit docs/task-quality-log.md directly \u2014 the qa \u2192 done transition writes or upserts that row automatically from status.json plus this section.\n\n ```markdown\n ## Quality Log\n - Spec verdict: <the FIRST spec_review verdict this task ever received \u2014 approved / approved_with_nits / changes_requested; leave blank only if truly unknown>\n - Human reroute?: <Yes/No \u2014 did the human reject at human_review and force a re-implement? Do not infer this from any reroute counter; if this is a fresh QA pass with no human_review rejection yet, answer No>\n - Dropped ACs: <count of ACs the implementation missed, caught in code review>\n - Validation gaps: <count of validation checks that should have run but did not>\n - Notes: <one-line summary of anything notable \u2014 single line, no embedded line breaks>\n ```\n\n On a re-upsert after a reroute, only fill in cells that changed or that you are correcting \u2014 an omitted cell keeps its previously recorded value. In particular, do not overwrite an already-recorded `Spec verdict` with the current status.json verdict; that column means the first spec_review verdict, and status.json only retains the latest.\n\n**Handoff Validation pre-merge checklist** (include in `done.md` Human Verification section if any item cannot be confirmed):\n- [ ] Version correct (per project policy; skip if unversioned)\n- [ ] Changelog updated if needed (per project policy; skip if unversioned)\n- [ ] PR body current\n- [ ] Final CI/CD checks green\n- [ ] Final diff matches spec intent\n\n**Output Format for Human** \u2014 `done.md` must contain:\n1. One-paragraph plain-English summary\n2. Files changed\n3. How to test (product-level steps, not code)\n4. Test results table\n5. Decisions made during implementation\n6. Open questions needing human input\n\n**Code is Canonical; Docs Reference Symbols**: Code is the source of truth for anything derivable from code: numbers, thresholds, file locations, function signatures, type shapes, observable behavior. Docs that restate these facts inline rot silently \u2014 reference the symbol or path; do not restate the value.\n\n**Commit Ownership** \u2014 three change categories:\n- Code changes \u2192 task branch, committed by the orchestrator after Codex static validation.\n- Pre-implement scaffold \u2192 base branch, committed by the orchestrator before first implement.\n- Changelog + version bump (if versioned) \u2192 separate commit, human + Claude, after human_review.\n\n- **Docs freshness \u2014 Two-checkpoint**: scan the five protected docs (`docs/architecture.md`, `docs/codebase-map.md`, `docs/patterns.md`, `docs/product-context.md`, `docs/decisions.md`) for references that {{docsScope}} *contradicts* \u2014 a renamed symbol, a moved file, a behavior this task changed \u2014 and correct those stale references. That is the only edit QA makes to permanent docs. Do not add new lessons, pitfalls, or decisions here, and do not promote buffer entries \u2014 promotion is the human sweep, not Docs freshness.\n- **Buffer signal** (not an action): after appending, if docs/lessons-learned.md now holds more than ~15 entries, add one line to this task\'s done.md \u2014 `Maintenance: lessons-learned.md has N entries; a human lessons sweep is due (see docs/lessons-learned.md \u2192 "How to use this doc").` Do not perform the sweep yourself.\n\nWhen done, run (use the Bash tool \u2014 do not just output the command as text):\n{{{phaseCommands}}}\n';
4738
4759
 
4739
4760
  // src/orchestrator/prompts/templates/spec.md
4740
- var spec_default = '{{{header}}}\n\n{{{startup}}}\n\n{{{instructions}}}\n{{{bundleNote}}}\n{{#doneNote}}\nNote: {{{doneNote}}}{{/doneNote}}\n\n**Spec-writing rules of thumb** \u2014 apply when writing each spec:\n\n- **Name effects to DELETE** \u2014 frame supersession as replacement, not add-plus-remove. State: "replace `oldFn` with `newFn`; `oldFn` must not exist after" \u2014 not separate "Add" and "Remove" bullets.\n- **Prefer positive or structural assertions** over prose negations for load-bearing constraints. Back a "must not" with a grep AC or positive reframe; bare prose negation is fragile.\n- **Symbols named in ACs must exist** \u2014 grep for every function or symbol an AC names; verify its return shape matches the spec\'s assumed data contract before marking spec done.\n- **Enumerate every caller and classify its execution context before asserting how a mechanism works** \u2014 "counter X only increments on event Y" or "this writer can only be invoked from surface Z" is a control-flow claim, and control-flow claims need every caller traced. A helper named for one trigger routinely serves several, and whether a call site runs inside an agent\'s own session or in the orchestrator\'s process is invisible from the function body. Grep every caller of the function or counter \u2014 including skills and docs, not just source \u2014 and classify each site as in-agent-session or orchestrator-context before the spec asserts anything about it.\n- **Behavioral contracts, not mechanics** \u2014 ACs describe observable behavior; defer implementation mechanics (signatures, constant names, precise algorithms) to plan/implement. If review rounds start re-litigating literal regexes, exact file layouts, or verbatim command strings, the spec has dropped to code altitude and each round\'s "underspecified" finding just adds more reviewable surface: move the mechanics into a clearly-labeled non-binding *Implementation Notes* subsection owned by plan/implement, rather than wording the same mechanism more precisely.\n- **Bug and flake-fix specs need a confirmed mechanism and red-first test** \u2014 For a bug or flake fix, the spec author must state, in *Problem*, both the confirmed mechanism and how it was confirmed \u2014 not merely a plausible cause. Evidence must match the mechanism class: a deterministic mechanism (fixed inputs hit the same wrong branch every run) may cite a trace with the verified trigger values; a runtime-dependent mechanism (race, timing, environment/config interaction) needs executed confirmation \u2014 a throwaway prototype-fix spike that makes the symptom vanish, or a deterministic forced repro. The author must satisfy that checkpoint before the spec is marked done; on fast-tier (XS, non-delicate) tasks the `spec_review` checkpoint is skipped, so no reviewer will catch an unverified mechanism. The *Acceptance Criteria* must include a red-first regression-test AC: a test that fails on the pre-fix code for the stated reason and passes after the fix. If the mechanism is environment-bound and a faithful repro is impractical, *Problem* must say so and name a deterministic alternative (integration fixture or documented manual repro) instead of skipping verification silently.\n- **At \u22653 spec_review iterations, read the rounds\' content, not the count** \u2014 label each round *edge-fine-tune* (it narrows an already-identified case \u2014 converging, safe to finish once the remaining findings are addressed) or *scope-expansion* (it names a **new** structural case the spec hadn\'t covered \u2014 a real design gap). Round count alone is not the signal; a 6-round run of successive fine-tunes is convergence, not churn. On genuine scope-expansion, don\'t refine again: check whether the last two rejections share one structural root, and whether an existing layer already owns the concern. Dropping the whole mechanism class \u2014 or deleting the mechanism in favor of the layer that already owns it \u2014 is usually cheaper than another round of hardening.\n- **Refactor specs need structural caps** \u2014 provide hard size caps, explicit deletion expectations per symbol, and an allow-list grep AC for any symbol that must disappear.\n- **UI spatial / gesture tasks** \u2014 flag "visual positioning \u2014 expect human iteration" or "runtime debugging required" in *Known Risks*.\n- **Sensitive-surface escalation** \u2014 flag these categories as `delicate: true` in `status.json` and call them out in *Known Risks*: auth, billing / payments, privacy / data handling, destructive operations, schema / data-model migrations, analytics-event changes. The human spec gate is where such tasks stop for review.\n\n{{{selfCheck}}}\n\nWhen done, run (one per task):\n{{{phaseCommands}}}\n';
4761
+ var spec_default = '{{{header}}}\n\n{{{startup}}}\n\n{{{instructions}}}\n{{{bundleNote}}}\n{{#doneNote}}\nNote: {{{doneNote}}}{{/doneNote}}\n\n**Spec-writing rules of thumb** \u2014 apply when writing each spec:\n\n- **Name effects to DELETE** \u2014 frame supersession as replacement, not add-plus-remove. State: "replace `oldFn` with `newFn`; `oldFn` must not exist after" \u2014 not separate "Add" and "Remove" bullets.\n- **Prefer positive or structural assertions** over prose negations for load-bearing constraints. Back a "must not" with a grep AC or positive reframe; bare prose negation is fragile.\n- **Symbols named in ACs must exist** \u2014 grep for every function or symbol an AC names; verify its return shape matches the spec\'s assumed data contract before marking spec done.\n- **Enumerate every caller and classify its execution context before asserting how a mechanism works** \u2014 "counter X only increments on event Y" or "this writer can only be invoked from surface Z" is a control-flow claim, and control-flow claims need every caller traced. A helper named for one trigger routinely serves several, and whether a call site runs inside an agent\'s own session or in the orchestrator\'s process is invisible from the function body. Grep every caller of the function or counter \u2014 including skills and docs, not just source \u2014 and classify each site as in-agent-session or orchestrator-context before the spec asserts anything about it.\n- **Behavioral contracts, not mechanics** \u2014 ACs describe observable behavior; defer implementation mechanics (signatures, constant names, precise algorithms) to plan/implement. If review rounds start re-litigating literal regexes, exact file layouts, or verbatim command strings, the spec has dropped to code altitude and each round\'s "underspecified" finding just adds more reviewable surface: move the mechanics into a clearly-labeled non-binding *Implementation Notes* subsection owned by plan/implement, rather than wording the same mechanism more precisely.\n- **Bug and flake-fix specs need a confirmed mechanism and red-first test** \u2014 For a bug or flake fix, the spec author must state, in *Problem*, both the confirmed mechanism and how it was confirmed \u2014 not merely a plausible cause. Evidence must match the mechanism class: a deterministic mechanism (fixed inputs hit the same wrong branch every run) may cite a trace with the verified trigger values; a runtime-dependent mechanism (race, timing, environment/config interaction) needs executed confirmation \u2014 a throwaway prototype-fix spike that makes the symptom vanish, or a deterministic forced repro. The author must satisfy that checkpoint before the spec is marked done; on fast-tier (XS, non-delicate) tasks the `spec_review` checkpoint is skipped, so no reviewer will catch an unverified mechanism. The *Acceptance Criteria* must include a red-first regression-test AC: a test that fails on the pre-fix code for the stated reason and passes after the fix. If the mechanism is environment-bound and a faithful repro is impractical, *Problem* must say so and name a deterministic alternative (integration fixture or documented manual repro) instead of skipping verification silently.\n- **At \u22653 spec_review iterations, read the rounds\' content, not the count** \u2014 label each round *edge-fine-tune* (it narrows an already-identified case \u2014 converging, safe to finish once the remaining findings are addressed) or *scope-expansion* (it names a **new** structural case the spec hadn\'t covered \u2014 a real design gap). Round count alone is not the signal; a 6-round run of successive fine-tunes is convergence, not churn. On genuine scope-expansion, don\'t refine again: check whether the last two rejections share one structural root, and whether an existing layer already owns the concern. Dropping the whole mechanism class \u2014 or deleting the mechanism in favor of the layer that already owns it \u2014 is usually cheaper than another round of hardening.\n- **Refactor specs need structural caps** \u2014 provide hard size caps, explicit deletion expectations per symbol, and an allow-list grep AC for any symbol that must disappear.\n- **Refactor specs need a correctness audit** \u2014 For each behavior a refactor declares preserved, record in Problem whether it is correct today, and state one outcome: correct as-is, fixed deliberately (with its own AC), split into a separate task, or kept as a named quirk. A behavior confirmed correct needs only a one-line record. N/A for features and bug fixes.\n- **UI spatial / gesture tasks** \u2014 flag "visual positioning \u2014 expect human iteration" or "runtime debugging required" in *Known Risks*.\n- **Sensitive-surface escalation** \u2014 flag these categories as `delicate: true` in `status.json` and call them out in *Known Risks*: auth, billing / payments, privacy / data handling, destructive operations, schema / data-model migrations, analytics-event changes. The human spec gate is where such tasks stop for review.\n\n{{{selfCheck}}}\n\nWhen done, run (one per task):\n{{{phaseCommands}}}\n';
4741
4762
 
4742
4763
  // src/orchestrator/prompts/templates/spec-revision.md
4743
- var spec_revision_default = 'You are revising specs for {{taskScope}} for {{projectName}}.\n\n{{{startup}}}\n\nTasks with review feedback:\n{{{reviewLines}}}\n\nAddress every `changes_requested` finding in each spec.md.{{#combined}}\nAlso update plan.md if spec changes affect the implementation approach.{{/combined}}\n\n**Spec-writing rules of thumb** \u2014 apply when revising each spec:\n\n- **Name effects to DELETE** \u2014 frame supersession as replacement, not add-plus-remove. State: "replace `oldFn` with `newFn`; `oldFn` must not exist after" \u2014 not separate "Add" and "Remove" bullets.\n- **Prefer positive or structural assertions** over prose negations for load-bearing constraints. Back a "must not" with a grep AC or positive reframe; bare prose negation is fragile.\n- **Symbols named in ACs must exist** \u2014 grep for every function or symbol an AC names; verify its return shape matches the spec\'s assumed data contract before marking spec done.\n- **Enumerate every caller and classify its execution context before asserting how a mechanism works** \u2014 "counter X only increments on event Y" or "this writer can only be invoked from surface Z" is a control-flow claim, and control-flow claims need every caller traced. A helper named for one trigger routinely serves several, and whether a call site runs inside an agent\'s own session or in the orchestrator\'s process is invisible from the function body. Grep every caller of the function or counter \u2014 including skills and docs, not just source \u2014 and classify each site as in-agent-session or orchestrator-context before the spec asserts anything about it.\n- **Behavioral contracts, not mechanics** \u2014 ACs describe observable behavior; defer implementation mechanics (signatures, constant names, precise algorithms) to plan/implement. If review rounds start re-litigating literal regexes, exact file layouts, or verbatim command strings, the spec has dropped to code altitude and each round\'s "underspecified" finding just adds more reviewable surface: move the mechanics into a clearly-labeled non-binding *Implementation Notes* subsection owned by plan/implement, rather than wording the same mechanism more precisely.\n- **Bug and flake-fix specs need a confirmed mechanism and red-first test** \u2014 For a bug or flake fix, the spec author must state, in *Problem*, both the confirmed mechanism and how it was confirmed \u2014 not merely a plausible cause. Evidence must match the mechanism class: a deterministic mechanism (fixed inputs hit the same wrong branch every run) may cite a trace with the verified trigger values; a runtime-dependent mechanism (race, timing, environment/config interaction) needs executed confirmation \u2014 a throwaway prototype-fix spike that makes the symptom vanish, or a deterministic forced repro. The author must satisfy that checkpoint before the spec is marked done; on fast-tier (XS, non-delicate) tasks the `spec_review` checkpoint is skipped, so no reviewer will catch an unverified mechanism. The *Acceptance Criteria* must include a red-first regression-test AC: a test that fails on the pre-fix code for the stated reason and passes after the fix. If the mechanism is environment-bound and a faithful repro is impractical, *Problem* must say so and name a deterministic alternative (integration fixture or documented manual repro) instead of skipping verification silently.\n- **At \u22653 spec_review iterations, read the rounds\' content, not the count** \u2014 label each round *edge-fine-tune* (it narrows an already-identified case \u2014 converging, safe to finish once the remaining findings are addressed) or *scope-expansion* (it names a **new** structural case the spec hadn\'t covered \u2014 a real design gap). Round count alone is not the signal; a 6-round run of successive fine-tunes is convergence, not churn. On genuine scope-expansion, don\'t refine again: check whether the last two rejections share one structural root, and whether an existing layer already owns the concern. Dropping the whole mechanism class \u2014 or deleting the mechanism in favor of the layer that already owns it \u2014 is usually cheaper than another round of hardening.\n- **Refactor specs need structural caps** \u2014 provide hard size caps, explicit deletion expectations per symbol, and an allow-list grep AC for any symbol that must disappear.\n- **UI spatial / gesture tasks** \u2014 flag "visual positioning \u2014 expect human iteration" or "runtime debugging required" in *Known Risks*.\n- **Sensitive-surface escalation** \u2014 flag these categories as `delicate: true` in `status.json` and call them out in *Known Risks*: auth, billing / payments, privacy / data handling, destructive operations, schema / data-model migrations, analytics-event changes. The human spec gate is where such tasks stop for review.\n\nWhen done, run:\n{{{phaseCommands}}}\n';
4764
+ var spec_revision_default = 'You are revising specs for {{taskScope}} for {{projectName}}.\n\n{{{startup}}}\n\nTasks with review feedback:\n{{{reviewLines}}}\n\nAddress every `changes_requested` finding in each spec.md.{{#combined}}\nAlso update plan.md if spec changes affect the implementation approach.{{/combined}}\n\n**Spec-writing rules of thumb** \u2014 apply when revising each spec:\n\n- **Name effects to DELETE** \u2014 frame supersession as replacement, not add-plus-remove. State: "replace `oldFn` with `newFn`; `oldFn` must not exist after" \u2014 not separate "Add" and "Remove" bullets.\n- **Prefer positive or structural assertions** over prose negations for load-bearing constraints. Back a "must not" with a grep AC or positive reframe; bare prose negation is fragile.\n- **Symbols named in ACs must exist** \u2014 grep for every function or symbol an AC names; verify its return shape matches the spec\'s assumed data contract before marking spec done.\n- **Enumerate every caller and classify its execution context before asserting how a mechanism works** \u2014 "counter X only increments on event Y" or "this writer can only be invoked from surface Z" is a control-flow claim, and control-flow claims need every caller traced. A helper named for one trigger routinely serves several, and whether a call site runs inside an agent\'s own session or in the orchestrator\'s process is invisible from the function body. Grep every caller of the function or counter \u2014 including skills and docs, not just source \u2014 and classify each site as in-agent-session or orchestrator-context before the spec asserts anything about it.\n- **Behavioral contracts, not mechanics** \u2014 ACs describe observable behavior; defer implementation mechanics (signatures, constant names, precise algorithms) to plan/implement. If review rounds start re-litigating literal regexes, exact file layouts, or verbatim command strings, the spec has dropped to code altitude and each round\'s "underspecified" finding just adds more reviewable surface: move the mechanics into a clearly-labeled non-binding *Implementation Notes* subsection owned by plan/implement, rather than wording the same mechanism more precisely.\n- **Bug and flake-fix specs need a confirmed mechanism and red-first test** \u2014 For a bug or flake fix, the spec author must state, in *Problem*, both the confirmed mechanism and how it was confirmed \u2014 not merely a plausible cause. Evidence must match the mechanism class: a deterministic mechanism (fixed inputs hit the same wrong branch every run) may cite a trace with the verified trigger values; a runtime-dependent mechanism (race, timing, environment/config interaction) needs executed confirmation \u2014 a throwaway prototype-fix spike that makes the symptom vanish, or a deterministic forced repro. The author must satisfy that checkpoint before the spec is marked done; on fast-tier (XS, non-delicate) tasks the `spec_review` checkpoint is skipped, so no reviewer will catch an unverified mechanism. The *Acceptance Criteria* must include a red-first regression-test AC: a test that fails on the pre-fix code for the stated reason and passes after the fix. If the mechanism is environment-bound and a faithful repro is impractical, *Problem* must say so and name a deterministic alternative (integration fixture or documented manual repro) instead of skipping verification silently.\n- **At \u22653 spec_review iterations, read the rounds\' content, not the count** \u2014 label each round *edge-fine-tune* (it narrows an already-identified case \u2014 converging, safe to finish once the remaining findings are addressed) or *scope-expansion* (it names a **new** structural case the spec hadn\'t covered \u2014 a real design gap). Round count alone is not the signal; a 6-round run of successive fine-tunes is convergence, not churn. On genuine scope-expansion, don\'t refine again: check whether the last two rejections share one structural root, and whether an existing layer already owns the concern. Dropping the whole mechanism class \u2014 or deleting the mechanism in favor of the layer that already owns it \u2014 is usually cheaper than another round of hardening.\n- **Refactor specs need structural caps** \u2014 provide hard size caps, explicit deletion expectations per symbol, and an allow-list grep AC for any symbol that must disappear.\n- **Refactor specs need a correctness audit** \u2014 For each behavior a refactor declares preserved, record in Problem whether it is correct today, and state one outcome: correct as-is, fixed deliberately (with its own AC), split into a separate task, or kept as a named quirk. A behavior confirmed correct needs only a one-line record. N/A for features and bug fixes.\n- **UI spatial / gesture tasks** \u2014 flag "visual positioning \u2014 expect human iteration" or "runtime debugging required" in *Known Risks*.\n- **Sensitive-surface escalation** \u2014 flag these categories as `delicate: true` in `status.json` and call them out in *Known Risks*: auth, billing / payments, privacy / data handling, destructive operations, schema / data-model migrations, analytics-event changes. The human spec gate is where such tasks stop for review.\n\nWhen done, run:\n{{{phaseCommands}}}\n';
4744
4765
 
4745
4766
  // src/orchestrator/prompts/templates/spec-review-reroute.md
4746
4767
  var spec_review_reroute_default = "You are reviewing {{taskScope}} for {{projectName}}.\n\n{{{startup}}}\n\nA human amended the spec and rerouted this task after a completed implementation round. The original spec was already reviewed and approved. Your job is to review **the amendment and its integration** with the already-approved spec, not to re-litigate settled findings.\n\n{{{roundBanner}}}Tasks with amendments to review:\n{{{taskLines}}}\n\n**Amendment review scope** (for each task \u2014 EXCEPT tasks whose line above marks them EXEMPT: those have no amendment and require no review work this round; do not request changes for a missing Amendment heading on them):\n1. Read `tasks/<id>/spec.md` from your current directory. Locate the exact amendment heading named above: `## Amendment` for round 1, or `## Amendment Round N` for round 2+.\n2. Read `tasks/<id>/spec-review.md` so you know what was already reviewed and do not re-raise settled findings.\n3. Review the amendment itself: is it implementable as written, are ACs verifiable, and are edge cases handled?\n4. Review integration with approved ACs: does the amendment contradict, weaken, duplicate, or leave gaps against previously approved requirements?\n5. Review overall shape: with the amendment included, is the spec still coherent and in scope?\n6. Do **not** read or audit `handoff.md`, `review.md`, or `done.md`. This phase stays in the spec domain.\n\nGrounding rule: if a finding depends on a symbol or file, re-open it before claiming it exists.\n\n**Cross-review rule**: No agent reviews its own output. Claude writes specs \u2192 Codex reviews specs. Codex writes code \u2192 Claude reviews code.\n\n**Diagnose before you fix \u2014 3-role checkpoint**: For any amendment addressing a bug or flake fix, each role owns a checkpoint: the spec author states the confirmed mechanism and its evidence in *Problem* (a deterministic mechanism \u2014 fixed inputs hit the same wrong branch every run \u2014 may cite a trace with the verified trigger values; a runtime-dependent mechanism \u2014 race, timing, environment/config interaction \u2014 needs executed confirmation: a throwaway prototype-fix spike that makes the symptom vanish, or a deterministic forced repro); the reviewer challenges whether that evidence establishes the root cause; the implementer runs the spec's red-first test against the pre-fix code (or, under the environment-bound-and-impractical escape, its named deterministic alternative) and reports the result in the handoff. A mechanism with no confirmation evidence, or evidence below its class's required rung, is a blocking Shape Check concern \u2014 as is a bug/flake amendment carrying neither a red-first regression-test AC nor the explicit environment-bound-and-impractical escape with a named deterministic alternative.\n\n**Verdict rules** (same as normal spec review):\n- `changes_requested` \u2014 one or more blocking findings. The human must revise the amendment and re-run.\n- `approved_with_nits` \u2014 no blockers; non-blocking observations only. Loop exits immediately.\n- `approved` \u2014 no findings.\n\nFor each task, append a new amendment-review section to `tasks/<id>/spec-review.md`; do not overwrite the prior review. Use this exact heading for the section (the orchestrator's evidence gate requires it before advancing):\n - Round 1: `## Amendment Review`\n - Round N >= 2: `## Amendment Review Round N`\nRecord your verdict **inside that section** as a checked box \u2014 the evidence gate reads the verdict from this section, not from the original review above it. Check exactly one of:\n`- [x] **Approved**` / `- [x] **Approved with nits**` / `- [x] **Changes requested**`.\n\nWhen done, run (one per task with actual verdict):\n{{{phaseCommands}}}\n\n<!-- per-round append shape (round 1 omits the round suffix):\n## Amendment Review (round 1)\n## Amendment Review Round N (round N >= 2)\n- [x] **Approved** (check exactly one verdict box)\n> Findings: ...\n-->\n";
4747
4768
 
4748
4769
  // src/orchestrator/prompts/templates/spec-review.md
4749
- var spec_review_default = "You are reviewing {{taskScope}} for {{projectName}}.\n\n{{{startup}}}\n\nTasks to review:\n{{{taskLines}}}\n\n{{#fullSendActive}}\n**Full-send mode active**: The human grilled Claude to resolve the decision tree but did not read this spec before pipeline execution, so your review is the primary rigor layer before implementation. The silence default, scope boundary, and verdict rules this prompt sets out still apply \u2014 don't manufacture findings. What full-send changes is *where you look*, not how much you flag: give extra attention to (1) missed cases the spec's ACs might overlook, (2) scope drift between the Decision section and the ACs, and (3) ambiguity in AC verification steps, since no human will catch a gap there before implementation.\n{{/fullSendActive}}\nGrounding rule: if a finding depends on code, a symbol, or a validation result, verify the current file or diff before you claim it exists. If you did not re-open it, do not infer it from memory.\n\n**Your objective: catch genuine blocking problems, precisely.** Read the spec as the implementer would: what would break, be ambiguous, or be missing? A spec with no blocking findings is a valid, expected result \u2014 approving a clean spec is not a shortfall in your review.\n\n**First, a strategic read of the spec itself \u2014 shape before implementability.** Ask:\n- Is the problem real? (Would doing nothing be fine? Is this a symptom of something else?)\n- For a bug or flake fix: does the stated evidence actually establish the targeted mechanism, at the rung its class requires? A deterministic mechanism (fixed inputs hit the same wrong branch every run) may be confirmed by a trace with the verified trigger values; a runtime-dependent mechanism (race, timing, environment/config interaction) requires executed confirmation \u2014 a throwaway prototype-fix spike that makes the symptom vanish, or a deterministic forced repro (fault injection, forced race, targeted repro). A paper argument can rule out a wrong hypothesis but doesn't by itself verify a runtime-dependent cause. Blocking Shape Check concerns: no confirmation evidence at all, evidence below the mechanism class's required rung, or a missing red-first regression-test AC without the environment-bound-and-impractical escape. Each role owns a checkpoint: the spec author states the confirmed mechanism and its evidence in *Problem*; the reviewer (Codex) challenges whether that evidence establishes the root cause; the implementer runs the spec's red-first test against the pre-fix code (or, under the environment-bound-and-impractical escape, its named deterministic alternative) and reports the result in the handoff.\n- Is the framing right? (Does the spec solve the stated problem, or one adjacent to it?)\n- Is there a materially simpler solution that changes the shape of the work?\n- Is the AC decomposition right? (Compound ACs, missing ACs, ACs solving symptoms not causes?)\n\n**Silence is the default \u2014 for this whole review, Shape Check and implementability alike.** Only write a finding where something is actually off; do not manufacture one to fill a section or satisfy an obligation. A real shape concern becomes the lead reason for a `changes_requested` verdict; write it under the Shape Check section in spec-review.md. If none, leave that section as \"no concerns\" and proceed.\n\nThen for each task, apply that same default while probing implementability: Can this be implemented as written? Are ACs testable and unambiguous? Are edge cases handled? Are there type safety gaps? Are there file/interaction dependencies Claude missed? Does this conflict with existing patterns in the codebase? An empty list here is a valid result, not a gap in your review.{{#isBundle}}\nAlso probe for cross-task conflicts or missing dependencies between tasks.{{/isBundle}}\n{{#combined}}\nReview plan.md for each task as well \u2014 flag if the approach is unsound.{{/combined}}\n\n**Scope boundary.** Pre-existing behavior the task's spec *explicitly excludes and verifies as unaffected* (for example, named in Non-Goals) is out of scope for this review \u2014 a nit at most, never blocking. This carve-out does not cover: a change the spec *should* make but omitted (a required caller, parser, migration, or test surface), a transitive effect of the change, or an internal contradiction between spec sections \u2014 those remain **blocking** implementability findings even though the affected code is pre-existing. The test is not \"can I reach this code\" \u2014 it's whether the spec named it out of scope and showed it stays unaffected.\n\n**Classify every finding before deciding your verdict:**\n- **Blocking**: would cause wrong behavior, a silent bug, or make an AC unimplementable as written. Requires `changes_requested`.\n- **Non-blocking (nit)**: an implementation detail Codex can resolve by reading the codebase (prop flow, state threading, naming); a minor ambiguity with an obvious default; a question the plan phase should address. Does NOT require `changes_requested`.\n- *Example*: an under-specification whose intended value the surrounding task context makes obvious (e.g. a field name implied by an adjacent example or existing convention) is a nit for the plan phase, not Blocking.\n\n**Verdict rules:**\n- `changes_requested` \u2014 one or more blocking findings. Spec must be revised before the plan phase.\n- `approved_with_nits` \u2014 no blocking findings, but non-blocking nits worth passing forward. **Loop exits immediately.** Nits are written to spec-review.md and the plan phase picks them up.\n- `approved` \u2014 no findings worth noting.\n\n**Batch related nits.** If you have multiple non-blocking observations, include them all in one `approved_with_nits` verdict rather than raising one per round.\n\n**Cross-review rule**: No agent reviews its own output. Claude writes specs \u2192 Codex reviews specs. Codex writes code \u2192 Claude reviews code.\n\nIf you encounter surprising codebase behavior, append to tasks/<id>/notes.md (prefix: [spec_review]).\n\nFor each task, write tasks/<id>/spec-review.md using the template. Set your verdict: approved, approved_with_nits, or changes_requested.\n\nWhen done, run (one per task with actual verdict):\n{{{phaseCommands}}}\n";
4770
+ var spec_review_default = "You are reviewing {{taskScope}} for {{projectName}}.\n\n{{{startup}}}\n\nTasks to review:\n{{{taskLines}}}\n\n{{#fullSendActive}}\n**Full-send mode active**: The human grilled Claude to resolve the decision tree but did not read this spec before pipeline execution, so your review is the primary rigor layer before implementation. The silence default, scope boundary, and verdict rules this prompt sets out still apply \u2014 don't manufacture findings. What full-send changes is *where you look*, not how much you flag: give extra attention to (1) missed cases the spec's ACs might overlook, (2) scope drift between the Decision section and the ACs, and (3) ambiguity in AC verification steps, since no human will catch a gap there before implementation.\n{{/fullSendActive}}\nGrounding rule: if a finding depends on code, a symbol, or a validation result, verify the current file or diff before you claim it exists. If you did not re-open it, do not infer it from memory.\n\n**Your objective: catch genuine blocking problems, precisely.** Read the spec as the implementer would: what would break, be ambiguous, or be missing? A spec with no blocking findings is a valid, expected result \u2014 approving a clean spec is not a shortfall in your review.\n\n**First, a strategic read of the spec itself \u2014 shape before implementability.** Ask:\n- Is the problem real? (Would doing nothing be fine? Is this a symptom of something else?)\n- For a bug or flake fix: does the stated evidence actually establish the targeted mechanism, at the rung its class requires? A deterministic mechanism (fixed inputs hit the same wrong branch every run) may be confirmed by a trace with the verified trigger values; a runtime-dependent mechanism (race, timing, environment/config interaction) requires executed confirmation \u2014 a throwaway prototype-fix spike that makes the symptom vanish, or a deterministic forced repro (fault injection, forced race, targeted repro). A paper argument can rule out a wrong hypothesis but doesn't by itself verify a runtime-dependent cause. Blocking Shape Check concerns: no confirmation evidence at all, evidence below the mechanism class's required rung, or a missing red-first regression-test AC without the environment-bound-and-impractical escape. Each role owns a checkpoint: the spec author states the confirmed mechanism and its evidence in *Problem*; the reviewer (Codex) challenges whether that evidence establishes the root cause; the implementer runs the spec's red-first test against the pre-fix code (or, under the environment-bound-and-impractical escape, its named deterministic alternative) and reports the result in the handoff.\n- For a refactor: correctness audit \u2014 does the spec record, for each behavior it declares preserved, whether it is correct today and one outcome: correct as-is, fixed deliberately (with its own AC), split into a separate task, or kept as a named quirk? A behavior confirmed correct needs only a one-line record. A missing audit is a Blocking Shape Check concern (requires a changes_requested verdict), matching the bug-fix evidence bullet above.\n- Is the framing right? (Does the spec solve the stated problem, or one adjacent to it?)\n- Is there a materially simpler solution that changes the shape of the work?\n- Is the AC decomposition right? (Compound ACs, missing ACs, ACs solving symptoms not causes?)\n\n**Silence is the default \u2014 for this whole review, Shape Check and implementability alike.** Only write a finding where something is actually off; do not manufacture one to fill a section or satisfy an obligation. A real shape concern becomes the lead reason for a `changes_requested` verdict; write it under the Shape Check section in spec-review.md. If none, leave that section as \"no concerns\" and proceed.\n\nThen for each task, apply that same default while probing implementability: Can this be implemented as written? Are ACs testable and unambiguous? Are edge cases handled? Are there type safety gaps? Are there file/interaction dependencies Claude missed? Does this conflict with existing patterns in the codebase? An empty list here is a valid result, not a gap in your review.{{#isBundle}}\nAlso probe for cross-task conflicts or missing dependencies between tasks.{{/isBundle}}\n{{#combined}}\nReview plan.md for each task as well \u2014 flag if the approach is unsound.{{/combined}}\n\n**Scope boundary.** Pre-existing behavior the task's spec *explicitly excludes and verifies as unaffected* (for example, named in Non-Goals) is out of scope for this review \u2014 a nit at most, never blocking. This carve-out does not cover: a change the spec *should* make but omitted (a required caller, parser, migration, or test surface), a transitive effect of the change, or an internal contradiction between spec sections \u2014 those remain **blocking** implementability findings even though the affected code is pre-existing. The test is not \"can I reach this code\" \u2014 it's whether the spec named it out of scope and showed it stays unaffected.\n\n**Classify every finding before deciding your verdict:**\n- **Blocking**: would cause wrong behavior, a silent bug, or make an AC unimplementable as written. Requires `changes_requested`.\n- **Non-blocking (nit)**: an implementation detail Codex can resolve by reading the codebase (prop flow, state threading, naming); a minor ambiguity with an obvious default; a question the plan phase should address. Does NOT require `changes_requested`.\n- *Example*: an under-specification whose intended value the surrounding task context makes obvious (e.g. a field name implied by an adjacent example or existing convention) is a nit for the plan phase, not Blocking.\n\n**Verdict rules:**\n- `changes_requested` \u2014 one or more blocking findings. Spec must be revised before the plan phase.\n- `approved_with_nits` \u2014 no blocking findings, but non-blocking nits worth passing forward. **Loop exits immediately.** Nits are written to spec-review.md and the plan phase picks them up.\n- `approved` \u2014 no findings worth noting.\n\n**Batch related nits.** If you have multiple non-blocking observations, include them all in one `approved_with_nits` verdict rather than raising one per round.\n\n**Cross-review rule**: No agent reviews its own output. Claude writes specs \u2192 Codex reviews specs. Codex writes code \u2192 Claude reviews code.\n\nIf you encounter surprising codebase behavior, append to tasks/<id>/notes.md (prefix: [spec_review]).\n\nFor each task, write tasks/<id>/spec-review.md using the template. Set your verdict: approved, approved_with_nits, or changes_requested.\n\nWhen done, run (one per task with actual verdict):\n{{{phaseCommands}}}\n";
4750
4771
 
4751
4772
  // src/orchestrator/prompts/index.ts
4752
4773
  var TEMPLATES = {
@@ -4771,7 +4792,15 @@ function render3(name, view) {
4771
4792
  return renderTemplate(loadTemplate(name), view);
4772
4793
  }
4773
4794
  function buildAffectedFilesBlock(affectedFiles, baseBranch) {
4774
- if (!affectedFiles) return "";
4795
+ if (affectedFiles === void 0) return "";
4796
+ if (affectedFiles === null) {
4797
+ return [
4798
+ "## Committed diff vs base branch",
4799
+ "",
4800
+ "The committed diff vs the base branch could not be determined (the git diff failed). Apply the full default check matrix from the spec's *Validation Required* section \u2014 run every check unconditionally; do not evaluate predicate gates against an assumed file set.",
4801
+ ""
4802
+ ].join("\n");
4803
+ }
4775
4804
  if (affectedFiles.length === 0) {
4776
4805
  return [
4777
4806
  "## Committed diff vs base branch",
@@ -4792,6 +4821,20 @@ function buildAffectedFilesBlock(affectedFiles, baseBranch) {
4792
4821
  ""
4793
4822
  ].join("\n");
4794
4823
  }
4824
+ function planFeasibilityCheck(placement, indent = "") {
4825
+ const lines = [
4826
+ `**Feasibility check.** Before writing steps, confirm each item below and record the results in a few lines ${placement}. Mark an item that doesn't apply \`N/A\` in a word.`,
4827
+ "- **Exists:** the functions, files, and patterns the plan relies on exist \u2014 found by searching the code, not recalled.",
4828
+ "- **Real path:** the actual runtime call path the change sits on, including any existing code on it that already does part of the work.",
4829
+ "- **Callers:** every caller of each function whose behavior or error contract changes, found and accounted for.",
4830
+ "- **Tests that can fail:** for each regression test the plan prescribes, the input or state that sends it through the changed path, so it fails without the change.",
4831
+ "- **Predicates:** for each condition or state check the plan writes out, its boundary values (for example zero, empty, or non-numeric) and every state it must handle.",
4832
+ "- **Scope:** every file the steps change is inside the spec's Affected Files.",
4833
+ "- **Async/stateful:** for async or stateful changes \u2014 re-entry, cancellation or unmount, stale state, and ownership.",
4834
+ "A finding that changes a step goes in that step. If the check contradicts the spec, record the contradiction in the plan and in `tasks/<id>/notes.md` with a `[plan]` prefix. Do not change the spec or widen scope to resolve it."
4835
+ ];
4836
+ return lines.map((line, index) => index === 0 ? line : indent + line).join("\n");
4837
+ }
4795
4838
  function promptSpec(state) {
4796
4839
  const { tasks, tier, isBundle } = state;
4797
4840
  const combined = tier === "fast";
@@ -4807,9 +4850,13 @@ Directory: tasks/${task.taskId}/`,
4807
4850
  startup: CLAUDE_STARTUP,
4808
4851
  instructions: isBundle ? tasks.map(
4809
4852
  (t) => `**Task \`${t.taskId}\`**: Write tasks/${t.taskId}/spec.md using the template.` + (combined ? ` Also write tasks/${t.taskId}/plan.md with ordered implementation steps, specific file references, and existing patterns.` : "")
4810
- ).join("\n\n") : `Write tasks/${task.taskId}/spec.md using the template in .canon/templates/spec.md. Be concrete \u2014 Codex implements directly from this.` + (combined ? `
4853
+ ).join("\n\n") + (combined ? `
4811
4854
 
4812
- Also write tasks/${task.taskId}/plan.md with ordered implementation steps, specific file references, and existing patterns to use.` : ""),
4855
+ ${planFeasibilityCheck("in each plan.md's Approach section")}` : "") : `Write tasks/${task.taskId}/spec.md using the template in .canon/templates/spec.md. Be concrete \u2014 Codex implements directly from this.` + (combined ? `
4856
+
4857
+ Also write tasks/${task.taskId}/plan.md with ordered implementation steps, specific file references, and existing patterns to use.
4858
+
4859
+ ${planFeasibilityCheck("in plan.md's Approach section")}` : ""),
4813
4860
  bundleNote: isBundle ? "\nThese tasks are related \u2014 consider cross-task interactions while speccing." : "",
4814
4861
  doneNote: combined ? "The orchestrator will handle spec_review and plan-phase advancement automatically for fast-tier tasks." : "",
4815
4862
  selfCheck: [
@@ -4817,10 +4864,12 @@ Also write tasks/${task.taskId}/plan.md with ordered implementation steps, speci
4817
4864
  '- Every AC is verifiable with a specific test (not just "it works" \u2014 state exactly how to verify)',
4818
4865
  "- Affected Files lists specific files (not directories) with specific, actionable change descriptions",
4819
4866
  combined ? "- Plan steps reference actual function/file names from the codebase (not just concepts)" : null,
4867
+ combined ? "- plan.md's Approach records the feasibility check (exists, real path, callers, tests that can fail, predicates, Affected Files, async/stateful; N/A where it doesn't apply)" : null,
4820
4868
  "- Known Risks covers failure modes for the trickiest ACs",
4821
4869
  "- Human Test Plan describes product behavior only (no code, no file names, no TypeScript)",
4822
4870
  '- Validation Required has at least one entry checked (or explicitly "None" with a reason)',
4823
- "- (Bug/flake fixes; N/A for features/refactors) *Problem* states the confirmed mechanism and how it was confirmed, with evidence matching the mechanism class (runtime-dependent mechanisms need executed confirmation), not merely a plausible cause; *Acceptance Criteria* includes a red-first regression-test AC or an explicit environment-bound and faithful-repro-impractical escape with a deterministic alternative"
4871
+ "- (Bug/flake fixes; N/A for features/refactors) *Problem* states the confirmed mechanism and how it was confirmed, with evidence matching the mechanism class (runtime-dependent mechanisms need executed confirmation), not merely a plausible cause; *Acceptance Criteria* includes a red-first regression-test AC or an explicit environment-bound and faithful-repro-impractical escape with a deterministic alternative",
4872
+ "- (Refactors; N/A for features/bug fixes) the spec records a correctness audit of each behavior declared preserved, with one outcome each: correct as-is, fixed deliberately (with its own AC), split into a separate task, or kept as a named quirk"
4824
4873
  ].filter(Boolean).join("\n"),
4825
4874
  phaseCommands: phaseCommands(tasks.map((t) => t.taskId), "spec", "done")
4826
4875
  });
@@ -4920,6 +4969,7 @@ function promptPlan(state) {
4920
4969
  taskScope: tasks.length > 1 ? "a bundle of tasks" : `task "${tasks[0].taskId}"`,
4921
4970
  roundBanner,
4922
4971
  verdictLines: verdictLines2,
4972
+ feasibilityCheck: planFeasibilityCheck("at the top of the appended `### Delta` section", " "),
4923
4973
  phaseCommands: phaseCommands(tasks.map((t) => t.taskId), "plan", "done")
4924
4974
  });
4925
4975
  }
@@ -4931,6 +4981,7 @@ function promptPlan(state) {
4931
4981
  startup: CLAUDE_STARTUP,
4932
4982
  taskScope: tasks.length > 1 ? "a bundle of tasks" : `task "${tasks[0].taskId}"`,
4933
4983
  verdictLines,
4984
+ feasibilityCheck: planFeasibilityCheck("in the plan's Approach section"),
4934
4985
  phaseCommands: phaseCommands(tasks.map((t) => t.taskId), "plan", "done")
4935
4986
  });
4936
4987
  }
@@ -5119,7 +5170,8 @@ function promptCodeReview(state, baseBranch, scopedDiff = null, coldCodexFinding
5119
5170
  hasColdCodexFindings: coldCodexFindings !== null,
5120
5171
  hasOutOfScopeFiles: outOfScopeFiles.length > 0,
5121
5172
  outOfScopeFilesList: outOfScopeFiles.map((file) => `- \`${file}\``).join("\n"),
5122
- phaseCommands: phaseCommands(tasks.map((t) => t.taskId), "code_review", "done", "<verdict>")
5173
+ phaseCommands: phaseCommands(tasks.map((t) => t.taskId), "code_review", "done", "<verdict>"),
5174
+ blockedCommands: phaseCommands(tasks.map((t) => t.taskId), "code_review", "blocked")
5123
5175
  });
5124
5176
  }
5125
5177
  function promptQa(state, prTemplate) {
@@ -5153,7 +5205,7 @@ function isInstalledSourcePath(sourcePath) {
5153
5205
  return sourcePath.includes("/node_modules/") || sourcePath.includes("\\node_modules\\") || sourcePath.includes("/_npx/") || sourcePath.includes("\\_npx\\");
5154
5206
  }
5155
5207
  function resolveCanonVersion(explicit) {
5156
- return explicit ?? "3.4.0" ?? "dev";
5208
+ return explicit ?? "3.6.0" ?? "dev";
5157
5209
  }
5158
5210
  function resolveOrchestratorCommit(repoRoot, upstreamCommit, runGitAt) {
5159
5211
  const ownToplevel = captureGitOutput(repoRoot, ["rev-parse", "--show-toplevel"], runGitAt);
@@ -5738,9 +5790,18 @@ function taskPhasePreflightRejected(id, phaseArg) {
5738
5790
 
5739
5791
  // src/orchestrator/phases/code-review.ts
5740
5792
  function findUnjudgedFullSendFilesFromData(changedFiles, allowlist, taskIds, verdicts) {
5741
- if (verdicts.some((verdict) => verdict === "changes_requested" || verdict === "needs_re_review" || verdict === "spec_gap")) return [];
5793
+ if (isFullSendScopeCheckExempt(verdicts)) return [];
5742
5794
  return verifyBaseDriftFromData(changedFiles, allowlist.paths, taskIds, allowlist.prefixes);
5743
5795
  }
5796
+ function isFullSendScopeCheckExempt(verdicts) {
5797
+ return verdicts.some((verdict) => verdict === "changes_requested" || verdict === "needs_re_review" || verdict === "spec_gap");
5798
+ }
5799
+ function scopeUnverifiedReason(context, taskIds, baseBranch, stderr, verdictNote = "") {
5800
+ return `${context} could not verify scope for ${taskIds.join(", ")}: the committed diff against base branch '${baseBranch}' could not be read.
5801
+ Git error: ${stderr || "unknown error"}
5802
+ Check that the task's base branch exists and shares history with HEAD; resolve the git error above.${verdictNote}
5803
+ Then re-run \`canon run ${taskIds.join(" ")}\`.`;
5804
+ }
5744
5805
  function fullSendScopeBlockReason(taskIds, files, recordedVerdicts) {
5745
5806
  const verdictNote = recordedVerdicts.length > 0 ? ` Recorded code-review verdict(s): ${recordedVerdicts.join(", ")}; inspect the review before resuming.` : "";
5746
5807
  return `Full-send code review left out-of-scope files unjudged: ${files.join(", ")}. Add them to a task spec's ### Affected Files table, then re-run \`canon run ${taskIds.join(" ")}\`; or reroute with a note telling the implementer to remove them.${verdictNote}`;
@@ -5933,7 +5994,14 @@ async function runCodeReviewPhase(state, interactive, resumeId, deps = defaultDe
5933
5994
  autoBlockPhase(taskIds, "code_review", codeReviewCheck.count, codeReviewCheck.reason);
5934
5995
  process.exit(2);
5935
5996
  }
5936
- const changedFiles = new Set(deps.getAffectedFiles(baseBranch, activeCwd));
5997
+ const affected = deps.getAffectedFiles(baseBranch, activeCwd);
5998
+ if (!affected.ok) {
5999
+ const reason = scopeUnverifiedReason("Code review pre-flight", taskIds, baseBranch, affected.stderr);
6000
+ warn(reason);
6001
+ autoBlockPhase(taskIds, "code_review", codeReviewCheck.count, reason);
6002
+ process.exit(2);
6003
+ }
6004
+ const changedFiles = new Set(affected.files);
5937
6005
  const bundleIssues = deps.verifyHandoffAgainstDiff(taskIds, baseBranch);
5938
6006
  const preflightFailed = [];
5939
6007
  for (const t of tasks) {
@@ -6099,7 +6167,11 @@ async function runImplementPhase(state, interactive, resumeId, force = false) {
6099
6167
  ensureBranch(taskIds, { force });
6100
6168
  const activeCwd = getActiveCwd(taskIds);
6101
6169
  const baseBranch = getBaseBranch(taskIds);
6102
- const affectedFiles = getAffectedFiles(baseBranch, activeCwd);
6170
+ const affected = getAffectedFiles(baseBranch, activeCwd);
6171
+ if (!affected.ok) {
6172
+ warn(`Could not compute the committed diff vs ${baseBranch} (${affected.stderr || "unknown error"}); the full check matrix applies.`);
6173
+ }
6174
+ const affectedFiles = affected.ok ? affected.files : null;
6103
6175
  const isRevision = shouldUseImplementRevision(tasks);
6104
6176
  const isRerouted = tasks.some((t) => t.status.phases.implement?.rerouted === true);
6105
6177
  const wasImplementInProgress = tasks.some((t) => t.status.phases.implement?.status === "in_progress");
@@ -7327,11 +7399,20 @@ Git error: ${baseDriftResult.diffError ?? "unknown error"}
7327
7399
  This failure cannot be bypassed with --force.`
7328
7400
  );
7329
7401
  } else if (baseDriftResult.drift.length > 0) {
7402
+ const taskChanged = getAffectedFiles(`origin/${baseBranch}`, cwd);
7403
+ if (!taskChanged.ok) {
7404
+ die(
7405
+ `--pr aborted: base-drift detected, but the task-changed files could not be computed against origin/${baseBranch}.
7406
+ Drifted files:
7407
+ ${baseDriftResult.drift.map((filePath) => ` ${filePath}`).join("\n")}
7408
+ Git error: ${taskChanged.stderr || "unknown error"}
7409
+ This failure cannot be bypassed with --force.`
7410
+ );
7411
+ }
7330
7412
  if (!cliArgs.force) {
7331
- const taskChangedFiles = new Set(getAffectedFiles(`origin/${baseBranch}`, cwd));
7332
7413
  const classification = classifyBaseDriftFilesFromData(
7333
7414
  baseDriftResult.drift,
7334
- taskChangedFiles
7415
+ new Set(taskChanged.files)
7335
7416
  );
7336
7417
  die2(buildBaseDriftAbortMessage(baseBranch, classification));
7337
7418
  }
@@ -8641,8 +8722,30 @@ async function recoverPhaseForTask(taskId, phase, initialStatus) {
8641
8722
  warn2(`Retry did not recover '${taskId}' ${phase} (${postEvidence.note}).`);
8642
8723
  return false;
8643
8724
  }
8725
+ function stopForReviewerBlock(taskIds, blockedIds, statuses) {
8726
+ const maxIter = statuses.reduce((max, status) => Math.max(max, getIterations(status)), 0);
8727
+ const recovery = taskIds.map((id) => `canon task reset-code-review ${id}`).join("; ");
8728
+ const reason = `Code review stopped without a verdict for task(s): ${blockedIds.join(", ")}. The reviewer set code_review blocked; see ${taskIds.map((id) => `tasks/${id}/review.md`).join(", ")}. No evidence recovery or retry was attempted. Recovery for the full blocked bundle [${taskIds.join(" ")}]: ${recovery}; then canon run ${taskIds.join(" ")}.`;
8729
+ console.log("");
8730
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
8731
+ console.log(" \u270B CODE REVIEW STOPPED \u2014 the reviewer could not reach a verdict.");
8732
+ console.log(" Read the stopped review:");
8733
+ for (const id of taskIds) console.log(` tasks/${id}/review.md`);
8734
+ console.log(" Reset every task in the bundle, then run again:");
8735
+ for (const id of taskIds) console.log(` canon task reset-code-review ${id}`);
8736
+ console.log(` canon run ${taskIds.join(" ")}`);
8737
+ console.log(" Re-running without the reset starts a fresh review pass without archiving the stopped review.md.");
8738
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
8739
+ console.log("");
8740
+ autoBlockPhase(taskIds, "code_review", maxIter, reason);
8741
+ process.exit(2);
8742
+ }
8644
8743
  async function checkAndRoute(phase, taskIds) {
8645
8744
  let statuses = taskIds.map(readStatus);
8745
+ if (phase === "code_review") {
8746
+ const blockedIds = taskIds.filter((_, index) => getPhaseStatus(statuses[index], "code_review") === "blocked");
8747
+ if (blockedIds.length > 0) stopForReviewerBlock(taskIds, blockedIds, statuses);
8748
+ }
8646
8749
  for (let i = 0; i < taskIds.length; i += 1) {
8647
8750
  const phaseStatus = getPhaseStatus(statuses[i], phase);
8648
8751
  if (phase === "implement" && phaseStatus === "done") {
@@ -8678,32 +8781,47 @@ async function checkAndRoute(phase, taskIds) {
8678
8781
  }
8679
8782
  statuses = taskIds.map(readStatus);
8680
8783
  let specGapScopeFiles = [];
8784
+ let specGapScopeError = null;
8681
8785
  if (phase === "code_review" && statuses.every((status) => status.full_send === true)) {
8682
8786
  const cwd = getActiveCwd(taskIds);
8683
8787
  const baseBranch = getBaseBranch(taskIds);
8684
- const changedFiles = getAffectedFiles(baseBranch, cwd);
8685
- const allowlist = buildAffectedFilesAllowlist(taskIds, { admitManagedDocs: true });
8788
+ const affected = getAffectedFiles(baseBranch, cwd);
8686
8789
  const verdicts = statuses.map((status) => getVerdict(status, "code_review"));
8687
- if (verdicts.includes("spec_gap")) {
8688
- specGapScopeFiles = verifyBaseDriftFromData(
8790
+ const maxIter = statuses.reduce((max, status) => Math.max(max, getIterations(status)), 0);
8791
+ if (!affected.ok) {
8792
+ if (verdicts.includes("spec_gap")) {
8793
+ specGapScopeError = affected.stderr || "unknown error";
8794
+ } else if (!isFullSendScopeCheckExempt(verdicts)) {
8795
+ const recorded = verdicts.filter(Boolean);
8796
+ const verdictNote = ` Recorded code-review verdict(s): ${recorded.length > 0 ? recorded.join(", ") : "none"}; inspect the review before resuming.`;
8797
+ const reason = scopeUnverifiedReason("Full-send code review", taskIds, baseBranch, affected.stderr, verdictNote);
8798
+ warn2(reason);
8799
+ autoBlockPhase(taskIds, "code_review", maxIter, reason);
8800
+ process.exit(2);
8801
+ }
8802
+ } else {
8803
+ const changedFiles = affected.files;
8804
+ const allowlist = buildAffectedFilesAllowlist(taskIds, { admitManagedDocs: true });
8805
+ if (verdicts.includes("spec_gap")) {
8806
+ specGapScopeFiles = verifyBaseDriftFromData(
8807
+ changedFiles,
8808
+ allowlist.paths,
8809
+ taskIds,
8810
+ allowlist.prefixes
8811
+ );
8812
+ }
8813
+ const unjudged = findUnjudgedFullSendFilesFromData(
8689
8814
  changedFiles,
8690
- allowlist.paths,
8815
+ allowlist,
8691
8816
  taskIds,
8692
- allowlist.prefixes
8817
+ verdicts
8693
8818
  );
8694
- }
8695
- const unjudged = findUnjudgedFullSendFilesFromData(
8696
- changedFiles,
8697
- allowlist,
8698
- taskIds,
8699
- verdicts
8700
- );
8701
- if (unjudged.length > 0) {
8702
- const reason = fullSendScopeBlockReason(taskIds, unjudged, verdicts.filter(Boolean));
8703
- warn2(reason);
8704
- const maxIter = statuses.reduce((max, status) => Math.max(max, getIterations(status)), 0);
8705
- autoBlockPhase(taskIds, "code_review", maxIter, reason);
8706
- process.exit(2);
8819
+ if (unjudged.length > 0) {
8820
+ const reason = fullSendScopeBlockReason(taskIds, unjudged, verdicts.filter(Boolean));
8821
+ warn2(reason);
8822
+ autoBlockPhase(taskIds, "code_review", maxIter, reason);
8823
+ process.exit(2);
8824
+ }
8707
8825
  }
8708
8826
  }
8709
8827
  if (lastCodexExitStatus !== 0) {
@@ -8781,7 +8899,10 @@ async function checkAndRoute(phase, taskIds) {
8781
8899
  const specGapIds = taskIds.filter((_, index) => getVerdict(statuses[index], "code_review") === "spec_gap");
8782
8900
  if (specGapIds.length > 0) {
8783
8901
  const maxIter = statuses.reduce((max, s) => Math.max(max, getIterations(s)), 0);
8784
- const scopeNote = specGapScopeFiles.length > 0 ? ` Full-send files remain outside Affected Files: ${specGapScopeFiles.join(", ")}. A BLESS accepts the verdict but does not amend the spec, so --pr will still reject these files: before blessing, add them to the spec's Affected Files (as an ## Amendment) or remove them from the branch.` : "";
8902
+ const scopeNote = specGapScopeFiles.length > 0 ? `Full-send files remain outside Affected Files: ${specGapScopeFiles.join(", ")}. A BLESS accepts the verdict but does not amend the spec, so --pr will still reject these files: before blessing, add them to the spec's Affected Files (as an ## Amendment) or remove them from the branch. ` : specGapScopeError !== null ? `Full-send scope could not be verified: the committed diff against the base branch could not be read.
8903
+ Git error: ${specGapScopeError}
8904
+ Scope was not checked. Fix the base branch or repository history before choosing a recovery option below, and verify scope manually before blessing.
8905
+ ` : "";
8785
8906
  const reason = `Code review surfaced a spec_gap verdict for task(s): ${specGapIds.join(", ")}. The implementation cannot resolve this \u2014 the root cause is in the spec. ` + scopeNote + `Recovery options (both operate on the full blocked bundle [${taskIds.join(" ")}]):
8786
8907
  FIX: amend spec.md with ## Amendment, then: canon run ${taskIds.join(" ")} --reroute
8787
8908
  BLESS: canon task accept ${taskIds.join(" ")} code_review --reason "<why>"`;
@@ -8797,6 +8918,10 @@ async function checkAndRoute(phase, taskIds) {
8797
8918
  for (const file of specGapScopeFiles) console.log(` ${file}`);
8798
8919
  console.log(" BLESS does not amend the spec, so --pr will still reject these files.");
8799
8920
  console.log(" Before blessing, add them to Affected Files (## Amendment) or remove them.");
8921
+ } else if (specGapScopeError !== null) {
8922
+ console.log(" Full-send scope could not be verified.");
8923
+ console.log(` Git error: ${specGapScopeError}`);
8924
+ console.log(" Fix the base branch or repository history before choosing FIX or BLESS; verify scope manually before blessing.");
8800
8925
  }
8801
8926
  console.log("");
8802
8927
  console.log(" Two recovery options:");