@erclx/canon 4.118.0 → 4.120.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 (103) hide show
  1. package/README.md +2 -2
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/canon-rollout/SKILL.md +2 -2
  4. package/claude/skills/create-skill/SKILL.md +2 -1
  5. package/claude/skills/design-extract/SKILL.md +1 -1
  6. package/claude/skills/docs-fold/SKILL.md +2 -2
  7. package/claude/skills/draft-ready/REQUIREMENT.md +1 -1
  8. package/claude/skills/draft-ready/SKILL.md +1 -1
  9. package/claude/skills/draft-screencast/SKILL.md +1 -1
  10. package/claude/skills/git-pr/REQUIREMENT.md +1 -0
  11. package/claude/skills/git-pr/SKILL.md +39 -7
  12. package/claude/skills/memory-capture/SKILL.md +3 -3
  13. package/claude/skills/memory-review/SKILL.md +3 -3
  14. package/claude/skills/plan-feature/SKILL.md +1 -1
  15. package/claude/skills/review-branch/SKILL.md +1 -1
  16. package/claude/skills/review-pr/SKILL.md +13 -242
  17. package/claude/skills/review-pr/references/close-out.md +30 -0
  18. package/claude/skills/review-pr/references/post.md +145 -0
  19. package/claude/skills/review-pr/references/scope.md +82 -0
  20. package/claude/skills/role-orchestrator/REQUIREMENT.md +9 -16
  21. package/claude/skills/role-orchestrator/SKILL.md +9 -153
  22. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +4 -193
  23. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +22 -0
  24. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +85 -0
  25. package/claude/skills/role-orchestrator/references/orchestrator-output.md +61 -0
  26. package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -2
  27. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +1 -1
  28. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +56 -0
  29. package/claude/skills/role-orchestrator/references/orchestrator-review-fallback.md +18 -0
  30. package/claude/skills/role-orchestrator/references/orchestrator-sweep.md +1 -1
  31. package/claude/skills/role-planner/REQUIREMENT.md +1 -1
  32. package/claude/skills/role-planner/SKILL.md +1 -1
  33. package/claude/skills/role-worker/REQUIREMENT.md +1 -1
  34. package/claude/skills/role-worker/SKILL.md +10 -41
  35. package/claude/skills/seed-sync/SKILL.md +1 -1
  36. package/claude/skills/session-compact/SKILL.md +1 -1
  37. package/claude/skills/session-map/SKILL.md +1 -1
  38. package/claude/skills/session-relay/REQUIREMENT.md +7 -1
  39. package/claude/skills/session-relay/SKILL.md +4 -7
  40. package/claude/skills/session-worktree/REQUIREMENT.md +5 -1
  41. package/claude/skills/session-worktree/SKILL.md +7 -1
  42. package/claude/skills/session-worktree/references/main-root-writes.md +24 -0
  43. package/claude/skills/task-board/SKILL.md +4 -3
  44. package/claude/skills/teach-workspace/SKILL.md +4 -4
  45. package/claude/skills/ui-checklist/SKILL.md +1 -1
  46. package/claude/skills/ux-audit/SKILL.md +1 -1
  47. package/claude/skills/ux-measure/SKILL.md +1 -1
  48. package/claude/skills/ux-walkthrough/references/relay.md +1 -1
  49. package/docs/agents/commands.md +4 -3
  50. package/docs/agents/counts.md +1 -1
  51. package/docs/agents/index.md +8 -3
  52. package/docs/agents/install-and-sync.md +11 -6
  53. package/docs/agents/pr-evidence.md +19 -3
  54. package/docs/agents/pr-local.md +102 -0
  55. package/docs/agents/pr-preview.md +2 -2
  56. package/docs/agents/records-push.md +61 -0
  57. package/docs/agents/records-validate.md +73 -0
  58. package/docs/agents/records.md +3 -119
  59. package/docs/agents/sessions-transfer.md +91 -0
  60. package/docs/agents/sessions.md +3 -82
  61. package/docs/agents/skills-audit.md +2 -2
  62. package/docs/agents/tasks.md +4 -4
  63. package/docs/agents/teach-authoring.md +112 -0
  64. package/docs/agents/teach.md +3 -103
  65. package/docs/target-projects.md +2 -2
  66. package/docs/workflow/ai-workflow.md +10 -10
  67. package/docs/workflow/visual-design-workflow.md +2 -2
  68. package/governance/renames.toml +13 -0
  69. package/governance/rules/claude/570-skill.md +2 -1
  70. package/governance/rules/core/085-worktrees.md +2 -8
  71. package/package.json +1 -1
  72. package/scripts/core/check-ignore-parity.sh +1 -1
  73. package/scripts/lib/sandbox-git.sh +1 -1
  74. package/src/claude/cases/workflow.ts +10 -0
  75. package/src/commands/pr.ts +324 -2
  76. package/src/commands/sync.ts +11 -3
  77. package/src/commands/tasks.ts +9 -8
  78. package/src/context/classify/extract.ts +2 -2
  79. package/src/counts/scan.ts +1 -1
  80. package/src/design/base.css +19 -19
  81. package/src/design/board.ts +18 -7
  82. package/src/design/neutral.ts +127 -0
  83. package/src/design/regen.ts +6 -4
  84. package/src/design/tokens.ts +6 -3
  85. package/src/gate/stages.ts +6 -6
  86. package/src/gov/adapter.ts +51 -1
  87. package/src/gov/install.ts +13 -3
  88. package/src/gov/renames.ts +76 -0
  89. package/src/gov/stacks.ts +5 -1
  90. package/src/pr/evidence.ts +42 -2
  91. package/src/pr/local.ts +125 -0
  92. package/src/sync/check.ts +19 -4
  93. package/src/sync/engine.ts +67 -3
  94. package/src/tasks/archive.ts +28 -7
  95. package/src/tasks/record.ts +16 -9
  96. package/src/tasks/validate.ts +3 -1
  97. package/standards/index.md +1 -0
  98. package/standards/skill-requirement.md +72 -0
  99. package/standards/skill.md +34 -152
  100. package/standards/tasks.md +5 -3
  101. package/tooling/astro/reference.md +1 -1
  102. package/tooling/base/configs/.github/workflows/pr-local-preview.yml +32 -0
  103. package/governance/rules/core/091-channel.md +0 -13
