@erclx/canon 5.38.0 → 5.40.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 (246) hide show
  1. package/README.md +5 -5
  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 +37 -18
  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 +9 -7
  43. package/claude/skills/draft-slides/references/deck.md +183 -0
  44. package/claude/skills/git-branch/SKILL.md +3 -3
  45. package/claude/skills/git-commit/SKILL.md +5 -3
  46. package/claude/skills/git-followup/SKILL.md +16 -9
  47. package/claude/skills/git-issue/SKILL.md +6 -5
  48. package/claude/skills/git-pr/SKILL.md +56 -44
  49. package/claude/skills/git-pr/references/evidence.md +2 -2
  50. package/claude/skills/git-pr/references/final-command.md +1 -1
  51. package/claude/skills/git-pr/references/labels.md +1 -1
  52. package/claude/skills/git-pr/references/publish.md +1 -1
  53. package/claude/skills/git-ship/SKILL.md +43 -34
  54. package/claude/skills/git-ship/references/ci-watch.md +31 -0
  55. package/claude/skills/git-split/SKILL.md +28 -100
  56. package/claude/skills/git-split/references/independent.md +40 -0
  57. package/claude/skills/git-split/references/stacked.md +73 -0
  58. package/claude/skills/git-stage/SKILL.md +9 -7
  59. package/claude/skills/git-worktree/SKILL.md +27 -49
  60. package/claude/skills/git-worktree/references/cleanup.md +55 -0
  61. package/claude/skills/markdown-craft/SKILL.md +2 -2
  62. package/claude/skills/markdown-craft/references/markdown.md +82 -53
  63. package/claude/skills/plan-feature/SKILL.md +52 -46
  64. package/claude/skills/plan-feature/references/batches.md +46 -0
  65. package/claude/skills/plan-groundwork/SKILL.md +60 -74
  66. package/claude/skills/plan-groundwork/references/output.md +48 -0
  67. package/claude/skills/plan-intake/SKILL.md +28 -21
  68. package/claude/skills/plan-intake-answer/SKILL.md +14 -13
  69. package/claude/skills/read-frames/SKILL.md +2 -0
  70. package/claude/skills/record-screencast/SKILL.md +31 -11
  71. package/claude/skills/review-address/SKILL.md +76 -158
  72. package/claude/skills/review-address/references/rebase-only.md +34 -0
  73. package/claude/skills/review-craft/SKILL.md +27 -11
  74. package/claude/skills/review-pr/SKILL.md +91 -40
  75. package/claude/skills/review-ui/SKILL.md +63 -37
  76. package/claude/skills/review-ui/references/hosted-preview.md +30 -0
  77. package/claude/skills/role-orchestrator/SKILL.md +105 -74
  78. package/claude/skills/role-orchestrator/references/orchestrator-filing.md +26 -0
  79. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
  80. package/claude/skills/role-planner/SKILL.md +66 -64
  81. package/claude/skills/role-reviewer/SKILL.md +57 -46
  82. package/claude/skills/role-worker/SKILL.md +68 -63
  83. package/claude/skills/search-craft/SKILL.md +7 -5
  84. package/claude/skills/session-compact/SKILL.md +16 -11
  85. package/claude/skills/session-map/SKILL.md +31 -10
  86. package/claude/skills/session-relay/SKILL.md +32 -65
  87. package/claude/skills/session-resume/SKILL.md +18 -13
  88. package/claude/skills/session-worktree/SKILL.md +70 -42
  89. package/claude/skills/systematic-debugging/SKILL.md +6 -1
  90. package/claude/skills/target-check/SKILL.md +13 -6
  91. package/claude/skills/target-setup/SKILL.md +39 -27
  92. package/claude/skills/task-board/SKILL.md +68 -60
  93. package/claude/skills/task-board/references/board.md +1 -1
  94. package/claude/skills/task-board/references/create-report.md +36 -0
  95. package/claude/skills/task-board/references/tasks.md +1 -1
  96. package/claude/skills/task-complete/SKILL.md +20 -18
  97. package/claude/skills/teach-workspace/SKILL.md +61 -33
  98. package/claude/skills/test-craft/SKILL.md +2 -1
  99. package/claude/skills/test-first/SKILL.md +15 -9
  100. package/claude/skills/ui-checklist/SKILL.md +18 -16
  101. package/claude/skills/ux-audit/SKILL.md +9 -10
  102. package/claude/skills/ux-measure/SKILL.md +23 -19
  103. package/claude/skills/video-craft/SKILL.md +15 -7
  104. package/claude/skills/write-human/SKILL.md +9 -1
  105. package/claude/skills/youtube-transcripts/SKILL.md +2 -1
  106. package/docs/agents/audits.md +61 -60
  107. package/docs/agents/context-audit.md +40 -58
  108. package/docs/agents/index.md +11 -61
  109. package/docs/agents/indexes.md +25 -29
  110. package/docs/agents/install-and-sync.md +125 -235
  111. package/docs/agents/markdown-audit.md +54 -96
  112. package/docs/agents/migrate.md +41 -16
  113. package/docs/agents/output-shape.md +60 -45
  114. package/docs/agents/overview.md +36 -12
  115. package/docs/agents/records.md +37 -79
  116. package/docs/agents/sessions-transfer.md +26 -70
  117. package/docs/agents/targets.md +25 -77
  118. package/docs/target/index.md +0 -1
  119. package/docs/target/migrations.md +57 -66
  120. package/docs/target/projects.md +36 -78
  121. package/docs/target/scenarios.md +9 -19
  122. package/docs/target/sync.md +27 -68
  123. package/docs/workflow/ai-workflow.md +22 -68
  124. package/docs/workflow/operating-model.md +32 -186
  125. package/docs/workflow/parallel-features.md +29 -66
  126. package/docs/workflow/skill-map-on-request.md +22 -24
  127. package/docs/workflow/skill-map.md +72 -76
  128. package/docs/workflow/visual-design-workflow.md +25 -118
  129. package/docs/workflow/zshrc-aliases.md +18 -37
  130. package/package.json +1 -1
  131. package/src/canvas/client/inspector/fields.css +4 -0
  132. package/src/canvas/index.html +6 -0
  133. package/src/claude/skills-reach.ts +2 -2
  134. package/src/cli/ui.ts +3 -1
  135. package/src/commands/canvas.ts +23 -0
  136. package/src/commands/capture.ts +14 -0
  137. package/src/commands/claude.ts +14 -1
  138. package/src/commands/context.ts +106 -7
  139. package/src/commands/demo.ts +22 -0
  140. package/src/commands/deps.ts +6 -0
  141. package/src/commands/driver.ts +28 -2
  142. package/src/commands/gov.ts +52 -18
  143. package/src/commands/hooks.ts +63 -0
  144. package/src/commands/intake.ts +17 -0
  145. package/src/commands/labels.ts +38 -5
  146. package/src/commands/markdown.ts +169 -32
  147. package/src/commands/pr.ts +160 -43
  148. package/src/commands/records.ts +8 -0
  149. package/src/commands/secrets.ts +6 -0
  150. package/src/commands/sessions.ts +8 -0
  151. package/src/commands/tasks.ts +52 -0
  152. package/src/commands/teach.ts +13 -0
  153. package/src/commands/upstream.ts +25 -1
  154. package/src/commands/worktrees.ts +5 -0
  155. package/src/context/audit.ts +274 -5
  156. package/src/context/classify/prompts.ts +1 -1
  157. package/src/context/folders.ts +1 -1
  158. package/src/context/gate.ts +23 -7
  159. package/src/context/shape-gated.ts +30 -0
  160. package/src/counts/scan.ts +1 -1
  161. package/src/design/components.ts +1 -1
  162. package/src/design/document.ts +1 -1
  163. package/src/design/tokens.ts +1 -1
  164. package/src/gate/boundaries.ts +4 -1
  165. package/src/gate/measures.ts +16 -191
  166. package/src/gate/stages.ts +14 -30
  167. package/src/git/env.ts +6 -0
  168. package/src/hooks/reply-links.ts +110 -0
  169. package/src/markdown/density.ts +258 -0
  170. package/src/markdown/gate.ts +57 -18
  171. package/src/markdown/reference-shape.ts +103 -0
  172. package/src/markdown/scope.ts +162 -0
  173. package/src/markdown/structure.ts +9 -59
  174. package/src/pr/lift.ts +153 -19
  175. package/src/pr/painted.ts +87 -0
  176. package/src/roots/record.ts +18 -1
  177. package/src/sandbox/census.ts +1 -1
  178. package/src/sandbox/headless/run.ts +3 -2
  179. package/src/sandbox/headless/watch.ts +3 -4
  180. package/src/shipped/references.ts +1 -1
  181. package/src/slides/convert/master.ts +3 -0
  182. package/src/sync/format.ts +46 -0
  183. package/src/sync/stamp.ts +6 -0
  184. package/tooling/astro/reference.md +2 -2
  185. package/tooling/base/reference.md +1 -1
  186. package/tooling/claude/seeds/.claude/hooks/reply-links.sh +72 -0
  187. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +22 -1
  188. package/tooling/claude/seeds/.claude/settings.json +4 -0
  189. package/tooling/go/reference.md +1 -1
  190. package/tooling/nextjs/reference.md +1 -1
  191. package/tooling/php/reference.md +1 -1
  192. package/tooling/python/reference.md +1 -1
  193. package/tooling/vite-react/reference.md +1 -1
  194. package/tooling/web/reference.md +1 -1
  195. package/docs/agents/canvas.md +0 -86
  196. package/docs/agents/capture.md +0 -65
  197. package/docs/agents/census.md +0 -23
  198. package/docs/agents/commands.md +0 -154
  199. package/docs/agents/comments.md +0 -34
  200. package/docs/agents/context-audit-checks.md +0 -90
  201. package/docs/agents/context-classify.md +0 -101
  202. package/docs/agents/counts.md +0 -74
  203. package/docs/agents/demo.md +0 -140
  204. package/docs/agents/docs.md +0 -27
  205. package/docs/agents/drift-surfaces.md +0 -201
  206. package/docs/agents/driver.md +0 -103
  207. package/docs/agents/gate.md +0 -90
  208. package/docs/agents/gov-detect.md +0 -47
  209. package/docs/agents/hooks.md +0 -58
  210. package/docs/agents/intake.md +0 -81
  211. package/docs/agents/key-changes.md +0 -116
  212. package/docs/agents/label-coverage.md +0 -86
  213. package/docs/agents/markdown-audit-weight.md +0 -81
  214. package/docs/agents/pr-evidence-layout.md +0 -70
  215. package/docs/agents/pr-evidence.md +0 -273
  216. package/docs/agents/pr-frames.md +0 -131
  217. package/docs/agents/pr-lift.md +0 -132
  218. package/docs/agents/pr-local.md +0 -111
  219. package/docs/agents/pr-preview.md +0 -155
  220. package/docs/agents/pr-reads.md +0 -111
  221. package/docs/agents/pr-tick.md +0 -72
  222. package/docs/agents/records-push.md +0 -61
  223. package/docs/agents/records-stale.md +0 -40
  224. package/docs/agents/records-validate.md +0 -57
  225. package/docs/agents/restated.md +0 -83
  226. package/docs/agents/routing.md +0 -61
  227. package/docs/agents/rule-citations.md +0 -90
  228. package/docs/agents/sandbox.md +0 -126
  229. package/docs/agents/scripting.md +0 -142
  230. package/docs/agents/sessions.md +0 -128
  231. package/docs/agents/skills-audit.md +0 -120
  232. package/docs/agents/skills-reach.md +0 -64
  233. package/docs/agents/slides.md +0 -176
  234. package/docs/agents/state-scoped-risk.md +0 -105
  235. package/docs/agents/superseded.md +0 -85
  236. package/docs/agents/tasks-archive.md +0 -83
  237. package/docs/agents/tasks-plans.md +0 -152
  238. package/docs/agents/tasks-validate.md +0 -133
  239. package/docs/agents/tasks.md +0 -123
  240. package/docs/agents/teach-authoring.md +0 -118
  241. package/docs/agents/teach.md +0 -157
  242. package/docs/agents/test-order.md +0 -58
  243. package/docs/agents/upstream.md +0 -94
  244. package/docs/agents/worktrees.md +0 -118
  245. package/docs/target/standards-moved.md +0 -34
  246. package/src/markdown/growth.ts +0 -43
