@workerstack/repo 0.0.0-stage → 0.1.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 (438) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +21 -0
  3. package/README.md +0 -3
  4. package/agents/global-settings/antigravity.json +3 -0
  5. package/agents/global-settings/claude.json +6 -0
  6. package/agents/global-settings/codex.toml +2 -0
  7. package/agents/global-settings/cursor.json +3 -0
  8. package/agents/global-settings/grok.toml +36 -0
  9. package/agents/global-settings/manifest.json +36 -0
  10. package/agents/grok/hooks/orchestrate-parent.json +60 -0
  11. package/agents/grok/hooks/rtk.json +16 -0
  12. package/agents/grok/hooks/rulesync.json +3 -0
  13. package/agents/grok/roles/scout.toml +5 -0
  14. package/agents/hooks.jsonc +67 -0
  15. package/agents/mcp-policy.jsonc +16 -0
  16. package/agents/rules/overview.md +21 -0
  17. package/agents/settings/claude.json +5 -0
  18. package/agents/skills/ask/SKILL.md +13 -0
  19. package/agents/skills/auto/SKILL.md +13 -0
  20. package/agents/skills/away/SKILL.md +19 -0
  21. package/agents/skills/blast-radius/SKILL.md +54 -0
  22. package/agents/skills/bro/SKILL.md +9 -0
  23. package/agents/skills/browser/SKILL.md +16 -0
  24. package/agents/skills/clasp/SKILL.md +11 -0
  25. package/agents/skills/commit/SKILL.md +35 -0
  26. package/agents/skills/commit-and-push/SKILL.md +9 -0
  27. package/agents/skills/commit-push/SKILL.md +59 -0
  28. package/agents/skills/commit-push-deploy/SKILL.md +74 -0
  29. package/agents/skills/commit-push-release/SKILL.md +75 -0
  30. package/agents/skills/correct/SKILL.md +39 -0
  31. package/agents/skills/do/SKILL.md +21 -0
  32. package/agents/skills/explain/SKILL.md +13 -0
  33. package/agents/skills/google-workspace/SKILL.md +12 -0
  34. package/agents/skills/handoff/SKILL.md +81 -0
  35. package/agents/skills/implement-plan/SKILL.md +29 -0
  36. package/agents/skills/implement-roadmap/SKILL.md +27 -0
  37. package/agents/skills/jev/SKILL.md +17 -0
  38. package/agents/skills/listen/SKILL.md +15 -0
  39. package/agents/skills/pickup/SKILL.md +58 -0
  40. package/agents/skills/principle-attack-the-premise/SKILL.md +24 -0
  41. package/agents/skills/principle-boundary-discipline/SKILL.md +38 -0
  42. package/agents/skills/principle-build-the-lever/SKILL.md +28 -0
  43. package/agents/skills/principle-encode-lessons-in-structure/SKILL.md +15 -0
  44. package/agents/skills/principle-exhaust-the-design-space/SKILL.md +12 -0
  45. package/agents/skills/principle-experience-first/SKILL.md +22 -0
  46. package/agents/skills/principle-explain-the-number/SKILL.md +21 -0
  47. package/agents/skills/principle-fix-root-causes/SKILL.md +24 -0
  48. package/agents/skills/principle-foundational-thinking/SKILL.md +25 -0
  49. package/agents/skills/principle-guard-the-context-window/SKILL.md +16 -0
  50. package/agents/skills/principle-laziness-protocol/SKILL.md +13 -0
  51. package/agents/skills/principle-make-operations-idempotent/SKILL.md +25 -0
  52. package/agents/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +18 -0
  53. package/agents/skills/principle-minimize-reader-load/SKILL.md +22 -0
  54. package/agents/skills/principle-model-the-domain/SKILL.md +26 -0
  55. package/agents/skills/principle-never-block-on-the-human/SKILL.md +17 -0
  56. package/agents/skills/principle-outcome-oriented-execution/SKILL.md +25 -0
  57. package/agents/skills/principle-prove-it-works/SKILL.md +25 -0
  58. package/agents/skills/principle-redesign-from-first-principles/SKILL.md +18 -0
  59. package/agents/skills/principle-separate-before-serializing-shared-state/SKILL.md +20 -0
  60. package/agents/skills/principle-sequence-verifiable-units/SKILL.md +22 -0
  61. package/agents/skills/principle-subtract-before-you-add/SKILL.md +21 -0
  62. package/agents/skills/principle-test-behavior-not-implementation/SKILL.md +24 -0
  63. package/agents/skills/principle-type-system-discipline/SKILL.md +17 -0
  64. package/agents/skills/review-plan/SKILL.md +28 -0
  65. package/agents/skills/review-roadmap/SKILL.md +28 -0
  66. package/agents/skills/show-me/SKILL.md +21 -0
  67. package/agents/skills/speak/SKILL.md +15 -0
  68. package/agents/skills/su/SKILL.md +11 -0
  69. package/agents/skills/teach/SKILL.md +25 -0
  70. package/agents/skills/tmp/SKILL.md +54 -0
  71. package/agents/skills/typescript-best-practices/SKILL.md +32 -0
  72. package/agents/skills/typescript-best-practices/references/patterns.md +328 -0
  73. package/agents/skills/unslop/SKILL.md +62 -0
  74. package/agents/skills/write-plan/SKILL.md +33 -0
  75. package/agents/skills/write-roadmap/SKILL.md +66 -0
  76. package/agents/subagents/implementer-deep.md +57 -0
  77. package/agents/subagents/implementer-fast.md +57 -0
  78. package/agents/subagents/orchestrator.md +43 -0
  79. package/agents/subagents/researcher.md +47 -0
  80. package/agents/subagents/scout.md +44 -0
  81. package/bin/repo.ts +4 -0
  82. package/cli/actionlint-default.test.ts +38 -0
  83. package/cli/actionlint-download.test.ts +162 -0
  84. package/cli/actionlint-real.test.ts +33 -0
  85. package/cli/actionlint.constants.ts +43 -0
  86. package/cli/actionlint.test.ts +108 -0
  87. package/cli/actionlint.ts +178 -0
  88. package/cli/bin.test.ts +54 -0
  89. package/cli/bin.ts +40 -0
  90. package/cli/check-changelog.test.ts +40 -0
  91. package/cli/check-changelog.ts +12 -0
  92. package/cli/check-comments.test.ts +157 -0
  93. package/cli/check-comments.ts +123 -0
  94. package/cli/check-env-modules.test.ts +85 -0
  95. package/cli/check-env-modules.ts +56 -0
  96. package/cli/check-magic.test.ts +139 -0
  97. package/cli/check-magic.ts +120 -0
  98. package/cli/check-manifests.constants.ts +4 -0
  99. package/cli/check-manifests.test.ts +210 -0
  100. package/cli/check-manifests.ts +71 -0
  101. package/cli/check-nightly.test.ts +103 -0
  102. package/cli/check-nightly.ts +37 -0
  103. package/cli/check-ratchets-missing.test.ts +46 -0
  104. package/cli/check-ratchets-mutation.test.ts +49 -0
  105. package/cli/check-ratchets.constants.ts +20 -0
  106. package/cli/check-ratchets.test.ts +190 -0
  107. package/cli/check-ratchets.ts +193 -0
  108. package/cli/check-rejections.test.ts +220 -0
  109. package/cli/check-rejections.ts +153 -0
  110. package/cli/check-reports.ts +37 -0
  111. package/cli/check-tsconfig.test.ts +507 -0
  112. package/cli/check-tsconfig.ts +205 -0
  113. package/cli/commands.test.ts +114 -0
  114. package/cli/commands.ts +218 -0
  115. package/cli/commit-changelog.ts +27 -0
  116. package/cli/commit-msg-changelog.test.ts +93 -0
  117. package/cli/commit-msg-range.test.ts +126 -0
  118. package/cli/commit-msg-red-proof.test.ts +147 -0
  119. package/cli/commit-msg-repo.test.ts +153 -0
  120. package/cli/commit-msg.test.ts +101 -0
  121. package/cli/commit-msg.ts +194 -0
  122. package/cli/commit-range.constants.ts +17 -0
  123. package/cli/commit-range.ts +57 -0
  124. package/cli/config-knip.test.ts +70 -0
  125. package/cli/config-nightly.test.ts +84 -0
  126. package/cli/config-tdd.test.ts +46 -0
  127. package/cli/config.test.ts +576 -0
  128. package/cli/config.ts +261 -0
  129. package/cli/conventional.ts +9 -0
  130. package/cli/coverage-config.ts +62 -0
  131. package/cli/coverage.constants.ts +6 -0
  132. package/cli/coverage.test.ts +199 -0
  133. package/cli/coverage.ts +127 -0
  134. package/cli/env.constants.ts +10 -0
  135. package/cli/env.test.ts +56 -0
  136. package/cli/env.ts +72 -0
  137. package/cli/fixtures/base-config.ts +13 -0
  138. package/cli/fixtures/gate-world.ts +48 -0
  139. package/cli/fixtures/knip-run.ts +38 -0
  140. package/cli/fixtures/mutate-fakes.ts +137 -0
  141. package/cli/fixtures/planted-repo.ts +50 -0
  142. package/cli/fixtures/publish-world.ts +71 -0
  143. package/cli/fixtures/red-proof-repo.ts +72 -0
  144. package/cli/fixtures/release-context.ts +15 -0
  145. package/cli/fixtures/release-world.ts +111 -0
  146. package/cli/fixtures/stub-bin.ts +40 -0
  147. package/cli/guard-diagnostics.test.ts +157 -0
  148. package/cli/guard-diagnostics.ts +195 -0
  149. package/cli/init.test.ts +487 -0
  150. package/cli/init.ts +275 -0
  151. package/cli/knip-check-ignores.test.ts +106 -0
  152. package/cli/knip-check-real.test.ts +65 -0
  153. package/cli/knip-check.constants.ts +46 -0
  154. package/cli/knip-check.test.ts +114 -0
  155. package/cli/knip-check.ts +169 -0
  156. package/cli/knip-schema.ts +67 -0
  157. package/cli/known-failures.constants.ts +22 -0
  158. package/cli/known-failures.test.ts +138 -0
  159. package/cli/known-failures.ts +131 -0
  160. package/cli/lint/as-needs-reason.ts +41 -0
  161. package/cli/lint/boundaries.ts +73 -0
  162. package/cli/lint/class-file.ts +39 -0
  163. package/cli/lint/config-file.ts +32 -0
  164. package/cli/lint/entry-file.ts +54 -0
  165. package/cli/lint/env-in-config.ts +66 -0
  166. package/cli/lint/filename-matches-export.ts +63 -0
  167. package/cli/lint/index-file.ts +22 -0
  168. package/cli/lint/index.ts +47 -0
  169. package/cli/lint/is-reason.ts +6 -0
  170. package/cli/lint/manifest.ts +46 -0
  171. package/cli/lint/names.ts +59 -0
  172. package/cli/lint/no-commented-out-code.ts +105 -0
  173. package/cli/lint/no-mock-of-subject.ts +64 -0
  174. package/cli/lint/no-module-side-effects.ts +94 -0
  175. package/cli/lint/no-module-state.ts +58 -0
  176. package/cli/lint/no-side-effect-import.ts +17 -0
  177. package/cli/lint/no-v8-ignore.ts +18 -0
  178. package/cli/lint/script-rules.ts +9 -0
  179. package/cli/lint/skip-on-platform.constants.ts +8 -0
  180. package/cli/lint/skip-on-platform.ts +118 -0
  181. package/cli/lint/todo-needs-id.ts +63 -0
  182. package/cli/lint/types-file.ts +28 -0
  183. package/cli/lint-schema.ts +32 -0
  184. package/cli/magic/fixtures/magic-repo.ts +64 -0
  185. package/cli/magic/magic-hit.ts +2 -0
  186. package/cli/magic/magic-nodes.ts +44 -0
  187. package/cli/magic/magic-numbers.test.ts +99 -0
  188. package/cli/magic/magic-numbers.ts +117 -0
  189. package/cli/magic/magic-strings.test.ts +38 -0
  190. package/cli/magic/magic-strings.ts +81 -0
  191. package/cli/main.test.ts +853 -0
  192. package/cli/main.ts +292 -0
  193. package/cli/mutate-args.ts +79 -0
  194. package/cli/mutate-check.test.ts +147 -0
  195. package/cli/mutate-deps.ts +42 -0
  196. package/cli/mutate-edges.test.ts +150 -0
  197. package/cli/mutate-files.test.ts +100 -0
  198. package/cli/mutate-files.ts +31 -0
  199. package/cli/mutate-floors.test.ts +91 -0
  200. package/cli/mutate-floors.ts +104 -0
  201. package/cli/mutate-real-deps.ts +57 -0
  202. package/cli/mutate-real.test.ts +96 -0
  203. package/cli/mutate-report.ts +96 -0
  204. package/cli/mutate-run.ts +128 -0
  205. package/cli/mutate-shards.ts +64 -0
  206. package/cli/mutate.constants.ts +67 -0
  207. package/cli/mutate.test.ts +182 -0
  208. package/cli/mutate.ts +64 -0
  209. package/cli/nested-vitest-flags.ts +8 -0
  210. package/cli/nightly-config.constants.ts +14 -0
  211. package/cli/nightly-config.ts +62 -0
  212. package/cli/prompt.test.ts +202 -0
  213. package/cli/prompt.ts +1 -0
  214. package/cli/red-proof-edges.test.ts +78 -0
  215. package/cli/red-proof-failures.test.ts +82 -0
  216. package/cli/red-proof-tree.test.ts +157 -0
  217. package/cli/red-proof-verdict.test.ts +44 -0
  218. package/cli/red-proof.constants.ts +14 -0
  219. package/cli/red-proof.ts +202 -0
  220. package/cli/release/changelog-parse.ts +83 -0
  221. package/cli/release/changelog.test.ts +141 -0
  222. package/cli/release/changelog.ts +106 -0
  223. package/cli/release/gate-refusals.test.ts +85 -0
  224. package/cli/release/gate.test.ts +63 -0
  225. package/cli/release/gate.ts +70 -0
  226. package/cli/release/git.ts +66 -0
  227. package/cli/release/github.test.ts +128 -0
  228. package/cli/release/github.ts +46 -0
  229. package/cli/release/list-dir.test.ts +11 -0
  230. package/cli/release/list-dir.ts +6 -0
  231. package/cli/release/main.test.ts +48 -0
  232. package/cli/release/main.ts +51 -0
  233. package/cli/release/manifest.test.ts +24 -0
  234. package/cli/release/manifest.ts +34 -0
  235. package/cli/release/pack.test.ts +107 -0
  236. package/cli/release/pack.ts +76 -0
  237. package/cli/release/plan.ts +127 -0
  238. package/cli/release/publish-run.test.ts +98 -0
  239. package/cli/release/publish.test.ts +127 -0
  240. package/cli/release/publish.ts +125 -0
  241. package/cli/release/refuse.ts +5 -0
  242. package/cli/release/release-messages.constants.ts +96 -0
  243. package/cli/release/release-refusals.test.ts +149 -0
  244. package/cli/release/release.constants.ts +128 -0
  245. package/cli/release/release.test.ts +199 -0
  246. package/cli/release/release.ts +145 -0
  247. package/cli/release/version.test.ts +118 -0
  248. package/cli/release/version.ts +69 -0
  249. package/cli/release/wait.test.ts +101 -0
  250. package/cli/release/wait.ts +95 -0
  251. package/cli/script-diagnostics.test.ts +76 -0
  252. package/cli/script-diagnostics.ts +62 -0
  253. package/cli/scripts/board.ts +519 -0
  254. package/cli/scripts/browser.ts +994 -0
  255. package/cli/scripts/change-set.ts +105 -0
  256. package/cli/scripts/check-globals.ts +199 -0
  257. package/cli/scripts/ci-skips.constants.ts +17 -0
  258. package/cli/scripts/ci-skips.ts +118 -0
  259. package/cli/scripts/ci-wait-run.ts +28 -0
  260. package/cli/scripts/ci-wait.constants.ts +44 -0
  261. package/cli/scripts/ci-wait.ts +175 -0
  262. package/cli/scripts/ci.constants.ts +15 -0
  263. package/cli/scripts/ci.ts +208 -0
  264. package/cli/scripts/clean.ts +363 -0
  265. package/cli/scripts/comments-step.ts +15 -0
  266. package/cli/scripts/doctor.ts +828 -0
  267. package/cli/scripts/emit-agent-shell-tools.ts +91 -0
  268. package/cli/scripts/emit-mcp-policy.ts +402 -0
  269. package/cli/scripts/folder-map.ts +353 -0
  270. package/cli/scripts/gate-fat-tools.ts +1095 -0
  271. package/cli/scripts/gitignore-guard.ts +338 -0
  272. package/cli/scripts/google-workspace.ts +779 -0
  273. package/cli/scripts/hook-command.ts +72 -0
  274. package/cli/scripts/hook-input.ts +178 -0
  275. package/cli/scripts/hook-io.constants.ts +5 -0
  276. package/cli/scripts/hook-io.ts +46 -0
  277. package/cli/scripts/hooks-union.ts +469 -0
  278. package/cli/scripts/interactive.ts +30 -0
  279. package/cli/scripts/jev.ts +420 -0
  280. package/cli/scripts/job.ts +934 -0
  281. package/cli/scripts/jsonc.ts +9 -0
  282. package/cli/scripts/lessons.ts +564 -0
  283. package/cli/scripts/lint-baseline.ts +233 -0
  284. package/cli/scripts/lint-main.ts +24 -0
  285. package/cli/scripts/lint-skips.ts +139 -0
  286. package/cli/scripts/lint.constants.ts +6 -0
  287. package/cli/scripts/lint.ts +142 -0
  288. package/cli/scripts/listen.ts +962 -0
  289. package/cli/scripts/managed-text.ts +47 -0
  290. package/cli/scripts/orchestrate-parent.ts +1155 -0
  291. package/cli/scripts/parse-need.ts +81 -0
  292. package/cli/scripts/persist-handoff.ts +228 -0
  293. package/cli/scripts/persist-implementation.ts +276 -0
  294. package/cli/scripts/persist-plan.ts +888 -0
  295. package/cli/scripts/persist-roadmap.ts +478 -0
  296. package/cli/scripts/persist-tmp.ts +177 -0
  297. package/cli/scripts/persist.ts +54 -0
  298. package/cli/scripts/phase-files.ts +121 -0
  299. package/cli/scripts/prompt.ts +38 -0
  300. package/cli/scripts/purge-global-mcps.ts +313 -0
  301. package/cli/scripts/rejections-step.ts +37 -0
  302. package/cli/scripts/rtk-grok.ts +102 -0
  303. package/cli/scripts/scrub-spawn-prompt.ts +103 -0
  304. package/cli/scripts/scrub-text-lib.ts +58 -0
  305. package/cli/scripts/session-map.ts +69 -0
  306. package/cli/scripts/setup.ts +862 -0
  307. package/cli/scripts/skill-conflicts.ts +82 -0
  308. package/cli/scripts/speak.ts +370 -0
  309. package/cli/scripts/strategy.ts +168 -0
  310. package/cli/scripts/sync-global-settings.ts +316 -0
  311. package/cli/scripts/turn-steps.ts +249 -0
  312. package/cli/scripts/turn.ts +157 -0
  313. package/cli/scripts/typecheck-step.ts +43 -0
  314. package/cli/scripts/vscode-tasks.ts +175 -0
  315. package/cli/scripts/workspace-root.ts +160 -0
  316. package/cli/scripts/worktree.ts +806 -0
  317. package/cli/spawn.test.ts +34 -0
  318. package/cli/spawn.ts +27 -0
  319. package/cli/staged-change.constants.ts +11 -0
  320. package/cli/staged-change.ts +79 -0
  321. package/cli/stryker-preload.test.ts +50 -0
  322. package/cli/stryker-preload.ts +9 -0
  323. package/cli/sync/agents/frontmatter.test.ts +197 -0
  324. package/cli/sync/agents/frontmatter.ts +140 -0
  325. package/cli/sync/agents/hooks.test.ts +517 -0
  326. package/cli/sync/agents/hooks.ts +401 -0
  327. package/cli/sync/agents/rules.test.ts +229 -0
  328. package/cli/sync/agents/rules.ts +84 -0
  329. package/cli/sync/agents/sources.test.ts +375 -0
  330. package/cli/sync/agents/sources.ts +244 -0
  331. package/cli/sync/agents/subagents.test.ts +149 -0
  332. package/cli/sync/agents/subagents.ts +158 -0
  333. package/cli/sync/agents-text.test.ts +31 -0
  334. package/cli/sync/agents-text.ts +36 -0
  335. package/cli/sync/fixtures/copied.README.md +35 -0
  336. package/cli/sync/fixtures/copied.tgz +0 -0
  337. package/cli/sync/fixtures/maps/golden/INDEX.md +22 -0
  338. package/cli/sync/fixtures/maps-ws.tgz +0 -0
  339. package/cli/sync/git-files.test.ts +340 -0
  340. package/cli/sync/git-files.ts +197 -0
  341. package/cli/sync/git-hooks.test.ts +94 -0
  342. package/cli/sync/golden.test.ts +15 -0
  343. package/cli/sync/hooks-mode.test.ts +99 -0
  344. package/cli/sync/hosts.ts +28 -0
  345. package/cli/sync/knip-config.constants.ts +4 -0
  346. package/cli/sync/knip-config.test.ts +147 -0
  347. package/cli/sync/knip-config.ts +108 -0
  348. package/cli/sync/manifest.test.ts +78 -0
  349. package/cli/sync/manifest.ts +60 -0
  350. package/cli/sync/maps.test.ts +304 -0
  351. package/cli/sync/maps.ts +297 -0
  352. package/cli/sync/migrate.test.ts +2000 -0
  353. package/cli/sync/migrate.ts +1104 -0
  354. package/cli/sync/nightly-jobs.test.ts +63 -0
  355. package/cli/sync/nightly.constants.ts +48 -0
  356. package/cli/sync/nightly.test.ts +100 -0
  357. package/cli/sync/nightly.ts +75 -0
  358. package/cli/sync/outputs.test.ts +351 -0
  359. package/cli/sync/outputs.ts +168 -0
  360. package/cli/sync/oxlint-config.test.ts +178 -0
  361. package/cli/sync/oxlint-config.ts +168 -0
  362. package/cli/sync/release-jobs.test.ts +29 -0
  363. package/cli/sync/release-reports.test.ts +31 -0
  364. package/cli/sync/release-workflow.constants.ts +61 -0
  365. package/cli/sync/release-workflow.test.ts +192 -0
  366. package/cli/sync/release-workflow.ts +86 -0
  367. package/cli/sync/release.constants.ts +93 -0
  368. package/cli/sync/release.test.ts +187 -0
  369. package/cli/sync/release.ts +112 -0
  370. package/cli/sync/repo-dir.test.ts +371 -0
  371. package/cli/sync/repo-dir.ts +205 -0
  372. package/cli/sync/standards.constants.ts +8 -0
  373. package/cli/sync/standards.test.ts +206 -0
  374. package/cli/sync/standards.ts +179 -0
  375. package/cli/sync/stock-history.json +2796 -0
  376. package/cli/sync/stock-history.test.ts +66 -0
  377. package/cli/sync/stock-history.ts +84 -0
  378. package/cli/sync/stock.test.ts +47 -0
  379. package/cli/sync/stock.ts +24 -0
  380. package/cli/sync/trees.test.ts +621 -0
  381. package/cli/sync/trees.ts +280 -0
  382. package/cli/sync/validate.test.ts +299 -0
  383. package/cli/sync/validate.ts +402 -0
  384. package/cli/sync/workspace.test.ts +180 -0
  385. package/cli/sync/workspace.ts +138 -0
  386. package/cli/sync/write.test.ts +341 -0
  387. package/cli/sync/write.ts +231 -0
  388. package/cli/sync.test.ts +1495 -0
  389. package/cli/sync.ts +360 -0
  390. package/cli/tdd-scope.test.ts +36 -0
  391. package/cli/tdd-scope.ts +41 -0
  392. package/cli/test-files.ts +16 -0
  393. package/cli/ts-program.ts +69 -0
  394. package/cli/tsconfig.constants.ts +8 -0
  395. package/cli/workerstack-config.types.ts +21 -0
  396. package/dist/cli/env.d.ts +10 -0
  397. package/dist/cli/env.js +64 -0
  398. package/dist/presets/temp-root-env.d.ts +10 -0
  399. package/dist/presets/temp-root-env.js +20 -0
  400. package/dist/presets/temp-root.constants.d.ts +6 -0
  401. package/dist/presets/temp-root.constants.js +6 -0
  402. package/dist/presets/temp-root.d.ts +2 -0
  403. package/dist/presets/temp-root.js +60 -0
  404. package/dist/presets/vitest.constants.d.ts +16 -0
  405. package/dist/presets/vitest.constants.js +16 -0
  406. package/dist/presets/vitest.d.ts +11 -0
  407. package/dist/presets/vitest.js +67 -0
  408. package/dist/presets/workers-plugin.d.ts +10 -0
  409. package/dist/presets/workers-plugin.js +54 -0
  410. package/package.json +141 -4
  411. package/references/INDEX.md +100 -0
  412. package/references/browser.md +66 -0
  413. package/references/cascade.md +15 -0
  414. package/references/clasp.md +21 -0
  415. package/references/cloudflare.md +36 -0
  416. package/references/google-workspace.md +79 -0
  417. package/references/hooks.md +103 -0
  418. package/references/implementation-format.md +119 -0
  419. package/references/jev.md +56 -0
  420. package/references/lessons.md +73 -0
  421. package/references/listen.md +80 -0
  422. package/references/map.md +107 -0
  423. package/references/output.md +47 -0
  424. package/references/plan-format.md +264 -0
  425. package/references/review-loop.md +48 -0
  426. package/references/roadmap-format.md +104 -0
  427. package/references/rtk.md +27 -0
  428. package/references/scope.md +76 -0
  429. package/references/setup.md +42 -0
  430. package/references/speak.md +59 -0
  431. package/tsconfig/base.json +28 -0
  432. package/tsconfig/bun.d.ts +1 -0
  433. package/tsconfig/bun.json +8 -0
  434. package/tsconfig/reset.d.ts +1 -0
  435. package/tsconfig/strict.json +12 -0
  436. package/tsconfig/web.json +7 -0
  437. package/tsconfig/worker.d.ts +1 -0
  438. package/tsconfig/worker.json +8 -0
