@erclx/canon 5.34.0 → 5.35.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 (143) hide show
  1. package/README.md +3 -5
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/api-design/REQUIREMENT.md +1 -1
  4. package/claude/skills/auto-ship/references/draft-mark.md +1 -1
  5. package/claude/skills/canon-cli/SKILL.md +10 -11
  6. package/claude/skills/create-skill/SKILL.md +2 -1
  7. package/claude/skills/create-skill/references/skill-paths.md +12 -15
  8. package/{standards/standard.md → claude/skills/create-skill/references/skill-reference.md} +50 -45
  9. package/claude/skills/create-skill/references/skill.md +9 -3
  10. package/claude/skills/draft-and-pick/SKILL.md +1 -1
  11. package/claude/skills/git-commit/references/versioning.md +13 -13
  12. package/claude/skills/git-pr/SKILL.md +1 -1
  13. package/claude/skills/git-pr/references/publish.md +1 -1
  14. package/claude/skills/review-address/REQUIREMENT.md +1 -1
  15. package/claude/skills/review-address/SKILL.md +2 -2
  16. package/claude/skills/review-pr/SKILL.md +3 -3
  17. package/claude/skills/review-pr/references/post.md +1 -1
  18. package/claude/skills/review-ui/REQUIREMENT.md +1 -1
  19. package/claude/skills/review-ui/SKILL.md +1 -1
  20. package/claude/skills/role-orchestrator/REQUIREMENT.md +2 -2
  21. package/claude/skills/role-orchestrator/SKILL.md +11 -12
  22. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +3 -1
  23. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +11 -8
  24. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +10 -4
  25. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +2 -2
  26. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +11 -12
  27. package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +12 -15
  28. package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +6 -4
  29. package/claude/skills/role-orchestrator/scripts/poll.ts +50 -1
  30. package/claude/skills/role-reviewer/REQUIREMENT.md +17 -7
  31. package/claude/skills/role-reviewer/SKILL.md +17 -10
  32. package/claude/skills/role-worker/REQUIREMENT.md +1 -1
  33. package/claude/skills/role-worker/SKILL.md +6 -6
  34. package/claude/skills/session-compact/references/handoff-note.md +1 -1
  35. package/claude/skills/target-setup/SKILL.md +2 -2
  36. package/claude/skills/target-setup/references/detect.md +3 -3
  37. package/docs/agents/audits.md +1 -1
  38. package/docs/agents/commands.md +2 -4
  39. package/docs/agents/counts.md +5 -6
  40. package/docs/agents/drift-surfaces.md +15 -19
  41. package/docs/agents/index.md +2 -3
  42. package/docs/agents/install-and-sync.md +7 -31
  43. package/docs/agents/markdown-audit.md +10 -12
  44. package/docs/agents/migrate.md +1 -1
  45. package/docs/agents/records-stale.md +1 -1
  46. package/docs/agents/records-validate.md +11 -23
  47. package/docs/agents/records.md +1 -1
  48. package/docs/agents/routing.md +1 -1
  49. package/docs/agents/rule-citations.md +11 -14
  50. package/docs/agents/scripting.md +2 -20
  51. package/docs/agents/state-scoped-risk.md +1 -1
  52. package/docs/agents/tasks-validate.md +1 -1
  53. package/docs/agents/teach.md +1 -1
  54. package/docs/target/index.md +1 -0
  55. package/docs/target/migrations.md +4 -0
  56. package/docs/target/projects.md +9 -9
  57. package/docs/target/scenarios.md +5 -6
  58. package/docs/target/standards-moved.md +32 -0
  59. package/docs/target/sync.md +5 -5
  60. package/docs/workflow/operating-model.md +16 -18
  61. package/docs/workflow/skill-map.md +1 -1
  62. package/governance/detect.toml +0 -4
  63. package/governance/renames.toml +0 -1
  64. package/governance/stacks/base.toml +15 -8
  65. package/governance/stacks/go.toml +2 -0
  66. package/governance/stacks/node.toml +1 -1
  67. package/governance/stacks/php.toml +2 -0
  68. package/governance/stacks/python.toml +1 -1
  69. package/package.json +1 -4
  70. package/scripts/core/install-check.sh +2 -3
  71. package/src/audits/catalog.ts +0 -9
  72. package/src/claude/routing.ts +1 -1
  73. package/src/claude/skills-reach.ts +4 -6
  74. package/src/cli/help.ts +3 -4
  75. package/src/cli.ts +0 -2
  76. package/src/commands/gov.ts +5 -9
  77. package/src/commands/init.ts +1 -1
  78. package/src/commands/labels.ts +4 -44
  79. package/src/commands/migrate.ts +1 -1
  80. package/src/commands/records.ts +4 -20
  81. package/src/commands/tooling.ts +2 -4
  82. package/src/counts/catalogs.ts +0 -6
  83. package/src/gate/boundaries.ts +2 -2
  84. package/src/gate/measures.ts +6 -28
  85. package/src/gate/sequencer.ts +51 -2
  86. package/src/gate/stages.ts +0 -18
  87. package/src/gov/citations.ts +11 -43
  88. package/src/markdown/bans.ts +1 -1
  89. package/src/migrate/scratch-evidence.ts +2 -2
  90. package/src/records/validate.ts +6 -194
  91. package/src/sandbox/provision.ts +0 -5
  92. package/src/secrets/shipped.ts +1 -2
  93. package/src/serve/static.ts +1 -2
  94. package/src/shipped/references.ts +29 -80
  95. package/src/sync/check.ts +18 -14
  96. package/src/tooling/inject.ts +6 -37
  97. package/src/tooling/read.ts +2 -3
  98. package/src/tooling/scan.ts +4 -17
  99. package/src/tooling/subfolder.ts +2 -66
  100. package/tooling/base/configs/scripts/verify.json +1 -1
  101. package/tooling/base/configs/scripts/verify.ts +1 -12
  102. package/tooling/base/manifest.toml +1 -2
  103. package/tooling/base/reference.md +10 -16
  104. package/tooling/base/seeds/.github/workflows/verify.yml +0 -3
  105. package/tooling/base/seeds/.lintstagedrc +1 -2
  106. package/tooling/base/seeds/canon/context/ci.md +0 -1
  107. package/tooling/base/seeds/canon/context/development.md +1 -1
  108. package/tooling/claude/reference.md +1 -1
  109. package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +13 -1
  110. package/tooling/go/reference.md +1 -1
  111. package/tooling/php/reference.md +1 -1
  112. package/tooling/python/reference.md +2 -2
  113. package/tooling/web/configs/.github/workflows/verify.yml +0 -3
  114. package/tooling/web/manifest.toml +0 -1
  115. package/tooling/web/reference.md +2 -4
  116. package/docs/agents/standards-audit.md +0 -40
  117. package/governance/rules/tooling/765-spelling.md +0 -19
  118. package/governance/stacks/astro.toml +0 -2
  119. package/governance/stacks/nextjs.toml +0 -2
  120. package/governance/stacks/node-server.toml +0 -2
  121. package/governance/stacks/python-fastapi.toml +0 -2
  122. package/governance/stacks/react.toml +0 -2
  123. package/src/commands/standards.ts +0 -278
  124. package/src/labels/spelling.ts +0 -120
  125. package/src/standards/audit.ts +0 -146
  126. package/src/standards/list.ts +0 -33
  127. package/src/standards/read.ts +0 -177
  128. package/src/standards/scope.ts +0 -56
  129. package/standards/index.md +0 -10
  130. package/tooling/base/configs/.vscode/extensions.json +0 -9
  131. package/tooling/base/configs/.vscode/settings.json +0 -3
  132. package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
  133. package/tooling/base/seeds/.cspell/tech-stack.txt +0 -25
  134. package/tooling/base/seeds/cspell.json +0 -20
  135. package/tooling/go/seeds/.cspell/tech-stack.txt +0 -16
  136. package/tooling/nextjs/seeds/.cspell/tech-stack.txt +0 -3
  137. package/tooling/php/seeds/.cspell/tech-stack.txt +0 -5
  138. package/tooling/python/seeds/.cspell/tech-stack.txt +0 -18
  139. package/tooling/vite-react/seeds/.cspell/project-terms.txt +0 -1
  140. package/tooling/vite-react/seeds/.cspell/tech-stack.txt +0 -2
  141. package/tooling/web/configs/.vscode/extensions.json +0 -13
  142. package/tooling/web/configs/.vscode/settings.json +0 -10
  143. package/tooling/web/seeds/.cspell/tech-stack.txt +0 -21
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: role-orchestrator
3
- description: What the one warm control session owns, why it plans and reviews without building or merging, and the conflict test that holds a parallel build
3
+ description: What the one warm control session owns, why it plans and dispatches review without building or merging, and the conflict test that holds a parallel build
4
4
  ---
