@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,576 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { rm } from 'node:fs/promises'
3
+ import { isAbsolute, relative, resolve } from 'node:path'
4
+ import { copyPreservingMode } from '@/copy'
5
+ import { findInstalledOrigin, readHistoryIndex } from '@/sync/history'
6
+ import {
7
+ type DomainHashes,
8
+ hashFile,
9
+ readStamp,
10
+ type StampSource,
11
+ stampedHashes,
12
+ toStampKey,
13
+ writeStamp,
14
+ } from '@/sync/stamp'
15
+ import { isDirectory } from '@/target'
16
+ import {
17
+ intro,
18
+ isNonInteractive,
19
+ logAdd,
20
+ logError,
21
+ logInfo,
22
+ logStep,
23
+ logWarn,
24
+ outro,
25
+ palette,
26
+ select,
27
+ } from '@/ui'
28
+
29
+ /**
30
+ * One installed file in three path flavours: absolute, relative to the
31
+ * domain's installed root, and relative to the target. Adapters match on
32
+ * `relToRoot` and every log line prints `rel`.
33
+ */
34
+ export interface InstalledFile {
35
+ readonly path: string
36
+ readonly relToRoot: string
37
+ readonly rel: string
38
+ }
39
+
40
+ export interface RetiredSurface {
41
+ readonly path: string
42
+ readonly rel: string
43
+ readonly notice: string
44
+ }
45
+
46
+ export type SyncChange =
47
+ | {
48
+ readonly kind: 'copy'
49
+ readonly source: string
50
+ readonly dest: string
51
+ readonly rel: string
52
+ }
53
+ | { readonly kind: 'delete'; readonly dest: string; readonly rel: string }
54
+
55
+ /**
56
+ * How an installed file compares to its toolkit source, and when it differs,
57
+ * who moved it. `stale` and `customized` need the stamp to tell apart, so
58
+ * `drifted` stays the verdict for a difference no stamp covers.
59
+ *
60
+ * `orphaned` and `stranded` both mean the walk found no source, and they are
61
+ * separate because they need opposite treatment. A project-authored file is
62
+ * orphaned and stays that way forever. A stamped file the toolkit no longer
63
+ * installs to is stranded, which is a relocation waiting on a decision.
64
+ *
65
+ * `missing` is the one state the walk cannot produce on its own, since the
66
+ * walk only iterates files that exist. It comes from `collectMissing`
67
+ * instead, an adapter naming an entitled file the target does not hold.
68
+ */
69
+ export type EntryState =
70
+ | 'matching'
71
+ | 'stale'
72
+ | 'customized'
73
+ | 'drifted'
74
+ | 'orphaned'
75
+ | 'stranded'
76
+ | 'missing'
77
+
78
+ export interface ScanEntry {
79
+ readonly state: EntryState
80
+ readonly rel: string
81
+ /** Toolkit revision this file's content came from, when history proved it. */
82
+ readonly since?: string
83
+ /**
84
+ * Overrides `report`'s generic text for this entry's state. Two producers
85
+ * write one: `collectMissing`, since a stack name is only known to the
86
+ * adapter that resolved it, and an orphan sitting outside the declared
87
+ * project subfolder, since the generic line names no destination.
88
+ */
89
+ readonly notice?: string
90
+ }
91
+
92
+ export interface SyncPlan {
93
+ readonly entries: readonly ScanEntry[]
94
+ readonly retired: readonly RetiredSurface[]
95
+ readonly changes: readonly SyncChange[]
96
+ /** Set when a file needed history to attribute it and this toolkit has none. */
97
+ readonly historyUnavailable: boolean
98
+ }
99
+
100
+ /**
101
+ * What a headless run does once the plan has changes. Domains whose files the
102
+ * toolkit owns apply them. Domains whose files a project is expected to edit
103
+ * refuse, because an unattended overwrite of a customized file is data loss
104
+ * with no prompt in front of it.
105
+ */
106
+ export type NonInteractivePolicy =
107
+ | { readonly kind: 'apply' }
108
+ | {
109
+ readonly kind: 'refuse'
110
+ readonly message: string
111
+ readonly hint: string
112
+ }
113
+
114
+ /**
115
+ * The two holes every domain sync leaves open: where a destination file's
116
+ * source lives, and what counts as a change beyond a plain content diff.
117
+ * Everything else in a sync is identical across gov, snippets, and standards,
118
+ * so it lives in the engine.
119
+ *
120
+ * The optional members exist because one adapter needed each of them. Leaving
121
+ * all three unset reproduces the behavior gov and snippets already had.
122
+ */
123
+ export interface SyncAdapter {
124
+ readonly banner: string
125
+ /** Rendered as `Scanning <label>`. */
126
+ readonly label: string
127
+ readonly missingMessage: string
128
+ /** Noun for the completion count, e.g. `changes`. */
129
+ readonly unit: string
130
+ installedRoot(target: string): string
131
+ locateSource(file: InstalledFile): string | undefined
132
+ /** Surfaces the file walk cannot see, such as a retired doc to delete. */
133
+ collectRetired?(target: string): RetiredSurface[]
134
+ /**
135
+ * Entitled files the walk cannot see because they do not exist yet.
136
+ * Reported as `missing` and queued as no change, since installing one
137
+ * changes what the project is governed by and stays a separate command.
138
+ */
139
+ collectMissing?(target: string): RetiredSurface[]
140
+ /** Dropped from the walk, so neither matching nor orphaned. */
141
+ isExcluded?(file: InstalledFile): boolean
142
+ /**
143
+ * Top-level folder under `installedRoot` that is project-authored by
144
+ * location rather than by the name inference `locateSource` runs.
145
+ * Checked before `locateSource`, so a file here is orphaned even when its
146
+ * name also matches a toolkit source, and never enters the stamp.
147
+ *
148
+ * Declaring it also gives the report a destination to name when the name
149
+ * inference orphans a file sitting anywhere else.
150
+ */
151
+ readonly projectSubdir?: string
152
+ /** Defaults to applying. */
153
+ readonly nonInteractive?: NonInteractivePolicy
154
+ /** Runs on a completed sync, including one with no changes. */
155
+ onComplete?(target: string): Promise<void>
156
+ /** Where this domain's hashes are stamped. Unset domains go unstamped. */
157
+ readonly stamp?: StampSource
158
+ }
159
+
160
+ /**
161
+ * Lists installed markdown, dotfiles included. `Bun.Glob` skips entries
162
+ * beginning with a dot unless `dot` is set, and every domain installs under
163
+ * `.claude/`, so a nested dot-directory would silently drop out of the walk.
164
+ */
165
+ export function listInstalled(root: string, target: string): InstalledFile[] {
166
+ if (!existsSync(root)) return []
167
+
168
+ return [
169
+ ...new Bun.Glob('**/*.md').scanSync({
170
+ cwd: root,
171
+ onlyFiles: true,
172
+ dot: true,
173
+ }),
174
+ ]
175
+ .sort()
176
+ .map((relToRoot) => {
177
+ const path = resolve(root, relToRoot)
178
+ return { path, relToRoot, rel: relative(target, path) }
179
+ })
180
+ }
181
+
182
+ /**
183
+ * Classifies every installed file against its source without writing anything.
184
+ * A file with no source is left alone rather than deleted, which is what keeps
185
+ * project-authored rules alive across a sync.
186
+ */
187
+ export function planSync(adapter: SyncAdapter, target: string): SyncPlan {
188
+ const entries: ScanEntry[] = []
189
+ const changes: SyncChange[] = []
190
+ const unattributed: UnattributedFile[] = []
191
+
192
+ const hashes = stampedHashes(readStamp(target), adapter.stamp?.domain)
193
+ const walked = new Set<string>()
194
+
195
+ for (const file of listInstalled(adapter.installedRoot(target), target)) {
196
+ if (adapter.isExcluded?.(file) === true) continue
197
+ walked.add(toStampKey(file.rel))
198
+
199
+ if (isProjectAuthored(adapter, file)) {
200
+ entries.push({ state: 'orphaned', rel: file.rel })
201
+ continue
202
+ }
203
+
204
+ const source = adapter.locateSource(file)
205
+
206
+ if (source === undefined || !existsSync(source)) {
207
+ entries.push(misplacedOrphan(adapter, target, file))
208
+ continue
209
+ }
210
+
211
+ if (sameContent(source, file.path)) {
212
+ entries.push({ state: 'matching', rel: file.rel })
213
+ continue
214
+ }
215
+
216
+ const state = attribute(hashes, file)
217
+ if (state === 'drifted') {
218
+ unattributed.push({ index: entries.length, source, path: file.path })
219
+ }
220
+
221
+ entries.push({ state, rel: file.rel })
222
+ changes.push({
223
+ kind: 'copy',
224
+ source,
225
+ dest: file.path,
226
+ rel: file.rel,
227
+ })
228
+ }
229
+
230
+ const historyUnavailable = recoverAttribution(adapter, entries, unattributed)
231
+
232
+ entries.push(...strandedByRelocation(target, hashes, walked))
233
+
234
+ for (const surface of adapter.collectMissing?.(target) ?? []) {
235
+ entries.push({ state: 'missing', rel: surface.rel, notice: surface.notice })
236
+ }
237
+
238
+ const retired = adapter.collectRetired?.(target) ?? []
239
+ for (const surface of retired) {
240
+ changes.push({ kind: 'delete', dest: surface.path, rel: surface.rel })
241
+ }
242
+
243
+ return { entries, retired, changes, historyUnavailable }
244
+ }
245
+
246
+ interface UnattributedFile {
247
+ readonly index: number
248
+ readonly source: string
249
+ readonly path: string
250
+ }
251
+
252
+ /**
253
+ * Second pass over the files the stamp could not attribute, which is every file
254
+ * in a target installed before stamping shipped. Matching the installed content
255
+ * against the toolkit's own history recovers the fact a stamp would have held,
256
+ * and a file matching no published version stays unattributed.
257
+ *
258
+ * Runs as one git call for the whole domain rather than one per file, and only
259
+ * when the first pass left something to attribute. Reports whether history was
260
+ * readable at all, so a registry install can say why it fell short instead of
261
+ * reporting every file as a local edit.
262
+ */
263
+ function recoverAttribution(
264
+ adapter: SyncAdapter,
265
+ entries: ScanEntry[],
266
+ unattributed: readonly UnattributedFile[],
267
+ ): boolean {
268
+ const toolkitRoot = adapter.stamp?.toolkitRoot
269
+ if (toolkitRoot === undefined || unattributed.length === 0) return false
270
+
271
+ const index = readHistoryIndex(
272
+ toolkitRoot,
273
+ unattributed.map((file) => relative(toolkitRoot, file.source)),
274
+ )
275
+
276
+ if (index === undefined) return true
277
+
278
+ for (const file of unattributed) {
279
+ const since = findInstalledOrigin(
280
+ index,
281
+ relative(toolkitRoot, file.source),
282
+ file.path,
283
+ )
284
+
285
+ if (since === undefined) continue
286
+ entries[file.index] = { ...entries[file.index], state: 'stale', since }
287
+ }
288
+
289
+ return false
290
+ }
291
+
292
+ export async function applyChanges(
293
+ changes: readonly SyncChange[],
294
+ ): Promise<void> {
295
+ logStep('Applying changes')
296
+
297
+ for (const change of changes) {
298
+ if (change.kind === 'copy') {
299
+ await copyPreservingMode(change.source, change.dest)
300
+ logAdd(change.rel)
301
+ continue
302
+ }
303
+
304
+ await rm(change.dest, { force: true })
305
+ logWarn(`removed ${change.rel}`)
306
+ }
307
+ }
308
+
309
+ export interface SyncRunOptions {
310
+ /** Path the sync refuses to run against, normally the toolkit root. */
311
+ readonly protectedRoot: string
312
+ }
313
+
314
+ /**
315
+ * Runs one domain sync end to end and returns the process exit code. Callers
316
+ * register a command, build an adapter, and hand both to this function.
317
+ */
318
+ export async function runDomainSync(
319
+ adapter: SyncAdapter,
320
+ target: string,
321
+ options: SyncRunOptions,
322
+ ): Promise<number> {
323
+ intro(adapter.banner)
324
+
325
+ const resolved = resolve(target)
326
+
327
+ if (!isDirectory(resolved)) {
328
+ logError(`Target directory not found: ${target}`)
329
+ outro()
330
+ return 1
331
+ }
332
+
333
+ if (resolved === options.protectedRoot) {
334
+ logError(
335
+ 'Cannot run against toolkit root. Files here are the source of truth.',
336
+ )
337
+ outro()
338
+ return 1
339
+ }
340
+
341
+ const plan = planSync(adapter, resolved)
342
+ const { GREEN, GREY, NC } = palette(process.stderr)
343
+
344
+ if (
345
+ !existsSync(adapter.installedRoot(resolved)) &&
346
+ plan.retired.length === 0
347
+ ) {
348
+ logWarn(adapter.missingMessage)
349
+ outro()
350
+ return 0
351
+ }
352
+
353
+ report(adapter, plan)
354
+
355
+ const count = plan.changes.length
356
+ if (count === 0) {
357
+ await adapter.onComplete?.(resolved)
358
+ await recordStamp(adapter, resolved, new Date())
359
+ outro()
360
+ process.stderr.write(`${GREEN}✓ Everything up to date${NC}\n`)
361
+ return 0
362
+ }
363
+
364
+ const policy = adapter.nonInteractive ?? { kind: 'apply' }
365
+ if (
366
+ policy.kind === 'refuse' &&
367
+ isNonInteractive() &&
368
+ hasUnattributedDrift(plan)
369
+ ) {
370
+ logWarn(policy.message)
371
+ logInfo(policy.hint)
372
+ outro()
373
+ return 0
374
+ }
375
+
376
+ const shouldApply = await select({
377
+ message: `Apply ${count} changes?`,
378
+ options: [
379
+ { value: true, label: 'Apply all' },
380
+ { value: false, label: 'Cancel' },
381
+ ],
382
+ nonInteractiveDefault: true,
383
+ })
384
+
385
+ if (!shouldApply) {
386
+ logWarn('Sync cancelled')
387
+ outro()
388
+ return 0
389
+ }
390
+
391
+ await applyChanges(plan.changes)
392
+ await adapter.onComplete?.(resolved)
393
+ await recordStamp(adapter, resolved, new Date())
394
+
395
+ outro()
396
+ process.stderr.write(
397
+ `${GREEN}✓ Sync complete${NC} ${GREY}(${count} ${adapter.unit})${NC}\n`,
398
+ )
399
+ return 0
400
+ }
401
+
402
+ /**
403
+ * Reports in walk order rather than grouped by state, so a long rule tree
404
+ * reads as a directory listing with drift called out in place.
405
+ */
406
+ function report(adapter: SyncAdapter, plan: SyncPlan): void {
407
+ logStep(`Scanning ${adapter.label}`)
408
+
409
+ for (const entry of plan.entries) {
410
+ if (entry.state === 'matching') logInfo(entry.rel)
411
+ else if (entry.state === 'drifted') logWarn(entry.rel)
412
+ else if (entry.state === 'stale')
413
+ logWarn(
414
+ entry.since === undefined
415
+ ? `${entry.rel} (toolkit updated)`
416
+ : `${entry.rel} (toolkit updated since ${entry.since.slice(0, 7)})`,
417
+ )
418
+ else if (entry.state === 'customized')
419
+ logWarn(`${entry.rel} (locally customized)`)
420
+ else if (entry.state === 'stranded')
421
+ logWarn(`${entry.rel} (installed here by an older toolkit, now moved)`)
422
+ else if (entry.state === 'missing')
423
+ logWarn(
424
+ entry.notice ?? `${entry.rel} (listed by the stack, not installed)`,
425
+ )
426
+ else
427
+ logWarn(entry.notice ?? `${entry.rel} (not in toolkit source, skipping)`)
428
+ }
429
+
430
+ for (const surface of plan.retired) {
431
+ logWarn(surface.notice)
432
+ }
433
+
434
+ if (plan.historyUnavailable) {
435
+ logWarn(
436
+ 'Attribution unavailable: this toolkit has no git history to match against.',
437
+ )
438
+ }
439
+ }
440
+
441
+ function sameContent(left: string, right: string): boolean {
442
+ return readFileSync(left).equals(readFileSync(right))
443
+ }
444
+
445
+ /**
446
+ * Splits a difference by cause. Matching the stamp means the file is untouched
447
+ * since install and the toolkit is what moved, so the update is mechanical.
448
+ * Anything else is a local edit, and an uncovered file stays unattributed.
449
+ */
450
+ function attribute(hashes: DomainHashes, file: InstalledFile): EntryState {
451
+ const stamped = hashes[toStampKey(file.rel)]
452
+ if (stamped === undefined) return 'drifted'
453
+
454
+ return stamped === hashFile(file.path) ? 'stale' : 'customized'
455
+ }
456
+
457
+ /**
458
+ * Stamped paths the walk never reached, because the toolkit installs to a root
459
+ * it no longer uses. Reporting them is what makes a relocation visible instead
460
+ * of silent. They are left alone, and queue no change.
461
+ *
462
+ * A key escaping the target is dropped rather than reported, so a hand-edited
463
+ * stamp cannot make the report name paths outside the project.
464
+ */
465
+ function strandedByRelocation(
466
+ target: string,
467
+ hashes: DomainHashes,
468
+ walked: ReadonlySet<string>,
469
+ ): ScanEntry[] {
470
+ const entries: ScanEntry[] = []
471
+
472
+ for (const key of Object.keys(hashes).sort()) {
473
+ if (walked.has(key)) continue
474
+
475
+ const path = resolve(target, key)
476
+ if (!isInside(target, path) || !existsSync(path)) continue
477
+
478
+ entries.push({ state: 'stranded', rel: relative(target, path) })
479
+ }
480
+
481
+ return entries
482
+ }
483
+
484
+ /**
485
+ * `Bun.Glob` reports `relToRoot` with `/` separators regardless of platform,
486
+ * so the declared subfolder is compared against the walk's first segment
487
+ * rather than through a path-aware join.
488
+ */
489
+ function isProjectAuthored(adapter: SyncAdapter, file: InstalledFile): boolean {
490
+ if (adapter.projectSubdir === undefined) return false
491
+ return file.relToRoot.split('/')[0] === adapter.projectSubdir
492
+ }
493
+
494
+ /**
495
+ * An orphan the name inference caught rather than the location test. No source
496
+ * name matched, which is what a project-authored file looks like and also what
497
+ * a file the toolkit shipped and later renamed looks like, so the line offers
498
+ * the destination on a condition rather than asserting who wrote the file.
499
+ * Moving a toolkit leftover into the project subfolder would mark it the
500
+ * project's permanently, and only the operator can tell the two apart.
501
+ *
502
+ * The stamp cannot tell them apart either. `recordStamp` skips a file whose
503
+ * source is gone and `writeStamp` replaces the domain's whole `files` map, so
504
+ * a renamed rule's entry survives exactly one sync past the rename, and a
505
+ * target installed before stamping shipped has no entry to read at all.
506
+ *
507
+ * Naming the destination is all this does. Moving the file rewrites a path the
508
+ * project's own rules, skills, and docs may cite, so the sync leaves it where
509
+ * it is.
510
+ */
511
+ function misplacedOrphan(
512
+ adapter: SyncAdapter,
513
+ target: string,
514
+ file: InstalledFile,
515
+ ): ScanEntry {
516
+ const subdir = adapter.projectSubdir
517
+ if (subdir === undefined) return { state: 'orphaned', rel: file.rel }
518
+
519
+ const belongs = relative(
520
+ target,
521
+ resolve(adapter.installedRoot(target), subdir, file.relToRoot),
522
+ )
523
+
524
+ return {
525
+ state: 'orphaned',
526
+ rel: file.rel,
527
+ notice: `${file.rel} (not in toolkit source, skipping. Move it to ${belongs} if the project authored it.)`,
528
+ }
529
+ }
530
+
531
+ function isInside(target: string, path: string): boolean {
532
+ const rel = relative(target, path)
533
+ return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)
534
+ }
535
+
536
+ /**
537
+ * Drift a refusing domain must not touch unattended. A file proven to match
538
+ * what was installed carries no local edit to lose, so it does not count.
539
+ */
540
+ function hasUnattributedDrift(plan: SyncPlan): boolean {
541
+ return plan.entries.some(
542
+ (entry) => entry.state === 'customized' || entry.state === 'drifted',
543
+ )
544
+ }
545
+
546
+ /**
547
+ * Records what the toolkit placed, after the copies land, so a partial apply
548
+ * that throws leaves the previous stamp rather than a claim the target does not
549
+ * meet. A file with no source, or one orphaned by location, is
550
+ * project-authored and stays out.
551
+ *
552
+ * Reads the installed tree rather than the caller's file list, so a partial
553
+ * install still stamps the domain's whole installed set.
554
+ */
555
+ export async function recordStamp(
556
+ adapter: SyncAdapter,
557
+ target: string,
558
+ now: Date,
559
+ ): Promise<void> {
560
+ const stampSource = adapter.stamp
561
+ if (stampSource === undefined) return
562
+
563
+ const hashes: Record<string, string> = {}
564
+
565
+ for (const file of listInstalled(adapter.installedRoot(target), target)) {
566
+ if (adapter.isExcluded?.(file) === true) continue
567
+ if (isProjectAuthored(adapter, file)) continue
568
+
569
+ const source = adapter.locateSource(file)
570
+ if (source === undefined || !existsSync(source)) continue
571
+
572
+ hashes[toStampKey(file.rel)] = hashFile(file.path)
573
+ }
574
+
575
+ await writeStamp(target, stampSource, hashes, now)
576
+ }