@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,114 @@
1
+ /**
2
+ * The readers an inventory can run, one per treatment.
3
+ *
4
+ * A reader runs inside the page rather than here, so each one is serialized to
5
+ * source and evaluated by the browser. That is why every helper a reader needs
6
+ * is declared inside its own body: a reference to anything at module scope
7
+ * survives typechecking and throws once the page tries to call it.
8
+ *
9
+ * `focus` is the first and, for now, the only subject. The four sibling
10
+ * instruments this shape was lifted from differ only in their element query and
11
+ * the property they read, which is what makes a subject a row here rather than
12
+ * a command of its own.
13
+ */
14
+
15
+ /** One element as a reader saw it, before any grouping. */
16
+ export interface SubjectReading {
17
+ readonly selector: string
18
+ readonly treatment: string
19
+ }
20
+
21
+ export interface Subject {
22
+ readonly name: string
23
+ /** What the listing means, printed above the rows so a count reads correctly. */
24
+ readonly summary: string
25
+ readonly read: (query: string) => SubjectReading[]
26
+ }
27
+
28
+ /**
29
+ * Reports what each element changes about itself when it takes focus, which is
30
+ * the reading `governance/rules/ui/410-a11y.md` states three rules against and
31
+ * nothing measures.
32
+ *
33
+ * The difference between rest and focus is the treatment, rather than the
34
+ * focused style on its own. A card carrying a resting shadow computes a shadow
35
+ * either way, so reading only the focused state would report a ring on an
36
+ * element whose appearance never moves.
37
+ *
38
+ * An element the browser refuses to focus is named as such rather than folded
39
+ * into the no-treatment row, since a disabled control and a control with no
40
+ * ring are different findings with different remedies.
41
+ */
42
+ function readFocusTreatments(query: string): SubjectReading[] {
43
+ const PROPERTIES = [
44
+ 'outlineStyle',
45
+ 'outlineWidth',
46
+ 'outlineColor',
47
+ 'outlineOffset',
48
+ 'boxShadow',
49
+ 'borderColor',
50
+ 'backgroundColor',
51
+ 'color',
52
+ ] as const
53
+
54
+ const describe = (element: Element): string => {
55
+ const tag = element.tagName.toLowerCase()
56
+ if (element.id) return `${tag}#${element.id}`
57
+ const className = element.getAttribute('class')?.trim().split(/\s+/)[0]
58
+ return className ? `${tag}.${className}` : tag
59
+ }
60
+
61
+ const snapshot = (element: Element): Record<string, string> => {
62
+ const computed = getComputedStyle(element)
63
+ const values: Record<string, string> = {}
64
+ for (const property of PROPERTIES) values[property] = computed[property]
65
+ return values
66
+ }
67
+
68
+ const rows: SubjectReading[] = []
69
+
70
+ // The walk presses Tab before this runs, which leaves one element focused.
71
+ // Reading that element's rest state while it holds focus reports no
72
+ // difference and hides whatever ring it actually draws, so the page starts
73
+ // from nothing focused and every element is blurred again after its turn.
74
+ const entryFocus = document.activeElement
75
+ if (entryFocus instanceof HTMLElement) entryFocus.blur()
76
+
77
+ for (const element of Array.from(document.querySelectorAll(query))) {
78
+ if (!(element instanceof HTMLElement)) continue
79
+
80
+ const rest = snapshot(element)
81
+ element.focus()
82
+ if (document.activeElement !== element) {
83
+ rows.push({ selector: describe(element), treatment: 'not focusable' })
84
+ continue
85
+ }
86
+
87
+ const focused = snapshot(element)
88
+ const changed = PROPERTIES.filter(
89
+ (property) => rest[property] !== focused[property],
90
+ ).map((property) => `${property} ${focused[property]}`)
91
+
92
+ rows.push({
93
+ selector: describe(element),
94
+ treatment:
95
+ changed.length === 0 ? 'no visible change' : changed.join(', '),
96
+ })
97
+ element.blur()
98
+ }
99
+
100
+ return rows
101
+ }
102
+
103
+ const FOCUS: Subject = {
104
+ name: 'focus',
105
+ summary: 'what each element changes about itself when it takes focus',
106
+ read: readFocusTreatments,
107
+ }
108
+
109
+ export const SUBJECTS: readonly Subject[] = [FOCUS]
110
+
111
+ /** Resolves a subject by name, so an unknown one is the caller's to report. */
112
+ export function findSubject(name: string): Subject | undefined {
113
+ return SUBJECTS.find((subject) => subject.name === name)
114
+ }
@@ -0,0 +1,129 @@
1
+ import { chromium } from 'playwright-core'
2
+ import type { Browser } from 'playwright-core'
3
+ import { isBrowserMissing } from '@/browser/engine'
4
+ import { routeUrl } from '@/inventory/config'
5
+ import type { Reading } from '@/inventory/group'
6
+ import type { Subject } from '@/inventory/subjects'
7
+
8
+ /**
9
+ * Walks a running application and reads one property off every element a
10
+ * subject names. Every browser reference the inventory feature adds lives here,
11
+ * and `src/commands/inventory.ts` reaches it through a dynamic import so no
12
+ * other command resolves the engine at startup.
13
+ *
14
+ * Like `@/demo/drive` and unlike `@/capture/render`, this module ships, because
15
+ * a command whose whole purpose is running inside someone else's project cannot
16
+ * stay toolkit-only.
17
+ */
18
+
19
+ /**
20
+ * The reader and the query arrive apart because they come from different
21
+ * owners. The toolkit ships the reader and the project declares which elements
22
+ * it runs over, which is what keeps the walk answering to the target rather
23
+ * than to a fixed selector nobody there chose.
24
+ */
25
+ export interface WalkOptions {
26
+ readonly baseUrl: string
27
+ readonly routes: readonly string[]
28
+ readonly subject: Subject
29
+ readonly query: string
30
+ }
31
+
32
+ /** A route that answered, and the elements read off it. */
33
+ export interface RouteReading {
34
+ readonly route: string
35
+ readonly elements: number
36
+ }
37
+
38
+ export type WalkRefusal =
39
+ | 'browser-missing'
40
+ | 'server-unreachable'
41
+ | 'walk-failed'
42
+
43
+ export type WalkResult =
44
+ | {
45
+ readonly status: 'read'
46
+ readonly readings: readonly Reading[]
47
+ readonly routes: readonly RouteReading[]
48
+ readonly durationMs: number
49
+ }
50
+ | {
51
+ readonly status: 'failed'
52
+ readonly reason: WalkRefusal
53
+ readonly message: string
54
+ }
55
+
56
+ function failed(reason: WalkRefusal, error: unknown): WalkResult {
57
+ return {
58
+ status: 'failed',
59
+ reason,
60
+ message: error instanceof Error ? error.message : String(error),
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Separates a server nobody started from a page that failed for its own
66
+ * reasons. The first is the precondition this command cannot create, and
67
+ * reporting it as an empty listing would say the site gives no answers when
68
+ * nothing was ever asked.
69
+ */
70
+ function isServerUnreachable(error: unknown): boolean {
71
+ const text = error instanceof Error ? error.message : String(error)
72
+ return /ERR_CONNECTION_REFUSED|ERR_NAME_NOT_RESOLVED|ERR_CONNECTION_RESET|ERR_EMPTY_RESPONSE/i.test(
73
+ text,
74
+ )
75
+ }
76
+
77
+ export async function walk(options: WalkOptions): Promise<WalkResult> {
78
+ const started = Date.now()
79
+
80
+ let browser: Browser
81
+ try {
82
+ browser = await chromium.launch()
83
+ } catch (error) {
84
+ return failed(
85
+ isBrowserMissing(error) ? 'browser-missing' : 'walk-failed',
86
+ error,
87
+ )
88
+ }
89
+
90
+ const readings: Reading[] = []
91
+ const routes: RouteReading[] = []
92
+
93
+ try {
94
+ const page = await browser.newPage()
95
+
96
+ for (const route of options.routes) {
97
+ await page.goto(routeUrl(options.baseUrl, route), {
98
+ waitUntil: 'domcontentloaded',
99
+ })
100
+
101
+ // Puts the page in keyboard modality before anything is focused, because
102
+ // a `:focus-visible` ring is the treatment a pointer never reveals and
103
+ // programmatic focus alone does not match it.
104
+ await page.keyboard.press('Tab')
105
+
106
+ const rows = await page.evaluate(options.subject.read, options.query)
107
+ for (const row of rows) readings.push({ route, ...row })
108
+ routes.push({ route, elements: rows.length })
109
+ }
110
+ } catch (error) {
111
+ return failed(
112
+ isServerUnreachable(error) ? 'server-unreachable' : 'walk-failed',
113
+ error,
114
+ )
115
+ } finally {
116
+ // The rejection is dropped rather than propagated, because a close that
117
+ // fails beside a walk that already failed would replace the refusal the
118
+ // caller was about to receive with a reason about teardown. Nothing the
119
+ // caller does depends on the browser having closed cleanly.
120
+ await browser.close().catch(() => undefined)
121
+ }
122
+
123
+ return {
124
+ status: 'read',
125
+ readings,
126
+ routes,
127
+ durationMs: Date.now() - started,
128
+ }
129
+ }
@@ -0,0 +1,82 @@
1
+ import { listChangedFiles, resolveBaseRef } from '@/git-files'
2
+ import { type Coverage, resolveCoverage } from '@/labels/coverage'
3
+ import { type MapRefusal, readLabelMap } from '@/labels/map'
4
+
5
+ /**
6
+ * Why an audit produced no reading.
7
+ *
8
+ * `no-map` travels through from the map reader and stays an answer rather than
9
+ * a fault. The git reasons are the opposite: a range this check asked for and
10
+ * could not get, which is a broken invocation rather than a project that
11
+ * declared nothing.
12
+ *
13
+ * A named ref that will not resolve is its own reason. Folding it into
14
+ * `no-base` sends the caller who already passed `--base` a message telling
15
+ * them to pass `--base`.
16
+ */
17
+ export type LabelAuditRefusal =
18
+ | MapRefusal
19
+ | 'no-base'
20
+ | 'bad-base'
21
+ | 'unreadable-changes'
22
+
23
+ export type LabelAudit =
24
+ | {
25
+ readonly kind: 'measured'
26
+ /** Absent when the caller supplied the changed set rather than a range. */
27
+ readonly base?: string
28
+ readonly changed: readonly string[]
29
+ readonly coverage: Coverage
30
+ }
31
+ | { readonly kind: 'refused'; readonly reason: LabelAuditRefusal }
32
+
33
+ export interface LabelAuditOptions {
34
+ /** Far side of the range, defaulting to the merge base against the trunk. */
35
+ readonly base?: string
36
+ /** A changed set the caller already holds, which skips git entirely. */
37
+ readonly paths?: readonly string[]
38
+ }
39
+
40
+ /**
41
+ * Resolves a changed set against the map a project declares, and reports both
42
+ * what it earns and what it leaves uncovered.
43
+ *
44
+ * One verb for two readers by design. `git-pr` reads the labels at open time
45
+ * and the audit aggregate reads the uncovered count, and a verb shaped for the
46
+ * first alone returns nothing the second can retain.
47
+ */
48
+ export async function auditLabels(
49
+ root: string,
50
+ options: LabelAuditOptions = {},
51
+ ): Promise<LabelAudit> {
52
+ const map = readLabelMap(root)
53
+ if (map.kind === 'refused') return { kind: 'refused', reason: map.reason }
54
+
55
+ if (options.paths !== undefined) {
56
+ return {
57
+ kind: 'measured',
58
+ changed: [...options.paths],
59
+ coverage: resolveCoverage(map, options.paths),
60
+ }
61
+ }
62
+
63
+ const base = await resolveBaseRef(root, options.base)
64
+ if (base === undefined) {
65
+ return {
66
+ kind: 'refused',
67
+ reason: options.base === undefined ? 'no-base' : 'bad-base',
68
+ }
69
+ }
70
+
71
+ const changed = await listChangedFiles(root, base)
72
+ if (changed === undefined) {
73
+ return { kind: 'refused', reason: 'unreadable-changes' }
74
+ }
75
+
76
+ return {
77
+ kind: 'measured',
78
+ base,
79
+ changed,
80
+ coverage: resolveCoverage(map, changed),
81
+ }
82
+ }
@@ -0,0 +1,79 @@
1
+ import type { LabelMap } from '@/labels/map'
2
+
3
+ /** A path a row leaves unlabelled on purpose, carrying the reason it gives. */
4
+ export interface DeclinedPath {
5
+ readonly path: string
6
+ readonly reason: string
7
+ }
8
+
9
+ export interface Coverage {
10
+ /** Distinct labels the whole set earns, ordered as the map declares them. */
11
+ readonly labels: readonly string[]
12
+ /**
13
+ * Paths a `[declined]` row covers, which are a decision rather than a gap.
14
+ *
15
+ * Kept apart from `uncovered` because the response to the two differs. A
16
+ * surface nobody has gotten to wants a row, and one somebody decided against
17
+ * wants nothing, so a report folding them together is useful about neither.
18
+ */
19
+ readonly declined: readonly DeclinedPath[]
20
+ /** Paths no row of either table reaches, which is the finding. */
21
+ readonly uncovered: readonly string[]
22
+ }
23
+
24
+ /**
25
+ * Prefix-anchored, matching what the map's own comment describes and what the
26
+ * census behind its 41 prefixes was measured against.
27
+ *
28
+ * A glob would reach every existing prefix and invalidate that measurement, so
29
+ * the rule stays a `startsWith` even where a glob would read more naturally.
30
+ */
31
+ function matches(path: string, prefixes: readonly string[]): boolean {
32
+ return prefixes.some((prefix) => path.startsWith(prefix))
33
+ }
34
+
35
+ /**
36
+ * Resolves what a changed set earns from a map, and what it leaves behind.
37
+ *
38
+ * One pass answers both readers. `git-pr` wants the labels to apply, and the
39
+ * audit wants the paths that earned none, and a function shaped for the first
40
+ * alone returns nothing the second can count.
41
+ */
42
+ export function resolveCoverage(
43
+ map: Extract<LabelMap, { kind: 'map' }>,
44
+ paths: readonly string[],
45
+ ): Coverage {
46
+ const earned = new Set<string>()
47
+ const declined: DeclinedPath[] = []
48
+ const uncovered: string[] = []
49
+
50
+ for (const path of paths) {
51
+ const labels = map.domains.filter((row) => matches(path, row.prefixes))
52
+
53
+ // A label wins over a decline. A path both tables claim already carries a
54
+ // subject, so reporting it as deliberately unlabelled would contradict the
55
+ // label the same run is about to apply.
56
+ if (labels.length > 0) {
57
+ for (const row of labels) earned.add(row.label)
58
+ continue
59
+ }
60
+
61
+ const row = map.declined.find((entry) => matches(path, entry.prefixes))
62
+ if (row !== undefined) {
63
+ declined.push({ path, reason: row.reason })
64
+ continue
65
+ }
66
+
67
+ uncovered.push(path)
68
+ }
69
+
70
+ return {
71
+ // Read back off the map rather than out of the set, so two runs over one
72
+ // branch produce one order and one string.
73
+ labels: map.domains
74
+ .map((row) => row.label)
75
+ .filter((label) => earned.has(label)),
76
+ declined,
77
+ uncovered,
78
+ }
79
+ }
@@ -0,0 +1,101 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+
4
+ /**
5
+ * Where a project declares its pull request label map, spelled once.
6
+ *
7
+ * The file has already moved once, and that relocation rewrote every mention
8
+ * across four surfaces. Nothing in code spells it anywhere else, so the next
9
+ * move is one edit here rather than a sweep.
10
+ */
11
+ export const MAP_REL = join('.claude', 'canon', 'pr-labels.toml')
12
+
13
+ /** A label name and the path prefixes that earn it, in the map's own order. */
14
+ export interface DomainRow {
15
+ readonly label: string
16
+ readonly prefixes: readonly string[]
17
+ }
18
+
19
+ /** A reason a path is deliberately unlabelled, and the prefixes it covers. */
20
+ export interface DeclinedRow {
21
+ readonly reason: string
22
+ readonly prefixes: readonly string[]
23
+ }
24
+
25
+ /**
26
+ * Why a map could not be read, which is never the same as a map with no rows.
27
+ *
28
+ * `no-map` is an answer rather than a fault. A project declaring no map is
29
+ * labelled silently by design, so a refusal read as a break would make the map
30
+ * mandatory for every target, which that decision declined. The other two are
31
+ * a file that exists and cannot be used, which is a defect in the map itself.
32
+ */
33
+ export type MapRefusal = 'no-map' | 'unreadable-map' | 'no-domains'
34
+
35
+ export type LabelMap =
36
+ | {
37
+ readonly kind: 'map'
38
+ readonly domains: readonly DomainRow[]
39
+ readonly declined: readonly DeclinedRow[]
40
+ }
41
+ | { readonly kind: 'refused'; readonly reason: MapRefusal }
42
+
43
+ /**
44
+ * Reads a TOML table of string arrays into rows, dropping any key whose value
45
+ * carries no usable prefix.
46
+ *
47
+ * A malformed row is skipped rather than refused, because both tables are
48
+ * authored by hand and one bad entry should not blind the check to the other
49
+ * forty. What it costs is that a typo reads as a row nobody wrote, which the
50
+ * uncovered report surfaces from the other side.
51
+ */
52
+ function readRows(table: unknown): { key: string; prefixes: string[] }[] {
53
+ if (typeof table !== 'object' || table === null || Array.isArray(table)) {
54
+ return []
55
+ }
56
+
57
+ const rows: { key: string; prefixes: string[] }[] = []
58
+ for (const [key, value] of Object.entries(table)) {
59
+ if (!Array.isArray(value)) continue
60
+ const prefixes = value.filter(
61
+ (entry): entry is string => typeof entry === 'string' && entry !== '',
62
+ )
63
+ if (prefixes.length > 0) rows.push({ key, prefixes })
64
+ }
65
+
66
+ return rows
67
+ }
68
+
69
+ /** Parses map text, so a caller holding the bytes skips the filesystem. */
70
+ export function parseLabelMap(source: string): LabelMap {
71
+ let parsed: Record<string, unknown>
72
+ try {
73
+ parsed = Bun.TOML.parse(source) as Record<string, unknown>
74
+ } catch {
75
+ return { kind: 'refused', reason: 'unreadable-map' }
76
+ }
77
+
78
+ const domains = readRows(parsed.domains)
79
+ if (domains.length === 0) return { kind: 'refused', reason: 'no-domains' }
80
+
81
+ return {
82
+ kind: 'map',
83
+ domains: domains.map(({ key, prefixes }) => ({ label: key, prefixes })),
84
+ declined: readRows(parsed.declined).map(({ key, prefixes }) => ({
85
+ reason: key,
86
+ prefixes,
87
+ })),
88
+ }
89
+ }
90
+
91
+ /** Reads the map a project declares at `root`, or says why it could not. */
92
+ export function readLabelMap(root: string): LabelMap {
93
+ let source: string
94
+ try {
95
+ source = readFileSync(join(root, MAP_REL), 'utf8')
96
+ } catch {
97
+ return { kind: 'refused', reason: 'no-map' }
98
+ }
99
+
100
+ return parseLabelMap(source)
101
+ }
@@ -0,0 +1,95 @@
1
+ import { linesOutsideFences, maskCodeSpans } from '@/markdown/scan'
2
+
3
+ /**
4
+ * The two version namespaces `standards/versioning.md` keeps apart, and why a
5
+ * pull request cannot be sorted between them by pattern alone.
6
+ *
7
+ * A phase label (`v68.5`) and a semver reference (`v3.44`) both match
8
+ * `v<digits>(.<digits>){1,2}`, so a shape-only scan cannot tell them apart. The
9
+ * first measurement of this defect tried exactly that and returned 28 of 60
10
+ * correct. What separates the two is not the token but the pull request: a
11
+ * release-please pull request legitimately carries version numbers in its
12
+ * generated body, and every other pull request does not.
13
+ */
14
+ export interface PhaseScanInput {
15
+ readonly title: string
16
+ readonly body: string
17
+ readonly headRefName: string
18
+ }
19
+
20
+ export interface PhaseScanResult {
21
+ /** Whether this pull request is release-please's own, per its fixed shape. */
22
+ readonly cutsRelease: boolean
23
+ readonly phaseLabels: readonly string[]
24
+ readonly semverTags: readonly string[]
25
+ }
26
+
27
+ const VERSION_TOKEN = /\bv\d+(?:\.\d+){1,2}\b/g
28
+
29
+ /**
30
+ * The head branch release-please opens every release pull request under.
31
+ *
32
+ * `release-please-config.json` names the package `canon`, so the observed
33
+ * branch is `release-please--branches--main--components--canon`. The prefix
34
+ * stops short of the component segment, since that segment moves with the
35
+ * package name and the branch segment does not.
36
+ */
37
+ const RELEASE_HEAD_PREFIX = 'release-please--branches--main'
38
+
39
+ const RELEASE_TITLE_PREFIX = 'chore(main): release '
40
+
41
+ /**
42
+ * Two fixed signals rather than one, because either alone is a string an
43
+ * author's own pull request could reproduce. A title starting with the
44
+ * release commit type is plain conventional-commit text, and a head branch is
45
+ * exactly what a PR renamed for review would want to imitate to slip a real
46
+ * phase label past this check. Together they name release-please's own act of
47
+ * opening the pull request, which nothing else on the remote can perform.
48
+ */
49
+ function isReleasePullRequest(input: PhaseScanInput): boolean {
50
+ return (
51
+ input.headRefName.startsWith(RELEASE_HEAD_PREFIX) &&
52
+ input.title.startsWith(RELEASE_TITLE_PREFIX)
53
+ )
54
+ }
55
+
56
+ /**
57
+ * Drops a fenced block outright and blanks a code span inside what remains,
58
+ * so a token quoted rather than written is read the way a reader reads it:
59
+ * shown, not asserted.
60
+ *
61
+ * `canon markdown audit` excludes a fenced block and a code span from its own
62
+ * ban scan over the same kind of text, and this reuses that reading rather
63
+ * than inventing a second one. `#1208` is the corpus case that forced it: a
64
+ * backticked span quoting a test fixture's own version-shaped name, which the
65
+ * shape-only scan below cannot tell from a leak on its own.
66
+ *
67
+ * A link destination stays unmasked, unlike the ban scan's own reading. A
68
+ * release-please body's real semver reference sits inside the generated
69
+ * compare link's URL, and masking it would empty `semverTags` on the one
70
+ * pull request this check exists to pass, trading the corpus's one code-span
71
+ * leak for a hole in every release.
72
+ */
73
+ function readable(text: string): string {
74
+ return linesOutsideFences(text).map(maskCodeSpans).join('\n')
75
+ }
76
+
77
+ /**
78
+ * Reads a title and a body for version-shaped tokens and sorts every one
79
+ * found into the namespace this pull request is allowed to carry.
80
+ *
81
+ * The split runs on the pull request rather than on each token. A release
82
+ * pull request's tokens are read as the semver references its generated body
83
+ * legitimately carries, and every other pull request's tokens are read as
84
+ * leaked phase labels, which is what `standards/versioning.md` names the
85
+ * defect this exists to catch.
86
+ */
87
+ export function scanPhaseLabels(input: PhaseScanInput): PhaseScanResult {
88
+ const text = readable(`${input.title}\n${input.body}`)
89
+ const tokens = [...new Set(text.match(VERSION_TOKEN) ?? [])]
90
+ const cutsRelease = isReleasePullRequest(input)
91
+
92
+ return cutsRelease
93
+ ? { cutsRelease, phaseLabels: [], semverTags: tokens }
94
+ : { cutsRelease, phaseLabels: tokens, semverTags: [] }
95
+ }
@@ -0,0 +1,94 @@
1
+ import type { BanSets } from '@/markdown/scan'
2
+
3
+ /**
4
+ * Single characters `markdown.md` bans under `## Punctuation`.
5
+ *
6
+ * The parenthetical-aside ban in that section is absent because it quotes a
7
+ * whole clause, and a literal match built from a clause reports the compliant
8
+ * text and reaches none of the violations.
9
+ */
10
+ const CHARACTERS = ['—', ';'] as const
11
+
12
+ /**
13
+ * Single lowercase words `markdown.md` bans under `## Language`.
14
+ *
15
+ * A multi-word ban is absent by the same test the character set applies. The
16
+ * standard bans a pattern like `It's not X, it's Y` with a placeholder standing
17
+ * in for the rest of the sentence, so no literal match reaches it and the
18
+ * phrase bans stay a reader's judgment.
19
+ */
20
+ const WORDS = [
21
+ 'seamless',
22
+ 'robust',
23
+ 'powerful',
24
+ 'revolutionary',
25
+ 'enhanced',
26
+ 'allows',
27
+ 'leverage',
28
+ 'simply',
29
+ 'just',
30
+ 'easily',
31
+ 'quickly',
32
+ 'very',
33
+ 'really',
34
+ ] as const
35
+
36
+ /**
37
+ * British spellings of the American examples `markdown.md` lists.
38
+ *
39
+ * The set is carried whole rather than derived from a suffix rule, because a
40
+ * suffix pattern run over prose produced 46 of the 58 false positives measured
41
+ * during intake: `exercises`, `promises`, and `revised` all end in `-ise` and
42
+ * none is a British spelling. Matching whole words reaches none of them.
43
+ *
44
+ * `analyse` is absent because the standard's example is `analyze`, which ends
45
+ * in `-yze` rather than the `-ize` its rule states. The set records what the
46
+ * standard reaches rather than what a reader would extend it to, so adding the
47
+ * spelling here would widen the check past the prose it answers to.
48
+ */
49
+ const SPELLINGS = [
50
+ 'organise',
51
+ 'summarise',
52
+ 'recognise',
53
+ 'behaviour',
54
+ 'colour',
55
+ 'centre',
56
+ ] as const
57
+
58
+ /**
59
+ * The three closed sets the audit measures, owned here rather than harvested
60
+ * from the standards stating them.
61
+ *
62
+ * Parsing the prose was the alternative and it put a parser contract on two
63
+ * documents authored for people, which the prose standard had to carry a
64
+ * paragraph of its own to protect. The sets are a prior an author already
65
+ * knows rather than a filter: measured at `60fc97bf` on 2026-08-19, these 21
66
+ * terms ran across 483 markdown files for a clean exit, and every one of the
67
+ * 70 occurrences of a banned word sat inside the ban list itself or inside an
68
+ * example demonstrating the ban.
69
+ *
70
+ * The set is closed rather than extensible. Enumeration cannot close the gap
71
+ * it aims at, and each addition costs a false-positive class, since `just`,
72
+ * `allows`, and `very` have honest uses no literal match separates. A project
73
+ * wanting its own vocabulary is asking for a different feature than this one.
74
+ */
75
+ export const BAN_SETS: BanSets = {
76
+ characters: CHARACTERS,
77
+ words: WORDS,
78
+ spellings: SPELLINGS,
79
+ }
80
+
81
+ /**
82
+ * Names any set that arrived empty, so a run measuring nothing says so.
83
+ *
84
+ * Empty is not the same state as finding no hit. A scan with no terms reports
85
+ * a clean file having looked for nothing, which is the silence the audit
86
+ * refused to ship back when a standard could go missing. The sets ship with
87
+ * the package now, so the only way one empties is an edit to this file, and
88
+ * the guard is what keeps that edit loud rather than quiet.
89
+ */
90
+ export function emptyBanSets(sets: BanSets = BAN_SETS): string[] {
91
+ return (['characters', 'words', 'spellings'] as const).filter(
92
+ (name) => sets[name].length === 0,
93
+ )
94
+ }