5
5
 
6
6
  # Role orchestrator requirement
@@ -82,7 +82,7 @@ Dispatch parts from what it checked in three ways. The gate and the worker each
82
82
  - Stop dispatching once `## Run now` is empty or every row in it reads claimed, rather than waking again to fire on a board nobody is clearing
83
83
  - Launch a planner or worker the operator will talk with from the talk shape, with the `-talk` suffix and the brief sentence that tells it so, since a cold launch retires the session the moment its delivery lands while the operator still wants the conversation
84
84
  - Retire a talk session only on the operator's word and report it as held open until then, since its delivery says nothing about whether the conversation is over
85
- - Dispatch a pull request's review, first pass included, to `role-reviewer` on load or on executable content, a skill body included, and keep one cross-branch pass per wave here, since a warm pass holding a wave skims what a cold one reads
85
+ - Dispatch every non-release pull request's review to `role-reviewer`, leave each round's messages to it and the worker, and keep one cross-branch pass per wave here, since any pass or relay run here bills the context the wave shares
86
86
 
87
87
  ## Must not
88
88
 
@@ -9,7 +9,7 @@ metadata:
9
9
  # Role orchestrator
10
10
 
11
11
  This session is the orchestrator: the one warm session that holds the
12
- cross-feature picture. It plans and reviews.
12
+ cross-feature picture. It plans and dispatches every review.
13
13
 
14
14
  It does not build, and it does not merge. Building happens in cold worker
15
15
  sessions, dispatched by this skill once the branch is unclaimed and no conflict
@@ -20,8 +20,8 @@ that builds something runs an existing skill. The queue rules below decide which
20
20
  one runs and when.
21
21
 
22
22
  Run `canon docs operating-model` for the model this skill enacts: the two roles
23
- and what each owns, the loop end to end, why this session's review is the first
24
- one a change gets, and how a feature is sized.
23
+ and what each owns, the loop end to end, why a dispatched reviewer's pass is
24
+ the first one a change gets, and how a feature is sized.
25
25
 
26
26
  ## On invocation
27
27
 
@@ -63,9 +63,9 @@ Report the state of play in the invocation block, and open every later sweep rep
63
63
  2. Decide parallelism and merge order. Note which plans depend on another or change a contract another consumes, since those hold, and which merely share a file, since those build together and the second to merge rebases.
64
64
  3. Verify the plan against the tree. Reading it is not enough, since a plan goes stale from whatever merged after it was written. Grep for each construct it names and count the sites against the count it claims. Check that every phase label it cites is still open. Open each file it describes rather than trusting its account of the contents. Correct the plan before handing it over.
65
65
  4. Hand off. Read `${CLAUDE_SKILL_DIR}/references/orchestrator-dispatch.md` and follow it: check the branch is unclaimed, run its conflict check against every track in flight, then dispatch a background worker with `claude --bg`. Fall back to the human-launch line it replaces when the branch check refuses or the conflict check holds the row behind something already out. Where the operator wants to talk with the worker, launch the talk build from the talk shape in the launch runbook after the same checks clear.
66
- 5. Review the PR. When a worker opens a PR, its first pass runs through `review-pr` in a reviewer dispatched under `role-reviewer` when `### The review dispatch` below fires, and in this session otherwise. Either is the deep, independent pass. The worker's chain carries no review step, so this is the first review the change gets.
66
+ 5. Dispatch the review. When a worker opens a PR, its first pass runs through `review-pr` in a reviewer dispatched under `role-reviewer`, per `### The review dispatch` below, and never in this session. That pass is the deep, independent one. The worker's chain carries no review step, so this is the first review the change gets.
67
67
  - Learning that a PR moved is the mechanical half, so read `${CLAUDE_SKILL_DIR}/references/orchestrator-poll.md` and run the poll under the condition it states rather than checking the board by hand. That runbook holds the routing and the trigger, and a summary of it here is a second source that drifts from it.
68
- 6. Dispatch the handback. A pass posting anything owed, a finding at any severity or a testing question, tells the session holding that branch to run `review-address`, rather than waiting for a person to relay it. Re-review when the worker's own message says the address pass finished, per the channel `role-worker` states, rather than polling for an answer nothing else marks as landed, and send the re-review back to the dispatched reviewer when one took the first pass. Once a pass posts `## Review closed`, a UI close names the current head wherever the PR carries a checklist, the poll run as `--check <number>` prints `CLEAR`, and `--check` reports `settled`, lift the pull request's draft mark yourself, per Boundaries below. Then the human merges. Tell each trailing worker whose files the merged diff touches to run `review-address`, which rebases it when it no longer merges or when `main` changed a file it writes, after each merge rather than once per wave.
68
+ 6. Back the round up. A pass posting anything owed, a finding at any severity or a testing question, reaches the session holding that branch from the reviewer directly, and the worker's re-review request goes straight back to that reviewer, per the channels `role-reviewer` and `role-worker` state. This session sends either leg only where a cloud reviewer posted the pass or one side reports the other unreachable. Each reviewer runs `canon pr lift` on posting its close, and the verb lifts the draft mark only once both reviews read closed at the head, the evidence reads settled, the head is current against the base, and CI passed. Run it yourself when a reviewer's close message reports a refusal other than the other review being open, or when a cloud reviewer's close posts, per Boundaries below. Then the human merges. Tell each trailing worker whose files the merged diff touches to run `review-address`, which rebases it when it no longer merges or when `main` changed a file it writes, after each merge rather than once per wave.
69
69
  - Read `${CLAUDE_SKILL_DIR}/references/orchestrator-handback.md` on reaching this step. It holds how the message is addressed and worded, what to do when no live session holds the branch, and where a worker's reply goes.
70
70
 
71
71
  A plan written here is written against a tree several branches are already changing, so it names the file set of every track in flight as a constraint, one set per track, read from the Touches column of that track's row. State for each set which of the two acts it forbids, per Constraints in `${CLAUDE_SKILL_DIR}/../plan-feature/references/plan.md`. A bare path list leaves the worker guessing, which is how a plan ends up forbidding the repair of a citation the change broke. A path the two share forbids an act only where the conflict check in `orchestrator-dispatch.md` holds the pair, and otherwise names what the second branch to merge rebases over.
