canon-ai 3.3.0 → 3.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canon-ai",
3
- "version": "3.3.0",
3
+ "version": "3.4.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": {
@@ -12,7 +12,7 @@
12
12
  >
13
13
  > **Deleting a file?** In this table use the `[path/to/file.ext](path/to/file.ext)` markdown-link form — **not** backticks and **not** bare prose. Backticks trip `docs-refs-check` (a backtick path-ref to a now-missing path under a `validDirs` dir reads as broken); bare prose fails this table's path parse (the first column must be a backtick-path or a markdown-link). The markdown-link is the one form that satisfies both.
14
14
 
15
- > **Never backtick a bare directory path** anywhere in this file (e.g. write "the app source tree (apps/app/src)", not `` `apps/app/src` ``). `docs-refs-check` treats a backticked path as a file reference and aborts the pipeline's auto-commit with "missing file" when it is a directory. Backtick only real files.
15
+ > **Directory paths:** backticking a path to an existing directory is fine — `docs-refs-check` accepts it. A backticked directory path that no longer exists (for example, one this task deleted) can be reported as a missing reference, so describe removed directories in prose or use the markdown-link form above.
16
16
 
17
17
  | File | What Changed |
18
18
  |---|---|
@@ -8,6 +8,8 @@ Code review is synthesized by a foreman from three lenses: an anchored Claude le
8
8
 
9
9
  The anchored review runs in two stages on the first round. **Stage 1 is a gate.** If it fails, skip Stage 2 entirely and send back — do not write code-quality findings against code that's about to change.
10
10
 
11
+ **Scope:** Full — base `<baseBranch>` — reason: Round 1 (initial review)
12
+
11
13
  ## Stage 1 — Spec Compliance (gate)
12
14
 
13
15
  ### Validation Gate
@@ -99,6 +101,8 @@ breaks routing. Administrative appends use a non-Round heading (e.g.
99
101
 
100
102
  ## Round N — verifying iteration N-1's response to round N-1
101
103
 
104
+ **Scope:** Full | Delta — base `<baseBranch or prevSHA>` — reason: <trigger reason, or "delta">
105
+
102
106
  ### Stage 1 — Acceptance Criteria Re-Check
103
107
 
104
108
  Re-fill this table with every AC from spec.md against the latest code. Earlier AC tables were snapshots of earlier iterations, not reusable proof. ACs whose relevant code paths did not change may be marked `Met (unchanged from round N-1)` with a one-line evidence pointer.
@@ -25,6 +25,8 @@ Apply canon's existing code-review charter to the diff, spec, handoff, and prior
25
25
 
26
26
  Run Stage 2 only if Stage 1 passed.
27
27
 
28
+ **Project conventions — do this first.** Check whether `docs/patterns.md` exists. If it does, read it before reviewing: use its Trigger Table (or skim the file if it has none) to find the sections relevant to the files the diff changes, skipping unfilled placeholder content marked `TODO[canon]` and `_example_` rows. Check the changed code against those rules. Report each violation as a `risk/guardrail` finding that names the rule it breaks (or as a `correctness bug` when it causes incorrect behavior). A violation of a written project rule is not a nit. Apply this check in every round, limited to code changed by the diff.
29
+
28
30
  Find correctness bugs, risk/guardrail issues, optional cleanup/nits, and spec gaps. A changed test that passes against broken behavior is a correctness bug. Reference findings by file:line and AC number where applicable.
29
31
 
30
32
  Report every issue you find, including ones you are uncertain about or consider low-severity. Do not filter for importance or confidence here — the foreman does that downstream. Tag each Stage 2 finding with both a severity and a confidence so the foreman can rank and filter. Coverage is your job; filtering is not.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: code-review-cold
3
- description: Spec-blind adversarial code reviewer for canon code_review. Reviews only the diff and base ref, then returns structured findings to the foreman.
3
+ description: Spec-blind adversarial code reviewer for canon code_review. Receives the diff and base ref, searches code for sibling guards when needed, then returns findings to the foreman.
4
4
  ---
5
5
 
6
6
  You are the cold-Claude code-review lens in canon's three-lens review pipeline. You receive a code diff and base ref only: no spec, no acceptance criteria, no handoff rationale, and no canon docs.
@@ -15,9 +15,11 @@ Review adversarially for bugs the diff introduces: race conditions, lifecycle is
15
15
 
16
16
  **Diff-local pattern**: when you see the same safety check, guard, or invariant applied at multiple call sites but a new call site introduced by this diff is missing it, flag it — an inconsistently applied guard is a correctness gap regardless of intent.
17
17
 
18
+ **Sibling-site sweep**: when the diff adds or changes a guard, check, or invariant, search the repository for other sites that need the same treatment and report any missing site. Keep that search spec-blind; do not read task artifacts or canon docs.
19
+
18
20
  Report every issue you find, including ones you are uncertain about or consider low-severity. Do not filter for importance or confidence here — the foreman does that downstream. Tag each finding with both a confidence and a severity so the foreman can rank and filter. Coverage is your job; filtering is not.
19
21
 
20
- If the visible diff is truncated and you need more context, inspect only changed files or run a diff against the provided base ref. Do not read `spec.md`, `handoff.md`, `review.md`, canon docs, task notes, or acceptance criteria.
22
+ If the visible diff is truncated and you need more context, inspect changed files or run a diff against the provided base ref. The sibling-site sweep may search other code files. Do not read `spec.md`, `handoff.md`, `review.md`, canon docs, task notes, or acceptance criteria.
21
23
 
22
24
  ## Return Format
23
25
 
@@ -328,7 +328,9 @@ Non-managed Affected Files entries (source files, test files, fixtures) do not e
328
328
 
329
329
  When a managed doc is committed via the Affected Files allow-list, an advisory warning fires per file inviting the operator to `git diff HEAD -- <path>` to verify the content before `--ship` (residual guard against same-file sibling-pipeline overlap).
330
330
 
331
- Before that dirty-tree auto-commit path runs, the orchestrator also runs remote-boundary gates. First, the base-divergence check fetches `origin/<base>` and lists commits on local `<base>` that are not yet on `origin/<base>`. Those commits block `--push`, `--pr`, and `--ship` because they will collide when the base is pulled; bypass only this commit-divergence gate with `--allow-divergent-base`. Second, for `--pr` and `--push`, the base-drift check fetches `origin/<base>` and compares `origin/<base>` to `HEAD` with a two-dot tree diff (`git diff origin/<base> HEAD --name-status -M -z`). Any diff path outside the spec's `### Affected Files`, `tasks/<id>/`, `PIPELINE_TELEMETRY_FILES`, and — once `qa.status === 'done'` — any `PIPELINE_MANAGED_DOCS` entry is drift and aborts the run. This catches cross-pipeline contamination Mode 1 (the base branch advanced while the task branch was running) and wider Mode 2 (foreign content was already committed to the task branch before `--pr`). It is complementary to the dirty-tree gate: the dirty-tree gate stops bad content from being committed at `human_review`, while base-drift stops already-committed branch content from being pushed or PR'd.
331
+ Before that dirty-tree auto-commit path runs, the orchestrator also runs remote-boundary gates. First, the base-divergence check fetches `origin/<base>` and lists commits on local `<base>` that are not yet on `origin/<base>`. Those commits block `--push`, `--pr`, and `--ship` because they will collide when the base is pulled; bypass only this commit-divergence gate with `--allow-divergent-base`. Second, for `--pr` and `--push`, the base-drift check fetches `origin/<base>` and compares `origin/<base>` to `HEAD` with a two-dot tree diff (`git diff origin/<base> HEAD --name-status -M -z`). Any diff path outside the spec's `### Affected Files`, `tasks/<id>/`, `PIPELINE_TELEMETRY_FILES`, and — once `qa.status === 'done'` — any `PIPELINE_MANAGED_DOCS` entry is drift and aborts the run. The abort message distinguishes files changed only by base advancement (merge or rebase the base into the task branch) from task-changed files outside Affected Files. Both lists appear when both causes exist. This catches cross-pipeline contamination Mode 1 (the base branch advanced while the task branch was running) and wider Mode 2 (foreign content was already committed to the task branch before `--pr`). It is complementary to the dirty-tree gate: the dirty-tree gate stops bad content from being committed at `human_review`, while base-drift stops already-committed branch content from being pushed or PR'd.
332
+
333
+ At `code_review` entry, a pre-flight checks the task's own rename-aware three-dot diff against the same Affected Files allowlist, always admitting pipeline-managed docs that QA may update. In a normal run, an out-of-scope file blocks before either review agent runs and does not consume review-loop budget. Add the file to the affected task spec's `### Affected Files` table in the active checkout, then rerun `canon run <id>` (pass every bundle ID for a bundle); or reroute with a note to remove the file. In full-send mode, when every bundle member has `full_send: true`, the foreman judges each file as a scope expansion, spec miss, or change to remove. The first two outcomes require an `## Amendment (full-send scope)` section with an Affected Files row and reason (that heading feeds the Affected Files allowlist but never satisfies the human reroute-amendment gate); the last requires `changes_requested`. The foreman records each judgment in `review.md`, and an unjudged file auto-blocks before QA, including when a review finishes during recovery. Amend the spec and rerun `canon run <id>` to resume an unjudged-file block.
332
334
 
333
335
  For renames, both the old path and the new path must appear in the spec's `### Affected Files` table. The two-dot diff uses rename detection and surfaces both sides; listing only one side leaves the other as drift. `--force` bypasses detected drift with a loud warning after the operator has verified it, but `--force` does not bypass diff-computation failure. `--force` and `--allow-divergent-base` are independent bypasses for distinct gates; pass both only when both the file-allow-list drift and the commit-divergence are intentional.
334
336
 
@@ -412,15 +414,17 @@ PIPELINE_STALL_TIMEOUT_MS=1800000 canon run <id>
412
414
 
413
415
  ## Code Review Diff Injection
414
416
 
415
- Code review starts with a cold-Codex diff review run by the orchestrator in the active worktree: `codex exec review --json -c model_reasoning_effort=<effort> --base <baseBranch> -m <miniModel>`, where canon resolves `<miniModel>` and `<effort>` through the `code_review` policy row (mini / `high` at every size). The captured findings are written verbatim to `tasks/<id>/review-cold-codex.md` for every task in the invocation. Bundles run this once over the combined branch diff, and the same findings reach every member.
417
+ Code review starts with a cold-Codex diff review run by the orchestrator in the active worktree: `codex exec review --json -c model_reasoning_effort=<effort> --base <reviewBase> -m <miniModel>`, where canon resolves `<miniModel>` and `<effort>` through the `code_review` policy row (mini / `high` at every size). The captured findings are written verbatim to `tasks/<id>/review-cold-codex.md` for every task in the invocation. Bundles run this once over the combined branch diff, and the same findings reach every member.
418
+
419
+ The review base is the task's base branch for Round 1, effective XL or delicate tasks, an absent or unusable previous-round archive, a delta touching a path outside the previous change set, or a delta over 400 added and deleted lines. Otherwise the base is the reviewed SHA in the newest archive for round N−1. Both path sets include both sides of renames; task artifacts and telemetry do not count toward the path or line triggers. Missing, unresolvable, non-ancestor, or HEAD-equal previous SHAs force a full review, as does a failed delta path or line-stat probe. Every successful cold-Codex invocation adds `tasks/<id>/review-cold-codex-run-N.md` with a header recording round, reviewed SHA, scope, base, and reason; the unnumbered file retains only the latest verbatim findings.
416
420
 
417
421
  If the cold-Codex review cannot be obtained (no captured findings output, spawn error, stall, or signal), `code_review` stops before any Claude session. Re-run `canon run <id>` when Codex is available; there is no two-Claude-lens fallback. Successful runs emit one duration line in the run log: `→ cold-codex review (<taskIds>): <n>s`.
418
422
 
419
- After that, the code-review foreman prompt includes a scoped diff: `git diff <baseBranch>...HEAD` run in the active worktree (three-dot, so only commits on the task branch are included, not unrelated divergence on `baseBranch`). The diff is capped at 50,000 bytes; if truncated, the prompt notes it. The foreman receives the cold-Codex findings as the pre-obtained third lens input, gives the anchored Claude lens the diff plus spec/handoff context, and gives the cold-Claude lens the diff without spec/AC/canon context. The Codex `--base` review and the Claude scoped diff are both over the task branch versus `<baseBranch>` range.
423
+ After that, the foreman receives the cold-Codex findings as the pre-obtained third lens input. On a full round, the prompt includes `git diff <baseBranch>...HEAD` from the active worktree, capped at 50,000 bytes. On a delta round, it includes `<prevSHA>..HEAD` for cold-Claude and explicitly tells anchored Claude to retrieve the full task diff by command. If the delta diff cannot be injected, the foreman is told to retrieve it by command before spawning cold-Claude. Anchored Claude also receives spec/handoff/prior review context; cold-Claude remains spec-blind. The prompt states the scope, base, and reason and tells the foreman to record them in `review.md`.
420
424
 
421
425
  ## Per-Iteration Prompt Slimming
422
426
 
423
- Round 2+ of code review and implement do not re-inject the full task framing. Resumed sessions already have spec/plan/repo conventions in context; round-1 findings are durable in the artifacts; the round-2+ prompts target only the delta. Code-review re-obtains cold-Codex findings and re-runs the Claude lenses from scratch on every round before the foreman synthesizes the new verdict.
427
+ Round 2+ of code review and implement do not re-inject the full task framing. Resumed sessions already have spec/plan/repo conventions in context, and round-1 findings are durable in the artifacts. Code review selects a full or delta scope as described above, then re-obtains cold-Codex findings and re-runs the Claude lenses from scratch before the foreman synthesizes the new verdict.
424
428
 
425
429
  **Cumulative artifacts.** `handoff.md` and `review.md` grow by section per round:
426
430
  - Round 1 fills the existing template structure.
@@ -429,23 +433,7 @@ Round 2+ of code review and implement do not re-inject the full task framing. Re
429
433
 
430
434
  The append-don't-rewrite convention is enforced both in the prompts and via a comment block at the bottom of each template showing the expected shape.
431
435
 
432
- **Slim resumed-session prompts.** Round 2+ prompts for both code review and implement are tight. Code review's slim shape:
433
-
434
- ```
435
- [REVIEW ROUND N — verifying iteration N-1's response to round N-1 findings]
436
-
437
- Codex appended `## Iteration N-1` to handoff.md addressing your prior round's findings.
438
- [Resumed session: framing in context. Cold start: re-read spec.md and earlier review.md sections.]
439
-
440
- Tasks to re-review: <one-line per task pointing at the specific section>
441
-
442
- For each task:
443
- 1. Read `## Iteration N-1` of handoff.md
444
- 2. Read git diff since prior review
445
- 3. Re-fill the Stage 1 AC table with every AC from spec.md against the latest code
446
- 4. Verify each prior finding addressed, cross-referencing the refreshed AC table; flag NEW issues
447
- 5. APPEND `## Round N` to review.md
448
- ```
436
+ **Resumed-session prompts.** The code-review foreman prompt renders the round number and prior handoff iteration, then asks the foreman to spawn both Claude lenses and synthesize their findings with newly obtained cold-Codex findings. Delta rounds inject only the capped fix diff for cold-Claude and direct anchored Claude to retrieve the full task diff by command. Full rounds inject the capped full task diff as before. The foreman re-fills the AC table and appends `## Round N` to `review.md`; implement revisions point at the prior round's findings.
449
437
 
450
438
  The Stage 1 AC table is redone on round 2+. Earlier AC tables were snapshots of earlier code states; a revision can fix a cited finding while regressing an unrelated AC. Every AC appears every round. ACs whose relevant code paths did not change may be marked `Met (unchanged from round N-1)` with a one-line evidence pointer rather than re-derived evidence. Implement-revision prompts are composable: code-review reroutes point at the new `## Round N-1` of `review.md`.
451
439
 
@@ -483,7 +483,7 @@ function stripLineCitation(target) {
483
483
 
484
484
  // `templates/` markdown is intentionally scanned — those files ship to
485
485
  // adopters via `canon upgrade`, so broken refs there would propagate
486
- // silently. Three exempt classes, each by named purpose (not by filename
486
+ // silently. Exempt classes, each by named purpose (not by filename
487
487
  // suffix alone):
488
488
  //
489
489
  // 1. `docs/BACKLOG.md` — deliberate forward-refs to unbuilt work.
@@ -517,7 +517,8 @@ function isNoisySourceFile(relPath, skipPaths = []) {
517
517
  return (
518
518
  relPath === 'docs/BACKLOG.md' ||
519
519
  /(?:^|\/)templates\/(?:.*\/)?(spec|plan|notes|spec-review)\.md$/.test(relPath) ||
520
- /^tasks\/[^/]+\/(spec|plan|notes|spec-review)\.md$/.test(relPath)
520
+ /^tasks\/[^/]+\/(spec|plan|notes|spec-review)\.md$/.test(relPath) ||
521
+ /^tasks\/[^/]+\/review-cold-codex-run-\d+\.md$/.test(relPath)
521
522
  );
522
523
  }
523
524
 
@@ -769,7 +770,9 @@ function findBrokenRefs(repoRoot, options = {}) {
769
770
  if (gitIgnoredTargets.has(target)) continue;
770
771
 
771
772
  const targetPath = resolveRepoRelative(repoRoot, target);
772
- if (!fs.existsSync(targetPath) || !fs.statSync(targetPath).isFile()) {
773
+ const targetExists = fs.existsSync(targetPath)
774
+ && (fs.statSync(targetPath).isFile() || fs.statSync(targetPath).isDirectory());
775
+ if (!targetExists) {
773
776
  addFinding(sourceFile, lineNumber, refText, 'missing file');
774
777
  }
775
778
  }