@erclx/canon 4.86.0 → 4.88.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 (52) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/auto-ship/SKILL.md +6 -6
  3. package/claude/skills/canon-feedback-file/SKILL.md +2 -2
  4. package/claude/skills/design-extract/SKILL.md +3 -3
  5. package/claude/skills/docs-fold/SKILL.md +6 -6
  6. package/claude/skills/draft-and-pick/SKILL.md +1 -1
  7. package/claude/skills/draft-diagram/SKILL.md +2 -2
  8. package/claude/skills/draft-slides/SKILL.md +1 -1
  9. package/claude/skills/git-followup/SKILL.md +1 -1
  10. package/claude/skills/git-pr/SKILL.md +8 -8
  11. package/claude/skills/git-split/SKILL.md +19 -19
  12. package/claude/skills/memory-capture/SKILL.md +2 -2
  13. package/claude/skills/memory-review/SKILL.md +2 -2
  14. package/claude/skills/plan-groundwork/SKILL.md +1 -1
  15. package/claude/skills/review-address/SKILL.md +11 -11
  16. package/claude/skills/review-branch/SKILL.md +3 -3
  17. package/claude/skills/review-pr/SKILL.md +2 -2
  18. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +1 -1
  19. package/claude/skills/role-orchestrator/scripts/poll.sh +1 -1
  20. package/claude/skills/sketch-design/SKILL.md +4 -4
  21. package/claude/skills/teach-workspace/SKILL.md +2 -2
  22. package/claude/skills/ui-test/REQUIREMENT.md +1 -1
  23. package/claude/skills/ui-test/SKILL.md +2 -2
  24. package/docs/agents/commands.md +99 -96
  25. package/docs/agents/design-board.md +7 -7
  26. package/docs/agents/records.md +27 -0
  27. package/docs/agents/sandbox.md +1 -1
  28. package/docs/workflow/ai-workflow.md +5 -3
  29. package/docs/workflow/visual-design-workflow.md +1 -1
  30. package/governance/rules/core/055-scratch.md +1 -0
  31. package/package.json +1 -1
  32. package/scripts/tooling/verify.sh +2 -2
  33. package/src/claude/skills-headings.ts +1 -1
  34. package/src/cli.ts +1 -1
  35. package/src/commands/design.ts +3 -3
  36. package/src/commands/feedback.ts +12 -12
  37. package/src/commands/migrate.ts +36 -13
  38. package/src/commands/records.ts +159 -0
  39. package/src/commands/slides.ts +2 -2
  40. package/src/design/board.ts +17 -10
  41. package/src/migrate/evidence-ordinal.ts +79 -0
  42. package/src/migrate/record-layout.ts +328 -75
  43. package/src/migrate/scratch-evidence.ts +60 -35
  44. package/src/record-root.ts +3 -0
  45. package/src/records/prune.ts +488 -0
  46. package/src/records/size.ts +24 -1
  47. package/standards/publish.md +1 -1
  48. package/standards/skill.md +1 -1
  49. package/tooling/claude/seeds/.claude/hooks/index-reminder.sh +2 -2
  50. package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +2 -2
  51. package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +2 -2
  52. package/tooling/claude/seeds/CLAUDE.md +2 -6
@@ -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.86.0",
4
+ "version": "4.88.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -121,7 +121,7 @@ The verb ships with the CLI and this body ships with the plugin, matching Step 6
121
121
 
122
122
  If the diff touches UI files (JSX, TSX, Vue, Svelte, HTML, or CSS under `src/`), invoke `canon:ui-test`.
123
123
 
124
- If `ui-test` produces a manual checklist, stop: `❌ UI requires visual verification. Checklist at .canon/tmp/ui-checklist/<slug>.md, which reaches the pull request once /git-ship runs. Verify manually, then run /git-ship.`
124
+ If `ui-test` produces a manual checklist, stop: `❌ UI requires visual verification. Checklist at .canon/tmp/handoff/ui-checklist/<slug>.md, which reaches the pull request once /git-ship runs. Verify manually, then run /git-ship.`
125
125
 
126
126
  If all UI changes are covered by e2e tests, continue.
127
127
 
@@ -172,17 +172,17 @@ The verb reads the same set from `src/autoship/paths.ts`, so a path added here b
172
172
 
173
173
  ## Step 7: evaluate findings
174
174
 
175
- Skip this step when Step 6 skipped review. Otherwise read `.canon/review/branch/review-<slug>.md` at the main worktree root. Split every finding by origin before parsing the summary line (`X critical, Y should-fix, Z minor`), since the stop exists for a defect the branch inherited rather than for one this run introduced.
175
+ Skip this step when Step 6 skipped review. Otherwise read `.canon/review/branch-<slug>.md` at the main worktree root. Split every finding by origin before parsing the summary line (`X critical, Y should-fix, Z minor`), since the stop exists for a defect the branch inherited rather than for one this run introduced.
176
176
 
177
177
  - **This run caused it, at any severity.** Fix it, re-run the Step 3 verify commands, re-read the fixed file against what the finding claimed, and continue. Do not report it as a stop and do not offer the fix as a choice, which is the same stop wearing a proposal.
178
- - **It predates this run, critical or should-fix.** Stop: `❌ Review found non-minor issues that predate this run. See .canon/review/branch/review-<slug>.md. Fix and run /git-ship.`
178
+ - **It predates this run, critical or should-fix.** Stop: `❌ Review found non-minor issues that predate this run. See .canon/review/branch-<slug>.md. Fix and run /git-ship.`
179
179
  - **It predates this run, minor only.** Continue. The minor findings stay in the on-disk review receipt. Fold any a reviewer needs into the PR's `## Technical Context`. Do not add a separate review-notes section to the PR body.
180
180
 
181
181
  Read origin as causation rather than authorship. Staleness this run induced in a file it never opened is a finding it caused, and the plan's "Files to touch" list scopes what the run builds rather than what it may repair.
182
182
 
183
- Bound the repair at one pass, the way Step 3 bounds verify. When that re-read shows the finding still standing, stop: `❌ A self-introduced finding survived one fix pass. See .canon/review/branch/review-<slug>.md. Fix and run /git-ship.`
183
+ Bound the repair at one pass, the way Step 3 bounds verify. When that re-read shows the finding still standing, stop: `❌ A self-introduced finding survived one fix pass. See .canon/review/branch-<slug>.md. Fix and run /git-ship.`
184
184
 
185
- This chain owns the receipt's lifetime, which is what makes the Output block's citation resolve on a run that reaches it. `docs-fold` used to delete the current slug's receipt while running under Step 8 below, so the closing line named a file the same run had already removed. That sweep now reaches only reports whose branch is gone, which collects this one a branch later rather than during the run that wrote it. The cost is one receipt per live branch left in `.canon/review/branch/`, bounded by the branch count rather than by the lifetime of the checkout.
185
+ This chain owns the receipt's lifetime, which is what makes the Output block's citation resolve on a run that reaches it. `docs-fold` used to delete the current slug's receipt while running under Step 8 below, so the closing line named a file the same run had already removed. That sweep now reaches only reports whose branch is gone, which collects this one a branch later rather than during the run that wrote it. The cost is one `branch-<slug>.md` receipt per live branch left in `.canon/review/`, bounded by the branch count rather than by the lifetime of the checkout.
186
186
 
