@erclx/canon 4.119.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 (54) hide show
  1. package/README.md +2 -2
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/create-skill/SKILL.md +2 -1
  4. package/claude/skills/git-pr/REQUIREMENT.md +1 -0
  5. package/claude/skills/git-pr/SKILL.md +34 -4
  6. package/claude/skills/review-pr/SKILL.md +13 -242
  7. package/claude/skills/review-pr/references/close-out.md +30 -0
  8. package/claude/skills/review-pr/references/post.md +145 -0
  9. package/claude/skills/review-pr/references/scope.md +82 -0
  10. package/claude/skills/role-orchestrator/REQUIREMENT.md +9 -16
  11. package/claude/skills/role-orchestrator/SKILL.md +9 -153
  12. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +4 -193
  13. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +22 -0
  14. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +85 -0
  15. package/claude/skills/role-orchestrator/references/orchestrator-output.md +61 -0
  16. package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -2
  17. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +1 -1
  18. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +56 -0
  19. package/claude/skills/role-orchestrator/references/orchestrator-review-fallback.md +18 -0
  20. package/claude/skills/role-orchestrator/references/orchestrator-sweep.md +1 -1
  21. package/claude/skills/role-planner/REQUIREMENT.md +1 -1
  22. package/docs/agents/commands.md +2 -1
  23. package/docs/agents/counts.md +1 -1
  24. package/docs/agents/index.md +8 -3
  25. package/docs/agents/pr-evidence.md +19 -3
  26. package/docs/agents/pr-local.md +102 -0
  27. package/docs/agents/pr-preview.md +2 -2
  28. package/docs/agents/records-push.md +61 -0
  29. package/docs/agents/records-validate.md +73 -0
  30. package/docs/agents/records.md +3 -119
  31. package/docs/agents/sessions-transfer.md +91 -0
  32. package/docs/agents/sessions.md +3 -82
  33. package/docs/agents/skills-audit.md +2 -2
  34. package/docs/agents/teach-authoring.md +112 -0
  35. package/docs/agents/teach.md +3 -103
  36. package/docs/target-projects.md +1 -1
  37. package/docs/workflow/ai-workflow.md +1 -1
  38. package/docs/workflow/visual-design-workflow.md +1 -1
  39. package/governance/rules/claude/570-skill.md +2 -1
  40. package/package.json +1 -1
  41. package/scripts/core/check-ignore-parity.sh +1 -1
  42. package/scripts/lib/sandbox-git.sh +1 -1
  43. package/src/commands/pr.ts +324 -2
  44. package/src/context/classify/extract.ts +2 -2
  45. package/src/counts/scan.ts +1 -1
  46. package/src/design/tokens.ts +1 -1
  47. package/src/gate/stages.ts +2 -2
  48. package/src/pr/evidence.ts +42 -2
  49. package/src/pr/local.ts +125 -0
  50. package/standards/index.md +1 -0
  51. package/standards/skill-requirement.md +72 -0
  52. package/standards/skill.md +34 -152
  53. package/tooling/astro/reference.md +1 -1
  54. package/tooling/base/configs/.github/workflows/pr-local-preview.yml +32 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Orchestrator dispatch runbook
3
- description: The plan-answer gate, the collision check before a self-dispatch, the file-set disjointness gate, the branch and model the launch names, the planning and handback dispatch shapes, and the loop's stopping condition
3
+ description: The plan-answer gate, the collision check before a self-dispatch, the file-set disjointness gate, the branch and model the launch names, the fallback to a human launch, and the loop's stopping condition
4
4
  ---
5
5
 
6
6
  Run this at loop step 4, for a `## Run now` row whose plan is verified, in place of handing the worktree to a human. The disjointness gate below is where that row's file set is tested against every track in flight.
@@ -74,7 +74,7 @@ A declared set is what a branch sets out to write rather than a bound on it, so
74
74
 
