@erclx/canon 5.32.0 → 5.33.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 (210) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/auto-ship/SKILL.md +1 -1
  3. package/claude/skills/auto-ship/references/worktree-name.md +1 -1
  4. package/claude/skills/backlog-triage/REQUIREMENT.md +2 -2
  5. package/claude/skills/backlog-triage/SKILL.md +1 -1
  6. package/claude/skills/canon-cli/REQUIREMENT.md +16 -13
  7. package/claude/skills/canon-cli/SKILL.md +75 -32
  8. package/claude/skills/canon-feedback/SKILL.md +1 -1
  9. package/claude/skills/{canvas → canvas-board}/REQUIREMENT.md +2 -2
  10. package/claude/skills/{canvas → canvas-board}/SKILL.md +2 -2
  11. package/{standards → claude/skills/canvas-board/references}/design.md +11 -2
  12. package/claude/skills/context-fold/REQUIREMENT.md +25 -33
  13. package/claude/skills/context-fold/SKILL.md +25 -70
  14. package/claude/skills/context-fold/references/classify.md +3 -3
  15. package/claude/skills/context-fold/references/completion-report.md +4 -4
  16. package/claude/skills/context-fold/references/context-refresh.md +5 -5
  17. package/{standards → claude/skills/context-fold/references}/context.md +16 -5
  18. package/claude/skills/context-fold/references/unusable-baseline.md +3 -5
  19. package/claude/skills/create-rule/SKILL.md +2 -2
  20. package/{standards → claude/skills/create-rule/references}/rule.md +12 -5
  21. package/claude/skills/create-skill/SKILL.md +2 -2
  22. package/{standards → claude/skills/create-skill/references}/skill.md +17 -14
  23. package/claude/skills/design-taste/REQUIREMENT.md +3 -3
  24. package/claude/skills/design-taste/SKILL.md +1 -1
  25. package/claude/skills/docs-sync/REQUIREMENT.md +1 -1
  26. package/claude/skills/docs-sync/SKILL.md +3 -3
  27. package/{standards → claude/skills/docs-sync/references}/docs.md +11 -0
  28. package/{standards → claude/skills/docs-sync/references}/readme.md +9 -2
  29. package/claude/skills/draft-and-pick/SKILL.md +5 -5
  30. package/claude/skills/draft-doc/SKILL.md +6 -6
  31. package/claude/skills/draft-figure/REQUIREMENT.md +2 -2
  32. package/claude/skills/draft-figure/SKILL.md +2 -2
  33. package/claude/skills/draft-figure/references/hand-drawn.md +1 -1
  34. package/claude/skills/draft-figure/references/mermaid-path.md +1 -1
  35. package/claude/skills/draft-identity/REQUIREMENT.md +2 -2
  36. package/claude/skills/draft-identity/SKILL.md +2 -2
  37. package/claude/skills/draft-ready/SKILL.md +1 -1
  38. package/claude/skills/git-branch/SKILL.md +1 -1
  39. package/{standards → claude/skills/git-branch/references}/branch.md +1 -1
  40. package/claude/skills/git-commit/SKILL.md +2 -2
  41. package/claude/skills/git-followup/REQUIREMENT.md +1 -1
  42. package/claude/skills/git-followup/SKILL.md +1 -1
  43. package/claude/skills/git-issue/SKILL.md +1 -1
  44. package/claude/skills/git-pr/SKILL.md +13 -13
  45. package/claude/skills/git-pr/references/evidence.md +2 -2
  46. package/claude/skills/git-pr/references/final-command.md +38 -14
  47. package/claude/skills/git-pr/references/labels.md +5 -1
  48. package/{standards → claude/skills/git-pr/references}/pr.md +19 -9
  49. package/{standards → claude/skills/git-pr/references}/publish.md +4 -2
  50. package/claude/skills/git-pr/references/task-number.md +1 -1
  51. package/claude/skills/git-ship/SKILL.md +15 -14
  52. package/claude/skills/git-split/SKILL.md +3 -3
  53. package/claude/skills/git-stage/SKILL.md +1 -1
  54. package/claude/skills/plan-feature/REQUIREMENT.md +1 -1
  55. package/claude/skills/review-address/SKILL.md +1 -1
  56. package/claude/skills/review-address/references/post-review-findings.md +1 -1
  57. package/claude/skills/review-pr/REQUIREMENT.md +1 -1
  58. package/claude/skills/review-pr/SKILL.md +3 -3
  59. package/claude/skills/review-pr/references/body-variants.md +2 -2
  60. package/claude/skills/review-pr/references/key-changes.md +3 -3
  61. package/claude/skills/review-pr/references/post.md +4 -2
  62. package/claude/skills/review-ui/REQUIREMENT.md +1 -1
  63. package/claude/skills/review-ui/SKILL.md +1 -1
  64. package/claude/skills/review-ui/references/post.md +1 -1
  65. package/claude/skills/role-orchestrator/REQUIREMENT.md +3 -0
  66. package/claude/skills/role-orchestrator/SKILL.md +2 -2
  67. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +2 -2
  68. package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +8 -5
  69. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +23 -1
  70. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +2 -2
  71. package/claude/skills/role-orchestrator/references/orchestrator-resume.md +3 -0
  72. package/claude/skills/role-planner/REQUIREMENT.md +2 -1
  73. package/claude/skills/role-planner/SKILL.md +13 -2
  74. package/claude/skills/role-reviewer/SKILL.md +1 -1
  75. package/claude/skills/role-worker/REQUIREMENT.md +1 -0
  76. package/claude/skills/role-worker/SKILL.md +9 -1
  77. package/claude/skills/session-map/SKILL.md +1 -1
  78. package/claude/skills/session-resume/REQUIREMENT.md +2 -2
  79. package/claude/skills/session-worktree/SKILL.md +5 -5
  80. package/claude/skills/target-setup/references/detect.md +1 -1
  81. package/claude/skills/task-board/REQUIREMENT.md +7 -12
  82. package/claude/skills/task-board/SKILL.md +5 -17
  83. package/claude/skills/task-board/references/tasks.md +2 -2
  84. package/claude/skills/task-complete/REQUIREMENT.md +47 -0
  85. package/claude/skills/task-complete/SKILL.md +86 -0
  86. package/claude/skills/{task-board → task-complete}/references/archive.md +4 -4
  87. package/claude/skills/{context-fold → task-complete}/references/receipt-sweep.md +5 -5
  88. package/claude/skills/task-complete/references/unusable-baseline.md +19 -0
  89. package/claude/skills/teach-workspace/REQUIREMENT.md +6 -2
  90. package/claude/skills/teach-workspace/SKILL.md +8 -4
  91. package/claude/skills/teach-workspace/references/lesson-craft.md +1 -1
  92. package/claude/skills/{context-fold/references/promoted-pages.md → teach-workspace/references/promotion-fold.md} +3 -3
  93. package/claude/skills/teach-workspace/references/promotion.md +3 -3
  94. package/claude/skills/ui-checklist/SKILL.md +1 -1
  95. package/claude/skills/ux-audit/SKILL.md +1 -1
  96. package/claude/skills/ux-measure/SKILL.md +1 -1
  97. package/docs/agents/canvas.md +1 -1
  98. package/docs/agents/commands.md +3 -2
  99. package/docs/agents/context-audit-checks.md +4 -4
  100. package/docs/agents/index.md +3 -3
  101. package/docs/agents/indexes.md +1 -1
  102. package/docs/agents/install-and-sync.md +6 -11
  103. package/docs/agents/key-changes.md +5 -5
  104. package/docs/agents/label-coverage.md +11 -0
  105. package/docs/agents/migrate.md +1 -1
  106. package/docs/agents/records-push.md +2 -2
  107. package/docs/agents/records-stale.md +12 -47
  108. package/docs/agents/records-validate.md +3 -7
  109. package/docs/agents/records.md +5 -27
  110. package/docs/agents/rule-citations.md +1 -1
  111. package/docs/agents/sandbox.md +4 -4
  112. package/docs/agents/skills-audit.md +1 -1
  113. package/docs/agents/tasks-plans.md +2 -2
  114. package/docs/agents/tasks.md +2 -2
  115. package/docs/target/migrations.md +10 -2
  116. package/docs/target/projects.md +1 -1
  117. package/docs/target/sync.md +1 -1
  118. package/docs/workflow/operating-model.md +2 -2
  119. package/docs/workflow/parallel-features.md +7 -7
  120. package/docs/workflow/skill-map-on-request.md +9 -9
  121. package/docs/workflow/skill-map.md +12 -11
  122. package/docs/workflow/visual-design-workflow.md +2 -2
  123. package/governance/renames.toml +0 -11
  124. package/governance/rules/canon/context.md +13 -0
  125. package/governance/rules/claude/skill.md +17 -0
  126. package/governance/stacks/base.toml +2 -4
  127. package/package.json +4 -4
  128. package/scripts/core/install-check.sh +3 -3
  129. package/scripts/lib/ui.sh +0 -159
  130. package/src/audits/catalog.ts +0 -1
  131. package/src/claude/cases/misc.ts +1 -1
  132. package/src/claude/cases/setup.ts +4 -0
  133. package/src/claude/cases/workflow.ts +6 -1
  134. package/src/claude/seeds.ts +1 -1
  135. package/src/claude/skills-audit.ts +2 -2
  136. package/src/cli/help.ts +3 -1
  137. package/src/cli/ui.ts +2 -3
  138. package/src/commands/claude.ts +1 -1
  139. package/src/commands/gov.ts +2 -15
  140. package/src/commands/labels.ts +326 -7
  141. package/src/commands/migrate.ts +6 -1
  142. package/src/commands/pr.ts +1 -1
  143. package/src/commands/records.ts +13 -399
  144. package/src/commands/sandbox.ts +3 -6
  145. package/src/commands/tasks.ts +3 -3
  146. package/src/context/audit.ts +5 -5
  147. package/src/context/folders.ts +1 -1
  148. package/src/design/budget.ts +1 -1
  149. package/src/design/components.ts +1 -1
  150. package/src/design/document.ts +1 -1
  151. package/src/design/regen.ts +1 -1
  152. package/src/design/render.ts +1 -1
  153. package/src/gate/measures.ts +1 -1
  154. package/src/gate/sequencer.ts +56 -1
  155. package/src/gate/stages.ts +1 -7
  156. package/src/gov/install.ts +2 -3
  157. package/src/gov/stacks.ts +1 -1
  158. package/src/hooks/post-merge.ts +1 -1
  159. package/src/labels/apply.ts +39 -0
  160. package/src/labels/format.ts +3 -3
  161. package/src/labels/phase.ts +3 -3
  162. package/src/labels/sections.ts +43 -0
  163. package/src/migrate/skill-names.ts +20 -1
  164. package/src/pr/paths.ts +4 -4
  165. package/src/records/backup.ts +16 -6
  166. package/src/records/stale-canonical.ts +31 -1
  167. package/src/records/validate.ts +2 -188
  168. package/src/sandbox/census.ts +1 -1
  169. package/src/sandbox/coverage.ts +2 -6
  170. package/src/sandbox/equivalence.ts +5 -44
  171. package/src/sandbox/expect.ts +4 -4
  172. package/src/sandbox/provision.ts +13 -36
  173. package/src/sandbox/scenario.ts +26 -48
  174. package/src/sandbox/tree.ts +2 -6
  175. package/src/standards/read.ts +14 -1
  176. package/src/sync/git.ts +1 -1
  177. package/src/tasks/archive.ts +5 -5
  178. package/src/tasks/branch.ts +3 -3
  179. package/src/tasks/label.ts +1 -1
  180. package/src/tasks/record.ts +2 -2
  181. package/src/tasks/validate.ts +1 -1
  182. package/standards/index.md +0 -13
  183. package/standards/standard.md +1 -7
  184. package/tooling/base/reference.md +1 -1
  185. package/tooling/base/seeds/.github/pull_request_template.md +2 -2
  186. package/tooling/claude/reference.md +1 -1
  187. package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +9 -1
  188. package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +171 -0
  189. package/tooling/claude/seeds/.claude/settings.json +9 -0
  190. package/governance/rules/standards/context.md +0 -14
  191. package/governance/rules/standards/design.md +0 -12
  192. package/governance/rules/standards/readme.md +0 -12
  193. package/governance/rules/standards/rule.md +0 -13
  194. package/governance/rules/standards/skill.md +0 -21
  195. package/governance/rules/standards/standard.md +0 -13
  196. package/scripts/config.sh +0 -11
  197. package/scripts/lib/gov.sh +0 -14
  198. package/scripts/lib/sandbox-fixtures.sh +0 -191
  199. package/scripts/lib/sandbox-git.sh +0 -125
  200. package/scripts/lib/sandbox-path.sh +0 -50
  201. package/scripts/sandbox-hook.sh +0 -99
  202. package/src/gov/standard-rules.ts +0 -179
  203. package/src/records/migrate.ts +0 -78
  204. package/src/records/stale.ts +0 -243
  205. package/src/sandbox/hooks.ts +0 -128
  206. /package/claude/skills/{canvas → canvas-board}/references/canvas.md +0 -0
  207. /package/{standards → claude/skills/draft-figure/references}/figures.md +0 -0
  208. /package/{standards → claude/skills/git-branch/references}/slug.md +0 -0
  209. /package/{standards → claude/skills/git-commit/references}/commit.md +0 -0
  210. /package/{standards → claude/skills/git-commit/references}/versioning.md +0 -0
