@erclx/canon 5.41.0 → 5.42.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 (154) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/api-design/references/adopted.md +34 -14
  3. package/claude/skills/auto-ship/REQUIREMENT.md +1 -0
  4. package/claude/skills/auto-ship/SKILL.md +22 -2
  5. package/claude/skills/auto-ship/references/draft-mark.md +32 -6
  6. package/claude/skills/auto-ship/references/failure-recovery.md +15 -13
  7. package/claude/skills/auto-ship/references/tier-failures.md +31 -10
  8. package/claude/skills/auto-ship/references/ui-checklist.md +9 -4
  9. package/claude/skills/auto-ship/references/worktree-name.md +9 -3
  10. package/claude/skills/build-in-slices/references/adopted.md +35 -21
  11. package/claude/skills/canvas-board/references/canvas.md +17 -6
  12. package/claude/skills/canvas-board/references/design.md +26 -7
  13. package/claude/skills/code-craft/references/adopted.md +58 -20
  14. package/claude/skills/codebase-layout/references/adopted.md +49 -16
  15. package/claude/skills/codebase-layout/references/frontend.md +11 -4
  16. package/claude/skills/codebase-layout/references/tests.md +4 -2
  17. package/claude/skills/context-fold/references/classify.md +10 -15
  18. package/claude/skills/context-fold/references/completion-report.md +11 -9
  19. package/claude/skills/context-fold/references/context-refresh.md +26 -9
  20. package/claude/skills/context-fold/references/unusable-baseline.md +5 -3
  21. package/claude/skills/create-rule/REQUIREMENT.md +5 -0
  22. package/claude/skills/create-rule/SKILL.md +9 -1
  23. package/claude/skills/create-rule/references/checklist.md +26 -0
  24. package/claude/skills/create-rule/references/rule.md +98 -20
  25. package/claude/skills/create-skill/SKILL.md +1 -0
  26. package/claude/skills/create-skill/references/skill-disclosure.md +10 -2
  27. package/claude/skills/create-skill/references/skill-frontmatter.md +43 -0
  28. package/claude/skills/create-skill/references/skill-paths.md +7 -4
  29. package/claude/skills/create-skill/references/skill-practice.md +6 -3
  30. package/claude/skills/create-skill/references/skill-requirement.md +4 -2
  31. package/claude/skills/create-skill/references/skill.md +67 -44
  32. package/claude/skills/deploy-app/references/vercel.md +2 -1
  33. package/claude/skills/deprecation-migration/references/adopted.md +44 -25
  34. package/claude/skills/design-taste/references/adopted.md +9 -1
  35. package/claude/skills/design-taste/references/craft.md +3 -1
  36. package/claude/skills/design-taste/references/kinds.md +8 -7
  37. package/claude/skills/design-taste/references/preflight.md +12 -0
  38. package/claude/skills/design-taste/references/systems.md +7 -6
  39. package/claude/skills/design-taste/references/tells.md +24 -25
  40. package/claude/skills/design-taste/references/vocabulary.md +7 -5
  41. package/claude/skills/document-health/references/axes.md +54 -30
  42. package/claude/skills/draft-and-pick/references/live-arms.md +2 -2
  43. package/claude/skills/draft-doc/references/context.md +4 -3
  44. package/claude/skills/draft-doc/references/docs.md +6 -3
  45. package/claude/skills/draft-doc/references/readme/agent-facing.md +6 -0
  46. package/claude/skills/draft-doc/references/readme/application.md +8 -2
  47. package/claude/skills/draft-doc/references/readme/cli.md +5 -1
  48. package/claude/skills/draft-doc/references/readme/library.md +4 -0
  49. package/claude/skills/draft-doc/references/readme/plugin.md +9 -1
  50. package/claude/skills/draft-doc/references/readme.md +30 -13
  51. package/claude/skills/draft-doc/references/requirements.md +17 -4
  52. package/claude/skills/draft-doc/references/wiki-kind.md +20 -10
  53. package/claude/skills/draft-figure/references/architecture-set.md +7 -3
  54. package/claude/skills/draft-figure/references/figures.md +8 -6
  55. package/claude/skills/draft-figure/references/hand-drawn.md +3 -1
  56. package/claude/skills/draft-figure/references/mermaid-path.md +19 -9
  57. package/claude/skills/draft-figure/references/mermaid.md +10 -5
  58. package/claude/skills/draft-ready/references/ready.md +22 -15
  59. package/claude/skills/git-branch/references/branch.md +11 -1
  60. package/claude/skills/git-branch/references/slug.md +17 -5
  61. package/claude/skills/git-commit/references/commit.md +8 -0
  62. package/claude/skills/git-commit/references/versioning.md +23 -17
  63. package/claude/skills/git-followup/REQUIREMENT.md +1 -1
  64. package/claude/skills/git-followup/SKILL.md +5 -6
  65. package/claude/skills/git-issue/references/issue.md +29 -16
  66. package/claude/skills/git-pr/REQUIREMENT.md +1 -5
  67. package/claude/skills/git-pr/SKILL.md +11 -22
  68. package/claude/skills/git-pr/references/final-command.md +21 -8
  69. package/claude/skills/git-pr/references/labels.md +45 -28
  70. package/claude/skills/git-pr/references/pr.md +28 -9
  71. package/claude/skills/git-pr/references/publish.md +42 -28
  72. package/claude/skills/git-pr/references/task-number.md +20 -8
  73. package/claude/skills/git-ship/SKILL.md +1 -1
  74. package/claude/skills/plan-feature/references/plan-lifecycle.md +10 -3
  75. package/claude/skills/plan-feature/references/plan.md +57 -20
  76. package/claude/skills/plan-groundwork/references/groundwork-spikes.md +4 -1
  77. package/claude/skills/plan-groundwork/references/groundwork.md +14 -16
  78. package/claude/skills/plan-intake/references/intake.md +29 -24
  79. package/claude/skills/pr-evidence/REQUIREMENT.md +40 -0
  80. package/claude/skills/pr-evidence/SKILL.md +53 -0
  81. package/claude/skills/pr-evidence/references/evidence.md +150 -0
  82. package/claude/skills/review-address/references/post-review-findings.md +10 -3
  83. package/claude/skills/review-address/references/rebase-conflicts.md +15 -7
  84. package/claude/skills/review-address/references/rest-reads.md +3 -1
  85. package/claude/skills/review-craft/references/adopted.md +35 -18
  86. package/claude/skills/review-craft/references/security.md +42 -7
  87. package/claude/skills/review-craft/references/ui.md +23 -8
  88. package/claude/skills/review-pr/SKILL.md +6 -6
  89. package/claude/skills/review-pr/references/body-variants.md +29 -9
  90. package/claude/skills/review-pr/references/close-out.md +20 -5
  91. package/claude/skills/review-pr/references/key-changes.md +27 -3
  92. package/claude/skills/review-pr/references/post.md +83 -31
  93. package/claude/skills/review-pr/references/scope.md +54 -19
  94. package/claude/skills/review-ui/references/close-out.md +7 -5
  95. package/claude/skills/review-ui/references/driving.md +24 -4
  96. package/claude/skills/review-ui/references/post.md +43 -15
  97. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +67 -17
  98. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +80 -40
  99. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +52 -15
  100. package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +48 -26
  101. package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +12 -13
  102. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +72 -36
  103. package/claude/skills/role-orchestrator/references/orchestrator-output.md +12 -4
  104. package/claude/skills/role-orchestrator/references/orchestrator-parked.md +64 -24
  105. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +80 -26
  106. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +41 -18
  107. package/claude/skills/role-orchestrator/references/orchestrator-resume.md +19 -7
  108. package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +22 -12
  109. package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +15 -6
  110. package/claude/skills/role-orchestrator/references/orchestrator-sweep.md +25 -7
  111. package/claude/skills/role-orchestrator/references/orchestrator-watch.md +34 -8
  112. package/claude/skills/role-orchestrator/scripts/poll.ts +16 -24
  113. package/claude/skills/role-worker/REQUIREMENT.md +1 -0
  114. package/claude/skills/role-worker/SKILL.md +8 -0
  115. package/claude/skills/search-craft/references/adopted.md +18 -6
  116. package/claude/skills/session-map/references/session.md +26 -7
  117. package/claude/skills/session-worktree/references/main-root-writes.md +25 -8
  118. package/claude/skills/systematic-debugging/references/adopted.md +30 -16
  119. package/claude/skills/target-check/references/domains.md +39 -11
  120. package/claude/skills/target-setup/references/detect.md +80 -65
  121. package/claude/skills/target-setup/references/indexes.md +8 -6
  122. package/claude/skills/task-board/references/board.md +86 -30
  123. package/claude/skills/task-board/references/decline.md +29 -13
  124. package/claude/skills/task-board/references/tasks-decline.md +12 -5
  125. package/claude/skills/task-board/references/tasks.md +65 -33
  126. package/claude/skills/task-complete/references/archive.md +41 -17
  127. package/claude/skills/task-complete/references/receipt-sweep.md +23 -9
  128. package/claude/skills/task-complete/references/unusable-baseline.md +10 -3
  129. package/claude/skills/teach-workspace/references/glossary.md +6 -2
  130. package/claude/skills/teach-workspace/references/lesson-build.md +44 -16
  131. package/claude/skills/teach-workspace/references/lesson-craft.md +12 -7
  132. package/claude/skills/teach-workspace/references/pedagogy.md +6 -3
  133. package/claude/skills/teach-workspace/references/promotion-fold.md +17 -11
  134. package/claude/skills/teach-workspace/references/promotion.md +31 -8
  135. package/claude/skills/teach-workspace/references/revisit.md +8 -2
  136. package/claude/skills/teach-workspace/references/teach.md +17 -9
  137. package/claude/skills/test-craft/references/adopted.md +48 -14
  138. package/claude/skills/test-craft/references/component.md +7 -4
  139. package/claude/skills/test-craft/references/e2e.md +25 -7
  140. package/claude/skills/test-craft/references/unit.md +3 -1
  141. package/claude/skills/test-first/references/adopted.md +12 -4
  142. package/claude/skills/ui-checklist/REQUIREMENT.md +1 -1
  143. package/claude/skills/ui-checklist/SKILL.md +1 -1
  144. package/claude/skills/video-craft/references/adopted.md +62 -18
  145. package/claude/skills/write-human/references/adopted.md +21 -8
  146. package/docs/workflow/parallel-features.md +1 -1
  147. package/docs/workflow/skill-map.md +1 -0
  148. package/package.json +1 -1
  149. package/src/claude/cases/workflow.ts +5 -0
  150. package/src/commands/pr.ts +142 -28
  151. package/src/markdown/scope.ts +144 -0
  152. package/src/pr/lift.ts +6 -3
  153. package/src/pr/review-state.ts +182 -0
  154. package/claude/skills/git-pr/references/evidence.md +0 -124