187
187
  ## Step 8: ship
188
188
 
@@ -209,7 +209,7 @@ Respond with up to five lines:
209
209
 
210
210
  ```plaintext
211
211
  ✅ Autoshipped (<state>): <PR url>
212
- <N minor findings kept in .canon/review/branch/review-<slug>.md>
212
+ <N minor findings kept in .canon/review/branch-<slug>.md>
213
213
  <N facts routed to context entries>
214
214
  <N memories captured in .canon/memory/>
215
215
  <Memory proposal at .canon/memory/review/memory-review-<slug>.md>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: canon-feedback-file
3
- description: Format a paste-back report about something broken, missing, or off in canon and write it directly to the toolkit's `.canon/review/feedback/` folder via `canon feedback`. Use when asked to "send this to the toolkit", "report this to canon", "file toolkit feedback", or "give the toolkit feedback about X". Do NOT use for general complaints about other tooling, IDE issues, or in-project bugs that do not implicate canon surfaces.
3
+ description: Format a paste-back report about something broken, missing, or off in canon and write it directly to the toolkit's `.canon/feedback/` folder via `canon feedback`. Use when asked to "send this to the toolkit", "report this to canon", "file toolkit feedback", or "give the toolkit feedback about X". Do NOT use for general complaints about other tooling, IDE issues, or in-project bugs that do not implicate canon surfaces.
4
4
  ---
5
5
 
6
6
  # Canon feedback file
@@ -93,7 +93,7 @@ cat <<'EOF' | canon feedback
93
93
  EOF
94
94
  ```
95
95
 
96
- It writes to `.canon/review/feedback/feedback-<slug>-<ts>.md` and prints the absolute path on stdout. Report the printed path back to the user on its own line, in the form the project's instruction file sets under `## Output`.
96
+ It writes to `.canon/feedback/feedback-<slug>-<ts>.md` and prints the absolute path on stdout. Report the printed path back to the user on its own line, in the form the project's instruction file sets under `## Output`.
97
97
 
98
98
  If `canon` is not on PATH, fall back: print the block in chat and tell the user `📋 Copy the block above into a toolkit-repo session.`
99
99
 
@@ -39,7 +39,7 @@ On the source path, also read the UI surfaces matched in Step 1 plus `canon docs
39
39
 
40
40
  On the greenfield path, also read `canon/ARCHITECTURE.md` for platform, tech stack, and surface type. Do not scan `src/`, stylesheets, or UI modules. Step 1 already established they hold nothing.
41
41
 
42
- On the greenfield path, also check `.canon/review/evidence/*/design-handoff.md` for a file the `sketch-design` skill wrote, taking the most recently modified match when more than one exists. Read it when found. Its presence is what Step 4 traces cells from instead of proposing them.
42
+ On the greenfield path, also check `.canon/picks/*/design-handoff.md` for a file the `sketch-design` skill wrote, taking the most recently modified match when more than one exists. Read it when found. Its presence is what Step 4 traces cells from instead of proposing them.
43
43
 
44
44
  Run these reads in parallel. Do not speculatively recurse into every directory.
45
45
 
@@ -94,13 +94,13 @@ Write the filled template to `canon/DESIGN.md` from the project root. Then run:
94
94
  canon design render
95
95
  ```
96
96
 
97
- The command writes an HTML plus CSS preview to `.canon/review/design/`. Output the path in chat so the user can open it.
97
+ The command writes an HTML plus CSS preview to `.canon/tmp/render/design/`. Output the path in chat so the user can open it.
98
98
 
99
99
  ## Response format
100
100
 
101
101
  ```plaintext
102
102
  📝 Wrote canon/DESIGN.md
103
- 📝 Wrote .canon/review/design/index.html
103
+ 📝 Wrote .canon/tmp/render/design/index.html
104
104
 
105
105
  Ran the <source|greenfield> path. N cells marked `? verify`. Open the preview and confirm before committing.
106
106
  ```
@@ -149,7 +149,7 @@ Read `canon/context/index.md` at `pwd` to see which domain entries exist. Skip t
149
149
 
150
150
  Two sources feed this step, the same split Step 2 runs on. The diff carries what the repository changed. The routed facts carry what the session learned, which a diff cannot show.
151
151
 
152
- **Routed facts.** Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`, falling back to `latest` on an empty result, and read `.canon/tmp/memory-routing/<slug>.md` at the main worktree root. `memory-capture` writes it, one H2 per target entry naming the path, with the fact underneath. Fold each fact into the entry its heading names, which for a nested `canon/context/<domain>/index.md` heading is the sibling file the fact belongs under rather than the generated index itself. Then delete the handoff file so a later run does not fold it twice.
152
+ **Routed facts.** Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`, falling back to `latest` on an empty result, and read `.canon/tmp/handoff/memory-routing/<slug>.md` at the main worktree root. `memory-capture` writes it, one H2 per target entry naming the path, with the fact underneath. Fold each fact into the entry its heading names, which for a nested `canon/context/<domain>/index.md` heading is the sibling file the fact belongs under rather than the generated index itself. Then delete the handoff file so a later run does not fold it twice.
153
153
 
154
154
  This half is not diff-scoped and must not be. A gotcha a session hit while working is exactly the fact the diff never shows, and scoping it to changed files would drop the entries worth keeping. The handoff is a named input rather than a scan, so the reach stays bounded to what capture decided.
155
155
 
@@ -183,13 +183,13 @@ Write each updated entry immediately. Output one line per file, naming the path
183
183
 
184
184
  Add a line naming the handoff when one was consumed:
185
185
 
186
- `🧹 Folded: .canon/tmp/memory-routing/<slug>.md`
186
+ `🧹 Folded: .canon/tmp/handoff/memory-routing/<slug>.md`
187
187
 
188
188
  The base lint-staged config runs `canon indexes regen` on every committed `*.md`, so `canon/context/index.md` refreshes automatically on commit. No manual step needed.
189
189
 
190
190
  ## Step 8: fold promoted pages
191
191
 
192
- Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`, falling back to `latest` on an empty result, and read `.canon/tmp/teach-promotion/<slug>.md` at the main worktree root. `teach-workspace` writes it, one H2 per destination naming the path, with a source line under the heading and the page body in a fenced block below that. Read the body out of the fence rather than off the heading level, since a reference page carries headings of its own and only the fence separates them from the next destination. Skip this step silently when the file is absent, which is every run where nothing was promoted.
192
+ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`, falling back to `latest` on an empty result, and read `.canon/tmp/handoff/teach-promotion/<slug>.md` at the main worktree root. `teach-workspace` writes it, one H2 per destination naming the path, with a source line under the heading and the page body in a fenced block below that. Read the body out of the fence rather than off the heading level, since a reference page carries headings of its own and only the fence separates them from the next destination. Skip this step silently when the file is absent, which is every run where nothing was promoted.
193
193
 
