@erclx/canon 4.118.0 → 4.119.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 (59) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/canon-rollout/SKILL.md +2 -2
  3. package/claude/skills/design-extract/SKILL.md +1 -1
  4. package/claude/skills/docs-fold/SKILL.md +2 -2
  5. package/claude/skills/draft-ready/REQUIREMENT.md +1 -1
  6. package/claude/skills/draft-ready/SKILL.md +1 -1
  7. package/claude/skills/draft-screencast/SKILL.md +1 -1
  8. package/claude/skills/git-pr/SKILL.md +5 -3
  9. package/claude/skills/memory-capture/SKILL.md +3 -3
  10. package/claude/skills/memory-review/SKILL.md +3 -3
  11. package/claude/skills/plan-feature/SKILL.md +1 -1
  12. package/claude/skills/review-branch/SKILL.md +1 -1
  13. package/claude/skills/review-pr/SKILL.md +1 -1
  14. package/claude/skills/role-planner/SKILL.md +1 -1
  15. package/claude/skills/role-worker/REQUIREMENT.md +1 -1
  16. package/claude/skills/role-worker/SKILL.md +10 -41
  17. package/claude/skills/seed-sync/SKILL.md +1 -1
  18. package/claude/skills/session-compact/SKILL.md +1 -1
  19. package/claude/skills/session-map/SKILL.md +1 -1
  20. package/claude/skills/session-relay/REQUIREMENT.md +7 -1
  21. package/claude/skills/session-relay/SKILL.md +4 -7
  22. package/claude/skills/session-worktree/REQUIREMENT.md +5 -1
  23. package/claude/skills/session-worktree/SKILL.md +7 -1
  24. package/claude/skills/session-worktree/references/main-root-writes.md +24 -0
  25. package/claude/skills/task-board/SKILL.md +4 -3
  26. package/claude/skills/teach-workspace/SKILL.md +4 -4
  27. package/claude/skills/ui-checklist/SKILL.md +1 -1
  28. package/claude/skills/ux-audit/SKILL.md +1 -1
  29. package/claude/skills/ux-measure/SKILL.md +1 -1
  30. package/claude/skills/ux-walkthrough/references/relay.md +1 -1
  31. package/docs/agents/commands.md +2 -2
  32. package/docs/agents/install-and-sync.md +11 -6
  33. package/docs/agents/tasks.md +4 -4
  34. package/docs/target-projects.md +1 -1
  35. package/docs/workflow/ai-workflow.md +9 -9
  36. package/docs/workflow/visual-design-workflow.md +1 -1
  37. package/governance/renames.toml +13 -0
  38. package/governance/rules/core/085-worktrees.md +2 -8
  39. package/package.json +1 -1
  40. package/src/claude/cases/workflow.ts +10 -0
  41. package/src/commands/sync.ts +11 -3
  42. package/src/commands/tasks.ts +9 -8
  43. package/src/design/base.css +19 -19
  44. package/src/design/board.ts +18 -7
  45. package/src/design/neutral.ts +127 -0
  46. package/src/design/regen.ts +6 -4
  47. package/src/design/tokens.ts +5 -2
  48. package/src/gate/stages.ts +4 -4
  49. package/src/gov/adapter.ts +51 -1
  50. package/src/gov/install.ts +13 -3
  51. package/src/gov/renames.ts +76 -0
  52. package/src/gov/stacks.ts +5 -1
  53. package/src/sync/check.ts +19 -4
  54. package/src/sync/engine.ts +67 -3
  55. package/src/tasks/archive.ts +28 -7
  56. package/src/tasks/record.ts +16 -9
  57. package/src/tasks/validate.ts +3 -1
  58. package/standards/tasks.md +5 -3
  59. package/governance/rules/core/091-channel.md +0 -13
@@ -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.119.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
 
@@ -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
 
@@ -219,7 +219,7 @@ gh pr comment <number> --body-file <main-root>/.canon/tmp/handoff/ui-checklist/<
219
219
 
220
220
  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
221
 
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:
222
+ 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
223
 
