@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,455 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { join, resolve } from 'node:path'
3
+ import { $ } from 'bun'
4
+ import { gitEnv } from '@/git-env'
5
+
6
+ /**
7
+ * The folders a backup carries, relative to `.claude/`. Most of them are the
8
+ * `# Claude` group the claude manifest ships, minus three: `.claude/.tmp`,
9
+ * which is defined as deletable without loss, `.claude/worktrees/`, whose
10
+ * contents belong to the enclosing repository already, and
11
+ * `.claude/.records.git/`, which is the history the rest are pushed into. The
12
+ * list is spelled out rather than read off that group so adding an ignore entry
13
+ * cannot silently enlarge the payload.
14
+ *
15
+ * `diagrams` is the one name the manifest group does not carry, so a target
16
+ * tracks it where this repository ignores it. That is the second reason to
17
+ * spell the list out: the manifest bounds it rather than producing it, and no
18
+ * count taken off that group matches this one. `scripts/core/check-ignore-parity.sh`
19
+ * compares the two lists and holds the reason `diagrams` stays apart, so the
20
+ * one difference is enforced rather than assumed.
21
+ *
22
+ * The manifest group is the one this reads rather than the enclosing
23
+ * repository's own `.gitignore`, which spreads the same entries across two
24
+ * headers and carries `.claude/README.md` that no target receives.
25
+ *
26
+ * Each entry is a top-level record folder and every archive sits inside the one
27
+ * it archives, so the three former archive entries are covered by their parents
28
+ * rather than named here. That is what keeps this list at one line per surface
29
+ * as archives spread, which a sibling-per-archive layout could not.
30
+ *
31
+ * `RECORD_KINDS` in `validate.ts` overlaps this on five names and carries one
32
+ * more that no backup reaches. The two lists differ on purpose: one is what a
33
+ * standard governs, this is what a disk loss would take, and `standards` is
34
+ * tracked so a backup would carry a second copy of committed files.
35
+ */
36
+ export const BACKED_FOLDERS = [
37
+ 'diagrams',
38
+ 'groundwork',
39
+ 'intake',
40
+ 'memory',
41
+ 'plans',
42
+ 'proposals',
43
+ 'review',
44
+ 'tasks',
45
+ 'teach',
46
+ ] as const
47
+
48
+ /**
49
+ * Names that have left `BACKED_FOLDERS` and whose removal still has to reach a
50
+ * records history once.
51
+ *
52
+ * Dropping a name from the list above stops it entering the pathspec, so `add`
53
+ * never stages its deletion, the remote keeps the folder forever, and a `pull`
54
+ * onto another machine restores it beside whatever replaced it. These three are
55
+ * the archives that moved inside the records they archive, so the same files
56
+ * are already on the remote under their new paths.
57
+ *
58
+ * Retire a name here once no records history still carries it. Nothing measures
59
+ * that, so the cost of leaving one is three pathspec entries that match nothing
60
+ * and are filtered out before `add` ever sees them.
61
+ */
62
+ const RETIRED_FOLDERS = [
63
+ 'plans-archive',
64
+ 'review-archive',
65
+ 'task-archive',
66
+ ] as const
67
+
68
+ /** Holds the records history beside the folders it tracks, ignored by the enclosing repository. */
69
+ const RECORDS_GIT_DIR = join('.claude', '.records.git')
70
+
71
+ const WORK_TREE = '.claude'
72
+
73
+ /** Both directions name the branch, so a machine whose `init.defaultBranch` differs still lands on it. */
74
+ const RECORDS_BRANCH = 'main'
75
+
76
+ /**
77
+ * The records history is machine-written and nobody reads its authorship, so a
78
+ * fixed identity keeps `push` from failing inside a git hook on a machine where
79
+ * `user.email` was never configured.
80
+ */
81
+ const COMMIT_IDENTITY = [
82
+ '-c',
83
+ 'user.name=canon',
84
+ '-c',
85
+ 'user.email=canon@local',
86
+ ]
87
+
88
+ export const BACKUP_REFUSALS = [
89
+ 'no-repository',
90
+ 'no-remote',
91
+ 'remote-unreadable',
92
+ 'remote-shared',
93
+ 'no-remote-records',
94
+ 'local-changes',
95
+ 'local-ahead',
96
+ 'git-failed',
97
+ ] as const
98
+
99
+ export type BackupRefusal = (typeof BACKUP_REFUSALS)[number]
100
+
101
+ export interface BackupRefused {
102
+ readonly ok: false
103
+ readonly reason: BackupRefusal
104
+ readonly message: string
105
+ }
106
+
107
+ export interface PushReport {
108
+ readonly ok: true
109
+ readonly root: string
110
+ readonly folders: readonly string[]
111
+ readonly changed: number
112
+ readonly commit?: string
113
+ readonly pushed: boolean
114
+ }
115
+
116
+ export interface PullReport {
117
+ readonly ok: true
118
+ readonly root: string
119
+ readonly folders: readonly string[]
120
+ readonly commit: string
121
+ readonly files: number
122
+ }
123
+
124
+ export type PushOutcome = PushReport | BackupRefused
125
+ export type PullOutcome = PullReport | BackupRefused
126
+
127
+ interface GitResult {
128
+ readonly ok: boolean
129
+ readonly text: string
130
+ readonly stderr: string
131
+ }
132
+
133
+ /**
134
+ * Runs one git command against the records history, from the work tree.
135
+ *
136
+ * All three flags go on every call. `git --git-dir=<path> init` writes
137
+ * `core.bare = true`, and an explicit `--work-tree` is what overrides it, so
138
+ * dropping that flag on a single call reads the enclosing project as the tree
139
+ * and stages everything in it.
140
+ *
141
+ * `-C` is what makes a bare pathspec like `groundwork` mean the work-tree root
142
+ * wherever the caller stands. Git derives a pathspec prefix from the current
143
+ * directory, so without it a caller sitting inside `.claude/`, which is every
144
+ * session in a linked worktree under `.claude/worktrees/<name>/`, prefixes
145
+ * each name with its own path and matches nothing. The root a caller names
146
+ * does not reach that prefix, so `--root` cannot stand in for this.
147
+ *
148
+ * Both paths are absolute because `-C` takes effect before the other two flags
149
+ * are read, so a relative root would otherwise send them looking inside the
150
+ * work tree.
151
+ */
152
+ async function records(root: string, args: string[]): Promise<GitResult> {
153
+ const gitDir = resolve(root, RECORDS_GIT_DIR)
154
+ const workTree = resolve(root, WORK_TREE)
155
+
156
+ const result =
157
+ await $`git -C ${workTree} --git-dir=${gitDir} --work-tree=${workTree} ${args}`
158
+ .env(gitEnv())
159
+ .quiet()
160
+ .nothrow()
161
+
162
+ return {
163
+ ok: result.exitCode === 0,
164
+ text: result.stdout.toString().trim(),
165
+ stderr: result.stderr.toString().trim(),
166
+ }
167
+ }
168
+
169
+ function refuse(reason: BackupRefusal, message: string): BackupRefused {
170
+ return { ok: false, reason, message }
171
+ }
172
+
173
+ function failed(action: string, result: GitResult): BackupRefused {
174
+ return refuse(
175
+ 'git-failed',
176
+ `git ${action} failed against the records history: ${result.stderr || 'no output'}.`,
177
+ )
178
+ }
179
+
180
+ /**
181
+ * Reduces a remote URL to `host/path`, so every spelling of one repository
182
+ * compares equal.
183
+ *
184
+ * Transport is what the reduction drops. `git@github.com:owner/repo.git` and
185
+ * `https://github.com/owner/repo` name the same repository, and comparing them
186
+ * as written passes a records origin that publishes the payload through the
187
+ * other protocol.
188
+ */
189
+ function remoteIdentity(url: string): string {
190
+ return url
191
+ .trim()
192
+ .toLowerCase()
193
+ .replace(/^[a-z+]+:\/\//, '')
194
+ .replace(/^[^@/]+@/, '')
195
+ .replace(/^([^/:]+):/, '$1/')
196
+ .replace(/\/+$/, '')
197
+ .replace(/\.git$/, '')
198
+ .replace(/\/+$/, '')
199
+ }
200
+
201
+ /**
202
+ * Lists every remote of the enclosing project, or undefined when git cannot
203
+ * answer.
204
+ *
205
+ * The caller refuses on undefined rather than smoothing it into an empty list.
206
+ * An empty list clears the gate below for every URL, so a git that failed for
207
+ * any reason would publish the payload to whatever origin the records history
208
+ * happens to name. A project with no remotes answers `0` with an exit of zero,
209
+ * so the two states stay distinguishable.
210
+ */
211
+ async function enclosingRemoteUrls(
212
+ root: string,
213
+ ): Promise<string[] | undefined> {
214
+ const result = await $`git -C ${root} remote -v`
215
+ .env(gitEnv())
216
+ .quiet()
217
+ .nothrow()
218
+ if (result.exitCode !== 0) return undefined
219
+
220
+ return result.stdout
221
+ .toString()
222
+ .split('\n')
223
+ .filter(Boolean)
224
+ .map((line) => line.split(/\s+/)[1] ?? '')
225
+ .filter(Boolean)
226
+ .map(remoteIdentity)
227
+ }
228
+
229
+ /**
230
+ * Clears the four gates both verbs share and returns the records remote URL.
231
+ *
232
+ * The last two are the ones the payload depends on. This repository is public,
233
+ * so a records branch on any of its remotes serves the memory pen and the
234
+ * groundwork trails to anyone who fetches all refs. Comparing the configured
235
+ * URL against every remote of the enclosing repository is what keeps a
236
+ * misconfigured `origin` from publishing them, and refusing when that list
237
+ * cannot be read is what keeps a failed comparison from reading as a pass.
238
+ */
239
+ async function resolveRemote(root: string): Promise<string | BackupRefused> {
240
+ if (!existsSync(join(root, RECORDS_GIT_DIR))) {
241
+ return refuse(
242
+ 'no-repository',
243
+ [
244
+ `No records history at ${RECORDS_GIT_DIR}. Create it once, against a private repository:`,
245
+ ` git --git-dir=${join(root, RECORDS_GIT_DIR)} init`,
246
+ ` git --git-dir=${join(root, RECORDS_GIT_DIR)} remote add origin <private-repo-url>`,
247
+ ].join('\n'),
248
+ )
249
+ }
250
+
251
+ const remote = await records(root, ['remote', 'get-url', 'origin'])
252
+ if (!remote.ok || remote.text.length === 0) {
253
+ return refuse(
254
+ 'no-remote',
255
+ [
256
+ 'The records history has no origin. Point it at a private repository:',
257
+ ` git --git-dir=${join(root, RECORDS_GIT_DIR)} remote add origin <private-repo-url>`,
258
+ ].join('\n'),
259
+ )
260
+ }
261
+
262
+ const enclosing = await enclosingRemoteUrls(root)
263
+ if (!enclosing) {
264
+ return refuse(
265
+ 'remote-unreadable',
266
+ `Cannot read the remotes of the project at ${root}, so the records origin cannot be checked against them. Records carry the memory pen and the groundwork trails, and an unchecked origin risks publishing them.`,
267
+ )
268
+ }
269
+
270
+ const url = remoteIdentity(remote.text)
271
+ if (enclosing.includes(url)) {
272
+ return refuse(
273
+ 'remote-shared',
274
+ `The records origin ${remote.text} is a remote of this project. Records carry the memory pen and the groundwork trails, so they need a repository of their own.`,
275
+ )
276
+ }
277
+
278
+ return url
279
+ }
280
+
281
+ /**
282
+ * The subset of the backed and retired names a pathspec can name: on disk, or
283
+ * already in the records index.
284
+ *
285
+ * A pathspec matching neither fails the whole `add`, which is why the subset
286
+ * exists. The index half is what covers a folder deleted in full. Reading disk
287
+ * alone drops it from the pathspec, so its deletion never stages, the remote
288
+ * keeps it forever, and a later `pull` restores it past the gate that refuses
289
+ * every other unpushed deletion.
290
+ *
291
+ * The retired names are the same case one level up, where the folder left the
292
+ * backed list rather than the disk, and the index is the only side that still
293
+ * knows it existed.
294
+ */
295
+ async function scopedFolders(root: string): Promise<string[]> {
296
+ const tracked = await records(root, ['ls-files'])
297
+ const indexed = new Set(
298
+ tracked.ok ? tracked.text.split('\n').filter(Boolean).map(topSegment) : [],
299
+ )
300
+
301
+ return [...BACKED_FOLDERS, ...RETIRED_FOLDERS].filter(
302
+ (folder) =>
303
+ existsSync(join(root, WORK_TREE, folder)) || indexed.has(folder),
304
+ )
305
+ }
306
+
307
+ function topSegment(path: string): string {
308
+ return path.split('/')[0]
309
+ }
310
+
311
+ /** What a report names, which is the folders a reader can go and open. */
312
+ function presentFolders(root: string): string[] {
313
+ return BACKED_FOLDERS.filter((folder) =>
314
+ existsSync(join(root, WORK_TREE, folder)),
315
+ )
316
+ }
317
+
318
+ function countLines(text: string): number {
319
+ return text.split('\n').filter(Boolean).length
320
+ }
321
+
322
+ /**
323
+ * Stages the backed folders, commits when any of them changed, and pushes.
324
+ *
325
+ * The push runs whether or not this call committed, because a previous run can
326
+ * have committed and then failed to reach the network. Skipping it would leave
327
+ * that commit on one disk, which is the state the whole verb exists to end.
328
+ */
329
+ export async function pushRecords(root: string): Promise<PushOutcome> {
330
+ const remote = await resolveRemote(root)
331
+ if (typeof remote !== 'string') return remote
332
+
333
+ const scope = await scopedFolders(root)
334
+
335
+ if (scope.length > 0) {
336
+ // `-f` is what carries the payload: every backed folder is ignored by the
337
+ // enclosing repository, and the pathspecs are the whole list, so nothing
338
+ // outside them can enter the index however the ignore rules read.
339
+ const staged = await records(root, ['add', '-A', '-f', '--', ...scope])
340
+ if (!staged.ok) return failed('add', staged)
341
+ }
342
+
343
+ const diff = await records(root, [
344
+ 'diff',
345
+ '--cached',
346
+ '--name-only',
347
+ '--',
348
+ ...scope,
349
+ ])
350
+ if (!diff.ok) return failed('diff', diff)
351
+
352
+ const changed = countLines(diff.text)
353
+
354
+ if (changed > 0) {
355
+ const stamp = new Date().toISOString().replace('T', ' ').slice(0, 16)
356
+ const commit = await records(root, [
357
+ ...COMMIT_IDENTITY,
358
+ 'commit',
359
+ '--quiet',
360
+ '-m',
361
+ `records: ${changed} changed at ${stamp}`,
362
+ ])
363
+ if (!commit.ok) return failed('commit', commit)
364
+ }
365
+
366
+ const folders = presentFolders(root)
367
+ const head = await records(root, ['rev-parse', '--short', 'HEAD'])
368
+ if (!head.ok) {
369
+ return { ok: true, root, folders, changed, pushed: false }
370
+ }
371
+
372
+ const pushed = await records(root, [
373
+ 'push',
374
+ 'origin',
375
+ `HEAD:refs/heads/${RECORDS_BRANCH}`,
376
+ ])
377
+ if (!pushed.ok) return failed('push', pushed)
378
+
379
+ return { ok: true, root, folders, changed, commit: head.text, pushed: true }
380
+ }
381
+
382
+ /**
383
+ * Fetches the records history and writes it into the backed folders.
384
+ *
385
+ * The two directions are not symmetric. A push only ever adds, while a pull
386
+ * onto a machine holding work that never reached the remote would discard it,
387
+ * so both gates below refuse rather than choosing a merge strategy. A person
388
+ * resolves by pushing first or by moving the local folders aside.
389
+ */
390
+ export async function pullRecords(root: string): Promise<PullOutcome> {
391
+ const remote = await resolveRemote(root)
392
+ if (typeof remote !== 'string') return remote
393
+
394
+ const fetched = await records(root, [
395
+ 'fetch',
396
+ '--quiet',
397
+ 'origin',
398
+ `refs/heads/${RECORDS_BRANCH}`,
399
+ ])
400
+ if (!fetched.ok) {
401
+ // A missing branch and an unreachable remote both fail the fetch, and only
402
+ // the first is an ordinary state a person resolves by pushing once.
403
+ if (fetched.stderr.includes("couldn't find remote ref")) {
404
+ return refuse(
405
+ 'no-remote-records',
406
+ `The records origin carries no ${RECORDS_BRANCH} branch yet. Run canon records push from the machine holding the records.`,
407
+ )
408
+ }
409
+ return failed('fetch', fetched)
410
+ }
411
+
412
+ const target = await records(root, ['rev-parse', 'FETCH_HEAD'])
413
+ if (!target.ok) return failed('rev-parse', target)
414
+
415
+ const scope = await scopedFolders(root)
416
+
417
+ if (scope.length > 0) {
418
+ const dirty = await records(root, ['status', '--porcelain', '--', ...scope])
419
+ if (!dirty.ok) return failed('status', dirty)
420
+
421
+ if (dirty.text.length > 0) {
422
+ return refuse(
423
+ 'local-changes',
424
+ `${countLines(dirty.text)} local record(s) are not in the records history. Run canon records push first, or move them aside.`,
425
+ )
426
+ }
427
+ }
428
+
429
+ const head = await records(root, ['rev-parse', '--verify', '--quiet', 'HEAD'])
430
+ if (head.ok && head.text.length > 0) {
431
+ const ahead = await records(root, ['rev-list', `${target.text}..HEAD`])
432
+ if (!ahead.ok) return failed('rev-list', ahead)
433
+
434
+ if (ahead.text.length > 0) {
435
+ return refuse(
436
+ 'local-ahead',
437
+ `${countLines(ahead.text)} local commit(s) have not reached the records origin. Run canon records push first.`,
438
+ )
439
+ }
440
+ }
441
+
442
+ const reset = await records(root, ['reset', '--hard', '--quiet', target.text])
443
+ if (!reset.ok) return failed('reset', reset)
444
+
445
+ const files = await records(root, ['ls-files'])
446
+ if (!files.ok) return failed('ls-files', files)
447
+
448
+ return {
449
+ ok: true,
450
+ root,
451
+ folders: presentFolders(root),
452
+ commit: target.text.slice(0, 7),
453
+ files: countLines(files.text),
454
+ }
455
+ }
@@ -0,0 +1,78 @@
1
+ import { parseFrontmatter, readField } from '@/indexes/frontmatter'
2
+ import {
3
+ CATEGORY_BY_TYPE,
4
+ type FindingRemedy,
5
+ MEMORY_NAME,
6
+ memoryType,
7
+ } from '@/records/validate'
8
+
9
+ export const MIGRATE_REFUSALS = ['field-unrecoverable'] as const
10
+
11
+ export type MigrateRefusal = (typeof MIGRATE_REFUSALS)[number]
12
+
13
+ export interface MigrateRepair {
14
+ readonly ok: true
15
+ readonly text: string
16
+ }
17
+
18
+ export interface MigrateRefused {
19
+ readonly ok: false
20
+ readonly reason: MigrateRefusal
21
+ readonly message: string
22
+ }
23
+
24
+ export type MigrateOutcome = MigrateRepair | MigrateRefused
25
+
26
+ function refuse(message: string): MigrateRefused {
27
+ return { ok: false, reason: 'field-unrecoverable', message }
28
+ }
29
+
30
+ /**
31
+ * Repairs the one recoverable shape of `frontmatter-incomplete`: a memory
32
+ * record missing `category` alone. The value sits in the filename's type
33
+ * prefix already, which is the same fact `checkMemory` derives it from, so a
34
+ * repair here writes back what the record already states rather than a value
35
+ * a transform invented. It re-reads the file rather than trusting a value
36
+ * carried on the `Finding`, so a transform and the check it repairs cannot
37
+ * drift apart.
38
+ */
39
+ function categoryFromName(name: string, text: string): MigrateOutcome {
40
+ const frontmatter = parseFrontmatter(text)
41
+ if (!frontmatter) {
42
+ return refuse(`${name} carries no frontmatter block to add category to.`)
43
+ }
44
+
45
+ if (readField(frontmatter, 'category')) {
46
+ return refuse(`${name} already carries a category.`)
47
+ }
48
+
49
+ const match = MEMORY_NAME.exec(name)
50
+ const type = match ? memoryType(match[1]) : undefined
51
+ if (!type) {
52
+ return refuse(
53
+ `${name} is not named <type>-<slug>.md, so category has no type to derive from.`,
54
+ )
55
+ }
56
+
57
+ const rewritten = frontmatter.raw.replace(
58
+ /\n---$/,
59
+ `\ncategory: ${CATEGORY_BY_TYPE[type]}\n---`,
60
+ )
61
+
62
+ return { ok: true, text: rewritten + text.slice(frontmatter.raw.length) }
63
+ }
64
+
65
+ const TRANSFORMS: Readonly<
66
+ Record<FindingRemedy, (name: string, text: string) => MigrateOutcome>
67
+ > = {
68
+ 'category-from-name': categoryFromName,
69
+ }
70
+
71
+ /** Applies the transform a finding's `remedy` names against its own text. */
72
+ export function migrateRecord(
73
+ remedy: FindingRemedy,
74
+ name: string,
75
+ text: string,
76
+ ): MigrateOutcome {
77
+ return TRANSFORMS[remedy](name, text)
78
+ }