@@ -50,13 +50,21 @@ A plan failing these is non-conforming even when it satisfies every shape rule b
50
50
  ## Filename and slug
51
51
 
52
52
  - Name the file `feature-<slug>.md`, with `<slug>` two to four kebab-case words naming the concern.
53
- - Write one concern per file. A request spanning two independent concerns takes two plans rather than one bundling both, since a bundled plan cannot be executed by two sessions or abandoned by half.
54
- - A staged batch is a concern of its own and takes its own file rather than a `**Batch N**` sub-heading inside one plan's `**Files to touch:**`. One plan filename derives one branch, so batches sharing a file have nothing to open a pull request against once the first merges. State a batch's dependency on the ones before it in its own `**Constraints:**`. A batch that stacks on another and shares most of its files with it folds into that batch as contiguous commits on one branch rather than taking a file, unless its constraints carry ``Judged apart from `feature-<parent>`: <reason>`` with a reason after the colon. `canon records validate plans` reports `stack-foldable` for a stacked batch that has neither.
53
+ - Write one concern per file, since a plan bundling two cannot be executed by two sessions or abandoned by half
55
54
  - Derive the slug from the concern rather than from a branch, because the plan is written before the branch exists.
56
- - Give the branch that executes the plan the same slug. A later surface finds the plan from the branch name and finds nothing when the two spellings differ.
55
+ - Give the executing branch the same slug, since a later surface finds the plan from the branch name and finds nothing when the spellings differ
57
56
 
58
57
  A plan carries no frontmatter. Its filename is the whole of its identity, so a folder of plans is read by name rather than by a generated catalog.
59
58
 
59
+ ### Staged batches
60
+
61
+ A staged batch is a concern of its own and takes its own file rather than a `**Batch N**` sub-heading inside one plan's `**Files to touch:**`. One plan filename derives one branch, so batches sharing a file have nothing to open a pull request against once the first merges.
62
+
63
+ - State a batch's dependency on the ones before it in its own `**Constraints:**`
64
+ - A batch stacking on another and sharing most of its files folds into that batch as contiguous commits on one branch, rather than taking a file
65
+ - It takes a file anyway when its constraints carry ``Judged apart from `feature-<parent>`: <reason>`` with a reason after the colon
66
+ - `canon records validate plans` reports `stack-foldable` for a stacked batch that has neither
67
+
60
68
  ## Sections
61
69
 
62
70
  The document opens with `# Feature: <short title>` and one paragraph stating what is being built and why now. The sections below follow in this order.
@@ -73,62 +81,86 @@ The document opens with `# Feature: <short title>` and one paragraph stating wha
73
81
  | `**Sources:**` | the outside sources a decision rests on, each with its quoted passage | When a decision rests on an outside source |
74
82
 
