@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,576 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { afterEach, describe, expect, it, vi } from "vitest";
7
+ import {
8
+ CONFIG_FILE,
9
+ HOSTS,
10
+ SKILL_PACKS,
11
+ defaultPacks,
12
+ defineConfig,
13
+ enabledSkills,
14
+ findRoot,
15
+ loadConfig,
16
+ } from "./config.ts";
17
+
18
+ const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
19
+ const temps: string[] = [];
20
+ const AGENTS = { hosts: ["claude", "codex", "antigravity", "grok", "cursor"], skills: [] };
21
+ const COMMIT = { maxChangedLines: 1000 };
22
+ const LINT = { skip: [] };
23
+ const KNIP = { ignore: [] };
24
+ const NIGHTLY = { shards: 1, secrets: [] };
25
+ const COVERAGE = {
26
+ floors: "coverage-floors.json",
27
+ knownFailures: "known-failures.json",
28
+ mutate: ["src/**/*.ts"],
29
+ mutationFloors: "mutation-floors.json",
30
+ };
31
+
32
+ afterEach(() => {
33
+ for (const dir of temps.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
34
+ });
35
+
36
+ function tempDir(): string {
37
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "workerstack-config-"));
38
+ temps.push(dir);
39
+ return dir;
40
+ }
41
+
42
+ /** A temp repo root holding `config` as workerstack.config.ts, and `manifest` as package.json unless it is null. */
43
+ function repo(config: string, manifest: object | string | null = { name: "fixture" }): string {
44
+ const root = tempDir();
45
+ fs.writeFileSync(path.join(root, CONFIG_FILE), config);
46
+ if (manifest !== null) {
47
+ const text = typeof manifest === "string" ? manifest : JSON.stringify(manifest);
48
+ fs.writeFileSync(path.join(root, "package.json"), text);
49
+ }
50
+ return root;
51
+ }
52
+
53
+ /** Links `<root>/node_modules/@workerstack/repo` to this checkout, as an install of the package would. */
54
+ function linkPackage(root: string): void {
55
+ const scope = path.join(root, "node_modules", "@workerstack");
56
+ fs.mkdirSync(scope, { recursive: true });
57
+ fs.symlinkSync(REPO_ROOT, path.join(scope, "repo"), "junction");
58
+ }
59
+
60
+ describe("loadConfig", () => {
61
+ it("returns the packs a config declares through defineConfig", async () => {
62
+ const root = repo(
63
+ 'import { defineConfig } from "@workerstack/repo"\n\nexport default defineConfig({\n packs: ["docs", "agents"],\n})\n',
64
+ );
65
+ linkPackage(root);
66
+ expect(await loadConfig(root)).toEqual({
67
+ ok: true,
68
+ config: {
69
+ packs: ["docs", "agents"],
70
+ agents: AGENTS,
71
+ commit: COMMIT,
72
+ lint: LINT,
73
+ knip: KNIP,
74
+ coverage: COVERAGE,
75
+ nightly: NIGHTLY,
76
+ tdd: false,
77
+ },
78
+ });
79
+ });
80
+
81
+ it("keeps an empty pack list as declared", async () => {
82
+ expect(await loadConfig(repo("export default { packs: [] }\n"))).toEqual({
83
+ ok: true,
84
+ config: {
85
+ packs: [],
86
+ agents: AGENTS,
87
+ commit: COMMIT,
88
+ lint: LINT,
89
+ knip: KNIP,
90
+ coverage: COVERAGE,
91
+ nightly: NIGHTLY,
92
+ tdd: false,
93
+ },
94
+ });
95
+ });
96
+
97
+ it("accepts the standards pack", async () => {
98
+ expect(await loadConfig(repo('export default { packs: ["standards"] }\n'))).toEqual({
99
+ ok: true,
100
+ config: {
101
+ packs: ["standards"],
102
+ agents: AGENTS,
103
+ commit: COMMIT,
104
+ lint: LINT,
105
+ knip: KNIP,
106
+ coverage: COVERAGE,
107
+ nightly: NIGHTLY,
108
+ tdd: false,
109
+ },
110
+ });
111
+ });
112
+
113
+ it("defaults the packs to agents, standards and docs, with or without workspaces in package.json", async () => {
114
+ const expected = {
115
+ ok: true,
116
+ config: {
117
+ packs: ["agents", "standards", "docs"],
118
+ agents: AGENTS,
119
+ commit: COMMIT,
120
+ lint: LINT,
121
+ knip: KNIP,
122
+ coverage: COVERAGE,
123
+ nightly: NIGHTLY,
124
+ tdd: false,
125
+ },
126
+ };
127
+ expect(await loadConfig(repo("export default {}\n"))).toEqual(expected);
128
+ expect(await loadConfig(repo("export default {}\n", { name: "fixture", workspaces: ["packages/*"] }))).toEqual(
129
+ expected,
130
+ );
131
+ expect(await loadConfig(repo("export default {}\n", null))).toEqual(expected);
132
+ });
133
+
134
+ it.each(["workspace", "personal"])("rejects the removed pack %s", async (name) => {
135
+ expect(await loadConfig(repo(`export default { packs: ["${name}"] }\n`))).toEqual({
136
+ ok: false,
137
+ errors: [`workerstack.config.ts: packs[0]: "${name}" is not a pack; use agents, standards, docs or release`],
138
+ });
139
+ });
140
+
141
+ it("accepts the release pack", async () => {
142
+ expect(await loadConfig(repo('export default { packs: ["release"] }\n'))).toMatchObject({
143
+ ok: true,
144
+ config: { packs: ["release"] },
145
+ });
146
+ });
147
+
148
+ it.each(['["docs"]', "[]"])("rejects agents without the agents pack (packs %s)", async (list) => {
149
+ expect(await loadConfig(repo(`export default { packs: ${list}, agents: { skills: ["listen"] } }\n`))).toEqual({
150
+ ok: false,
151
+ errors: ['workerstack.config.ts: agents: set only with the agents pack; add "agents" to packs or delete agents'],
152
+ });
153
+ });
154
+
155
+ it("reads the commit size limit under the standards pack, the default packs included", async () => {
156
+ const limit = { commit: { maxChangedLines: 1500 } };
157
+ expect(
158
+ await loadConfig(repo('export default { packs: ["standards"], commit: { maxChangedLines: 1500 } }\n')),
159
+ ).toMatchObject({ ok: true, config: limit });
160
+ expect(await loadConfig(repo("export default { commit: { maxChangedLines: 1500 } }\n"))).toMatchObject({
161
+ ok: true,
162
+ config: limit,
163
+ });
164
+ expect(await loadConfig(repo('export default { packs: ["standards"], commit: {} }\n'))).toMatchObject({
165
+ ok: true,
166
+ config: { commit: COMMIT },
167
+ });
168
+ });
169
+
170
+ it.each(['["docs"]', "[]"])("rejects commit without the standards pack (packs %s)", async (list) => {
171
+ expect(await loadConfig(repo(`export default { packs: ${list}, commit: { maxChangedLines: 1500 } }\n`))).toEqual({
172
+ ok: false,
173
+ errors: [
174
+ 'workerstack.config.ts: commit: set only with the standards pack; add "standards" to packs or delete commit',
175
+ ],
176
+ });
177
+ });
178
+
179
+ it.each(["0", "-5", "10.5", '"1500"'])("rejects a size limit of %s", async (value) => {
180
+ expect(await loadConfig(repo(`export default { commit: { maxChangedLines: ${value} } }\n`))).toEqual({
181
+ ok: false,
182
+ errors: ["workerstack.config.ts: commit.maxChangedLines: must be a whole number of lines above 0"],
183
+ });
184
+ });
185
+
186
+ it("rejects an unknown key under commit, and a commit that is not an object", async () => {
187
+ expect(await loadConfig(repo("export default { commit: { maxLines: 5 } }\n"))).toEqual({
188
+ ok: false,
189
+ errors: ['workerstack.config.ts: commit: unknown key "maxLines"'],
190
+ });
191
+ expect(await loadConfig(repo("export default { commit: 5 }\n"))).toEqual({
192
+ ok: false,
193
+ errors: ["workerstack.config.ts: commit: must be an object like { maxChangedLines: 1000 }"],
194
+ });
195
+ });
196
+
197
+ it("rejects an unknown key", async () => {
198
+ expect(await loadConfig(repo("export default { pakcs: [] }\n"))).toEqual({
199
+ ok: false,
200
+ errors: ['workerstack.config.ts: unknown key "pakcs"'],
201
+ });
202
+ });
203
+
204
+ it("rejects each unknown key alongside a valid one", async () => {
205
+ expect(await loadConfig(repo('export default { packs: ["docs"], a: 1, b: 2 }\n'))).toEqual({
206
+ ok: false,
207
+ errors: ['workerstack.config.ts: unknown key "a"', 'workerstack.config.ts: unknown key "b"'],
208
+ });
209
+ });
210
+
211
+ it("rejects a name that is not a pack", async () => {
212
+ expect(await loadConfig(repo('export default { packs: ["docs", "lint"] }\n'))).toEqual({
213
+ ok: false,
214
+ errors: ['workerstack.config.ts: packs[1]: "lint" is not a pack; use agents, standards, docs or release'],
215
+ });
216
+ });
217
+
218
+ it("rejects a pack listed twice", async () => {
219
+ expect(await loadConfig(repo('export default { packs: ["docs", "docs"] }\n'))).toEqual({
220
+ ok: false,
221
+ errors: ['workerstack.config.ts: packs: "docs" is listed twice'],
222
+ });
223
+ });
224
+
225
+ it("counts a pack listed more than twice", async () => {
226
+ expect(await loadConfig(repo('export default { packs: ["docs", "agents", "docs", "docs"] }\n'))).toEqual({
227
+ ok: false,
228
+ errors: ['workerstack.config.ts: packs: "docs" is listed 3 times'],
229
+ });
230
+ });
231
+
232
+ it("rejects packs that are not a list", async () => {
233
+ const result = await loadConfig(repo('export default { packs: "docs" }\n'));
234
+ expect(result.ok).toBe(false);
235
+ if (result.ok) return;
236
+ expect(result.errors).toHaveLength(1);
237
+ expect(result.errors[0]).toMatch(/^workerstack\.config\.ts: packs: \S/);
238
+ });
239
+
240
+ it("rejects a default export that is not an object", async () => {
241
+ const result = await loadConfig(repo('export default "docs"\n'));
242
+ expect(result.ok).toBe(false);
243
+ if (result.ok) return;
244
+ expect(result.errors).toHaveLength(1);
245
+ expect(result.errors[0]).toMatch(/^workerstack\.config\.ts: .*default export/);
246
+ });
247
+
248
+ it("rejects a config with no default export", async () => {
249
+ const result = await loadConfig(repo('export const packs = ["docs"]\n'));
250
+ expect(result.ok).toBe(false);
251
+ if (result.ok) return;
252
+ expect(result.errors).toHaveLength(1);
253
+ expect(result.errors[0]).toMatch(/^workerstack\.config\.ts: .*no default export/);
254
+ });
255
+
256
+ it("says to install where the config imports @workerstack/repo and nothing provides it", async () => {
257
+ // vitest resolves a bare import from its own root, so the import bun makes is run in bun itself.
258
+ const root = repo('import { defineConfig } from "@workerstack/repo";\n\nexport default defineConfig({});\n');
259
+ const script = `import { loadConfig } from ${JSON.stringify(path.join(REPO_ROOT, "cli/config.ts"))};
260
+ console.log(JSON.stringify(await loadConfig(${JSON.stringify(root)})));`;
261
+ const run = spawnSync(process.execPath, ["-e", script], { cwd: root, encoding: "utf8" });
262
+ const installHint = `workerstack.config.ts: @workerstack/repo is not installed in ${root}; run \`bun install\` there`;
263
+ expect(JSON.parse(run.stdout)).toEqual({ ok: false, errors: [installHint] });
264
+ const missing = () =>
265
+ Promise.reject(new Error(`Cannot find package '@workerstack/repo' imported from ${root}/workerstack.config.ts`));
266
+ expect(await loadConfig(root, missing)).toEqual({ ok: false, errors: [installHint] });
267
+ });
268
+
269
+ it("keeps the import's own message for any other package the config cannot find", async () => {
270
+ const result = await loadConfig(repo('import x from "left-pad-nope";\n\nexport default x;\n'));
271
+ expect(result).toMatchObject({
272
+ ok: false,
273
+ errors: [expect.stringMatching(/^workerstack\.config\.ts: failed to load: .*left-pad-nope/)],
274
+ });
275
+ });
276
+
277
+ it("reports the message of an import that throws", async () => {
278
+ const result = await loadConfig(repo('throw new Error("config exploded")\n'));
279
+ expect(result.ok).toBe(false);
280
+ if (result.ok) return;
281
+ expect(result.errors).toHaveLength(1);
282
+ expect(result.errors[0]).toMatch(/^workerstack\.config\.ts: .*config exploded/);
283
+ });
284
+
285
+ it("reports a thrown value that is not an Error", async () => {
286
+ const result = await loadConfig(repo('throw "config exploded"\n'));
287
+ expect(result.ok).toBe(false);
288
+ if (result.ok) return;
289
+ expect(result.errors).toHaveLength(1);
290
+ expect(result.errors[0]).toMatch(/^workerstack\.config\.ts: .*config exploded/);
291
+ });
292
+
293
+ it("reads again a config whose text changed, and writes no copy for an unchanged one", async () => {
294
+ const root = repo('export default { packs: ["agents"] }\n');
295
+ const seen: string[] = [];
296
+ const watcher = fs.watch(root, (_event, name) => seen.push(String(name)));
297
+ try {
298
+ expect(await loadConfig(root)).toMatchObject({ ok: true, config: { packs: ["agents"] } });
299
+ expect(await loadConfig(root)).toMatchObject({ ok: true, config: { packs: ["agents"] } });
300
+ await new Promise((resolve) => setTimeout(resolve, 50));
301
+ expect(seen.filter((name) => name.startsWith(".workerstack.config."))).toEqual([]);
302
+
303
+ fs.writeFileSync(path.join(root, CONFIG_FILE), 'export default { packs: ["docs"] }\n');
304
+ expect(await loadConfig(root)).toMatchObject({ ok: true, config: { packs: ["docs"] } });
305
+ expect(fs.readdirSync(root).sort()).toEqual(["package.json", CONFIG_FILE]);
306
+ } finally {
307
+ watcher.close();
308
+ }
309
+ });
310
+
311
+ it("reports a changed config that throws, leaving no copy behind", async () => {
312
+ const root = repo('export default { packs: ["agents"] }\n');
313
+ await loadConfig(root);
314
+ fs.writeFileSync(path.join(root, CONFIG_FILE), 'throw new Error("config exploded")\n');
315
+ expect(await loadConfig(root)).toMatchObject({ ok: false, errors: [expect.stringMatching(/config exploded/)] });
316
+ expect(fs.readdirSync(root).sort()).toEqual(["package.json", CONFIG_FILE]);
317
+ });
318
+ });
319
+
320
+ const SKIP = {
321
+ files: ["src/cache.ts"],
322
+ rules: ["repo/no-module-state"],
323
+ reason: "the parsed config cache lives for the process",
324
+ };
325
+ const NO_REASON = "say why the rules are off, in three words or more";
326
+ /** A config with the agents and standards packs, or `packs`, and one `lint.skip` entry. */
327
+ const skipConfig = (skip: unknown, packs = '["agents", "standards"]') =>
328
+ `export default { packs: ${packs}, lint: { skip: [${JSON.stringify(skip)}] } }\n`;
329
+ const refusal = (...errors: string[]) => ({ ok: false, errors: errors.map((e) => `workerstack.config.ts: ${e}`) });
330
+
331
+ it("lint.skip: Usage's config resolves to its skip entries", async () => {
332
+ const lint = ` lint: {\n skip: [${JSON.stringify(SKIP)}],\n },\n`;
333
+ const usage = `export default defineConfig({\n packs: ["agents", "standards"],\n${lint}});\n`;
334
+ const root = repo(`import { defineConfig } from "@workerstack/repo";\n\n${usage}`);
335
+ linkPackage(root);
336
+ expect(await loadConfig(root)).toEqual({
337
+ ok: true,
338
+ config: {
339
+ packs: ["agents", "standards"],
340
+ agents: AGENTS,
341
+ commit: COMMIT,
342
+ lint: { skip: [SKIP] },
343
+ knip: KNIP,
344
+ coverage: COVERAGE,
345
+ nightly: NIGHTLY,
346
+ tdd: false,
347
+ },
348
+ });
349
+ });
350
+
351
+ it("lint.skip: defaults to no skips, with lint omitted or empty, the default packs included", async () => {
352
+ const empty = { ok: true, config: { lint: LINT, knip: KNIP, coverage: COVERAGE } };
353
+ expect(await loadConfig(repo('export default { packs: ["standards"], lint: {} }\n'))).toMatchObject(empty);
354
+ expect(await loadConfig(repo('export default { packs: ["standards"] }\n'))).toMatchObject(empty);
355
+ expect(await loadConfig(repo("export default { lint: { skip: [] } }\n"))).toMatchObject(empty);
356
+ });
357
+
358
+ it("lint.skip: refuses an entry without a reason, or with a reason under three words", async () => {
359
+ const { reason: _, ...noReason } = SKIP;
360
+ expect(await loadConfig(repo(skipConfig(noReason)))).toEqual(refusal(`lint.skip[0].reason: ${NO_REASON}`));
361
+ const short = skipConfig({ ...SKIP, reason: "cache state" });
362
+ expect(await loadConfig(repo(short))).toEqual(refusal(`lint.skip[0].reason: ${NO_REASON}`));
363
+ });
364
+
365
+ it("lint.skip: refuses an entry with no files or no rules", async () => {
366
+ expect(await loadConfig(repo(skipConfig({ ...SKIP, files: [] })))).toEqual(
367
+ refusal("lint.skip[0].files: list at least one file glob"),
368
+ );
369
+ expect(await loadConfig(repo(skipConfig({ ...SKIP, rules: [] })))).toEqual(
370
+ refusal('lint.skip[0].rules: list at least one rule, like "repo/no-module-state"'),
371
+ );
372
+ });
373
+
374
+ it("lint.skip: refuses an unknown key in an entry and under lint", async () => {
375
+ expect(await loadConfig(repo(skipConfig({ ...SKIP, rule: "x" })))).toEqual(
376
+ refusal('lint.skip[0]: unknown key "rule"'),
377
+ );
378
+ const misspelt = 'export default { packs: ["standards"], lint: { skips: [] } }\n';
379
+ expect(await loadConfig(repo(misspelt))).toEqual(refusal('lint: unknown key "skips"'));
380
+ });
381
+
382
+ it.each(['["docs"]', "[]"])("lint.skip: refuses lint without the standards pack (packs %s)", async (list) => {
383
+ expect(await loadConfig(repo(skipConfig(SKIP, list)))).toEqual(
384
+ refusal('lint: set only with the standards pack; add "standards" to packs or delete lint'),
385
+ );
386
+ });
387
+
388
+ describe("agents", () => {
389
+ const agentsOf = async (source: string) => {
390
+ const result = await loadConfig(repo(source));
391
+ return result.ok ? result.config.agents : result.errors;
392
+ };
393
+
394
+ it("lists the hosts and skill packs", () => {
395
+ expect(HOSTS).toEqual(["claude", "codex", "antigravity", "grok", "cursor"]);
396
+ expect(SKILL_PACKS).toEqual(["google-workspace", "listen", "speak", "clasp"]);
397
+ });
398
+
399
+ it("parses agents.hosts and agents.skills", async () => {
400
+ expect(
401
+ await agentsOf(
402
+ 'export default { packs: ["agents"], agents: { hosts: ["codex", "claude"], skills: ["listen", "clasp"] } }\n',
403
+ ),
404
+ ).toEqual({
405
+ hosts: ["codex", "claude"],
406
+ skills: ["listen", "clasp"],
407
+ });
408
+ });
409
+
410
+ it("defaults hosts to all five and skills to none when only one is given", async () => {
411
+ expect(await agentsOf('export default { packs: ["agents"], agents: { skills: ["speak"] } }\n')).toEqual({
412
+ hosts: [...HOSTS],
413
+ skills: ["speak"],
414
+ });
415
+ expect(await agentsOf('export default { packs: ["agents"], agents: { hosts: ["grok"] } }\n')).toEqual({
416
+ hosts: ["grok"],
417
+ skills: [],
418
+ });
419
+ });
420
+
421
+ it("resolves a missing agents to every host and no skills", async () => {
422
+ expect(await agentsOf("export default { packs: [] }\n")).toEqual(AGENTS);
423
+ expect(await agentsOf('export default { packs: ["agents"] }\n')).toEqual(AGENTS);
424
+ });
425
+
426
+ it("rejects an unknown host", async () => {
427
+ expect(await agentsOf('export default { packs: ["agents"], agents: { hosts: ["clod"] } }\n')).toEqual([
428
+ 'workerstack.config.ts: agents.hosts[0]: "clod" is not a host; use claude, codex, antigravity, grok or cursor',
429
+ ]);
430
+ });
431
+
432
+ it("rejects an unknown skill", async () => {
433
+ expect(await agentsOf('export default { packs: ["agents"], agents: { skills: ["listen", "nope"] } }\n')).toEqual([
434
+ 'workerstack.config.ts: agents.skills[1]: "nope" is not a skill pack; use google-workspace, listen, speak or clasp',
435
+ ]);
436
+ });
437
+
438
+ it("rejects a duplicate host and a duplicate skill", async () => {
439
+ expect(
440
+ await agentsOf(
441
+ 'export default { packs: ["agents"], agents: { hosts: ["claude", "claude"], skills: ["listen", "listen"] } }\n',
442
+ ),
443
+ ).toEqual([
444
+ 'workerstack.config.ts: agents.hosts: "claude" is listed twice',
445
+ 'workerstack.config.ts: agents.skills: "listen" is listed twice',
446
+ ]);
447
+ });
448
+
449
+ it("rejects an empty hosts list", async () => {
450
+ expect(await agentsOf('export default { packs: ["agents"], agents: { hosts: [] } }\n')).toEqual([
451
+ "workerstack.config.ts: agents.hosts: list at least one host",
452
+ ]);
453
+ });
454
+
455
+ it("rejects an unknown key inside agents", async () => {
456
+ expect(await agentsOf('export default { packs: ["agents"], agents: { host: [] } }\n')).toEqual([
457
+ 'workerstack.config.ts: agents: unknown key "host"',
458
+ ]);
459
+ });
460
+ });
461
+
462
+ describe("findRoot", () => {
463
+ it("returns the directory that holds the config when started there", () => {
464
+ const root = repo("export default {}\n");
465
+ expect(findRoot(root)).toBe(root);
466
+ });
467
+
468
+ it("walks up to the nearest ancestor that holds the config", () => {
469
+ const outer = repo("export default {}\n");
470
+ const inner = path.join(outer, "apps", "web");
471
+ fs.mkdirSync(path.join(inner, "src"), { recursive: true });
472
+ fs.writeFileSync(path.join(inner, CONFIG_FILE), "export default {}\n");
473
+ expect(findRoot(path.join(inner, "src"))).toBe(inner);
474
+ expect(findRoot(path.join(outer, "apps"))).toBe(outer);
475
+ });
476
+
477
+ it("returns null when no ancestor holds the config, whatever CLAUDE_PROJECT_DIR says", () => {
478
+ const elsewhere = repo("export default {}\n");
479
+ vi.stubEnv("CLAUDE_PROJECT_DIR", elsewhere);
480
+ const empty = path.join(tempDir(), "a", "b");
481
+ fs.mkdirSync(empty, { recursive: true });
482
+ expect(findRoot(empty)).toBeNull();
483
+ });
484
+ });
485
+
486
+ describe("defineConfig", () => {
487
+ it("returns its argument", () => {
488
+ const config = { packs: ["docs", "agents"] as const };
489
+ expect(defineConfig(config)).toBe(config);
490
+ });
491
+
492
+ it("rejects an unknown pack or key at compile time", () => {
493
+ // @ts-expect-error "lint" is not a pack
494
+ expect(defineConfig({ packs: ["lint"] })).toEqual({ packs: ["lint"] });
495
+ // @ts-expect-error "pakcs" is not a key
496
+ expect(defineConfig({ pakcs: [] })).toEqual({ pakcs: [] });
497
+ });
498
+ });
499
+
500
+ describe("defaultPacks", () => {
501
+ it("is agents, standards and docs", () => {
502
+ expect(defaultPacks()).toEqual(["agents", "standards", "docs"]);
503
+ });
504
+ });
505
+
506
+ describe("coverage", () => {
507
+ it("defaults the floors and known-failures files, with coverage omitted or empty", async () => {
508
+ const expected = { ok: true, config: { coverage: COVERAGE } };
509
+ expect(await loadConfig(repo("export default {}\n"))).toMatchObject(expected);
510
+ expect(await loadConfig(repo("export default { coverage: {} }\n"))).toMatchObject(expected);
511
+ });
512
+
513
+ it("takes each path as given and defaults the other", async () => {
514
+ const floors = await loadConfig(repo('export default { coverage: { floors: "ratchets/floors.json" } }\n'));
515
+ expect(floors).toMatchObject({ ok: true, config: { coverage: { ...COVERAGE, floors: "ratchets/floors.json" } } });
516
+ const failures = await loadConfig(repo('export default { coverage: { knownFailures: "ratchets/kf.json" } }\n'));
517
+ expect(failures).toMatchObject({
518
+ ok: true,
519
+ config: { coverage: { ...COVERAGE, knownFailures: "ratchets/kf.json" } },
520
+ });
521
+ });
522
+
523
+ it("refuses an unknown key and a path that is not text", async () => {
524
+ expect(await loadConfig(repo("export default { coverage: { floor: 'a.json' } }\n"))).toEqual({
525
+ ok: false,
526
+ errors: ['workerstack.config.ts: coverage: unknown key "floor"'],
527
+ });
528
+ expect(await loadConfig(repo("export default { coverage: { floors: 5 } }\n"))).toEqual({
529
+ ok: false,
530
+ errors: ["workerstack.config.ts: coverage.floors: must be a file path like coverage-floors.json"],
531
+ });
532
+ });
533
+ });
534
+
535
+ describe("coverage, mutation", () => {
536
+ it("takes the globs to mutate and the mutation floors path as given", async () => {
537
+ const source = 'export default { coverage: { mutate: ["cli/**/*.ts", "lib/*.ts"], mutationFloors: "m/f.json" } }\n';
538
+ expect(await loadConfig(repo(source))).toMatchObject({
539
+ ok: true,
540
+ config: { coverage: { ...COVERAGE, mutate: ["cli/**/*.ts", "lib/*.ts"], mutationFloors: "m/f.json" } },
541
+ });
542
+ });
543
+
544
+ it("refuses an empty glob list, a glob that is not text and a floors path that is not text", async () => {
545
+ const errors = async (body: string) => {
546
+ const loaded = await loadConfig(repo(`export default { coverage: { ${body} } }\n`));
547
+ return loaded.ok ? [] : loaded.errors;
548
+ };
549
+ expect(await errors("mutate: []")).toEqual([
550
+ 'workerstack.config.ts: coverage.mutate: must be a non-empty list of globs like ["src/**/*.ts"]',
551
+ ]);
552
+ expect(await errors("mutate: [5]")).toEqual([
553
+ 'workerstack.config.ts: coverage.mutate[0]: must be a non-empty list of globs like ["src/**/*.ts"]',
554
+ ]);
555
+ expect(await errors("mutationFloors: 5")).toEqual([
556
+ "workerstack.config.ts: coverage.mutationFloors: must be a file path like coverage-floors.json",
557
+ ]);
558
+ });
559
+ });
560
+
561
+ describe("enabledSkills", () => {
562
+ it("lists agents.skills of the repo at or above cwd", async () => {
563
+ const root = repo('export default { packs: ["agents"], agents: { skills: ["speak", "clasp"] } }\n');
564
+ fs.mkdirSync(path.join(root, "sub"));
565
+ expect(await enabledSkills(path.join(root, "sub"))).toEqual(["speak", "clasp"]);
566
+ });
567
+
568
+ it("is empty with no config above cwd", async () => {
569
+ expect(await enabledSkills(tempDir())).toEqual([]);
570
+ });
571
+
572
+ it("is empty when the config is invalid", async () => {
573
+ const root = repo('export default { packs: ["agents"], agents: { skills: ["nope"] } }\n');
574
+ expect(await enabledSkills(root)).toEqual([]);
575
+ });
576
+ });