@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
@@ -0,0 +1,85 @@
1
+ ---
2
+ title: Orchestrator launch runbook
3
+ description: The launch every dispatch kind shares, being the build, review-address, and planning templates, the session name and controller id each carries, why the command sits at position zero, and what the brief may carry
4
+ ---
5
+
6
+ Run this once `orchestrator-dispatch.md` has cleared a row, or from its planning or review shape below when that runbook's checks do not bind. The model each template names comes from `## Pick the model` in that runbook.
7
+
8
+ ## Dispatch
9
+
10
+ ```bash
11
+ claude --bg --model <model> -n "worker-<project>-<slug>" "/canon:auto-ship <plan>
12
+ Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id through canon sessions list --json, which carries sessionId per row, at the moment you send, and never resolve an addressee by name prefix. Message it when the pull request opens, carrying the number, the branch, the head sha, the CI state, and every point you departed from the plan on, and message it again if you stop on a question."
13
+ ```
14
+
15
+ `--bg, --background` starts the session as a background agent and returns immediately, `-n, --name` sets the display name that tells a self-dispatched worker from an operator's own launch in `canon sessions list`, and `--model` overrides the inheritance `## Pick the model` states. Pass `-n` on every dispatch rather than letting the client derive one. A launch that omits it leaves the session named for a fragment of its own identifier, which is both its address on the send channel and the whole of what the operator sees for it in agent view.
16
+
17
+ The prefix reads `worker-` because that is the role it marks. A prefix naming a role the session does not hold sends a worker filtering the roster for that string to a sibling or itself on every row. Nothing matches the prefix programmatically, which keeps a rename down to three strings.
18
+
19
+ `<project>` is the basename of the main worktree root, not of wherever the dispatcher happens to be running. Resolve the main root first, the way `session-worktree` Step 1 does, since a bare `git rev-parse --show-toplevel` inside a linked worktree returns the worktree path rather than the project's. `claude agents` lists every session on the machine with no path column and no per-project filter, so `<project>` in the name is the only thing left telling two fleets apart.
20
+
21
+ Read `<dispatcher-id>` with `canon sessions list --self --json` and interpolate the `sessionId` that row carries. Carry the id rather than the name. A name is derived from whatever the session turned out to be doing and can be renamed minutes after launch, inside the window a worker announces its pull request in, so a name written into the prompt is aimed at a send that happens after it goes stale.
22
+
23
+ Where the installed CLI answers `--self` with an unknown option, that flag is newer than the release the target holds. Read the `sessionId` from the record the client writes for this session under its configuration directory, and say which route answered so the reader knows whether the id was read or inferred.
24
+
25
+ The worker resolves that id back to a name through `canon sessions list --json`, which carries `sessionId` per row, rather than through the agent listing, which prints a name and a short ref and no id at all. A worker reaching for the listing first therefore finds no lookup and can conclude there is none. That failure is silent in both directions: the session has nothing useful to do with the message it owes and goes idle holding it, and nothing on this side reports the quiet, so the loss surfaces as a missing worktree or a pull request that never opens rather than as anything watching for it.
26
+
27
+ The template carries no worktree call. `auto-ship` Step 0 invokes `canon:role-worker` and then `canon:session-worktree` itself, and neither carries the flag, so both are reachable through the `Skill` tool regardless of where a call to them would sit in a prompt. The autoship call carries `<plan>`, the same file `orchestrator-dispatch.md` read to derive the branch, so its Step 1 takes it as the caller-supplied plan rather than re-deriving one from the slug the worker's branch happens to carry.
28
+
29
+ The template names no branch, and it does not need to. `auto-ship` Step 0 runs `canon tasks plan-branch <plan>` on the same file the candidate was derived from, and hands the `<type>/<slug>` it reports to `session-worktree` as its tier 0 argument, so the two sides agree by calling one derivation rather than by a string copied between them. What still travels on judgment is the fallback: a worker whose installed binary carries no `plan-branch` derives by prose.
30
+
31
+ ### Position zero
32
+
33
+ Put a command whose skill carries `disable-model-invocation: true` at position zero of the launch prompt, followed by a space and its argument, with nothing before it. The client expands a slash command there as a user invocation, which is the route the flag permits. Anything reaching the session as prose falls to the model, which invokes it through the `Skill` tool, and that route answers a flagged skill inconsistently. `auto-ship` carries the flag. A comma or other punctuation glued to the command name at position zero has failed to expand, so treat the space as part of the rule until a dispatch shows otherwise.
34
+
35
+ Never depend on the `Skill` tool route for a flagged skill. A refusal there tells the session not to replicate the workflow by other means, so a refused worker stops with a clean worktree, which is the correct outcome. The refusal is sticky inside a session, so recovery belongs to whoever writes the next prompt: re-dispatch onto the same branch with the build template above.
36
+
37
+ The review shape and the planning shape below depend on no expansion at all. None of `role-worker`, `review-address`, `role-planner`, or `plan-feature` carries the flag, so both correctly keep their leading word regardless of the delimiter or the position it sits at.
38
+
39
+ The same rule reaches a human relay. Hand an operator one message carrying one command at position zero, since two chained blocks pasted as separate messages can land as one, where everything after the first command's name reads as that command's argument and the second never fires.
40
+
41
+ ### What the brief may carry
42
+
43
+ The prompt carries pointers and standing context, and stops there. The branch and the plan stay arguments, because a skill resolves an argument through a documented ladder and reads no prose at all. What the prose reaches is the worker's judgment, so it holds only what a session has to weigh:
44
+
45
+ - Name the addressee and what it is owed, which the two message clauses above already do.
46
+ - Carry standing context this session holds that a cold one cannot derive, such as a constraint settled in conversation that never reached the plan.
47
+ - Leave out anything scope-shaped. A file list, a naming convention, or a check to run belongs in the plan, where the review reads it back against the diff. Scope that arrives as prose is scope nothing verifies afterward.
48
+
49
+ Report the dispatch as loudly as the human-launch line it replaces: name the branch, the model, the task, and the session name, so a person reading the transcript can follow what fired without watching it happen.
50
+
51
+ ## Dispatch to address a review
52
+
53
+ `review-address` is a single pass, not a chain, so a launch naming it alone reaches no `role-worker` and takes no role, which owes no message either. Reach the role directly on this launch instead of wrapping a second chain around one skill that has none of its own.
54
+
55
+ The branch already exists here, opened by whatever built it, so this shape skips the plan-derived name the build shape resolves above. Take `<branch>` off the pull request's own head ref. Enter the worktree the original build left on disk, `.claude/worktrees/<slug>/`, with `EnterWorktree`'s `path` form when it is still there, or `git worktree add .claude/worktrees/<slug>/ <branch>` when it was cleaned up, so `<slug>` is that directory name either way.
56
+
57
+ `EnterWorktree` refuses that path in the ordinary case, because the original build session stays registered against its own worktree after going idle and holds a harness-level lock the roster does not report. Work in the folder directly with `Bash`, `Read`, and `Edit` instead of retrying the tool.
58
+
59
+ ```bash
60
+ claude --bg --model <model> -n "worker-<project>-<slug>" "Enter the worktree for <branch> at .claude/worktrees/<slug>/, creating it from that branch if the folder is gone. Run /canon:role-worker, then /canon:review-address. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id through canon sessions list --json, which carries sessionId per row, at the moment you send, and never resolve an addressee by name prefix. Message it when the address pass finishes, carrying what was addressed and the PR's CI state, and message it again if you stop on a question."
61
+ ```
62
+
63
+ `<dispatcher-id>`, `<model>`, and `<project>` resolve the same way the build shape resolves them above.
64
+
65
+ Take this shape wherever a review needs answering and no live session already holds the branch. Where one does, message it to run `review-address` instead, per the loop's own step 6, since a session already there needs no second one dispatched onto the same branch.
66
+
67
+ That check is blind to a session working through the direct-path fallback above, since a session that never runs `EnterWorktree` never moves its registered branch off `main`, so `canon sessions list --branch` reports nothing holding it. A dispatch landing on a branch worked that way collides with nothing the check can see.
68
+
69
+ ## Dispatch to plan a row
70
+
71
+ `plan-feature` is a procedure rather than a role, so a launch naming it alone reaches no `role-planner` and takes no role, which owes no message either. Reach the role directly on this launch, the way the build shape above reaches `role-worker`, rather than retyping the role's obligations as prose into each launch.
72
+
73
+ No branch and no worktree exist here and none is created. A planner writes one gitignored file at the main worktree root, so this shape names the row's task file rather than a branch and opens with the role instead of a worktree call. That write meets the isolation guard the same way a linked worktree's main-root write does, with no worktree here to redirect it to, so `role-planner` sends it as a `Bash` heredoc rather than through `Write`.
74
+
75
+ ```bash
76
+ claude --bg --model <model> -n "planner-<project>-<slug>" "Run /canon:role-planner, then /canon:plan-feature <task>. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id through canon sessions list --json, which carries sessionId per row, at the moment you send, and never resolve an addressee by name prefix. Message it when the plan lands, carrying the path and what the task file got wrong, and message it again if you stop on a question."
77
+ ```
78
+
79
+ `<task>` is the row's task file path and `<slug>` the slug its plan will take, resolved off the row the way the build shape resolves one off a plan. `<dispatcher-id>`, `<model>`, and `<project>` resolve the same way they do above. The prefix reads `planner-` for the reason the worker's reads `worker-`, which is that it marks the role of the session it names rather than the one that launched it.
80
+
81
+ None of the three checks in `orchestrator-dispatch.md` binds this shape. The branch check has no candidate to read, and the disjointness gate has nothing to compare, since a planner writes one file no track in flight can hold. The plan-answer gate reaches no plan at all, because the planner is dispatched to write the file a build would later read, so running it here would refuse every planning dispatch over a plan nobody has written yet.
82
+
83
+ What a planning dispatch owes instead is the reverse reading, because the plan it produces carries a constraint per track in flight and a row planned during a wave is planned against a tree that wave is changing. `role-planner` composes the session roster with the pull request list for that read rather than reading pull requests alone, which is why the brief carries no branch list for it: the planner takes this reading itself either way.
84
+
85
+ One row per dispatch. A session reused across a batch pays the context load once and ages its picture of the tree while it works, which is what puts the in-flight read on the task rather than on the batch, and one that compacts mid-batch loses the reasoning behind its earlier plans with nothing reporting it. Cap a reused session where the saving is worth it and say what the cap was.
@@ -0,0 +1,61 @@
1
+ ---
2
+ title: Orchestrator output formats
3
+ description: The invocation block with its four sections, the three-slot shape every later report opens with, and the rule against a shape for a correction
4
+ ---
5
+
6
+ Read this on invocation, before reporting the state of play, and again before any later report.
7
+
8
+ ## On invocation
9
+
10
+ ```plaintext
11
+ Orchestrator ready.
12
+
13
+ Ready to build (hand each to its own worker):
14
+
15
+ <feature>
16
+ plan: .canon/plans/feature-<slug>.md
17
+ → /auto-ship
18
+
19
+ <feature>
20
+ no plan yet. A handoff without a plan has no scope
21
+ → /plan-feature here first
22
+
23
+ In flight (this session's workers, building now):
24
+
25
+ <session name> <branch> <model>
26
+ task: <row>, <n> commits, <n> files changed
27
+
28
+ In review (your turn):
29
+
30
+ PR #<n> <title>
31
+ → /review-pr
32
+
33
+ Merge order: #<a> before #<b> (shared seam: <files>).
34
+
35
+ Next: <the single most useful action>
36
+ ```
37
+
38
+ Omit any section with nothing in it. Recommend a handoff only for a plan whose file set is disjoint from every track already in flight, per `## Parallelism` in the skill body.
39
+
40
+ `In flight` covers the state between the other two, which lasts ten to thirty minutes and is most of what an operator sees once this session dispatches its own workers. Read the progress figures off each worker's worktree rather than from the worker, since a busy status says a session is alive and nothing about whether it is moving, and name the model because a dispatcher now picks one per row.
41
+
42
+ Leave a plan out of `Ready to build` once a row in flight names it. The plan file stays in `.canon/plans/` for the whole build, so listing it there recommends handing off work already underway, and the disjointness rule in `## Parallelism` withdraws the recommendation only for a reader who already knows what is running.
43
+
44
+ The block opens on the board rather than on a version, because no committed file states one, and a version line would restate what a reader can already see on the rows, dated by nothing.
45
+
46
+ ## Every later turn
47
+
48
+ The block above covers invocation alone. A sweep report, a board report, and an analysis each end in something the human decides, so each opens with the same three slots and puts its evidence underneath:
49
+
50
+ ```plaintext
51
+ State: <what changed since they last looked>
52
+
53
+ Decisions:
54
+ 1. <one line each, or "none open">
55
+
56
+ Next: <the single most useful action>
57
+ ```
58
+
59
+ Keep the detail below the block and keep it skippable. A decision reached at the bottom of three paragraphs has been buried, which is the failure this shape exists to prevent. Reuse the vocabulary above rather than inventing a second one, and keep a file set on the row claiming it so the reader can check a disjointness claim instead of taking it.
60
+
61
+ Write no shape for a correction. A correction is a sentence, and a format for admitting error invites ceremony where plainness is the whole value.
@@ -59,7 +59,7 @@ Run `plan-feature` for each, carrying the constraint per in-flight track that `#
59
59
 
60
60
  Stop where `## Parallelism` stops rather than planning every row the pass cleared. A plan whose file set collides with every track in flight is one nobody can dispatch.