75
75
  Disjointness is necessary and not sufficient, so hold a candidate whose sets do not touch when a stated reason serializes it, and write the reason on the hold. One row creating a skill and another auditing that catalog and counting it write nothing in common, and dispatching both still leaves the audit counting a denominator that moves underneath it. Nothing verifies that a reason was written, so the rule holds only while the dispatcher applies it.
76
76
 
77
- What binds past that is review attention rather than a count, and `## Parallelism` in the skill body states it along with the cap an operator can set for a session. The one number this skill carries is the review fallback's count of three in `## Parallelism`, which moves a review rather than binding a track, and this runbook carries none.
77
+ What binds past that is review attention rather than a count, and `## Parallelism` in the skill body states it along with the cap an operator can set for a session. The one number this skill carries is the review fallback's count of three in `orchestrator-review-fallback.md`, which moves a review rather than binding a track, and this runbook carries none.
78
78
 
79
79
  ## Pick the model
80
80
 
@@ -84,196 +84,7 @@ Name `<model>` on the launch, and pick it against the task rather than copying w
84
84
 
85
85
  ## Dispatch
86
86
 
87
- ```bash
88
- claude --bg --model <model> -n "worker-<project>-<slug>" "/canon:auto-ship <plan>
89
- Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id through canon sessions list --json, which carries sessionId per row, at the moment you send, and never resolve an addressee by name prefix. Message it when the pull request opens, carrying the number, the branch, the head sha, the CI state, and every point you departed from the plan on, and message it again if you stop on a question."
90
- ```
91
-
92
- `--bg, --background` starts the session as a background agent and returns immediately, `-n, --name` sets the display name that tells a self-dispatched worker from an operator's own launch in `canon sessions list`, and `--model` overrides the inheritance the section above states. Pass `-n` on every dispatch rather than letting the client derive one. A launch that omits it leaves the session named for a fragment of its own identifier, which is both its address on the send channel and the whole of what the operator sees for it in agent view.
93
-
94
- The prefix reads `worker-` because that is the role it marks. A prefix naming a role the session does not hold sends a worker filtering the roster for that string to a sibling or itself on every row. Nothing matches the prefix programmatically, which keeps a rename down to three strings.
95
-
96
- `<project>` is the basename of the main worktree root, not of wherever the dispatcher happens to be running. Resolve the main root first, the way `session-worktree` Step 1 does, since a bare `git rev-parse --show-toplevel` inside a linked worktree returns the worktree path rather than the project's.
97
-
98
- `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, and a session named off the worktree path instead would carry the branch folder rather than the project. Two projects each dispatching a bare `worker-page-driver` used to read as one row in that view.
99
-
100
- 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 across the 181 records stamping both fields, nine were renamed after launch at a median of 5.4 minutes and a maximum of 509. Three landed more than ten minutes in, which is 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.
101
-
102
- 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.
103
-
104
- 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.
105
-
106
- 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 this runbook already 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.
107
-
108
- 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 this runbook derived the candidate 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.
109
-
110
- That retires the inference four workers took before the verb existed, which was to derive the name from `<plan>` by their own reading of it. Nothing has to reach `session-worktree`'s ladder now, which mattered because a worker launched onto `main` cannot match tier 1, a board carrying more than one plan puts tier 2 out of reach, and tier 3 tells it to ask a person who is not there. What still travels on judgment is the fallback: a worker whose installed binary carries no `plan-branch` derives by prose, which is where both live disagreements came from.
111
-
112
- ### Expansion needs position zero and a clean delimiter, not leading order alone
113
-
114
- The client expands a slash command at position zero of a launch prompt as a
115
- user invocation, which is the route `disable-model-invocation: true` permits
116
- and gates. Everything that reaches the session as prose instead falls to the
117
- model, which invokes it through the `Skill` tool, and that route answers a
118
- flagged skill inconsistently. `auto-ship` has carried the flag since early in its life, and seven other
119
- shipped skills carry it too.
120
-
121
- Three launches bound what makes a command take
122
- that route. Observation A is the first refused worker, launched as `Run
123
- /canon:session-worktree ..., then /canon:auto-ship ...`, which expanded
124
- nothing. Observation B is a re-dispatch launched as `/canon:auto-ship
125
- <plan>` with a space before the path, which expanded and shipped.
126
-
127
- Observation C is a planning dispatch launched as `/canon:role-planner, then
128
- /canon:plan-feature <task>` with a comma glued to the command name at
129
- position zero, which expanded nothing and reached both bodies through the
130
- `Skill` tool instead. A rules out leading order alone, C rules out position
131
- zero on its own, and the only visible difference between B and C is the
132
- delimiter after the command token: a space in B, a comma in C.
133
-
134
- Read that delimiter reading as a candidate with a falsifier rather than as
135
- settled. The cheapest test is one dispatch leading with a bare command whose
136
- name is followed directly by a period, and the next real dispatch can carry it
137
- at no extra cost. Until it fails, the operational rule is the conjunction the
138
- three observations support: put the flagged command at position zero, followed
139
- by a space and its argument, with nothing before it.
140
-
141
- Four sessions made the same tool call against the same plugin cache on one
142
- day. Two were answered with the body and shipped, and two were refused
143
- with `Skill canon:auto-ship cannot be used with Skill tool due to
144
- disable-model-invocation`. Prefixing separated nothing, since three of the
145
- four carried the namespace and those three landed on both answers, so nothing
146
- a dispatcher writes predicts which answer a launch through the tool gets.
147
-
148
- Read that as a route a dispatch may not depend on rather than one that usually
149
- works. The refusal closes the fallback in the same message, telling the
150
- session not to replicate the workflow by other means, so a refused worker has
151
- no route left and stops with a clean worktree. Both failed dispatches produced
152
- nothing rather than a degraded run, which is the correct outcome and not a
153
- thing to soften.
154
-
155
- Recovery belongs to whoever writes the next prompt, since a blocked session
156
- cannot replay its own launch. The refusal is sticky inside a session rather
157
- than something a retry clears, measured when one refused worker repeated the
158
- identical prefixed call and got the byte-identical error back. Re-dispatch
159
- onto the same branch with the build template above, which already leads with
160
- the one command that needs the expansion route.
161
-
162
- The review shape and the planning shape below depend on no expansion at all.
163
- None of `role-worker`, `review-address`, `role-planner`, or
164
- `plan-feature` carries the flag, so both correctly keep their leading word
165
- regardless of the delimiter or the position it sits at.
166
-
167
- The same collapse reaches a human relay rather than a `claude --bg` string. A
168
- controller that hands an operator two chained blocks to paste as separate
169
- messages risks both landing as one, where everything after the first
170
- command's name is read as that command's own argument and the second command
171
- never fires, measured four times out of four.
172
- A slash command expands as a user invocation only at position zero of a
173
- prompt, and a later one in the same text reaches the session as prose instead.
174
- The fix is what the template above already takes: one message, one command,
175
- at position zero.
176
-
177
- ### What the brief may carry
178
-
179
- 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:
180
-
181
- - Name the addressee and what it is owed, which the two message clauses above already do.
182
- - Carry standing context this session holds that a cold one cannot derive, such as a constraint settled in conversation that never reached the plan.
183
- - 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.
184
-
185
- The last bullet is the one under pressure, since the dispatch that first proved unattended work possible sent a prompt naming the task file, the likely files, the conventions, and the check to run. It shipped in 874 seconds and touched four files its task never named. Scope that arrives as prose is scope nothing verifies afterward.
186
-
187
- 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.
188
-
189
- ## Dispatch to address a review
190
-
191
- `review-address` is a single pass, not a chain, so a launch naming it
192
- alone reaches no `role-worker` and takes no role, which owes no message
193
- either. A replacement session was launched that way once, onto the branch a
194
- review had already posted findings against, and it answered by posting a
195
- thread reply and telling its controller nothing. Reach the role directly on
196
- this launch instead of wrapping a second chain around one skill that has none
197
- of its own.
198
-
199
- The branch already exists here, opened by whatever built it, so this shape
200
- skips the plan-derived name the build shape resolves above. Take `<branch>`
201
- off the pull request's own head ref. Enter the worktree the original build
202
- left on disk, `.claude/worktrees/<slug>/`, with `EnterWorktree`'s `path` form
203
- when it is still there, or `git worktree add .claude/worktrees/<slug>/
204
- <branch>` when it was cleaned up, so `<slug>` is that directory name either
205
- way.
206
-
207
- `EnterWorktree` refuses that path in the ordinary case, because the original
208
- build session stays registered against its own worktree after going idle and
209
- holds a harness-level lock the roster does not report. Work in the folder
210
- directly with `Bash`, `Read`, and `Edit` instead of retrying the tool, which
211
- is the route two workers already took today on two different branches.
212
-
213
- ```bash
214
- 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."
215
- ```
216
-
217
- `<dispatcher-id>`, `<model>`, and `<project>` resolve the same way the build
218
- shape resolves them above.
219
-
220
- Take this shape wherever a review needs answering and no live session already
221
- holds the branch. Where one does, message it to run `review-address`
222
- instead, per the loop's own step 6, since a session already there needs no
223
- second one dispatched onto the same branch.
224
-
225
- That check is blind to a session working through the direct-path fallback
226
- above, since a session that never runs `EnterWorktree` never moves its
227
- registered branch off `main`, so `canon sessions list --branch` reports nothing
228
- holding it. A dispatch landing on a branch worked that way collides with
229
- nothing the check can see.
230
-
231
- ## Dispatch to plan a row
232
-
233
- `plan-feature` is a procedure rather than a role, so a launch naming it alone
234
- reaches no `role-planner` and takes no role, which owes no message either.
235
- Both trials of this shape ran on prose the controller retyped into each launch,
236
- which held every obligation those sessions took and is where the first one's
237
- in-flight read went wrong. Reach the role directly on this launch, the way the
238
- build shape above reaches `role-worker`.
239
-
240
- No branch and no worktree exist here and none is created. A planner writes one
241
- gitignored file at the main worktree root, so this shape names the row's task
242
- file rather than a branch and opens with the role instead of a worktree call.
243
- That write meets the isolation guard the same way a linked worktree's
244
- main-root write does, with no worktree here to redirect it to, so
245
- `role-planner` sends it as a `Bash` heredoc rather than through `Write`.
246
-
247
- ```bash
248
- 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."
249
- ```
250
-
251
- `<task>` is the row's task file path and `<slug>` the slug its plan will take,
252
- resolved off the row the way the build shape resolves one off a plan.
253
- `<dispatcher-id>`, `<model>`, and `<project>` resolve the same way they do
254
- above. The prefix reads `planner-` for the reason the worker's reads `worker-`,
255
- which is that it marks the role of the session it names rather than the one
256
- that launched it.
257
-
258
- None of the three checks above binds this shape. The branch check has no
259
- candidate to read, and the disjointness gate has nothing to compare, since a
260
- planner writes one file no track in flight can hold. The plan-answer gate
261
- reaches no plan at all, because the planner is dispatched to write the file a
262
- build would later read, so running it here would refuse every planning dispatch
263
- over a plan nobody has written yet.
264
-
265
- What a planning dispatch owes instead is the reverse reading, because the plan
266
- it produces carries a constraint per track in flight and a row planned during a
267
- wave is planned against a tree that wave is changing. `role-planner` composes
268
- the session roster with the pull request list for that read rather than reading
269
- pull requests alone, which is why the brief carries no branch list for it: the
270
- planner takes this reading itself either way.
271
-
272
- One row per dispatch. A session reused across a batch pays the context load once
273
- and ages its picture of the tree while it works, which is what puts the in-flight
274
- read on the task rather than on the batch, and one that compacts mid-batch loses
275
- the reasoning behind its earlier plans with nothing reporting it. Cap a reused
276
- session where the saving is worth it and say what the cap was.
87
+ Read `${CLAUDE_SKILL_DIR}/references/orchestrator-launch.md` once every check above clears, and launch from its build template. It also holds the review-address and planning shapes, which skip the checks above.
277
88
 
278
89
  ## Fall back to the human
279
90
 
@@ -281,7 +92,7 @@ Hand the row to the human-launch line in step 4 instead of dispatching when any
281
92
 
282
93
  The first of those five is the one that reaches a person rather than the board. A row held for a collision or for a serialize reason waits on the wave clearing, where a row held on its plan waits on an answer only the operator can give, so hand that one over with the question label and its stated reason attached rather than as a name and a refusal.
283
94
 
284
- Hand the person one command: `/canon:auto-ship <plan>`, naming the row, the plan path, and the branch together, with no worktree call ahead of it. A leading worktree call adds nothing beyond what `auto-ship` Step 0 already reaches for itself, by the same judgment the template above calls a residual risk rather than a settled contract. A second command also risks a client folding two commands into one message, which reads everything after the first command's name as its own argument and drops the second: that happened in four dispatches out of four before the fix became one message carrying the autoship call alone.
95
+ Hand the person one command: `/canon:auto-ship <plan>`, naming the row, the plan path, and the branch together, with no worktree call ahead of it. A leading worktree call adds nothing beyond what `auto-ship` Step 0 already reaches for itself. A second command also risks a client folding two commands into one message, which reads everything after the first command's name as its own argument and drops the second, per `### Position zero` in `orchestrator-launch.md`.
285
96
 