194
194
  Each block is a page an operator already confirmed a destination for, so this step lands it rather than judging it again. Write to the destination the heading names, at `pwd` rather than at the main root, since every destination here is a tracked file that commits with the branch:
195
195
 
@@ -204,7 +204,7 @@ Output one line per page landed:
204
204
 
205
205
  Add a line naming the handoff when one was consumed:
206
206
 
207
- `🧹 Folded: .canon/tmp/teach-promotion/<slug>.md`
207
+ `🧹 Folded: .canon/tmp/handoff/teach-promotion/<slug>.md`
208
208
 
209
209
  Report a block left unfolded rather than dropping it:
210
210
 
@@ -220,11 +220,11 @@ Plans are not swept here. A plan is settled by the merge rather than by an outco
220
220
 
221
221
  ### Reviews
222
222
 
223
- Leave the current branch's review receipt where it is. `auto-ship` Step 6 keeps minor findings in `.canon/review/branch/review-<slug>.md` and its closing block hands the reader that path, so deleting it here removes the file the chain that invoked this skill is still citing. Seven runs recorded that collision across two days before a sandbox fixture asserted the receipt and could pass only on a run the chain stopped early.
223
+ Leave the current branch's review receipt where it is. `auto-ship` Step 6 keeps minor findings in `.canon/review/branch-<slug>.md` and its closing block hands the reader that path, so deleting it here removes the file the chain that invoked this skill is still citing. Seven runs recorded that collision across two days before a sandbox fixture asserted the receipt and could pass only on a run the chain stopped early.
224
224
 
225
225
  The body that writes a receipt owns its lifetime. This skill sweeps on behalf of whatever called it and has no way to read whether a file is still in use, where the chain that wrote this one cites it in its own output and knows. What reaps it is the branch sweep below, one branch later, once the branch it names is gone.
226
226
 
227
- Sweep the branch reports this session never opened. List `.canon/review/branch/review-*.md`, run the slug transform in `${CLAUDE_SKILL_DIR}/../../standards/slug.md` over every name `git branch --format='%(refname:short)'` prints, and delete a report whose slug matches none of them. Take the names from that format rather than from `git branch --list`, which marks the current branch with `* ` and a branch checked out in another worktree with `+ `, so a transform reading the marked lines as written turns a live branch into a slug nothing matches and sweeps a report a sibling worktree is still working from. A branch report is read once, by the session addressing it, and the durable record of what a review found is the comment `review-pr` posts on the pull request, so a report outliving its branch is holding nothing. Skipping this leaves them accumulating for the life of the checkout, since a slug is unique per feature and no later branch ever looks for one.
227
+ Sweep the branch reports this session never opened. List `.canon/review/branch-*.md`, run the slug transform in `${CLAUDE_SKILL_DIR}/../../standards/slug.md` over every name `git branch --format='%(refname:short)'` prints, and delete a report whose slug matches none of them. Take the names from that format rather than from `git branch --list`, which marks the current branch with `* ` and a branch checked out in another worktree with `+ `, so a transform reading the marked lines as written turns a live branch into a slug nothing matches and sweeps a report a sibling worktree is still working from. A branch report is read once, by the session addressing it, and the durable record of what a review found is the comment `review-pr` posts on the pull request, so a report outliving its branch is holding nothing. Skipping this leaves them accumulating for the life of the checkout, since a slug is unique per feature and no later branch ever looks for one.
228
228
 
229
229
  What that removes is a local-only review on a branch deleted before it opened a pull request. `review-branch` says so where a reader meets the report, and the sweep runs anyway rather than keeping every report against the one case, since nothing else ever clears them.
230
230
 
@@ -69,7 +69,7 @@ Put the choice to the operator through the structured question surface, since a
69
69
  1. Apply the winning arm to the real surface, in one change.
70
70
  2. Close out whatever document stated the decision as open, in the same change, naming the arm that won and the ones that stayed defensible. A pick that changes a surface and records nothing about why leaves the next reader to re-derive it from a diff. Skip this where nothing stated the decision.
71
71
  3. Batch-capture the final round's arm files, when `<dest>` is the scratch path: `canon capture <dest>/arms --selector <wrapper-class> --out <archive-dir>`, naming Step 2's chosen class. This is the directory-batch convention `draft-identity` Step 6 already uses.
72
- 4. Resolve `<archive-dir>` as `.canon/review/evidence/<slug>/` against the main worktree root, since shared session scratch resolves there rather than against a linked worktree this run happens to be building in. The capture is what keeps every arm past the pick, the losing ones included, as a durable revert record distinct from the live comparison page.
72
+ 4. Resolve `<archive-dir>` as `.canon/picks/<slug>/` against the main worktree root, since shared session scratch resolves there rather than against a linked worktree this run happens to be building in. The capture is what keeps every arm past the pick, the losing ones included, as a durable revert record distinct from the live comparison page.
73
73
  5. Delete `<dest>` and every file inside it, when `<dest>` is the scratch path, now that every arm sits at the durable path above. A variant left behind there is a second design nobody maintains.
74
74
  6. Leave `<dest>` in place when it is a live track's `evidence/<slug>/`: `plan-groundwork`'s write scope treats evidence as durable rather than as scratch a session may delete, and the arms already sit at a durable path there.
75
75
  7. Report `<dest>` as still standing when the scratch-path delete is refused, naming the path for the operator to remove, rather than closing on a report the tree contradicts. The pick is applied either way, so the run has done its work and the folder is what outlives it.
@@ -167,11 +167,11 @@ After a migration pass, add: `Converted .claude/DIAGRAMS.md into N entries. The
167
167
  If the user asks to export the diagrams (`export to svg`, `give me images`, `render to png`), run:
168
168
 
169
169
  ```bash
170
- mkdir -p .canon/review/diagrams && bunx -y @mermaid-js/mermaid-cli -i .canon/diagrams/<kind>.md -o .canon/review/diagrams/<kind>.png
170
+ mkdir -p .canon/tmp/render/diagrams && bunx -y @mermaid-js/mermaid-cli -i .canon/diagrams/<kind>.md -o .canon/tmp/render/diagrams/<kind>.png
171
171
  ```
172
172
 
173
173
  Export PNG by default. Swap the extension for `.svg` only when the user asks for vector, and never for the Step 5 verification path. The CLI writes one file per `mermaid` block, suffixing when an entry holds more than one. Export every entry when the user names no kind. Output line:
174
174
 
175
175
  ```plaintext
176
- 📝 Wrote N files to .canon/review/diagrams/
176
+ 📝 Wrote N files to .canon/tmp/render/diagrams/
177
177
  ```
@@ -40,7 +40,7 @@ The palette and type scale live in the CLI. Own the choices the source controls.
40
40
  Shell out to the CLI. It writes the deck and reports the path.
41
41
 
42
42
  ```bash
