@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,407 @@
1
+ import { execaSync } from 'execa'
2
+ import { gitEnv } from '@/git-env'
3
+
4
+ /**
5
+ * Preferred first, matching `src/tasks/trunk.ts`. A clone with no remote still
6
+ * answers off its local trunk, and a local `main` trailing the remote widens
7
+ * the range rather than narrowing it, which over-reports instead of hiding.
8
+ */
9
+ const TRUNK_REFS = ['origin/main', 'main'] as const
10
+
11
+ /**
12
+ * The extensions this check can pair. A test sits beside its subject under one
13
+ * name throughout this corpus, so the weakest assumption available is also the
14
+ * one the corpus already honors. Every other extension is read past and named,
15
+ * since a bash script and its test are related by nothing a filename carries.
16
+ */
17
+ export const SOURCE_EXTENSIONS = ['.ts', '.tsx'] as const
18
+
19
+ /** The suffix removed to derive the subject a test covers. */
20
+ export const TEST_SUFFIXES = ['.test.ts', '.test.tsx'] as const
21
+
22
+ /** A `%H` line, which is 40 hex characters under sha1 and 64 under sha256. */
23
+ const COMMIT = /^[0-9a-f]{40,64}$/
24
+
25
+ /** A `--name-status` line: a status letter, a tab, then one path. */
26
+ const NAME_STATUS = /^([A-Z])\d*\t(.+)$/
27
+
28
+ export type Verdict = 'satisfied' | 'implementation-first' | 'unclassified'
29
+
30
+ export interface PairRecord {
31
+ /** The implementation path, which is what a subject is named by. */
32
+ readonly subject: string
33
+ /** The test paired to it, or `null` when the pairing found no partner. */
34
+ readonly test: string | null
35
+ readonly verdict: Verdict
36
+ readonly implementationCommit: string | null
37
+ readonly testCommit: string | null
38
+ /** Why the verdict reads the way it does, in the report's own words. */
39
+ readonly reason: string
40
+ }
41
+
42
+ export interface Scope {
43
+ readonly extensions: readonly string[]
44
+ readonly testSuffixes: readonly string[]
45
+ }
46
+
47
+ /**
48
+ * Every reason `canon gov test-order` refuses for.
49
+ *
50
+ * None is an ordinary absence. A depth-1 checkout falls back to the root
51
+ * commit and reports zero rather than reaching any of these, so what remains
52
+ * is a repository with no commit at all or a git operation that failed
53
+ * outright, both a broken checkout rather than a target's ordinary state.
54
+ */
55
+ export type TestOrderRefusal =
56
+ | 'no-history'
57
+ | 'no-log'
58
+ | 'no-tree'
59
+ | 'bad-base'
60
+ | 'no-base'
61
+
62
+ export type TestOrderReport =
63
+ | {
64
+ readonly kind: 'measured'
65
+ readonly base: string
66
+ readonly head: string
67
+ readonly scope: Scope
68
+ readonly satisfied: readonly PairRecord[]
69
+ readonly findings: readonly PairRecord[]
70
+ readonly unclassified: readonly PairRecord[]
71
+ /** Changed paths outside the pairing's reach, named rather than counted. */
72
+ readonly ignored: readonly string[]
73
+ }
74
+ | {
75
+ readonly kind: 'unreadable'
76
+ readonly reason: TestOrderRefusal
77
+ readonly message: string
78
+ }
79
+
80
+ export interface TestOrderOptions {
81
+ /** The far side of the range, defaulting to the merge base against the trunk. */
82
+ readonly base?: string
83
+ }
84
+
85
+ /** One path as one commit in the range touched it. */
86
+ interface Change {
87
+ readonly path: string
88
+ readonly status: string
89
+ readonly commit: string
90
+ /** Position in the range, oldest first, which is what orders a pair. */
91
+ readonly order: number
92
+ }
93
+
94
+ /**
95
+ * Reads `--name-status` log output, oldest commit first, into one entry per
96
+ * path per commit.
97
+ *
98
+ * The order field rather than the commit is what a comparison reads, since two
99
+ * commits carry no ordering a caller can derive from their hashes alone.
100
+ */
101
+ function parseChanges(output: string): Change[] {
102
+ const changes: Change[] = []
103
+ let commit = ''
104
+ let order = -1
105
+
106
+ for (const line of output.split('\n')) {
107
+ const trimmed = line.trimEnd()
108
+ if (trimmed === '') continue
109
+
110
+ if (COMMIT.test(trimmed)) {
111
+ commit = trimmed
112
+ order += 1
113
+ continue
114
+ }
115
+
116
+ const match = NAME_STATUS.exec(trimmed)
117
+ if (match === null || commit === '') continue
118
+
119
+ changes.push({ path: match[2], status: match[1], commit, order })
120
+ }
121
+
122
+ return changes
123
+ }
124
+
125
+ /** Whether a path is a test this check can derive a subject from. */
126
+ function testSuffix(path: string): string | undefined {
127
+ return TEST_SUFFIXES.find((suffix) => path.endsWith(suffix))
128
+ }
129
+
130
+ /**
131
+ * The implementation a test covers, derived by removing the test suffix.
132
+ *
133
+ * A behavior split across two modules pairs wrongly or not at all under this,
134
+ * which is what the unclassified bucket exists to catch rather than hide.
135
+ */
136
+ function subjectOf(path: string): string {
137
+ const suffix = testSuffix(path)
138
+ if (suffix === undefined) return path
139
+ return `${path.slice(0, -suffix.length)}${suffix.replace('.test', '')}`
140
+ }
141
+
142
+ /** The one test path that would cover `subject` under the beside convention. */
143
+ function testPathFor(subject: string): string {
144
+ return subject.replace(/\.(tsx?)$/, '.test.$1')
145
+ }
146
+
147
+ /**
148
+ * Whether a path is an implementation this check can pair. A declaration file
149
+ * carries no behavior to test, so it is read past rather than reported as a
150
+ * module nothing covers.
151
+ */
152
+ function isImplementation(path: string): boolean {
153
+ if (path.endsWith('.d.ts')) return false
154
+ if (testSuffix(path) !== undefined) return false
155
+ return SOURCE_EXTENSIONS.some((extension) => path.endsWith(extension))
156
+ }
157
+
158
+ /** The first commit in the range that added `path`, or `undefined`. */
159
+ function introduction(
160
+ changes: readonly Change[],
161
+ path: string,
162
+ ): Change | undefined {
163
+ return changes.find((change) => change.path === path && change.status === 'A')
164
+ }
165
+
166
+ function classify(
167
+ changes: readonly Change[],
168
+ atBase: ReadonlySet<string>,
169
+ ): {
170
+ satisfied: PairRecord[]
171
+ findings: PairRecord[]
172
+ unclassified: PairRecord[]
173
+ } {
174
+ const satisfied: PairRecord[] = []
175
+ const findings: PairRecord[] = []
176
+ const unclassified: PairRecord[] = []
177
+
178
+ // Keyed by subject so a module whose implementation and test both moved is
179
+ // one record rather than two, and so a test with no partner still reports
180
+ // under the implementation path a reader would go looking for.
181
+ const subjects = new Set<string>()
182
+ for (const change of changes) {
183
+ if (isImplementation(change.path)) subjects.add(change.path)
184
+ else if (testSuffix(change.path) !== undefined) {
185
+ subjects.add(subjectOf(change.path))
186
+ }
187
+ }
188
+
189
+ for (const subject of [...subjects].sort()) {
190
+ const candidate = testPathFor(subject)
191
+ const testChange = introduction(changes, candidate)
192
+ const testAtBase = atBase.has(candidate)
193
+ const test = testAtBase || testChange !== undefined ? candidate : undefined
194
+
195
+ const implementation = introduction(changes, subject)
196
+
197
+ if (implementation === undefined) {
198
+ unclassified.push({
199
+ subject,
200
+ test: test ?? null,
201
+ verdict: 'unclassified',
202
+ implementationCommit: null,
203
+ testCommit: null,
204
+ reason: atBase.has(subject)
205
+ ? 'the implementation predates the range, so a change to it cannot be separated from a refactor'
206
+ : 'no implementation reached the range beside this test',
207
+ })
208
+ continue
209
+ }
210
+
211
+ if (test === undefined) {
212
+ unclassified.push({
213
+ subject,
214
+ test: null,
215
+ verdict: 'unclassified',
216
+ implementationCommit: implementation.commit,
217
+ testCommit: null,
218
+ reason: 'no test names this module, so the ordering has no second side',
219
+ })
220
+ continue
221
+ }
222
+
223
+ // A paired test the range never added is one that already sat at the base
224
+ // commit, since those are the only two ways `test` gets a value at all.
225
+ if (testChange === undefined) {
226
+ satisfied.push({
227
+ subject,
228
+ test,
229
+ verdict: 'satisfied',
230
+ implementationCommit: implementation.commit,
231
+ testCommit: null,
232
+ reason: 'the test predates the range',
233
+ })
234
+ continue
235
+ }
236
+
237
+ // One commit carrying both sides counts as satisfied. The rule asks that
238
+ // the test not come after, and a single commit is the shape a small change
239
+ // takes here, so reporting it would flag most of the corpus.
240
+ const record: PairRecord = {
241
+ subject,
242
+ test,
243
+ verdict:
244
+ testChange.order <= implementation.order
245
+ ? 'satisfied'
246
+ : 'implementation-first',
247
+ implementationCommit: implementation.commit,
248
+ testCommit: testChange.commit,
249
+ reason:
250
+ testChange.order <= implementation.order
251
+ ? 'the test reached history no later than the implementation'
252
+ : 'the implementation reached history before the test covering it',
253
+ }
254
+
255
+ if (record.verdict === 'satisfied') satisfied.push(record)
256
+ else findings.push(record)
257
+ }
258
+
259
+ return { satisfied, findings, unclassified }
260
+ }
261
+
262
+ /**
263
+ * Where an implementation reached a commit ahead of the test covering it,
264
+ * between `base` and the current `HEAD` of the tree at `root`.
265
+ *
266
+ * This reports and never gates. Pairing a test to an implementation is a
267
+ * judgment, so a change the pairing cannot read lands in `unclassified` with
268
+ * its reason stated rather than being counted as a pass. Coverage is narrower
269
+ * than the rule the check answers to, and `scope` and `ignored` are what say so
270
+ * on every run.
271
+ */
272
+ export function readTestOrder(
273
+ root: string,
274
+ options: TestOrderOptions = {},
275
+ ): TestOrderReport {
276
+ const head = revParse(root, 'HEAD')
277
+ if (head === undefined) {
278
+ return {
279
+ kind: 'unreadable',
280
+ reason: 'no-history',
281
+ message: `No git history under ${root}. History is the only surface carrying the ordering, so there is nothing to read.`,
282
+ }
283
+ }
284
+
285
+ const base = resolveBase(root, options.base, head)
286
+ if (typeof base !== 'string') return base
287
+
288
+ const log = git(root, [
289
+ 'log',
290
+ '--reverse',
291
+ '--name-status',
292
+ '--no-renames',
293
+ '--format=%H',
294
+ `${base}..${head}`,
295
+ ])
296
+
297
+ if (log === undefined) {
298
+ return {
299
+ kind: 'unreadable',
300
+ reason: 'no-log',
301
+ message: `Reading history between ${base} and HEAD failed under ${root}.`,
302
+ }
303
+ }
304
+
305
+ const tree = git(root, ['ls-tree', '-r', '--name-only', base])
306
+ if (tree === undefined) {
307
+ return {
308
+ kind: 'unreadable',
309
+ reason: 'no-tree',
310
+ message: `Reading the tree at ${base} failed under ${root}. Without it a test written before the range reads as absent.`,
311
+ }
312
+ }
313
+
314
+ const changes = parseChanges(log)
315
+ const atBase = new Set(tree.split('\n').filter((line) => line !== ''))
316
+
317
+ const ignored = [
318
+ ...new Set(
319
+ changes
320
+ .map((change) => change.path)
321
+ .filter(
322
+ (path) => !isImplementation(path) && testSuffix(path) === undefined,
323
+ ),
324
+ ),
325
+ ].sort()
326
+
327
+ return {
328
+ kind: 'measured',
329
+ base,
330
+ head,
331
+ scope: { extensions: SOURCE_EXTENSIONS, testSuffixes: TEST_SUFFIXES },
332
+ ...classify(changes, atBase),
333
+ ignored,
334
+ }
335
+ }
336
+
337
+ /**
338
+ * The far side of the range. A ref the caller named has to resolve, since
339
+ * falling back to the trunk there would measure a range nobody asked for. With
340
+ * no ref named, the merge base against the trunk scopes the run to the branch,
341
+ * and a repository carrying no trunk falls back to the root commit rather than
342
+ * refusing.
343
+ */
344
+ function resolveBase(
345
+ root: string,
346
+ ref: string | undefined,
347
+ head: string,
348
+ ): string | { kind: 'unreadable'; reason: TestOrderRefusal; message: string } {
349
+ if (ref !== undefined) {
350
+ const resolved = revParse(root, ref)
351
+ if (resolved === undefined) {
352
+ return {
353
+ kind: 'unreadable',
354
+ reason: 'bad-base',
355
+ message: `Ref ${ref} resolves to no commit in ${root}. Pass a commit this tree carries.`,
356
+ }
357
+ }
358
+ return resolved
359
+ }
360
+
361
+ for (const trunk of TRUNK_REFS) {
362
+ if (revParse(root, trunk) === undefined) continue
363
+ const merged = git(root, ['merge-base', head, trunk])
364
+ if (merged !== undefined && merged !== '') return merged
365
+ }
366
+
367
+ const rootCommits = git(root, ['rev-list', '--max-parents=0', head])
368
+ if (rootCommits === undefined || rootCommits === '') {
369
+ return {
370
+ kind: 'unreadable',
371
+ reason: 'no-base',
372
+ message: `No base resolves against ${root}. Fetch origin or pass --base.`,
373
+ }
374
+ }
375
+
376
+ return rootCommits.split('\n')[0]
377
+ }
378
+
379
+ /**
380
+ * `execaSync` with git's repository-resolution variables stripped, so `-C`
381
+ * resolves against `root` and not against whatever repository a hook exported.
382
+ * `undefined` is the refusal, which every caller turns into its own reason.
383
+ */
384
+ function git(root: string, args: readonly string[]): string | undefined {
385
+ const result = execaSync('git', ['-C', root, ...args], {
386
+ reject: false,
387
+ env: gitEnv(),
388
+ extendEnv: false,
389
+ })
390
+
391
+ return result.exitCode === 0 ? result.stdout.trimEnd() : undefined
392
+ }
393
+
394
+ /**
395
+ * The commit a ref names. `^{commit}` is what turns a tag or a tree into the
396
+ * commit behind it, so a caller never compares a ref against another type.
397
+ */
398
+ function revParse(root: string, ref: string): string | undefined {
399
+ const resolved = git(root, [
400
+ 'rev-parse',
401
+ '--verify',
402
+ '--quiet',
403
+ `${ref}^{commit}`,
404
+ ])
405
+
406
+ return resolved === undefined || resolved === '' ? undefined : resolved.trim()
407
+ }
@@ -0,0 +1,46 @@
1
+ const FRONTMATTER = /^---\n([\s\S]*?)\n---/
2
+
3
+ export interface Frontmatter {
4
+ readonly raw: string
5
+ readonly fields: Readonly<Record<string, unknown>>
6
+ }
7
+
8
+ /**
9
+ * Splits a leading `---` block off a markdown source.
10
+ *
11
+ * `raw` keeps the delimiters so an index can be rewritten with its own
12
+ * frontmatter untouched, including comments and key order that a parse and
13
+ * re-emit would lose.
14
+ */
15
+ export function parseFrontmatter(source: string): Frontmatter | undefined {
16
+ const match = source.match(FRONTMATTER)
17
+ if (!match) return undefined
18
+
19
+ const parsed = Bun.YAML.parse(match[1])
20
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
21
+ return undefined
22
+ }
23
+
24
+ return {
25
+ raw: match[0],
26
+ fields: parsed as Record<string, unknown>,
27
+ }
28
+ }
29
+
30
+ export function readField(
31
+ frontmatter: Frontmatter | undefined,
32
+ key: string,
33
+ ): string | undefined {
34
+ const value = frontmatter?.fields[key]
35
+ if (typeof value === 'string') return value
36
+ if (typeof value === 'number' || typeof value === 'boolean') {
37
+ return String(value)
38
+ }
39
+ return undefined
40
+ }
41
+
42
+ export async function readFrontmatter(
43
+ path: string,
44
+ ): Promise<Frontmatter | undefined> {
45
+ return parseFrontmatter(await Bun.file(path).text())
46
+ }
@@ -0,0 +1,84 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { readField, readFrontmatter } from '@/indexes/frontmatter'
3
+ import { computeIndex } from '@/indexes/render'
4
+ import { INDEX_FILE } from '@/indexes/walk'
5
+
6
+ export const REGEN_ACTIONS = [
7
+ 'written',
8
+ 'would-write',
9
+ 'unchanged',
10
+ 'skipped',
11
+ 'error',
12
+ ] as const
13
+
14
+ export type RegenAction = (typeof REGEN_ACTIONS)[number]
15
+
16
+ export interface RegenResult {
17
+ readonly path: string
18
+ readonly action: RegenAction
19
+ readonly reason?: string
20
+ readonly errors?: string[]
21
+ }
22
+
23
+ export interface RegenOptions {
24
+ readonly dryRun: boolean
25
+ }
26
+
27
+ /**
28
+ * Regenerates one folder's index and reports what happened.
29
+ *
30
+ * A folder marked `auto: false` is skipped rather than errored, which is how
31
+ * hand-maintained catalogs opt out of the walker.
32
+ */
33
+ export async function regenOne(
34
+ dir: string,
35
+ options: RegenOptions,
36
+ ): Promise<RegenResult> {
37
+ const path = `${dir}/${INDEX_FILE}`
38
+
39
+ if (!existsSync(path)) {
40
+ return { path, action: 'error', reason: 'no index.md' }
41
+ }
42
+
43
+ const auto = readField(await readFrontmatter(path), 'auto')
44
+ if (auto === 'false') {
45
+ return { path, action: 'skipped', reason: 'auto:false' }
46
+ }
47
+
48
+ const rendered = await computeIndex(dir)
49
+ if (!rendered.ok) {
50
+ return {
51
+ path,
52
+ action: 'error',
53
+ reason: 'frontmatter',
54
+ errors: rendered.errors,
55
+ }
56
+ }
57
+
58
+ if (rendered.content === (await Bun.file(path).text())) {
59
+ return { path, action: 'unchanged' }
60
+ }
61
+
62
+ if (options.dryRun) {
63
+ return { path, action: 'would-write' }
64
+ }
65
+
66
+ await Bun.write(path, rendered.content)
67
+ return { path, action: 'written' }
68
+ }
69
+
70
+ /**
71
+ * Maps a run's results onto the documented exit codes: 0 clean, 1 error,
72
+ * 2 drift found under `--dry-run`. Skills branch on the JSON instead, but the
73
+ * codes are load-bearing for `bun run check`.
74
+ */
75
+ export function exitCodeFor(
76
+ results: readonly RegenResult[],
77
+ options: RegenOptions,
78
+ ): 0 | 1 | 2 {
79
+ if (results.some((result) => result.action === 'error')) return 1
80
+ if (options.dryRun && results.some((r) => r.action === 'would-write')) {
81
+ return 2
82
+ }
83
+ return 0
84
+ }