@erclx/canon 5.38.0 → 5.39.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 (149) hide show
  1. package/README.md +1 -1
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/api-design/SKILL.md +12 -9
  4. package/claude/skills/auto-ship/SKILL.md +43 -23
  5. package/claude/skills/backlog-triage/SKILL.md +62 -53
  6. package/claude/skills/backlog-triage/references/apply.md +69 -0
  7. package/claude/skills/build-in-slices/SKILL.md +8 -4
  8. package/claude/skills/canon-cli/SKILL.md +5 -1
  9. package/claude/skills/canon-feedback/SKILL.md +18 -8
  10. package/claude/skills/canon-feedback-triage/SKILL.md +10 -3
  11. package/claude/skills/canvas-board/SKILL.md +36 -17
  12. package/claude/skills/ci-workflow/SKILL.md +5 -4
  13. package/claude/skills/code-craft/SKILL.md +10 -6
  14. package/claude/skills/codebase-layout/SKILL.md +3 -5
  15. package/claude/skills/context-fold/REQUIREMENT.md +4 -4
  16. package/claude/skills/context-fold/SKILL.md +42 -23
  17. package/claude/skills/context-fold/references/context-refresh.md +4 -3
  18. package/claude/skills/context-fold/references/context.md +79 -64
  19. package/claude/skills/create-rule/SKILL.md +7 -3
  20. package/claude/skills/create-skill/SKILL.md +27 -7
  21. package/claude/skills/create-skill/references/skill-reference.md +41 -17
  22. package/claude/skills/decision-escalate/SKILL.md +8 -5
  23. package/claude/skills/deploy-app/SKILL.md +1 -1
  24. package/claude/skills/deprecation-migration/SKILL.md +12 -4
  25. package/claude/skills/design-taste/SKILL.md +17 -11
  26. package/claude/skills/docs-sync/REQUIREMENT.md +1 -0
  27. package/claude/skills/docs-sync/SKILL.md +17 -6
  28. package/claude/skills/docs-sync/references/docs.md +54 -21
  29. package/claude/skills/docs-sync/references/readme.md +32 -8
  30. package/claude/skills/document-health/SKILL.md +3 -4
  31. package/claude/skills/draft-and-pick/SKILL.md +73 -47
  32. package/claude/skills/draft-doc/SKILL.md +9 -4
  33. package/claude/skills/draft-doc/references/context.md +4 -4
  34. package/claude/skills/draft-doc/references/docs.md +1 -0
  35. package/claude/skills/draft-doc/references/wiki.md +3 -2
  36. package/claude/skills/draft-figure/SKILL.md +9 -7
  37. package/claude/skills/draft-identity/SKILL.md +24 -52
  38. package/claude/skills/draft-identity/references/card-route.md +46 -0
  39. package/claude/skills/draft-ready/SKILL.md +19 -7
  40. package/claude/skills/draft-screencast/SKILL.md +6 -112
  41. package/claude/skills/draft-screencast/references/beats-template.md +119 -0
  42. package/claude/skills/draft-slides/SKILL.md +7 -4
  43. package/claude/skills/git-branch/SKILL.md +3 -3
  44. package/claude/skills/git-commit/SKILL.md +5 -3
  45. package/claude/skills/git-followup/SKILL.md +16 -9
  46. package/claude/skills/git-issue/SKILL.md +6 -5
  47. package/claude/skills/git-pr/SKILL.md +56 -44
  48. package/claude/skills/git-ship/SKILL.md +43 -34
  49. package/claude/skills/git-ship/references/ci-watch.md +31 -0
  50. package/claude/skills/git-split/SKILL.md +28 -100
  51. package/claude/skills/git-split/references/independent.md +40 -0
  52. package/claude/skills/git-split/references/stacked.md +73 -0
  53. package/claude/skills/git-stage/SKILL.md +9 -7
  54. package/claude/skills/git-worktree/SKILL.md +27 -49
  55. package/claude/skills/git-worktree/references/cleanup.md +55 -0
  56. package/claude/skills/markdown-craft/SKILL.md +2 -2
  57. package/claude/skills/markdown-craft/references/markdown.md +82 -53
  58. package/claude/skills/plan-feature/SKILL.md +52 -46
  59. package/claude/skills/plan-feature/references/batches.md +46 -0
  60. package/claude/skills/plan-groundwork/SKILL.md +60 -74
  61. package/claude/skills/plan-groundwork/references/output.md +48 -0
  62. package/claude/skills/plan-intake/SKILL.md +28 -21
  63. package/claude/skills/plan-intake-answer/SKILL.md +14 -13
  64. package/claude/skills/read-frames/SKILL.md +2 -0
  65. package/claude/skills/record-screencast/SKILL.md +31 -11
  66. package/claude/skills/review-address/SKILL.md +76 -158
  67. package/claude/skills/review-address/references/rebase-only.md +34 -0
  68. package/claude/skills/review-craft/SKILL.md +27 -11
  69. package/claude/skills/review-pr/SKILL.md +91 -40
  70. package/claude/skills/review-ui/SKILL.md +62 -37
  71. package/claude/skills/review-ui/references/hosted-preview.md +30 -0
  72. package/claude/skills/role-orchestrator/SKILL.md +105 -74
  73. package/claude/skills/role-orchestrator/references/orchestrator-filing.md +26 -0
  74. package/claude/skills/role-planner/SKILL.md +66 -64
  75. package/claude/skills/role-reviewer/SKILL.md +57 -46
  76. package/claude/skills/role-worker/SKILL.md +68 -63
  77. package/claude/skills/search-craft/SKILL.md +7 -5
  78. package/claude/skills/session-compact/SKILL.md +16 -11
  79. package/claude/skills/session-map/SKILL.md +31 -10
  80. package/claude/skills/session-relay/SKILL.md +32 -65
  81. package/claude/skills/session-resume/SKILL.md +18 -13
  82. package/claude/skills/session-worktree/SKILL.md +70 -42
  83. package/claude/skills/systematic-debugging/SKILL.md +6 -1
  84. package/claude/skills/target-check/SKILL.md +13 -6
  85. package/claude/skills/target-setup/SKILL.md +39 -27
  86. package/claude/skills/task-board/SKILL.md +68 -60
  87. package/claude/skills/task-board/references/create-report.md +36 -0
  88. package/claude/skills/task-complete/SKILL.md +20 -18
  89. package/claude/skills/teach-workspace/SKILL.md +61 -33
  90. package/claude/skills/test-craft/SKILL.md +2 -1
  91. package/claude/skills/test-first/SKILL.md +15 -9
  92. package/claude/skills/ui-checklist/SKILL.md +18 -16
  93. package/claude/skills/ux-audit/SKILL.md +9 -10
  94. package/claude/skills/ux-measure/SKILL.md +22 -18
  95. package/claude/skills/video-craft/SKILL.md +15 -7
  96. package/claude/skills/write-human/SKILL.md +9 -1
  97. package/claude/skills/youtube-transcripts/SKILL.md +2 -1
  98. package/docs/agents/context-audit-checks.md +19 -1
  99. package/docs/agents/context-audit.md +2 -2
  100. package/docs/agents/driver.md +1 -1
  101. package/docs/agents/hooks.md +21 -1
  102. package/docs/agents/index.md +10 -14
  103. package/docs/agents/indexes.md +25 -29
  104. package/docs/agents/install-and-sync.md +124 -235
  105. package/docs/agents/markdown-audit-density.md +39 -0
  106. package/docs/agents/markdown-audit-weight.md +9 -7
  107. package/docs/agents/markdown-audit.md +18 -14
  108. package/docs/agents/migrate.md +41 -16
  109. package/docs/agents/output-shape.md +60 -45
  110. package/docs/agents/overview.md +36 -12
  111. package/docs/agents/routing.md +1 -1
  112. package/docs/agents/rule-citations.md +1 -1
  113. package/docs/agents/skills-reach.md +1 -1
  114. package/docs/agents/targets.md +25 -77
  115. package/docs/target/migrations.md +1 -1
  116. package/docs/target/projects.md +2 -2
  117. package/docs/workflow/ai-workflow.md +2 -2
  118. package/docs/workflow/parallel-features.md +2 -2
  119. package/docs/workflow/skill-map.md +1 -1
  120. package/docs/workflow/visual-design-workflow.md +1 -1
  121. package/package.json +1 -1
  122. package/src/commands/claude.ts +1 -1
  123. package/src/commands/context.ts +106 -7
  124. package/src/commands/hooks.ts +63 -0
  125. package/src/commands/markdown.ts +169 -32
  126. package/src/context/audit.ts +256 -5
  127. package/src/context/classify/prompts.ts +1 -1
  128. package/src/context/gate.ts +23 -7
  129. package/src/context/shape-gated.ts +28 -0
  130. package/src/design/document.ts +1 -1
  131. package/src/design/tokens.ts +1 -1
  132. package/src/gate/measures.ts +15 -190
  133. package/src/gate/stages.ts +7 -23
  134. package/src/hooks/reply-links.ts +110 -0
  135. package/src/markdown/density.ts +258 -0
  136. package/src/markdown/gate.ts +57 -18
  137. package/src/markdown/reference-shape.ts +103 -0
  138. package/src/markdown/scope.ts +174 -0
  139. package/src/markdown/structure.ts +9 -59
  140. package/src/shipped/references.ts +1 -1
  141. package/tooling/claude/seeds/.claude/hooks/reply-links.sh +72 -0
  142. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +22 -1
  143. package/tooling/claude/seeds/.claude/settings.json +4 -0
  144. package/docs/agents/commands.md +0 -154
  145. package/docs/agents/docs.md +0 -27
  146. package/docs/agents/drift-surfaces.md +0 -201
  147. package/docs/agents/gov-detect.md +0 -47
  148. package/docs/agents/scripting.md +0 -142
  149. package/src/markdown/growth.ts +0 -43