286
97
  Suggest, as one line to the operator, that they rename their own session to the row's id, so a process listing shows what the session is for without a cross-reference to the board.
287
98
 
@@ -0,0 +1,22 @@
1
+ ---
2
+ title: Orchestrator handback runbook
3
+ description: Addressing the session that answers a posted review, what to report when none is live, and where a worker's reply is placed by what it changes
4
+ ---
5
+
6
+ Read this at loop step 6, once a review pass has posted anything owed and the session holding that branch is to run `review-address`.
7
+
8
+ ## Sending the handback
9
+
10
+ - Read the threshold off `review-pr`, which states it once and governs the heading with it, so an open heading and an owed dispatch answer the same question and either one is enough to send
11
+ - Resolve the target at the moment of sending with `canon sessions list --branch`, never from a mapping written down earlier, since names rotate as sessions end and one recorded earlier in a session has failed inside the hour. `orchestrator-poll.md` routes on the count and the confidence it answers with
12
+ - Open the message with the worktree and branch the sender believes the reader holds, asking to be corrected, whenever that mapping is inferred rather than confirmed
13
+ - Name the skill for the reader to run rather than writing an invocation, which arrives as text
14
+ - Read the pull request's own draft flag rather than the state a worker reports, and report what the read returned and when rather than the state alone. `canon docs pr-reads` states why a reported field can lag and say nothing about it. The flag settles the question only once the worker's chain has run its undo, and nothing marks that moment, so a read taken between the pull request opening and that call sees a genuinely ready pull request about to become a draft and has told an operator the opposite of what the worker had said.
15
+
16
+ A session is reachable when it appears in a live listing, which reads what each session registered on disk rather than probing it, and a message carries plain text and no authority. When no live session holds the branch, report the invocation for the human, naming the branch, the pull request, and the skill to run, then stop. Retrying or waiting leaves the loop believing it is open while nothing acts on it.
17
+
18
+ The channel runs both ways and the return leg carries what the pull request cannot. A worker answering a posted finding by naming the plan question that had already declined it changes the outcome in the moment, where a thread comment waits on whoever reads it next. Read what a worker volunteers as part of the review rather than as an aside, and read a refusal the same way, since the corrections that landed on this session's model of the world arrived as a worker arguing back rather than complying.
19
+
20
+ What the worker owes on its own side is stated in the `role-worker` skill that session loads, being the pull-request announcement, the message a block goes out as before it becomes a prompt, and the ban on writing the board. Do not restate any of it here.
21
+
22
+ What arrives there does not become a record by being read, so place it by what it changes. An answer that settles a finding goes onto the pull request through the next pass, which withdraws or regrades that finding and names the fact behind it, per `review-pr`. An answer that changes what this session believes about the world instead, which is a mapping correction or a constraint on what a worker can do, settles no finding and reaches no thread, so route it the way `## Boundaries` in the skill body routes a change found while orchestrating, which lands it on the task owning the surface it describes. Writing a tracked file to hold either is forbidden here, which leaves the pull request and the board as the two surfaces this session writes.
@@ -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
 
