@erclx/canon 4.120.0 → 4.122.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 (152) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/auto-ship/SKILL.md +29 -115
  3. package/claude/skills/auto-ship/references/draft-mark.md +30 -0
  4. package/claude/skills/auto-ship/references/failure-recovery.md +27 -0
  5. package/claude/skills/auto-ship/references/tier-failures.md +26 -0
  6. package/claude/skills/auto-ship/references/verb-absent.md +37 -0
  7. package/claude/skills/auto-ship/references/worktree-name.md +22 -0
  8. package/claude/skills/backlog-triage/REQUIREMENT.md +47 -0
  9. package/claude/skills/backlog-triage/SKILL.md +139 -0
  10. package/claude/skills/bash-script/SKILL.md +1 -1
  11. package/claude/skills/bash-script/references/patterns.md +0 -99
  12. package/claude/skills/bash-script/references/prompts.md +100 -0
  13. package/claude/skills/canon-rollout/SKILL.md +6 -6
  14. package/claude/skills/context-fold/REQUIREMENT.md +75 -0
  15. package/claude/skills/context-fold/SKILL.md +162 -0
  16. package/claude/skills/{docs-fold → context-fold}/references/anchor-sweep.md +1 -1
  17. package/claude/skills/{docs-fold → context-fold}/references/classify.md +4 -2
  18. package/claude/skills/context-fold/references/context-refresh.md +48 -0
  19. package/claude/skills/context-fold/references/promoted-pages.md +29 -0
  20. package/claude/skills/context-fold/references/receipt-sweep.md +43 -0
  21. package/claude/skills/context-fold/references/unusable-baseline.md +25 -0
  22. package/claude/skills/{docs-fold → context-fold}/references/wireframe-sweep.md +1 -1
  23. package/claude/skills/create-skill/SKILL.md +1 -1
  24. package/claude/skills/create-snippet/SKILL.md +1 -1
  25. package/claude/skills/create-standard/SKILL.md +1 -1
  26. package/claude/skills/design-extract/SKILL.md +1 -1
  27. package/claude/skills/design-taste/REQUIREMENT.md +2 -2
  28. package/claude/skills/docs-fold/REQUIREMENT.md +8 -58
  29. package/claude/skills/docs-fold/SKILL.md +3 -278
  30. package/claude/skills/docs-sync/REQUIREMENT.md +1 -1
  31. package/claude/skills/docs-sync/SKILL.md +2 -2
  32. package/claude/skills/draft-context/REQUIREMENT.md +2 -2
  33. package/claude/skills/draft-context/SKILL.md +4 -4
  34. package/claude/skills/draft-docs/REQUIREMENT.md +1 -1
  35. package/claude/skills/draft-docs/SKILL.md +1 -1
  36. package/claude/skills/draft-figure/SKILL.md +1 -1
  37. package/claude/skills/draft-readme/SKILL.md +1 -1
  38. package/claude/skills/draft-wiki/SKILL.md +1 -1
  39. package/claude/skills/draft-wireframes/REQUIREMENT.md +1 -1
  40. package/claude/skills/draft-wireframes/SKILL.md +3 -3
  41. package/claude/skills/git-issue/SKILL.md +1 -1
  42. package/claude/skills/git-issue/references/issue.md +1 -1
  43. package/claude/skills/git-pr/SKILL.md +9 -147
  44. package/claude/skills/git-pr/references/evidence.md +114 -0
  45. package/claude/skills/git-pr/references/labels.md +20 -0
  46. package/claude/skills/git-pr/references/task-number.md +26 -0
  47. package/claude/skills/git-ship/SKILL.md +1 -1
  48. package/claude/skills/git-stage/SKILL.md +1 -1
  49. package/claude/skills/memory-capture/REQUIREMENT.md +2 -2
  50. package/claude/skills/memory-capture/SKILL.md +6 -6
  51. package/claude/skills/memory-review/REQUIREMENT.md +1 -1
  52. package/claude/skills/memory-review/SKILL.md +5 -101
  53. package/claude/skills/memory-review/references/later-phases.md +108 -0
  54. package/claude/skills/plan-feature/REQUIREMENT.md +1 -1
  55. package/claude/skills/restate-plainly/REQUIREMENT.md +1 -1
  56. package/claude/skills/review-address/REQUIREMENT.md +1 -1
  57. package/claude/skills/review-address/SKILL.md +5 -4
  58. package/claude/skills/review-branch/REQUIREMENT.md +1 -0
  59. package/claude/skills/review-branch/SKILL.md +6 -7
  60. package/claude/skills/review-craft/REQUIREMENT.md +43 -0
  61. package/claude/skills/review-craft/SKILL.md +50 -0
  62. package/claude/skills/review-craft/references/security.md +21 -0
  63. package/claude/skills/review-craft/references/ui.md +72 -0
  64. package/claude/skills/review-pr/REQUIREMENT.md +1 -0
  65. package/claude/skills/review-pr/SKILL.md +1 -1
  66. package/claude/skills/review-pr/references/post.md +1 -1
  67. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +1 -1
  68. package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -0
  69. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +2 -2
  70. package/claude/skills/role-planner/REQUIREMENT.md +1 -1
  71. package/claude/skills/session-resume/REQUIREMENT.md +1 -1
  72. package/claude/skills/session-worktree/SKILL.md +4 -10
  73. package/claude/skills/standards-audit/SKILL.md +1 -1
  74. package/claude/skills/task-board/REQUIREMENT.md +1 -1
  75. package/claude/skills/task-board/SKILL.md +6 -80
  76. package/claude/skills/task-board/references/archive.md +54 -0
  77. package/claude/skills/task-board/references/decline.md +40 -0
  78. package/claude/skills/teach-workspace/SKILL.md +8 -105
  79. package/claude/skills/teach-workspace/references/lesson-craft.md +51 -1
  80. package/claude/skills/teach-workspace/references/promotion.md +41 -0
  81. package/claude/skills/write-human/REQUIREMENT.md +7 -4
  82. package/claude/skills/write-human/SKILL.md +4 -2
  83. package/claude/skills/write-human/references/machine-tells.md +1 -1
  84. package/claude/skills/write-human/references/source-material.md +5 -2
  85. package/docs/agents/architecture-record.md +26 -0
  86. package/docs/agents/audits.md +1 -1
  87. package/docs/agents/commands.md +87 -154
  88. package/docs/agents/context-audit-checks.md +2 -20
  89. package/docs/agents/context-audit.md +1 -1
  90. package/docs/agents/context-classify.md +1 -1
  91. package/docs/agents/drift-surfaces.md +192 -0
  92. package/docs/agents/index.md +12 -4
  93. package/docs/agents/install-and-sync.md +2 -181
  94. package/docs/agents/markdown-audit-weight.md +73 -0
  95. package/docs/agents/markdown-audit.md +11 -104
  96. package/docs/agents/migrate.md +34 -0
  97. package/docs/agents/pr-local.md +6 -3
  98. package/docs/agents/records-stale.md +62 -0
  99. package/docs/agents/records.md +5 -1
  100. package/docs/agents/sandbox.md +1 -1
  101. package/docs/agents/tasks-archive.md +81 -0
  102. package/docs/agents/tasks-plans.md +147 -0
  103. package/docs/agents/tasks-validate.md +131 -0
  104. package/docs/agents/tasks.md +6 -333
  105. package/docs/target-projects.md +4 -4
  106. package/docs/workflow/ai-workflow.md +33 -27
  107. package/governance/rules/claude/500-prose.md +1 -1
  108. package/governance/rules/claude/501-markdown.md +3 -3
  109. package/governance/rules/core/045-memory.md +1 -1
  110. package/package.json +1 -1
  111. package/src/autoship/paths.ts +3 -2
  112. package/src/claude/cases/setup.ts +1 -1
  113. package/src/claude/cases/workflow.ts +27 -1
  114. package/src/commands/audits.ts +2 -2
  115. package/src/commands/markdown.ts +7 -9
  116. package/src/commands/pr.ts +11 -4
  117. package/src/commands/records.ts +146 -0
  118. package/src/commands/sandbox.ts +6 -4
  119. package/src/context/classify/extract.ts +1 -1
  120. package/src/context/classify/ollama.ts +1 -1
  121. package/src/context/classify/run.ts +1 -1
  122. package/src/gate/measures.ts +5 -5
  123. package/src/gate/stages.ts +0 -1
  124. package/src/markdown/bans.ts +11 -68
  125. package/src/markdown/gate.ts +1 -1
  126. package/src/markdown/scan.ts +7 -88
  127. package/src/migrate/skill-names.ts +15 -2
  128. package/src/records/stale.ts +243 -0
  129. package/src/sandbox/expect.ts +1 -1
  130. package/src/tasks/archive.ts +28 -4
  131. package/src/tasks/record.ts +1 -1
  132. package/src/tasks/validate.ts +36 -3
  133. package/standards/docs.md +1 -1
  134. package/standards/index.md +1 -1
  135. package/standards/markdown.md +5 -7
  136. package/standards/memory.md +13 -1
  137. package/standards/pr.md +1 -1
  138. package/standards/publish.md +1 -1
  139. package/standards/readme.md +2 -2
  140. package/standards/tasks.md +1 -1
  141. package/tooling/astro/reference.md +1 -1
  142. package/tooling/base/configs/.husky/post-merge +1 -1
  143. package/tooling/base/configs/scripts/verify.sh +2 -2
  144. package/tooling/base/seeds/canon/context/development.md +1 -1
  145. package/tooling/claude/reference.md +1 -1
  146. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +5 -6
  147. package/tooling/go/reference.md +1 -1
  148. package/tooling/nextjs/reference.md +1 -1
  149. package/tooling/php/reference.md +1 -1
  150. package/tooling/python/reference.md +1 -1
  151. package/tooling/vite-react/reference.md +1 -1
  152. package/tooling/web/reference.md +1 -1