@@ -41,7 +41,11 @@ canon tasks next-label --claim --json
41
41
 
42
42
  The claim reserves the label under `.canon/ordinal-locks/`, so a second session allocating at the same moment gets the next one. A bare read reserves nothing and can return a label a live claim already holds.
43
43
 
44
- Branch on the record's `reason` rather than on the exit code, taking `label` from a record that carries one. `label-contended` means re-run the claim. Report the verb as unavailable and stop when the installed binary carries no `next-label` subcommand, or when an unknown `--claim` prints no record, since that binary predates the flag. Never fall back to a bare read or a label picked by hand, which reopens the collision the claim exists to prevent.
44
+ Branch on the record's `reason` rather than on the exit code, taking `label` from a record that carries one.
45
+
46
+ - `label-contended` means re-run the claim
47
+ - Report the verb as unavailable and stop when the binary carries no `next-label` subcommand, or prints no record for an unknown `--claim`, since it predates the flag
48
+ - Never fall back to a bare read or a label picked by hand, which reopens the collision the claim exists to prevent
45
49
 
46
50
  ## Step 3: write the overview
47
51
 
@@ -71,7 +75,9 @@ Run the match check in `${CLAUDE_SKILL_DIR}/references/assembly.md` before going
71
75
 
72
76
  ## Step 6: write the task
73
77
 
74
- Write `<main-root>/.canon/tasks/<label>-<slug>.md` by heredoc, from the label Step 2 claimed, without calling the verb again, since a second call would claim a different label. Its frontmatter, H1, and `## Outcomes` follow `${CLAUDE_SKILL_DIR}/../task-board/references/tasks.md`. Write a `Ready:` line beside the `Plan:` line pointing at the folder, then point the task at the plan through the verb:
78
+ Write `<main-root>/.canon/tasks/<label>-<slug>.md` by heredoc from the label Step 2 claimed, never calling the verb again, which would claim another. Its frontmatter, H1, and `## Outcomes` follow `${CLAUDE_SKILL_DIR}/../task-board/references/tasks.md`.
79
+
80
+ Write a `Ready:` line beside the `Plan:` line pointing at the folder, then point the task at the plan through the verb:
75
81
 
