canon-ai 3.1.2 → 3.2.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 +16 -0
- package/README.md +5 -2
- package/dist/cli/index.js +11 -8
- package/dist/orchestrator/run-task.js +1 -1
- package/package.json +6 -6
- package/templates/.canon/templates/handoff.md +3 -0
- package/templates/.claude/agents/code-review-anchored.md +1 -0
- package/templates/.claude/skills/canon-init/SKILL.md +3 -1
- package/templates/.claude/skills/canon-spec/SKILL.md +1 -1
- package/templates/.claude/skills/canon-sweep/SKILL.md +141 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [3.2.0] — 2026-09-11
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **New `/canon-sweep` skill triages the lessons-learned buffer.** It walks every entry with a prune-or-keep default, prefers the smallest carrier (a site comment, a test, or a clause on an existing rule) over a new rule, and proposes a per-entry verdict table for human approval before writing anything. Installed by `canon init` / `canon upgrade` and checked by `canon doctor`.
|
|
12
|
+
|
|
13
|
+
## [3.1.3] — 2026-09-08
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **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.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **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.
|
|
22
|
+
|
|
7
23
|
## [3.1.2] — 2026-09-08
|
|
8
24
|
|
|
9
25
|
### Fixed
|
package/README.md
CHANGED
|
@@ -130,6 +130,7 @@ The other installed skills (auto-trigger on natural-language phrases — see eac
|
|
|
130
130
|
| `/canon-spec` | Authoring a new task — "let's add X", "start a task for…" |
|
|
131
131
|
| `/canon-spec-review` | Pre-flighting a spec before invoking the pipeline |
|
|
132
132
|
| `/canon-inline-review` | Independent cross-review of inline or below-pipeline changes before commit or PR |
|
|
133
|
+
| `/canon-sweep` | Triaging `docs/lessons-learned.md` — promote, keep, or prune, human-approved |
|
|
133
134
|
| `/canon-pipeline` | Driving an existing task forward (`canon run`, `--pr`, `--ship`, recovery) |
|
|
134
135
|
| `/canon-status` | "Where are we?" — surfaces phases and blockers across in-flight tasks |
|
|
135
136
|
| `/canon-changelog` | Drafting release notes (projects that version their releases) |
|
|
@@ -211,7 +212,9 @@ Canon drives a lot of `git`, `gh`, `codex`, and `npm` invocations, plus short sh
|
|
|
211
212
|
"Skill(canon-spec-review)",
|
|
212
213
|
"Skill(canon-spec-review:*)",
|
|
213
214
|
"Skill(canon-inline-review)",
|
|
214
|
-
"Skill(canon-inline-review:*)"
|
|
215
|
+
"Skill(canon-inline-review:*)",
|
|
216
|
+
"Skill(canon-sweep)",
|
|
217
|
+
"Skill(canon-sweep:*)"
|
|
215
218
|
]
|
|
216
219
|
}
|
|
217
220
|
}
|
|
@@ -308,7 +311,7 @@ Drop this into any repo and you have:
|
|
|
308
311
|
- Agent rules and per-agent guidance delivered just-in-time via per-phase prompt templates (`src/orchestrator/prompts/templates/`) and the `/canon-*` skills; adopters generate their own `AGENTS.md` / `CLAUDE.md` with the built-in `/init`
|
|
309
312
|
- `docs/` — knowledge corpus templates with detailed scaffolding
|
|
310
313
|
- `.claude/settings.json` — Claude Code permissions + SessionStart hook
|
|
311
|
-
- Claude Code skills installed by `canon init`: `/canon-init` (knowledge-corpus bootstrap), `/canon-spec` (new task authoring), `/canon-spec-review` (pre-flight a spec), `/canon-inline-review` (independent cross-review of below-pipeline work), `/canon-pipeline` (drive an existing task), `/canon-status` (in-flight task map), `/canon-changelog` (release notes for versioned projects)
|
|
314
|
+
- Claude Code skills installed by `canon init`: `/canon-init` (knowledge-corpus bootstrap), `/canon-spec` (new task authoring), `/canon-spec-review` (pre-flight a spec), `/canon-inline-review` (independent cross-review of below-pipeline work), `/canon-pipeline` (drive an existing task), `/canon-status` (in-flight task map), `/canon-changelog` (release notes for versioned projects), `/canon-sweep` (triage the lessons-learned buffer)
|
|
312
315
|
- Unit-test suite covering the policy module, orchestrator extractors, and validation parsers (`npm test`)
|
|
313
316
|
|
|
314
317
|
## Where to Go Deeper
|
package/dist/cli/index.js
CHANGED
|
@@ -1117,7 +1117,9 @@ var RECOMMENDED_ALLOW = [
|
|
|
1117
1117
|
"Skill(canon-spec-review)",
|
|
1118
1118
|
"Skill(canon-spec-review:*)",
|
|
1119
1119
|
"Skill(canon-inline-review)",
|
|
1120
|
-
"Skill(canon-inline-review:*)"
|
|
1120
|
+
"Skill(canon-inline-review:*)",
|
|
1121
|
+
"Skill(canon-sweep)",
|
|
1122
|
+
"Skill(canon-sweep:*)"
|
|
1121
1123
|
];
|
|
1122
1124
|
var RECOMMENDED_NUDGE = [
|
|
1123
1125
|
"This project uses canon, a spec-first multi-agent pipeline.",
|
|
@@ -1245,7 +1247,7 @@ function checkTemplates(cwd) {
|
|
|
1245
1247
|
}
|
|
1246
1248
|
function checkCanonVersion(cwd) {
|
|
1247
1249
|
const versionPath = join(cwd, ".canon", "version");
|
|
1248
|
-
const installedVersion = "3.
|
|
1250
|
+
const installedVersion = "3.2.0";
|
|
1249
1251
|
if (!existsSync(versionPath)) {
|
|
1250
1252
|
return { label: ".canon/version", status: "warn", detail: "missing \u2014 run `canon upgrade`" };
|
|
1251
1253
|
}
|
|
@@ -1268,7 +1270,7 @@ function checkSkills(cwd) {
|
|
|
1268
1270
|
detail: "canon-init skill missing \u2014 run `canon init` or `canon upgrade`"
|
|
1269
1271
|
};
|
|
1270
1272
|
}
|
|
1271
|
-
const skillNames = ["canon-spec", "canon-pipeline", "canon-status", "canon-changelog", "canon-spec-review", "canon-inline-review"];
|
|
1273
|
+
const skillNames = ["canon-spec", "canon-pipeline", "canon-status", "canon-changelog", "canon-spec-review", "canon-inline-review", "canon-sweep"];
|
|
1272
1274
|
const missing = skillNames.filter((s) => !existsSync(join(cwd, ".claude", "skills", s, "SKILL.md")));
|
|
1273
1275
|
if (missing.length > 0) {
|
|
1274
1276
|
return {
|
|
@@ -1742,7 +1744,7 @@ function initCmd(_args) {
|
|
|
1742
1744
|
}
|
|
1743
1745
|
function writeCanonVersion(cwd) {
|
|
1744
1746
|
const versionPath = join2(cwd, ".canon", "version");
|
|
1745
|
-
const version = "3.
|
|
1747
|
+
const version = "3.2.0";
|
|
1746
1748
|
mkdirSync(dirname2(versionPath), { recursive: true });
|
|
1747
1749
|
writeFileSync(versionPath, version + "\n");
|
|
1748
1750
|
}
|
|
@@ -2751,7 +2753,7 @@ function isInstalledSourcePath(sourcePath) {
|
|
|
2751
2753
|
return sourcePath.includes("/node_modules/") || sourcePath.includes("\\node_modules\\") || sourcePath.includes("/_npx/") || sourcePath.includes("\\_npx\\");
|
|
2752
2754
|
}
|
|
2753
2755
|
function resolveCanonVersion(explicit) {
|
|
2754
|
-
return explicit ?? "3.
|
|
2756
|
+
return explicit ?? "3.2.0" ?? "dev";
|
|
2755
2757
|
}
|
|
2756
2758
|
function resolveOrchestratorCommit(repoRoot, upstreamCommit, runGitAt) {
|
|
2757
2759
|
const ownToplevel = captureGitOutput(repoRoot, ["rev-parse", "--show-toplevel"], runGitAt);
|
|
@@ -4877,7 +4879,7 @@ function currentPinFromManifest(manifest) {
|
|
|
4877
4879
|
return "unknown";
|
|
4878
4880
|
}
|
|
4879
4881
|
function bakedVersion() {
|
|
4880
|
-
return "3.
|
|
4882
|
+
return "3.2.0";
|
|
4881
4883
|
}
|
|
4882
4884
|
function ownPackageName(pkgDir) {
|
|
4883
4885
|
try {
|
|
@@ -5089,6 +5091,7 @@ var CANON_OWNED = [
|
|
|
5089
5091
|
".claude/skills/canon-changelog/SKILL.md",
|
|
5090
5092
|
".claude/skills/canon-spec-review/SKILL.md",
|
|
5091
5093
|
".claude/skills/canon-inline-review/SKILL.md",
|
|
5094
|
+
".claude/skills/canon-sweep/SKILL.md",
|
|
5092
5095
|
".claude/agents/code-review-anchored.md",
|
|
5093
5096
|
".claude/agents/code-review-cold.md",
|
|
5094
5097
|
".canon/templates/status.json",
|
|
@@ -5351,7 +5354,7 @@ function runUpgrade(cwd, pkgDir, options = {}) {
|
|
|
5351
5354
|
cutoverWarnings.push(docsRefsCheckRel);
|
|
5352
5355
|
}
|
|
5353
5356
|
const versionPath = join5(cwd, ".canon", "version");
|
|
5354
|
-
const newVersion = "3.
|
|
5357
|
+
const newVersion = "3.2.0";
|
|
5355
5358
|
const currentVersion = existsSync5(versionPath) ? readFileSync4(versionPath, "utf8").trim() : null;
|
|
5356
5359
|
if (currentVersion !== newVersion) {
|
|
5357
5360
|
pending.push({ rel: ".canon/version", projectPath: versionPath, content: newVersion + "\n" });
|
|
@@ -5659,7 +5662,7 @@ Global:
|
|
|
5659
5662
|
`);
|
|
5660
5663
|
}
|
|
5661
5664
|
function printVersion() {
|
|
5662
|
-
console.log("3.
|
|
5665
|
+
console.log("3.2.0");
|
|
5663
5666
|
}
|
|
5664
5667
|
switch (command) {
|
|
5665
5668
|
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.
|
|
4964
|
+
return explicit ?? "3.2.0" ?? "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.
|
|
3
|
+
"version": "3.2.0",
|
|
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": {
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/mustache": "^4.2.6",
|
|
30
|
-
"@types/node": "^
|
|
31
|
-
"eslint": "^10.
|
|
30
|
+
"@types/node": "^24.13.3",
|
|
31
|
+
"eslint": "^10.10.0",
|
|
32
32
|
"mustache": "^4.2.0",
|
|
33
|
-
"simple-git-hooks": "^2.
|
|
33
|
+
"simple-git-hooks": "^2.14.0",
|
|
34
34
|
"tsup": "^8.5.1",
|
|
35
|
-
"tsx": "^4.23.
|
|
35
|
+
"tsx": "^4.23.13",
|
|
36
36
|
"typescript": "^5.9.3",
|
|
37
|
-
"typescript-eslint": "^8.
|
|
37
|
+
"typescript-eslint": "^8.70.0"
|
|
38
38
|
},
|
|
39
39
|
"simple-git-hooks": {
|
|
40
40
|
"pre-commit": "npm run sync-templates -- --stage"
|
|
@@ -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
|
|
|
@@ -135,7 +135,9 @@ By default, Claude Code prompts on every `git`, `gh`, `codex`, and `canon` subpr
|
|
|
135
135
|
"Skill(canon-pipeline)", "Skill(canon-pipeline:*)",
|
|
136
136
|
"Skill(canon-status)", "Skill(canon-status:*)",
|
|
137
137
|
"Skill(canon-changelog)", "Skill(canon-changelog:*)",
|
|
138
|
-
"Skill(canon-spec-review)", "Skill(canon-spec-review:*)"
|
|
138
|
+
"Skill(canon-spec-review)", "Skill(canon-spec-review:*)",
|
|
139
|
+
"Skill(canon-inline-review)", "Skill(canon-inline-review:*)",
|
|
140
|
+
"Skill(canon-sweep)", "Skill(canon-sweep:*)"
|
|
139
141
|
]
|
|
140
142
|
}
|
|
141
143
|
}
|
|
@@ -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.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: canon-sweep
|
|
3
|
+
description: Use when the human asks to sweep, triage, or prune `docs/lessons-learned.md` — phrases like "sweep the lessons buffer", "let's do a lessons sweep", "promote or prune lessons learned", or explicit `/canon-sweep` invocation. Human-invoked only. A QA "buffer exceeds ~15 entries" note in a task's `done.md` or a just-closed release milestone are reasons to *suggest* a sweep to the human, never to start one. The skill proposes a verdict and the exact promoted text per entry and writes nothing until the human confirms. Not for appending new lessons (the QA phase does that) or for editing task notes.
|
|
4
|
+
allowed-tools: Read Glob Grep Edit Write Bash(git log *) Bash(git status *) Bash(git branch *) Bash(git diff *) Bash(git show *) Bash(git worktree list) Bash(canon task list)
|
|
5
|
+
effort: medium
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Lessons Sweep
|
|
9
|
+
|
|
10
|
+
Use this skill to triage the lessons-learned buffer: decide, entry by entry, whether each one is promoted into a permanent doc, kept in the buffer, or pruned. The buffer is a staging area, not an archive. The sweep exists to keep it small and to keep the permanent docs from bloating.
|
|
11
|
+
|
|
12
|
+
**Promotion is rare.** Every line promoted is read by every agent on every future task, so it must earn that recurring cost. Most entries end as keep or prune. But the threshold that prompted the sweep is a reason to pay attention, not a target: never manufacture a prune to hit a count.
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- The human asks for a sweep.
|
|
17
|
+
- You notice a reason to suggest one: a task's `done.md` carries the QA signal that the buffer exceeds ~15 entries, or a release milestone just closed. Suggest it in one line and wait. Never start a sweep on your own.
|
|
18
|
+
|
|
19
|
+
## Preconditions
|
|
20
|
+
|
|
21
|
+
Triage (Steps 1 to 3) is read-only and can run from any checkout. Before **applying** anything (Step 4), run this preflight and stop if any check fails:
|
|
22
|
+
|
|
23
|
+
- `git status` is clean and `git branch --show-current` is a dedicated non-default branch.
|
|
24
|
+
- `git worktree list` shows the current directory as the main working tree, not a linked worktree. A task worktree is where a pipeline edits code; shared docs must not change there.
|
|
25
|
+
- `canon task list` shows no task with a phase in progress. A pipeline running in another worktree would otherwise pick up or collide with the sweep's edits to shared docs at merge time. If one is in flight, wait for it to reach `human_review` or stop it before applying. The sweep touches shared docs and should ship as its own small change through the normal review path.
|
|
26
|
+
|
|
27
|
+
Read before triaging:
|
|
28
|
+
|
|
29
|
+
1. `docs/lessons-learned.md` in full, including the HTML sweep comments at the bottom. They record what was kept with a re-evaluation condition and what was already promoted; they are the cheapest way to avoid redoing work.
|
|
30
|
+
2. The candidate destinations, so you can check coverage: `docs/patterns.md`, `docs/decisions.md`, `docs/architecture.md`, and the project's agent instruction files (`AGENTS.md`, `CLAUDE.md`) if it keeps them.
|
|
31
|
+
3. `git log docs/lessons-learned.md` since the last sweep comment, and the task IDs shipped in that window, so you can tell "did not recur" from "had no chance to recur."
|
|
32
|
+
|
|
33
|
+
## Step 1 — Triage every entry
|
|
34
|
+
|
|
35
|
+
Walk each entry through this sequence. Stop at the first stage that decides it, and write a one-line reason.
|
|
36
|
+
|
|
37
|
+
### Stage A — Is it already settled? → Prune
|
|
38
|
+
|
|
39
|
+
- **Already covered.** Grep the permanent docs for the concept, not the exact words. A restatement of an existing pitfall, decision, or rule is a prune.
|
|
40
|
+
- **Obsolete.** The code path, tool, or convention it describes no longer exists.
|
|
41
|
+
- **Self-evident.** A competent agent with no doc at all would already do this. Reminders of general good practice are not lessons.
|
|
42
|
+
- **Its prevention has landed.** A test, lint check, or gate now enforces it, or a tracked follow-up (an issue, a backlog item the project actually works from) has explicitly taken ownership. A note in a sweep comment is not ownership; do not prune an unresolved structural lesson on the promise of a future test.
|
|
43
|
+
|
|
44
|
+
### Stage B — Is the evidence sufficient? → Evaluate promotion
|
|
45
|
+
|
|
46
|
+
Promotion is on the table when either holds:
|
|
47
|
+
|
|
48
|
+
- **Recurrence.** It has bitten at least twice, in independent tasks. Check archived task notes and prior sweep comments for the second occurrence before claiming it.
|
|
49
|
+
- **Severity.** It bit once, but the cost of one more occurrence clearly exceeds the recurring cost of a rule read on every task. Say why in the reason.
|
|
50
|
+
|
|
51
|
+
And both of these hold:
|
|
52
|
+
|
|
53
|
+
- **Surprising or project-specific.** The reader could not have derived it from general experience.
|
|
54
|
+
- **Not already absorbed.** If an existing rule can take it as a clause, that is still a promotion, as an extension (Step 2). Only a brand-new rule requires that no nearby rule exists.
|
|
55
|
+
|
|
56
|
+
If the evidence is sufficient, go to Step 2 to pick the carrier. If it is not, fall through.
|
|
57
|
+
|
|
58
|
+
### Stage C — Otherwise → Keep, with a condition
|
|
59
|
+
|
|
60
|
+
- **Happened once, not severe.** Leave it and see whether it bites again.
|
|
61
|
+
- **Borderline and adjacent to an existing rule.** Note which rule, so the next sweep can decide on a one-clause graft without re-reading.
|
|
62
|
+
- **Kept before, with no chance to recur.** If no task since the last sweep touched the area the lesson is about, the absence of recurrence is not evidence. Keep it. Prune on "no recurrence" only when there was relevant exposure and it still did not bite, or the human decides to let it go. This never applies to an unresolved structural lesson: one whose prevention is a test, lint check, or gate that has not landed and has no tracked owner stays until it does (Stage A), regardless of recurrence.
|
|
63
|
+
|
|
64
|
+
A kept entry always carries an explicit condition: "re-evaluate if X recurs in a different task" or "prune at the next sweep if tasks touching Y ship without hitting this." "Keep, unsure" is not a verdict.
|
|
65
|
+
|
|
66
|
+
## Step 2 — Choose the smallest carrier
|
|
67
|
+
|
|
68
|
+
Promotion does not mean "write a new rule." Choose the lightest form that would have prevented the incident, in this order of preference:
|
|
69
|
+
|
|
70
|
+
1. **A comment at the site.** If the trap is local to one function, file, or config block, a one-line comment where the next person will trip is better than a rule read by every agent on every task.
|
|
71
|
+
2. **A test, lint check, or gate.** If the lesson can be enforced mechanically, prefer that over prose. If writing it is out of scope for the sweep, the entry stays in the buffer until the check lands or a tracked follow-up owns it (Stage A).
|
|
72
|
+
3. **Extending an existing rule.** Add a clause, a trigger-table row, or a second example to a rule that already exists. This is the most common shape of a real promotion.
|
|
73
|
+
4. **A new rule.** Only when no existing rule is close enough to absorb it. Keep it to the rule and its concrete prevention; cut the story of how it was discovered.
|
|
74
|
+
|
|
75
|
+
When the carrier is a doc, match the doc to the kind of truth:
|
|
76
|
+
|
|
77
|
+
| Kind of lesson | Home |
|
|
78
|
+
|---|---|
|
|
79
|
+
| A pitfall: something that looks right and silently isn't | `docs/patterns.md` (and its trigger table, if the project keeps one) |
|
|
80
|
+
| A settled decision or an explicit rule the project has chosen | `docs/decisions.md` |
|
|
81
|
+
| A command, validation, or environment gotcha | `docs/architecture.md`, validation section |
|
|
82
|
+
| A workflow rule every agent must know on every task, no matter what it is working on | The project's agent instruction files, if it keeps them. This is the highest bar: those files load into every session, so a rule lands here only when it is unconditionally load-bearing |
|
|
83
|
+
|
|
84
|
+
Rules of thumb for the promoted text:
|
|
85
|
+
|
|
86
|
+
- **Write the rule, not the war story.** Title names the rule. Body is rule, failure mode in one sentence, concrete prevention. Drop dates, task IDs, and the narrative of discovery; those stay in the buffer's git history.
|
|
87
|
+
- **Generalize past the incident.** The promoted form should read correctly to someone working on an unrelated feature. If it only makes sense with the original task in mind, it is not ready.
|
|
88
|
+
- **Verify anything executable.** If the entry prescribes a command, flag, or path, run it or check it exists before promoting. The buffer can carry an error for weeks; a permanent doc will carry it for years.
|
|
89
|
+
- **Lessons about the pipeline itself go upstream.** If the lesson is about canon's own behavior rather than this project's code, it belongs in a report to the canon-ai maintainers, not in this project's docs. Canon-managed files are overwritten on upgrade, so a rule written into one is lost. If this repository *is* the canon-ai source, upstream is here: the promotion lands in the managed file's authoritative copy and follows the project's own mirror-sync workflow.
|
|
90
|
+
- **A rule in the wrong home is nearly as bad as no rule.** Agents read docs just in time; a spec rule buried in a pitfalls doc is never read at spec time.
|
|
91
|
+
|
|
92
|
+
## Step 3 — Propose before writing
|
|
93
|
+
|
|
94
|
+
Present the human with a verdict table before touching any file:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
| # | Entry (title) | Verdict | Carrier / condition | Why (one line) |
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**For every promote row, also show the exact text you intend to add and exactly where it goes** (file and section, or the existing rule being extended, with the new clause in place). The permanent rule is the most consequential output of the sweep; the human approves the words, not just the classification.
|
|
101
|
+
|
|
102
|
+
Then ask for approval with `AskUserQuestion`, offering: approve all, approve with changes (the human names which rows to flip or which text to edit), or stop. Do not write until the human has confirmed. If the human flips a verdict or rewrites a rule, accept it and move on.
|
|
103
|
+
|
|
104
|
+
## Step 4 — Apply
|
|
105
|
+
|
|
106
|
+
Run the preflight above, then for each approved row:
|
|
107
|
+
|
|
108
|
+
- **Promote:** land the approved carrier, then delete the entry from the buffer. For a doc, make the edit exactly as approved. For a site comment, write the comment at the approved location. For a test, lint check, or gate, the entry leaves the buffer only once the check is actually in place; if it is deferred, the row is a keep, not a promote.
|
|
109
|
+
- **Prune:** delete the entry.
|
|
110
|
+
- **Keep:** leave the entry in place, byte for byte.
|
|
111
|
+
|
|
112
|
+
Then update the HTML sweep comments at the bottom of `docs/lessons-learned.md`. Add one comment for this sweep in the same shape as the existing ones:
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
<!-- Buffer swept YYYY-MM-DD (N entries reviewed: A promoted, B kept in buffer, C pruned).
|
|
116
|
+
Promotions → <destination>: <what was added, one line each>.
|
|
117
|
+
Kept in buffer: <entry> — re-evaluate if <condition>.
|
|
118
|
+
Pruned: <entry> — <reason>.
|
|
119
|
+
Unresolved follow-ups: <structural fixes not yet landed, upstream reports not yet filed>.
|
|
120
|
+
Prior entries are in git history. -->
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Keep the comment block bounded, or it becomes the archive this file says it is not. Carry forward only what the next sweep needs: the current keep conditions and any unresolved follow-ups. Drop older comments whose promotions and prunes are fully settled; git history retains them.
|
|
124
|
+
|
|
125
|
+
## Step 5 — Hand off
|
|
126
|
+
|
|
127
|
+
Report to the human:
|
|
128
|
+
|
|
129
|
+
- the counts, and the number of entries remaining in the buffer. If it is still near or above the QA signal threshold, say why (approved keeps, unresolved structural follow-ups) rather than treating that as a failure;
|
|
130
|
+
- each promotion and where it landed;
|
|
131
|
+
- each follow-up that needs a decision: a test to write, an issue to file upstream, an entry waiting on ownership;
|
|
132
|
+
- the verification you ran on any command or path you promoted.
|
|
133
|
+
|
|
134
|
+
Do not commit. The human decides how the change ships, and it should get an independent review like any other below-pipeline edit.
|
|
135
|
+
|
|
136
|
+
## What this skill does not do
|
|
137
|
+
|
|
138
|
+
- Append new lessons. That is the QA phase's job, and its entry format is its own concern.
|
|
139
|
+
- Set the sweep threshold. The buffer's own "How to use" section owns that.
|
|
140
|
+
- Rewrite or reorganize entries it is keeping.
|
|
141
|
+
- Start a sweep without a human asking.
|