canon-ai 3.5.0 → 3.7.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,28 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [3.7.0] — 2026-10-06
8
+
9
+ ### Added
10
+
11
+ - **`canon task set <id> human_spec_gate true|false` arms or disarms the human spec gate.** You no longer need to hand-edit `status.json`. Arming the gate on a full-send task also clears `full_send`, because the gate never fires on a full-send task, and prints how to turn full-send back on (`canon run --full-send <id>`). If the task's `spec_review` is already done, it prints a note that the gate fires again only after a full-tier `canon run --reroute <id>`. Disarming is refused on delicate tasks; `canon run --full-send --force <id>` is still the only way to skip the gate there. `canon task new` now points at `canon task set` instead of telling you to edit `status.json`.
12
+
13
+ ### Changed
14
+
15
+ - **The `full_send` refusal message now says how the flag clears.** It stays in `status.json` until `--reroute` or arming the spec gate clears it.
16
+ - **The npm package's homepage now links to <https://timothystraub.com/canon>.**
17
+
18
+ ## [3.6.0] — 2026-10-04
19
+
20
+ ### Changed
21
+
22
+ - **`/canon-spec-review` reviewers report every finding they can cite, and the final report does the filtering.** They now use Codex spec review's blocking and nit definitions, so behavior a spec explicitly excludes and verifies as unaffected is a nit at most. The Affected Files check now names the exact heading the pipeline reads, accepts any amendment section, and expects generated or rebuilt outputs to be listed. The reviewers now run as a read-only `spec-review-lens` agent that has no edit tools, can't launch other agents or skills, and is told to use the shell only for reading. Run `canon upgrade`, then start a new Claude Code session to pick it up.
23
+ - **Refactor specs must check that the behavior they preserve is actually correct.** Before a "preserve behavior" criterion, a refactor spec now records in *Problem* whether each preserved behavior is correct today, and gives one outcome for each: correct as-is, fixed deliberately with its own criterion, split into a separate task, or kept as a named quirk. A behavior confirmed correct needs only a one-line record. `/canon-spec`, the spec template, and the pipeline's spec phases all ask for it. Pipeline spec review treats a missing audit as blocking, while `/canon-spec-review` reports it as STRONG. Feature and bug-fix specs are unaffected.
24
+
25
+ ### Fixed
26
+
27
+ - **A task whose base branch can't be diffed now blocks instead of passing scope checks.** If git can't read a task's changes against its base (a stale or mistyped `base_branch`, a deleted base), the unreadable diff used to look like "nothing changed," so scope checks let the work through. Full-send review and the `code_review` pre-flight now block with git's error instead of advancing to QA or looping back to implement. `--pr` and `--push` report that the task's changed files couldn't be computed instead of advising a rebase, and `--force` does not bypass it. Implementation continues, with the full check matrix required.
28
+
7
29
  ## [3.5.0] — 2026-09-29
8
30
 
9
31
  ### Added
package/dist/cli/index.js CHANGED
@@ -1224,7 +1224,7 @@ function checkTemplates(cwd) {
1224
1224
  }