@@ -12,7 +12,7 @@ Read these files in parallel:
12
12
  - `${CLAUDE_SKILL_DIR}/../../standards/branch.md`: branch format, valid types, and constraints
13
13
  - `${CLAUDE_SKILL_DIR}/../../standards/pr.md`: structure, rules, and banned phrases
14
14
  - `${CLAUDE_SKILL_DIR}/references/labels.md`: label map format, matching, and the missing-label warning. Skip when the project has no `canon/config/pr-labels.toml` and no `.claude/canon/pr-labels.toml`.
15
- - `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: banned words, punctuation, and formatting for all generated text
15
+ - `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: punctuation and formatting for all generated text
16
16
  - The `write-human` skill: voice, rhythm, and sentence construction for all generated text
17
17
  - `${CLAUDE_SKILL_DIR}/../../standards/versioning.md`: phase label vs semver discipline
18
18
 
@@ -32,11 +32,7 @@ Then run these commands in parallel to gather git context:
32
32
 
33
33
  ## Diff baseline
34
34
 
35
- Prefer `origin/main` over local `main`. Both reads resolve against `<base>`, so the commits listed and the changes described come from one scope.
36
-
37
- `git diff main..HEAD` is the form the diff replaces. A two-dot range compares tips and resolves no merge base, so once local `main` advances past the branch point it reports main's newer commits as reversed changes and the description describes work the branch never did. On `main` itself the local ref resolves to HEAD and every committed change drops out instead.
38
-
39
- `git log main..HEAD` is the matching defect on the commit side. It excludes what local `main` reaches, so a local `main` trailing `origin/main` leaves commits in the range that are already on the remote and are not this branch's work. The diff resolved from `<base>` excludes those same commits, and the description then lists commits whose changes appear nowhere in it. Reading both against `<base>` is what keeps the two halves describing one branch.
35
+ 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.
40
36
 
