@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,402 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { parse as parseJsonc, printParseErrorCode, type ParseError } from "jsonc-parser";
4
+ import { parse as parseToml } from "smol-toml";
5
+ import { parse as parseYaml } from "yaml";
6
+ import { z } from "zod";
7
+ import type { Output } from "./outputs.ts";
8
+
9
+ /**
10
+ * How an output's text is read: `text` is not parsed; `markdown` parses its YAML frontmatter when it has one;
11
+ * `jsonc` is JSON with comments and trailing commas, as an editor's settings file.
12
+ */
13
+ type OutputFormat = "json" | "jsonc" | "toml" | "markdown" | "text";
14
+
15
+ /**
16
+ * One registered output path pattern. `schema` checks the parsed JSON or TOML, or a markdown file's frontmatter
17
+ * (an empty mapping when it has none).
18
+ */
19
+ export type FormatEntry = { match: (path: string) => boolean; format: OutputFormat; schema?: z.ZodType };
20
+
21
+ const REGISTER_HINT = "no output format registered; add it to cli/sync/validate.ts";
22
+
23
+ // Every host schema is loose: a host may add keys, and sync must not reject what the host reads.
24
+ const hookCommand = z.looseObject({ type: z.string(), command: z.string().optional(), timeout: z.number().optional() });
25
+ /** A claude or codex hook entry: an optional matcher and the handlers it runs. */
26
+ const hookEntry = z.looseObject({ matcher: z.string().optional(), hooks: z.array(hookCommand).optional() });
27
+ /** An event name to its entries, the shape of `hooks` in claude's settings and in codex's and grok's hook files. */
28
+ const hooksByEvent = z.record(z.string(), z.array(hookEntry));
29
+
30
+ const AGENT_FRONTMATTER = z.looseObject({ name: z.string(), description: z.string() });
31
+ const CLAUDE_RULE_FRONTMATTER = z.looseObject({ paths: z.array(z.string()).optional() });
32
+ const CLAUDE_SETTINGS = z.looseObject({
33
+ permissions: z.looseObject({ deny: z.array(z.string()).optional() }).optional(),
34
+ hooks: hooksByEvent.optional(),
35
+ enableAllProjectMcpServers: z.boolean().optional(),
36
+ enabledMcpjsonServers: z.array(z.string()).optional(),
37
+ });
38
+ const MCP_SERVERS = z.looseObject({ mcpServers: z.record(z.string(), z.looseObject({})).optional() });
39
+ const CODEX_HOOKS = z.looseObject({ hooks: hooksByEvent.optional() });
40
+ const CURSOR_HOOKS = z.looseObject({
41
+ version: z.number().optional(),
42
+ hooks: z
43
+ .record(
44
+ z.string(),
45
+ z.array(
46
+ z.looseObject({
47
+ type: z.string().optional(),
48
+ command: z.string().optional(),
49
+ matcher: z.string().optional(),
50
+ timeout: z.number().optional(),
51
+ }),
52
+ ),
53
+ )
54
+ .optional(),
55
+ });
56
+ /** Antigravity's file nests flat entries (a command) and matcher entries (handlers) under one `rulesync` key. */
57
+ const AGENTS_HOOKS = z.looseObject({
58
+ rulesync: z.record(z.string(), z.array(hookEntry.extend({ command: z.string().optional() }))).optional(),
59
+ });
60
+
61
+ // The tool configs are strict at the top: sync writes them whole, and a key the tool does not know is a typo.
62
+ const strings = z.array(z.string());
63
+ const severity = z.enum(["off", "allow", "warn", "error", "deny"]);
64
+ const lintRules = z.record(z.string(), z.union([severity, z.tuple([severity], z.unknown())]));
65
+ /** `.oxlintrc.json`, one key per property of oxlint's shipped `configuration_schema.json`. */
66
+ export const OXLINT_CONFIG: z.ZodObject<
67
+ {
68
+ $schema: z.ZodOptional<z.ZodString>;
69
+ categories: z.ZodOptional<
70
+ z.ZodRecord<
71
+ z.ZodString,
72
+ z.ZodEnum<{
73
+ error: "error";
74
+ deny: "deny";
75
+ off: "off";
76
+ allow: "allow";
77
+ warn: "warn";
78
+ }>
79
+ >
80
+ >;
81
+ env: z.ZodOptional<z.ZodUnknown>;
82
+ extends: z.ZodOptional<z.ZodArray<z.ZodString>>;
83
+ globals: z.ZodOptional<z.ZodUnknown>;
84
+ ignorePatterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
85
+ jsPlugins: z.ZodOptional<z.ZodUnknown>;
86
+ options: z.ZodOptional<
87
+ z.ZodObject<
88
+ {
89
+ typeAware: z.ZodOptional<z.ZodBoolean>;
90
+ respectEslintDisableDirectives: z.ZodOptional<z.ZodBoolean>;
91
+ },
92
+ z.core.$loose
93
+ >
94
+ >;
95
+ overrides: z.ZodOptional<
96
+ z.ZodArray<
97
+ z.ZodObject<
98
+ {
99
+ files: z.ZodArray<z.ZodString>;
100
+ rules: z.ZodOptional<
101
+ z.ZodRecord<
102
+ z.ZodString,
103
+ z.ZodUnion<
104
+ readonly [
105
+ z.ZodEnum<{
106
+ error: "error";
107
+ deny: "deny";
108
+ off: "off";
109
+ allow: "allow";
110
+ warn: "warn";
111
+ }>,
112
+ z.ZodTuple<
113
+ [
114
+ z.ZodEnum<{
115
+ error: "error";
116
+ deny: "deny";
117
+ off: "off";
118
+ allow: "allow";
119
+ warn: "warn";
120
+ }>,
121
+ ],
122
+ z.ZodUnknown
123
+ >,
124
+ ]
125
+ >
126
+ >
127
+ >;
128
+ },
129
+ z.core.$loose
130
+ >
131
+ >
132
+ >;
133
+ plugins: z.ZodOptional<z.ZodArray<z.ZodString>>;
134
+ rules: z.ZodOptional<
135
+ z.ZodRecord<
136
+ z.ZodString,
137
+ z.ZodUnion<
138
+ readonly [
139
+ z.ZodEnum<{
140
+ error: "error";
141
+ deny: "deny";
142
+ off: "off";
143
+ allow: "allow";
144
+ warn: "warn";
145
+ }>,
146
+ z.ZodTuple<
147
+ [
148
+ z.ZodEnum<{
149
+ error: "error";
150
+ deny: "deny";
151
+ off: "off";
152
+ allow: "allow";
153
+ warn: "warn";
154
+ }>,
155
+ ],
156
+ z.ZodUnknown
157
+ >,
158
+ ]
159
+ >
160
+ >
161
+ >;
162
+ settings: z.ZodOptional<z.ZodUnknown>;
163
+ },
164
+ z.core.$strict
165
+ > = z.strictObject({
166
+ $schema: z.string().optional(),
167
+ categories: z.record(z.string(), severity).optional(),
168
+ env: z.unknown().optional(),
169
+ extends: strings.optional(),
170
+ globals: z.unknown().optional(),
171
+ ignorePatterns: strings.optional(),
172
+ jsPlugins: z.unknown().optional(),
173
+ options: z
174
+ .looseObject({ typeAware: z.boolean().optional(), respectEslintDisableDirectives: z.boolean().optional() })
175
+ .optional(),
176
+ overrides: z.array(z.looseObject({ files: strings, rules: lintRules.optional() })).optional(),
177
+ plugins: strings.optional(),
178
+ rules: lintRules.optional(),
179
+ settings: z.unknown().optional(),
180
+ });
181
+ /** `.oxfmtrc.jsonc`, one key per property of oxfmt's shipped `configuration_schema.json`. */
182
+ export const OXFMT_CONFIG: z.ZodObject<
183
+ {
184
+ arrowParens: z.ZodOptional<z.ZodUnknown>;
185
+ associations: z.ZodOptional<z.ZodUnknown>;
186
+ astro: z.ZodOptional<z.ZodUnknown>;
187
+ bracketSameLine: z.ZodOptional<z.ZodUnknown>;
188
+ bracketSpacing: z.ZodOptional<z.ZodUnknown>;
189
+ embeddedLanguageFormatting: z.ZodOptional<z.ZodUnknown>;
190
+ endOfLine: z.ZodOptional<z.ZodUnknown>;
191
+ experimentalOperatorPosition: z.ZodOptional<z.ZodUnknown>;
192
+ htmlWhitespaceSensitivity: z.ZodOptional<z.ZodUnknown>;
193
+ ignorePatterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
194
+ insertFinalNewline: z.ZodOptional<z.ZodUnknown>;
195
+ jsdoc: z.ZodOptional<z.ZodUnknown>;
196
+ jsxSingleQuote: z.ZodOptional<z.ZodUnknown>;
197
+ objectWrap: z.ZodOptional<z.ZodUnknown>;
198
+ overrides: z.ZodOptional<z.ZodUnknown>;
199
+ printWidth: z.ZodOptional<z.ZodNumber>;
200
+ proseWrap: z.ZodOptional<z.ZodUnknown>;
201
+ quoteProps: z.ZodOptional<z.ZodUnknown>;
202
+ semi: z.ZodOptional<z.ZodUnknown>;
203
+ singleAttributePerLine: z.ZodOptional<z.ZodUnknown>;
204
+ singleQuote: z.ZodOptional<z.ZodUnknown>;
205
+ sortImports: z.ZodOptional<z.ZodUnknown>;
206
+ sortPackageJson: z.ZodOptional<z.ZodUnknown>;
207
+ sortTailwindcss: z.ZodOptional<z.ZodUnknown>;
208
+ svelte: z.ZodOptional<z.ZodUnknown>;
209
+ tabWidth: z.ZodOptional<z.ZodUnknown>;
210
+ trailingComma: z.ZodOptional<z.ZodUnknown>;
211
+ useTabs: z.ZodOptional<z.ZodUnknown>;
212
+ vueIndentScriptAndStyle: z.ZodOptional<z.ZodUnknown>;
213
+ },
214
+ z.core.$strict
215
+ > = z.strictObject({
216
+ arrowParens: z.unknown().optional(),
217
+ associations: z.unknown().optional(),
218
+ astro: z.unknown().optional(),
219
+ bracketSameLine: z.unknown().optional(),
220
+ bracketSpacing: z.unknown().optional(),
221
+ embeddedLanguageFormatting: z.unknown().optional(),
222
+ endOfLine: z.unknown().optional(),
223
+ experimentalOperatorPosition: z.unknown().optional(),
224
+ htmlWhitespaceSensitivity: z.unknown().optional(),
225
+ ignorePatterns: strings.optional(),
226
+ insertFinalNewline: z.unknown().optional(),
227
+ jsdoc: z.unknown().optional(),
228
+ jsxSingleQuote: z.unknown().optional(),
229
+ objectWrap: z.unknown().optional(),
230
+ overrides: z.unknown().optional(),
231
+ printWidth: z.number().int().positive().optional(),
232
+ proseWrap: z.unknown().optional(),
233
+ quoteProps: z.unknown().optional(),
234
+ semi: z.unknown().optional(),
235
+ singleAttributePerLine: z.unknown().optional(),
236
+ singleQuote: z.unknown().optional(),
237
+ sortImports: z.unknown().optional(),
238
+ sortPackageJson: z.unknown().optional(),
239
+ sortTailwindcss: z.unknown().optional(),
240
+ svelte: z.unknown().optional(),
241
+ tabWidth: z.unknown().optional(),
242
+ trailingComma: z.unknown().optional(),
243
+ useTabs: z.unknown().optional(),
244
+ vueIndentScriptAndStyle: z.unknown().optional(),
245
+ });
246
+
247
+ const exact = (rel: string) => (candidate: string) => candidate === rel;
248
+ /** Matches `rel` at the root and under any workspace package dir: `AGENTS.md`, `packages/web/AGENTS.md`. */
249
+ const named = (rel: string) => (candidate: string) => candidate === rel || candidate.endsWith(`/${rel}`);
250
+ /** Matches `<dir>/<one name><ext>` for a dir with no slash at its end. */
251
+ const inDir = (dir: string, ext: string) => (candidate: string) =>
252
+ candidate.startsWith(`${dir}/`) && candidate.endsWith(ext) && !candidate.slice(dir.length + 1).includes("/");
253
+
254
+ /** Matches `<dir>/<any depth><ext>`: a rule may sit in a subfolder. */
255
+ const under = (dir: string, ext: string) => (candidate: string) =>
256
+ candidate.startsWith(`${dir}/`) && candidate.endsWith(ext);
257
+
258
+ /**
259
+ * Every output path a generator may produce, first match wins. A later slice that adds a generated file registers
260
+ * its format here, and its schema when the host has a shape worth checking.
261
+ */
262
+ const OUTPUT_FORMATS: readonly FormatEntry[] = [
263
+ { match: exact(".claude/settings.json"), format: "json", schema: CLAUDE_SETTINGS },
264
+ { match: exact(".mcp.json"), format: "json", schema: MCP_SERVERS },
265
+ { match: exact(".agents/mcp_config.json"), format: "json", schema: MCP_SERVERS },
266
+ { match: exact(".cursor/hooks.json"), format: "json", schema: CURSOR_HOOKS },
267
+ { match: exact(".codex/hooks.json"), format: "json", schema: CODEX_HOOKS },
268
+ { match: exact(".agents/hooks.json"), format: "json", schema: AGENTS_HOOKS },
269
+ { match: inDir(".grok/hooks", ".json"), format: "json", schema: CODEX_HOOKS },
270
+ { match: exact(".vscode/tasks.json"), format: "json" },
271
+ { match: exact(".vscode/settings.json"), format: "jsonc" },
272
+ { match: inDir(".claude/agents", ".md"), format: "markdown", schema: AGENT_FRONTMATTER },
273
+ { match: inDir(".cursor/agents", ".md"), format: "markdown", schema: AGENT_FRONTMATTER },
274
+ { match: inDir(".grok/agents", ".md"), format: "markdown", schema: AGENT_FRONTMATTER },
275
+ { match: inDir(".agents/agents", ".md"), format: "markdown", schema: AGENT_FRONTMATTER },
276
+ { match: under(".agents/rules", ".md"), format: "markdown" },
277
+ { match: under(".claude/rules", ".md"), format: "markdown", schema: CLAUDE_RULE_FRONTMATTER },
278
+ { match: named("AGENTS.md"), format: "markdown" },
279
+ { match: named("CLAUDE.md"), format: "markdown" },
280
+ { match: named("references/INDEX.md"), format: "markdown" },
281
+ { match: exact(".repo/lessons/INDEX.md"), format: "markdown" },
282
+ { match: inDir(".codex/agents", ".toml"), format: "toml" },
283
+ { match: inDir(".grok/roles", ".toml"), format: "toml" },
284
+ { match: exact(".codex/config.toml"), format: "toml" },
285
+ { match: exact(".grok/config.toml"), format: "toml" },
286
+ { match: exact("bunfig.toml"), format: "toml" },
287
+ { match: exact(".oxlintrc.json"), format: "json", schema: OXLINT_CONFIG },
288
+ { match: exact(".oxfmtrc.jsonc"), format: "jsonc", schema: OXFMT_CONFIG },
289
+ { match: exact("knip.json"), format: "json" },
290
+ { match: exact(".gitignore"), format: "text" },
291
+ { match: exact(".gitattributes"), format: "text" },
292
+ { match: exact(".githooks/pre-commit"), format: "text" },
293
+ { match: exact(".githooks/commit-msg"), format: "text" },
294
+ { match: exact(".github/workflows/ci.yml"), format: "text" },
295
+ { match: exact(".github/workflows/nightly.yml"), format: "text" },
296
+ { match: exact(".github/workflows/release.yml"), format: "text" },
297
+ ];
298
+
299
+ /** Parses `content` as `format` and returns the value a schema checks, or a problem. */
300
+ function parseContent(format: OutputFormat, content: string): { value: unknown } | { problem: string } {
301
+ try {
302
+ if (format === "json") return { value: JSON.parse(content) };
303
+ if (format === "toml") return { value: parseToml(content) };
304
+ } catch (error) {
305
+ return { problem: `invalid ${format === "json" ? "JSON" : "TOML"}: ${(error as Error).message}` };
306
+ }
307
+ if (format === "jsonc") {
308
+ const errors: ParseError[] = [];
309
+ const value: unknown = parseJsonc(content, errors, { allowTrailingComma: true });
310
+ const first = errors[0];
311
+ return first === undefined
312
+ ? { value }
313
+ : { problem: `invalid JSONC: ${printParseErrorCode(first.error)} at offset ${first.offset}` };
314
+ }
315
+ return format === "markdown" ? parseFrontmatter(content) : { value: undefined };
316
+ }
317
+
318
+ /** The YAML frontmatter of a markdown file as a mapping; empty when the file has none. */
319
+ function parseFrontmatter(content: string): { value: unknown } | { problem: string } {
320
+ if (!content.startsWith("---\n") && !content.startsWith("---\r\n")) return { value: {} };
321
+ const closed = /^---\r?\n(?:([\s\S]*?)\r?\n)?---[ \t]*(?:\r?\n|$)/.exec(content);
322
+ if (closed === null) return { problem: "invalid frontmatter: no closing ---" };
323
+ let data: unknown;
324
+ try {
325
+ data = parseYaml(closed[1] ?? "") ?? {};
326
+ } catch (error) {
327
+ return { problem: `invalid frontmatter: ${(error as Error).message.split("\n")[0]}` };
328
+ }
329
+ if (typeof data !== "object" || Array.isArray(data)) return { problem: "invalid frontmatter: must be a mapping" };
330
+ return { value: data };
331
+ }
332
+
333
+ /** `dotted.path: message` per zod issue; an issue at the root has no path. */
334
+ function schemaProblems(schema: z.ZodType, value: unknown): string[] {
335
+ const result = schema.safeParse(value);
336
+ if (result.success) return [];
337
+ return result.error.issues.map((issue) => {
338
+ const where = issue.path.map(String).join(".");
339
+ return where === "" ? issue.message : `${where}: ${issue.message}`;
340
+ });
341
+ }
342
+
343
+ /** The problems of one file output: its parse error, its schema misses, or the missing registration. */
344
+ function fileProblems(output: Extract<Output, { kind: "file" }>, formats: readonly FormatEntry[]): string[] {
345
+ const entry = formats.find((candidate) => candidate.match(output.path));
346
+ if (entry === undefined) return [REGISTER_HINT];
347
+ const parsed = parseContent(entry.format, output.content);
348
+ if ("problem" in parsed) return [parsed.problem];
349
+ return entry.schema === undefined ? [] : schemaProblems(entry.schema, parsed.value);
350
+ }
351
+
352
+ /**
353
+ * Whether the link planned at `linkRel` will point at something once sync has run: its target is on disk, a planned
354
+ * output, or the dir above one; a target that is itself a planned link is followed.
355
+ */
356
+ function resolves(
357
+ root: string,
358
+ linkRel: string,
359
+ links: Map<string, string>,
360
+ planned: Set<string>,
361
+ seen: Set<string>,
362
+ ): boolean {
363
+ const target = path
364
+ .relative(root, path.resolve(root, path.dirname(linkRel), links.get(linkRel) as string))
365
+ .split(path.sep)
366
+ .join("/");
367
+ if (fs.existsSync(path.join(root, target)) || planned.has(target)) return true;
368
+ if (!links.has(target) || seen.has(target)) return false;
369
+ return resolves(root, target, links, planned, seen.add(target));
370
+ }
371
+
372
+ /**
373
+ * Every problem in `outputs` as `path: problem` lines, in output order, so a caller can refuse before it writes
374
+ * anything. A file must parse in its registered format and match that format's schema; a path with no registered
375
+ * format is a problem. A link must resolve, against what is on disk under `root` plus the planned outputs (a link
376
+ * to another planned link, or into the dir of a planned file, resolves). `formats` is a test seam.
377
+ */
378
+ export function validateOutputs(
379
+ root: string,
380
+ outputs: readonly Output[],
381
+ formats: readonly FormatEntry[] = OUTPUT_FORMATS,
382
+ ): string[] {
383
+ const links = new Map<string, string>();
384
+ const planned = new Set<string>();
385
+ for (const output of outputs) {
386
+ if (output.kind === "link") links.set(output.path, output.target);
387
+ else planned.add(output.path);
388
+ }
389
+ // A planned file also plans every dir above it.
390
+ for (const rel of [...planned]) {
391
+ for (let dir = path.posix.dirname(rel); dir !== "."; dir = path.posix.dirname(dir)) planned.add(dir);
392
+ }
393
+ const lines: string[] = [];
394
+ for (const output of outputs) {
395
+ if (output.kind === "file") {
396
+ lines.push(...fileProblems(output, formats).map((problem) => `${output.path}: ${problem}`));
397
+ } else if (!resolves(root, output.path, links, planned, new Set([output.path]))) {
398
+ lines.push(`${output.path}: link target ${output.target} does not exist`);
399
+ }
400
+ }
401
+ return lines;
402
+ }
@@ -0,0 +1,180 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { readWorkspace } from "./workspace.ts";
6
+
7
+ const temps: string[] = [];
8
+
9
+ afterEach(() => {
10
+ for (const dir of temps.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
11
+ });
12
+
13
+ /** A temp root holding each `files` entry, objects written as JSON. */
14
+ function repo(files: Record<string, object | string>): string {
15
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "workerstack-workspace-"));
16
+ temps.push(root);
17
+ for (const [rel, content] of Object.entries(files)) {
18
+ const file = path.join(root, rel);
19
+ fs.mkdirSync(path.dirname(file), { recursive: true });
20
+ fs.writeFileSync(file, typeof content === "string" ? content : JSON.stringify(content));
21
+ }
22
+ return root;
23
+ }
24
+
25
+ function dirsOf(root: string, glob?: (pattern: string, cwd: string) => string[]): string[] {
26
+ const result = readWorkspace(root, glob);
27
+ if (!result.ok) throw new Error(result.errors.join("\n"));
28
+ return result.workspace.packages.map((pkg) => pkg.dir);
29
+ }
30
+
31
+ describe("readWorkspace", () => {
32
+ it("reads a repo with no workspaces as the root alone", () => {
33
+ const manifest = { name: "solo", dependencies: { "@workerstack/repo": "^1.0.0" } };
34
+ const root = repo({ "package.json": manifest });
35
+ expect(readWorkspace(root)).toEqual({ ok: true, workspace: { root, manifest, packages: [] } });
36
+ });
37
+
38
+ it("keeps version, sideEffects, packageManager, bin and exports as they are written", () => {
39
+ const manifest = {
40
+ name: "solo",
41
+ version: 3,
42
+ sideEffects: ["*.css"],
43
+ packageManager: "bun@^1",
44
+ bin: { solo: "./bin/solo.ts" },
45
+ exports: { "./lint": { default: "./lint.ts" } },
46
+ };
47
+ const root = repo({ "package.json": manifest });
48
+ expect(readWorkspace(root)).toEqual({ ok: true, workspace: { root, manifest, packages: [] } });
49
+ });
50
+
51
+ it("reads a root without package.json as an empty manifest", () => {
52
+ const root = repo({});
53
+ expect(readWorkspace(root)).toEqual({ ok: true, workspace: { root, manifest: {}, packages: [] } });
54
+ });
55
+
56
+ it("expands an array of workspace globs into sorted package dirs with their manifests", () => {
57
+ const root = repo({
58
+ "package.json": { name: "mono", workspaces: ["packages/*", "apps/*"] },
59
+ "packages/web/package.json": { name: "@acme/web", devDependencies: { "@workerstack/ui": "1.0.0" } },
60
+ "packages/api/package.json": { name: "@acme/api" },
61
+ "apps/site/package.json": { name: "site" },
62
+ });
63
+ const result = readWorkspace(root);
64
+ expect(result).toEqual({
65
+ ok: true,
66
+ workspace: {
67
+ root,
68
+ manifest: { name: "mono", workspaces: ["packages/*", "apps/*"] },
69
+ packages: [
70
+ { dir: "apps/site", manifest: { name: "site" } },
71
+ { dir: "packages/api", manifest: { name: "@acme/api" } },
72
+ { dir: "packages/web", manifest: { name: "@acme/web", devDependencies: { "@workerstack/ui": "1.0.0" } } },
73
+ ],
74
+ },
75
+ });
76
+ });
77
+
78
+ it("expands the { packages } workspaces form", () => {
79
+ const root = repo({
80
+ "package.json": { workspaces: { packages: ["libs/*"], catalog: { zod: "^4.0.0" } } },
81
+ "libs/core/package.json": { name: "core" },
82
+ });
83
+ expect(dirsOf(root)).toEqual(["libs/core"]);
84
+ });
85
+
86
+ it("drops dirs a ! pattern excludes", () => {
87
+ const root = repo({
88
+ "package.json": { workspaces: ["packages/*", "!packages/legacy"] },
89
+ "packages/api/package.json": {},
90
+ "packages/legacy/package.json": {},
91
+ });
92
+ expect(dirsOf(root)).toEqual(["packages/api"]);
93
+ });
94
+
95
+ it("skips matched dirs without package.json, matched files and anything under node_modules", () => {
96
+ const root = repo({
97
+ "package.json": { workspaces: ["packages/*", "packages/**/deep", "./tools/*/"] },
98
+ "packages/api/package.json": {},
99
+ "packages/notes/README.md": "no manifest",
100
+ "packages/README.md": "a file",
101
+ "packages/node_modules/dep/package.json": {},
102
+ "packages/api/node_modules/x/deep/package.json": {},
103
+ "packages/a/b/deep/package.json": {},
104
+ "tools/lint/package.json": {},
105
+ });
106
+ expect(dirsOf(root)).toEqual(["packages/a/b/deep", "packages/api", "tools/lint"]);
107
+ });
108
+
109
+ it("lists a dir matched by two patterns once", () => {
110
+ const root = repo({
111
+ "package.json": { workspaces: ["packages/*", "packages/api"] },
112
+ "packages/api/package.json": {},
113
+ });
114
+ expect(dirsOf(root)).toEqual(["packages/api"]);
115
+ });
116
+
117
+ it("normalizes backslash dirs from the glob to POSIX separators", () => {
118
+ const root = repo({
119
+ "package.json": { workspaces: ["packages/*", "!packages/old"] },
120
+ "packages/api/package.json": {},
121
+ "packages/web/package.json": {},
122
+ "packages/old/package.json": {},
123
+ });
124
+ const glob = (pattern: string): string[] =>
125
+ pattern === "packages/*" ? ["packages\\web", "packages\\api\\", "packages\\old"] : ["packages\\old"];
126
+ expect(dirsOf(root, glob)).toEqual(["packages/api", "packages/web"]);
127
+ });
128
+
129
+ it("keeps a package's private flag and refuses one that is not true or false", () => {
130
+ const root = repo({
131
+ "package.json": { workspaces: ["packages/*"] },
132
+ "packages/api/package.json": { private: true },
133
+ "packages/web/package.json": {},
134
+ });
135
+ const result = readWorkspace(root);
136
+ expect(result.ok && result.workspace.packages.map((pkg) => pkg.manifest.private)).toEqual([true, undefined]);
137
+ const bad = readWorkspace(repo({ "package.json": { private: "yes" } }));
138
+ expect(!bad.ok && bad.errors).toEqual(["package.json: private: must be true or false"]);
139
+ });
140
+
141
+ it("reports a root package.json that is not JSON", () => {
142
+ const root = repo({ "package.json": "{ nope" });
143
+ const result = readWorkspace(root);
144
+ expect(result.ok).toBe(false);
145
+ expect(!result.ok && result.errors).toEqual([expect.stringMatching(/^package\.json: not valid JSON: .+/)]);
146
+ });
147
+
148
+ it("reports manifest fields of the wrong shape, naming the file and the field", () => {
149
+ const root = repo({
150
+ "package.json": { workspaces: "packages/*", dependencies: { "@workerstack/repo": 1 } },
151
+ });
152
+ expect(readWorkspace(root)).toEqual({
153
+ ok: false,
154
+ errors: [
155
+ 'package.json: workspaces: must be a list of globs or { "packages": [...] }',
156
+ "package.json: dependencies.@workerstack/repo: must be a version string",
157
+ ],
158
+ });
159
+ });
160
+
161
+ it("reports a manifest that is not an object", () => {
162
+ const root = repo({ "package.json": "[]" });
163
+ expect(readWorkspace(root)).toEqual({ ok: false, errors: ["package.json: must be a JSON object"] });
164
+ });
165
+
166
+ it("reports every malformed workspace package manifest", () => {
167
+ const root = repo({
168
+ "package.json": { workspaces: ["packages/*"] },
169
+ "packages/api/package.json": { name: 7 },
170
+ "packages/ok/package.json": {},
171
+ "packages/web/package.json": "",
172
+ });
173
+ const result = readWorkspace(root);
174
+ expect(result.ok).toBe(false);
175
+ expect(!result.ok && result.errors).toEqual([
176
+ "packages/api/package.json: name: must be a string",
177
+ expect.stringMatching(/^packages\/web\/package\.json: not valid JSON: .+/),
178
+ ]);
179
+ });
180
+ });