@erclx/canon 4.116.0 → 4.118.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.
Files changed (77) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/auto-ship/SKILL.md +1 -1
  3. package/claude/skills/canon-cli/SKILL.md +12 -12
  4. package/claude/skills/canon-operator/SKILL.md +1 -1
  5. package/claude/skills/create-skill/REQUIREMENT.md +3 -0
  6. package/claude/skills/create-skill/SKILL.md +3 -1
  7. package/claude/skills/docs-fold/SKILL.md +1 -1
  8. package/claude/skills/draft-ready/references/assembly.md +8 -0
  9. package/claude/skills/memory-review/SKILL.md +4 -4
  10. package/claude/skills/plan-feature/REQUIREMENT.md +1 -0
  11. package/claude/skills/plan-feature/SKILL.md +7 -0
  12. package/claude/skills/review-pr/REQUIREMENT.md +3 -0
  13. package/claude/skills/review-pr/SKILL.md +5 -3
  14. package/claude/skills/role-orchestrator/REQUIREMENT.md +2 -0
  15. package/claude/skills/role-orchestrator/SKILL.md +2 -2
  16. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +14 -14
  17. package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +1 -1
  18. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +2 -2
  19. package/claude/skills/role-worker/REQUIREMENT.md +2 -0
  20. package/claude/skills/role-worker/SKILL.md +2 -2
  21. package/claude/skills/session-compact/REQUIREMENT.md +2 -1
  22. package/claude/skills/session-compact/SKILL.md +5 -1
  23. package/claude/skills/target-check/references/domains.md +1 -1
  24. package/claude/skills/write-human/references/source-material.md +2 -2
  25. package/docs/agents/commands.md +1 -1
  26. package/docs/agents/context-audit-checks.md +3 -29
  27. package/docs/agents/context-audit.md +1 -1
  28. package/docs/agents/install-and-sync.md +24 -18
  29. package/docs/agents/markdown-audit.md +7 -3
  30. package/docs/agents/skills-audit.md +8 -1
  31. package/docs/target-projects.md +2 -2
  32. package/docs/workflow/ai-workflow.md +9 -9
  33. package/docs/workflow/operating-model.md +1 -1
  34. package/docs/workflow/visual-design-workflow.md +1 -1
  35. package/governance/rules/claude/510-context.md +0 -12
  36. package/governance/rules/core/005-behavior.md +1 -2
  37. package/governance/rules/core/015-output.md +1 -3
  38. package/governance/rules/core/035-tasks.md +1 -3
  39. package/governance/rules/core/045-memory.md +0 -1
  40. package/governance/rules/core/055-scratch.md +2 -4
  41. package/governance/rules/core/070-planning.md +1 -4
  42. package/governance/rules/core/087-git.md +2 -2
  43. package/governance/rules/core/090-code-comments.md +1 -1
  44. package/governance/rules/ui/440-surface-capture.md +2 -0
  45. package/governance/rules/ui/460-design-taste.md +0 -15
  46. package/package.json +1 -1
  47. package/scripts/core/check-capability-seeding.sh +4 -2
  48. package/src/claude/cases/misc.ts +5 -0
  49. package/src/claude/skills-audit.ts +59 -2
  50. package/src/commands/claude.ts +30 -0
  51. package/src/commands/context.ts +11 -111
  52. package/src/commands/markdown.ts +63 -2
  53. package/src/commands/sync.ts +2 -2
  54. package/src/context/audit.ts +9 -153
  55. package/src/gate/measures.ts +97 -0
  56. package/src/gate/stages.ts +21 -2
  57. package/src/gov/adapter.ts +8 -6
  58. package/src/markdown/ceiling.ts +70 -0
  59. package/src/markdown/structure.ts +22 -0
  60. package/src/records/validate.ts +5 -1
  61. package/src/sync/check.ts +7 -1
  62. package/src/sync/engine.ts +90 -10
  63. package/src/sync/stamp.ts +44 -0
  64. package/src/tooling/inject.ts +13 -5
  65. package/src/tooling/manifest.ts +9 -0
  66. package/src/tooling/scan.ts +23 -6
  67. package/src/tooling/subfolder.ts +3 -3
  68. package/standards/context.md +2 -2
  69. package/standards/markdown.md +2 -0
  70. package/standards/plan.md +24 -1
  71. package/standards/skill.md +1 -0
  72. package/tooling/base/reference.md +2 -2
  73. package/tooling/web/configs/e2e/screenshot.ts +43 -7
  74. package/tooling/web/reference.md +1 -1
  75. package/src/context/narration.ts +0 -99
  76. /package/tooling/base/{configs → seeds}/.github/pull_request_template.md +0 -0
  77. /package/tooling/base/{configs → seeds}/.github/workflows/verify.yml +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "4.116.0",
4
+ "version": "4.118.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -53,7 +53,7 @@ canon tasks plan-branch <argument> --json
53
53
  - `conforms: false`: the plan's own filename breaks a cap in `${CLAUDE_SKILL_DIR}/../../standards/branch.md`. Pass `branch` anyway and say the cap it broke, since the alternative is a name this session shortened by hand, which is a second derivation and the thing this call exists to prevent. `git-branch` decides the rename at ship.
54
54
  - Anything else, including a refusal, a record carrying no `branch` key, and an installed binary carrying no `plan-branch` subcommand: invoke `canon:session-worktree` bare and let its ladder derive the name. Say the verb did not answer, so a reader can tell a derived name from a fallback one.
55
55
 
56
- The dispatch runbook runs the same verb on the same plan to pick the branch its collision check clears, so calling it here is what makes the checked branch and the taken branch one string. Deriving a name from `<plan>` by reading it was the alternative, and it is what produced three strings for one plan across four dispatches on 2026-09-05.
56
+ The dispatch runbook runs the same verb on the same plan to pick the branch its collision check clears, so calling it here is what makes the checked branch and the taken branch one string. Deriving a name from `<plan>` by reading it was the alternative, and two readings of one plan part on the slug and on the type alike.
57
57
 
58
58
  A caller that supplied a task path, or supplied nothing at all, has no plan to hand the verb here, since resolving either is Step 1's work and Step 1 has not run. Invoke `canon:session-worktree` bare in both cases. That is the ladder unchanged rather than a regression, and it leaves the hole open: a dispatched worker reaching this step through a task path derives its name from a tier rather than from the plan the dispatcher checked.
59
59
 
@@ -15,21 +15,21 @@ Consult before running an unfamiliar `canon` verb, before a sync or install, or
15
15
 
16
16
  ## Overwrite contract
17
17
 