41
37
  The baseline is unusable in two cases:
42
38
 
@@ -87,17 +83,17 @@ Then run:
87
83
  canon labels scan --title "<title>" --body-file .canon/tmp/pr/body.md --json
88
84
  ```
89
85
 
90
- 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 `### Labels` below branches on its own record rather than the exit.
86
+ 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.
91
87
 
92
88
  Stop and fix the title or body on a non-empty `phaseLabels`, `boardReferences`, `sessionLinks`, `unspelledWords`, or `titleFormatIssues`. Do not proceed to `### Final command` until a re-run comes back clean on all five. `titleFormatIssues` names which `## Title` rule in `${CLAUDE_SKILL_DIR}/../../standards/pr.md` the title breaks, structure, casing, or length, so fix the named rule rather than guessing. Leave `cutsRelease` and `semverTags` alone, since a release-please pull request legitimately carries version references its own fixed shape explains.
93
89
 
94
- A `pull_request` workflow job now backs the phase-label half for this repository, running `canon labels scan` against the opened title and body. A project holding an older `canon` carries no such job, and one predating this plan carries no `titleFormatIssues` key at all, so the scan above stays required rather than optional.
90
+ 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` key.
95
91
 
96
92
  ### Resolving the pull request
97
93
 
98
94
  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.
99
95
 
100
- `gh pr view` is the form this replaces. It resolves by head branch and ignores state, so a branch name reused after an earlier pull request merged returns the closed one. The detection then takes the edit path and rewrites a merged pull request's title and body, and the run reports that pull request's URL as the one it opened, so nothing surfaces the write landing on the wrong object. Scoping the lookup with `--state open` returns empty there and sends the run down the create path.
96
+ Never detect with `gh pr view`. It resolves by head branch and ignores state, so a branch name reused after an earlier pull request merged returns the closed one and the run rewrites it. Scoping the lookup with `--state open` returns empty there and sends the run down the create path.
101
97
 
102
98
  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 `gh pr create` agree on which pull request the run is about.
103
99
 
@@ -105,23 +101,7 @@ A detached HEAD gives `git branch --show-current` an empty result, which would r
105
101
 
106
102
  ### Labels
107
103
 
108
- Ask the CLI first:
109
-
110
- ```bash
111
- canon labels audit --base <base> --json
112
- ```
113
-
114
- The record carries `labels`, the set this branch earns, and `uncovered`, the changed paths no row of the map reaches. Join `labels` with commas into `pr_labels` below. Report each `uncovered` path beside the result line, naming the map so the reader knows where a row would go, since a surface nobody covered merges bare and nothing else says so.
115
-
116
- Branch on the record rather than on the exit. An operator's shell profile may wrap `canon` in a function whose status comes from a trailing command, and the binary exits 1 for an unknown subcommand and 1 for an ordinary refusal alike.
117
-
118
- A `reason` of `no-map` is the answer that the project declared no map, which earns no labels and no warning: a label set this skill supplied would be a guess about that project's surfaces. Stop there and label nothing.
119
-
120
- Every other `reason` is a map or a range the verb could not read, which is `unreadable-map`, `no-domains`, `no-base`, and `unreadable-changes`, plus `bad-base` for a ref this skill resolved wrongly. Take the fallback below and warn beside the result line, naming the reason. A map with a typo in it still has rows a prefix match can reach, and reading the refusal as an absence would open the pull request with no labels and nothing said, which is the surface merging bare that the verb exists to name.
121
-
122
- The fallback is reading `canon/config/pr-labels.toml`, or `.claude/canon/pr-labels.toml` when the project has not moved, and matching it against the name-only diff per `${CLAUDE_SKILL_DIR}/references/labels.md`. It also covers no record coming back at all, which is an installed `canon` predating the verb, since a skill reaches a target the moment it merges while the CLI reaches one only when a release publishes. Naming both spellings matters exactly here: the binary old enough to need this fallback is the same binary that may predate the move, so the project's map can still sit at the older path. The fallback labels correctly and reports no uncovered path, which is the half only the verb carries.
123
-
124
- Leave `pr_labels` empty when no map resolves or no prefix matches, which skips the labelling command rather than running it against nothing.
104
+ 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`.
125
105
 
126
106
  ### Final command
127
107
 
@@ -170,131 +150,13 @@ The check compares a number against a branch and never derives a number from one
170
150
 
171
151
  The last output line carries `head=` so a caller relaying the number holds a branch to compare it against rather than a bare integer. A caller that writes to the pull request itself, such as a draft mark, runs the same comparison in the shell first, reading `gh pr view <number> --json headRefName,state` and refusing unless it matches `head` and `OPEN`.
172
152
 