@@ -1,155 +0,0 @@
1
- ---
2
- title: The pull request preview deploy
3
- description: How canon pr preview finds a fenced Cloudflare Pages deploy, refuses a branch its push filter does not serve, dispatches it on a pull request's branch, reads the alias the run prints, the refusal reasons it names, and why an unfenced deploy is refused before anything runs
4
- ---
5
-
6
- # The pull request preview deploy
7
-
8
- `canon pr preview` publishes a pull request's branch to a Cloudflare Pages
9
- preview and reports the address. `git-pr` calls it after the evidence
10
- comparison, then re-renders that comment through `canon pr evidence --preview`
11
- so the link sits on the address line under its heading. A reviewer gets a page
12
- to click into before merging rather than screenshots and a checklist alone.
13
-
14
- ```bash
15
- canon pr preview --json
16
- canon pr preview 1341 --json --timeout 20
17
- canon pr preview 1341 --check --json
18
- ```
19
-
20
- ## Which workflow it dispatches
21
-
22
- The verb reads `.github/workflows/*.yml` and `*.yaml` in the checkout and picks
23
- the first file, in path order, that meets four tests:
24
-
25
- - It runs `pages deploy`.
26
- - It carries a `workflow_dispatch` trigger, since nothing else lets a caller
27
- start it on a branch.
28
- - Its deploy passes `--branch=${{ github.ref_name }}`.
29
- - It prints a `canon-preview-alias:` line.
30
-
31
- The workflow is read before the pull request, so a project with no fenced
32
- deploy is refused without a network call.
33
-
34
- ## Which branches it serves
35
-
36
- A dispatch runs the workflow whatever its `on.push.paths` filter says, so a
37
- branch that touched nothing the site builds from would get a preview showing
38
- none of its change. The verb reads the pull request's changed paths and matches
39
- them against the picked workflow's own `on.push.paths` or `on.push.paths-ignore`
40
- list, applying `!` patterns in order the way GitHub does. When nothing matches,
41
- it refuses as `unserved` and dispatches nothing. `--check` answers `unserved`
42
- the same way.
43
-
44
- The push filter is the one statement of what the site builds from, since it
45
- already decides when production deploys. A workflow with neither list, with no
46
- push trigger, or that does not parse as YAML serves every change, so a project
47
- whose every change ships keeps minting for every branch.
48
-
49
- A removed path counts, the same as GitHub's own push filter counts it, so a
50
- branch whose only site change deletes a file still gets a preview.
51
-
52
- ## Why an unfenced deploy is refused
53
-
54
- Cloudflare Pages publishes a deploy that names no branch to production. A
55
- dispatch from an unmerged branch through such a workflow ships that branch to
56
- the live site, so the verb refuses as `unfenced` and dispatches nothing. The
57
- fix is one flag on the deploy command, and the cloudflare stack's
58
- `deploy.yml` already carries it.
59
-
60
- ## Where the address comes from
61
-
62
- The workflow prints the alias Cloudflare returned for the deploy, read from
63
- `wrangler-action`'s `pages-deployment-alias-url` output, on a fixed line:
64
-
65
- ```plaintext
66
- canon-preview-alias: https://<alias>.<project>.pages.dev
67
- ```
68
-
69
- The verb reads that line out of the finished run's log rather than computing
70
- the address from the branch name. Cloudflare flattens a branch into its alias
71
- and may truncate it, and a computed address that is wrong is worse than none.
72
- The output needs wrangler 3.78 or later, so the workflow pins
73
- `wranglerVersion`.
74
-
75
- `gh workflow run` reports no run id. The verb lists the branch's dispatched
76
- runs before dispatching and takes the one id that appears afterwards, which
77
- keeps the match independent of the local clock.
78
-
79
- ## What the record carries
80
-
81
- `reason` on the record is what a caller branches on, not the exit code:
82
-
83
- | Reason | What it means |
84
- | -------------------- | -------------------------------------------------------------------------- |
85
- | `ok` | The preview was published. `url` carries the address and `runId` the run. |
86
- | `no-deploy` | No dispatchable workflow runs `pages deploy`. |
87
- | `unserved` | The push path filter matches no changed path, so nothing was dispatched. |
88
- | `unreadable-changes` | The pull request's changed paths could not be read. |
89
- | `unfenced` | The deploy passes no `--branch`, so nothing was dispatched. |
90
- | `no-alias` | The workflow prints no alias line, or the finished run's log carried none. |
91
- | `run-failed` | The deploy run finished without succeeding. `runId` names it. |
92
- | `timeout` | The run did not finish inside `--timeout` minutes, 15 by default. |
93
- | `bad-timeout` | `--timeout` was not a positive number. |
94
- | `gh-missing` | `gh` is not on the path. |
95
- | `gh-failed` | `gh` could not read the pull request, or list or dispatch the workflow. |
96
- | `no-branch` | The pull request carries no head branch name. |
97
- | `check-timeout` | `--check` was combined with `--timeout`. |
98
-
99
- The exit is 0 on `ok` and 1 on every refusal. A `timeout` leaves the run
100
- going, so the preview may still land after the verb has given up on it.
101
-
102
- ## Checking which head a preview was built from
103
-
104
- `--check` dispatches nothing and waits on nothing. It lists the branch's
105
- `workflow_dispatch` runs, takes the newest successful one by creation time, and
106
- compares the head it ran at with the branch tip read from the remote. The tip
107
- comes from the remote rather than the pull request object's `headRefOid`, which
108
- lags a push. `--check` takes no `--timeout` and refuses one as `check-timeout`.
109
-
110
- The alias serves the newest successful deployment for the branch and names no
111
- sha, so the run listing is the only record of the built head. The
112
- `workflow_dispatch` filter also drops the cleanup run a closed pull request
113
- fires, which concludes `success` with its deploy skipped. A project that deploys
114
- on `push` to a feature branch would need that filter widened.
115
-
116
- | Reason | What it means |
117
- | ---------- | --------------------------------------------------------------------- |
118
- | `fresh` | The newest successful deploy built the tip. Exit 0. |
119
- | `stale` | It built an earlier head. `built` and `tip` name both. Mint again. |
120
- | `building` | A deploy of the tip is still running. Wait rather than minting again. |
121
- | `no-build` | No deploy of the branch has succeeded. |
122
-
123
- The record also carries `runId` for the deploy it read. The check covers the
124
- hosted address only, so a local address has no run to read and stays unverified.
125
- `review-ui` runs it before driving a hosted preview and posts nothing on any
126
- reason but `fresh`.
127
-
128
- ## How the address reaches the pull request
129
-
130
- `canon pr evidence --preview <url>` puts `**Preview:** <url>` on the address
131
- line under the evidence body's `## Evidence` heading. With no evidence image in
132
- the diff, the body is the heading, that line, and the trailing marker alone, so
133
- a pull request whose screenshots did not change still gets one comment a later
134
- call can find and edit.
135
-
136
- A later `canon pr evidence` run without `--preview` reads the address off the
137
- marked comment and carries it into the new body. `git-followup` re-renders the
138
- comment after every push, and without that carry the first push would delete
139
- the link. A visual checklist folded in through `--checklist` carries the same
140
- way, which is what keeps that push from wiping boxes a reviewer already ticked.
141
-
142
- ## Removing a preview
143
-
144
- A `pull_request` trigger on `closed` in the same workflow runs a cleanup job
145
- that deletes every preview deployment Cloudflare holds for the branch. The
146
- deploy job carries `if: github.event_name != 'pull_request'`, so a closed pull
147
- request never deploys. The same close runs `canon pr local --remove`, which
148
- drops the address line from the evidence comment, hosted link included, so the
149
- comment never links a deployment that no longer exists.
150
-
151
- ## What this does not cover
152
-
153
- The verb builds only the deployed-and-public case. A private repository whose
154
- previews need access protection waits for a project that needs it. A local
155
- server stands in where there is no deploy, through `canon docs pr-local`.
@@ -1,111 +0,0 @@
1
- ---
2
- title: Head-sensitive pull request reads
3
- description: Resolving a branch tip from the remote rather than from the pull request object, reading what a review pass covered off its own marker rather than off GitHub's submission stamps, why an empty run list is not a pass, the refusal reasons each verb names, and what the remote read costs
4
- ---
5
-
6
- # Head-sensitive pull request reads
7
-
8
- `canon pr head`, `canon pr checks`, and `canon pr review-state` each answer about a commit, and each exists because the field GitHub offers answers a different question. The first two resolve the branch tip from the remote with `git ls-remote` rather than taking the pull request object's word for which commit the branch is on. The third reads what a review pass covered off a marker the pass wrote, rather than off the head GitHub stamped when the review was submitted.
9
-
10
- ```bash
11
- canon pr head
12
- canon pr head 1341 --json
13
- canon pr checks
14
- canon pr checks 1341 --json
15
- canon pr review-state
16
- canon pr review-state 1341 --json
17
- ```
18
-
19
- ## The pull request object is not the authority for a head
20
-
21
- The pull request's head sha, `head.sha` on the REST pull read and `headRefOid` in `gh pr view`, is a field on the pull request object. That object trails the branch ref by up to a minute after a push and carries nothing saying how far behind it is, so a session that keys a read on it is describing whichever commit GitHub last folded into the object.
22
-
23
- Two failures on 2026-09-01 came off that one field. A reviewing session posted a finding calling a pushed commit unpushed. A worker fired a green claim and retracted it on its own thread minutes later. Neither session did anything wrong with the value it was handed. The value was stale and said so nowhere.
24
-
25
- The remote ref carries no such lag. `git ls-remote --heads origin <branch>` asks the remote itself rather than a tracking ref, which is only as current as the last fetch, and the push worth catching is the one this process never saw.
26
-
27
- The argument is stated here once. The shipped skill bodies call the verb rather than repeating it, because a sentence telling a session to compare two shas is a sentence a session can decide it already followed.
28
-
29
- ## What each verb answers
30
-
31
- `canon pr head` reports `fresh` when the object and the remote name the same commit and `stale` when they disagree, with both shas on the record. The tip is the authority and the object's head is the claim being checked against it.
32
-
33
- `canon pr checks` reads `repos/{owner}/{repo}/commits/<tip>/check-runs` and collapses it to `passing`, `failing`, or `pending`. A failure outranks a run still going, since a run in flight cannot clear a job that already failed.
34
-
35
- `canon pr review-state` reports `heading` and `state` for the newest pass in the review family, `commit` and `readAt` for what it covered, `submittedAt` for when GitHub recorded it, and `source` for which of those readings answered. A thread carrying no pass reports `state` and `source` both as `none` with nothing else on the record.
36
-
37
- `gh pr checks` cannot be made to answer this question at all, which is the argument for the move rather than a preference between two working commands. Its `--json` field set is `bucket, completedAt, description, event, link, name, startedAt, state, workflow`, with no sha among them, so a caller cannot even learn which commit its answer describes.
38
-
39
- ## A review's stamps describe the submission, not the read
40
-
41
- `commit.oid` on a review names whatever the pull request head was at the instant the review was submitted. `submittedAt` names that instant. Neither names the commit the reviewing session actually opened, and the two are only the same when nothing was pushed while the comment was being written.
42
-
43
- A push landing in that window moves both onto a commit nobody reviewed. The next pass then scopes its delta past that commit and reports it covered, which is the failure direction that loses work: the commit is skipped rather than re-read. It fired on a pull request in this toolkit on 2026-09-07, where a pass read one commit, the branch pushed another during the compose, and the poll reported the second as already seen. What it skipped was a genuine fix, and a reader comparing the sha in the report against the sha in the pass caught it.
44
-
45
- `review-pr` therefore ends every body it posts with a marker naming what it read:
46
-
47
- ```markdown
48
- <!-- review-pr: commit=<sha> read-at=<iso8601> -->
49
- ```
50
-
51
- Both values are taken at the top of the pass rather than at the post, so the compose window falls outside what the marker claims. An HTML comment renders as nothing on GitHub, so a reader of the thread never meets it.
52
-
53
- The last non-empty line is where it has to sit, and the reader looks nowhere else. Searching a body for the last match instead reads a marker the body was displaying rather than claiming, since a fenced block showing the format on its own line trims to exactly the pattern, and a pass carrying no marker of its own would then hand the next reader a commit taken from an illustration. That is the same defect through a second door, so position rather than shape is what separates a claim from a quotation.
54
-
55
- `canon pr review-state` is the only parser of that format. Two parsers was the alternative, one in the shipped skill body and one in the orchestrator poll's own filters, and those two ship on different cadences, so a format change to either would leave the other reading a reviewed commit as unreviewed.
56
-
57
- `source` on the record says which reading answered, and a caller checks it before trusting the rest:
58
-
59
- | Source | What it means |
60
- | ---------- | ------------------------------------------------------------------------------------------- |
61
- | `marker` | The pass wrote its own read-time record, which is the authority |
62
- | `fallback` | A pass posted before this shipped, read off the submission stamps and carrying their defect |
63
- | `none` | The thread carries no pass, so the next one is a first pass |
64
-
65
- The record carries `submittedAt` beside `readAt` rather than in place of it, because each is right about a different question. What a pass had read is a fact about the read, and how long a posted comment has waited on a human is a fact about the submission. The orchestrator poll reads the first for its coverage test and the second for its age test.
66
-
67
- A `PUT` rewrite of a standing close-out inherits the fix without a mechanism of its own. That request replaces the body and the marker rides in it, so a rewritten comment names the commit the rewriting pass read. `submittedAt` stays pinned to the original submission, which is the reading the age test wants anyway.
68
-
69
- ## An empty run list is not a pass
70
-
71
- Keying the query on a sha is necessary and not sufficient. The endpoint answered with `total_count` 2 and an empty row list during a measured window on 2026-09-02, so a reader that finds no run for the tip and reports `passing` reproduces the false green behind a better query.
72
-
73
- Both empty cases report `pending` instead: a tip carrying no run yet, and a listing whose rows all belong to some other commit. The record separates them, since `matched` counts the runs belonging to the tip and `foreign` counts the rest, and a caller that wants to tell "not started" from "still going" reads those two numbers rather than the state alone.
74
-
75
- The verb keeps the newest run per check name rather than rolling every matched run into the state. A workflow that fires twice on one commit used to leave its first, stale run beside the rerun, where a failing one could outvote a passing rerun. Grouping the matched set by name and keeping the highest `id` per group removes that stale run before the rollup runs. `collapsed` on the record counts what the fold discards, so a caller reading it against `matched` sees the dedupe directly, instead of reading a `matched` above the check count as the verb disagreeing with `gh pr checks`.
76
-
77
- A conflicted branch produces no `pull_request` check runs rather than a failing one. GitHub builds no merge ref for it, so no `pull_request` workflow starts and a repository running only that trigger leaves the listing empty, which reads as `pending` and never leaves it. A workflow triggered by a push to the branch still runs on the tip, so `matched` rises above zero, `conflicted` stays false, and the wait ends on those runs rather than on the reading. The pull request's `mergeable_state` is the field that separates the two. The verb raises it to upper case, matching the GraphQL `mergeStateStatus` it replaced, and the record carries `conflicted: true` and `mergeState` when it reads `DIRTY` with no run belonging to the tip. A `DIRTY` reading beside runs on the tip leaves the state as those runs say, since a conflict arising after they completed does not unsay them. `UNKNOWN` is transient while GitHub computes the merge state, right after a push, so it reads as not conflicted and costs one more poll. A binary predating the field keeps answering `pending`.
78
-
79
- A listing carrying even one foreign run reports `pending` whatever the matching half says. A set that describes another commit says nothing about this one, so answering off the rows that happen to match would put a verdict on a set already known to be incomplete.
80
-
81
- A count above the rows returned reports `pending` for the same reason. The query asks for 100 rows against the endpoint's default of 30, and a commit carrying more than that comes back short, so the verb reports the tip as unread rather than collapsing the page it happens to hold. `reported` against `matched` and `foreign` is where a reader sees which of the three shapes produced the answer.
82
-
83
- ## Reading the answer
84
-
85
- Branch on the record rather than on the exit. Both verbs exit 0 whenever they resolved a tip, whatever the verdict, and 1 only when they refused. An operator shell profile can wrap `canon` in a function whose status comes from a trailing command, which flattens every non-zero exit to 0, so the exit is not a channel either verb puts a verdict on. `gh pr checks` reserving exit 8 for pending is the pattern being replaced rather than one to copy.
86
-
87
- Each refusal names a different repair:
88
-
89
- | Reason | What it means |
90
- | -------------------- | ------------------------------------------------------------------------------------------ |
91
- | `gh-missing` | `gh` is not on the path, so no pull request resolved |
92
- | `gh-failed` | `gh` could not answer for this branch, so name a number instead |
93
- | `ambiguous-pull` | Two open pull requests share the branch against different bases, so name a number |
94
- | `no-branch` | The pull request carries no head branch name, so no ref could be read |
95
- | `unresolvable-ref` | The remote read failed, which is not the same answer as an absent branch |
96
- | `no-remote-branch` | The remote carries no branch by that name, so there is no tip to compare against |
97
- | `no-object-head` | The pull request object reported no head, so `head` has nothing to compare the tip against |
98
- | `runs-unreadable` | The check runs for the tip could not be read, which is unread rather than none |
99
- | `reviews-unreadable` | The reviews on the pull request could not be parsed, which is unread rather than none |
100
-
101
- A failed remote read and a branch the remote does not carry stay apart rather than collapsing into one reason. Reading the first as the second would report a network refusal as a deleted branch, and the repairs have nothing in common.
102
-
103
- ## What it costs
104
-
105
- One remote round trip per call, sampled here at 0.41s, 0.55s, and 0.67s on 2026-09-01 and 2026-09-02, against 0.001s for the local ref read. The spread is the network rather than the command, so read it as roughly half a second and not as a figure to compare a later reading against. Every caller pointed at these verbs already spends a `gh` round trip on the same line, so the added cost lands on a path that was never local to begin with.
106
-
107
- One head-sensitive read is left on the object deliberately. `canon targets pulls` reads `statusCheckRollup` for every open pull request across every target, where resolving a tip per row would cost one remote read per pull request across a dozen projects, against a surface that reports a listing rather than gating a push.
108
-
109
- The orchestrator poll spends that same read per open pull request and repeats it on a three-minute timer, which makes it the heaviest caller here rather than an exception to the paragraph above. Six open pull requests is around 120 remote reads an hour. It reads over REST alone, one reviews read and one issue-comments read per pull request, plus a pull read only when `canon pr head` returns no tip. It spends a further read per pull request per tick besides, since `canon pr review-state` makes its own REST reviews read beside the payload the poll already fetched. That one is an API round trip rather than a remote ref read, and it buys a coverage test that answers about what a session read. What separates the two cases is what each reading decides rather than what it costs. The poll's head fires the review trigger, so a stale one sends a pass at a commit nobody read or withholds one that is owed. The listing decides nothing, so the same spend buys a fresher column in a report and no correctness at all.
110
-
111
- `canon pr key-changes` also stays on the object, for a different reason. It reads the body and the head off one pull read, so the two describe one commit. That is a consistency requirement rather than a freshness one, and keying its head elsewhere would break it. The file list comes off a second read, since only GraphQL returns it beside the body and a cloud session's proxy refuses GraphQL.
@@ -1,72 +0,0 @@
1
- ---
2
- title: The pull request checklist tick
3
- description: How canon pr tick marks the checklist boxes a UI pass drove, the stamp that names the commit each passed at, why a render at a newer head clears a tick, the refusal reasons it names, and why a taste box is never ticked
4
- ---
5
-
6
- # The pull request checklist tick
7
-
8
- `canon pr tick` ticks the boxes of the visual checklist on the pull request's
9
- evidence comment. `review-ui` calls it once its verdict has posted, for every
10
- box that passed at the head it drove, so the person merging reads a checklist
11
- a driver worked through instead of one that stays empty.
12
-
13
- ```bash
14
- canon pr tick 1341 --boxes 1,2,4 --head <sha> --json
15
- ```
16
-
17
- Box numbers come from the `boxes` field of `canon pr evidence --json`, which
18
- numbers the checklist's box lines in order. A caller never counts lines itself.
19
-
20
- ## What a tick looks like
21
-
22
- A ticked box ends in the commit it passed at, visible on the comment:
23
-
24
- ```markdown
25
- - [x] click "Pricing" in the header → the pricing page opens · passed at `<short-sha>`
26
- ```
27
-
28
- The stamp is visible rather than an HTML comment because the person it is for
29
- reads the merge record, and a hidden head would tell the machine what the
30
- reader cannot see. The reader matches the stamp only as the trailing segment of
31
- a line, so a box whose own text says `passed at` is left alone.
32
-
33
- ## When a tick is cleared
34
-
35
- A tick is a claim about one commit, so a render at another commit does not
36
- carry it forward. `canon pr evidence` settles the checklist before it renders:
37
-
38
- - A tick stamped for the render's head stays.
39
- - A tick stamped for any other head is cleared and loses its stamp.
40
- - A tick with no stamp, such as one an author or the operator added by hand,
41
- stays only when the comment it came from described the render's head.
42
-
43
- A render at the same head after `--preview` keeps every tick. A push that
44
- reaches the branch without a render leaves its ticks in place until the next
45
- one, and the stamp still shows which head earned them.
46
-
47
- ## What it refuses
48
-
49
- `reason` on the record is what a caller branches on, not the exit code:
50
-
51
- | Reason | What it means |
52
- | -------------- | ------------------------------------------------------------------------------------------------------------ |
53
- | `ticked` | The boxes were ticked, with the remote tip in `head`. |
54
- | `stale-head` | `--head` is not the branch tip on the remote. Nothing is written. |
55
- | `taste-box` | A named box ends in `(taste)`. Nothing is written. |
56
- | `no-box` | A named number is past the end of the checklist. Nothing is written. |
57
- | `no-checklist` | The evidence comment carries no checklist. |
58
- | `no-comment` | No comment on the pull request carries the evidence marker. |
59
- | `bad-boxes` | `--boxes` is not a list of positive whole numbers, or `--head` is not a hex sha of seven characters or more. |
60
-
61
- A taste box is a judgment of look or feel that a driver reports as needing a
62
- person's eyes. Refusing it in the verb makes that rule a check rather than a
63
- sentence a driver can talk itself out of, and a refusal writes none of the
64
- boxes named beside it.
65
-
66
- The head is compared against the tip read from the remote rather than the pull
67
- request object, which lags a push by up to a minute. A push landing between
68
- that read and the write leaves a tick at an older head until the next render
69
- clears it, the same window `canon pr head` documents.
70
-
71
- The verb edits the marked comment in place with a `PATCH`, the way
72
- `canon pr local --remove` does, and ticking a box again restamps it.
@@ -1,61 +0,0 @@
1
- ---
2
- title: Records push
3
- description: Backing the record folders to a private remote and restoring them, which folders the payload carries, the one-time setup, the refusal reasons, and the hook that runs the push
4
- ---
5
-
6
- # Records push
7
-
8
- `canon records push` commits the backed record folders to a private remote and pushes them. `canon records pull` fetches the other direction and writes them back. Both take `--json` and `--root` the way `validate` does, and both exit `0` on agreement and `1` on a refusal.
9
-
10
- ```bash
11
- canon records push
12
- canon records push --json
13
- canon records pull
14
- ```
15
-
16
- At the `.canon` root, the backed folders are every top-level directory less three: `tmp`, which is deletable without loss, `ordinal-locks`, whose entries are transient per claim and would race the claim they guard, and `.records.git`, which is the history the rest are pushed into. Nothing bounds the set from outside, since the claude manifest ships one `.canon/` root entry and names no folder, so a record folder added later enters the payload on its own rather than waiting on a name written here. A push names each folder in scope that the records index has never tracked before, so a folder that picked up a name by mistake, such as a misrouted scratch write, is visible in the report rather than entering the payload silently. The report also lists every file new to the history under `added`, since a stray file inside a folder already tracked is visible only by name.
17
-
18
- The three excluded names bound the records index as well as the disk. A name an older binary committed, such as `tmp`, is removed from the history once and reported under `dropped`, and no later push stages it again. That removes it from the tip going forward and purges nothing from earlier commits the remote already holds. A project's own drafts folder needs no config to be backed: moved to `.canon/<name>/`, it is a top-level directory like any other and enters the payload on the next push. The legacy `.claude` root keeps a fixed allowlist instead, since that root also holds tracked `skills/`, `rules/`, and `hooks/` a push must never carry, and an exclusion set there would stage all three. Each name is a top-level record folder and every archive sits inside the one it archives, so the set stays at one entry per surface however many archives appear, and it deliberately does not match the six record kinds `validate` hardcodes.
19
-
20
- Records are gitignored by design, so the history lives in a second git directory at `.records.git` inside the record root, with that root as its work tree. Both resolve off the root together rather than folder by folder, since a history opened at one root beside a work tree at the other would stage the deletion of every folder a move relocated. Every path stays where it is, which is what a separate checkout could not do. The verbs stage the backed folders by explicit pathspec with `--force`, so nothing outside them can enter the index however the ignore rules read, and the project working tree and its index are never touched. Each pathspec is a bare folder name and git reads it against the current directory rather than against the work tree the same call names, so the invocation carries `-C` at the work tree beside the other two flags. That is what lets either verb run from a linked worktree under `.claude/worktrees/`, which sits inside the records work tree and would otherwise prefix every name with its own path.
21
-
22
- ## Setup
23
-
24
- A person creates the records repository once per machine, and the verbs refuse with the commands when it is absent:
25
-
26
- ```bash
27
- git --git-dir=.canon/.records.git init
28
- git --git-dir=.canon/.records.git remote add origin <private-repo-url>
29
- printf '.canon/\n' >> .gitignore
30
- ```
31
-
32
- The ignore line is repeated here rather than left to the install, because the person running these commands is the one who creates the directory and the rule is worth reading beside the command that needs it. The claude manifest ships `.canon/` too, so a project that ran `canon tooling sync` already carries it and this line is a no-op there. One root entry covers the history and every record beside it, which is what makes the rule worth stating once rather than per folder.
33
-
34
- Point it at a private repository, and at one that is not a remote of the project. Records carry the plans, the review reports, and the groundwork trails, so a public project publishes all of it to anyone who fetches all refs. `push` compares the configured origin against every remote of the project and refuses on a match. A read of that list which fails refuses as well, since an empty list clears the comparison for every origin and a gate that passes on its own failure is no gate.
35
-
36
- ## Refusals
37
-
38
- | Reason | What fired |
39
- | ------------------- | -------------------------------------------------------------------------------------- |
40
- | `split-roots` | Record folders sit under both roots, so the resolved work tree is not the whole set |
41
- | `no-repository` | No `.canon/.records.git`, answered with the two setup commands |
42
- | `no-remote` | The records history has no `origin` |
43
- | `remote-unreadable` | The project's own remotes could not be read, so the shared-origin gate could not run |
44
- | `remote-shared` | The records origin is also a remote of the project |
45
- | `no-remote-records` | `pull` found no branch on the records origin |
46
- | `local-changes` | `pull` found records on disk that the history does not carry |
47
- | `local-ahead` | `pull` found local commits that never reached the origin |
48
- | `unsafe-payload` | `push` found a pending file over 25 MB or carrying a credential, named under `blocked` |
49
- | `git-failed` | A git call failed, with its stderr in the message |
50
-
51
- `split-roots` runs ahead of every gate below it and fires on a half-migrated tree, which is what a `canon migrate records` run that failed partway leaves. `recordRoot` answers for the whole tree on the first root that exists, so a folder left at the old root is absent from the work tree while the records index still names it, and an unguarded `add -A` would stage its deletion and drop it from the remote on the next push. Finish the move, or put the stranded folders back beside the others.
52
-
53
- `unsafe-payload` runs before anything is staged, so a refusal leaves the index, the log, and the object store as it found them. It reads every new or changed file the push would stage and blocks one over 25 MB, which is read off the file's size without opening it, or one carrying a value `canon secrets scan` would report, through the same patterns and the same `canon-allow-secret` marker. It refuses the whole push rather than skipping the file, and `--json` lists each path with its cause and a detail that names the credential's kind and line, never its value. Move the file out of the record folders, such as into `.canon/tmp/`, or remove the credential, then push again. The guard reads only what this push would add, so a credential committed before it existed stays in the history unreported.
54
-
55
- A push the remote rejects undoes the commit that run made and leaves the records on disk, so the next run does not send a payload the remote already refused. A history an older binary committed and left unpushed is outside that undo, and the guard never reads it either, so every push sends the same blob again. Reset it by hand from the project root to the last commit the origin holds, `git --git-dir=.canon/.records.git --work-tree=.canon reset <commit>`, and push again. The `--work-tree` is required, since the setup `init` marks the history bare and git refuses that reset on a bare repository.
56
-
57
- The two `pull` refusals exist because the directions are not symmetric. A push only adds, while a pull onto a machine holding work that never left it would discard that work. Resolve either by running `push` first, or by moving the local folders aside. A machine holding no backed folders has nothing to lose, so a restore onto a fresh checkout runs straight through.
58
-
59
- ## When it runs
60
-
61
- `canon hooks post-merge`, which `.husky/post-merge` calls, runs `push` after the task archiving, on every merge rather than only on one that archived a task. A review report and a plan edit both land on runs that close nothing. The verb exits zero whatever the push answers, so an unreachable remote neither aborts the hook nor delays the archiving before it, and a checkout that never ran the setup reports nothing. `hooks.md` covers the verb. Anything the hook misses is covered by running the verb by hand.
@@ -1,40 +0,0 @@
1
- ---
2
- title: Records stale
3
- description: Counting the releases since the requirements record was last reviewed, where the review date is read from, and the exit codes and refusals
4
- ---
5
-
6
- # Records stale
7
-
8
- `canon records stale canonical` reports when the requirements record was last reviewed and how many releases have shipped since. A skill takes the count from the verb rather than re-deriving it from the tag list.
9
-
10
- ```bash
11
- canon records stale canonical
12
- canon records stale canonical --json
13
- ```
14
-
15
- | Option | Behavior |
16
- | --------------- | ----------------------------------------------------------------- |
17
- | `--json` | Add a machine-readable record on stdout |
18
- | `--root <path>` | Project root, defaulting to the current worktree rather than main |
19
-
20
- `canonical` is the one kind it reads. Any other argument refuses as `unknown-kind`, including `memory`, which the verb read until the memory pen retired.
21
-
22
- It reports and never writes, matching `validate` and `size`.
23
-
24
- ## Canonical records
25
-
26
- `canonical` reads `REQUIREMENTS.md` at whichever surface root carries it, `canon/` ahead of `.claude/`, and omits it when the project does not hold one. It is tracked, so `--root` defaults to the worktree the caller stands in rather than the main one, and a stamp a branch just wrote reads back from that branch.
27
-
28
- The review point is an optional `reviewed: YYYY-MM-DD` frontmatter field, set by whoever finishes a review, per `claude/skills/draft-doc/references/requirements.md`. It is a date rather than a commit, since a commit written on a feature branch never reaches a trunk that squash-merges. The field is read off the raw frontmatter block rather than a parsed value, since a YAML parser resolves a bare date to a date object on one schema and a string on another. A value that is not a calendar date reads as never reviewed, and the record carries the raw value as `invalidReviewed` so a review can see it rather than the verb throwing over one hand edit.
29
-
30
- The count is every release tag merged into `HEAD` whose creator date falls after the reviewed day. A release tag ends in a dotted version, bare as `v5.25.0` or behind a component prefix as `canon-v2.1.0`, so an eval, deploy, or snapshot tag on the trunk never inflates it. A lightweight tag's creator date is its commit's date and an annotated tag's is the tagging date, so a project mixing both counts by two clocks.
31
-
32
- The record carries `tagged`, false when `HEAD` has merged no release tag or the repository has no commit yet, and one `docs` row per doc with `path`, `reviewed`, `releasesSince`, and `latestRelease`. `releasesSince` is null on a doc never reviewed and `0` on one reviewed after every tag. With no tags it is also `0`, which is why `tagged` travels beside it.
33
-
34
- It gates nothing, and no hook, gate stage, or workflow runs it. `canon:document-health` reads it when asked to review the two records.
35
-
36
- ## Exit codes and refusals
37
-
38
- Exit codes: `0` the reading completed, whatever it found, and `1` refused. A release count is a prompt for the review to judge rather than a failure, so nothing here gates.
39
-
40
- A `reason` field carries which gate fired: `unknown-kind` for any kind but `canonical`, and `unreadable-tags` when the tags cannot be read.
@@ -1,57 +0,0 @@
1
- ---
2
- title: Records validate
3
- description: Validating the session records, the per-kind checks, which root they default to, the exit codes, and the refusal reasons
4
- ---
5
-
6
- # Records validate
7
-
8
- `canon records validate <kind>` reports where a file and the standard governing it disagree. Four kinds exist, each a gitignored folder under the record root resolved at `.canon/` first: `plans`, `groundwork`, `intake`, and `teach`.
9
-
10
- ```bash
11
- canon records validate plans
12
- canon records validate intake --json
13
- ```
14
-
15
- | Option | Behavior |
16
- | --------------- | --------------------------------------------- |
17
- | `--json` | Add a machine-readable record on stdout |
18
- | `--root <path>` | Project root, defaulting to the main worktree |
19
-
20
- It reads and never writes. A session record is per-machine scratch with no history behind it, so a repair that guessed wrong could not be undone.
21
-
22
- Nothing fires it automatically. The four record folders are gitignored, so the standards-audit hook exits early on them and any check reading changed files from git never lists one. The verb runs at the moment a session claims the record is finished, which is the same placement `canon tasks validate` takes over the board.
23
-
24
- The two record roots every kind resolves at are in `records.md`.
25
-
26
- ## What each kind checks
27
-
28
- | Kind | What it reports |
29
- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
30
- | `plans` | A filename that is not `feature-<slug>.md`, a missing `# Feature:` heading, a missing required section, a files-to-touch entry naming no file or saying nothing about one, a question carrying no suggestion or no answer slot, and a batch still staged with a `**Batch N**` sub-heading inside the one file rather than split into its own plan |
31
- | `groundwork` | A track with no `README.md` or no `01-current-state.md`, a file missing `title` or `description`, a `README.md` with no `date` as `YYYY-MM-DD`, an unnumbered file, and a track holding a decision without its handoff or the reverse |
32
- | `intake` | A dump with no `00-overview.md`, the same frontmatter and numbering checks, an item missing any of `Problem`, `Fix`, `Worth it`, or `You`, and an item carrying `Open` with no `Suggested` |
33
- | `teach` | A workspace folder carrying no two-digit ordinal, an absent `MISSION.md`, `RESOURCES.md`, or `GLOSSARY.md`, a file missing `title` or `description`, a mission with no `date` as `YYYY-MM-DD` or no `## Success looks like` section, an unnumbered learning record, and a reference page opening with an ordinal |
34
-
35
- The half-closed track is the groundwork check a reader cannot run by eye. A folder holding `06` without `07` reads as closed to anyone scanning filenames while the file a returning session actually opens is absent.
36
-
37
- The item check skips `00-overview.md` and `99-next-session.md`, since neither holds items and running it over the handoff would report every heading it carries.
38
-
39
- A plan section opens as a bold label or as an H2 and the check counts both, naming the reference's spelling when it reports one missing. Plans split roughly four to one between the two forms, so failing the variant would report nearly every plan on a rule that costs a reader nothing.
40
-
41
- A section runs to the next marker-shaped line whatever it names, so a plan carrying a label of its own closes the section above it rather than collecting into it. Fenced blocks are dropped before any of this, since a plan showing the shape it writes puts real-looking bullets and headings inside a fence.
42
-
43
- ## Exit codes and refusals
44
-
45
- Exit codes: `0` every check passed, `1` refused, `2` at least one record carries a finding. A `reason` field carries which gate fired: `no-folder` when none of the kind's directories exist, and `unknown-kind` when the argument names no published kind. A `no-folder` message names every candidate, so a refusal names both record roots.
46
-
47
- An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the record's `findings` array and its `reason` rather than the exit when a skill consumes this.
48
-
49
- The four record folders are shared scratch at the main worktree root, so `--root` defaults to the first entry of `git worktree list` rather than the working directory. A linked worktree validates the same records every other session reads.
50
-
51
- Skills branch on the findings rather than on the exit code:
52
-
53
- ```bash
54
- canon records validate plans --json | jq -r '.findings[] | "\(.kind): \(.subject)"'
55
- ```
56
-
57
- For the shapes each check enforces, see `claude/skills/plan-feature/references/plan.md`, `claude/skills/plan-groundwork/references/groundwork.md`, and `claude/skills/plan-intake/references/intake.md`.
@@ -1,83 +0,0 @@
1
- ---
2
- title: Restated instructions
3
- description: Counting the instructions the always-loaded file and every path-scoped rule share with the seed, the skill bodies, and each other, how a match is decided, the three classes, which surface a later edit starts from, and why the sweep reports rather than gates
4
- ---
5
-
6
- # Restated instructions
7
-
8
- `canon gov restated` reports every instruction the always-loaded file or a path-scoped rule states that a further surface states too. It answers a question nothing else here could: one rule was found written in three places, and it was found by accident when a shipped skill deleted a file the rule said to keep.
9
-
10
- ```bash
11
- canon gov restated
12
- canon gov restated --json
13
- canon gov restated --root ../my-app
14
- ```
15
-
16
- | Option | Behavior |
17
- | --------------- | ---------------------------------------------------------- |
18
- | `--root <path>` | Tree to read, defaulting to the current directory |
19
- | `--json` | Add a machine-readable record on stdout, keeping the frame |
20
-
21
- Under `--json` the record holds stdout alone and the frame still renders on stderr, refusals included, which is the split `output-shape.md` fixes for every mode.
22
-
23
- ## The corpus it reads
24
-
25
- Four surfaces, and they are not read symmetrically. Every top-level bullet in `CLAUDE.md` and in every path-scoped rule is a subject, and every surface below is searched, rules included a second time:
26
-
27
- - `tooling/claude/seeds/CLAUDE.md`, read as bullets, since the seed carries the same shape as the file it is authored from
28
- - `claude/skills/*/SKILL.md` and `.claude/skills/*/SKILL.md`, read as every prose line and bullet, since the motivating case was stated in a body as a paragraph rather than a list item. The second root holds the project's own skills, which are equally a place a rule gets restated
29
- - `governance/rules/**/*.md` and `internal/rules/**/*.md`, the authoring roots rather than the consumed copy under `.claude/rules/`, read as bullets the same way the seed is. Each record's `file` carries the root it was read from
30
-
31
- A rule reads as both a subject and a candidate, unlike the seed and the skill bodies. A stack ships a whole rule folder, so a bullet duplicated between two rules reaches a target exactly as a bullet duplicated between the always-loaded file and a rule does, and neither shape is visible from one side alone. A candidate sharing its subject's file is skipped, since two bullets inside one rule sharing anchors are adjacent instructions on one topic rather than the same rule shipped twice, and a pair found from one direction is not reported again from the other.
32
-
33
- Frontmatter, headings, tables, and fenced blocks are read past. A heading names a section instead of stating a rule, a fenced block is an example whose words belong to the prose around it, and a body's `description` restates that skill's own purpose, so sweeping it would match every subject naming its domain.
34
-
35
- A rule stated in two skill bodies and never in `CLAUDE.md` or another rule is outside this reading. A body is searched but never read as a subject, and a subject is what the search runs from.
36
-
37
- ## How a match is decided
38
-
39
- Matching is recall-first and keyed on shared distinctive tokens rather than on a phrase two surfaces spell the same way. The case this exists for was one rule written three different ways, so a near-exact matcher would ship a cheap report blind to the defect it was built for.
40
-
41
- A token is distinctive when it appears in at most 20 of the corpus's statements, which is under one percent of them. `.canon/plans/` sits at 14 and is the anchor the motivating case turns on, while `file` sits at 371 and would match most of the tree.
42
-
43
- A backticked token counts double. An author marking a span as code named an identifier rather than describing one, so `.canon/plans/archive/` says more about what a statement governs than any two prose words do. Two statements are one rule when their shared anchors reach a weight of 3.
44
-
45
- Every record names the anchors its match rested on, so a reader can weigh a finding instead of taking it.
46
-
47
- ## The three classes
48
-
49
- - **Mirror.** Both files sit on a declared path pair whose duplication is deliberate. `CLAUDE.md` and the seed are the one pair, since the seed is authored from it and a hand merge reconciles the two. Excluding by pair rather than by content is the point: the duplication is a location fact this repository already records, and a content test would rediscover it on every run.
50
- - **Repetition.** Two surfaces state one rule and neither is declared a copy of the other.
51
- - **Contradiction.** The prohibition falls on one surface alone, on a match strong enough to read that as a disagreement. This is a polarity reading rather than a judgment about meaning, so weigh each against the surfaces it names.
52
-
53
- The contradiction floor sits above the match floor deliberately. A thin match says two statements touch the same subject, which is not enough to claim one forbids what the other prescribes, so a weak pair reports as a repetition and the loudest class is reserved for a pair sharing real identity.
54
-
55
- Two further rules decide where a prohibition counts, and both came out of false positives rather than from reasoning ahead of the corpus.
56
-
57
- Polarity is read off the clause the anchors landed in, and off the densest such clause rather than every one carrying an anchor. A statement states one rule across several clauses, so a union answers true whenever any clause anywhere carries a marker, which is the whole statement again under another name.
58
-
59
- The marker also has to open its clause, because an instruction leads with its verb. `Never delete a task file` prohibits where `a fallback never fires` reports, and no test reading the marker anywhere in the clause tells those apart. What that costs is a prohibition written mid-clause, which now reads as description and lands the pair in the repetition class, so both surfaces still reach the report and only the label is weaker.
60
-
61
- One mid-clause shape still counts. A `no` opening a clause or a comma-set phrase directly on a code span prohibits, as in `pinned to major tags, no` followed by the pinned tag, since sitting on an identifier it names what is ruled out. The phrase boundary keeps description out: `returns no` then a code span puts the marker after a verb and reports what happens. `not` and `never` in the same position are left out, because admitting them turned agreeing pairs into false contradictions.
62
-
63
- A mirror that disagrees stays a finding. The exclusion reaches a repetition alone, because the two files on a declared pair are meant to agree.
64
-
65
- ## Which surface is authoritative
66
-
67
- Each restatement names where a later edit starts.
68
-
69
- - `claude-md` for a seed match, since the always-loaded file is authored first and the seed carries it to a target
70
- - `skill-body` where the subject names that skill, which is the content-ownership rule that behavior triggered only when editing one domain belongs to that domain's skill
71
- - `unknown` everywhere else, a rule match included, which is a first-class answer rather than a gap
72
-
73
- The ownership table assigns a cross-domain rule and a domain-triggered one, and reaches nothing stated in a skill body the always-loaded file never names. Guessing there would put a reader on a surface nobody decided.
74
-
75
- ## Exit codes
76
-
77
- Exit codes are `0` when no instruction is restated outside a declared mirror, `1` for a refusal, and `2` for at least one restatement outside one. Mirrors move no exit code.
78
-
79
- Nothing wires this into `bun run check` or into a hook. A restatement is legitimate more often than not, and gating a measure whose ordinary result is a finding is what teaches contributors to route around the stage. `canon gov test-order` and `canon labels audit` are the siblings.
80
-
81
- Both refusals are absences rather than breaks. A target may hold none of the seed, a skills tree, or a rules tree, so `canon audits run` reads `no-instructions` and `no-surfaces` as a corpus that is not there rather than a verb that failed.
82
-
83
- An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the record's `counts` rather than the exit when a skill consumes this.