@@ -0,0 +1,205 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import ts from "typescript";
5
+ import { canonicalPath } from "./scripts/workspace-root.ts";
6
+ import { RESET, RUNTIME_CONFIG, SRC_CONFIG } from "./tsconfig.constants.ts";
7
+
8
+ const TSCONFIG_NAME = /^tsconfig(\..+)?\.json$/;
9
+ const PACKAGE_FILE = "package.json";
10
+ /** `-p x`, `--project x` and `--project=x` in a script; the value is the first capture. */
11
+ const PROJECT_FLAG = /(?:^|\s)(?:-p|--project)(?:=|\s+)(\S+)/g;
12
+ const QUOTES = /^["']|["']$/g;
13
+ const REJECT_FILE = /\.reject\.tsx?$/;
14
+ /** The configs that type-check a package's code, which a reject file must stay out of. */
15
+ const CHECKING_CONFIGS: readonly string[] = [RUNTIME_CONFIG, SRC_CONFIG];
16
+ /** The flags `tsconfig/strict.json` turns on, which a `tsconfig.src.json` must resolve with. */
17
+ const STRICT_FLAGS = [
18
+ "isolatedDeclarations",
19
+ "erasableSyntaxOnly",
20
+ "verbatimModuleSyntax",
21
+ "exactOptionalPropertyTypes",
22
+ "noUncheckedIndexedAccess",
23
+ ] as const;
24
+
25
+ /** Tracked and untracked, non-ignored tsconfigs under `cwd`, posix and relative to it; none outside a git repo. */
26
+ function listTsconfigs(cwd: string): string[] {
27
+ let out: string;
28
+ try {
29
+ out = execFileSync("git", ["ls-files", "-z", "-co", "--exclude-standard"], {
30
+ cwd,
31
+ encoding: "utf8",
32
+ stdio: ["ignore", "pipe", "ignore"],
33
+ });
34
+ } catch {
35
+ return [];
36
+ }
37
+ // An unmerged path is listed once per stage, and a deleted tracked one stays in the index.
38
+ const rels = new Set(out.split("\0").filter((rel) => TSCONFIG_NAME.test(path.posix.basename(rel))));
39
+ return [...rels].filter((rel) => fs.existsSync(path.join(cwd, rel)));
40
+ }
41
+
42
+ /** The line check prints, and turn repeats, for a folder whose `tsconfig.json` has no strict sibling; `.` is the root. */
43
+ export function missingSrcConfig(dir: string): string {
44
+ return `tsconfig: ${dir} has no ${SRC_CONFIG}; add one with "extends": ["./tsconfig.json", "@workerstack/repo/tsconfig/strict"] and an "exclude" that repeats tsconfig.json's and adds the tests`;
45
+ }
46
+
47
+ /** A tsconfig parsed, with what its own text sets, or the line naming why it cannot be read. */
48
+ type Read =
49
+ | {
50
+ ok: true;
51
+ setsFiles: boolean;
52
+ solution: boolean;
53
+ parsed: ts.ParsedCommandLine;
54
+ }
55
+ | { ok: false; line: string };
56
+
57
+ /** typescript keys its source files by forward-slash paths and asserts that spelling on win32. */
58
+ function tsPath(cwd: string, rel: string): string {
59
+ return path.join(cwd, rel).split(path.sep).join("/");
60
+ }
61
+
62
+ function readTsconfig(cwd: string, rel: string): Read {
63
+ const file = tsPath(cwd, rel);
64
+ const read = ts.readConfigFile(file, ts.sys.readFile);
65
+ if (read.error) {
66
+ return {
67
+ ok: false,
68
+ line: `tsconfig: ${rel}: ${ts.flattenDiagnosticMessageText(read.error.messageText, " ")}`,
69
+ };
70
+ }
71
+ // Without a read error, typescript has parsed the root as a JSON object.
72
+ const raw: { files?: unknown; references?: unknown } = read.config;
73
+ // Read before parsing, which writes the inherited "files" into the raw object.
74
+ const setsFiles = raw.files !== undefined;
75
+ const solution = Array.isArray(raw.files) && raw.files.length === 0 && raw.references !== undefined;
76
+ const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, path.dirname(file), undefined, file);
77
+ return { ok: true, setsFiles, solution, parsed };
78
+ }
79
+
80
+ function resetProblem(rel: string, read: Read & { ok: true }, reset: string): string | undefined {
81
+ if (read.setsFiles) {
82
+ return `tsconfig: ${rel} sets "files", which replaces the base's reset.d.ts. List sources under "include".`;
83
+ }
84
+ const names = read.parsed.fileNames;
85
+ const keepsReset = names.some((name) => path.basename(name) === RESET && canonicalPath(name) === reset);
86
+ if (keepsReset) return undefined;
87
+ return `tsconfig: ${rel} does not extend a base. Extend "@workerstack/repo/tsconfig/base", "bun", "worker" or "web".`;
88
+ }
89
+
90
+ /** A `tsconfig.src.json` that resolves with any strict flag off. */
91
+ function weakProblem(rel: string, options: ts.CompilerOptions): string | undefined {
92
+ const off = STRICT_FLAGS.filter((flag) => options[flag] !== true);
93
+ if (off.length === 0) return undefined;
94
+ return `tsconfig: ${rel} resolves without ${off.join(", ")}; extend "@workerstack/repo/tsconfig/strict" and drop the override`;
95
+ }
96
+
97
+ /** One line per reject file a checking config includes, which would fail its type check on purpose. */
98
+ function rejectProblems(cwd: string, rel: string, fileNames: readonly string[]): string[] {
99
+ if (!CHECKING_CONFIGS.includes(path.posix.basename(rel))) return [];
100
+ const included = fileNames.filter((name) => REJECT_FILE.test(name));
101
+ return included.map((name) => {
102
+ const file = path.relative(cwd, name).split(path.sep).join("/");
103
+ return `tsconfig: ${rel} includes ${file}; add "**/*.reject.ts" and "**/*.reject.tsx" to its exclude`;
104
+ });
105
+ }
106
+
107
+ /** What a check of the tsconfigs under `cwd` reads: every listed one, and the canonical path of the base reset.d.ts. */
108
+ type Scan = { cwd: string; listed: ReadonlySet<string>; reset: string };
109
+
110
+ function problems({ cwd, listed, reset }: Scan, rel: string): string[] {
111
+ const read = readTsconfig(cwd, rel);
112
+ if (!read.ok) return [read.line];
113
+ if (read.solution) return [];
114
+ const name = path.posix.basename(rel);
115
+ const dir = path.posix.dirname(rel);
116
+ const lines = [resetProblem(rel, read, reset)];
117
+ if (name === SRC_CONFIG) lines.push(weakProblem(rel, read.parsed.options));
118
+ if (name === RUNTIME_CONFIG && !listed.has(path.posix.join(dir, SRC_CONFIG))) lines.push(missingSrcConfig(dir));
119
+ return [...lines.filter((line) => line !== undefined), ...rejectProblems(cwd, rel, read.parsed.fileNames)];
120
+ }
121
+
122
+ function isObject(value: unknown): value is { path?: unknown } {
123
+ return value instanceof Object;
124
+ }
125
+
126
+ /** The raw JSON object at `cwd`/`rel`, empty when it is missing or unreadable. */
127
+ function readRaw(cwd: string, rel: string): { scripts?: unknown; extends?: unknown; references?: unknown } {
128
+ const read = ts.readConfigFile(tsPath(cwd, rel), ts.sys.readFile);
129
+ return read.error ? {} : read.config;
130
+ }
131
+
132
+ /** The config a `-p` value or a reference `path` names from `dir`: the file, or a folder's `tsconfig.json`. */
133
+ function projectFile(dir: string, spec: string): string {
134
+ const target = path.posix.join(dir, spec.replace(QUOTES, ""));
135
+ return target.endsWith(".json") ? target : path.posix.join(target, RUNTIME_CONFIG);
136
+ }
137
+
138
+ /** Every config the scripts of `dir`'s package.json run with `-p` or `--project`, relative to `cwd`. */
139
+ function scriptTargets(cwd: string, dir: string): string[] {
140
+ const scripts = readRaw(cwd, path.posix.join(dir, PACKAGE_FILE)).scripts;
141
+ const commands = typeof scripts === "object" && scripts !== null ? Object.values(scripts) : [];
142
+ const flags = commands
143
+ .filter((command) => typeof command === "string")
144
+ .flatMap((command) => [...command.matchAll(PROJECT_FLAG)]);
145
+ return flags.flatMap((flag) => flag.slice(1).map((spec) => projectFile(dir, spec)));
146
+ }
147
+
148
+ /** The folders from `rel`'s own up to `cwd` that hold a package.json. */
149
+ function packageDirs(cwd: string, rel: string): string[] {
150
+ const dirs: string[] = [];
151
+ for (let dir = path.posix.dirname(rel); ; dir = path.posix.dirname(dir)) {
152
+ if (fs.existsSync(path.join(cwd, dir, PACKAGE_FILE))) dirs.push(dir);
153
+ if (dir === ".") return dirs;
154
+ }
155
+ }
156
+
157
+ /** The local configs `rel` extends and references; an extends of a package is outside the repo's own files. */
158
+ function linkedConfigs(cwd: string, rel: string): string[] {
159
+ const raw = readRaw(cwd, rel);
160
+ const dir = path.posix.dirname(rel);
161
+ const parents = [raw.extends]
162
+ .flat()
163
+ .flatMap((spec) => (typeof spec === "string" && spec.startsWith(".") ? [spec] : []));
164
+ const refs = Array.isArray(raw.references) ? raw.references : [];
165
+ const extended = parents.map((spec) => path.posix.join(dir, spec.endsWith(".json") ? spec : `${spec}.json`));
166
+ const referenced = refs.flatMap((ref) =>
167
+ isObject(ref) && typeof ref.path === "string" ? [projectFile(dir, ref.path)] : [],
168
+ );
169
+ return [...extended, ...referenced];
170
+ }
171
+
172
+ /** The configs a gate runs: the two by name, those a script names, and all they extend or reference. */
173
+ function runConfigs(cwd: string, rels: readonly string[]): Set<string> {
174
+ const named = rels.filter((rel) => CHECKING_CONFIGS.includes(path.posix.basename(rel)));
175
+ const scripted = [...new Set(rels.flatMap((rel) => packageDirs(cwd, rel)))].flatMap((dir) => scriptTargets(cwd, dir));
176
+ const queue = [...named, ...scripted];
177
+ const seen = new Set<string>();
178
+ for (const rel of queue) {
179
+ if (seen.has(rel)) continue;
180
+ seen.add(rel);
181
+ queue.push(...linkedConfigs(cwd, rel));
182
+ }
183
+ return seen;
184
+ }
185
+
186
+ function unrunProblem(rel: string, run: ReadonlySet<string>): string[] {
187
+ if (run.has(rel)) return [];
188
+ return [`tsconfig: ${rel} is run by no gate; delete it, or run it from a package.json script with tsc -p ${rel}`];
189
+ }
190
+
191
+ /**
192
+ * One stderr line per tsconfig under `cwd` that drops ts-reset (it sets `files`, extends no base from `pkgRoot`, or
193
+ * cannot be read), per `tsconfig.json` with no `tsconfig.src.json` beside it, and per `tsconfig.src.json` that
194
+ * resolves with a strict flag off, and per reject file a `tsconfig.json` or `tsconfig.src.json` includes. A
195
+ * solution-style `{"files": [], "references": [...]}` checks no code and passes. A tsconfig no gate runs gets a line
196
+ * too: run means named `tsconfig.json` or `tsconfig.src.json`, named by `-p` or `--project` in a script of a
197
+ * package.json above it, or extended or referenced, transitively, by a run config.
198
+ */
199
+ export function checkTsconfigs(cwd: string, pkgRoot: string): string[] {
200
+ const reset = canonicalPath(path.join(pkgRoot, "tsconfig", RESET));
201
+ const rels = listTsconfigs(cwd);
202
+ const scan: Scan = { cwd, listed: new Set(rels), reset };
203
+ const run = runConfigs(cwd, rels);
204
+ return rels.flatMap((rel) => [...problems(scan, rel), ...unrunProblem(rel, run)]);
205
+ }
@@ -0,0 +1,114 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { HOOK_NAMES } from "./scripts/hook-command.ts";
4
+ import { COMMANDS, HOOKS, dispatch, isCommandName, runCommand, type Registry } from "./commands.ts";
5
+
6
+ describe("registry", () => {
7
+ it.each(Object.keys(COMMANDS))("command %s loads a main", async (name) => {
8
+ const entry = COMMANDS[name as keyof typeof COMMANDS];
9
+ expect(typeof (await entry.load()).main).toBe("function");
10
+ });
11
+
12
+ it.each(HOOK_NAMES)("hook %s loads a main", async (name) => {
13
+ expect(typeof (await HOOKS[name].load()).main).toBe("function");
14
+ });
15
+
16
+ it("names commands, and nothing else", () => {
17
+ expect(isCommandName("job")).toBe(true);
18
+ expect(isCommandName("toString")).toBe(false);
19
+ expect(isCommandName(undefined)).toBe(false);
20
+ });
21
+ });
22
+
23
+ describe("coverage", () => {
24
+ it("is a command, with the help line Usage shows", () => {
25
+ expect(isCommandName("coverage")).toBe(true);
26
+ expect(COMMANDS.coverage.summary).toBe("run the suite with coverage and raise coverage-floors.json; a drop fails");
27
+ });
28
+ });
29
+
30
+ describe("mutate", () => {
31
+ it("is a command, with a help line that names its shards and floors", () => {
32
+ expect(isCommandName("mutate")).toBe(true);
33
+ expect(COMMANDS.mutate.summary).toBe(
34
+ "mutation-test the tracked files in a clone of HEAD (--shard i/n, --report <file>); check <dir> [--write]: score reports against mutation-floors.json",
35
+ );
36
+ });
37
+ });
38
+
39
+ describe("bun repo", () => {
40
+ it("has no tracked file but the bin that bun would run in its place", () => {
41
+ const files = execFileSync("git", ["ls-files"], { encoding: "utf8" }).split("\n");
42
+ expect(files.filter((f) => /(?:^|\/)repo\.(?:[cm]?js|jsx|tsx?)$/.test(f))).toEqual(["bin/repo.ts"]);
43
+ });
44
+ });
45
+
46
+ describe("dispatch", () => {
47
+ const registry = (main: (argv: string[]) => Promise<number>): Registry => {
48
+ const stub = <T extends Record<string, object>>(table: T) =>
49
+ Object.fromEntries(Object.entries(table).map(([k, v]) => [k, { ...v, load: async () => ({ main }) }])) as T;
50
+ return { commands: stub(COMMANDS), hooks: stub(HOOKS) };
51
+ };
52
+
53
+ it.each([
54
+ ["yolo", ["--dry-run", "--host", "grok"]],
55
+ ["purge-mcps", ["--apply"]],
56
+ ["doctor", []],
57
+ ["setup", ["speak", "--skip-verify"]],
58
+ ["job", ["--job", "exec", "--query", "echo hi", "-y"]],
59
+ ["worktree", ["add", "x"]],
60
+ ])("%s runs in-process with its argv untouched", async (cmd, args) => {
61
+ const main = vi.fn(async () => 3);
62
+ expect(await dispatch([cmd, ...args], registry(main))).toBe(3);
63
+ expect(main).toHaveBeenCalledWith(args);
64
+ });
65
+
66
+ it("hook <name> runs the hook with its fixed argv first", async () => {
67
+ const main = vi.fn(async () => 0);
68
+ expect(await dispatch(["hook", "worktree"], registry(main))).toBe(0);
69
+ expect(main).toHaveBeenCalledWith(["hook"]);
70
+ expect(await dispatch(["hook", "gate-fat-tools", "x"], registry(main))).toBe(0);
71
+ expect(main).toHaveBeenLastCalledWith(["x"]);
72
+ });
73
+
74
+ it.each([
75
+ [
76
+ [],
77
+ "repo hook: name a hook; use gate-fat-tools, orchestrate-parent, rtk-grok, scrub-spawn-prompt, session-map or worktree\n",
78
+ ],
79
+ [
80
+ ["nope"],
81
+ 'repo hook: unknown hook "nope"; use gate-fat-tools, orchestrate-parent, rtk-grok, scrub-spawn-prompt, session-map or worktree\n',
82
+ ],
83
+ ])("hook %j prints the hook list and exits 1", async (args, message) => {
84
+ const main = vi.fn(async () => 0);
85
+ const err = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
86
+ try {
87
+ expect(await dispatch(["hook", ...args], registry(main))).toBe(1);
88
+ expect(err.mock.calls.map((c) => String(c[0])).join("")).toBe(message);
89
+ } finally {
90
+ err.mockRestore();
91
+ }
92
+ expect(main).not.toHaveBeenCalled();
93
+ });
94
+
95
+ it("leaves every other command to cli/main.ts", async () => {
96
+ const main = vi.fn(async () => 0);
97
+ for (const argv of [[], ["init"], ["map", "cli"], ["help"], ["toString"]])
98
+ expect(await dispatch(argv, registry(main)), argv.join(" ")).toBeNull();
99
+ expect(main).not.toHaveBeenCalled();
100
+ });
101
+
102
+ it("with no registry given, still leaves a non-command to cli/main.ts", async () => {
103
+ expect(await dispatch(["init"])).toBeNull();
104
+ });
105
+
106
+ it("runCommand runs the real registry entry by default", async () => {
107
+ const err = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
108
+ try {
109
+ expect(await runCommand("lessons", [])).toBe(1);
110
+ } finally {
111
+ err.mockRestore();
112
+ }
113
+ });
114
+ });
@@ -0,0 +1,218 @@
1
+ /** Every script an agent, a hook or a person runs, behind `repo <cmd>` and `repo hook <name>`. */
2
+ import { HOOK_USAGE, isHookName, type HookName } from "./scripts/hook-command.ts";
3
+
4
+ export type Main = (argv: string[]) => number | Promise<number>;
5
+
6
+ export interface Entry {
7
+ readonly summary: string;
8
+ readonly load: () => Promise<{ readonly main: Main }>;
9
+ }
10
+
11
+ export interface HookEntry extends Entry {
12
+ /** Arguments the hook's `main` gets in front of any the command line adds. */
13
+ readonly argv: readonly string[];
14
+ }
15
+
16
+ export type CommandName =
17
+ | "job"
18
+ | "worktree"
19
+ | "persist"
20
+ | "phase-files"
21
+ | "lessons"
22
+ | "browser"
23
+ | "google-workspace"
24
+ | "jev"
25
+ | "listen"
26
+ | "speak"
27
+ | "setup"
28
+ | "doctor"
29
+ | "clean"
30
+ | "yolo"
31
+ | "purge-mcps"
32
+ | "tasks"
33
+ | "lint"
34
+ | "fmt"
35
+ | "turn"
36
+ | "ci"
37
+ | "coverage"
38
+ | "mutate"
39
+ | "release"
40
+ | "commit-msg";
41
+
42
+ export const COMMANDS: Readonly<Record<CommandName, Entry>> = {
43
+ job: {
44
+ summary: "Compressed search, exec, slice, fetch and test dumps (--job search|exec|slice|fetch|test)",
45
+ load: () => import("./scripts/job.ts"),
46
+ },
47
+ worktree: {
48
+ summary: "Managed worktrees: add, list, finish, remove",
49
+ load: () => import("./scripts/worktree.ts"),
50
+ },
51
+ persist: {
52
+ summary: "Save a plan, roadmap, handoff, implementation or tmp body from stdin (persist <kind> [--workspace dir])",
53
+ load: () => import("./scripts/persist.ts"),
54
+ },
55
+ "phase-files": {
56
+ summary: "Map a parallel group's unstaged changes to its phases",
57
+ load: () => import("./scripts/phase-files.ts"),
58
+ },
59
+ lessons: {
60
+ summary: "Match, hit and list lessons",
61
+ load: () => import("./scripts/lessons.ts"),
62
+ },
63
+ browser: {
64
+ summary: "Browse with Lightpanda, Chromium when blocked",
65
+ load: () => import("./scripts/browser.ts"),
66
+ },
67
+ "google-workspace": {
68
+ summary: "Google Sheets, Docs, Slides, Calendar and Gmail",
69
+ load: () => import("./scripts/google-workspace.ts"),
70
+ },
71
+ jev: {
72
+ summary: "Yes/no or named-option decision on saved text",
73
+ load: () => import("./scripts/jev.ts"),
74
+ },
75
+ listen: {
76
+ summary: "Speech-to-text from the mic",
77
+ load: () => import("./scripts/listen.ts"),
78
+ },
79
+ speak: {
80
+ summary: "Text-to-speech",
81
+ load: () => import("./scripts/speak.ts"),
82
+ },
83
+ setup: {
84
+ summary: "Handheld keys and OAuth (speak, listen, google-workspace, browser, jev)",
85
+ load: () => import("./scripts/setup.ts"),
86
+ },
87
+ doctor: {
88
+ summary: "Check the tools and versions this repo needs",
89
+ load: () => import("./scripts/doctor.ts"),
90
+ },
91
+ clean: {
92
+ summary: "Remove scratch and stale generated files (--dry-run lists only)",
93
+ load: () => import("./scripts/clean.ts"),
94
+ },
95
+ yolo: {
96
+ summary: "Turn on full-permission mode on every host (--check, --dry-run, --host <id>)",
97
+ load: () => import("./scripts/sync-global-settings.ts"),
98
+ },
99
+ "purge-mcps": {
100
+ summary: "Purge global MCP servers (dry-run; --apply to write)",
101
+ load: () => import("./scripts/purge-global-mcps.ts"),
102
+ },
103
+ tasks: {
104
+ summary: "Write .vscode/tasks.json from package scripts (VS Code and Cursor)",
105
+ load: () => import("./scripts/vscode-tasks.ts"),
106
+ },
107
+ lint: {
108
+ summary:
109
+ "Lint with the standards pack's oxlint config (args go to oxlint; --baseline: lower .repo/lint-baseline.json)",
110
+ load: () => import("./scripts/lint-main.ts").then(({ lintMain }) => ({ main: lintMain })),
111
+ },
112
+ fmt: {
113
+ summary: "Format with the standards pack's oxfmt config (--check: report only; args go to oxfmt)",
114
+ load: () => import("./scripts/lint.ts").then(({ fmt }) => ({ main: fmt })),
115
+ },
116
+ turn: {
117
+ summary: "Gate the changed files: fmt, lint, typecheck, related tests, sync, check (--staged: the index)",
118
+ load: () => import("./scripts/turn.ts"),
119
+ },
120
+ ci: {
121
+ summary: "Run every gate (--report: skipped tests); skips <dir>: tests skipped on every OS; wait: the run for HEAD",
122
+ load: () => import("./scripts/ci.ts"),
123
+ },
124
+ coverage: {
125
+ summary: "run the suite with coverage and raise coverage-floors.json; a drop fails",
126
+ load: () => import("./coverage.ts"),
127
+ },
128
+ mutate: {
129
+ summary:
130
+ "mutation-test the tracked files in a clone of HEAD (--shard i/n, --report <file>); check <dir> [--write]: score reports against mutation-floors.json",
131
+ load: () => import("./mutate.ts"),
132
+ },
133
+ release: {
134
+ summary:
135
+ "Release the root package: bump from commits, stamp CHANGELOG.md, commit, push, wait on CI, tag ([--stable | --pre <id>]); wait <name>@<version>: poll npm",
136
+ load: () => import("./release/main.ts"),
137
+ },
138
+ "commit-msg": {
139
+ summary:
140
+ "Check a commit message file against the staged change (the commit-msg git hook), or each commit of --range <base>..<head>",
141
+ load: () => import("./commit-msg.ts"),
142
+ },
143
+ };
144
+
145
+ export const HOOKS: Readonly<Record<HookName, HookEntry>> = {
146
+ "gate-fat-tools": {
147
+ summary: "Fat-tool gate",
148
+ argv: [],
149
+ load: () => import("./scripts/gate-fat-tools.ts"),
150
+ },
151
+ "orchestrate-parent": {
152
+ summary: "Persist, approval and session gate",
153
+ argv: [],
154
+ load: () => import("./scripts/orchestrate-parent.ts"),
155
+ },
156
+ "rtk-grok": {
157
+ summary: "rtk rewrite for Grok",
158
+ argv: [],
159
+ load: () => import("./scripts/rtk-grok.ts"),
160
+ },
161
+ "scrub-spawn-prompt": {
162
+ summary: "Spawn prompt scrub",
163
+ argv: [],
164
+ load: () => import("./scripts/scrub-spawn-prompt.ts"),
165
+ },
166
+ "session-map": {
167
+ summary: "Session-start folder map",
168
+ argv: [],
169
+ load: () => import("./scripts/session-map.ts"),
170
+ },
171
+ worktree: {
172
+ summary: "Claude Code worktree create and remove",
173
+ argv: ["hook"],
174
+ load: () => import("./scripts/worktree.ts"),
175
+ },
176
+ };
177
+
178
+ export function isCommandName(name: string | undefined): name is CommandName {
179
+ return name !== undefined && Object.hasOwn(COMMANDS, name);
180
+ }
181
+
182
+ /** The tables `dispatch` runs from; tests pass stubs. */
183
+ export interface Registry {
184
+ readonly commands: Readonly<Record<CommandName, Entry>>;
185
+ readonly hooks: Readonly<Record<HookName, HookEntry>>;
186
+ }
187
+
188
+ const REGISTRY: Registry = { commands: COMMANDS, hooks: HOOKS };
189
+
190
+ export async function runCommand(
191
+ name: CommandName,
192
+ args: string[],
193
+ { commands }: Registry = REGISTRY,
194
+ ): Promise<number> {
195
+ const { main } = await commands[name].load();
196
+ return await main(args);
197
+ }
198
+
199
+ /**
200
+ * Runs `repo hook <name>` or a registry command in-process and returns its exit code, or null when argv names
201
+ * neither and `cli/main.ts` owns it. Registry commands own their flags, so they get the raw argv. The bin calls this
202
+ * before it loads `cli/main.ts`, so a hook pays for its own module only.
203
+ */
204
+ export async function dispatch(argv: readonly string[], registry: Registry = REGISTRY): Promise<number | null> {
205
+ const [head, ...rest] = argv;
206
+ if (head === "hook") {
207
+ const [name, ...args] = rest;
208
+ if (!isHookName(name)) {
209
+ const named = name === undefined ? "name a hook" : `unknown hook "${name}"`;
210
+ process.stderr.write(`repo hook: ${named}; ${HOOK_USAGE}\n`);
211
+ return 1;
212
+ }
213
+ const hook = registry.hooks[name];
214
+ const { main } = await hook.load();
215
+ return await main([...hook.argv, ...args]);
216
+ }
217
+ return isCommandName(head) ? await runCommand(head, rest, registry) : null;
218
+ }
@@ -0,0 +1,27 @@
1
+ import type { Owner } from "./tdd-scope.ts";
2
+
3
+ const CHANGELOG = "CHANGELOG.md";
4
+ /** Commit types that change what a user of a package sees. */
5
+ const USER_FACING: readonly string[] = ["feat", "fix", "perf"];
6
+
7
+ /** What a message says about itself, as far as the changelog rule reads it. */
8
+ export type Kind = { type: string; breaking: boolean };
9
+
10
+ /**
11
+ * The problem when a feat, fix, perf or breaking commit stages a file of a public package but not that package's
12
+ * CHANGELOG.md; one per package that is missing its entry.
13
+ */
14
+ export function changelogProblems(kind: Kind, staged: readonly string[], owners: (file: string) => Owner): string[] {
15
+ if (!kind.breaking && !USER_FACING.includes(kind.type)) return [];
16
+ const what = kind.breaking ? "a breaking change" : `a ${kind.type}`;
17
+ const dirs = new Set(
18
+ staged
19
+ .map(owners)
20
+ .filter((owner) => owner.public)
21
+ .map((owner) => owner.dir),
22
+ );
23
+ return [...dirs]
24
+ .map((dir) => (dir === "." ? CHANGELOG : `${dir}/${CHANGELOG}`))
25
+ .filter((file) => !staged.includes(file))
26
+ .map((file) => `${what} changes what users see; add an entry under ## [Unreleased] in ${file} and stage it`);
27
+ }
@@ -0,0 +1,93 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+ import { main } from "./commit-msg.ts";
5
+ import { git, removePlanted, tempDir } from "./fixtures/planted-repo.ts";
6
+ import { plant, stage } from "./fixtures/red-proof-repo.ts";
7
+
8
+ /** The hook's changelog rule: which commits must stage an entry, and for which package. */
9
+ afterEach(removePlanted);
10
+
11
+ const PLAIN = { "workerstack.config.ts": "export default {}\n" };
12
+ const SRC = { "src/x.ts": "export const x = 1;\n", "src/x.test.ts": "t\n" };
13
+ const PRIVATE = { ...PLAIN, "package.json": { name: "p", private: true } };
14
+ const MONO = {
15
+ ...PLAIN,
16
+ "package.json": { name: "mono", private: true, workspaces: ["packages/*"] },
17
+ "packages/pub/package.json": { name: "pub" },
18
+ };
19
+ const refusal = (what: string, file = "CHANGELOG.md") =>
20
+ `commit-msg: the message was refused\n - ${what} changes what users see; add an entry under ## [Unreleased] in ${file} and stage it\n`;
21
+
22
+ /** Runs the hook on `message` (or on `range`) with the staged change as is; red-proof passes. */
23
+ async function hook(root: string, message: string, range?: string) {
24
+ const file = path.join(tempDir(), "message");
25
+ fs.writeFileSync(file, message);
26
+ const err: string[] = [];
27
+ const redProof = () => ({ ok: true as const, line: "red-proof: ok" });
28
+ const argv = range === undefined ? [file] : ["--range", range];
29
+ const code = await main(argv, { cwd: root, stderr: (text) => err.push(text), redProof });
30
+ return { code, stderr: err.join("") };
31
+ }
32
+
33
+ describe("the commit-msg hook asks for a changelog entry", () => {
34
+ it.each([
35
+ ["feat: add x\n", "a feat"],
36
+ ["fix: handle x\n", "a fix"],
37
+ ["perf: speed x\n", "a perf"],
38
+ ["feat(cli)!: drop x\n", "a breaking change"],
39
+ ["refactor: move x\n\nBREAKING CHANGE: x moved\n", "a breaking change"],
40
+ ])("for %j in a public package", async (message, what) => {
41
+ const root = plant(PLAIN);
42
+ stage(root, SRC);
43
+ expect(await hook(root, message)).toEqual({ code: 1, stderr: refusal(what) });
44
+ });
45
+
46
+ it("only after the header itself is right", async () => {
47
+ const root = plant(PLAIN);
48
+ stage(root, SRC);
49
+ const got = await hook(root, "added stuff\n");
50
+ expect(got.stderr).not.toContain("CHANGELOG.md");
51
+ expect(got.code).toBe(1);
52
+ });
53
+
54
+ it("in the public package that changed, by path", async () => {
55
+ const root = plant(MONO);
56
+ stage(root, { "packages/pub/x.ts": "x\n", "packages/pub/x.test.ts": "t\n" });
57
+ const got = await hook(root, "feat(pub): add x\n");
58
+ expect(got).toEqual({ code: 1, stderr: refusal("a feat", "packages/pub/CHANGELOG.md") });
59
+ });
60
+ });
61
+
62
+ describe("the commit-msg hook lets a commit through", () => {
63
+ it("once the package's CHANGELOG.md is staged", async () => {
64
+ const root = plant(MONO);
65
+ stage(root, { "packages/pub/x.ts": "x\n", "packages/pub/x.test.ts": "t\n", "packages/pub/CHANGELOG.md": "# C\n" });
66
+ expect(await hook(root, "feat(pub): add x\n")).toEqual({ code: 0, stderr: "red-proof: ok\n" });
67
+ });
68
+
69
+ it.each(["chore: x\n", "docs: x\n", "test: x\n"])("for the type of %j", async (message) => {
70
+ const root = plant(PLAIN);
71
+ stage(root, SRC);
72
+ expect((await hook(root, message)).code).toBe(0);
73
+ });
74
+
75
+ it("for a merge message, which has no type", async () => {
76
+ const root = plant(PLAIN);
77
+ stage(root, SRC);
78
+ expect((await hook(root, "Merge branch 'x'\n")).code).toBe(0);
79
+ });
80
+
81
+ it("for a feat in a private package", async () => {
82
+ const root = plant(PRIVATE);
83
+ stage(root, SRC);
84
+ expect((await hook(root, "feat: add x\n")).code).toBe(0);
85
+ });
86
+
87
+ it("over --range, which CI runs over commits that carry no entry", async () => {
88
+ const root = plant(PLAIN);
89
+ stage(root, SRC);
90
+ git(root, "commit", "-q", "-m", "feat: add x");
91
+ expect(await hook(root, "", "HEAD~1..HEAD")).toEqual({ code: 0, stderr: "" });
92
+ });
93
+ });