173
- ### Find the UI checklist
174
-
175
- `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}/../../standards/slug.md`. This skill is the file's sole consumer. Resolve the main root the way `session-worktree` does (`git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`) and check for the file there. A missing file means no checklist was produced, and the two steps below each skip their checklist half.
176
-
177
- When it exists, scan it against `${CLAUDE_SKILL_DIR}/../../standards/publish.md` before either step posts it, the same as the pull request body above.
178
-
179
- Where it lands is decided by the evidence step below rather than here, since a checklist reads next to the screenshots it annotates and posting it on its own is the fallback for a branch that changed no screenshot.
180
-
181
- ### Find the local server
182
-
183
- 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 for the server this worktree is running before the evidence step, so the first evidence comment already carries the link:
184
-
185
- ```bash
186
- canon pr local --json
187
- ```
188
-
189
- The verb reads the listening sockets on this machine, keeps those whose process runs inside this worktree, and reports the lowest port serving an HTML page. A server in another worktree or in the main checkout is never reported, since it shows a different branch. `canon docs pr-local` states the contract. Read `reason` on the record rather than the exit code.
190
-
191
- - `ok`: hold the record's `url` for the evidence step below.
192
- - `no-server` or `no-listener-reader`: hold the reason for the closing report, pass no link, and move on. Do not start a server here, since one started inside the ship chain has no owner to stop it.
193
- - Anything else, including no record at all: move on silently. An installed binary older than the verb answers with an unknown-subcommand error, which is a skip rather than a stop.
194
-
195
- Pass `--local` to the evidence step only on an `ok` here. The binary answering `ok` is the one carrying the flag, so a binary lacking the verb never meets a flag it would reject.
196
-
197
- With both `--checklist` and `--local`, a diff carrying no evidence image still renders `ok`, with the link opening a comment the checklist closes, so a reviewer gets both in one place. A local link with neither an evidence image nor a checklist still reports `no-evidence`, which keeps a branch that changed no rendered surface from getting a comment holding a link and nothing else.
198
-
199
- A seeded workflow replaces the line with a note once the pull request closes, so the link does not outlive the branch it points at.
200
-
201
- ### Post the evidence comparison
202
-
203
- Run the verb once against the number the pull request step above resolved. Pass `--checklist` when the checklist step found a file, and `--local` when the step above returned `ok`. Leave each flag off otherwise:
204
-
205
- ```bash
206
- canon pr evidence <number> --checklist <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md --local <url> --json
207
- ```
208
-
209
- One call answers both questions because a checklist does not decide `no-evidence`. The verb reports `no-evidence` on a diff carrying no evidence image whether or not a checklist came with it, so the branch below reads the same `reason` it would have read without the flag, and the checklist is folded in only on the path that has a comparison to fold it into. The local link is the one exception, stated in the step above.
210
-
211
- Pass `--checklist` only for a file that exists. The verb refuses as `unreadable-checklist` on a path it cannot read or one holding nothing, which is a caller bug rather than a transient failure, so stop and repair the path rather than posting a body with the checklist silently dropped.
212
-
213
- Read `reason` on the record rather than the exit code.
214
-
215
- - `no-evidence`: nothing changed under an `evidence/` segment, so there is no comparison to post and no body was rendered. Say nothing about the evidence and fall through to the checklist step below.
216
- - `ok`: write `body` to `.canon/tmp/pr/evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post or update the comment:
217
-
218
- ```bash
219
- gh pr comment <number> --body-file <main-root>/.canon/tmp/pr/evidence/body-<number>.md
220
- ```
221
-
222
- When the record carries a `commentId`, edit that comment in place instead of posting a second one, reading the body field from the tmp file with `@`, which needs the typed-field flag `-F` because the raw-string flag `-f` posts the path itself as the body:
223
-
224
- ```bash
225
- gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -F body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md
226
- ```
227
-
228
- Delete the handoff file once that call reports success, per the cleanup below, since the checklist now lives on the pull request. Clean up the tmp body file the same way.
229
-
230
- Any other `reason` is one of the mirrored git refusals (`gh-missing`, `gh-failed`, `no-base`, `unreadable-tree`, `unreadable-changes`). Report it and move on without stopping the chain: a branch that carries no evidence images most of the time should not fail here on a transient git or `gh` read. Fall through to the checklist step, which posts the checklist alone rather than losing it to a transient read.
231
-
232
- ### Post the UI checklist alone
233
-
234
- Run this step only when the evidence step above did not carry the checklist, meaning it reported `no-evidence` or one of the git refusals, and a checklist file exists. A local link sends the checklist through the evidence step instead, so this step runs on a branch with no evidence image only when the local step found no server. Post it as its own comment on `<number>`:
235
-
236
- ```bash
237
- gh pr comment <number> --body-file <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
238
- ```
239
-
240
- Run the cleanup below only once the call that carried the checklist reports success, whichever of the two steps that was. On a failure, stop and leave the file in place: a retry needs the checklist to still be there, and deleting it on a failed post loses the only copy with nothing landed on the pull request.
241
-
242
- The cleanup is a main-root delete, so it goes out as a plain `rm` and then a plain `rmdir`, the file and then the folder, routed the way `session-worktree` states:
243
-
244
- ```bash
245
- rm <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
246
- ```
247
-
248
- ```bash
249
- rmdir <main-root>/.canon/tmp/handoff/ui-checklist 2>/dev/null || true
250
- ```
251
-
252
- The `rmdir` is a no-op when another branch's pending checklist still sits in the folder, which keeps this step from deleting a handoff that is not its own.
253
-
254
- Deleting the file is what makes the later re-render safe. `git-followup` re-runs `canon pr evidence` with no `--checklist`, and the verb carries the checklist forward out of the comment it is editing, so the boxes a reviewer already ticked survive the push.
255
-
256
- ### Post the preview address
257
-
258
- Run this step only when the evidence step above returned `ok` or a checklist was posted, by either step. Either one means the pull request changes a rendered surface, and a reviewer holding a checklist with no screenshots needs the live page most. Otherwise skip it silently.
153
+ ### Post the rendered-surface evidence
259
154
 
260
- The evidence comment is already posted, so the reviewer has the screenshots while the deploy runs. Mint the preview against the same `<number>`:
261
-
262
- ```bash
263
- canon pr preview <number> --json
264
- ```
265
-
266
- The verb dispatches the project's Pages deploy on the pull request's branch and waits on the run for up to 15 minutes. It refuses before dispatching anything when the deploy command passes no `--branch`, since that deploy would publish the branch to production. `canon docs pr-preview` states the contract. Read `reason` on the record rather than the exit code.
267
-
268
- - `ok`: re-render the evidence body with the address on its first line, then post or update it exactly as the evidence step does, through the same tmp file and the `commentId` the record carries:
269
-
270
- ```bash
271
- canon pr evidence <number> --preview <url> --json
272
- ```
273
-
274
- The hosted address takes the first line and a local link already on the comment moves to the second, carried forward with no flag.
275
-
276
- - `no-deploy`: the project deploys nothing a dispatch can start. Say nothing and move on.
277
- - Any other `reason`, being `unfenced`, `no-alias`, `run-failed`, `timeout`, or a mirrored `gh` refusal: report it with the record's `message` and move on without stopping the chain. A missing link costs the reviewer a click, and a held ship costs the whole chain.
155
+ Skip this step silently when no UI checklist sits at `.canon/tmp/handoff/ui-checklist/<slug>.md` at the main worktree root and the diff changes nothing under an `evidence/` segment, since neither half then has anything to post. Otherwise 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.
278
156
 
279
157
  ### Record the number on the task
280
158
 
281
- Write the `number` the final command printed onto the task the branch is closing. Do not resolve it again. A final command that refused on a head mismatch printed no number, so this step writes nothing on that run. `${CLAUDE_SKILL_DIR}/REQUIREMENT.md` states why: a lookup that resolves by branch alone can return a closed pull request sharing that head, so the number is resolved once and reused rather than re-derived.
282
-
283
- The task is the one whose `Plan:` line names the plan this branch implemented. Name that plan by its file, which is `.canon/plans/feature-<slug>.md` at the main worktree root with `<slug>` derived per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. `plan-feature` writes the plan under the branch slug, so the two correspond on any branch that came through the plan-to-execute path. When the session already knows which plan it implemented, because a caller read it earlier in the chain, use that filename instead of re-deriving.
284
-
285
- ```bash
286
- canon tasks pull-request <number> --plan feature-<slug> --json
287
- ```
288
-
289
- The slug is a guess at which plan this branch carries rather than a fact about the task, which is why the verb re-checks it against the board and refuses instead of writing on a near miss. A branch whose slug names no plan file falls to the silent skip below, the same as one whose plan no task cites.
290
-
291
- The verb resolves the board at the main worktree root in-process, adds `Pull request: #NNN` under the `Plan:`, `Groundwork:`, `Intake:`, or `Issue:` lines the task already carries, and corrects the number in place when the line exists. This is the route because the write is an edit inside an existing main-root file, which `session-worktree` routes through a verb. That root is the one `session-worktree` resolves on entry.
292
-
293
- The correction reaches only a line that does not read as a list of `#NNN` entries. A line that does takes the new number appended rather than replaced, reported as `appended`, so a task shipped in slices keeps every pull request.
294
-
295
- Skip this silently when the record is `ok: false` and `reason` is `no-board`, `no-match`, or `ambiguous`. Those are the three cases a guessed write would compound: no board, no task naming the plan, or more than one. One pull request names one task, and a wrong match archives the wrong task unattended once the branch merges. Report any other refusal rather than swallowing it.
296
-
297
- The number is what lets the merge close the task. Every merge on `main` is a squash carrying it in the subject, so the number survives where a branch name does not, and `post-merge` reads it back to call `canon tasks archive`. Writing it here rather than at worktree time is what makes it a pull request number rather than a branch the squash discards.
159
+ 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.
298
160
 