224
224
  ```bash
225
225
  rm <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
@@ -266,9 +266,11 @@ canon tasks pull-request <number> --plan feature-<slug> --json
266
266
 
267
267
  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
268
 
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.
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 main-root file, which `session-worktree` routes through a verb. That root is the one `session-worktree` resolves on entry.
270
270
 
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.
271
+ 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.
272
+
273
+ 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
274
 
273
275
  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
276
 
@@ -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
 
@@ -169,7 +169,7 @@ Use severity: `critical` (blocks merge), `should-fix` (fix before merge), `minor
169
169
 
170
170
  ## Step 4: post to the PR
171
171
 
172
- 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.
172
+ 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 and send the write as a heredoc, both the way `session-worktree` states. 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.
173
173
 
174
174
  Derive both segments from Step 1. Never pick a suffix by hand, and never reuse a name the folder already holds.
175
175
 
@@ -30,7 +30,7 @@ produces.
30
30
  - Write only into the folder the running skill names: `plan-feature` at `.canon/plans/`, `plan-groundwork` at `.canon/groundwork/<nn>-<slug>/`, and `plan-intake` at `.canon/intake/<nn>-<slug>/`. Each skill's own `## Write scope` carries its exceptions, which is why they are not listed here. A change to one scope then edits one body.
31
31
  - Resolve every one of those folders, and `.canon/tasks/` and `.canon/review/` beside them, at the main worktree root. They are gitignored, so a copy beside a linked worktree is absent rather than empty and reads as a folder nobody has written yet.
32
32
  - Never enter a worktree, never create a branch, and never write a tracked file. A thinking session that builds has stopped being one, and the row loses the independent read the worker's own session was going to bring to it.
33
- - This session holds no worktree, so send each record out as a plain `Bash` heredoc past the same main-root refusal a linked worktree meets. That route skips the write-matched hooks, which are no-ops on a record file.
33
+ - This session holds no worktree, so send each record out as a heredoc, the main-root route `session-worktree` states. That route skips the write-matched hooks, which are no-ops on a record file.
34
34
  - Work the row the launch named. Report a second row met while reading rather than taking it on, since which rows run is the controller's call.
35
35
 
36
36
  ## What to read before deciding
@@ -28,7 +28,7 @@ The draft mark fails from both ends. On 2026-08-31 four sessions re-drafted a pu
28
28
  - State the shared board as read-only for a building session and name what it does instead when a row is owed, since a worker cannot pick a free label without reading every task file and every archive entry
29
29
  - Owe an announcement the moment the pull request opens, carrying the number, the branch, and the task, since that transition is the one interval only the worker can observe
30
30
  - Owe a message before a block becomes an interactive prompt, since a session already waiting on input never reaches the tool round an inbound message drains at
31
- - Name the addressee as the session the launch named, falling back to the roster and saying so, since an operator's own launch names nobody
31
+ - Defer the addressee resolution to `session-relay` and keep only the operator ask and the last-rung inference, which discriminates here because a worker holds a feature branch and a controller does not, and which falls back to the roster and says so, since an operator's own launch names nobody
32
32
  - Keep refusing a dispatcher a first-class move carrying its evidence, since the halts measured so far were correct and the cost of each fell where it belonged
33
33
  - State that a held body may be older than the branch under it, since a plugin skill loads from the marketplace cache rather than from the working tree
34
34
  - Point at `session-worktree`, `auto-ship`, and `review-address` for the steps each already owns
@@ -44,24 +44,15 @@ and nothing else.
44
44
  - Announce the pull request as the ship chain's pull request step returns, carrying the number, the branch, and the task it closes. That transition is the one moment only this session knows, and the controller's review poll no longer starts on a dispatch because of it.
45
45
  - Carry the reach in that same message: the paths this branch wrote that another live plan or `## Run now` row holds, and who holds each, which `canon tasks plan-reach` reports as `claimed`. The gate cleared this branch against a prediction it has since outgrown, so the controller holds a disjointness reading that stopped being true hours ago and nothing else tells it. Say the reach was unread rather than clear when the installed binary carries no such subcommand, and leave the undeclared list to the pull request.
46
46
  - Announce when an address-review pass finishes, as `review-address` Step 8 returns, carrying what was addressed and the PR's new CI state. That transition is the other moment only this session knows, and it is what tells the controller to re-review rather than leaving it to poll for an answer nothing marks as landed.
