@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,256 @@
1
+ import { resolve } from 'node:path'
2
+ import type { Command } from 'commander'
3
+ import { INSTALL_BROWSER, isEngineMissing } from '@/browser/engine'
4
+ import {
5
+ CONFIG_REL,
6
+ type ConfigRefusal,
7
+ readInventoryConfig,
8
+ } from '@/inventory/config'
9
+ import { abbreviate, groupByTreatment } from '@/inventory/group'
10
+ import { findSubject, SUBJECTS } from '@/inventory/subjects'
11
+ import type { WalkRefusal } from '@/inventory/walk'
12
+ import { intro, logError, logInfo, logStep, logWarn, outro, plural } from '@/ui'
13
+
14
+ /**
15
+ * Holds wiring only. Every browser reference sits behind `loadWalker`, because
16
+ * `src/cli.ts` imports this module at startup and resolving the engine there
17
+ * would put a browser launch in front of every other command.
18
+ */
19
+ type Walker = typeof import('@/inventory/walk')
20
+
21
+ interface RunOptions {
22
+ readonly root?: string
23
+ readonly baseUrl?: string
24
+ readonly json?: boolean
25
+ }
26
+
27
+ /** What a reader does about each way the config produced no walk. */
28
+ const CONFIG_REFUSALS: Record<ConfigRefusal, string> = {
29
+ 'no-config': `No ${CONFIG_REL} here, so no routes and no element query are declared.`,
30
+ 'unreadable-config': `${CONFIG_REL} is not valid TOML, so no route could be read.`,
31
+ 'no-routes': `${CONFIG_REL} carries no route under routes, so there is nothing to walk.`,
32
+ 'no-base-url': `${CONFIG_REL} carries no base-url, so no route resolves to an address.`,
33
+ 'no-subjects': `${CONFIG_REL} declares no subject, and the element query comes from this project rather than from the toolkit.`,
34
+ }
35
+
36
+ const WALK_REFUSALS: Record<WalkRefusal, string> = {
37
+ 'browser-missing': 'The browser binary is not installed in this project.',
38
+ 'server-unreachable':
39
+ 'Nothing answered at the base URL, so no route was read.',
40
+ 'walk-failed': 'The walk failed against a running server.',
41
+ }
42
+
43
+ export function register(program: Command): void {
44
+ const inventory = program
45
+ .command('inventory')
46
+ .description('Report one computed property across every route of a project')
47
+ .helpOption('-h, --help', 'Show this help message')
48
+
49
+ inventory
50
+ .command('run', { isDefault: true })
51
+ .description(
52
+ 'Walk every route and group its elements by the answer each gives',
53
+ )
54
+ .argument('[subject]', 'Treatment to read, defaulting to focus', 'focus')
55
+ .helpOption('-h, --help', 'Show this help message')
56
+ .option('--root <path>', 'Project to read, defaulting to the cwd')
57
+ .option('--base-url <url>', 'Address to walk, overriding the config')
58
+ .option('--json', 'Add a machine-readable record on stdout')
59
+ .addHelpText(
60
+ 'after',
61
+ [
62
+ '',
63
+ `Reads ${CONFIG_REL} for the base URL, the routes, and the element query`,
64
+ 'each subject runs over, so what gets walked comes from the project',
65
+ 'rather than from the toolkit.',
66
+ '',
67
+ 'It reports a listing and never a verdict. The value is seeing how many',
68
+ 'different answers one site gives, and a gate collapses that to one bit.',
69
+ '',
70
+ 'Needs a running server and a browser binary. Install the browser with:',
71
+ ` ${INSTALL_BROWSER}`,
72
+ '',
73
+ 'Subjects:',
74
+ ...SUBJECTS.map((subject) => ` ${subject.name} ${subject.summary}`),
75
+ '',
76
+ 'Exit codes:',
77
+ ' 0 the walk read at least one element and reported its rows',
78
+ ' 1 refused, with the reason on stderr or in the JSON record',
79
+ '',
80
+ 'Examples:',
81
+ ' canon inventory focus',
82
+ ' canon inventory focus --json',
83
+ ' canon inventory focus --base-url http://localhost:3000',
84
+ '',
85
+ ].join('\n'),
86
+ )
87
+ .action(async (subject: string, opts: RunOptions) => {
88
+ process.exitCode = await runInventory(subject, opts)
89
+ })
90
+ }
91
+
92
+ async function runInventory(
93
+ subjectName: string,
94
+ opts: RunOptions,
95
+ ): Promise<number> {
96
+ const root = resolve(opts.root ?? process.cwd())
97
+ const emitJson = opts.json ?? false
98
+
99
+ intro(`canon inventory ${subjectName}`)
100
+
101
+ const subject = findSubject(subjectName)
102
+ if (!subject) {
103
+ const known = SUBJECTS.map((entry) => entry.name).join(', ')
104
+ return refuse(
105
+ emitJson,
106
+ root,
107
+ 'unknown-subject',
108
+ `No reader named ${subjectName}. This build ships: ${known}.`,
109
+ )
110
+ }
111
+
112
+ const read = readInventoryConfig(root)
113
+ if (read.kind === 'refused') {
114
+ return refuse(emitJson, root, read.reason, CONFIG_REFUSALS[read.reason])
115
+ }
116
+
117
+ const declared = read.config.subjects.find(
118
+ (entry) => entry.name === subjectName,
119
+ )
120
+ if (!declared) {
121
+ const named = read.config.subjects.map((entry) => entry.name).join(', ')
122
+ return refuse(
123
+ emitJson,
124
+ root,
125
+ 'undeclared-subject',
126
+ `${CONFIG_REL} declares no query for ${subjectName}. It declares: ${named}.`,
127
+ )
128
+ }
129
+
130
+ const baseUrl = opts.baseUrl ?? read.config.baseUrl
131
+
132
+ logStep('Scope')
133
+ logInfo(
134
+ `${plural(read.config.routes.length, 'route')} from ${baseUrl}, matching ${declared.query}`,
135
+ )
136
+
137
+ const walker = await loadWalker()
138
+ if (!walker) {
139
+ logStep('Browser')
140
+ logError('the browser engine is not installed in this project')
141
+ logWarn(`Install it with: ${INSTALL_BROWSER}`)
142
+ outro()
143
+ emit(emitJson, {
144
+ root,
145
+ subject: subjectName,
146
+ reason: 'engine-missing',
147
+ install: INSTALL_BROWSER,
148
+ })
149
+ return 1
150
+ }
151
+
152
+ const result = await walker.walk({
153
+ baseUrl,
154
+ routes: read.config.routes,
155
+ subject,
156
+ query: declared.query,
157
+ })
158
+
159
+ if (result.status === 'failed') {
160
+ logStep('Refused')
161
+ logWarn(WALK_REFUSALS[result.reason])
162
+ if (result.reason === 'server-unreachable') {
163
+ logWarn(`Start the project at ${baseUrl}, then run this again.`)
164
+ }
165
+ if (result.reason === 'browser-missing') {
166
+ logWarn(`Install the browser binary with: ${INSTALL_BROWSER}`)
167
+ }
168
+ logWarn(result.message.split('\n')[0] ?? '')
169
+ outro()
170
+ emit(emitJson, {
171
+ root,
172
+ subject: subjectName,
173
+ baseUrl,
174
+ reason: result.reason,
175
+ message: result.message,
176
+ ...(result.reason === 'browser-missing'
177
+ ? { install: INSTALL_BROWSER }
178
+ : {}),
179
+ })
180
+ return 1
181
+ }
182
+
183
+ const groups = groupByTreatment(result.readings)
184
+
185
+ logStep('Routes')
186
+ for (const route of result.routes) {
187
+ logInfo(`${route.route} ${plural(route.elements, 'element')}`)
188
+ }
189
+
190
+ // A walk that matched nothing refuses rather than printing an empty listing,
191
+ // because no rows and one row read the same to anything counting them, and
192
+ // the first says the query reached nothing while the second says the site
193
+ // gives one consistent answer.
194
+ if (groups.length === 0) {
195
+ return refuse(
196
+ emitJson,
197
+ root,
198
+ 'no-elements',
199
+ `No element matched ${declared.query} on any of the ${plural(result.routes.length, 'route')} walked.`,
200
+ )
201
+ }
202
+
203
+ logStep('Treatments')
204
+ logInfo(
205
+ `${plural(groups.length, 'answer')} across ${plural(result.readings.length, 'element')}`,
206
+ )
207
+ for (const group of groups) {
208
+ logInfo(` ${group.count}x ${group.treatment}`)
209
+ logInfo(
210
+ ` ${abbreviate(group.samples, group.count)} on ${abbreviate(group.routes, group.routes.length)}`,
211
+ )
212
+ }
213
+ outro()
214
+
215
+ emit(emitJson, {
216
+ root,
217
+ subject: subjectName,
218
+ baseUrl,
219
+ routes: result.routes,
220
+ elements: result.readings.length,
221
+ treatments: groups,
222
+ durationMs: result.durationMs,
223
+ })
224
+ return 0
225
+ }
226
+
227
+ /**
228
+ * Frames a refusal on stderr and puts the record on stdout, so an operator
229
+ * reading the terminal sees the reason rather than a command that appeared to
230
+ * do nothing.
231
+ */
232
+ function refuse(
233
+ emitJson: boolean,
234
+ root: string,
235
+ reason: string,
236
+ message: string,
237
+ ): number {
238
+ logStep('Refused')
239
+ logWarn(message)
240
+ outro()
241
+ emit(emitJson, { root, reason, message })
242
+ return 1
243
+ }
244
+
245
+ async function loadWalker(): Promise<Walker | undefined> {
246
+ try {
247
+ return await import('@/inventory/walk')
248
+ } catch (error) {
249
+ if (isEngineMissing(error)) return undefined
250
+ throw error
251
+ }
252
+ }
253
+
254
+ function emit(json: boolean, record: unknown): void {
255
+ if (json) process.stdout.write(`${JSON.stringify(record)}\n`)
256
+ }
@@ -0,0 +1,361 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+ import type { Command } from 'commander'
4
+ import { type LabelAuditRefusal, auditLabels } from '@/labels/audit'
5
+ import { MAP_REL } from '@/labels/map'
6
+ import { scanPhaseLabels } from '@/labels/phase'
7
+ import { intro, logInfo, logStep, logWarn, outro, plural } from '@/ui'
8
+
9
+ interface AuditOptions {
10
+ readonly base?: string
11
+ readonly root?: string
12
+ readonly json?: boolean
13
+ }
14
+
15
+ interface ScanOptions {
16
+ readonly event?: string
17
+ readonly title?: string
18
+ readonly body?: string
19
+ readonly head?: string
20
+ readonly json?: boolean
21
+ }
22
+
23
+ /** What a reader does about each way the audit produced no reading. */
24
+ const REFUSALS: Record<LabelAuditRefusal, string> = {
25
+ // An answer rather than a fault. A project declaring no map is labelled
26
+ // silently by design, so a refusal reading as a break would make the map
27
+ // mandatory for every target.
28
+ 'no-map': `No ${MAP_REL} here, so this project labels nothing and declares no surfaces to cover.`,
29
+ 'unreadable-map': `${MAP_REL} is not valid TOML, so no row could be read.`,
30
+ 'no-domains': `${MAP_REL} carries no usable row under [domains], so every path would read as uncovered.`,
31
+ 'no-base': 'No base resolves against the trunk. Fetch origin or pass --base.',
32
+ 'bad-base':
33
+ 'The ref passed to --base resolves to no commit here. Pass one this tree carries.',
34
+ 'unreadable-changes':
35
+ 'git could not list what this branch changed, so the set is unknown.',
36
+ }
37
+
38
+ export function register(program: Command): void {
39
+ const labels = program
40
+ .command('labels')
41
+ .description('Resolve a changed set against the pull request label map')
42
+ .helpOption('-h, --help', 'Show this help message')
43
+
44
+ labels
45
+ .command('audit')
46
+ .description(
47
+ 'Report the labels a changed set earns and the paths no row reaches',
48
+ )
49
+ .argument(
50
+ '[paths...]',
51
+ 'Changed set to read, defaulting to the branch range',
52
+ )
53
+ .helpOption('-h, --help', 'Show this help message')
54
+ .option('--base <ref>', 'Far side of the range, defaulting to the trunk')
55
+ .option('--root <path>', 'Repository to read, defaulting to the cwd')
56
+ .option('--json', 'Add a machine-readable record on stdout')
57
+ .addHelpText(
58
+ 'after',
59
+ [
60
+ '',
61
+ `Reads ${MAP_REL} and matches it prefix-anchored, which is the rule the`,
62
+ "map's own census was measured against. It reports and never gates,",
63
+ 'because whether an uncovered surface deserves a label is a judgment.',
64
+ '',
65
+ 'What it separates:',
66
+ ' uncovered a surface no row reaches, which is a gap wanting a row',
67
+ ' declined a path a [declined] row names, which is a decision already taken',
68
+ '',
69
+ 'What it does not measure:',
70
+ ' a prefix reaching no path, which is the map going stale from the other',
71
+ ' side and a second measure rather than this one',
72
+ '',
73
+ 'Exit codes:',
74
+ ' 0 every changed path is labelled or declined',
75
+ ' 1 refused, with the reason on stderr or in the JSON record',
76
+ ' 2 at least one changed path is reached by no row',
77
+ '',
78
+ 'Examples:',
79
+ ' canon labels audit',
80
+ ' canon labels audit --json',
81
+ ' canon labels audit --base origin/main',
82
+ ' canon labels audit src/cli.ts docs/index.md --json',
83
+ '',
84
+ ].join('\n'),
85
+ )
86
+ .action(async (paths: string[], opts: AuditOptions) => {
87
+ process.exitCode = await runAudit(paths, opts)
88
+ })
89
+
90
+ labels
91
+ .command('scan')
92
+ .description(
93
+ 'Fail a pull request whose title or body carries a phase label',
94
+ )
95
+ .helpOption('-h, --help', 'Show this help message')
96
+ .option(
97
+ '--event <path>',
98
+ 'GitHub pull_request event payload to read, such as $GITHUB_EVENT_PATH',
99
+ )
100
+ .option('--title <text>', 'Title to scan, overriding the event payload')
101
+ .option('--body <text>', 'Body to scan, overriding the event payload')
102
+ .option('--head <ref>', 'Head branch, overriding the event payload')
103
+ .option('--json', 'Add a machine-readable record on stdout')
104
+ .addHelpText(
105
+ 'after',
106
+ [
107
+ '',
108
+ 'Reads canon standards versioning for the two namespaces and sorts every',
109
+ 'version-shaped token this pull request carries into the one the pull',
110
+ 'request is allowed to hold. A release-please pull request, read off its',
111
+ 'own fixed head branch and title, may carry semver references. Every',
112
+ 'other pull request may carry neither, so any token found there is a',
113
+ 'leaked phase label.',
114
+ '',
115
+ 'Exit codes:',
116
+ ' 0 no phase label found',
117
+ ' 1 refused, with the reason on stderr or in the JSON record',
118
+ ' 2 the title or body carries a phase label',
119
+ '',
120
+ 'Examples:',
121
+ ' canon labels scan --event "$GITHUB_EVENT_PATH"',
122
+ ' canon labels scan --title "feat: x" --body "planned under v68.5" --head feat/x',
123
+ '',
124
+ ].join('\n'),
125
+ )
126
+ .action(async (opts: ScanOptions) => {
127
+ process.exitCode = await runScan(opts)
128
+ })
129
+ }
130
+
131
+ async function runAudit(paths: string[], opts: AuditOptions): Promise<number> {
132
+ const root = resolve(opts.root ?? process.cwd())
133
+ const emitJson = opts.json ?? false
134
+
135
+ const report = await auditLabels(root, {
136
+ base: opts.base,
137
+ ...(paths.length > 0 && { paths }),
138
+ })
139
+
140
+ intro('canon labels audit')
141
+
142
+ // The frame renders on stderr in both modes and the record goes to stdout
143
+ // alone, so an operator reading the terminal sees the refusal rather than a
144
+ // command that appeared to do nothing.
145
+ if (report.kind === 'refused') {
146
+ logStep('Refused')
147
+ logWarn(REFUSALS[report.reason])
148
+ outro()
149
+
150
+ if (emitJson) {
151
+ process.stdout.write(
152
+ `${JSON.stringify({
153
+ root,
154
+ reason: report.reason,
155
+ message: REFUSALS[report.reason],
156
+ })}\n`,
157
+ )
158
+ }
159
+ return 1
160
+ }
161
+
162
+ const { coverage } = report
163
+
164
+ logStep('Scope')
165
+ logInfo(
166
+ report.base === undefined
167
+ ? `${plural(report.changed.length, 'path')} supplied by the caller`
168
+ : `${plural(report.changed.length, 'path')} changed since ${report.base.slice(0, 8)}`,
169
+ )
170
+
171
+ logStep('Labels')
172
+ logInfo(
173
+ coverage.labels.length === 0
174
+ ? 'this set earns no label'
175
+ : coverage.labels.join(', '),
176
+ )
177
+
178
+ // Named rather than counted into the uncovered line. A path somebody decided
179
+ // against wants nothing done, and folding it in would ask for a row that was
180
+ // already refused.
181
+ logStep('Declined')
182
+ if (coverage.declined.length === 0) {
183
+ logInfo('no changed path is deliberately unlabelled')
184
+ } else {
185
+ for (const entry of coverage.declined) {
186
+ logInfo(`${entry.path}: ${entry.reason}`)
187
+ }
188
+ }
189
+
190
+ logStep(coverage.uncovered.length === 0 ? 'Covered' : 'Uncovered')
191
+ if (coverage.uncovered.length === 0) {
192
+ logInfo('every changed path is reached by a row')
193
+ } else {
194
+ logWarn(
195
+ `${plural(coverage.uncovered.length, 'path')} reached by no row. Give each a prefix on the row that owns its subject, or a [declined] row with the reason it earns none.`,
196
+ )
197
+ for (const path of coverage.uncovered) logWarn(path)
198
+ }
199
+
200
+ outro()
201
+
202
+ if (emitJson) {
203
+ process.stdout.write(
204
+ `${JSON.stringify({
205
+ root,
206
+ ...(report.base !== undefined && { base: report.base }),
207
+ changed: report.changed,
208
+ labels: coverage.labels,
209
+ declined: coverage.declined,
210
+ uncovered: coverage.uncovered,
211
+ })}\n`,
212
+ )
213
+ }
214
+
215
+ return coverage.uncovered.length === 0 ? 0 : 2
216
+ }
217
+
218
+ /** Why `runScan` had no title and body to hand `scanPhaseLabels`. */
219
+ type ScanInputRefusal = 'no-input' | 'unreadable-event' | 'not-a-pull-request'
220
+
221
+ type ResolvedScanInput =
222
+ | {
223
+ readonly kind: 'resolved'
224
+ readonly title: string
225
+ readonly body: string
226
+ readonly headRefName: string
227
+ }
228
+ | {
229
+ readonly kind: 'refused'
230
+ readonly reason: ScanInputRefusal
231
+ readonly message: string
232
+ }
233
+
234
+ /**
235
+ * Reads a title, a body, and a head branch from explicit flags first and the
236
+ * named event payload second, so a caller testing the wiring by hand never
237
+ * needs a real GitHub event file on disk.
238
+ */
239
+ function resolveScanInput(opts: ScanOptions): ResolvedScanInput {
240
+ let title = opts.title
241
+ let body = opts.body
242
+ let headRefName = opts.head
243
+
244
+ if (opts.event !== undefined) {
245
+ let raw: string
246
+ try {
247
+ raw = readFileSync(opts.event, 'utf8')
248
+ } catch {
249
+ return {
250
+ kind: 'refused',
251
+ reason: 'unreadable-event',
252
+ message: `${opts.event} could not be read, so no payload was there to scan.`,
253
+ }
254
+ }
255
+
256
+ let payload: unknown
257
+ try {
258
+ payload = JSON.parse(raw)
259
+ } catch {
260
+ return {
261
+ kind: 'refused',
262
+ reason: 'unreadable-event',
263
+ message: `${opts.event} is not valid JSON, so no payload was there to scan.`,
264
+ }
265
+ }
266
+
267
+ const pullRequest =
268
+ typeof payload === 'object' && payload !== null
269
+ ? (payload as Record<string, unknown>).pull_request
270
+ : undefined
271
+
272
+ if (typeof pullRequest !== 'object' || pullRequest === null) {
273
+ return {
274
+ kind: 'refused',
275
+ reason: 'not-a-pull-request',
276
+ message: `${opts.event} carries no pull_request, so no title or body exists to scan.`,
277
+ }
278
+ }
279
+
280
+ const record = pullRequest as Record<string, unknown>
281
+ const head = record.head
282
+ title ??= typeof record.title === 'string' ? record.title : undefined
283
+ body ??= typeof record.body === 'string' ? record.body : undefined
284
+ headRefName ??=
285
+ typeof head === 'object' &&
286
+ head !== null &&
287
+ typeof (head as Record<string, unknown>).ref === 'string'
288
+ ? ((head as Record<string, unknown>).ref as string)
289
+ : undefined
290
+ }
291
+
292
+ if (title === undefined) {
293
+ return {
294
+ kind: 'refused',
295
+ reason: 'no-input',
296
+ message:
297
+ 'No --event, --title, or --body given, so there is nothing to scan.',
298
+ }
299
+ }
300
+
301
+ return {
302
+ kind: 'resolved',
303
+ title,
304
+ body: body ?? '',
305
+ headRefName: headRefName ?? '',
306
+ }
307
+ }
308
+
309
+ async function runScan(opts: ScanOptions): Promise<number> {
310
+ const emitJson = opts.json ?? false
311
+
312
+ intro('canon labels scan')
313
+
314
+ const resolved = resolveScanInput(opts)
315
+
316
+ if (resolved.kind === 'refused') {
317
+ logStep('Refused')
318
+ logWarn(resolved.message)
319
+ outro()
320
+
321
+ if (emitJson) {
322
+ process.stdout.write(
323
+ `${JSON.stringify({ reason: resolved.reason, message: resolved.message })}\n`,
324
+ )
325
+ }
326
+ return 1
327
+ }
328
+
329
+ const result = scanPhaseLabels(resolved)
330
+
331
+ logStep('Pull request')
332
+ logInfo(
333
+ result.cutsRelease
334
+ ? 'reads as a release-please pull request'
335
+ : 'reads as an ordinary pull request',
336
+ )
337
+
338
+ logStep(result.phaseLabels.length === 0 ? 'Clean' : 'Phase label found')
339
+ if (result.phaseLabels.length === 0) {
340
+ logInfo('no phase label in the title or body')
341
+ } else {
342
+ logWarn(
343
+ `${plural(result.phaseLabels.length, 'phase label')} in the title or body. Describe the change itself and drop the internal label before publishing.`,
344
+ )
345
+ for (const label of result.phaseLabels) logWarn(label)
346
+ }
347
+
348
+ outro()
349
+
350
+ if (emitJson) {
351
+ process.stdout.write(
352
+ `${JSON.stringify({
353
+ cutsRelease: result.cutsRelease,
354
+ phaseLabels: result.phaseLabels,
355
+ semverTags: result.semverTags,
356
+ })}\n`,
357
+ )
358
+ }
359
+
360
+ return result.phaseLabels.length === 0 ? 0 : 2
361
+ }