75
83
  - Write each marker as the table gives it. `Summary` opens the prose a reader scans, so it takes an H2, and the others are labels over lists rather than sections of prose.
76
- - A section written in the other spelling is still that section, since `## Risks` and `**Risks:**` name one thing. Write the table's form in a new plan and leave an existing plan's spelling alone. Either spelling breaks a depth run.
84
+ - A section in the other spelling is still that section, since `## Risks` and `**Risks:**` name one thing, and either spelling breaks a depth run
85
+ - Write the table's form in a new plan and leave an existing plan's spelling alone
77
86
  - Write `None identified.` under a required section with nothing to report rather than dropping the marker. A dropped section and an unconsidered one read identically.
78
87
  - Aim `## Summary` at a person scanning the plan, not at the session executing it. The other sections carry what execution needs.
79
88
 
80
89
  ### Files to touch entries
81
90
 
82
- - Give every `**Files to touch:**` entry a backticked path and something said about it. A bare path states scope and not intent, and the reason is what an executing session checks its edit against. Lead with the path or lead with a label carrying the path, whichever reads better for the entry.
83
- - Separate the two halves with a colon, and keep every path the entry declares ahead of it. What sits before the colon reads as a target, so both sides of a rename belong there and a file merely cited by the reason does not.
84
- - Read the list as what the branch sets out to write rather than as a bound on it, since the doc syncs at ship write past it on nearly every branch. `canon tasks plan-reach <plan>` reads the branch back against this list and every other live plan.
91
+ Give every `**Files to touch:**` entry a backticked path and something said about it. A bare path states scope and not intent, and the reason is what an executing session checks its edit against.
92
+
93
+ - Lead with the path or with a label carrying the path, whichever reads better for the entry
94
+ - Separate the two halves with a colon, and keep every path the entry declares ahead of it, since what sits before the colon reads as a target
95
+ - Both sides of a rename belong before the colon, and a file merely cited by the reason does not
96
+ - Read the list as what the branch sets out to write rather than a bound, since the doc syncs at ship write past it on nearly every branch
97
+ - `canon tasks plan-reach <plan>` reads the branch back against this list and every other live plan
85
98
 
86
99
  ### Measured claims
87
100
 
88
- - State every count and every claim about the tree as measured during the pass that wrote the plan. A figure carried in from a summary or an earlier session is the most common way a plan ships the wrong scope.
89
- - Prefer a short plan over a padded one. A section filled to look thorough costs the reader the same attention as one that matters.
101
+ State every count and every claim about the tree as measured during the pass that wrote the plan. A figure carried in from a summary or an earlier session is the most common way a plan ships the wrong scope.
102
+
103
+ Prefer a short plan over a padded one. A section filled to look thorough costs the reader the same attention as one that matters.
90
104
 
91
105
  ### Constraints
92
106
 
93
107
  Work in flight reaches a plan as a hold or as a shared path. A hold leaves a surface alone and says which of two acts it forbids, rather than leaving the executing session to guess. Neither act is forbidden over a shared path.
94
108
 
95
- - Write a hold only for a dependency, a contract the other track consumes, a relocation, or a sweep, which a rebase cannot settle mechanically. A bare shared file is none of these, and forbidding it sends a worker to concede a file the change needs.
109
+ Write a hold only for a dependency, a contract the other track consumes, a relocation, or a sweep, which a rebase cannot settle mechanically. A bare shared file is none of these, and forbidding it sends a worker to concede a file the change needs.
110
+
96
111
  - Forbid conforming that surface to whatever shape the change introduces, which keeps the branch to one concern.
97
- - Never forbid retargeting a pointer the change breaks. A rename, a split, or a deletion that leaves a citation behind ships a dangling reference, so repairing it is required work rather than scope creep.
112
+ - Never forbid retargeting a pointer the change breaks, since a rename, split, or deletion leaving a citation behind ships a dangling reference
98
113
  - Decide both acts for every surface a hold names, not for one file among its siblings.
99
114
  - Write a shared path as the track, the path, and the branch that rebases, being the one merging second. The executing session edits the file and keeps both sides on the rebase.
100
115
 
116
+ ### Stamping a constraint
117
+
101
118
  A constraint measured against work in flight expires when that work merges, as a shared-path line does, so stamp the block with what it was measured against.
102
119
 
103
120
  - Give the stamp its own leading bullet, written as Measured against `<commit>` on <YYYY-MM-DD>. One stamp covers the whole block.
104
- - Stamp the commit rather than the track names or a date alone. The constraint already names each track, and a date does not separate two plans written on either side of a merge.
105
- - Re-test a stamped constraint before honoring it with `git fetch origin main --quiet && git log <commit>..origin/main --oneline -- <the paths the constraint names>`. Any commit it reports means the track landed and the constraint is dead. Fetch and scope the log to the paths in the same command, since a stale ref reads a dead constraint as live and a squashed subject names no track.
121
+ - Stamp the commit rather than the track names or a date alone, since a date does not separate two plans written either side of a merge
106
122
  - Read an unstamped constraint as unverified rather than as live, and confirm it against the open pull request list for the named paths.
107
123
 
124
+ Re-test a stamped constraint before honoring it, fetching and scoping the log in one command, since a stale ref reads a dead constraint as live and a squashed subject names no track. Any commit it reports means the track landed and the constraint is dead.
125
+
126
+ ```bash
127
+ git fetch origin main --quiet && git log <commit>..origin/main --oneline -- <the paths the constraint names>
128
+ ```
129
+
130
+ ### Fan-in and ready constraints
131
+
108
132
  A batch in a fan-in set declares its side in a constraint bullet, ``Fan-in member of `feature-<integrator>` `` on a member and ``Fans in `feature-<a>`, `feature-<b>` `` on the integrator. `batches.md` holds when to fan in, and `canon records validate plans` reports `fan-in-unmatched` for a set whose two sides disagree.
109
133
 
110
134
  A constraint naming a `.canon/ready/` folder is a third shape beside the hold and the shared path. It makes that folder's files the verbatim source for the paths this plan's `**Files to touch:**` lists, per `ready.md`, so the executing session copies those paths rather than authoring them.
111
135
 
112
136
  ### Verification
113
137
 
138
+ Every outcome reaches the review with its proof named.
139
+
114
140
  - Name, for each outcome, the test or the command that proves it, as one bullet leading with the outcome and ending on the command in backticks.
115
141
  - Write a command a later session can run as given, from the project root. A description of what would be checked leaves the executing session to invent the check.
116
142
  - Name the visual check where no test reaches, and say it is visual, so a reader can tell a proof from a look.
117
143
 
118
144
  ### Risks
119
145
 
