@erclx/canon 4.0.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 (643) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +129 -0
  3. package/claude/.claude-plugin/plugin.json +19 -0
  4. package/claude/skills/bash-cli-script/REQUIREMENT.md +42 -0
  5. package/claude/skills/bash-cli-script/SKILL.md +48 -0
  6. package/claude/skills/bash-cli-script/references/template.md +43 -0
  7. package/claude/skills/bash-script/REQUIREMENT.md +36 -0
  8. package/claude/skills/bash-script/SKILL.md +100 -0
  9. package/claude/skills/bash-script/references/patterns.md +349 -0
  10. package/claude/skills/canon-cli/REQUIREMENT.md +41 -0
  11. package/claude/skills/canon-cli/SKILL.md +103 -0
  12. package/claude/skills/canon-feedback-file/REQUIREMENT.md +40 -0
  13. package/claude/skills/canon-feedback-file/SKILL.md +80 -0
  14. package/claude/skills/canon-feedback-triage/REQUIREMENT.md +40 -0
  15. package/claude/skills/canon-feedback-triage/SKILL.md +63 -0
  16. package/claude/skills/canon-operator/REQUIREMENT.md +61 -0
  17. package/claude/skills/canon-operator/SKILL.md +108 -0
  18. package/claude/skills/canon-rollout/REQUIREMENT.md +59 -0
  19. package/claude/skills/canon-rollout/SKILL.md +147 -0
  20. package/claude/skills/canon-screencast/REQUIREMENT.md +39 -0
  21. package/claude/skills/canon-screencast/SKILL.md +167 -0
  22. package/claude/skills/canon-slides-draft/REQUIREMENT.md +39 -0
  23. package/claude/skills/canon-slides-draft/SKILL.md +62 -0
  24. package/claude/skills/ci-workflow/REQUIREMENT.md +40 -0
  25. package/claude/skills/ci-workflow/SKILL.md +65 -0
  26. package/claude/skills/ci-workflow/references/workflows.md +98 -0
  27. package/claude/skills/claude-address-review/REQUIREMENT.md +57 -0
  28. package/claude/skills/claude-address-review/SKILL.md +212 -0
  29. package/claude/skills/claude-address-review/references/rebase-conflicts.md +39 -0
  30. package/claude/skills/claude-autoship/REQUIREMENT.md +50 -0
  31. package/claude/skills/claude-autoship/SKILL.md +207 -0
  32. package/claude/skills/claude-design-extract/REQUIREMENT.md +42 -0
  33. package/claude/skills/claude-design-extract/SKILL.md +102 -0
  34. package/claude/skills/claude-diagram/REQUIREMENT.md +45 -0
  35. package/claude/skills/claude-diagram/SKILL.md +177 -0
  36. package/claude/skills/claude-docs/REQUIREMENT.md +60 -0
  37. package/claude/skills/claude-docs/SKILL.md +287 -0
  38. package/claude/skills/claude-docs/references/anchor-sweep.md +58 -0
  39. package/claude/skills/claude-docs/references/wireframe-sweep.md +45 -0
  40. package/claude/skills/claude-feature/REQUIREMENT.md +36 -0
  41. package/claude/skills/claude-feature/SKILL.md +115 -0
  42. package/claude/skills/claude-groundwork/REQUIREMENT.md +48 -0
  43. package/claude/skills/claude-groundwork/SKILL.md +142 -0
  44. package/claude/skills/claude-intake/REQUIREMENT.md +49 -0
  45. package/claude/skills/claude-intake/SKILL.md +114 -0
  46. package/claude/skills/claude-intake-answer/REQUIREMENT.md +48 -0
  47. package/claude/skills/claude-intake-answer/SKILL.md +90 -0
  48. package/claude/skills/claude-markdown-propose/REQUIREMENT.md +48 -0
  49. package/claude/skills/claude-markdown-propose/SKILL.md +118 -0
  50. package/claude/skills/claude-markdown-propose/references/format.md +107 -0
  51. package/claude/skills/claude-memory-capture/REQUIREMENT.md +50 -0
  52. package/claude/skills/claude-memory-capture/SKILL.md +101 -0
  53. package/claude/skills/claude-memory-review/REQUIREMENT.md +50 -0
  54. package/claude/skills/claude-memory-review/SKILL.md +210 -0
  55. package/claude/skills/claude-memory-review/references/receipt-format.md +48 -0
  56. package/claude/skills/claude-orchestrate/REQUIREMENT.md +121 -0
  57. package/claude/skills/claude-orchestrate/SKILL.md +241 -0
  58. package/claude/skills/claude-orchestrate/references/orchestrator-dispatch.md +132 -0
  59. package/claude/skills/claude-orchestrate/references/orchestrator-handoff.md +32 -0
  60. package/claude/skills/claude-orchestrate/references/orchestrator-parked.md +72 -0
  61. package/claude/skills/claude-orchestrate/references/orchestrator-poll.md +87 -0
  62. package/claude/skills/claude-orchestrate/references/orchestrator-resume.md +30 -0
  63. package/claude/skills/claude-orchestrate/references/orchestrator-sweep.md +21 -0
  64. package/claude/skills/claude-orchestrate/scripts/poll.sh +373 -0
  65. package/claude/skills/claude-orchestrate/scripts/watch.sh +181 -0
  66. package/claude/skills/claude-pr-review/REQUIREMENT.md +47 -0
  67. package/claude/skills/claude-pr-review/SKILL.md +295 -0
  68. package/claude/skills/claude-review/REQUIREMENT.md +39 -0
  69. package/claude/skills/claude-review/SKILL.md +142 -0
  70. package/claude/skills/claude-seed-sync/REQUIREMENT.md +45 -0
  71. package/claude/skills/claude-seed-sync/SKILL.md +156 -0
  72. package/claude/skills/claude-standards-audit/REQUIREMENT.md +33 -0
  73. package/claude/skills/claude-standards-audit/SKILL.md +99 -0
  74. package/claude/skills/claude-tasks/REQUIREMENT.md +43 -0
  75. package/claude/skills/claude-tasks/SKILL.md +161 -0
  76. package/claude/skills/claude-teach/REQUIREMENT.md +56 -0
  77. package/claude/skills/claude-teach/SKILL.md +196 -0
  78. package/claude/skills/claude-teach/references/lesson-craft.md +59 -0
  79. package/claude/skills/claude-teach/references/pedagogy.md +67 -0
  80. package/claude/skills/claude-teach/references/promotion.md +54 -0
  81. package/claude/skills/claude-ui-test/REQUIREMENT.md +40 -0
  82. package/claude/skills/claude-ui-test/SKILL.md +77 -0
  83. package/claude/skills/claude-ux-audit/REQUIREMENT.md +40 -0
  84. package/claude/skills/claude-ux-audit/SKILL.md +79 -0
  85. package/claude/skills/claude-ux-measure/REQUIREMENT.md +48 -0
  86. package/claude/skills/claude-ux-measure/SKILL.md +122 -0
  87. package/claude/skills/claude-worker/REQUIREMENT.md +54 -0
  88. package/claude/skills/claude-worker/SKILL.md +96 -0
  89. package/claude/skills/claude-worktree/REQUIREMENT.md +58 -0
  90. package/claude/skills/claude-worktree/SKILL.md +136 -0
  91. package/claude/skills/create-rule/REQUIREMENT.md +45 -0
  92. package/claude/skills/create-rule/SKILL.md +68 -0
  93. package/claude/skills/create-skill/REQUIREMENT.md +38 -0
  94. package/claude/skills/create-skill/SKILL.md +32 -0
  95. package/claude/skills/create-snippet/REQUIREMENT.md +39 -0
  96. package/claude/skills/create-snippet/SKILL.md +30 -0
  97. package/claude/skills/create-standard/REQUIREMENT.md +35 -0
  98. package/claude/skills/create-standard/SKILL.md +29 -0
  99. package/claude/skills/decision-escalate/REQUIREMENT.md +45 -0
  100. package/claude/skills/decision-escalate/SKILL.md +79 -0
  101. package/claude/skills/docs-sync/REQUIREMENT.md +41 -0
  102. package/claude/skills/docs-sync/SKILL.md +95 -0
  103. package/claude/skills/git-branch/REQUIREMENT.md +38 -0
  104. package/claude/skills/git-branch/SKILL.md +60 -0
  105. package/claude/skills/git-commit/REQUIREMENT.md +36 -0
  106. package/claude/skills/git-commit/SKILL.md +49 -0
  107. package/claude/skills/git-followup/REQUIREMENT.md +43 -0
  108. package/claude/skills/git-followup/SKILL.md +48 -0
  109. package/claude/skills/git-issue/REQUIREMENT.md +38 -0
  110. package/claude/skills/git-issue/SKILL.md +65 -0
  111. package/claude/skills/git-pr/REQUIREMENT.md +50 -0
  112. package/claude/skills/git-pr/SKILL.md +164 -0
  113. package/claude/skills/git-pr/references/labels.md +95 -0
  114. package/claude/skills/git-ship/REQUIREMENT.md +42 -0
  115. package/claude/skills/git-ship/SKILL.md +55 -0
  116. package/claude/skills/git-split/REQUIREMENT.md +39 -0
  117. package/claude/skills/git-split/SKILL.md +162 -0
  118. package/claude/skills/git-stage/REQUIREMENT.md +39 -0
  119. package/claude/skills/git-stage/SKILL.md +73 -0
  120. package/claude/skills/git-worktree/REQUIREMENT.md +38 -0
  121. package/claude/skills/git-worktree/SKILL.md +130 -0
  122. package/claude/skills/migration-claude-md/REQUIREMENT.md +40 -0
  123. package/claude/skills/migration-claude-md/SKILL.md +76 -0
  124. package/claude/skills/migration-context/REQUIREMENT.md +36 -0
  125. package/claude/skills/migration-context/SKILL.md +95 -0
  126. package/claude/skills/migration-standards-drop/REQUIREMENT.md +55 -0
  127. package/claude/skills/migration-standards-drop/SKILL.md +113 -0
  128. package/claude/skills/migration-superseded/REQUIREMENT.md +44 -0
  129. package/claude/skills/migration-superseded/SKILL.md +115 -0
  130. package/claude/skills/project-commands/REQUIREMENT.md +42 -0
  131. package/claude/skills/project-commands/SKILL.md +85 -0
  132. package/claude/skills/restate-plainly/REQUIREMENT.md +41 -0
  133. package/claude/skills/restate-plainly/SKILL.md +39 -0
  134. package/claude/skills/session-map/REQUIREMENT.md +57 -0
  135. package/claude/skills/session-map/SKILL.md +70 -0
  136. package/claude/skills/session-resume/REQUIREMENT.md +49 -0
  137. package/claude/skills/session-resume/SKILL.md +51 -0
  138. package/claude/skills/setup-gov/REQUIREMENT.md +37 -0
  139. package/claude/skills/setup-gov/SKILL.md +77 -0
  140. package/claude/skills/setup-indexes/REQUIREMENT.md +45 -0
  141. package/claude/skills/setup-indexes/SKILL.md +153 -0
  142. package/claude/skills/setup-init/REQUIREMENT.md +45 -0
  143. package/claude/skills/setup-init/SKILL.md +127 -0
  144. package/claude/skills/setup-plugins/REQUIREMENT.md +42 -0
  145. package/claude/skills/setup-plugins/SKILL.md +81 -0
  146. package/claude/skills/setup-plugins/references/plugin-catalog.md +53 -0
  147. package/claude/skills/setup-verify/REQUIREMENT.md +39 -0
  148. package/claude/skills/setup-verify/SKILL.md +51 -0
  149. package/claude/skills/systematic-debugging/REQUIREMENT.md +41 -0
  150. package/claude/skills/systematic-debugging/SKILL.md +70 -0
  151. package/claude/skills/write-human/REQUIREMENT.md +46 -0
  152. package/claude/skills/write-human/SKILL.md +68 -0
  153. package/claude/skills/write-human/references/density.md +38 -0
  154. package/claude/skills/write-human/references/machine-tells.md +107 -0
  155. package/claude/skills/write-human/references/source-material.md +37 -0
  156. package/claude/skills/youtube-transcripts/REQUIREMENT.md +38 -0
  157. package/claude/skills/youtube-transcripts/SKILL.md +34 -0
  158. package/docs/agents/audits.md +98 -0
  159. package/docs/agents/capture.md +37 -0
  160. package/docs/agents/census.md +23 -0
  161. package/docs/agents/commands.md +146 -0
  162. package/docs/agents/comments.md +34 -0
  163. package/docs/agents/context-audit-checks.md +120 -0
  164. package/docs/agents/context-audit.md +83 -0
  165. package/docs/agents/counts.md +76 -0
  166. package/docs/agents/demo.md +86 -0
  167. package/docs/agents/docs.md +17 -0
  168. package/docs/agents/gate.md +84 -0
  169. package/docs/agents/index.md +47 -0
  170. package/docs/agents/indexes.md +35 -0
  171. package/docs/agents/install-and-sync.md +385 -0
  172. package/docs/agents/intake.md +81 -0
  173. package/docs/agents/key-changes.md +103 -0
  174. package/docs/agents/label-coverage.md +73 -0
  175. package/docs/agents/markdown-audit.md +197 -0
  176. package/docs/agents/output-shape.md +70 -0
  177. package/docs/agents/overview.md +26 -0
  178. package/docs/agents/records.md +170 -0
  179. package/docs/agents/restated.md +81 -0
  180. package/docs/agents/review-classification.md +77 -0
  181. package/docs/agents/routing.md +61 -0
  182. package/docs/agents/rule-citations.md +98 -0
  183. package/docs/agents/sandbox.md +71 -0
  184. package/docs/agents/scripting.md +149 -0
  185. package/docs/agents/sessions.md +120 -0
  186. package/docs/agents/skills-audit.md +94 -0
  187. package/docs/agents/skills-reach.md +64 -0
  188. package/docs/agents/standards-audit.md +38 -0
  189. package/docs/agents/state-scoped-risk.md +105 -0
  190. package/docs/agents/superseded.md +85 -0
  191. package/docs/agents/targets.md +83 -0
  192. package/docs/agents/tasks.md +200 -0
  193. package/docs/agents/teach.md +158 -0
  194. package/docs/agents/test-order.md +56 -0
  195. package/docs/agents/worktrees.md +62 -0
  196. package/docs/ai-workflow.md +317 -0
  197. package/docs/index.md +23 -0
  198. package/docs/operating-model.md +223 -0
  199. package/docs/target-projects.md +258 -0
  200. package/docs/visual-design-workflow.md +151 -0
  201. package/docs/zshrc-aliases.md +65 -0
  202. package/governance/rules/ci/700-ci-workflow.md +44 -0
  203. package/governance/rules/claude/500-prose.md +15 -0
  204. package/governance/rules/claude/501-markdown.md +14 -0
  205. package/governance/rules/claude/510-context.md +28 -0
  206. package/governance/rules/claude/511-indexes.md +14 -0
  207. package/governance/rules/claude/520-wireframes.md +12 -0
  208. package/governance/rules/claude/530-requirements.md +11 -0
  209. package/governance/rules/claude/540-architecture.md +11 -0
  210. package/governance/rules/claude/550-design.md +11 -0
  211. package/governance/rules/claude/555-tasks.md +12 -0
  212. package/governance/rules/claude/556-groundwork.md +11 -0
  213. package/governance/rules/claude/557-intake.md +11 -0
  214. package/governance/rules/claude/558-plan.md +22 -0
  215. package/governance/rules/claude/559-memory.md +11 -0
  216. package/governance/rules/claude/560-diagrams.md +18 -0
  217. package/governance/rules/claude/561-teach.md +13 -0
  218. package/governance/rules/claude/562-session.md +15 -0
  219. package/governance/rules/claude/570-skill.md +24 -0
  220. package/governance/rules/claude/575-hooks.md +17 -0
  221. package/governance/rules/claude/576-settings.md +14 -0
  222. package/governance/rules/claude/580-readme.md +11 -0
  223. package/governance/rules/claude/590-rule-authoring.md +12 -0
  224. package/governance/rules/claude/591-standard-authoring.md +12 -0
  225. package/governance/rules/claude/592-claude-md.md +19 -0
  226. package/governance/rules/core/000-constitution.md +30 -0
  227. package/governance/rules/core/005-behavior.md +27 -0
  228. package/governance/rules/core/010-testing.md +35 -0
  229. package/governance/rules/core/015-output.md +20 -0
  230. package/governance/rules/core/020-concurrency.md +22 -0
  231. package/governance/rules/core/025-indexes.md +9 -0
  232. package/governance/rules/core/030-error-handling.md +31 -0
  233. package/governance/rules/core/035-tasks.md +13 -0
  234. package/governance/rules/core/040-performance.md +20 -0
  235. package/governance/rules/core/045-memory.md +12 -0
  236. package/governance/rules/core/050-logging.md +20 -0
  237. package/governance/rules/core/055-scratch.md +9 -0
  238. package/governance/rules/core/060-naming.md +19 -0
  239. package/governance/rules/core/065-spelling.md +19 -0
  240. package/governance/rules/core/070-planning.md +18 -0
  241. package/governance/rules/core/075-dependencies.md +25 -0
  242. package/governance/rules/core/080-config-comments.md +22 -0
  243. package/governance/rules/core/085-worktrees.md +17 -0
  244. package/governance/rules/core/087-git.md +11 -0
  245. package/governance/rules/core/090-code-comments.md +39 -0
  246. package/governance/rules/framework/200-react.md +51 -0
  247. package/governance/rules/framework/210-astro.md +41 -0
  248. package/governance/rules/framework/220-fastapi.md +43 -0
  249. package/governance/rules/framework/230-nextjs.md +48 -0
  250. package/governance/rules/framework/250-tailwind.md +32 -0
  251. package/governance/rules/framework/260-shadcn.md +34 -0
  252. package/governance/rules/lang/100-typescript.md +40 -0
  253. package/governance/rules/lang/110-python.md +42 -0
  254. package/governance/rules/lang/120-bash.md +19 -0
  255. package/governance/rules/lib/300-testing-ts.md +39 -0
  256. package/governance/rules/lib/305-e2e-reliability.md +34 -0
  257. package/governance/rules/lib/306-test-scope.md +25 -0
  258. package/governance/rules/lib/310-zod.md +25 -0
  259. package/governance/rules/lib/320-tanstack-query.md +32 -0
  260. package/governance/rules/lib/330-testing-py.md +44 -0
  261. package/governance/rules/lib/340-pydantic.md +38 -0
  262. package/governance/rules/lib/350-security-web.md +32 -0
  263. package/governance/rules/lib/360-security-server.md +39 -0
  264. package/governance/rules/lib/370-database.md +35 -0
  265. package/governance/rules/snippets/505-at-references.md +9 -0
  266. package/governance/rules/ui/400-ui.md +36 -0
  267. package/governance/rules/ui/410-a11y.md +48 -0
  268. package/governance/rules/ui/420-forms.md +36 -0
  269. package/governance/rules/ui/430-ux-completeness.md +65 -0
  270. package/governance/rules/ui/440-surface-capture.md +34 -0
  271. package/governance/rules/ui/450-link-behavior.md +19 -0
  272. package/governance/stacks/astro.toml +2 -0
  273. package/governance/stacks/base.toml +8 -0
  274. package/governance/stacks/node-server.toml +2 -0
  275. package/governance/stacks/node.toml +2 -0
  276. package/governance/stacks/python-fastapi.toml +2 -0
  277. package/governance/stacks/python.toml +2 -0
  278. package/governance/stacks/react.toml +2 -0
  279. package/package.json +69 -0
  280. package/scripts/config.sh +11 -0
  281. package/scripts/core/bootstrap.sh +81 -0
  282. package/scripts/core/check-color-source.sh +41 -0
  283. package/scripts/core/check-ignore-parity.sh +162 -0
  284. package/scripts/core/check-plugin-boundary.sh +45 -0
  285. package/scripts/core/check-seed-independence.sh +59 -0
  286. package/scripts/core/check-skill-paths.sh +24 -0
  287. package/scripts/core/clean.sh +36 -0
  288. package/scripts/core/install-check.sh +101 -0
  289. package/scripts/core/list-seed-roots.sh +18 -0
  290. package/scripts/core/regen-claude-copies.sh +10 -0
  291. package/scripts/core/regen-hero.sh +217 -0
  292. package/scripts/core/regen-indexes.sh +10 -0
  293. package/scripts/core/regen-tooling-paths.sh +61 -0
  294. package/scripts/core/repair-bare-flag.sh +19 -0
  295. package/scripts/core/snapshot.sh +134 -0
  296. package/scripts/core/update.sh +35 -0
  297. package/scripts/docs/list.sh +165 -0
  298. package/scripts/lib/frontmatter.sh +30 -0
  299. package/scripts/lib/gov.sh +14 -0
  300. package/scripts/lib/sandbox-fixtures.sh +191 -0
  301. package/scripts/lib/sandbox-git.sh +125 -0
  302. package/scripts/lib/sandbox-path.sh +206 -0
  303. package/scripts/lib/tooling.sh +35 -0
  304. package/scripts/lib/ui.sh +266 -0
  305. package/scripts/lib/worktree.sh +20 -0
  306. package/scripts/manage-sandbox.sh +466 -0
  307. package/scripts/snippets/create.sh +156 -0
  308. package/scripts/standards/list.sh +115 -0
  309. package/scripts/tooling/create.sh +109 -0
  310. package/scripts/tooling/verify.sh +179 -0
  311. package/snippets/align.md +12 -0
  312. package/snippets/claude/decision-memo.md +39 -0
  313. package/snippets/claude/feature-recap.md +19 -0
  314. package/snippets/claude/figma-steps.md +24 -0
  315. package/snippets/compact-summary.md +5 -0
  316. package/snippets/decision-help.md +6 -0
  317. package/snippets/meta-prompt.md +14 -0
  318. package/snippets/research-prompt.md +7 -0
  319. package/snippets/session-notes.md +11 -0
  320. package/snippets/snippets.toml +5 -0
  321. package/snippets/step-by-step.md +10 -0
  322. package/snippets/web-research.md +21 -0
  323. package/src/audits/baseline.ts +201 -0
  324. package/src/audits/catalog.ts +876 -0
  325. package/src/audits/run.ts +204 -0
  326. package/src/autoship/classify.ts +75 -0
  327. package/src/autoship/paths.ts +51 -0
  328. package/src/binary.ts +16 -0
  329. package/src/browser/engine.ts +40 -0
  330. package/src/census/count.ts +113 -0
  331. package/src/claude/cases/all.ts +24 -0
  332. package/src/claude/cases/authoring.ts +53 -0
  333. package/src/claude/cases/claude-workflow.ts +158 -0
  334. package/src/claude/cases/git.ts +44 -0
  335. package/src/claude/cases/misc.ts +27 -0
  336. package/src/claude/cases/setup.ts +94 -0
  337. package/src/claude/gitignore.ts +51 -0
  338. package/src/claude/routing.ts +283 -0
  339. package/src/claude/seeds-list.ts +47 -0
  340. package/src/claude/seeds.ts +150 -0
  341. package/src/claude/settings.ts +151 -0
  342. package/src/claude/skills-audit.ts +228 -0
  343. package/src/claude/skills-drift.ts +156 -0
  344. package/src/claude/skills-list.ts +99 -0
  345. package/src/claude/skills-rank.ts +320 -0
  346. package/src/claude/skills-reach.ts +227 -0
  347. package/src/cli-run.ts +43 -0
  348. package/src/cli.ts +200 -0
  349. package/src/commands/audits.ts +350 -0
  350. package/src/commands/autoship.ts +129 -0
  351. package/src/commands/capture.ts +133 -0
  352. package/src/commands/census.ts +105 -0
  353. package/src/commands/claude.ts +1286 -0
  354. package/src/commands/comments.ts +240 -0
  355. package/src/commands/context.ts +857 -0
  356. package/src/commands/demo.ts +389 -0
  357. package/src/commands/deps.ts +173 -0
  358. package/src/commands/design.ts +36 -0
  359. package/src/commands/docs.ts +60 -0
  360. package/src/commands/feedback-format.ts +23 -0
  361. package/src/commands/feedback.ts +112 -0
  362. package/src/commands/gate.ts +189 -0
  363. package/src/commands/gov.ts +1265 -0
  364. package/src/commands/indexes.ts +184 -0
  365. package/src/commands/init.ts +113 -0
  366. package/src/commands/intake.ts +406 -0
  367. package/src/commands/inventory.ts +256 -0
  368. package/src/commands/labels.ts +361 -0
  369. package/src/commands/markdown.ts +544 -0
  370. package/src/commands/migrate.ts +175 -0
  371. package/src/commands/pass-through.ts +39 -0
  372. package/src/commands/pr.ts +411 -0
  373. package/src/commands/records.ts +728 -0
  374. package/src/commands/sandbox.ts +468 -0
  375. package/src/commands/secrets.ts +132 -0
  376. package/src/commands/serve.ts +159 -0
  377. package/src/commands/sessions.ts +408 -0
  378. package/src/commands/slides.ts +126 -0
  379. package/src/commands/snippets.ts +84 -0
  380. package/src/commands/standards.ts +247 -0
  381. package/src/commands/sync.ts +428 -0
  382. package/src/commands/targets.ts +319 -0
  383. package/src/commands/tasks.ts +743 -0
  384. package/src/commands/teach.ts +786 -0
  385. package/src/commands/tooling.ts +573 -0
  386. package/src/commands/transcripts.ts +44 -0
  387. package/src/commands/upgrade.ts +231 -0
  388. package/src/commands/wiki.ts +100 -0
  389. package/src/commands/worktrees.ts +191 -0
  390. package/src/comments/scan.ts +338 -0
  391. package/src/comments/trend.ts +207 -0
  392. package/src/comments/vocabulary.ts +85 -0
  393. package/src/context/architecture.ts +364 -0
  394. package/src/context/audit.ts +790 -0
  395. package/src/context/citations.ts +196 -0
  396. package/src/context/folders.ts +186 -0
  397. package/src/context/gate.ts +57 -0
  398. package/src/context/index-drift.ts +64 -0
  399. package/src/context/narration.ts +99 -0
  400. package/src/copy.ts +30 -0
  401. package/src/counts/catalogs.ts +96 -0
  402. package/src/counts/numbers.ts +79 -0
  403. package/src/counts/scan.ts +314 -0
  404. package/src/demo/beats.ts +135 -0
  405. package/src/demo/compile.ts +326 -0
  406. package/src/demo/container.ts +63 -0
  407. package/src/demo/cursors.ts +55 -0
  408. package/src/demo/drive.ts +357 -0
  409. package/src/demo/pointer.ts +178 -0
  410. package/src/demo/theme.ts +112 -0
  411. package/src/deps/audit.ts +153 -0
  412. package/src/design/parse.ts +116 -0
  413. package/src/design/render.ts +249 -0
  414. package/src/docs/read.ts +77 -0
  415. package/src/exec.ts +16 -0
  416. package/src/exempt-marker.ts +43 -0
  417. package/src/frontmatter.ts +13 -0
  418. package/src/gate/measures.ts +682 -0
  419. package/src/gate/sequencer.ts +386 -0
  420. package/src/gate/stages.ts +412 -0
  421. package/src/git-env.ts +36 -0
  422. package/src/git-files.ts +105 -0
  423. package/src/git-ignore.ts +46 -0
  424. package/src/github-format.ts +13 -0
  425. package/src/github.ts +24 -0
  426. package/src/gov/adapter.ts +103 -0
  427. package/src/gov/citations.ts +514 -0
  428. package/src/gov/consumed.ts +129 -0
  429. package/src/gov/install.ts +132 -0
  430. package/src/gov/list.ts +106 -0
  431. package/src/gov/payload.ts +39 -0
  432. package/src/gov/restated.ts +814 -0
  433. package/src/gov/stacks.ts +205 -0
  434. package/src/gov/superseded.ts +415 -0
  435. package/src/gov/test-order.ts +407 -0
  436. package/src/indexes/frontmatter.ts +46 -0
  437. package/src/indexes/regen.ts +84 -0
  438. package/src/indexes/render.ts +201 -0
  439. package/src/indexes/walk.ts +83 -0
  440. package/src/init/flags.ts +60 -0
  441. package/src/init/plan.ts +114 -0
  442. package/src/init/run.ts +46 -0
  443. package/src/init/steps.ts +77 -0
  444. package/src/intake/folder.ts +320 -0
  445. package/src/intake/items.ts +174 -0
  446. package/src/inventory/config.ts +117 -0
  447. package/src/inventory/group.ts +76 -0
  448. package/src/inventory/subjects.ts +114 -0
  449. package/src/inventory/walk.ts +129 -0
  450. package/src/labels/audit.ts +82 -0
  451. package/src/labels/coverage.ts +79 -0
  452. package/src/labels/map.ts +101 -0
  453. package/src/labels/phase.ts +95 -0
  454. package/src/markdown/bans.ts +94 -0
  455. package/src/markdown/files.ts +102 -0
  456. package/src/markdown/gate.ts +28 -0
  457. package/src/markdown/scan.ts +295 -0
  458. package/src/markdown/structure.ts +730 -0
  459. package/src/migrate/apply.ts +115 -0
  460. package/src/migrate/plan.ts +103 -0
  461. package/src/migrate/rename.ts +183 -0
  462. package/src/pr/bijection.ts +145 -0
  463. package/src/pr/paths.ts +335 -0
  464. package/src/process/harness.ts +167 -0
  465. package/src/project-root.ts +19 -0
  466. package/src/records/backup.ts +455 -0
  467. package/src/records/migrate.ts +78 -0
  468. package/src/records/size.ts +260 -0
  469. package/src/records/validate.ts +1162 -0
  470. package/src/sandbox/census.ts +228 -0
  471. package/src/sandbox/coverage.ts +115 -0
  472. package/src/sandbox/expect.ts +629 -0
  473. package/src/sandbox/tree.ts +47 -0
  474. package/src/secrets/marker.ts +30 -0
  475. package/src/secrets/patterns.ts +142 -0
  476. package/src/secrets/scan.ts +123 -0
  477. package/src/secrets/shipped.ts +111 -0
  478. package/src/seed-marker.ts +74 -0
  479. package/src/serve/static.ts +322 -0
  480. package/src/sessions/claim.ts +85 -0
  481. package/src/sessions/live.ts +79 -0
  482. package/src/sessions/registry.ts +137 -0
  483. package/src/sessions/resolve.ts +333 -0
  484. package/src/slides/layouts.ts +391 -0
  485. package/src/slides/open.ts +18 -0
  486. package/src/slides/parse.ts +84 -0
  487. package/src/slides/render.ts +88 -0
  488. package/src/slides/styles.ts +44 -0
  489. package/src/snippets/categories.ts +66 -0
  490. package/src/snippets/list.ts +32 -0
  491. package/src/snippets/presets.ts +50 -0
  492. package/src/standards/audit.ts +132 -0
  493. package/src/standards/read.ts +100 -0
  494. package/src/sync/check.ts +692 -0
  495. package/src/sync/engine.ts +576 -0
  496. package/src/sync/git.ts +225 -0
  497. package/src/sync/history.ts +123 -0
  498. package/src/sync/layout.ts +140 -0
  499. package/src/sync/reverse.ts +268 -0
  500. package/src/sync/seeds-report.ts +126 -0
  501. package/src/sync/stamp.ts +358 -0
  502. package/src/sync/target.ts +70 -0
  503. package/src/sync/workflow.ts +200 -0
  504. package/src/target.ts +43 -0
  505. package/src/targets/pulls.ts +250 -0
  506. package/src/targets/registry.ts +203 -0
  507. package/src/targets/resolve.ts +145 -0
  508. package/src/targets/sweep.ts +246 -0
  509. package/src/tasks/archive.ts +506 -0
  510. package/src/tasks/record.ts +311 -0
  511. package/src/tasks/trunk.ts +89 -0
  512. package/src/tasks/validate.ts +1001 -0
  513. package/src/teach/lesson.ts +180 -0
  514. package/src/teach/workspace.ts +842 -0
  515. package/src/tooling/gitignore.ts +122 -0
  516. package/src/tooling/inject.ts +193 -0
  517. package/src/tooling/list.ts +39 -0
  518. package/src/tooling/manifest.ts +176 -0
  519. package/src/tooling/package.ts +166 -0
  520. package/src/tooling/read.ts +65 -0
  521. package/src/tooling/scan.ts +147 -0
  522. package/src/tooling/stamp.ts +44 -0
  523. package/src/transcripts/fetch.ts +156 -0
  524. package/src/transcripts/metadata.ts +54 -0
  525. package/src/transcripts/vtt.ts +114 -0
  526. package/src/ui.ts +266 -0
  527. package/src/version/compare.ts +53 -0
  528. package/src/version/installed.ts +40 -0
  529. package/src/version/manager.ts +67 -0
  530. package/src/version/skew.ts +192 -0
  531. package/src/wiki/init.ts +85 -0
  532. package/src/worktree.ts +143 -0
  533. package/src/worktrees/reclaim.ts +306 -0
  534. package/standards/architecture.md +72 -0
  535. package/standards/branch.md +59 -0
  536. package/standards/commit.md +72 -0
  537. package/standards/context.md +151 -0
  538. package/standards/design.md +93 -0
  539. package/standards/diagrams.md +152 -0
  540. package/standards/glossary.md +75 -0
  541. package/standards/groundwork.md +211 -0
  542. package/standards/index.md +36 -0
  543. package/standards/intake.md +192 -0
  544. package/standards/issue.md +94 -0
  545. package/standards/markdown.md +137 -0
  546. package/standards/memory.md +144 -0
  547. package/standards/plan.md +172 -0
  548. package/standards/pr.md +139 -0
  549. package/standards/publish.md +51 -0
  550. package/standards/readme.md +208 -0
  551. package/standards/requirements.md +70 -0
  552. package/standards/rule.md +118 -0
  553. package/standards/session.md +109 -0
  554. package/standards/skill.md +300 -0
  555. package/standards/slug.md +39 -0
  556. package/standards/snippets.md +76 -0
  557. package/standards/standard.md +170 -0
  558. package/standards/tasks.md +254 -0
  559. package/standards/teach.md +153 -0
  560. package/standards/versioning.md +71 -0
  561. package/standards/wireframes.md +113 -0
  562. package/tooling/astro/configs/astro.config.mjs +31 -0
  563. package/tooling/astro/configs/eslint.config.js +79 -0
  564. package/tooling/astro/configs/playwright.config.ts +26 -0
  565. package/tooling/astro/configs/tsconfig.json +12 -0
  566. package/tooling/astro/configs/vitest.config.ts +22 -0
  567. package/tooling/astro/manifest.toml +34 -0
  568. package/tooling/astro/reference.md +60 -0
  569. package/tooling/base/configs/.editorconfig +5 -0
  570. package/tooling/base/configs/.github/pull_request_template.md +18 -0
  571. package/tooling/base/configs/.github/workflows/verify.yml +35 -0
  572. package/tooling/base/configs/.husky/commit-msg +1 -0
  573. package/tooling/base/configs/.husky/post-merge +61 -0
  574. package/tooling/base/configs/.husky/post-rewrite +21 -0
  575. package/tooling/base/configs/.husky/pre-commit +1 -0
  576. package/tooling/base/configs/.husky/pre-push +1 -0
  577. package/tooling/base/configs/.prettierrc +12 -0
  578. package/tooling/base/configs/.shellcheckrc +1 -0
  579. package/tooling/base/configs/.vscode/extensions.json +9 -0
  580. package/tooling/base/configs/.vscode/settings.json +3 -0
  581. package/tooling/base/configs/commitlint.config.js +11 -0
  582. package/tooling/base/configs/scripts/verify.sh +64 -0
  583. package/tooling/base/manifest.toml +30 -0
  584. package/tooling/base/reference.md +98 -0
  585. package/tooling/base/seeds/.claude/context/ci.md +33 -0
  586. package/tooling/base/seeds/.claude/context/development.md +37 -0
  587. package/tooling/base/seeds/.claude/context/index.md +11 -0
  588. package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
  589. package/tooling/base/seeds/.cspell/tech-stack.txt +19 -0
  590. package/tooling/base/seeds/.lintstagedrc +8 -0
  591. package/tooling/base/seeds/.prettierignore +0 -0
  592. package/tooling/base/seeds/cspell.json +20 -0
  593. package/tooling/claude/manifest.toml +14 -0
  594. package/tooling/claude/reference.md +79 -0
  595. package/tooling/claude/seeds/.claude/ARCHITECTURE.md +13 -0
  596. package/tooling/claude/seeds/.claude/DESIGN.md +62 -0
  597. package/tooling/claude/seeds/.claude/REQUIREMENTS.md +18 -0
  598. package/tooling/claude/seeds/.claude/diagrams/index.md +8 -0
  599. package/tooling/claude/seeds/.claude/hooks/index-reminder.sh +50 -0
  600. package/tooling/claude/seeds/.claude/hooks/memory-index.sh +68 -0
  601. package/tooling/claude/seeds/.claude/hooks/path-form.sh +57 -0
  602. package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +52 -0
  603. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +86 -0
  604. package/tooling/claude/seeds/.claude/hooks/tasks-index.sh +71 -0
  605. package/tooling/claude/seeds/.claude/memory/index.md +8 -0
  606. package/tooling/claude/seeds/.claude/settings.json +47 -0
  607. package/tooling/claude/seeds/.claude/tasks/index.md +8 -0
  608. package/tooling/claude/seeds/.claude/wireframes/index.md +8 -0
  609. package/tooling/claude/seeds/CLAUDE.md +29 -0
  610. package/tooling/claude/user/settings.template.json +10 -0
  611. package/tooling/claude/user/statusline-command.sh +53 -0
  612. package/tooling/python/configs/.coveragerc +14 -0
  613. package/tooling/python/configs/.python-version +1 -0
  614. package/tooling/python/configs/mypy.ini +6 -0
  615. package/tooling/python/configs/pytest.ini +4 -0
  616. package/tooling/python/configs/ruff.toml +15 -0
  617. package/tooling/python/configs/scripts/verify.sh +77 -0
  618. package/tooling/python/manifest.toml +16 -0
  619. package/tooling/python/reference.md +66 -0
  620. package/tooling/python/seeds/.cspell/tech-stack.txt +19 -0
  621. package/tooling/python/seeds/tests/test_smoke.py +2 -0
  622. package/tooling/vite-react/configs/playwright.config.ts +26 -0
  623. package/tooling/vite-react/configs/tsconfig.json +35 -0
  624. package/tooling/vite-react/configs/vite.config.ts +24 -0
  625. package/tooling/vite-react/configs/vitest.config.ts +26 -0
  626. package/tooling/vite-react/manifest.toml +23 -0
  627. package/tooling/vite-react/reference.md +55 -0
  628. package/tooling/vite-react/seeds/.cspell/project-terms.txt +1 -0
  629. package/tooling/vite-react/seeds/.cspell/tech-stack.txt +1 -0
  630. package/tooling/web/configs/.github/workflows/verify.yml +134 -0
  631. package/tooling/web/configs/.vscode/extensions.json +13 -0
  632. package/tooling/web/configs/.vscode/settings.json +10 -0
  633. package/tooling/web/configs/e2e/home.spec.ts +6 -0
  634. package/tooling/web/configs/e2e/screenshot.ts +53 -0
  635. package/tooling/web/configs/eslint.config.js +82 -0
  636. package/tooling/web/configs/scripts/screenshot.sh +28 -0
  637. package/tooling/web/configs/scripts/verify.sh +80 -0
  638. package/tooling/web/configs/scripts/worktree-port.sh +74 -0
  639. package/tooling/web/configs/src/test/setup.ts +8 -0
  640. package/tooling/web/manifest.toml +58 -0
  641. package/tooling/web/reference.md +115 -0
  642. package/tooling/web/seeds/.cspell/tech-stack.txt +18 -0
  643. package/tsconfig.json +14 -0
