@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
package/src/ui.ts ADDED
@@ -0,0 +1,266 @@
1
+ export interface Palette {
2
+ readonly GREEN: string
3
+ readonly RED: string
4
+ readonly YELLOW: string
5
+ readonly WHITE: string
6
+ readonly GREY: string
7
+ readonly NC: string
8
+ }
9
+
10
+ const COLOR: Palette = {
11
+ GREEN: '\x1b[0;32m',
12
+ RED: '\x1b[0;31m',
13
+ YELLOW: '\x1b[0;33m',
14
+ WHITE: '\x1b[1;37m',
15
+ GREY: '\x1b[0;90m',
16
+ NC: '\x1b[0m',
17
+ }
18
+
19
+ /**
20
+ * The blank palette keeps every frame character and drops only the escapes, so
21
+ * a captured run still reads as one block.
22
+ */
23
+ const PLAIN: Palette = {
24
+ GREEN: '',
25
+ RED: '',
26
+ YELLOW: '',
27
+ WHITE: '',
28
+ GREY: '',
29
+ NC: '',
30
+ }
31
+
32
+ /**
33
+ * `NO_COLOR` follows the published convention, where any non-empty value turns
34
+ * color off whatever the value says.
35
+ */
36
+ export function supportsColor(stream: { isTTY?: boolean }): boolean {
37
+ const optOut = process.env.NO_COLOR
38
+ if (optOut !== undefined && optOut !== '') return false
39
+ return stream.isTTY === true
40
+ }
41
+
42
+ /**
43
+ * The question is asked per stream rather than once for the process. The framed
44
+ * output goes to stderr and a structured record to stdout, so a run piping only
45
+ * its data keeps a terminal on stderr and keeps its color there. This is a
46
+ * third question again from `isNonInteractive`, which answers whether a caller
47
+ * can be prompted rather than whether a destination renders escapes.
48
+ *
49
+ * Read at write time rather than at import, so nothing freezes an answer taken
50
+ * before the caller's environment was in place.
51
+ */
52
+ export function palette(stream: { isTTY?: boolean }): Palette {
53
+ return supportsColor(stream) ? COLOR : PLAIN
54
+ }
55
+
56
+ export function intro(title: string): void {
57
+ const { GREY, NC, WHITE } = palette(process.stderr)
58
+ process.stderr.write(`${GREY}┌${NC}\n${GREY}│${NC} ${WHITE}${title}${NC}\n`)
59
+ }
60
+
61
+ /**
62
+ * Timeline log levels matching `scripts/lib/ui.sh`, so a migrated command
63
+ * prints the same frame as the bash it replaced. All output is stderr,
64
+ * leaving stdout clean for JSON and lists.
65
+ */
66
+ export function logInfo(message: string): void {
67
+ const { GREEN, GREY, NC } = palette(process.stderr)
68
+ process.stderr.write(`${GREY}│${NC} ${GREEN}✓${NC} ${message}\n`)
69
+ }
70
+
71
+ export function logWarn(message: string): void {
72
+ const { GREY, NC, YELLOW } = palette(process.stderr)
73
+ process.stderr.write(`${GREY}│${NC} ${YELLOW}!${NC} ${message}\n`)
74
+ }
75
+
76
+ export function logAdd(message: string): void {
77
+ const { GREEN, GREY, NC } = palette(process.stderr)
78
+ process.stderr.write(`${GREY}│${NC} ${GREEN}+${NC} ${message}\n`)
79
+ }
80
+
81
+ export function logRemove(message: string): void {
82
+ const { GREY, NC, RED } = palette(process.stderr)
83
+ process.stderr.write(`${GREY}│${NC} ${RED}-${NC} ${message}\n`)
84
+ }
85
+
86
+ /**
87
+ * Renders the `✗` shape `docs/agents/output-shape.md` specifies for a failure inside an
88
+ * open frame. It does not exit, so the caller closes the frame and returns an
89
+ * exit code rather than terminating mid-write.
90
+ */
91
+ export function logError(message: string): void {
92
+ const { GREY, NC, RED } = palette(process.stderr)
93
+ process.stderr.write(`${GREY}│${NC} ${RED}✗${NC} ${message}\n`)
94
+ }
95
+
96
+ export function logStep(message: string): void {
97
+ const { GREY, NC, WHITE } = palette(process.stderr)
98
+ process.stderr.write(`${GREY}│${NC}\n${GREY}├${NC} ${WHITE}${message}${NC}\n`)
99
+ }
100
+
101
+ export function outro(): void {
102
+ const { GREY, NC } = palette(process.stderr)
103
+ process.stderr.write(`${GREY}└${NC}\n`)
104
+ }
105
+
106
+ /**
107
+ * Indents borrowed output inside an open frame, matching `pipe_output` in
108
+ * `scripts/lib/ui.sh`. Used for a document the command did not compose itself,
109
+ * such as a pull request body or the output of a git mutation.
110
+ */
111
+ export function pipeOutput(text: string): void {
112
+ const { GREY, NC } = palette(process.stderr)
113
+ const lines = text.replace(/\n$/, '').split('\n')
114
+ process.stderr.write(
115
+ `${lines.map((line) => `${GREY}│${NC} ${line}`).join('\n')}\n`,
116
+ )
117
+ }
118
+
119
+ /** Counts a noun for a report line, where the plural is the bare `s` form. */
120
+ export function plural(count: number, noun: string): string {
121
+ return `${count} ${noun}${count === 1 ? '' : 's'}`
122
+ }
123
+
124
+ export function frameError(message: string): void {
125
+ const { GREY, NC, RED } = palette(process.stderr)
126
+ process.stderr.write(
127
+ `${GREY}┌${NC}\n${GREY}│${NC} ${RED}✗${NC} ${message}\n${GREY}└${NC}\n`,
128
+ )
129
+ }
130
+
131
+ export function frameSuccess(command: string, target: string): void {
132
+ const { GREEN, GREY, NC, WHITE } = palette(process.stderr)
133
+ process.stderr.write(
134
+ `${GREY}┌${NC}\n${GREY}│${NC} ${WHITE}${command}${NC}\n${GREY}│${NC}\n${GREY}│${NC} ${GREEN}✓${NC} ${target}\n${GREY}└${NC}\n`,
135
+ )
136
+ }
137
+
138
+ /**
139
+ * The retired spelling is still honored, and permanently.
140
+ *
141
+ * This variable is the documented way to drive any command headlessly, so
142
+ * every skill body, CI job, and operator script already written sets the old
143
+ * name. Those callers live in other repositories and in plugin caches this
144
+ * release cannot reach, and the failure mode of dropping the fallback is a
145
+ * command that quietly waits for a TTY nobody is watching rather than one that
146
+ * refuses and says why.
147
+ */
148
+ export function isNonInteractive(): boolean {
149
+ return (
150
+ process.env.CANON_NON_INTERACTIVE === '1' ||
151
+ // canon-keep-retired
152
+ process.env.AITK_NON_INTERACTIVE === '1'
153
+ )
154
+ }
155
+
156
+ /**
157
+ * `nonInteractiveDefault` opts a prompt into the `select_option` behavior from
158
+ * `scripts/lib/ui.sh`, where `CANON_NON_INTERACTIVE=1` resolves to the first
159
+ * option instead of failing. Callers that would rather fail loudly than pick
160
+ * for the user leave it off.
161
+ */
162
+ export async function select<Value>(opts: {
163
+ message: string
164
+ options: { value: Value; label: string; hint?: string }[]
165
+ nonInteractiveDefault?: boolean
166
+ }): Promise<Value> {
167
+ const { message, options } = opts
168
+ const { GREEN, GREY, NC, RED, WHITE } = palette(process.stderr)
169
+ const count = options.length
170
+ let cursor = 0
171
+
172
+ if (opts.nonInteractiveDefault && isNonInteractive()) {
173
+ process.stderr.write(
174
+ `${GREY}│${NC}\n${GREY}◇${NC} ${message} ${WHITE}${options[0].label}${NC}\n`,
175
+ )
176
+ return options[0].value
177
+ }
178
+
179
+ const render = (): void => {
180
+ let out = `${GREY}│${NC}\n${GREEN}◆${NC} ${message}\n`
181
+ for (let i = 0; i < count; i++) {
182
+ const label = options[i].label
183
+ const hint = options[i].hint ? ` ${GREY}${options[i].hint}${NC}` : ''
184
+ if (i === cursor) {
185
+ out += `${GREY}│${NC} ${GREEN}❯ ${label}${NC}${hint}\n`
186
+ } else {
187
+ out += `${GREY}│${NC} ${GREY}${label}${NC}${hint}\n`
188
+ }
189
+ }
190
+ process.stderr.write(out)
191
+ }
192
+
193
+ const clear = (): void => {
194
+ process.stderr.write(`\x1b[${count + 2}A\x1b[J`)
195
+ }
196
+
197
+ return new Promise<Value>((resolve) => {
198
+ if (!process.stdin.isTTY) {
199
+ process.stderr.write(
200
+ `${GREY}│${NC} ${RED}✗${NC} ${message} requires a TTY. Pass an argument or set CANON_NON_INTERACTIVE=1.\n${GREY}└${NC}\n`,
201
+ )
202
+ process.exit(1)
203
+ }
204
+
205
+ const wasRaw = process.stdin.isRaw
206
+ process.stdin.setRawMode(true)
207
+ process.stdin.resume()
208
+
209
+ render()
210
+
211
+ const onData = (data: Buffer): void => {
212
+ const key = data.toString()
213
+
214
+ if (key === '\x1b[A' || key === 'k') {
215
+ cursor = (cursor - 1 + count) % count
216
+ clear()
217
+ render()
218
+ } else if (key === '\x1b[B' || key === 'j') {
219
+ cursor = (cursor + 1) % count
220
+ clear()
221
+ render()
222
+ } else if (key === '\r' || key === '\n') {
223
+ cleanup()
224
+ clear()
225
+ process.stderr.write(
226
+ `${GREY}│${NC}\n${GREY}◇${NC} ${message} ${WHITE}${options[cursor].label}${NC}\n`,
227
+ )
228
+ resolve(options[cursor].value)
229
+ } else if (key === '\x1b' || key === 'q' || key === '\x03') {
230
+ cleanup()
231
+ clear()
232
+ process.stderr.write(
233
+ `${GREY}│${NC}\n${GREY}◇${NC} ${message} ${RED}Cancelled${NC}\n${GREY}└${NC}\n`,
234
+ )
235
+ process.exit(1)
236
+ }
237
+ }
238
+
239
+ const cleanup = (): void => {
240
+ process.stdin.removeListener('data', onData)
241
+ process.stdin.setRawMode(wasRaw ?? false)
242
+ process.stdin.pause()
243
+ }
244
+
245
+ process.stdin.on('data', onData)
246
+ })
247
+ }
248
+
249
+ export async function confirm(opts: {
250
+ message: string
251
+ active?: string
252
+ inactive?: string
253
+ }): Promise<boolean> {
254
+ const active = opts.active ?? 'Yes'
255
+ const inactive = opts.inactive ?? 'No'
256
+
257
+ const result = await select({
258
+ message: opts.message,
259
+ options: [
260
+ { value: true, label: active },
261
+ { value: false, label: inactive },
262
+ ],
263
+ })
264
+
265
+ return result
266
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * A `major.minor.patch` core with an optional prerelease tail. The registry
3
+ * publishes both shapes under the same dist tag, so a comparison that only
4
+ * understood the core would read `1.0.0-rc.1` as unparseable and report the
5
+ * whole lookup as unknown.
6
+ */
7
+ const VERSION =
8
+ /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/
9
+
10
+ export interface ParsedVersion {
11
+ readonly major: number
12
+ readonly minor: number
13
+ readonly patch: number
14
+ /** Absent on a release, which sorts above every prerelease of the same core. */
15
+ readonly prerelease?: string
16
+ }
17
+
18
+ export function parseVersion(raw: string): ParsedVersion | undefined {
19
+ const match = VERSION.exec(raw.trim())
20
+ if (match === null) return undefined
21
+
22
+ return {
23
+ major: Number(match[1]),
24
+ minor: Number(match[2]),
25
+ patch: Number(match[3]),
26
+ ...(match[4] === undefined ? {} : { prerelease: match[4] }),
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Negative when `left` is older, positive when it is newer, zero when the two
32
+ * name the same version.
33
+ *
34
+ * Prerelease identifiers compare as whole strings rather than dot segment by
35
+ * dot segment, which is narrower than semver states. Every version this repo
36
+ * publishes is a plain core, so the ordering inside a prerelease series decides
37
+ * nothing here, and the one comparison that matters is that any prerelease
38
+ * sorts below the release sharing its core.
39
+ */
40
+ export function compareVersions(
41
+ left: ParsedVersion,
42
+ right: ParsedVersion,
43
+ ): number {
44
+ if (left.major !== right.major) return left.major - right.major
45
+ if (left.minor !== right.minor) return left.minor - right.minor
46
+ if (left.patch !== right.patch) return left.patch - right.patch
47
+
48
+ if (left.prerelease === right.prerelease) return 0
49
+ if (left.prerelease === undefined) return 1
50
+ if (right.prerelease === undefined) return -1
51
+
52
+ return left.prerelease < right.prerelease ? -1 : 1
53
+ }
@@ -0,0 +1,40 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { PROJECT_ROOT } from '@/project-root'
4
+
5
+ /** What a reader prints in place of a field the manifest did not carry. */
6
+ export const UNKNOWN_LABEL = 'unknown'
7
+
8
+ /**
9
+ * Either field is absent when the manifest could not be read or did not carry
10
+ * it. They are optional rather than sentinel strings so a caller has to narrow
11
+ * before using one, which is what makes a name the manifest never supplied
12
+ * impossible to interpolate into a shell command. A sentinel typed `string`
13
+ * reads as an ordinary value at every call site and hides that case.
14
+ */
15
+ export interface InstalledPackage {
16
+ readonly name?: string
17
+ readonly version?: string
18
+ }
19
+
20
+ /**
21
+ * Read at runtime rather than inlined, because a literal is a second place the
22
+ * version lives and it stopped tracking `package.json` at `0.1.0`. The release
23
+ * tool writes one file and this follows it. `package.json` ships in every npm
24
+ * tarball regardless of the `files` list, so the read resolves from a registry
25
+ * install as well as from a clone.
26
+ *
27
+ * This sits apart from the skew read so `src/cli.ts` can name the version on
28
+ * every invocation without pulling the registry lookup into the startup import
29
+ * graph of a CLI that compiles nothing ahead of time.
30
+ */
31
+ export function readInstalled(root: string = PROJECT_ROOT): InstalledPackage {
32
+ try {
33
+ const raw = readFileSync(join(root, 'package.json'), 'utf8')
34
+ const parsed = JSON.parse(raw) as { name?: string; version?: string }
35
+
36
+ return { name: parsed.name, version: parsed.version }
37
+ } catch {
38
+ return {}
39
+ }
40
+ }
@@ -0,0 +1,67 @@
1
+ export type ManagerId = 'bun' | 'pnpm' | 'yarn' | 'npm'
2
+
3
+ export interface Manager {
4
+ readonly id: ManagerId
5
+ /** The path segment the detection matched, so a wrong read is correctable. */
6
+ readonly evidence: string
7
+ }
8
+
9
+ /**
10
+ * Segments that only appear in one manager's global install tree, checked
11
+ * before the `node_modules` fallback because every one of these trees contains
12
+ * a `node_modules` too.
13
+ */
14
+ const SIGNATURES: readonly (readonly [ManagerId, string])[] = [
15
+ ['bun', '.bun'],
16
+ ['pnpm', 'pnpm'],
17
+ ['pnpm', '.pnpm'],
18
+ ['yarn', 'yarn'],
19
+ ['yarn', '.yarn'],
20
+ ]
21
+
22
+ /**
23
+ * Which package manager installed the package rooted at `root`, read off the
24
+ * install path rather than guessed from what is on `PATH`.
25
+ *
26
+ * Removing the guess is the whole case for the upgrade verb, so a detection
27
+ * that cannot be read back is worth no more than the guess it replaced. The
28
+ * evidence travels with the answer and the verb prints it before running
29
+ * anything, which lets an operator correct a wrong read without the detection
30
+ * having to be right every time.
31
+ *
32
+ * Returns `undefined` for a path outside any install tree, which is a source
33
+ * checkout. That is not a case to guess at either: reinstalling over a clone
34
+ * would replace what the operator is working in.
35
+ */
36
+ export function detectManager(root: string): Manager | undefined {
37
+ const segments = root.split(/[/\\]/).filter((segment) => segment !== '')
38
+
39
+ for (const [id, signature] of SIGNATURES) {
40
+ if (segments.includes(signature)) return { id, evidence: signature }
41
+ }
42
+
43
+ if (segments.includes('node_modules')) {
44
+ return { id: 'npm', evidence: 'node_modules' }
45
+ }
46
+
47
+ return undefined
48
+ }
49
+
50
+ /** The global reinstall each manager spells, pinned to the newest published. */
51
+ export function installCommand(
52
+ manager: ManagerId,
53
+ name: string,
54
+ ): readonly string[] {
55
+ const spec = `${name}@latest`
56
+
57
+ switch (manager) {
58
+ case 'bun':
59
+ return ['bun', 'add', '--global', spec]
60
+ case 'pnpm':
61
+ return ['pnpm', 'add', '--global', spec]
62
+ case 'yarn':
63
+ return ['yarn', 'global', 'add', spec]
64
+ case 'npm':
65
+ return ['npm', 'install', '--global', spec]
66
+ }
67
+ }
@@ -0,0 +1,192 @@
1
+ import { PROJECT_ROOT } from '@/project-root'
2
+ import { compareVersions, parseVersion } from '@/version/compare'
3
+ import {
4
+ type InstalledPackage,
5
+ readInstalled,
6
+ UNKNOWN_LABEL,
7
+ } from '@/version/installed'
8
+ import { detectManager } from '@/version/manager'
9
+
10
+ const REGISTRY = 'https://registry.npmjs.org'
11
+
12
+ /**
13
+ * Short enough that a check waiting on a dead network still returns inside the
14
+ * time an operator would give the command anyway. The skew line is one section
15
+ * of a report the rest of which needs no network at all, so the budget is set
16
+ * against how long the report may be held up rather than against how long the
17
+ * registry usually takes.
18
+ */
19
+ const LOOKUP_TIMEOUT_MS = 3_000
20
+
21
+ /**
22
+ * Three states and no fourth. `unknown` covers every way the question could not
23
+ * be answered and carries the reason, which is what a caller reports instead of
24
+ * a version.
25
+ *
26
+ * An installed version ahead of the published one reports `current`. That is a
27
+ * source checkout between a release commit and the publish job, or a local
28
+ * build, and neither is skew. Giving it a state of its own would fire a warning
29
+ * on every maintainer run for a condition with no remedy.
30
+ */
31
+ export type SkewState = 'current' | 'behind' | 'unknown'
32
+
33
+ interface SkewBase {
34
+ readonly name: string
35
+ readonly installed: string
36
+ }
37
+
38
+ /**
39
+ * A union rather than one shape with two optional fields, so a `behind` report
40
+ * cannot exist without the version it is behind and an `unknown` one cannot
41
+ * exist without its reason. Both are what `describeSkew` renders into a line an
42
+ * operator reads, and an optional field renders the word `undefined` there.
43
+ */
44
+ export type SkewReport =
45
+ | (SkewBase & {
46
+ readonly state: 'current' | 'behind'
47
+ readonly latest: string
48
+ })
49
+ | (SkewBase & { readonly state: 'unknown'; readonly reason: string })
50
+
51
+ /** Resolves the newest published version, or throws for `readSkew` to absorb. */
52
+ export type LatestLookup = (name: string) => Promise<string>
53
+
54
+ export interface SkewOptions {
55
+ readonly installed?: InstalledPackage
56
+ readonly lookup?: LatestLookup
57
+ }
58
+
59
+ /**
60
+ * The installed version against the newest published one.
61
+ *
62
+ * Never rejects and never reports through an exit code. `canon sync --check
63
+ * --exit-code` gates CI on drift it measured locally, so a lookup that failed
64
+ * the caller would turn an offline machine into a failing check and the check
65
+ * would be routed around. Every failure lands in `unknown` with its reason.
66
+ */
67
+ export async function readSkew(options: SkewOptions = {}): Promise<SkewReport> {
68
+ const installed = options.installed ?? readInstalled()
69
+ const lookup = options.lookup ?? fetchLatest
70
+ const { name, version } = installed
71
+
72
+ if (version === undefined) {
73
+ return unknown(
74
+ installed,
75
+ 'No version in the package manifest, so there is nothing to compare.',
76
+ )
77
+ }
78
+
79
+ if (name === undefined) {
80
+ return unknown(
81
+ installed,
82
+ 'No name in the package manifest, so the registry has nothing to look up.',
83
+ )
84
+ }
85
+
86
+ const local = parseVersion(version)
87
+ if (local === undefined) {
88
+ return unknown(
89
+ installed,
90
+ `Installed version ${version} is not a version this can parse.`,
91
+ )
92
+ }
93
+
94
+ let raw: string
95
+ try {
96
+ raw = await lookup(name)
97
+ } catch (error) {
98
+ return unknown(installed, `Registry lookup failed: ${describe(error)}`)
99
+ }
100
+
101
+ const published = parseVersion(raw)
102
+ if (published === undefined) {
103
+ return unknown(
104
+ installed,
105
+ `Registry reported ${raw} as the newest version, which is not a version this can parse.`,
106
+ )
107
+ }
108
+
109
+ return {
110
+ state: compareVersions(local, published) < 0 ? 'behind' : 'current',
111
+ name,
112
+ installed: version,
113
+ latest: raw,
114
+ }
115
+ }
116
+
117
+ function unknown(installed: InstalledPackage, reason: string): SkewReport {
118
+ return {
119
+ state: 'unknown',
120
+ name: installed.name ?? UNKNOWN_LABEL,
121
+ installed: installed.version ?? UNKNOWN_LABEL,
122
+ reason,
123
+ }
124
+ }
125
+
126
+ /** The newest published version, or `undefined` when it could not be read. */
127
+ export function latestOf(report: SkewReport): string | undefined {
128
+ return report.state === 'unknown' ? undefined : report.latest
129
+ }
130
+
131
+ function describe(error: unknown): string {
132
+ return error instanceof Error ? error.message : String(error)
133
+ }
134
+
135
+ /**
136
+ * The dist-tag endpoint rather than the full packument, which for this package
137
+ * carries every published manifest and is the larger part of a megabyte. The
138
+ * question is one string and this is the endpoint that answers only it.
139
+ */
140
+ async function fetchLatest(name: string): Promise<string> {
141
+ const response = await fetch(
142
+ `${REGISTRY}/-/package/${encodeURIComponent(name)}/dist-tags`,
143
+ {
144
+ signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS),
145
+ headers: { accept: 'application/json' },
146
+ },
147
+ )
148
+
149
+ if (!response.ok) {
150
+ throw new Error(`registry returned ${response.status}`)
151
+ }
152
+
153
+ const tags = (await response.json()) as Record<string, unknown>
154
+ const latest = tags.latest
155
+
156
+ if (typeof latest !== 'string') {
157
+ throw new Error('registry reported no latest dist-tag')
158
+ }
159
+
160
+ return latest
161
+ }
162
+
163
+ /**
164
+ * One line naming the state, for a caller that renders the skew beside sections
165
+ * it does not own. Held here so `canon sync --check` and `canon claude skills
166
+ * drift` cannot word the same three states differently.
167
+ *
168
+ * The remedy is chosen by the same detection `canon upgrade` runs, because both
169
+ * callers run from a source checkout routinely and that is where the verb
170
+ * refuses. Naming it unconditionally sends a contributor whose clone sits a
171
+ * release behind to a command that declines. The read is a match against the
172
+ * root string rather than a filesystem call, so the line stays cheap.
173
+ */
174
+ export function describeSkew(
175
+ report: SkewReport,
176
+ root: string = PROJECT_ROOT,
177
+ ): string {
178
+ if (report.state === 'unknown') {
179
+ return `Installed ${report.installed}, published unknown. ${report.reason}`
180
+ }
181
+
182
+ if (report.state === 'current') {
183
+ return `Installed ${report.installed}, which is the newest published.`
184
+ }
185
+
186
+ const remedy =
187
+ detectManager(root) === undefined
188
+ ? 'This is a source checkout, so pull rather than reinstalling.'
189
+ : 'Run `canon upgrade`.'
190
+
191
+ return `Installed ${report.installed}, published ${report.latest}. ${remedy}`
192
+ }
@@ -0,0 +1,85 @@
1
+ import { existsSync, statSync } from 'node:fs'
2
+ import { mkdir, writeFile } from 'node:fs/promises'
3
+ import { join } from 'node:path'
4
+
5
+ const WIKI_DIR = join('.claude', 'wiki')
6
+ const LEGACY_WIKI_DIR = 'wiki'
7
+ const INDEX_FILE = 'index.md'
8
+
9
+ export const WIKI_DIR_REL = `${WIKI_DIR}/`
10
+ export const WIKI_INDEX_REL = join(WIKI_DIR, INDEX_FILE)
11
+ export const LEGACY_WIKI_DIR_REL = `${LEGACY_WIKI_DIR}/`
12
+
13
+ const INDEX_SEED = `---
14
+ title: Wiki
15
+ subtitle: Reference pages for tools, workflows, and concepts
16
+ ---
17
+
18
+ # Wiki
19
+
20
+ Reference pages for tools, workflows, and concepts.
21
+ `
22
+
23
+ export type WikiChange = 'dir' | 'index'
24
+
25
+ export interface WikiPlan {
26
+ readonly changes: readonly WikiChange[]
27
+ readonly hasIndex: boolean
28
+ readonly hasLegacyWiki: boolean
29
+ }
30
+
31
+ export function wikiDir(target: string): string {
32
+ return join(target, WIKI_DIR)
33
+ }
34
+
35
+ /**
36
+ * A target scaffolded before the wiki moved under `.claude/` keeps its pages at
37
+ * the root. The verb reports that folder rather than moving it, because a move
38
+ * is a migration and the `migration-*` skills own those.
39
+ */
40
+ export function legacyWikiDir(target: string): string {
41
+ return join(target, LEGACY_WIKI_DIR)
42
+ }
43
+
44
+ /**
45
+ * Stands in for the `cd "$target"` in the bash `guard_root`, which validated
46
+ * the target existed as a side effect. Without it a typo'd path scaffolds a
47
+ * whole new tree, since the apply step creates parents.
48
+ */
49
+ export function isWikiTarget(target: string): boolean {
50
+ try {
51
+ return statSync(target).isDirectory()
52
+ } catch {
53
+ return false
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Reports what a scaffold would create without writing. The directory and the
59
+ * index are tracked separately because a target can carry one without the
60
+ * other, and only the index has content worth preserving.
61
+ */
62
+ export function planWikiInit(target: string): WikiPlan {
63
+ const dir = wikiDir(target)
64
+ const index = join(dir, INDEX_FILE)
65
+ const changes: WikiChange[] = []
66
+
67
+ if (!existsSync(dir)) changes.push('dir')
68
+
69
+ const hasIndex = existsSync(index)
70
+ if (!hasIndex) changes.push('index')
71
+
72
+ return { changes, hasIndex, hasLegacyWiki: existsSync(legacyWikiDir(target)) }
73
+ }
74
+
75
+ export async function applyWikiInit(
76
+ target: string,
77
+ plan: WikiPlan,
78
+ ): Promise<void> {
79
+ const dir = wikiDir(target)
80
+ await mkdir(dir, { recursive: true })
81
+
82
+ if (plan.changes.includes('index')) {
83
+ await writeFile(join(dir, INDEX_FILE), INDEX_SEED)
84
+ }
85
+ }