47
- - Send a block out as a message before it becomes an interactive prompt. A session already waiting on input never reaches the tool round that drains an inbound message, so a relayed answer arrives under the open question and changes nothing.
47
+ - Send a block out as a message before it becomes an interactive prompt.
48
48
  - Send nothing on progress. A worker reporting progress rebuilds, on this side of the channel, the poll the announcement retired on the other.
49
49
 
50
- Address the session the launch named. It names a `sessionId` rather than a name,
51
- so read `canon sessions list --json`, find the row carrying that id, and send to
52
- the `name` on it. Resolve that name at the moment of sending rather than at
53
- launch, since a name is derived from what a session turned out to be doing and
54
- has gone stale inside the hour that a build takes.
55
-
56
- Check that name against the agent listing before sending it. A name is not
57
- unique, and the roster carries no field that separates two live sessions holding
58
- one, so the resolution can end on a string that reaches the wrong session. Send
59
- the name bare where the listing shows one row under it. Where it shows more than
60
- one, complete the address with the `[ref]` that listing prints beside each row
61
- rather than sending to the name alone, which lands on whichever row the channel
62
- resolves first. Two sessions differing only by a trailing ` (3)` is the shape
63
- this meets in practice, and one of them being a controller is what makes the
64
- wrong pick silent.
50
+ Address the session the launch named, which it names as a `sessionId` rather
51
+ than a name. `canon:session-relay` turns that id into an address and carries the
52
+ send, so invoke it rather than resolving a name here. It reads the roster at the
53
+ moment of sending, checks the result against the agent listing, and falls back
54
+ to a copyable block where this session holds no tool to send through. The
55
+ message owed stays in its own bullet above.
65
56
 
66
57
  Ask the operator when the launch named nobody and a person is there to answer.
67
58
  Put the candidate rows to them through the structured question surface, so they
@@ -76,31 +67,9 @@ looks.
76
67
  Infer only where no operator is present. Read `canon sessions list --json` and
77
68
  take the sessions holding no feature branch as the candidates, since a
78
69
  controlling session holds none. Say the addressee was inferred so the reader can
79
- correct it. Never filter that roster by name prefix: every self-dispatched
80
- worker is named `worker-<project>-<slug>`, so a prefix scan returns a sibling or
81
- this session itself, which is the defect that sent messages owed to a controller
82
- somewhere else.
83
-
84
- Report a resolution that returns nothing rather than falling back to a guess.
85
- The roster and the send channel enumerate different populations in both
86
- directions, measured at one moment: a live background session sat on the roster
87
- that the agent listing did not carry, and nine sessions were addressable there
88
- with no roster row at all. Those nine were driving through Remote Control, which
89
- writes no local process record, so a controller working from a phone is exactly
90
- the case this read answers nothing for.
91
-
92
- Send through `canon:session-relay`. It holds the mechanical half of the send,
93
- turning the `sessionId` the launch named into an address at the moment of
94
- sending, checking the result against the agent listing, and falling back to a
95
- copyable block where this session holds no tool to send through. The message
96
- owed stays in its own bullet above.
97
-
98
- The rungs above restate the resolution the relay also carries. What is a
99
- worker's own is the last of them, the inference over sessions holding no feature
100
- branch, which discriminates here because a worker holds one and a controller
101
- does not. The rest is duplicated deliberately, so this body still reads whole
102
- for a session that reaches it with the relay unresolved, which is the ordinary
103
- case in a target that installed governance without the plugin.
70
+ correct it. This rung stays here rather than in the relay because it differs
71
+ between roles, and it discriminates for a worker because a worker holds a
72
+ feature branch and a controller does not.
104
73
 
105
74
  ## Refusing is part of the job
106
75
 
@@ -70,7 +70,7 @@ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to
70
70
 
71
71
  Write the full proposal to `.canon/review/seed-audit-<slug>.md` at the main worktree root. Do not print the proposal inline.
72
72
 
73
- From a linked worktree the file-editing tools refuse that path, so the proposal 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.
73
+ The proposal is a main-root write, so it goes out as a heredoc, routed the way `session-worktree` states.
74
74
 
75
75
  Structure: a summary block at the top, a legend, a scope table, then one H2 per numbered item. Number items across all files so the user can reference them by number. Fuse the status, action, and target into each H2. Every item starts as 📝 pending.
76
76
 
@@ -13,7 +13,7 @@ Write only what a compaction destroys. Everything a later session can read from
13
13
 