@@ -0,0 +1,728 @@
1
+ import { readFile, writeFile } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+ import type { Command } from 'commander'
4
+ import { BACKED_FOLDERS, pullRecords, pushRecords } from '@/records/backup'
5
+ import { migrateRecord } from '@/records/migrate'
6
+ import {
7
+ type FolderSize,
8
+ formatBytes,
9
+ GROWTH_WINDOWS,
10
+ SIZED_FOLDERS,
11
+ type SizeOutcome,
12
+ sizeRecords,
13
+ } from '@/records/size'
14
+ import {
15
+ type Finding,
16
+ type FindingRemedy,
17
+ isRecordKind,
18
+ isSharedScratch,
19
+ RECORD_KINDS,
20
+ type RecordKind,
21
+ recordsDir,
22
+ type ValidateOutcome,
23
+ validateRecords,
24
+ } from '@/records/validate'
25
+ import {
26
+ intro,
27
+ logError,
28
+ logInfo,
29
+ logStep,
30
+ logWarn,
31
+ outro,
32
+ pipeOutput,
33
+ plural,
34
+ } from '@/ui'
35
+ import { currentWorktreeRoot, mainWorktreeRoot } from '@/worktree'
36
+
37
+ /** Returned when a record carries a finding, which is the gating result. */
38
+ const EXIT_FINDINGS = 2
39
+
40
+ /** Returned when a record carries a known transform and `--write` was not passed. */
41
+ const EXIT_MIGRATABLE = 2
42
+
43
+ interface ValidateCommandOptions {
44
+ readonly json?: boolean
45
+ readonly root?: string
46
+ }
47
+
48
+ type BackupCommandOptions = ValidateCommandOptions
49
+
50
+ interface MigrateCommandOptions extends ValidateCommandOptions {
51
+ readonly write?: boolean
52
+ }
53
+
54
+ export function register(program: Command): void {
55
+ const records = program
56
+ .command('records')
57
+ .description(
58
+ 'Check a governed corpus against its standard, and back up the session records under .claude/',
59
+ )
60
+ .helpOption('-h, --help', 'Show this help message')
61
+
62
+ records
63
+ .command('validate')
64
+ .description('Report where a file and the standard governing it disagree')
65
+ .argument('<kind>', `Record folder: ${RECORD_KINDS.join(', ')}`)
66
+ .helpOption('-h, --help', 'Show this help message')
67
+ .option('--json', 'Add a machine-readable record on stdout')
68
+ .option(
69
+ '--root <path>',
70
+ 'Project root, defaulting to the main worktree except on standards',
71
+ )
72
+ .addHelpText(
73
+ 'after',
74
+ [
75
+ '',
76
+ 'Checks:',
77
+ ' plans filename, required sections, and the suggested-and-answer contract',
78
+ ' groundwork README and current-state files, numbering, dating, and a half-closed track',
79
+ ' intake overview file, numbering, dating, and the four bullets every item carries',
80
+ ' memory filename and type prefix, frontmatter, and the body shape each type carries',
81
+ ' standards frontmatter, the scope section and its handoff list, and a filename',
82
+ ' derived from the governed path',
83
+ '',
84
+ 'Exit codes:',
85
+ ' 0 every check passed',
86
+ ' 1 refused, with the reason on stderr or in the JSON record',
87
+ ' 2 at least one record carries a finding',
88
+ '',
89
+ 'It reports and never writes, so a session fixes what the report names. A',
90
+ 'session record has no history to undo a wrong repair from, and a standard',
91
+ 'is installed and cited everywhere, so a rename is larger than a file move.',
92
+ '',
93
+ 'Examples:',
94
+ ' canon records validate plans',
95
+ ' canon records validate memory',
96
+ ' canon records validate standards',
97
+ ' canon records validate intake --json',
98
+ '',
99
+ ].join('\n'),
100
+ )
101
+ .action(async (kind: string, opts: ValidateCommandOptions) => {
102
+ process.exitCode = await runValidate(kind, opts)
103
+ })
104
+
105
+ records
106
+ .command('migrate')
107
+ .description('Rewrite the records a validate finding names a transform for')
108
+ .argument('<kind>', `Record folder: ${RECORD_KINDS.join(', ')}`)
109
+ .helpOption('-h, --help', 'Show this help message')
110
+ .option('--json', 'Add a machine-readable record on stdout')
111
+ .option('--write', 'Rewrite every record a transform can repair')
112
+ .option(
113
+ '--root <path>',
114
+ 'Project root, defaulting to the main worktree except on standards',
115
+ )
116
+ .addHelpText(
117
+ 'after',
118
+ [
119
+ '',
120
+ 'Exit codes:',
121
+ ' 0 nothing carries a known transform, or --write repaired every one',
122
+ ' 1 refused, with the reason on stderr or in the JSON record, every',
123
+ ' candidate it found failed to repair, or --write repaired only',
124
+ ' some of them',
125
+ ' 2 a record carries a known transform and --write was not passed',
126
+ '',
127
+ 'It reports and never writes without --write, matching canon records',
128
+ 'validate: a session record has no history to undo a wrong repair from.',
129
+ 'A transform is only offered where the old value is recoverable from the',
130
+ 'file itself, so a finding with no transform is left for a session to fix.',
131
+ '',
132
+ 'Examples:',
133
+ ' canon records migrate memory',
134
+ ' canon records migrate memory --write',
135
+ ' canon records migrate memory --json',
136
+ '',
137
+ ].join('\n'),
138
+ )
139
+ .action(async (kind: string, opts: MigrateCommandOptions) => {
140
+ process.exitCode = await runMigrate(kind, opts)
141
+ })
142
+
143
+ records
144
+ .command('size')
145
+ .description('Report what each record folder holds and how much is recent')
146
+ .helpOption('-h, --help', 'Show this help message')
147
+ .option('--json', 'Add a machine-readable record on stdout')
148
+ .option('--root <path>', 'Project root, defaulting to the main worktree')
149
+ .addHelpText(
150
+ 'after',
151
+ [
152
+ '',
153
+ 'Folders read under .claude/:',
154
+ ` ${SIZED_FOLDERS.join(', ')}`,
155
+ '',
156
+ 'Exit codes:',
157
+ ' 0 the reading completed',
158
+ ' 1 refused, with the reason on stderr or in the JSON record',
159
+ '',
160
+ 'It gates nothing. A record folder has no correct size, so the reading is',
161
+ 'a number to notice rather than a threshold to fail, and a session takes it',
162
+ `by running this rather than by counting the folder. The ${GROWTH_WINDOWS.join(
163
+ ' and ',
164
+ )} day`,
165
+ 'counts read mtime, so a file rewritten long after it landed reads as recent,',
166
+ 'and a machine restored by records pull reads its whole tree as one week old.',
167
+ '',
168
+ 'Examples:',
169
+ ' canon records size',
170
+ ' canon records size --json',
171
+ '',
172
+ ].join('\n'),
173
+ )
174
+ .action(async (opts: BackupCommandOptions) => {
175
+ process.exitCode = await runSize(opts)
176
+ })
177
+
178
+ records
179
+ .command('push')
180
+ .description(
181
+ 'Commit the backed record folders and push them to the records remote',
182
+ )
183
+ .helpOption('-h, --help', 'Show this help message')
184
+ .option('--json', 'Add a machine-readable record on stdout')
185
+ .option('--root <path>', 'Project root, defaulting to the main worktree')
186
+ .addHelpText('after', backupHelp('push'))
187
+ .action(async (opts: BackupCommandOptions) => {
188
+ process.exitCode = await runPush(opts)
189
+ })
190
+
191
+ records
192
+ .command('pull')
193
+ .description(
194
+ 'Fetch the records remote and write it into the backed record folders',
195
+ )
196
+ .helpOption('-h, --help', 'Show this help message')
197
+ .option('--json', 'Add a machine-readable record on stdout')
198
+ .option('--root <path>', 'Project root, defaulting to the main worktree')
199
+ .addHelpText('after', backupHelp('pull'))
200
+ .action(async (opts: BackupCommandOptions) => {
201
+ process.exitCode = await runPull(opts)
202
+ })
203
+ }
204
+
205
+ function backupHelp(verb: 'push' | 'pull'): string {
206
+ const direction =
207
+ verb === 'push'
208
+ ? [
209
+ 'It commits nothing outside the folders above and leaves the project working',
210
+ 'tree untouched. The push runs even when this call committed nothing, since a',
211
+ 'previous run can have committed and then failed to reach the network.',
212
+ ]
213
+ : [
214
+ 'It refuses rather than discarding local records that never reached the remote,',
215
+ 'so a machine holding uncommitted or unpushed records is told to push first.',
216
+ ]
217
+
218
+ return [
219
+ '',
220
+ 'Backed folders under .claude/:',
221
+ ` ${BACKED_FOLDERS.join(', ')}`,
222
+ '',
223
+ 'Exit codes:',
224
+ ' 0 the records remote and this machine agree',
225
+ ' 1 refused, with the reason on stderr or in the JSON record',
226
+ '',
227
+ ...direction,
228
+ '',
229
+ 'The history lives in a second git directory at .claude/.records.git, pointed at a',
230
+ 'private repository a person creates once. The verbs refuse with the setup command',
231
+ 'when it is absent, and refuse when its origin is also a remote of this project.',
232
+ '',
233
+ 'Examples:',
234
+ ` canon records ${verb}`,
235
+ ` canon records ${verb} --json`,
236
+ '',
237
+ ].join('\n')
238
+ }
239
+
240
+ async function runSize(opts: BackupCommandOptions): Promise<number> {
241
+ const root = opts.root ?? (await mainWorktreeRoot())
242
+ const outcome = await sizeRecords(root)
243
+
244
+ if (!outcome.ok)
245
+ return reportRefusal('canon records size', outcome, opts.json ?? false)
246
+
247
+ if (opts.json ?? false) {
248
+ process.stdout.write(`${JSON.stringify(outcome)}\n`)
249
+ return 0
250
+ }
251
+
252
+ reportSize(outcome)
253
+ return 0
254
+ }
255
+
256
+ /** Widest cell in the column, so a row lines up against the header as well. */
257
+ function columnWidth(header: string, cells: readonly string[]): number {
258
+ return Math.max(header.length, ...cells.map((cell) => cell.length))
259
+ }
260
+
261
+ function sizeRow(entry: FolderSize): string[] {
262
+ return [
263
+ entry.folder,
264
+ String(entry.files),
265
+ formatBytes(entry.bytes),
266
+ ...entry.touched.map((window) => String(window.files)),
267
+ entry.oldest ?? '',
268
+ entry.newest ?? '',
269
+ ]
270
+ }
271
+
272
+ /**
273
+ * Renders the present folders as a table, heaviest first.
274
+ *
275
+ * The order is what makes the reading worth taking. A folder listed
276
+ * alphabetically hides behind its neighbors, and the one that grew is the row
277
+ * a reader came for, so it leads.
278
+ */
279
+ function reportSize(outcome: Extract<SizeOutcome, { ok: true }>): void {
280
+ const present = outcome.folders
281
+ .filter((entry) => entry.present)
282
+ .toSorted((left, right) => right.files - left.files)
283
+ const absent = outcome.folders
284
+ .filter((entry) => !entry.present)
285
+ .map((entry) => entry.folder)
286
+
287
+ intro('canon records size')
288
+ logStep('Folders')
289
+
290
+ if (present.length === 0) {
291
+ logInfo('none of the record folders exist yet')
292
+ } else {
293
+ const headers = [
294
+ 'folder',
295
+ 'files',
296
+ 'size',
297
+ ...GROWTH_WINDOWS.map((days) => `${days}d`),
298
+ 'oldest',
299
+ 'newest',
300
+ ]
301
+ const rows = present.map(sizeRow)
302
+ const widths = headers.map((header, column) =>
303
+ columnWidth(
304
+ header,
305
+ rows.map((row) => row[column]),
306
+ ),
307
+ )
308
+
309
+ // The name column reads as a list and the rest as numbers, so one is
310
+ // left-aligned and the others are not.
311
+ const render = (cells: readonly string[]): string =>
312
+ cells
313
+ .map((cell, column) =>
314
+ column === 0
315
+ ? cell.padEnd(widths[column])
316
+ : cell.padStart(widths[column]),
317
+ )
318
+ .join(' ')
319
+ .trimEnd()
320
+
321
+ pipeOutput([render(headers), ...rows.map(render)].join('\n'))
322
+ }
323
+
324
+ if (absent.length > 0) logInfo(`absent: ${absent.join(', ')}`)
325
+
326
+ logStep('Total')
327
+ logInfo(
328
+ `${plural(outcome.files, 'file')}, ${formatBytes(outcome.bytes)} across ${plural(present.length, 'folder')}`,
329
+ )
330
+ outro()
331
+ }
332
+
333
+ async function runPush(opts: BackupCommandOptions): Promise<number> {
334
+ const root = opts.root ?? (await mainWorktreeRoot())
335
+ const outcome = await pushRecords(root)
336
+
337
+ if (!outcome.ok)
338
+ return reportRefusal('canon records push', outcome, opts.json ?? false)
339
+
340
+ if (opts.json ?? false) {
341
+ process.stdout.write(`${JSON.stringify(outcome)}\n`)
342
+ return 0
343
+ }
344
+
345
+ intro('canon records push')
346
+ logStep('Staged')
347
+ logInfo(
348
+ `${outcome.folders.length} folder(s), ${outcome.changed} path(s) changed`,
349
+ )
350
+ logStep(outcome.pushed ? 'Pushed' : 'Nothing to push')
351
+ logInfo(
352
+ outcome.commit
353
+ ? `records at ${outcome.commit}`
354
+ : 'no records committed yet',
355
+ )
356
+ outro()
357
+ return 0
358
+ }
359
+
360
+ async function runPull(opts: BackupCommandOptions): Promise<number> {
361
+ const root = opts.root ?? (await mainWorktreeRoot())
362
+ const outcome = await pullRecords(root)
363
+
364
+ if (!outcome.ok)
365
+ return reportRefusal('canon records pull', outcome, opts.json ?? false)
366
+
367
+ if (opts.json ?? false) {
368
+ process.stdout.write(`${JSON.stringify(outcome)}\n`)
369
+ return 0
370
+ }
371
+
372
+ intro('canon records pull')
373
+ logStep('Fetched')
374
+ logInfo(`records at ${outcome.commit}`)
375
+ logStep('Written')
376
+ logInfo(`${outcome.files} file(s) across ${outcome.folders.length} folder(s)`)
377
+ outro()
378
+ return 0
379
+ }
380
+
381
+ /**
382
+ * Reports a refusal from any of the three verbs that carry one.
383
+ *
384
+ * The parameter is structural rather than the union of their outcome types,
385
+ * because the three refusal vocabularies are separate lists and naming them all
386
+ * here would grow with every verb added.
387
+ */
388
+ function reportRefusal(
389
+ banner: string,
390
+ outcome: { readonly reason: string; readonly message: string },
391
+ emitJson: boolean,
392
+ ): number {
393
+ if (emitJson) {
394
+ process.stderr.write(`${outcome.message}\n`)
395
+ process.stdout.write(
396
+ `${JSON.stringify({
397
+ ok: false,
398
+ reason: outcome.reason,
399
+ message: outcome.message,
400
+ })}\n`,
401
+ )
402
+ return 1
403
+ }
404
+
405
+ // The setup refusals carry the commands to run on their own lines. A marker
406
+ // beside a command reads as a result rather than as something to copy, so the
407
+ // remainder goes out unmarked.
408
+ const [reason, ...rest] = outcome.message.split('\n')
409
+
410
+ intro(banner)
411
+ logStep('Refused')
412
+ logError(reason)
413
+ if (rest.length > 0) pipeOutput(rest.join('\n'))
414
+ outro()
415
+ return 1
416
+ }
417
+
418
+ /**
419
+ * A shared-scratch kind reads the main worktree root, so a linked worktree
420
+ * validates the records every other session reads. A tracked corpus reads the
421
+ * checkout the caller stands in, which is the copy that session has edited.
422
+ */
423
+ function defaultRoot(kind: RecordKind): Promise<string> {
424
+ return isSharedScratch(kind) ? mainWorktreeRoot() : currentWorktreeRoot()
425
+ }
426
+
427
+ async function runValidate(
428
+ kind: string,
429
+ opts: ValidateCommandOptions,
430
+ ): Promise<number> {
431
+ const emitJson = opts.json ?? false
432
+
433
+ if (!isRecordKind(kind)) {
434
+ return report(
435
+ {
436
+ ok: false,
437
+ reason: 'unknown-kind',
438
+ message: `Not a record kind: ${kind}. Expected one of: ${RECORD_KINDS.join(', ')}.`,
439
+ },
440
+ emitJson,
441
+ process.cwd(),
442
+ )
443
+ }
444
+
445
+ const root = opts.root ?? (await defaultRoot(kind))
446
+
447
+ return report(await validateRecords(root, kind), emitJson, root)
448
+ }
449
+
450
+ function report(
451
+ outcome: ValidateOutcome,
452
+ emitJson: boolean,
453
+ root: string,
454
+ ): number {
455
+ if (!outcome.ok) {
456
+ // The framed branch below already reaches stderr through logError, so the
457
+ // bare write is what keeps the JSON mode from reporting the reason on
458
+ // stdout alone.
459
+ if (emitJson) {
460
+ process.stderr.write(`${outcome.message}\n`)
461
+ process.stdout.write(
462
+ `${JSON.stringify({
463
+ ok: false,
464
+ reason: outcome.reason,
465
+ message: outcome.message,
466
+ })}\n`,
467
+ )
468
+ return 1
469
+ }
470
+
471
+ intro('canon records validate')
472
+ logStep('Refused')
473
+ logError(outcome.message)
474
+ outro()
475
+ return 1
476
+ }
477
+
478
+ if (emitJson) {
479
+ process.stdout.write(
480
+ `${JSON.stringify({
481
+ ok: true,
482
+ root,
483
+ kind: outcome.kind,
484
+ records: outcome.records,
485
+ findings: outcome.findings,
486
+ })}\n`,
487
+ )
488
+ } else {
489
+ intro('canon records validate')
490
+ logStep(outcome.kind)
491
+ logInfo(`${outcome.records} record(s) read`)
492
+
493
+ logStep(outcome.findings.length === 0 ? 'Clean' : 'Findings')
494
+ if (outcome.findings.length === 0) {
495
+ logInfo('every record matches the shape its standard fixes')
496
+ } else {
497
+ for (const found of outcome.findings) logWarn(describe(found))
498
+ }
499
+ outro()
500
+ }
501
+
502
+ return outcome.findings.length > 0 ? EXIT_FINDINGS : 0
503
+ }
504
+
505
+ function describe(found: Finding): string {
506
+ const scope = found.record === found.subject ? '' : `${found.record}: `
507
+ return `${scope}${found.subject} ${found.message}`
508
+ }
509
+
510
+ export interface Repair {
511
+ readonly record: string
512
+ readonly remedy: FindingRemedy
513
+ readonly path: string
514
+ readonly text: string
515
+ }
516
+
517
+ export interface Refusal {
518
+ readonly record: string
519
+ readonly message: string
520
+ }
521
+
522
+ /** The message a rejected promise leaves, for a record whose read or write failed. */
523
+ function describeFailure(reason: unknown): string {
524
+ return reason instanceof Error ? reason.message : String(reason)
525
+ }
526
+
527
+ /**
528
+ * Runs every finding's transform, without writing anything back.
529
+ *
530
+ * `Promise.allSettled` rather than `Promise.all`, so a record whose read
531
+ * fails, such as one deleted between `validate`'s listing and this read,
532
+ * becomes a refusal for that one record rather than an unhandled rejection
533
+ * that `program.parse()` has no top-level catch for.
534
+ */
535
+ async function attemptMigrations(
536
+ dir: string,
537
+ findings: readonly Finding[],
538
+ ): Promise<{ repaired: Repair[]; refused: Refusal[] }> {
539
+ const candidates = findings.filter(
540
+ (found): found is Finding & { readonly remedy: FindingRemedy } =>
541
+ found.remedy !== undefined,
542
+ )
543
+
544
+ const settled = await Promise.allSettled(
545
+ candidates.map(async (found) => {
546
+ const path = join(dir, found.record)
547
+ const outcome = migrateRecord(
548
+ found.remedy,
549
+ found.record,
550
+ await readFile(path, 'utf8'),
551
+ )
552
+ return { record: found.record, remedy: found.remedy, path, outcome }
553
+ }),
554
+ )
555
+
556
+ const repaired: Repair[] = []
557
+ const refused: Refusal[] = []
558
+
559
+ settled.forEach((result, index) => {
560
+ if (result.status === 'rejected') {
561
+ refused.push({
562
+ record: candidates[index].record,
563
+ message: `could not be read: ${describeFailure(result.reason)}`,
564
+ })
565
+ return
566
+ }
567
+
568
+ const { record, remedy, path, outcome } = result.value
569
+
570
+ if (outcome.ok) {
571
+ repaired.push({ record, remedy, path, text: outcome.text })
572
+ } else {
573
+ refused.push({ record, message: outcome.message })
574
+ }
575
+ })
576
+
577
+ return { repaired, refused }
578
+ }
579
+
580
+ /**
581
+ * Writes every repair, independently. `Promise.allSettled` so one record's
582
+ * write failing does not abort the writes that would otherwise have
583
+ * succeeded, per the concurrency standard's rule on batched partial failure.
584
+ */
585
+ async function writeRepairs(
586
+ repaired: readonly Repair[],
587
+ ): Promise<{ written: Repair[]; failed: Refusal[] }> {
588
+ const settled = await Promise.allSettled(
589
+ repaired.map((entry) => writeFile(entry.path, entry.text, 'utf8')),
590
+ )
591
+
592
+ const written: Repair[] = []
593
+ const failed: Refusal[] = []
594
+
595
+ settled.forEach((result, index) => {
596
+ const entry = repaired[index]
597
+
598
+ if (result.status === 'fulfilled') {
599
+ written.push(entry)
600
+ } else {
601
+ failed.push({
602
+ record: entry.record,
603
+ message: `could not be written: ${describeFailure(result.reason)}`,
604
+ })
605
+ }
606
+ })
607
+
608
+ return { written, failed }
609
+ }
610
+
611
+ async function runMigrate(
612
+ kind: string,
613
+ opts: MigrateCommandOptions,
614
+ ): Promise<number> {
615
+ const emitJson = opts.json ?? false
616
+
617
+ if (!isRecordKind(kind)) {
618
+ return reportRefusal(
619
+ 'canon records migrate',
620
+ {
621
+ reason: 'unknown-kind',
622
+ message: `Not a record kind: ${kind}. Expected one of: ${RECORD_KINDS.join(', ')}.`,
623
+ },
624
+ emitJson,
625
+ )
626
+ }
627
+
628
+ const root = opts.root ?? (await defaultRoot(kind))
629
+ const outcome = await validateRecords(root, kind)
630
+
631
+ if (!outcome.ok) {
632
+ return reportRefusal('canon records migrate', outcome, emitJson)
633
+ }
634
+
635
+ const write = opts.write ?? false
636
+ const { repaired, refused } = await attemptMigrations(
637
+ recordsDir(root, kind),
638
+ outcome.findings,
639
+ )
640
+
641
+ if (!write) {
642
+ return reportMigrate(root, outcome.kind, repaired, refused, write, emitJson)
643
+ }
644
+
645
+ const { written, failed } = await writeRepairs(repaired)
646
+
647
+ return reportMigrate(
648
+ root,
649
+ outcome.kind,
650
+ written,
651
+ [...refused, ...failed],
652
+ write,
653
+ emitJson,
654
+ )
655
+ }
656
+
657
+ function reportMigrate(
658
+ root: string,
659
+ kind: RecordKind,
660
+ repaired: readonly Repair[],
661
+ refused: readonly Refusal[],
662
+ write: boolean,
663
+ emitJson: boolean,
664
+ ): number {
665
+ const total = repaired.length + refused.length
666
+
667
+ if (emitJson) {
668
+ process.stdout.write(
669
+ `${JSON.stringify({
670
+ ok: true,
671
+ root,
672
+ kind,
673
+ written: write,
674
+ migrated: repaired.map((entry) => entry.record),
675
+ refused,
676
+ })}\n`,
677
+ )
678
+ } else {
679
+ intro('canon records migrate')
680
+
681
+ if (total === 0) {
682
+ logStep('Clean')
683
+ logInfo('no finding carries a known transform')
684
+ } else {
685
+ logStep(write ? 'Rewritten' : 'Would rewrite')
686
+ if (repaired.length === 0) {
687
+ logInfo('none, every candidate refused, see below')
688
+ } else {
689
+ for (const entry of repaired)
690
+ logInfo(`${entry.record}: ${entry.remedy}`)
691
+ }
692
+
693
+ if (refused.length > 0) {
694
+ logStep('Refused')
695
+ for (const entry of refused)
696
+ logWarn(`${entry.record}: ${entry.message}`)
697
+ }
698
+
699
+ if (!write && repaired.length > 0) {
700
+ logInfo('Re-run with --write to apply.')
701
+ }
702
+ }
703
+
704
+ outro()
705
+ }
706
+
707
+ return migrateExitCode(repaired, refused, write)
708
+ }
709
+
710
+ /**
711
+ * `repaired` carries only records a transform actually fixed, whether this
712
+ * is a dry run or the set `--write` wrote, so exit 2 (a write is available)
713
+ * never fires when every candidate refused. A dry run reaching that state
714
+ * previously returned the same code as a genuine offer to write, telling a
715
+ * caller `--write` would repair something when it would repair nothing.
716
+ */
717
+ export function migrateExitCode(
718
+ repaired: readonly Repair[],
719
+ refused: readonly Refusal[],
720
+ write: boolean,
721
+ ): number {
722
+ const total = repaired.length + refused.length
723
+
724
+ if (total === 0) return 0
725
+ if (repaired.length === 0) return 1
726
+ if (!write) return EXIT_MIGRATABLE
727
+ return refused.length > 0 ? 1 : 0
728
+ }