299
161
  ## After execution
300
162
 
@@ -0,0 +1,114 @@
1
+ ---
2
+ title: Rendered-surface evidence
3
+ description: How git-pr finds the UI checklist and the local server, posts the evidence comparison or the checklist alone, and adds the preview address, for a change that touches a rendered surface
4
+ ---
5
+
6
+ # Rendered-surface evidence
7
+
8
+ The rendered-surface step of `git-pr`, reached after the final command printed the pull request number. The session reads this file when a UI checklist exists or the diff changes a path under an `evidence/` segment.
9
+
10
+ ## Find the UI checklist
11
+
12
+ `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}/../../standards/slug.md`. This skill is the file's sole consumer. Resolve the main root the way `session-worktree` does (`git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`) and check for the file there. A missing file means no checklist was produced, and the two steps below each skip their checklist half.
13
+
14
+ When it exists, scan it against `${CLAUDE_SKILL_DIR}/../../standards/publish.md` before either step posts it, the same as the pull request body in the skill's pre-publish scan.
15
+
16
+ Where it lands is decided by the evidence step below rather than here, since a checklist reads next to the screenshots it annotates and posting it on its own is the fallback for a branch that changed no screenshot.
17
+
18
+ ## Find the local server
19
+
20
+ 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 for the server this worktree is running before the evidence step, so the first evidence comment already carries the link:
21
+
22
+ ```bash
23
+ canon pr local --json
24
+ ```
25
+
26
+ The verb reads the listening sockets on this machine, keeps those whose process runs inside this worktree, and reports the lowest port serving an HTML page. A server in another worktree or in the main checkout is never reported, since it shows a different branch. `canon docs pr-local` states the contract. Read `reason` on the record rather than the exit code.
27
+
28
+ - `ok`: hold the record's `url` for the evidence step below.
29
+ - `no-server` or `no-listener-reader`: hold the reason for the closing report, pass no link, and move on. Do not start a server here, since one started inside the ship chain has no owner to stop it.
30
+ - Anything else, including no record at all: move on silently. An installed binary older than the verb answers with an unknown-subcommand error, which is a skip rather than a stop.
31
+
32
+ Pass `--local` to the evidence step only on an `ok` here. The binary answering `ok` is the one carrying the flag, so a binary lacking the verb never meets a flag it would reject.
33
+
34
+ With both `--checklist` and `--local`, a diff carrying no evidence image still renders `ok`, with the link opening a comment the checklist closes, so a reviewer gets both in one place. A local link with neither an evidence image nor a checklist still reports `no-evidence`, which keeps a branch that changed no rendered surface from getting a comment holding a link and nothing else.
35
+
36
+ A seeded workflow replaces the line with a note once the pull request closes, so the link does not outlive the branch it points at.
37
+
38
+ ## Post the evidence comparison
39
+
40
+ Run the verb once against the number the skill's final command printed. Pass `--checklist` when the checklist step found a file, and `--local` when the step above returned `ok`. Leave each flag off otherwise:
41
+
42
+ ```bash
43
+ canon pr evidence <number> --checklist <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md --local <url> --json
44
+ ```
45
+
46
+ One call answers both questions because a checklist does not decide `no-evidence`. The verb reports `no-evidence` on a diff carrying no evidence image whether or not a checklist came with it, so the branch below reads the same `reason` it would have read without the flag, and the checklist is folded in only on the path that has a comparison to fold it into. The local link is the one exception, stated in the step above.
47
+
48
+ Pass `--checklist` only for a file that exists. The verb refuses as `unreadable-checklist` on a path it cannot read or one holding nothing, which is a caller bug rather than a transient failure, so stop and repair the path rather than posting a body with the checklist silently dropped.
49
+
50
+ Read `reason` on the record rather than the exit code.
51
+
52
+ - `no-evidence`: nothing changed under an `evidence/` segment, so there is no comparison to post and no body was rendered. Say nothing about the evidence and fall through to the checklist step below.
53
+ - `ok`: write `body` to `.canon/tmp/pr/evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post or update the comment:
54
+
55
+ ```bash
56
+ gh pr comment <number> --body-file <main-root>/.canon/tmp/pr/evidence/body-<number>.md
57
+ ```
58
+
59
+ When the record carries a `commentId`, edit that comment in place instead of posting a second one, reading the body field from the tmp file with `@`, which needs the typed-field flag `-F` because the raw-string flag `-f` posts the path itself as the body:
60
+
61
+ ```bash
62
+ gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -F body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md
63
+ ```
64
+
65
+ Delete the handoff file once that call reports success, per the cleanup below, since the checklist now lives on the pull request. Clean up the tmp body file the same way.
66
+
67
+ Any other `reason` is one of the mirrored git refusals (`gh-missing`, `gh-failed`, `no-base`, `unreadable-tree`, `unreadable-changes`). Report it and move on without stopping the chain: a branch that carries no evidence images most of the time should not fail here on a transient git or `gh` read. Fall through to the checklist step, which posts the checklist alone rather than losing it to a transient read.
68
+
69
+ ## Post the UI checklist alone
70
+
71
+ Run this step only when the evidence step above did not carry the checklist, meaning it reported `no-evidence` or one of the git refusals, and a checklist file exists. A local link sends the checklist through the evidence step instead, so this step runs on a branch with no evidence image only when the local step found no server. Post it as its own comment on `<number>`:
72
+
73
+ ```bash
74
+ gh pr comment <number> --body-file <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
75
+ ```
76
+
77
+ Run the cleanup below only once the call that carried the checklist reports success, whichever of the two steps that was. On a failure, stop and leave the file in place: a retry needs the checklist to still be there, and deleting it on a failed post loses the only copy with nothing landed on the pull request.
78
+
79
+ The cleanup is a main-root delete, so it goes out as a plain `rm` and then a plain `rmdir`, the file and then the folder, routed the way `session-worktree` states:
80
+
81
+ ```bash
82
+ rm <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
83
+ ```
84
+
85
+ ```bash
86
+ rmdir <main-root>/.canon/tmp/handoff/ui-checklist 2>/dev/null || true
87
+ ```
88
+
89
+ The `rmdir` is a no-op when another branch's pending checklist still sits in the folder, which keeps this step from deleting a handoff that is not its own.
90
+
91
+ Deleting the file is what makes the later re-render safe. `git-followup` re-runs `canon pr evidence` with no `--checklist`, and the verb carries the checklist forward out of the comment it is editing, so the boxes a reviewer already ticked survive the push.
92
+
93
+ ## Post the preview address
94
+
95
+ Run this step only when the evidence step above returned `ok` or a checklist was posted, by either step. Either one means the pull request changes a rendered surface, and a reviewer holding a checklist with no screenshots needs the live page most. Otherwise skip it silently.
96
+
97
+ The evidence comment is already posted, so the reviewer has the screenshots while the deploy runs. Mint the preview against the same `<number>`:
98
+
99
+ ```bash
100
+ canon pr preview <number> --json
101
+ ```
102
+
103
+ The verb dispatches the project's Pages deploy on the pull request's branch and waits on the run for up to 15 minutes. It refuses before dispatching anything when the deploy command passes no `--branch`, since that deploy would publish the branch to production. `canon docs pr-preview` states the contract. Read `reason` on the record rather than the exit code.
104
+
105
+ - `ok`: re-render the evidence body with the address on its first line, then post or update it exactly as the evidence step does, through the same tmp file and the `commentId` the record carries:
106
+
107
+ ```bash
108
+ canon pr evidence <number> --preview <url> --json
109
+ ```
110
+
111
+ The hosted address takes the first line and a local link already on the comment moves to the second, carried forward with no flag.
112
+
113
+ - `no-deploy`: the project deploys nothing a dispatch can start. Say nothing and move on.
114
+ - Any other `reason`, being `unfenced`, `no-alias`, `run-failed`, `timeout`, or a mirrored `gh` refusal: report it with the record's `message` and move on without stopping the chain. A missing link costs the reviewer a click, and a held ship costs the whole chain.
@@ -93,3 +93,23 @@ The refusal names the label it rejected and applies none of the set, so a warnin
93
93
  ## Release pull requests
94
94
 
95
95
  Release automation opens its own pull requests without this skill and applies its own labels, so nothing here needs a skip condition for them.
96
+
97
+ ## Labels at run time
98
+
99
+ The labels step of `git-pr` reads from here. Ask the CLI first:
100
+
101
+ ```bash
102
+ canon labels audit --base <base> --json
103
+ ```
104
+
105
+ The record carries `labels`, the set this branch earns, and `uncovered`, the changed paths no row of the map reaches. Join `labels` with commas into `pr_labels`, which the skill's final command takes. Report each `uncovered` path beside the result line, naming the map so the reader knows where a row would go, since a surface nobody covered merges bare and nothing else says so.
106
+
107
+ Branch on the record rather than on the exit. An operator's shell profile may wrap `canon` in a function whose status comes from a trailing command, and the binary exits 1 for an unknown subcommand and 1 for an ordinary refusal alike.
108
+
109
+ A `reason` of `no-map` is the answer that the project declared no map, which earns no labels and no warning: a label set this skill supplied would be a guess about that project's surfaces. Stop there and label nothing.
110
+
111
+ Every other `reason` is a map or a range the verb could not read, which is `unreadable-map`, `no-domains`, `no-base`, and `unreadable-changes`, plus `bad-base` for a ref this skill resolved wrongly. Take the fallback below and warn beside the result line, naming the reason. A map with a typo in it still has rows a prefix match can reach, and reading the refusal as an absence would open the pull request with no labels and nothing said, which is the surface merging bare that the verb exists to name.
112
+
113
+ The fallback is reading `canon/config/pr-labels.toml`, or `.claude/canon/pr-labels.toml` when the project has not moved, and matching it against the name-only diff per `## Matching` above. It also covers no record coming back at all, which is an installed `canon` predating the verb, since a skill reaches a target the moment it merges while the CLI reaches one only when a release publishes. Naming both spellings matters exactly here: the binary old enough to need this fallback is the same binary that may predate the move, so the project's map can still sit at the older path. The fallback labels correctly and reports no uncovered path, which is the half only the verb carries.
114
+
115
+ Leave `pr_labels` empty when no map resolves or no prefix matches, which skips the labelling command rather than running it against nothing.
@@ -0,0 +1,26 @@
1
+ ---
2
+ title: Record the number on the task
3
+ description: How git-pr writes the pull request number the final command printed onto the task whose Plan line names the branch's plan, and which refusals it skips silently
4
+ ---
5
+
6
+ # Record the number on the task
7
+
8
+ The last step of `git-pr` before its report, reached on every run whose final command printed a number.
9
+
10
+ Write the `number` the final command printed onto the task the branch is closing. Do not resolve it again. A final command that refused on a head mismatch printed no number, so this step writes nothing on that run. `${CLAUDE_SKILL_DIR}/REQUIREMENT.md` states why: a lookup that resolves by branch alone can return a closed pull request sharing that head, so the number is resolved once and reused rather than re-derived.
11
+
12
+ The task is the one whose `Plan:` line names the plan this branch implemented. Name that plan by its file, which is `.canon/plans/feature-<slug>.md` at the main worktree root with `<slug>` derived per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. `plan-feature` writes the plan under the branch slug, so the two correspond on any branch that came through the plan-to-execute path. When the session already knows which plan it implemented, because a caller read it earlier in the chain, use that filename instead of re-deriving.
13
+
14
+ ```bash
15
+ canon tasks pull-request <number> --plan feature-<slug> --json
16
+ ```
17
+
18
+ The slug is a guess at which plan this branch carries rather than a fact about the task, which is why the verb re-checks it against the board and refuses instead of writing on a near miss. A branch whose slug names no plan file falls to the silent skip below, the same as one whose plan no task cites.
19
+
20
+ The verb resolves the board at the main worktree root in-process, adds `Pull request: #NNN` under the `Plan:`, `Groundwork:`, `Intake:`, or `Issue:` lines the task already carries, and corrects the number in place when the line exists. This is the route because the write is an edit inside an existing main-root file, which `session-worktree` routes through a verb. That root is the one `session-worktree` resolves on entry.
21
+
22
+ The correction reaches only a line that does not read as a list of `#NNN` entries. A line that does takes the new number appended rather than replaced, reported as `appended`, so a task shipped in slices keeps every pull request.
23
+
24
+ Skip this silently when the record is `ok: false` and `reason` is `no-board`, `no-match`, or `ambiguous`. Those are the three cases a guessed write would compound: no board, no task naming the plan, or more than one. One pull request names one task, and a wrong match archives the wrong task unattended once the branch merges. Report any other refusal rather than swallowing it.
25
+
26
+ The number is what lets the merge close the task. Every merge on `main` is a squash carrying it in the subject, so the number survives where a branch name does not, and `post-merge` reads it back to call `canon tasks archive`. Writing it here rather than at worktree time is what makes it a pull request number rather than a branch the squash discards.
@@ -24,7 +24,7 @@ Run `git diff --cached --name-only 2>/dev/null` to check for staged files. If ou
24
24
  ## Sequence
