@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,692 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { basename, join, sep } from 'node:path'
3
+ import { execa } from 'execa'
4
+ import { gitEnv } from '@/git-env'
5
+ import { createGovAdapter, rulesSourceDir } from '@/gov/adapter'
6
+ import { loadGovStack, resolveMissingRules, resolveRules } from '@/gov/stacks'
7
+ import { planSync, type ScanEntry, type SyncAdapter } from '@/sync/engine'
8
+ import {
9
+ collectSuperseded,
10
+ detectUnmigrated,
11
+ type SupersededEntry,
12
+ type UnmigratedDomain,
13
+ } from '@/sync/layout'
14
+ import {
15
+ buildReverseReport,
16
+ emptyReverseReport,
17
+ type ReverseReport,
18
+ } from '@/sync/reverse'
19
+ import { buildSeedsReport, type SeedsReport } from '@/sync/seeds-report'
20
+ import {
21
+ isLegacyStamped,
22
+ readStamp,
23
+ type Stamp,
24
+ stampedChain,
25
+ stampedCommit,
26
+ type StampDomain,
27
+ } from '@/sync/stamp'
28
+ import { isDirectory } from '@/target'
29
+ import { loadManifest } from '@/tooling/manifest'
30
+ import { scan } from '@/tooling/scan'
31
+ import { readSkew, type SkewReport } from '@/version/skew'
32
+
33
+ /**
34
+ * Domains the sync engine walks file by file. Tooling is a stamp domain without
35
+ * being one of these, because `src/tooling/` never calls `planSync`, so the
36
+ * three lookups below have no entry to offer it. Standards and snippets left
37
+ * the list with their install channels: nothing writes either corpus into a
38
+ * target, so there is no installed copy to attribute.
39
+ */
40
+ export const SCANNED_DOMAINS = [
41
+ 'governance',
42
+ ] as const satisfies readonly StampDomain[]
43
+
44
+ export type ScannedDomain = (typeof SCANNED_DOMAINS)[number]
45
+
46
+ /**
47
+ * The toolkit path whose commits change what each domain holds. `claude/skills/`
48
+ * is deliberately absent: skills load live from the plugin directory, so they
49
+ * never go stale and belong in the read-only section instead.
50
+ */
51
+ const SYNCED_SOURCES: Record<ScannedDomain, string> = {
52
+ governance: 'governance/rules/',
53
+ }
54
+
55
+ const ADAPTERS: Record<ScannedDomain, (root: string) => SyncAdapter> = {
56
+ governance: createGovAdapter,
57
+ }
58
+
59
+ const INSTALL_MARKERS: Record<ScannedDomain, readonly string[]> = {
60
+ governance: ['.claude', 'rules'],
61
+ }
62
+
63
+ export interface StateCounts {
64
+ readonly matching: number
65
+ readonly stale: number
66
+ readonly customized: number
67
+ readonly drifted: number
68
+ readonly orphaned: number
69
+ readonly stranded: number
70
+ readonly missing: number
71
+ }
72
+
73
+ export interface DomainReport {
74
+ readonly domain: ScannedDomain
75
+ readonly stamped: boolean
76
+ /** This domain's own anchor, not the target's most recent sync. */
77
+ readonly commit?: string
78
+ readonly syncedAt?: string
79
+ readonly counts: StateCounts
80
+ readonly entries: readonly ScanEntry[]
81
+ readonly upstream: readonly UpstreamCommit[]
82
+ /**
83
+ * Separates a toolkit that could not attribute from one that attributed and
84
+ * found a local edit. Only the first is a capability the install lacks.
85
+ */
86
+ readonly historyUnavailable: boolean
87
+ }
88
+
89
+ export interface UpstreamCommit {
90
+ readonly sha: string
91
+ readonly subject: string
92
+ }
93
+
94
+ /** Pending changes per category, from the same scan `canon tooling sync` reads. */
95
+ export interface ToolingCounts {
96
+ readonly configs: number
97
+ readonly seeds: number
98
+ readonly scripts: number
99
+ readonly deps: number
100
+ readonly gitignore: number
101
+ }
102
+
103
+ /**
104
+ * Tooling's own section. `measured` is the field the report exists for: without
105
+ * it a target that never installed tooling and a target whose tooling is current
106
+ * both render as zero changes, which is a claim rather than an absence of one.
107
+ */
108
+ export interface ToolingReport {
109
+ readonly measured: boolean
110
+ /**
111
+ * Stack names the install resolved, nearest first. Carried even when the
112
+ * report is unmeasured, so a chain naming stacks this toolkit no longer ships
113
+ * stays distinguishable from a target that recorded none.
114
+ */
115
+ readonly chain: readonly string[]
116
+ readonly commit?: string
117
+ readonly syncedAt?: string
118
+ readonly counts: ToolingCounts
119
+ readonly changes: number
120
+ }
121
+
122
+ const UNMEASURED_TOOLING: ToolingReport = {
123
+ measured: false,
124
+ chain: [],
125
+ counts: {
126
+ configs: 0,
127
+ seeds: 0,
128
+ scripts: 0,
129
+ deps: 0,
130
+ gitignore: 0,
131
+ },
132
+ changes: 0,
133
+ }
134
+
135
+ export interface CheckReport {
136
+ readonly covers: readonly StampDomain[]
137
+ /**
138
+ * True when the stamp `readStamp` found sits at one of the retired paths
139
+ * rather than the current one. Read only: nothing in the check migrates a
140
+ * target's config as a side effect of reporting it.
141
+ *
142
+ * canon-keep-retired
143
+ * The two are `.claude/aitk/config.json` and `.claude/aitk.json`.
144
+ */
145
+ readonly stampAtLegacyPath: boolean
146
+ /** False when the target is not a toolkit project, so every section stays empty. */
147
+ readonly managed: boolean
148
+ readonly domains: readonly DomainReport[]
149
+ /**
150
+ * Reported beside the domains rather than as one of them, because tooling
151
+ * carries no per-file entries and no upstream range, so it fills almost none
152
+ * of `DomainReport`.
153
+ */
154
+ readonly tooling: ToolingReport
155
+ /**
156
+ * Reported beside the domains rather than as one of them, because seeds carry
157
+ * no stamp and produce no change. See `@/sync/seeds-report`.
158
+ */
159
+ readonly seeds: SeedsReport
160
+ readonly superseded: readonly SupersededEntry[]
161
+ readonly unmigrated: readonly UnmigratedDomain[]
162
+ readonly newSkills: readonly string[]
163
+ /**
164
+ * Rules the toolkit authored after this target's governance anchor, filtered
165
+ * to what the target could receive. It rides beside `newSkills` rather than
166
+ * folding into it because the two answer the same question about different
167
+ * corpora and a reader acting on one runs a different command from the other.
168
+ */
169
+ readonly newRules: readonly string[]
170
+ /**
171
+ * The one section built by walking the target rather than the catalog. It
172
+ * reports beside `superseded`, `unmigrated`, and `newSkills` rather than
173
+ * absorbing them, because each of those already answers a narrower version of
174
+ * the same question correctly. See `@/sync/reverse`.
175
+ */
176
+ readonly reverse: ReverseReport
177
+ /**
178
+ * The binary running the check, not the target. It reports on an unmanaged
179
+ * target too, since a reader told to run `canon init` is better off knowing
180
+ * first whether the binary about to install is the current one.
181
+ *
182
+ * `hasDrift` deliberately ignores it. A registry lookup inside a check that
183
+ * gates would fail CI on an offline machine for a condition the check never
184
+ * measured, and the state reaching the reader is the point rather than the
185
+ * exit code.
186
+ */
187
+ readonly skew: SkewReport
188
+ }
189
+
190
+ export function installedStampDomains(target: string): ScannedDomain[] {
191
+ return SCANNED_DOMAINS.filter((domain) =>
192
+ isDirectory(join(target, ...INSTALL_MARKERS[domain])),
193
+ )
194
+ }
195
+
196
+ /**
197
+ * Reads the chain the install recorded and scans against those stacks rather
198
+ * than re-resolving from the leaf. A run that passed `--skip` installed fewer
199
+ * layers than the leaf's own chain reproduces, so re-resolving would report
200
+ * drift against a layer the target deliberately does not carry.
201
+ *
202
+ * A recorded stack the toolkit no longer ships resolves to nothing, and a chain
203
+ * where none resolve reads as unmeasured. Scanning the survivors would measure
204
+ * against a chain neither side agrees on.
205
+ */
206
+ export function buildToolingReport(
207
+ toolkitRoot: string,
208
+ target: string,
209
+ stamp: Stamp | undefined,
210
+ ): ToolingReport {
211
+ const chain = stampedChain(stamp, 'tooling')
212
+ const manifests = chain
213
+ .map((name) => loadManifest(toolkitRoot, name))
214
+ .filter((manifest) => manifest !== undefined)
215
+
216
+ if (manifests.length === 0) return { ...UNMEASURED_TOOLING, chain }
217
+
218
+ const result = scan(manifests, target)
219
+ const record = stamp?.domains.tooling
220
+
221
+ return {
222
+ measured: true,
223
+ chain,
224
+ commit: record?.commit,
225
+ syncedAt: record?.syncedAt,
226
+ counts: {
227
+ configs: result.configs.filter((entry) => entry.state !== 'matching')
228
+ .length,
229
+ seeds: result.seeds.filter((entry) => entry.state === 'missing').length,
230
+ scripts: result.scripts.filter((entry) => entry.state !== 'matching')
231
+ .length,
232
+ deps: result.deps.filter((entry) => entry.state === 'missing').length,
233
+ gitignore: result.gitignore.filter((entry) => entry.state === 'missing')
234
+ .length,
235
+ },
236
+ changes: result.totalChanges,
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Whether the target is a toolkit-managed project at all. Seeds are enumerated
242
+ * from the source rather than from what a target installed, so without this gate
243
+ * a directory the toolkit has never touched reports every seed as `missing` and
244
+ * routes to a skill that reconciles section by section. `installedStampDomains`
245
+ * gates the three scanned domains the same way, which is why they stay quiet on
246
+ * the same directory.
247
+ *
248
+ * An unmigrated domain counts as a marker in its own right. `detectUnmigrated`
249
+ * fires only on root files whose basename the toolkit ships, so it firing proves
250
+ * the toolkit installed here before the layout moved under `.claude/`. Reading
251
+ * only the markers would report such a target as unmanaged while the same report
252
+ * carried its unmigrated domain, and a consumer reading the JSON would route to
253
+ * the relocation while the rendered half routed to install.
254
+ */
255
+ export function isManagedTarget(
256
+ target: string,
257
+ unmigrated: readonly UnmigratedDomain[],
258
+ ): boolean {
259
+ if (unmigrated.length > 0) return true
260
+
261
+ return (
262
+ isDirectory(join(target, '.claude')) ||
263
+ existsSync(join(target, 'CLAUDE.md'))
264
+ )
265
+ }
266
+
267
+ export function countStates(entries: readonly ScanEntry[]): StateCounts {
268
+ return {
269
+ matching: count(entries, 'matching'),
270
+ stale: count(entries, 'stale'),
271
+ customized: count(entries, 'customized'),
272
+ drifted: count(entries, 'drifted'),
273
+ orphaned: count(entries, 'orphaned'),
274
+ stranded: count(entries, 'stranded'),
275
+ missing: count(entries, 'missing'),
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Whether the target has diverged from the toolkit in a way a sync could close.
281
+ * Orphaned files are excluded: a project-authored rule never converges, and
282
+ * counting it would leave `--exit-code` failing forever with no remedy.
283
+ *
284
+ * An unmigrated domain counts, because running the relocation closes it. A
285
+ * superseded artifact does not, for the same reason orphaned files do not: only
286
+ * the user can move content they wrote, so failing a job on it leaves the job
287
+ * red with no mechanical remedy. Seeds are excluded on the same grounds, since
288
+ * every seed a project edits would otherwise fail the check forever.
289
+ *
290
+ * Tooling is excluded on exactly the seeds grounds: a golden config is one the
291
+ * project is expected to edit, so a job counting it stays red with no remedy.
292
+ * Being unmeasured is not what excludes it, since an unmeasured report carries
293
+ * zero changes and would pass a count either way.
294
+ *
295
+ * The reverse report is excluded because every entry in it is a judgment about
296
+ * a file the project may own. `detectUnmigrated` already shipped that exact
297
+ * false positive once, failing a push with no action that cleared it, and a
298
+ * walk that reports `unattributed` by design would repeat it.
299
+ *
300
+ * `missing` is excluded on the same grounds `newRules` already reports on: a
301
+ * sync that adds a rule silently changes what a project is governed by, and
302
+ * nobody chose that, so gating CI on the count would pressure a target into
303
+ * adopting a rule nobody picked.
304
+ */
305
+ export function hasDrift(report: CheckReport): boolean {
306
+ if (report.unmigrated.length > 0) return true
307
+
308
+ return report.domains.some(
309
+ (domain) =>
310
+ domain.counts.stale +
311
+ domain.counts.customized +
312
+ domain.counts.drifted +
313
+ domain.counts.stranded >
314
+ 0,
315
+ )
316
+ }
317
+
318
+ /**
319
+ * Bounds each domain's upstream read by that domain's own anchor and its own
320
+ * source path. A shared anchor would let a gov sync advance the revision
321
+ * snippets measures from, silently dropping a snippets change out of the read.
322
+ */
323
+ export async function buildCheckReport(
324
+ toolkitRoot: string,
325
+ target: string,
326
+ ): Promise<CheckReport> {
327
+ const stamp = readStamp(target)
328
+
329
+ // Started before the local scan and awaited after it, so the network wait
330
+ // overlaps work the report needs anyway rather than adding to it.
331
+ const skewRead = readSkew()
332
+
333
+ const domains = await Promise.all(
334
+ installedStampDomains(target).map((domain) =>
335
+ buildDomainReport(toolkitRoot, target, stamp, domain),
336
+ ),
337
+ )
338
+
339
+ const anchors = domains
340
+ .map((domain) => domain.commit)
341
+ .filter((commit): commit is string => commit !== undefined)
342
+
343
+ const unmigrated = detectUnmigrated(toolkitRoot, target)
344
+ const managed = isManagedTarget(target, unmigrated)
345
+
346
+ if (!managed) {
347
+ return {
348
+ covers: [],
349
+ stampAtLegacyPath: isLegacyStamped(target),
350
+ managed,
351
+ domains: [],
352
+ tooling: UNMEASURED_TOOLING,
353
+ seeds: { entries: [], historyUnavailable: false },
354
+ superseded: [],
355
+ unmigrated: [],
356
+ newSkills: [],
357
+ newRules: [],
358
+ reverse: emptyReverseReport(),
359
+ skew: await skewRead,
360
+ }
361
+ }
362
+
363
+ return {
364
+ covers: stamp?.covers ?? [],
365
+ stampAtLegacyPath: isLegacyStamped(target),
366
+ managed,
367
+ domains,
368
+ tooling: buildToolingReport(toolkitRoot, target, stamp),
369
+ seeds: buildSeedsReport(toolkitRoot, target),
370
+ superseded: collectSuperseded(target),
371
+ unmigrated,
372
+ newSkills: await readNewSkills(toolkitRoot, anchors),
373
+ newRules: await readNewRules(toolkitRoot, target, stamp),
374
+ reverse: buildReverseReport(toolkitRoot, target),
375
+ skew: await skewRead,
376
+ }
377
+ }
378
+
379
+ async function buildDomainReport(
380
+ toolkitRoot: string,
381
+ target: string,
382
+ stamp: Stamp | undefined,
383
+ domain: ScannedDomain,
384
+ ): Promise<DomainReport> {
385
+ const plan = planSync(ADAPTERS[domain](toolkitRoot), target)
386
+ const record = stamp?.domains[domain]
387
+ const since = stampedCommit(stamp, domain)
388
+
389
+ return {
390
+ domain,
391
+ stamped: record !== undefined,
392
+ commit: since,
393
+ syncedAt: record?.syncedAt,
394
+ counts: countStates(plan.entries),
395
+ entries: plan.entries,
396
+ historyUnavailable: plan.historyUnavailable,
397
+ upstream:
398
+ since === undefined
399
+ ? []
400
+ : await readUpstream(toolkitRoot, since, SYNCED_SOURCES[domain]),
401
+ }
402
+ }
403
+
404
+ export function parseUpstream(log: string): UpstreamCommit[] {
405
+ const commits: UpstreamCommit[] = []
406
+
407
+ for (const line of log.split('\n')) {
408
+ const trimmed = line.trim()
409
+ if (trimmed === '') continue
410
+
411
+ const boundary = trimmed.indexOf(' ')
412
+ if (boundary === -1) continue
413
+
414
+ commits.push({
415
+ sha: trimmed.slice(0, boundary),
416
+ subject: trimmed.slice(boundary + 1),
417
+ })
418
+ }
419
+
420
+ return commits
421
+ }
422
+
423
+ /** A skill is new when its `SKILL.md` was added, not when a support file was. */
424
+ export function parseNewSkills(paths: string): string[] {
425
+ const names = paths
426
+ .split('\n')
427
+ .map((line) => line.trim())
428
+ .filter((line) => line.endsWith('/SKILL.md'))
429
+ .map((line) => line.split('/').at(-2))
430
+ .filter((name): name is string => name !== undefined)
431
+
432
+ return [...new Set(names)].sort()
433
+ }
434
+
435
+ /**
436
+ * What the target's `.claude/rules/` proves about its entitlement. `held` is
437
+ * every rule name it carries and `bands` every band folder those names sit in.
438
+ *
439
+ * Read off the installed tree rather than off a stack name, because
440
+ * `gov install` records file hashes and never the stack it resolved, so the
441
+ * chain a target consumed survives nowhere else. `installRules` copies each
442
+ * rule into the subdirectory it was authored in, which is what makes the band
443
+ * folders legible as evidence.
444
+ */
445
+ export function readInstalledRules(target: string): {
446
+ held: Set<string>
447
+ bands: Set<string>
448
+ } {
449
+ const dir = join(target, ...INSTALL_MARKERS.governance)
450
+ const held = new Set<string>()
451
+ const bands = new Set<string>()
452
+ if (!isDirectory(dir)) return { held, bands }
453
+
454
+ for (const rel of new Bun.Glob('**/*.md').scanSync({
455
+ cwd: dir,
456
+ onlyFiles: true,
457
+ dot: true,
458
+ })) {
459
+ const posix = rel.split(sep).join('/')
460
+ const boundary = posix.indexOf('/')
461
+
462
+ held.add(basename(posix, '.md'))
463
+ if (boundary > 0) bands.add(posix.slice(0, boundary))
464
+ }
465
+
466
+ return { held, bands }
467
+ }
468
+
469
+ /**
470
+ * Narrows rules added upstream to the ones this target could receive and does
471
+ * not already hold.
472
+ *
473
+ * The band test is the entitlement filter, and `bands` carries two sources: the
474
+ * folders the target already holds, and the folders the base stack takes whole.
475
+ * A rule authored under `lang/` or `ui/` is named by an individual stack, so it
476
+ * belongs to some targets and not others, and listing every added file would
477
+ * tell a base consumer about rules it was never entitled to. The test
478
+ * over-reports inside a band a target already carries, since one folder can be
479
+ * reached by more than one stack, and that costs a line where under-reporting
480
+ * would cost the whole point of the section.
481
+ *
482
+ * The `held` test is what keeps a rule that moved bands upstream out. A rename
483
+ * reaches this diff as an addition, and the target already has the file under
484
+ * its old folder, so matching by name is what tells the two apart.
485
+ */
486
+ export function selectNewRules(
487
+ paths: string,
488
+ held: ReadonlySet<string>,
489
+ bands: ReadonlySet<string>,
490
+ ): string[] {
491
+ const prefix = 'governance/rules/'
492
+ const names = new Set<string>()
493
+
494
+ for (const line of paths.split('\n')) {
495
+ const trimmed = line.trim()
496
+ if (!trimmed.startsWith(prefix) || !trimmed.endsWith('.md')) continue
497
+
498
+ const rel = trimmed.slice(prefix.length)
499
+ const boundary = rel.indexOf('/')
500
+ const band = boundary === -1 ? '' : rel.slice(0, boundary)
501
+ const name = basename(rel, '.md')
502
+
503
+ if (held.has(name)) continue
504
+ if (band !== '' && !bands.has(band)) continue
505
+
506
+ names.add(name)
507
+ }
508
+
509
+ return [...names].sort()
510
+ }
511
+
512
+ /**
513
+ * Band folders the base stack takes whole. Every governance stack extends base,
514
+ * so a rule authored under one of these is entitled to every target.
515
+ *
516
+ * This is what covers a band no target can carry yet. Entitlement is otherwise
517
+ * read off folders the target already holds, and a folder added to base later
518
+ * exists in no installed tree, so without this the rules inside it would reach
519
+ * nobody. Read from the stack file rather than fixed, so that addition needs no
520
+ * code change here.
521
+ *
522
+ * A folder only a leaf stack names is deliberately absent. It is entitled to
523
+ * some targets and not others, which is the distinction the band test makes and
524
+ * the installed tree is the only evidence of.
525
+ */
526
+ export function baseBands(root: string): Set<string> {
527
+ const stack = loadGovStack(root, 'base')
528
+ if (stack === undefined) return new Set()
529
+
530
+ return new Set(
531
+ stack.rules.filter((entry) =>
532
+ isDirectory(join(rulesSourceDir(root), entry)),
533
+ ),
534
+ )
535
+ }
536
+
537
+ /**
538
+ * A recorded chain answers this without the anchor at all: `resolveMissingRules`
539
+ * compares the entitled set against what the target holds right now, so a rule
540
+ * that shipped before the target's anchor is not a permanent blind spot the
541
+ * way the diff below leaves it. This is the primary path, and it is also what
542
+ * `collectMissing` reports per file through the domain scan, so a rule the
543
+ * chain names and the target lacks reaches both surfaces the same way.
544
+ *
545
+ * The diff-and-bands path stays as the fallback for a target stamped before
546
+ * governance recorded a chain. Rules are domain-scoped there too, so it
547
+ * measures from governance's own anchor rather than from the oldest anchor
548
+ * across domains the way `readNewSkills` does. A shared anchor would let
549
+ * another domain's sync move the revision rules are measured from and drop a
550
+ * rule out of the read.
551
+ *
552
+ * A target carrying no chain and no governance anchor reports nothing. It has
553
+ * no date to measure against, and diffing from the beginning of history would
554
+ * read every rule the toolkit ships as new.
555
+ *
556
+ * An anchor this clone cannot resolve reports nothing by a different route and
557
+ * says so nowhere. `read` yields an empty string on a non-zero exit, so a stamp
558
+ * naming a revision a registry install or a shallow clone has never seen reads
559
+ * as a target holding everything. `readNewSkills` carries the same gap, and
560
+ * neither has the `historyUnavailable` flag the per-domain scan uses to tell an
561
+ * unmeasured result from a clean one.
562
+ *
563
+ * A recorded chain naming a stack the toolkit no longer ships falls through to
564
+ * the band-based path below rather than reporting the empty list an
565
+ * unresolved chain would otherwise produce. That empty list reads exactly
566
+ * like a target holding everything, which is the same failure this function
567
+ * exists to close, so a retired stack name is read the same as no chain at
568
+ * all instead of reintroducing it.
569
+ */
570
+ export async function readNewRules(
571
+ root: string,
572
+ target: string,
573
+ stamp: Stamp | undefined,
574
+ ): Promise<string[]> {
575
+ const chain = stampedChain(stamp, 'governance')
576
+ const stack = chain[0]
577
+
578
+ if (stack !== undefined && resolveRules(root, stack).ok) {
579
+ return resolveMissingRules(root, target, chain)
580
+ .map((source) => source.rule)
581
+ .sort()
582
+ }
583
+
584
+ const since = stampedCommit(stamp, 'governance')
585
+ if (since === undefined) return []
586
+
587
+ const paths = await read(root, [
588
+ 'diff',
589
+ '--name-only',
590
+ '--diff-filter=A',
591
+ `${since}..HEAD`,
592
+ '--',
593
+ 'governance/rules/',
594
+ ])
595
+
596
+ const { held, bands } = readInstalledRules(target)
597
+ return selectNewRules(paths, held, bands.union(baseBands(root)))
598
+ }
599
+
600
+ async function readUpstream(
601
+ root: string,
602
+ since: string,
603
+ sourcePath: string,
604
+ ): Promise<UpstreamCommit[]> {
605
+ const log = await read(root, [
606
+ 'log',
607
+ '--oneline',
608
+ '--no-decorate',
609
+ `${since}..HEAD`,
610
+ '--',
611
+ sourcePath,
612
+ ])
613
+
614
+ return parseUpstream(log)
615
+ }
616
+
617
+ /**
618
+ * Skills are not domain-scoped, so the read runs from the oldest anchor across
619
+ * domains. Over-reporting a skill costs a line, while measuring from the newest
620
+ * would hide one that arrived before the most recent domain sync.
621
+ */
622
+ async function readNewSkills(
623
+ root: string,
624
+ anchors: readonly string[],
625
+ ): Promise<string[]> {
626
+ const since = await oldestAnchor(root, anchors)
627
+ if (since === undefined) return []
628
+
629
+ const paths = await read(root, [
630
+ 'diff',
631
+ '--name-only',
632
+ '--diff-filter=A',
633
+ `${since}..HEAD`,
634
+ '--',
635
+ 'claude/skills/',
636
+ ])
637
+
638
+ return parseNewSkills(paths)
639
+ }
640
+
641
+ async function oldestAnchor(
642
+ root: string,
643
+ anchors: readonly string[],
644
+ ): Promise<string | undefined> {
645
+ const unique = [...new Set(anchors)]
646
+ let oldest = unique[0]
647
+
648
+ for (const candidate of unique.slice(1)) {
649
+ if (await isAncestor(root, candidate, oldest)) oldest = candidate
650
+ }
651
+
652
+ return oldest
653
+ }
654
+
655
+ async function isAncestor(
656
+ root: string,
657
+ candidate: string,
658
+ reference: string,
659
+ ): Promise<boolean> {
660
+ const result = await execa(
661
+ 'git',
662
+ ['-C', root, 'merge-base', '--is-ancestor', candidate, reference],
663
+ { reject: false, env: gitEnv(), extendEnv: false },
664
+ )
665
+
666
+ return result.exitCode === 0
667
+ }
668
+
669
+ /**
670
+ * A toolkit outside a git clone, or a stamped revision this clone has never
671
+ * seen, yields no range. The per-file report still stands on its own.
672
+ *
673
+ * Scrubbed through `gitEnv` because a git hook exports `GIT_DIR` and its
674
+ * siblings into every process it runs and they outrank `-C`. A check invoked
675
+ * from a hook would otherwise diff the hook's repository and report a range for
676
+ * a tree nobody asked about, which reads as an ordinary answer.
677
+ */
678
+ async function read(root: string, args: readonly string[]): Promise<string> {
679
+ const result = await execa('git', ['-C', root, ...args], {
680
+ reject: false,
681
+ env: gitEnv(),
682
+ extendEnv: false,
683
+ })
684
+ return result.exitCode === 0 ? result.stdout : ''
685
+ }
686
+
687
+ function count(
688
+ entries: readonly ScanEntry[],
689
+ state: ScanEntry['state'],
690
+ ): number {
691
+ return entries.filter((entry) => entry.state === state).length
692
+ }