@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,468 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import type { Command } from 'commander'
4
+ import { execScript } from '@/exec'
5
+ import { PROJECT_ROOT } from '@/project-root'
6
+ import {
7
+ assertedPercent,
8
+ collectCensus,
9
+ type CensusReport,
10
+ } from '@/sandbox/census'
11
+ import {
12
+ collectCoverage,
13
+ coveragePercent,
14
+ type CoverageReport,
15
+ } from '@/sandbox/coverage'
16
+ import {
17
+ expectFilePath,
18
+ parseTarget,
19
+ resolveVerdict,
20
+ verdictExitCode,
21
+ type RunEnvelope,
22
+ type Verdict,
23
+ } from '@/sandbox/expect'
24
+ import { sandboxTree } from '@/sandbox/tree'
25
+ import {
26
+ frameError,
27
+ intro,
28
+ logError,
29
+ logInfo,
30
+ logRemove,
31
+ logStep,
32
+ logWarn,
33
+ outro,
34
+ select,
35
+ } from '@/ui'
36
+
37
+ const SANDBOX_DIR = join(PROJECT_ROOT, 'scripts', 'sandbox')
38
+
39
+ /**
40
+ * Reports that the scenario tree does not ship, and answers whether it reported,
41
+ * so a caller that sees `true` returns without touching the tree.
42
+ *
43
+ * `scripts/sandbox` is excluded from the published package, so an installed
44
+ * `canon` resolves `SANDBOX_DIR` to a directory that is not there. Both entry
45
+ * points that walk the tree ask here rather than carrying a check each, because
46
+ * absence is a property of the install rather than of a verb, and a second copy
47
+ * of the question is a second message to keep true. `check` reads the tree as
48
+ * well, through `expectFilePath`, and needs no guard because its provisioned-tree
49
+ * check already stops an installed run before the read.
50
+ *
51
+ * The distinction it preserves is between an absent tree and an empty one. Only
52
+ * the second is a real zero, and a coverage percentage over a denominator nobody
53
+ * looked at reads as a suite that examined everything and found it clean.
54
+ *
55
+ * The frame opens and closes here, so this runs before `intro` rather than
56
+ * inside an open frame.
57
+ */
58
+ function reportAbsentScenarioTree(): boolean {
59
+ if (existsSync(SANDBOX_DIR)) return false
60
+
61
+ frameError('sandbox is toolkit-only and is absent from an installed canon')
62
+ process.exitCode = 1
63
+
64
+ return true
65
+ }
66
+
67
+ /**
68
+ * Holds fixture content for scenarios rather than scenarios of its own.
69
+ * Twin of the `-not -name fixtures` filter in `scripts/manage-sandbox.sh`.
70
+ */
71
+ const FIXTURES_DIR = 'fixtures'
72
+
73
+ /** A run that reported nothing counts as clean, so only real signals fail. */
74
+ const CLEAN_ENVELOPE: RunEnvelope = { isError: false, turns: 0, denials: 0 }
75
+
76
+ interface CheckOptions {
77
+ readonly envelope?: string
78
+ readonly writes?: string
79
+ readonly escapes?: string
80
+ readonly escapesWatched?: boolean
81
+ readonly json?: boolean
82
+ readonly strict?: boolean
83
+ }
84
+
85
+ interface CoverageOptions {
86
+ readonly json?: boolean
87
+ readonly strict?: boolean
88
+ readonly skills?: boolean
89
+ }
90
+
91
+ function getCategories(): string[] {
92
+ return readdirSync(SANDBOX_DIR, { withFileTypes: true })
93
+ .filter((d) => d.isDirectory() && d.name !== FIXTURES_DIR)
94
+ .map((d) => d.name)
95
+ .sort()
96
+ }
97
+
98
+ function getCommands(category: string): string[] {
99
+ return readdirSync(join(SANDBOX_DIR, category), { withFileTypes: true })
100
+ .filter((f) => f.isFile() && f.name.endsWith('.sh'))
101
+ .map((f) => f.name.replace(/\.sh$/, ''))
102
+ .sort()
103
+ }
104
+
105
+ async function interactivePicker(): Promise<string> {
106
+ intro('canon sandbox')
107
+
108
+ const categories = getCategories()
109
+ const category = await select({
110
+ message: 'Select category:',
111
+ options: categories.map((c) => ({ value: c, label: c })),
112
+ })
113
+
114
+ const commands = getCommands(category)
115
+ const command = await select({
116
+ message: 'Select command:',
117
+ options: commands.map((c) => ({ value: c, label: c })),
118
+ })
119
+
120
+ return `${category}:${command}`
121
+ }
122
+
123
+ /**
124
+ * Reads the `claude -p --output-format json` envelope. Returns undefined when no
125
+ * file was given, so the turn ceiling reports as skipped rather than passing on a
126
+ * fabricated zero. A file that exists but does not parse falls back to clean,
127
+ * since decision 8 lets the envelope fail a run but never pass one.
128
+ *
129
+ * `result` carries the reply text and is left undefined on both fallbacks, which
130
+ * routes a reply assertion to skipped. Fabricating an empty string there would
131
+ * fail every reply assertion on a run that supplied no envelope, turning a gap in
132
+ * the input into a red arm.
133
+ */
134
+ function readEnvelope(path: string | undefined): RunEnvelope | undefined {
135
+ if (path === undefined) return undefined
136
+ if (!existsSync(path)) return CLEAN_ENVELOPE
137
+
138
+ try {
139
+ const parsed = JSON.parse(readFileSync(path, 'utf8')) as Record<
140
+ string,
141
+ unknown
142
+ >
143
+ const denials = parsed.permission_denials
144
+
145
+ return {
146
+ isError: parsed.is_error === true,
147
+ turns: typeof parsed.num_turns === 'number' ? parsed.num_turns : 0,
148
+ denials: Array.isArray(denials) ? denials.length : 0,
149
+ reply: typeof parsed.result === 'string' ? parsed.result : undefined,
150
+ }
151
+ } catch {
152
+ return CLEAN_ENVELOPE
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Shared by `--writes` and `--escapes`, which are both a newline-delimited
158
+ * path list written by `run.sh`. Undefined when no file was given, which is
159
+ * not the same as a run that produced no paths. Write scope and escape scope
160
+ * both need that distinction: an empty list is a finding, an absent list is a
161
+ * gap in what the caller supplied.
162
+ */
163
+ function readPathList(path: string | undefined): string[] | undefined {
164
+ if (path === undefined) return undefined
165
+ if (!existsSync(path)) return []
166
+
167
+ return readFileSync(path, 'utf8')
168
+ .split('\n')
169
+ .map((line) => line.trim())
170
+ .filter((line) => line !== '')
171
+ }
172
+
173
+ /**
174
+ * The unchecked count rides the summary line in every verdict, including a pass.
175
+ * It is the number the rollout is measured by, and a count that surfaces only on
176
+ * arms that happen to carry prose is a count nobody drains.
177
+ */
178
+ function reportVerdict(verdict: Verdict): void {
179
+ if (verdict.results.length > 0) {
180
+ logStep('Checking expectations')
181
+ for (const result of verdict.results) {
182
+ if (result.ok) logInfo(result.message)
183
+ else logRemove(result.message)
184
+ }
185
+ }
186
+
187
+ if (verdict.manual.length > 0) {
188
+ logStep('Not checked (needs a reader)')
189
+ for (const line of verdict.manual) logWarn(line)
190
+ }
191
+
192
+ if (verdict.skipped.length > 0) {
193
+ logStep('Not checked (no data supplied)')
194
+ for (const line of verdict.skipped) logWarn(line)
195
+ }
196
+
197
+ logStep(`Verdict: ${verdict.state.toUpperCase()}`)
198
+ if (verdict.note !== undefined) logWarn(verdict.note)
199
+
200
+ const summary = `${verdict.asserted} asserted, ${verdict.failed} failed, ${verdict.unchecked} unchecked`
201
+ if (verdict.state === 'unchecked') logWarn(summary)
202
+ else logInfo(summary)
203
+ }
204
+
205
+ /**
206
+ * Lists the undeclared scenarios by name rather than counting them. A percentage
207
+ * alone is a number nobody acts on, while a name is the next thing to arm.
208
+ */
209
+ function reportCoverage(report: CoverageReport): void {
210
+ const armed = report.scenarios.filter((s) => s.armed.length > 0)
211
+ const bare = report.scenarios.filter((s) => s.armed.length === 0)
212
+
213
+ if (armed.length > 0) {
214
+ logStep('Declares expectations')
215
+ for (const scenario of armed) {
216
+ logInfo(
217
+ `${scenario.category}:${scenario.command} (${scenario.armed.join(', ')})`,
218
+ )
219
+ }
220
+ }
221
+
222
+ if (bare.length > 0) {
223
+ logStep('Provisions only, asserts nothing')
224
+ for (const scenario of bare)
225
+ logWarn(`${scenario.category}:${scenario.command}`)
226
+ }
227
+
228
+ logStep('Coverage')
229
+ const summary =
230
+ `${report.armedScenarios}/${report.totalScenarios} scenarios declared ` +
231
+ `(${coveragePercent(report)}%), ${report.armedArms} armed arms`
232
+ if (bare.length > 0) logWarn(summary)
233
+ else logInfo(summary)
234
+ }
235
+
236
+ /**
237
+ * Names the skills nothing can fail, which is the work queue the arm batches
238
+ * consume. Exempt entries print their reason, since an exemption with no reason
239
+ * beside it is indistinguishable from a skill nobody has got to yet.
240
+ */
241
+ function reportCensus(report: CensusReport): void {
242
+ const shouldBe = report.skills.filter(
243
+ (s) => s.verdict === 'should-be-asserted',
244
+ )
245
+ const exempt = report.skills.filter((s) => s.verdict === 'exempt')
246
+
247
+ if (shouldBe.length > 0) {
248
+ logStep('No arm can fail these')
249
+ for (const entry of shouldBe)
250
+ logWarn(
251
+ `${entry.skill} (${entry.scenarios.length === 0 ? 'no scenario' : entry.scenarios.join(', ')})`,
252
+ )
253
+ }
254
+
255
+ if (exempt.length > 0) {
256
+ logStep('Exempt')
257
+ for (const entry of exempt) logInfo(`${entry.skill}: ${entry.reason}`)
258
+ }
259
+
260
+ // A wrong exemption is the one way this report can overstate itself, so both
261
+ // kinds print as errors even on a run that is otherwise clean. They separate
262
+ // because the reader checks a different thing: whether the skill left the
263
+ // tree, or whether an arm landed and the entry outlived its reason.
264
+ if (report.staleExemptions.length > 0) {
265
+ logStep('Exemptions naming no shipped skill')
266
+ for (const skill of report.staleExemptions) logError(skill)
267
+ }
268
+
269
+ if (report.supersededExemptions.length > 0) {
270
+ logStep('Exemptions an arm now asserts')
271
+ for (const skill of report.supersededExemptions)
272
+ logError(`${skill}, delete the entry`)
273
+ }
274
+
275
+ logStep('Skills')
276
+ const summary =
277
+ `${report.asserted}/${report.totalSkills} skills asserted ` +
278
+ `(${assertedPercent(report)}%), ${report.shouldBeAsserted} should be, ` +
279
+ `${report.exempt} exempt`
280
+ if (report.shouldBeAsserted > 0) logWarn(summary)
281
+ else logInfo(summary)
282
+ }
283
+
284
+ function runCoverage(options: CoverageOptions): void {
285
+ if (reportAbsentScenarioTree()) return
286
+
287
+ intro('canon sandbox coverage')
288
+
289
+ const report = collectCoverage(PROJECT_ROOT)
290
+
291
+ // A broken `exempt.toml` reads as a caller error, not as a crash. The parser
292
+ // throws rather than returning a smaller set, since a dropped exemption is
293
+ // invisible in the counts, and the conversion belongs here for the reason
294
+ // `resolveVerdict` catches its own parse: a typo in a declaration should read
295
+ // the way a pattern that does not compile does rather than as a stack trace.
296
+ let census: CensusReport | undefined
297
+ if (options.skills === true) {
298
+ try {
299
+ census = collectCensus(PROJECT_ROOT, report)
300
+ } catch (error) {
301
+ const reason = error instanceof Error ? error.message : String(error)
302
+ reportCoverage(report)
303
+ logStep('Skills')
304
+ logError(`Cannot read the exemptions: ${reason}`)
305
+ outro()
306
+ process.exitCode = 1
307
+
308
+ return
309
+ }
310
+ }
311
+
312
+ // The scenario view stays whichever way `--skills` is set. The two count
313
+ // different denominators, and replacing one with the other loses the scenario
314
+ // rollout the strict gate is written against.
315
+ reportCoverage(report)
316
+ if (census !== undefined) reportCensus(census)
317
+
318
+ if (options.json === true)
319
+ process.stdout.write(
320
+ `${JSON.stringify(census === undefined ? report : { ...report, ...census })}\n`,
321
+ )
322
+
323
+ outro()
324
+
325
+ // A wrong exemption exits non-zero without `--strict`. It is a wrong claim in
326
+ // committed data rather than a rollout still in progress, and the whole point
327
+ // of the verdict is that an exemption someone can no longer check is worse
328
+ // than no exemption at all.
329
+ const wrongExemption =
330
+ (census?.staleExemptions.length ?? 0) > 0 ||
331
+ (census?.supersededExemptions.length ?? 0) > 0
332
+ const rolloutIncomplete =
333
+ options.strict === true && report.armedScenarios < report.totalScenarios
334
+ process.exitCode = wrongExemption || rolloutIncomplete ? 1 : 0
335
+ }
336
+
337
+ function runCheck(
338
+ target: string,
339
+ arm: string | undefined,
340
+ options: CheckOptions,
341
+ ): void {
342
+ intro('canon sandbox check')
343
+
344
+ const parsed = parseTarget(target)
345
+ if (parsed === undefined) {
346
+ logError('Invalid target. Use <category>:<command>, e.g. claude:docs.')
347
+ outro()
348
+ process.exitCode = 1
349
+ return
350
+ }
351
+
352
+ // A sandbox that was never provisioned fails every path assertion, reading as a
353
+ // skill that did nothing rather than a caller that ran check too early. The
354
+ // whole point of a verdict is that it means what it says.
355
+ const sandboxDir = sandboxTree()
356
+ if (!existsSync(sandboxDir)) {
357
+ logError(`No sandbox at ${sandboxDir}. Provision one with canon sandbox.`)
358
+ outro()
359
+ process.exitCode = 1
360
+ return
361
+ }
362
+
363
+ const verdict = resolveVerdict(
364
+ expectFilePath(PROJECT_ROOT, parsed.category, parsed.command, arm ?? ''),
365
+ {
366
+ sandboxDir,
367
+ writes: readPathList(options.writes),
368
+ escapes: readPathList(options.escapes),
369
+ escapesWatched:
370
+ options.escapes === undefined
371
+ ? undefined
372
+ : options.escapesWatched === true,
373
+ envelope: readEnvelope(options.envelope),
374
+ },
375
+ )
376
+
377
+ // The report always renders on stderr. `--json` adds the machine copy on
378
+ // stdout rather than replacing the frame, per the stream contract in
379
+ // `docs/agents/output-shape.md`, so one invocation serves a human and a caller at once.
380
+ reportVerdict(verdict)
381
+ if (options.json === true)
382
+ process.stdout.write(`${JSON.stringify(verdict)}\n`)
383
+
384
+ outro()
385
+ process.exitCode = verdictExitCode(verdict.state, options.strict === true)
386
+ }
387
+
388
+ export function register(program: Command): void {
389
+ const sandbox = program
390
+ .command('sandbox')
391
+ .description('Provision and run sandbox scenarios')
392
+ .allowUnknownOption()
393
+ .allowExcessArguments(true)
394
+ .passThroughOptions()
395
+ .action(async (_opts: unknown, cmd: Command) => {
396
+ if (reportAbsentScenarioTree()) return
397
+
398
+ const args = cmd.args
399
+
400
+ if (args.length === 0) {
401
+ const resolved = await interactivePicker()
402
+ await execScript('manage-sandbox.sh', ['--no-header', resolved])
403
+ } else {
404
+ await execScript('manage-sandbox.sh', args)
405
+ }
406
+ })
407
+
408
+ sandbox
409
+ .command('check')
410
+ .description('Check a provisioned sandbox against a scenario expectation')
411
+ .argument('<target>', 'Scenario as <category>:<command>, e.g. claude:docs')
412
+ .argument('[arm]', 'Named scenario arm, e.g. drift')
413
+ .helpOption('-h, --help', 'Show this help message')
414
+ .option('--envelope <file>', 'Run envelope JSON from claude -p')
415
+ .option('--writes <file>', 'Newline-delimited paths the session wrote')
416
+ .option(
417
+ '--escapes <file>',
418
+ 'Newline-delimited paths written to a watched toolkit root, for escape scope',
419
+ )
420
+ .option(
421
+ '--escapes-watched',
422
+ 'At least one watched root held a target this run, so a zero-escape file is a clean watch rather than one with nothing to watch',
423
+ )
424
+ .option('--json', 'Emit the verdict as JSON on stdout')
425
+ .option('--strict', 'Exit non-zero when the arm declares no expectation')
426
+ .addHelpText(
427
+ 'after',
428
+ [
429
+ '',
430
+ 'Examples:',
431
+ ' canon sandbox check claude:docs drift',
432
+ ' canon sandbox check claude:docs drift --envelope run.json --json',
433
+ '',
434
+ 'Exit codes: 0 on pass or unchecked, 1 on failure.',
435
+ 'With --strict, unchecked exits 1 as well.',
436
+ ].join('\n'),
437
+ )
438
+ .action(
439
+ (target: string, arm: string | undefined, options: CheckOptions) => {
440
+ runCheck(target, arm, options)
441
+ },
442
+ )
443
+
444
+ sandbox
445
+ .command('coverage')
446
+ .description('Report which scenarios declare expectations')
447
+ .helpOption('-h, --help', 'Show this help message')
448
+ .option('--json', 'Emit the report as JSON on stdout')
449
+ .option('--strict', 'Exit non-zero while any scenario declares nothing')
450
+ .option('--skills', 'Add a per-skill asserted, should-be, or exempt census')
451
+ .addHelpText(
452
+ 'after',
453
+ [
454
+ '',
455
+ 'Examples:',
456
+ ' canon sandbox coverage',
457
+ ' canon sandbox coverage --json',
458
+ ' canon sandbox coverage --skills',
459
+ '',
460
+ 'Exit codes: 0, unless --strict and a scenario declares nothing, or',
461
+ '--skills and an exemption names no shipped skill or one an arm asserts.',
462
+ 'Where the scenario tree does not ship, exits 1 without a report.',
463
+ ].join('\n'),
464
+ )
465
+ .action((options: CoverageOptions) => {
466
+ runCoverage(options)
467
+ })
468
+ }
@@ -0,0 +1,132 @@
1
+ import { resolve } from 'node:path'
2
+ import type { Command } from 'commander'
3
+ import { type ScanRefusal, scanShippedTree } from '@/secrets/scan'
4
+ import { intro, logError, logInfo, logStep, logWarn, outro, plural } from '@/ui'
5
+
6
+ interface ScanCommandOptions {
7
+ readonly json?: boolean
8
+ }
9
+
10
+ /** What a reader does about each way the corpus fails to build. */
11
+ const REFUSALS: Record<ScanRefusal, string> = {
12
+ 'no-manifest':
13
+ 'No package.json here, so nothing is published from this tree.',
14
+ 'no-publish':
15
+ 'The manifest declares private, so this project publishes nothing.',
16
+ // Stated as an unread corpus rather than an absent one. A publish with no
17
+ // files field packs the whole tree, so this is the package that ships the
18
+ // most, and calling it nothing to read is the denial the reasoning in
19
+ // src/secrets/shipped.ts warns against.
20
+ 'no-files-field':
21
+ 'package.json declares no files field, so a publish would pack the whole tree. This check reads a declared corpus and left that one unread.',
22
+ 'no-git': 'git could not list this tree, so the corpus is unknown.',
23
+ 'no-shipped-files':
24
+ 'The files field matched nothing git lists, so nothing would be scanned.',
25
+ }
26
+
27
+ export function register(program: Command): void {
28
+ const secrets = program
29
+ .command('secrets')
30
+ .description('Read committed state for credentials that ship to a target')
31
+ .helpOption('-h, --help', 'Show this help message')
32
+
33
+ secrets
34
+ .command('scan')
35
+ .description('Report credential-shaped values in the tree this repo ships')
36
+ .argument(
37
+ '[path]',
38
+ 'Repository to scan, defaulting to the current directory',
39
+ )
40
+ .helpOption('-h, --help', 'Show this help message')
41
+ .option('--json', 'Add a machine-readable record on stdout')
42
+ .addHelpText(
43
+ 'after',
44
+ [
45
+ '',
46
+ 'Scope:',
47
+ " The package's own files field, so the corpus is what npm packs",
48
+ ' and what the plugin ships. Nothing outside it is read.',
49
+ '',
50
+ 'Exit codes:',
51
+ ' 0 the shipped tree carries no credential-shaped value',
52
+ ' 1 refused, with the reason on stderr',
53
+ ' 2 at least one value was found',
54
+ '',
55
+ 'Examples:',
56
+ ' canon secrets scan',
57
+ ' canon secrets scan --json',
58
+ '',
59
+ ].join('\n'),
60
+ )
61
+ .action(async (path: string | undefined, opts: ScanCommandOptions) => {
62
+ process.exitCode = await runScan(path, opts)
63
+ })
64
+ }
65
+
66
+ async function runScan(
67
+ path: string | undefined,
68
+ opts: ScanCommandOptions,
69
+ ): Promise<number> {
70
+ const root = resolve(path ?? process.cwd())
71
+ const emitJson = opts.json ?? false
72
+
73
+ intro('canon secrets scan')
74
+
75
+ const scan = await scanShippedTree(root)
76
+
77
+ if (scan.kind === 'refused') {
78
+ logStep('Refused')
79
+ logWarn(REFUSALS[scan.reason])
80
+ outro()
81
+
82
+ if (emitJson) {
83
+ process.stdout.write(
84
+ `${JSON.stringify({
85
+ root,
86
+ reason: scan.reason,
87
+ message: REFUSALS[scan.reason],
88
+ })}\n`,
89
+ )
90
+ }
91
+ return 1
92
+ }
93
+
94
+ logStep('Corpus')
95
+ logInfo(
96
+ `${plural(scan.files, 'file')} read, ${scan.skipped} skipped as binary or unreadable`,
97
+ )
98
+ // Stated on every run, including a clean one. The corpus answers what the
99
+ // package publishes, and a reader who sees only the passing count reads the
100
+ // verdict as covering the repository.
101
+ logInfo(
102
+ `${scan.listed - scan.files - scan.skipped} of ${scan.listed} listed files sit outside the published corpus and were not read`,
103
+ )
104
+
105
+ logStep('Findings')
106
+ if (scan.findings.length === 0) {
107
+ logInfo('No credential-shaped value in the shipped tree.')
108
+ } else {
109
+ logError(plural(scan.findings.length, 'value'))
110
+ for (const finding of scan.findings) {
111
+ logWarn(
112
+ `${finding.file}:${finding.line}:${finding.column} ${finding.label} ${finding.preview}`,
113
+ )
114
+ }
115
+ }
116
+
117
+ outro()
118
+
119
+ if (emitJson) {
120
+ process.stdout.write(
121
+ `${JSON.stringify({
122
+ root,
123
+ files: scan.files,
124
+ skipped: scan.skipped,
125
+ listed: scan.listed,
126
+ findings: scan.findings,
127
+ })}\n`,
128
+ )
129
+ }
130
+
131
+ return scan.findings.length === 0 ? 0 : 2
132
+ }