@@ -96,8 +96,9 @@ Full help: `canon <command> --help`. Bare `canon --help` lists every top-level c
96
96
  | `canon pr head` | Compare the head a pull request object reports against the branch tip the remote carries, naming both shas (`--root`, `--json`) |
97
97
  | `canon pr checks` | Report the check runs belonging to the branch tip, reading pending for a tip carrying none rather than clean, and `conflicted` when the branch conflicts with its base (`--root`, `--json`) |
98
98
  | `canon pr review-state` | Report the commit and instant the last review pass covered, off the pass's own marker rather than the fields GitHub stamps at submission (`--root`, `--json`) |
99
- | `canon pr evidence` | Render a before-and-after comment comparing changed `evidence/` images against the merge base, editing its own prior comment in place rather than posting a second (`--root`, `--json`, `--preview`) |
99
+ | `canon pr evidence` | Render a before-and-after comment comparing changed `evidence/` images against the merge base, editing its own prior comment in place rather than posting a second (`--root`, `--json`, `--preview`, `--checklist`, `--local`) |
100
100
  | `canon pr preview` | Dispatch the Pages deploy on a pull request's branch and report the preview address the run prints, refusing a deploy that passes no `--branch` (`--root`, `--json`, `--timeout`) |
101
+ | `canon pr local` | Report the `localhost` address of the dev server this worktree is running, never one in another worktree, or replace that line on the evidence comment with a note on `--remove` (`--root`, `--json`, `--remove`, `--note`) |
101
102
  | `canon repo metadata propose` | Compare a description, homepage, and topic set computed from the README and `package.json` against what the remote carries, writing nothing (`--root`, `--json`) |