1225
1225
  function checkCanonVersion(cwd) {
1226
1226
  const versionPath = join(cwd, ".canon", "version");
1227
- const installedVersion = "3.5.0";
1227
+ const installedVersion = "3.7.0";
1228
1228
  if (!existsSync(versionPath)) {
1229
1229
  return { label: ".canon/version", status: "warn", detail: "missing \u2014 run `canon upgrade`" };
1230
1230
  }
@@ -1721,7 +1721,7 @@ function initCmd(_args) {
1721
1721
  }
1722
1722
  function writeCanonVersion(cwd) {
1723
1723
  const versionPath = join2(cwd, ".canon", "version");
1724
- const version = "3.5.0";
1724
+ const version = "3.7.0";
1725
1725
  mkdirSync(dirname2(versionPath), { recursive: true });
1726
1726
  writeFileSync(versionPath, version + "\n");
1727
1727
  }
@@ -2731,7 +2731,7 @@ function isInstalledSourcePath(sourcePath) {
2731
2731
  return sourcePath.includes("/node_modules/") || sourcePath.includes("\\node_modules\\") || sourcePath.includes("/_npx/") || sourcePath.includes("\\_npx\\");
2732
2732
  }
2733
2733
  function resolveCanonVersion(explicit) {
2734
- return explicit ?? "3.5.0" ?? "dev";
2734
+ return explicit ?? "3.7.0" ?? "dev";
2735
2735
  }
2736
2736
  function resolveOrchestratorCommit(repoRoot, upstreamCommit, runGitAt) {
2737
2737
  const ownToplevel = captureGitOutput(repoRoot, ["rev-parse", "--show-toplevel"], runGitAt);
@@ -3447,17 +3447,21 @@ function rescaffoldReview(taskDir, identity) {
3447
3447
  }
3448
3448
  }
3449
3449
 
3450
+ // src/lib/pipeline-policy.ts
3451
+ function isPlanCombined(task) {
3452
+ return task.task_size === "XS" && !(task.delicate ?? false);
3453
+ }
3454
+
3450
3455
  // src/task/index.ts
3451
3456
  var VALID_PHASES = new Set(PHASE_ORDER);
3452
3457
  var VALID_STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress", "done", "changes_requested", "blocked"]);
3453
3458
  var VALID_VERDICTS = /* @__PURE__ */ new Set(["approved", "approved_with_nits", "changes_requested", "needs_re_review", "spec_gap", "sanctioned"]);
3454
3459
  var REVIEW_PHASES = /* @__PURE__ */ new Set(["spec_review", "code_review"]);
3455
- var SETTABLE_FIELDS = ["title", "task_size", "delicate", "worktree", "base_branch"];
3460
+ var SETTABLE_FIELDS = ["title", "task_size", "delicate", "worktree", "base_branch", "human_spec_gate"];
3456
3461
  var SETTABLE_FIELD_SET = new Set(SETTABLE_FIELDS);
3457
3462
  var IMMUTABLE_FIELDS = /* @__PURE__ */ new Set(["id", "created", "updated"]);
3458
3463
  var REDIRECT_MESSAGES = {
3459
- full_send: "a per-run stance, not durable metadata. Use `canon run --full-send <id>`, which also clears the spec gate and enforces the delicate\u2192`--force` guard.",
3460
- human_spec_gate: "the spec gate is self-clearing. Re-run `canon run <id>` to proceed past it, or use `canon run --full-send <id>` to skip it entirely.",
3464
+ full_send: "persisted in status.json until `canon run --reroute` clears it or arming the gate with `canon task set <id> human_spec_gate true` does. Use `canon run --full-send <id>` to enable it; the delicate-task guard requires `--force`.",
3461
3465
  status: "derived from phase states. Use `canon task phase <id> <phase> <status>`.",
3462
3466
  branch: "load-bearing git identity; retargeting it desyncs the worktree. Not settable via `canon task set`.",
3463
3467
  phases: "nested orchestrator-owned state. Use `canon task phase`, `canon task reset-spec-review`, `canon task reset-code-review`, or `canon task accept` instead.",
@@ -3568,7 +3572,7 @@ function listTemplateFiles() {
3568
3572
  }
3569
3573
  return fs14.readdirSync(root).filter((name) => name.endsWith(".md") || name.endsWith(".json")).sort();
3570
3574
  }
3571
- function printCreatedTask(taskDir, baseBranch) {
3575
+ function printCreatedTask(taskId, taskDir, baseBranch) {
3572
3576
  console.log(`Created task: ${taskDir}`);
3573
3577
  console.log("Files:");
3574
3578
  for (const file of fs14.readdirSync(taskDir).sort()) {
@@ -3578,7 +3582,7 @@ function printCreatedTask(taskDir, baseBranch) {
3578
3582
  console.log(`Next: Write the spec in ${taskDir}/spec.md`);
3579
3583
  console.log("");
3580
3584
  console.log(` Defaults: task_size=M, delicate=false, human_spec_gate=true, base_branch=${baseBranch}`);
3581
- console.log(` Edit ${taskDir}/status.json to adjust before running the pipeline.`);
3585
+ console.log(` Adjust with: canon task set ${taskId} <field> <value>`);
3582
3586
  }
3583
3587
  function taskNew(args2) {
3584
3588
  let id = "";
@@ -3634,7 +3638,7 @@ function taskNew(args2) {
3634
3638
  const message = error instanceof Error ? error.message : String(error);
3635
3639
  console.error(`Warning: created task without canon snapshot refresh: ${message}`);
3636
3640
  }
3637
- printCreatedTask(taskDir, baseBranch);
3641
+ printCreatedTask(id, taskDir, baseBranch);
3638
3642
  }
3639
3643
  function derivePhase(status) {
3640
3644
  return deriveTopLevelStatus(status);
@@ -4470,13 +4474,13 @@ function taskSetValue(taskId, field, value, status) {
4470
4474
  throw new Error("Error: title must be single-line (no embedded newlines).");
4471
4475
  }
4472
4476
  status.title = value;
4473
- return;
4477
+ return [];
4474
4478
  case "task_size":
4475
4479
  if (!TASK_SIZE_VALUES.has(value)) {
4476
4480
  throw new Error(`Error: invalid task_size '${value}'. Must be one of: XS, S, M, L, XL.`);
4477
4481
  }
4478
4482
  status.task_size = value;
4479
- return;
4483
+ return [];
4480
4484
  case "delicate":
4481
4485
  case "worktree": {
4482
4486
  const normalized = value.toLowerCase();
@@ -4484,7 +4488,26 @@ function taskSetValue(taskId, field, value, status) {
4484
4488
  throw new Error(`Error: invalid ${field} '${value}'. Must be true or false.`);
4485
4489
  }
4486
4490
  status[field] = normalized === "true";
4487
- return;
4491
+ return [];
4492
+ }
4493
+ case "human_spec_gate": {
4494
+ const normalized = value.toLowerCase();
4495
+ if (normalized !== "true" && normalized !== "false") {
4496
+ throw new Error(`Error: invalid ${field} '${value}'. Must be true or false.`);
4497
+ }
4498
+ if (normalized === "false") {
4499
+ if (status.delicate === true) {
4500
+ throw new Error(`Error: human_spec_gate cannot be disarmed on delicate task ${taskId}. Use \`canon run --full-send --force ${taskId}\` to skip the spec gate on a delicate task, or re-run \`canon run ${taskId}\` to proceed past a gate that already halted.`);
4501
+ }
4502
+ status.human_spec_gate = false;
4503
+ return [];
4504
+ }
4505
+ status.human_spec_gate = true;
4506
+ if (status.full_send === true) {
4507
+ status.full_send = false;
4508
+ return [`Note: full-send was cleared on task ${taskId} because arming the spec gate re-introduces a human checkpoint. Run \`canon run --full-send ${taskId}\` to re-enable it.`];
4509
+ }
4510
+ return [];
4488
4511
  }
4489
4512
  case "base_branch": {
4490
4513
  const trimmed = value.trim();
@@ -4493,7 +4516,7 @@ function taskSetValue(taskId, field, value, status) {
4493
4516
  }
4494
4517
  validateBranchField(trimmed, taskId, "base_branch");
4495
4518
  status.base_branch = trimmed;
4496
- return;
4519
+ return [];
4497
4520
  }
4498
4521
  default:
4499
4522
  throw new Error(`Error: internal error \u2014 unsupported settable field '${field}'.`);
@@ -4502,6 +4525,16 @@ function taskSetValue(taskId, field, value, status) {
4502
4525
  function taskSetRedirectMessage(field) {
4503
4526
  return REDIRECT_MESSAGES[field] ?? "nested orchestrator-owned state. Use the owning canon task command instead.";
4504
4527
  }
4528
+ var REROUTE_ADMITTED_PHASES = /* @__PURE__ */ new Set(["code_review", "qa", "human_review"]);
4529
+ function specGateDoneNote(id, status) {
4530
+ if (isPlanCombined(status)) {
4531
+ return `Note: spec_review is already done on task ${id}. A fast-tier reroute skips spec_review, so the armed gate fires again only if this task is rerouted in a bundle with a full-tier task.`;
4532
+ }
4533
+ if (REROUTE_ADMITTED_PHASES.has(deriveTopLevelStatus(status))) {
4534
+ return `Note: spec_review is already done on task ${id}. The armed gate fires after the next spec_review, which a full-tier reroute runs: add an amendment to the spec, then run \`canon run --reroute ${id}\`.`;
4535
+ }
4536
+ return `Note: spec_review is already done on task ${id}, so the armed gate will not fire on this pass. On a full-tier task it can fire again only if an amendment is rerouted once the task reaches code_review, qa, or human_review.`;
4537
+ }
4505
4538
  function taskHasStarted(status) {
4506
4539
  return Object.values(status.phases).some((entry) => (entry?.status ?? "pending") !== "pending");
4507
4540
  }
@@ -4527,10 +4560,16 @@ function taskSet(args2) {
4527
4560
  );
4528
4561
  }
4529
4562
  if (SETTABLE_FIELD_SET.has(field)) {
4530
- taskSetValue(id, field, value, status);
4563
+ const notes = taskSetValue(id, field, value, status);
4531
4564
  status.updated = today();
4532
4565
  writeStatusAtomic(statusPath, status);
4533
- if (taskHasStarted(status)) {
4566
+ for (const note of notes) console.log(note);
4567
+ const specReviewDone = status.phases.spec_review?.status === "done";
4568
+ if (field === "human_spec_gate" && specReviewDone) {
4569
+ if (status.human_spec_gate === true) {
4570
+ console.log(specGateDoneNote(id, status));
4571
+ }
4572
+ } else if (taskHasStarted(status)) {
4534
4573
  console.log(`Warning: ${field} on task ${id} takes effect on the next canon run.`);
4535
4574
  }
4536
4575
  return;
@@ -4538,9 +4577,6 @@ function taskSet(args2) {
4538
4577
  if (field === "full_send") {
4539
4578
  throw new Error(`Error: full_send is ${taskSetRedirectMessage(field)}`);
4540
4579
  }
4541
- if (field === "human_spec_gate") {
4542
- throw new Error(`Error: human_spec_gate is ${taskSetRedirectMessage(field)}`);
4543
- }
4544
4580
  if (field === "status") {
4545
4581
  throw new Error(`Error: status is ${taskSetRedirectMessage(field)}`);
4546
4582
  }
@@ -4857,7 +4893,7 @@ function currentPinFromManifest(manifest) {
4857
4893
  return "unknown";
4858
4894
  }
4859
4895
  function bakedVersion() {
4860
- return "3.5.0";
4896
+ return "3.7.0";
4861
4897
  }
4862
4898
  function ownPackageName(pkgDir) {
4863
4899
  try {
@@ -5073,6 +5109,7 @@ var CANON_OWNED = [
5073
5109
  ".claude/skills/canon-sweep/SKILL.md",
5074
5110
  ".claude/agents/code-review-anchored.md",
5075
5111
  ".claude/agents/code-review-cold.md",
5112
+ ".claude/agents/spec-review-lens.md",
5076
5113
  ".canon/templates/status.json",
5077
5114
  ".canon/templates/spec.md",
5078
5115
  ".canon/templates/plan.md",
@@ -5333,7 +5370,7 @@ function runUpgrade(cwd, pkgDir, options = {}) {
5333
5370
  cutoverWarnings.push(docsRefsCheckRel);
5334
5371
  }
5335
5372
  const versionPath = join5(cwd, ".canon", "version");
5336
- const newVersion = "3.5.0";
5373
+ const newVersion = "3.7.0";
5337
5374
  const currentVersion = existsSync5(versionPath) ? readFileSync4(versionPath, "utf8").trim() : null;
5338
5375
  if (currentVersion !== newVersion) {
5339
5376
  pending.push({ rel: ".canon/version", projectPath: versionPath, content: newVersion + "\n" });
@@ -5641,7 +5678,7 @@ Global:
5641
5678
  `);
5642
5679
  }
5643
5680
  function printVersion() {
5644
- console.log("3.5.0");
5681
+ console.log("3.7.0");
5645
5682
  }
5646
5683
  switch (command) {
5647
5684
  case "doctor":
@@ -1586,7 +1586,9 @@ function parseNameStatusOutput(raw) {
1586
1586
  return [...paths].sort();
1587
1587
  }
1588
1588
  function getAffectedFiles(baseRef, cwd) {
1589
- 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) };
1590
1592
  }
1591
1593
  function getPathsInRange(rangeExpr, cwd) {
1592
1594
  const result = gitSafeAtRaw(cwd, "diff", rangeExpr, "--name-status", "-M", "-z");
@@ -4756,16 +4758,16 @@ var plan_default = "You are writing implementation plans for {{taskScope}} for {
4756
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';
4757
4759
 
4758
4760
  // src/orchestrator/prompts/templates/spec.md
4759
- 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';
4760
4762
 
4761
4763
  // src/orchestrator/prompts/templates/spec-revision.md
4762
- 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';
4763
4765
 
4764
4766
  // src/orchestrator/prompts/templates/spec-review-reroute.md
4765
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";
4766
4768
 
4767
4769
  // src/orchestrator/prompts/templates/spec-review.md
4768
- 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";
4769
4771
 
4770
4772
  // src/orchestrator/prompts/index.ts
4771
4773
  var TEMPLATES = {
@@ -4790,7 +4792,15 @@ function render3(name, view) {
4790
4792
  return renderTemplate(loadTemplate(name), view);
4791
4793
  }
4792
4794
  function buildAffectedFilesBlock(affectedFiles, baseBranch) {
4793
- 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
+ }
4794
4804
  if (affectedFiles.length === 0) {
4795
4805
  return [
4796
4806
  "## Committed diff vs base branch",
@@ -4858,7 +4868,8 @@ ${planFeasibilityCheck("in plan.md's Approach section")}` : ""),
4858
4868
  "- Known Risks covers failure modes for the trickiest ACs",
4859
4869
  "- Human Test Plan describes product behavior only (no code, no file names, no TypeScript)",
4860
4870
  '- Validation Required has at least one entry checked (or explicitly "None" with a reason)',
4861
- "- (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"
4862
4873
  ].filter(Boolean).join("\n"),
4863
4874
  phaseCommands: phaseCommands(tasks.map((t) => t.taskId), "spec", "done")
4864
4875
  });
@@ -5194,7 +5205,7 @@ function isInstalledSourcePath(sourcePath) {
5194
5205
  return sourcePath.includes("/node_modules/") || sourcePath.includes("\\node_modules\\") || sourcePath.includes("/_npx/") || sourcePath.includes("\\_npx\\");
5195
5206
  }
5196
5207
  function resolveCanonVersion(explicit) {
5197
- return explicit ?? "3.5.0" ?? "dev";
5208
+ return explicit ?? "3.7.0" ?? "dev";
5198
5209
  }
5199
5210
  function resolveOrchestratorCommit(repoRoot, upstreamCommit, runGitAt) {
5200
5211
  const ownToplevel = captureGitOutput(repoRoot, ["rev-parse", "--show-toplevel"], runGitAt);
@@ -5581,7 +5592,7 @@ var VALID_PHASES = new Set(PHASE_ORDER);
5581
5592
  var VALID_STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress", "done", "changes_requested", "blocked"]);
5582
5593
  var VALID_VERDICTS = /* @__PURE__ */ new Set(["approved", "approved_with_nits", "changes_requested", "needs_re_review", "spec_gap", "sanctioned"]);
5583
5594
  var REVIEW_PHASES = /* @__PURE__ */ new Set(["spec_review", "code_review"]);
5584
- var SETTABLE_FIELDS = ["title", "task_size", "delicate", "worktree", "base_branch"];
5595
+ var SETTABLE_FIELDS = ["title", "task_size", "delicate", "worktree", "base_branch", "human_spec_gate"];
5585
5596
  var SETTABLE_FIELD_SET = new Set(SETTABLE_FIELDS);
5586
5597
  function today() {
5587
5598
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -5779,9 +5790,18 @@ function taskPhasePreflightRejected(id, phaseArg) {
5779
5790
 
5780
5791
  // src/orchestrator/phases/code-review.ts
5781
5792
  function findUnjudgedFullSendFilesFromData(changedFiles, allowlist, taskIds, verdicts) {
5782
- if (verdicts.some((verdict) => verdict === "changes_requested" || verdict === "needs_re_review" || verdict === "spec_gap")) return [];
5793
+ if (isFullSendScopeCheckExempt(verdicts)) return [];
5783
5794
  return verifyBaseDriftFromData(changedFiles, allowlist.paths, taskIds, allowlist.prefixes);
5784
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
+ }
5785
5805
  function fullSendScopeBlockReason(taskIds, files, recordedVerdicts) {
5786
5806
  const verdictNote = recordedVerdicts.length > 0 ? ` Recorded code-review verdict(s): ${recordedVerdicts.join(", ")}; inspect the review before resuming.` : "";
5787
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}`;
@@ -5974,7 +5994,14 @@ async function runCodeReviewPhase(state, interactive, resumeId, deps = defaultDe
5974
5994
  autoBlockPhase(taskIds, "code_review", codeReviewCheck.count, codeReviewCheck.reason);
5975
5995
  process.exit(2);
5976
5996
  }
5977
- 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);
5978
6005
  const bundleIssues = deps.verifyHandoffAgainstDiff(taskIds, baseBranch);
5979
6006
  const preflightFailed = [];
5980
6007
  for (const t of tasks) {
@@ -6140,7 +6167,11 @@ async function runImplementPhase(state, interactive, resumeId, force = false) {
6140
6167
  ensureBranch(taskIds, { force });
6141
6168
  const activeCwd = getActiveCwd(taskIds);
6142
6169
  const baseBranch = getBaseBranch(taskIds);
6143
- 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;
6144
6175
  const isRevision = shouldUseImplementRevision(tasks);
6145
6176
  const isRerouted = tasks.some((t) => t.status.phases.implement?.rerouted === true);
6146
6177
  const wasImplementInProgress = tasks.some((t) => t.status.phases.implement?.status === "in_progress");
@@ -7368,11 +7399,20 @@ Git error: ${baseDriftResult.diffError ?? "unknown error"}
7368
7399
  This failure cannot be bypassed with --force.`
7369
7400
  );
7370
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
+ }
7371
7412
  if (!cliArgs.force) {
7372
- const taskChangedFiles = new Set(getAffectedFiles(`origin/${baseBranch}`, cwd));
7373
7413
  const classification = classifyBaseDriftFilesFromData(
7374
7414
  baseDriftResult.drift,
7375
- taskChangedFiles
7415
+ new Set(taskChanged.files)
7376
7416
  );
7377
7417
  die2(buildBaseDriftAbortMessage(baseBranch, classification));
7378
7418
  }
@@ -8741,32 +8781,47 @@ async function checkAndRoute(phase, taskIds) {
8741
8781
  }
8742
8782
  statuses = taskIds.map(readStatus);
8743
8783
  let specGapScopeFiles = [];
8784
+ let specGapScopeError = null;
8744
8785
  if (phase === "code_review" && statuses.every((status) => status.full_send === true)) {
8745
8786
  const cwd = getActiveCwd(taskIds);
8746
8787
  const baseBranch = getBaseBranch(taskIds);
8747
- const changedFiles = getAffectedFiles(baseBranch, cwd);
8748
- const allowlist = buildAffectedFilesAllowlist(taskIds, { admitManagedDocs: true });
8788
+ const affected = getAffectedFiles(baseBranch, cwd);
8749
8789
  const verdicts = statuses.map((status) => getVerdict(status, "code_review"));
8750
- if (verdicts.includes("spec_gap")) {
8751
- 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(
8752
8814
  changedFiles,
8753
- allowlist.paths,
8815
+ allowlist,
8754
8816
  taskIds,
8755
- allowlist.prefixes
8817
+ verdicts
8756
8818
  );
8757
- }
8758
- const unjudged = findUnjudgedFullSendFilesFromData(
8759
- changedFiles,
8760
- allowlist,
8761
- taskIds,
8762
- verdicts
8763
- );
8764
- if (unjudged.length > 0) {
8765
- const reason = fullSendScopeBlockReason(taskIds, unjudged, verdicts.filter(Boolean));
8766
- warn2(reason);
8767
- const maxIter = statuses.reduce((max, status) => Math.max(max, getIterations(status)), 0);
8768
- autoBlockPhase(taskIds, "code_review", maxIter, reason);
8769
- 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
+ }
8770
8825
  }
8771
8826
  }
8772
8827
  if (lastCodexExitStatus !== 0) {
@@ -8844,7 +8899,10 @@ async function checkAndRoute(phase, taskIds) {
8844
8899
  const specGapIds = taskIds.filter((_, index) => getVerdict(statuses[index], "code_review") === "spec_gap");
8845
8900
  if (specGapIds.length > 0) {
8846
8901
  const maxIter = statuses.reduce((max, s) => Math.max(max, getIterations(s)), 0);
8847
- 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
+ ` : "";
8848
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(" ")}]):
8849
8907
  FIX: amend spec.md with ## Amendment, then: canon run ${taskIds.join(" ")} --reroute
8850
8908
  BLESS: canon task accept ${taskIds.join(" ")} code_review --reason "<why>"`;
@@ -8860,6 +8918,10 @@ async function checkAndRoute(phase, taskIds) {
8860
8918
  for (const file of specGapScopeFiles) console.log(` ${file}`);
8861
8919
  console.log(" BLESS does not amend the spec, so --pr will still reject these files.");
8862
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.");
8863
8925
  }
8864
8926
  console.log("");
8865
8927
  console.log(" Two recovery options:");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canon-ai",
3
- "version": "3.5.0",
3
+ "version": "3.7.0",
4
4
  "type": "module",
5
5
  "description": "A spec-first, multi-agent AI coding pipeline you drop into any repo. The law your agents work under.",
6
6
  "engines": {
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "license": "MIT",
43
43
  "author": "Tim Straub",
44
- "homepage": "https://github.com/tstraub89/canon-ai#readme",
44
+ "homepage": "https://timothystraub.com/canon",
45
45
  "repository": {
46
46
  "type": "git",
47
47
  "url": "git+https://github.com/tstraub89/canon-ai.git"
@@ -22,6 +22,7 @@ What are we explicitly NOT doing in this task? This prevents scope creep.
22
22
  Checklist of verifiable outcomes. Each item must be testable.
23
23
 
24
24
  > **For a bug or flake fix:** Include a regression-test AC that fails on the pre-fix code for the stated reason and passes after the fix (red-first). If the mechanism is environment-bound and a faithful repro is impractical, the AC must say so and name the deterministic alternative rather than omitting verification.
25
+ > **For a refactor:** Before any "preserve behavior" AC, run a correctness audit. 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.
25
26
 
26
27
  - [ ] AC-1: ...
27
28
  - [ ] AC-2: ...
@@ -98,3 +99,4 @@ Steps the human should perform to verify the feature works as intended. Written
98
99
  - [ ] Human Test Plan uses product language only (no code, no file names)
99
100
  - [ ] Validation Required has at least one entry marked `- [x]` (not `- [ ]`). `- [ ]` is a placeholder; the spec author flips required checks to `- [x]` before marking spec done. The orchestrator's code_review pre-flight blocks if no `[x]` items are present.
100
101
  - [ ] (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
102
+ - [ ] (Refactors; N/A for features/bug fixes) *Problem* records the 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) stated before any "preserve" AC
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: spec-review-lens
3
+ description: Read-only spec reviewer for the /canon-spec-review skill. Dispatched once per angle (shape, factual, spec-quality) with the spec inline; returns cited findings to the synthesizing session.
4
+ tools: Read, Grep, Glob, Bash
5
+ ---
6
+
7
+ You are one lens in `/canon-spec-review`. The prompt gives you the spec text, your angle, and your rubric.
8
+
9
+ Review the spec yourself and spawn no sub-agents.
10
+ Invoke no skills.
11
+ Write no files. Run no `canon` commands and ask the user nothing.
12
+ Bash is read-only: search, list, print file ranges, and inspect read-only git history. Use no redirects, in-place edits, or state-changing git.
13
+ Inherit the session's model and effort.
14
+
15
+ Every finding cites `file:line` or an AC number. Re-open the current file before making any claim about code; do not rely on memory.
16
+
17
+ Coverage first: report every finding you can back with a citation, each tagged with a severity. Filtering is the synthesizer's job. `[NO FINDINGS]` is a valid return; do not invent findings.
18
+
19
+ **Severity definitions**
20
+ - **BLOCKING:** would cause wrong behavior or a silent bug, or makes an AC unimplementable as written.
21
+ - **NIT:** an implementation detail the implementer resolves by reading the codebase, a minor ambiguity with an obvious default, or a question the plan phase should answer.
22
+ - **STRONG:** nit-grade under those rules, but likely to cost a review round, so cheaper to fix now.
23
+
24
+ Behavior the spec's Non-Goals explicitly exclude and verify as unaffected is a NIT at most. An omitted required change (such as a caller, parser, migration or test surface), a transitive effect of the change, or an internal contradiction between spec sections stays BLOCKING.
25
+
26
+ Return format:
27
+
28
+ ```text
29
+ - [BLOCKING|STRONG|NIT] <one-line finding> — <file:line or AC#>
30
+ <2-3 sentence rationale, citing evidence>
31
+ ```
32
+
33
+ Return exactly `- [NO FINDINGS]` if you find nothing. Add no preamble or closing remarks.
@@ -128,13 +128,13 @@ After scope is approved:
128
128
  canon task new TASK-ID "Title"
129
129
  ```
130
130
 
131
- 2. Edit `tasks/TASK-ID/status.json`: set `task_size`, `delicate`, and `human_spec_gate: true`.
131
+ 2. Set metadata with `canon task set TASK-ID task_size <XS|S|M|L|XL>` and `canon task set TASK-ID delicate <true|false>`. The spec gate defaults to armed for new tasks.
132
132
 
133
133
  3. Write `tasks/TASK-ID/spec.md` using `.canon/templates/spec.md` as structure. Fill every section — no placeholders, no "TBD".
134
134
  - If full-send mode is active, prepend this line immediately after the title block and before `## Problem`:
135
135
  `> **Full-send mode**: This spec was produced in full-send mode.`
136
136
 
137
- 4. Set `spec.status` to `"done"` in `status.json`.
137
+ 4. Run `canon task phase TASK-ID spec done`.
138
138
 
139
139
  Self-check before presenting:
140
140
  - [ ] Every AC states exactly how to verify it (not just "it works")
@@ -146,6 +146,7 @@ Self-check before presenting:
146
146
  - [ ] Non-Goals rules out the most tempting scope expansions — back load-bearing exclusions with a positive scope-bound or grep AC, not prose "NOT" alone
147
147
  - [ ] Symbols named in ACs actually exist in the codebase — grep-verify before presenting
148
148
  - [ ] (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
149
+ - [ ] (Refactors; N/A for features/bug fixes) *Problem* records the 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) stated before any "preserve" AC
149
150
 
150
151
  **Spec-writing rules of thumb** (apply when writing ACs and structure):
151
152
  - **Name effects to DELETE**: frame supersession as replacement ("replace `oldFn` with `newFn`; `oldFn` must not exist after"), not separate add/remove bullets.
@@ -156,6 +157,7 @@ Self-check before presenting:
156
157
  - **Bug and flake-fix specs need a confirmed mechanism and red-first test**: For a bug or flake fix, the spec author must state, in *Problem*, both the confirmed mechanism and how it was confirmed — 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 — 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.
157
158
  - **At ≥3 spec_review iterations, read the rounds' content, not the count.** Label each round *edge-fine-tune* (it narrows an already-identified case — converging, safe to finish once the findings are addressed) or *scope-expansion* (it names a **new** structural case the spec hadn't covered — a real design gap). Round count alone is not the signal; a 6-round run of successive fine-tunes is convergence. 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 — or deleting the mechanism in favor of the layer that already owns it — is usually cheaper than another round of hardening.
158
159
  - **Refactor specs need hard structural caps**: size cap, explicit deletion expectations per symbol, grep AC for disappeared symbols.
160
+ - **Refactor specs need a correctness audit before "preserve behavior"**: 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.
159
161
  - **Codebase-wide term renames: gate per string family, not per enumerated hit.** Hand-enumerated grep lists cause round-over-round scope expansion — each review round surfaces a new label *family*, not a missed instance. Decompose the stale term into its distinct string families up front via a structural sweep, then express each family's post-change invariant as a zero-result grep AC with a word-bounded pattern that catches all separator variants and cannot match the new term. Where the old string is a substring of the new one, zero-result gating is structurally impossible — verify those surfaces with positive targeted ACs instead, and document the asymmetry in the spec so reviewers don't read it as a gap. Two checks before finalizing any zero-result grep AC: (1) on prose surfaces (`docs/`, templates, backlog), read every affected file for *paraphrases* of the retired claim — an exact-string grep proves only that the literal phrase is gone, not that the retired behavior is undocumented everywhere; (2) scan the spec's own other ACs, Docs Impact, and Non-Goals for hits they already tolerate (historical docs, a release-time step, incidental test fixtures) and fold those into an explicit permitted-to-remain bucket inside the same AC, so a later section doesn't quietly contradict the gate.
160
162
 
161
163
  **⛔ STOP — present the spec and wait for approval.**
@@ -182,7 +184,7 @@ Self-check before presenting:
182
184
  canon task phase TASK-ID spec_review done approved
183
185
  canon task phase TASK-ID plan done
184
186
  ```
185
- Then set `human_spec_gate: false` in `status.json` (already cleared by the human's conversational approval).
187
+ Then run `canon task set TASK-ID human_spec_gate false` (already cleared by the human's conversational approval).
186
188
  4. Invoke the pipeline:
187
189
  ```bash
188
190
  canon run TASK-ID
@@ -8,16 +8,16 @@ effort: high
8
8
 
9
9
  # canon-spec-review
10
10
 
11
- Previews what Codex's `spec_review` phase would surface — BEFORE `canon run <id>` — by dispatching three parallel sub-agents at the spec from different angles. Returns one inline report; the human revises spec.md or proceeds.
11
+ Previews what Codex's `spec_review` phase would surface — BEFORE `canon run <id>` — by dispatching three parallel sub-agents at the spec from different angles. Returns one inline report; the human revises the spec or proceeds.
12
12
 
13
13
  ## When to use
14
14
 
15
15
  - Spec is written, `human_spec_gate` not yet cleared
16
- - Task is **full-tier (S/M/L/XL/delicate)** — pre-empts Codex spec_review iterations; saves ~10 min × N rounds + $
16
+ - Task is **full-tier (S/M/L/XL/delicate)** — pre-empts Codex spec_review iterations; saves time and cost
17
17
  - Task is **fast-tier (XS non-delicate)** — this is the *only* automated review layer (fast-tier auto-approves `spec_review`, so without this skill the human's read is the only gate before Codex hits the spec at implement-time)
18
18
  - After a reroute amendment, before re-invoking the pipeline
19
19
 
20
- Don't use for already-committed code review (use `codex review --commit <SHA>` or `codex review --base <branch>` for that), or for genuinely trivial inline patches (typo fixes, version bumps) where there's no logic to vet.
20
+ Don't use for code-diff review (use `/canon-inline-review`), or for XS changes with no logic to vet (typo fixes, version bumps).
21
21
 
22
22
  ## Workflow
23
23
 
@@ -39,65 +39,54 @@ State briefly: task ID, size, delicate flag, "dispatching 3 parallel sub-agents.
39
39
 
40
40
  ### 2. Dispatch 3 sub-agents in one message
41
41
 
42
- Send a SINGLE message with three Agent tool calls so they run concurrently. Each gets a specific scope, the spec content inline, a calibration constraint, and a fixed output schema. **Do not interleave** — single message, three Agent calls.
43
-
44
- Each sub-agent returns findings in this format (no preamble, no closing remarks):
45
-
46
- ```
47
- - [BLOCKING|STRONG|NIT] <one-line finding> — <file:line or AC#>
48
- <2-3 sentence rationale, citing evidence>
49
- ```
50
-
51
- **Calibration applied to every angle**: silence is the default. Only flag issues that would cause real problems during implementation. A real shape concern becomes the lead reason for a `changes_requested` verdict. Don't manufacture findings to look thorough.
42
+ Send a SINGLE message with three Agent tool calls so they run concurrently. Every call uses `subagent_type: spec-review-lens` and carries the full spec text inline, its angle, scope and rubric. Each lens returns cited findings in its own format. If the Agent tool rejects `spec-review-lens`, stop and tell the human to run `canon upgrade`, then start a new Claude Code session; do not fall back to another agent type.
52
43
 
53
44
  #### Agent A — Structural / shape
54
45
 
55
- Subagent type: `general-purpose`. Scope: "Apply the Shape Check rubric from canon's spec_review prompt."
56
-
57
- Goal: answer "is this spec solving the right problem in the right shape?" Four questions: (1) is the problem real, (2) is the framing right, (3) is there a materially simpler solution, (4) is the AC decomposition right (compound ACs, missing ACs, ACs solving symptoms not causes)?
46
+ Scope: review whether the spec solves the right problem in the right shape. Answer four questions: is the problem real, is the framing right, is there a materially simpler solution, and is the AC decomposition right (compound ACs, missing ACs, ACs solving symptoms rather than causes)?
58
47
 
59
48
  Constraints: don't audit factual claims (Agent B's job); don't audit completeness against canon's spec-writing rules (Agent C's job). Stay in shape territory.
60
49
 
61
- Output: the format above. If no shape concerns, return exactly `- [NO FINDINGS]`.
62
-
63
50
  #### Agent B — Factual / ground-truth
64
51
 
65
- Subagent type: `Explore`. Scope: "Verify every factual claim in the spec against the actual codebase."
66
-
67
- Goal: for each named symbol, file path, function, or behavior the spec mentions — does it exist? Is the description correct? Are Affected Files entries real paths? For symbols in ACs (e.g. "extend `parseFooBar` to handle X"): grep for the symbol, read the function signature, verify the spec's assumptions about return shape and call sites.
68
-
69
- Constraints: cite `file:line` for every claim. Don't audit shape (Agent A) or stylistic completeness (Agent C).
52
+ Scope: verify every factual claim in the spec against the actual codebase. For each named symbol, file path, function, or behavior: does it exist, is the description correct, and are Affected Files entries real paths? For symbols in ACs, verify signatures, return shapes, and call sites.
70
53
 
71
- Output: the format above. If everything checks out, return exactly `- [NO FINDINGS]`.
54
+ Read whole functions and call sites, not excerpts, when verifying return shapes. Cite `file:line` for every claim. Don't audit shape (Agent A) or stylistic completeness (Agent C).
72
55
 
73
- This angle catches the highest-value class: "spec assumes X exists in Y but it's actually in Z" — which Codex's spec_review catches eventually but usually after 1-2 iterations.
56
+ This angle catches the highest-value class: a spec that assumes something exists in one place when it is elsewhere, or describes its contract incorrectly.
74
57
 
75
58
  #### Agent C — Spec-quality completeness
76
59
 
77
- Subagent type: `general-purpose`. Scope: "Audit against canon's spec-writing rules of thumb."
78
-
79
- Goal: check these spec-quality rules:
60
+ Scope: audit against canon's spec-writing rules of thumb. Check these ten items:
80
61
  (1) **Name effects to DELETE** — when a change supersedes prior code, is it framed as a single replacement, not separate add/remove bullets?
81
62
  (2) **Prefer positive or structural assertions** — are load-bearing "must not" constraints backed by a grep AC or positive reframe, not just prose negation?
82
- (3) **Affected Files** — files that will *change* are listed; files only read for context are not.
63
+ (3) **Affected Files** — the table sits under a heading that is exactly `### Affected Files`, inside `## Design` or any amendment section (an H2 whose first word is `Amendment`, such as `## Amendment`, `## Amendment Round N` or `## Amendment (full-send scope)`). It lists every file the change writes, including generated mirrors and rebuilt outputs. Files only read for context stay out. A mismatched heading or a missing generated output is BLOCKING, because a pipeline gate rejects it later.
83
64
  (4) **Validation Required** — section present AND has at least one `- [x]` checked entry (or an explicit checked "None — <reason>"). A section with zero `[x]` entries is a failing check.
84
65
  (5) **Non-goals** — rule out the most tempting scope expansions.
85
66
  (6) **Human Test Plan** — product language only; no code, no file paths.
86
67
  (7) **Known Risks** — names actual failure modes for the trickiest ACs.
87
- (8) **Symbols in ACs exist** — for any named function or symbol, has the author grepped for it and verified the return shape matches the spec's assumed data contract?
88
- (9) **Bug/flake-fix evidence** (N/A for features/refactors) — *Problem* states the confirmed mechanism and how it was confirmed, with evidence matching 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 (a throwaway prototype-fix spike that makes the symptom vanish, or a deterministic forced repro). *Acceptance Criteria* includes a red-first regression-test AC or the explicit environment-bound-and-impractical escape with a named deterministic alternative. Missing or under-rung evidence, or a missing red-first AC without the escape, is BLOCKING.
68
+ (8) **Symbols in ACs exist** — for any named function or symbol, has the author verified the return shape matches the spec's assumed data contract?
69
+ (9) **Bug/flake-fix evidence** (N/A for features/refactors) — *Problem* states the confirmed mechanism and how it was confirmed, with evidence matching the mechanism class: a deterministic mechanism may cite a trace with verified trigger values; a runtime-dependent mechanism needs executed confirmation (a throwaway prototype-fix spike or a deterministic forced repro). *Acceptance Criteria* includes a red-first regression-test AC or the explicit environment-bound-and-impractical escape with a named deterministic alternative. Missing or under-rung evidence, or a missing red-first AC without the escape, is BLOCKING.
70
+ (10) **Refactor correctness audit evidence** (N/A for features/bug fixes) — 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. Missing audit evidence is STRONG, never BLOCKING.
71
+
72
+ Constraints: stay structural/completeness. Don't second-guess shape (Agent A) or independently re-verify symbols against the codebase (Agent B's job). Check (8) audits whether the author verified before writing the AC.
73
+
74
+ ### Severity definitions and scope boundary
89
75
 
90
- Constraints: stay structural/completeness. Don't second-guess shape (Agent A) or independently re-verify symbols against the codebase (Agent B's job). Check (8) is a spec-quality audit — did the *author* do the grep + return-shape verification before writing the AC — not a re-run of Agent B's ground-truth pass.
76
+ - **BLOCKING:** would cause wrong behavior or a silent bug, or makes an AC unimplementable as written.
77
+ - **NIT:** an implementation detail the implementer resolves by reading the codebase, a minor ambiguity with an obvious default, or a question the plan phase should answer.
78
+ - **STRONG:** nit-grade under those rules, but likely to cost a review round, so cheaper to fix now.
91
79
 
92
- Output: the format above. If nothing's missing, return exactly `- [NO FINDINGS]`.
80
+ Behavior the spec's Non-Goals explicitly exclude and verify as unaffected is a NIT at most. An omitted required change (such as a caller, parser, migration or test surface), a transitive effect of the change, or an internal contradiction between spec sections stays BLOCKING.
93
81
 
94
82
  ### 3. Synthesize and report
95
83
 
96
- When all three return:
84
+ Silence is the default for what reaches the report. Apply these filters:
97
85
 
98
- 1. **De-dupe**: if two agents flag the same thing, merge — keep the more specific rationale.
99
- 2. **Re-classify**: if Agent A says BLOCKING but Agent B's evidence shows it's actually a minor ambiguity, downgrade. Trust the most evidence-grounded classification.
100
- 3. **Order**: BLOCKING first (numbered), then STRONG, then NIT.
86
+ 1. Drop any finding with no citation (uncited).
87
+ 2. Downgrade any finding inside the scope boundary to NIT (scope-boundary downgrade).
88
+ 3. de-dupe overlapping findings, keeping the more specific rationale.
89
+ 4. Re-classify on the strongest evidence, then order BLOCKING, STRONG, NIT.
101
90
 
102
91
  Print this inline (do NOT write to a file):
103
92
 
@@ -127,22 +116,21 @@ Print this inline (do NOT write to a file):
127
116
  - Worth a quick pass — STRONG items will probably surface in spec_review; cheaper to fix now
128
117
  ```
129
118
 
130
- If an agent returned `[NO FINDINGS]`, say so under its section. Don't pad. If all three returned no findings, the report is two lines: header + "Proceed — three sub-agents found no issues."
119
+ If a lens returned `[NO FINDINGS]`, say so under its section. Don't pad. If all three returned no findings, the report is two lines: header + "Proceed — three sub-agents found no issues."
131
120
 
132
121
  ## Output principles
133
122
 
134
123
  - **Inline report only.** No file writes. No status.json updates. No spec-review.md mutations.
135
124
  - **Operator owns the decision.** This skill surfaces findings; it doesn't revise specs or invoke the pipeline.
136
- - **One example of what this catches** (from canon-ai's own worktree-canonical-task-state spec authoring): a Pass 1 multi-agent run caught a `taskDirFor → resolveTaskCwd` rename that would have produced infinite recursion at every pipeline phase entry, plus 6 other BLOCKING items. Codex's spec_review would have surfaced these across 2-3 iterations (~30 min each); the skill closed them in one ~15-min pass.
137
125
 
138
126
  ## Anti-patterns
139
127
 
140
128
  | Anti-pattern | Why it's wrong | Do instead |
141
129
  |---|---|---|
142
- | Run on truly trivial S patches | Overhead > value for typo fixes, version bumps, single-line fixes with no logic | Skip when there's no logic to vet; use freely on any S spec that has decisions in it |
143
- | Use to revise the spec | The skill doesn't edit files | Read the report, edit spec.md manually |
144
- | Manufacture findings to look thorough | Dilutes signal | Silence is the default — `[NO FINDINGS]` is a real verdict |
145
- | Use for code-diff review | Spec mode only in v1 | Use `codex review --uncommitted` for diff review |
130
+ | Run on XS typo fixes or version bumps | Overhead exceeds value when there is no logic to vet | Skip when there's no logic to vet; use freely on any spec that has decisions in it, XS included |
131
+ | Use to revise the spec | The skill doesn't edit files | Read the report, edit the spec manually |
132
+ | Manufacture findings to look thorough | Dilutes signal | `[NO FINDINGS]` is a real verdict; report only cited findings |
133
+ | Use for code-diff review | Spec mode only | Use `/canon-inline-review` for diff review |
146
134
 
147
135
  ---
148
136
 
@@ -117,7 +117,7 @@ canon task <subcommand> [args]
117
117
  | `status` | `<id>` | Print full `status.json` detail for a task. |
118
118
  | `phase` | `<id> <phase> <status> [verdict]` | Update a task phase and re-derive the top-level `status` pointer. Phases: `spec spec_review plan implement code_review qa human_review`. Status: `pending in_progress done changes_requested blocked`. |
119
119
  | `accept` | `<id...> <phase> [--reason "<text>"] [--force]` | Operator accept supports `implement`, `spec_review`, and `code_review`. For `implement`, it is the escape hatch for already-committed work: it marks implement done and sets `operator_accepted` so post-implement auto-commit is skipped when the recorded HEAD still matches. For `spec_review` and `code_review`, `--reason` is required; accept writes a `sanctioned` verdict for non-advancing review verdicts, preserves already-advancing verdicts in a blocked bundle, records `operator_accepted*`, and appends the audit reason to `notes.md`. Review accept intentionally skips the implement-only diff/handoff guards. |
120
- | `set` | `<id> <field> <value>` | Set flat task metadata with validation. Metadata fields `title`, `task_size`, and `delicate` remain settable whenever the task is reachable. Topology fields `worktree` and `base_branch` are only settable before `status.branch` is recorded; once branched, `set` rejects them and points operators at recreating the task or migrating `status.json` manually. Redirected: `full_send` → `canon run --full-send`; `human_spec_gate` → re-run `canon run`; `status` → `canon task phase`; `branch` → git identity, not `canon task set`; `phases`/`sessions`/`canon`/`escalations` → their owning canon task commands. Immutable: `id`, `created`, `updated`, and `_`-prefixed inline-doc keys. Writes warn after dispatch has started because metadata changes take effect on the next `canon run`. |
120
+ | `set` | `<id> <field> <value>` | Set flat task metadata with validation. Metadata fields `title`, `task_size`, `delicate`, and `human_spec_gate` remain settable whenever the task is reachable; setting `human_spec_gate true` clears `full_send` with a note, while setting it `false` is refused on delicate tasks (use `canon run --full-send --force`). Topology fields `worktree` and `base_branch` are only settable before `status.branch` is recorded; once branched, `set` rejects them and points operators at recreating the task or migrating `status.json` manually. Redirected: `full_send` → `canon run --full-send` to enable it (it persists until `--reroute` clears it or arming the spec gate does); `status` → `canon task phase`; `branch` → git identity, not `canon task set`; `phases`/`sessions`/`canon`/`escalations` → their owning canon task commands. Immutable: `id`, `created`, `updated`, and `_`-prefixed inline-doc keys. Writes warn after dispatch has started because metadata changes take effect on the next `canon run`. |
121
121
  | `reset-spec-review` | `<id>` | Clear router-relevant state for a fresh spec-review pass after an auto-block. Zeroes iterations, clears verdict, archives the prior `spec-review.md`, and drops the stored `sessions.claude_spec` ID so the next pass runs against a fresh Claude spec session. |
122
122
  | `reset-code-review` | `<id>` | Clear router-relevant state for a fresh code-review pass after an auto-block. Zeroes the loop-local review counters, clears verdict, archives the prior `review.md` and re-scaffolds a fresh one from the task templates, and drops the stored `sessions.claude_review` ID so the next pass runs against a fresh Claude review session. |
123
123
  | `post-merge-sync` | `[<branch>]` | After a squash-merge PR, reconcile local branch with origin. Hard-resets if the only divergence is pipeline telemetry; refuses if real new work exists. |
@@ -185,7 +185,7 @@ Set in `status.json` at task creation:
185
185
  |---|---|---|
186
186
  | `task_size` | `XS \| S \| M \| L \| XL` | Drives the pipeline tier and effective-size model/effort cells for Codex and Claude. XS is fast-tier; S+ runs the full pipeline (including Codex spec review). |
187
187
  | `delicate` | `true \| false` | Forces the XL bucket (full Codex model and high Claude spec/plan/code-review effort) regardless of nominal size. Set when an undetected bug has materially harder-to-recover blast radius than a normal bug — common examples: auth, payments, premium gating, persistent storage migrations, security-sensitive cryptography. Project-specific surfaces also qualify (medical PHI, scientific reproducibility, regulated data). The bar is *blast radius*, not difficulty. |
188
- | `human_spec_gate` | `true \| false` | **Single-use latch**, not a persistent toggle. `true` arms a one-time halt after `spec_review`, before planning (default: `true`). The orchestrator flips it to `false` *at the moment it halts* — so `false` means "the gate already fired (or was pre-cleared)," not "review was skipped." See [Spec gate is a single-use latch](#spec-gate-is-a-single-use-latch). |
188
+ | `human_spec_gate` | `true \| false` | **Single-use latch**, not a persistent toggle. `true` arms a one-time halt after `spec_review`, before planning (default: `true`). A human can re-arm it with `canon task set <id> human_spec_gate true`; setting it `false` on a delicate task is refused. The orchestrator flips it to `false` *at the moment it halts*, but a human can also disarm it, so `false` means "fired, pre-cleared, or disarmed," not proof the gate fired. See [Spec gate is a single-use latch](#spec-gate-is-a-single-use-latch). |
189
189
  | `worktree` | `true \| false` | Worktree isolation. `canon task new` scaffolds this to `true`, so worktree mode is the effective default for every scaffolded task — set it to `false` to opt out and run in the main checkout. The orchestrator treats an *absent* field as `false`, but that fallback only applies to hand-rolled `status.json` files. See Worktree Isolation below. |
190
190
  | `base_branch` | string (default `"main"`) | Branch the task branches off and PRs against. Auto-set by `canon task new` from the current git checkout at task creation. |
191
191
 
@@ -356,7 +356,7 @@ For projects that version their releases, changelog and version bump remain a ma
356
356
 
357
357
  `human_spec_gate` confuses operators because it looks like a persistent on/off setting and isn't. It is a **one-shot latch the orchestrator consumes the first time the pipeline reaches the gate.**
358
358
 
359
- The flip to `false` *is the gate firing* — it happens at the halt, **before** the human has reviewed, not after. Concretely, when the pipeline reaches the gate point with the latch armed, the orchestrator does three things in one step: (1) sets `human_spec_gate = false`, (2) prints the `✋ SPEC GATE` banner, (3) `process.exit(0)`. The reason it burns the latch *at* the halt is so the **next** `canon run <id>` — the one the human types after approving the spec — passes straight through instead of halting on the same gate forever.
359
+ When the pipeline reaches the gate point with the latch armed, the orchestrator does three things in one step: (1) sets `human_spec_gate = false`, (2) prints the `✋ SPEC GATE` banner, (3) `process.exit(0)`. A human can also re-arm or disarm the latch with `canon task set`; disarming it on a delicate task is refused. The orchestrator consumes the latch at the halt so the **next** `canon run <id>` — the one the human types after approving the spec — passes straight through instead of halting on the same gate forever.
360
360
 
361
361
  Where the gate fires by tier:
362
362
 
@@ -364,7 +364,9 @@ Where the gate fires by tier:
364
364
  - **Full tier (S/M/L/XL/delicate)**: after Codex `spec_review` completes, before planning.
365
365
  - **`--full-send`**: pre-clears the latch to `false` at launch, so the gate never fires.
366
366
 
367
- So when you inspect `status.json` mid-pipeline and see `human_spec_gate: false`, read it as **"the gate already fired (or was pre-cleared) — review was *not* skipped"**, not "the halt was lost." There is no separate "gate satisfied" field; the consumed latch *is* the record that the gate fired. Re-running `canon run <id>` after the banner is the intended next move — it does not re-arm or re-fire the gate.
367
+ On a full-tier reroute, an armed latch halts after the amendment's `spec_review`, unless that `spec_review` is closed with `canon task accept`, which is itself the human sign-off and skips the gate. A fast-tier reroute re-enters at `implement`, so it never reaches the gate. A reroute does not re-arm a consumed latch by itself; to halt after the amendment's review, run `canon task set <id> human_spec_gate true` before rerouting.
368
+
369
+ So when you inspect `status.json` mid-pipeline and see `human_spec_gate: false`, read it as **"fired, pre-cleared, or disarmed"**; the value alone does not prove the gate fired or that review was not skipped. Re-running `canon run <id>` after the banner proceeds past the consumed latch.
368
370
 
369
371
  **Bundle rule**: the gate skip is all-or-nothing. One task in the bundle with the latch still armed re-engages the halt for the whole invocation; `--full-send` must apply to every task in the bundle to skip it.
370
372
 
@@ -470,7 +472,7 @@ The Stage 1 AC table is redone on round 2+. Earlier AC tables were snapshots of
470
472
 
471
473
  Write the new requirements into **`tasks/<id>/spec.md` in the active task directory** for every named task as an Amendment section — except on a spec_gap-entry reroute (every named task blocked at `code_review` with at least one `spec_gap` verdict), where only the `spec_gap` tasks need an Amendment section; approved or other non-gap siblings in the same bundle do not. If a worktree exists for the task, edit the worktree copy; edit REPO_ROOT only before the task has a worktree. `review.md` alone is not sufficient — Codex reads `spec.md` as the contract.
472
474
 
473
- Full-tier reroute (any S/M/L/XL task or any `delicate` task) re-enters at the same review altitude as the original spec — `spec_review` → `plan` → `implement` — regardless of which admitted phase (`code_review`, `qa`, or `human_review`) the task was rerouted from. Codex reviews the amendment in the context of the previously approved ACs and prior `spec-review.md`, without auditing `handoff.md`, `review.md`, or `done.md`. If the amendment is approved, the pipeline flows through to `plan` without re-arming the human spec gate; Claude appends a reroute plan section (`## Reroute Plan` or `## Reroute Plan Round N`) to `plan.md`; Codex then implements from the amendment plus that reroute plan.
475
+ Full-tier reroute (any S/M/L/XL task or any `delicate` task) re-enters at the same review altitude as the original spec — `spec_review` → `plan` → `implement` — regardless of which admitted phase (`code_review`, `qa`, or `human_review`) the task was rerouted from. Codex reviews the amendment in the context of the previously approved ACs and prior `spec-review.md`, without auditing `handoff.md`, `review.md`, or `done.md`. If the amendment is approved, the pipeline flows through to `plan` unless the operator re-armed the human spec gate first, in which case it halts at the gate. Once past `spec_review`, Claude appends a reroute plan section (`## Reroute Plan` or `## Reroute Plan Round N`) to `plan.md`; Codex then implements from the amendment plus that reroute plan.
474
476
 
475
477
  Rerouting from `code_review` or a `qa` `pending`/`in_progress` entry happens before the QA-end commit, so task artifacts may still be uncommitted in the worktree. `--reroute` performs no git operation itself, so nothing is destroyed by the reroute. Do not run a manual git command that can discard uncommitted state (`reset --hard`, `checkout HEAD~N`, `stash drop`, or equivalent) in that window.
476
478