@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,205 @@
1
+ import { existsSync, readFileSync, statSync } from 'node:fs'
2
+ import { basename, join } from 'node:path'
3
+ import {
4
+ installedRuleNames,
5
+ listRuleSourcePaths,
6
+ lookupRules,
7
+ type RuleSource,
8
+ rulesSourceDir,
9
+ } from '@/gov/install'
10
+
11
+ export interface GovStack {
12
+ readonly name: string
13
+ readonly parent?: string
14
+ readonly rules: readonly string[]
15
+ }
16
+
17
+ export type RuleResolution =
18
+ | { readonly ok: true; readonly rules: string[] }
19
+ | { readonly ok: false; readonly missingStack: string }
20
+
21
+ function stacksDir(root: string): string {
22
+ return join(root, 'governance', 'stacks')
23
+ }
24
+
25
+ export function govStackPath(root: string, stack: string): string {
26
+ return join(stacksDir(root), `${stack}.toml`)
27
+ }
28
+
29
+ export function govStackExists(root: string, stack: string): boolean {
30
+ return existsSync(govStackPath(root, stack))
31
+ }
32
+
33
+ /**
34
+ * Lists stack names the way `find -name "*.toml" -exec basename {} .toml` did.
35
+ */
36
+ export function listGovStacks(root: string): string[] {
37
+ const dir = stacksDir(root)
38
+ if (!existsSync(dir)) return []
39
+
40
+ return [...new Bun.Glob('*.toml').scanSync({ cwd: dir, onlyFiles: true })]
41
+ .map((entry) => entry.slice(0, -'.toml'.length))
42
+ .sort()
43
+ }
44
+
45
+ /**
46
+ * Reads one stack file. `Bun.TOML.parse` replaces a `BASH_REMATCH` loop that
47
+ * consumed each rules line with `sed` as it walked, which is the hand-rolled
48
+ * parser shape the tooling manifest already moved off.
49
+ */
50
+ export function loadGovStack(
51
+ root: string,
52
+ stack: string,
53
+ ): GovStack | undefined {
54
+ const path = govStackPath(root, stack)
55
+ if (!existsSync(path)) return undefined
56
+
57
+ const parsed = Bun.TOML.parse(readFileSync(path, 'utf8')) as Record<
58
+ string,
59
+ unknown
60
+ >
61
+ const parent = typeof parsed.extends === 'string' ? parsed.extends : ''
62
+
63
+ return {
64
+ name: stack,
65
+ parent: parent === '' ? undefined : parent,
66
+ rules: Array.isArray(parsed.rules)
67
+ ? parsed.rules.filter(
68
+ (rule): rule is string => typeof rule === 'string' && rule !== '',
69
+ )
70
+ : [],
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Expands one stack entry. An entry naming a directory under
76
+ * `governance/rules/` resolves to every rule inside it, sorted, and any other
77
+ * entry resolves to itself, so a folder and a slug reach the caller as one
78
+ * shape rather than two the caller has to tell apart.
79
+ *
80
+ * The directory wins over a rule file of the same name. They cannot collide
81
+ * while `standards/rule.md` requires a numeric prefix on a rule slug, since a
82
+ * band folder carries none.
83
+ */
84
+ export function expandStackEntry(root: string, entry: string): string[] {
85
+ const dir = join(rulesSourceDir(root), entry)
86
+ if (!existsSync(dir) || !statSync(dir).isDirectory()) return [entry]
87
+
88
+ return [...new Bun.Glob('**/*.md').scanSync({ cwd: dir, onlyFiles: true })]
89
+ .sort()
90
+ .map((rel) => basename(rel, '.md'))
91
+ }
92
+
93
+ /**
94
+ * Walks `extends` ancestors first, then the stack's own rules, deduped by
95
+ * first appearance. Tooling's `resolveChain` returns full manifests nearest
96
+ * first and carries `skipStack` truncation, so the two walks stay separate
97
+ * rather than fitting one shape to both.
98
+ *
99
+ * Dedupe runs on expanded names rather than on the entries, so a stack naming
100
+ * a folder and an ancestor naming a rule inside it yield that rule once.
101
+ */
102
+ export function resolveRules(root: string, stack: string): RuleResolution {
103
+ const rules: string[] = []
104
+ const seen = new Set<string>()
105
+ const visited = new Set<string>()
106
+
107
+ const walk = (current: string): string | undefined => {
108
+ if (visited.has(current)) return undefined
109
+ visited.add(current)
110
+
111
+ const loaded = loadGovStack(root, current)
112
+ if (!loaded) return current
113
+
114
+ if (loaded.parent !== undefined) {
115
+ const missing = walk(loaded.parent)
116
+ if (missing !== undefined) return missing
117
+ }
118
+
119
+ for (const entry of loaded.rules) {
120
+ for (const rule of expandStackEntry(root, entry)) {
121
+ if (seen.has(rule)) continue
122
+ seen.add(rule)
123
+ rules.push(rule)
124
+ }
125
+ }
126
+
127
+ return undefined
128
+ }
129
+
130
+ const missingStack = walk(stack)
131
+ if (missingStack !== undefined) return { ok: false, missingStack }
132
+
133
+ return { ok: true, rules }
134
+ }
135
+
136
+ /**
137
+ * Names every rule no stack reaches, sorted. A rule outside every stack still
138
+ * installs through `--add`, so this reports an opt-in library and an oversight
139
+ * alike and leaves telling them apart to the reader.
140
+ *
141
+ * A stack whose `extends` does not resolve contributes nothing rather than
142
+ * aborting the sweep, or one broken stack would report the whole catalog as
143
+ * unreferenced.
144
+ */
145
+ export function unreferencedRules(root: string): string[] {
146
+ const reached = new Set<string>()
147
+
148
+ for (const stack of listGovStacks(root)) {
149
+ const resolution = resolveRules(root, stack)
150
+ if (!resolution.ok) continue
151
+ for (const rule of resolution.rules) reached.add(rule)
152
+ }
153
+
154
+ return listRuleSourcePaths(root)
155
+ .map((rel) => basename(rel, '.md'))
156
+ .filter((rule) => !reached.has(rule))
157
+ .sort()
158
+ }
159
+
160
+ /**
161
+ * Rules the target's recorded chain entitles it to that its installed tree
162
+ * does not hold. `resolveRules` already walks a stack's `extends` ancestors,
163
+ * so reading its leaf entry is enough; no second walk resolves the chain
164
+ * itself. A stack the toolkit no longer ships resolves to nothing rather than
165
+ * throwing, the same way `readNewRules`'s band fallback already treats it.
166
+ */
167
+ export function resolveMissingRules(
168
+ root: string,
169
+ target: string,
170
+ chain: readonly string[],
171
+ ): readonly RuleSource[] {
172
+ const stack = chain[0]
173
+ if (stack === undefined) return []
174
+
175
+ const resolution = resolveRules(root, stack)
176
+ if (!resolution.ok) return []
177
+
178
+ const { found } = lookupRules(root, resolution.rules)
179
+ const held = installedRuleNames(target)
180
+
181
+ return found
182
+ .filter((source) => !held.has(source.rule))
183
+ .sort((left, right) => left.rule.localeCompare(right.rule))
184
+ }
185
+
186
+ /**
187
+ * Layers `--add` names on top of a resolved stack. The bash trimmed a single
188
+ * leading and trailing space per entry; trimming fully is the same result for
189
+ * every input that parsed before.
190
+ */
191
+ export function mergeExtraRules(
192
+ rules: readonly string[],
193
+ add: string,
194
+ ): string[] {
195
+ const merged = [...rules]
196
+
197
+ for (const raw of add.split(',')) {
198
+ const extra = raw.trim()
199
+ if (extra === '') continue
200
+ if (merged.includes(extra)) continue
201
+ merged.push(extra)
202
+ }
203
+
204
+ return merged
205
+ }
@@ -0,0 +1,415 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+ import { isBinary } from '@/binary'
4
+ import { isMarked } from '@/exempt-marker'
5
+ import { listRepositoryFiles } from '@/git-files'
6
+
7
+ /**
8
+ * The inline token exempting one line from this sweep, shaped on the
9
+ * `canon-allow-secret` precedent and read by the same two-line rule.
10
+ *
11
+ * A declaration disagreeing with a convention for a stated reason is the case
12
+ * question 3 of the plan settled against gating on, and the marker is where
13
+ * that reason goes. A bare token names nothing a later reader can weigh, so it
14
+ * mutes nothing.
15
+ */
16
+ export const SUPERSEDED_MARKER = 'canon-allow-superseded'
17
+
18
+ /**
19
+ * What matched at one column.
20
+ *
21
+ * `literal` is the superseded value itself. The other three are the stem
22
+ * followed by a glob, by an angle-bracket placeholder, or by nothing that
23
+ * continues a name, which are the three forms this corpus writes a family in.
24
+ * A literal comparison reaches none of them, which is why a rename running the
25
+ * verb once per name reported clean over seven stale citations.
26
+ */
27
+ export type SupersededMatch = 'literal' | 'glob' | 'placeholder' | 'prefix'
28
+
29
+ export interface SupersededHit {
30
+ readonly file: string
31
+ /** One-based, matching the `file:line` form a reader clicks. */
32
+ readonly line: number
33
+ readonly column: number
34
+ readonly match: SupersededMatch
35
+ /**
36
+ * The nearest heading above the hit in a markdown file, absent elsewhere and
37
+ * above the first heading.
38
+ *
39
+ * A line reads differently under the section holding it. `Use the canon-*
40
+ * prefix on an internal skill` is a prohibition under `## Must not` and an
41
+ * instruction anywhere else, and a reviewer reading the line alone made
42
+ * exactly that misreading against this tree.
43
+ */
44
+ readonly heading: string | undefined
45
+ /**
46
+ * Whether the replacement sits on the same line, outside the superseded
47
+ * occurrences themselves.
48
+ *
49
+ * Read outside them because the replacement is routinely a substring of the
50
+ * value it replaced, which is what a convention dropping a segment produces.
51
+ * A plain containment test answers true for every line under that shape and
52
+ * separates nothing.
53
+ *
54
+ * A reading aid rather than a filter. The line stating the change carries
55
+ * both values and so does a fixture halfway through a repair, and nothing
56
+ * here can tell those apart. Suppressing on it would hide the second case,
57
+ * which is the whole class this sweep exists to reach.
58
+ */
59
+ readonly carriesReplacement: boolean
60
+ readonly preview: string
61
+ }
62
+
63
+ export interface SupersededOptions {
64
+ /** The value the convention used to produce, matched as a literal substring. */
65
+ readonly superseded: string
66
+ /** What it produces now, reported so a finding names what to write instead. */
67
+ readonly replacement: string
68
+ }
69
+
70
+ /**
71
+ * The segment pair a templated citation is matched on, reported so a run states
72
+ * the net it cast rather than only what the net caught.
73
+ */
74
+ export interface SupersededStems {
75
+ readonly superseded: string
76
+ readonly replacement: string
77
+ }
78
+
79
+ export type SupersededReport =
80
+ | {
81
+ readonly kind: 'measured'
82
+ readonly superseded: string
83
+ readonly replacement: string
84
+ /** Absent when the two values yield no bounded stem to match on. */
85
+ readonly stems: SupersededStems | undefined
86
+ /** Everything git listed, so the report states its own bound. */
87
+ readonly listed: number
88
+ /** Files opened, which is what the verdict actually covers. */
89
+ readonly files: number
90
+ /** Binary or unreadable, counted so a pass is not claimed over them. */
91
+ readonly skipped: number
92
+ readonly findings: readonly SupersededHit[]
93
+ readonly exempt: readonly SupersededHit[]
94
+ }
95
+ | { readonly kind: 'unreadable'; readonly reason: string }
96
+
97
+ /**
98
+ * The longest preview a hit carries. A generated or minified line is one line
99
+ * to git and a screen of noise to a reader, and the report prints one hit per
100
+ * line found.
101
+ */
102
+ const PREVIEW_LIMIT = 200
103
+
104
+ /** The separator a family name is built from across this corpus. */
105
+ const SEGMENT_SEPARATOR = '-'
106
+
107
+ /**
108
+ * A character that continues a name, so `canon-` inside `canon-cli` is read as
109
+ * one name rather than as the family prefix written bare.
110
+ */
111
+ const NAME_CHARACTER = /[A-Za-z0-9]/
112
+
113
+ /**
114
+ * A character that puts the stem mid-name when it sits directly before it, so
115
+ * a stem is only read where a name starts.
116
+ *
117
+ * The separator is here and not in `NAME_CHARACTER` because it decides one side
118
+ * only. `canon-check-toolkit-` is a temp-directory prefix and matched the stem
119
+ * `toolkit` on four fixtures before this, where `claude/skills/toolkit-*` is a
120
+ * path and has to keep matching.
121
+ */
122
+ const SEGMENT_CONTINUATION = /[A-Za-z0-9_-]/
123
+
124
+ const HEADING = /^#{1,6}\s+\S/
125
+
126
+ /**
127
+ * A fence opening or closing a code block, tracked so a shell comment inside
128
+ * one is not read as the section a hit below it sits under. A `# Install` line
129
+ * in a bash block is the ordinary shape of that.
130
+ */
131
+ const FENCE = /^\s*(?:```|~~~)/
132
+
133
+ /**
134
+ * The segment the two values differ on, carried with everything they share
135
+ * ahead of it.
136
+ *
137
+ * The shared prefix alone is what this exists against. `canon-cli` and
138
+ * `canon-feedback-file` share `canon`, so a stem cut there matches every sibling
139
+ * and reports the whole family on a rename of one folder. Including the
140
+ * differing segment bounds the net to what actually changed, which leaves
141
+ * `canon-cli` to `canon-shell` matching neither sibling and `toolkit-operator` to
142
+ * `canon-operator` matching the family prefix that did move.
143
+ *
144
+ * An empty replacement yields nothing. Retiring a value outright leaves no
145
+ * second value to diverge from, so every stem would run to the first segment
146
+ * and match the family the retirement never touched.
147
+ */
148
+ export function deriveStems(
149
+ options: SupersededOptions,
150
+ ): SupersededStems | undefined {
151
+ if (options.replacement === '') return undefined
152
+
153
+ const supersededSegments = options.superseded.split(SEGMENT_SEPARATOR)
154
+ const replacementSegments = options.replacement.split(SEGMENT_SEPARATOR)
155
+
156
+ let index = 0
157
+ while (
158
+ index < supersededSegments.length &&
159
+ index < replacementSegments.length &&
160
+ supersededSegments[index] === replacementSegments[index]
161
+ ) {
162
+ index += 1
163
+ }
164
+
165
+ const superseded = trimSeparators(
166
+ supersededSegments.slice(0, index + 1).join(SEGMENT_SEPARATOR),
167
+ )
168
+ const replacement = trimSeparators(
169
+ replacementSegments.slice(0, index + 1).join(SEGMENT_SEPARATOR),
170
+ )
171
+
172
+ if (superseded === '' || replacement === '') return undefined
173
+ if (superseded === replacement) return undefined
174
+
175
+ return { superseded, replacement }
176
+ }
177
+
178
+ function trimSeparators(value: string): string {
179
+ let end = value.length
180
+ while (end > 0 && value[end - 1] === SEGMENT_SEPARATOR) end -= 1
181
+ return value.slice(0, end)
182
+ }
183
+
184
+ /**
185
+ * Which templated form sits at `column`, or nothing when the stem there
186
+ * continues into an ordinary name.
187
+ *
188
+ * The character after `<stem>-` decides all three, so one scan reads every
189
+ * form. A name character means a sibling spelled out, which the literal
190
+ * comparison already answers for or correctly ignores.
191
+ */
192
+ function classifyStem(
193
+ line: string,
194
+ column: number,
195
+ stem: string,
196
+ ): SupersededMatch | undefined {
197
+ const before = column === 0 ? '' : line[column - 1]
198
+ if (before !== '' && SEGMENT_CONTINUATION.test(before)) return undefined
199
+
200
+ const after = line[column + stem.length + 1] ?? ''
201
+ if (after === '*') return 'glob'
202
+ if (after === '<') return 'placeholder'
203
+ if (after !== '' && NAME_CHARACTER.test(after)) return undefined
204
+ return 'prefix'
205
+ }
206
+
207
+ /**
208
+ * Whether the value replacing whatever matched appears on the line somewhere
209
+ * other than inside the matched occurrences, which is what makes the flag mean
210
+ * anything when one value contains the other.
211
+ *
212
+ * An empty replacement carries nothing, so it answers false rather than the
213
+ * true every line returns from a containment test against the empty string.
214
+ * Retiring a value outright is what passes one, and reporting every finding as
215
+ * carrying its replacement there says the opposite of what happened.
216
+ *
217
+ * A templated hit reads the stem pair instead, since the line repairing
218
+ * `toolkit-*` carries `canon-*` and never the full name either value spells.
219
+ */
220
+ function carriesReplacement(
221
+ line: string,
222
+ options: SupersededOptions,
223
+ stems: SupersededStems | undefined,
224
+ match: SupersededMatch,
225
+ ): boolean {
226
+ if (match === 'literal') {
227
+ if (options.replacement === '') return false
228
+ return line.split(options.superseded).join('').includes(options.replacement)
229
+ }
230
+
231
+ if (stems === undefined) return false
232
+ const matched = `${stems.superseded}${SEGMENT_SEPARATOR}`
233
+ return line
234
+ .split(matched)
235
+ .join('')
236
+ .includes(`${stems.replacement}${SEGMENT_SEPARATOR}`)
237
+ }
238
+
239
+ /**
240
+ * Every column in one line carrying the superseded value or its family stem,
241
+ * ordered left to right.
242
+ *
243
+ * A stem occurrence sharing a column with a literal one is dropped. The stem is
244
+ * a prefix of the value it derives from whenever only the last segment moved,
245
+ * so the same text would otherwise report twice under two kinds.
246
+ */
247
+ function matchLine(
248
+ line: string,
249
+ options: SupersededOptions,
250
+ stems: SupersededStems | undefined,
251
+ ): { column: number; match: SupersededMatch }[] {
252
+ const matches: { column: number; match: SupersededMatch }[] = []
253
+
254
+ let column = line.indexOf(options.superseded)
255
+ while (column !== -1) {
256
+ matches.push({ column, match: 'literal' })
257
+ column = line.indexOf(options.superseded, column + 1)
258
+ }
259
+
260
+ if (stems !== undefined) {
261
+ const pattern = `${stems.superseded}${SEGMENT_SEPARATOR}`
262
+ let at = line.indexOf(pattern)
263
+ while (at !== -1) {
264
+ const form = classifyStem(line, at, stems.superseded)
265
+ if (form !== undefined && !matches.some((hit) => hit.column === at)) {
266
+ matches.push({ column: at, match: form })
267
+ }
268
+ at = line.indexOf(pattern, at + 1)
269
+ }
270
+ }
271
+
272
+ return matches.sort((first, second) => first.column - second.column)
273
+ }
274
+
275
+ /**
276
+ * Every occurrence of `superseded` or of its family stem in one file's text,
277
+ * exemptions separated.
278
+ */
279
+ export function sweepText(
280
+ file: string,
281
+ text: string,
282
+ options: SupersededOptions,
283
+ ): { findings: SupersededHit[]; exempt: SupersededHit[] } {
284
+ const lines = text.split('\n')
285
+ const findings: SupersededHit[] = []
286
+ const exempt: SupersededHit[] = []
287
+ const stems = deriveStems(options)
288
+ const sectioned = file.endsWith('.md')
289
+ let heading: string | undefined
290
+ let fenced = false
291
+
292
+ for (const [index, line] of lines.entries()) {
293
+ if (sectioned) {
294
+ if (FENCE.test(line)) fenced = !fenced
295
+ else if (!fenced && HEADING.test(line)) heading = line.trim()
296
+ }
297
+
298
+ const matches = matchLine(line, options, stems)
299
+ if (matches.length === 0) continue
300
+
301
+ const muted = isMarked(lines, index, SUPERSEDED_MARKER)
302
+ const bucket = muted ? exempt : findings
303
+ const trimmed = line.trim()
304
+ const preview =
305
+ trimmed.length > PREVIEW_LIMIT
306
+ ? `${trimmed.slice(0, PREVIEW_LIMIT)}…`
307
+ : trimmed
308
+
309
+ for (const { column, match } of matches) {
310
+ bucket.push({
311
+ file,
312
+ line: index + 1,
313
+ column: column + 1,
314
+ match,
315
+ heading,
316
+ carriesReplacement: carriesReplacement(line, options, stems, match),
317
+ preview,
318
+ })
319
+ }
320
+ }
321
+
322
+ return { findings, exempt }
323
+ }
324
+
325
+ /**
326
+ * Where the tree still asserts a value the convention behind it no longer
327
+ * produces, keyed on the value rather than on the file stating the rule.
328
+ *
329
+ * The file-scoped map is what this exists against. A fixture asserting an old
330
+ * output names neither the rule nor the standard, so nothing reaches it from
331
+ * the change that superseded it, and the value it carries is the only key both
332
+ * sides share.
333
+ *
334
+ * This reports and never gates. A string appears for reasons unrelated to the
335
+ * convention, so the output is a reading a person settles, and the counts of
336
+ * what was listed, opened, and skipped are what keep it from reading as a
337
+ * verdict over the whole tree.
338
+ *
339
+ * Beside the literal comparison it matches the family stem the two values
340
+ * differ on, so a citation writing the family as a pattern enters the report.
341
+ * The trade is false positives, bounded at one across a sixteen-folder rename,
342
+ * which is why this reports rather than gates.
343
+ *
344
+ * What it cannot see is a prose reference that went stale without carrying the
345
+ * value, such as a declaration citing the wrong standard for the transform, and
346
+ * a family written in a form neither the value nor the three stem shapes reach.
347
+ * A value sweep closes most of this class and no part of either one.
348
+ */
349
+ export async function readSuperseded(
350
+ root: string,
351
+ options: SupersededOptions,
352
+ ): Promise<SupersededReport> {
353
+ if (options.superseded === '') {
354
+ return {
355
+ kind: 'unreadable',
356
+ reason:
357
+ 'The superseded value is empty, which matches every line in the tree rather than a convention.',
358
+ }
359
+ }
360
+
361
+ if (options.superseded === options.replacement) {
362
+ return {
363
+ kind: 'unreadable',
364
+ reason: `The superseded value and its replacement are both ${options.superseded}, so no convention changed and there is nothing to sweep for.`,
365
+ }
366
+ }
367
+
368
+ const listed = await listRepositoryFiles(root)
369
+ if (listed === undefined) {
370
+ return {
371
+ kind: 'unreadable',
372
+ reason: `Git listed no corpus under ${root}. An empty list passes each of its zero files, so a tree git cannot read refuses rather than reporting clean.`,
373
+ }
374
+ }
375
+
376
+ const findings: SupersededHit[] = []
377
+ const exempt: SupersededHit[] = []
378
+ let files = 0
379
+ let skipped = 0
380
+
381
+ for (const path of listed) {
382
+ let text: string
383
+ try {
384
+ text = await readFile(join(root, path), 'utf8')
385
+ } catch {
386
+ // A listed path that will not open is a symlink leaving the tree or a
387
+ // file removed since git answered. Counted rather than reported, so the
388
+ // run still states that it measured less than it listed.
389
+ skipped += 1
390
+ continue
391
+ }
392
+
393
+ if (isBinary(text)) {
394
+ skipped += 1
395
+ continue
396
+ }
397
+
398
+ files += 1
399
+ const swept = sweepText(path, text, options)
400
+ findings.push(...swept.findings)
401
+ exempt.push(...swept.exempt)
402
+ }
403
+
404
+ return {
405
+ kind: 'measured',
406
+ superseded: options.superseded,
407
+ replacement: options.replacement,
408
+ stems: deriveStems(options),
409
+ listed: listed.length,
410
+ files,
411
+ skipped,
412
+ findings,
413
+ exempt,
414
+ }
415
+ }