76
82
  ```bash
77
83
  canon tasks plan-link <task-stem> .canon/plans/feature-<slug>.md --json
@@ -79,13 +85,19 @@ canon tasks plan-link <task-stem> .canon/plans/feature-<slug>.md --json
79
85
 
80
86
  Branch on the record rather than on the exit. The `Ready:` line orients a worker reading the task, and `canon tasks archive` reads it to move the folder on ship.
81
87
 
82
- A task file with no row is a dropped task, so place the row in the same pass. The plan exists, so the row takes `## Run now`, per the tests in `${CLAUDE_SKILL_DIR}/../task-board/references/board.md`. A path `canon tasks plan-reach` reports as claimed is a shared file rather than a hold. Name each one and its holder wherever the row is reported below, so whoever controls the board decides whether a contract, a relocation, or a sweep moves the row to `## Up next`.
88
+ ### Place the row
89
+
90
+ A task file with no row is a dropped task, so place the row in the same pass. The plan exists, so the row takes `## Run now`, per the tests in `${CLAUDE_SKILL_DIR}/../task-board/references/board.md`.
91
+
92
+ A path `canon tasks plan-reach` reports as claimed is a shared file rather than a hold. Name each one and its holder wherever the row is reported, so whoever controls the board decides whether a contract, a relocation, or a sweep moves the row to `## Up next`.
93
+
94
+ ### Check the roster
83
95
 
84
- Check the roster the way `canon:task-board` Step 4 does, reading `canon sessions list --self --json` for this session and `canon sessions list --json` for a row from the same repository, under another `sessionId`, whose `name` starts with `orchestrator-`. Treat a refused read as a roster read that failed.
96
+ Check the roster the way `canon:task-board` Step 4 does, reading `canon sessions list --self --json` for this session and `canon sessions list --json` for a same-repository row under another `sessionId` whose `name` starts with `orchestrator-`. A refused read counts as failed.
85
97
 
86
- - **Orchestrator found.** Write neither file. Message that session with the row's title, the plan link, and the group with its reason, so it places the row itself rather than two sessions writing the board at once.
87
- - **Not found, and this session's name starts with `worker-` or `planner-`.** Write neither file, since both role bodies ban a board write with no exception. Report the row and its group in Step 8.
88
- - **Not found otherwise, or the roster read fails.** Write the row into `priority.md` through the same `Bash` route the task file took. A failed read looks like a solo project, and stopping would strand the task on the one path with nobody to place it.
98
+ - **Orchestrator found.** Write neither file. Message that session with the row's title, the plan link, and the group with its reason, so two sessions never write the board at once.
99
+ - **Not found, and this session's name starts with `worker-` or `planner-`.** Write neither file, since both role bodies ban a board write. Report the row and its group in Step 8.
100
+ - **Not found otherwise, or the roster read fails.** Write the row into `priority.md` through the task file's `Bash` route, since a failed read looks like a solo project and stopping strands the task.
89
101
 
90
102
  Say which branch fired in Step 8.
91
103
 
@@ -10,8 +10,9 @@ metadata:
10
10
  ## Guards
11
11
 
12
12
  - If no topic is provided, stop: `❌ No screencast topic. Describe what you are recording.`
13
- - Draft, then hand off. Do not edit video or generate captions, and do not drive the application. A recording is another skill's job rather than something forbidden: write the draft, name `record-screencast` as the next step, and stop.
14
- - Stack-agnostic in the draft. Never name a recording tool, an editing tool, a font, or a window manager. Keep selectors, URLs, wait conditions, and timings out of the beats too, since those four are exactly what the compiler adds in a plan of its own. A beat carrying them stops being a document a person can read and edit down.
13
+ - Draft, then hand off. Do not edit video, generate captions, or drive the application, since a recording is `record-screencast`'s job. Name it as the next step and stop.
14
+ - Stack-agnostic in the draft. Never name a recording tool, an editing tool, a font, or a window manager.
15
+ - Keep selectors, URLs, wait conditions, and timings out of the beats, since the compiler adds those four in a plan of its own and a beat carrying them stops being readable
15
16
 
16
17
  ## Step 1: read the project context
17
18
 
@@ -45,118 +46,11 @@ Build a 2-to-4-word kebab-case slug from the topic and discovery answers. Exampl
45
46
 
46
47
  Create `demos/<slug>/beats.md` at the project root, creating the folder if it does not exist. The file is committed with the rest of the demo's sources, so a recording can be rebuilt from the folder alone.
47
48
 
48
- Write all nine sections. Pre-seed every section with concrete content so the draft is shippable as-is. The user edits down rather than fills blanks.
49
+ ### Fill the template
49
50
 
50
- Load `canon:video-craft` for every zoom and caption value the draft names, and report it rather than proceeding silently when it does not resolve. The template below states none, since each number there traces to a source that skill records.
51
+ Write all nine sections from `${CLAUDE_SKILL_DIR}/references/beats-template.md`, read on reaching this step. Pre-seed every section with concrete content so the draft ships as-is, and the user edits down rather than fills blanks. A run stopped at discovery skips it.
51
52
 