43
- canon slides render --source .claude/SLIDES.md --out .canon/review/slides
43
+ canon slides render --source .claude/SLIDES.md --out .canon/tmp/render/slides
44
44
  ```
45
45
 
46
46
  Pass `--variant light` or `--variant dark` to override the source variant for a one-off render. Pass `--open` to open the deck, and `--mirror <dir>` (or set `CANON_SLIDES_MIRROR`) to copy it into a synced folder.
@@ -31,7 +31,7 @@ A missing tracking ref is no longer a guard. An open pull request proves the bra
31
31
 
32
32
  5. Post the evidence comparison, on every invocation including `reply-owned`, since this comment is not the reply step 8 owns. Run `canon pr evidence <number> --json`, resolving `<number>` from `gh pr view --json number`, and read `reason` on the record rather than the exit code.
33
33
  - `no-evidence`: nothing changed under an `evidence/` segment this push. Say nothing and move on.
34
- - `ok`: write `body` to `.canon/tmp/pr-evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post the comment with `gh pr comment <number> --body-file <main-root>/.canon/tmp/pr-evidence/body-<number>.md` when the record carries no `commentId`, or edit the existing one in place with `gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -f body=@<main-root>/.canon/tmp/pr-evidence/body-<number>.md` when it does. Clean up the tmp file only after the call reports success.
34
+ - `ok`: write `body` to `.canon/tmp/pr/evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post the comment with `gh pr comment <number> --body-file <main-root>/.canon/tmp/pr/evidence/body-<number>.md` when the record carries no `commentId`, or edit the existing one in place with `gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -f body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md` when it does. Clean up the tmp file only after the call reports success.
35
35
  - Any other reason is one of the mirrored git refusals (`gh-missing`, `gh-failed`, `no-base`, `unreadable-tree`, `unreadable-changes`). Report it and continue without stopping the chain.
36
36
  6. Check for existing review comments: `gh api 'repos/{owner}/{repo}/pulls/<number>/comments' --jq 'length'`, resolving `<number>` from `gh pr view --json number`.
37
37
  7. Sync the body and title on every invocation, before the routing below decides on the reply.
@@ -148,18 +148,18 @@ if [ -n "$pr_labels" ]; then
148
148
  gh pr edit "$pr_number" --add-label "$pr_labels" >/dev/null ||
149
149
  printf 'Label apply failed. Create a missing label with: gh label create <name>\n' >&2
150
150
  fi
151
- rm -rf .canon/tmp/pr
151
+ rm -rf .canon/tmp/pr/body
152
152
  printf 'number=%s\nurl=%s\n' "$pr_number" "$pr_url"
153
153
  ```
154
154
 
155
155
  ### Post the UI checklist
156
156
 
157
- `ui-test` writes a manual checklist to `.canon/tmp/ui-checklist/<slug>.md` at the main worktree root when a change needs visual verification, with `<slug>` derived per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. This step is the file's sole consumer. Resolve the main root the way `session-worktree` does (`git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`) and check for the file there. A missing file means no checklist was produced, and there is nothing to post.
157
+ `ui-test` writes a manual checklist to `.canon/tmp/handoff/ui-checklist/<slug>.md` at the main worktree root when a change needs visual verification, with `<slug>` derived per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. This step is the file's sole consumer. Resolve the main root the way `session-worktree` does (`git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`) and check for the file there. A missing file means no checklist was produced, and there is nothing to post.
158
158
 
159
159
  When it exists, scan it against `${CLAUDE_SKILL_DIR}/../../standards/publish.md` before posting, the same as the pull request body above. Post it as its own comment on `<number>`, the number the final command above resolved, rather than folding it into the body, since a later push editing the body would overwrite checkboxes a reviewer already ticked:
160
160
 
161
161
  ```bash
162
- gh pr comment <number> --body-file <main-root>/.canon/tmp/ui-checklist/<slug>.md
162
+ gh pr comment <number> --body-file <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
163
163
  ```
164
164
 
165
165
  Run the cleanup below only once that call reports success. On a failure, stop and leave the file in place: a retry needs the checklist to still be there, and deleting it on a failed post loses the only copy with nothing landed on the pull request.
@@ -167,11 +167,11 @@ Run the cleanup below only once that call reports success. On a failure, stop an
167
167
  From a linked worktree the file-editing tools refuse a main-root path, so the cleanup goes out through `Bash` as two plain commands, the file and then the folder, rather than joined by `&&`, which is refused as compound:
168
168
 
169
169
  ```bash
170
- rm <main-root>/.canon/tmp/ui-checklist/<slug>.md
170
+ rm <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
171
171
  ```
172
172
 
173
173
  ```bash
174
- rmdir <main-root>/.canon/tmp/ui-checklist 2>/dev/null || true
174
+ rmdir <main-root>/.canon/tmp/handoff/ui-checklist 2>/dev/null || true
175
175
  ```
176
176
 
177
177
  The `rmdir` is a no-op when another branch's pending checklist still sits in the folder, which keeps this step from deleting a handoff that is not its own.
@@ -181,16 +181,16 @@ The `rmdir` is a no-op when another branch's pending checklist still sits in the
181
181
  Run `canon pr evidence <number> --json` against the number the pull request step above resolved. Read `reason` on the record rather than the exit code.
182
182
 
183
183
  - `no-evidence`: nothing changed under an `evidence/` segment. Say nothing and move on.
184
- - `ok`: write `body` to `.canon/tmp/pr-evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post or update the comment:
184
+ - `ok`: write `body` to `.canon/tmp/pr/evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post or update the comment:
185
185
 
186
186
  ```bash
187
- gh pr comment <number> --body-file <main-root>/.canon/tmp/pr-evidence/body-<number>.md
187
+ gh pr comment <number> --body-file <main-root>/.canon/tmp/pr/evidence/body-<number>.md
188
188
  ```
189
189
 
190
190
  When the record carries a `commentId`, edit that comment in place instead of posting a second one, reading the body field from the tmp file with `@`:
191
191
 
192
192
  ```bash
193
- gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -f body=@<main-root>/.canon/tmp/pr-evidence/body-<number>.md
193
+ gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -f body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md
194
194
  ```
195
195
 
196
196
  Clean up the tmp file the way the UI-checklist step does, only after the call reports success.
@@ -80,25 +80,25 @@ For independent mode, base every branch on `main`:
80
80
  git branch -m <current_branch> <new_name>
81
81
 
82
82
  # Create, cherry-pick, push, and open PR for each secondary branch
83
- mkdir -p .canon/tmp/pr-split
83
+ mkdir -p .canon/tmp/pr/split
84
84
  git switch -c <branch> main && git cherry-pick <sha> <sha> \
85
85
  && git push -u origin <branch> \