@@ -77,7 +77,7 @@ Stamp the block with the commit this session read the tree at, which the same se
77
77
  - Run one orchestrator at a time. The board is gitignored, so a second session sees none of this one's writes: two task files land minutes apart under different labels for the same work, one session archives a task mid-sweep in the other, and each archives a plan the other had retargeted. An Owner column does not fix this, since neither session can read the other's rows.
78
78
  - Do not implement features in this session. Hand the plan to a worker.
79
79
  - Do not merge. Recommend merge or changes. The human merges.
80
- - Lift a pull request's draft mark once its review closes, whether this session or a reviewer it dispatched closed it, acting directly on the pull request rather than dispatching a worker. Hold it on any `canon pr evidence <number> --check --json` reason but `settled`, reporting that reason, and where that record carries a `checklist`, until a UI close names the current head. `role-worker` and `role-reviewer` state the mirroring refusal: neither can verify who is asking or whether review actually closed, so the act stays with this session.
80
+ - Lift a pull request's draft mark through `canon pr lift <number>` alone, never by readying the pull request directly. The verb reads every gate itself, so a reviewer that closes a review runs it with no one vouching for who is asking, and this session is the fallback lifter for a cloud reviewer, whose proxy refuses the mutation, and for a refusal a reviewer reports. Report the reason a refusal names rather than retrying it.
81
81
  - Do not spawn a worker with the Agent tool. An in-process subagent shares this session's context and cannot be steered or reached independently, which breaks the property this boundary protects rather than the mechanism it names. The launch in `orchestrator-launch.md` is a separate `claude --bg` process with its own worktree and its own PR, so it preserves that property instead.
82
82
  - Dispatch a background worker only once the branch check in `orchestrator-dispatch.md` clears and its conflict check holds the row behind no track in flight. Colliding with an existing worktree or session is what the branch check exists to catch rather than a judgment call this session makes case by case, and a shared file alone is not a hold. No fixed count binds how many tracks run at once, and Parallelism below states what does.
83
83
  - Do not edit tracked files from this session, at any size. The boundary offers no proportionality exception and nothing enforces it.
@@ -130,9 +130,8 @@ hand only when two worktrees derive the same offset.
130
130
  ### The review dispatch
131
131
 
132
132
  Review splits by what each side can see. A reviewer dispatched under
133
- `role-reviewer` takes the per-pull-request read, first pass included, when the
134
- load is past what this session can hold or the change touches executable or
135
- security surface. Below both, this session reviews in place. It keeps one
136
- cross-branch pass per wave either way, over file sets, the board, and merge
137
- order. Read `${CLAUDE_SKILL_DIR}/references/orchestrator-review-dispatch.md` on
138
- every first pass for the trigger and how that pass reaches each brief.
133
+ `role-reviewer` takes every pull request's read but a release one's, first pass
134
+ included, and this session reviews none in place. It keeps one cross-branch pass
135
+ per wave, over file sets, the board, and merge order. Read
136
+ `${CLAUDE_SKILL_DIR}/references/orchestrator-review-dispatch.md` on every first
137
+ pass for the model pick, the UI reviewer, and how that pass reaches each brief.
@@ -110,4 +110,6 @@ Create the session with the wrapper under `## The create`, setting `dir` to that
110
110
 
111
111
  The pull request is the record to act on, and the transcript is read back only to check for a refused call. Run `claude --teleport <session>` under `script` from a throwaway clone of the target outside this repository, answer its trust prompt with the same piped keys, and set the terminal to 500 rows first, since only the screen tail is captured otherwise. Search the capture for `HTTP 403` and `graphql`. A hit names a call still on GraphQL, so report it as a finding rather than reading the posted review as clean.
112
112
 
113
- A cloud reviewer cannot be messaged after it posts. A re-review after a worker's address pass is a fresh dispatch, local or cloud by the same pick, rather than the message `orchestrator-handback.md` sends a live reviewer.
113
+ A cloud reviewer cannot be messaged after it posts, and it cannot message the worker either. `poll.ts` reports nothing when a code review posts on an unchanged head, so the poll's cloud-reviewer rule reads `canon pr review-state <number> --json` on every run for each pull request one holds, and sends the `review-address` dispatch from this session when that read shows an open pass. A re-review after the worker's address pass is a fresh dispatch, local or cloud by the same pick, rather than the message a worker sends a live local reviewer.
114
+
115
+ A cloud reviewer cannot lift the draft mark, since `canon pr lift` makes its one write through the GraphQL mutation the proxy refuses. Leave the lift out of its prompt. The same per-run read runs the verb from this session once the review reads closed at the head on a pull request still a draft. A local reviewer whose run refused reports that refusal in its close message, which reaches loop step 6 rather than the poll.
@@ -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 conflict check against the tracks in flight and the evidence it rests on, the branch and model the launch names, the fallback to a human launch, and the loop's stopping condition
3
+ description: The plan-answer gate, the collision check before a self-dispatch, the conflict check against the tracks in flight and the evidence it rests on, the branch the launch names, the one model test applied to each role, 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 conflict check below is where that row is tested against every track in flight, and it is the one statement of what holds a row there.
@@ -31,7 +31,7 @@ Branch on the record rather than on the exit code, which a shell function wrappi
31
31
 
32
32
  The worker calls the same verb on the same plan at `auto-ship` Step 0, so the branch this gate checks and the branch that session takes are one string by construction rather than two readings of one paragraph. Two readings of one plan part on the slug and on the type alike, and a check against a branch nobody uses verifies nothing.
33
33
 
34
- The type the verb reports is fixed at `feat` whatever the row does, which is the half of the derivation that disagreed most. What makes that safe is that a branch type is cosmetic: `git-stage` reads a commit's type off the staged diff and `git-pr` reads a title off the diff, so nothing a release reads passes through the branch name. What it costs is a worktree listing where every dispatched branch reads `feat/`, which a person scanning one loses. Nothing renames it later, since `git-branch`'s conventions guard fires on a conforming `feat/` before reaching any type judgment.
34
+ The type the verb reports is fixed at `feat`, which is safe because a branch type is cosmetic: `git-stage` and `git-pr` read their types off the diff, and nothing a release reads passes through the branch name. The cost is a worktree listing where every dispatched branch reads `feat/`.
35
35
 
36
36
  ## Check the plan waits on nobody
37
37
 
@@ -46,9 +46,7 @@ Run `canon tasks plan-answers <plan> --json` and read `launchable` off the recor
46
46
 
47
47
  Branch on `launchable` rather than on the exit code, which a shell function wrapping `canon` can flatten to zero and so read a held row as a clear one.
48
48
 
49
- This gate runs ahead of the two collision checks because it is the cheapest reading of the three, needing no roster and no ref, and because it is the only one asking about the row itself rather than about what else is in flight. A row nobody can launch does not need testing against the tracks already out.
50
-
51
- It also reads the plan rather than a cell describing one, which is the input the gate below it does not have. The conflict check reads the sets a dispatcher wrote into the constraints and the Touches column, so a cell omitting a renamed or relocated path clears a check the tree would fail, and what catches it then is a worker's message rather than any check.
49
+ This gate runs first because it is the cheapest reading, needing no roster and no ref, and the only one asking about the row itself. It reads the plan rather than a cell describing one, since the conflict check reads sets a dispatcher wrote, and a cell omitting a renamed path clears a check the tree would fail.
52
50
 
