@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,150 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'
3
+ import { dirname, join } from 'node:path'
4
+ import { copyPreservingMode } from '@/copy'
5
+ import { rewritesOnInstall, stripSeedMarker } from '@/seed-marker'
6
+
7
+ const SEEDS_DIR = join('tooling', 'claude', 'seeds')
8
+ const CLAUDE_DIR = '.claude'
9
+ const CLAUDE_MD = 'CLAUDE.md'
10
+ const HOOKS = 'hooks'
11
+ /**
12
+ * Seed subdirectories under `.claude/`. Exported because each one replaced a
13
+ * single file of the same stem in an older layout, which is what
14
+ * `@/sync/layout` pairs a target against to find a superseded artifact.
15
+ */
16
+ export const SUBDIRS: readonly string[] = [
17
+ HOOKS,
18
+ 'context',
19
+ 'diagrams',
20
+ 'memory',
21
+ 'tasks',
22
+ 'wireframes',
23
+ ]
24
+
25
+ export type SeedScope = 'claude' | 'root'
26
+
27
+ export interface Seed {
28
+ readonly src: string
29
+ readonly dest: string
30
+ readonly scanLabel: string
31
+ readonly applyLabel: string
32
+ readonly scope: SeedScope
33
+ readonly executable: boolean
34
+ }
35
+
36
+ export interface SeedEntry {
37
+ readonly seed: Seed
38
+ readonly present: boolean
39
+ }
40
+
41
+ export interface SeedCounts {
42
+ readonly claude: number
43
+ readonly root: number
44
+ }
45
+
46
+ function seedsRoot(root: string): string {
47
+ return join(root, SEEDS_DIR)
48
+ }
49
+
50
+ /**
51
+ * Lists a single directory level, sorted the way `find -maxdepth 1 -type f |
52
+ * sort` was. Bun.Glob skips dotfiles without `dot`, and every path here sits
53
+ * under `.claude`, so omitting it would match nothing.
54
+ */
55
+ function listLevel(dir: string): string[] {
56
+ if (!existsSync(dir)) return []
57
+ return [
58
+ ...new Bun.Glob('*').scanSync({ cwd: dir, onlyFiles: true, dot: true }),
59
+ ].sort()
60
+ }
61
+
62
+ /**
63
+ * Builds the seed list in the order the bash scanned it: the `.claude` root
64
+ * level, then each subdirectory, then the project-level `CLAUDE.md`. Order is
65
+ * load-bearing because it is also the order the timeline prints.
66
+ */
67
+ export function planSeeds(root: string, target: string): SeedEntry[] {
68
+ const source = join(seedsRoot(root), CLAUDE_DIR)
69
+ const destDir = join(target, CLAUDE_DIR)
70
+ const seeds: Seed[] = []
71
+
72
+ for (const name of listLevel(source)) {
73
+ seeds.push({
74
+ src: join(source, name),
75
+ dest: join(destDir, name),
76
+ scanLabel: name,
77
+ applyLabel: join(CLAUDE_DIR, name),
78
+ scope: 'claude',
79
+ executable: false,
80
+ })
81
+ }
82
+
83
+ for (const subdir of SUBDIRS) {
84
+ for (const name of listLevel(join(source, subdir))) {
85
+ const rel = `${subdir}/${name}`
86
+ seeds.push({
87
+ src: join(source, subdir, name),
88
+ dest: join(destDir, subdir, name),
89
+ scanLabel: rel,
90
+ applyLabel: join(CLAUDE_DIR, subdir, name),
91
+ scope: 'claude',
92
+ executable: subdir === HOOKS,
93
+ })
94
+ }
95
+ }
96
+
97
+ const claudeMd = join(seedsRoot(root), CLAUDE_MD)
98
+ if (existsSync(claudeMd)) {
99
+ seeds.push({
100
+ src: claudeMd,
101
+ dest: join(target, CLAUDE_MD),
102
+ scanLabel: CLAUDE_MD,
103
+ applyLabel: CLAUDE_MD,
104
+ scope: 'root',
105
+ executable: false,
106
+ })
107
+ }
108
+
109
+ return seeds.map((seed) => ({ seed, present: existsSync(seed.dest) }))
110
+ }
111
+
112
+ export function pendingSeeds(entries: readonly SeedEntry[]): Seed[] {
113
+ return entries.filter((entry) => !entry.present).map((entry) => entry.seed)
114
+ }
115
+
116
+ export function countByScope(seeds: readonly Seed[]): SeedCounts {
117
+ return {
118
+ claude: seeds.filter((seed) => seed.scope === 'claude').length,
119
+ root: seeds.filter((seed) => seed.scope === 'root').length,
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Copies each pending seed. Hooks get the executable bit the way `chmod +x`
125
+ * granted it, added on top of whatever mode the destination already carried.
126
+ *
127
+ * A markdown seed is rewritten rather than copied, so the stub marker the seed
128
+ * gate reads does not reach the target. Every other seed copies byte for byte,
129
+ * which is what keeps the hook scripts and `settings.json` untouched.
130
+ */
131
+ export async function applySeeds(seeds: readonly Seed[]): Promise<string[]> {
132
+ const applied: string[] = []
133
+
134
+ for (const seed of seeds) {
135
+ if (rewritesOnInstall(seed.src)) {
136
+ await mkdir(dirname(seed.dest), { recursive: true })
137
+ await writeFile(
138
+ seed.dest,
139
+ stripSeedMarker(await readFile(seed.src, 'utf8')),
140
+ )
141
+ } else {
142
+ await copyPreservingMode(seed.src, seed.dest)
143
+ }
144
+
145
+ if (seed.executable) await chmod(seed.dest, 0o755)
146
+ applied.push(seed.applyLabel)
147
+ }
148
+
149
+ return applied
150
+ }
@@ -0,0 +1,151 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { chmod, mkdir, readFile, stat, writeFile } from 'node:fs/promises'
3
+ import { dirname } from 'node:path'
4
+
5
+ const DEFAULT_INDENT = ' '
6
+ const INDENT = /^([ \t]+)\S/m
7
+
8
+ export type SettingsRecord = Record<string, unknown>
9
+
10
+ export interface SettingsKey {
11
+ readonly label: string
12
+ readonly changed: boolean
13
+ }
14
+
15
+ export interface SettingsPlan {
16
+ readonly keys: readonly SettingsKey[]
17
+ readonly next: SettingsRecord
18
+ readonly changed: boolean
19
+ }
20
+
21
+ /**
22
+ * Reads the indentation of the first indented line. Rewriting an operator's
23
+ * settings file at a different width is a diff they did not ask for, and this
24
+ * verb is the only one in the toolkit that writes outside a target.
25
+ */
26
+ export function detectIndent(source: string): string {
27
+ return INDENT.exec(source)?.[1] ?? DEFAULT_INDENT
28
+ }
29
+
30
+ export function serializeSettings(
31
+ value: SettingsRecord,
32
+ indent: string,
33
+ ): string {
34
+ return `${JSON.stringify(value, null, indent)}\n`
35
+ }
36
+
37
+ /**
38
+ * Parses a settings file, treating an absent one as empty. A malformed one
39
+ * throws rather than resolving to empty, because the alternative silently
40
+ * replaces a file the operator owns with a generated one.
41
+ */
42
+ export async function readSettings(
43
+ path: string,
44
+ ): Promise<{ value: SettingsRecord; indent: string }> {
45
+ if (!existsSync(path)) return { value: {}, indent: DEFAULT_INDENT }
46
+
47
+ const source = await readFile(path, 'utf8')
48
+ const indent = detectIndent(source)
49
+ if (source.trim() === '') return { value: {}, indent }
50
+
51
+ let parsed: unknown
52
+ try {
53
+ parsed = JSON.parse(source)
54
+ } catch (cause) {
55
+ throw new Error(`${path} is not valid JSON`, { cause })
56
+ }
57
+
58
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
59
+ throw new Error(`${path} is not a JSON object`)
60
+ }
61
+
62
+ return { value: parsed as SettingsRecord, indent }
63
+ }
64
+
65
+ function asRecord(value: unknown): SettingsRecord {
66
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
67
+ return {}
68
+ }
69
+ return value as SettingsRecord
70
+ }
71
+
72
+ function asStrings(value: unknown): string[] {
73
+ if (!Array.isArray(value)) return []
74
+ return value.filter((entry): entry is string => typeof entry === 'string')
75
+ }
76
+
77
+ function equals(left: unknown, right: unknown): boolean {
78
+ return Bun.deepEquals(left, right)
79
+ }
80
+
81
+ /**
82
+ * Unions two permission lists into the sorted, deduplicated shape `jq unique`
83
+ * produced. The sort is load-bearing, since the comparison that decides
84
+ * whether to write runs against the sorted result.
85
+ */
86
+ function mergeList(current: unknown, incoming: unknown): string[] {
87
+ return [...new Set([...asStrings(current), ...asStrings(incoming)])].sort()
88
+ }
89
+
90
+ /**
91
+ * Applies the four settings the toolkit owns, leaving every other key in
92
+ * place. Each key reports whether it changed so the caller prints `+` or `✓`
93
+ * per key, matching the four separate `jq` passes this replaced.
94
+ */
95
+ export function planSettings(
96
+ current: SettingsRecord,
97
+ template: SettingsRecord,
98
+ statusLineCommand: string,
99
+ ): SettingsPlan {
100
+ const next: SettingsRecord = { ...current }
101
+ const keys: SettingsKey[] = []
102
+
103
+ const statusLine = asRecord(next.statusLine)
104
+ const statusLineChanged = statusLine.command !== statusLineCommand
105
+ if (statusLineChanged) {
106
+ next.statusLine = { type: 'command', command: statusLineCommand }
107
+ }
108
+ keys.push({ label: 'statusLine', changed: statusLineChanged })
109
+
110
+ const attribution = template.attribution
111
+ const attributionChanged = !equals(next.attribution, attribution)
112
+ if (attributionChanged) next.attribution = attribution
113
+ keys.push({ label: 'attribution', changed: attributionChanged })
114
+
115
+ const permissions = asRecord(next.permissions)
116
+ const merged: SettingsRecord = { ...permissions }
117
+ const templatePermissions = asRecord(template.permissions)
118
+
119
+ for (const field of ['allow', 'deny'] as const) {
120
+ const union = mergeList(permissions[field], templatePermissions[field])
121
+ const changed = !equals(asStrings(permissions[field]), union)
122
+ if (changed) merged[field] = union
123
+ keys.push({ label: `permissions.${field}`, changed })
124
+ }
125
+
126
+ if (keys.some((key) => key.label.startsWith('permissions.') && key.changed)) {
127
+ next.permissions = merged
128
+ }
129
+
130
+ return { keys, next, changed: keys.some((key) => key.changed) }
131
+ }
132
+
133
+ /**
134
+ * Writes in place, restoring the file's existing mode. The bash wrote through
135
+ * `mktemp` and `mv`, which handed the destination the temp file's 600 and
136
+ * silently tightened a settings file the operator had left at 644.
137
+ */
138
+ export async function writeSettings(
139
+ path: string,
140
+ content: string,
141
+ ): Promise<void> {
142
+ await mkdir(dirname(path), { recursive: true })
143
+
144
+ const mode = await stat(path)
145
+ .then((info) => info.mode)
146
+ .catch(() => undefined)
147
+
148
+ await writeFile(path, content)
149
+
150
+ if (mode !== undefined) await chmod(path, mode)
151
+ }
@@ -0,0 +1,228 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
+ import {
4
+ type Frontmatter,
5
+ parseFrontmatter,
6
+ readField,
7
+ } from '@/indexes/frontmatter'
8
+
9
+ /** Returned when a skill folder carries no `REQUIREMENT.md`, the gating check. */
10
+ export const EXIT_MISSING_REQUIREMENT = 2
11
+
12
+ /** The description ceiling stated in the frontmatter rules of `standards/skill.md`. */
13
+ export const DESCRIPTION_LIMIT = 1024
14
+
15
+ /** The two headings the requirement template declares, matched at any level. */
16
+ export const REQUIREMENT_SECTIONS: readonly string[] = ['Gap', 'Must']
17
+
18
+ /**
19
+ * Both trees the standard governs. `claude/skills/` ships to a target and
20
+ * `.claude/skills/` stays here, and every rule measured below applies to each,
21
+ * so a corpus reading one of them reports a pass over half the subject.
22
+ */
23
+ export const CORPORA: readonly string[] = [
24
+ join('claude', 'skills'),
25
+ join('.claude', 'skills'),
26
+ ]
27
+
28
+ /**
29
+ * The one reason this audit refuses. A project carrying neither corpus is the
30
+ * ordinary state of a target that has not adopted either skills convention,
31
+ * the same absence `no-skills` reads for the shipped citation reach check.
32
+ */
33
+ export type SkillsAuditRefusal = 'no-corpus'
34
+
35
+ /** Kebab-case, which the standard states as no spaces, capitals, or underscores. */
36
+ const KEBAB_CASE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
37
+
38
+ const HEADING = /^#{1,6}\s+(.+?)\s*$/
39
+
40
+ export interface SkillFinding {
41
+ readonly rel: string
42
+ readonly detail: string
43
+ }
44
+
45
+ export interface CorpusReport {
46
+ /**
47
+ * Left as `join` produced it, where `SkillsCorpus.rel` in `skills-list.ts`
48
+ * normalizes the same spelling to POSIX. This one is an existing JSON field
49
+ * a caller already reads, so the split holds until a branch reading this verb
50
+ * is the one to close it.
51
+ */
52
+ readonly rel: string
53
+ readonly skills: number
54
+ }
55
+
56
+ export interface SkillsAudit {
57
+ readonly corpora: readonly CorpusReport[]
58
+ readonly skills: number
59
+ readonly missingRequirement: readonly string[]
60
+ readonly readme: readonly string[]
61
+ readonly folderName: readonly string[]
62
+ readonly missingDescription: readonly string[]
63
+ readonly nameMismatch: readonly SkillFinding[]
64
+ readonly longDescription: readonly SkillFinding[]
65
+ readonly requirementSections: readonly SkillFinding[]
66
+ }
67
+
68
+ interface SkillSource {
69
+ readonly rel: string
70
+ readonly folder: string
71
+ readonly hasReadme: boolean
72
+ readonly name: string | undefined
73
+ readonly description: string | undefined
74
+ /** Undefined when the folder carries no `REQUIREMENT.md` at all. */
75
+ readonly requirementHeadings: readonly string[] | undefined
76
+ }
77
+
78
+ /**
79
+ * Measures both skill corpora against the rules `standards/skill.md` states
80
+ * mechanically. A corpus the project does not carry is skipped rather than
81
+ * reported, so a target holding only `.claude/skills/` reads as in scope.
82
+ *
83
+ * Reads raw frontmatter rather than `listSkills`, which prefers the folder name
84
+ * over the declared one and so can never surface a disagreement between them.
85
+ */
86
+ export async function auditSkills(root: string): Promise<SkillsAudit> {
87
+ const present = CORPORA.map((rel) => ({ rel, dir: join(root, rel) })).filter(
88
+ (corpus) => existsSync(corpus.dir),
89
+ )
90
+
91
+ const perCorpus = await Promise.all(
92
+ present.map((corpus) => readCorpus(corpus.rel, corpus.dir)),
93
+ )
94
+ const sources = perCorpus.flat()
95
+
96
+ return {
97
+ corpora: present.map((corpus, index) => ({
98
+ rel: corpus.rel,
99
+ skills: perCorpus[index]?.length ?? 0,
100
+ })),
101
+ skills: sources.length,
102
+ missingRequirement: sources
103
+ .filter((source) => source.requirementHeadings === undefined)
104
+ .map((source) => source.rel),
105
+ readme: sources
106
+ .filter((source) => source.hasReadme)
107
+ .map((source) => source.rel),
108
+ folderName: sources
109
+ .filter((source) => !KEBAB_CASE.test(source.folder))
110
+ .map((source) => source.rel),
111
+ missingDescription: sources
112
+ .filter((source) => source.description === undefined)
113
+ .map((source) => source.rel),
114
+ nameMismatch: sources.flatMap(nameFindings),
115
+ longDescription: sources.flatMap(lengthFindings),
116
+ requirementSections: sources.flatMap(sectionFindings),
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Only a required file that is absent sets a failing code, which is a fact.
122
+ * Every other measure is a judgment a reader settles, and failing a push on one
123
+ * teaches contributors to route around the stage.
124
+ */
125
+ export function auditExitCode(report: SkillsAudit): number {
126
+ return report.missingRequirement.length > 0 ? EXIT_MISSING_REQUIREMENT : 0
127
+ }
128
+
129
+ async function readCorpus(rel: string, dir: string): Promise<SkillSource[]> {
130
+ const paths = [
131
+ ...new Bun.Glob('*/SKILL.md').scanSync({ cwd: dir, onlyFiles: true }),
132
+ ].sort()
133
+
134
+ return Promise.all(paths.map((path) => readSkill(rel, dir, dirname(path))))
135
+ }
136
+
137
+ async function readSkill(
138
+ rel: string,
139
+ dir: string,
140
+ folder: string,
141
+ ): Promise<SkillSource> {
142
+ const skillDir = join(dir, folder)
143
+ const requirementPath = join(skillDir, 'REQUIREMENT.md')
144
+
145
+ const [body, requirement] = await Promise.all([
146
+ Bun.file(join(skillDir, 'SKILL.md')).text(),
147
+ existsSync(requirementPath)
148
+ ? Bun.file(requirementPath).text()
149
+ : Promise.resolve(undefined),
150
+ ])
151
+
152
+ const fields = readFields(body)
153
+
154
+ return {
155
+ rel: join(rel, folder),
156
+ folder,
157
+ hasReadme: existsSync(join(skillDir, 'README.md')),
158
+ name: declared(readField(fields, 'name')),
159
+ description: declared(readField(fields, 'description')),
160
+ requirementHeadings:
161
+ requirement === undefined ? undefined : headings(requirement),
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Returns undefined rather than throwing on unparseable YAML, so one malformed
167
+ * body reports as missing its required fields instead of ending the audit
168
+ * before it reaches the rest of the corpus.
169
+ */
170
+ function readFields(body: string): Frontmatter | undefined {
171
+ try {
172
+ return parseFrontmatter(body)
173
+ } catch {
174
+ return undefined
175
+ }
176
+ }
177
+
178
+ /**
179
+ * A key carrying an empty value declares nothing, so blank reads as absent
180
+ * rather than as a name that disagrees with every folder.
181
+ */
182
+ function declared(value: string | undefined): string | undefined {
183
+ const trimmed = value?.trim()
184
+ return trimmed === undefined || trimmed === '' ? undefined : trimmed
185
+ }
186
+
187
+ function headings(source: string): string[] {
188
+ return source
189
+ .split('\n')
190
+ .map((line) => HEADING.exec(line)?.[1])
191
+ .filter((text): text is string => text !== undefined)
192
+ }
193
+
194
+ function nameFindings(source: SkillSource): SkillFinding[] {
195
+ if (source.name === undefined) {
196
+ return [{ rel: source.rel, detail: 'frontmatter declares no name' }]
197
+ }
198
+ if (source.name === source.folder) return []
199
+ return [{ rel: source.rel, detail: `frontmatter name: ${source.name}` }]
200
+ }
201
+
202
+ function lengthFindings(source: SkillSource): SkillFinding[] {
203
+ const { description } = source
204
+ if (description === undefined) return []
205
+ if (description.length <= DESCRIPTION_LIMIT) return []
206
+ return [{ rel: source.rel, detail: `${description.length} characters` }]
207
+ }
208
+
209
+ /**
210
+ * Stays silent on a folder carrying no requirement at all, which the presence
211
+ * check already reports. Counting the same skill twice reads as two defects.
212
+ */
213
+ function sectionFindings(source: SkillSource): SkillFinding[] {
214
+ const { requirementHeadings } = source
215
+ if (requirementHeadings === undefined) return []
216
+
217
+ const missing = REQUIREMENT_SECTIONS.filter(
218
+ (section) => !requirementHeadings.includes(section),
219
+ )
220
+ if (missing.length === 0) return []
221
+
222
+ return [
223
+ {
224
+ rel: join(source.rel, 'REQUIREMENT.md'),
225
+ detail: `missing: ${missing.join(', ')}`,
226
+ },
227
+ ]
228
+ }
@@ -0,0 +1,156 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { execaSync } from 'execa'
4
+ import { gitEnv } from '@/git-env'
5
+
6
+ /**
7
+ * The shipped corpus alone. A session loads a body from the plugin root, which
8
+ * resolves to `claude/skills/`, so the internal tree carries no held copy that
9
+ * could go stale in one.
10
+ *
11
+ * Held with posix separators because it is passed to git as a pathspec. Git
12
+ * emits and accepts posix separators on every platform, so a `join()` form would
13
+ * reach a Windows checkout as `claude\skills`, match no path, and return an
14
+ * empty log that reads as a tree where nothing moved.
15
+ */
16
+ const SHIPPED_SKILLS = 'claude/skills'
17
+
18
+ /** A `%H` line, which is 40 hex characters under sha1 and 64 under sha256. */
19
+ const COMMIT = /^[0-9a-f]{40,64}$/
20
+
21
+ /**
22
+ * A skill body, which is the only file in the folder a session loads. A
23
+ * reference or a requirement beside it is read by whoever opens it, so it holds
24
+ * no copy that outlives the read.
25
+ */
26
+ const BODY = /^claude\/skills\/([^/]+)\/SKILL\.md$/
27
+
28
+ export interface MovedBody {
29
+ readonly name: string
30
+ /** The newest commit that rewrote this body, not the first. */
31
+ readonly commit: string
32
+ }
33
+
34
+ export type DriftReport =
35
+ | {
36
+ readonly kind: 'measured'
37
+ readonly base: string
38
+ readonly head: string
39
+ readonly moved: readonly MovedBody[]
40
+ }
41
+ | { readonly kind: 'unreadable'; readonly reason: string }
42
+
43
+ /**
44
+ * Reads `--name-only` log output into one entry per skill whose body moved.
45
+ *
46
+ * Reverse-chronological input means the first commit naming a body is the
47
+ * newest that rewrote it, so an existing name is never overwritten. A body
48
+ * rewritten twice therefore reports the recent commit, which is the one a
49
+ * session can read to see what it is missing.
50
+ */
51
+ export function parseMovedBodies(output: string): MovedBody[] {
52
+ const moved = new Map<string, string>()
53
+ let commit = ''
54
+
55
+ for (const line of output.split('\n')) {
56
+ const trimmed = line.trim()
57
+ if (trimmed === '') continue
58
+
59
+ if (COMMIT.test(trimmed)) {
60
+ commit = trimmed
61
+ continue
62
+ }
63
+
64
+ const match = BODY.exec(trimmed)
65
+ if (match === null || commit === '') continue
66
+ if (!moved.has(match[1])) moved.set(match[1], commit)
67
+ }
68
+
69
+ return [...moved]
70
+ .map(([name, sha]) => ({ name, commit: sha }))
71
+ .sort((left, right) => left.name.localeCompare(right.name))
72
+ }
73
+
74
+ /**
75
+ * Which shipped skill bodies were rewritten between `ref` and the current
76
+ * `HEAD` of the tree at `root`.
77
+ *
78
+ * This measures a file moving rather than a held copy differing. A session
79
+ * cannot read its own loaded body as bytes, so the comparison runs against
80
+ * history and a session passing a ref older than its oldest load over-reports.
81
+ * That is the safe direction, since confirming a name costs one read of the
82
+ * body while the failure this answers is silence.
83
+ *
84
+ * Every path that cannot answer returns `unreadable` with its own reason. A
85
+ * target project loads the plugin from a marketplace cache with no history
86
+ * behind it, where reporting an empty result would read as a clean tree.
87
+ */
88
+ export function readDrift(root: string, ref: string): DriftReport {
89
+ if (!existsSync(join(root, SHIPPED_SKILLS))) {
90
+ return {
91
+ kind: 'unreadable',
92
+ reason: `No shipped skill corpus under ${root}. Looked for ${SHIPPED_SKILLS}.`,
93
+ }
94
+ }
95
+
96
+ const head = revParse(root, 'HEAD')
97
+ if (head === undefined) {
98
+ return {
99
+ kind: 'unreadable',
100
+ reason: `No git history under ${root}. A plugin loaded from a marketplace cache carries none, so a ref cannot be resolved there.`,
101
+ }
102
+ }
103
+
104
+ const base = revParse(root, ref)
105
+ if (base === undefined) {
106
+ return {
107
+ kind: 'unreadable',
108
+ reason: `Ref ${ref} resolves to no commit in ${root}. Pass a commit this tree carries.`,
109
+ }
110
+ }
111
+
112
+ const result = execaSync(
113
+ 'git',
114
+ [
115
+ '-C',
116
+ root,
117
+ 'log',
118
+ '--name-only',
119
+ '--no-renames',
120
+ '--format=%H',
121
+ `${base}..${head}`,
122
+ '--',
123
+ SHIPPED_SKILLS,
124
+ ],
125
+ { reject: false, env: gitEnv(), extendEnv: false },
126
+ )
127
+
128
+ if (result.exitCode !== 0) {
129
+ return {
130
+ kind: 'unreadable',
131
+ reason: `Reading history between ${ref} and HEAD failed: ${result.stderr || result.stdout}`,
132
+ }
133
+ }
134
+
135
+ return {
136
+ kind: 'measured',
137
+ base,
138
+ head,
139
+ moved: parseMovedBodies(result.stdout),
140
+ }
141
+ }
142
+
143
+ /**
144
+ * The commit a ref names, or `undefined` when the tree carries no repository or
145
+ * no such ref. `^{commit}` is what turns a tag or a tree into the commit behind
146
+ * it, so a caller never compares a ref against an object of another type.
147
+ */
148
+ function revParse(root: string, ref: string): string | undefined {
149
+ const result = execaSync(
150
+ 'git',
151
+ ['-C', root, 'rev-parse', '--verify', '--quiet', `${ref}^{commit}`],
152
+ { reject: false, env: gitEnv(), extendEnv: false },
153
+ )
154
+
155
+ return result.exitCode === 0 ? result.stdout.trim() : undefined
156
+ }