@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,99 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
+ import { CORPORA } from '@/claude/skills-audit'
4
+
5
+ const FRONTMATTER = /^---\n([\s\S]*?)\n---/
6
+
7
+ export interface SkillListing {
8
+ readonly name: string
9
+ readonly description: string
10
+ readonly requirement: boolean
11
+ }
12
+
13
+ export interface SkillsCorpus {
14
+ /**
15
+ * The corpus spelling in POSIX form, so a report reads the same on Windows.
16
+ * `CorpusReport.rel` in `skills-audit.ts` is the same spelling left as `join`
17
+ * produced it, so the two verbs disagree there. Normalizing it is a contract
18
+ * change on an existing JSON field and belongs to a branch reading the audit.
19
+ */
20
+ readonly rel: string
21
+ /** The folder a listing reads, absolute against the root it was resolved at. */
22
+ readonly dir: string
23
+ }
24
+
25
+ /**
26
+ * The skill corpus a measure reads at a given root: the shipped tree in this
27
+ * repository and a target's own `.claude/skills/` in a project that consumes
28
+ * it. `CORPORA` order settles a tree carrying both, so every reading taken
29
+ * here still comes from `claude/skills/`.
30
+ *
31
+ * Kept apart from `listSkills` deliberately. `src/counts/catalogs.ts` counts
32
+ * the shipped catalog through that function, so teaching it to read both
33
+ * corpora would move the reported total off the tree that installs and
34
+ * falsify every sentence in the corpus stating it.
35
+ */
36
+ export function resolveSkillsCorpus(root: string): SkillsCorpus | undefined {
37
+ for (const rel of CORPORA) {
38
+ const dir = join(root, rel)
39
+ if (existsSync(dir)) return { rel: rel.replaceAll('\\', '/'), dir }
40
+ }
41
+
42
+ return undefined
43
+ }
44
+
45
+ /**
46
+ * Enumerates the plugin skill catalog, which is the corpus under `claude/`
47
+ * rather than the internal skills under `.claude/`. Only the former installs
48
+ * into a target, so a count taken across both overstates what ships.
49
+ *
50
+ * The folder name wins over the frontmatter `name` when they disagree, because
51
+ * Claude Code invokes a skill by its directory.
52
+ *
53
+ * `requirement` reports whether the folder carries `REQUIREMENT.md`. Every skill
54
+ * is meant to carry one, so a false is a gap rather than a recorded exemption.
55
+ * `canon claude skills audit` is what fails on it, across both corpora.
56
+ */
57
+ export function listSkills(root: string): SkillListing[] {
58
+ return listSkillsAt(join(root, 'claude', 'skills'))
59
+ }
60
+
61
+ /**
62
+ * The same enumeration against a corpus folder the caller already resolved,
63
+ * which is what `resolveSkillsCorpus` hands a measure that reaches a target.
64
+ */
65
+ export function listSkillsAt(skillsRoot: string): SkillListing[] {
66
+ if (!existsSync(skillsRoot)) return []
67
+
68
+ const paths = [
69
+ ...new Bun.Glob('*/SKILL.md').scanSync({
70
+ cwd: skillsRoot,
71
+ onlyFiles: true,
72
+ }),
73
+ ].sort()
74
+
75
+ return paths.map((path) => ({
76
+ name: dirname(path),
77
+ description: readDescription(join(skillsRoot, path)),
78
+ requirement: existsSync(join(skillsRoot, dirname(path), 'REQUIREMENT.md')),
79
+ }))
80
+ }
81
+
82
+ /**
83
+ * Returns an empty description rather than throwing on a skill whose
84
+ * frontmatter is missing or unparseable, so one malformed file does not hide
85
+ * the rest of the catalog from a caller counting it.
86
+ */
87
+ function readDescription(path: string): string {
88
+ const match = FRONTMATTER.exec(readFileSync(path, 'utf8'))
89
+ if (!match) return ''
90
+
91
+ try {
92
+ const parsed = Bun.YAML.parse(match[1])
93
+ if (typeof parsed !== 'object' || parsed === null) return ''
94
+ const { description } = parsed as Record<string, unknown>
95
+ return typeof description === 'string' ? description : ''
96
+ } catch {
97
+ return ''
98
+ }
99
+ }
@@ -0,0 +1,320 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { listSkillsAt, resolveSkillsCorpus } from '@/claude/skills-list'
3
+
4
+ /**
5
+ * Whether a prompt reaches the right skill, measured by TF-IDF cosine
6
+ * similarity over the shipped catalog's own frontmatter descriptions. This is
7
+ * a necessary condition rather than a report of real routing behavior: it
8
+ * asks whether the descriptions are separable by the words they use, and
9
+ * Claude Code does not route this way.
10
+ *
11
+ * Ported from `.claude/groundwork/42-ai-blueprint/evidence/rank.ts`, which
12
+ * ran once against this catalog and named the collisions this measure now
13
+ * tracks on a cadence.
14
+ */
15
+
16
+ const STOP = new Set(
17
+ 'a about after again all also and any are as at be before but by can do does for from has have help how in into is it its just make not of on or our so that the then there these this to use used uses using want was what when where which who why with you your run'.split(
18
+ ' ',
19
+ ),
20
+ )
21
+
22
+ export interface RankedSkill {
23
+ readonly name: string
24
+ readonly description: string
25
+ }
26
+
27
+ /** One prompt and the skill it should reach. */
28
+ export interface SkillCase {
29
+ readonly prompt: string
30
+ readonly expect: string
31
+ }
32
+
33
+ /** A case whose prompt did not rank its expected skill first. */
34
+ export interface Miss {
35
+ readonly prompt: string
36
+ readonly expect: string
37
+ /** The skill the ranker placed first instead. */
38
+ readonly won: string
39
+ /** Where `expect` placed, or 0 when it never appears in the catalog. */
40
+ readonly rank: number
41
+ }
42
+
43
+ /** Why a measure produced no reading, which is never the same as a clean one. */
44
+ export type RankRefusal = 'no-skills' | 'no-cases' | 'bad-cases'
45
+
46
+ /** The refusals a case corpus read produces, which the scan itself cannot raise. */
47
+ export type CaseCorpusRefusal = Extract<RankRefusal, 'no-cases' | 'bad-cases'>
48
+
49
+ export type CaseCorpusReport =
50
+ | { readonly kind: 'cases'; readonly cases: readonly SkillCase[] }
51
+ | {
52
+ readonly kind: 'refused'
53
+ readonly reason: CaseCorpusRefusal
54
+ /** What the caller has to change, which the reason alone never says. */
55
+ readonly detail: string
56
+ }
57
+
58
+ export type RankReport =
59
+ | {
60
+ readonly kind: 'measured'
61
+ /** The corpus spelling measured, since a root can carry either one. */
62
+ readonly corpus: string
63
+ readonly skills: number
64
+ readonly cases: number
65
+ readonly rank1: number
66
+ readonly top3: number
67
+ readonly misses: readonly Miss[]
68
+ /** Cases whose prompt carried no vocabulary to score. */
69
+ readonly unmeasurable: readonly SkillCase[]
70
+ }
71
+ | { readonly kind: 'refused'; readonly reason: RankRefusal }
72
+
73
+ /**
74
+ * Reads a project's own case corpus, which is JSON in the shape `SKILL_CASES`
75
+ * already holds. A target authors its own skills and its own vocabulary, so
76
+ * the toolkit corpus answers a question no other project asked.
77
+ *
78
+ * No standard stands behind the shape until a third project needs one, so
79
+ * every way the file fails is reported with what to change rather than
80
+ * measured against a spec. An empty array refuses for the reason a missing
81
+ * file does: a corpus of nothing scores 0 of 0 and reads as a clean pass.
82
+ */
83
+ export function loadCaseCorpus(path: string): CaseCorpusReport {
84
+ if (!existsSync(path)) {
85
+ return { kind: 'refused', reason: 'no-cases', detail: path }
86
+ }
87
+
88
+ let parsed: unknown
89
+ try {
90
+ parsed = JSON.parse(readFileSync(path, 'utf8'))
91
+ } catch (error) {
92
+ const detail = error instanceof Error ? error.message : String(error)
93
+ return { kind: 'refused', reason: 'bad-cases', detail }
94
+ }
95
+
96
+ if (!Array.isArray(parsed)) {
97
+ return {
98
+ kind: 'refused',
99
+ reason: 'bad-cases',
100
+ detail: 'the file holds something other than an array of cases',
101
+ }
102
+ }
103
+
104
+ const cases: SkillCase[] = []
105
+ for (const [index, entry] of parsed.entries()) {
106
+ const record = entry as Record<string, unknown> | null
107
+ if (
108
+ typeof record !== 'object' ||
109
+ record === null ||
110
+ typeof record.prompt !== 'string' ||
111
+ typeof record.expect !== 'string'
112
+ ) {
113
+ return {
114
+ kind: 'refused',
115
+ reason: 'bad-cases',
116
+ detail: `case ${index} carries no string prompt or expect`,
117
+ }
118
+ }
119
+
120
+ cases.push({ prompt: record.prompt, expect: record.expect })
121
+ }
122
+
123
+ if (cases.length === 0) {
124
+ return {
125
+ kind: 'refused',
126
+ reason: 'bad-cases',
127
+ detail: 'the file holds no cases at all',
128
+ }
129
+ }
130
+
131
+ return { kind: 'cases', cases }
132
+ }
133
+
134
+ /**
135
+ * Every shipped skill's frontmatter description, read the way a prompt is
136
+ * matched against it: whole, including the quoted trigger phrases it states.
137
+ * A skill whose frontmatter carries no description contributes no vocabulary
138
+ * and never wins a rank, so it is dropped rather than scored on nothing.
139
+ */
140
+ export function loadCatalog(root: string): RankedSkill[] {
141
+ const corpus = resolveSkillsCorpus(root)
142
+ return corpus === undefined ? [] : loadCatalogAt(corpus.dir)
143
+ }
144
+
145
+ /** The same read against a corpus folder the caller already resolved. */
146
+ export function loadCatalogAt(skillsRoot: string): RankedSkill[] {
147
+ return listSkillsAt(skillsRoot)
148
+ .filter((skill) => skill.description !== '')
149
+ .map((skill) => ({ name: skill.name, description: skill.description }))
150
+ }
151
+
152
+ function tokenize(text: string): string[] {
153
+ return text
154
+ .toLowerCase()
155
+ .replace(/[^a-z0-9\s-]/g, ' ')
156
+ .split(/\s+/)
157
+ .filter((token) => token.length > 2 && !STOP.has(token))
158
+ }
159
+
160
+ function termCounts(tokens: readonly string[]): Map<string, number> {
161
+ const counts = new Map<string, number>()
162
+ for (const token of tokens) counts.set(token, (counts.get(token) ?? 0) + 1)
163
+ return counts
164
+ }
165
+
166
+ function buildIdf(
167
+ docs: readonly Map<string, number>[],
168
+ ): (term: string) => number {
169
+ const total = docs.length
170
+ const documentFrequency = new Map<string, number>()
171
+ for (const doc of docs) {
172
+ for (const term of doc.keys()) {
173
+ documentFrequency.set(term, (documentFrequency.get(term) ?? 0) + 1)
174
+ }
175
+ }
176
+ return (term) =>
177
+ Math.log((total + 1) / ((documentFrequency.get(term) ?? 0) + 1)) + 1
178
+ }
179
+
180
+ function tfIdfVector(
181
+ terms: Map<string, number>,
182
+ idf: (term: string) => number,
183
+ ): Map<string, number> {
184
+ const vec = new Map<string, number>()
185
+ for (const [term, frequency] of terms) vec.set(term, frequency * idf(term))
186
+ return vec
187
+ }
188
+
189
+ function cosineSimilarity(
190
+ a: Map<string, number>,
191
+ b: Map<string, number>,
192
+ ): number {
193
+ let dot = 0
194
+ for (const [term, weight] of a) dot += weight * (b.get(term) ?? 0)
195
+ if (dot === 0) return 0
196
+
197
+ const norm = (vec: Map<string, number>) =>
198
+ Math.sqrt(
199
+ [...vec.values()].reduce((sum, weight) => sum + weight * weight, 0),
200
+ )
201
+ const denominator = norm(a) * norm(b)
202
+ return denominator === 0 ? 0 : dot / denominator
203
+ }
204
+
205
+ export interface RankedResult {
206
+ readonly name: string
207
+ readonly score: number
208
+ }
209
+
210
+ /**
211
+ * Every skill's TF-IDF vector over the catalog it was loaded with, and the
212
+ * IDF weighting that both the catalog and a scored prompt read from. A model
213
+ * is built once per catalog and reused across every case, since the IDF
214
+ * weights are the same question asked of the same corpus each time.
215
+ */
216
+ export interface RankModel {
217
+ readonly rank: (prompt: string) => readonly RankedResult[]
218
+ }
219
+
220
+ export function buildModel(catalog: readonly RankedSkill[]): RankModel {
221
+ const docs = catalog.map((skill) => termCounts(tokenize(skill.description)))
222
+ const idf = buildIdf(docs)
223
+ const vectors = new Map(
224
+ catalog.map((skill, index) => [skill.name, tfIdfVector(docs[index], idf)]),
225
+ )
226
+
227
+ return {
228
+ rank: (prompt: string): readonly RankedResult[] => {
229
+ const promptVector = tfIdfVector(termCounts(tokenize(prompt)), idf)
230
+ // A prompt built entirely from stopwords and short words tokenizes to
231
+ // nothing, so every skill would score 0 and the sort would fall through
232
+ // to `localeCompare`, handing the alphabetically first skill a win no
233
+ // description earned. Reporting no ranking at all is what keeps that
234
+ // tie-break from reading as a measurement.
235
+ if (promptVector.size === 0) return []
236
+
237
+ return [...vectors]
238
+ .map(([name, vector]) => ({
239
+ name,
240
+ score: cosineSimilarity(promptVector, vector),
241
+ }))
242
+ .sort(
243
+ (left, right) =>
244
+ right.score - left.score || left.name.localeCompare(right.name),
245
+ )
246
+ },
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Scores every case against the catalog's own descriptions, which is the
252
+ * production mechanism: Claude Code matches a prompt against the whole
253
+ * description a skill ships, triggers included.
254
+ */
255
+ export function measureCases(
256
+ catalog: readonly RankedSkill[],
257
+ cases: readonly SkillCase[],
258
+ ): {
259
+ readonly rank1: number
260
+ readonly top3: number
261
+ readonly misses: readonly Miss[]
262
+ readonly unmeasurable: readonly SkillCase[]
263
+ } {
264
+ const model = buildModel(catalog)
265
+ let rank1 = 0
266
+ let top3 = 0
267
+ const misses: Miss[] = []
268
+ const unmeasurable: SkillCase[] = []
269
+
270
+ for (const skillCase of cases) {
271
+ const ranked = model.rank(skillCase.prompt)
272
+ if (ranked.length === 0) {
273
+ unmeasurable.push(skillCase)
274
+ continue
275
+ }
276
+
277
+ const at = ranked.findIndex((entry) => entry.name === skillCase.expect) + 1
278
+
279
+ if (at === 1) rank1 += 1
280
+ if (at > 0 && at <= 3) top3 += 1
281
+ if (at !== 1) {
282
+ misses.push({
283
+ prompt: skillCase.prompt,
284
+ expect: skillCase.expect,
285
+ won: ranked[0]?.name ?? '',
286
+ rank: at,
287
+ })
288
+ }
289
+ }
290
+
291
+ return { rank1, top3, misses, unmeasurable }
292
+ }
293
+
294
+ /**
295
+ * Reads whichever skill corpus the root carries and scores it against the
296
+ * given cases. Measures the cwd's catalog rather than the toolkit root,
297
+ * matching the reach and audit verbs, so a linked worktree reads its own
298
+ * branch and a target reads the skills it wrote itself.
299
+ */
300
+ export function scanRank(
301
+ root: string,
302
+ cases: readonly SkillCase[],
303
+ ): RankReport {
304
+ const corpus = resolveSkillsCorpus(root)
305
+ if (corpus === undefined) return { kind: 'refused', reason: 'no-skills' }
306
+
307
+ const catalog = loadCatalogAt(corpus.dir)
308
+ const { rank1, top3, misses, unmeasurable } = measureCases(catalog, cases)
309
+
310
+ return {
311
+ kind: 'measured',
312
+ corpus: corpus.rel,
313
+ skills: catalog.length,
314
+ cases: cases.length,
315
+ rank1,
316
+ top3,
317
+ misses,
318
+ unmeasurable,
319
+ }
320
+ }
@@ -0,0 +1,227 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { resolveSkillsCorpus } from '@/claude/skills-list'
4
+
5
+ /**
6
+ * The authoring roots this repository owns and no install channel delivers.
7
+ *
8
+ * Every entry is a folder a target never holds under that spelling. Standards
9
+ * and snippets install nowhere and are reached through the plugin corpus,
10
+ * rules install under `.claude/rules/`, and the rest are this repository's
11
+ * own source, docs, and catalogs.
12
+ *
13
+ * `src/`, `scripts/`, and bare `docs/` are deliberately absent. A body naming
14
+ * one of those is describing the reader's own tree, so listing them would
15
+ * report a correct citation on every run and bury the defect this measures.
16
+ * `docs/agents/` is the exception, being the CLI contract pages that exist
17
+ * here alone.
18
+ */
19
+ const AUTHORING_ROOTS = [
20
+ '.claude/context/',
21
+ 'claude/',
22
+ 'docs/agents/',
23
+ 'governance/',
24
+ 'internal/',
25
+ 'snippets/',
26
+ 'standards/',
27
+ 'tooling/',
28
+ 'wiki/',
29
+ ] as const
30
+
31
+ /**
32
+ * What marks a citation as deliberately naming this repository's own copy.
33
+ *
34
+ * The word rather than a notation, matching the three bodies that already
35
+ * spell it and the repair the plan settled on. A parser-visible syntax was the
36
+ * alternative and it invents a spelling for a handful of lines while leaving
37
+ * the shipped precedent unreadable.
38
+ */
39
+ const QUALIFIER = /toolkit/i
40
+
41
+ /** A backticked token, which is how every body spells a path it cites. */
42
+ const TOKEN = /`([^`\s]+)`/g
43
+
44
+ /**
45
+ * A path a reader could open, which is the only kind worth measuring.
46
+ *
47
+ * Requires an extension and a separator, and admits no `<`, `$`, or `*`. A
48
+ * body writes `.claude/context/<domain>.md` to name a shape rather than a
49
+ * file, and `${CLAUDE_SKILL_DIR}/../../standards/markdown.md` to resolve
50
+ * against the plugin root, which is self-contained by construction.
51
+ */
52
+ const CONCRETE = /^[.A-Za-z0-9_][A-Za-z0-9._/-]*\.[a-z]{1,4}$/
53
+
54
+ export interface Citation {
55
+ readonly file: string
56
+ /** One-based, matching the `file:line` form a reader clicks. */
57
+ readonly line: number
58
+ readonly path: string
59
+ readonly qualified: boolean
60
+ }
61
+
62
+ /** Why a scan produced no corpus, which is never the same as a clean one. */
63
+ export type ReachRefusal = 'no-skills'
64
+
65
+ export type ReachReport =
66
+ | {
67
+ readonly kind: 'measured'
68
+ /** The corpus spelling read, since a root can carry either one. */
69
+ readonly corpus: string
70
+ /** Files opened, so a report can state what the verdict covers. */
71
+ readonly bodies: number
72
+ readonly qualified: readonly Citation[]
73
+ readonly unqualified: readonly Citation[]
74
+ }
75
+ | { readonly kind: 'refused'; readonly reason: ReachRefusal }
76
+
77
+ export function isQualified(line: string): boolean {
78
+ return QUALIFIER.test(line)
79
+ }
80
+
81
+ /**
82
+ * The roots that belong to the toolkit rather than to the reader, read
83
+ * against the corpus being measured.
84
+ *
85
+ * A target's `.claude/context/` is the reader's own tree. A seed put the
86
+ * entries there and the project owns them afterward, so a body under
87
+ * `.claude/skills/` citing one names a file its reader holds. Measuring it
88
+ * would report a correct citation on every run, which is exactly why `src/`
89
+ * and `scripts/` are absent from the list above. In this repository the seed
90
+ * tree settles the same question through `readReceivedPaths`, which a target
91
+ * carrying no `tooling/` folder cannot answer at all, so the root comes out
92
+ * by corpus instead.
93
+ */
94
+ export function authoringRootsFor(corpus: string): readonly string[] {
95
+ if (!corpus.startsWith('.claude/')) return AUTHORING_ROOTS
96
+
97
+ return AUTHORING_ROOTS.filter((root) => !root.startsWith('.claude/'))
98
+ }
99
+
100
+ /**
101
+ * Every path a seed lands on in a target, spelled the way a body would cite it.
102
+ *
103
+ * Read off the seed tree rather than listed, so a seed added to any stack
104
+ * clears its own citations without this module being edited. Dotfiles are in
105
+ * scope because the whole seeded context corpus sits under `.claude/`.
106
+ */
107
+ export function readReceivedPaths(root: string): Set<string> {
108
+ const toolingRoot = join(root, 'tooling')
109
+ if (!existsSync(toolingRoot)) return new Set()
110
+
111
+ const received = new Set<string>()
112
+ for (const path of new Bun.Glob('*/seeds/**/*').scanSync({
113
+ cwd: toolingRoot,
114
+ onlyFiles: true,
115
+ dot: true,
116
+ })) {
117
+ const posix = path.replaceAll('\\', '/')
118
+ received.add(posix.replace(/^[^/]+\/seeds\//, ''))
119
+ }
120
+ return received
121
+ }
122
+
123
+ /**
124
+ * Whether a cited path is this repository's own rather than the reader's.
125
+ *
126
+ * A seeded path is disowned twice over: under its own name, and under the
127
+ * folder spelling it takes once a target splits the entry. A domain that
128
+ * outgrows one file becomes `<domain>/`, which is still the entry the seed
129
+ * delivered, so reporting the split form would fail a target for growing.
130
+ */
131
+ export function isToolkitOwned(
132
+ path: string,
133
+ received: Set<string>,
134
+ roots: readonly string[] = AUTHORING_ROOTS,
135
+ ): boolean {
136
+ if (received.has(path)) return false
137
+
138
+ for (const seeded of received) {
139
+ const stem = seeded.replace(/\.md$/, '')
140
+ if (stem !== seeded && path.startsWith(`${stem}/`)) return false
141
+ }
142
+
143
+ return roots.some((prefix) => path.startsWith(prefix))
144
+ }
145
+
146
+ /**
147
+ * Every toolkit-owned path one shipped file cites, with the line's verdict.
148
+ *
149
+ * Existence is not checked here. A body may name a path this repository once
150
+ * held, and separating the shape test from the disk read is what lets the
151
+ * shape be tested without a tree on disk.
152
+ */
153
+ export function citationsIn(
154
+ file: string,
155
+ text: string,
156
+ received: Set<string>,
157
+ roots: readonly string[] = AUTHORING_ROOTS,
158
+ ): Citation[] {
159
+ const citations: Citation[] = []
160
+
161
+ for (const [index, line] of text.split('\n').entries()) {
162
+ const qualified = isQualified(line)
163
+
164
+ for (const match of line.matchAll(TOKEN)) {
165
+ const path = match[1]
166
+ if (!CONCRETE.test(path) || !path.includes('/')) continue
167
+ if (!isToolkitOwned(path, received, roots)) continue
168
+
169
+ citations.push({ file, line: index + 1, path, qualified })
170
+ }
171
+ }
172
+
173
+ return citations
174
+ }
175
+
176
+ /**
177
+ * Reads every body in the root's skill corpus for a path its reader cannot
178
+ * open.
179
+ *
180
+ * A citation of a path this repository does not hold is dropped rather than
181
+ * reported. The measure asks whether a claim true here is false in a target,
182
+ * and a path true in neither is a different defect that `canon context audit`
183
+ * already reports against its own corpus.
184
+ *
185
+ * `resolveSkillsCorpus` prefers `claude/skills/`, which is the tree that
186
+ * installs into a target, so this repository's own reading is the one it
187
+ * always was. A project carrying `.claude/skills/` alone has no shipped tree
188
+ * and its own skills are the whole corpus a reader there opens.
189
+ */
190
+ export function scanReach(root: string): ReachReport {
191
+ const corpus = resolveSkillsCorpus(root)
192
+ if (corpus === undefined) return { kind: 'refused', reason: 'no-skills' }
193
+
194
+ const received = readReceivedPaths(root)
195
+ const roots = authoringRootsFor(corpus.rel)
196
+ const files = [
197
+ ...new Bun.Glob('**/*.md').scanSync({ cwd: corpus.dir, onlyFiles: true }),
198
+ ].sort()
199
+
200
+ const qualified: Citation[] = []
201
+ const unqualified: Citation[] = []
202
+
203
+ for (const file of files) {
204
+ const posix = file.replaceAll('\\', '/')
205
+ const text = readFileSync(join(corpus.dir, file), 'utf8')
206
+
207
+ for (const citation of citationsIn(
208
+ `${corpus.rel}/${posix}`,
209
+ text,
210
+ received,
211
+ roots,
212
+ )) {
213
+ if (!existsSync(join(root, citation.path))) continue
214
+
215
+ if (citation.qualified) qualified.push(citation)
216
+ else unqualified.push(citation)
217
+ }
218
+ }
219
+
220
+ return {
221
+ kind: 'measured',
222
+ corpus: corpus.rel,
223
+ bodies: files.length,
224
+ qualified,
225
+ unqualified,
226
+ }
227
+ }
package/src/cli-run.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { join } from 'node:path'
2
+ import { execa } from 'execa'
3
+
4
+ export interface CliRunOptions {
5
+ /** Sets `CANON_NON_INTERACTIVE=1` for the child. Defaults to false. */
6
+ readonly nonInteractive?: boolean
7
+ /** Defaults to `inherit`, so a child prompt still reaches the operator. */
8
+ readonly stdin?: 'ignore' | 'inherit'
9
+ }
10
+
11
+ export function cliPath(root: string): string {
12
+ return join(root, 'src', 'cli.ts')
13
+ }
14
+
15
+ /**
16
+ * Spawns a `canon` subcommand as its own process, by path rather than through
17
+ * the global binary, so a linked worktree exercises its own code.
18
+ *
19
+ * Both callers want a separately failable child rather than an in-process call:
20
+ * `canon init` reports each domain's outcome independently, and `canon sync`
21
+ * relies on each domain opening its own frame. Returns whether the child
22
+ * succeeded rather than throwing, because a failed domain is a reportable
23
+ * outcome and not an abort.
24
+ */
25
+ export function cliRun(
26
+ path: string,
27
+ args: readonly string[],
28
+ options: CliRunOptions = {},
29
+ ): () => Promise<boolean> {
30
+ return async () => {
31
+ const env = options.nonInteractive
32
+ ? { ...process.env, CANON_NON_INTERACTIVE: '1' }
33
+ : process.env
34
+
35
+ const result = await execa('bun', [path, ...args], {
36
+ stdio: [options.stdin ?? 'inherit', 'inherit', 'inherit'],
37
+ env,
38
+ reject: false,
39
+ })
40
+
41
+ return result.exitCode === 0
42
+ }
43
+ }