@erclx/canon 5.25.0 → 5.27.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 (315) hide show
  1. package/README.md +4 -3
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/api-design/SKILL.md +2 -0
  4. package/claude/skills/auto-ship/SKILL.md +3 -11
  5. package/claude/skills/auto-ship/references/review-findings.md +20 -0
  6. package/claude/skills/backlog-triage/SKILL.md +2 -0
  7. package/claude/skills/bash-cli-script/SKILL.md +2 -0
  8. package/claude/skills/build-in-slices/SKILL.md +2 -0
  9. package/claude/skills/canon-cli/SKILL.md +2 -0
  10. package/claude/skills/canon-feedback/SKILL.md +2 -0
  11. package/claude/skills/canon-feedback-triage/SKILL.md +2 -0
  12. package/claude/skills/canon-operator/SKILL.md +2 -0
  13. package/claude/skills/canon-rollout/SKILL.md +3 -11
  14. package/claude/skills/canon-rollout/references/failure-recovery.md +20 -0
  15. package/claude/skills/canvas/SKILL.md +14 -1
  16. package/claude/skills/ci-workflow/SKILL.md +2 -0
  17. package/claude/skills/code-craft/SKILL.md +2 -0
  18. package/claude/skills/codebase-layout/SKILL.md +2 -0
  19. package/claude/skills/context-fold/REQUIREMENT.md +6 -2
  20. package/claude/skills/context-fold/SKILL.md +9 -21
  21. package/claude/skills/context-fold/references/anchor-sweep.md +2 -2
  22. package/claude/skills/context-fold/references/completion-report.md +26 -0
  23. package/claude/skills/create-rule/SKILL.md +2 -0
  24. package/claude/skills/create-skill/SKILL.md +2 -0
  25. package/claude/skills/create-standard/SKILL.md +2 -0
  26. package/claude/skills/decision-escalate/SKILL.md +2 -0
  27. package/claude/skills/deploy-app/SKILL.md +2 -0
  28. package/claude/skills/deprecation-migration/SKILL.md +2 -0
  29. package/claude/skills/design-extract/SKILL.md +2 -0
  30. package/claude/skills/design-taste/REQUIREMENT.md +1 -1
  31. package/claude/skills/design-taste/SKILL.md +6 -2
  32. package/claude/skills/design-taste/references/adopted.md +40 -0
  33. package/claude/skills/design-taste/references/kinds.md +1 -1
  34. package/claude/skills/design-taste/references/systems.md +1 -7
  35. package/claude/skills/design-taste/references/tells.md +2 -24
  36. package/claude/skills/docs-sync/REQUIREMENT.md +2 -1
  37. package/claude/skills/docs-sync/SKILL.md +2 -0
  38. package/claude/skills/document-health/REQUIREMENT.md +2 -0
  39. package/claude/skills/document-health/SKILL.md +23 -0
  40. package/claude/skills/document-health/references/axes.md +12 -0
  41. package/claude/skills/draft-and-pick/SKILL.md +3 -1
  42. package/claude/skills/draft-doc/SKILL.md +2 -0
  43. package/claude/skills/draft-doc/references/wiki.md +1 -1
  44. package/claude/skills/draft-figure/SKILL.md +2 -0
  45. package/claude/skills/draft-identity/SKILL.md +3 -1
  46. package/claude/skills/draft-ready/SKILL.md +2 -0
  47. package/claude/skills/draft-screencast/SKILL.md +11 -7
  48. package/claude/skills/draft-slides/REQUIREMENT.md +17 -12
  49. package/claude/skills/draft-slides/SKILL.md +33 -20
  50. package/claude/skills/git-branch/SKILL.md +2 -0
  51. package/claude/skills/git-commit/SKILL.md +2 -0
  52. package/claude/skills/git-followup/SKILL.md +2 -0
  53. package/claude/skills/git-issue/SKILL.md +2 -0
  54. package/claude/skills/git-issue/references/issue.md +10 -0
  55. package/claude/skills/git-pr/SKILL.md +2 -0
  56. package/claude/skills/git-pr/references/evidence.md +14 -6
  57. package/claude/skills/git-pr/references/labels.md +12 -0
  58. package/claude/skills/git-ship/SKILL.md +2 -0
  59. package/claude/skills/git-split/SKILL.md +21 -8
  60. package/claude/skills/git-stage/SKILL.md +2 -0
  61. package/claude/skills/git-worktree/SKILL.md +2 -0
  62. package/claude/skills/index-lookup/SKILL.md +2 -0
  63. package/claude/skills/markdown-propose/SKILL.md +2 -0
  64. package/claude/skills/markdown-propose/references/format.md +9 -0
  65. package/claude/skills/memory-capture/SKILL.md +2 -0
  66. package/claude/skills/memory-review/SKILL.md +2 -0
  67. package/claude/skills/memory-review/references/later-phases.md +7 -0
  68. package/claude/skills/plan-feature/SKILL.md +2 -0
  69. package/claude/skills/plan-groundwork/SKILL.md +2 -0
  70. package/claude/skills/plan-intake/SKILL.md +2 -0
  71. package/claude/skills/plan-intake-answer/SKILL.md +2 -0
  72. package/claude/skills/project-commands/SKILL.md +2 -0
  73. package/claude/skills/read-frames/SKILL.md +2 -0
  74. package/claude/skills/record-screencast/SKILL.md +2 -0
  75. package/claude/skills/repo-metadata/SKILL.md +2 -0
  76. package/claude/skills/restate-plainly/SKILL.md +2 -0
  77. package/claude/skills/review-address/SKILL.md +3 -7
  78. package/claude/skills/review-address/references/post-review-findings.md +16 -0
  79. package/claude/skills/review-branch/SKILL.md +2 -0
  80. package/claude/skills/review-craft/SKILL.md +2 -0
  81. package/claude/skills/review-craft/references/ui.md +2 -2
  82. package/claude/skills/review-pr/SKILL.md +4 -8
  83. package/claude/skills/review-pr/references/body-variants.md +52 -0
  84. package/claude/skills/review-pr/references/key-changes.md +16 -0
  85. package/claude/skills/review-pr/references/post.md +15 -43
  86. package/claude/skills/review-ui/SKILL.md +3 -1
  87. package/claude/skills/review-ui/references/post.md +4 -2
  88. package/claude/skills/role-orchestrator/SKILL.md +5 -14
  89. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +8 -0
  90. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +13 -0
  91. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
  92. package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +23 -0
  93. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +5 -33
  94. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +1 -21
  95. package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +1 -1
  96. package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +38 -0
  97. package/claude/skills/role-orchestrator/references/orchestrator-watch.md +26 -0
  98. package/claude/skills/role-planner/SKILL.md +2 -0
  99. package/claude/skills/role-reviewer/REQUIREMENT.md +1 -1
  100. package/claude/skills/role-reviewer/SKILL.md +2 -0
  101. package/claude/skills/role-worker/SKILL.md +2 -0
  102. package/claude/skills/search-craft/REQUIREMENT.md +1 -1
  103. package/claude/skills/search-craft/SKILL.md +5 -1
  104. package/claude/skills/search-craft/references/adopted.md +30 -0
  105. package/claude/skills/seed-sync/SKILL.md +2 -0
  106. package/claude/skills/session-compact/SKILL.md +2 -0
  107. package/claude/skills/session-map/SKILL.md +2 -0
  108. package/claude/skills/session-relay/SKILL.md +2 -0
  109. package/claude/skills/session-resume/SKILL.md +2 -0
  110. package/claude/skills/session-worktree/SKILL.md +3 -11
  111. package/claude/skills/session-worktree/references/install-command.md +20 -0
  112. package/claude/skills/sketch-design/SKILL.md +2 -0
  113. package/claude/skills/systematic-debugging/SKILL.md +2 -0
  114. package/claude/skills/target-check/SKILL.md +2 -0
  115. package/claude/skills/target-setup/SKILL.md +2 -0
  116. package/claude/skills/target-setup/references/detect.md +8 -0
  117. package/claude/skills/target-setup/references/indexes.md +13 -0
  118. package/claude/skills/target-setup/references/verify.md +8 -0
  119. package/claude/skills/task-board/SKILL.md +2 -0
  120. package/claude/skills/teach-workspace/SKILL.md +5 -15
  121. package/claude/skills/teach-workspace/references/lesson-build.md +75 -0
  122. package/claude/skills/teach-workspace/references/lesson-craft.md +13 -69
  123. package/claude/skills/teach-workspace/references/revisit.md +22 -0
  124. package/claude/skills/test-craft/SKILL.md +2 -0
  125. package/claude/skills/test-first/SKILL.md +4 -0
  126. package/claude/skills/test-first/references/adopted.md +36 -0
  127. package/claude/skills/ui-checklist/SKILL.md +2 -0
  128. package/claude/skills/ux-audit/SKILL.md +2 -0
  129. package/claude/skills/ux-measure/SKILL.md +2 -0
  130. package/claude/skills/ux-walkthrough/SKILL.md +2 -0
  131. package/claude/skills/video-craft/SKILL.md +2 -0
  132. package/claude/skills/write-human/SKILL.md +3 -1
  133. package/claude/skills/write-human/references/machine-tells.md +13 -0
  134. package/claude/skills/youtube-transcripts/SKILL.md +2 -0
  135. package/docs/agents/canvas.md +24 -6
  136. package/docs/agents/capture.md +6 -6
  137. package/docs/agents/commands.md +5 -5
  138. package/docs/agents/index.md +5 -5
  139. package/docs/agents/install-and-sync.md +7 -0
  140. package/docs/agents/key-changes.md +1 -1
  141. package/docs/agents/pr-evidence.md +38 -15
  142. package/docs/agents/pr-local.md +19 -13
  143. package/docs/agents/pr-preview.md +44 -21
  144. package/docs/agents/records-stale.md +22 -9
  145. package/docs/agents/records.md +1 -1
  146. package/docs/agents/sandbox.md +29 -5
  147. package/docs/agents/scripting.md +8 -1
  148. package/docs/agents/skills-audit.md +11 -3
  149. package/docs/agents/slides.md +15 -3
  150. package/docs/workflow/ai-workflow.md +1 -1
  151. package/docs/workflow/index.md +1 -0
  152. package/docs/workflow/operating-model.md +5 -6
  153. package/docs/workflow/parallel-features.md +23 -2
  154. package/docs/workflow/skill-map-on-request.md +42 -0
  155. package/docs/workflow/skill-map.md +3 -34
  156. package/docs/workflow/zshrc-aliases.md +1 -1
  157. package/package.json +1 -1
  158. package/scripts/core/regen-hero.ts +1 -1
  159. package/scripts/lib/sandbox-git.sh +1 -9
  160. package/scripts/lib/sandbox-path.sh +6 -162
  161. package/scripts/sandbox-hook.sh +99 -0
  162. package/src/audits/run.ts +1 -1
  163. package/src/canvas/address.ts +12 -3
  164. package/src/canvas/capture.ts +1 -9
  165. package/src/canvas/client/inspector/align-grid.tsx +162 -0
  166. package/src/canvas/client/inspector/color-field.tsx +398 -0
  167. package/src/canvas/client/inspector/color-inputs.tsx +139 -0
  168. package/src/canvas/client/inspector/color-picker.tsx +668 -0
  169. package/src/canvas/client/inspector/color-space.ts +240 -0
  170. package/src/canvas/client/inspector/field.tsx +5 -1
  171. package/src/canvas/client/inspector/fields.css +547 -17
  172. package/src/canvas/client/inspector/size-field.tsx +69 -0
  173. package/src/canvas/client/inspector/values.ts +166 -1
  174. package/src/canvas/client/inspector.tsx +236 -73
  175. package/src/canvas/client/selection.tsx +197 -0
  176. package/src/canvas/client/shell.css +104 -12
  177. package/src/canvas/client/state.ts +69 -9
  178. package/src/canvas/client/surface.tsx +147 -22
  179. package/src/canvas/content.ts +50 -1
  180. package/src/canvas/edit.ts +37 -4
  181. package/src/canvas/editing.ts +203 -0
  182. package/src/canvas/global.d.ts +15 -0
  183. package/src/canvas/server.ts +149 -12
  184. package/src/canvas/tokens.ts +59 -17
  185. package/src/capture/render.ts +45 -7
  186. package/src/capture/sources.ts +43 -0
  187. package/src/census/count.ts +2 -2
  188. package/src/claude/cases/workflow.ts +5 -0
  189. package/src/claude/seeds.ts +2 -2
  190. package/src/claude/skills-audit.ts +96 -14
  191. package/src/claude/skills-drift.ts +1 -1
  192. package/src/claude/skills-families.ts +36 -0
  193. package/src/claude/skills-list.ts +76 -15
  194. package/src/commands/audits.ts +2 -2
  195. package/src/commands/canvas.ts +179 -2
  196. package/src/commands/claude.ts +94 -11
  197. package/src/commands/design.ts +142 -2
  198. package/src/commands/feedback.ts +3 -3
  199. package/src/commands/gov.ts +1 -1
  200. package/src/commands/hooks.ts +1 -1
  201. package/src/commands/indexes.ts +1 -1
  202. package/src/commands/init.ts +1 -1
  203. package/src/commands/intake.ts +1 -1
  204. package/src/commands/migrate.ts +1 -1
  205. package/src/commands/pr.ts +105 -58
  206. package/src/commands/records.ts +89 -7
  207. package/src/commands/repo.ts +1 -1
  208. package/src/commands/sandbox.ts +103 -11
  209. package/src/commands/sessions.ts +1 -1
  210. package/src/commands/slides.ts +31 -77
  211. package/src/commands/sync.ts +1 -1
  212. package/src/commands/tasks.ts +2 -2
  213. package/src/commands/teach.ts +1 -1
  214. package/src/commands/transcripts.ts +1 -1
  215. package/src/commands/worktrees.ts +1 -1
  216. package/src/comments/scan.ts +1 -1
  217. package/src/comments/trend.ts +1 -1
  218. package/src/context/audit.ts +1 -1
  219. package/src/context/citations.ts +1 -1
  220. package/src/context/classify/extract.ts +2 -2
  221. package/src/design/project-fonts.ts +392 -0
  222. package/src/design/tokens.ts +2 -18
  223. package/src/docs/read.ts +1 -1
  224. package/src/{github.ts → feedback/github.ts} +2 -2
  225. package/src/{client-commands.ts → gate/client-commands.ts} +1 -1
  226. package/src/gate/measures.ts +53 -13
  227. package/src/gate/sequencer.ts +1 -1
  228. package/src/gate/stages.ts +9 -0
  229. package/src/{git-files.ts → git/files.ts} +2 -2
  230. package/src/{git-ignore.ts → git/ignore.ts} +1 -1
  231. package/src/{worktree.ts → git/worktree.ts} +1 -1
  232. package/src/gov/citations.ts +2 -2
  233. package/src/gov/install.ts +1 -1
  234. package/src/gov/payload.ts +1 -1
  235. package/src/gov/superseded.ts +3 -3
  236. package/src/gov/test-order.ts +2 -2
  237. package/src/help.ts +1 -1
  238. package/src/hooks/post-merge.ts +1 -1
  239. package/src/indexes/walk.ts +1 -1
  240. package/src/labels/audit.ts +1 -1
  241. package/src/markdown/ceiling.ts +1 -1
  242. package/src/markdown/files.ts +1 -1
  243. package/src/migrate/apply.ts +1 -1
  244. package/src/pr/bijection.ts +1 -1
  245. package/src/pr/evidence.ts +96 -43
  246. package/src/pr/local.ts +16 -10
  247. package/src/pr/preview.ts +78 -2
  248. package/src/record-root.ts +1 -0
  249. package/src/records/backup.ts +2 -1
  250. package/src/records/push-guard.ts +1 -1
  251. package/src/records/stale-canonical.ts +146 -0
  252. package/src/records/stale.ts +1 -1
  253. package/src/sandbox/headless/dispatch.ts +264 -0
  254. package/src/sandbox/headless/run.ts +653 -0
  255. package/src/sandbox/headless/watch.ts +145 -0
  256. package/src/sandbox/hooks.ts +128 -0
  257. package/src/sandbox/provision.ts +607 -0
  258. package/src/sandbox/tree.ts +99 -4
  259. package/src/secrets/marker.ts +2 -2
  260. package/src/secrets/scan.ts +2 -2
  261. package/src/sessions/claim.ts +1 -1
  262. package/src/sessions/resolve.ts +1 -1
  263. package/src/sessions/transfer/bundle.ts +1 -1
  264. package/src/shipped/references.ts +2 -2
  265. package/src/slides/convert/export.ts +2 -16
  266. package/src/slides/decks.ts +110 -0
  267. package/src/slides/package/fonts.ts +6 -2
  268. package/src/standards/audit.ts +2 -2
  269. package/src/standards/read.ts +1 -1
  270. package/src/sync/check.ts +2 -2
  271. package/src/sync/engine.ts +2 -2
  272. package/src/sync/git.ts +1 -1
  273. package/src/sync/history.ts +1 -1
  274. package/src/sync/reverse.ts +1 -1
  275. package/src/sync/seeds-report.ts +1 -1
  276. package/src/sync/target.ts +1 -1
  277. package/src/sync/workflow.ts +1 -1
  278. package/src/targets/pulls.ts +2 -2
  279. package/src/targets/sweep.ts +2 -2
  280. package/src/tasks/answers.ts +1 -1
  281. package/src/tasks/archive.ts +1 -1
  282. package/src/tasks/reach.ts +1 -1
  283. package/src/tasks/trunk.ts +1 -1
  284. package/src/teach/nav.ts +1 -1
  285. package/src/tooling/inject.ts +2 -2
  286. package/src/tooling/subfolder.ts +1 -1
  287. package/src/web/readme-citations.ts +2 -2
  288. package/src/worktrees/bare-flag.ts +1 -1
  289. package/src/worktrees/reclaim.ts +2 -2
  290. package/src/worktrees/remove.ts +1 -1
  291. package/standards/architecture.md +1 -0
  292. package/standards/canvas.md +3 -2
  293. package/standards/requirements.md +2 -0
  294. package/standards/skill-practice.md +2 -1
  295. package/standards/skill.md +2 -0
  296. package/standards/wiki.md +12 -3
  297. package/tooling/base/configs/.github/workflows/pr-local-preview.yml +2 -2
  298. package/scripts/lib/sandbox-dispatch.sh +0 -190
  299. package/scripts/manage-sandbox.sh +0 -472
  300. package/src/slides/layouts.ts +0 -478
  301. package/src/slides/parse.ts +0 -84
  302. package/src/slides/render.ts +0 -110
  303. package/src/slides/styles.ts +0 -96
  304. /package/claude/skills/write-human/references/{source-material.md → adopted.md} +0 -0
  305. /package/src/{commands/feedback-format.ts → feedback/body.ts} +0 -0
  306. /package/src/{github-format.ts → feedback/github-format.ts} +0 -0
  307. /package/src/{git-base.ts → git/base.ts} +0 -0
  308. /package/src/{git-env.ts → git/env.ts} +0 -0
  309. /package/src/{frontmatter.ts → markdown/frontmatter.ts} +0 -0
  310. /package/src/{binary.ts → sweep/binary.ts} +0 -0
  311. /package/src/{exempt-marker.ts → sweep/exempt-marker.ts} +0 -0
  312. /package/src/{copy.ts → sync/copy.ts} +0 -0
  313. /package/src/{target.ts → targets/validate.ts} +0 -0
  314. /package/src/{paths.ts → tasks/paths.ts} +0 -0
  315. /package/src/{seed-marker.ts → tooling/seed-marker.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
3
  import type { Command } from 'commander'
4
- import { listRepositoryFiles } from '@/git-files'
4
+ import { listRepositoryFiles } from '@/git/files'
5
5
  import { indexSourceRules } from '@/gov/adapter'
6
6
  import { applyRecordsMove, applyRename, readSources } from '@/migrate/apply'
7
7
  import { isToolkitOwned, planRename, type RenamePlan } from '@/migrate/plan'
@@ -4,7 +4,7 @@ import { basename, join, resolve } from 'node:path'
4
4
  import { $ } from 'bun'
5
5
  import type { Command } from 'commander'
6
6
  import { execa } from 'execa'
7
- import { gitEnv } from '@/git-env'
7
+ import { gitEnv } from '@/git/env'
8
8
  import {
9
9
  listChangedFiles,
10
10
  listIgnoreAdditions,
@@ -13,7 +13,7 @@ import {
13
13
  parseIgnoreAdditions,
14
14
  type RenamePair,
15
15
  resolveBaseRef,
16
- } from '@/git-files'
16
+ } from '@/git/files'
17
17
  import {
18
18
  type Bijection,
19
19
  type BijectionRefusal,
@@ -51,13 +51,13 @@ import {
51
51
  } from '@/pr/frames'
52
52
  import { type HeadRefusal, resolveHead, resolveTip } from '@/pr/head'
53
53
  import {
54
+ dropAddressLine,
54
55
  findLocalServer,
55
56
  type Listener,
56
57
  type LocalRefusal,
57
58
  type LocalRunner,
58
59
  parseLsofListeners,
59
60
  parseProcNetTcp,
60
- stripLocalLine,
61
61
  } from '@/pr/local'
62
62
  import { KEY_CHANGES } from '@/pr/paths'
63
63
  import {
@@ -68,6 +68,7 @@ import {
68
68
  type PreviewRefusal,
69
69
  type PreviewRunner,
70
70
  type RunRow,
71
+ servesChange,
71
72
  type WorkflowFile,
72
73
  } from '@/pr/preview'
73
74
  import {
@@ -110,17 +111,12 @@ interface EvidenceOptions extends ReadOptions {
110
111
 
111
112
  interface LocalOptions extends ReadOptions {
112
113
  readonly remove?: boolean
113
- readonly note?: string
114
114
  }
115
115
 
116
116
  /** Shared by every verb that resolves a pull request from the checkout's branch. */
117
117
  const AMBIGUOUS_PULL =
118
118
  'More than one pull request is open on this branch, each against another base. Name the pull request number.'
119
119
 
120
- /** What replaces the local address line once the pull request closes. */
121
- const LOCAL_REMOVED_NOTE =
122
- '_Local preview removed when the pull request closed._'
123
-
124
120
  /** How long the probe waits on a listener before reading it as not a server. */
125
121
  const LOCAL_PROBE_TIMEOUT_MS = 2_000
126
122
 
@@ -194,7 +190,7 @@ const PREVIEW_TIMEOUT_MINUTES = 15
194
190
  const PREVIEW_POLL_MS = 15_000
195
191
 
196
192
  const PREVIEW_REFUSALS: Record<
197
- PreviewRefusal | 'bad-timeout' | 'check-timeout',
193
+ PreviewRefusal | 'bad-timeout' | 'check-timeout' | 'unreadable-changes',
198
194
  string
199
195
  > = {
200
196
  'bad-timeout': '--timeout takes a positive number of minutes.',
@@ -202,6 +198,8 @@ const PREVIEW_REFUSALS: Record<
202
198
  '--check only reads the deploy runs and waits on nothing, so it takes no --timeout.',
203
199
  'no-deploy':
204
200
  'No workflow under .github/workflows/ runs pages deploy and carries a workflow_dispatch trigger, so there is nothing to dispatch.',
201
+ unserved:
202
+ "The deploy workflow's push path filter matches no path this pull request changed, so the site it publishes would not show the change. Nothing was dispatched.",
205
203
  // biome-ignore lint/suspicious/noTemplateCurlyInString: a workflow expression, not a template
206
204
  unfenced:
207
205
  'The deploy workflow passes no --branch=${{ github.ref_name }}, so a dispatch from this branch would publish it to production. Add the flag before asking for a preview.',
@@ -209,6 +207,8 @@ const PREVIEW_REFUSALS: Record<
209
207
  'The deploy never printed a canon-preview-alias line, so no address could be read. Add the alias step from the cloudflare stack reference to the workflow.',
210
208
  'gh-failed':
211
209
  'gh could not list or dispatch the deploy workflow for this branch.',
210
+ 'unreadable-changes':
211
+ 'GitHub could not list what this pull request changed, so whether the deploy serves it is unknown. Nothing was dispatched.',
212
212
  'run-failed':
213
213
  'The deploy run finished without succeeding, so no preview was published. Read the run log.',
214
214
  timeout:
@@ -508,7 +508,7 @@ export function register(program: Command): void {
508
508
  .option('--json', 'Add a machine-readable record on stdout')
509
509
  .option(
510
510
  '--preview <url>',
511
- 'Open the body with this preview address, from canon pr preview',
511
+ 'Put this preview address on the line under the heading, from canon pr preview',
512
512
  )
513
513
  .option(
514
514
  '--checklist <path>',
@@ -516,7 +516,7 @@ export function register(program: Command): void {
516
516
  )
517
517
  .option(
518
518
  '--local <url>',
519
- "Add this worktree's running server under any preview line, from canon pr local",
519
+ "Add this worktree's running server to the address line, from canon pr local",
520
520
  )
521
521
  .option(
522
522
  '--check',
@@ -544,11 +544,21 @@ export function register(program: Command): void {
544
544
  ' would-empty the render holds no cases and the marked comment holds',
545
545
  ' some, so no body is printed and a person edits the comment',
546
546
  '',
547
- "--preview puts the address on the body's first line. With no evidence",
548
- 'in the diff, the body is that line and the marker alone, reported as',
549
- 'ok, so the preview still lands in one comment a later call can edit.',
550
- 'Without --preview, an address the marked comment already opens with',
551
- 'is carried into the new body, so a re-render after a push keeps it.',
547
+ 'Every body opens with `## Evidence`, then one address line joining',
548
+ 'the hosted and local previews with " · ", carrying whichever apply,',
549
+ 'then the Base and Head line when there are screenshots.',
550
+ '',
551
+ '--preview puts the hosted address on that line. With no evidence in',
552
+ 'the diff, the body is the heading, that line, and the marker alone,',
553
+ 'reported as ok, so the preview still lands in one comment a later',
554
+ 'call can edit. Without --preview, an address the marked comment',
555
+ 'already carries is carried into the new body, so a re-render after a',
556
+ 'push keeps it, unless a deploy workflow resolves and its push path',
557
+ 'filter matches no path the pull request changed, in which case the',
558
+ 'carried hosted address is dropped. An explicit --preview is always',
559
+ 'kept. The address region above the comparison is read for each',
560
+ 'prefix wherever it sits, so a comment that opens on the addresses',
561
+ 'still carries them forward.',
552
562
  '',
553
563
  '--checklist closes the body with a visual checklist, below the',
554
564
  'comparison it annotates. A checklist the marked comment already',
@@ -558,17 +568,17 @@ export function register(program: Command): void {
558
568
  'image renders a marked body holding the checklist, so a later call',
559
569
  'finds and edits that comment.',
560
570
  '',
561
- '--local adds a **Local preview:** line under the hosted preview line,',
562
- 'or opens the body with it when there is none, and is carried forward',
563
- 'the same way. It does not by itself turn no-evidence into ok, so a',
571
+ '--local adds a **Local preview:** segment after any hosted one on the',
572
+ 'address line, or carries the line alone, and is carried forward the',
573
+ 'same way. It does not by itself turn no-evidence into ok, so a',
564
574
  'branch with a server running and nothing to show posts no link-only',
565
575
  'comment. Together with --checklist the link and the checklist land in',
566
576
  'one comment.',
567
577
  '',
568
578
  'Both reasons carry what the marked comment already posted, each field',
569
579
  'present only when that comment holds it:',
570
- ' preview the hosted **Preview:** address the comment opens with',
571
- ' local the **Local preview:** address under it',
580
+ ' preview the hosted **Preview:** address the comment carries',
581
+ ' local the **Local preview:** address beside it',
572
582
  ' checklist the checklist between its delimiters, ticks included',
573
583
  'These come from the comment already posted, never from the flags this',
574
584
  'call passed. A checklist the caller posted raw after a refused render',
@@ -582,8 +592,9 @@ export function register(program: Command): void {
582
592
  ' owed `owed` lists evidence, preview, or both, in that order',
583
593
  'evidence is owed when an evidence image changed and no comment carries',
584
594
  'the marker. preview is owed when either holds, a deploy workflow in',
585
- "this checkout resolves, and the marked comment's first line is no",
586
- '**Preview:** line. A failed deploy reads the same as a skipped one.',
595
+ 'this checkout resolves, its push path filter matches a path the pull',
596
+ 'request changed, and the marked comment carries no **Preview:**',
597
+ 'address. A failed deploy reads the same as a skipped one.',
587
598
  'The record carries `owed` and the same marked fields. --check refuses',
588
599
  'with --preview, --local, or --checklist as check-writes.',
589
600
  '',
@@ -636,9 +647,16 @@ export function register(program: Command): void {
636
647
  'A deploy without the --branch flag is refused before anything runs,',
637
648
  'because Pages publishes an unfenced deploy to production.',
638
649
  '',
650
+ "The pull request's changed paths are then matched against the",
651
+ "workflow's own on.push.paths or paths-ignore filter, since a dispatch",
652
+ 'ignores that filter and would publish a site showing none of the',
653
+ 'change. A workflow with neither filter serves every change.',
654
+ '',
639
655
  'Read `reason` on the JSON record:',
640
656
  ' ok the preview was published, with its address in `url`',
641
657
  ' no-deploy no dispatchable workflow runs pages deploy',
658
+ ' unserved the push filter matches no changed path, so nothing was',
659
+ ' dispatched, and --check answers the same',
642
660
  ' unfenced the deploy passes no --branch, so nothing was dispatched',
643
661
  ' no-alias the workflow prints no canon-preview-alias line',
644
662
  ' run-failed the deploy run finished without succeeding',
@@ -675,15 +693,7 @@ export function register(program: Command): void {
675
693
  .helpOption('-h, --help', 'Show this help message')
676
694
  .option('--root <path>', 'Worktree to read, defaulting to the cwd')
677
695
  .option('--json', 'Add a machine-readable record on stdout')
678
- .option(
679
- '--remove',
680
- "Replace the evidence comment's local preview line with a note",
681
- )
682
- .option(
683
- '--note <text>',
684
- 'What replaces the line on --remove',
685
- LOCAL_REMOVED_NOTE,
686
- )
696
+ .option('--remove', "Drop the evidence comment's preview address line")
687
697
  .addHelpText(
688
698
  'after',
689
699
  [
@@ -700,16 +710,19 @@ export function register(program: Command): void {
700
710
  'without it.',
701
711
  '',
702
712
  '--remove reads the pull request comment carrying the pr-evidence',
703
- 'marker and replaces its **Local preview:** line with --note, editing',
704
- 'the comment itself so a close workflow with no session can call it.',
713
+ 'marker and drops its address line, the **Preview:** and **Local',
714
+ 'preview:** segments together, leaving no note in its place. It edits',
715
+ 'the comment itself so a close workflow with no session can call it,',
716
+ 'and drops the hosted link too because the same close deletes the',
717
+ "branch's preview deployments.",
705
718
  '',
706
719
  'Read `reason` on the JSON record:',
707
720
  ' ok a page answered, with its address in `url`',
708
721
  ' no-server nothing inside this worktree served a page',
709
722
  ' no-listener-reader neither lsof nor /proc is available',
710
- ' removed --remove replaced the line',
723
+ ' removed --remove dropped the line',
711
724
  ' no-comment --remove found no marked comment, a no-op',
712
- ' no-line --remove found no local line, a no-op',
725
+ ' no-line --remove found no address line, a no-op',
713
726
  '',
714
727
  'Exit codes:',
715
728
  ' 0 a server was found, or --remove finished, including a no-op',
@@ -1597,18 +1610,24 @@ async function runReviewState(
1597
1610
  return 0
1598
1611
  }
1599
1612
 
1613
+ interface PullFiles {
1614
+ /** Each path that still exists at the head, tagged with whether the pull request added it. */
1615
+ readonly present: ReadonlyMap<string, boolean>
1616
+ /** Each path the pull request removed, which a deploy's push filter still counts. */
1617
+ readonly removed: readonly string[]
1618
+ }
1619
+
1600
1620
  /**
1601
- * The paths a pull request changed that still exist at its head, each tagged
1602
- * with whether the pull request added it, read through the paginated files
1603
- * endpoint so the set is the same from any checkout and never capped at the
1604
- * first view. `renamed` and `copied` count as added, since the new path has no
1621
+ * The paths a pull request changed, read through the paginated files endpoint
1622
+ * so the set is the same from any checkout and never capped at the first
1623
+ * view. `renamed` and `copied` count as added, since the new path has no
1605
1624
  * counterpart at the merge base. Returns undefined when the read fails, which
1606
1625
  * refuses rather than rendering a short set.
1607
1626
  */
1608
1627
  async function listPullFiles(
1609
1628
  cwd: string,
1610
1629
  number: number,
1611
- ): Promise<ReadonlyMap<string, boolean> | undefined> {
1630
+ ): Promise<PullFiles | undefined> {
1612
1631
  const stdout = await gh(cwd, [
1613
1632
  'api',
1614
1633
  '--paginate',
@@ -1617,15 +1636,19 @@ async function listPullFiles(
1617
1636
  '.[] | [.status, .filename] | @tsv',
1618
1637
  ])
1619
1638
  if (stdout === null) return undefined
1620
- const files = new Map<string, boolean>()
1639
+ const present = new Map<string, boolean>()
1640
+ const removed: string[] = []
1621
1641
  for (const line of stdout.split('\n').filter(Boolean)) {
1622
1642
  const [status, path] = line.split('\t')
1623
- if (status === undefined || path === undefined || status === 'removed') {
1624
- continue
1625
- }
1626
- files.set(path, status !== 'modified' && status !== 'changed')
1643
+ if (status === undefined || path === undefined) continue
1644
+ if (status === 'removed') removed.push(path)
1645
+ else present.set(path, status !== 'modified' && status !== 'changed')
1627
1646
  }
1628
- return files
1647
+ return { present, removed }
1648
+ }
1649
+
1650
+ function everyChangedPath(files: PullFiles): string[] {
1651
+ return [...files.present.keys(), ...files.removed]
1629
1652
  }
1630
1653
 
1631
1654
  /**
@@ -1729,8 +1752,8 @@ async function runEvidence(
1729
1752
  }
1730
1753
 
1731
1754
  const grouped = await groupEvidence(
1732
- [...pullFiles.keys()],
1733
- async (path) => pullFiles.get(path) === false,
1755
+ [...pullFiles.present.keys()],
1756
+ async (path) => pullFiles.present.get(path) === false,
1734
1757
  )
1735
1758
 
1736
1759
  let existingCases = 0
@@ -1766,13 +1789,20 @@ async function runEvidence(
1766
1789
  ...(carriedChecklist !== undefined && { checklist: carriedChecklist }),
1767
1790
  }
1768
1791
 
1792
+ // A deploy that resolves and does not build from any changed path makes a
1793
+ // carried hosted link point at a site that shows none of this change.
1794
+ const deploy = findDeployWorkflow(await readWorkflows(root))
1795
+ const deployServesChange =
1796
+ deploy.kind === 'found' &&
1797
+ servesChange(deploy.filter, everyChangedPath(pullFiles))
1798
+ const isUnserved = deploy.kind === 'found' && !deployServesChange
1799
+
1769
1800
  if (isCheck) {
1770
1801
  const owed = readOwed({
1771
1802
  hasEvidenceChange: grouped.kind === 'read',
1772
1803
  hasMarkedComment: hasMarkedEvidenceComment(comments),
1773
1804
  carriedPreview: carriedPreview !== undefined,
1774
- deployWorkflowFound:
1775
- findDeployWorkflow(await readWorkflows(root)).kind === 'found',
1805
+ deployServesChange,
1776
1806
  })
1777
1807
  logStep(owed.length === 0 ? 'Settled' : 'Owed')
1778
1808
  logInfo(
@@ -1838,7 +1868,7 @@ async function runEvidence(
1838
1868
  )
1839
1869
  }
1840
1870
 
1841
- const preview = opts.preview ?? carriedPreview
1871
+ const preview = opts.preview ?? (isUnserved ? undefined : carriedPreview)
1842
1872
  const local = opts.local ?? carriedLocal
1843
1873
  const checklist = suppliedChecklist ?? carriedChecklist
1844
1874
  const states = grouped.kind === 'read' ? grouped.states : []
@@ -2037,6 +2067,24 @@ async function runPreview(
2037
2067
  }
2038
2068
  const { identity } = read
2039
2069
 
2070
+ // A dispatch ignores the workflow's own push filter, so the filter is read
2071
+ // here, ahead of both the mint and the check, rather than left to GitHub.
2072
+ const changed =
2073
+ identity.number === undefined
2074
+ ? undefined
2075
+ : await listPullFiles(root, identity.number)
2076
+ if (changed === undefined) {
2077
+ return refuseWith(
2078
+ 'unreadable-changes',
2079
+ PREVIEW_REFUSALS['unreadable-changes'],
2080
+ emitJson,
2081
+ root,
2082
+ )
2083
+ }
2084
+ if (!servesChange(pick.filter, everyChangedPath(changed))) {
2085
+ return refuseWith('unserved', PREVIEW_REFUSALS.unserved, emitJson, root)
2086
+ }
2087
+
2040
2088
  if (opts.check === true) {
2041
2089
  return runPreviewCheck(root, emitJson, pick.path, identity)
2042
2090
  }
@@ -2280,7 +2328,7 @@ async function runLocal(opts: LocalOptions): Promise<number> {
2280
2328
  }
2281
2329
 
2282
2330
  /**
2283
- * Replaces the local line on the marked comment. The verb writes here rather
2331
+ * Drops the address line on the marked comment. The verb writes here rather
2284
2332
  * than handing a body back, because its caller is a close workflow with no
2285
2333
  * session to post one.
2286
2334
  */
@@ -2290,7 +2338,6 @@ async function runLocalRemove(
2290
2338
  ): Promise<number> {
2291
2339
  const root = resolve(opts.root ?? process.cwd())
2292
2340
  const emitJson = opts.json ?? false
2293
- const note = opts.note ?? LOCAL_REMOVED_NOTE
2294
2341
 
2295
2342
  intro('canon pr local --remove')
2296
2343
 
@@ -2333,9 +2380,9 @@ async function runLocalRemove(
2333
2380
  return finish('no-comment', 'No comment carries the evidence marker.')
2334
2381
  }
2335
2382
 
2336
- const stripped = stripLocalLine(marked.body, note)
2337
- if (stripped === undefined) {
2338
- return finish('no-line', 'The evidence comment carries no local line.')
2383
+ const dropped = dropAddressLine(marked.body)
2384
+ if (dropped === undefined) {
2385
+ return finish('no-line', 'The evidence comment carries no address line.')
2339
2386
  }
2340
2387
 
2341
2388
  const patched = await gh(root, [
@@ -2344,7 +2391,7 @@ async function runLocalRemove(
2344
2391
  'PATCH',
2345
2392
  `repos/{owner}/{repo}/issues/comments/${commentId}`,
2346
2393
  '-f',
2347
- `body=${stripped}`,
2394
+ `body=${dropped}`,
2348
2395
  ])
2349
2396
  if (patched === null) {
2350
2397
  return refuseWith('gh-failed', LOCAL_REFUSALS['gh-failed'], emitJson, root)
@@ -30,6 +30,11 @@ import {
30
30
  type StaleReport,
31
31
  staleMemory,
32
32
  } from '@/records/stale'
33
+ import {
34
+ type CanonicalDoc,
35
+ type CanonicalReport,
36
+ staleCanonical,
37
+ } from '@/records/stale-canonical'
33
38
  import {
34
39
  type Finding,
35
40
  type FindingRemedy,
@@ -51,7 +56,7 @@ import {
51
56
  pipeOutput,
52
57
  plural,
53
58
  } from '@/ui'
54
- import { currentWorktreeRoot, mainWorktreeRoot } from '@/worktree'
59
+ import { currentWorktreeRoot, mainWorktreeRoot } from '@/git/worktree'
55
60
 
56
61
  /** Returned when a record carries a finding, which is the gating result. */
57
62
  const EXIT_FINDINGS = 2
@@ -85,8 +90,11 @@ interface StaleCommandOptions extends ValidateCommandOptions {
85
90
  readonly days?: string
86
91
  }
87
92
 
88
- /** The one kind `stale` reads, since only a memory entry carries a review date. */
89
- const STALE_KINDS = ['memory'] as const
93
+ /**
94
+ * The kinds `stale` reads. A memory entry ages by days in the shared pen, and
95
+ * a canonical doc ages by the releases shipped since its review.
96
+ */
97
+ const STALE_KINDS = ['memory', 'canonical'] as const
90
98
 
91
99
  interface OrdinalCommandOptions extends ValidateCommandOptions {
92
100
  readonly claim?: boolean
@@ -270,14 +278,17 @@ export function register(program: Command): void {
270
278
  .option('--json', 'Add a machine-readable record on stdout')
271
279
  .option(
272
280
  '--days <n>',
273
- `Days after a review before an entry is due again (default ${DEFAULT_REVIEW_DAYS})`,
281
+ `Days after a review before a memory entry is due again (default ${DEFAULT_REVIEW_DAYS})`,
282
+ )
283
+ .option(
284
+ '--root <path>',
285
+ 'Project root, defaulting to the main worktree except on canonical',
274
286
  )
275
- .option('--root <path>', 'Project root, defaulting to the main worktree')
276
287
  .addHelpText(
277
288
  'after',
278
289
  [
279
290
  '',
280
- 'Reads the top-level entries of the memory folder, never review/, archive/,',
291
+ 'memory: reads the top-level entries of the memory folder, never review/, archive/,',
281
292
  'or index.md. An entry is due when it carries no reviewed date, a reviewed',
282
293
  'value that is not a date, or one older than --days. A path is a backticked',
283
294
  'token holding a / and a file extension, resolved against the project root',
@@ -288,17 +299,24 @@ export function register(program: Command): void {
288
299
  'Entries are ordered due first, then those citing a missing path, then the',
289
300
  'longest unreviewed, then by name, so a review takes the first N as a batch.',
290
301
  '',
302
+ 'canonical: reads REQUIREMENTS.md and ARCHITECTURE.md at whichever surface',
303
+ 'root carries each, in the current worktree since both are tracked. Each',
304
+ 'reports its reviewed date and the release tags merged into HEAD after that',
305
+ 'day, or never when it carries no date. --days does not apply.',
306
+ '',
291
307
  'Exit codes:',
292
308
  ' 0 the reading completed, whatever it found',
293
309
  ' 1 refused, with the reason on stderr or in the JSON record',
294
310
  '',
295
311
  'It gates nothing and writes nothing. A due entry is a queue position rather',
296
- 'than a failure, and a missing path is a prompt for the review to judge.',
312
+ 'than a failure, and a missing path or a release count is a prompt for the',
313
+ 'review to judge.',
297
314
  '',
298
315
  'Examples:',
299
316
  ' canon records stale memory',
300
317
  ' canon records stale memory --json',
301
318
  ' canon records stale memory --days 60 --json',
319
+ ' canon records stale canonical --json',
302
320
  '',
303
321
  ].join('\n'),
304
322
  )
@@ -560,6 +578,8 @@ async function runStale(
560
578
  )
561
579
  }
562
580
 
581
+ if (kind === 'canonical') return runStaleCanonical(opts, emitJson)
582
+
563
583
  const days = Number(opts.days ?? DEFAULT_REVIEW_DAYS)
564
584
 
565
585
  if (!Number.isInteger(days) || days <= 0) {
@@ -588,6 +608,68 @@ async function runStale(
588
608
  return 0
589
609
  }
590
610
 
611
+ /**
612
+ * Both docs are tracked, so the root is the checkout the caller stands in. The
613
+ * main worktree would answer from its own copy and miss a stamp this branch
614
+ * just wrote.
615
+ */
616
+ async function runStaleCanonical(
617
+ opts: StaleCommandOptions,
618
+ emitJson: boolean,
619
+ ): Promise<number> {
620
+ if (opts.days !== undefined) {
621
+ return reportRefusal(
622
+ 'canon records stale',
623
+ {
624
+ reason: 'bad-days',
625
+ message:
626
+ '--days does not apply to canonical, which counts releases rather than days.',
627
+ },
628
+ emitJson,
629
+ )
630
+ }
631
+
632
+ const root = opts.root ?? (await currentWorktreeRoot())
633
+ const outcome = await staleCanonical(root)
634
+
635
+ if (!outcome.ok)
636
+ return reportRefusal('canon records stale', outcome, emitJson)
637
+
638
+ if (emitJson) {
639
+ process.stdout.write(`${JSON.stringify(outcome)}\n`)
640
+ return 0
641
+ }
642
+
643
+ intro('canon records stale')
644
+ logStep('Canonical')
645
+
646
+ if (outcome.docs.length === 0) {
647
+ logInfo('neither REQUIREMENTS.md nor ARCHITECTURE.md exists')
648
+ } else {
649
+ for (const doc of outcome.docs) logInfo(describeCanonical(doc, outcome))
650
+ }
651
+
652
+ if (!outcome.tagged) logInfo('no release tags merged into HEAD')
653
+ outro()
654
+ return 0
655
+ }
656
+
657
+ function describeCanonical(
658
+ doc: CanonicalDoc,
659
+ outcome: CanonicalReport,
660
+ ): string {
661
+ if (doc.reviewed === null) {
662
+ return doc.invalidReviewed === undefined
663
+ ? `${doc.path}: never reviewed`
664
+ : `${doc.path}: reviewed value is not a date: ${doc.invalidReviewed}`
665
+ }
666
+
667
+ const since = outcome.tagged
668
+ ? `${plural(doc.releasesSince ?? 0, 'release')} since`
669
+ : 'no releases to count'
670
+ return `${doc.path}: reviewed ${doc.reviewed}, ${since}`
671
+ }
672
+
591
673
  function staleRow(entry: StaleEntry): string[] {
592
674
  return [
593
675
  entry.name,
@@ -1,7 +1,7 @@
1
1
  import { resolve } from 'node:path'
2
2
  import type { Command } from 'commander'
3
3
  import { execa } from 'execa'
4
- import { gitEnv } from '@/git-env'
4
+ import { gitEnv } from '@/git/env'
5
5
  import {
6
6
  compareMetadata,
7
7
  type CurrentMetadata,