86
- && (cat <<'BODY' > .canon/tmp/pr-split/<branch>.md
86
+ && (cat <<'BODY' > .canon/tmp/pr/split/<branch>.md
87
87
  <body following pr.md template, written from the cherry-picked commits>
88
88
  BODY
89
- ) && gh pr create --title "<title>" --body-file .canon/tmp/pr-split/<branch>.md \
90
- && rm .canon/tmp/pr-split/<branch>.md
89
+ ) && gh pr create --title "<title>" --body-file .canon/tmp/pr/split/<branch>.md \
90
+ && rm .canon/tmp/pr/split/<branch>.md
91
91
 
92
92
  # Return to primary branch, push, and open its PR
93
93
  git checkout <new_name> && git push -u origin <new_name> \
94
- && (cat <<'BODY' > .canon/tmp/pr-split/<new_name>.md
94
+ && (cat <<'BODY' > .canon/tmp/pr/split/<new_name>.md
95
95
  <body following pr.md template, written from the primary's commits>
96
96
  BODY
97
- ) && gh pr create --title "<title>" --body-file .canon/tmp/pr-split/<new_name>.md \
98
- && rm .canon/tmp/pr-split/<new_name>.md
97
+ ) && gh pr create --title "<title>" --body-file .canon/tmp/pr/split/<new_name>.md \
98
+ && rm .canon/tmp/pr/split/<new_name>.md
99
99
 
100
100
  # Clean up the body-file dir if all PRs succeeded (no-op when non-empty)
101
- rmdir .canon/tmp/pr-split 2>/dev/null || true
101
+ rmdir .canon/tmp/pr/split 2>/dev/null || true
102
102
  ```
103
103
 
104
104
  For stacked mode, base each branch on the previous and cherry-pick only that group's commits:
@@ -107,36 +107,36 @@ For stacked mode, base each branch on the previous and cherry-pick only that gro
107
107
  # Rename current branch to reflect primary concern
108
108
  git branch -m <current_branch> <new_name>
109
109
 
110
- mkdir -p .canon/tmp/pr-split
110
+ mkdir -p .canon/tmp/pr/split
111
111
 
112
112
  # Group 1: based on main
113
113
  git switch -c <branch-1> main && git cherry-pick <g1-sha> <g1-sha> \
114
114
  && git push -u origin <branch-1> \
115
- && (cat <<'BODY' > .canon/tmp/pr-split/<branch-1>.md
115
+ && (cat <<'BODY' > .canon/tmp/pr/split/<branch-1>.md
116
116
  <body following pr.md template, written from the cherry-picked commits>
117
117
  BODY
118
- ) && gh pr create --title "<title>" --body-file .canon/tmp/pr-split/<branch-1>.md \
119
- && rm .canon/tmp/pr-split/<branch-1>.md
118
+ ) && gh pr create --title "<title>" --body-file .canon/tmp/pr/split/<branch-1>.md \
119
+ && rm .canon/tmp/pr/split/<branch-1>.md
120
120
 
121
121
  # Group 2: based on <branch-1>, this group's commits only
122
122
  git checkout -b <branch-2> && git cherry-pick <g2-sha> <g2-sha> \
123
123
  && git push -u origin <branch-2> \
124
- && (cat <<'BODY' > .canon/tmp/pr-split/<branch-2>.md
124
+ && (cat <<'BODY' > .canon/tmp/pr/split/<branch-2>.md
125
125
  <body following pr.md template, written from the cherry-picked commits>
126
126
  BODY
127
- ) && gh pr create --title "<title>" --body-file .canon/tmp/pr-split/<branch-2>.md \
128
- && rm .canon/tmp/pr-split/<branch-2>.md
127
+ ) && gh pr create --title "<title>" --body-file .canon/tmp/pr/split/<branch-2>.md \
128
+ && rm .canon/tmp/pr/split/<branch-2>.md
129
129
 
130
130
  # Return to primary branch, push, and open its PR
131
131
  git checkout <new_name> && git push -u origin <new_name> \
132
- && (cat <<'BODY' > .canon/tmp/pr-split/<new_name>.md
132
+ && (cat <<'BODY' > .canon/tmp/pr/split/<new_name>.md
133
133
  <body following pr.md template, written from the primary's commits>
134
134
  BODY
135
- ) && gh pr create --title "<title>" --body-file .canon/tmp/pr-split/<new_name>.md \
136
- && rm .canon/tmp/pr-split/<new_name>.md
135
+ ) && gh pr create --title "<title>" --body-file .canon/tmp/pr/split/<new_name>.md \
136
+ && rm .canon/tmp/pr/split/<new_name>.md
137
137
 
138
138
  # Clean up the body-file dir if all PRs succeeded (no-op when non-empty)
139
- rmdir .canon/tmp/pr-split 2>/dev/null || true
139
+ rmdir .canon/tmp/pr/split 2>/dev/null || true
140
140
  ```
141
141
 
142
142
  ## After execution
@@ -44,7 +44,7 @@ For each project candidate, match its subject against `canon/context/index.md`.
44
44
 
45
45
  Fail closed. A project candidate matching no entry stays a memory file, and so does one matching two entries where neither is clearly the owner. The residue is what the folder is for, and a fact filed under the wrong entry is worse than one in memory because a context entry is a surface sessions trust.
46
46
 
47
- Do not edit a context entry here. `docs-fold` owns those edits and folds the routed facts in on its own pass, or two skills write one file at the same step. Write each routed fact to `.canon/tmp/memory-routing/<slug>.md` at the main worktree root instead, appending when the file exists. Name the heading with the entry's own path from `canon/context/index.md`, flat or the nested `index.md`, since that heading is what tells `docs-fold`'s routed-facts fold which file to open. An append is a whole-file operation the shell does directly, so send it as a plain single `Bash` command carrying a heredoc:
47
+ Do not edit a context entry here. `docs-fold` owns those edits and folds the routed facts in on its own pass, or two skills write one file at the same step. Write each routed fact to `.canon/tmp/handoff/memory-routing/<slug>.md` at the main worktree root instead, appending when the file exists. Name the heading with the entry's own path from `canon/context/index.md`, flat or the nested `index.md`, since that heading is what tells `docs-fold`'s routed-facts fold which file to open. An append is a whole-file operation the shell does directly, so send it as a plain single `Bash` command carrying a heredoc:
48
48
 
49
49
  A flat domain takes:
50
50
 
@@ -96,7 +96,7 @@ Respond with one line per fact routed, written, or updated:
96
96
 
97
97
  When anything routed, add a line naming the handoff so the caller knows a `docs-fold` pass is owed:
98
98
 
99
- `→ Routed facts wait at .canon/tmp/memory-routing/<slug>.md. Run /docs-fold to fold them in.`
99
+ `→ Routed facts wait at .canon/tmp/handoff/memory-routing/<slug>.md. Run /docs-fold to fold them in.`
100
100
 
101
101
  Omit that line when the caller runs `docs-fold` itself later in its own chain.
102
102
 
@@ -68,7 +68,7 @@ For each in-scope entry (see Scope), pick one action:
68
68
  - In a target project, append the rule to an existing file under `.claude/rules/project/` with `Edit`, since a project rule is project-owned and sync never overwrites it, or hand off to the `create-rule` skill when no existing file fits.