53
51
  A blank `- Answer:` is not an unanswered question. `${CLAUDE_SKILL_DIR}/../plan-feature/references/plan.md` fixes an empty slot as accepting the `- Suggested:` line above it, which is what makes a plan decision-ready in one pass. The narrow case this reads is `- Suggested: needs your call, <why>` and its two demonstrated paraphrases, `needs operator's call` and `needs the operator's call`, over an empty slot, the form that same standard writes where the answer turns on preference rather than on a technical default. A gate reading every blank slot as open would refuse every plan in the folder.
54
52
 
@@ -66,7 +64,7 @@ Reading `claimed` off the record is what keeps this a check rather than a rule a
66
64
 
67
65
  `refs` is the reading that catches a shipped row. A branch behind a merged pull request has no worktree and no session, so the check answered clear on one until a worker refused the instruction and named the consequences: a second pull request against a head GitHub already shows merged, a row whose pull-request line points at two numbers, and the `ambiguous` refusal `canon tasks archive` documents.
68
66
 
69
- What the ref read cannot see is a branch pushed from another machine since the last fetch, because it reads the remote-tracking ref rather than the remote. Nobody has hit that, and a `git ls-remote` per dispatch costs 0.438s against 0.001s, so the gap is recorded rather than closed.
67
+ What the ref read cannot see is a branch pushed from another machine since the last fetch. A `git ls-remote` per dispatch costs 0.438s against 0.001s, so the gap is recorded rather than closed.
70
68
 
71
69
  ### A cloud worker's claim
72
70
 
@@ -98,9 +96,14 @@ A declared set predicts what a branch writes rather than bounding it, since `can
98
96
 
99
97
  ## Pick the model
100
98
 
101
- A `claude --bg` session inherits the model of whatever launched it rather than reading the machine's configured default. An orchestrator on the larger model therefore spends it on every worker it launches, and the operator who set the default never sees the override.
99
+ This section binds every launch shape that names `<model>`, the planner and both reviewers as well as the step 4 build, none of which pass through that step.
100
+
101
+ A `claude --bg` session inherits the launching session's model, so an orchestrator on the larger model spends it on every session it launches. Name `<model>` on each launch with one test: `opus` where a wrong result is costly and nothing automatic would catch it, `sonnet` otherwise, never `haiku`. Read the role's worst failure and whether a test, gate, or later review stops it, and never the folder or the plan's length.
102
102
 
103
- Name `<model>` on the launch, and pick it against the task rather than copying whatever this session happens to run. Sizing the model to the row is the dispatcher's call, the same call it already makes on the branch. A mechanical row moving files under a written plan is not the row that needs the largest model, and one whose plan carries an open judgment is.
103
+ - Planner: `opus` for a row that changes a contract others consume, spans modules, or needs slicing. `sonnet` for a narrow row, even one inside a hook or governance folder.
104
+ - Worker, and the review-address pass against the findings in hand: `opus` where the plan leaves a cross-module call tests will not settle. `sonnet` where it pins the fix, however long.
105
+ - Code reviewer: `opus` where a miss would ship unseen, a change to behavior no test or gate exercises that other surfaces read. `sonnet` otherwise, a one-line hook edit included.
106
+ - UI reviewer: always `sonnet`. A cheaper tier was measured on the same checklist and passed real defects as clean while judging the taste boxes it was told to leave.
104
107
 
105
108
  ## Pick local or cloud
106
109
 
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  title: Orchestrator handback runbook
3
- description: Addressing the session that answers a posted review, the rebase handback a stale or conflicted branch earns, what to report when none is live, and where a worker's reply is placed by what it changes
3
+ description: The fallback for the direct channel between reviewer and worker, addressing the session that answers a posted review, the rebase handback a stale or conflicted branch earns, what to report when none is live, and where a worker's reply is placed by what it changes
4
4
  ---
5
5
 
6
- Read this at loop step 6, once a review pass has posted anything owed or the poll reports a branch stale or conflicted, and the session holding that branch is to run `review-address`.
6
+ Read this at loop step 6, once the poll reports a branch stale or conflicted, or a review pass posted anything owed that no live reviewer sent on, and the session holding that branch is to run `review-address`.
7
7
 
8
8
  ## Sending the handback
9
9
 
@@ -23,9 +23,15 @@ The worker that built the branch rebases it, not this session. It holds the plan
23
23
 
24
24
  ## Re-review and reachability
25
25
 
26
- A re-review goes back to the reviewer that took the first pass. Find a live `reviewer-<project>-<number>` for that pull request through `canon sessions list --json` by name and message it to re-run `review-pr`. When none is live, launch a fresh reviewer through the reviewer shape in `orchestrator-reviewer-launch.md` or review in place, under the same trigger `orchestrator-review-dispatch.md` states, rather than messaging a name that no longer answers.
26
+ A dispatched reviewer sends the `review-address` dispatch to the worker itself, carrying its own sessionId, and the worker sends the re-review request straight back to it. Neither leg passes through this session, which sends either one only as the fallback:
27
27
 
28
- 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.
28
+ - A pass a cloud reviewer posted, since a cloud session can send no message. The script reports no code review posting on an unchanged head, so the poll's cloud-reviewer rule reads `canon pr review-state` for each pull request a cloud reviewer holds and routes the dispatch here.
29
+ - A reviewer reporting that `canon sessions list --branch` returned no session or an unconfirmed match. Route it the way the poll's dispatch rule routes the count: on zero, launch the review-address shape in `orchestrator-launch.md`, carrying the reviewer's sessionId so the worker it launches answers the reviewer directly, and on an unconfirmed match, pick from a session listing and ask to be corrected, since a session may well hold the branch.
30
+ - A worker reporting that the reviewer's sessionId resolved to no live session, or a re-review request from a worker whose dispatch named no reviewer. Launch a fresh reviewer through the reviewer shape in `orchestrator-reviewer-launch.md`, never a pass in place, rather than messaging a name that no longer answers.
31
+
32
+ A re-review this session sends goes to the reviewer that took the first pass. Find a live `reviewer-<project>-<number>` for that pull request through `canon sessions list --json` by name and message it to re-run `review-pr`, and launch a fresh one only when none is live.
33
+
34
+ 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, launch the review-address shape in `orchestrator-launch.md`, which enters the branch's own worktree. Report the invocation for the human only where that launch cannot run, 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.
29
35
 
30
36
  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.
31
37
 
@@ -71,10 +71,10 @@ The branch already exists here, opened by whatever built it, so this shape skips
71
71
  `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.
72
72
 
73
73
  ```bash
74
- 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."
74
+ 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>, and your reviewer is the session whose sessionId is <reviewer-id>. Resolve each current name from its 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 the reviewer when the address pass finishes, asking it to re-review and carrying what was addressed and the PR's CI state, and send that to the controller instead when the reviewer's id resolves to no live session. Message the controller if you stop on a question."
75
75
  ```
76
76
 
77
- `<dispatcher-id>`, `<model>`, and `<project>` resolve the same way the build shape resolves them above.
77
+ `<dispatcher-id>`, `<model>`, and `<project>` resolve the same way the build shape resolves them above. `<reviewer-id>` is the sessionId the reviewer's own message carried, so a worker launched fresh answers the reviewer directly. Where no local reviewer is live, which a cloud reviewer always leaves, drop the reviewer clause and send the finished message to the controller, which launches the re-review itself.
78
78
 
79
79
  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.
80
80
 
@@ -35,21 +35,22 @@ Resolve `${CLAUDE_SKILL_DIR}/scripts/poll.ts` to an absolute path and paste that
35
35
  Poll GitHub for pull request movement by running `bun <POLL_SCRIPT>`, then act on what it reports.
36
36
 
