@erclx/canon 5.33.0 → 5.34.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 (64) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/api-design/REQUIREMENT.md +1 -2
  3. package/claude/skills/api-design/SKILL.md +2 -3
  4. package/claude/skills/api-design/references/adopted.md +1 -1
  5. package/claude/skills/canon-feedback/REQUIREMENT.md +1 -0
  6. package/claude/skills/canon-feedback/SKILL.md +10 -28
  7. package/claude/skills/canon-feedback/references/template.md +53 -0
  8. package/claude/skills/canvas-board/SKILL.md +4 -1
  9. package/claude/skills/canvas-board/references/canvas.md +26 -3
  10. package/claude/skills/code-craft/REQUIREMENT.md +1 -1
  11. package/claude/skills/code-craft/SKILL.md +0 -1
  12. package/claude/skills/create-rule/SKILL.md +1 -1
  13. package/claude/skills/create-rule/references/rule.md +3 -1
  14. package/claude/skills/create-skill/references/skill.md +1 -1
  15. package/claude/skills/git-issue/references/issue.md +4 -1
  16. package/claude/skills/review-craft/references/ui.md +1 -1
  17. package/claude/skills/review-ui/SKILL.md +7 -5
  18. package/claude/skills/review-ui/references/driving.md +8 -8
  19. package/claude/skills/review-ui/references/post.md +52 -16
  20. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +9 -0
  21. package/claude/skills/target-setup/SKILL.md +10 -4
  22. package/claude/skills/target-setup/references/detect.md +19 -2
  23. package/claude/skills/test-craft/REQUIREMENT.md +2 -3
  24. package/claude/skills/test-craft/SKILL.md +15 -3
  25. package/docs/agents/canvas.md +5 -1
  26. package/docs/agents/commands.md +1 -1
  27. package/docs/agents/gov-detect.md +47 -0
  28. package/docs/agents/index.md +4 -1
  29. package/docs/agents/install-and-sync.md +4 -0
  30. package/docs/agents/pr-evidence-layout.md +70 -0
  31. package/docs/agents/pr-evidence.md +8 -9
  32. package/docs/agents/pr-frames.md +7 -2
  33. package/docs/agents/pr-lift.md +132 -0
  34. package/docs/agents/scripting.md +2 -1
  35. package/docs/target/projects.md +3 -3
  36. package/governance/detect.toml +201 -0
  37. package/governance/rules/code/000-code.md +1 -13
  38. package/governance/rules/code/010-testing.md +2 -30
  39. package/governance/rules/ui/400-ui.md +1 -1
  40. package/governance/rules/ui/480-canvas.md +13 -0
  41. package/package.json +1 -1
  42. package/scripts/core/regen-hero.ts +0 -6
  43. package/scripts/lib/ui.sh +0 -9
  44. package/src/canvas/arrange.ts +66 -0
  45. package/src/canvas/capture.ts +20 -6
  46. package/src/canvas/content.ts +39 -0
  47. package/src/canvas/fit.ts +238 -0
  48. package/src/canvas/tokens.ts +28 -10
  49. package/src/capture/frame-values.ts +2 -32
  50. package/src/commands/canvas.ts +179 -1
  51. package/src/commands/gov.ts +144 -1
  52. package/src/commands/pr.ts +376 -1
  53. package/src/feedback/body.ts +26 -5
  54. package/src/gate/measures.ts +58 -44
  55. package/src/gate/stages.ts +3 -4
  56. package/src/gov/detect.ts +301 -0
  57. package/src/gov/list.ts +23 -2
  58. package/src/gov/stacks.ts +9 -0
  59. package/src/pr/evidence.ts +218 -45
  60. package/src/pr/frames.ts +23 -3
  61. package/src/pr/lift.ts +418 -0
  62. package/src/pr/rest.ts +3 -0
  63. package/src/pr/review-scope.ts +76 -0
  64. package/src/sandbox/equivalence.ts +14 -7
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
4
- "version": "5.33.0",
4
+ "version": "5.34.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -19,7 +19,7 @@ The one skill nearest the subject leaves the pointer open. `codebase-layout` exc
19
19
 
20
20
  - State Hyrum's Law as design rules a session can act on: decide what is observable, and expose no storage type, internal identifier, ordering, or timing by accident
21
21
  - Tell the session to write the contract, being the signature, flags, output shape, or schema, before the body
22
- - State one error shape per surface with a stable code a caller can branch on, pointing at the core code rule for structured errors and hidden internals rather than restating it
22
+ - State one error shape per surface with a stable code a caller can branch on, and own the hidden-internals clause in the body, since no project rule carries it for every code stack
23
23
  - State validation at the boundary, once, with the value trusted inside
24
24
  - State additive change as the default: new fields and flags optional, nothing renamed or removed in place, no change of meaning under an existing name, and the One-Version Rule for a caller that cannot pin
25
25
  - State the four idempotency rules: store and honor a caller's key, claim it in one atomic step under a unique constraint, fail loudly on a reused key with a different body, and keep a key past the longest retry window
@@ -30,7 +30,6 @@ The one skill nearest the subject leaves the pointer open. `codebase-layout` exc
30
30
  ## Must not
31
31
 
32
32
  - Name a framework, a protocol style, or a language in the body. Status codes, verbs, pagination, and schema languages stay out, so the body holds for a function, a command, a file format, and an endpoint alike.
33
- - Restate the two error bullets of the core code rule
34
33
  - Restate the consumer-listing axis `review-craft` carries for a finished change
35
34
  - Cover interface depth or the internals behind a boundary, which govern what sits behind a surface rather than what crosses it