69
69
  - **Promote to a skill body**: the rule fires only when editing a specific path-scoped domain. Name the target skill.
70
70
  - **Promote to a standards file**: the rule is an authoring reference that belongs in the project's own standards folder as `<domain>.md`.
71
- - **Promote to a context entry**: the entry states a fact about a domain carrying an entry in `canon/context/index.md`. Append it to `.canon/tmp/memory-routing/<slug>.md` at the main worktree root, in the format `memory-capture` writes, and tell the user to run `/docs-fold` from a branch. Do not edit the context entry here. A project-identity or command fact takes this action when a context entry owns the subject, such as a development entry for commands, and **Retire** otherwise, naming a hand edit to `CLAUDE.md` as the reason. Memory review does not write the root file itself.
71
+ - **Promote to a context entry**: the entry states a fact about a domain carrying an entry in `canon/context/index.md`. Append it to `.canon/tmp/handoff/memory-routing/<slug>.md` at the main worktree root, in the format `memory-capture` writes, and tell the user to run `/docs-fold` from a branch. Do not edit the context entry here. A project-identity or command fact takes this action when a context entry owns the subject, such as a development entry for commands, and **Retire** otherwise, naming a hand edit to `CLAUDE.md` as the reason. Memory review does not write the root file itself.
72
72
  - **Hand off to governance**: the rule is coding-standards class (typescript, testing, naming, error-handling, performance, logging, concurrency, planning), never a cross-domain behavior rule. Do not author the rule file inline. Never edit the synced `.claude/rules/` copies of toolkit rules, because `canon gov sync` overwrites them. Stop at handoff. This and **Promote to an always-loaded rule** never both claim one entry: class names the topic (coding-standards routes here), firing axis names the rest (applies-every-session routes to the rule promote).
73
73
  - In the toolkit repo, point the user at `internal-governance` and `${CLAUDE_SKILL_DIR}/../../standards/rule.md`, which own the source-of-truth rules under `governance/rules/`.
74
74
  - In a target project, point the user at the `create-rule` skill, which scaffolds a project-local rule under `.claude/rules/`.
@@ -153,7 +153,7 @@ Free-form text after the verb is a reason. Capture it in the receipt but do not
153
153
  Action by action type:
154
154
 
155
155
  - **Promote**: use `Edit` to insert the rewritten rule into the target surface, then archive the memory file. A promote to an always-loaded rule in the toolkit repo never reaches this line: it stops as a handoff to `internal-governance`, the same as **Hand off** below, and archives only on the user's explicit confirmation.
156
- - **Promote to a context entry**: append the fact to `.canon/tmp/memory-routing/<slug>.md` at the main worktree root, then archive the memory file. `docs-fold` folds it in on its next run from a branch, which is what keeps one skill writing context entries.
156
+ - **Promote to a context entry**: append the fact to `.canon/tmp/handoff/memory-routing/<slug>.md` at the main worktree root, then archive the memory file. `docs-fold` folds it in on its next run from a branch, which is what keeps one skill writing context entries.
157
157
  - **Hand off**: do not edit governance. Archive the memory file only if the user confirmed the handoff explicitly. Otherwise leave it in place.
158
158
  - **Retire**: archive the memory file.
159
159
 
@@ -21,7 +21,7 @@ Read `${CLAUDE_SKILL_DIR}/../../standards/groundwork.md` before writing any file
21
21
 
22
22
  - Write only inside `.canon/groundwork/<nn>-<slug>/`. A feature plan, source changes, a standard, a rule, and a reference doc all live outside that folder, so this one rule forbids every one of them.
23
23
  - One exception, at close only: write one task file recording what the track concluded.
24
- - A second exception, for what a spike reads: write an input under `.canon/tmp/groundwork-fixtures/<slug>/`. Keep it out of `.canon/groundwork/` so mode detection never matches a fixture as a track. A fixture a headless run is pointed at goes outside the repository instead, per the rule in `## Running a spike`.
24
+ - A second exception, for what a spike reads: write an input under `.canon/tmp/runs/groundwork-fixtures/<slug>/`. Keep it out of `.canon/groundwork/` so mode detection never matches a fixture as a track. A fixture a headless run is pointed at goes outside the repository instead, per the rule in `## Running a spike`.
25
25
  - What a spike produces stays inside the track rather than joining the two exceptions above: write evidence a spike file cites under `.canon/groundwork/<nn>-<slug>/evidence/`, which the first rule already permits. Mode detection matches entries at the top level of the tracks directory, so the sibling the fixtures rule guards against cannot be a folder nested inside a track, and the scratch path holds only what can be deleted without loss, which a recording a finding rests on is not.
26
26
  - A `draft-and-pick` run invoked from inside a live track follows its own branch for this, writing to `evidence/` here rather than `.canon/tmp/<slug>/`.
27
27
  - Reading is not restricted. External research is in scope, so read documentation, comparable projects, and papers whenever a live question needs them.
@@ -99,7 +99,7 @@ moved, which the earlier test could not see. Rebase again under
99
99
  continue. The second push costs one extra force-push in a
100
100
  case that needs the fix and the sibling to touch the same lines.
101
101
 
102
- Then write a summary reply to `.canon/tmp/address-review/reply-<number>.md`
102
+ Then write a summary reply to `.canon/tmp/pr/reply/reply-<number>.md`
103
103
  mapping each finding to what changed, or to a one-line reason when it is a
104
104
  conscious-accept rather than a defect. Key the filename on the PR number so two
105
105
  sessions addressing different pull requests never overwrite each other between
@@ -154,7 +154,7 @@ Before posting, follow `${CLAUDE_SKILL_DIR}/../../standards/publish.md`
154
154
  and run its scan against the reply:
155
155
 
156
156
  ```bash
157
- canon labels scan --body-file .canon/tmp/address-review/reply-<number>.md
157
+ canon labels scan --body-file .canon/tmp/pr/reply/reply-<number>.md
158
158
  ```
159
159
 
160
160
  The hook skips `.canon/tmp/`, so this scan is the only gate on the published
@@ -163,8 +163,8 @@ capture the posted comment's id, since Step 7 edits this exact comment rather
163
163
  than trusting whichever one `gh` considers last:
164
164
 
165
165
  ```bash
166
- comment_url=$(gh pr comment <number> --body-file .canon/tmp/address-review/reply-<number>.md)
167
- echo "${comment_url##*issuecomment-}" > .canon/tmp/address-review/reply-<number>.id
166
+ comment_url=$(gh pr comment <number> --body-file .canon/tmp/pr/reply/reply-<number>.md)
167
+ echo "${comment_url##*issuecomment-}" > .canon/tmp/pr/reply/reply-<number>.id
168
168
  ```
169
169
 
170
170
  ## Step 7: confirm resolution
@@ -176,7 +176,7 @@ the closing confirmation to the reply file Step 6 already posted, so the thread
176
176
  carries one terminal state rather than a second comment under no heading:
177
177
 
