canon-ai 3.1.2 → 3.1.3

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,16 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [3.1.3] — 2026-09-08
8
+
9
+ ### Added
10
+
11
+ - **Code review and handoff guidance now cover three adopter-hit traps.** The anchored code-review lens checks sibling call sites sharing a fixed cross-cutting invariant before closing a round; the handoff template warns against backticking bare directory paths and against citing in-diff files in an out-of-diff `Fail` row's notes.
12
+
13
+ ### Changed
14
+
15
+ - **Spec-authoring guidance now calls out ACs that pin exact operation counts across an unrelated async subsystem's own activity**, extending the existing "behavioral contracts, not mechanics" rule of thumb.
16
+
7
17
  ## [3.1.2] — 2026-09-08
8
18
 
9
19
  ### Fixed
package/dist/cli/index.js CHANGED
@@ -1245,7 +1245,7 @@ function checkTemplates(cwd) {
1245
1245
  }
1246
1246
  function checkCanonVersion(cwd) {
1247
1247
  const versionPath = join(cwd, ".canon", "version");
1248
- const installedVersion = "3.1.2";
1248
+ const installedVersion = "3.1.3";
1249
1249
  if (!existsSync(versionPath)) {
1250
1250
  return { label: ".canon/version", status: "warn", detail: "missing \u2014 run `canon upgrade`" };
1251
1251
  }
@@ -1742,7 +1742,7 @@ function initCmd(_args) {
1742
1742
  }
1743
1743
  function writeCanonVersion(cwd) {
1744
1744
  const versionPath = join2(cwd, ".canon", "version");
1745
- const version = "3.1.2";
1745
+ const version = "3.1.3";
1746
1746
  mkdirSync(dirname2(versionPath), { recursive: true });
1747
1747
  writeFileSync(versionPath, version + "\n");
1748
1748
  }
@@ -2751,7 +2751,7 @@ function isInstalledSourcePath(sourcePath) {
2751
2751
  return sourcePath.includes("/node_modules/") || sourcePath.includes("\\node_modules\\") || sourcePath.includes("/_npx/") || sourcePath.includes("\\_npx\\");
2752
2752
  }
2753
2753
  function resolveCanonVersion(explicit) {
2754
- return explicit ?? "3.1.2" ?? "dev";
2754
+ return explicit ?? "3.1.3" ?? "dev";
2755
2755
  }
2756
2756
  function resolveOrchestratorCommit(repoRoot, upstreamCommit, runGitAt) {
2757
2757
  const ownToplevel = captureGitOutput(repoRoot, ["rev-parse", "--show-toplevel"], runGitAt);
@@ -4877,7 +4877,7 @@ function currentPinFromManifest(manifest) {
4877
4877
  return "unknown";
4878
4878
  }
4879
4879
  function bakedVersion() {
4880
- return "3.1.2";
4880
+ return "3.1.3";
4881
4881
  }
4882
4882
  function ownPackageName(pkgDir) {
4883
4883
  try {
@@ -5351,7 +5351,7 @@ function runUpgrade(cwd, pkgDir, options = {}) {
5351
5351
  cutoverWarnings.push(docsRefsCheckRel);
5352
5352
  }
5353
5353
  const versionPath = join5(cwd, ".canon", "version");
5354
- const newVersion = "3.1.2";
5354
+ const newVersion = "3.1.3";
5355
5355
  const currentVersion = existsSync5(versionPath) ? readFileSync4(versionPath, "utf8").trim() : null;
5356
5356
  if (currentVersion !== newVersion) {
5357
5357
  pending.push({ rel: ".canon/version", projectPath: versionPath, content: newVersion + "\n" });
@@ -5659,7 +5659,7 @@ Global:
5659
5659
  `);
5660
5660
  }
5661
5661
  function printVersion() {
5662
- console.log("3.1.2");
5662
+ console.log("3.1.3");
5663
5663
  }
5664
5664
  switch (command) {
5665
5665
  case "doctor":
@@ -4961,7 +4961,7 @@ function isInstalledSourcePath(sourcePath) {
4961
4961
  return sourcePath.includes("/node_modules/") || sourcePath.includes("\\node_modules\\") || sourcePath.includes("/_npx/") || sourcePath.includes("\\_npx\\");
4962
4962
  }
4963
4963
  function resolveCanonVersion(explicit) {
4964
- return explicit ?? "3.1.2" ?? "dev";
4964
+ return explicit ?? "3.1.3" ?? "dev";
4965
4965
  }
4966
4966
  function resolveOrchestratorCommit(repoRoot, upstreamCommit, runGitAt) {
4967
4967
  const ownToplevel = captureGitOutput(repoRoot, ["rev-parse", "--show-toplevel"], runGitAt);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canon-ai",
3
- "version": "3.1.2",
3
+ "version": "3.1.3",
4
4
  "type": "module",
5
5
  "description": "A spec-first, multi-agent coding pipeline you drop into any repo. The law your agents work under.",
6
6
  "engines": {
@@ -12,6 +12,8 @@
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.
16
+
15
17
  | File | What Changed |
16
18
  |---|---|
17
19
 
@@ -71,6 +73,7 @@ Cross-reference each Acceptance Criterion from spec.md and confirm it is met. AC
71
73
  > | `deferred_by_spec` | Explicitly out of scope per spec. Requires a spec citation in Notes (e.g., `Spec: §Non-Goals — explicitly defers this`). |
72
74
  > | `blocked` | Check would have run but infrastructure was unavailable (CI down, network out). Triage required — distinct from `Fail`. |
73
75
  >
76
+ > A `Fail` row whose cause lies outside this task's diff: name the result `Fail – unrelated` explicitly, and Notes must cite a specific file reference outside this task's affected files (a sibling worktree path, a fixed-port test's own file, an unrelated spec's path) — the code reviewer only accepts `Fail – unrelated` when Notes names such a reference credibly. Pre-flight's own check is textual — naming a changed file in that row, even to say it passed, can reclassify the whole row as task-owned and reject the handoff. Don't rely on an unqualified filename escaping the check; keep Notes free of any path from this task's diff.
74
77
  > Record every check in spec.md's Validation Required section here, plus any extra checks you ran. Required checks should not be marked `N/A` or `not_configured` — run the check or adjust the spec; the code reviewer verifies coverage against the spec. The `Check` cell is for human readability (the pre-flight gate no longer string-matches it against the spec), so write whatever names the check clearly — but keep a check's label identical across a baseline row and any later `### Re-run validation` row so its result updates in place.
75
78
 
76
79
  | Check | Result | Notes |
@@ -34,6 +34,7 @@ Report every issue you find, including ones you are uncertain about or consider
34
34
  - **Verify handoff claims by running `git diff HEAD -- <file>`**: the auto-commit step can silently drop edits not in the handoff Changes table — don't trust the handoff claim; diff the working tree to confirm fixes landed.
35
35
  - **Delicate-task review must audit cross-cutting guards at every mutation entry point**: when a `delicate: true` task refactors a state/data layer, explicitly verify that auth, gating, and payment guards still hold at every mutation chokepoint — not just at the call sites the spec called out.
36
36
  - **Don't infer one git invariant from another**: `git status --porcelain` empty ≠ origin matches HEAD; `origin/<branch>` exists ≠ origin matches HEAD. Do the actual check directly, not a proxy.
37
+ - **A cross-cutting invariant fix isn't verified until every sibling call site with the same shape has been checked**: a captured reference that must be revalidated, or a guard that must run, because an intervening `await` can invalidate it, is the kind of defect that recurs wherever the same shape appears. Once a round establishes the violation is real at one call site, a byte-identical-to-base sibling branch elsewhere in the same file is not "out of scope" — it shares the same shape. Grep the file for every other site matching the pattern before accepting the fix and closing the round.
37
38
 
38
39
  ## Return Format
39
40
 
@@ -152,7 +152,7 @@ Self-check before presenting:
152
152
  - **Prefer positive or structural assertions** over prose negations for load-bearing constraints. Back a "must not" with a grep AC or positive reframe.
153
153
  - **Symbols in ACs must exist** — grep for every named function or symbol; verify return shape matches the spec's assumed data contract.
154
154
  - **Enumerate every caller and classify its execution context before asserting how a mechanism works.** "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 (including skills and docs, not just source) and classify each site as in-agent-session or orchestrator-context before the spec asserts anything about it.
155
- - **Behavioral contracts, not mechanics** — ACs describe observable behavior; defer implementation mechanics 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.
155
+ - **Behavioral contracts, not mechanics** — ACs describe observable behavior; defer implementation mechanics 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. The recurring form of this trap is an AC that pins exact operation counts over a fixture in which an unchanged async subsystem (a generation queue, a worker pool) performs its own reads inside the measurement window — an adopter spec took five review rounds on one such AC because each revision worded the same brittle count more precisely instead of removing the dependency; make the side effects inert or assert the discriminating deltas, do not itemize the subsystem's arithmetic.
156
156
  - **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
157
  - **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
158
  - **Refactor specs need hard structural caps**: size cap, explicit deletion expectations per symbol, grep AC for disappeared symbols.