@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,786 @@
1
+ import { relative } from 'node:path'
2
+ import type { Command } from 'commander'
3
+ import { type LessonOutcome, planLesson } from '@/teach/lesson'
4
+ import {
5
+ defineTerms,
6
+ type ListOutcome,
7
+ listWorkspaces,
8
+ type OpenOutcome,
9
+ openWorkspace,
10
+ type ReadOutcome,
11
+ readWorkspace,
12
+ recordSources,
13
+ type Source,
14
+ type SourceOutcome,
15
+ type TeachRefused,
16
+ type Term,
17
+ type TermOutcome,
18
+ type WorkspaceSummary,
19
+ } from '@/teach/workspace'
20
+ import {
21
+ intro,
22
+ logAdd,
23
+ logError,
24
+ logInfo,
25
+ logStep,
26
+ logWarn,
27
+ outro,
28
+ pipeOutput,
29
+ } from '@/ui'
30
+ import { mainWorktreeRoot } from '@/worktree'
31
+
32
+ /**
33
+ * A pair as the command line spells it, split on the first `=` so a value
34
+ * carrying its own separator survives. A source URL is the case that needs it.
35
+ */
36
+ const PAIR = /^([^=]+)=([\s\S]+)$/
37
+
38
+ interface ListCommandOptions {
39
+ readonly json?: boolean
40
+ readonly root?: string
41
+ }
42
+
43
+ interface OpenCommandOptions {
44
+ readonly date?: string
45
+ readonly json?: boolean
46
+ readonly outOfScope?: readonly string[]
47
+ readonly root?: string
48
+ readonly startingPoint?: string
49
+ readonly subject?: string
50
+ readonly success?: readonly string[]
51
+ readonly title?: string
52
+ }
53
+
54
+ interface ResourceCommandOptions {
55
+ readonly json?: boolean
56
+ readonly lead?: readonly string[]
57
+ readonly read?: readonly string[]
58
+ readonly root?: string
59
+ }
60
+
61
+ interface LessonCommandOptions {
62
+ readonly json?: boolean
63
+ readonly options?: string
64
+ readonly questions?: string
65
+ readonly root?: string
66
+ readonly slug?: string
67
+ }
68
+
69
+ interface GlossaryCommandOptions {
70
+ readonly firstSeen?: string
71
+ readonly json?: boolean
72
+ readonly root?: string
73
+ readonly term?: readonly string[]
74
+ }
75
+
76
+ export function register(program: Command): void {
77
+ const teach = program
78
+ .command('teach')
79
+ .description('Manage learning workspaces in .claude/teach/')
80
+ .helpOption('-h, --help', 'Show this help message')
81
+
82
+ teach
83
+ .command('list')
84
+ .description('List learning workspaces, or what one workspace holds')
85
+ .argument('[topic]', 'Workspace folder or topic, as in regular-expressions')
86
+ .helpOption('-h, --help', 'Show this help message')
87
+ .option('--json', 'Emit a machine-readable record on stdout')
88
+ .option('--root <path>', 'Teach root, defaulting to the main worktree')
89
+ .addHelpText(
90
+ 'after',
91
+ [
92
+ '',
93
+ 'Exit codes:',
94
+ ' 0 the workspaces or their contents were listed',
95
+ ' 1 refused, with the reason on stderr or in the JSON record',
96
+ '',
97
+ 'With no topic it reports one line per workspace and the ordinal an',
98
+ 'open would take. With one it reports the files behind each count.',
99
+ '',
100
+ 'A workspace not named NN-<topic> is still listed. It sorts last and',
101
+ 'moves no ordinal, since dropping it hides the folder needing a fix.',
102
+ '',
103
+ 'Examples:',
104
+ ' canon teach list',
105
+ ' canon teach list regular-expressions --json',
106
+ '',
107
+ ].join('\n'),
108
+ )
109
+ .action(async (topic: string | undefined, opts: ListCommandOptions) => {
110
+ process.exitCode = await runList(topic, opts)
111
+ })
112
+
113
+ teach
114
+ .command('open')
115
+ .description('Open a workspace at the next ordinal with its required files')
116
+ .argument('<topic>', 'Kebab-case topic, as in regular-expressions')
117
+ .helpOption('-h, --help', 'Show this help message')
118
+ .option('--subject <line>', 'One line stating what the workspace covers')
119
+ .option('--starting-point <text>', 'What the learner already knows')
120
+ .option(
121
+ '--success <line>',
122
+ 'Observable thing the learner will be able to do, repeatable',
123
+ collect,
124
+ [] as string[],
125
+ )
126
+ .option(
127
+ '--out-of-scope <line>',
128
+ 'What this workspace does not cover, repeatable',
129
+ collect,
130
+ [] as string[],
131
+ )
132
+ .option('--title <text>', 'Title, defaulting to the topic in sentence case')
133
+ .option('--date <YYYY-MM-DD>', 'Opening date, defaulting to today')
134
+ .option('--json', 'Emit a machine-readable record on stdout')
135
+ .option('--root <path>', 'Teach root, defaulting to the main worktree')
136
+ .addHelpText(
137
+ 'after',
138
+ [
139
+ '',
140
+ 'Exit codes:',
141
+ ' 0 the workspace was created',
142
+ ' 1 refused, with the reason on stderr or in the JSON record',
143
+ '',
144
+ 'It derives the ordinal from the highest already present and writes',
145
+ 'MISSION.md, RESOURCES.md, and GLOSSARY.md. A topic another workspace',
146
+ 'already covers is refused, since a second one forks the records.',
147
+ '',
148
+ 'Examples:',
149
+ ' canon teach open regular-expressions --subject "Reading and writing regular expressions" \\',
150
+ ' --starting-point "Comfortable with the shell, has never written a group" \\',
151
+ ' --success "Write a pattern matching a date" --success "Explain a backreference"',
152
+ '',
153
+ ].join('\n'),
154
+ )
155
+ .action(async (topic: string, opts: OpenCommandOptions) => {
156
+ process.exitCode = await runOpen(topic, opts)
157
+ })
158
+
159
+ teach
160
+ .command('resource')
161
+ .description('Record sources and leads in a workspace RESOURCES.md')
162
+ .argument('<topic>', 'Workspace folder or topic, as in regular-expressions')
163
+ .helpOption('-h, --help', 'Show this help message')
164
+ .option(
165
+ '--read <title=url>',
166
+ 'Source that stands behind the material, repeatable',
167
+ collect,
168
+ [] as string[],
169
+ )
170
+ .option(
171
+ '--lead <title=url>',
172
+ 'Source found and not opened, repeatable',
173
+ collect,
174
+ [] as string[],
175
+ )
176
+ .option('--json', 'Emit a machine-readable record on stdout')
177
+ .option('--root <path>', 'Teach root, defaulting to the main worktree')
178
+ .addHelpText(
179
+ 'after',
180
+ [
181
+ '',
182
+ 'Exit codes:',
183
+ ' 0 every source was written',
184
+ ' 1 refused, with the reason on stderr or in the JSON record',
185
+ '',
186
+ 'The pair splits on the first =, so a URL carrying one survives. Say',
187
+ 'in the title which claims rest on the source, since the entry is the',
188
+ 'only place a later session reads that from.',
189
+ '',
190
+ 'A URL already listed under either heading is refused rather than',
191
+ 'repeated, because two entries split what rests on one source.',
192
+ '',
193
+ 'Examples:',
194
+ ' canon teach resource regular-expressions --read "MDN regular expressions=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions"',
195
+ ' canon teach resource regular-expressions --lead "RE2 syntax=https://github.com/google/re2/wiki/Syntax" --json',
196
+ '',
197
+ ].join('\n'),
198
+ )
199
+ .action(async (topic: string, opts: ResourceCommandOptions) => {
200
+ process.exitCode = await runResource(topic, opts)
201
+ })
202
+
203
+ teach
204
+ .command('glossary')
205
+ .description('Add terms to a workspace GLOSSARY.md, alphabetically')
206
+ .argument('<topic>', 'Workspace folder or topic, as in regular-expressions')
207
+ .helpOption('-h, --help', 'Show this help message')
208
+ .option(
209
+ '--term <term=definition>',
210
+ 'Term the subject defines, repeatable',
211
+ collect,
212
+ [] as string[],
213
+ )
214
+ .option(
215
+ '--first-seen <file>',
216
+ 'Lesson or reference page the batch first defines these in',
217
+ )
218
+ .option('--json', 'Emit a machine-readable record on stdout')
219
+ .option('--root <path>', 'Teach root, defaulting to the main worktree')
220
+ .addHelpText(
221
+ 'after',
222
+ [
223
+ '',
224
+ 'Exit codes:',
225
+ ' 0 every term now carries an entry',
226
+ ' 1 refused, with the reason on stderr or in the JSON record',
227
+ '',
228
+ 'One call writes one file, which is what keeps a batch from racing on',
229
+ 'the glossary every term shares. --first-seen names one page for the',
230
+ 'whole batch, since a batch comes from one lesson.',
231
+ '',
232
+ 'A term already defined is refused rather than replaced. A definition',
233
+ 'the subject has moved under is a revision of the entry it has.',
234
+ '',
235
+ 'Examples:',
236
+ ' canon teach glossary regular-expressions --term "capture group=A parenthesised part of a pattern whose match is kept" --first-seen 0002-groups.html',
237
+ '',
238
+ ].join('\n'),
239
+ )
240
+ .action(async (topic: string, opts: GlossaryCommandOptions) => {
241
+ process.exitCode = await runGlossary(topic, opts)
242
+ })
243
+
244
+ teach
245
+ .command('lesson')
246
+ .description('Resolve what the next lesson needs before it is written')
247
+ .argument('<topic>', 'Workspace folder or topic, as in regular-expressions')
248
+ .helpOption('-h, --help', 'Show this help message')
249
+ .option('--slug <kebab>', "The lesson's own topic, as in capture-groups")
250
+ .option('--questions <n>', 'How many questions the quiz carries')
251
+ .option('--options <n>', 'How many options each question carries', '4')
252
+ .option('--json', 'Emit a machine-readable record on stdout')
253
+ .option('--root <path>', 'Teach root, defaulting to the main worktree')
254
+ .addHelpText(
255
+ 'after',
256
+ [
257
+ '',
258
+ 'Exit codes:',
259
+ ' 0 the lesson was planned',
260
+ ' 1 refused, with the reason on stderr or in the JSON record',
261
+ '',
262
+ 'It writes nothing. It reports the numbered path the lesson takes, the',
263
+ 'shared stylesheet with whether that file exists yet, the mission',
264
+ 'success lines to report progress against, and one option order per',
265
+ 'question.',
266
+ '',
267
+ 'Write the correct option first and present the options in the order',
268
+ 'reported. Position is drawn here rather than chosen, so an answer',
269
+ 'cannot settle into the first slot.',
270
+ '',
271
+ 'Examples:',
272
+ ' canon teach lesson regular-expressions --slug capture-groups --questions 3 --json',
273
+ '',
274
+ ].join('\n'),
275
+ )
276
+ .action(async (topic: string, opts: LessonCommandOptions) => {
277
+ process.exitCode = await runLesson(topic, opts)
278
+ })
279
+ }
280
+
281
+ function collect(value: string, previous: string[]): string[] {
282
+ return [...previous, value]
283
+ }
284
+
285
+ /**
286
+ * Splits every pair or reports the ones that carry no separator. Both halves
287
+ * are reported together, so a caller passing four pairs learns about all the
288
+ * broken ones from one run rather than from four.
289
+ */
290
+ function parsePairs(
291
+ raw: readonly string[],
292
+ ): { pairs: Array<[string, string]> } | { invalid: string[] } {
293
+ const pairs: Array<[string, string]> = []
294
+ const invalid: string[] = []
295
+
296
+ for (const entry of raw) {
297
+ const match = PAIR.exec(entry)
298
+ const left = match?.[1].trim()
299
+ const right = match?.[2].trim()
300
+
301
+ if (!left || !right) {
302
+ invalid.push(entry)
303
+ continue
304
+ }
305
+
306
+ pairs.push([left, right])
307
+ }
308
+
309
+ return invalid.length > 0 ? { invalid } : { pairs }
310
+ }
311
+
312
+ function badInput(
313
+ message: string,
314
+ detail: readonly string[] = [],
315
+ ): TeachRefused {
316
+ return { ok: false, reason: 'bad-input', message, detail }
317
+ }
318
+
319
+ async function rootFor(given: string | undefined): Promise<string> {
320
+ return given ?? (await mainWorktreeRoot())
321
+ }
322
+
323
+ async function runList(
324
+ topic: string | undefined,
325
+ opts: ListCommandOptions,
326
+ ): Promise<number> {
327
+ const emitJson = opts.json ?? false
328
+ const root = await rootFor(opts.root)
329
+
330
+ if (topic === undefined) {
331
+ return reportList(await listWorkspaces(root), emitJson, root)
332
+ }
333
+
334
+ return reportWorkspace(await readWorkspace(root, topic), emitJson, root)
335
+ }
336
+
337
+ async function runOpen(
338
+ topic: string,
339
+ opts: OpenCommandOptions,
340
+ ): Promise<number> {
341
+ const emitJson = opts.json ?? false
342
+
343
+ if (!opts.subject) {
344
+ return reportRefusal(
345
+ 'canon teach open',
346
+ badInput('No subject. Pass --subject <line>.'),
347
+ emitJson,
348
+ process.cwd(),
349
+ )
350
+ }
351
+
352
+ if (!opts.startingPoint) {
353
+ return reportRefusal(
354
+ 'canon teach open',
355
+ badInput(
356
+ 'No starting point. Pass --starting-point <text>, so difficulty has a floor.',
357
+ ),
358
+ emitJson,
359
+ process.cwd(),
360
+ )
361
+ }
362
+
363
+ const root = await rootFor(opts.root)
364
+
365
+ return reportOpen(
366
+ await openWorkspace(root, {
367
+ topic,
368
+ subject: opts.subject,
369
+ startingPoint: opts.startingPoint,
370
+ success: opts.success ?? [],
371
+ outOfScope: opts.outOfScope ?? [],
372
+ title: opts.title,
373
+ date: opts.date,
374
+ }),
375
+ emitJson,
376
+ root,
377
+ )
378
+ }
379
+
380
+ async function runResource(
381
+ topic: string,
382
+ opts: ResourceCommandOptions,
383
+ ): Promise<number> {
384
+ const emitJson = opts.json ?? false
385
+ const raw = [...(opts.read ?? []), ...(opts.lead ?? [])]
386
+
387
+ if (raw.length === 0) {
388
+ return reportRefusal(
389
+ 'canon teach resource',
390
+ badInput('No source given. Pass --read or --lead as <title>=<url>.'),
391
+ emitJson,
392
+ process.cwd(),
393
+ )
394
+ }
395
+
396
+ const parsed = parsePairs(raw)
397
+
398
+ if ('invalid' in parsed) {
399
+ return reportRefusal(
400
+ 'canon teach resource',
401
+ badInput(
402
+ `Not a title and url: ${parsed.invalid.join(', ')}`,
403
+ parsed.invalid,
404
+ ),
405
+ emitJson,
406
+ process.cwd(),
407
+ )
408
+ }
409
+
410
+ const readCount = (opts.read ?? []).length
411
+ const sources: Source[] = parsed.pairs.map(([title, url]) => ({ title, url }))
412
+ const root = await rootFor(opts.root)
413
+
414
+ return reportResource(
415
+ await recordSources(
416
+ root,
417
+ topic,
418
+ sources.slice(0, readCount),
419
+ sources.slice(readCount),
420
+ ),
421
+ emitJson,
422
+ root,
423
+ )
424
+ }
425
+
426
+ async function runGlossary(
427
+ topic: string,
428
+ opts: GlossaryCommandOptions,
429
+ ): Promise<number> {
430
+ const emitJson = opts.json ?? false
431
+ const raw = opts.term ?? []
432
+
433
+ if (raw.length === 0) {
434
+ return reportRefusal(
435
+ 'canon teach glossary',
436
+ badInput('No term given. Pass --term <term>=<definition>.'),
437
+ emitJson,
438
+ process.cwd(),
439
+ )
440
+ }
441
+
442
+ const parsed = parsePairs(raw)
443
+
444
+ if ('invalid' in parsed) {
445
+ return reportRefusal(
446
+ 'canon teach glossary',
447
+ badInput(
448
+ `Not a term and definition: ${parsed.invalid.join(', ')}`,
449
+ parsed.invalid,
450
+ ),
451
+ emitJson,
452
+ process.cwd(),
453
+ )
454
+ }
455
+
456
+ const terms: Term[] = parsed.pairs.map(([term, definition]) => ({
457
+ term,
458
+ definition,
459
+ }))
460
+
461
+ const duplicated = terms
462
+ .map((term) => term.term.toLowerCase())
463
+ .filter((term, index, all) => all.indexOf(term) !== index)
464
+
465
+ if (duplicated.length > 0) {
466
+ return reportRefusal(
467
+ 'canon teach glossary',
468
+ badInput(`Two definitions for ${[...new Set(duplicated)].join(', ')}.`),
469
+ emitJson,
470
+ process.cwd(),
471
+ )
472
+ }
473
+
474
+ const root = await rootFor(opts.root)
475
+
476
+ return reportGlossary(
477
+ await defineTerms(root, topic, terms, opts.firstSeen),
478
+ emitJson,
479
+ root,
480
+ )
481
+ }
482
+
483
+ async function runLesson(
484
+ topic: string,
485
+ opts: LessonCommandOptions,
486
+ ): Promise<number> {
487
+ const emitJson = opts.json ?? false
488
+
489
+ if (!opts.slug) {
490
+ return reportRefusal(
491
+ 'canon teach lesson',
492
+ badInput('No lesson slug. Pass --slug <kebab>.'),
493
+ emitJson,
494
+ process.cwd(),
495
+ )
496
+ }
497
+
498
+ if (!opts.questions) {
499
+ return reportRefusal(
500
+ 'canon teach lesson',
501
+ badInput('No question count. Pass --questions <n>.'),
502
+ emitJson,
503
+ process.cwd(),
504
+ )
505
+ }
506
+
507
+ const root = await rootFor(opts.root)
508
+
509
+ return reportLesson(
510
+ await planLesson(root, topic, {
511
+ slug: opts.slug,
512
+ questions: Number(opts.questions),
513
+ options: Number(opts.options ?? '4'),
514
+ }),
515
+ emitJson,
516
+ root,
517
+ )
518
+ }
519
+
520
+ function reportRefusal(
521
+ title: string,
522
+ refused: TeachRefused,
523
+ emitJson: boolean,
524
+ root: string,
525
+ ): number {
526
+ // The framed branch reaches stderr through logError, so the bare write is
527
+ // what keeps the JSON mode from reporting the reason on stdout alone.
528
+ if (emitJson) {
529
+ process.stderr.write(`${refused.message}\n`)
530
+ process.stdout.write(
531
+ `${JSON.stringify({
532
+ ok: false,
533
+ root,
534
+ reason: refused.reason,
535
+ message: refused.message,
536
+ detail: refused.detail,
537
+ })}\n`,
538
+ )
539
+ return 1
540
+ }
541
+
542
+ intro(title)
543
+ logStep('Refused')
544
+ logError(refused.message)
545
+ if (refused.detail.length > 0) pipeOutput(refused.detail.join('\n'))
546
+ outro()
547
+
548
+ return 1
549
+ }
550
+
551
+ function describe(workspace: WorkspaceSummary): string {
552
+ return `${workspace.slug}: ${workspace.lessons} lesson(s), ${workspace.records} record(s), ${workspace.reference} reference page(s), ${workspace.terms} term(s)`
553
+ }
554
+
555
+ function reportList(
556
+ outcome: ListOutcome,
557
+ emitJson: boolean,
558
+ root: string,
559
+ ): number {
560
+ if (!outcome.ok)
561
+ return reportRefusal('canon teach list', outcome, emitJson, root)
562
+
563
+ if (emitJson) {
564
+ process.stdout.write(
565
+ `${JSON.stringify({
566
+ ok: true,
567
+ root,
568
+ workspaces: outcome.workspaces,
569
+ next: outcome.next,
570
+ })}\n`,
571
+ )
572
+ return 0
573
+ }
574
+
575
+ intro('canon teach list')
576
+ logStep(outcome.workspaces.length > 0 ? 'Workspaces' : 'No workspaces')
577
+
578
+ for (const workspace of outcome.workspaces) logInfo(describe(workspace))
579
+
580
+ const incomplete = outcome.workspaces.filter(
581
+ (workspace) => workspace.missing.length > 0,
582
+ )
583
+
584
+ if (incomplete.length > 0) {
585
+ logStep('Missing a required file')
586
+ for (const workspace of incomplete) {
587
+ logWarn(`${workspace.slug}: no ${workspace.missing.join(' and no ')}`)
588
+ }
589
+ }
590
+
591
+ logStep('Next ordinal')
592
+ logInfo(outcome.next)
593
+ outro()
594
+
595
+ return 0
596
+ }
597
+
598
+ function reportWorkspace(
599
+ outcome: ReadOutcome,
600
+ emitJson: boolean,
601
+ root: string,
602
+ ): number {
603
+ if (!outcome.ok)
604
+ return reportRefusal('canon teach list', outcome, emitJson, root)
605
+
606
+ const workspace = outcome.workspace
607
+
608
+ if (emitJson) {
609
+ process.stdout.write(`${JSON.stringify({ ok: true, root, workspace })}\n`)
610
+ return 0
611
+ }
612
+
613
+ intro('canon teach list')
614
+ logStep(workspace.slug)
615
+ logInfo(`${workspace.title ?? 'no title'}, opened ${workspace.opened ?? '?'}`)
616
+ logInfo(workspace.path)
617
+
618
+ for (const [label, files] of [
619
+ ['Lessons', workspace.lessonFiles],
620
+ ['Learning records', workspace.recordFiles],
621
+ ['Reference', workspace.referenceFiles],
622
+ ] as const) {
623
+ logStep(files.length > 0 ? label : `${label} (none)`)
624
+ for (const file of files) logInfo(file)
625
+ }
626
+
627
+ logStep(workspace.glossary.length > 0 ? 'Glossary' : 'Glossary (empty)')
628
+ for (const entry of workspace.glossary) logInfo(entry)
629
+
630
+ logStep(
631
+ workspace.success.length > 0 ? 'Exit criteria' : 'Exit criteria (none)',
632
+ )
633
+ for (const line of workspace.success) logInfo(line)
634
+
635
+ if (workspace.missing.length > 0) {
636
+ logStep('Missing a required file')
637
+ logWarn(`no ${workspace.missing.join(' and no ')}`)
638
+ }
639
+
640
+ outro()
641
+
642
+ return 0
643
+ }
644
+
645
+ function reportOpen(
646
+ outcome: OpenOutcome,
647
+ emitJson: boolean,
648
+ root: string,
649
+ ): number {
650
+ if (!outcome.ok)
651
+ return reportRefusal('canon teach open', outcome, emitJson, root)
652
+
653
+ if (emitJson) {
654
+ process.stdout.write(
655
+ `${JSON.stringify({
656
+ ok: true,
657
+ root,
658
+ slug: outcome.slug,
659
+ path: outcome.path,
660
+ created: outcome.created,
661
+ })}\n`,
662
+ )
663
+ return 0
664
+ }
665
+
666
+ intro('canon teach open')
667
+ logStep('Opened')
668
+ logInfo(outcome.slug)
669
+ for (const file of outcome.created) logAdd(file)
670
+ outro()
671
+
672
+ return 0
673
+ }
674
+
675
+ function reportResource(
676
+ outcome: SourceOutcome,
677
+ emitJson: boolean,
678
+ root: string,
679
+ ): number {
680
+ if (!outcome.ok) {
681
+ return reportRefusal('canon teach resource', outcome, emitJson, root)
682
+ }
683
+
684
+ if (emitJson) {
685
+ process.stdout.write(
686
+ `${JSON.stringify({
687
+ ok: true,
688
+ root,
689
+ slug: outcome.slug,
690
+ path: relative(root, outcome.path),
691
+ read: outcome.read,
692
+ leads: outcome.leads,
693
+ })}\n`,
694
+ )
695
+ return 0
696
+ }
697
+
698
+ intro('canon teach resource')
699
+ logStep('Recorded')
700
+ for (const source of outcome.read) logInfo(`read: ${source.title}`)
701
+ for (const source of outcome.leads) logInfo(`lead: ${source.title}`)
702
+ logAdd(relative(root, outcome.path))
703
+ outro()
704
+
705
+ return 0
706
+ }
707
+
708
+ function reportGlossary(
709
+ outcome: TermOutcome,
710
+ emitJson: boolean,
711
+ root: string,
712
+ ): number {
713
+ if (!outcome.ok) {
714
+ return reportRefusal('canon teach glossary', outcome, emitJson, root)
715
+ }
716
+
717
+ if (emitJson) {
718
+ process.stdout.write(
719
+ `${JSON.stringify({
720
+ ok: true,
721
+ root,
722
+ slug: outcome.slug,
723
+ path: relative(root, outcome.path),
724
+ defined: outcome.defined,
725
+ })}\n`,
726
+ )
727
+ return 0
728
+ }
729
+
730
+ intro('canon teach glossary')
731
+ logStep('Defined')
732
+ for (const term of outcome.defined) logInfo(term.term)
733
+ logAdd(relative(root, outcome.path))
734
+ outro()
735
+
736
+ return 0
737
+ }
738
+
739
+ function reportLesson(
740
+ outcome: LessonOutcome,
741
+ emitJson: boolean,
742
+ root: string,
743
+ ): number {
744
+ if (!outcome.ok) {
745
+ return reportRefusal('canon teach lesson', outcome, emitJson, root)
746
+ }
747
+
748
+ if (emitJson) {
749
+ process.stdout.write(
750
+ `${JSON.stringify({
751
+ ok: true,
752
+ root,
753
+ slug: outcome.slug,
754
+ path: outcome.path,
755
+ lesson: outcome.lesson,
756
+ stylesheet: outcome.stylesheet,
757
+ stylesheetHref: outcome.stylesheetHref,
758
+ stylesheetExists: outcome.stylesheetExists,
759
+ success: outcome.success,
760
+ quiz: outcome.quiz,
761
+ })}\n`,
762
+ )
763
+ return 0
764
+ }
765
+
766
+ intro('canon teach lesson')
767
+ logStep('Lesson')
768
+ logInfo(outcome.lesson)
769
+
770
+ logStep(outcome.stylesheetExists ? 'Stylesheet' : 'Stylesheet (to write)')
771
+ logInfo(`${outcome.stylesheet} linked as ${outcome.stylesheetHref}`)
772
+
773
+ logStep(outcome.success.length > 0 ? 'Exit criteria' : 'Exit criteria (none)')
774
+ for (const line of outcome.success) logInfo(line)
775
+
776
+ logStep('Option order')
777
+ for (const question of outcome.quiz) {
778
+ logInfo(
779
+ `question ${question.question}: present ${question.order.join(', ')}, correct answer in position ${question.answer}`,
780
+ )
781
+ }
782
+
783
+ outro()
784
+
785
+ return 0
786
+ }