120
- - Name the collision rather than the category. A risk a reader cannot act on is padding.
121
- - Where the work establishes a resource with more than one consumer, list the consumers and mark each one read or write. A policy stated over that resource has to hold for the writers and not only for the consumer that prompted it.
122
- - Where the work changes a contract another track consumes, such as an exported signature, a verb's flags or record, or a config key, name the contract and list each consumer. The dispatcher reads this bullet for the contract hold, so a change left out reaches it as a clean shared file.
146
+ Name the collision rather than the category, since a risk a reader cannot act on is padding.
147
+
148
+ - For a resource with more than one consumer, list the consumers and mark each read or write, so a policy over it holds for every writer
149
+ - For a changed contract another track consumes, such as an exported signature, a verb's flags or record, or a config key, name it and list each consumer
150
+ - The dispatcher reads that contract bullet for the hold, so a change left out reaches it as a clean shared file
123
151
 
124
152
  ### Review focus
125
153
 
154
+ This section is where the planner's understanding of the risk reaches the review pass.
155
+
126
156
  - Name each input or state that would break the change if the implementation got it wrong, one bullet each, with what the change must do there.
127
- - Write it for the reviewer rather than the builder, since this section is where the planner's understanding of the risk reaches the review pass.
157
+ - Write it for the reviewer rather than the builder
128
158
  - Keep it apart from `**Risks:**`. A risk is something the work might collide with, and a focus item is something the finished diff must be shown to handle.
129
159
 
130
160
  ### Sources
131
161
 
162
+ A source earns an entry only once someone opened it.
163
+
132
164
  - Give each entry a link, the decision it backs, and the passage relied on in double quotes.
133
165
  - Mark a source that could not be fetched `unverified` and never quote it. A quote for a page nobody opened is the failure this section exists to prevent.
134
166
  - Cite no source that was found and not opened, following the lead rule in `groundwork.md`.
@@ -158,14 +190,19 @@ An execution that deviates rewrites the suggestion into the form below, leaving
158
190
 
159
191
  ### Preference questions and answers
160
192
 
193
+ The suggestion line is the planner's, and the answer line belongs to whoever owns the call.
194
+
161
195
  - Write `- Suggested: needs your call, <why>` where the answer turns on preference rather than on a technical default. Fabricating a default there hides an open question behind an accepted answer.
162
196
  - Never fill an `- Answer:` slot on behalf of the person who owns it. Recording the pick as the suggestion is what the suggestion line is for.
163
197
 
164
198
  ### Overrides and settled questions
165
199
 
166
- - Rewrite the `- Suggested:` line to the pick execution made when it deviates from an unanswered question, and leave the slot blank. The prohibition above reaches the answer line alone, so the suggestion line is where a deviation goes.
167
- - Open the rewritten text with `overridden at execution to <pick>,` and follow it with the measurement that moved the pick. The fixed phrase is what tells an override from an authored suggestion, which routinely carries a number too.
168
- - Take a deviation from an answered question back to whoever answered it rather than rewriting either line. A filled slot is a decision already made, and a suggestion rewritten under one leaves the plan holding two picks with no default resolving them.
200
+ A deviation from an unanswered question rewrites the `- Suggested:` line to the pick execution made and leaves the slot blank, since the prohibition above reaches the answer line alone.
201
+
202
+ - Open the rewritten text with `overridden at execution to <pick>,` and follow it with the measurement that moved the pick
203
+ - The fixed phrase tells an override from an authored suggestion, which routinely carries a number too
204
+ - Take a deviation from an answered question back to whoever answered it rather than rewriting either line
205
+ - A filled slot is a decision already made, and a suggestion rewritten under one leaves two picks with no default resolving them
169
206
  - Answer in place when a question is settled in conversation, so the file and the decision do not disagree.
170
207
 
171
208
  The plan is archived at ship, so the same deviation takes one line in the open task's `## Findings` naming what shipped. The plan holds why the pick moved and the task holds what the tree now has.
@@ -31,7 +31,8 @@ The file is optional, since measuring what is already there settles most questio
31
31
  Each spike carries four things:
32
32
 
33
33
  - The open question it answers, named by file and number. A spike attached to no question is a runaway.
34
- - The method, stated fully enough for a later reader to re-run it. Name the fixture and where it lived, since an arm pointed at a fixture inside the project measured the project, plus the exact command and the repetition count.
34
+ - The method, stated fully enough for a later reader to re-run it, with the exact command and the repetition count.
35
+ - The fixture and where it lived, since an arm pointed at a fixture inside the project measured the project
35
36
  - The result, and which question it closes. Record a spike that settles nothing too, so a later pass does not pay for it twice.
36
37
  - The measured cost, even for a single read, and the caveats bounding what the result proves. Cost is a report rather than a limit, and it makes the next spike estimable.
37
38
 
@@ -47,6 +48,8 @@ A file a reader opens to verify how a result was produced or what it showed live
47
48
 
48
49
  Open no other subfolder: `spikes/`, `web/`, and `research/` fold into these three, with a fetched page in `clones/` and a reference-site screenshot in `evidence/`.
49
50
 
51
+ ### Harnesses and read checks
52
+
50
53
  Reach for a test harness the project already carries before building one. An experiment no existing harness can express is a finding for the folder, not a new abstraction.
51
54
 
52
55
  Never count transcript matches to show a file was read. An instruction naming a path puts it in the transcript whether or not anything opened it, so the check is the tool call.
@@ -44,9 +44,11 @@ Does not govern:
44
44
 
45
45
  - Name the folder `<nn>-<slug>`, a two-digit ordinal and a kebab-case slug.
46
46
  - The ordinal marks a folder per track, opened once and worked over time, unlike a file addressed by name, one with its own sorting prefix, or one overwritten on purpose.
47
- - Claim the ordinal through `canon records ordinal groundwork <slug> --claim`, which reads the highest one across both `.canon/groundwork/` and `.canon/intake/` and creates the folder in one atomic act, so two sessions opening at once never share a number. The two kinds share one order.
47
+ - Claim the ordinal through `canon records ordinal groundwork <slug> --claim`, which reads the highest across `.canon/groundwork/` and `.canon/intake/`, since the two kinds share one order
48
+ - The claim creates the folder in the same atomic act, so two sessions opening at once never share a number
48
49
  - With neither folder holding an entry, the first one opened takes `01`, whatever number a track's own files start at.
49
- - Never renumber an existing folder. The ordinal is the order it opened, and a later reader cites it by that name.
50
+
51
+ Never renumber an existing folder. The ordinal is the order it opened, and a later reader cites it by that name.
50
52
 
51
53
  ## What a working track looks like
52
54
 
@@ -86,20 +88,15 @@ A folder missing `06` and `07` is live, and that is its only status marker.
86
88
 
87
89
  ## README.md
88
90
 
89
- Orients. Holds no findings.
91
+ Orients and holds no findings. It states the phase out loud in its first three lines, in the form `Groundwork phase. Nothing here is a feature plan.`
90
92
 
91
93
  - A one-line definition of the investigation