@@ -1,8 +1,5 @@
1
- import { readFile, writeFile } from 'node:fs/promises'
2
- import { join } from 'node:path'
3
1
  import type { Command } from 'commander'
4
2
  import { pullRecords, pushRecords } from '@/records/backup'
5
- import { migrateRecord } from '@/records/migrate'
6
3
  import {
7
4
  type ClaimOutcome,
8
5
  claimOrdinal,
@@ -24,12 +21,6 @@ import {
24
21
  type SizeOutcome,
25
22
  sizeRecords,
26
23
  } from '@/records/size'
27
- import {
28
- DEFAULT_REVIEW_DAYS,
29
- type StaleEntry,
30
- type StaleReport,
31
- staleMemory,
32
- } from '@/records/stale'
33
24
  import {
34
25
  type CanonicalDoc,
35
26
  type CanonicalReport,
@@ -37,12 +28,10 @@ import {
37
28
  } from '@/records/stale-canonical'
38
29
  import {
39
30
  type Finding,
40
- type FindingRemedy,
41
31
  isRecordKind,
42
32
  isSharedScratch,
43
33
  RECORD_KINDS,
44
34
  type RecordKind,
45
- recordsDir,
46
35
  type ValidateOutcome,
47
36
  validateRecords,
48
37
  } from '@/records/validate'
@@ -62,9 +51,6 @@ import { readLivePlans, stackFindings } from '@/tasks/stack'
62
51
  /** Returned when a record carries a finding, which is the gating result. */
63
52
  const EXIT_FINDINGS = 2
64
53
 
65
- /** Returned when a record carries a known transform and `--write` was not passed. */
66
- const EXIT_MIGRATABLE = 2
67
-
68
54
  /** Returned when `--claim` loses every retry to a collision. */
69
55
  const EXIT_CONTENDED = 2
70
56
 
@@ -78,24 +64,15 @@ interface ValidateCommandOptions {
78
64
 
79
65
  type BackupCommandOptions = ValidateCommandOptions
80
66
 
81
- interface MigrateCommandOptions extends ValidateCommandOptions {
82
- readonly write?: boolean
83
- }
84
-
85
67
  interface PruneCommandOptions extends ValidateCommandOptions {
86
68
  readonly write?: boolean
87
69
  readonly olderThan?: string
88
70
  }
89
71
 
90
- interface StaleCommandOptions extends ValidateCommandOptions {
91
- readonly days?: string
92
- }
72
+ type StaleCommandOptions = ValidateCommandOptions
93
73
 
94
- /**
95
- * The kinds `stale` reads. A memory entry ages by days in the shared pen, and
96
- * a canonical doc ages by the releases shipped since its review.
97
- */
98
- const STALE_KINDS = ['memory', 'canonical'] as const
74
+ /** The kinds `stale` reads. A canonical doc ages by the releases shipped since its review. */
75
+ const STALE_KINDS = ['canonical'] as const
99
76
 
100
77
  interface OrdinalCommandOptions extends ValidateCommandOptions {
101
78
  readonly claim?: boolean
@@ -127,7 +104,6 @@ export function register(program: Command): void {
127
104
  ' plans filename, required sections, and the suggested-and-answer contract',
128
105
  ' groundwork README and current-state files, numbering, dating, and a half-closed track',
129
106
  ' intake overview file, numbering, dating, and the four bullets every item carries',
130
- ' memory filename and type prefix, frontmatter, and the body shape each type carries',
131
107
  ' standards frontmatter, the scope section and its handoff list, and a filename',
132
108
  ' derived from the governed path',
133
109
  '',
@@ -142,7 +118,6 @@ export function register(program: Command): void {
142
118
  '',
143
119
  'Examples:',
144
120
  ' canon records validate plans',
145
- ' canon records validate memory',
146
121
  ' canon records validate standards',
147
122
  ' canon records validate intake --json',
148
123
  '',
@@ -152,44 +127,6 @@ export function register(program: Command): void {
152
127
  process.exitCode = await runValidate(kind, opts)
153
128
  })
154
129
 
155
- records
156
- .command('migrate')
157
- .description('Rewrite the records a validate finding names a transform for')
158
- .argument('<kind>', `Record folder: ${RECORD_KINDS.join(', ')}`)
159
- .helpOption('-h, --help', 'Show this help message')
160
- .option('--json', 'Add a machine-readable record on stdout')
161
- .option('--write', 'Rewrite every record a transform can repair')
162
- .option(
163
- '--root <path>',
164
- 'Project root, defaulting to the main worktree except on standards',
165
- )
166
- .addHelpText(
167
- 'after',
168
- [
169
- '',
170
- 'Exit codes:',
171
- ' 0 nothing carries a known transform, or --write repaired every one',
172
- ' 1 refused, with the reason on stderr or in the JSON record, every',
173
- ' candidate it found failed to repair, or --write repaired only',
174
- ' some of them',
175
- ' 2 a record carries a known transform and --write was not passed',
176
- '',
177
- 'It reports and never writes without --write, matching canon records',
178
- 'validate: a session record has no history to undo a wrong repair from.',
179
- 'A transform is only offered where the old value is recoverable from the',
180
- 'file itself, so a finding with no transform is left for a session to fix.',
181
- '',
182
- 'Examples:',
183
- ' canon records migrate memory',
184
- ' canon records migrate memory --write',
185
- ' canon records migrate memory --json',
186
- '',
187
- ].join('\n'),
188
- )
189
- .action(async (kind: string, opts: MigrateCommandOptions) => {
190
- process.exitCode = await runMigrate(kind, opts)
191
- })
192
-
193
130
  records
194
131
  .command('ordinal')
195
132
  .description(
@@ -272,51 +209,30 @@ export function register(program: Command): void {
272
209
  records
273
210
  .command('stale')
274
211
  .description(
275
- 'Report which entries are due for review and which cite paths the tree no longer holds',
212
+ 'Report when the requirements record was last reviewed and how many releases have shipped since',
276
213
  )
277
214
  .argument('<kind>', `Record folder: ${STALE_KINDS.join(', ')}`)
278
215
  .helpOption('-h, --help', 'Show this help message')
279
216
  .option('--json', 'Add a machine-readable record on stdout')
280
- .option(
281
- '--days <n>',
282
- `Days after a review before a memory entry is due again (default ${DEFAULT_REVIEW_DAYS})`,
283
- )
284
- .option(
285
- '--root <path>',
286
- 'Project root, defaulting to the main worktree except on canonical',
287
- )
217
+ .option('--root <path>', 'Project root, defaulting to the current worktree')
288
218
  .addHelpText(
289
219
  'after',
290
220
  [
291
- '',
292
- 'memory: reads the top-level entries of the memory folder, never review/, archive/,',
293
- 'or index.md. An entry is due when it carries no reviewed date, a reviewed',
294
- 'value that is not a date, or one older than --days. A path is a backticked',
295
- 'token holding a / and a file extension, resolved against the project root',
296
- 'after any :line or #heading anchor is stripped. Tokens under .canon/,',
297
- 'scratch, or worktrees, and any holding a placeholder, glob, variable, or',
298
- 'elided ... segment, are never reported.',
299
- '',
300
- 'Entries are ordered due first, then those citing a missing path, then the',
301
- 'longest unreviewed, then by name, so a review takes the first N as a batch.',
302
221
  '',
303
222
  'canonical: reads REQUIREMENTS.md at whichever surface root carries it,',
304
223
  'in the current worktree since it is tracked. It reports its reviewed date',
305
224
  'and the release tags merged into HEAD after that day, or never when it',
306
- 'carries no date. --days does not apply.',
225
+ 'carries no date.',
307
226
  '',
308
227
  'Exit codes:',
309
228
  ' 0 the reading completed, whatever it found',
310
229
  ' 1 refused, with the reason on stderr or in the JSON record',
311
230
  '',
312
- 'It gates nothing and writes nothing. A due entry is a queue position rather',
313
- 'than a failure, and a missing path or a release count is a prompt for the',
314
- 'review to judge.',
231
+ 'It gates nothing and writes nothing. A release count is a prompt for the',
232
+ 'review to judge rather than a failure.',
315
233
  '',
316
234
  'Examples:',
317
- ' canon records stale memory',
318
- ' canon records stale memory --json',
319
- ' canon records stale memory --days 60 --json',
235
+ ' canon records stale canonical',
320
236
  ' canon records stale canonical --json',
321
237
  '',
322
238
  ].join('\n'),
@@ -357,8 +273,8 @@ export function register(program: Command): void {
357
273
  ' delete failed',
358
274
  ' 2 candidates exist and --write was not passed',
359
275
  '',
360
- 'It writes nothing until --write is passed, matching canon records',
361
- 'migrate: a session record has no history to undo a wrong delete from.',
276
+ 'It writes nothing until --write is passed, since a session record has',
277
+ 'no history to undo a wrong delete from.',
362
278
  'It reads mtime like canon records size, so a machine restored by canon',
363
279
  'records pull reads everything as new, which fails safe by offering',
364
280
  'nothing.',
@@ -579,34 +495,7 @@ async function runStale(
579
495
  )
580
496
  }
581
497
 
582
- if (kind === 'canonical') return runStaleCanonical(opts, emitJson)
583
-
584
- const days = Number(opts.days ?? DEFAULT_REVIEW_DAYS)
585
-
586
- if (!Number.isInteger(days) || days <= 0) {
587
- return reportRefusal(
588
- 'canon records stale',
589
- {
590
- reason: 'bad-days',
591
- message: `--days must be a positive whole number of days, got ${opts.days}.`,
592
- },
593
- emitJson,
594
- )
595
- }
596
-
597
- const root = opts.root ?? (await mainWorktreeRoot())
598
- const outcome = await staleMemory(root, days)
599
-
600
- if (!outcome.ok)
601
- return reportRefusal('canon records stale', outcome, emitJson)
602
-
603
- if (emitJson) {
604
- process.stdout.write(`${JSON.stringify(outcome)}\n`)
605
- return 0
606
- }
607
-
608
- reportStale(outcome)
609
- return 0
498
+ return runStaleCanonical(opts, emitJson)
610
499
  }
611
500
 
612
501
  /**
@@ -618,18 +507,6 @@ async function runStaleCanonical(
618
507
  opts: StaleCommandOptions,
619
508
  emitJson: boolean,
620
509
  ): Promise<number> {
621
- if (opts.days !== undefined) {
622
- return reportRefusal(
623
- 'canon records stale',
624
- {
625
- reason: 'bad-days',
626
- message:
627
- '--days does not apply to canonical, which counts releases rather than days.',
628
- },
629
- emitJson,
630
- )
631
- }
632
-
633
510
  const root = opts.root ?? (await currentWorktreeRoot())
634
511
  const outcome = await staleCanonical(root)
635
512
 
@@ -671,48 +548,6 @@ function describeCanonical(
671
548
  return `${doc.path}: reviewed ${doc.reviewed}, ${since}`
672
549
  }
673
550
 
674
- function staleRow(entry: StaleEntry): string[] {
675
- return [
676
- entry.name,
677
- entry.reviewed ?? (entry.invalidReviewed ? 'invalid' : 'never'),
678
- entry.unresolved.join(', '),
679
- ]
680
- }
681
-
682
- function reportStale(outcome: StaleReport): void {
683
- const due = outcome.entries.filter((entry) => entry.due)
684
- const moved = outcome.entries.filter((entry) => entry.unresolved.length > 0)
685
-
686
- intro('canon records stale')
687
- logStep('Due')
688
-
689
- if (due.length === 0) {
690
- logInfo(`none, every entry reviewed within ${plural(outcome.days, 'day')}`)
691
- } else {
692
- const headers = ['entry', 'reviewed', 'unresolved']
693
- const rows = due.map(staleRow)
694
- const widths = headers.map((header, column) =>
695
- columnWidth(
696
- header,
697
- rows.map((row) => row[column]),
698
- ),
699
- )
700
- const render = (cells: readonly string[]): string =>
701
- cells
702
- .map((cell, column) => cell.padEnd(widths[column]))
703
- .join(' ')
704
- .trimEnd()
705
-
706
- pipeOutput([render(headers), ...rows.map(render)].join('\n'))
707
- }
708
-
709
- logStep('Total')
710
- logInfo(
711
- `${outcome.due} of ${outcome.total} due, ${moved.length} citing a missing path, in ${outcome.folder}`,
712
- )
713
- outro()
714
- }
715
-
716
551
  function pruneRow(entry: PruneUnit): string[] {
717
552
  return [
718
553
  entry.path,
@@ -917,8 +752,7 @@ async function runValidate(
917
752
 
918
753
  /**
919
754
  * Appends the one plans finding that reads across files. It sits in the command
920
- * layer because `src/records/` sits below `src/tasks/`, and `migrate` iterates
921
- * `validateRecords` alone, so it never meets a finding with no safe repair.
755
+ * layer because `src/records/` sits below `src/tasks/`.
922
756
  */
923
757
  async function validateWithStacks(
924
758
  root: string,
@@ -992,226 +826,6 @@ function describe(found: Finding): string {
992
826
  return `${scope}${found.subject} ${found.message}`
993
827
  }
994
828
 
995
- export interface Repair {
996
- readonly record: string
997
- readonly remedy: FindingRemedy
998
- readonly path: string
999
- readonly text: string
1000
- }
1001
-
1002
- export interface Refusal {
1003
- readonly record: string
1004
- readonly message: string
1005
- }
1006
-
1007
- /** The message a rejected promise leaves, for a record whose read or write failed. */
1008
- function describeFailure(reason: unknown): string {
1009
- return reason instanceof Error ? reason.message : String(reason)
1010
- }
1011
-
1012
- /**
1013
- * Runs every finding's transform, without writing anything back.
1014
- *
1015
- * `Promise.allSettled` rather than `Promise.all`, so a record whose read
1016
- * fails, such as one deleted between `validate`'s listing and this read,
1017
- * becomes a refusal for that one record rather than an unhandled rejection
1018
- * that `program.parse()` has no top-level catch for.
1019
- */
1020
- async function attemptMigrations(
1021
- dir: string,
1022
- findings: readonly Finding[],
1023
- ): Promise<{ repaired: Repair[]; refused: Refusal[] }> {
1024
- const candidates = findings.filter(
1025
- (found): found is Finding & { readonly remedy: FindingRemedy } =>
1026
- found.remedy !== undefined,
1027
- )
1028
-
1029
- const settled = await Promise.allSettled(
1030
- candidates.map(async (found) => {
1031
- const path = join(dir, found.record)
1032
- const outcome = migrateRecord(
1033
- found.remedy,
1034
- found.record,
1035
- await readFile(path, 'utf8'),
1036
- )
1037
- return { record: found.record, remedy: found.remedy, path, outcome }
1038
- }),
1039
- )
1040
-
1041
- const repaired: Repair[] = []
1042
- const refused: Refusal[] = []
1043
-
1044
- settled.forEach((result, index) => {
1045
- if (result.status === 'rejected') {
1046
- refused.push({
1047
- record: candidates[index].record,
1048
- message: `could not be read: ${describeFailure(result.reason)}`,
1049
- })
1050
- return
1051
- }
1052
-
1053
- const { record, remedy, path, outcome } = result.value
1054
-
1055
- if (outcome.ok) {
1056
- repaired.push({ record, remedy, path, text: outcome.text })
1057
- } else {
1058
- refused.push({ record, message: outcome.message })
1059
- }
1060
- })
1061
-
1062
- return { repaired, refused }
1063
- }
1064
-
1065
- /**
1066
- * Writes every repair, independently. `Promise.allSettled` so one record's
1067
- * write failing does not abort the writes that would otherwise have
1068
- * succeeded, per the concurrency standard's rule on batched partial failure.
1069
- */
1070
- async function writeRepairs(
1071
- repaired: readonly Repair[],
1072
- ): Promise<{ written: Repair[]; failed: Refusal[] }> {
1073
- const settled = await Promise.allSettled(
1074
- repaired.map((entry) => writeFile(entry.path, entry.text, 'utf8')),
1075
- )
1076
-
1077
- const written: Repair[] = []
1078
- const failed: Refusal[] = []
1079
-
1080
- settled.forEach((result, index) => {
1081
- const entry = repaired[index]
1082
-
1083
- if (result.status === 'fulfilled') {
1084
- written.push(entry)
1085
- } else {
1086
- failed.push({
1087
- record: entry.record,
1088
- message: `could not be written: ${describeFailure(result.reason)}`,
1089
- })
1090
- }
1091
- })
1092
-
1093
- return { written, failed }
1094
- }
1095
-
1096
- async function runMigrate(
1097
- kind: string,
1098
- opts: MigrateCommandOptions,
1099
- ): Promise<number> {
1100
- const emitJson = opts.json ?? false
1101
-
1102
- if (!isRecordKind(kind)) {
1103
- return reportRefusal(
1104
- 'canon records migrate',
1105
- {
1106
- reason: 'unknown-kind',
1107
- message: `Not a record kind: ${kind}. Expected one of: ${RECORD_KINDS.join(', ')}.`,
1108
- },
1109
- emitJson,
1110
- )
1111
- }
1112
-
1113
- const root = opts.root ?? (await defaultRoot(kind))
1114
- const outcome = await validateRecords(root, kind)
1115
-
1116
- if (!outcome.ok) {
1117
- return reportRefusal('canon records migrate', outcome, emitJson)
1118
- }
1119
-
1120
- const write = opts.write ?? false
1121
- const { repaired, refused } = await attemptMigrations(
1122
- recordsDir(root, kind),
1123
- outcome.findings,
1124
- )
1125
-
1126
- if (!write) {
1127
- return reportMigrate(root, outcome.kind, repaired, refused, write, emitJson)
1128
- }
1129
-
1130
- const { written, failed } = await writeRepairs(repaired)
1131
-
1132
- return reportMigrate(
1133
- root,
1134
- outcome.kind,
1135
- written,
1136
- [...refused, ...failed],
1137
- write,
1138
- emitJson,
1139
- )
1140
- }
1141
-
1142
- function reportMigrate(
1143
- root: string,
1144
- kind: RecordKind,
1145
- repaired: readonly Repair[],
1146
- refused: readonly Refusal[],
1147
- write: boolean,
1148
- emitJson: boolean,
1149
- ): number {
1150
- const total = repaired.length + refused.length
1151
-
1152
- if (emitJson) {
1153
- process.stdout.write(
1154
- `${JSON.stringify({
1155
- ok: true,
1156
- root,
1157
- kind,
1158
- written: write,
1159
- migrated: repaired.map((entry) => entry.record),
1160
- refused,
1161
- })}\n`,
1162
- )
1163
- } else {
1164
- intro('canon records migrate')
1165
-
1166
- if (total === 0) {
1167
- logStep('Clean')
1168
- logInfo('no finding carries a known transform')
1169
- } else {
1170
- logStep(write ? 'Rewritten' : 'Would rewrite')
1171
- if (repaired.length === 0) {
1172
- logInfo('none, every candidate refused, see below')
1173
- } else {
1174
- for (const entry of repaired)
1175
- logInfo(`${entry.record}: ${entry.remedy}`)
1176
- }
1177
-
1178
- if (refused.length > 0) {
1179
- logStep('Refused')
1180
- for (const entry of refused)
1181
- logWarn(`${entry.record}: ${entry.message}`)
1182
- }
1183
-
1184
- if (!write && repaired.length > 0) {
1185
- logInfo('Re-run with --write to apply.')
1186
- }
1187
- }
1188
-
1189
- outro()
1190
- }
1191
-
1192
- return migrateExitCode(repaired, refused, write)
1193
- }
1194
-
1195
- /**
1196
- * `repaired` carries only records a transform actually fixed, whether this
1197
- * is a dry run or the set `--write` wrote, so exit 2 (a write is available)
1198
- * never fires when every candidate refused. A dry run reaching that state
1199
- * previously returned the same code as a genuine offer to write, telling a
1200
- * caller `--write` would repair something when it would repair nothing.
1201
- */
1202
- export function migrateExitCode(
1203
- repaired: readonly Repair[],
1204
- refused: readonly Refusal[],
1205
- write: boolean,
1206
- ): number {
1207
- const total = repaired.length + refused.length
1208
-
1209
- if (total === 0) return 0
1210
- if (repaired.length === 0) return 1
1211
- if (!write) return EXIT_MIGRATABLE
1212
- return refused.length > 0 ? 1 : 0
1213
- }
1214
-
1215
829
  async function runOrdinal(
1216
830
  kind: string,
1217
831
  slug: string,
@@ -126,13 +126,10 @@ function getCategories(): string[] {
126
126
  return listCategories(SANDBOX_DIR)
127
127
  }
128
128
 
129
- /** Lists a stem in both forms too, so picking it reaches the refusal that names it. */
130
129
  function getCommands(category: string): string[] {
131
- const { scenarios, ambiguous } = listScenarioFiles(
132
- join(SANDBOX_DIR, category),
133
- )
130
+ const { scenarios } = listScenarioFiles(join(SANDBOX_DIR, category))
134
131
 
135
- return [...scenarios.map((s) => s.command), ...ambiguous].sort()
132
+ return scenarios.map((s) => s.command)
136
133
  }
137
134
 
138
135
  async function interactivePicker(): Promise<string> {
@@ -188,7 +185,7 @@ function readEnvelope(path: string | undefined): RunEnvelope | undefined {
188
185
 
189
186
  /**
190
187
  * Shared by `--writes` and `--escapes`, which are both a newline-delimited
191
- * path list written by `run.sh`. Undefined when no file was given, which is
188
+ * path list written by `canon sandbox run`. Undefined when no file was given, which is
192
189
  * not the same as a run that produced no paths. Write scope and escape scope
193
190
  * both need that distinction: an empty list is a finding, an absent list is a
194
191
  * gap in what the caller supplied.
@@ -347,7 +347,7 @@ export function register(program: Command): void {
347
347
  [
348
348
  '',
349
349
  'Exit codes:',
350
- ' 0 the branch is derived and conforms to standards/branch.md',
350
+ ' 0 the branch is derived and conforms to claude/skills/git-branch/references/branch.md',
351
351
  ' 1 refused as no-plan, archived, or bad-input',
352
352
  ' 2 derived, and conforms is false because the slug breaks a cap',
353
353
  '',
@@ -566,7 +566,7 @@ export function register(program: Command): void {
566
566
  'same second can take the same answer. --claim reserves the label',
567
567
  'under .canon/ordinal-locks/ and counts every reservation in the',
568
568
  'scan, so two claims return different labels. A dead claim leaves a',
569
- 'gap, which standards/versioning.md permits.',
569
+ 'gap, which claude/skills/git-commit/references/versioning.md permits.',
570
570
  '',
571
571
  'Examples:',
572
572
  ' canon tasks next-label',
@@ -1245,7 +1245,7 @@ function reportBranch(
1245
1245
  `derived from ${outcome.plan}, ${outcome.words} words and ${outcome.branch.length} characters.`,
1246
1246
  )
1247
1247
  logError(
1248
- 'The slug breaks a cap in standards/branch.md, so hand the row to a person rather than renaming it.',
1248
+ 'The slug breaks a cap in claude/skills/git-branch/references/branch.md, so hand the row to a person rather than renaming it.',
1249
1249
  )
1250
1250
  outro()
1251
1251
 
@@ -10,7 +10,7 @@ import { isStubSeed } from '@/tooling/seed-marker'
10
10
  * Checkpoint quoted from the standard stating it. It is not a cap.
11
11
  *
12
12
  * Entry length rests on one entry per domain, which is a domain fact, so
13
- * `standards/context.md` keeps it. Depth and bullet weight read the same over
13
+ * `claude/skills/context-fold/references/context.md` keeps it. Depth and bullet weight read the same over
14
14
  * any markdown file, so they are stated at the attribute tier and measured by
15
15
  * `canon markdown audit` rather than here.
16
16
  */
@@ -111,7 +111,7 @@ const PROVENANCE: readonly { kind: ProvenanceKind; pattern: RegExp }[] = [
111
111
  /**
112
112
  * Verbs that read the date behind them as stamping a measurement.
113
113
  *
114
- * `standards/context.md` cuts a date attached to a change and permits one
114
+ * `claude/skills/context-fold/references/context.md` cuts a date attached to a change and permits one
115
115
  * stamping a measurement, so a pattern matching every ISO date reports against
116
116
  * a rule that admits half of what it finds. Separating the two needs the clause
117
117
  * rather than the token, since `measured on 2026-08-19` and `overturned on
@@ -154,7 +154,7 @@ const SENTENCE_END = /[.!?:;]\s(?=[^.!?:;]*$)/
154
154
  * JSON record publishes as `checkpoints.provenanceFolder` breaks every consumer
155
155
  * reading that field for a gain of one word.
156
156
  *
157
- * `standards/context.md` opens its scope by handing figures to the
157
+ * `claude/skills/context-fold/references/context.md` opens its scope by handing figures to the
158
158
  * `draft-figure` skill, so a marker reported in a diagrams entry would cite a
159
159
  * rule that entry's own standard routes elsewhere. The length and table
160
160
  * checkpoints are quoted from the same standard and keep reaching every audited
@@ -164,7 +164,7 @@ const SENTENCE_END = /[.!?:;]\s(?=[^.!?:;]*$)/
164
164
  * What gates here is a rule only this standard states. Bullet weight does not,
165
165
  * since `claude/skills/markdown-craft/references/markdown.md` owns that checkpoint across document types and
166
166
  * its remedy sends the overflow to prose, which any entry type can act on.
167
- * `standards/context.md` specializes that remedy for an entry carrying
167
+ * `claude/skills/context-fold/references/context.md` specializes that remedy for an entry carrying
168
168
  * decisions, and specializing a rule narrows the advice rather than the measure.
169
169
  *
170
170
  * Restating the exclusion in the sibling standards was the alternative for what
@@ -241,7 +241,7 @@ export interface EntryReport {
241
241
  }
242
242
 
243
243
  /**
244
- * The three questions `standards/context.md` asks of an entry past the
244
+ * The three questions `claude/skills/context-fold/references/context.md` asks of an entry past the
245
245
  * checkpoint, in the order it states them.
246
246
  *
247
247
  * The standard calls no entry over the checkpoint wrong. It asks whether the
@@ -10,7 +10,7 @@ import { SURFACE_ROOTS } from '@/roots/surface'
10
10
  * A named list rather than the index-plus-entry contract read off disk, so a
11
11
  * generated tree satisfying that contract is never measured against a rule
12
12
  * written for per-domain narrative. This list names the folders
13
- * `standards/context.md` governs, and `--folder` admits another without an edit
13
+ * `claude/skills/context-fold/references/context.md` governs, and `--folder` admits another without an edit
14
14
  * here.
15
15
  *
16
16
  * It doubles as the citation check's scope, since `citationPattern` spells only
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The prose budget `standards/design.md` sets for a design record, and the
2
+ * The prose budget `claude/skills/canvas-board/references/design.md` sets for a design record, and the
3
3
  * counter the `design` gate stage reads it through.
4
4
  *
5
5
  * The limits live here and in the standard, never in the stage, so the stage
@@ -15,7 +15,7 @@
15
15
  * `buildDesignCss`'s `components` option, which is what widening that option
16
16
  * from a boolean to `boolean | readonly Component[]` bought.
17
17
  *
18
- * It does not live in `canon/DESIGN.md`. `standards/design.md` keeps CSS class
18
+ * It does not live in `canon/DESIGN.md`. `claude/skills/canvas-board/references/design.md` keeps CSS class
19
19
  * names out of that record and says they live in code, which is here.
20
20
  */
21
21
 
@@ -94,7 +94,7 @@ function borderRows(tokens: readonly BorderToken[]): string[][] {
94
94
  * and every reason behind them lives in the entry.
95
95
  */
96
96
  const LEAD = [
97
- 'Authoring guidance: `standards/design.md`. Rendered from `src/design/tokens.ts` by `canon design regen`, so edit the module and never this file.',
97
+ 'Authoring guidance: `claude/skills/canvas-board/references/design.md`. Rendered from `src/design/tokens.ts` by `canon design regen`, so edit the module and never this file.',
98
98
  'Why each value was chosen lives in `canon/context/design/tokens.md`.',
99
99
  ].join(' ')
100
100
 
@@ -16,7 +16,7 @@ import { NEUTRAL_TOKENS } from '@/design/neutral'
16
16
  * gate stage is what keeps silently out of it.
17
17
  */
18
18
 
19
- /** The rendered record, at the path `standards/design.md` fixes for it. */
19
+ /** The rendered record, at the path `claude/skills/canvas-board/references/design.md` fixes for it. */
20
20
  export const DESIGN_DOCUMENT = join('canon', 'DESIGN.md')
21
21
 
22
22
  /**