@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,322 @@
1
+ import { existsSync, realpathSync, statSync } from 'node:fs'
2
+ import { join, resolve, sep } from 'node:path'
3
+
4
+ /**
5
+ * The loopback interface, never a wildcard bind. A preview serves whatever
6
+ * directory it is pointed at, and the folders this exists for are the
7
+ * gitignored record trees, so reaching the network is the one thing it must
8
+ * not do.
9
+ */
10
+ export const SERVE_HOST = '127.0.0.1'
11
+
12
+ /** Tried first, then the next ports in order, so a second preview still opens. */
13
+ export const DEFAULT_PORT = 8787
14
+
15
+ /** How far past the requested port to look before refusing. */
16
+ const PORT_ATTEMPTS = 20
17
+
18
+ const DEFAULT_ENTRY = 'index.html'
19
+
20
+ /**
21
+ * Extensions a browser has to be told about. Anything absent is served as an
22
+ * octet stream, which downloads rather than renders, and that is the safe
23
+ * direction for a type this map does not claim to know.
24
+ */
25
+ const CONTENT_TYPES: Readonly<Record<string, string>> = {
26
+ css: 'text/css; charset=utf-8',
27
+ gif: 'image/gif',
28
+ htm: 'text/html; charset=utf-8',
29
+ html: 'text/html; charset=utf-8',
30
+ ico: 'image/x-icon',
31
+ jpeg: 'image/jpeg',
32
+ jpg: 'image/jpeg',
33
+ js: 'text/javascript; charset=utf-8',
34
+ json: 'application/json; charset=utf-8',
35
+ /**
36
+ * Plain text rather than `text/markdown`, which a browser offers to save
37
+ * instead of showing. A reader following a link to a source page wants to
38
+ * read it, and the rendered sibling is a separate file.
39
+ */
40
+ md: 'text/plain; charset=utf-8',
41
+ mjs: 'text/javascript; charset=utf-8',
42
+ pdf: 'application/pdf',
43
+ png: 'image/png',
44
+ svg: 'image/svg+xml',
45
+ txt: 'text/plain; charset=utf-8',
46
+ webp: 'image/webp',
47
+ woff: 'font/woff',
48
+ woff2: 'font/woff2',
49
+ }
50
+
51
+ export type ServeRefusal =
52
+ | 'no-root'
53
+ | 'not-a-directory'
54
+ | 'no-port'
55
+ | 'no-entry'
56
+ | 'bind-failed'
57
+
58
+ export interface ServeRefused {
59
+ readonly ok: false
60
+ readonly reason: ServeRefusal
61
+ readonly detail: string
62
+ }
63
+
64
+ export interface ServeStarted {
65
+ readonly ok: true
66
+ /** Absolute, so a report names the directory rather than the caller's cwd. */
67
+ readonly root: string
68
+ readonly host: string
69
+ readonly port: number
70
+ /** The entry page relative to the root, as the URL spells it. */
71
+ readonly entry: string
72
+ /** What a reader clicks. Complete, including the entry page. */
73
+ readonly url: string
74
+ /** Whether the entry page exists. A missing one is reported, never fatal. */
75
+ readonly entryExists: boolean
76
+ readonly stop: () => Promise<void>
77
+ }
78
+
79
+ export type ServeOutcome = ServeStarted | ServeRefused
80
+
81
+ export interface ServeOptions {
82
+ readonly port?: number
83
+ readonly entry?: string
84
+ }
85
+
86
+ function refuse(reason: ServeRefusal, detail: string): ServeRefused {
87
+ return { ok: false, reason, detail }
88
+ }
89
+
90
+ function contentType(path: string): string {
91
+ const dot = path.lastIndexOf('.')
92
+ if (dot === -1) return 'application/octet-stream'
93
+ const ext = path.slice(dot + 1).toLowerCase()
94
+ return CONTENT_TYPES[ext] ?? 'application/octet-stream'
95
+ }
96
+
97
+ /**
98
+ * Resolves a request path inside the root, or returns undefined when it escapes.
99
+ * The containment test compares resolved absolute paths rather than inspecting
100
+ * the request for `..`, because an encoded traversal survives a textual scan and
101
+ * does not survive resolution.
102
+ */
103
+ export function resolveWithin(
104
+ root: string,
105
+ requestPath: string,
106
+ ): string | undefined {
107
+ let decoded: string
108
+ try {
109
+ decoded = decodeURIComponent(requestPath)
110
+ } catch {
111
+ return undefined
112
+ }
113
+ /**
114
+ * A NUL truncates the path at the filesystem layer, so a request carrying one
115
+ * asks for a different file than the one the containment test read.
116
+ */
117
+ if (decoded.includes('\0')) return undefined
118
+
119
+ const relativePath = decoded.replace(/^\/+/, '')
120
+ const target = resolve(root, relativePath)
121
+ if (target !== root && !target.startsWith(root + sep)) return undefined
122
+ return target
123
+ }
124
+
125
+ /**
126
+ * Re-tests containment after following symlinks. `resolveWithin` is lexical
127
+ * and `resolve` does not follow a link, so a link inside the root clears that
128
+ * test while the file it points at sits outside. This repository is a live
129
+ * instance, since `claude/standards` and `claude/snippets` are links out of
130
+ * `claude/`.
131
+ *
132
+ * Only a path that exists is checked, because a link can be followed only once
133
+ * there is something on the other end, and a path that resolves to nothing is
134
+ * a 404 rather than an escape.
135
+ */
136
+ function escapesThroughLink(root: string, target: string): boolean {
137
+ if (!existsSync(target)) return false
138
+ try {
139
+ const realRoot = realpathSync(root)
140
+ const realTarget = realpathSync(target)
141
+ return realTarget !== realRoot && !realTarget.startsWith(realRoot + sep)
142
+ } catch {
143
+ /* Unreadable resolves to no answer, and no answer is refused. */
144
+ return true
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Whether a bind failure is contention worth trying the next port for.
150
+ *
151
+ * Lifted out of the loop so the decision is testable. Manufacturing a real
152
+ * non-contention bind failure needs a privileged port or an unavailable
153
+ * interface and neither travels between machines, where an error value does,
154
+ * so this is unit-tested and the bind itself is not.
155
+ */
156
+ export function shouldWalkPast(error: unknown): boolean {
157
+ return (error as NodeJS.ErrnoException | null)?.code === 'EADDRINUSE'
158
+ }
159
+
160
+ /**
161
+ * Picks a listening port, starting at the requested one and walking forward.
162
+ * A busy port is the ordinary case rather than a failure, since a preview of
163
+ * one workspace is routinely open while another is started.
164
+ */
165
+ function listen(
166
+ root: string,
167
+ first: number,
168
+ ): { server: ReturnType<typeof Bun.serve>; port: number } | undefined {
169
+ for (let port = first; port < first + PORT_ATTEMPTS; port++) {
170
+ try {
171
+ const server = Bun.serve({
172
+ hostname: SERVE_HOST,
173
+ port,
174
+ fetch: (request) => respond(root, request),
175
+ })
176
+ /**
177
+ * The bound port rather than the requested one. Port 0 asks the OS to
178
+ * choose, so reporting the request builds a URL pointing at nothing.
179
+ * The type admits undefined for a unix socket, which a bind carrying a
180
+ * hostname and a port never is, and the request is the honest fallback.
181
+ */
182
+ return { server, port: server.port ?? port }
183
+ } catch (error) {
184
+ /**
185
+ * Contention is the one cause worth walking past. A permission failure
186
+ * or an unavailable interface swallowed here would be retried twenty
187
+ * times and then reported as a port range being full, which names a
188
+ * cause nothing checked. `startServer` turns the rethrow into a refusal.
189
+ */
190
+ if (!shouldWalkPast(error)) throw error
191
+ }
192
+ }
193
+ return undefined
194
+ }
195
+
196
+ export async function respond(
197
+ root: string,
198
+ request: Request,
199
+ ): Promise<Response> {
200
+ const { pathname, search } = new URL(request.url)
201
+ const forbidden = () =>
202
+ new Response('Forbidden\n', {
203
+ status: 403,
204
+ headers: { 'content-type': 'text/plain; charset=utf-8' },
205
+ })
206
+
207
+ const target = resolveWithin(root, pathname)
208
+ if (!target) return forbidden()
209
+
210
+ /**
211
+ * Tested here as well as before the read, because the redirect below answers
212
+ * ahead of that one. A redirect firing on an out-of-root directory reports
213
+ * that it exists, where one that does not answers 404, and the pair is a
214
+ * fact about the filesystem outside the root.
215
+ */
216
+ if (escapesThroughLink(root, target)) return forbidden()
217
+
218
+ let path = target
219
+ if (existsSync(path) && statSync(path).isDirectory()) {
220
+ /**
221
+ * A browser resolves a relative asset against the last slash of the URL it
222
+ * is on, so answering a directory in place leaves `/lesson` asking for
223
+ * `/course.css` rather than `/lesson/course.css` and the page renders
224
+ * unstyled. The redirect moves the base before the index is served.
225
+ */
226
+ if (!pathname.endsWith('/')) {
227
+ return new Response(null, {
228
+ status: 301,
229
+ headers: { location: `${pathname}/${search}` },
230
+ })
231
+ }
232
+ path = join(path, DEFAULT_ENTRY)
233
+ }
234
+
235
+ /**
236
+ * Sits immediately before the read rather than beside the path that produced
237
+ * it, so every path reaching `Bun.file` has been tested whatever produced
238
+ * it. Checking the request path alone left the appended index untested, and
239
+ * a real directory holding a linked index was served.
240
+ */
241
+ if (escapesThroughLink(root, path)) return forbidden()
242
+
243
+ const file = Bun.file(path)
244
+ if (!(await file.exists())) {
245
+ return new Response(`Not found: ${pathname}\n`, {
246
+ status: 404,
247
+ headers: { 'content-type': 'text/plain; charset=utf-8' },
248
+ })
249
+ }
250
+
251
+ return new Response(file, {
252
+ headers: {
253
+ 'content-type': contentType(path),
254
+ /**
255
+ * A preview is edited and reloaded continuously, and a cached stylesheet
256
+ * reads as a fix that did not work. Revalidation is the whole point of
257
+ * the surface, so it is not negotiable per response.
258
+ */
259
+ 'cache-control': 'no-store',
260
+ },
261
+ })
262
+ }
263
+
264
+ export function startServer(
265
+ dir: string,
266
+ options: ServeOptions = {},
267
+ ): ServeOutcome {
268
+ const root = resolve(process.cwd(), dir)
269
+ if (!existsSync(root)) return refuse('no-root', `${dir} does not exist`)
270
+ if (!statSync(root).isDirectory())
271
+ return refuse('not-a-directory', `${dir} is not a directory`)
272
+
273
+ const entry = (options.entry ?? DEFAULT_ENTRY).replace(/^\/+/, '')
274
+
275
+ /**
276
+ * Checked before a port is taken, since `url` is the field a caller is told
277
+ * to read and hand to a reader. An entry the containment test rejects would
278
+ * otherwise be reported as a link the server then refuses. An entry that is
279
+ * merely absent is not this case and does not refuse, which `entryExists`
280
+ * reports instead.
281
+ */
282
+ const entryPath = resolveWithin(root, entry)
283
+ if (!entryPath) return refuse('no-entry', `${entry} escapes ${dir}`)
284
+
285
+ const first = options.port ?? DEFAULT_PORT
286
+
287
+ /**
288
+ * A bind failure that is not contention reaches here as a throw, and the
289
+ * command's own help promises a reason on stderr or in the record. A stack
290
+ * trace is neither, so it is caught and named.
291
+ */
292
+ let bound: ReturnType<typeof listen>
293
+ try {
294
+ bound = listen(root, first)
295
+ } catch (error) {
296
+ const code = (error as NodeJS.ErrnoException).code ?? 'unknown'
297
+ return refuse(
298
+ 'bind-failed',
299
+ `could not bind ${SERVE_HOST}:${first} (${code})`,
300
+ )
301
+ }
302
+
303
+ if (!bound) {
304
+ return refuse(
305
+ 'no-port',
306
+ `no free port between ${first} and ${first + PORT_ATTEMPTS - 1}`,
307
+ )
308
+ }
309
+
310
+ return {
311
+ ok: true,
312
+ root,
313
+ host: SERVE_HOST,
314
+ port: bound.port,
315
+ entry,
316
+ url: `http://${SERVE_HOST}:${bound.port}/${entry}`,
317
+ entryExists: existsSync(entryPath),
318
+ stop: async () => {
319
+ await bound.server.stop(true)
320
+ },
321
+ }
322
+ }
@@ -0,0 +1,85 @@
1
+ import {
2
+ repositoryOf,
3
+ resolveSessions,
4
+ type ResolvedSession,
5
+ type SessionReport,
6
+ } from '@/sessions/resolve'
7
+ import {
8
+ branchRefs,
9
+ listWorktrees,
10
+ type RefReport,
11
+ type WorktreeEntry,
12
+ } from '@/worktree'
13
+
14
+ export interface ClaimReport {
15
+ readonly claimed: boolean
16
+ readonly worktree: string | null
17
+ readonly sessions: readonly ResolvedSession[]
18
+ /** The refs already naming the branch, local head and `origin` remote-tracking alike. */
19
+ readonly refs: readonly string[]
20
+ /** False when the session roster could not be read, so `claimed` covers the readings around it alone and cannot be trusted as a clean "unclaimed". */
21
+ readonly sessionsReadable: boolean
22
+ /** False when the ref read failed, which is the same false clean under a different reading. */
23
+ readonly refsReadable: boolean
24
+ }
25
+
26
+ export interface ClaimOptions {
27
+ /**
28
+ * The repository the claim is answered about, not merely where the caller
29
+ * stands. Every reading below is taken against it, so handing another
30
+ * project's path asks about that project, which is what lets a dispatcher in
31
+ * one repository see a branch held in another. Defaults to the caller's own
32
+ * directory, so a call omitting it answers exactly as it always has.
33
+ */
34
+ readonly cwd?: string
35
+ readonly resolve?: () => Promise<SessionReport>
36
+ readonly listWorktrees?: (cwd: string) => Promise<readonly WorktreeEntry[]>
37
+ readonly branchRefs?: (branch: string, cwd: string) => Promise<RefReport>
38
+ }
39
+
40
+ /**
41
+ * Answers whether a branch is already claimed, composing the three readings no
42
+ * one surface can answer alone: a worktree can outlive the session that made
43
+ * it, a session can hold a branch before a worktree exists for it, and a branch
44
+ * behind a merged pull request has neither while still being taken.
45
+ *
46
+ * The two readable flags stay separate because a caller told the roster failed
47
+ * looks at the roster. Folding either into `claimed` would report the failure
48
+ * as a clean "unclaimed", which is the answer this exists to stop giving.
49
+ */
50
+ export async function checkClaim(
51
+ branch: string,
52
+ opts: ClaimOptions = {},
53
+ ): Promise<ClaimReport> {
54
+ const cwd = opts.cwd ?? process.cwd()
55
+ const resolve = opts.resolve ?? resolveSessions
56
+ const listAll = opts.listWorktrees ?? listWorktrees
57
+ const readRefs = opts.branchRefs ?? branchRefs
58
+
59
+ const [repository, worktrees, report, refs] = await Promise.all([
60
+ repositoryOf(cwd),
61
+ listAll(cwd),
62
+ resolve(),
63
+ readRefs(branch, cwd),
64
+ ])
65
+
66
+ const worktree =
67
+ worktrees.find((entry) => entry.branch === branch)?.path ?? null
68
+
69
+ const sessions =
70
+ report.kind === 'resolved'
71
+ ? report.sessions.filter(
72
+ (session) =>
73
+ session.branch === branch && session.repository === repository,
74
+ )
75
+ : []
76
+
77
+ return {
78
+ claimed: worktree !== null || sessions.length > 0 || refs.refs.length > 0,
79
+ worktree,
80
+ sessions,
81
+ refs: refs.refs,
82
+ sessionsReadable: report.kind === 'resolved',
83
+ refsReadable: refs.readable,
84
+ }
85
+ }
@@ -0,0 +1,79 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import type { SessionRecord } from '@/sessions/registry'
3
+
4
+ /**
5
+ * How far the liveness answer can be trusted.
6
+ *
7
+ * `confirmed` means the running process was matched against the start time the
8
+ * record stamped, so the pid belongs to the session that wrote the file.
9
+ * `unverified` means the pid answers a signal probe and nothing more, which
10
+ * cannot separate the original session from whatever inherited its pid.
11
+ */
12
+ export type Confidence = 'confirmed' | 'unverified'
13
+
14
+ export interface Liveness {
15
+ readonly alive: boolean
16
+ readonly confidence: Confidence
17
+ }
18
+
19
+ export interface LivenessProbes {
20
+ /** The start time of a running process, or null where it cannot be read. */
21
+ readonly procStartOf: (pid: number) => string | null
22
+ /** Whether the pid answers at all, ignoring which process holds it. */
23
+ readonly responds: (pid: number) => boolean
24
+ }
25
+
26
+ /**
27
+ * Reads a running process's start time from the process filesystem.
28
+ *
29
+ * The comm field is parenthesised and may itself contain spaces, so the split
30
+ * runs from the last `)` rather than over the whole line. Fields resume at the
31
+ * third, which puts the twenty-second at offset nineteen.
32
+ */
33
+ function procStartOf(pid: number): string | null {
34
+ try {
35
+ const stat = readFileSync(`/proc/${pid}/stat`, 'utf8')
36
+ const tail = stat.slice(stat.lastIndexOf(')') + 1).trim()
37
+ return tail.split(/\s+/)[19] ?? null
38
+ } catch {
39
+ return null
40
+ }
41
+ }
42
+
43
+ /** Signal zero performs the permission and existence checks and delivers nothing. */
44
+ function responds(pid: number): boolean {
45
+ try {
46
+ process.kill(pid, 0)
47
+ return true
48
+ } catch (error) {
49
+ // A live process owned by another user answers EPERM, which is an answer.
50
+ return (error as NodeJS.ErrnoException).code === 'EPERM'
51
+ }
52
+ }
53
+
54
+ export const SYSTEM_PROBES: LivenessProbes = { procStartOf, responds }
55
+
56
+ /**
57
+ * Decides whether a record describes a session that is still running.
58
+ *
59
+ * The two paths are not interchangeable and the caller is told which one ran.
60
+ * Where the process filesystem answers, a start-time mismatch is a dead session
61
+ * whose pid was reused and the record is discarded. Where it does not, the
62
+ * probe alone stands, and every surviving row is marked so a caller never reads
63
+ * a recycled pid as a confirmed identity.
64
+ */
65
+ export function liveness(
66
+ record: SessionRecord,
67
+ probes: LivenessProbes = SYSTEM_PROBES,
68
+ ): Liveness {
69
+ const started = record.procStart
70
+
71
+ if (started !== undefined && started.length > 0) {
72
+ const running = probes.procStartOf(record.pid)
73
+ if (running !== null) {
74
+ return { alive: running === started, confidence: 'confirmed' }
75
+ }
76
+ }
77
+
78
+ return { alive: probes.responds(record.pid), confidence: 'unverified' }
79
+ }
@@ -0,0 +1,137 @@
1
+ import { readdirSync, readFileSync } from 'node:fs'
2
+ import { homedir } from 'node:os'
3
+ import { join } from 'node:path'
4
+
5
+ /**
6
+ * One session's own record of itself, as the client writes it.
7
+ *
8
+ * Only the fields this domain reads are declared. The client writes several
9
+ * more, and naming them here would put a second copy of its schema in a
10
+ * repository that does not own it.
11
+ */
12
+ export interface SessionRecord {
13
+ readonly pid: number
14
+ readonly cwd: string
15
+ readonly name: string
16
+ /**
17
+ * Optional because the guard admitting a record checks the three fields above
18
+ * and no more. A client predating any of these writes a record the roster
19
+ * still wants, so the type says what the guard actually proved.
20
+ */
21
+ readonly sessionId: string | undefined
22
+ readonly kind: string | undefined
23
+ readonly status: string | undefined
24
+ /**
25
+ * The epoch millisecond the client last changed `status`. Measured against
26
+ * the live registry, 23 of 341 usable records carry it, and the one record
27
+ * that has ever carried `status: "waiting"` is not among them, so its
28
+ * absence tracks a client version rather than a record's age alone.
29
+ * Declared here rather than read opportunistically off the parsed object,
30
+ * since this file is what states what the domain reads and an undeclared
31
+ * field read anyway is the drift this domain exists downstream of.
32
+ */
33
+ readonly statusUpdatedAt: number | undefined
34
+ /**
35
+ * The epoch millisecond the client last wrote the record at all, a coarser
36
+ * stamp than `statusUpdatedAt` that a client writes whether or not it also
37
+ * stamps the status change itself. Declared as the fallback dwell source
38
+ * for a record predating the narrower field, per the same reasoning above.
39
+ */
40
+ readonly updatedAt: number | undefined
41
+ readonly startedAt: number | undefined
42
+ /**
43
+ * The process start time the client stamped at launch, compared against the
44
+ * running process to separate a live session from a record whose pid has
45
+ * been handed to something else.
46
+ */
47
+ readonly procStart: string | undefined
48
+ }
49
+
50
+ /**
51
+ * Resolves the folder holding one file per session.
52
+ *
53
+ * `CLAUDE_CONFIG_DIR` comes first because a client honouring it writes its
54
+ * records nowhere near the home directory, and a read that ignored it would
55
+ * report an empty roster on a machine running sessions.
56
+ */
57
+ export function registryDir(): string {
58
+ const configured = process.env.CLAUDE_CONFIG_DIR
59
+ const base =
60
+ configured && configured.length > 0
61
+ ? configured
62
+ : join(homedir(), '.claude')
63
+ return join(base, 'sessions')
64
+ }
65
+
66
+ /**
67
+ * Fields whose absence leaves a row unable to answer the question asked of it.
68
+ *
69
+ * The pid has to be positive rather than merely numeric. Signal zero addresses
70
+ * the caller's own process group rather than a process, so a record carrying
71
+ * zero would answer the liveness probe and enter the roster as a live session.
72
+ */
73
+ function isUsable(value: Partial<SessionRecord>): value is SessionRecord {
74
+ return (
75
+ typeof value.pid === 'number' &&
76
+ Number.isInteger(value.pid) &&
77
+ value.pid > 0 &&
78
+ typeof value.cwd === 'string' &&
79
+ value.cwd.length > 0 &&
80
+ typeof value.name === 'string' &&
81
+ value.name.length > 0
82
+ )
83
+ }
84
+
85
+ /**
86
+ * An absent folder and an empty one are separate answers.
87
+ *
88
+ * The first means no client ever wrote a record here, so the read never ran and
89
+ * a roster of none would report a machine with no sessions when the truth is a
90
+ * lookup that failed. The second is a machine whose sessions have all ended.
91
+ */
92
+ export type Registry =
93
+ | { readonly kind: 'absent'; readonly dir: string }
94
+ | {
95
+ readonly kind: 'read'
96
+ readonly dir: string
97
+ readonly records: SessionRecord[]
98
+ }
99
+
100
+ /**
101
+ * Reads every session record in the folder, newest first.
102
+ *
103
+ * A file that does not parse, or that parses without the fields a row is built
104
+ * from, is dropped rather than reported. The folder accumulates a record per
105
+ * session and is never pruned, so it holds thousands of entries from clients
106
+ * spanning many versions, and a finding per stale shape would bury the live
107
+ * sessions this exists to name.
108
+ */
109
+ export function readRegistry(dir: string = registryDir()): Registry {
110
+ let names: string[]
111
+ try {
112
+ names = readdirSync(dir)
113
+ } catch {
114
+ return { kind: 'absent', dir }
115
+ }
116
+
117
+ const records: SessionRecord[] = []
118
+
119
+ for (const name of names) {
120
+ if (!name.endsWith('.json')) continue
121
+
122
+ try {
123
+ const parsed: unknown = JSON.parse(readFileSync(join(dir, name), 'utf8'))
124
+ if (typeof parsed !== 'object' || parsed === null) continue
125
+ const record = parsed as Partial<SessionRecord>
126
+ if (isUsable(record)) records.push(record)
127
+ } catch {
128
+ // Unreadable or malformed. See the note above.
129
+ }
130
+ }
131
+
132
+ return {
133
+ kind: 'read',
134
+ dir,
135
+ records: records.sort((a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0)),
136
+ }
137
+ }