52
- ```markdown
53
- # Screencast: <short title>
54
-
55
- ## 1. Header
56
-
57
- - Audience: <from discovery>
58
- - Length: <from discovery>
59
- - Hero moment: <from discovery>
60
- - Pitch: <one sentence, derived from project context>
61
-
62
- ## 2. Pre-recording checklist
63
-
64
- - [ ] Browser at 1920x1080 with notifications silenced
65
- - [ ] Session storage and history cleared for the recorded surfaces
66
- - [ ] Cursor highlighter on
67
- - [ ] Live URLs and fixtures verified healthy
68
- - [ ] <one or two project-specific items derived from context>
69
-
70
- ## 3. Beat sheet
71
-
72
- Five beats. Each uses five fields. Add a sixth `Transition out` only when non-default.
73
-
74
- ### Beat 1: Cold open
75
-
76
- - On screen: <derived from hero moment>
77
- - Action: <one verb, derived>
78
- - Watch for:
79
- - Emphasis: zoom onto the action, scale and speed per `canon:video-craft`
80
- - Caption:
81
-
82
- ### Beat 2: Setup
83
-
84
- - On screen:
85
- - Action:
86
- - Watch for:
87
- - Emphasis: none
88
- - Caption:
89
-
90
- ### Beat 3: Hero moment
91
-
92
- - On screen: <hero moment from discovery>
93
- - Action:
94
- - Watch for:
95
- - Emphasis: highlight overlay
96
- - Caption:
97
-
98
- ### Beat 4: Payoff
99
-
100
- - On screen:
101
- - Action:
102
- - Watch for:
103
- - Emphasis: none
104
- - Caption:
105
-
106
- ### Beat 5: Outro
107
-
108
- - On screen:
109
- - Action:
110
- - Watch for:
111
- - Emphasis: zoom out to full frame
112
- - Caption:
113
-
114
- ## 4. Sequencing notes
115
-
116
- - Hover before click. The cursor needs a beat to register.
117
- - Do not cut mid-reset. Let state changes complete on screen.
118
- - Cut on action, not on stillness.
119
- - <one or two project-specific gotchas derived from context>
120
-
121
- ## 5. Production notes
122
-
123
- - Resolution: 1920x1080
124
- - Frame rate: 30fps
125
- - Container: mp4 H.264
126
- - Zoom: only on an action too small to read at full frame, scale, speed, and dwell per `canon:video-craft`, zoomed out before every cut
127
- - Highlight overlay: ~1.5s soft glow
128
-
129
- ## 6. Captions
130
-
131
- - Each caption names what its beat's frame shows, at most two lines of 42 characters, per `canon:video-craft`
132
- - Hold and reading rate per `canon:video-craft`, splitting a caption across beats rather than shortening its hold
133
- - Placed off the element the beat acts on
134
-
135
- ## 7. Distribution
136
-
137
- Where the recording ships. Strike rows that do not apply.
138
-
139
- - [ ] Native upload to social platform
140
- - [ ] Canonical host (long-form video platform)
141
- - [ ] Linked from project README or release notes
142
-
143
- ## 8. Resolved decisions
144
-
145
- Pre-seeded with the common picks. Strike or rewrite as needed.
146
-
147
- - Audio: silent plus captions
148
- - Language: English
149
- - Chip order: <leave for user, derived from beats>
150
- - Thumbnail: hero-moment frame
151
-
152
- ## 9. Wrap
153
-
154
- What surrounds the take in the finished video. Each row carries a proposed default. Write `none` on every row to ship the raw take.
155
-
156
- - Intro: <proposed title card of two to three seconds naming the subject, or `none`>
157
- - Outro: <proposed closing card naming where to go next, or `none`>
158
- - Music: none
159
- ```
53
+ Load `canon:video-craft` for every zoom and caption value the draft names, and report it rather than proceeding silently when it does not resolve.
160
54
 
161
55
  ## Step 5: output
162
56
 
@@ -0,0 +1,119 @@
1
+ ---
2
+ title: Beats template
3
+ description: The nine-section template draft-screencast writes to demos/<slug>/beats.md, pre-seeded so the draft ships as-is
4
+ ---
5
+
6
+ # Beats template
7
+
8
+ Applies to the draft `draft-screencast` Step 4 writes. Fill every placeholder from the project context and the discovery answers. The template states no zoom or caption value, since each traces to a source `canon:video-craft` records.
9
+
10
+ ## Template
11
+
12
+ ```markdown
13
+ # Screencast: <short title>
14
+
15
+ ## 1. Header
16
+
17
+ - Audience: <from discovery>
18
+ - Length: <from discovery>
19
+ - Hero moment: <from discovery>
20
+ - Pitch: <one sentence, derived from project context>
21
+
22
+ ## 2. Pre-recording checklist
23
+
24
+ - [ ] Browser at 1920x1080 with notifications silenced
25
+ - [ ] Session storage and history cleared for the recorded surfaces
26
+ - [ ] Cursor highlighter on
27
+ - [ ] Live URLs and fixtures verified healthy
28
+ - [ ] <one or two project-specific items derived from context>
29
+
30
+ ## 3. Beat sheet
31
+
32
+ Five beats. Each uses five fields. Add a sixth `Transition out` only when non-default.
33
+
34
+ ### Beat 1: Cold open
35
+
36
+ - On screen: <derived from hero moment>
37
+ - Action: <one verb, derived>
38
+ - Watch for:
39
+ - Emphasis: zoom onto the action, scale and speed per `canon:video-craft`
40
+ - Caption:
41
+
42
+ ### Beat 2: Setup
43
+
44
+ - On screen:
45
+ - Action:
46
+ - Watch for:
47
+ - Emphasis: none
48
+ - Caption:
49
+
50
+ ### Beat 3: Hero moment
51
+
52
+ - On screen: <hero moment from discovery>
53
+ - Action:
54
+ - Watch for:
55
+ - Emphasis: highlight overlay
56
+ - Caption:
57
+
58
+ ### Beat 4: Payoff
59
+
60
+ - On screen:
61
+ - Action:
62
+ - Watch for:
63
+ - Emphasis: none
64
+ - Caption:
65
+
66
+ ### Beat 5: Outro
67
+
68
+ - On screen:
69
+ - Action:
70
+ - Watch for:
71
+ - Emphasis: zoom out to full frame
72
+ - Caption:
73
+
74
+ ## 4. Sequencing notes
75
+
76
+ - Hover before click. The cursor needs a beat to register.
77
+ - Do not cut mid-reset. Let state changes complete on screen.
78
+ - Cut on action, not on stillness.
79
+ - <one or two project-specific gotchas derived from context>
80
+
81
+ ## 5. Production notes
82
+
83
+ - Resolution: 1920x1080
84
+ - Frame rate: 30fps
85
+ - Container: mp4 H.264
86
+ - Zoom: only on an action too small to read at full frame, scale, speed, and dwell per `canon:video-craft`, zoomed out before every cut
87
+ - Highlight overlay: ~1.5s soft glow
88
+
89
+ ## 6. Captions
90
+
91
+ - Each caption names what its beat's frame shows, at most two lines of 42 characters, per `canon:video-craft`
92
+ - Hold and reading rate per `canon:video-craft`, splitting a caption across beats rather than shortening its hold
93
+ - Placed off the element the beat acts on
94
+
95
+ ## 7. Distribution
96
+
97
+ Where the recording ships. Strike rows that do not apply.
98
+
99
+ - [ ] Native upload to social platform
100
+ - [ ] Canonical host (long-form video platform)
101
+ - [ ] Linked from project README or release notes
102
+
103
+ ## 8. Resolved decisions
104
+
105
+ Pre-seeded with the common picks. Strike or rewrite as needed.
106
+
107
+ - Audio: silent plus captions
108
+ - Language: English
109
+ - Chip order: <leave for user, derived from beats>
110
+ - Thumbnail: hero-moment frame
111
+
112
+ ## 9. Wrap
113
+
114
+ What surrounds the take in the finished video. Each row carries a proposed default. Write `none` on every row to ship the raw take.
115
+
116
+ - Intro: <proposed title card of two to three seconds naming the subject, or `none`>
117
+ - Outro: <proposed closing card naming where to go next, or `none`>
118
+ - Music: none
119
+ ```
@@ -38,15 +38,18 @@ Each slide is a full HTML document laid out at 1280 by 720 CSS pixels. Position
38
38
 
