@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,857 @@
1
+ import { resolve } from 'node:path'
2
+ import type { Command } from 'commander'
3
+ import {
4
+ type ContextAuditRefusal,
5
+ type EntryReport,
6
+ governsContent,
7
+ type LengthCause,
8
+ type LengthFinding,
9
+ lengthFindings,
10
+ type LengthQuestion,
11
+ LENGTH_CHECKPOINT,
12
+ matchesSiblings,
13
+ measureFolders,
14
+ missingSections,
15
+ PROVENANCE_FOLDER,
16
+ REQUIRED_SECTIONS,
17
+ type SectionFinding,
18
+ } from '@/context/audit'
19
+ import {
20
+ type ArchitectureReport,
21
+ coveredCount,
22
+ isOverLength,
23
+ measureArchitecture,
24
+ RECORD_REL,
25
+ testableCount,
26
+ } from '@/context/architecture'
27
+ import { auditCitations, type CitationReport } from '@/context/citations'
28
+ import {
29
+ type AuditedFolder,
30
+ DEFAULT_FOLDERS,
31
+ presentNames,
32
+ resolveFolders,
33
+ } from '@/context/folders'
34
+ import { isGating } from '@/context/gate'
35
+ import { auditIndexes, type FolderDrift } from '@/context/index-drift'
36
+ import {
37
+ loadNarration,
38
+ type Narration,
39
+ PRONOUN_HEADING,
40
+ VERB_HEADING,
41
+ } from '@/context/narration'
42
+ import { RENDER_WIDTH } from '@/markdown/structure'
43
+ import {
44
+ frameError,
45
+ intro,
46
+ logError,
47
+ logInfo,
48
+ logStep,
49
+ logWarn,
50
+ outro,
51
+ pipeOutput,
52
+ plural,
53
+ } from '@/ui'
54
+
55
+ /** Returned when a gating finding is present. */
56
+ const EXIT_GATE = 2
57
+
58
+ /** A name of dots alone is `.` or `..`, both of which escape the audit root. */
59
+ const FOLDER_NAME = /^(?!\.+$)[A-Za-z0-9._-]+$/
60
+
61
+ interface AuditCommandOptions {
62
+ readonly json?: boolean
63
+ readonly folder?: string
64
+ readonly citationsOnly?: boolean
65
+ readonly gate?: boolean
66
+ }
67
+
68
+ export function register(program: Command): void {
69
+ const context = program
70
+ .command('context')
71
+ .description('Report the structural state of the context folders')
72
+ .helpOption('-h, --help', 'Show this help message')
73
+
74
+ context
75
+ .command('audit')
76
+ .description(
77
+ 'Report required sections, entry length, citations, reference form, catalog tables, provenance, superseded-decision narration, index drift, and the architecture record against its own ceiling',
78
+ )
79
+ .argument('[path]', 'Project root, defaulting to the current directory')
80
+ .helpOption('-h, --help', 'Show this help message')
81
+ .option('--json', 'Add a machine-readable record on stdout')
82
+ .option(
83
+ '--folder <list>',
84
+ 'Comma-separated folder names, resolved under .claude/ then the project root',
85
+ )
86
+ .option('--citations-only', 'Run the gating citation check alone')
87
+ .option(
88
+ '--gate',
89
+ 'Also fail on a missing required section or index drift, the findings that are facts',
90
+ )
91
+ .addHelpText(
92
+ 'after',
93
+ [
94
+ '',
95
+ 'Exit codes:',
96
+ ' 0 the audit completed with no gating finding',
97
+ ' 1 refused, with the reason on stderr',
98
+ ' 2 a gating finding is present',
99
+ '',
100
+ 'An unresolved citation always gates. An architecture record that',
101
+ 'states its own line allowances gates when it is past the ceiling',
102
+ 'those derive, on any run except --citations-only, which never',
103
+ 'measures it. A record stating no allowance is reported and never',
104
+ 'gated. --gate widens the gate to the other two findings that are',
105
+ 'facts rather than judgments: a missing required section and index',
106
+ 'drift. Entry length, reference form, table, provenance, narration,',
107
+ 'and the record claim classification are judgments under both.',
108
+ '',
109
+ 'Depth and bullet weight are stated over every markdown file rather',
110
+ 'than over a context entry, so `canon markdown audit` measures them.',
111
+ '',
112
+ 'Examples:',
113
+ ' canon context audit',
114
+ ' canon context audit --json',
115
+ ' canon context audit --citations-only',
116
+ ' canon context audit --folder context,diagrams',
117
+ ' canon context audit --folder docs',
118
+ ' canon context audit tooling/base/seeds --gate',
119
+ '',
120
+ ].join('\n'),
121
+ )
122
+ .action(async (path: string | undefined, opts: AuditCommandOptions) => {
123
+ process.exitCode = await runAudit(path, opts)
124
+ })
125
+ }
126
+
127
+ function parseFolders(list: string | undefined): string[] | string {
128
+ if (!list) return [...DEFAULT_FOLDERS]
129
+
130
+ const names = list
131
+ .split(',')
132
+ .map((name) => name.trim())
133
+ .filter(Boolean)
134
+
135
+ if (names.length === 0) return 'Empty --folder list. Pass at least one name.'
136
+
137
+ // `..` would resolve the audited folder above the project root, taking the
138
+ // scan and the citation pattern outside the tree the audit describes.
139
+ const invalid = names.filter((name) => !FOLDER_NAME.test(name))
140
+ if (invalid.length > 0) {
141
+ return `--folder takes folder names, not paths: ${invalid.join(', ')}`
142
+ }
143
+
144
+ return names
145
+ }
146
+
147
+ async function runAudit(
148
+ path: string | undefined,
149
+ opts: AuditCommandOptions,
150
+ ): Promise<number> {
151
+ const root = resolve(path ?? process.cwd())
152
+ const names = parseFolders(opts.folder)
153
+ const gateOnly = opts.citationsOnly ?? false
154
+ const widened = opts.gate ?? false
155
+
156
+ // `--citations-only` runs the citation check alone, so the two findings
157
+ // `--gate` adds are never measured. Honouring both would exit 0 on a seed
158
+ // short a required section, which is the pass a gate exists to prevent.
159
+ if (gateOnly && widened) {
160
+ return refuse(
161
+ 'conflicting-options',
162
+ '--citations-only runs the citation check alone, so --gate would widen the gate to findings the run never measures. Pass one.',
163
+ gateOnly,
164
+ root,
165
+ opts.json ?? false,
166
+ )
167
+ }
168
+
169
+ if (typeof names === 'string') {
170
+ return refuse('bad-folder-list', names, gateOnly, root, opts.json ?? false)
171
+ }
172
+
173
+ // The root base is opt-in. A target carrying a root `wireframes/` would
174
+ // otherwise be audited against a standard it never adopted, on a bare run
175
+ // that named nothing.
176
+ const named = opts.folder !== undefined
177
+ const { folders, missing } = await resolveFolders(root, names, {
178
+ canResolveAtRoot: named,
179
+ })
180
+ if (folders.length === 0) {
181
+ return refuse(
182
+ 'no-folders',
183
+ `No audited folder found ${named ? 'under .claude/ or the project root' : 'under .claude/'}, since resolving one needs its own index.md file. Looked for: ${names.join(', ')}.`,
184
+ gateOnly,
185
+ root,
186
+ opts.json ?? false,
187
+ )
188
+ }
189
+
190
+ // A default folder a project does not carry is the ordinary case and stays
191
+ // silent. A name passed by hand that resolves nowhere is a typo, and the run
192
+ // measuring the names that did resolve reads as a pass against a folder it
193
+ // never opened.
194
+ const unresolved = named ? missing : []
195
+
196
+ // The gate runs one check. Letting it exit 0 against a scope it could not
197
+ // build reports a pass on nothing measured, which is the outcome a gate is
198
+ // there to prevent.
199
+ const cited = presentNames(folders)
200
+ if (gateOnly && cited.length === 0) {
201
+ return refuse(
202
+ 'no-citation-scope',
203
+ `The citation check spells the .claude/ prefix and no audited folder resolved there. Looked for: ${names.join(', ')}.`,
204
+ gateOnly,
205
+ root,
206
+ opts.json ?? false,
207
+ )
208
+ }
209
+
210
+ const citations = await auditCitations(root, cited)
211
+ if (citations.kind === 'unavailable') {
212
+ return refuse(
213
+ 'no-git',
214
+ 'git could not list the tree, so no citation was checked. Run inside a git repository.',
215
+ gateOnly,
216
+ root,
217
+ opts.json ?? false,
218
+ )
219
+ }
220
+
221
+ const narration: Narration = gateOnly
222
+ ? { kind: 'absent' }
223
+ : await loadNarration(root)
224
+ const entries = gateOnly
225
+ ? []
226
+ : await measureFolders(
227
+ root,
228
+ folders,
229
+ narration.kind === 'loaded' ? narration : undefined,
230
+ )
231
+ const drift = gateOnly ? [] : await auditIndexes(folders)
232
+ const sections = gateOnly ? [] : missingSections(root, folders, entries)
233
+ const length = gateOnly ? undefined : lengthFindings(entries)
234
+ // Absent under `--citations-only` and null when the project carries no
235
+ // record, for the reason `checkpoints.narration` states about its own two
236
+ // absences. A run that never looked and a project with nothing to look at
237
+ // are different answers, and one value for both reports the second as the
238
+ // first.
239
+ const record = gateOnly ? undefined : await measureArchitecture(root)
240
+
241
+ if (gateOnly) {
242
+ reportGate(citations)
243
+ } else {
244
+ intro('canon context audit')
245
+ reportScope(folders, unresolved)
246
+ reportCitations(citations, cited)
247
+ reportReferenceForm(entries, folders)
248
+ reportSections(sections, folders)
249
+ reportLength(length ?? [])
250
+ reportTables(entries)
251
+ reportProvenance(entries, folders)
252
+ reportNarration(entries, folders, narration)
253
+ reportDrift(drift)
254
+ reportRecord(record)
255
+ outro()
256
+ }
257
+
258
+ if (opts.json) {
259
+ process.stdout.write(
260
+ `${JSON.stringify({
261
+ root,
262
+ folders: folders.map((folder) => ({
263
+ path: folder.rel,
264
+ base: folder.base,
265
+ entries: folder.entries.length,
266
+ governsContent: governsContent(folder),
267
+ })),
268
+ unresolvedFolders: unresolved,
269
+ citations: {
270
+ scanned: citations.scanned,
271
+ total: citations.total,
272
+ unresolved: citations.unresolved,
273
+ },
274
+ entries,
275
+ // The join is published rather than left to a consumer, since deriving
276
+ // it from `entries` means restating which question the provenance count
277
+ // answers, and one wrong restatement is a cause reported against the
278
+ // wrong entry.
279
+ //
280
+ // Absent rather than empty under `--citations-only`, for the reason
281
+ // `checkpoints.narration` below carries. That mode measures no entry,
282
+ // so an empty array here reads as a corpus with nothing past the
283
+ // checkpoint rather than as a run that never looked.
284
+ length,
285
+ missingSections: sections,
286
+ indexDrift: drift,
287
+ // Null says the run opened the project and found no record, which a
288
+ // target that never wrote one is entitled to. Absent says the run
289
+ // never looked, which is `--citations-only`.
290
+ architecture: gateOnly ? undefined : (record ?? null),
291
+ checkpoints: {
292
+ lines: LENGTH_CHECKPOINT,
293
+ renderWidth: RENDER_WIDTH,
294
+ provenanceFolder: PROVENANCE_FOLDER,
295
+ requiredSections: REQUIRED_SECTIONS,
296
+ // Three states rather than two, so a record showing no finding says
297
+ // which terms were looked for. The key is absent when the run never
298
+ // scanned, which is `--citations-only`, and null when it scanned and
299
+ // no rule published both headings. Collapsing the first into null
300
+ // reports an absent rule against a run that never opened one.
301
+ narration: gateOnly
302
+ ? undefined
303
+ : narration.kind === 'loaded'
304
+ ? {
305
+ source: narration.source,
306
+ pronouns: narration.pronouns,
307
+ verbs: narration.verbs,
308
+ }
309
+ : null,
310
+ },
311
+ })}\n`,
312
+ )
313
+ }
314
+
315
+ const gating = isGating({
316
+ unresolvedCitations: citations.unresolved.length,
317
+ recordOverLength: record !== undefined && isOverLength(record),
318
+ sections,
319
+ drift,
320
+ widened,
321
+ })
322
+
323
+ return gating ? EXIT_GATE : 0
324
+ }
325
+
326
+ function refuse(
327
+ reason: ContextAuditRefusal,
328
+ message: string,
329
+ gateOnly: boolean,
330
+ root: string,
331
+ emitJson: boolean,
332
+ ): number {
333
+ if (gateOnly) {
334
+ frameError(message)
335
+ } else {
336
+ intro('canon context audit')
337
+ logStep('Refused')
338
+ logWarn(message)
339
+ outro()
340
+ }
341
+
342
+ if (emitJson) {
343
+ process.stdout.write(`${JSON.stringify({ root, reason, message })}\n`)
344
+ }
345
+
346
+ return 1
347
+ }
348
+
349
+ /**
350
+ * Prints nothing when every path resolves.
351
+ *
352
+ * `--citations-only` is what the merge gate runs on every push, and it pipes
353
+ * a stage's whole output into its own frame. A passing gate that printed
354
+ * its frame would nest one inside the other on every contributor's push.
355
+ */
356
+ function reportGate(report: ScannedCitations): void {
357
+ const count = report.unresolved.length
358
+ if (count === 0) return
359
+
360
+ intro('canon context audit')
361
+ logError(
362
+ count === 1
363
+ ? '1 cited path does not resolve'
364
+ : `${count} cited paths do not resolve`,
365
+ )
366
+ pipeOutput(
367
+ report.unresolved
368
+ .map((citation) => `${citation.file}:${citation.line} ${citation.path}`)
369
+ .join('\n'),
370
+ )
371
+ outro()
372
+ }
373
+
374
+ /**
375
+ * Names the resolved path of every audited folder, plus the requested names
376
+ * that resolved nowhere.
377
+ *
378
+ * The path is what says which base a name was taken from, which matters once a
379
+ * name can resolve under `.claude/` or at the project root and a project may
380
+ * carry both.
381
+ */
382
+ function reportScope(
383
+ folders: readonly AuditedFolder[],
384
+ unresolved: readonly string[],
385
+ ): void {
386
+ logStep('Scope')
387
+
388
+ for (const folder of folders) {
389
+ logInfo(`${folder.rel}: ${folder.entries.length} entries`)
390
+ }
391
+
392
+ if (unresolved.length === 0) return
393
+
394
+ logWarn(
395
+ `Not audited, since resolving needs its own index.md: ${unresolved.join(', ')}`,
396
+ )
397
+ }
398
+
399
+ type ScannedCitations = Extract<CitationReport, { kind: 'scanned' }>
400
+
401
+ /**
402
+ * States the reach before the count, for the reason the provenance report
403
+ * states its own.
404
+ *
405
+ * A run auditing a folder at the project root builds no pattern, and a count of
406
+ * zero followed by a line saying every path resolves is indistinguishable from
407
+ * a corpus that cites nothing.
408
+ */
409
+ function reportCitations(
410
+ report: ScannedCitations,
411
+ cited: readonly string[],
412
+ ): void {
413
+ logStep('Citations')
414
+
415
+ if (cited.length === 0) {
416
+ logInfo(
417
+ 'Out of scope. The pattern spells the .claude/ prefix, and no audited folder resolved there.',
418
+ )
419
+ return
420
+ }
421
+
422
+ logInfo(
423
+ `${plural(report.total, 'cited path')} across ${plural(report.scanned, 'file')}, fixtures and fenced examples excluded`,
424
+ )
425
+
426
+ if (report.unresolved.length === 0) {
427
+ logInfo('Every cited path resolves.')
428
+ return
429
+ }
430
+
431
+ logWarn(`${report.unresolved.length} unresolved`)
432
+ pipeOutput(
433
+ report.unresolved
434
+ .map((citation) => `${citation.file}:${citation.line} ${citation.path}`)
435
+ .join('\n'),
436
+ )
437
+ }
438
+
439
+ /**
440
+ * Reports the references naming a sibling entry by bare filename.
441
+ *
442
+ * This prints beside the citation check rather than among the readability
443
+ * measures, since the two read the same thing: one resolves a path a reference
444
+ * spells and this one finds the references that spell none. The reach line
445
+ * names the split folders rather than the governed folder alone, because a run
446
+ * whose only context folder is flat measures nothing here and would otherwise
447
+ * print the same clean line as a run that measured every split.
448
+ */
449
+ function reportReferenceForm(
450
+ entries: readonly EntryReport[],
451
+ folders: readonly AuditedFolder[],
452
+ ): void {
453
+ logStep('Reference form')
454
+
455
+ const scoped = folders.filter(matchesSiblings)
456
+ if (scoped.length === 0) {
457
+ logInfo(
458
+ `Out of scope. A bare name is matched against the siblings of a domain split into a folder, and no audited folder under .claude/${PROVENANCE_FOLDER}/ is one.`,
459
+ )
460
+ return
461
+ }
462
+
463
+ logInfo(
464
+ `Covers ${plural(scoped.length, 'split folder')} under .claude/${PROVENANCE_FOLDER}/, whose standard asks a reference to spell its path.`,
465
+ )
466
+ logInfo(
467
+ 'The flat folder is out of reach, since a domain filename there is shared by seeds and other trees.',
468
+ )
469
+
470
+ const carrying = entries
471
+ .filter((entry) => entry.bareReferences.length > 0)
472
+ .sort((a, b) => b.bareReferences.length - a.bareReferences.length)
473
+
474
+ if (carrying.length === 0) {
475
+ logInfo('Every reference to a sibling entry spells its path.')
476
+ return
477
+ }
478
+
479
+ const total = carrying.reduce(
480
+ (sum, entry) => sum + entry.bareReferences.length,
481
+ 0,
482
+ )
483
+ logWarn(
484
+ `${plural(total, 'bare name')} across ${carrying.length} ${carrying.length === 1 ? 'entry' : 'entries'}`,
485
+ )
486
+ pipeOutput(
487
+ carrying
488
+ .map(
489
+ (entry) =>
490
+ `${entry.rel} ${plural(entry.bareReferences.length, 'bare name')}\n${entry.bareReferences
491
+ .map((found) => ` :${found.line} ${found.name}`)
492
+ .join('\n')}`,
493
+ )
494
+ .join('\n'),
495
+ )
496
+ }
497
+
498
+ /**
499
+ * Names the path each finding belongs to, which is an entry in the folder named
500
+ * under `.claude/` and the folder itself in a domain split across one. States
501
+ * the reach on every run for the reason the provenance report does.
502
+ *
503
+ * This prints ahead of the four readability measures because a missing section
504
+ * asks whether the entry is the right shape at all, which precedes asking
505
+ * whether it has grown too long.
506
+ */
507
+ function reportSections(
508
+ missing: readonly SectionFinding[],
509
+ folders: readonly AuditedFolder[],
510
+ ): void {
511
+ logStep('Sections')
512
+
513
+ const governed = folders.filter(governsContent)
514
+ if (governed.length === 0) {
515
+ logInfo(
516
+ `Out of scope. The list is stated in the standard governing .claude/${PROVENANCE_FOLDER}/, and no audited folder is that one.`,
517
+ )
518
+ return
519
+ }
520
+
521
+ logInfo(
522
+ `Covers .claude/${PROVENANCE_FOLDER}/ alone, whose standard requires ${REQUIRED_SECTIONS.join(' and ')}.`,
523
+ )
524
+ logInfo(
525
+ 'A heading at any level counts. Each entry answers for itself, except in a domain split across a folder, where a sibling answers for the rest.',
526
+ )
527
+
528
+ if (missing.length === 0) {
529
+ logInfo('Every entry declares each required section.')
530
+ return
531
+ }
532
+
533
+ logWarn(`${plural(missing.length, 'path')} short a required section`)
534
+ pipeOutput(
535
+ missing
536
+ .map((found) => `${found.rel} missing: ${found.missing.join(', ')}`)
537
+ .join('\n'),
538
+ )
539
+ }
540
+
541
+ /** How each question reads in the report, in the standard's own order. */
542
+ const QUESTION_LABEL: Record<LengthQuestion, string> = {
543
+ domain: 'one domain',
544
+ reproduced: 'reproduced content',
545
+ history: 'own history',
546
+ }
547
+
548
+ function readCause(cause: LengthCause): string {
549
+ const label = QUESTION_LABEL[cause.question]
550
+
551
+ if (cause.state === 'unanswered') return `${label}: open`
552
+ if (cause.state === 'no') return `${label}: no`
553
+
554
+ return `${label}: yes, ${plural(cause.markers ?? 0, 'change marker')}`
555
+ }
556
+
557
+ /**
558
+ * Names each entry past the checkpoint with the three questions the standard
559
+ * asks of it, rather than a count a reader has to cross-reference by eye.
560
+ *
561
+ * The checkpoint is not a cap, so the legend says what the list is for. An
562
+ * entry that answers all three and is still long is a correct outcome under a
563
+ * standard stating there is no hard cap, and a run reporting success as a
564
+ * smaller count would have adopted the number the standard declines.
565
+ */
566
+ function reportLength(over: readonly LengthFinding[]): void {
567
+ logStep('Length')
568
+ logInfo(
569
+ `Entries measure rendered lines at ${RENDER_WIDTH} columns, counting frontmatter and fenced blocks.`,
570
+ )
571
+ logInfo(
572
+ 'A reference-heavy entry therefore ranks by its examples, which the depth check in `canon markdown audit` excludes.',
573
+ )
574
+
575
+ if (over.length === 0) {
576
+ logInfo(`No entry past the ${LENGTH_CHECKPOINT}-line checkpoint.`)
577
+ return
578
+ }
579
+
580
+ logWarn(`${over.length} past the ${LENGTH_CHECKPOINT}-line checkpoint`)
581
+ logInfo(
582
+ 'The checkpoint is not a cap. Each entry carries the three questions the standard asks past it, and the fix goes to whichever is true.',
583
+ )
584
+ logInfo(
585
+ 'Own history is answered from the change markers below. A date stamping a measurement is not one of them, so an entry recording what its runs cost answers no. The other two are judgments no measure settles, so they stay open for a reader.',
586
+ )
587
+ pipeOutput(
588
+ over
589
+ .map(
590
+ (finding) =>
591
+ `${finding.rel} ${finding.lines} rendered lines\n ${finding.causes
592
+ .map(readCause)
593
+ .join(' ')}`,
594
+ )
595
+ .join('\n'),
596
+ )
597
+ }
598
+
599
+ function reportTables(entries: readonly EntryReport[]): void {
600
+ logStep('Tables')
601
+
602
+ const candidates = entries.flatMap((entry) =>
603
+ entry.catalogTables.map(
604
+ (table) => `${entry.rel}:${table.line} ${table.rows} rows`,
605
+ ),
606
+ )
607
+
608
+ if (candidates.length === 0) {
609
+ logInfo('No table reads as a catalog that grows a row per shipped thing.')
610
+ return
611
+ }
612
+
613
+ logWarn(`${plural(candidates.length, 'candidate')} for a bullet list`)
614
+ pipeOutput(candidates.join('\n'))
615
+ }
616
+
617
+ /**
618
+ * Groups by entry rather than listing every marker.
619
+ *
620
+ * The two entries carrying most of a corpus's markers carry them a dozen at a
621
+ * time, and a flat list of those buries the entries holding one. What a reader
622
+ * acts on is which file to open, so the count sits beside the name and the
623
+ * lines follow it.
624
+ *
625
+ * The reach is stated on every run, including the run where nothing is in
626
+ * scope. A check that covered three folders and now covers one reads as quietly
627
+ * missing things unless the report says which folder it measured.
628
+ */
629
+ function reportProvenance(
630
+ entries: readonly EntryReport[],
631
+ folders: readonly AuditedFolder[],
632
+ ): void {
633
+ logStep('Provenance')
634
+
635
+ const governed = folders.filter(governsContent)
636
+ if (governed.length === 0) {
637
+ logInfo(
638
+ `Out of scope. The rule is stated in the standard governing .claude/${PROVENANCE_FOLDER}/, and no audited folder is that one.`,
639
+ )
640
+ return
641
+ }
642
+
643
+ logInfo(
644
+ `Covers .claude/${PROVENANCE_FOLDER}/ alone, whose standard carries the rule. The sibling standards do not restate it.`,
645
+ )
646
+ logInfo(
647
+ 'Fenced blocks are excluded, and so is a date whose clause stamps a measurement. A marker is a judgment, never a defect.',
648
+ )
649
+
650
+ const carrying = entries
651
+ .filter((entry) => entry.provenance.length > 0)
652
+ .sort((a, b) => b.provenance.length - a.provenance.length)
653
+
654
+ if (carrying.length === 0) {
655
+ logInfo('No entry narrates a change.')
656
+ return
657
+ }
658
+
659
+ const total = carrying.reduce(
660
+ (sum, entry) => sum + entry.provenance.length,
661
+ 0,
662
+ )
663
+ logWarn(
664
+ `${plural(total, 'marker')} across ${carrying.length} ${carrying.length === 1 ? 'entry' : 'entries'}`,
665
+ )
666
+ pipeOutput(
667
+ carrying
668
+ .map(
669
+ (entry) =>
670
+ `${entry.rel} ${plural(entry.provenance.length, 'marker')}\n${entry.provenance
671
+ .map((found) => ` :${found.line} ${found.kind} ${found.text}`)
672
+ .join('\n')}`,
673
+ )
674
+ .join('\n'),
675
+ )
676
+ }
677
+
678
+ /**
679
+ * Reports the bullets narrating a decision the bullet above them replaced.
680
+ *
681
+ * The reach line names the rule the sets were read from, since the check is
682
+ * silent when no rule publishes them and a run that scanned nothing otherwise
683
+ * prints the same clean line as a run that scanned everything.
684
+ *
685
+ * The legitimate-hit line is here for the same reason the provenance section
686
+ * says a marker is a judgment. The standard keeps a rejected alternative and
687
+ * why it lost, which is a back-reference in the past tense by construction, and
688
+ * no measure separates one from the shape the rule bans.
689
+ */
690
+ function reportNarration(
691
+ entries: readonly EntryReport[],
692
+ folders: readonly AuditedFolder[],
693
+ narration: Narration,
694
+ ): void {
695
+ logStep('Narration')
696
+
697
+ const governed = folders.filter(governsContent)
698
+ if (governed.length === 0) {
699
+ logInfo(
700
+ `Out of scope. The rule is stated in the standard governing .claude/${PROVENANCE_FOLDER}/, and no audited folder is that one.`,
701
+ )
702
+ return
703
+ }
704
+
705
+ if (narration.kind === 'absent') {
706
+ logWarn(
707
+ `Not scanned. No rule under .claude/rules/ or governance/rules/ publishes both ${PRONOUN_HEADING} and ${VERB_HEADING}.`,
708
+ )
709
+ return
710
+ }
711
+
712
+ logInfo(
713
+ `Covers .claude/${PROVENANCE_FOLDER}/ alone, reading ${plural(narration.pronouns.length, 'pronoun')} and ${plural(narration.verbs.length, 'verb')} from ${narration.source}.`,
714
+ )
715
+ logInfo(
716
+ 'A rejected alternative is a legitimate hit, since the standard keeps what was tried and why it lost.',
717
+ )
718
+
719
+ const carrying = entries
720
+ .filter((entry) => entry.narration.length > 0)
721
+ .sort((a, b) => b.narration.length - a.narration.length)
722
+
723
+ if (carrying.length === 0) {
724
+ logInfo('No bullet narrates a decision the bullet above it replaced.')
725
+ return
726
+ }
727
+
728
+ const total = carrying.reduce((sum, entry) => sum + entry.narration.length, 0)
729
+ logWarn(
730
+ `${plural(total, 'bullet')} to read across ${carrying.length} ${carrying.length === 1 ? 'entry' : 'entries'}`,
731
+ )
732
+ pipeOutput(
733
+ carrying
734
+ .map(
735
+ (entry) =>
736
+ `${entry.rel} ${plural(entry.narration.length, 'bullet')}\n${entry.narration
737
+ .map(
738
+ (found) =>
739
+ ` :${found.line} ${found.pronoun} with ${found.verb}`,
740
+ )
741
+ .join('\n')}`,
742
+ )
743
+ .join('\n'),
744
+ )
745
+ }
746
+
747
+ /** How each classification reads in the report. */
748
+ const CLAIM_LABEL: Record<string, string> = {
749
+ countable: 'countable claim',
750
+ invariant: 'structural invariant',
751
+ neither: 'reasoning only',
752
+ }
753
+
754
+ /**
755
+ * Reports the architecture record against the ceiling it states for itself and
756
+ * against what a machine could test in it.
757
+ *
758
+ * The length reading is a fact and gates. Everything below it names candidates
759
+ * a reader adjudicates, because deciding whether a sentence states a claim is a
760
+ * judgment no parser settles, and a stored verdict would age the way the
761
+ * anchors it sits beside already do. Nothing is stored: every run reclassifies,
762
+ * so an entry rewritten tomorrow is read as it stands then.
763
+ */
764
+ function reportRecord(report: ArchitectureReport | undefined): void {
765
+ logStep('Architecture record')
766
+
767
+ if (report === undefined) {
768
+ logInfo(
769
+ `Out of scope. The project carries no ${RECORD_REL}, so there was no record to measure.`,
770
+ )
771
+ return
772
+ }
773
+
774
+ const decisions = report.decisions.length
775
+ const { allowances } = report
776
+
777
+ if (allowances === undefined) {
778
+ logInfo(
779
+ `Covers ${report.rel} alone. No standard sets a length rule for it and this record states none, so its ${plural(report.lines, 'line')} across ${plural(decisions, 'decision')} are reported and nothing is gated.`,
780
+ )
781
+ logInfo(
782
+ 'A record declaring an allowance for its frame and one a decision is measured against the ceiling those two derive. That rule belongs to whichever record writes it, never to the toolkit.',
783
+ )
784
+ } else {
785
+ logInfo(
786
+ `Covers ${report.rel} alone, which states its own allowance of ${plural(allowances.frame, 'line')} for the frame and ${allowances.perDecision} a decision.`,
787
+ )
788
+ if (isOverLength(report)) {
789
+ logError(
790
+ `${report.lines} lines against a ceiling of ${report.ceiling} from ${plural(decisions, 'decision')}`,
791
+ )
792
+ } else {
793
+ logInfo(
794
+ `${report.lines} lines against a ceiling of ${report.ceiling} from ${plural(decisions, 'decision')}.`,
795
+ )
796
+ }
797
+ logInfo(
798
+ `The ceiling rises with the decision count, so adding a decision buys ${allowances.perDecision} lines and the check passes exactly when the file grew.`,
799
+ )
800
+ }
801
+
802
+ if (decisions === 0) {
803
+ logWarn('The record declares no decision, so nothing was classified.')
804
+ return
805
+ }
806
+
807
+ const testable = testableCount(report)
808
+ const covered = coveredCount(report)
809
+
810
+ logInfo(
811
+ 'A countable claim carries a figure a run could recompute and an invariant quantifies over a named tree a walk could falsify. Both are candidates a reader settles, and neither gates.',
812
+ )
813
+ logInfo(
814
+ 'A figure spelled in words reads as uncounted, since a cardinal in prose is pronominal more often than measured. Entries are counted by heading, so a heading carrying several decisions counts once and the total reads low by however many it holds.',
815
+ )
816
+ const line = `${testable} of ${decisions} carry a claim a machine could test, ${covered} of which name a check that exists`
817
+ // A record whose every testable claim names a check has nothing to act on,
818
+ // and so does one carrying no testable claim at all. Warning on both is how
819
+ // a section becomes one nobody reads after the second run.
820
+ if (testable > covered) logWarn(line)
821
+ else logInfo(`${line}.`)
822
+ logInfo(
823
+ 'Coverage reads the entry rather than the tree, so a claim some check happens to cover without the entry naming it reads as unchecked.',
824
+ )
825
+
826
+ pipeOutput(
827
+ report.decisions
828
+ .map((entry) => {
829
+ const kind = CLAIM_LABEL[entry.claim] ?? entry.claim
830
+ const evidence =
831
+ entry.figures.length > 0 ? ` ${entry.figures.join(' ')}` : ''
832
+ const checks =
833
+ entry.checks.length > 0
834
+ ? `\n checked by ${entry.checks.join(', ')}`
835
+ : ''
836
+ return `${report.rel}:${entry.line} ${kind}${evidence}\n ${entry.heading}${checks}`
837
+ })
838
+ .join('\n'),
839
+ )
840
+ }
841
+
842
+ function reportDrift(drift: readonly FolderDrift[]): void {
843
+ logStep('Index drift')
844
+
845
+ const lines = drift.flatMap((folder) => [
846
+ ...folder.unlisted.map((name) => `${folder.rel} unlisted: ${name}`),
847
+ ...folder.missing.map((name) => `${folder.rel} missing: ${name}`),
848
+ ])
849
+
850
+ if (lines.length === 0) {
851
+ logInfo('Every index agrees with its siblings.')
852
+ return
853
+ }
854
+
855
+ logWarn(plural(lines.length, 'disagreement'))
856
+ pipeOutput(lines.join('\n'))
857
+ }