92
94
  - A `## Why` section stating why the track is running now
93
95
  - A file-map table of filename and what it holds, kept current
94
96
  - A `## Method` section splitting internal sources from external ones, naming which were used and which were not yet done, and listing under a leads heading any external source found but not opened
97
+ - Closing `## Prior art` and `## Source citation` sections, the second stating the rule below so a returning session finds it in the folder
95
98
 
96
- ### Closing sections and phase
97
-
98
- - A `## Prior art` section
99
- - A `## Source citation` section stating the rule below, so a returning session finds it in the folder
100
- - The phase stated out loud in the first three lines, in the form `Groundwork phase. Nothing here is a feature plan.`
101
-
102
- Every claim about a source outside the project carries a link to it, wherever the claim appears in the folder. Without one, a later reader cannot tell a fetched page from recall. A source found and not read is listed as a lead and is never cited, since a link to a page nobody opened is worse than no link. Listing it stops a later pass re-searching for it.
99
+ Every claim about a source outside the project carries a link to it, wherever the claim appears in the folder, so a later reader can tell a fetched page from recall. A source found and not read is listed as a lead and never cited, since a link to a page nobody opened is worse than none. Listing it stops a later pass re-searching for it.
103
100
 
104
101
  Where the track supersedes an earlier plan or an earlier folder, name it and say not to go looking for it. Without that, the old reasoning keeps circulating.
105
102
 
@@ -117,11 +114,10 @@ Written when the subject is large enough to run away. Holds constraints, risks,
117
114
 
118
115
  ## 06-decision.md
119
116
 
120
- Closes the folder. Everything above it is input. Write it from the skeleton under `## Template`, with an H3 per dropped alternative.
117
+ Closes the folder. Everything above it is input. Write it from the skeleton under `## Template`, with an H3 per dropped alternative, a list that stops a future session re-proposing a rejected idea.
121
118
 
122
- `## Revisit when` carries the "Overturned by" line of each lean that became the decision, as written. A lean the decision did not take carries nothing, and a track whose deciding leans carried no overturn line omits the section.
123
-
124
- The dropped list stops a future session re-proposing a rejected idea.
119
+ - `## Revisit when` carries the "Overturned by" line of each lean that became the decision, as written
120
+ - A lean the decision did not take carries nothing, and a track whose deciding leans carried no overturn line omits the section
125
121
 
126
122
  ## 07-next-session.md
127
123
 
@@ -158,7 +154,9 @@ A lean is weaker than a plan's suggestion: it records the current read on an ope
158
154
 
159
155
  ## Template
160
156
 
161
- ### README.md
157
+ The orienting README comes first, then the closing decision, with an H3 per dropped alternative.
158
+
159
+ **`README.md`**
162
160
 
163
161
  ```markdown
164
162
  ---
@@ -203,7 +201,7 @@ Every claim about a source outside the project carries a link. A source found
203
201
  and not read is listed as a lead and is never cited.
204
202
  ```
205
203
 
206
- ### 06-decision.md
204
+ **`06-decision.md`**
207
205
 
208
206
  ```markdown
209
207
  ---
@@ -7,7 +7,7 @@ description: Folder layout, ordinal naming, reserved index number, frontmatter a
7
7
 
8
8
  Applies to an intake folder at `.canon/intake/<nn>-<slug>/`. One folder holds one dump, filed by domain, and every finding in it is an item carrying a measured problem, one proposed fix, and a verdict.
9
9
 
10
- The folder is gitignored, and backed wherever a records remote is configured: `canon records push` and `canon records pull` protect it against the machine being lost there, refuse with `no-remote` where it is not, and neither protects against a compaction dropping a session's reasoning before anyone has pushed. No check reaches its contents, so the shape below survives only by being read.
10
+ The folder is gitignored. Where a records remote is configured, `canon records push` and `canon records pull` protect it against losing the machine, and elsewhere they refuse with `no-remote`. Neither saves reasoning a compaction drops before a push. No check reaches its contents, so the shape below survives only by being read.
11
11
 
12
12
  <!-- canon-length-exempt: the folder shape and the item template share one reader, and a split into a second reference is queued as its own change -->
13
13
 
@@ -41,11 +41,13 @@ Does not govern:
41
41
 
42
42
  ## Folder name
43
43
 
44
- - Name the folder `<nn>-<slug>`, a two-digit zero-padded ordinal followed by a kebab-case slug. This is the folder's own ordinal, distinct from the `NN-<domain>.md` numbering a cluster file carries inside it.
44
+ - Name the folder `<nn>-<slug>`, a two-digit zero-padded ordinal and a kebab-case slug, distinct from the `NN-<domain>.md` numbering of its cluster files
45
45
  - The ordinal marks a folder per track, opened once and worked over time, distinct from a file addressed by name, one already carrying its own sorting prefix, or one deliberately overwritten.
46
- - Claim the ordinal through `canon records ordinal intake <slug> --claim`, which reads the highest one already present across both `.canon/intake/` and `.canon/groundwork/` and creates the folder in the same atomic act, closing the race a read-then-create sequence leaves open between two sessions opening at once. A listing then sorts by when each folder opened rather than alphabetically, and the count includes both kinds because the two share one creation-order line.
47
- - With neither folder holding an entry, the first one opened takes `01`. Do not read this off the numbering inside a dump, which starts at `00` and disagrees with groundwork's own first required file.
48
- - Never renumber an existing folder. The ordinal is the order it opened, and a later reader cites it by that name.
46
+ - Claim the ordinal through `canon records ordinal intake <slug> --claim`, which reads the highest across `.canon/intake/` and `.canon/groundwork/`, since the two share one creation order
47
+ - The claim creates the folder in the same atomic act, closing the race a read-then-create leaves open between two sessions opening at once
48
+ - With neither folder holding an entry, the first one opened takes `01`, never read off a dump's inner numbering, which starts at `00`
49
+
50
+ Never renumber an existing folder. The ordinal is the order it opened, and a later reader cites it by that name.
49
51
 
50
52
  ## What a working intake looks like
51
53
 
@@ -68,9 +70,8 @@ An intake failing these is non-conforming even when it satisfies every shape rul
68
70
 
69
71
  `00` is the only reserved number. Everything else is read order, and the domain rides in the filename so a reader knows what `07-tooling.md` holds without opening it.
70
72
 
71
- Do not reserve mid-range numbers. Clusters differ per dump, so a contract over `06` would force every future intake into one dump's shape. A folder whose shape is fixed can reserve its numbers, and that half of the convention does not transfer.
72
-
73
- Let the file count follow the number of separable domains. A large dump with two domains is a small folder.
73
+ - Do not reserve mid-range numbers, since clusters differ per dump and a contract over `06` would force every intake into one dump's shape
74
+ - Let the file count follow the number of separable domains. A large dump with two domains is a small folder.
74
75
 
75
76
  ## Frontmatter and dating
76
77
 
@@ -113,35 +114,39 @@ Where an item touches a task already on the board, say so in the index rather th
113
114
  ```