25
25
 
26
26
  1. Invoke `canon:memory-capture` to route what this session learned to the context entries that own it and write the residue to `.canon/memory/`
27
- 2. Invoke `canon:docs-fold` to sync internal planning docs against session decisions, folding in the routed facts
27
+ 2. Invoke `canon:context-fold` to sync internal planning docs against session decisions, folding in the routed facts
28
28
  3. Invoke `canon:docs-sync` to sync public docs against changes since main
29
29
  4. Run `git add -A` to stage any files the sync skills wrote
30
30
  5. Run `canon tasks plan-reach <plan> --json` and report both lists it carries. Name the plan this branch built under, by path or by slug. Read `claimed` first and say who holds each path, since that is the half a reader acts on, then say how many of the changed paths `undeclared` names. Branch on the record rather than on the exit code, which a shell function wrapping `canon` can flatten to zero. This step reports and never stops the sequence.
@@ -8,7 +8,7 @@ description: Groups staged files by concern and generates one conventional commi
8
8
  Read these files in parallel:
9
9
 
10
10
  - `${CLAUDE_SKILL_DIR}/../../standards/commit.md`: format, types, scopes, and constraints
11
- - `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: banned words, punctuation, and formatting for all generated text
11
+ - `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: punctuation and formatting for all generated text
12
12
  - The `write-human` skill: voice, rhythm, and sentence construction for all generated text