61
61
 
62
- Do not restate the refill procedure. `role-orchestrator` owns it under `## Refilling the ready queue` and `orchestrator-sweep.md` wraps it for a batch of merges, so this pass promotes through that method rather than a second one.
62
+ Do not restate the refill procedure. `orchestrator-refill.md` owns it and `orchestrator-sweep.md` wraps it for a batch of merges, so this pass promotes through that method rather than a second one.
63
63
 
64
64
  ## Two results that are not a re-test
65
65
 
@@ -82,4 +82,4 @@ Still backlogged: <row>, <what was checked> found unchanged
82
82
 
83
83
  Omit any row with nothing in it. Name what was measured rather than the verdict alone, since a re-test the reader cannot check is the same claim the row already carried.
84
84
 
85
- Lead the reply with the three slots under `### Every later turn` in `role-orchestrator`, so the human reads what they own before the evidence for it.
85
+ Lead the reply with the three slots under `## Every later turn` in `orchestrator-output.md`, so the human reads what they own before the evidence for it.
@@ -68,7 +68,7 @@ The state reaches every stalled dispatch, since one threshold governs the headin
68
68
 
69
69
  ### The count behind the review fallback
70
70
 
71
- The report is also where the count in `## Parallelism` is legible. That threshold trips on open pull requests awaiting a first pass, which is what `OPENED` and a pull request with no prior pass name here and what `SEEN` excludes, so read the count off these lines rather than off `gh pr list`, which counts a branch closed out and waiting on a merge the same as one nobody has read. It is a separate condition from the poll-start fallback above, which decides when this loop runs rather than where a review runs.
71
+ The report is also where the count in `orchestrator-review-fallback.md` is legible. That threshold trips on open pull requests awaiting a first pass, which is what `OPENED` and a pull request with no prior pass name here and what `SEEN` excludes, so read the count off these lines rather than off `gh pr list`, which counts a branch closed out and waiting on a merge the same as one nobody has read. It is a separate condition from the poll-start fallback above, which decides when this loop runs rather than where a review runs.
72
72
 