39
39
  The render injects the project's token stylesheet ahead of each slide's own styles, so the deck takes the project's design rather than the toolkit's.
40
40
 
41
- - Use the project's custom properties, such as `var(--color-accent)`, for every color, face, and spacing value the stylesheet defines. Read the names from `.claude/design/base.css` and any overrides under `.claude/design/project/`. When neither exists, say the deck renders unstyled rather than inventing values to cover it.
42
- - Leave the `--color-*` roles to the stylesheet rather than redefining them in a slide. The master reads its colors off the first slide's `<html>`, and a slide body computing its own `--color-text` is reported, since the bands keep the master's colors.
41
+ - Use the project's custom properties, such as `var(--color-accent)`, for every color, face, and spacing value, read from `.claude/design/base.css` and overrides under `.claude/design/project/`
42
+ - When neither exists, say the deck renders unstyled rather than inventing values to cover it
43
+ - Leave the `--color-*` roles to the stylesheet. The master reads its colors off the first slide's `<html>`, so a slide computing its own `--color-text` is reported, since the bands keep the master's.
43
44
  - Vary the composition across slides. Never repeat a title over bullets on every slide. Reach for columns, a row of large numbers, a grid of cards, a chart, a pull quote, and section dividers.
44
45
 
45
46
  ### Fit and shapes
46
47
 
48
+ Text and shapes have to survive the conversion.
49
+
47
50
  - Keep titles short and lines tight, so text fits its box at the size the design sets rather than overflowing the frame.
48
51
  - Never write centered body text.
49
- - Prefer CSS the converter maps to native shapes. An element computing a property it cannot map is drawn as a picture of itself and reported on stderr, so a slide full of gradients and filters stops being editable.
52
+ - Prefer CSS the converter maps to native shapes. An unmappable property is drawn as a picture and reported on stderr, so a slide full of gradients and filters stops being editable.
50
53
 
51
54
  ## Render
52
55
 
@@ -64,7 +67,7 @@ Read every `✗` line the render prints. A picture fallback, a refused link, cha
64
67
 
65
68
  Run this once after the first render, then stop.
66
69
 
67
- 1. Convert the deck to images: `soffice --headless --convert-to pdf <deck>.pptx` then `pdftoppm -r 90 -png <deck>.pdf slide`. If `soffice` or `pdftoppm` is missing, skip the image pass and say so. Do not fail the render.
70
+ 1. Convert the deck with `soffice --headless --convert-to pdf <deck>.pptx`, then `pdftoppm -r 90 -png <deck>.pdf slide`. If either is missing, skip the image pass and say so rather than fail the render.
68
71
  2. Open and inspect every rendered image in this session, checking each slide for overlap, overflow, low contrast, and empty regions.
69
72
  3. Fix the reported issues and the `✗` lines in the slide files once, re-render, and stop. Do not loop indefinitely on aesthetics.
70
73
 
@@ -31,6 +31,8 @@ Run these commands in parallel to gather git context:
31
31
 
32
32
  ### Preview
33
33
 
34
+ Output the preview first.
35
+
34
36
  - **Current:** <current_branch>
35
37
  - **Suggested:** <suggested_name>
36
38
  - **Length:** <count>/50
@@ -55,8 +57,6 @@ git branch -m <current> <suggested>
55
57
 
56
58
  ## After execution
57
59
 
58
- Respond with exactly one line:
60
+ Respond with exactly one line and add no other text:
59
61
 
60
62
  `✅ Renamed: <current> → <suggested>`
61
-
62
- Do not add any other text.
@@ -30,6 +30,8 @@ Run these commands in parallel to gather git context:
30
30
 
31
31
  ### Preview
32
32
 
33
+ Output the preview first.
34
+
33
35
  - **Files:** <if ≤3 list all, if >3 show first 3 + "+N more">
34
36
  - **Message:** `<type>(<scope>): <subject>`
35
37
  - **Length:** <count>/72
@@ -38,14 +40,14 @@ After outputting the preview, execute the final command immediately. Claude Code
38
40
 
39
41
  ### Final command
40
42
 
43
+ Run it as soon as the preview is out.
44
+
41
45
  ```bash
42
46
  git commit -m "<type>(<scope>): <subject>"
43
47
  ```
44
48
 
45
49
  ## After execution
46
50
 
47
- Respond with exactly one line:
51
+ Respond with exactly one line and add no other text:
48
52
 
49
53
  `✅ Committed: <type>(<scope>): <subject>`
50
-
51
- Do not add any other text.
@@ -16,18 +16,25 @@ its own reply, so skip the comment in step 7. The push and body sync still run.
16
16
 
17
17
  - If `git branch --show-current` returns `main`, stop: `❌ On main. Switch to a PR branch first.`
18
18
  - If `git status --porcelain` is empty, stop: `❌ No changes to ship.`
19
- - Resolve `<number>` once, here, through `canon pr head --json`, reading the record's `number`. Every later step names this `<number>` rather than resolving it again. A record refusing with `ambiguous-pull` stops: `❌ Two open PRs on this branch. Name the one to update.`
20
- - When no record comes back, or it carries no `number`, read the open pull requests on the branch over REST instead. The branch is encoded so a `/` in its name reaches the query as `%2F`:
19
+ - Resolve `<number>` once, here, through `canon pr head --json`, reading the record's `number`. Every later step names this `<number>` rather than resolving it again.
20
+ - A record refusing with `ambiguous-pull` stops: `❌ Two open PRs on this branch. Name the one to update.`
21
21
 
22
- ```bash
23
- gh api "repos/{owner}/{repo}/pulls?head={owner}:$(git branch --show-current | jq -Rr @uri)&state=open" --jq '.[].number'
24
- ```
22
+ ### Fallback lookup
25
23
 