13
13
 
14
14
  ## Context
@@ -27,7 +27,7 @@ The threshold is what the remaining folder lives or dies on. A first-occurrence
27
27
 
28
28
  ## Must not
29
29
 
30
- - Edit a context entry, which `docs-fold` owns
30
+ - Edit a context entry, which `context-fold` owns
31
31
  - Route a feedback, user, or reference candidate, since no context entry owns how to work or who to ask
32
32
  - Route anything when the caller does not commit, because a context entry is a tracked file
33
33
  - Hand-append a row to the memory index, which is generated from sibling frontmatter
@@ -45,6 +45,6 @@ The threshold is what the remaining folder lives or dies on. A first-occurrence
45
45
 
46
46
  ## Out of scope
47
47
 
48
- - Editing the context entries themselves, which `docs-fold` owns on its own pass
48
+ - Editing the context entries themselves, which `context-fold` owns on its own pass
49
49
  - Curating what is already in the folder, which `memory-review` owns
50
50
  - Promoting an entry into an instruction file or a skill body, which mutates how the agent operates and ships as its own change
@@ -27,7 +27,7 @@ Read in parallel, skipping any that do not exist:
27
27
  - `CLAUDE.md`: the project's write location and any rule it states over the folder
28
28
  - `.canon/memory/index.md`: existing index, to avoid duplicates