14
14
  - If `git rev-parse --git-dir` does not resolve, the note still writes. The filename comes from the work rather than from the branch, so nothing here needs a repository.
15
15
  - Decline where the session holds no reasoning a reader could not get faster from git or from a record already written. Say so in one line and write nothing. A padded note is worse than an absent one, because a reader who finds a note trusts it.
16
- - Resolve `.canon/` at the main worktree root, the way `session-worktree` does. From a linked worktree the file-editing tools refuse that path, so the write goes out through `Bash` as one plain command carrying a heredoc.
16
+ - Resolve `.canon/` at the main worktree root and send the write out as a heredoc, both the way `session-worktree` states.
17
17
 
18
18
  ## Step 1: capture memory
19
19
 
@@ -47,7 +47,7 @@ Record what the verb names under `## Standing cautions`. A refusal is the bounda
47
47
 
48
48
  Follow items 4 through 6. Write only what a compaction destroys and no other artifact already carries, and cite a commit, a task, or a file and line for every claim.
49
49
 
50
- The file sits at the main worktree root, which the file-editing tools refuse from inside a linked worktree while offering a copy under that worktree instead. Take neither the refusal nor the copy. The map is written whole every time, so send it out as one plain shell command carrying a heredoc.
50
+ The file sits at the main worktree root and is written whole every time, so send it out as a heredoc, routed the way `session-worktree` states.
51
51
 
52
52
  ## Step 4: extend it only where a role is held
53
53
 
@@ -39,6 +39,12 @@ role and a send tool was refused too, which left the resolution ladder
39
39
  duplicated in both role bodies for the ordinary case. Both guards came off
40
40
  together, since the mechanics are the same whichever route carries the text.
41
41
 
42
+ No governance rule carries the addressing or the reason for the order, so this
43
+ body is the only surface stating either for a session holding neither role. A
44
+ project that installs governance without the plugin therefore gets no surface
45
+ telling a roleless session to send before it prompts, a cost the operator
46
+ accepted over a rule loading into every session of every governed target.
47
+
42
48
  ## Must
43
49
 
44
50
  - Fire for any session sending to another session, with or without a role and with or without a message-sending tool
@@ -46,7 +52,7 @@ together, since the mechanics are the same whichever route carries the text.
46
52
  - Resolve the addressee from a `sessionId` or a branch at the moment of sending, never by a name prefix
47
53
  - Check the resolved name against the agent listing, sending it bare under a single row and completing it with the `[ref]` under more than one
48
54
  - Send through a message-sending tool where one exists, and compose the copyable block where none does or where the listing carries no row to address
49
- - State block-before-prompt, since the shipped channel rule carrying it for a session holding neither role reaches only a target that installed governance
55
+ - State why a block goes out before a prompt as the only surface carrying that reason, since no rule carries it and a caller restating the reason drifts from this body with nothing comparing them, while each caller still states that the message is owed
50
56
  - Carry the message body verbatim from whatever names it
51
57
  - Open the composed text with a header naming the sender, the addressee, and what the message is
52
58
  - State the header as a default a session may write around, rather than a fixed vocabulary
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: session-relay
3
- description: Relays a message to another session, turning the sessionId the caller named into an address at the moment of sending and composing a copyable block where no send tool exists. Use when asked to "relay this", "message the controller", "send this over to the session that dispatched me", or "tell the other session", with or without a role. Do NOT use to decide what the message says, and do NOT use to assert what a session may write or is on the hook for, which is `role-worker` or `role-planner`.
3
+ description: Relays a message to another session, turning the sessionId the caller named into an address at the moment of sending and composing a copyable block where no send tool exists. Use when asked to "relay this", "message the controller", "send this over to the session that dispatched me", or "tell the other session", with or without a role, and when a session stuck on a question owes the session that dispatched it a message before it stops to ask. Do NOT use to decide what the message says, and do NOT use to assert what a session may write or is on the hook for, which is `role-worker` or `role-planner`.
4
4
  ---
5
5
 
6
6
  # Session relay
@@ -14,7 +14,9 @@ own `## The channel` section where the session holds a role.
14
14
 
15
15
  It fires for a session holding a role and for one holding none. A roleless
16
16
  session reaches this body directly and finds everything it owes stated here,