73
73
  The count used to read low, and it erred in the direction that breaks the trigger. A review's `commit.oid` is stamped with the head at submission rather than with the commit the reviewer read, so an author pushing between the diff read and the post left the pass recorded against a commit it never saw, and `SEEN` then fired on a head still awaiting its first look at that delta.
74
74
 
@@ -0,0 +1,56 @@
1
+ ---
2
+ title: Orchestrator refill runbook
3
+ description: The queue-refill procedure run after a merge, on a thin ready list, or mid-wave, its report, and the rules for writing the board
4
+ ---
5
+
6
+ Run this when `## Refilling the ready queue` in the skill body names one of its three triggers. `orchestrator-sweep.md` wraps it for a batch of merges and adds the plan re-verification that a merge invalidates.
7
+
8
+ The third trigger reaches a window the first two cannot. A session with three workers mid-build has merged nothing and has watched its ready list sit still, so both reactive triggers stay silent across the one stretch where planning costs the session nothing, and the wave finishes into an empty `## Run now`. Nothing starts this pass, in the way nothing starts the review poll, so it holds only while the session applies it.
9
+
10
+ It counts unclaimed plans against workers rather than reading the reserve in step 4, which is sized for one worker finishing and falls short of a wave landing together. A plan a worker has already taken serves nobody who finishes next, so counting it is what lets the queue read full while it is about to empty. How many to write forward past that floor is the parallelism call rather than a count of free slots, and `## Parallelism` in the skill body states what binds it.
11
+
12
+ ## Running the refill
13
+
14
+ 1. Run `gh pr list --state open` and `git log --oneline -8`. Report any pull request whose review has not been posted and stop for that one first.
15
+ 2. For each pull request merged since the last sweep, place every finding it produced. Route a finding that changes a rule to the standard or rule that states it, one that changes another task to that task's Findings, and one that overturns a groundwork lean to that folder marked answered. Never leave a finding in a pull request thread alone.
16
+ 3. Archive what closed.
17
+ - A task whose outcomes are all `[x]` runs `docs-fold` for the plan sweep, then `task-board` to archive
18
+ - A task whose outcomes describe standing policy rather than a deliverable never closes on its own, so hand it to a worker to encode the policy where it is enforced, then cut the outcomes with the reason recorded and archive once that branch merges. Encoding it from this session would write a tracked file, which `## Boundaries` in the skill body forbids.
19
+ 4. Read `.canon/tasks/priority.md` and count entries under its `## Run now` heading that carry a written plan. Keep one in reserve beyond what is running.
20
+ 5. Promote from the top of `## Needs a plan`, which is where the last sweep recorded what to plan next. Depart from that order when something has changed under it and say what changed, since a position nobody honors is the ordering going stale on the surface built to hold it. What sets the order in the first place is whether a task establishes functionality rather than how old it is, so prefer a task that adds or proves a mechanism over one that trims, tidies, or audits an existing surface.
21
+ - Re-take the demotion half of the board-or-backlog call while the file is open. A row that has stopped being near-term moves to `backlog.md`, one line removed from `priority.md` and written there, per the standard's test. The promotion half is `orchestrator-parked.md`'s to run as a full walk over every backlog row, so this step does not repeat it. A row that pass clears already sits at the bottom of `## Needs a plan` before this sweep reads the heading.
22
+ 6. Before promoting a candidate, list the files it touches against every task already running, per `## Parallelism` in the skill body. Name the overlap and serialize when the sets are not disjoint.
23
+ - A candidate held by something outside the tree stays where it is whatever those sets show. A collision is one of the reasons a task cannot start, so disjointness clears that reason alone and leaves an external condition standing.
24
+ 7. Write a plan for each newly promoted task with `plan-feature`, carrying the in-flight constraint that `## The loop` in the skill body states, then report:
25
+
26
+ ```plaintext
27
+ Capture: owed since <the last handoff, or session start when none has run>
28
+ Findings placed: <finding> → <destination>
29
+ Archived: <task>
30
+ Promoted: <task>, touches <surfaces>, parallel with <task> because <disjoint sets>
31
+ Serialized: <task> behind <task>, both write <file>
32
+ Backlogged: <task>, because <what stopped being near-term>
33
+ Ready now: <tasks with plans, and what each waits on>
34
+ ```
35
+
36
+ The capture row states a standing debt rather than a per-run result. Running capture from here costs the operator a pass between merges while nothing ships, so the row leaves the timing to them and dates the debt, since a capture owed for twenty minutes and one owed all day want different answers and undated text reads the same either way. `orchestrator-handoff.md` holds the step that pays it.
37
+
38
+ That block is the detail. Lead the reply with the three slots under `## Every later turn` in `orchestrator-output.md`, so the human reads what they own before the evidence for it.
39
+
40
+ Treat a task that edits `canon/context/` entries wholesale as conflicting with every other task, because the root instruction file requires each task to update its own domain entry as it lands.
41
+
42
+ Do not promote a task to fill the queue when nothing qualifies. A thin queue is a real answer and it beats a plan nobody needed.
43
+
44
+ ## Writing the board
45
+
46
+ Promoting, demoting, and archiving a row all write `.canon/tasks/priority.md`, and this session is the only writer apart from `canon tasks archive`. Moving a task between the board and `.canon/tasks/backlog.md` writes both files, and this session is that file's only writer.
47
+
48
+ - Edit the file with the file-editing tool. A shell stream editor and an inline string replace both exit clean on a non-match, so a promotion that matched nothing leaves the board wrong with nothing reporting it, and the file-editing tool errors instead.
49
+ - Write both halves of a move before reporting it. A row removed from one surface and not written to the other leaves a task file nothing names, and the folder is gitignored with no history to recover the row from. `canon tasks validate` reports that state, so run it after any move.
50
+ - Put the reason a row sits where it does in its Waiting on cell. Position is the ordering and the cell is where the ordering's rationale lives, so a row promoted with the cell left alone carries an order the next sweep cannot check.
51
+ - Put a pointer in the Plan column, never prose. `## Run now` claims a written plan covers every open outcome, and `auto-ship` refuses at its guard when it follows the column and finds no plan, which spends a worker dispatch to learn what the row should have said.
52
+ - Name the file set in the Touches column. The disjointness call in step 6 is only checkable later when the sets are written down rather than reasoned once and discarded.
53
+ - Re-resolve every Plan pointer after anything archives a plan
54
+ - Read the file back after writing it, since the row that lands is the row a worker acts on
55
+
56
+ A Plan pointer goes stale from a branch this board never sees. `docs-fold` moves a plan to `.canon/plans/archive/` and rewrites the citation in the task file alone, so a row for a task still on the board keeps pointing into `.canon/plans/` at a file that has moved. Workers running the ship chain on their own branches archive plans this board still cites, and the board reads as correct until a pointer is followed.
@@ -0,0 +1,18 @@
1
+ ---
2
+ title: Orchestrator review fallback runbook
3
+ description: What moves out of this session when a diff is too large or three pull requests await a first pass, why only the narrow re-review dispatches, and what its launch has to say
4
+ ---
5
+
6
+ Run this when `### The review fallback` in the skill body trips, on a diff too large for this session to hold or on three or more open pull requests awaiting a first pass. Either one makes review the bottleneck every track is waiting on, and both answer the same way: dispatch the narrow re-review and keep the first pass here.
7
+
8
+ ## The count
9
+
10
+ Three is the operator's number, set by hand and marked as such so a measurement replaces it rather than argues with it. What counts toward it is a pull request awaiting a first pass rather than every open one, since a branch already closed out and waiting on a merge costs this session nothing and counting it would fire the switch on a queue that is clear. `orchestrator-poll.md` states where the count is legible.
11
+
12
+ Nothing counts the pull requests. This is prose this session applies to itself, on the same standing as the poll's own start condition, so a wave past three reviewed one at a time is a rule that went unread rather than a check that failed.
13
+
14
+ ## What dispatches
15
+
16
+ Only the narrow re-review dispatches. A first pass reads across branches, and the findings that pay for its cost are the ones no single pull request shows: two branches regenerating one binary asset for the same count, three writing one context entry, a merge order making one branch's figure true only after another lands. A re-review asks whether the prior findings landed and whether the fix regressed anything, which is bounded to a delta and carries none of that reading, so it is the half that leaves cleanly.
17
+
18
+ What it dispatches has no role skill yet. `role-worker` and `role-planner` each state what their session may not do and no third body states a reviewer's, so a dispatched re-review would hold its obligations in whatever launch string this session types. Write the third after the first trial rather than before it, since a body written ahead of any dispatched reviewer encodes a shape nobody has driven. Until it exists, say in the launch itself that the pass is bounded to the delta and that the cross-branch reading stays here.
@@ -6,7 +6,7 @@ description: The once-per-batch board sweep, plan re-verification, and where eac
6
6
  Sweep the board as orchestrator after merging. Run this once per batch of merges, before answering what to do next, because the surfaces that record what shipped are the ones nothing updates on its own.