36
35
  - Import an external skill's tone, such as a law stated as absolute, or an excuse row rebutting an objection no session has raised
@@ -31,8 +31,8 @@ Every observable behavior becomes a dependency once there are enough callers, wh
31
31
 
32
32
  ## Keep one error shape per surface
33
33
 
34
- - Give every failure on one surface the same shape, carrying a stable code a caller can branch on and a message for the person reading it. Branching on message text breaks the first time the message is reworded.
35
- - Follow the project's own rule on structured errors and hidden internals where it states one, rather than inventing a second error convention beside it.
34
+ - Give every failure on one surface the same shape, carrying a stable code a caller can branch on and a message for the person reading it. Branching on message text breaks the first time the message is reworded. Keep internal implementation details out of the message, since a caller reads it and cannot act on a stack frame.
35
+ - Reuse the error type the project already throws or returns where one exists, rather than inventing a second error convention beside it.
36
36
 
37
37
  ## Validate at the boundary
38
38
 
@@ -98,7 +98,6 @@ Check each line against the surface the change produced. A line answering no is
98
98
  ## What this delegates
99
99
 
100
100
  - Where the file holding a surface sits on disk: `codebase-layout`
101
- - The structured error rule and hidden internals floor every code stack loads: `000-code`
102
101
  - Listing the consumers a finished change breaks, and the docs it made false, during review: `review-craft`
103
102
  - A visual interface, its layout, and its look: `design-taste`
104
103
  - Interface depth and the internals behind a boundary: `code-craft`