178
178
  ```bash
179
- printf '\n✅ All review findings addressed, CI green.\n' >> .canon/tmp/address-review/reply-<number>.md
179
+ printf '\n✅ All review findings addressed, CI green.\n' >> .canon/tmp/pr/reply/reply-<number>.md
180
180
  ```
181
181
 
182
182
  A rebase-only run addressed no finding, so it appends its own confirmation
@@ -184,14 +184,14 @@ instead of that one. Claiming findings were addressed on a pull request that
184
184
  carries none is false on a surface nothing else checks:
185
185
 
186
186
  ```bash
187
- printf '\n✅ Rebased onto origin/main, CI green. No review findings were open.\n' >> .canon/tmp/address-review/reply-<number>.md
187
+ printf '\n✅ Rebased onto origin/main, CI green. No review findings were open.\n' >> .canon/tmp/pr/reply/reply-<number>.md
188
188
  ```
189
189
 
190
190
  Re-run the scan against the updated file, since the appended line is new
191
191
  content the Step 6 scan never saw:
192
192
 
193
193
  ```bash
194
- canon labels scan --body-file .canon/tmp/address-review/reply-<number>.md
194
+ canon labels scan --body-file .canon/tmp/pr/reply/reply-<number>.md
195
195
  ```
196
196
 
197
197
  Then edit the exact comment Step 6 posted, read back from the id it saved,
@@ -201,12 +201,12 @@ so read the id as its own plain command and write the printed value as a
201
201
  literal in the `gh api` call rather than a substitution or a variable:
202
202
 
203
203
  ```bash
204
- cat .canon/tmp/address-review/reply-<number>.id
204
+ cat .canon/tmp/pr/reply/reply-<number>.id
205
205
  ```
206
206
 
207
207
  ```bash
208
208
  gh api -X PATCH "repos/{owner}/{repo}/issues/comments/<id printed above>" \
209
- -F body=@.canon/tmp/address-review/reply-<number>.md
209
+ -F body=@.canon/tmp/pr/reply/reply-<number>.md
210
210
  ```
211
211
 
212
212
  `--edit-last` was the first shape and it targets the wrong object here.
@@ -243,10 +243,10 @@ Do not merge. Hand back to the orchestrator for re-review.
243
243
 
244
244
  ## Post-review findings
245
245
 
246
- Not everything worth reaching the reviewing session surfaces inside the numbered flow above. A worker that settled a risk, filed a follow-up, or found something else worth reporting after Step 7 already closed the review posts it directly rather than waiting on a review pass that has nothing left to trigger it. Write the body the way Step 6 writes a reply: load `write-human` for voice, follow `${CLAUDE_SKILL_DIR}/../../standards/markdown.md` for the banned words, and run the `${CLAUDE_SKILL_DIR}/../../standards/publish.md` scan before posting with `canon labels scan --body-file .canon/tmp/address-review/reply-<number>.md`.
246
+ Not everything worth reaching the reviewing session surfaces inside the numbered flow above. A worker that settled a risk, filed a follow-up, or found something else worth reporting after Step 7 already closed the review posts it directly rather than waiting on a review pass that has nothing left to trigger it. Write the body the way Step 6 writes a reply: load `write-human` for voice, follow `${CLAUDE_SKILL_DIR}/../../standards/markdown.md` for the banned words, and run the `${CLAUDE_SKILL_DIR}/../../standards/publish.md` scan before posting with `canon labels scan --body-file .canon/tmp/pr/reply/reply-<number>.md`.
247
247
 
248
248
  Open with `## Post-review findings` rather than `## Review response`, since nothing on the thread is being answered. `review-pr` states the full heading set this belongs to and routes it the same as a response: `role-orchestrator`'s poll picks it up and sends the reviewing session back for a pass. Close the body with `🤖 Addressed by Claude Code` on its own line, matching the reply's footer.
249
249
 
250
250
  ```bash
251
- gh pr comment <number> --body-file .canon/tmp/address-review/reply-<number>.md
251
+ gh pr comment <number> --body-file .canon/tmp/pr/reply/reply-<number>.md
252
252
  ```
@@ -114,7 +114,7 @@ If nothing is wrong, use: `✅ No findings.`
114
114
 
115
115
  Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to `latest` on an empty result.
116
116
 
117
- Write the full report directly to `.canon/review/branch/review-<slug>.md` at the main worktree root, not the current worktree. Resolve that root the way `session-worktree` does. Create the directory if it does not exist. Always overwrite.
117
+ Write the full report directly to `.canon/review/branch-<slug>.md` at the main worktree root, not the current worktree. Resolve that root the way `session-worktree` does. Create the directory if it does not exist. Always overwrite.
118
118
 
119
119
  From a linked worktree the file-editing tools refuse that path, so the report goes out through `Bash`. Send the `mkdir -p` and the heredoc as two plain commands rather than joining them with `&&`, which is refused as compound.
120
120
 
@@ -130,7 +130,7 @@ Output only the summary line and the file path. Do not repeat the full report in
130
130
 
131
131
  ```plaintext
132
132
  X critical, Y should-fix, Z minor across N files.
133
- 📝 Wrote .canon/review/branch/review-<slug>.md
133
+ 📝 Wrote .canon/review/branch-<slug>.md
134
134
  ```
135
135
 
136
- If no findings: `✅ No findings. Wrote .canon/review/branch/review-<slug>.md`
136
+ If no findings: `✅ No findings. Wrote .canon/review/branch-<slug>.md`
@@ -165,7 +165,7 @@ Use severity: `critical` (blocks merge), `should-fix` (fix before merge), `minor
165
165
 
166
166
  ## Step 4: post to the PR
167
167
 
168
- Write the comment to `.canon/tmp/pr-review/body-<number>-<short-sha>.md` at the main worktree root, not the current worktree, which the rest of this step calls `<body-file>`. Resolve that root the way `session-worktree` does, and send the write as a plain single `Bash` command carrying a heredoc from a linked worktree, since `Edit` and `Write` refuse a main-root path there. The PR number stops two sessions reviewing different pull requests from overwriting each other between the write and the post, and the head commit stops a second pass overwriting the first one's body, leaving the folder a record of which commit each review covered.
168
+ Write the comment to `.canon/tmp/pr/review/body-<number>-<short-sha>.md` at the main worktree root, not the current worktree, which the rest of this step calls `<body-file>`. Resolve that root the way `session-worktree` does, and send the write as a plain single `Bash` command carrying a heredoc from a linked worktree, since `Edit` and `Write` refuse a main-root path there. The PR number stops two sessions reviewing different pull requests from overwriting each other between the write and the post, and the head commit stops a second pass overwriting the first one's body.
169
169
 
170
170
  Derive both segments from Step 1. Never pick a suffix by hand, and never reuse a name the folder already holds.
171
171
 
@@ -279,7 +279,7 @@ Before posting, run the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md`
279
279
  Do not run the command below when `<prior-heading>` from Step 2 reads `## Review closed` and this pass carries nothing owed. That pass replaces the standing comment rather than adding one, under `### A close-out that repeats the standing one` at the end of this step. Posting first and reaching that section afterward leaves two close-outs both naming the new head, which is worse than the pair the guard exists against.