package/README.md CHANGED
@@ -162,8 +162,8 @@ Each domain carries an entry written for someone maintaining the toolkit rather
162
162
  - [Governance rules](canon/context/governance/index.md)
163
163
  - [Standards](canon/context/standards/index.md)
164
164
  - [Snippets](canon/context/snippets.md)
165
- - [Tooling stacks](canon/context/tooling.md)
166
- - [Design system](canon/context/design.md)
165
+ - [Tooling stacks](canon/context/tooling/index.md)
166
+ - [Design system](canon/context/design/index.md)
167
167
  - [Slides](canon/context/slides.md)
168
168
  - [Transcripts](canon/context/transcripts.md)
169
169
  - [Sandbox](canon/context/sandbox/index.md)
@@ -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.118.0",
4
+ "version": "4.120.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -104,11 +104,11 @@ One session, one target, from the worktree entry to the merge of the branch it o
104
104
  3. Check what this target holds. Invoke `canon:target-check`, which reports per domain what the target carries against what the toolkit ships and repairs nothing. It runs before any repair so the wave reads one stated set of domains per target rather than whatever each session decided to examine.
105
105
  4. Diagnose and repair. Invoke `canon:canon-operator`, which reads `canon sync --check . --json` and routes each finding to the command or the skill that owns it. Do not restate that routing here and do not edit a managed file by hand.
106
106
  5. Commit and open the pull request through `canon:git-commit` and `canon:git-pr`, handing each the fixed shape above rather than taking the title the generator derives from the diff.