37
37
  - A release pull request, whatever state follows it: report it and stop. Its sweep carries no findings, so no pass is owed. Test this before any rule below, since a release pull request is reported OPENED like any other and would otherwise match that rule first.
38
- - MOVED or RESPONSE on a pull request I have already reviewed: run the canon:review-pr skill on it immediately, narrow pass, or message the live reviewer that took its first pass to run it. Re-reviews read prior..head and gain nothing from waiting.
39
- - OPENED, or a pull request with no prior review pass: take its first pass where the review dispatch runbook of the canon:role-orchestrator skill places it, running the canon:review-pr skill here or launching a reviewer. A draft counts, since every pull request here opens as one and skipping drafts skips everything.
38
+ - MOVED or RESPONSE on a pull request already reviewed: when a live local reviewer holds it, report it and stop, since the worker asks that reviewer for the re-review directly. When none does, which a cloud reviewer always leaves, launch a fresh reviewer under the review dispatch runbook of the canon:role-orchestrator skill. Never run the canon:review-pr skill here. Re-reviews read prior..head and gain nothing from waiting.
39
+ - OPENED, or a pull request with no prior review pass: launch its reviewer under the review dispatch runbook of the canon:role-orchestrator skill, and its UI reviewer beside it when the evidence record carries a checklist. Never run the first pass here. A draft counts, since every pull request here opens as one and skipping drafts skips everything.
40
40
  - SEEN: report it and stop. A pass already covers that head, whether it arrived out of band or before the poll first saw the pull request, so no review follows.
41
41
  - STALLED: read the last pass and report what it carried. The pass has sat open for hours with nothing following it, so a worker mid-task is already ruled out and the dispatch either never went out or the session holding it is gone. Confirm and re-send it under the dispatch rule below, whatever grades the pass carried. Do not re-run a review to correct the heading, since a pass on an unchanged head with no response behind it stops by design.
42
- - CONFLICT or STALE: send the session holding that branch the rebase handback in the canon:role-orchestrator skill's handback runbook, resolving it the way the handback rule below does. The branch owner rebases, not this session. The poll prints one of the two for a pull request, never both, so it earns one message. Hold the draft lift on it until `bun <POLL_SCRIPT> --check <number>` prints CLEAR, since its green run tested a main that no longer exists.
42
+ - CONFLICT or STALE: send the session holding that branch the rebase handback in the canon:role-orchestrator skill's handback runbook, resolving it the way the handback rule below does. The branch owner rebases, not this session. The poll prints one of the two for a pull request, never both, so it earns one message. The canon pr lift verb refuses on either standing itself, since the green run tested a main that no longer exists, so nothing here lifts the mark.
43
43
  - OVERLAP: report the pair and the files, and settle their merge order now under the Parallelism rule of the canon:role-orchestrator skill. Dispatch nobody. The second to merge reads STALE or CONFLICT once the first lands, and the handback goes out then.
44
44
  - UNMATCHED: report it and stop. A comment posted under a heading outside the known set reaches nobody automatically, so a person decides whether to answer it by hand or the set needs a ninth heading.
45
- - UI-OPEN: dispatch the canon:review-address skill under the rule below, the same as an open code pass. A later RESPONSE re-sends the UI reviewer too.
46
- - UI-CLOSED: report it, and lift the draft mark only once the code review is closed too and `bun <POLL_SCRIPT> --check <number>` prints CLEAR.
45
+ - UI-OPEN: report it and stop when the live UI reviewer holds the pull request, since that reviewer sends the canon:review-address dispatch itself. Otherwise dispatch it under the fallback rule below, the same as an open code pass.
46
+ - UI-CLOSED: report it. When the pull request is still a draft and its code review is closed too, run `canon pr lift <number> --json` and report the reason it returns. Each reviewer runs the verb on its own close, so this run is the fallback for a lift neither reviewer's run made, and the verb reads every gate itself.
47
+ - A pull request a cloud reviewer holds, on every run whatever the script printed for it: run `canon pr review-state <number> --json`. The script reports nothing when a code review posts on an unchanged head, so this read is the only one that sees a cloud pass land. An open pass newer than the last dispatch earns the dispatch rule below. A closed pass at the head on a pull request still a draft earns `canon pr lift <number> --json`, reporting the reason it returns, since a cloud reviewer can neither message nor lift.
47
48
  - UI-STALE: the UI verdict trails the head. Message the live UI reviewer to drive the new head, or launch one, and lift no mark on it.
48
49
  - GONE: report it, then sweep the board by invoking the canon:role-orchestrator skill and following its queue-refill sweep.
49
50
  - A line starting `poll:`: report it verbatim and treat that pull request as unread this run. It is a failed query, not a state.
50
51
  - Nothing changed: say exactly "No movement." and nothing else.
51
52
 
