@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,122 @@
1
+ import type { GitignoreSection } from '@/tooling/manifest'
2
+
3
+ export interface MergeResult {
4
+ readonly content: string
5
+ readonly added: readonly string[]
6
+ }
7
+
8
+ export interface PruneResult {
9
+ readonly content: string
10
+ readonly removed: readonly string[]
11
+ }
12
+
13
+ /**
14
+ * Splits a gitignore body the way `while IFS= read -r line` did, so a file
15
+ * with or without a trailing newline yields the same logical lines.
16
+ */
17
+ function toLines(content: string): string[] {
18
+ if (content === '') return []
19
+ const lines = content.split('\n')
20
+ if (lines[lines.length - 1] === '') lines.pop()
21
+ return lines
22
+ }
23
+
24
+ function fromLines(lines: readonly string[]): string {
25
+ if (lines.length === 0) return ''
26
+ return `${lines.join('\n')}\n`
27
+ }
28
+
29
+ function withoutTrailingSlash(entry: string): string {
30
+ return entry.endsWith('/') ? entry.slice(0, -1) : entry
31
+ }
32
+
33
+ function hasEntry(lines: readonly string[], entry: string): boolean {
34
+ return lines.includes(entry) || lines.includes(withoutTrailingSlash(entry))
35
+ }
36
+
37
+ /**
38
+ * Appends every manifest entry the file does not already carry.
39
+ *
40
+ * Two behaviors are load-bearing and preserved from `merge_gitignore`. The
41
+ * section header is written only when the whole section is missing, so a
42
+ * partially present section appends its entries bare at the end of the file.
43
+ * Presence is re-checked against the growing file, so an entry added by an
44
+ * earlier section or an earlier stack in the chain is not added twice.
45
+ */
46
+ export function mergeSections(
47
+ content: string,
48
+ sections: readonly GitignoreSection[],
49
+ ): MergeResult {
50
+ let next = content
51
+ const added: string[] = []
52
+
53
+ for (const section of sections) {
54
+ const lines = toLines(next)
55
+ const missing = section.entries.filter((entry) => !hasEntry(lines, entry))
56
+ if (missing.length === 0) continue
57
+
58
+ if (missing.length === section.entries.length) {
59
+ next += `\n${section.header}\n`
60
+ }
61
+
62
+ for (const entry of missing) {
63
+ next += `${entry}\n`
64
+ added.push(entry)
65
+ }
66
+ }
67
+
68
+ return { content: next, added }
69
+ }
70
+
71
+ /**
72
+ * Drops entries that sit under a managed header but are no longer in the
73
+ * manifest. A section runs from its header line until the first blank line or
74
+ * the next comment, matching `prune_gitignore_section`.
75
+ */
76
+ export function pruneSections(
77
+ content: string,
78
+ sections: readonly GitignoreSection[],
79
+ ): PruneResult {
80
+ let lines = toLines(content)
81
+ const removed: string[] = []
82
+
83
+ for (const section of sections) {
84
+ const kept: string[] = []
85
+ let inSection = false
86
+
87
+ for (const line of lines) {
88
+ if (line === section.header) {
89
+ inSection = true
90
+ kept.push(line)
91
+ continue
92
+ }
93
+
94
+ if (!inSection) {
95
+ kept.push(line)
96
+ continue
97
+ }
98
+
99
+ if (line === '' || line.startsWith('#')) {
100
+ inSection = false
101
+ kept.push(line)
102
+ continue
103
+ }
104
+
105
+ const isAllowed = section.entries.some(
106
+ (entry) =>
107
+ line === entry ||
108
+ withoutTrailingSlash(line) === withoutTrailingSlash(entry),
109
+ )
110
+
111
+ if (isAllowed) {
112
+ kept.push(line)
113
+ } else {
114
+ removed.push(line)
115
+ }
116
+ }
117
+
118
+ lines = kept
119
+ }
120
+
121
+ return { content: removed.length > 0 ? fromLines(lines) : content, removed }
122
+ }
@@ -0,0 +1,193 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'
3
+ import { dirname, join } from 'node:path'
4
+ import { $ } from 'bun'
5
+ import { copyPreservingMode } from '@/copy'
6
+ import { rewritesOnInstall, stripSeedMarker } from '@/seed-marker'
7
+ import { mergeSections, pruneSections } from '@/tooling/gitignore'
8
+ import { ancestorsFirst, listFiles, type Manifest } from '@/tooling/manifest'
9
+ import {
10
+ applyScripts,
11
+ collectDeps,
12
+ readPackage,
13
+ serializePackage,
14
+ } from '@/tooling/package'
15
+ import { logAdd, logRemove, logStep } from '@/ui'
16
+
17
+ export async function injectConfigs(
18
+ chain: readonly Manifest[],
19
+ target: string,
20
+ ): Promise<string[]> {
21
+ const applied: string[] = []
22
+
23
+ for (const manifest of ancestorsFirst(chain)) {
24
+ const files = listFiles(manifest.configsDir)
25
+ if (files.length === 0) continue
26
+
27
+ logStep(`Applying ${manifest.name} configs`)
28
+ for (const rel of files) {
29
+ await copyPreservingMode(
30
+ join(manifest.configsDir, rel),
31
+ join(target, rel),
32
+ )
33
+ logAdd(rel)
34
+ applied.push(rel)
35
+ }
36
+ }
37
+
38
+ return applied
39
+ }
40
+
41
+ /**
42
+ * Writes a seed to a target that does not have it, dropping the stub marker on
43
+ * the way. The marker is toolkit bookkeeping read by the seed gate, so a target
44
+ * receiving it would carry a field its own tooling never reads. Only markdown
45
+ * carries frontmatter, and every other seed copies byte for byte.
46
+ */
47
+ async function writeSeed(src: string, dest: string): Promise<void> {
48
+ if (!rewritesOnInstall(src)) {
49
+ await copyFile(src, dest)
50
+ return
51
+ }
52
+
53
+ await writeFile(dest, stripSeedMarker(await readFile(src, 'utf8')))
54
+ }
55
+
56
+ /**
57
+ * Copies a seed when the target lacks it. When the target already has one and
58
+ * the seed is a `.txt` word list, missing lines are appended and the file is
59
+ * re-sorted. Every other existing file is left untouched.
60
+ */
61
+ async function mergeSeedFile(src: string, dest: string): Promise<void> {
62
+ await mkdir(dirname(dest), { recursive: true })
63
+
64
+ if (!existsSync(dest)) {
65
+ await writeSeed(src, dest)
66
+ return
67
+ }
68
+
69
+ if (!src.endsWith('.txt')) return
70
+
71
+ const existing = (await readFile(dest, 'utf8')).split('\n')
72
+ const incoming = (await readFile(src, 'utf8')).split('\n')
73
+ const missing = incoming.filter(
74
+ (word) => word !== '' && !existing.includes(word),
75
+ )
76
+
77
+ if (missing.length === 0) return
78
+
79
+ const merged = [...existing.filter((line) => line !== ''), ...missing].sort()
80
+ await writeFile(dest, `${merged.join('\n')}\n`)
81
+ }
82
+
83
+ export async function injectSeeds(
84
+ chain: readonly Manifest[],
85
+ target: string,
86
+ ): Promise<string[]> {
87
+ const applied: string[] = []
88
+
89
+ for (const manifest of ancestorsFirst(chain)) {
90
+ const files = listFiles(manifest.seedsDir)
91
+ if (files.length === 0) continue
92
+
93
+ logStep(`Applying ${manifest.name} seeds`)
94
+ for (const rel of files) {
95
+ await mergeSeedFile(join(manifest.seedsDir, rel), join(target, rel))
96
+ logAdd(rel)
97
+ applied.push(rel)
98
+ }
99
+ }
100
+
101
+ return applied
102
+ }
103
+
104
+ export async function injectGitignore(
105
+ chain: readonly Manifest[],
106
+ target: string,
107
+ ): Promise<string[]> {
108
+ const path = join(target, '.gitignore')
109
+ const added: string[] = []
110
+
111
+ for (const manifest of ancestorsFirst(chain)) {
112
+ if (manifest.gitignore.length === 0) continue
113
+
114
+ const content = existsSync(path) ? await readFile(path, 'utf8') : ''
115
+ const result = mergeSections(content, manifest.gitignore)
116
+ if (result.added.length === 0) continue
117
+
118
+ logStep(`Applying ${manifest.name} gitignore`)
119
+ await writeFile(path, result.content)
120
+ for (const entry of result.added) {
121
+ logAdd(entry)
122
+ added.push(entry)
123
+ }
124
+ }
125
+
126
+ if (added.length === 0 && !existsSync(path)) await writeFile(path, '')
127
+
128
+ return added
129
+ }
130
+
131
+ export async function pruneGitignore(
132
+ chain: readonly Manifest[],
133
+ target: string,
134
+ ): Promise<string[]> {
135
+ const path = join(target, '.gitignore')
136
+ if (!existsSync(path)) return []
137
+
138
+ const removed: string[] = []
139
+
140
+ for (const manifest of ancestorsFirst(chain)) {
141
+ if (manifest.gitignore.length === 0) continue
142
+
143
+ const content = await readFile(path, 'utf8')
144
+ const result = pruneSections(content, manifest.gitignore)
145
+ if (result.removed.length === 0) continue
146
+
147
+ await writeFile(path, result.content)
148
+ for (const entry of result.removed) {
149
+ logRemove(entry)
150
+ removed.push(entry)
151
+ }
152
+ }
153
+
154
+ return removed
155
+ }
156
+
157
+ /**
158
+ * Installs missing dev dependencies, fills package scripts, and merges
159
+ * gitignore entries. This is the `inject_tooling_manifest` sequence, which
160
+ * assumed a target that already has a package.json.
161
+ */
162
+ export async function injectManifest(
163
+ chain: readonly Manifest[],
164
+ target: string,
165
+ ): Promise<void> {
166
+ const packagePath = join(target, 'package.json')
167
+ const pkg = readPackage(packagePath)
168
+
169
+ if (pkg) {
170
+ const missing = collectDeps(chain, pkg)
171
+ .filter((dep) => dep.state === 'missing')
172
+ .map((dep) => dep.spec)
173
+
174
+ if (missing.length > 0) {
175
+ logStep('Installing missing dependencies')
176
+ await $`bun add -D ${missing}`.cwd(target)
177
+ for (const spec of missing) logAdd(spec)
178
+ }
179
+
180
+ const fresh = readPackage(packagePath)
181
+ if (fresh) {
182
+ const result = applyScripts(fresh, chain)
183
+ if (result.added.length > 0 || result.overridden.length > 0) {
184
+ logStep('Applying scripts')
185
+ for (const key of result.added) logAdd(key)
186
+ for (const key of result.overridden) logAdd(key)
187
+ await writeFile(packagePath, serializePackage(result.pkg))
188
+ }
189
+ }
190
+ }
191
+
192
+ await injectGitignore(chain, target)
193
+ }
@@ -0,0 +1,39 @@
1
+ import { listStacks, loadManifest } from '@/tooling/manifest'
2
+
3
+ export interface StackSummary {
4
+ readonly name: string
5
+ readonly extends: string | null
6
+ readonly devDeps: number
7
+ readonly scripts: number
8
+ readonly gitignoreGroups: number
9
+ }
10
+
11
+ /**
12
+ * Summarizes each installable stack. The counts come from `loadManifest`
13
+ * rather than the three `awk` programs the bash carried, so the list and the
14
+ * sync it previews read the same parse.
15
+ */
16
+ export function buildStackSummaries(root: string): StackSummary[] {
17
+ const summaries: StackSummary[] = []
18
+
19
+ for (const stack of listStacks(root)) {
20
+ const manifest = loadManifest(root, stack)
21
+ if (!manifest) continue
22
+
23
+ summaries.push({
24
+ name: manifest.name,
25
+ extends: manifest.parent ?? null,
26
+ devDeps: manifest.devPackages.length,
27
+ scripts: Object.keys(manifest.scripts).length,
28
+ gitignoreGroups: manifest.gitignore.length,
29
+ })
30
+ }
31
+
32
+ return summaries
33
+ }
34
+
35
+ export function describeStack(summary: StackSummary): string {
36
+ const counts = `${summary.devDeps} dev deps, ${summary.scripts} scripts, ${summary.gitignoreGroups} gitignore groups`
37
+ const prefix = summary.extends === null ? '' : `extends: ${summary.extends}, `
38
+ return `${summary.name} (${prefix}${counts})`
39
+ }
@@ -0,0 +1,176 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+
4
+ const EXCLUDED_STACKS = ['claude']
5
+
6
+ export interface GitignoreSection {
7
+ readonly header: string
8
+ readonly entries: readonly string[]
9
+ }
10
+
11
+ export interface Manifest {
12
+ readonly name: string
13
+ readonly parent?: string
14
+ readonly dir: string
15
+ readonly configsDir: string
16
+ readonly seedsDir: string
17
+ readonly scripts: Readonly<Record<string, string>>
18
+ readonly scriptOverrides: Readonly<Record<string, string>>
19
+ readonly gitignore: readonly GitignoreSection[]
20
+ readonly devPackages: readonly string[]
21
+ }
22
+
23
+ export interface ChainOptions {
24
+ readonly skipStack?: string
25
+ }
26
+
27
+ function toolingDir(root: string): string {
28
+ return join(root, 'tooling')
29
+ }
30
+
31
+ function manifestPath(root: string, stack: string): string {
32
+ return join(toolingDir(root), stack, 'manifest.toml')
33
+ }
34
+
35
+ /**
36
+ * Lists every file under a directory, dotfiles included. Bun.Glob skips
37
+ * entries beginning with a dot unless `dot` is set, and tooling configs are
38
+ * almost entirely dotfiles, so omitting it silently matches nothing.
39
+ */
40
+ export function listFiles(dir: string): string[] {
41
+ if (!existsSync(dir)) return []
42
+ return [
43
+ ...new Bun.Glob('**/*').scanSync({ cwd: dir, onlyFiles: true, dot: true }),
44
+ ].sort()
45
+ }
46
+
47
+ /**
48
+ * Reverses a chain so the furthest ancestor is applied first and nearer
49
+ * stacks overwrite it.
50
+ */
51
+ export function ancestorsFirst(chain: readonly Manifest[]): Manifest[] {
52
+ return [...chain].reverse()
53
+ }
54
+
55
+ export function stackExists(root: string, stack: string): boolean {
56
+ return existsSync(join(toolingDir(root), stack))
57
+ }
58
+
59
+ export function isStackExcluded(stack: string): boolean {
60
+ return EXCLUDED_STACKS.includes(stack)
61
+ }
62
+
63
+ /**
64
+ * Lists installable stack names, filtering the `claude` stack the way
65
+ * `is_tooling_stack_excluded` in `scripts/lib/tooling.sh` did. Claude is
66
+ * managed by `canon claude`, not `canon tooling`.
67
+ */
68
+ export function listStacks(root: string): string[] {
69
+ const dir = toolingDir(root)
70
+ if (!existsSync(dir)) return []
71
+
72
+ return [...new Bun.Glob('*/manifest.toml').scanSync({ cwd: dir })]
73
+ .map((entry) => entry.split('/')[0])
74
+ .filter((name) => !isStackExcluded(name))
75
+ .sort()
76
+ }
77
+
78
+ /**
79
+ * Reads one manifest into a typed shape. Returns undefined when the file is
80
+ * absent, matching the bash guards that silently skipped a missing manifest.
81
+ */
82
+ export function loadManifest(
83
+ root: string,
84
+ stack: string,
85
+ ): Manifest | undefined {
86
+ const path = manifestPath(root, stack)
87
+ if (!existsSync(path)) return undefined
88
+
89
+ const parsed = asTable(Bun.TOML.parse(readFileSync(path, 'utf8')))
90
+ const stackTable = asTable(parsed.stack)
91
+ const parent =
92
+ typeof stackTable.extends === 'string' ? stackTable.extends : ''
93
+ const scriptsTable = asTable(parsed.scripts)
94
+ const dir = join(toolingDir(root), stack)
95
+
96
+ return {
97
+ name: stack,
98
+ parent: parent === '' ? undefined : parent,
99
+ dir,
100
+ configsDir: join(dir, 'configs'),
101
+ seedsDir: join(dir, 'seeds'),
102
+ scripts: pickStrings(scriptsTable),
103
+ scriptOverrides: pickStrings(asTable(scriptsTable.override)),
104
+ gitignore: readGitignoreSections(asTable(parsed.gitignore)),
105
+ devPackages: readDevPackages(parsed.dependencies),
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Walks the `extends` chain once, self first. Callers that apply parent
111
+ * values before child values reverse the result. Replaces the seventeen
112
+ * copies of `grep '^extends' | cut -d'"' -f2` the bash carried.
113
+ *
114
+ * A stack matching `skipStack` truncates the chain at that point, mirroring
115
+ * the `[ "$stack" = "${SKIP_STACK:-}" ] && return` guard.
116
+ */
117
+ export function resolveChain(
118
+ root: string,
119
+ stack: string,
120
+ options: ChainOptions = {},
121
+ ): Manifest[] {
122
+ const chain: Manifest[] = []
123
+ const seen = new Set<string>()
124
+ let current: string | undefined = stack
125
+
126
+ while (current !== undefined) {
127
+ if (current === options.skipStack) break
128
+ if (seen.has(current)) break
129
+ seen.add(current)
130
+
131
+ const manifest: Manifest | undefined = loadManifest(root, current)
132
+ if (!manifest) break
133
+
134
+ chain.push(manifest)
135
+ current = manifest.parent
136
+ }
137
+
138
+ return chain
139
+ }
140
+
141
+ function asTable(value: unknown): Record<string, unknown> {
142
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
143
+ return {}
144
+ }
145
+ return value as Record<string, unknown>
146
+ }
147
+
148
+ function pickStrings(table: Record<string, unknown>): Record<string, string> {
149
+ const result: Record<string, string> = {}
150
+ for (const [key, value] of Object.entries(table)) {
151
+ if (typeof value === 'string') result[key] = value
152
+ }
153
+ return result
154
+ }
155
+
156
+ function readGitignoreSections(
157
+ table: Record<string, unknown>,
158
+ ): GitignoreSection[] {
159
+ const sections: GitignoreSection[] = []
160
+ for (const [header, value] of Object.entries(table)) {
161
+ if (!Array.isArray(value)) continue
162
+ const entries = value.filter(
163
+ (entry): entry is string => typeof entry === 'string' && entry !== '',
164
+ )
165
+ sections.push({ header, entries })
166
+ }
167
+ return sections
168
+ }
169
+
170
+ function readDevPackages(dependencies: unknown): string[] {
171
+ const dev = asTable(asTable(dependencies).dev)
172
+ if (!Array.isArray(dev.packages)) return []
173
+ return dev.packages.filter(
174
+ (entry): entry is string => typeof entry === 'string' && entry !== '',
175
+ )
176
+ }
@@ -0,0 +1,166 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import type { Manifest } from '@/tooling/manifest'
3
+
4
+ export interface PackageJson {
5
+ readonly dependencies?: Record<string, string>
6
+ readonly devDependencies?: Record<string, string>
7
+ readonly scripts?: Record<string, string>
8
+ readonly [key: string]: unknown
9
+ }
10
+
11
+ export interface ScriptState {
12
+ readonly key: string
13
+ readonly state: 'missing' | 'drifted' | 'matching'
14
+ }
15
+
16
+ export interface DepState {
17
+ readonly spec: string
18
+ readonly name: string
19
+ readonly state: 'missing' | 'present'
20
+ }
21
+
22
+ export function readPackage(path: string): PackageJson | undefined {
23
+ if (!existsSync(path)) return undefined
24
+ try {
25
+ return JSON.parse(readFileSync(path, 'utf8')) as PackageJson
26
+ } catch {
27
+ return undefined
28
+ }
29
+ }
30
+
31
+ export function serializePackage(pkg: PackageJson): string {
32
+ return `${JSON.stringify(pkg, null, 2)}\n`
33
+ }
34
+
35
+ /**
36
+ * Splits a dependency spec into its package name, handling the scoped case
37
+ * where the leading `@` is part of the name rather than a version separator.
38
+ * `@scope/pkg@^1.2.3` yields `@scope/pkg`, `react@19` yields `react`.
39
+ */
40
+ export function parseSpec(spec: string): { name: string; pin: string } {
41
+ let name = spec
42
+
43
+ if (spec.startsWith('@')) {
44
+ const rest = spec.slice(1)
45
+ if (rest.includes('@')) name = `@${rest.slice(0, rest.indexOf('@'))}`
46
+ } else {
47
+ name = spec.split('@')[0]
48
+ }
49
+
50
+ const pin = spec === name ? '' : spec.slice(name.length + 1)
51
+ return { name, pin }
52
+ }
53
+
54
+ function firstInteger(value: string): string | undefined {
55
+ return value.match(/[0-9]+/)?.[0]
56
+ }
57
+
58
+ /**
59
+ * Compares a manifest pin against what package.json records. A spec counts as
60
+ * missing when absent, or when the pinned major differs from the installed
61
+ * major. Anything looser than a major mismatch is left alone.
62
+ */
63
+ function isSatisfied(installed: string | undefined, pin: string): boolean {
64
+ if (installed === undefined) return false
65
+ if (pin === '') return true
66
+
67
+ const pinMajor = firstInteger(pin)
68
+ const installedMajor = firstInteger(installed)
69
+ if (pinMajor === undefined) return true
70
+
71
+ return pinMajor === installedMajor
72
+ }
73
+
74
+ /**
75
+ * Resolves dev dependencies across the chain, ancestors first, deduplicated by
76
+ * package name so the outermost stack that names a package wins.
77
+ */
78
+ export function collectDeps(
79
+ chain: readonly Manifest[],
80
+ pkg: PackageJson,
81
+ ): DepState[] {
82
+ const installed = { ...pkg.dependencies, ...pkg.devDependencies }
83
+ const seen = new Set<string>()
84
+ const states: DepState[] = []
85
+
86
+ for (const manifest of [...chain].reverse()) {
87
+ for (const spec of manifest.devPackages) {
88
+ const { name, pin } = parseSpec(spec)
89
+ if (name === '' || seen.has(name)) continue
90
+ seen.add(name)
91
+
92
+ states.push({
93
+ spec,
94
+ name,
95
+ state: isSatisfied(installed[name], pin) ? 'present' : 'missing',
96
+ })
97
+ }
98
+ }
99
+
100
+ return states
101
+ }
102
+
103
+ /**
104
+ * Compares manifest scripts against package.json, nearest stack first, so a
105
+ * child definition shadows the parent it extends.
106
+ */
107
+ export function collectScripts(
108
+ chain: readonly Manifest[],
109
+ pkg: PackageJson,
110
+ ): ScriptState[] {
111
+ const scripts = pkg.scripts ?? {}
112
+ const seen = new Set<string>()
113
+ const states: ScriptState[] = []
114
+
115
+ for (const manifest of chain) {
116
+ for (const [key, value] of Object.entries(manifest.scripts)) {
117
+ if (seen.has(key)) continue
118
+ seen.add(key)
119
+
120
+ const current = scripts[key]
121
+ if (current === undefined) {
122
+ states.push({ key, state: 'missing' })
123
+ } else if (current !== value) {
124
+ states.push({ key, state: 'drifted' })
125
+ } else {
126
+ states.push({ key, state: 'matching' })
127
+ }
128
+ }
129
+ }
130
+
131
+ return states
132
+ }
133
+
134
+ /**
135
+ * Fills scripts the target does not define, nearest stack first, then replays
136
+ * `[scripts.override]` from the furthest ancestor inward so the nearest stack
137
+ * has the last word. This is the ordering `apply_stack_scripts` produced
138
+ * through its recursion.
139
+ */
140
+ export function applyScripts(
141
+ pkg: PackageJson,
142
+ chain: readonly Manifest[],
143
+ ): { pkg: PackageJson; added: string[]; overridden: string[] } {
144
+ const scripts: Record<string, string> = { ...pkg.scripts }
145
+ const added: string[] = []
146
+ const overridden: string[] = []
147
+
148
+ for (const manifest of chain) {
149
+ for (const [key, value] of Object.entries(manifest.scripts)) {
150
+ if (scripts[key] === undefined) {
151
+ scripts[key] = value
152
+ added.push(key)
153
+ }
154
+ }
155
+ }
156
+
157
+ for (const manifest of [...chain].reverse()) {
158
+ for (const [key, value] of Object.entries(manifest.scriptOverrides)) {
159
+ if (scripts[key] === value) continue
160
+ scripts[key] = value
161
+ if (!overridden.includes(key)) overridden.push(key)
162
+ }
163
+ }
164
+
165
+ return { pkg: { ...pkg, scripts }, added, overridden }
166
+ }