29
29
  - `canon/context/index.md`: the domain catalog Step 3 routes against, read under `.claude/` instead in a project the surface move has not reached
30
- - `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: banned words, punctuation, and formatting applied to memory file bodies
30
+ - `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: punctuation and formatting applied to memory file bodies
31
31
  - The `write-human` skill: voice, rhythm, and sentence construction applied to memory file bodies
32
32
 
33
33
  ## Step 2: classify candidates
@@ -44,7 +44,7 @@ For each project candidate, match its subject against `canon/context/index.md`.
44
44
 
45
45
  Fail closed. A project candidate matching no entry stays a memory file, and so does one matching two entries where neither is clearly the owner. The residue is what the folder is for, and a fact filed under the wrong entry is worse than one in memory because a context entry is a surface sessions trust.
46
46
 
47
- Do not edit a context entry here. `docs-fold` owns those edits and folds the routed facts in on its own pass, or two skills write one file at the same step. Write each routed fact to `.canon/tmp/handoff/memory-routing/<slug>.md` at the main worktree root instead, appending when the file exists. Name the heading with the entry's own path from `canon/context/index.md`, flat or the nested `index.md`, since that heading is what tells `docs-fold`'s routed-facts fold which file to open. An append is a whole-file operation, so send it as a heredoc:
47
+ Do not edit a context entry here. `context-fold` owns those edits and folds the routed facts in on its own pass, or two skills write one file at the same step. Write each routed fact to `.canon/tmp/handoff/memory-routing/<slug>.md` at the main worktree root instead, appending when the file exists. Name the heading with the entry's own path from `canon/context/index.md`, flat or the nested `index.md`, since that heading is what tells `context-fold`'s routed-facts fold which file to open. An append is a whole-file operation, so send it as a heredoc:
48
48
 
49
49
  A flat domain takes:
50
50
 
@@ -64,7 +64,7 @@ A domain split into a folder takes its own generated index instead:
64
64
 
65
65
  Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to `latest` on an empty result.
66
66
 
67
- The handoff is a file rather than a spoken result so the routed fact survives a compaction between this step and the `docs-fold` pass, and so the standalone caller leaves something behind for a later `/docs-fold` to consume.
67
+ The handoff is a file rather than a spoken result so the routed fact survives a compaction between this step and the `context-fold` pass, and so the standalone caller leaves something behind for a later `/context-fold` to consume.
68
68
 
69
69
  ## Step 4: dedupe
70
70
 
@@ -94,11 +94,11 @@ Respond with one line per fact routed, written, or updated:
94
94
  - `✅ Wrote: .canon/memory/<file> (<type>)`
95
95
  - `✏️ Updated: .canon/memory/<file> (<type>)`
96
96
 
97
- When anything routed, add a line naming the handoff so the caller knows a `docs-fold` pass is owed:
97
+ When anything routed, add a line naming the handoff so the caller knows a `context-fold` pass is owed:
98
98
 
99
- `→ Routed facts wait at .canon/tmp/handoff/memory-routing/<slug>.md. Run /docs-fold to fold them in.`
99
+ `→ Routed facts wait at .canon/tmp/handoff/memory-routing/<slug>.md. Run /context-fold to fold them in.`
100
100
 
101
- Omit that line when the caller runs `docs-fold` itself later in its own chain.
101
+ Omit that line when the caller runs `context-fold` itself later in its own chain.
102
102
 
103
103
  If nothing was captured, output:
104
104
 
@@ -15,7 +15,7 @@ A pen the caller cannot face is the same gap wearing a different shape. Routing
15
15
 
16
16
  - Treat the folder as a holding pen, so every entry in scope leaves it as a promotion, a handoff, or an archive rather than surviving by default
17
17
  - Archive an entry out of the pen rather than deleting it, since nothing recovers a file from a gitignored folder
18
- - Hand a fact a context entry owns to `docs-fold` through the routing file, rather than editing the entry here
18
+ - Hand a fact a context entry owns to `context-fold` through the routing file, rather than editing the entry here
19
19
  - Verify the rule is not already stated or implied in the target before proposing a promotion, by reading the target rather than trusting the memory's claim about it
20
20
  - Rewrite a rule into the destination's voice instead of moving it unchanged
21
21
  - Write the proposal to a receipt on disk and take no action until the user decides per item