52
- After any pass that posts a finding, at any severity, tell the session holding that branch to run the canon:review-address skill. Resolve the target by running `canon sessions list --branch <branch> --json` at that moment, which scopes the match to this repository, then route on how many sessions it returned. Zero: report the invocation for me and dispatch nobody. Exactly one, with the confidence field reading "confirmed": address that name directly. Any other count, any other confidence, or a command that is missing or refuses: fall back to picking from a session listing, open by naming the worktree and branch you believe the reader holds, and ask to be corrected. Two sessions can hold one branch, so read the count rather than the first row. The threshold is stated once in the canon:review-pr skill 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.
53
+ After a pass that posts a finding, at any severity, and that no live local reviewer sent on, tell the session holding that branch to run the canon:review-address skill. That covers a cloud reviewer's pass, a reviewer's message reporting no live worker, and a STALLED pass, which is by definition one nobody acted on, and nothing else, since a second dispatch for one pass runs review-address twice. Resolve the target by running `canon sessions list --branch <branch> --json` at that moment, which scopes the match to this repository, then route on how many sessions it returned. Zero: launch the review-address shape in the canon:role-orchestrator skill's launch runbook, carrying the reviewer's sessionId when a local reviewer is live. Exactly one, with the confidence field reading "confirmed": address that name directly. Any other count, any other confidence, or a command that is missing or refuses: fall back to picking from a session listing, open by naming the worktree and branch you believe the reader holds, and ask to be corrected. Two sessions can hold one branch, so read the count rather than the first row. The threshold is stated once in the canon:review-pr skill 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.
53
54
  ```
54
55
 
55
56
  ## Reading the output
@@ -60,7 +61,7 @@ The script exits non-zero and classifies nothing when the open pull request list
60
61
 
61
62
  The baseline lives at `.canon/tmp/pr/poll/baseline.txt` under the main worktree root and is per-machine. A first run against a board already in flight reports each open pull request once before it settles.
62
63
 
63
- The eight headings the script knows are written by `review-pr`, `review-address`, `canon pr evidence`, and `review-ui`, and the whole set is stated once in the first. The reply family and the UI family are matched by filters in `poll.ts`, so a project posting either under different headings edits those to match. The review family is matched inside `canon pr review-state` instead, which the script and `review-pr` both read through, so a project renaming either review heading changes the verb rather than the script. Either way, a heading nothing matches reads as a pull request nobody has reviewed. The `UI-` states report the newest UI pass on its transition, and `UI-STALE` fires beside the `MOVED` of a push that left its marker behind. A first sighting reports none, so hold the mark wherever `canon pr evidence <number> --json` carries a `checklist` until a UI close names the head.
64
+ The eight headings the script knows are written by `review-pr`, `review-address`, `canon pr evidence`, and `review-ui`, and the whole set is stated once in the first. The reply family and the UI family are matched by filters in `poll.ts`, so a project posting either under different headings edits those to match. The review family is matched inside `canon pr review-state` instead, which the script and `review-pr` both read through, so a project renaming either review heading changes the verb rather than the script. Either way, a heading nothing matches reads as a pull request nobody has reviewed. The `UI-` states report the newest UI pass on its transition, and `UI-STALE` fires beside the `MOVED` of a push that left its marker behind. A first sighting reports none, which costs the mark nothing, since `canon pr lift` reads the UI review itself and holds the mark wherever `canon pr evidence <number> --json` carries a `checklist` until a UI close names the head.
64
65
 
65
66
  `UNMATCHED` is what a comment reaches under a heading outside the six the comment filters know, a UI heading posted as a comment included, carried the same way `RESPONSE` is: a rising count against the baseline is what is new to this script, and the message names the heading so a person can tell whether to answer it by hand or add it to the set. It fires on a tracked pull request only, since a first sighting reports `SEEN` or `OPENED` and takes whatever count already sits on the thread as its starting baseline rather than flagging history the poll never watched.
66
67
 
@@ -74,15 +75,13 @@ The state reaches every stalled dispatch, since one threshold governs the headin
74
75
 
75
76
  A pull request's run tests the merge with the base as of its last push, so once a sibling merges, that green describes a merge nobody will make. `STALE` closes that gap by hand where a ruleset requiring branches to be current, or a merge queue, would close it on the host. It fires when the base changed a file the pull request writes since the commit it branched from, and names the files. A base that moved only on files the branch does not write reads as nothing, since re-testing after every merge is a merge queue's cost without its batching.
76
77
 
77
- `STALE` and `CONFLICT` each report on their transition and again on every push that leaves the reading standing, then live only in the baseline, which is as old as the last run. Read a standing one with `--check <number>`, which fetches the base and reads the head at call time, so a sibling merging after the last run still shows, and prints `CONFLICT`, `STALE`, `CLEAR`, or `UNKNOWN`, writing nothing. A pull request whose written set could not be read, being a fork or a deleted head, reads as `UNKNOWN` rather than clear, so its lift waits on a person.
78
+ `STALE` and `CONFLICT` each report on their transition and again on every push that leaves the reading standing, then live only in the baseline, which is as old as the last run. Read a standing one with `--check <number>`, which fetches the base and reads the head at call time, so a sibling merging after the last run still shows, and prints `CONFLICT`, `STALE`, `CLEAR`, or `UNKNOWN`, writing nothing. It takes the standing `canon pr lift <number> --check --json` reads, so the lift and this check answer from one read, and falls back to its own merge-tree read where the record carries no standing gate, which a binary older than that verb leaves. A pull request whose written set could not be read, being a fork or a deleted head, reads as `UNKNOWN` rather than clear, so its lift waits on a person.
78
79
 
79
80
  `OVERLAP` names two open pull requests that conflict with each other, read by merging their heads locally. Only pairs sharing a written path are tried, which keeps a wave of ten to a handful of merges. It reports once per pair, carried in the tenth baseline field, so the merge order is settled before the second branch has to rebase rather than after.
80
81
 
81
- ### The count behind the review dispatch
82
+ ### What a SEEN rests on
82
83
 
83
- The report is also where the load count in `orchestrator-review-dispatch.md` is legible, and that count decides where a first pass runs, here or in a dispatched reviewer. 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 the open 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.
84
-
85
- 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.
84
+ `SEEN` used to fire early, on a head nobody had read. 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.
86
85
 
87
86
  The pass now carries its own record instead. `review-pr` writes the commit it read and the instant it read it as a marker on the last line of every body it posts, and both this script and that skill resolve the covered state through `canon pr review-state`, which is the one place the marker is parsed. A commit pushed inside a pass's compose window falls outside the marked range, so it reads as `MOVED` rather than as `SEEN`. A `SEEN` is a claim about what a session read rather than about what GitHub stamped, so it wants no second read to be believed.
88
87
 
@@ -1,41 +1,38 @@
1
1
  ---
2
2
  title: Orchestrator review dispatch runbook
3
- description: When a pull request's review leaves this session for a reviewer dispatched under role-reviewer, when a UI reviewer goes beside it, what stays in place below both code triggers, and how the cross-branch pass per wave reaches each brief
3
+ description: Which pull requests take a reviewer dispatched under role-reviewer, the model it runs on, when a UI reviewer goes beside it, and how the cross-branch pass per wave reaches each brief
4
4
  ---
5
5
 
6
6
  Run this on every first pass, from `### The review dispatch` in the skill body. Review splits by what each side can see rather than by pass number. A dispatched reviewer holds one pull request and reads it deeper than this session can while holding a wave, and this session holds the wave and reads what no single pull request shows.
7
7
 
8
- ## The two code triggers
8
+ ## Every pull request takes a reviewer
9
9
 
10
- Either one dispatches the pull request's review to a reviewer under `role-reviewer`, first pass included.
10
+ Dispatch every pull request's review to a reviewer under `role-reviewer`, first pass included, whatever its size or its paths. A release pull request is the one exception, since its sweep carries no findings and owes no pass.
11
11
 
12
- - Load. Three or more open pull requests awaiting a first pass, or one diff too large for this session to hold. 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. `orchestrator-poll.md` states where the count is legible.
13
- - Content. The change touches `src/`, `scripts/`, a workflow, a hook, a skill or rule body, or a security surface, meaning anything that writes files, runs commands, pushes records, or handles transcripts. A skill or rule body counts as executable whatever its extension, since a model runs it. A pull request carrying an `## Evidence` comment with screenshots counts as well, since a reviewer holding one pull request opens every capture and a long-lived context rarely pays for that.
12
+ This session reviews nothing in place. A warm pass reads the whole diff into the one context the wave shares, so every pull request reviewed here thins the attention each later one gets, and a cold session pays for its own context instead.
14
13
 
15
- ## The UI trigger
14
+ Nothing checks that a first pass was dispatched rather than run here. This is prose this session applies to itself, on the same standing as the poll's own start condition, so a pull request reviewed in place is a rule that went unread rather than a check that failed.
16
15
 
17
- A third trigger sends a second reviewer rather than moving the first. A pull request whose evidence record carries a checklist takes a UI reviewer under `review-ui`, dispatched beside the code review whichever way the two triggers above decide it, so the two passes run at once. Read the record with `canon pr evidence <number> --json` and branch on its `checklist` field rather than on the exit code. A record carrying none dispatches no UI reviewer, since the pass would stop on arrival.
16
+ Pick the reviewer's model by `## Pick the model` in `orchestrator-dispatch.md`, which states the test for every role this session launches. Stating the pick here as well would leave two sources for one call.
18
17
 
19
- The UI reviewer reads no diff and never hears the code reviewer's verdict, so neither brief carries the other's result. Each posts its own family, and the draft mark waits on both, per the draft-lift boundary in the skill body.
18
+ ## The UI reviewer
20
19
 
21
- Nothing counts any of the three triggers. This is prose this session applies to itself, on the same standing as the poll's own start condition, so a wave reviewed in place past both is a rule that went unread rather than a check that failed.
20
+ A pull request whose evidence record carries a checklist takes a second reviewer, a UI reviewer under `review-ui`, dispatched beside the code reviewer so the two passes run at once. Read the record with `canon pr evidence <number> --json` and branch on its `checklist` field rather than on the exit code. A record carrying none dispatches no UI reviewer, since the pass would stop on arrival.
22
21
 