114
115
 
115
116
  - `Problem:`, `Fix:`, `Worth it:`, and the empty `You:` slot ship on every item. The other two are conditional.
116
- - Number items per cluster file, and give a finding split after the fact a letter suffix on the number it came from, as in `3a` beside `3`. Renumbering the items below it instead moves every label a reader or an answer already cited.
117
- - `Suggested:` is required whenever `Open:` is present. A bare question invites a bare answer, and `ok` against two defensible options carries no information. Where the answer is the operator's preference rather than a technical call, say so in that form rather than inventing a default.
118
- - `Overlaps:` never replaces `Worth it:`. The items where a live board task might be the thing that is wrong are exactly the ones whose verdict matters most.
119
-
120
- Two heading levels is the right depth inside a cluster file. A third means the cluster should have been its own file.
117
+ - Number items per cluster file, giving a finding split later a letter suffix such as `3a` beside `3`, since renumbering moves every cited label
118
+ - `Suggested:` is required whenever `Open:` is present, since `ok` against two defensible options carries no information
119
+ - Where the answer is the operator's preference rather than a technical call, say so in that form rather than inventing a default
120
+ - `Overlaps:` never replaces `Worth it:`, since an item a live board task might have wrong is the one whose verdict matters most
121
121
 
122
- An item may carry a bolded standalone line between the bullets where a finding needs a name of its own. Keep it rare. Everything fitting the four bullets belongs in them.
122
+ Two heading levels is the right depth inside a cluster file. A third means the cluster should have been its own file. An item may carry a rare bolded standalone line between the bullets where a finding needs a name of its own, and everything fitting the four bullets belongs in them.
123
123
 
124
124
  ## The answer contract
125
125
 
126
- `You:` belongs to the operator and ships empty on every item.
126
+ `You:` belongs to the operator and ships empty on every item. Empty means unread and never agreement, so accepting a verdict is typed as one token, `- **You:** ok`.
127
127
 
128
- Empty means unread. It never means agreement. Accepting a verdict is typed as one token, `- **You:** ok`.
128
+ - Never fill a `You:` slot, and never infer a disposition from an empty one
129
+ - On a resume pass, report unread items by count rather than deciding them
129
130
 
130
- That inverts the plan file's contract, where a blank answer slot means accept the suggestion, and the inversion is deliberate. A plan is read in one sitting with every question already surfaced in conversation. An intake folder is read over weeks, so an empty slot is ambiguous between accepting the verdict and never having reached the item, and the second reading is far more likely. Acting on silence as consent ships a change nobody approved.
131
+ ### Why silence is not consent
131
132
 
132
- Never fill a `You:` slot, and never infer a disposition from an empty one. On a resume pass, report unread items by count rather than deciding them.
133
+ This inverts the plan file's contract, where a blank slot means accept the suggestion. A plan is read in one sitting with every question already surfaced. An intake folder is read over weeks, so an empty slot more likely means nobody reached the item, and acting on it ships a change nobody approved.
133
134
 
134
- A slot is filled two ways. The operator types into the cluster file, or answers in chat and a verb lands the selection on the item. Both put the answer on the item, which is what keeps retrieval working, and neither lets a session decide one. An answer given in conversation and never written back leaves the item unread, since the file rather than the conversation is the record.
135
+ ### Filling a slot
135
136
 
136
- An item already carrying an answer is refused rather than overwritten, whichever route the second answer arrives by. A filled slot is a decision already made, and revising one is the operator editing their own line.
137
+ The operator types into the cluster file, or answers in chat and a verb lands the selection on the item. Both put the answer on the item, which keeps retrieval working, and neither lets a session decide one.
137
138
 
138
- ## Retrieval
139
+ - An answer given in conversation and never written back leaves the item unread, since the file is the record
140
+ - An item already carrying an answer is refused rather than overwritten, whichever route the second answer takes
141
+ - Revising a filled slot is the operator editing their own line
139
142
 
140
- Answers live on items, so one pass over the folder reports every touched slot.
143
+ ## Retrieval
141
144
 
142
- A session with the toolkit CLI on PATH reads the folder through `canon intake list`, which reports per-folder counts bare and one folder's items with `--json`, and takes `--unread` to keep only the empty slots. It is the surface under test, and it skips the index and every fenced sample, which the greps below cannot do.
145
+ Answers live on items, so one pass over the folder reports every touched slot. With the toolkit CLI on PATH, read the folder through `canon intake list`, the surface under test.
143
146
 
144
- The greps stay for a reader without the CLI, and they overcount by whatever the folder displays in a fence.
147
+ - Bare, it reports per-folder counts, `--json` gives one folder's items, and `--unread` keeps only the empty slots
148
+ - It skips the index and every fenced sample, which the greps below cannot do
149
+ - The greps stay for a reader without the CLI, and they overcount by whatever the folder displays in a fence
145
150
 