107
- 6. Announce as the pull request opens, carrying its URL, its number, and the branch, to whoever dispatched this session. That transition is the one moment only this session can observe.
107
+ 6. Announce as the pull request opens, carrying its URL, its number, and the branch, to whoever dispatched this session, sending it through `canon:session-relay`. That transition is the one moment only this session can observe.
108
108
  7. Answer the review. Invoke `canon:review-address`, which pulls the findings and the CI state on this branch's open pull request, fixes each in the working tree, replies, and pushes. Answer every finding whatever its severity.
109
109
  8. Stop there. Do not mark the pull request ready, do not merge, and do not declare the review closed. A narrow re-review posts `## Review closed`.
110
110
 
111
- Send a block out as a message before it becomes an interactive prompt. A session already waiting on input never reaches the tool round an inbound message drains at, so an answer relayed afterwards arrives under the open question and changes nothing.
111
+ Send a block out through `canon:session-relay` before it becomes an interactive prompt, which states why the order matters.
112
112
 
113
113
  Refuse an instruction this target's tree contradicts and carry the evidence with the refusal, naming the commands read and what complying would cost. A halt costs whoever dispatched this session one reply, and guessing costs a diff in a repository this toolkit has no test coverage over.
114
114
 
@@ -8,6 +8,7 @@ description: Creates a new `SKILL.md` in `.claude/skills/`. Use when asked to "c
8
8
  Read these files in parallel:
9
9
 
10
10
  - `${CLAUDE_SKILL_DIR}/../../standards/skill.md`: skill structure, skill types, frontmatter fields, invocation rules
11
+ - `${CLAUDE_SKILL_DIR}/../../standards/skill-requirement.md`: what `REQUIREMENT.md` must answer, its sections, and its template
11
12
  - `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: banned words, punctuation, and formatting for skill body text
12
13
  - The `write-human` skill: voice, rhythm, and sentence construction for skill body text
13
14
 
@@ -22,7 +23,7 @@ Read these files in parallel:
22
23
  - 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
24
  - 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
25
  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
- 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
+ 3. Draft the sibling `REQUIREMENT.md` from what the skill is for, in the shape the requirement 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
27
  4. Confirm the skill name and both files with the user before writing
27
28
  5. Write to `.claude/skills/<name>/SKILL.md` and `.claude/skills/<name>/REQUIREMENT.md`
28
29
  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.
@@ -77,7 +77,7 @@ Anchor every proposal to a signal, never to a default. "Calm and dense" pins mut
77
77
  - **Traced cells**: when Step 2 matched a `design-handoff.md` file, fill only the fields it actually states, no tag, the same traced-versus-proposed distinction the source path draws for a stylesheet value.
78
78
  - The handoff carries hex and family and size, never Weight, Line height, or a color's Intent. Those cells, every field of a role the handoff never names, and Motion and Iconography regardless, still follow the proposal rules below, tag included.
79
79
  - **Personality**: transcribe the `## Personality` paragraph from `canon/REQUIREMENTS.md` verbatim. This is the one section that is not a proposal. No tag.
80
- - **Color**: one row per role. Rewrite the Intent cell in personality language, for example `warm off-white page canvas` instead of the seed default `page canvas`. Propose hex values matching the personality. Dense and calm gives low saturation and high text contrast. Playful gives saturated accents. Every Intent and Value cell gets `? verify`.
80
+ - **Color**: one row per role. Rewrite the Intent cell in personality language, for example `near-white page canvas` instead of the seed default `page canvas`. Propose hex values matching the personality. Dense and calm gives low saturation and high text contrast. Playful gives saturated accents. Every Intent and Value cell gets `? verify`.
81
81
  - **Typography**: one row per role. Propose families fitting the platform, system UI for web, monospaced for CLI tools, serif for editorial, and a harmonious scale. Every cell gets `? verify`.
82
82
  - **Spacing**: propose a base unit matching density intent. Dense gives a 4px base, roomy gives 8px. Keep the Multiplier column as the seed ships it, no tag. Only the Value column gets `? verify`.