280
280
 
281
281
  ```bash
282
- gh pr review <number> --comment --body-file .canon/tmp/pr-review/body-<number>-<short-sha>.md
282
+ gh pr review <number> --comment --body-file .canon/tmp/pr/review/body-<number>-<short-sha>.md
283
283
  ```
284
284
 
285
285
  A pass carrying nothing at all takes `## Review closed` and a short body, with the footer line included either way. On a first pass, post `✅ No findings. Reviewed against project docs and the board.` On a later pass, post `✅ Prior findings addressed. Re-reviewed <short-sha>, N commits since the prior pass.`
@@ -54,7 +54,7 @@ Every classification line names a pull request and a state. A line starting `pol
54
54
 
55
55
  The script exits non-zero and classifies nothing when the open pull request list itself fails to load. That case would otherwise report every tracked pull request as merged, so the baseline is left untouched and the run says so.
56
56
 
57
- The baseline lives at `.canon/tmp/pr-poll/baseline.txt` under the main worktree root and is per-machine. A first run against a board already in flight reports each open pull request once before it settles.
57
+ The baseline lives at `.canon/tmp/pr/poll/baseline.txt` under the main worktree root and is per-machine. A first run against a board already in flight reports each open pull request once before it settles.
58
58
 
59
59
  The five review headings the script matches are written by `review-pr` and `review-address`, and the whole set is stated once in the first. The reply family is matched by jq filters in the script, so a project posting its replies under different headings edits those to match. The review family is matched inside `canon pr review-state` instead, which the script and `review-pr` both read through, so a project renaming either review heading changes the verb rather than the script. Either way, a heading nothing matches reads as a pull request nobody has reviewed.
60
60
 
@@ -18,7 +18,7 @@ if [ -z "$MAIN_ROOT" ]; then
18
18
  echo "poll: not a git repository, so nothing is classified" >&2
19
19
  exit 1
20
20
  fi
21
- STATE_DIR="$MAIN_ROOT/.canon/tmp/pr-poll"
21
+ STATE_DIR="$MAIN_ROOT/.canon/tmp/pr/poll"
22
22
  mkdir -p "$STATE_DIR"
23
23
  STATE="$STATE_DIR/baseline.txt"
24
24
  touch "$STATE"
@@ -44,7 +44,7 @@ The arms here are fixed references rather than drafted markup, so nothing about
44
44
 
45
45
  1. Carry forward the reason Step 3's pick already stated, as written rather than paraphrased. Ask the operator to state, in their own words, why the picked reference won only when the pick arrived with no reason attached.
46
46
  2. Read the picked arm's render at `<dest>/refs/arm-<id>.png` and trace its color, type, and spacing by looking, the same reading a person does against a screenshot. Name a role only where the reference actually shows it. Leave one out rather than guessing.
47
- 3. Write `.canon/review/evidence/<slug>/design-handoff.md`, resolved against the main worktree root rather than the linked worktree this run may be building in, since shared session scratch always resolves there. One field per line, grouped under the section names `${CLAUDE_SKILL_DIR}/../../standards/design.md` fixes, so `design-extract` reads it mechanically:
47
+ 3. Write `.canon/picks/<slug>/design-handoff.md`, resolved against the main worktree root rather than the linked worktree this run may be building in, since shared session scratch always resolves there. One field per line, grouped under the section names `${CLAUDE_SKILL_DIR}/../../standards/design.md` fixes, so `design-extract` reads it mechanically:
48
48
 
49
49
  ```markdown
50
50
  # Design handoff
@@ -71,14 +71,14 @@ The arms here are fixed references rather than drafted markup, so nothing about
71
71
 
72
72
  Write only the sections and roles the trace actually names. A role or a whole section with nothing traced is left out rather than written empty, since `design-extract` Step 2 treats an absent line as untraced and an empty one as a claim about the source.
73
73
 
74
- 4. Batch-capture the final round's arm files, the same directory-batch convention `draft-and-pick` Step 6 uses: `canon capture <dest>/arms --selector <wrapper-class> --out .canon/review/evidence/<slug>/`, naming Step 2's chosen class, so every arm considered, not only the winner, survives past the pick as a revert record.
74
+ 4. Batch-capture the final round's arm files, the same directory-batch convention `draft-and-pick` Step 6 uses: `canon capture <dest>/arms --selector <wrapper-class> --out .canon/picks/<slug>/`, naming Step 2's chosen class, so every arm considered, not only the winner, survives past the pick as a revert record.
75
75
  5. Delete `<dest>` and everything in it, now that the handoff and the arms both sit at the durable path above. Report the path as still standing when the delete is refused, rather than closing on a report the tree contradicts.
76
76
 
77
77
  ## Response format
78
78
 
79
79
  ```plaintext
80
- 📝 Wrote .canon/review/evidence/<slug>/design-handoff.md
81
- 📝 Wrote .canon/review/evidence/<slug>/arm-<id>.png (and every other arm considered)
80
+ 📝 Wrote .canon/picks/<slug>/design-handoff.md
81
+ 📝 Wrote .canon/picks/<slug>/arm-<id>.png (and every other arm considered)
82
82
 
83
83
  Picked arm-<id>: <label>. Run /canon:design-extract to fill canon/DESIGN.md from the trace.
84
84
  ```
@@ -217,7 +217,7 @@ Still owed: <what the destination expects that the page does not carry yet>
217
217
 
218
218
  Then stop and let the operator strike, redirect, or confirm each block.
219
219
 
220
- Write nothing to a destination here. One skill owns the durable writes, and two skills editing one file at one step is the failure that rule exists against. Record each confirmed block in `.canon/tmp/teach-promotion/<slug>.md` at the main worktree root instead, appending when the file exists, with one H2 per destination naming its path, the source page beneath it, and the page body fenced:
220
+ Write nothing to a destination here. One skill owns the durable writes, and two skills editing one file at one step is the failure that rule exists against. Record each confirmed block in `.canon/tmp/handoff/teach-promotion/<slug>.md` at the main worktree root instead, appending when the file exists, with one H2 per destination naming its path, the source page beneath it, and the page body fenced:
221
221
 
222
222
  ````markdown
223
223
  ## <destination path>
@@ -258,7 +258,7 @@ A promotion pass reports its own shape instead, one line per page the operator c
258
258
 
259
259
  ```plaintext
260
260
  ➡️ Promoting: .canon/teach/<nn>-<topic>/reference/<slug>.md → <destination path>
261
- → Confirmed pages wait at .canon/tmp/teach-promotion/<slug>.md. Run /docs-fold from a branch to fold them in.
261
+ → Confirmed pages wait at .canon/tmp/handoff/teach-promotion/<slug>.md. Run /docs-fold from a branch to fold them in.
262
262
  ```
263
263
 
264
264
  A pass where the operator confirmed nothing writes no handoff file and reports that alone.