17
- rather than reading a ladder written for a role it does not have.
17
+ rather than reading a ladder written for a role it does not have. This body
18
+ alone holds the send mechanics and the reason a block goes out before a prompt.
19
+ A calling role or dispatch states only that the message is owed.
18
20
 
19
21
  ## Step 1: name the sender
20
22
 
@@ -78,11 +80,6 @@ session already waiting on input never reaches the tool round that drains an
78
80
  inbound message, so an answer relayed afterwards arrives under the open question
79
81
  and changes nothing. This holds on both routes.
80
82
 
81
- The shipped channel rule states the same thing for a session holding neither
82
- role, so the restatement here is for the target that installed the plugin and
83
- not governance, where that rule never arrives. A session that does load it reads
84
- the rule and this body in agreement rather than one of them alone.
85
-
86
83
  Carry the message body verbatim from whatever names it. This skill composes and
87
84
  never drafts, so a pull request announcement, an address-review announcement, or
88
85
  a blocked question is exactly the text the calling bullet or the dispatch
@@ -19,6 +19,8 @@ A submodule checkout is the state the entry path reads wrong while reporting not
19
19
 
20
20
  A stack that derives its ports from the working directory has the same shape. The number is correct and invisible, and `role-orchestrator` sends a reader here to read it rather than assign one, so the entry that knows the working directory is the surface that owes it.
21
21
 
22
+ A session writing to the main root meets a refusal from inside a linked worktree, and from a background session that entered none. The refusal points at a worktree copy that reports success and that no later session reads, and every skill body writing there carried its own copy of the route around it, which had begun to disagree. The skill that resolves the main root is the one surface every one of those bodies can name, so it owes the routing once.
23
+
22
24
  ## Must
23
25
 
24
26
  - Derive the name from the plan matched to the current branch, falling through the ordered sources rather than picking
@@ -33,6 +35,7 @@ A stack that derives its ports from the working directory has the same shape. Th
33
35
  - Report the same way when the project declares no package manifest, since silence there reads as a passing check
34
36
  - Report the port offset this worktree derives, and say so when the project installs no derivation
35
37
  - Name `git-worktree` where the description declines a list, cleanup, or rotation request, matching the pointer that skill already carries back
38
+ - State the route each kind of main-root write takes, being a heredoc for a whole-file create, a `canon` verb for a change inside an existing file, and a plain `rm` or `mv` for a delete or a move, along with the index cost a shell write carries
36
39
 
37
40
  ## Must not
38
41
 
@@ -42,6 +45,7 @@ A stack that derives its ports from the working directory has the same shape. Th
42
45
  - Delete or overwrite a branch or a worktree directory that already carries the target name
43
46
  - Rename when the worktree was entered by path, since that branch already has its own identity
44
47
  - Invoke the exit path, which is the user's call
48
+ - Enter a worktree or run a guard for a session that loaded the skill to route a main-root write
45
49
 
46
50
  ## Guards
47
51
 
@@ -55,4 +59,4 @@ A stack that derives its ports from the working directory has the same shape. Th
55
59
 
56
60
  - Listing, cleaning up, or rotating worktrees, which `git-worktree` owns and the description names, since this file routes no session
57
61
  - Leaving a worktree, which the user decides
58
- - What runs inside the worktree once entered, which the caller drives
62
+ - What runs inside the worktree once entered, which the caller drives, apart from the route a main-root write takes
@@ -1,12 +1,14 @@
1
1
  ---
2
2
  name: session-worktree
3
- description: Enters a Claude Code worktree at `.claude/worktrees/<name>/` with a name derived from the active plan or branch. Use when asked to "enter a worktree", "start a worktree", "work in a worktree", or at the plan-to-execute boundary after `/plan-feature`. Do NOT use to list, clean up, or rotate worktrees (use `git-worktree`).
3
+ description: Enters a Claude Code worktree at `.claude/worktrees/<name>/` with a name derived from the active plan or branch. Use when asked to "enter a worktree", "start a worktree", "work in a worktree", or at the plan-to-execute boundary after `/plan-feature`. Also use when an `Edit` or `Write` to a main-root file such as a plan, a task, or a memory entry was refused for session isolation. Do NOT use to list, clean up, or rotate worktrees (use `git-worktree`).
4
4
  ---
5
5
 
6
6
  # Session worktree
7
7
 