26
- Nothing printed stops: `❌ No open PR for this branch. Use git-ship to open one.` More than one number takes the `ambiguous-pull` stop above rather than the first, since two open pull requests on one head against different bases leave nothing to say which one this push updates.
24
+ When no record comes back, or it carries no `number`, read the open pull requests on the branch over REST instead. The branch is encoded so a `/` in its name reaches the query as `%2F`:
27
25
 
28
- Every read and write in this skill runs on REST, since the `gh pr` subcommands run on GraphQL and a cloud session's GitHub proxy refuses it. A cloud worker answering a review reaches this skill through `review-address`, so one GraphQL call here breaks that whole leg.
26
+ ```bash
27
+ gh api "repos/{owner}/{repo}/pulls?head={owner}:$(git branch --show-current | jq -Rr @uri)&state=open" --jq '.[].number'
28
+ ```
29
+
30
+ - Nothing printed stops: `❌ No open PR for this branch. Use git-ship to open one.`
31
+ - More than one number takes the `ambiguous-pull` stop rather than the first, since two open pull requests on one head leave nothing to say which one this push updates
32
+
33
+ ### REST and the tracking ref
34
+
35
+ Every read and write in this skill runs on REST, since the `gh pr` subcommands run on GraphQL and a cloud session's GitHub proxy refuses it. A cloud worker reaches this skill through `review-address`, so one GraphQL call breaks that whole leg.
29
36
 
30
- A missing tracking ref is no longer a guard. An open pull request proves the branch reached the remote, and a branch created by worktree entry has been measured carrying an open pull request with no tracking ref, where the old guard reported that the branch still needed pushing. That message describes a state the branch is not in, so the run stopped on a diagnosis nobody could act on. Step 4 sets the ref instead.
37
+ A missing tracking ref is not a guard, since an open pull request proves the branch reached the remote. A branch created by worktree entry carries an open pull request with no tracking ref, so step 4 sets the ref instead.
31
38
 
32
39
  ## Sequence
33
40
 
@@ -40,7 +47,7 @@ A missing tracking ref is no longer a guard. An open pull request proves the bra
40
47
 
41
48
  The lease is what stops the force from overwriting a commit this session never read. Run the ancestry test only where an upstream resolves, since it reads `@{u}`.
42
49
 
43
- 5. Post the evidence comparison, on every invocation including `reply-owned`, since this comment is not the reply step 8 owns. Run `canon pr evidence <number> --json` and read `reason` on the record rather than the exit code.
50
+ 5. Post the evidence comparison on every invocation, `reply-owned` included, since it is not step 8's reply. Run `canon pr evidence <number> --json` and read `reason` rather than the exit code.
44
51
  - `no-evidence`: nothing changed under an `evidence/` segment this push. Say nothing and move on.
45
52
  - `ok`: write `body` to `.canon/tmp/pr/evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post the comment with `gh api -X POST repos/{owner}/{repo}/issues/<number>/comments -F body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md --silent` when the record carries no `commentId`, or edit the existing one in place with `gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -F body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md` when it does. Clean up the tmp file only after the call reports success.
46
53
  - Any other reason is one of the mirrored git refusals (`gh-missing`, `gh-failed`, `no-base`, `unreadable-changes`, `would-empty`). Report it and continue without stopping the chain.
@@ -33,6 +33,8 @@ Then gather repo context in parallel:
33
33
 
34
34
  ### Preview
35
35
 
36
+ Output the preview first.
37
+
36
38
  - **Title:** `<type>: <subject>`
37
39
  - **Label:** <bug or enhancement>
38
40
  - **Analysis:** <one line on what the issue captures>
@@ -41,9 +43,10 @@ After outputting the preview, execute the final command immediately. Claude Code
41
43
 
42
44
  ### Pre-publish scan
43
45
 
44
- Before running the final command, run the scan in `${CLAUDE_SKILL_DIR}/../git-pr/references/publish.md` against the title and body. The title and body go straight to the remote with nothing checking them on the way, so this scan is the only gate. It covers the phase-label check as well as the characters, since both go to a reader who has no task board. It applies on top of the banned phrases in `${CLAUDE_SKILL_DIR}/references/issue.md`.
46
+ Before running the final command, run the scan in `${CLAUDE_SKILL_DIR}/../git-pr/references/publish.md` against the title and body, on top of the banned phrases in `${CLAUDE_SKILL_DIR}/references/issue.md`.
45
47
 
46
- The `pull_request` check the git-pr surface carries reads a pull request's own title and body alone, so an issue reaches no check behind this scan either.
48
+ - The title and body go straight to the remote, and the `pull_request` check reads a pull request alone, so this scan is the only gate
49
+ - It covers the phase-label check as well as the characters, since both reach a reader who has no task board
47
50
 
48
51
  ### Final command
49
52
 
@@ -60,8 +63,6 @@ rm -rf .canon/tmp/issue
60
63
 
61
64
  ## After execution
62
65
 
63
- Respond with exactly one line:
66
+ Respond with exactly one line and add no other text:
64
67
 
65
68
  `✅ Issue: <url>`
66
-
67
- Do not add any other text.
@@ -39,18 +39,20 @@ Then run these commands in parallel to gather git context:
39
39
 
40
40
  ## Diff baseline
41
41
 
42
- Prefer `origin/main` over local `main`. Both reads resolve against `<base>`, so the commits listed and the changes described come from one scope. Never read a two-dot range against local `main` for either half: it resolves no merge base, so once local `main` moves, the diff reports its newer commits as reversed changes and the log lists commits already on the remote.
42
+ Prefer `origin/main` over local `main`. Both reads resolve against `<base>`, so the commits listed and the changes described come from one scope.
43
43
 
44
- The baseline is unusable in two cases:
44
+ Never read a two-dot range against local `main` for either half. It resolves no merge base, so once local `main` moves, the diff reports its newer commits as reversed changes and the log lists commits already on the remote.
45
+
46
+ ### Unusable baseline
47
+
48
+ The baseline is unusable in two cases, and the no-commits guard below catches both, so stop there rather than describing an empty diff:
45
49
 
46
50
  - No merge base resolves against either ref.
47
51
  - The base equals HEAD, whichever ref resolved it. Nothing is committed ahead of the base to compare against.
48
52
 
49
- Either case leaves both reads empty, which the no-commits guard below catches. Stop there rather than composing a description from an empty diff.
50
-
51
53
  ## Guards
52
54
 
53
- - If branch name does not match `<type>/<description>` format (valid types are defined in `${CLAUDE_SKILL_DIR}/../git-branch/references/branch.md`), stop and output:
55
+ - If the branch name does not match `<type>/<description>`, with the types `branch.md` defines, stop and output:
54
56
  `❌ Branch name does not follow conventions. Run /git-branch to rename first.`
55
57
  - If no commits ahead of main, stop and output:
56
58
  `❌ No commits ahead of main. Nothing to PR.`
@@ -59,6 +61,8 @@ Either case leaves both reads empty, which the no-commits guard below catches. S
59
61
 
60
62
  ### Preview
61
63
 
64
+ Output the preview first.
65
+
62
66
  - **Title:** <title>
63
67
  - **Files changed:** <count>
64
68
  - **Analysis:** <brief summary of impact>
@@ -73,9 +77,12 @@ Leave a box unchecked only for the human-only cases the reference defines, and n
73
77
 
74
78
  ### Pre-publish scan
75
79
 
76
- Before running the final command, run the scan in `${CLAUDE_SKILL_DIR}/references/publish.md` against the PR title and body. The title and body go straight to the remote with nothing checking them on the way, so run the scan regardless of what backs it downstream. It covers the phase-label check as well as the characters, since both go to a reader who has no task board. It applies on top of the banned phrases in `${CLAUDE_SKILL_DIR}/references/pr.md`.
80
+ Before running the final command, run the scan in `${CLAUDE_SKILL_DIR}/references/publish.md` against the PR title and body, on top of the banned phrases in `${CLAUDE_SKILL_DIR}/references/pr.md`. The title and body go straight to the remote, so run it whatever backs it downstream.
81
+
82
+ - It covers the phase-label check as well as the characters, since both reach a reader who has no task board
83
+ - It stays required where a `pull_request` job runs the same verb, since an older `canon` carries no such job and none of the newer keys
77
84
 
78
- Write the body here, ahead of the final command, since the scan below needs a file to read it from:
85
+ Write the body here, ahead of the final command, since the scan needs a file to read it from:
79
86
 
80
87
  ```bash