83
83
  - **Borders**: propose radius and width per role. Sharp and technical gives a small radius, soft gives a larger one. Every Radius and Width cell gets `? verify`.
@@ -100,7 +100,7 @@ For each doc with relevant changes, apply updates following these rules. Read a
100
100
  - Do not touch task files this session did not change.
101
101
  - Never hand-edit `.canon/tasks/index.md`. A hook regenerates it.
102
102
 
103
- The verb resolves the board at the main worktree root in-process, which is the route because this is an edit inside an existing file and the file-editing tools refuse that path from a linked worktree.
103
+ The verb resolves the board at the main worktree root in-process, which is the route `session-worktree` states for an edit inside an existing main-root file.
104
104
 
105
105
  Read `ok` and `reason` out of that record rather than the exit. An operator's shell profile may wrap `canon` in a function that runs the binary and then a second command and takes the second status, which flattens every non-zero exit to zero. A refusal arriving as success leaves the outcome unmarked while the chain moves on, so the board reports shipped work as open and the next session re-plans it.
106
106
 
@@ -216,7 +216,7 @@ Report a block left unfolded rather than dropping it:
216
216
 
217
217
  Sweep the review and memory receipts this session consumed. Resolve all paths at the main worktree root, not the current worktree, the way `session-worktree` does.
218
218
 
219
- Every delete below is a shell operation, so send each as a plain single `Bash` command rather than joining two with `&&`, which is refused as compound from a linked worktree.
219
+ Every delete below is a plain `rm`, one per call, routed the way `session-worktree` states.
220
220
 
221
221
  Plans are not swept here. A plan is settled by the merge rather than by an outcome this run marked, and `canon tasks archive` moves it with the task the `post-merge` hook archives. Sweeping it from this step read a closure Step 3 had written moments earlier and moved a plan the branch was still building from.
222
222
 
@@ -28,7 +28,7 @@ Without this skill, a session holding finished files that hands them to a worker
28
28
  - Copy each finished file whole to its destination path inside the folder, and carry nothing else into it.
29
29
  - Write the plan and the task through `canon tasks plan-link`, `canon records validate plans`, and `canon markdown audit` rather than by restating what those verbs check.
30
30
  - Place the task's row by the three branches `task-board` Step 4 states, so a solo project never ends with a task file on no surface.
31
- - Route a write at the main root from a linked worktree through `Bash`, per `085-worktrees.md`.
31
+ - Route a write at the main root from a linked worktree through `Bash`, the way `session-worktree` routes it.
32
32
  - Name the archive move in the closing report, since the skill writes a folder that stays live until someone moves it.
33
33
 
34
34
  ## Must not
@@ -53,7 +53,7 @@ install -D -m 0644 <source> <main-root>/.canon/ready/<nn>-<slug>/<destination pa
53
53
 
54
54
  - Copy whole and edit nothing. A change wanted now is made in the source file first and copied after.
55
55
  - Carry no notes, alternates, or drafts into the folder. Anything else belongs in the plan or the pull request body.
56
- - Write each of these at the main root through `Bash`, one plain command apiece. From a linked worktree `Edit` and `Write` are refused there, and the refusal's redirect names a second gitignored copy no later session reads, per `085-worktrees.md`. Create a whole new file with a heredoc.
56
+ - Write each of these at the main root, routed the way `session-worktree` states. Create a whole new file with a heredoc.
57
57
 
58
58
  ## Step 5: write the thin plan
59
59
 
@@ -43,7 +43,7 @@ Build a 2-to-4-word kebab-case slug from the topic and discovery answers. Exampl
43
43
 
44
44
  Create `.canon/tmp/screencast/<slug>.md` at the main worktree root. Create the directory if it does not exist. The file is gitignored.
45
45
 
46
- From a linked worktree the file-editing tools refuse that path, so the draft 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.
46
+ The draft is a main-root write, so it goes out as a heredoc, routed the way `session-worktree` states.
47
47
 
48
48
  Write all eight sections. Pre-seed every section with concrete content so the draft is shippable as-is. The user edits down rather than fills blanks.