8
8
  Wrap the `EnterWorktree` entry path with name derivation so the user does not pick a name by hand.
9
9
 
10
+ The Guards and Steps gate entry alone. A session loading this skill for a main-root write reads `## Main-root writes`, enters nothing, and runs no guard, which is what keeps a session already inside a linked worktree off the first guard's stop.
11
+
10
12
  ## Guards
11
13
 
12
14
  - If `git rev-parse --git-dir` and `git rev-parse --git-common-dir` differ, the session is already inside a linked worktree. Stop: `❌ Already in a worktree. Run ExitWorktree first.`
@@ -156,3 +158,7 @@ The helper refuses a folder left behind after its worktree was removed, which St
156
158
  The offset is what `role-orchestrator` sends a reader here to read rather than assign, and what an operator overrides through `WORKTREE_PORT_OFFSET` when two worktrees derive the same value. Deriving it correctly and printing it nowhere leaves both instructions naming a number no surface emits.
157
159
 
158
160
  Do not invoke `ExitWorktree` from this skill. Exit is the user's call.
161
+
162
+ ## Main-root writes
163
+
164
+ Route a main-root write by what it does to the file: a whole-file create goes out as a heredoc, a change inside an existing file goes through a `canon` verb, and a delete or a move runs as a plain `rm` or `mv`. Read `${CLAUDE_SKILL_DIR}/references/main-root-writes.md` for which tools the refusal covers, the mechanics of each route, and the index cost a shell write carries.
@@ -0,0 +1,24 @@
1
+ ---
2
+ title: Main-root write routing
3
+ description: Which tools the isolation refusal covers, the route each kind of main-root write takes, the fallback when a heredoc is refused, and the index cost a shell write carries
4
+ ---
5
+
6
+ Shared session scratch, being `.canon/plans/`, `.canon/review/`, `.canon/memory/`, and `.canon/tasks/`, lives at the main worktree root. Resolve that root with the first `worktree` entry of `git worktree list --porcelain`, falling back to `pwd` outside a git repository.
7
+
8
+ ## The refusal
9
+
10
+ - The refusal is tool-scoped rather than path-scoped. `Edit` and `Write` are refused for every main-root path, `Read` resolves there normally, and `Bash` writes there.
11
+ - A background session that entered no worktree at all meets the identical refusal writing to the main root, since the guard gates on isolation rather than on sitting inside a linked worktree.
12
+ - The refusal names session isolation and points at a worktree copy. That copy is a second gitignored file no later session reads, so never take the redirect. The redirected write reports success, which is how a file gets lost with nothing saying so.
13
+
14
+ ## Route by what the write does to the file
15
+
16
+ - A whole-file create goes out as one plain `Bash` command carrying a heredoc. Send `mkdir -p` for a missing folder as its own command first, since a `&&` compound is refused for complexity.
17
+ - When the heredoc itself is refused, which can happen on a body quoting shell syntax, a session inside a linked worktree can `Write` the file there and copy it out with a two-argument `cp` to the main-root path. The copy carries no syntax to inspect.
18
+ - A background session that entered no worktree has nothing to `Write` into, so it sends a short file as one `printf '%s\n'` command carrying each line as an argument. When the file is too long for that, it reports the refusal rather than taking the redirect.
19
+ - A change inside a file that already exists goes through a `canon` verb that resolves the main root in-process, such as `canon tasks outcome` or `canon tasks pull-request`. Where no verb covers the edit, read the whole file and write it back whole through the heredoc route. Never reach for a stream editor, which rewrites the line it anchored to and exits zero on a non-match.
20
+ - A delete or a move runs as one plain `rm` or `mv` per call rather than chained.
21
+
22
+ ## The index cost
23
+
24
+ A shell write skips every `PostToolUse` hook, since those match `Write|Edit|MultiEdit` and never fire on `Bash`. That costs nothing on `.canon/plans/` and `.canon/review/`, which no hook indexes, and costs the generated `index.md` on `.canon/tasks/` and `.canon/memory/`. Regenerate positionally after a shell write there, by an explicit `canon indexes regen` call, unless the verb that made the write already calls it, as `canon tasks archive` does.
@@ -12,7 +12,7 @@ Read `${CLAUDE_SKILL_DIR}/../../standards/tasks.md` before writing any file. It
12
12
  ## Guards