23
- ## What stays in place
24
-
25
- Below both triggers this session runs `review-pr` itself: a change to `docs/` or a context entry, a wording sweep, a release or capture pull request, and anything this session can review in one read. A cold reviewer pays a full session context per pull request, which on a small prose change costs more than the warm pass it replaces.
22
+ The UI reviewer reads no diff and never hears the code reviewer's verdict, so neither brief carries the other's result. Each posts its own family and runs `canon pr lift` on posting its close, and the verb lifts the draft mark only once both read closed at the head, per the draft-lift boundary in the skill body.
26
23
 
27
24
  ## The cross-branch pass
28
25
 
29
- This session keeps one pass per wave, whichever way each review went. It reads the file sets of every open pull request against each other, the board, and the merge order, and never the diffs themselves. What it looks for is what no single pull request shows: two branches regenerating one asset, several writing one context entry, a figure true only after another branch lands.
26
+ This session keeps one pass per wave beside the dispatched reviews. It reads the file sets of every open pull request against each other, the board, and the merge order, and never the diffs themselves. What it looks for is what no single pull request shows: two branches regenerating one asset, several writing one context entry, a figure true only after another branch lands.
30
27
 
31
28
  Hand what it finds to each reviewer as facts in the brief, per `### What the brief may carry` in `orchestrator-launch.md`, rather than posting a comment of its own, so each pull request keeps one live verdict. A fact surfacing after a reviewer posted goes to that reviewer as a re-review request.
32
29
 
33
30
  ## Launching the reviewer
34
31
 
35
- Take the reviewer shape in `orchestrator-reviewer-launch.md`, or its UI reviewer shape for the UI trigger. Check `canon sessions list --json` for a live `reviewer-<project>-<number>`, or `reviewer-ui-<project>-<number>`, first and message it instead of launching a second, since two reviewers on one pull request post two verdicts. The re-review after a worker's address pass goes back the same way, per `orchestrator-handback.md`.
32
+ Take the reviewer shape in `orchestrator-reviewer-launch.md`, or its UI reviewer shape for a pull request carrying a checklist. Check `canon sessions list --json` for a live `reviewer-<project>-<number>`, or `reviewer-ui-<project>-<number>`, first and message it instead of launching a second, since two reviewers on one pull request post two verdicts. The re-review after a worker's address pass reaches that reviewer from the worker directly, and this session sends it only as the fallback `orchestrator-handback.md` states.
36
33
 
37
34
  ## Local or cloud
38
35
 
39
- A code review goes to a cloud reviewer rather than `claude --bg` only on the operator's explicit pick for that dispatch, the same standing the build shape's pick holds in `orchestrator-dispatch.md`. The cloud session costs no local context and no machine slot, which matters when the load trigger fired. It also reads nothing this machine holds, so a review that needs the board, a local preview, or a sandbox stays local.
36
+ A code review goes to a cloud reviewer rather than `claude --bg` only on the operator's explicit pick for that dispatch, the same standing the build shape's pick holds in `orchestrator-dispatch.md`. The cloud session costs no local context and no machine slot, which matters on a wide wave. It also reads nothing this machine holds, so a review that needs the board, a local preview, or a sandbox stays local.
40
37
 
41
38
  This session hears a cloud reviewer through `poll.ts` alone, which already lists every open pull request from this machine, so the posted review is the announcement and no message arrives. `orchestrator-cloud-launch.md` carries the shape under `## Dispatch a review to a cloud reviewer`. A UI review always stays local.
@@ -12,10 +12,12 @@ Run this once `orchestrator-review-dispatch.md` has sent a pull request to a dis
12
12
  No branch and no worktree are entered here. The reviewer reads the pull request at the head `review-pr` resolves and writes the comment and its body file alone.
13
13
 
14
14
  ```bash
15
- claude --bg --model <model> -n "reviewer-<project>-<number>" "Run /canon:role-reviewer, then /canon:review-pr <number>. 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 pass posts, carrying the heading and the count line, and message it again if you stop on a question."
15
+ claude --bg --model <model> -n "reviewer-<project>-<number>" "Run /canon:role-reviewer, then /canon:review-pr <number>. 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. When the pass posts under ## Review, message the worker holding the branch to run review-address, carrying your own sessionId, and send it to the controller instead when no worker resolves. When it posts ## Review closed, run canon pr lift <number> --json and message the controller with the heading, the count line, and the reason the verb returned, unless that reason names the UI review as still open or missing. Message the controller again if you stop on a question."
16
16
  ```
17
17
 
18
- `<number>` is the pull request's number, and the brief pins no head, since `review-pr` resolves the head itself and stamps the range it covered in its marker. `<dispatcher-id>`, `<model>`, and `<project>` resolve the same way they do in `orchestrator-launch.md`. Append the cross-branch facts after the controller clause, per `### What the brief may carry` in that runbook.
18
+ `<number>` is the pull request's number, and the brief pins no head, since `review-pr` resolves the head itself and stamps the range it covered in its marker. `<model>` comes from `## Pick the model` in `orchestrator-dispatch.md`, which states the test for a reviewer as for every other role. `<dispatcher-id>` and `<project>` resolve the same way they do in `orchestrator-launch.md`.
19
+
20
+ The channel clauses restate what `role-reviewer` holds, so a reviewer whose plugin cache predates that body still sends the dispatch itself rather than leaving it to this session. The worker reads the reviewer's sessionId off that message and sends its re-review request back to it, since a reviewer holds the trunk branch and no branch lookup finds it. Append the cross-branch facts after the controller clause, per `### What the brief may carry` in `orchestrator-launch.md`.
19
21
 
20
22
  Check `canon sessions list --json` for a live `reviewer-<project>-<number>` before launching, and message that session instead when one holds the pull request.
21
23
 
@@ -28,10 +30,10 @@ Read `${CLAUDE_SKILL_DIR}/references/orchestrator-cloud-launch.md` when the oper
28
30
  `review-ui` is the second pass `role-reviewer` may run, so this shape reaches the role first the way the code reviewer's does. `orchestrator-review-dispatch.md` decides whether a pull request takes it, and it runs beside the code reviewer rather than after it.
29
31
 
30
32
  ```bash
31
- claude --bg --model sonnet -n "reviewer-ui-<project>-<number>" "Run /canon:role-reviewer, then /canon:review-ui <number>. 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 pass posts, carrying the heading and the count line, and message it again if you stop on a question."
33
+ claude --bg --model sonnet -n "reviewer-ui-<project>-<number>" "Run /canon:role-reviewer, then /canon:review-ui <number>. 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. When the pass posts under ## UI review, message the worker holding the branch to run review-address, carrying your own sessionId, and send it to the controller instead when no worker resolves. When it posts ## UI review closed, run canon pr lift <number> --json and message the controller with the heading, the count line, and the reason the verb returned, unless that reason names the code review as still open or missing. Message the controller again if you stop on a question."
32
34
  ```
33
35
 
34
- The model is fixed at Sonnet rather than taken from `## Pick the model` in `orchestrator-dispatch.md`. A cheaper tier was measured on the same checklist and passed real defects as clean while judging the taste boxes it was told to leave, at a higher cost per run.
36
+ The model is `sonnet`, always, as the UI reviewer line in `## Pick the model` in `orchestrator-dispatch.md` states with its measured reason.
35
37
 
36
38
  Name an address in the brief only when the evidence record carries no `preview` and no `local` field, as `Drive <url>.` after the controller clause. An address the record already holds is the one the pass reads first, and a second in the brief is a second source for one fact. Name a CLI config path the same way when the operator keeps a GPU recipe for this machine, since the pass renders in software otherwise. Carry nothing else: the UI reviewer reads no diff and no code verdict, so the cross-branch facts the code brief carries stay out of this one.