49
49
 
@@ -30,6 +30,7 @@ A lookup that resolves by head branch alone carries its own failure. A branch na
30
30
  - Report a refused label, since a warning nothing surfaces leaves the run indistinguishable from one that labelled
31
31
  - Put a preview address on a pull request that changes a rendered surface when the project's deploy can mint one, since screenshots and a checklist leave a reviewer nothing to click into before merging
32
32
  - Bound the wait on that preview and finish the chain without the link on a timeout, since a slow deploy must not hold the ship
33
+ - Put the branch's running local server on the same pull request when this worktree is serving one, and remove it when the pull request closes, since a reviewer on the operator's machine can interact with the branch and a link that outlives it points at whatever runs there next
33
34
 
34
35
  ## Must not
35
36
 
@@ -178,15 +178,35 @@ When it exists, scan it against `${CLAUDE_SKILL_DIR}/../../standards/publish.md`
178
178
 
179
179
  Where it lands is decided by the evidence step below rather than here, since a checklist reads next to the screenshots it annotates and posting it on its own is the fallback for a branch that changed no screenshot.
180
180
 
181
+ ### Find the local server
182
+
183
+ A reviewer on the operator's machine can open the branch's running dev server, which screenshots and a checklist cannot stand in for. Ask the verb for the server this worktree is running before the evidence step, so the first evidence comment already carries the link:
184
+
185
+ ```bash
186
+ canon pr local --json
187
+ ```
188
+
189
+ The verb reads the listening sockets on this machine, keeps those whose process runs inside this worktree, and reports the lowest port serving an HTML page. A server in another worktree or in the main checkout is never reported, since it shows a different branch. `canon docs pr-local` states the contract. Read `reason` on the record rather than the exit code.
190
+
191
+ - `ok`: hold the record's `url` for the evidence step below.
192
+ - `no-server` or `no-listener-reader`: hold the reason for the closing report, pass no link, and move on. Do not start a server here, since one started inside the ship chain has no owner to stop it.
193
+ - Anything else, including no record at all: move on silently. An installed binary older than the verb answers with an unknown-subcommand error, which is a skip rather than a stop.
194
+
195
+ Pass `--local` to the evidence step only on an `ok` here. The binary answering `ok` is the one carrying the flag, so a binary lacking the verb never meets a flag it would reject.
196
+
197
+ With both `--checklist` and `--local`, a diff carrying no evidence image still renders `ok`, with the link opening a comment the checklist closes, so a reviewer gets both in one place. A local link with neither an evidence image nor a checklist still reports `no-evidence`, which keeps a branch that changed no rendered surface from getting a comment holding a link and nothing else.
198
+
199
+ A seeded workflow replaces the line with a note once the pull request closes, so the link does not outlive the branch it points at.
200
+
181
201
  ### Post the evidence comparison
182
202
 
183
- Run the verb once against the number the pull request step above resolved, passing `--checklist` when the step above found a file and leaving it off when it did not:
203
+ Run the verb once against the number the pull request step above resolved. Pass `--checklist` when the checklist step found a file, and `--local` when the step above returned `ok`. Leave each flag off otherwise:
184
204
 
185
205
  ```bash
186
- canon pr evidence <number> --checklist <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md --json
206
+ canon pr evidence <number> --checklist <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md --local <url> --json
187
207
  ```
188
208
 
189
- One call answers both questions because a checklist does not decide `no-evidence`. The verb reports `no-evidence` on a diff carrying no evidence image whether or not a checklist came with it, so the branch below reads the same `reason` it would have read without the flag, and the checklist is folded in only on the path that has a comparison to fold it into.
209
+ One call answers both questions because a checklist does not decide `no-evidence`. The verb reports `no-evidence` on a diff carrying no evidence image whether or not a checklist came with it, so the branch below reads the same `reason` it would have read without the flag, and the checklist is folded in only on the path that has a comparison to fold it into. The local link is the one exception, stated in the step above.
190
210
 