13
13
 
14
14
  - Resolve the board at the main worktree root, not `pwd`. Run `git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd` outside a git repo. Every read and write below resolves against that root. The board is gitignored scratch shared across worktrees, so a linked worktree writing to its own `pwd` creates a second board nothing else reads.
15
- - From a linked worktree the file-editing tools refuse that root, so a new task file goes out through `Bash` as a plain single command carrying a heredoc. Archiving already runs through `canon tasks archive`, which resolves the root in-process. Marking an outcome shipped is `docs-fold` and runs through `canon tasks outcome`. Resolve that root the way `session-worktree` does.
15
+ - A new task file is a main-root write, so it goes out as a heredoc. Archiving already runs through `canon tasks archive`, which resolves the root in-process. Marking an outcome shipped is `docs-fold` and runs through `canon tasks outcome`. Resolve that root and route the write the way `session-worktree` states.
16
16
  - If `.canon/tasks/` does not exist at that root, stop: `❌ No .canon/tasks/ board. Run canon claude init to set it up.`
17
17
  - Route on the request rather than on a flag. Creating names work that does not exist yet, archiving names a task file already shipped, and declining names one decided against. If the request fits none of the three, stop: `❌ Ambiguous. Say whether to create a task, archive one, or decline one.`
18
18
  - Never hand-edit `.canon/tasks/index.md`. A hook regenerates it from sibling frontmatter after a write. Do not run the regen command directly, except after a shell write from a linked worktree: the hook matches `Write|Edit|MultiEdit` and nothing fires on `Bash`, so that one case regenerates explicitly with `canon indexes regen --no-stage --root <main-root> <main-root>/.canon/tasks/index.md`.