146
151
  ```bash
147
152
  awk '/^### /{h=FILENAME": "$0} /^- \*\*You:\*\*./{print h; print " "$0}' *.md
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: pr-evidence
3
+ description: What posting a pull request's rendered-surface evidence is for, the gaps it closes, and what it refuses to do
4
+ ---
5
+
6
+ # PR evidence requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, the evidence a pull request carries is one step inside `git-pr`. A session refreshing the screenshots on a new head either reruns a title and body it did not mean to touch, or borrows the step's reference by path. Two skills borrowed it that way, and each copy drifted.
11
+
12
+ A session judging from the diff whether an evidence image changed cannot see a regenerated image it did not author, so the image ships with no comment. A reviewer holding screenshots and a checklist has nothing to click into, and a slow deploy holds the ship chain while it waits for an address.
13
+
14
+ ## Must
15
+
16
+ - Run the evidence verb on every pull request it is given and let `no-evidence` be the skip, since asking a session to look lets an unseen image ship with no comment
17
+ - Run alone against an open pull request, resolving the number from the branch when none is passed, so a later head gets evidence without a body rewrite
18
+ - Edit the one marked comment in place on a later run, so a re-post never duplicates the thread or drops a carried checklist
19
+ - Put a preview address on a pull request changing a rendered surface when the project's deploy can mint one, since a reviewer needs a page to click into
20
+ - Bound the wait on that preview and finish without the link on a timeout, since a slow deploy must not hold the ship
21
+ - Link the branch's running local server when this worktree serves one, since a reviewer on the operator's machine can drive the branch
22
+ - Drop that link when the pull request closes, since a link outliving its branch points at whatever runs there next
23
+
24
+ ## Must not
25
+
26
+ - Dispatch a deploy whose command names no branch, since Cloudflare Pages publishes such a deploy to production
27
+ - Create a pull request, or post against a number it guessed, since a write to a wrong number lands on another branch's thread
28
+ - Call a GraphQL-backed `gh` subcommand, since the cloud review-address leg runs this skill through `git-followup`
29
+ - Print a result line of its own, since `git-pr` prints one and its report ends with the lines this skill returns
30
+ - Exist only for the author to type its name. `git-pr`, `git-followup`, and `git-ship` each invoke it, and a review of the catalog drops it when none does.
31
+
32
+ ## Guards
33
+
34
+ - Stop and ask for the number when none was passed and no open pull request resolves for the branch
35
+
36
+ ## Out of scope
37
+
38
+ - The title, body, labels, and pre-publish scan, which `git-pr` owns
39
+ - Writing the checklist, which `ui-checklist` owns. This skill only consumes the handoff file and removes it after posting.
40
+ - Driving the checklist in the running app, which `review-ui` owns
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: pr-evidence
3
+ description: Posts the rendered-surface evidence on an open pull request, being the screenshot comparison with the UI checklist, the checklist alone when git refuses the comparison, the local server link, and the preview address, editing the one marked comment in place on every later head. Use when asked to "post the evidence", "refresh the screenshots on the PR", or "re-post the evidence on the new head", and as the closing step of `git-pr`. Do NOT use to write a pull request's title or body, which is `git-pr`, or to write the checklist, which is `ui-checklist`.
4
+ metadata:
5
+ family: after-pr
6
+ ---
7
+
8
+ # PR evidence
9
+
10
+ The evidence a pull request carries is owed again on every new head, while its title and body are not. This skill posts it against one pull request number, so `git-pr` runs it as its last step and any session can run it alone after a push.
11
+
12
+ ## Resolve the pull request
13
+
14
+ Take the number the caller passed and look nothing up. `git-pr` passes the number its final command printed, and never invokes this skill when that command refused on a head mismatch and printed none.
15
+
16
+ Run bare, resolve the open pull request for the current branch:
17
+
18
+ ```bash
19
+ canon pr head --json
20
+ ```
21
+
22
+ Read `reason` on the record rather than the exit code, since a shell function wrapping `canon` can flatten it to zero.
23
+
24
+ - A record carrying `number`: hold it for every step below
25
+ - Any refusal, including `gh-failed` for a branch with no open pull request, stops the run with `❌ No open pull request resolved for this branch: <message>. Pass the number and re-run.`
26
+ - Never create a pull request here and never guess a number, since a write to a wrong number lands on another branch's thread
27
+
28
+ ## Post the evidence
29
+
30
+ Read `${CLAUDE_SKILL_DIR}/references/evidence.md` and run its sections in order against the number, being the checklist, the local server, the evidence comparison, the checklist alone, and the preview address.
31
+
32
+ - Every post and edit goes through `gh api` over REST, since a cloud session's proxy refuses the GraphQL that `gh pr` runs on
33
+ - Never judge from the diff whether an evidence image changed, since the verb answers `no-evidence` for a branch that changed none and a session cannot see an image it did not author
34
+ - A lone run on a later head edits the marked comment through the record's `commentId` and passes no `--checklist` for a handoff already deleted, since the verb carries the checklist forward
35
+
36
+ ## Report
37
+
38
+ Collect only the lines below whose condition holds, and nothing when none does. A lone run prints them. A run another skill invoked hands them back to that caller and prints nothing, since `git-pr` prints its own result line and then these lines, and printing here too shows them twice.
39
+
40
+ | Condition | Line |
41
+ | --------------------------------------------------------- | ------------------------------------------------------------------ |
42
+ | A checklist posted in the evidence comment | `📋 Posted the UI checklist to <number>, in the evidence comment.` |
43
+ | A checklist posted alone | `📋 Posted the UI checklist to <number>, on its own.` |
44
+ | The preview step returned an address | `🔗 Preview: <url>` |
45
+ | The evidence step posted the local link | `🖥️ Local preview: <url>` |
46
+ | The local step refused and evidence or a checklist posted | `🖥️ No local preview: <reason>` |
47
+ | A git refusal or a preview refusal the reference reports | `⚠️ <reason>: <message>` |
48
+
49
+ ### The no-local-preview line
50
+
51
+ It fires when the local step refused as `no-server` or `no-listener-reader`, since that pull request changes a rendered surface and a reviewer could have used the link. A later run only carries a link forward, so it cannot add one the comment never had.
52
+
53
+ To add the link later, start the server and run this skill again, adding `--checklist` to the evidence call when one is still owed.
@@ -0,0 +1,150 @@
1
+ ---
2
+ title: Rendered-surface evidence
3
+ description: How pr-evidence finds the UI checklist and the local server, posts the evidence comparison or the checklist alone as the fallback for a git refusal, and adds the preview address, for a change that touches a rendered surface
4
+ ---
5
+
6
+ # Rendered-surface evidence
7
+
8
+ The procedure of `pr-evidence`, run in order against one pull request number. It runs on every pull request with no test on the diff ahead of it, since `no-evidence` is the silent branch for one that changes no rendered surface.
9
+
10
+ ## Contents
11
+
12
+ - [Find the UI checklist](#find-the-ui-checklist)
13
+ - [Find the local server](#find-the-local-server)
14
+ - [Post the evidence comparison](#post-the-evidence-comparison)
15
+ - [Post the UI checklist alone](#post-the-ui-checklist-alone)
16
+ - [Post the preview address](#post-the-preview-address)
17
+
18
+ ## Find the UI checklist
19
+
20
+ `ui-checklist` writes a visual checklist to `.canon/tmp/handoff/ui-checklist/<slug>.md` at the main worktree root when a change needs visual verification, with `<slug>` derived per `${CLAUDE_SKILL_DIR}/../git-branch/references/slug.md`. This skill is the file's sole consumer.
21
+
22
+ - Resolve the main root the way `session-worktree` does, `git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-` falling back to `pwd`
23
+ - A missing file means no checklist was produced, and the two steps below each skip their checklist half
24
+ - Scan an existing file against `${CLAUDE_SKILL_DIR}/../git-pr/references/publish.md` before either step posts it, the same as the body
25
+ - The evidence step decides where it lands, since a checklist reads next to its screenshots and posting it alone is the fallback for a refused render
26
+
27
+ ## Find the local server
28
+
29
+ A reviewer on the operator's machine can open the branch's running dev server, which screenshots and a checklist cannot stand in for. Ask the verb before the evidence step, so the first evidence comment already carries the link:
30
+
31
+ ```bash
32
+ canon pr local --json
33
+ ```
34
+
35
+ The verb reports the lowest port serving an HTML page whose process runs inside this worktree, never one in another worktree or the main checkout, which shows a different branch. `canon pr local --help` states the contract. Read `reason` on the record rather than the exit code.
36
+
37
+ - `ok`: hold the record's `url` for the evidence step below.
38
+ - `no-server` or `no-listener-reader`: hold the reason for the closing report and pass no link. Never start a server here, since one started inside the ship chain has no owner to stop it.
39
+ - Anything else, including no record or an older binary's unknown-subcommand error: move on silently, as a skip rather than a stop.
40
+
41
+ ### Where the link lands
42
+
43
+ Pass `--local` to the evidence step only on an `ok`. The binary answering `ok` is the one carrying the flag, so a binary lacking the verb never meets a flag it would reject.
44
+
45
+ - With a checklist, a diff carrying no evidence image still renders `ok`, and the link rides on the address line under `## Evidence`, above the checklist
46
+ - A link with neither an evidence image nor a checklist still reports `no-evidence`, so a branch changing no rendered surface gets no comment holding a link alone
47
+ - A seeded workflow drops the whole address line once the pull request closes, hosted and local together, so neither link outlives its branch
48
+
49
+ ## Post the evidence comparison
50
+
51
+ Run the verb once against the pull request number. Pass `--checklist` when the checklist step found a file and `--local` when the step above returned `ok`, and leave each flag off otherwise:
52
+
53
+ ```bash
54
+ canon pr evidence <number> --checklist <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md --local <url> --json
55
+ ```
56
+
57
+ One call answers both questions because a checklist decides `no-evidence`. A diff carrying a checklist and no evidence image renders `ok` with the checklist in a marked body, so the checklist lands in the one comment a later call finds and edits.
58
+
59
+ - Pass `--checklist` only for a file that exists
60
+ - The verb refuses as `unreadable-checklist` on a path it cannot read or one holding nothing, a caller bug rather than a transient failure
61
+ - Stop and repair the path on that refusal rather than posting a body with the checklist silently dropped
62
+
63
+ ### Reading the record
64
+
65
+ Read `reason` on the record rather than the exit code.
66
+
67
+ - `no-evidence`: nothing changed under an `evidence/` segment and no checklist was passed. Say nothing about the evidence and move on.
68
+ - `ok`: write `body` to `.canon/tmp/pr/evidence/body-<number>.md` at the main worktree root, then post or update the comment:
69
+
70
+ ```bash
71
+ gh api -X POST repos/{owner}/{repo}/issues/<number>/comments -F body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md --silent
72
+ ```
73
+
74
+ Both posts in this file use the REST issue-comments endpoint, since the `gh pr` subcommands run on GraphQL and a cloud session's GitHub proxy refuses it. `git-followup` runs this skill, so a GraphQL post here would break the cloud review-address leg.
75
+
76
+ When the record carries a `commentId`, edit that comment in place instead of posting a second. The body reads from the tmp file with `@`, which needs `-F`, since `-f` posts the path itself as the body:
77
+
78
+ ```bash
79
+ gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -F body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md
80
+ ```
81
+
82
+ Delete the handoff file and the tmp body once that call reports success, per the cleanup below, since the checklist now lives on the pull request.
83
+
84
+ ### Git refusals
85
+
86
+ Any other `reason` is a mirrored git refusal, being `gh-missing`, `gh-failed`, `no-base`, `unreadable-changes`, or `would-empty`. Report it and move on without stopping the chain, since a branch that rarely carries evidence images should not fail on a transient read.
87
+
88
+ Fall through to the checklist step, which posts the checklist alone rather than losing it.
89
+
90
+ ## Post the UI checklist alone
91
+
92
+ Run this step only as the fallback, when the evidence step reported a git refusal and a checklist file exists, since every other branch with a checklist rendered `ok` and carried it. The comment carries no marker, so a later `canon pr evidence` call reports no `checklist` for it.
93
+
94
+ ```bash
95
+ gh api -X POST repos/{owner}/{repo}/issues/<number>/comments -F body=@<main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md --silent
96
+ ```
97
+
98
+ ### Cleanup
99
+
100
+ Run the cleanup only once the call that carried the checklist reports success, whichever step that was. On a failure, stop and leave the file, since a retry needs it and deleting it loses the only copy.
101
+
102
+ The cleanup is a main-root delete, so it goes out as a plain `rm` and then a plain `rmdir`, routed the way `session-worktree` states:
103
+
104
+ ```bash
105
+ rm <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
106
+ ```
107
+
108
+ ```bash
109
+ rmdir <main-root>/.canon/tmp/handoff/ui-checklist 2>/dev/null || true
110
+ ```
111
+
112
+ - The `rmdir` is a no-op while another branch's pending checklist sits in the folder, so this step never deletes a handoff not its own
113
+ - `git-followup` re-runs `canon pr evidence` with no `--checklist`, and the verb carries the checklist forward out of the comment it edits
114
+ - A tick survives a render at the head its stamp names and is cleared by one at a newer head, so the next UI pass earns it again
115
+
116
+ ## Post the preview address
117
+
118
+ Run this step when the evidence step returned `ok`, the fallback posted a checklist, or `canon pr evidence <number> --check --json` lists `preview` in `owed`. Otherwise skip it silently.
119
+
120
+ - The first two mean the pull request changes a rendered surface, and a reviewer holding a checklist with no screenshots needs the live page most
121
+ - The third is how `git-ship` reaches this step alone when an earlier run's deploy timed out
122
+ - An evidence record carrying a `commentId` takes the third alone, since a preview already on the comment counts as settled whatever head built it, so a later push does not re-mint one
123
+ - The evidence comment is already posted, so the reviewer has the screenshots while the deploy runs
124
+
125
+ ```bash
126
+ canon pr preview <number> --json
127
+ ```
128
+
129
+ ### What the verb refuses
130
+
131
+ The verb dispatches the project's Pages deploy on the pull request's branch and waits on the run for up to 15 minutes. `canon pr preview --help` states the contract. It refuses before dispatching anything in two cases.
132
+
133
+ - The deploy command passes no `--branch`, since that deploy would publish the branch to production
134
+ - The workflow's push path filter matches no changed path, since a dispatch ignores that filter and the site would show none of the change
135
+
136
+ ### Reading the preview record
137
+
138
+ Read `reason` on the record rather than the exit code.
139
+
140
+ - `ok`: re-render the evidence body with the address under `## Evidence`, then post or update it as the evidence step does, through the same tmp file and the record's `commentId`:
141
+
142
+ ```bash
143
+ canon pr evidence <number> --preview <url> --json
144
+ ```
145
+
146
+ The hosted address opens the address line, and a local link already on the comment follows it, carried forward with no flag.
147
+
148
+ - `no-deploy` or `unserved`: the project deploys nothing a dispatch can start, or its site builds from nothing this branch changed. Say nothing and move on.
149
+ - `unfenced`, `no-alias`, `run-failed`, `timeout`, or a mirrored `gh` refusal: report the record's `message` and move on without stopping the chain
150
+ - A missing link costs the reviewer a click, and a held ship costs the whole chain