@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,159 @@
1
+ import type { Command } from 'commander'
2
+ import { relative } from 'node:path'
3
+ import { DEFAULT_PORT, type ServeOutcome, startServer } from '@/serve/static'
4
+ import { intro, logError, logInfo, logStep, logWarn, outro } from '@/ui'
5
+
6
+ const DEFAULT_DIR = '.'
7
+
8
+ interface ServeCommandOptions {
9
+ readonly entry?: string
10
+ readonly json?: boolean
11
+ readonly port?: string
12
+ }
13
+
14
+ export function register(program: Command): void {
15
+ program
16
+ .command('serve')
17
+ .description('Serve a directory over localhost and print the preview link')
18
+ .argument('[dir]', 'Directory to serve', DEFAULT_DIR)
19
+ .helpOption('-h, --help', 'Show this help message')
20
+ .option('--port <number>', `Port to try first, default ${DEFAULT_PORT}`)
21
+ .option('--entry <path>', 'Page the printed link opens, default index.html')
22
+ .option('--json', 'Emit a machine-readable record on stdout')
23
+ .addHelpText(
24
+ 'after',
25
+ [
26
+ '',
27
+ 'Exit codes:',
28
+ ' 0 the server stopped after running',
29
+ ' 1 refused, with the reason on stderr or in the JSON record',
30
+ '',
31
+ 'The server binds 127.0.0.1 and nothing else, and it sends no cache',
32
+ 'headers, so an edited stylesheet is never served stale. A port in use',
33
+ 'is not a failure: the next free one is taken and the link says which.',
34
+ '',
35
+ 'It runs until interrupted. A session wanting the link without waiting',
36
+ 'starts it in the background and reads the record off stdout.',
37
+ '',
38
+ 'Examples:',
39
+ ' canon serve .claude/teach',
40
+ ' canon serve .claude/teach --entry 03-fde-system-design/index.html',
41
+ ' canon serve dist --port 4000 --json',
42
+ '',
43
+ ].join('\n'),
44
+ )
45
+ .action(async (dir: string, opts: ServeCommandOptions) => {
46
+ process.exitCode = await runServe(dir, opts)
47
+ })
48
+ }
49
+
50
+ async function runServe(
51
+ dir: string,
52
+ opts: ServeCommandOptions,
53
+ ): Promise<number> {
54
+ const emitJson = opts.json ?? false
55
+
56
+ const port = parsePort(opts.port)
57
+ if (port === undefined) {
58
+ return report(
59
+ { ok: false, reason: 'no-port', detail: `${opts.port} is not a port` },
60
+ emitJson,
61
+ )
62
+ }
63
+
64
+ const outcome = startServer(dir, { port, entry: opts.entry })
65
+ const code = report(outcome, emitJson)
66
+ if (!outcome.ok) return code
67
+
68
+ await waitForInterrupt(outcome.stop)
69
+ return 0
70
+ }
71
+
72
+ /**
73
+ * The relative path where it stays inside the working directory, and the
74
+ * absolute one where it climbs out. A run from a linked worktree serving the
75
+ * main root reports `../../teach`, which names the directory without giving a
76
+ * reader anything they can open.
77
+ */
78
+ function displayPath(root: string): string {
79
+ const near = relative(process.cwd(), root)
80
+ if (near === '') return '.'
81
+ return near.startsWith('..') ? root : near
82
+ }
83
+
84
+ /**
85
+ * Undefined for a value that is not a port, distinct from an absent flag,
86
+ * which takes the default. A `Number` of a typo is `NaN`, and passing that on
87
+ * asks the runtime to bind a port nobody named.
88
+ */
89
+ function parsePort(raw: string | undefined): number | undefined {
90
+ if (raw === undefined) return DEFAULT_PORT
91
+ const value = Number(raw)
92
+ if (!Number.isInteger(value) || value < 0 || value > 65535) return undefined
93
+ return value
94
+ }
95
+
96
+ function report(outcome: ServeOutcome, emitJson: boolean): number {
97
+ if (!outcome.ok) {
98
+ if (emitJson) {
99
+ process.stdout.write(
100
+ `${JSON.stringify({ ok: false, reason: outcome.reason, detail: outcome.detail })}\n`,
101
+ )
102
+ return 1
103
+ }
104
+ intro('canon serve')
105
+ logError(outcome.detail)
106
+ outro()
107
+ return 1
108
+ }
109
+
110
+ if (emitJson) {
111
+ process.stdout.write(
112
+ `${JSON.stringify({
113
+ ok: true,
114
+ root: outcome.root,
115
+ host: outcome.host,
116
+ port: outcome.port,
117
+ entry: outcome.entry,
118
+ url: outcome.url,
119
+ entryExists: outcome.entryExists,
120
+ })}\n`,
121
+ )
122
+ return 0
123
+ }
124
+
125
+ intro('canon serve')
126
+ logStep('Serving')
127
+ logInfo(displayPath(outcome.root))
128
+ logStep('Open')
129
+ logInfo(outcome.url)
130
+
131
+ if (!outcome.entryExists) {
132
+ logStep('No entry page')
133
+ logWarn(
134
+ `${outcome.entry} is not in that directory, so the link opens a 404`,
135
+ )
136
+ }
137
+
138
+ logStep('Stop')
139
+ logInfo('Ctrl-C')
140
+ return 0
141
+ }
142
+
143
+ /**
144
+ * Resolves when the process is asked to stop. The frame is closed here rather
145
+ * than in the reporter, because the run is the serving rather than the start,
146
+ * and closing at start would print the frame's end while the server ran on.
147
+ */
148
+ function waitForInterrupt(stop: () => Promise<void>): Promise<void> {
149
+ return new Promise((settle) => {
150
+ const finish = () => {
151
+ void stop().then(() => {
152
+ outro()
153
+ settle()
154
+ })
155
+ }
156
+ process.once('SIGINT', finish)
157
+ process.once('SIGTERM', finish)
158
+ })
159
+ }
@@ -0,0 +1,408 @@
1
+ import { resolve } from 'node:path'
2
+ import type { Command } from 'commander'
3
+ import { checkClaim, type ClaimReport } from '@/sessions/claim'
4
+ import {
5
+ callerIdentity,
6
+ repositoryOf,
7
+ type ResolvedSession,
8
+ resolveSessions,
9
+ type SelfReport,
10
+ type SessionReport,
11
+ selfOf,
12
+ } from '@/sessions/resolve'
13
+ import {
14
+ intro,
15
+ logInfo,
16
+ logStep,
17
+ logWarn,
18
+ outro,
19
+ pipeOutput,
20
+ plural,
21
+ } from '@/ui'
22
+
23
+ interface ListCommandOptions {
24
+ readonly json?: boolean
25
+ readonly branch?: string
26
+ readonly repository?: string
27
+ readonly self?: boolean
28
+ }
29
+
30
+ const REASONS: Record<string, string> = {
31
+ 'not-a-repository': 'working outside any git repository',
32
+ 'detached-head': 'detached HEAD, so the worktree holds no branch name',
33
+ 'git-unavailable': 'git is not on the path, so nothing could be read',
34
+ }
35
+
36
+ export function register(program: Command): void {
37
+ const sessions = program
38
+ .command('sessions')
39
+ .description(
40
+ 'Resolve live peer sessions to the worktree and branch each holds',
41
+ )
42
+ .helpOption('-h, --help', 'Show this help message')
43
+
44
+ sessions
45
+ .command('list')
46
+ .description(
47
+ 'Report every live session with its working directory and branch',
48
+ )
49
+ .helpOption('-h, --help', 'Show this help message')
50
+ .option('--json', 'Add a machine-readable record on stdout')
51
+ .option(
52
+ '--branch <name>',
53
+ 'Report only the sessions holding this branch in this repository',
54
+ )
55
+ .option(
56
+ '--repository <path>',
57
+ 'Answer about this project rather than the working one',
58
+ )
59
+ .option(
60
+ '--self',
61
+ "Report the caller's own row, and refuse where the roster holds none",
62
+ )
63
+ .addHelpText(
64
+ 'after',
65
+ [
66
+ '',
67
+ 'Exit codes:',
68
+ ' 0 the roster was read',
69
+ ' 1 refused, with the reason on stderr',
70
+ '',
71
+ '--branch scopes the match to one repository, since a branch name',
72
+ 'identifies a branch there and nothing across a machine. That is the',
73
+ 'repository the command runs in unless --repository names another.',
74
+ 'A bare run reports every repository and carries the repository field,',
75
+ 'so a caller filtering by hand has something that identifies one.',
76
+ '',
77
+ '--repository moves every reading to the project at that path, the',
78
+ 'session match and the worktree and ref reads alike. The roster is',
79
+ 'machine-wide already, so this is what lets a dispatcher in one project',
80
+ 'see a branch a live session holds in another rather than reading it as',
81
+ 'unclaimed and sending a second session onto it.',
82
+ '',
83
+ 'With --branch, the JSON also carries "worktree" (the path of any',
84
+ 'worktree already checked out to it, or null), "refs" (the refs that',
85
+ 'already name it, local head and origin remote-tracking alike), and',
86
+ '"claimed" (true when a worktree, a live session, or an existing ref',
87
+ 'holds it). A dispatcher reads "claimed" rather than composing the',
88
+ 'three fields itself, since each alone misses a real claim: a worktree',
89
+ 'can outlive the session that made it, a session can hold a branch',
90
+ 'before a worktree exists for it, and a branch behind a merged pull',
91
+ 'request has neither while still being taken.',
92
+ '',
93
+ '"sessionsReadable" is false when the session roster could not be read',
94
+ 'and "refsReadable" is false when the ref read failed. Either one',
95
+ 'leaves "claimed" covering the readings around it alone. Treat that',
96
+ 'case as unverified rather than as a clean "false".',
97
+ '',
98
+ 'The ref read sees the remote at whatever the last fetch left, so a',
99
+ 'branch pushed from another machine since then reads absent here.',
100
+ '',
101
+ 'The match can return more than one session. Read the count rather than',
102
+ 'the first row, since two sessions can hold one branch.',
103
+ '',
104
+ "--self narrows the report to the caller's own row, which is what a",
105
+ 'dispatcher reads to learn the sessionId it carries into a launch. It',
106
+ 'joins on CLAUDE_CODE_SESSION_ID first, falls back to CLAUDE_PID, and',
107
+ 'falls back again to the pid the messaging socket path spells. It never',
108
+ 'reads CLAUDE_CODE_HOST_SESSION_ID, which holds a value from another',
109
+ 'namespace that matches no row.',
110
+ '',
111
+ 'It refuses with reason "no-self-identity" when the environment states',
112
+ 'none of the three, and "no-self-row" when it states one and no live',
113
+ 'row carries it. The second is the ordinary answer for a session',
114
+ 'driving from Remote Control, which is addressable on the message',
115
+ 'channel and holds no local process record for the roster to report.',
116
+ '',
117
+ 'Each session writes its own working directory beside its own name, so a',
118
+ 'name from a session listing joins to a branch by an exact match rather',
119
+ 'than by ordering the roster on start time.',
120
+ '',
121
+ 'The confidence field says how the liveness of a row was decided.',
122
+ '"confirmed" matched the running process against the start time the',
123
+ 'record stamped. "unverified" means only that the pid answers, which',
124
+ 'cannot rule out a pid handed to an unrelated process, so a roster',
125
+ 'reported that way is a candidate list rather than an identity.',
126
+ '',
127
+ 'The JSON record also carries "statusUpdatedAt" (the stamp the client',
128
+ 'wrote beside "status", or null where its record carries none) and',
129
+ '"statusDwellMs" (the elapsed milliseconds since that stamp, falling',
130
+ 'back to the coarser "updatedAt" where the narrower one is absent,',
131
+ 'computed at read time and clamped at zero against clock skew). The',
132
+ 'framed listing renders the same dwell beside the status, at the',
133
+ 'coarsest unit that keeps it a whole number.',
134
+ '',
135
+ 'Examples:',
136
+ ' canon sessions list',
137
+ ' canon sessions list --json',
138
+ ' canon sessions list --branch feat/parser --json',
139
+ ' canon sessions list --branch chore/agents --repository ../caret --json',
140
+ ' canon sessions list --self --json',
141
+ '',
142
+ ].join('\n'),
143
+ )
144
+ .action(async (opts: ListCommandOptions) => {
145
+ process.exitCode = await runList(opts)
146
+ })
147
+ }
148
+
149
+ async function runList(opts: ListCommandOptions): Promise<number> {
150
+ const report = await resolveSessions()
151
+
152
+ if (report.kind === 'absent') {
153
+ intro('canon sessions list')
154
+ logStep('Refused')
155
+ logWarn(
156
+ `No session registry at ${report.dir}. Nothing was read, so this is not a machine with no sessions.`,
157
+ )
158
+ outro()
159
+
160
+ if (opts.json) {
161
+ process.stdout.write(
162
+ `${JSON.stringify({ dir: report.dir, reason: 'no-registry', sessions: [] })}\n`,
163
+ )
164
+ }
165
+
166
+ return 1
167
+ }
168
+
169
+ // The roster read returns every row and marks none of them as the caller, so
170
+ // the join runs here, ahead of any scope. Resolving it after the branch
171
+ // filter would answer "no row" for a caller whose row was merely filtered
172
+ // out, which is a different failure wearing the same reason.
173
+ const own = opts.self ? selfOf(report.sessions, callerIdentity()) : null
174
+
175
+ if (own?.kind === 'unresolved') {
176
+ intro('canon sessions list')
177
+ logStep('Refused')
178
+ logWarn(selfRefusal(own))
179
+ outro()
180
+
181
+ if (opts.json) {
182
+ process.stdout.write(
183
+ `${JSON.stringify({
184
+ dir: report.dir,
185
+ reason:
186
+ own.reason === 'no-identity' ? 'no-self-identity' : 'no-self-row',
187
+ sessions: [],
188
+ })}\n`,
189
+ )
190
+ }
191
+
192
+ return 1
193
+ }
194
+
195
+ const pool = own === null ? report.sessions : [own.session]
196
+
197
+ // A branch name identifies a branch inside one repository and nothing across
198
+ // a machine, so an unscoped match reaches a session working in a different
199
+ // project. `main` is the name that collides on every machine running two.
200
+ //
201
+ // Which repository that is is the caller's to name. The roster this filters
202
+ // is machine-wide already, so a dispatcher asking about another project was
203
+ // answered "unclaimed" about a branch a live session there was holding.
204
+ const at = opts.repository ? resolve(opts.repository) : process.cwd()
205
+ const repository = opts.branch ? await repositoryOf(at) : null
206
+
207
+ if (opts.branch && repository === null) {
208
+ intro('canon sessions list')
209
+ logStep('Refused')
210
+ logWarn(
211
+ `--branch scopes the match to one repository, and none resolved at ${at}. Run it inside one, name another with --repository, or read the whole roster and filter on the repository field.`,
212
+ )
213
+ outro()
214
+
215
+ if (opts.json) {
216
+ process.stdout.write(
217
+ `${JSON.stringify({ dir: report.dir, reason: 'no-repository', sessions: [] })}\n`,
218
+ )
219
+ }
220
+
221
+ return 1
222
+ }
223
+
224
+ const shown = opts.branch
225
+ ? pool.filter(
226
+ (session) =>
227
+ session.branch === opts.branch && session.repository === repository,
228
+ )
229
+ : pool
230
+
231
+ const claim = opts.branch
232
+ ? await checkClaim(opts.branch, { cwd: at, resolve: async () => report })
233
+ : null
234
+
235
+ intro('canon sessions list')
236
+ reportConfidence(report)
237
+ reportSessions(shown, opts.branch, repository, own !== null)
238
+ if (claim) reportClaim(claim)
239
+ outro()
240
+
241
+ if (opts.json) {
242
+ process.stdout.write(
243
+ `${JSON.stringify({
244
+ dir: report.dir,
245
+ confidence: report.confidence,
246
+ branch: opts.branch ?? null,
247
+ repository,
248
+ worktree: claim?.worktree ?? null,
249
+ refs: claim?.refs ?? null,
250
+ claimed: claim?.claimed ?? null,
251
+ sessionsReadable: claim?.sessionsReadable ?? null,
252
+ refsReadable: claim?.refsReadable ?? null,
253
+ sessions: shown,
254
+ })}\n`,
255
+ )
256
+ }
257
+
258
+ return 0
259
+ }
260
+
261
+ /**
262
+ * Separates a client that states no identity from a roster holding no row for
263
+ * one it does state, since the two send a reader to different places.
264
+ */
265
+ function selfRefusal(own: Extract<SelfReport, { kind: 'unresolved' }>): string {
266
+ if (own.reason === 'no-identity') {
267
+ return 'Nothing in the environment identifies this session, so --self has nothing to match against. A client setting none of CLAUDE_CODE_SESSION_ID, CLAUDE_PID, or CLAUDE_CODE_MESSAGING_SOCKET cannot be located on the roster at all.'
268
+ }
269
+
270
+ const held =
271
+ own.identity.sessionId ??
272
+ (own.identity.pid === null ? 'nothing' : `pid ${own.identity.pid}`)
273
+
274
+ return `The environment identifies this session as ${held}, and no live row carries it. The roster holds local process records alone, so a session driving through Remote Control never appears here, and a record whose session has ended is dropped ahead of the match.`
275
+ }
276
+
277
+ /**
278
+ * States how liveness was decided on every run, including the run that decided
279
+ * it the strong way.
280
+ *
281
+ * A caller reading a roster has no other way to tell a confirmed identity from
282
+ * a pid that merely answered, and the two support different actions: the first
283
+ * addresses a session directly and the second opens by asking to be corrected.
284
+ */
285
+ function reportConfidence(
286
+ report: Extract<SessionReport, { kind: 'resolved' }>,
287
+ ): void {
288
+ logStep('Liveness')
289
+
290
+ // A roster of none decided nothing, so the confirmed line would claim a check
291
+ // over rows that do not exist. The registry is never pruned and holds a record
292
+ // per session ever run, which is what makes the empty result worth stating.
293
+ if (report.sessions.length === 0) {
294
+ logInfo(
295
+ 'No row to decide. Every record belongs to a session that has ended.',
296
+ )
297
+ return
298
+ }
299
+
300
+ if (report.confidence === 'confirmed') {
301
+ logInfo(
302
+ 'Every row matched a running process against the start time its record stamped.',
303
+ )
304
+ return
305
+ }
306
+
307
+ logWarn(
308
+ 'Start times could not be read, so rows rest on a pid answering alone. Treat the mapping as inferred and open by asking to be corrected.',
309
+ )
310
+ }
311
+
312
+ function reportSessions(
313
+ sessions: readonly ResolvedSession[],
314
+ branch: string | undefined,
315
+ repository: string | null,
316
+ scoped: boolean,
317
+ ): void {
318
+ logStep('Sessions')
319
+
320
+ if (branch) {
321
+ logInfo(
322
+ `Scoped to ${repository}, since a branch name identifies one there.`,
323
+ )
324
+ }
325
+
326
+ // An empty result under --self says nothing about the roster, since the pool
327
+ // was narrowed to one row before the branch filter ran. Reporting the wider
328
+ // answer there would claim a reading this run never took.
329
+ if (sessions.length === 0) {
330
+ if (branch) {
331
+ logInfo(
332
+ scoped
333
+ ? `This session does not hold ${branch}.`
334
+ : `No live session in this repository holds ${branch}.`,
335
+ )
336
+ return
337
+ }
338
+
339
+ logInfo(
340
+ 'No live session. Every record in the registry belongs to a session that has ended.',
341
+ )
342
+ return
343
+ }
344
+
345
+ // The count is what a dispatch turns on. One row is a target and several are
346
+ // candidates, and the caller cannot tell them apart from a roster alone.
347
+ if (branch && sessions.length > 1) {
348
+ logWarn(
349
+ `${sessions.length} sessions hold ${branch}. Confirm which one before addressing it.`,
350
+ )
351
+ }
352
+
353
+ logInfo(plural(sessions.length, 'live session'))
354
+ pipeOutput(
355
+ sessions
356
+ .map((session) => {
357
+ const held =
358
+ session.branch ??
359
+ `unresolved: ${REASONS[session.unresolved ?? ''] ?? 'unknown'}`
360
+ const dwell = formatDwell(session.statusDwellMs)
361
+ const status = dwell ? `${session.status} ${dwell}` : session.status
362
+ return `${session.name} ${status} ${held}\n ${session.cwd}`
363
+ })
364
+ .join('\n'),
365
+ )
366
+ }
367
+
368
+ /**
369
+ * Renders the dwell at the coarsest unit that keeps it a whole number, since a
370
+ * reader scanning a roster wants an age at a glance rather than a millisecond
371
+ * count. An absent dwell renders as nothing, folding a status carrying no
372
+ * stamp back to the bare status line the reader already knew.
373
+ */
374
+ function formatDwell(ms: number | null): string {
375
+ if (ms === null) return ''
376
+ const seconds = Math.round(ms / 1000)
377
+ if (seconds < 60) return `${seconds}s`
378
+ const minutes = Math.round(seconds / 60)
379
+ if (minutes < 60) return `${minutes}m`
380
+ const hours = Math.round(minutes / 60)
381
+ return `${hours}h`
382
+ }
383
+
384
+ function reportClaim(claim: ClaimReport): void {
385
+ logStep('Claim')
386
+
387
+ if (claim.worktree) {
388
+ logInfo(`Worktree: ${claim.worktree}`)
389
+ }
390
+
391
+ if (claim.refs.length > 0) {
392
+ logInfo(`Refs: ${claim.refs.join(', ')}`)
393
+ }
394
+
395
+ logInfo(claim.claimed ? 'Claimed.' : 'Unclaimed.')
396
+
397
+ if (!claim.sessionsReadable) {
398
+ logWarn(
399
+ 'The session roster could not be read, so this leaves the session half out. Treat it as unverified rather than clear.',
400
+ )
401
+ }
402
+
403
+ if (!claim.refsReadable) {
404
+ logWarn(
405
+ 'The ref read failed, so this cannot say whether the branch already exists. Treat it as unverified rather than clear.',
406
+ )
407
+ }
408
+ }
@@ -0,0 +1,126 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+ import type { Command } from 'commander'
4
+ import { LAYOUTS } from '@/slides/layouts'
5
+ import { openDeck } from '@/slides/open'
6
+ import { renderSlidesDoc } from '@/slides/render'
7
+ import type { Variant } from '@/slides/styles'
8
+ import { intro, outro, palette } from '@/ui'
9
+
10
+ export function register(program: Command): void {
11
+ const slides = program
12
+ .command('slides')
13
+ .description('Slide deck commands (render, list)')
14
+
15
+ slides
16
+ .command('render')
17
+ .description('Render a SLIDES.md source into a PowerPoint deck')
18
+ .option('-s, --source <path>', 'Source SLIDES.md path', '.claude/SLIDES.md')
19
+ .option('-o, --out <path>', 'Output directory', '.claude/review/slides')
20
+ .option('-v, --variant <variant>', 'Override variant (light or dark)')
21
+ .option(
22
+ '-m, --mirror <path>',
23
+ 'Copy the deck to this directory after writing',
24
+ )
25
+ .option('--open', 'Open the deck after writing')
26
+ .action(
27
+ async (opts: {
28
+ source: string
29
+ out: string
30
+ variant?: string
31
+ mirror?: string
32
+ open?: boolean
33
+ }) => {
34
+ try {
35
+ const sourcePath = resolve(process.cwd(), opts.source)
36
+ const outDir = resolve(process.cwd(), opts.out)
37
+ if (!existsSync(sourcePath)) {
38
+ fail(`${opts.source} not found`)
39
+ }
40
+ const variant = parseVariant(opts.variant)
41
+ const mirror = resolveMirror(opts.mirror)
42
+ const { GREEN, GREY, NC, RED } = palette(process.stderr)
43
+ intro('Render slides')
44
+ const result = await renderSlidesDoc(sourcePath, outDir, {
45
+ variant,
46
+ mirror,
47
+ })
48
+ process.stderr.write(
49
+ `${GREY}│${NC} ${GREEN}✓${NC} ${result.slideCount} slides\n${GREY}│${NC} ${GREEN}✓${NC} ${result.pptxPath}\n`,
50
+ )
51
+ if (result.mirrorPath) {
52
+ process.stderr.write(
53
+ `${GREY}│${NC} ${GREEN}✓${NC} mirrored to ${result.mirrorPath}\n`,
54
+ )
55
+ }
56
+ if (opts.open) {
57
+ const target = result.mirrorPath ?? result.pptxPath
58
+ const opened = await openDeck(target)
59
+ const mark = opened ? `${GREEN}✓${NC}` : `${RED}✗${NC}`
60
+ process.stderr.write(
61
+ `${GREY}│${NC} ${mark} ${opened ? 'opened' : 'could not open'} ${target}\n`,
62
+ )
63
+ }
64
+ outro()
65
+ } catch (error) {
66
+ reportFailure(error)
67
+ }
68
+ },
69
+ )
70
+
71
+ slides
72
+ .command('list')
73
+ .description('List the available slide layouts')
74
+ .option('--json', 'Output the layout catalog as JSON')
75
+ .action((opts: { json?: boolean }) => {
76
+ if (opts.json) {
77
+ process.stdout.write(`${JSON.stringify(LAYOUTS)}\n`)
78
+ return
79
+ }
80
+ const { GREEN, GREY, NC } = palette(process.stderr)
81
+ intro('Slide layouts')
82
+ for (const layout of LAYOUTS) {
83
+ process.stderr.write(
84
+ `${GREY}│${NC} ${GREEN}✓${NC} ${layout.name} ${GREY}${layout.description}${NC}\n`,
85
+ )
86
+ }
87
+ outro()
88
+ })
89
+ }
90
+
91
+ function parseVariant(value: string | undefined): Variant | undefined {
92
+ if (value === undefined) return undefined
93
+ if (value === 'light' || value === 'dark') return value
94
+ fail(`Invalid variant "${value}". Use light or dark.`)
95
+ }
96
+
97
+ function resolveMirror(value: string | undefined): string | undefined {
98
+ const mirror = value ?? process.env.CANON_SLIDES_MIRROR
99
+ return mirror ? resolve(process.cwd(), mirror) : undefined
100
+ }
101
+
102
+ /**
103
+ * Carries a fail-fast message from a validation helper to the action that
104
+ * called it. `fail` has to keep its `never` return, since that is what makes
105
+ * `parseVariant` exhaustive to the compiler, and a helper deep in the call
106
+ * stack cannot set `process.exitCode` and unwind on its own.
107
+ */
108
+ class SlidesError extends Error {}
109
+
110
+ function fail(message: string): never {
111
+ throw new SlidesError(message)
112
+ }
113
+
114
+ /**
115
+ * `src/cli.ts` calls `program.parse()` without awaiting it, so a rejected
116
+ * action promise reaches no handler and Bun prints a stack trace. Every action
117
+ * that calls `fail` catches at its own boundary.
118
+ */
119
+ function reportFailure(error: unknown): void {
120
+ if (!(error instanceof SlidesError)) throw error
121
+ const { GREY, NC, RED } = palette(process.stderr)
122
+ process.stderr.write(
123
+ `${GREY}┌${NC}\n${GREY}│${NC} ${RED}✗${NC} ${error.message}\n${GREY}└${NC}\n`,
124
+ )
125
+ process.exitCode = 1
126
+ }