102
103
  | `canon repo metadata apply` | Write an explicitly supplied description, homepage, or topic set to the remote through `gh repo edit` (`--description`, `--homepage`, `--topics`, `--root`, `--json`) |
103
104
  | `canon census [path]` | Report tracked file count, a breakdown by extension, and a line total that skips whatever reads as binary (`--json`) |
@@ -43,7 +43,7 @@ Nothing stands between the trigger and the number, which is the rule an author w
43
43
 
44
44
  The verb gate is not the first design tried. A bare number next to a catalog noun anywhere in the tracked corpus returned 290 findings against a repository whose actionable instance count was one. Reading that run showed why: `18 rules citing a standard`, `21 skill bodies`, and `eight internal skills` all pair a number with a catalog noun while naming a subset, an example, or a different catalog than the one matched, and that shape dominates ordinary prose. Every instance this sweep was written against reads the number as the direct object of a verb asserting the catalog's own total, and gating on that verb is what took the false-positive count from 290 to five on the same tree.
45
45
 
46
- The article half was added after a first review of the shipped design found a live miss: `canon/context/development/gates.md` stated a stale audit total while the tree held 20, and the verb gate never reached it, since that sentence puts its verb after the noun rather than ahead of the number. Widening the trigger set to admit an article ahead of the number closed that gap.
46
+ The article half was added after a first review of the shipped design found a live miss: the development domain's gating-stages entry stated a stale audit total while the tree held 20, and the verb gate never reached it, since that sentence puts its verb after the noun rather than ahead of the number. Widening the trigger set to admit an article ahead of the number closed that gap.
47
47
 
48
48
  The quantifier `all` and the verb `authors` joined on the same evidence, two more live misses failing only the trigger test. Allowing words to stand between the trigger and the number was the other candidate, measured and declined: at widths of one, two, and three it reached 77, 104, and 132 sentences against a baseline of 65, missed `took all 81 rules` at every width, and reached `the toolkit authors 81 rules` only at a width admitting the indirect-noun shape below. The two words reach both at 75 sentences and no false positive.
49
49