7
7
 
8
8
  1. Pull into the main worktree rather than fetching. A fetch leaves the local branch behind, so `git log` reports a state that has not arrived. A repository that adds a post-merge hook to name archive candidates gets it on a pull and never on a fetch.
9
- 2. Follow `## Refilling the ready queue` in `role-orchestrator`, every step in order. It owns the procedure. Do not restate it here and do not run it from memory.
9
+ 2. Follow `orchestrator-refill.md`, every step in order. It owns the procedure. Do not restate it here and do not run it from memory.
10
10
  3. Re-verify every plan already written, not only the ones this sweep writes. A queued plan goes stale from whatever merged while it waited, and the loop's verify step fires at handoff rather than after a merge, so nothing else catches it. Grep each construct the plan names and count the sites against its claim, then open each file rather than trusting its account.
11
11
  4. Re-check any precondition a plan states about live state outside the repository. A remote branch, an open issue, or an installed version was true when the plan was written and is not a fact about the tree.
12
12
 
@@ -73,7 +73,7 @@ four carried at least one, caught only because the planner ran
73
73
  - Tell a planner to report the path with no account of what the row got wrong. That instruction came from a trial condition holding a blind comparison intact, and it is not a durable obligation.
74
74
  - Report progress through the channel, which rebuilds on the sender's side the poll the announcement exists to retire
75
75
  - Write the priority board or the backlog, at any size, or the task file outside `plan-groundwork`'s close-time exception
76
- - Be a skill nothing invokes but its author typing the name. `orchestrator-dispatch.md` names it on the planning launch the way it names `role-worker` on a build, so a stretch where only a typed invocation reaches it is the signal that the role never took.
76
+ - Be a skill nothing invokes but its author typing the name. `orchestrator-launch.md` names it on the planning launch the way it names `role-worker` on a build, so a stretch where only a typed invocation reaches it is the signal that the role never took.
77
77
 
78
78
  ## Guards
79
79
 
@@ -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