37
39
 
@@ -809,12 +809,61 @@ const countSince =
809
809
  return log.isOk ? log.stdout.split('\n').filter((l) => l !== '').length : 0
810
810
  }
811
811
 
812
+ const LIFT_STANDING: Record<
813
+ string,
814
+ Pick<Baseline, 'merges' | 'stale'> | undefined
815
+ > = {
816
+ 'standing-conflict': { merges: 'conflict', stale: 'unknown' },
817
+ 'standing-stale': { merges: 'clean', stale: 'stale' },
818
+ 'standing-unknown': { merges: 'unknown', stale: 'unknown' },
819
+ }
820
+
821
+ /**
822
+ * The standing `canon pr lift --check` read, so the lift and this check answer
823
+ * from one authority. Null when the record names no head or no standing gate,
824
+ * which a pull request already ready, a closed one, and an older binary with no
825
+ * `pr lift` verb all leave.
826
+ */
827
+ export const standingFromLift = (
828
+ record: unknown,
829
+ ): null | Pick<Baseline, 'head' | 'merges' | 'stale'> => {
830
+ if (typeof record !== 'object' || record === null) return null
831
+ const { gates, head } = record as Record<string, unknown>
832
+ if (typeof head !== 'string' || head === '' || !Array.isArray(gates)) {
833
+ return null
834
+ }
835
+ const gate: unknown = gates.find(
836
+ (g: unknown) =>
837
+ typeof g === 'object' &&
838
+ g !== null &&
839
+ (g as Record<string, unknown>).gate === 'standing',
840
+ )
841
+ if (gate === undefined) return null
842
+ const { isOk, reason } = gate as Record<string, unknown>
843
+ if (isOk === true) return { head, merges: 'clean', stale: 'fresh' }
844
+ const standing =
845
+ typeof reason === 'string' ? LIFT_STANDING[reason] : undefined
846
+
847
+ return standing === undefined ? null : { head, ...standing }
848
+ }
849
+
812
850
  /**
813
851
  * `--check <number>` reads the base and the head at call time and writes
814
852
  * nothing. The baseline is as old as the last full run, and a sibling merging
815
- * after it would leave a stored reading claiming clear.
853
+ * after it would leave a stored reading claiming clear. The lift verb's reading
854
+ * comes first, and the read below stays as the fallback for an older binary.
816
855
  */
817
856
  const check = (number: string): number => {
857
+ // The verb exits 1 on any refused gate and still prints its record, so the
858
+ // exit says nothing about whether the standing was read.
859
+ const lifted = standingFromLift(
860
+ parseJson(run('canon', ['pr', 'lift', number, '--check', '--json']).stdout),
861
+ )
862
+ if (lifted !== null) {
863
+ console.log(standingLine({ ...lifted, number }))
864
+
865
+ return 0
866
+ }
818
867
  const ref = baseRef()
819
868
  run('git', ['fetch', '-q', 'origin', baseBranch(ref)])
820
869
  const head = readHead(number)
@@ -24,19 +24,27 @@ cold pass found a finding the warm pass missed and showed one of the warm pass's
24
24
  own findings wrong.
25
25
 
26
26
  Every one of those launches restated the reviewer's bounds by hand: write the
27
- comment alone, no worktree, no branch, no board write, no fix, no draft lift.
27
+ comment alone, no worktree, no branch, no board write, no fix, no direct draft
28
+ lift.
28
29
  Each is a property of the role rather than of the pull request, so each dispatch
29
30
  reproduced them from memory, the shape `role-planner` was written to retire on
30
31
  the planning side.
31
32
 
32
33
  Three bounds matter more than the rest. A reviewer asked to fix what it found
33
34
  has reviewed its own change afterwards, and nothing recovers the independent
34
- read. A reviewer posting `## Review closed` is one step from lifting the draft
35
- mark, and `review-pr` Step 5 reports a merge recommendation that reads as
36
- permission when the body is silent. A reviewer with no checkout reading a file
35
+ read. A reviewer posting `## Review closed` is one step from readying the pull
36
+ request by hand, which skips the gates `canon pr lift` reads, and `review-pr`
37
+ Step 5 reports a merge recommendation that reads as permission when the body is
38
+ silent. A reviewer with no checkout reading a file
37
39
  off the main worktree reads the trunk, so every finding it files there is about
38
40
  a tree the pull request never changed.
39
41
 
42
+ Routing through the controlling session costs that session the most. Until
43
+ 2026-10-12 each review round passed through it four times: the pass announced to
44
+ it, the dispatch to the worker, the worker's answer, and the re-review request.
45
+ Every one of those is a turn that reads the whole wave's context, and the
46
+ reviewer and the worker each already knew what the other needed next.
47
+
40
48
  ## Must
41
49
 
42
50
  - Assert what a reviewing session is, what it may write, and how long the role lasts, since `review-pr` carries the pass and no body carries the role that runs it when dispatched
@@ -44,8 +52,10 @@ a tree the pull request never changed.
44
52
  - Send every file read a finding rests on to `git show <head>:<path>` at the head `review-pr` resolved, since the main worktree holds the trunk
45
53
  - Name what the pass reads, the diff, the plan, the task's outcomes, the rules, and `review-craft`, and what it leaves, the author's argument and the worker's session, since the independence is what the trials measured
46
54
  - Read the cross-branch facts a launch carries as inputs rather than findings, since the controller's wave-level read reaches this session only through the brief
47
- - Refuse the fix, the draft lift, and the merge, whoever asks, and name who owns each, since each has a sender who will ask
48
- - Owe one announcement when the pass posts, carrying the number, the heading, and `review-pr` Step 5's count line, since the controller holds the channel to the worker and dispatches `review-address` off it
55
+ - Refuse the fix, a draft lift by any route but `canon pr lift`, and the merge, whoever asks, and name who owns each, since each has a sender who will ask
56
+ - Run `canon pr lift` after posting a close, since the verb reads every gate itself and a reviewer's close is the moment the last of them can turn clear
57
+ - Send the `review-address` dispatch to the worker on an open pass, carrying the number, the heading, `review-pr` Step 5's count line, and this session's sessionId, and fall back to the controller when no worker resolves, since every hop through the controller costs it a turn that reads the whole wave's context
58
+ - Message the controller on a close with the reason `canon pr lift` returned, unless the reason names the other review as still open, since that refusal needs nothing from anyone
49
59
  - 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
50
60
  - Keep a re-review with the reviewer that took the first pass while it is live, so the second pass keeps the read the first paid for
51
61
  - Defer the addressee resolution to `session-relay` and keep only the last-rung inference, which discriminates as it does for a planner because a reviewer holds no feature branch either
@@ -62,7 +72,7 @@ a tree the pull request never changed.
62
72
  ## Guards
63
73
 
64
74
  - Plan or task does not resolve from where the session stands: report it unreadable and name the main-root path, rather than reviewing as though the branch had no plan
65
- - Asked by anyone to fix, lift the draft, or merge: refuse, name who owns the act, and file the defect in the comment when the ask was a fix
75
+ - Asked by anyone to fix, ready the pull request directly, or merge: refuse, name who owns the act, and file the defect in the comment when the ask was a fix
66
76
  - Launch named no controlling session and an operator is present: ask which row to address rather than inferring
67
77
  - Launch named no controlling session and nobody is present: infer from the sessions holding no feature branch, report the candidates when more than one comes back, and say the addressee was inferred
68
78