18
- | Surface | Command | Effect on existing files |
19
- | -------------------------------------------------------- | -------------------- | --------------------------------------------------------------------------- |
20
- | Golden configs | `canon tooling sync` | Overwritten once `--write` is passed. Local edits are lost. |
21
- | Dictionary seeds (`.cspell/*.txt`) | `canon tooling sync` | Merged and sorted. Existing terms preserved. |
22
- | Other seeds (`cspell.json`, `.lintstagedrc`, state docs) | `canon tooling sync` | Copy-once. Dropped on first install, untouched after. |
23
- | Standards | none | Nothing installs. `canon standards <name>` reads and never writes. |
24
- | Seed docs and `CLAUDE.md` | `canon claude init` | Skipped when present. Never overwritten. |
25
- | Seed docs | `canon claude sync` | Never touched. Only `.gitignore` is written. |
26
- | Stack references | none | Nothing installs. `canon tooling reference <stack>` reads and never writes. |
27
- | `.gitignore`, deps, scripts | any sync | Additive. Existing entries preserved. Deps re-pin on major skew. |
28
- | Generated `index.md` | any sync or regen | Rewritten from target state. Hand edits are lost. |
18
+ | Surface | Command | Effect on existing files |
19
+ | --------------------------------------------------------------------------------------------- | -------------------- | --------------------------------------------------------------------------- |
20
+ | Golden configs | `canon tooling sync` | Overwritten once `--write` is passed. Local edits are lost. |
21
+ | Dictionary seeds (`.cspell/*.txt`) | `canon tooling sync` | Merged and sorted. Existing terms preserved. |
22
+ | Other seeds (`cspell.json`, `.lintstagedrc`, base's `verify.yml` and PR template, state docs) | `canon tooling sync` | Copy-once. Dropped on first install, untouched after. |
23
+ | Standards | none | Nothing installs. `canon standards <name>` reads and never writes. |
24
+ | Seed docs and `CLAUDE.md` | `canon claude init` | Skipped when present. Never overwritten. |
25
+ | Seed docs | `canon claude sync` | Never touched. Only `.gitignore` is written. |
26
+ | Stack references | none | Nothing installs. `canon tooling reference <stack>` reads and never writes. |
27
+ | `.gitignore`, deps, scripts | any sync | Additive. Existing entries preserved. Deps re-pin on major skew. |
28
+ | Generated `index.md` | any sync or regen | Rewritten from target state. Hand edits are lost. |
29
29
 
30
30
  ## What a tooling sync can overwrite
31
31
 
32
- A golden config is any file a stack ships under `configs/`, and the category is wider than its name suggests. It carries the CI workflow, the git hooks, the end-to-end harness, the shell scripts under `scripts/`, and the editor settings, alongside the linters and compilers a reader expects. A stack inherits its parent's configs, so syncing `astro` also writes everything `web` and `base` hold.
32
+ A golden config is any file a stack ships under `configs/`, and the category is wider than its name suggests. It carries the phase-label gate workflow, the web stack's CI workflow, the git hooks, the end-to-end harness, the shell scripts under `scripts/`, and the editor settings, alongside the linters and compilers a reader expects. A stack inherits its parent's configs, so syncing `astro` also writes everything `web` and `base` hold.
33
33
 
34
34
  No list of those paths ships in this body, since a copy would go stale on a different cadence than the stacks. Run `canon tooling diff <stack> <target>` for the list resolved against a real target. It reports every path, writes nothing, and exits 1 when any differs. A binary older than that verb answers the same question through `canon tooling sync <stack> <target> --check`, which names each file it would replace.
35
35
 
@@ -27,7 +27,7 @@ Read seven fields off the report and carry each to `## Route`:
27
27
  - `unmigrated`: a domain sitting at the root layout with nothing under `.claude/`. The most urgent finding, because that domain reports no drift of its own while being entirely behind
28
28
  - `superseded`: a file a newer seed folder replaced. No command moves it, since the content is the project's own, so this routes to `canon:target-check` for the full per-domain reading rather than ending at the report
29
29
  - `seeds`: entries are `matching`, `stale`, `drifted`, or `missing`. Anything but `matching` needs the seed handoff
30
- - `domains[].entries`: per-file `stale`, `customized`, `stranded`, and `orphaned` as before
30
+ - `domains[].entries`: per-file `stale`, `customized`, `stranded`, and `orphaned` as before, plus `retired` for a rule the toolkit no longer ships, which the next sync deletes
31
31
  - `reverse`: what the target holds that no live catalog claims. `unclaimed` lists folders at roots the toolkit stopped shipping, each with an `attribution` of `dropped`, `unattributed`, or `project`. Act on the first two and leave `project` alone, which history proved the project owns
32
32
  - `historyUnavailable` on a domain, on `seeds`, or on `reverse`: nothing could be dated, so treat every difference as unverified and say so rather than reporting a file as untouched. It is set on `reverse` when the toolkit itself ships without history, which is the registry-install case, so the walk found nothing rather than finding a clean target
33
33
  - `tooling`: read `measured` first. Every count under it is zero when it is false, which is an absence of measurement rather than a measured zero. Past that, `chain` names the stacks the install resolved, nearest first, and `counts.gitignore` counts the managed ignore entries the target is missing.
@@ -13,6 +13,8 @@ A skill born without its requirement is the second failure, and it surfaces much
13
13
 
14
14
  A third failure is a folder created for a moment a rule, a verb, or an existing skill already reaches, or for a procedure no session would get wrong from first principles. Nothing caught that before the folder existed, so the corpus grew by a skill an inward audit later had to argue for removing, with only a commit body recording the argument.
15
15
 
16
+ A fourth failure is dated provenance in the body. A target asked for an edit citing a measurement taken that day, and the draft carried "measured on" and the date inline, with the authoring standard, the markdown audit, and this skill all passing it. A body collecting those notes reads as history rather than as the rule a session follows.
17
+
16
18
  ## Must
17
19
 
18
20
  - Read the authoring standard and the prose standard before drafting, so the draft starts conformant rather than getting corrected into shape
@@ -20,6 +22,7 @@ A third failure is a folder created for a moment a rule, a verb, or an existing
20
22
  - Draft the sibling `REQUIREMENT.md` beside the body, from what the skill is for rather than from the drafted body
21
23
  - Confirm the name and both files with the user before writing. The name is the routing key and a folder that disagrees with its frontmatter fails silently.
22
24
  - Write to the conventional skills path, so discovery finds it without configuration
25
+ - Leave the skills audit's dated-provenance check clean for the new folder before reporting it, with each incident moved to the requirement's `Gap`
23
26
 
24
27
  ## Must not
25
28
 
@@ -21,10 +21,12 @@ Read these files in parallel:
21
21
  - Does a rule, a verb, or an existing skill already reach this moment? A folder that only wraps something already reachable duplicates it rather than adding to the catalog.
22
22
  - Does the body carry a procedure a session would get wrong from first principles? A one-line wrapper around a single command needs no skill.
23
23
  - Will anything invoke it other than the author typing its name? Carry this one into the drafted `REQUIREMENT.md`'s `Must not` section as a review criterion instead, since nothing can answer it before the skill has run.
24
- 2. Draft the full `SKILL.md` from the user's description
24
+ 2. Draft the full `SKILL.md` from the user's description, stating the current rule alone. An incident and the date it happened belong in the requirement's `Gap`, not in the body or a reference.
25
25
  3. Draft the sibling `REQUIREMENT.md` from what the skill is for, in the shape the standard states. Write the gaps from the user's description rather than from the drafted body, since a requirement derived from the body records whatever the draft overfitted to.
26
26
  4. Confirm the skill name and both files with the user before writing
27
27
  5. Write to `.claude/skills/<name>/SKILL.md` and `.claude/skills/<name>/REQUIREMENT.md`
28
+ 6. Run `canon claude skills audit --json` and read `findings.datedProvenance` for rows under `.claude/skills/<name>/`. Move each row's incident into `REQUIREMENT.md` under `Gap`, cut the date from the file the row names, and re-run until no row names the folder.
29
+ - When `canon` does not resolve or its record carries no `datedProvenance` key, say the check did not run rather than reporting the skill clean.
28
30
 
29
31
  Every skill carries a requirement. A skill created without one is a gap someone closes in a later sweep, and the sweep has to reconstruct what the skill was for from the body it already shipped.
30
32
 
@@ -232,7 +232,7 @@ What that removes is a local-only review on a branch deleted before it opened a
232
232
 
233
233
  Memory receipts sweep board-wide rather than by slug. Scan every `.canon/memory/review/memory-review-*.md`, not only the one matching this slug. `memory-review` writes its receipt after this skill has run in every ship chain, so a sweep keyed on the current slug looks for a file that does not exist yet, and no later branch looks for it either because a slug is unique per feature. Scanning the folder is what makes the sweep fire at all.
234
234
 
235
- For each receipt, count the H2 items still marked 📝 pending:
235
+ For each receipt, count the H2 items still pending. An item is pending when its H2 carries 📝, or when its H2 carries no status emoji and its `Decision:` slot holds nothing `memory-review` Apply would act on or skip, since a receipt written by hand or by an older binary may lack the marker, and Apply's parse leaves every other slot value undecided:
236
236
 
237
237
  - No pending item: fold it per the collection rule in `${CLAUDE_SKILL_DIR}/../../standards/memory.md`, then delete the receipt.
238
238
  - Any pending item: leave it and report the count. Pending items are decision state, and a branch shipping is not an operator deciding them.
@@ -47,10 +47,18 @@ destinations:
47
47
 
48
48
  - `<path/to/file>`: copy from the ready folder, <one clause on what it changes>
49
49
 
50
+ **Verification:**
51
+
52
+ - <the command that proves the copy landed, or "None identified.">
53
+
50
54
  **Risks:**
51
55
 
52
56
  - <what could go wrong, or "None identified.">
53
57
 
58
+ **Review focus:**
59
+
60
+ - <an input the copied files must handle, or "None identified.">
61
+
54
62
  **Questions:**
55
63
 
56
64
  None identified.
@@ -176,11 +176,11 @@ As each item resolves, update its status in the review file: flip the H2 emoji f
176
176
 
177
177
  ### Sweep the receipt
178
178
 
179
- Count the items still 📝 pending once the parse above has run. Apply leaves one pending on `defer`, on empty, and on any unrecognized verb, so a receipt reaching this point may still be holding decisions.
179
+ Count the items still pending once the parse above has run. An item is pending when its H2 carries 📝, or when its H2 carries no status emoji and its `Decision:` slot holds nothing the parse above would act on or skip, since a receipt written by hand or by an older binary may lack the marker. Apply leaves one pending on `defer`, on empty, and on any unrecognized verb, so a receipt reaching this point may still be holding decisions.
180
180
 
181
181
  Leave the receipt in place when any remain. When none do, collect it per the collection rule in `${CLAUDE_SKILL_DIR}/../../standards/memory.md`, which owns what a fold writes and which entry types take one.
182
182
 
183
- `docs-fold` Step 10 sweeps the same folder on the same rule once per shipped branch, and either may reach a receipt first. Whichever does, the other finds no file and moves on.
183
+ `docs-fold` Step 9 sweeps the same folder on the same rule once per shipped branch, and either may reach a receipt first. Whichever does, the other finds no file and moves on.
184
184
 
185
185
  End with: `✅ Applied: <nums> | ⏭ Skipped: <nums> | 📝 Pending: <nums>`. Omit empty buckets. If anything is pending, remind the user they can refine `Decision:` lines and re-ping, run "discuss" for question items, or commit a skip with `skip <nums>` in chat.
186
186
 
@@ -188,11 +188,11 @@ End with: `✅ Applied: <nums> | ⏭ Skipped: <nums> | 📝 Pending: <nums>`. Om
188
188
 
189
189
  Trigger: user says "cleanup" or "delete the receipt" after Apply has run.
190
190
 
191
- Cleanup folds one receipt's skips and removes that receipt, and does nothing else. It is the fallback route now that Apply and `docs-fold` Step 10 each collect a resolved receipt on their own, so it reaches a file those two left behind rather than being the only collector. Apply is still the only phase that moves a memory entry out of the pen, and it does so per approved item into `.canon/memory/archive/`. A user asking to sweep stale memories wants Propose, which classifies entries and writes a decision slot per entry.
191
+ Cleanup folds one receipt's skips and removes that receipt, and does nothing else. It is the fallback route now that Apply and `docs-fold` Step 9 each collect a resolved receipt on their own, so it reaches a file those two left behind rather than being the only collector. Apply is still the only phase that moves a memory entry out of the pen, and it does so per approved item into `.canon/memory/archive/`. A user asking to sweep stale memories wants Propose, which classifies entries and writes a decision slot per entry.
192
192
 
193
193
  If no `.canon/memory/review/memory-review-*.md` exists at the main root, stop: `✅ No review receipt to clean up.` Every other refusal in this skill carries a message, and the phase reads a receipt before it does anything else.
194
194
 
195
- 1. Read the latest `.canon/memory/review/memory-review-*.md` at the main root and confirm Apply has run against it. If any item is still 📝 pending, stop and name the pending numbers.
195
+ 1. Read the latest `.canon/memory/review/memory-review-*.md` at the main root and confirm Apply has run against it. If any item is still pending, by the test the Apply sweep states, stop and name the pending numbers.
196
196
  2. Collect it per the collection rule in `${CLAUDE_SKILL_DIR}/../../standards/memory.md`, folding each ⏭ skipped item before the file goes. The fold happens wherever a receipt is collected, so this phase runs the same rule the Apply sweep does.
197
197
  3. Delete that one file. Leave every other receipt beside it in place, because the pending test above covers the file it read and nothing has tested the rest.
198
198
  4. Leave every memory entry in the pen. A skip records the decline on the entry and keeps the file, and applied promotions, governance handoffs, and user-type memories each stay as the review left them.
@@ -14,6 +14,7 @@ Without this skill, implementation starts before anyone knows what it touches. A
14
14
  - Read the project's own Claude setup before scanning source, so the plan inherits decisions already made instead of reopening them
15
15
  - Name every file the work touches with the reason it is touched
16
16
  - Place each new path against the layout guidance with a one-clause reason, loading that guidance only when the plan creates a file or folder, since the planner decides placement and the worker copies whatever path it is given
17
+ - Name how each outcome is proven and which input or state would break the change, so the executing session has a check to run and the reviewer has a place to look
17
18
  - Surface each unresolved ambiguity as a numbered question carrying a suggested answer and an empty answer slot, so the plan is decision-ready in one pass
18
19
  - State which act a constraint forbids when it names a surface to leave alone, since conforming that surface and retargeting a pointer into it are different acts and only one is out of scope
19
20
  - Scale the output to the work. A two-file change with nothing to decide should not produce a plan file.
@@ -46,6 +46,7 @@ What this skill adds on top of the standard:
46
46
  - Apply senior judgment to every `- Suggested:` line. Pick the best option and state it in one line with its reason or main tradeoff. No padding, no alternatives unless they change the pick.
47
47
  - Suggest a real default when best practice, the codebase, or prior context points to one.
48
48
  - Load the `canon:codebase-layout` skill before writing the `**Files to touch:**` entries when any of them names a file or folder that does not exist yet, and give each new path its placement reason in its entry. Skip the load when every entry edits an existing file. Report it rather than proceeding silently when the skill does not resolve.
49
+ - Write `**Verification:**` as one bullet per outcome naming the test or command that proves it, and `**Review focus:**` as the inputs or states that would break the change. You understood the risk before any diff existed, and these two sections carry it to the executing session and to the reviewer.
49
50
  - Prefer `None identified.` over low-signal fillers. A small feature should produce a short plan, not a padded one.
50
51
  - When three or more questions remain, keep chat output to the file pointer plus a short summary. Inline chat is fine when two or fewer remain.
51
52
 
@@ -76,8 +77,14 @@ Output the plan to chat. Do not write a plan file. The markers match the standar
76
77
  **Files to touch:**
77
78
 
78
79
  - `path/to/file`: reason
80
+
81
+ **Verification:**
82
+
83
+ - <outcome>: `<test or command that proves it>`
79
84
  ```
80
85
 
86
+ Small mode carries no `**Review focus:**`, since it already requires Risks to come out `None identified.` and a change with no risk leaves the reviewer no breaking input to name.
87
+
81
88
  If real questions exist, include a numbered `**Questions:**` section below, each with a `- Suggested:` line and an `- Answer:` slot:
82
89
 
83
90
  ```markdown
@@ -13,6 +13,8 @@ A finding also stops being true by argument rather than by a fix. A worker namin
13
13
 
14
14
  A request written under `## For the reviewer` also reached no reader. The authoring standard names it as what the reviewing session should confirm, and nothing on this side read the section by that name, so a branch author's question sat in the body until this skill answered it.
15
15
 
16
+ Scoping a later pass off GitHub's review stamp skipped real work. On 2026-09-07 an author pushed between a pass's read and its post, the stamp moved onto a commit that pass never saw, and the next pass scoped its delta past it and skipped a genuine fix. A closed review invites a second close-out too: on 2026-08-28 two passes over commits that raised nothing each posted a comment repeating the verdict above it, and the operator caught the pair rather than any check.
17
+
16
18
  ## Must
17
19
 
18
20
  - Post until the review closes. A first pass opens against the whole change, and each later pass checks whether the prior findings landed.
@@ -26,6 +28,7 @@ A request written under `## For the reviewer` also reached no reader. The author
26
28
  - Key the body file on the pull request number and the head commit, and on the response it answers once the head repeats, so no two passes overwrite each other
27
29
  - Scan the comment for banned characters and internal phase labels before posting, since a finding phrased against a phase label reaches a reader with no task board
28
30
  - Answer a `## For the reviewer` bullet in the body, bounded to the section itself rather than the Summary or the Technical Context around it, so the independent vantage stays clear of the author's framing
31
+ - Confirm each `**Review focus:**` item the plan names on a first pass, and turn one the pass cannot confirm into a finding, since the planning session named the breaking inputs before the diff existed and no other reader holds that list
29
32
 
30
33
  ## Must not
31
34
 
@@ -47,7 +47,7 @@ Read these in parallel from the project root, skipping any that do not exist:
47
47
  - `canon/REQUIREMENTS.md`: feature scope and non-goals
48
48
  - `canon/ARCHITECTURE.md`: technical design decisions
49
49
  - `.canon/tasks/priority.md`: where this feature sits on the board and what each neighboring row waits on. Resolve this one at the main worktree root the way `session-worktree` does, since the board is gitignored and a linked worktree holds no copy of it
50
- - `.canon/plans/feature-<slug>.md` for the branch, when present: the intent the PR should satisfy
50
+ - `.canon/plans/feature-<slug>.md` for the branch, when present: the intent the PR should satisfy, and its `**Review focus:**` section as the inputs this pass must confirm the diff handles
51
51
 
52
52
  Coding standards from `.claude/rules/` are auto-loaded by Claude Code.
53
53
 
@@ -63,7 +63,7 @@ The fields are `<prior-oid>` off `commit`, `<prior-heading>` off `heading`, and
63
63
 
64
64
  `source` says which stamp answered. `marker` is the pass's own read-time record and is the authority. `fallback` is a pass posted before this mechanism shipped, so its commit is whatever the head was when GitHub recorded the review rather than what that session read, and a push inside its compose window is invisible. `none` is a thread carrying no pass at all.
65
65
 
66
- Do not read `commit.oid` or `submittedAt` off `gh pr view --json reviews` here. Both are stamped at submission, so a push landing between a pass's read and its post moves them onto a commit that pass never saw, and this step then scopes the delta past it and reports it covered. That fired for real on a pull request in this toolkit on 2026-09-07, and what it skipped was a genuine fix.
66
+ Do not read `commit.oid` or `submittedAt` off `gh pr view --json reviews` here. Both are stamped at submission, so a push landing between a pass's read and its post moves them onto a commit that pass never saw, and this step then scopes the delta past it and reports it covered.
67
67
 
68
68
  A target whose CLI predates the verb meets a missing subcommand rather than a record. Fall back there to the jq below, which reads the stamps and carries the defect above, and say the fallback answered so a reader can tell a marker read from a stamped one:
69
69
 
@@ -119,7 +119,7 @@ A non-empty result carries the comment id Step 4 needs for the third filename se
119
119
 
120
120
  A `## Post-review findings` reply carries no argued finding behind it, since it asserts a new defect rather than answering one, and this pass is its first independent reader. Restating it as a finding without opening anything is repeating the worker's claim rather than checking it. Read the file the comment names at `<headRefOid>`, the same `git show <headRefOid>:<path>` read Step 3 already runs to confirm a ticked box, and confirm the defect before it becomes a finding of this pass's own.
121
121
 
122
- A moved head has its own way to add nothing, which the ancestor test cannot see. When `<prior-heading>` reads `## Review closed`, the standing verdict already reports the branch clear, so a pass over the new commits that raises nothing posts a comment saying what the one above it said. Two close-outs landed that way on 2026-08-28, and the operator caught the pair rather than any check.
122
+ A moved head has its own way to add nothing, which the ancestor test cannot see. When `<prior-heading>` reads `## Review closed`, the standing verdict already reports the branch clear, so a pass over the new commits that raises nothing posts a comment saying what the one above it said.
123
123
 
124
124
  The producing shape is narrow. A prior pass tells the author a change is their own call, the author makes it, and the delta reaching the next pass has nothing left to say by construction.
125
125
 
@@ -157,6 +157,8 @@ Read `## For the reviewer` the same way, bounded to the bullets under that headi
157
157
 
158
158
  Answer each bullet in the body, on the same terms as a Testing question: no severity, no count. A request nobody can answer stays owed under the heading and dispatch Step 4 states, keeping the thread open exactly as an unanswered Testing box does. One the pass does answer carries no further weight, since the answer is discharged in the same comment that carries it.
159
159
 
160
+ On a first pass, confirm each `**Review focus:**` item the plan names against the diff, since the planning session named the inputs that would break the change before any code existed and no other reader holds that list. Report the items in a `**Review focus**` block after the `**For the reviewer**` block, one bullet per item naming what confirmed it, such as a test, a guard, or a branch in the code. The block carries no severity and enters no count. An item the pass cannot confirm becomes a `should-fix` finding under the file that should have handled it, so a reader can tell an item checked from one skipped. A plan whose section reads `None identified.`, or a branch with no plan, takes no block. The section binds the first pass only: a later pass checks whether the finding an unconfirmed item became has landed, like any other prior finding, and never re-raises a confirmed item against a delta that did not touch it.
161
+
160
162
  Apply the high-signal filter: flag only what will cause incorrect behavior, break a documented rule, or mislead a downstream feature. If uncertain, do not flag.
161
163
 
162
164
  A later pass applies the same axes to the delta, and adds one check the first pass cannot make: did each prior finding land, and did the fix regress anything it touched. Findings of its own are normal findings, stated at the same severity and counted the same way. That count is one of the two things Step 4 reads to pick the heading, so a pass raising a finding of its own is not a close-out at any severity.
@@ -43,6 +43,8 @@ Cost never enters the decision to widen a wave. The rule weighing how many track
43
43
 
44
44
  The session also records nothing of what it learns. Both other callers of memory capture are ship-chain skills and this one never ships, so the session taking every operator correction is the session with no moment that writes one down. Hanging that moment on the merge sweep answers it and bills the operator a capture pass per batch of merges while nothing is being built, which is a cost paid on the days shipping is fastest.
45
45
 
46
+ Dispatch parts from what it checked in three measured ways. The gate and the worker each derived the branch from their own reading of one plan until 2026-09-06: one run checked `docs/remaining-skill-verdicts` against a worker that took `docs/skill-verdicts-decide`, another checked `fix/path-form-hook` against `feat/path-form-hook`, and four dispatches on 2026-09-05 produced three strings for one plan. A launched worker inherits its launcher's model rather than the configured default, measured on 2026-08-27 with `~/.claude/settings.json` set to `sonnet` while both dispatched workers ran `claude-opus-5`. A cleared file set does not hold either, since three tracks in the wave of 2026-09-08 crossed a set they had cleared against and every one merged clean.
47
+
46
48
  ## Must
47
49
 
48
50
  - Read the priority file for execution order, since the index sorts by filename and states no order
@@ -129,7 +129,7 @@ What the worker owes on its own side is stated in the `role-worker` skill that s
129
129
 
130
130
  What arrives there does not become a record by being read, so place it by what it changes. An answer that settles a finding goes onto the pull request through the next pass, which withdraws or regrades that finding and names the fact behind it, per `review-pr`. An answer that changes what this session believes about the world instead, which is a mapping correction or a constraint on what a worker can do, settles no finding and reaches no thread, so route it the way Boundaries below routes a change found while orchestrating, which lands it on the task owning the surface it describes. Writing a tracked file to hold either is forbidden here, which leaves the pull request and the board as the two surfaces this session writes.
131
131
 
132
- Step 1 splits a rule that used to hold every plan in this session, and the evidence narrows it rather than retiring it. Two trials on 2026-08-31 put a cold planner on four rows, and it reported ten things the task files got wrong, corrected this session's own premise twice, and overturned one row's closing conclusion. What that measures is finding quality. The rule's own claim is that a warm plan front-loads reasoning a cold worker would otherwise re-derive, which is a statement about a plan's downstream value, and no plan from either trial has been built. So the per-row measurement goes cold on the evidence and the cross-feature call stays here on the boundary the same trials confirmed, which is that a planner reading the board sees blockers and file sets and can write a confident merge order off a partial picture.
132
+ Step 1 splits a rule that used to hold every plan in this session, and the evidence narrows it rather than retiring it. Two trials put a cold planner on four rows, and it reported ten things the task files got wrong, corrected this session's own premise twice, and overturned one row's closing conclusion. What that measures is finding quality. The rule's own claim is that a warm plan front-loads reasoning a cold worker would otherwise re-derive, which is a statement about a plan's downstream value, and no plan from either trial has been built. So the per-row measurement goes cold on the evidence and the cross-feature call stays here on the boundary the same trials confirmed, which is that a planner reading the board sees blockers and file sets and can write a confident merge order off a partial picture.
133
133
 
134
134
  A plan written here is written against a tree several branches are already changing, so it names the file set of every track in flight as a constraint, one set per track, read from the Touches column of that track's row. State for each set which of the two acts it forbids, per Constraints in `${CLAUDE_SKILL_DIR}/../../standards/plan.md`. A bare path list leaves the worker guessing, which is how a plan ends up forbidding the repair of a citation the change broke.
135
135
 
@@ -259,7 +259,7 @@ or more open pull requests awaiting a first pass. Either one makes review the
259
259
  bottleneck every track is waiting on, and both answer the same way: dispatch the
260
260
  narrow re-review and keep the first pass here.
261
261
 
262
- Three is the operator's number, set by hand on 2026-08-31 and marked as such so a
262
+ Three is the operator's number, set by hand and marked as such so a
263
263
  measurement replaces it rather than argues with it. It is calibrated against a
264
264
  session that ran five workers across two waves and reviewed five pull requests in
265
265
  one stretch, several of them twice, where a sixth track was declined on judgment
@@ -16,9 +16,9 @@ Run `canon tasks plan-branch <plan> --json` against the row's plan file and read
16
16
 
17
17
  Branch on the record rather than on the exit code, which a shell function wrapping `canon` can flatten to zero.
18
18
 
19
- The worker calls the same verb on the same plan at `auto-ship` Step 0, so the branch this gate checks and the branch that session takes are one string by construction rather than two readings of one paragraph. They were two readings until 2026-09-06. One run checked `docs/remaining-skill-verdicts` against a worker that took `docs/skill-verdicts-decide`, another checked `fix/path-form-hook` against a worker that took `feat/path-form-hook`, and four dispatches on 2026-09-05 produced three strings for one plan. A check against a branch nobody uses verifies nothing.
19
+ The worker calls the same verb on the same plan at `auto-ship` Step 0, so the branch this gate checks and the branch that session takes are one string by construction rather than two readings of one paragraph. Two readings of one plan part on the slug and on the type alike, and a check against a branch nobody uses verifies nothing.
20
20
 
21
- The type the verb reports is fixed at `feat` whatever the row does, which is the half of the derivation that disagreed most. What makes that safe is that a branch type is cosmetic: `git-stage` reads a commit's type off the staged diff and `git-pr` reads a title off the diff, so nothing a release reads passes through the branch name. What it costs is a worktree listing where every dispatched branch reads `feat/`, which a person scanning one loses. Nothing renames it later, and this paragraph said `git-branch` did until 2026-09-06, when its conventions guard turned out to fire on a conforming `feat/` before reaching any type judgment.
21
+ The type the verb reports is fixed at `feat` whatever the row does, which is the half of the derivation that disagreed most. What makes that safe is that a branch type is cosmetic: `git-stage` reads a commit's type off the staged diff and `git-pr` reads a title off the diff, so nothing a release reads passes through the branch name. What it costs is a worktree listing where every dispatched branch reads `feat/`, which a person scanning one loses. Nothing renames it later, since `git-branch`'s conventions guard fires on a conforming `feat/` before reaching any type judgment.
22
22
 
23
23
  ## Check the plan waits on nobody
24
24
 
@@ -34,7 +34,7 @@ Branch on `launchable` rather than on the exit code, which a shell function wrap
34
34
 
35
35
  This gate runs ahead of the two collision checks because it is the cheapest reading of the three, needing no roster and no ref, and because it is the only one asking about the row itself rather than about what else is in flight. A row nobody can launch does not need testing against the tracks already out.
36
36
 
37
- It also reads the plan rather than a cell describing one, which is the input the gate below it does not have. The disjointness gate compares the sets a dispatcher wrote into the constraints and the Touches column, so a cell omitting a file clears a check the tree would fail. That happened on 2026-08-31, when two rows were cleared against each other with one constraints block leaving out the context entry both were about to write, and what caught it was a worker sending a message rather than any check.
37
+ It also reads the plan rather than a cell describing one, which is the input the gate below it does not have. The disjointness gate compares the sets a dispatcher wrote into the constraints and the Touches column, so a cell omitting a file clears a check the tree would fail, and what catches it then is a worker's message rather than any check.
38
38
 
39
39
  A blank `- Answer:` is not an unanswered question. `${CLAUDE_SKILL_DIR}/../../standards/plan.md` fixes an empty slot as accepting the `- Suggested:` line above it, which is what makes a plan decision-ready in one pass. The narrow case this reads is `- Suggested: needs your call, <why>` and its two demonstrated paraphrases, `needs operator's call` and `needs the operator's call`, over an empty slot, the form that same standard writes where the answer turns on preference rather than on a technical default. A gate reading every blank slot as open would refuse every plan in the folder.
40
40
 
@@ -66,19 +66,19 @@ No count binds this. List the files the candidate's plan touches, from its `**Fi
66
66
 
67
67
  The board is not the whole set. A track a person launched by hand carries no row, so that column cannot see it, which is the ordinary shape whenever the operator is launching rather than dispatching. Read `canon sessions list --json` for the branches in flight, and take the file set of any branch no row names from the plan that branch is building. A candidate cleared against the board alone is cleared against a partial reading.
68
68
 
69
- Take the comparison at the file path rather than at a folder above it. `canon tasks validate` compares the paths each row wrote, so a collision it reports on a folder means a row's Touches cell claimed that folder rather than the verb widening anything. On 2026-08-28 it called two rows colliding on `src` because one cell named the bare folder while the other wrote `src/markdown/structure.ts`, which this paragraph once misread as the verb comparing path segments too coarsely.
69
+ Take the comparison at the file path rather than at a folder above it. `canon tasks validate` compares the paths each row wrote, so a collision it reports on a folder means a row's Touches cell claimed that folder rather than the verb widening anything. A cell naming a bare folder collides with every row writing a file under it, which reads as the verb comparing path segments too coarsely and is not.
70
70
 
71
71
  The finding names which row contributed the containing path, and a bare-folder cell reports as a claim of its own beside the findings. Read that output as a candidate list, settle each pair by file, and narrow the cell that over-claimed rather than discounting the collision it caused.
72
72
 
73
- A declared set is what a branch sets out to write rather than a bound on it, so this gate clears against a prediction and the branch outgrows it hours later. Three tracks crossed a set they had cleared against in the wave of 2026-09-08 and every one merged clean, which is a gate reporting disjoint while a real overlap stood. Two classes account for nearly all of it. The ship chain's own steps write past every plan, since `canon:docs-fold` refreshes whichever context entry a change reaches and `canon:docs-sync` reaches the public docs, and neither surface is one a planner can name before the change exists. The drift stages `bun run check` regenerates and asserts are the second, together with the test and sandbox siblings a source change drags in. Nothing here prevents either, because the files at issue are written long after this gate clears. What reads the other end is `canon tasks plan-reach`, which `canon:git-ship` runs at step 5 against the branch's own diff, so a crossing this gate could not see is named before the pull request opens rather than after it merges.
73
+ A declared set is what a branch sets out to write rather than a bound on it, so this gate clears against a prediction and the branch outgrows it hours later. A track can cross the set it cleared against and still merge clean, which is a gate reporting disjoint while a real overlap stood. Two classes account for nearly all of the crossing. The ship chain's own steps write past every plan, since `canon:docs-fold` refreshes whichever context entry a change reaches and `canon:docs-sync` reaches the public docs, and neither surface is one a planner can name before the change exists. The drift stages `bun run check` regenerates and asserts are the second, together with the test and sandbox siblings a source change drags in. Nothing here prevents either, because the files at issue are written long after this gate clears. What reads the other end is `canon tasks plan-reach`, which `canon:git-ship` runs at step 5 against the branch's own diff, so a crossing this gate could not see is named before the pull request opens rather than after it merges.
74
74
 
75
- Disjointness is necessary and not sufficient, so hold a candidate whose sets do not touch when a stated reason serializes it, and write the reason on the hold. One row creating a skill and another auditing that catalog and counting it write nothing in common, measured 2026-08-27, and dispatching both still leaves the audit counting a denominator that moves underneath it. Nothing verifies that a reason was written, so the rule holds only while the dispatcher applies it.
75
+ Disjointness is necessary and not sufficient, so hold a candidate whose sets do not touch when a stated reason serializes it, and write the reason on the hold. One row creating a skill and another auditing that catalog and counting it write nothing in common, and dispatching both still leaves the audit counting a denominator that moves underneath it. Nothing verifies that a reason was written, so the rule holds only while the dispatcher applies it.
76
76
 
77
77
  What binds past that is review attention rather than a count, and `## Parallelism` in the skill body states it along with the cap an operator can set for a session. The one number this skill carries is the review fallback's count of three in `## Parallelism`, which moves a review rather than binding a track, and this runbook carries none.
78
78
 
79
79
  ## Pick the model
80
80
 
81
- A `claude --bg` session inherits the model of whatever launched it rather than reading the machine's configured default. That was measured on 2026-08-27, with `~/.claude/settings.json` set to `sonnet` while both dispatched workers ran `claude-opus-5`. An orchestrator on the larger model therefore spends it on every worker it launches, and the operator who set the default never sees the override.
81
+ A `claude --bg` session inherits the model of whatever launched it rather than reading the machine's configured default. An orchestrator on the larger model therefore spends it on every worker it launches, and the operator who set the default never sees the override.
82
82
 
83
83
  Name `<model>` on the launch, and pick it against the task rather than copying whatever this session happens to run. Sizing the model to the row is the dispatcher's call, the same call it already makes on the branch. A mechanical row moving files under a written plan is not the row that needs the largest model, and one whose plan carries an open judgment is.
84
84
 
@@ -89,9 +89,9 @@ claude --bg --model <model> -n "worker-<project>-<slug>" "/canon:auto-ship <plan
89
89
  Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id through canon sessions list --json, which carries sessionId per row, at the moment you send, and never resolve an addressee by name prefix. Message it when the pull request opens, carrying the number, the branch, the head sha, the CI state, and every point you departed from the plan on, and message it again if you stop on a question."
90
90
  ```
91
91
 
92
- `--bg, --background` starts the session as a background agent and returns immediately, `-n, --name` sets the display name that tells a self-dispatched worker from an operator's own launch in `canon sessions list`, and `--model` overrides the inheritance the section above measured. Pass `-n` on every dispatch rather than letting the client derive one. A launch that omits it leaves the session named for a fragment of its own identifier, which is both its address on the send channel and the whole of what the operator sees for it in agent view.
92
+ `--bg, --background` starts the session as a background agent and returns immediately, `-n, --name` sets the display name that tells a self-dispatched worker from an operator's own launch in `canon sessions list`, and `--model` overrides the inheritance the section above states. Pass `-n` on every dispatch rather than letting the client derive one. A launch that omits it leaves the session named for a fragment of its own identifier, which is both its address on the send channel and the whole of what the operator sees for it in agent view.
93
93
 
94
- The prefix reads `worker-` because that is the role it marks. It read `orchestrator-` until 2026-08-31, and no controlling session ever carried it, so a worker filtering the roster for that string found a sibling or itself on every row. Nothing matches the prefix programmatically, which is what kept the rename down to three strings.
94
+ The prefix reads `worker-` because that is the role it marks. A prefix naming a role the session does not hold sends a worker filtering the roster for that string to a sibling or itself on every row. Nothing matches the prefix programmatically, which keeps a rename down to three strings.
95
95
 
96
96
  `<project>` is the basename of the main worktree root, not of wherever the dispatcher happens to be running. Resolve the main root first, the way `session-worktree` Step 1 does, since a bare `git rev-parse --show-toplevel` inside a linked worktree returns the worktree path rather than the project's.
97
97
 
@@ -118,7 +118,7 @@ model, which invokes it through the `Skill` tool, and that route answers a
118
118
  flagged skill inconsistently. `auto-ship` has carried the flag since early in its life, and seven other
119
119
  shipped skills carry it too.
120
120
 
121
- Three launches on 2026-08-31 and 2026-09-02 bound what makes a command take
121
+ Three launches bound what makes a command take
122
122
  that route. Observation A is the first refused worker, launched as `Run
123
123
  /canon:session-worktree ..., then /canon:auto-ship ...`, which expanded
124
124
  nothing. Observation B is a re-dispatch launched as `/canon:auto-ship
@@ -138,8 +138,8 @@ at no extra cost. Until it fails, the operational rule is the conjunction the
138
138
  three observations support: put the flagged command at position zero, followed
139
139
  by a space and its argument, with nothing before it.
140
140
 
141
- Four sessions made the same tool call against the same plugin cache on
142
- 2026-08-31. Two were answered with the body and shipped, and two were refused
141
+ Four sessions made the same tool call against the same plugin cache on one
142
+ day. Two were answered with the body and shipped, and two were refused
143
143
  with `Skill canon:auto-ship cannot be used with Skill tool due to
144
144
  disable-model-invocation`. Prefixing separated nothing, since three of the
145
145
  four carried the namespace and those three landed on both answers, so nothing
@@ -168,7 +168,7 @@ The same collapse reaches a human relay rather than a `claude --bg` string. A
168
168
  controller that hands an operator two chained blocks to paste as separate
169
169
  messages risks both landing as one, where everything after the first
170
170
  command's name is read as that command's own argument and the second command
171
- never fires, measured four times out of four on 2026-09-02.
171
+ never fires, measured four times out of four.
172
172
  A slash command expands as a user invocation only at position zero of a
173
173
  prompt, and a later one in the same text reaches the session as prose instead.
174
174
  The fix is what the template above already takes: one message, one command,
@@ -232,7 +232,7 @@ nothing the check can see.
232
232
 
233
233
  `plan-feature` is a procedure rather than a role, so a launch naming it alone
234
234
  reaches no `role-planner` and takes no role, which owes no message either.
235
- Both trials on 2026-08-31 ran on prose the controller retyped into each launch,
235
+ Both trials of this shape ran on prose the controller retyped into each launch,
236
236
  which held every obligation those sessions took and is where the first one's
237
237
  in-flight read went wrong. Reach the role directly on this launch, the way the
238
238
  build shape above reaches `role-worker`.
@@ -27,7 +27,7 @@ The read is the report this session is holding. Retire a session whose delivery
27
27
 
28
28
  ### The act, and the order it runs in
29
29
 
30
- `claude rm <id>` is meant to remove a background session and its worktree together, though its own report is not reliable, so `canon worktrees list` is the reading to trust afterward. It takes one target per call, so a wave costs one call per session. The argument is the id rather than the name, which `claude agents --json` carries beside the name on every row, so a session read off a roster by name is matched to its id there before the call. Both readings are from 2026-09-02, where the name form answered `No job matching`.
30
+ `claude rm <id>` is meant to remove a background session and its worktree together, though its own report is not reliable, so `canon worktrees list` is the reading to trust afterward. It takes one target per call, so a wave costs one call per session. The argument is the id rather than the name, which `claude agents --json` carries beside the name on every row, so a session read off a roster by name is matched to its id there before the call. The name form answers `No job matching`.
31
31
 
32
32
  The command belongs to the client rather than to this toolkit, so a target running another client performs whatever removal that client offers.
33
33
 
@@ -70,13 +70,13 @@ The state reaches every stalled dispatch, since one threshold governs the headin
70
70
 
71
71
  The report is also where the count in `## Parallelism` is legible. That threshold trips on open pull requests awaiting a first pass, which is what `OPENED` and a pull request with no prior pass name here and what `SEEN` excludes, so read the count off these lines rather than off `gh pr list`, which counts a branch closed out and waiting on a merge the same as one nobody has read. It is a separate condition from the poll-start fallback above, which decides when this loop runs rather than where a review runs.
72
72
 
73
- The count used to read low, and it erred in the direction that breaks the trigger. A review's `commit.oid` is stamped with the head at submission rather than with the commit the reviewer read, so an author pushing between the diff read and the post left the pass recorded against a commit it never saw, and `SEEN` then fired on a head still awaiting its first look at that delta. That fired for real on a pull request in this toolkit on 2026-09-07, and the delta it skipped was a genuine fix.
73
+ The count used to read low, and it erred in the direction that breaks the trigger. A review's `commit.oid` is stamped with the head at submission rather than with the commit the reviewer read, so an author pushing between the diff read and the post left the pass recorded against a commit it never saw, and `SEEN` then fired on a head still awaiting its first look at that delta.
74
74
 
75
75
  The pass now carries its own record instead. `review-pr` writes the commit it read and the instant it read it as a marker on the last line of every body it posts, and both this script and that skill resolve the covered state through `canon pr review-state`, which is the one place the marker is parsed. A commit pushed inside a pass's compose window falls outside the marked range, so it reads as `MOVED` rather than as `SEEN`. The manual double-check that used to hang off this paragraph is retired with the defect: a `SEEN` is now a claim about what a session read rather than about what GitHub stamped, so it wants no second read to be believed.
76
76
 
77
77
  Read `source` on the record before trusting a `SEEN` on a thread whose newest pass is old. `marker` is the read-time record. `fallback` is a pass posted before the mechanism shipped, or a target whose CLI predates the verb, and it carries the defect above unchanged, so a `SEEN` under it is worth one `gh pr view --json reviews` before it is believed. `none` is a thread carrying no pass at all.
78
78
 
79
- The other side of that comparison used to lag as well, which made the two errors compound rather than cancel. `gh pr view --json headRefOid` answers from the pull request object and that object trails the branch ref by up to a minute after a push, reporting nothing about the trail, so a pass stamped ahead of the commit it read was compared against a head stamped behind the commit that exists. Two sessions were wrong off that field on 2026-09-01: a reviewing session posted a finding calling a pushed commit unpushed, and a worker fired a green claim it retracted on its own thread minutes later. The head now comes from `canon pr head`, which resolves the tip through `git ls-remote` and reports which commit each source names, and the object's head stays behind it as the fallback for a target whose CLI predates the verb. Both halves of the comparison now answer about a commit somebody named rather than one GitHub stamped, which is what lets a `SEEN` be read as covered.
79
+ The other side of that comparison used to lag as well, which made the two errors compound rather than cancel. `gh pr view --json headRefOid` answers from the pull request object and that object trails the branch ref by up to a minute after a push, reporting nothing about the trail, so a pass stamped ahead of the commit it read was compared against a head stamped behind the commit that exists. The head now comes from `canon pr head`, which resolves the tip through `git ls-remote` and reports which commit each source names, and the object's head stays behind it as the fallback for a target whose CLI predates the verb. Both halves of the comparison now answer about a commit somebody named rather than one GitHub stamped, which is what lets a `SEEN` be read as covered.
80
80
 
81
81
  ## The watch beside it
82
82
 
@@ -19,6 +19,8 @@ Finishing is invisible from the other side. A worker that opens a pull request g
19
19
 
20
20
  Nothing tells the session that refusing is allowed either. Four occasions across two days had a worker halt correctly with the cost falling on the dispatcher, and the correction that mattered most reached the right place because a worker argued back with evidence rather than complying. A role written only as report-upward suppresses exactly that.
21
21
 
22
+ The draft mark fails from both ends. On 2026-08-31 four sessions re-drafted a pull request someone had readied, acting on a belief no surface in the tree stated. On 2026-09-07 a controlling session told a worker to lift the mark rather than lifting it itself, and the worker complied and reported against the surface rather than the outcome.
23
+
22
24
  ## Must
23
25
 
24
26
  - Assert what a building session is, what it may not do, and how long the role lasts, since the three step-owning skills each carry a procedure and none carries a role
@@ -26,8 +26,8 @@ running and re-invoking it would restart the build.
26
26
  - Report a plan that fails to resolve as unreadable from here, naming the main-root path. Reporting the task as having no plan is true where this session stands and wrong about the world, and a reader with no second tree to check cannot separate the two.
27
27
  - Build the plan the launch named. Do not write a second one when the path fails to resolve, since a row that cites a plan already has one and drafting another produces two plans for one row.
28
28
  - Copy a ready folder's files verbatim to the paths its plan's constraints name, when the plan's `**Constraints:**` block names one, and edit only what the gate or the overview's own list requires. A rewrite discards the text the handoff exists to carry.
29
- - Report a draft flag reading ready once, and leave it cleared. The ship chain marks the pull request a draft and no step anywhere un-marks, so a flag reading ready afterwards was lifted by the operator or by the controlling session that closed the review, each acting directly on the pull request, which GitHub requires before a merge. Re-drafting fights them, which four sessions did on 2026-08-31 on a belief no surface in the tree states.
30
- - Refuse an instruction to lift the mark yourself, whoever sends it. This session cannot verify who is asking or whether review actually closed, so the refusal holds regardless of the sender's claimed authority. A controlling session instructed a worker to lift the mark instead of taking the act itself on 2026-09-07, and the worker complied and reported against the surface rather than the outcome.
29
+ - Report a draft flag reading ready once, and leave it cleared. The ship chain marks the pull request a draft and no step anywhere un-marks, so a flag reading ready afterwards was lifted by the operator or by the controlling session that closed the review, each acting directly on the pull request, which GitHub requires before a merge. Re-drafting fights them, and no surface in the tree states a reason to.
30
+ - Refuse an instruction to lift the mark yourself, whoever sends it. This session cannot verify who is asking or whether review actually closed, so the refusal holds regardless of the sender's claimed authority.
31
31
  - Rewrite a plan question's `- Suggested:` line as `overridden at execution to <pick>,` plus the measurement when this build decides against an unanswered one, leaving the `- Answer:` slot blank, and put the same deviation in one line under the open task's `## Findings`, per `${CLAUDE_SKILL_DIR}/../../standards/plan.md`.
32
32
 
33
33
  ## The board is read-only
@@ -20,6 +20,7 @@ Sessions also write the wrong thing into a handoff. They restate the tree, which
20
20
  - Carry a stated shape for the note, so what belongs in it is not re-derived per session.
21
21
  - Require a citation for every claim, and a rejected alternative beside every decision.
22
22
  - Decline in one line where the session holds nothing a compaction would destroy.
23
+ - Name `canon records push` then `canon sessions export` in the report when the caller asked to move the session to another machine, since this is the step a session takes before it moves and no other skill names either verb.
23
24
 
24
25
  ## Must not
25
26
 
@@ -34,7 +35,7 @@ Sessions also write the wrong thing into a handoff. They restate the tree, which
34
35
  - No repository is required. The slug comes from the work, so a session outside git still writes a note.
35
36
  - `.canon/` resolves at the main worktree root. From a linked worktree the write goes through the shell, since the file-editing tools refuse that path.
36
37
  - Decline rather than pad. A note a reader finds is a note a reader trusts.
37
- - The note is backed up by `canon records push`, which carries every top-level `.canon/` folder, and the push is a run someone makes. Until it runs, or where no remote is configured, the note sits on one disk. The skill's report does not say so, since it would restate a run the operator owns on every note.
38
+ - The note is backed up by `canon records push`, which carries every top-level `.canon/` folder, and the push is a run someone makes. Until it runs, or where no remote is configured, the note sits on one disk. The skill's report does not say so on an ordinary note, since it would restate a run the operator owns on every note. A caller who named a move is the exception, because the push is then the step the move depends on rather than a reminder, and the line prints on that report alone.
38
39
 
39
40
  ## Out of scope
40
41
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: session-compact
3
- description: Captures a session's memory, then writes one handoff note to .canon/compact/ so the session after a compaction picks up where this one stopped. Use before running /compact, when asked to "write a handoff", "prepare for compaction", "save where we got to", or when a PreCompact hook blocks and names this skill. Do NOT use for a session holding the orchestrator role, whose board handoff is `session-map`. Do NOT use to record a decision a groundwork track owns, which is `plan-groundwork`.
3
+ description: Captures a session's memory, then writes one handoff note to .canon/compact/ so the session after a compaction picks up where this one stopped. Use before running /compact, when asked to "write a handoff", "prepare for compaction", "save where we got to", or before moving a session to another machine, or when a PreCompact hook blocks and names this skill. Do NOT use for a session holding the orchestrator role, whose board handoff is `session-map`. Do NOT use to record a decision a groundwork track owns, which is `plan-groundwork`.
4
4
  ---
5
5
 
6
6
  # Session compact
@@ -36,8 +36,11 @@ Overwrite a note of the same name. One session's work has one note, and a second
36
36
  ```plaintext
37
37
  ✅ Handoff written: .canon/compact/<slug>.md
38
38
  <the line memory-capture returned, where a fact routed>
39
+ Move: canon records push, then canon sessions export <id>
39
40
  ```
40
41
 
42
+ Print the `Move:` line only when the caller named moving the session to another machine, and leave it off every other report. Fill `<id>` from the `sessionId` of `canon sessions list --self --json`, and leave the literal `<id>` where that read refuses, since an id taken from any other row packs the wrong session. The push comes first so the note travels with the session. Name both commands for the operator and run neither: an export packs a transcript that can hold credentials, so the order belongs to the caller.
43
+
41
44
  A decline reports itself so a caller can tell it from a failure:
42
45
 
43
46
  ```plaintext
@@ -57,4 +60,5 @@ A decline reports itself so a caller can tell it from a failure:
57
60
  - The memory pass, its routing, and the pen's shape: `canon:memory-capture`
58
61
  - The orchestrator's handoff, which carries a drift check and writes a task row: `canon:session-map`
59
62
  - Reading a handoff back at the start of the next session: `canon:session-resume`
63
+ - Packing the session for another machine: `canon sessions export`, with `canon sessions import` and then `/canon:session-resume` on the arriving side
60
64
  - A decision a groundwork track owns, which belongs in its own `06-decision.md`: `canon:plan-groundwork`
@@ -39,7 +39,7 @@ Routes by finding. `unmigrated` routes to a relocation the user runs, since no c
39
39
 
40
40
  Read `domains` off the report and take every entry it carries rather than naming governance here. Each entry carries `stamped`, per-state `counts`, and `entries` with a `state` and a `rel` path.
41
41
 
42
- The states are `matching`, `stale`, `customized`, `drifted`, `orphaned`, `stranded`, and `missing`.
42
+ The states are `matching`, `stale`, `customized`, `drifted`, `orphaned`, `retired`, `stranded`, and `missing`. A `retired` rule is one the toolkit no longer ships, and the next sync deletes it.
43
43
 
44
44
  - Current: every count but `matching` reads zero.
45
45
  - Behind: any other count is non-zero. Report the counts and name the paths behind `stale` and `missing`, which a sync repairs, separately from `customized` and `drifted`, which the project wrote and a sync would overwrite.
@@ -5,7 +5,7 @@ description: Which external writing patterns this skill adopted, which it declin
5
5
 
6
6
  # Source material
7
7
 
8
- Two external writing skills were read on 2026-08-19 as source material for this one. The first catalogs 31 patterns of machine-sounding prose. The second layers document mode, sentence construction, information density, and ambiguity prevention.
8
+ Two external writing skills were read as source material for this one. The first catalogs 31 patterns of machine-sounding prose. The second layers document mode, sentence construction, information density, and ambiguity prevention.
9
9
 
10
10
  Neither was imported wholesale, and one measurement settles why. Read this before adding a rule from outside this corpus, and extend the position below rather than deriving a second one.
11
11
 
@@ -24,7 +24,7 @@ The ambiguity rules travel with that layer for the same reason. Compression is w
24
24
  - **Any restatement of a banned word set.** The bans ship as package data that `canon markdown audit` reads, and a copy here would be a second source for one list with a machine reading the other.
25
25
  - **A voice register per surface.** One baseline covers every surface. A surface needing a warmer register states its own voice and claims the yield, which lifts voice alone and leaves the bans, the rhythm rules, and the tells in force.
26
26
 
27
- The first of those four rests on a count. The source names `surface` among the nouns it bans, and tracked markdown in the authoring repository used `surface` 593 times, `harness` 95, `scaffolding` 17, and `primitive` 4, measured on 2026-08-19. Adopting the item either rewrites established vocabulary across the corpus or is ignored in silence, and a reader arriving later cannot tell which happened.
27
+ The first of those four rests on a count. The source names `surface` among the nouns it bans, and tracked markdown in the authoring repository used `surface` 593 times, `harness` 95, `scaffolding` 17, and `primitive` 4. Adopting the item either rewrites established vocabulary across the corpus or is ignored in silence, and a reader arriving later cannot tell which happened.
28
28
 
29
29
  ## Where this diverges
30
30
 
@@ -70,7 +70,7 @@ Full help: `canon <command> --help`. Bare `canon --help` lists every top-level c
70
70
  | `canon worktrees list` | Report which worktrees are reclaimable, keyed on the pull request having merged, with every refusal and the removal route named (`--json`) |
71
71
  | `canon worktrees reclaim` | Remove every reclaimable worktree and the branch behind it, reporting without acting under `--dry-run` (`--json`) |
72
72
  | `canon comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
73
- | `canon context audit` | Report required sections, length, cited paths, reference form, catalog tables, provenance, superseded-decision narration, index drift, the architecture record against its own ceiling and entry cap and its word weight, and wireframe states against their evidence folders |
73
+ | `canon context audit` | Report required sections, length, cited paths, reference form, catalog tables, provenance, index drift, the architecture record against its own ceiling and entry cap and its word weight, and wireframe states against their evidence folders |
74
74
  | `canon context classify diff` | Classify the chunks a git range changed, each with its enclosing section, as keep, replace, history, or move (`--base`, `--doc-types`, `--json`) |
75
75
  | `canon context classify sweep` | Classify every section of the five canonical doc types, split at H3, as keep, rewrite, or move (`--doc-types`, `--json`) |
76
76
  | `canon context classifier show` | Report the resolved classifier backend and model and which source decided them (`--json`) |