@@ -21,7 +21,7 @@ From `addyosmani/agent-skills@2686b620`, [`skills/api-and-interface-design/SKILL
21
21
 
22
22
  **Contract first.** Write the interface before the body. Adopted as its own group, widened from an endpoint to a signature, a flag set, an output shape, and a schema.
23
23
 
24
- **One error shape.** Every failure on one surface carries the same structure. Adopted with a stable code added for the caller to branch on, and pointed at the core code rule for the structured-error floor rather than restating it.
24
+ **One error shape.** Every failure on one surface carries the same structure. Adopted with a stable code added for the caller to branch on, and a clause keeping internal implementation details out of the message, since no project rule carries either for every code stack.
25
25
 
26
26
  **Validation at the boundary.** Check input where it enters and trust it inside. Adopted, with a value read back from a file or another service counted as input.
27
27
 
@@ -20,6 +20,7 @@ The last failure is the session's own effort. Asked to report, a session starts
20
20
  - Write the stated fallback for a field the session cannot fill, so an empty field reads as absent rather than as unreported
21
21
  - Open a GitHub issue on the toolkit repo through the CLI so the report lands where triage reads it, and print the returned URL on its own line
22
22
  - Surface the block and the reason when the issue call fails, since a failure that prints nothing leaves the report where nothing reads it
23
+ - Carry the whole request in the issue body alone, since a comment is easy to miss and a body and a comment that differ leave triage choosing
23
24
  - Hold a report the operator marks sensitive, since the issue is public
24
25
  - Fall back to printing the block when the CLI is absent, so the report still exists
25
26
 
@@ -27,41 +27,23 @@ From the conversation so far, identify:
27
27
  - Repro details already in context (commands run, files touched), or `none`
28
28
  - Proposed fix, or `open` when the session settled on no direction
29
29
 
30
- Format as a single fenced markdown block, one `###` heading per field:
30
+ **Fields the report omits when the session has none:**
31
31
 
32
- ```markdown
33
- ## Toolkit feedback
34
-
35
- ### From project
36
-
37
- <name or kind>
38
-
39
- ### Surface
40
-
41
- <type>, <file path or name>
42
-
43
- ### Observed
44
-
45
- <one or two lines>
32
+ - Suggested shape, when the session worked out what the change should look like, such as a template, a command's output, or a field list
33
+ - Why, when the session has reasons for the fix beyond the observed failure
34
+ - Links, when the session cites an issue, a pull request, or a document
46
35
 
47
- ### Expected
36
+ Format as a single fenced markdown block, one `###` heading per field. Read `${CLAUDE_SKILL_DIR}/references/template.md` on reaching this point for the block and each field's fallback.
48
37
 
49
- <one or two lines, or "unclear">
38
+ `canon feedback` refuses a report missing `### Surface`, `### Observed`, or `### Proposed fix`, naming the one it did not find. `### From project`, `### Expected`, and `### Repro` are optional, so write the literal fallback the template shows rather than dropping the heading, which is what keeps a field the session cannot fill reading as absent instead of unreported.
50
39
 
51
- ### Repro
52
-
53
- <commands or steps, or "none">
54
-
55
- ### Proposed fix
56
-
57
- <one line, or "open">
58
- ```
59
-
60
- `canon feedback` refuses a report missing `### Surface`, `### Observed`, or `### Proposed fix`, naming the one it did not find. The other three are optional, so write the literal fallback shown above rather than dropping the heading, which is what keeps a field the session cannot fill reading as absent instead of unreported.
40
+ `### Suggested shape`, `### Why`, and `### Links` are optional the other way. Omit the heading when the session has nothing for it and write no fallback, since a report filed without them is complete and a placeholder would read as a reporter who had nothing to say.
61
41
 
62
42
  Headings rather than bold labels, because a GitHub issue form renders a submitted field as `### <label>`. One shape reaches the toolkit whichever route a report takes, and the CLI parses both with one parser. The retired `**Surface:**` form is refused rather than accepted, so a block carrying it has to be rewritten.
63
43
 
64
- Keep each field to one or two lines.
44
+ Keep Observed and Expected to one or two lines each, so the report scans. Length belongs in Proposed fix, Suggested shape, and Why.
45
+
46
+ The issue body is the request. Put everything the toolkit needs to act on it there, since a comment is easy to miss and a body and a comment that differ leave triage choosing. A comment carries only what happens after filing.
65
47
 
66
48
  ## Step 2: ship to the toolkit
67
49
 
@@ -0,0 +1,53 @@
1
+ ---
2
+ title: Feedback report template
3
+ description: The fenced block canon-feedback fills, one heading per field, with each field's stated fallback
4
+ ---
5
+
6
+ # Feedback report template
7
+
8
+ Step 1 of `canon-feedback`. The session reads this file on reaching the point where it formats the block.
9
+
10
+ ```markdown
11
+ ## Toolkit feedback
12
+
13
+ ### From project
14
+
15
+ <name or kind>
16
+
17
+ ### Surface
18
+
19
+ <type>, <file path or name>
20
+
21
+ ### Observed
22
+
23
+ <one or two lines>
24
+
25
+ ### Expected
26
+
27
+ <one or two lines, or "unclear">
28
+
29
+ ### Repro
30
+
31
+ <commands or steps, or "none">
32
+
33
+ ### Proposed fix
34
+
35
+ <the direction in as many lines as it takes, or "open">
36
+
37
+ ### Suggested shape
38
+
39
+ <details>
40
+ <summary>Shape</summary>
41
+
42
+ <the template, output, or field list, in a fence one backtick longer than any fence it holds>
43
+
44
+ </details>
45
+
46
+ ### Why
47
+
48
+ - <one reason per bullet>
49
+
50
+ ### Links
51
+
52
+ <one link per line>
53
+ ```
@@ -47,9 +47,11 @@ Hand the operator the address as a markdown link carrying the URL as its text an
47
47
  ## Step 2: make pages and frames
48
48
 
49
49
  - Add a page for each decision being drafted: `canon canvas page add <page> --json`.
50
- - Add each frame through the verb, which writes the starting file and its box in one locked step: `canon canvas frame add <page> <frame> --width <px> --height <px> --json`. Give the width the frame should render at, since a capture and the canvas both render at it.
50
+ - Follow the house style in `${CLAUDE_SKILL_DIR}/references/canvas.md` before drafting an explanation frame.
51
+ - Add each frame through the verb, which writes the starting file and its box in one locked step: `canon canvas frame add <page> <frame> --width <px> --height <px> --json`. Give the width the frame should render at, since a capture and the canvas both render at it. The verb places the frame after the last one, and a position the operator asks for goes through `frame move`.
51
52
  - Lay frames out with `canon canvas frame move <page> <frame> --x <px> --y <px> --json` rather than writing `layout.json` by hand, since the operator's drags write the same file under a lock.
52
53
  - Name pages and frames for what they hold, such as `pricing` and `pricing-dense`, so the operator can say which one by name.
54
+ - After an insert or a resize, re-lay the page with `canon canvas arrange <page> --json`. It keeps the rows the operator dragged, places each on the standard gap, and drops layout entries whose frame file is gone. `--check` reports without writing.
53
55
 
54
56
  ## Step 3: edit a frame through its file
55
57
 
@@ -57,6 +59,7 @@ Write the frame's whole document to the `path` the add verb reported. The open c
57
59
 
58
60
  - From a linked worktree, `Edit` and `Write` refuse a main-root path. Write the whole file through one plain `Bash` heredoc instead, the route `session-worktree` states for a main-root write, and never take the redirect to a worktree copy.
59
61
  - Change one property of one element with `canon canvas edit <page>/<frame> --element <index> --set <property>=<value> --json` when the set it accepts covers the change, since it leaves every other byte as it was.
62
+ - Fit the box to the content once the write lands: `canon canvas fit <page>/<frame> --json`. Read each row's `wide` and `font`. A `wide` frame needs its box widened, which fit never does, and a `font` frame names a family the canvas does not serve, so set its text to a served face and fit again. A fit that grew a frame leaves the rows below overlapped until `canvas arrange` runs.
60
63
  - Use the project's custom properties for every value the token stylesheet defines. The record from `serve` names the token source under `tokens`, and a `none` source means frames render unstyled, so say so rather than inventing values to cover it.
61
64
 
62
65
  Mark the frame before the first write, so the operator sees which frame is being worked on before anything lands, and clear the mark once the edits to it are done:
@@ -15,6 +15,7 @@ The folder is gitignored, so nothing in a repository checks its shape and nothin
15
15
  - [What a working canvas looks like](#what-a-working-canvas-looks-like)
16
16
  - [Pages](#pages)
17
17
  - [Frames](#frames)
18
+ - [House style](#house-style)
18
19
  - [Layout](#layout)
19
20
  - [Tokens](#tokens)
20
21
  - [What a frame must not assume](#what-a-frame-must-not-assume)
@@ -24,9 +25,11 @@ The folder is gitignored, so nothing in a repository checks its shape and nothin
24
25
 
25
26
  Governs the canvas content under `.canon/canvas/`: page folder names, frame files, the `layout.json` shape, how tokens reach a frame, and what a frame must not assume about where it renders.
26
27
 
28
+ Also governs the house style of an explanation frame, being a diagram, a board of notes, or a comparison. A mockup of the project's own interface is not an explanation frame, and it follows the project's design document for type, color, and layout.
29
+
27
30
  Does not govern:
28
31
 
29
- - The design decisions a frame is drafting, such as type, color, and layout taste: the project's own design document
32
+ - The design decisions a mockup frame is drafting, such as type, color, and layout taste: the project's own design document
30
33
  - The project's design document and the token module it renders from, which a picked direction is carried into: `design.md`
31
34
  - The procedure that starts the canvas, reads the operator's selection, and carries a pick back, which belongs to the surface driving the canvas
32
35
  - Voice, rhythm, and sentence construction in any text a frame shows: the `write-human` skill
@@ -59,6 +62,19 @@ A canvas failing these is non-conforming even when it satisfies every shape rule
59
62
  - Use the project's custom properties, `var(--<name>)`, for every value the token stylesheet defines. A literal copied from it shows the right color today and the wrong one after the next token change.
60
63
  - Edit a frame by rewriting its file. The file is the frame, and nothing else holds a copy of its markup.
61
64
 
65
+ ## House style
66
+
67
+ Applies to an explanation frame only. A mockup of the project's interface renders at the app's own sizes and follows the project's design document instead.
68
+
69
+ - Write text in sentence case. Use no all-caps text and no `text-transform: uppercase`.
70
+ - Keep lines short, one fact a line.
71
+ - Set body text at 20px or larger and headings at 44px or larger. Set nothing under 16px.
72
+ - Render an explanation frame about 1100px wide.
73
+ - Take colors from the project's custom properties. A frame that defines colors of its own sets the dark values on `:root` and the light ones under `[data-theme="light"]`, since the shell sets `data-theme` on each frame's root.
74
+ - Name only the faces the canvas serves into every frame: `Geist Variable` for text, `Noto Sans Mono` for code, and `Excalifont` for a hand-drawn look. They render the same in a capture and in any browser, so a height fitted against them holds.
75
+ - Make each box tall enough to show all its content, with no scrollbar and no clipped line. Run `canon canvas fit <page>/<frame>` rather than estimating, since it measures the content in the capture browser and sets the height.
76
+ - Read frames left to right in reading order. Let `frame add` place each frame after the last with the standard gap, and move one only when the operator asks for a position.
77
+
62
78
  ## Layout
63
79
 
64
80
  - Keep one `layout.json` per page, beside its frames. It holds each frame's box and nothing else, so moving a frame never rewrites its markup.
@@ -89,8 +105,8 @@ The keys below are the ones a reader parses. The frame names are placeholders.
89
105
 
90
106
  - Do not assume a viewport. A frame renders at its layout width on the canvas and in a capture alike, so size it with media queries against that width rather than against the operator's window.
91
107
  - Do not assume the theme. The canvas theme toggle is the operator's own state, and a capture shows each frame in its default theme.
92
- - Do not assume a network. Name only fonts the machine has, or ship the font beside the frame, since a capture refuses a frame naming a font it cannot find.
93
- - Do not assume a default font. Set `font-family` with an installed family first on an element the frame's text inherits from, since a capture checks the first family of the text it renders and the browser's default serif is absent from many machines.
108
+ - Do not assume a network. Name a served face, or ship the font beside the frame, since a capture refuses a frame naming a font it cannot find.
109
+ - Do not assume a default font. Set `font-family` with a served face first on an element the frame's text inherits from, since a capture checks the first family of the text it renders and the browser's default serif is absent from many machines. A frame naming a machine family such as `DejaVu Sans` renders at another height in another browser, which `canon canvas fit` reports.
94
110
  - Do not assume the frame is the source. A picked direction is carried into the project's design document or a plan, and the operator clears the frame once it has been carried.
95
111
  - Do not assume the canvas survives the checkout. The folder is gitignored, so a lost checkout loses every page in it.
96
112
 
@@ -113,6 +129,13 @@ A new frame starts from this skeleton. The names in it are placeholders.
113
129
  color: var(--color-text);
114
130
  background: var(--color-background);
115
131
  }
132
+ /* only a color the frame defines itself: dark on :root, light here */
133
+ :root {
134
+ --frame-accent: #7aa2ff;
135
+ }
136
+ [data-theme='light'] {
137
+ --frame-accent: #1f4fd8;
138
+ }
116
139
  </style>
117
140
  </head>
118
141
  <body>
@@ -30,7 +30,7 @@ Before this skill, two shipped pointers left interface depth unowned. `api-desig
30
30
  - State a tension as one side only, such as "never duplicate"
31
31
  - Name a language, a framework, or a type system feature in the body
32
32
  - Restate the red, green, refactor loop `test-first` runs, or the design review procedure `review-craft` carries for a finished diff
33
- - Restate the error and naming bullets of the core code rule
33
+ - Restate error or naming bullets. The core code rule retired them on purpose, and the error shape lives in `api-design`.
34
34
 
35
35
  ## Guards
36
36
 
@@ -106,4 +106,3 @@ Check each line against the code the change produced. A line answering no is fix
106
106
  - Which layer a test belongs at: `test-craft`
107
107
  - The contract a caller depends on, being its exports, flags, output, and errors: `api-design`
108
108
  - Judging a finished diff, including a design review of it: `review-craft`
109
- - The error and naming floors every code stack loads: `000-code`
@@ -70,4 +70,4 @@ Title casing is sentence case, with proper nouns keeping their own casing (`# Ty
70
70
 
71
71
  ## After writing
72
72
 
73
- Emit the full path on its own line: `.claude/rules/project/<subdir>/<n>-<slug>.md`. Remind the user that Claude Code loads path-scoped rules when it reads a matching file, and always-on rules every session.
73
+ Emit the full path on its own line: `.claude/rules/project/<subdir>/<n>-<slug>.md`. Remind the user that Claude Code loads path-scoped rules when it reads or writes a matching file, and always-on rules every session.
@@ -21,7 +21,9 @@ description: Rule frontmatter, body shape, and voice for .claude/rules files
21
21
 
22
22
  Rules give Claude Code coding constraints scoped to file paths. Claude Code discovers `.claude/rules/**/*.md` at session start.
23
23
 
24
- A rule with no `paths:` field always applies, at the same priority as `CLAUDE.md`. A rule with `paths:` applies when Claude reads a file matching the glob. Author one rule per topic so the scope stays precise.
24
+ A rule with no `paths:` field always applies, at the same priority as `CLAUDE.md`. A rule with `paths:` applies when Claude reads or writes a file matching the glob. Author one rule per topic so the scope stays precise.
25
+
26
+ A rule with `paths:` loads on a read or a write of a matching file, inside a linked worktree as in the main one. A session creating the first matching file gets the rule only after that write lands, so a rule meant to shape new files also belongs in the skill or the always-on rule a session reads before it writes. Revisit when Claude Code loads a path-scoped rule before a write to a matching path. Re-measure with two headless `claude -p` runs in a linked worktree, one reading a matching file and one reading a file that does not match, each asking whether the rule's content is in context.
25
27
 
26
28
  ## Scope
27
29
 
@@ -50,7 +50,7 @@ Answer all four before editing, and carry the answers into wherever the change i
50
50
 
51
51
  ## Whether a rule belongs beside the skill
52
52
 
53
- A skill fires when a session invokes it or its description matches the request. A path-scoped rule fires when a session reads a file matching its glob, with no decision from the session at all. The rule is the floor and the skill is the depth.
53
+ A skill fires when a session invokes it or its description matches the request. A path-scoped rule fires when a session reads or writes a file matching its glob, with no decision from the session at all. The rule is the floor and the skill is the depth.
54
54
 
55
55
  Run the two-part test over what the body states before calling the skill finished. Does the invariant fire when a specific path is edited, and does violating it ship silently? An invariant passing both halves belongs in a rule as well, since a session that never invoked the skill still edits that path. An invariant failing either half stays here, which is most of a body.
56
56
 
@@ -59,7 +59,10 @@ Does not govern:
59
59
  - `### Expected` (optional): the expected behavior, or `unclear`.
60
60
  - `### Repro` (optional): repro details, or `none`.
61
61
  - `### Proposed fix` (required): a proposed fix, or `open` when the session settled on no direction.
62
- - A required field is refused when present but empty. Write the fallback literal for an optional field rather than leaving it blank.
62
+ - `### Suggested shape` (optional): the template, output, or field list the change should take, inside a `<details>` block. Omit the heading when there is none.
63
+ - `### Why` (optional): the reasons for the fix, one bullet each. Omit the heading when there are none.
64
+ - `### Links` (optional): the issues, pull requests, or documents the report cites, one per line. Omit the heading when there are none.
65
+ - A required field is refused when present but empty. Write the fallback literal for From project, Expected, and Repro rather than leaving one blank.
63
66
 
64
67
  ## Formatting
65
68
 
@@ -45,7 +45,7 @@ mkdir -p .canon/tmp/pr/review/evidence/<number>
45
45
  git show <sha>:<path> > .canon/tmp/pr/review/evidence/<number>/<side>-<name>.png
46
46
  ```
47
47
 
48
- Take the sha from the row, so the base and head sides are exactly what the comment shows. Then open each file with the file-reading tool. A `*(new)*` base cell has no before image, and a state whose cases are all added has no Base column at all. Read the URL out of the `src=` of each `<img>`, or out of `![](` in a comment posted before the embed changed.
48
+ Take the sha from the row, so the base and head sides are exactly what the comment shows. Then open each file with the file-reading tool. A `*(new)*` base cell has no before image, and a view or flat block whose cases are all added has no base column at all. A view row holds one case per theme cell, so a width row names two images where a flat row names one. Read the URL out of the `src=` of each `<img>`, or out of `![](` in a comment posted before the embed changed.
49
49
 
50
50
  ## How many to open
51
51
 
@@ -94,11 +94,13 @@ Read `${CLAUDE_SKILL_DIR}/references/driving.md` on reaching this step for how a
94
94
 
95
95
  Give every box exactly one verdict, in checklist order. A box the pass could not place on a route or a width is still driven, from the opened address at 1440 wide, and the verdict names the guess. Skipping a box silently reads the same as passing it.
96
96
 
97
- Never judge a box ending in `(taste)`. Drive to the state, capture the frame, and mark it needs eyes. Drive a ticked box the same as an unchecked one, since a tick is a claim this pass exists to check. A tick stamped at the current head still gets driven.
97
+ Never judge a box ending in `(taste)`. Drive to the state, capture both themes, and mark it needs eyes. Drive a ticked box the same as an unchecked one, since a tick is a claim this pass exists to check. A tick stamped at the current head still gets driven.
98
98
 
99
- A frame the verdict rests on is published only when the worker's evidence lacks it. Compare the box's route, width, and theme against the `states` Step 2 kept, and when a state and stem show the same thing, cite that stem and push nothing. The match is this pass's judgment over the box's wording, so the verb never makes it. Otherwise push the frame and keep the `link` the record returns:
99
+ A frame the verdict rests on is published only when the worker's evidence lacks it, matched per theme against the `states` Step 2 kept, as `references/driving.md` states. The match is this pass's judgment over the box's wording, never the verb's. Otherwise push the frame and keep the `link` the record returns:
100
100
 
101
101
  ```bash
102
+ # one shot of a taste box's pair, then a pass or fail frame with no theme
103
+ canon pr frames <number> --add frames/<box-number>-<theme>.png --box <box-number> --theme <dark|light> --head <head> --pass <pass> --json
102
104
  canon pr frames <number> --add frames/<box-number>.png --box <box-number> --head <head> --pass <pass> --json
103
105
  ```
104
106
 
@@ -118,7 +120,7 @@ Close the browser with `${CLAUDE_SKILL_DIR}/scripts/pw.sh -s=<project>-<number>
118
120
 
119
121
  ## Step 7: post
120
122
 
121
- Read `${CLAUDE_SKILL_DIR}/references/post.md` for the heading rule, the body shape, the marker every body ends on, and the post command. Each Evidence cell holds words and names the evidence stem it matched, and the `link` its Step 5 push returned goes as given into the Frames block below the table.
123
+ Read `${CLAUDE_SKILL_DIR}/references/post.md` for the heading rule, the body shape, the marker every body ends on, and the post command. Each box lands in the section its verdict names, and the `link` its Step 5 push returned goes as given into that box's own entry.
122
124
 
123
125
  ## Step 8: tick the passed boxes
124
126
 
@@ -139,10 +141,10 @@ gh run list --workflow phase-label-gate.yml --event pull_request_review --limit
139
141
  Read `<heading>` off the first line of the file Step 7 posted, never composed here, so the line reports what the review carries.
140
142
 
141
143
  ```plaintext
142
- P passed, F failed, E need eyes, N not driven. Posted to PR #<number> under <heading>.
144
+ F failed · E need your eyes · P passed · N not driven. Posted to PR #<number> under <heading>.
143
145
  ```
144
146
 
145
- Add `Review-event checks: <passing|failing|unread>.` and `Ticks: <ticked N|reason>.` after it, the second naming the count Step 8 ticked or the verb's refusal `reason`. Add `S sweep finding(s).` when the sweep raised any. Report no merge recommendation, since this pass saw the app and not the change.
147
+ Add `Review-event checks: <passing|failing|unread>.` and `Ticks: <ticked N|reason>.` after it, the second naming the count ticked or the refusal `reason`. Add `S sweep finding(s).` when the sweep raised any. Report no merge recommendation, since this pass saw the app and not the change.
146
148
 
147
149
  ## Rules
148
150
 
@@ -32,20 +32,20 @@ A box written before that format may carry no route, no width, and a verb such a
32
32
  - Text, presence, and attributes: `pw --raw eval "<expression>"`, quoting the value back.
33
33
  - Computed style: `getComputedStyle(<element>).<property>`.
34
34
  - Geometry and overflow: `getBoundingClientRect()`, and `scrollWidth` against `clientWidth` on `document.documentElement`.
35
- - A frame: `pw screenshot --filename=frames/<box-number>.png`, then open it with the file-reading tool before saying what it shows.
35
+ - A frame: `pw screenshot --filename=frames/<box-number>.png`, then open it with the file-reading tool before saying what it shows. A taste box takes the shot twice, once per theme, naming the theme in the file as `frames/<box-number>-<theme>.png`. Reach each theme through the page's own control when the box names one, and through `pw set-color-scheme <dark|light>` otherwise, then read the page's background before the shot to confirm it changed. A page that paints the same under both has no second theme to show, so the box is not driven, with the reason naming that. `pw clear-color-scheme` restores the default.
36
36
 
37
37
  ## The four verdicts
38
38
 
39
39
  Each box takes exactly one.
40
40
 
41
- | Verdict | When | Evidence it carries |
42
- | ------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
43
- | pass or fail, read | The result is a fact the page reports | The value read, quoted, such as `scrollWidth 436 against clientWidth 320` |
44
- | pass or fail, frame | The result has a stated criterion only an image settles | The matching evidence stem or the pushed frame's link going to the Frames block, and what the frame shows, in words |
45
- | needs eyes | The box ends in `(taste)` | The matching evidence stem or the pushed frame's link going to the Frames block, and what the frame showed, with nothing about whether it looks right |
46
- | not driven | The state could not be reached | The reason, such as an unreachable address, a `(local only)` box on a hosted preview, or a target the snapshot does not hold |
41
+ | Verdict | When | Evidence it carries |
42
+ | ------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
43
+ | pass or fail, read | The result is a fact the page reports | The value read, quoted, such as `scrollWidth 436 against clientWidth 320` |
44
+ | pass or fail, frame | The result has a stated criterion only an image settles | The matching evidence stem or the pushed frame's link, inline under the failed box's heading, and what the frame shows, in words |
45
+ | needs eyes | The box ends in `(taste)` | The dark and the light shot as a pair, each the matching evidence stem or a pushed frame's link, and what the frames showed, with nothing about whether it looks right |
46
+ | not driven | The state could not be reached | The reason, such as an unreachable address, a `(local only)` box on a hosted preview, or a target the snapshot does not hold |
47
47
 
48
- A frame verdict carries one image at most, and never one the worker's evidence already shows. When a state and stem in the evidence record match the box's route, width, and theme, the evidence names that stem and nothing is pushed. Otherwise the frame goes up through `canon pr frames` and the `link` the record returned, which names the frame's path on the frames branch, goes under the Frames block and never inside the table row. Either way the words stay, describing the value or region judged, such as the element, its position, and the colors or text read, so a reader whose image fails to load can still check it.
48
+ A pass or fail resting on a frame carries one image at most, and never one the worker's evidence already shows. A needs-eyes box carries the pair, dark and light, since a taste call is made by comparing them. Match each theme separately: when a state and stem in the evidence record match the box's route, width, and that theme, the evidence names that stem and nothing is pushed for it, so a view with the dark shot in evidence pushes the light one alone. Otherwise the frame goes up through `canon pr frames` with `--theme`, and the `link` the record returned, which names the frame's path on the frames branch, goes in the box's own section of the body and never inside a folded block. Either way the words stay, describing the value or region judged, such as the element, its position, and the colors or text read, so a reader whose image fails to load can still check it.
49
49
 
50
50
  A push that refuses leaves the words alone, with the reason named. Never cite the local frame path. A path under `.canon/` is a board identifier the label scan rejects, and it resolves only on this machine.
51
51
 
@@ -14,7 +14,7 @@ The heading reports whether the branch owes anything, the way the code review's
14
14
  - `## UI review` when a box failed, the sweep found a console error or an overflow, or a box was not driven for a cause on the page, such as a target the snapshot does not hold.
15
15
  - `## UI review closed` otherwise.
16
16
 
17
- A needs-eyes box leaves the review closed, and so does a box not driven for a cause off the page: an address that did not answer, or a `(local only)` box on a hosted preview. Each is owed to the person who merges rather than to the branch, and no commit on the branch can settle it, so opening the heading would hold the draft mark at every head. Name each such box in the summary line's not-driven count and in its row's evidence. Do not append the pull request number, which GitHub already renders.
17
+ A needs-eyes box leaves the review closed, and so does a box not driven for a cause off the page: an address that did not answer, or a `(local only)` box on a hosted preview. Each is owed to the person who merges rather than to the branch, and no commit on the branch can settle it, so opening the heading would hold the draft mark at every head. Name each such box in the count line's not-driven count and in its Not driven entry. Do not append the pull request number, which GitHub already renders.
18
18
 
19
19
  ## The body
20
20
 
@@ -23,19 +23,39 @@ Write it to `.canon/tmp/pr/review-ui/body-<number>-<short-sha>.md` at the main w
23
23
  ```markdown
24
24
  <## UI review, or ## UI review closed, by the rule above>
25
25
 
26
- P passed, F failed, E need eyes, N not driven. Drove <address> at <short-sha>, rendered by <renderer>.
26
+ F failed · E need your eyes · P passed · N not driven
27
+ Driven at `<short-sha>` on <address>, rendered by <renderer>.
27
28
 
28
- | Box | Evidence |
29
- | -------------------------------------------- | --------------------------------------------- |
30
- | **Fail.** 1. <the box as written, shortened> | <the value read against what the box expects> |
31
- | **Needs eyes.** 2. <box> | <what the frame showed, in words> |
32
- | **Pass.** 3. <box> | Evidence `<stem>` <what it showed, in words> |
29
+ ### Failed
33
30
 
34
- **Frames**
31
+ **<n>. <the box as written, shortened>**
32
+ <the value read against what the box expects>
35
33
 
36
- - Box 2, needs eyes:
34
+ ![box <n>](link)
37
35
 
38
- ![](link)
36
+ ### Needs your eyes
37
+
38
+ **<n>. <box> (taste)**
39
+
40
+ | Dark | Light |
41
+ | ---------------- | ----------------- |
42
+ | ![](<dark link>) | ![](<light link>) |
43
+
44
+ ### Not driven
45
+
46
+ **<n>. <box>**: <the reason, and whether the cause sits on the page or off it>.
47
+
48
+ <details>
49
+ <summary>Passed (P)</summary>
50
+
51
+ | Box | Proof |
52
+ | --------------------- | ------------------------------------------------------ |
53
+ | <n>. <box, shortened> | <the value read, and the evidence `<stem>` it matched> |
54
+
55
+ </details>
56
+
57
+ <details>
58
+ <summary>Sweep and guessed wording</summary>
39
59
 
40
60
  **Sweep**
41
61
 
@@ -45,26 +65,42 @@ P passed, F failed, E need eyes, N not driven. Drove <address> at <short-sha>, r
45
65
 
46
66
  - Box <n>: <what the line left out and what the pass assumed>
47
67
 
68
+ </details>
69
+
48
70
  🤖 Driven by Claude Code
49
71
 
50
72
  <!-- review-ui: head=<head> -->
51
73
  ```
52
74
 
53
- The table has two columns and each Box cell opens on its verdict in bold with a period: `**Pass.**`, `**Fail.**`, `**Needs eyes.**`, or `**Not driven.**`. A column holding only those short words loses its width beside two long ones and GitHub breaks its header mid-word, so the verdict rides in the Box cell instead.
75
+ The body opens on its count line, in that order, so the operator reads what they owe before anything else. The second line carries the address, the head, and the renderer, and says the address was driven unverified when it came from the local rung or the launch.
76
+
77
+ Each section is dropped when it holds no box, along with its heading. Order is fixed: Failed, Needs your eyes, Not driven, then the folded blocks. A box takes exactly one section, chosen by its verdict, so a box that fails and also ends in `(taste)` appears once, under Failed.
78
+
79
+ A box in a section opens on its number and the box as written, in bold, with the reading beneath it in words. A failed box that rests on a frame shows that frame inline under its heading, the `![box <n>](link)` embed with the link exactly as `canon pr frames` returned it. It names the frame's path on the frames branch, which stays put while other pull requests' frames are dropped and carries this pass's stamp, so a later pass never changes the image. A failed box that rests on a read has no image.
80
+
81
+ A needs-eyes box shows its dark and light shots in a two-cell table ahead of anything else it carries, since the operator decides from them without opening the app. A cell is either the `link` Step 5 pushed or, for a theme the worker's evidence already shows, the evidence image at `https://github.com/<owner>/<repo>/blob/<head>/<path>?raw=true`, with `<path>` the matching state item's `path` off the evidence record. A box whose evidence holds one theme still renders both cells, one evidence link and one pushed link. A push that refused leaves its cell holding the words `not shown, <reason>`, so the table keeps its shape, and the box's line names what the frame showed. Never cite the local frame path, since a path under `.canon/` is a board identifier the label scan rejects.
54
82
 
55
- An Evidence cell resting on a frame holds words only, so the table's text columns keep the body's width. A frame Step 5 pushed goes in the Frames block below the table, one `Box <n>, <verdict>` label then the `![](<link>)` embed for each framed box, with the link exactly as `canon pr frames` returned it. It names the frame's path on the frames branch, which stays put while other pull requests' frames are dropped and carries this pass's stamp, so a later pass never changes the image.
83
+ The Passed block holds one row per passed box, with each row's measurement and the evidence stem it matched in backticks, so every reading stays one click away. A pass that cites a stem links it as the evidence image at the form above when the record holds the item. Omit the block when nothing passed.
56
84
 
57
- The row still says what the frame showed, so it reads without the image. A state the worker's evidence already shows names the evidence stem in backticks and embeds nothing, since the evidence comment carries that image. A push that refused leaves the words alone and names the reason. Omit the Frames block when no box pushed a frame. Never cite the local frame path, since a path under `.canon/` is a board identifier the label scan rejects.
85
+ Omit the Sweep block when it found nothing, and the Guessed wording block when every box named its route and width, and drop the folded block when both are empty. Quote page text in backticks as content, never as the pass's own words.
58
86
 
59
- A pass where every box needs eyes and nothing else is owed takes the closed heading:
87
+ A pass where every box passed is the heading, the count line, the folded Passed block, and the marker, with no empty headings:
60
88
 
61
89
  ```markdown
62
90
  ## UI review closed
63
91
 
64
- 0 passed, 0 failed, 3 need eyes, 0 not driven. Drove <address> at <short-sha>, rendered by <renderer>.
92
+ 0 failed · 0 need your eyes · 8 passed · 0 not driven
93
+ Driven at `<short-sha>` on <address>, rendered by <renderer>.
94
+
95
+ <details>
96
+ <summary>Passed (8)</summary>
97
+
98
+ ...
99
+
100
+ </details>
65
101
  ```
66
102
 
67
- Omit the Sweep block when it found nothing, and the Guessed wording block when every box named its route and width. Quote page text in backticks as content, never as the pass's own words.
103
+ The count line and the sections beneath it hold the same totals, and Step 10's printed line is the same four counts. A pass where every box needs eyes and nothing else is owed takes the closed heading.
68
104
 
69
105
  ## The marker
70
106
 
@@ -5,6 +5,15 @@ description: The launch every dispatch kind shares, being the build, cloud build
5
5
 
6
6
  Run this once `orchestrator-dispatch.md` has cleared a row, or from its planning or review-address shape below when that runbook's checks do not bind. The model each template names comes from `## Pick the model` in that runbook.
7
7
 
8
+ ## Contents
9
+
10
+ - [Dispatch](#dispatch)
11
+ - [Dispatch to a cloud worker](#dispatch-to-a-cloud-worker)
12
+ - [Dispatch to address a review](#dispatch-to-address-a-review)
13
+ - [Dispatch to plan a row](#dispatch-to-plan-a-row)
14
+ - [Dispatch a talk session](#dispatch-a-talk-session)
15
+ - [Dispatch a reviewer](#dispatch-a-reviewer)
16
+
8
17
  ## Dispatch
9
18
 
10
19
  ```bash
@@ -33,7 +33,7 @@ rather than a step within it, so the full chain never runs it separately.
33
33
 
34
34
  ## Non-interactive path
35
35
 
36
- Every phase runs under `CANON_NON_INTERACTIVE=1` with no TTY. Two phases
36
+ Every phase runs under `CANON_NON_INTERACTIVE=1` with no TTY. Three phases
37
37
  otherwise pause, and each takes an argument that answers its prompt ahead of
38
38
  time:
39
39
 
@@ -41,11 +41,15 @@ time:
41
41
  takes every candidate the scan found, and `--folders <a>,<b>` takes a named
42
42
  set. With neither, and with no TTY, the phase reports its candidates and stops
43
43
  rather than choosing for the operator.
44
+ - `install` and `gov` confirm the proposed extras with the operator.
45
+ `--extras <a>,<b>` takes a named set and `--extras none` takes none. With
46
+ neither, and with nobody to answer, both take every proposal and list each in
47
+ the report rather than stopping.
44
48
  - `gov` stops on a detected technology with no matching rule. `--skip-gaps`
45
49
  proceeds with the matched layer and lists the gap in the report.
46
50
 
47
51
  Claude Code's tool permission dialog is the confirmation gate for everything
48
- else. Do not pause for a separate confirmation.
52
+ else. Do not pause for a separate confirmation beyond the extras question.
49
53
 
50
54
  ## Scope
51
55
 
@@ -71,7 +75,8 @@ the `install` and `gov` phases share rather than each stating.
71
75
 
72
76
  ## Phase: install
73
77
 
74
- Run the chain in order, starting immediately after the preview. Run from the
78
+ Run the chain in order, starting once the operator has confirmed the extras the
79
+ preview proposed, per the detect reference. Run from the
75
80
  target project's current directory. Do not cd into the toolkit source tree. The
76
81
  `canon` CLI is global.
77
82
 
@@ -116,7 +121,8 @@ Do not generate ESLint, Vitest, or Playwright configs. They ship as golden files
116
121
  ## Phase: gov
117
122
 
118
123
  Governance rules and nothing else, for a project declining the tooling layer.
119
- Resolve exactly as the detect reference states, then run:
124
+ Resolve exactly as the detect reference states, confirm the proposed extras
125
+ there, then run:
120
126
 
121
127
  ```bash
122
128
  CANON_NON_INTERACTIVE=1 canon gov install <stack> --add <extras> <target>