@@ -53,7 +53,7 @@ Check the roster for a live orchestrator before writing either file. Read `canon
53
53
 
54
54
  - **Found.** Do not write `priority.md` or `backlog.md`. Report the surface the row would take and the reason worked out above, then hand off by messaging that session with the same information so it places the row itself rather than two sessions writing the board at once.
55
55
  - **Not found, and this session's own name (already read above) starts with `worker-` or `planner-`.** Do not write either file. Those two role bodies ban a board write with no exception, and an orchestrator absent from the roster is not consent to break it. Report the row and the surface it would take, so the operator or a later orchestrator places it.
56
- - **Not found otherwise, or the roster read fails.** Write the row directly, as today. A failed read is indistinguishable from a solo project holding no orchestrator, and stopping would strand the row on the one path this section otherwise keeps unconditional. From a linked worktree the file-editing tools refuse the main root, so the row lands through the same `Bash` route the file itself took.
56
+ - **Not found otherwise, or the roster read fails.** Write the row directly, as today. A failed read is indistinguishable from a solo project holding no orchestrator, and stopping would strand the row on the one path this section otherwise keeps unconditional. The row is a main-root write too, so it lands through the same heredoc route the file itself took.
57
57
 
58
58
  Say which branch fired and why in the report. The call is a judgment restated on every sweep rather than a property of the task, and a placement with no stated reason is one the next sweep re-derives from nothing.
59
59
 
@@ -98,7 +98,7 @@ git fetch origin main --quiet && git log origin/main --oneline -20
98
98
 
99
99
  Match the shipped outcomes against that log, widening to `gh pr list --state merged --limit 20` when a remote is configured and the log does not settle it. When the work is not on `main`, name the task and stop: `❌ Work not on main. Archiving now loses the task if the pull request is abandoned.`
100
100
 
101
- The board is gitignored, so an archived task has no history behind it and nothing restores one archived early. Skip this check when the task carries a `Pull request:` line and that pull request is merged, since the number already proves what the log is being read for.
101
+ The board is gitignored, so an archived task has no history behind it and nothing restores one archived early. Skip this check when the task carries a `Pull request:` line and the last number it lists is merged, since the number already proves what the log is being read for.
102
102
 
103
103
  ### Step 2: run the archive
104
104
 
@@ -118,6 +118,7 @@ Each reason has one resolution and none of them is to archive around it:
118
118
 
119
119
  - `open-outcomes`: the named outcomes are unmarked or genuinely open. Run `docs-fold` when the work shipped and nothing marked it. Leave the task on the board when the outcome is real. When the work is being abandoned, cut it by striking the body, `- ~~<outcome>~~ <why>`, whatever the checkbox holds, so the board records what was dropped rather than meeting this refusal a second time.
120
120
  - `ambiguous`: two tasks name one pull request, which is the misfile `${CLAUDE_SKILL_DIR}/../../standards/tasks.md` rules out. Resolve the citation by hand, since no sweep repairs it.
121
+ - `earlier-slice`: the number is listed on the task but is not the last, so a later slice is still to merge. Leave the task on the board. Its last slice's merge archives it.
121
122
  - `no-match`: the stem or number names nothing on the board. Check the name against the listed stems.
122
123
  - `bad-input`: the command line was wrong rather than the board. Read the message, fix the arguments, and run it again. Nothing on the board needs repair, which is what separates this from the two above.
123
124
 
@@ -32,10 +32,10 @@ canon teach list --json
32
32
 
33
33
  It reports every workspace with its path, its counts, and the ordinal a new one would take. A `no-teach` refusal means the project has opened none yet, which is the new-workspace path in Step 1 rather than a reason to stop. The open verb creates the folder.
34
34
 
35
- From a linked worktree the file-editing tools refuse every path under the main root and offer a worktree copy instead, which is a second file no later session reads. Never take that redirect. The route splits by what the write does to the file:
35
+ Every path under the main root is a main-root write, routed the way `session-worktree` states. The route splits by what the write does to the file:
36
36
 
37
- - Creating a whole file that does not exist yet, which is a lesson, a reference page, and a learning record, goes out as one plain shell command carrying a heredoc
38
- - Changing a line inside a file that already exists goes through the verb that owns it, since the stream editors are banned and no other shell route reaches it
37
+ - Creating a whole file that does not exist yet, which is a lesson, a reference page, and a learning record, goes out as a heredoc
38
+ - Changing a line inside a file that already exists goes through the verb that owns it
39
39
 
40
40
  ## Step 1: open or resume
41
41
 
@@ -239,7 +239,7 @@ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to
239
239
 
240
240
  The handoff is its own file rather than a shared one. The routed-facts file another skill writes is deleted by whichever pass folds it, so a second producer's unread work goes with it, and a sibling path costs the folding skill one more read and removes the interaction.
241
241
 
242
- An append is a whole-file operation, so send it as a plain single `Bash` command carrying a heredoc, per Step 0. Then tell the operator that `/docs-fold` folds the file in from a branch. The proposal costs nothing tracked and runs anywhere, while the page it describes is a tracked file, so the fold is a worktree operation and the workspace it came from is not.
242
+ An append is a whole-file operation, so send it as a heredoc, per Step 0. Then tell the operator that `/docs-fold` folds the file in from a branch. The proposal costs nothing tracked and runs anywhere, while the page it describes is a tracked file, so the fold is a worktree operation and the workspace it came from is not.
243
243
 
244
244
  ## Output
245
245
 
@@ -72,7 +72,7 @@ When a checklist is produced, write it directly to `.canon/tmp/handoff/ui-checkl
72
72
 
73
73
  The path names the artifact rather than this skill, so it does not move when the skill is renamed. A checklist written by an older binary still lands where the consumer looks.
74
74
 
75
- From a linked worktree the file-editing tools refuse that path, so the checklist 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.
75
+ The checklist is a main-root write, so it goes out as a heredoc, routed the way `session-worktree` states.
76
76
 
77
77
  Skip the file write when there is nothing to verify visually and nothing shipping untested.
78
78
 
@@ -64,7 +64,7 @@ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to
64
64
 
65
65
  Write the full report directly to `.canon/review/ux-audit-<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.
66
66
 
67
- 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.
67
+ The report is a main-root write, so it goes out as a heredoc, routed the way `session-worktree` states.
68
68
 
69
69
  If there are no observations, write `✅ No observations.` to the file with a timestamp.
70
70
 
@@ -108,7 +108,7 @@ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to
108
108
 
109
109
  Write the full reading directly to `.canon/review/ux-measure-<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.
110
110
 
111
- From a linked worktree the file-editing tools refuse that path, so the reading 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.
111
+ The reading is a main-root write, so it goes out as a heredoc, routed the way `session-worktree` states.
112
112
 
113
113
  The `.canon/review/` directory is gitignored. Do not stage or commit the file.
114
114