81
88
  mkdir -p .canon/tmp/pr
@@ -90,70 +97,75 @@ Then run:
90
97
  canon labels scan --title "<title>" --body-file .canon/tmp/pr/body.md --json
91
98
  ```
92
99
 
93
- Branch on the JSON record rather than the exit code. An operator's shell can wrap `canon` in a function whose status comes from a trailing command, flattening a non-zero exit to 0, the same reason the labels step below branches on its own record rather than the exit.
100
+ ### Reading the scan
101
+
102
+ Branch on the JSON record rather than the exit code, since a shell function wrapping `canon` can flatten a non-zero exit to 0. Stop and fix the title or body on any key below, and do not proceed to `### Final command` until a re-run comes back clean on all six.
94
103
 
95
- Stop and fix the title or body on a non-empty `phaseLabels`, `boardReferences`, `sessionLinks`, `titleFormatIssues`, `bannedCharacters`, or a `sections` record with anything in `outside` or `missing`. Do not proceed to `### Final command` until a re-run comes back clean on all six. `titleFormatIssues` names which `## Title` rule in `${CLAUDE_SKILL_DIR}/references/pr.md` the title breaks, structure, casing, or length, so fix the named rule rather than guessing. `sections.outside` lists each heading to rename or drop, `sections.missing` each required heading to add, and `bannedCharacters` names the character to restructure the sentence around. Leave `cutsRelease` and `semverTags` alone, since a release-please pull request legitimately carries version references its own fixed shape explains.
104
+ | Key | Fix |
105
+ | ------------------- | ------------------------------------------------------------------------------------ |
106
+ | `phaseLabels` | Remove the phase label |
107
+ | `boardReferences` | Remove the board reference |
108
+ | `sessionLinks` | Remove the session link |
109
+ | `titleFormatIssues` | Fix the `## Title` rule in `pr.md` it names, being structure, casing, or length |
110
+ | `bannedCharacters` | Restructure the sentence around the named character |
111
+ | `sections` | Rename or drop each heading in `outside`, and add each required heading in `missing` |
96
112
 
97
- The scan stays required even where a `pull_request` workflow job runs the same verb, since a project holding an older `canon` carries no such job and no `titleFormatIssues`, `sections`, or `bannedCharacters` key.
113
+ Leave `cutsRelease` and `semverTags` alone, since a release-please pull request legitimately carries version references its own fixed shape explains.
98
114
 
99
115
  ### Resolving the pull request
100
116
 
101
117
  The run resolves the pull request once, in the final command below, and every later step reads what that command printed. Nothing else looks the number up again.
102
118
 
103
- Never detect by head branch alone. A lookup that ignores state returns the closed pull request a reused branch name left behind, and the run rewrites it. Scoping the lookup to `state=open` returns empty there and sends the run down the create path.
104
-
105
- The lookup scopes to the base as well as the head. One head can carry open pull requests against two bases, and a lookup reading the first result would pick between them by list order. Resolving the base from the repository's default branch is what makes the detection and the create agree on which pull request the run is about.
106
-
107
- Every call goes through `gh api` against the REST endpoints rather than through `gh pr` or `gh repo view`, which run on GraphQL. A cloud session's GitHub access blocks GraphQL and serves REST, so the same block converges on a laptop and on a cloud VM.
119
+ - Never detect by head branch alone. A lookup ignoring state returns the closed pull request a reused branch name left behind, so scope it to `state=open`.
120
+ - Scope the lookup to the base as well as the head, resolved from the default branch, so detection and create agree on one pull request. A head can carry two bases.
121
+ - Call `gh api` against REST rather than `gh pr` or `gh repo view`, which run on GraphQL. A cloud session's GitHub access blocks GraphQL and serves REST.
108
122
 
109
123
  A detached HEAD gives `git branch --show-current` an empty result, which would read as no open pull request and create a second one. The branch-name guard above stops the run first, since an empty name does not match `<type>/<description>`.
110
124
 
111
125
  ### Labels
112
126
 