191
211
  Pass `--checklist` only for a file that exists. The verb refuses as `unreadable-checklist` on a path it cannot read or one holding nothing, which is a caller bug rather than a transient failure, so stop and repair the path rather than posting a body with the checklist silently dropped.
192
212
 
@@ -211,7 +231,7 @@ Any other `reason` is one of the mirrored git refusals (`gh-missing`, `gh-failed
211
231
 
212
232
  ### Post the UI checklist alone
213
233
 
214
- Run this step only when the evidence step above did not carry the checklist, meaning it reported `no-evidence` or one of the git refusals, and a checklist file exists. Post it as its own comment on `<number>`:
234
+ Run this step only when the evidence step above did not carry the checklist, meaning it reported `no-evidence` or one of the git refusals, and a checklist file exists. A local link sends the checklist through the evidence step instead, so this step runs on a branch with no evidence image only when the local step found no server. Post it as its own comment on `<number>`:
215
235
 
216
236
  ```bash
217
237
  gh pr comment <number> --body-file <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
@@ -219,7 +239,7 @@ gh pr comment <number> --body-file <main-root>/.canon/tmp/handoff/ui-checklist/<
219
239
 
220
240
  Run the cleanup below only once the call that carried the checklist reports success, whichever of the two steps that was. 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.
221
241
 
222
- 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:
242
+ The cleanup is a main-root delete, so it goes out as a plain `rm` and then a plain `rmdir`, the file and then the folder, routed the way `session-worktree` states:
223
243
 
224
244
  ```bash
225
245
  rm <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
@@ -251,6 +271,8 @@ The verb dispatches the project's Pages deploy on the pull request's branch and
251
271
  canon pr evidence <number> --preview <url> --json
252
272
  ```
253
273
 
274
+ The hosted address takes the first line and a local link already on the comment moves to the second, carried forward with no flag.
275
+
254
276
  - `no-deploy`: the project deploys nothing a dispatch can start. Say nothing and move on.
255
277
  - Any other `reason`, being `unfenced`, `no-alias`, `run-failed`, `timeout`, or a mirrored `gh` refusal: report it with the record's `message` and move on without stopping the chain. A missing link costs the reviewer a click, and a held ship costs the whole chain.
256
278
 
@@ -266,9 +288,11 @@ canon tasks pull-request <number> --plan feature-<slug> --json
266
288
 
267
289
  The slug is a guess at which plan this branch carries rather than a fact about the task, which is why the verb re-checks it against the board and refuses instead of writing on a near miss. A branch whose slug names no plan file falls to the silent skip below, the same as one whose plan no task cites.
268
290
 
269
- The verb resolves the board at the main worktree root in-process, adds `Pull request: #NNN` under the `Plan:`, `Groundwork:`, `Intake:`, or `Issue:` lines the task already carries, and corrects the number in place when the line exists. This is the route because the write is an edit inside an existing file, which the file-editing tools refuse from a linked worktree and which no shell stream editor may make. That root is the one `session-worktree` resolves on entry.
291
+ The verb resolves the board at the main worktree root in-process, adds `Pull request: #NNN` under the `Plan:`, `Groundwork:`, `Intake:`, or `Issue:` lines the task already carries, and corrects the number in place when the line exists. This is the route because the write is an edit inside an existing main-root file, which `session-worktree` routes through a verb. That root is the one `session-worktree` resolves on entry.
292
+
293
+ The correction reaches only a line that does not read as a list of `#NNN` entries. A line that does takes the new number appended rather than replaced, reported as `appended`, so a task shipped in slices keeps every pull request.
270
294
 
271
- Skip this silently when the record is `ok: false` and `reason` is `no-board`, `no-match`, or `ambiguous`. Those are the three cases a guessed write would compound: no board, no task naming the plan, or more than one. One task, one pull request, and a wrong match archives the wrong task unattended once the branch merges. Report any other refusal rather than swallowing it.
295
+ Skip this silently when the record is `ok: false` and `reason` is `no-board`, `no-match`, or `ambiguous`. Those are the three cases a guessed write would compound: no board, no task naming the plan, or more than one. One pull request names one task, and a wrong match archives the wrong task unattended once the branch merges. Report any other refusal rather than swallowing it.
272
296
 
273
297
  The number is what lets the merge close the task. Every merge on `main` is a squash carrying it in the subject, so the number survives where a branch name does not, and `post-merge` reads it back to call `canon tasks archive`. Writing it here rather than at worktree time is what makes it a pull request number rather than a branch the squash discards.
274
298
 
@@ -296,4 +320,12 @@ Add a line when the preview step returned an address:
296
320
 
297
321
  `🔗 Preview: <url>`
298
322
 
323
+ Add a line when the evidence step posted the local link:
324
+
325
+ `🖥️ Local preview: <url>`
326
+
327
+ Add a line instead when the local step refused as `no-server` or `no-listener-reader` and the evidence or checklist step posted something, since that pull request changes a rendered surface and a reviewer could have used the link. To add it later, start the server, run `canon pr local --json`, then run `canon pr evidence <number> --local <url> --json`, adding `--checklist` when one is still owed, and post the body the way the evidence step does. `git-followup` only carries a line forward, so it cannot add one the comment never had:
328
+
329
+ `🖥️ No local preview: <reason>`
330
+
299
331
  Do not add any other text.
@@ -14,7 +14,7 @@ The filename and its type prefix, the frontmatter, the body shape each type carr
14
14
  ## Guards
15
15
 
16
16
  - All `.canon/memory/` reads and writes resolve at the main worktree root, not the current worktree. Resolve that root the way `session-worktree` does.
17
- - From a linked worktree the file-editing tools refuse every path below, so each write in this skill goes out through `Bash` as a plain single command. A memory entry holds one fact and this session has read it, so an update rewrites the whole file with a heredoc rather than editing a line inside it.
17
+ - Every path below sits at the main root, so each write in this skill goes out as a heredoc, routed the way `session-worktree` states. A memory entry holds one fact and this session has read it, so an update rewrites the whole file with a heredoc rather than editing a line inside it.
18
18
  - If `.canon/memory/` does not exist at the main worktree root, create it, along with an `index.md` carrying `title` and `subtitle` frontmatter. `canon claude init` seeds both, and a project predating that seed has neither. Regeneration errors without the index, so the first write into a bare folder would report a frontmatter failure against a file that is fine.
19
19
  - If the session produced no user corrections, confirmations, or context disclosures worth persisting, stop: `✅ Nothing worth capturing.`
20
20
  - Routing edits a tracked file, so it runs only where the caller commits. When the session is in the main worktree, or the caller states it does not commit, skip Step 3 and write every candidate as a memory file. `role-orchestrator` is the caller this covers.
@@ -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/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:
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, so send it as a heredoc:
48
48
 
49
49
  A flat domain takes:
50
50
 
@@ -78,7 +78,7 @@ Two of its rules are the ones a capture pass gets wrong under time pressure. Sta
78
78
 
79
79
  Do not edit the index. `.canon/memory/index.md` is generated from sibling frontmatter by a `PostToolUse` hook, the same way the task board's index is, so a hand-appended row is drift the next regeneration discards.
80
80
 
81
- The hook matches `Write|Edit|MultiEdit`, so nothing fires on the shell writes a linked worktree makes. Regenerate the index once after the last write when the entries went out through `Bash`:
81
+ A shell write skips that hook, as `session-worktree` states. Regenerate the index once after the last write when the entries went out through `Bash`:
82
82
 
83
83
  ```bash
84
84
  canon indexes regen --no-stage --root <main-root> <main-root>/.canon/memory/index.md
@@ -26,7 +26,7 @@ If the user re-pings the skill with no new phrase and a receipt exists, default
26
26
  - If `.canon/memory/` contains no top-level `*.md` entries other than `index.md`, stop: `✅ No memory entries to review.` The pen holds two subfolders now, `review/` and `archive/`, and neither is a memory entry, so this count and every entry read below stay at the top level and never recurse into either.
27
27
  - Cleanup is exempt from the two stops above. It works on receipts in `.canon/review/`, and a drained pen is the normal state once Apply has run, so a pen-shaped stop would strand the receipt it exists to delete.
28
28
  - Resolve the main root via `git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`. All review and memory reads anchor here.
29
- - From a linked worktree the file-editing tools refuse every main-root path, so each write below goes out through `Bash` as a plain single command. The receipt and a memory entry are both short and this session has read them whole, so a rewrite replaces the file with a heredoc rather than editing a line inside it. Promotion targets are tracked files at `pwd` and keep taking `Edit`.
29
+ - Every main-root write below goes out as a heredoc, routed the way `session-worktree` states. The receipt and a memory entry are both short and this session has read them whole, so a rewrite replaces the file with a heredoc rather than editing a line inside it. Promotion targets are tracked files at `pwd` and keep taking `Edit`.
30
30
 
31
31
  ## Propose phase
32
32
 
@@ -156,7 +156,7 @@ Action by action type:
156
156
  - **Hand off**: do not edit governance. Archive the memory file only if the user confirmed the handoff explicitly. Otherwise leave it in place.
157
157
  - **Retire**: archive the memory file.
158
158
 
159
- Archiving means creating `.canon/memory/archive/` at the main worktree root and moving the file there under its original name, overwriting any file already at that name. Send the `mkdir -p` and the `mv` as two plain commands rather than joining them with `&&`, which is refused as compound from a linked worktree. Never delete a memory entry. Nothing recovers one from a gitignored folder.
159
+ Archiving means creating `.canon/memory/archive/` at the main worktree root and moving the file there under its original name, overwriting any file already at that name. Send the `mkdir -p` and a plain `mv`, routed the way `session-worktree` states for a main-root move. Never delete a memory entry. Nothing recovers one from a gitignored folder.
160
160
 
161
161
  Do not hand-edit `.canon/memory/index.md`. Once every archive move is done, regenerate it instead:
162
162
 
@@ -164,7 +164,7 @@ Do not hand-edit `.canon/memory/index.md`. Once every archive move is done, rege
164
164
  canon indexes regen --no-stage --root <main-root> <main-root>/.canon/memory/index.md
165
165
  ```
166
166
 
167
- The `PostToolUse` hook that keeps the index current matches `Write|Edit|MultiEdit`, and an archive move is a shell `mv`, so nothing fires on it. Without this call the index keeps a row per archived entry and drifts exactly the way the hand-appended one did. Run it once after the last move rather than per item.
167
+ An archive move is a shell `mv`, which skips the index hook the way `session-worktree` states. Without this call the index keeps a row per archived entry and drifts exactly the way the hand-appended one did. Run it once after the last move rather than per item.
168
168
 
169
169
  Apply promotion edits one at a time via `Edit`. Claude Code's tool permission dialog is the confirmation gate per edit. Never rewrite a whole promotion target.
170
170
 
@@ -101,7 +101,7 @@ Omit empty sections. Do not print `None identified.` in chat.
101
101
 
102
102
  Derive a 2-to-4-word kebab-case slug from the feature description. Write the full plan to `.canon/plans/feature-<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.
103
103
 
104
- From a linked worktree, or from a background session sitting at the main root with none entered, the file-editing tools refuse that path, so the plan 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.
104
+ The plan is a main-root write, from a linked worktree and from a background session sitting at the main root with none entered alike, so it goes out as a heredoc, routed the way `session-worktree` states.
105
105
 
106
106
  The file follows the template in `${CLAUDE_SKILL_DIR}/../../standards/plan.md`. Copy the shape from there rather than from this body, so one edit to the standard moves every plan.
107
107
 
@@ -116,7 +116,7 @@ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to
116
116
 
117
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
- 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.
119
+ The report is a main-root write, so it goes out as a heredoc, routed the way `session-worktree` states.
120
120
 
121
121
  If there are no findings, write `✅ No findings.` to the file with a timestamp.
122
122