113
- Read `${CLAUDE_SKILL_DIR}/references/labels.md` on reaching this step, starting at its `## Labels at run time` section, for the verb to ask, the record to branch on, the fallback, and when `pr_labels` stays empty. The final command below takes the set it resolves as `pr_labels` and the record's `uncovered` paths as `pr_uncovered`, which only its fallback path reads.
127
+ Read `${CLAUDE_SKILL_DIR}/references/labels.md` on reaching this step, starting at its `## Labels at run time` section, for the verb to ask, the record to branch on, the fallback, and when `pr_labels` stays empty.
114
128
 
115
- ### Final command
129
+ The final command takes the resolved set as `pr_labels` and the record's `uncovered` paths as `pr_uncovered`, which only its fallback path reads.
116
130
 
117
- Read `${CLAUDE_SKILL_DIR}/references/final-command.md` on reaching this step for the one command that pushes, finds or creates the pull request over REST, re-scans its live body for a session link, applies the labels through `canon labels apply`, and prints `number=`, `url=`, `head=`, `owed=`, `missingOnRemote=`, and `uncovered=`, and for the check binding every write to this branch's pull request.
118
-
119
- ### Post the rendered-surface evidence
120
-
121
- Run this step on every pull request this skill opens or edits. Read `${CLAUDE_SKILL_DIR}/references/evidence.md` for finding the checklist and the local server, posting the evidence comparison, posting the checklist alone, and posting the preview address, each against the `<number>` the final command printed. 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.
122
-
123
- ### Record the number on the task
124
-
125
- Write the `number` the final command printed onto the task the branch is closing, through `canon tasks pull-request`, and never resolve it again. Read `${CLAUDE_SKILL_DIR}/references/task-number.md` on reaching this step for which task it names, the invocation, the refusals it skips silently, and why the number lands here. Skip it when the final command refused on a head mismatch and printed no number.
126
-
127
- ## After execution
131
+ ### Final command
128
132
 
129
- Respond with one line, using the `url` the final command printed:
133
+ Read `${CLAUDE_SKILL_DIR}/references/final-command.md` on reaching this step, for the one command that pushes and finds or creates the pull request over REST, and for the check binding every write to this branch's pull request.
130
134
 
131
- `✅ PR: <url>`
135
+ That command re-scans the live body for a session link, applies the labels through `canon labels apply`, and prints `number=`, `url=`, `head=`, `owed=`, `missingOnRemote=`, and `uncovered=`.
132
136
 
133
- Add a line for each path in the `uncovered=` value the final command printed, naming the path and the map it belongs in:
137
+ ### Post the rendered-surface evidence
134
138
 
135
- `⚠️ No label covers <path>. Add a row to canon/config/pr-labels.toml or a [declined] entry.`
139
+ Run this step on every pull request this skill opens or edits. Read `${CLAUDE_SKILL_DIR}/references/evidence.md` for finding the checklist and the local server and for posting the evidence comparison, the checklist alone, and the preview address, each against the `<number>` the final command printed.
136
140
 
137
- Add a further line only when `owed=` or `missingOnRemote=` printed a value, naming each owed label, `owed=` less the names in `missingOnRemote=`, and separately the names in `missingOnRemote=`, since `owed=` already holds every absent label. Drop either half that is empty:
141
+ 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.
138
142
 
139
- `⚠️ Labels not applied: <owed labels not in missingOnRemote>. Not on the remote: <missingOnRemote labels>.`
143
+ ### Record the number on the task
140
144
 
141
- Add a line when a checklist was posted, naming the pull request it landed on and which comment carries it:
145
+ Write the `number` the final command printed onto the task the branch is closing, through `canon tasks pull-request`, and never resolve it again. Skip it when the final command refused on a head mismatch and printed no number.
142
146
 
143
- `📋 Posted the UI checklist to <number>, in the evidence comment.`
147
+ Read `${CLAUDE_SKILL_DIR}/references/task-number.md` on reaching this step for which task it names, the invocation, the refusals it skips silently, and why the number lands here.
144
148
 
145
- `📋 Posted the UI checklist to <number>, on its own.`
149
+ ## After execution
146
150
 
147
- Add a line when the preview step returned an address:
151
+ Respond with `✅ PR: <url>`, using the `url` the final command printed, then add only the lines below whose condition holds. Add no other text.
148
152
 
149
- `🔗 Preview: <url>`
153
+ | Condition | Line |
154
+ | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
155
+ | Each path in `uncovered=` | `⚠️ No label covers <path>. Add a row to canon/config/pr-labels.toml or a [declined] entry.` |
156
+ | `owed=` or `missingOnRemote=` printed a value | `⚠️ Labels not applied: <owed labels not in missingOnRemote>. Not on the remote: <missingOnRemote labels>.` |
157
+ | A checklist posted in the evidence comment | `📋 Posted the UI checklist to <number>, in the evidence comment.` |
158
+ | A checklist posted alone | `📋 Posted the UI checklist to <number>, on its own.` |
159
+ | The preview step returned an address | `🔗 Preview: <url>` |
160
+ | The evidence step posted the local link | `🖥️ Local preview: <url>` |
161
+ | The local step refused and evidence or a checklist posted | `🖥️ No local preview: <reason>` |
150
162
 
151
- Add a line when the evidence step posted the local link:
163
+ ### The labels line
152
164
 
153
- `🖥️ Local preview: <url>`
165
+ `owed=` already holds every absent label, so the first half names `owed=` less the names in `missingOnRemote=`, and the second names `missingOnRemote=` alone. Drop either half that is empty.
154
166
 
155
- Add a line instead when the local step refused as `no-server` or `no-listener-reader` and the evidence or checklist step posted something, since that pull request changes a rendered surface and a reviewer could have used the link. To add it later, start the server, run `canon pr local --json`, then run `canon pr evidence <number> --local <url> --json`, adding `--checklist` when one is still owed, and post the body the way the evidence step does. `git-followup` only carries a line forward, so it cannot add one the comment never had:
167
+ ### The no-local-preview line
156
168
 
157
- `🖥️ No local preview: <reason>`
169
+ 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. `git-followup` only carries a line forward, so it cannot add one the comment never had.
158
170
 
159
- Do not add any other text.
171
+ To add the link later, start the server and run `canon pr local --json`, then `canon pr evidence <number> --local <url> --json`, adding `--checklist` when one is still owed. Post the body the way the evidence step does.