@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
package/cli/sync.ts ADDED
@@ -0,0 +1,360 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { CONFIG_FILE, findRoot, loadConfig, type ResolvedConfig } from "./config.ts";
4
+ import { canonicalPath } from "./scripts/workspace-root.ts";
5
+ import { normalizeRel, prunePaths, readManifest, renderManifest } from "./sync/manifest.ts";
6
+ import { gitFiles, ignoreBlockLines, strayIgnoreFiles, untrackedEntries, type PostPass } from "./sync/git-files.ts";
7
+ import {
8
+ applyMigration,
9
+ conflictLines,
10
+ generatedFiles,
11
+ planAdoption,
12
+ planMigration,
13
+ planRuleUpgrade,
14
+ } from "./sync/migrate.ts";
15
+ import { isSelf, stockRoot } from "./sync/stock.ts";
16
+ import { type GitRun, markHooksExecutable, standards, syncHooksPath } from "./sync/standards.ts";
17
+ import { GENERATORS, type Generator, type Output, type SyncContext } from "./sync/outputs.ts";
18
+ import { type FormatEntry, validateOutputs } from "./sync/validate.ts";
19
+ import { isCurrent, writeAll } from "./sync/write.ts";
20
+ import { readWorkspace, type Workspace } from "./sync/workspace.ts";
21
+
22
+ /** Where repo sync prints; each call gets whole lines. */
23
+ export type SyncIo = { cwd: string; stdout: (s: string) => void; stderr: (s: string) => void };
24
+
25
+ /** Seams for tests: the generators to run, the config reader, the output formats to validate against, the platform links are made for, and how one output is written. */
26
+ export type SyncDeps = {
27
+ generators?: readonly Generator[];
28
+ /** Run in order over the generators' outputs, each also seeing the outputs of those before it; `POST_PASSES` when omitted. */
29
+ post?: readonly PostPass[];
30
+ formats?: readonly FormatEntry[];
31
+ /** Reads the config; `loadConfig` when omitted. */
32
+ load?: typeof loadConfig;
33
+ platform?: NodeJS.Platform;
34
+ writeOutput?: (root: string, output: Output, platform: NodeJS.Platform) => string | null;
35
+ /** Runs git for the `core.hooksPath` step; `runGit` when omitted. */
36
+ git?: GitRun;
37
+ };
38
+
39
+ type Mode = { check: boolean; postinstall: boolean };
40
+ type ModeResult = { ok: true; mode: Mode } | { ok: false; error: string };
41
+
42
+ const PREFIX = "repo sync: ";
43
+
44
+ /** The passes over the generators' outputs: standards first, so the git files pass ignores its configs. */
45
+ export const POST_PASSES: readonly PostPass[] = [standards, gitFiles];
46
+
47
+ /**
48
+ * Runs every generator in order, then each pass of `post` over their outputs and those of the passes before it;
49
+ * nothing is written, so all errors surface before any file changes. A generator that fails on the file system
50
+ * reports its message as an error, like any other it found.
51
+ */
52
+ export function planSync(
53
+ ctx: SyncContext,
54
+ generators: readonly Generator[] = GENERATORS,
55
+ post: readonly PostPass[] = POST_PASSES,
56
+ ): { outputs: Output[]; errors: string[] } {
57
+ const outputs: Output[] = [];
58
+ const errors: string[] = [];
59
+ const planner = new Map<string, string>();
60
+ const collect = (label: string, run: () => { outputs: Output[]; errors: string[] }) => {
61
+ try {
62
+ const result = run();
63
+ for (const output of result.outputs) {
64
+ const first = planner.get(output.path);
65
+ if (first === undefined) planner.set(output.path, label);
66
+ else errors.push(`${output.path}: planned by ${first} and ${label}; rename one`);
67
+ }
68
+ outputs.push(...result.outputs);
69
+ errors.push(...result.errors);
70
+ } catch (error) {
71
+ errors.push((error as Error).message);
72
+ }
73
+ };
74
+ for (const generator of generators) collect(generator.name, () => generator(ctx));
75
+ for (const pass of post) {
76
+ const planned = [...outputs];
77
+ collect(pass.name, () => pass(ctx, planned));
78
+ }
79
+ return { outputs, errors };
80
+ }
81
+
82
+ /** Every generator's outputs for what is on disk under `root` now, as normalized paths; the errors when any generator or path is wrong. */
83
+ export function computeOutputs(
84
+ root: string,
85
+ config: ResolvedConfig,
86
+ workspace: Workspace,
87
+ generators?: readonly Generator[],
88
+ post?: readonly PostPass[],
89
+ ): { ok: true; outputs: Output[] } | { ok: false; errors: string[] } {
90
+ const readErrors: string[] = [];
91
+ const plan = planSync({ root, config, workspace, read: (rel) => readText(root, rel, readErrors) }, generators, post);
92
+ // An unreadable file reads as missing to the generators, so its error is the cause of anything they report.
93
+ if (readErrors.length > 0) return { ok: false, errors: readErrors };
94
+ if (plan.errors.length > 0) return { ok: false, errors: plan.errors };
95
+ // One spelling per path: outputs and manifest entries are compared only in the normalized form.
96
+ const normalized: Output[] = [];
97
+ const badPaths: string[] = [];
98
+ for (const output of plan.outputs) {
99
+ const rel = normalizeRel(output.path, root);
100
+ if (rel === null) badPaths.push(`${output.path}: not a path inside the repo`);
101
+ else normalized.push({ ...output, path: rel });
102
+ }
103
+ return badPaths.length > 0 ? { ok: false, errors: badPaths } : { ok: true, outputs: normalized };
104
+ }
105
+
106
+ /**
107
+ * `repo sync [--check | --postinstall]`: validates the config and every output, writes the outputs that changed,
108
+ * removes what the last run wrote and no output names now, then records every output path in the manifest.
109
+ * `--check` writes nothing and fails on drift; `--postinstall` reports errors but exits 0.
110
+ */
111
+ export async function runSync(args: string[], io: SyncIo, deps: SyncDeps = {}): Promise<number> {
112
+ const platform = deps.platform ?? process.platform;
113
+ const parsed = parseMode(args);
114
+ if (!parsed.ok) {
115
+ io.stderr(`${PREFIX}${parsed.error}\n`);
116
+ return 1;
117
+ }
118
+ const { check, postinstall } = parsed.mode;
119
+ const fail = (errors: string[]): number => {
120
+ for (const error of errors) io.stderr(`${PREFIX}${error}\n`);
121
+ return postinstall ? 0 : 1;
122
+ };
123
+
124
+ // A refusal before any output is written ends by saying so; `--check` writes nothing, so it has nothing to say about "written".
125
+ const refuse = (errors: string[]): number => fail(check ? errors : [...errors, "nothing written"]);
126
+
127
+ const root = findRoot(io.cwd);
128
+ if (root === null) return fail([`no ${CONFIG_FILE} in ${io.cwd} or any parent; run \`bun repo init\``]);
129
+
130
+ const load = deps.load ?? loadConfig;
131
+ const config = await load(root);
132
+ if (!config.ok) return refuse(config.errors);
133
+ // Here the trees are the stock source; without the agents pack sync would prune them.
134
+ if (isSelf(root) && !config.config.packs.includes("agents")) {
135
+ return refuse([`${CONFIG_FILE}: this repo ships the agent trees as its stock source; keep "agents" in packs`]);
136
+ }
137
+
138
+ const workspace = readWorkspace(root);
139
+ if (!workspace.ok) return refuse(workspace.errors);
140
+ let resolved = config.config;
141
+ // Nothing is migrated, adopted or written from outputs that would not parse or beside an ignore file sync refuses; `--check` has nothing to say about "written".
142
+ const invalid = (outputs: Output[]): number | null => {
143
+ const problems = [
144
+ ...strayIgnoreFiles(
145
+ root,
146
+ workspace.workspace,
147
+ ignoreBlockLines(workspace.workspace.manifest.name, resolved.agents.hosts, outputs),
148
+ ),
149
+ ...validateOutputs(root, outputs, deps.formats),
150
+ ];
151
+ if (problems.length === 0) return null;
152
+ return refuse(problems);
153
+ };
154
+ const compute = () => computeOutputs(root, resolved, workspace.workspace, deps.generators, deps.post);
155
+ // Old-format `.repo/` rules and subagents are rewritten, and marked where they replace a stock one, before anything reads them.
156
+ const upgrade = async (): Promise<void> => {
157
+ for (const line of await applyMigration(root, planRuleUpgrade(root, stockRoot(root)))) io.stdout(`${line}\n`);
158
+ };
159
+ if (!isSelf(root)) {
160
+ const pending = planRuleUpgrade(root, stockRoot(root)).length;
161
+ if (pending > 0 && check) {
162
+ io.stderr(`${PREFIX}${pending} migration step${pending === 1 ? "" : "s"} pending; run \`bun repo sync\`\n`);
163
+ return 1;
164
+ }
165
+ try {
166
+ await upgrade();
167
+ } catch (error) {
168
+ return fail([(error as Error).message]);
169
+ }
170
+ }
171
+ let planned = compute();
172
+ if (!planned.ok) return refuse(planned.errors);
173
+ const firstInvalid = invalid(planned.outputs);
174
+ if (firstInvalid !== null) return firstInvalid;
175
+
176
+ let migrated = false;
177
+ // A copied install moves to .repo/ and references/ before anything is planned from them; this repo never does.
178
+ // It has no overlay yet, so what sync plans now is the stock's own generated text, the base its copies are compared with.
179
+ if (!isSelf(root)) {
180
+ const steps = planMigration(
181
+ root,
182
+ stockRoot(root),
183
+ generatedFiles(planned.outputs),
184
+ untrackedEntries(resolved.agents.hosts, planned.outputs),
185
+ );
186
+ const conflicts = conflictLines(steps);
187
+ if (conflicts.length > 0) {
188
+ for (const line of conflicts) io.stderr(`${line}\n`);
189
+ return postinstall ? 0 : 1;
190
+ }
191
+ if (steps.length > 0 && check) {
192
+ io.stderr(
193
+ `${PREFIX}${steps.length} migration step${steps.length === 1 ? "" : "s"} pending; run \`bun repo sync\`\n`,
194
+ );
195
+ return 1;
196
+ }
197
+ migrated = steps.length > 0;
198
+ if (steps.length > 0) {
199
+ try {
200
+ for (const line of await applyMigration(root, steps)) io.stdout(`${line}\n`);
201
+ await upgrade();
202
+ } catch (error) {
203
+ return fail([(error as Error).message]);
204
+ }
205
+ const reloaded = await load(root);
206
+ if (!reloaded.ok) return fail(reloaded.errors);
207
+ resolved = reloaded.config;
208
+ planned = compute();
209
+ if (!planned.ok) return refuse(planned.errors);
210
+ const afterMigration = invalid(planned.outputs);
211
+ if (afterMigration !== null) return afterMigration;
212
+ }
213
+ }
214
+
215
+ // What a consumer already keeps where sync generates moves into `.repo/` before it is overwritten; a copied install did that in its migration.
216
+ if (!isSelf(root) && !migrated) {
217
+ const manifestPaths = readManifest(root).paths.flatMap((entry) => normalizeRel(entry, root) ?? []);
218
+ const steps = planAdoption(root, stockRoot(root), resolved, planned.outputs, manifestPaths);
219
+ const conflicts = conflictLines(steps);
220
+ if (conflicts.length > 0) {
221
+ for (const line of conflicts) io.stderr(`${line}\n`);
222
+ return postinstall ? 0 : 1;
223
+ }
224
+ if (steps.length > 0 && check) {
225
+ io.stderr(
226
+ `${PREFIX}${steps.length} adoption step${steps.length === 1 ? "" : "s"} pending; run \`bun repo sync\`\n`,
227
+ );
228
+ return 1;
229
+ }
230
+ if (steps.length > 0) {
231
+ try {
232
+ for (const line of await applyMigration(root, steps)) io.stdout(`${line}\n`);
233
+ await upgrade();
234
+ } catch (error) {
235
+ return fail([(error as Error).message]);
236
+ }
237
+ // The overlay changed what the generators read.
238
+ planned = compute();
239
+ if (!planned.ok) return refuse(planned.errors);
240
+ const afterAdoption = invalid(planned.outputs);
241
+ if (afterAdoption !== null) return afterAdoption;
242
+ }
243
+ }
244
+ const outputs = planned.outputs;
245
+
246
+ const changed = outputs.filter((output) => !isCurrentOutput(root, output, platform));
247
+ const prune = prunePaths(
248
+ readManifest(root),
249
+ outputs.map((output) => output.path),
250
+ root,
251
+ ).filter((rel) => isPrunable(path.join(root, rel)));
252
+ const manifest = renderManifest(outputs.map((output) => output.path));
253
+ if (check) {
254
+ for (const output of changed) io.stderr(`${PREFIX}${output.path} is out of date; run \`bun repo sync\`\n`);
255
+ for (const rel of prune) io.stderr(`${PREFIX}${rel} is stale and would be removed; run \`bun repo sync\`\n`);
256
+ if (changed.length === 0 && prune.length === 0) io.stdout(`${PREFIX}up to date\n`);
257
+ return changed.length === 0 && prune.length === 0 ? 0 : 1;
258
+ }
259
+
260
+ const written = writeAll(root, changed, prune, manifest, { platform, writeOutput: deps.writeOutput });
261
+ if (!written.ok) return fail(written.refused ? [...written.errors, "nothing written"] : written.errors);
262
+ const parts = [];
263
+ if (changed.length > 0) parts.push(`wrote ${summarize(changed.map((output) => output.path))}`);
264
+ if (prune.length > 0) parts.push(`removed ${summarize(prune)}`);
265
+ // Machine state, not repo content, so `--check` never gets here.
266
+ const hooks = syncHooksPath(root, resolved.packs.includes("standards"), deps.git);
267
+ const modes = resolved.packs.includes("standards")
268
+ ? markHooksExecutable(root, platform, deps.git)
269
+ : { done: [], warnings: [] };
270
+ parts.push(...hooks.done, ...modes.done);
271
+ for (const warning of [...hooks.warnings, ...modes.warnings]) io.stderr(`${PREFIX}${warning}\n`);
272
+ io.stdout(`${PREFIX}${parts.length === 0 ? "up to date" : parts.join("; ")}\n`);
273
+ return 0;
274
+ }
275
+
276
+ /** Path list for the summary line: outputs sharing a top dir collapse to `dir/ (n)`, single files keep their name. */
277
+ function summarize(paths: readonly string[]): string {
278
+ const top = (rel: string) => (rel.includes("/") ? rel.slice(0, rel.indexOf("/")) : null);
279
+ const counts = new Map<string, number>();
280
+ for (const rel of paths) {
281
+ const dir = top(rel);
282
+ if (dir !== null) counts.set(dir, (counts.get(dir) ?? 0) + 1);
283
+ }
284
+ const seen = new Set<string>();
285
+ const parts: string[] = [];
286
+ for (const rel of paths) {
287
+ const dir = top(rel);
288
+ if (dir === null || counts.get(dir) === 1) parts.push(rel);
289
+ else if (!seen.has(dir)) {
290
+ parts.push(`${dir}/ (${counts.get(dir)})`);
291
+ seen.add(dir);
292
+ }
293
+ }
294
+ return parts.join(", ");
295
+ }
296
+
297
+ function parseMode(args: string[]): ModeResult {
298
+ const mode: Mode = { check: false, postinstall: false };
299
+ for (const arg of args) {
300
+ if (arg === "--check") mode.check = true;
301
+ else if (arg === "--postinstall") mode.postinstall = true;
302
+ else return { ok: false, error: `unknown flag ${arg}; use --check or --postinstall` };
303
+ }
304
+ if (mode.check && mode.postinstall) return { ok: false, error: "use --check or --postinstall, not both" };
305
+ return { ok: true, mode };
306
+ }
307
+
308
+ /** A root-relative file's text, or null when it is missing; any other failure is recorded in `errors`. */
309
+ function readText(root: string, rel: string, errors: string[]): string | null {
310
+ try {
311
+ return fs.readFileSync(path.join(root, rel), "utf8");
312
+ } catch (error) {
313
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
314
+ errors.push(`${rel}: cannot read: ${(error as Error).message}`);
315
+ return null;
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Whether the output is on disk exactly as planned: same bytes for a file; for a link the same text, so a link
321
+ * that reaches its target through another generated link is rewritten before that one is pruned. A win32
322
+ * junction holds an absolute target, so it only has to name the same path once its parent dirs are resolved
323
+ * (8.3 short names, drive case); its last segment is never followed, so a junction into another link is stale.
324
+ */
325
+ function isCurrentOutput(root: string, output: Output, platform: NodeJS.Platform): boolean {
326
+ const abs = path.join(root, output.path);
327
+ if (output.kind === "file") {
328
+ // A hook git cannot run is out of date even with the right bytes; win32 has no execute bit.
329
+ return isCurrent(abs, output.content) && (output.executable !== true || platform === "win32" || isExecutable(abs));
330
+ }
331
+ try {
332
+ if (!fs.lstatSync(abs).isSymbolicLink()) return false;
333
+ if (platform !== "win32") return fs.readlinkSync(abs) === output.target;
334
+ const here = path.dirname(abs);
335
+ return (
336
+ parentCanonical(path.resolve(here, fs.readlinkSync(abs))) === parentCanonical(path.resolve(here, output.target))
337
+ );
338
+ } catch {
339
+ return false;
340
+ }
341
+ }
342
+
343
+ /** Whether the owner may run the file at `abs`, which exists. */
344
+ function isExecutable(abs: string): boolean {
345
+ return (fs.statSync(abs).mode & 0o100) !== 0;
346
+ }
347
+
348
+ /** `abs` with its parent dirs in canonical spelling and its last segment as written. */
349
+ function parentCanonical(abs: string): string {
350
+ return path.join(canonicalPath(path.dirname(abs)), path.basename(abs));
351
+ }
352
+
353
+ /** Whether a manifest path is on disk and removable: a file or a link; a real directory is left alone. */
354
+ function isPrunable(abs: string): boolean {
355
+ try {
356
+ return !fs.lstatSync(abs).isDirectory();
357
+ } catch {
358
+ return false;
359
+ }
360
+ }
@@ -0,0 +1,36 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { afterEach, expect, it } from "vitest";
5
+ import { ownerLookup } from "./tdd-scope.ts";
6
+
7
+ const temps: string[] = [];
8
+ afterEach(() => {
9
+ for (const dir of temps.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
10
+ });
11
+
12
+ function tree(files: Record<string, string>): (file: string) => ReturnType<ReturnType<typeof ownerLookup>> {
13
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "owners-"));
14
+ temps.push(root);
15
+ for (const [rel, text] of Object.entries(files)) {
16
+ fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
17
+ fs.writeFileSync(path.join(root, rel), text);
18
+ }
19
+ return ownerLookup(root);
20
+ }
21
+
22
+ it("finds the nearest package.json up to the root, and whether it is private", () => {
23
+ const owner = tree({
24
+ "package.json": "{}",
25
+ "packages/a/package.json": '{ "private": true }',
26
+ "packages/b/package.json": '{ "private": false }',
27
+ });
28
+ expect(owner("packages/a/src/x.ts")).toEqual({ dir: "packages/a", public: false });
29
+ expect(owner("packages/b/x.ts")).toEqual({ dir: "packages/b", public: true });
30
+ expect(owner("cli/x.ts")).toEqual({ dir: ".", public: true });
31
+ });
32
+
33
+ it("treats a file with no manifest, or one it cannot read or parse, as private", () => {
34
+ expect(tree({})("x.ts")).toEqual({ dir: ".", public: false });
35
+ expect(tree({ "a/package.json": "{ nope" })("a/x.ts")).toEqual({ dir: "a", public: false });
36
+ });
@@ -0,0 +1,41 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import type { NameStatus } from "./red-proof.ts";
4
+ import { isSource, isTestFile } from "./test-files.ts";
5
+
6
+ /** The package that owns a file: its dir relative to the root (`.` for the root), and whether it is public. */
7
+ export type Owner = { dir: string; public: boolean };
8
+
9
+ /** Whether the manifest at `file` leaves the package public (`private` is not `true`); a missing or invalid one does not. */
10
+ function isPublic(file: string): boolean {
11
+ try {
12
+ return (
13
+ (JSON.parse(fs.readFileSync(file, "utf8")) /* only private is read */ as { private?: unknown }).private !== true
14
+ );
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Looks up the owner of a repo-relative file at `root`: the package of its nearest `package.json` up to the root, or
22
+ * the root itself when none is found. A missing or invalid manifest makes the owner private.
23
+ */
24
+ export function ownerLookup(root: string): (file: string) => Owner {
25
+ return (file) => {
26
+ let dir = path.dirname(file);
27
+ while (dir !== "." && !fs.existsSync(path.join(root, dir, "package.json"))) dir = path.dirname(dir);
28
+ return { dir, public: isPublic(path.join(root, dir, "package.json")) };
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Whether red-proof applies to `change`: always with `tdd`, else when a changed source file belongs to a public
34
+ * package, or, with no source file changed, a changed test file does.
35
+ */
36
+ export function proofApplies(change: readonly NameStatus[], tdd: boolean, owners: (file: string) => Owner): boolean {
37
+ if (tdd) return true;
38
+ const sources = change.filter((entry) => isSource(entry.path));
39
+ const files = sources.length > 0 ? sources : change.filter((entry) => isTestFile(entry.path));
40
+ return files.some((entry) => owners(entry.path).public);
41
+ }
@@ -0,0 +1,16 @@
1
+ const CODE = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
2
+
3
+ /** True when the path is a test: a `*.test.*` or `*.spec.*` file, or a file under an `e2e/` folder. */
4
+ export function isTestFile(file: string): boolean {
5
+ return /\.(test|spec)\.[^/]+$/.test(file) || /(^|\/)e2e\//.test(file);
6
+ }
7
+
8
+ /** True when the path is a code file (`.ts .tsx .mts .cts .js .jsx .mjs .cjs`). */
9
+ export const isCodeFile = (file: string): boolean => CODE.test(file);
10
+
11
+ /** A code file a change counts as source: not a test, a fixture, a snapshot, a mock or a config. */
12
+ export const isSource = (file: string): boolean =>
13
+ CODE.test(file) &&
14
+ !isTestFile(file) &&
15
+ !/(^|\/)(fixtures|__snapshots__|__mocks__)\//.test(file) &&
16
+ !/\.config\.[^/]+$/.test(file);
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The compiler options a script checks a file with: those of the `tsconfig.json` nearest above it (its owner), or of a
3
+ * config beside that one, such as the strict `tsconfig.src.json`. Shared by the magic and rejection checks.
4
+ */
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import ts from "typescript";
8
+ import type { Result } from "./scripts/lint-baseline.ts";
9
+ import { RUNTIME_CONFIG } from "./tsconfig.constants.ts";
10
+
11
+ /** A program's options, and the files its tsconfig includes, as program paths. */
12
+ export type Setup = { options: ts.CompilerOptions; fileNames: readonly string[] };
13
+
14
+ const TOP = ".";
15
+ const SOURCE = /\.(?:tsx?|mts|cts)$/;
16
+ const DECLARATION = /\.d\.[cm]?ts$/;
17
+ const TEST = /\.(?:test|spec|e2e)\./;
18
+ /** Code that must not compile, which `check-rejections.ts` checks instead. */
19
+ const REJECT = /\.reject\.tsx?$/;
20
+ const SKIPPED_DIRS = ["fixtures", "e2e", "vendor", "node_modules"];
21
+ /** The options a file with no tsconfig.json above it is checked with: strict, resolved as a bundler would. */
22
+ const DEFAULTS: ts.CompilerOptions = {
23
+ strict: true,
24
+ target: ts.ScriptTarget.ESNext,
25
+ module: ts.ModuleKind.Preserve,
26
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
27
+ allowImportingTsExtensions: true,
28
+ noEmit: true,
29
+ skipLibCheck: true,
30
+ };
31
+
32
+ /** Whether a root-relative file is TypeScript outside a fixture, e2e or vendored dir, and not a declaration or reject file. */
33
+ function isScripted(rel: string): boolean {
34
+ const dirs = path.posix.dirname(rel).split("/");
35
+ const skipped = DECLARATION.test(rel) || REJECT.test(rel) || dirs.some((dir) => SKIPPED_DIRS.includes(dir));
36
+ return SOURCE.test(rel) && !skipped;
37
+ }
38
+
39
+ /** A root-relative source file the script checks read: TypeScript, not a declaration, test, fixture, e2e or vendored file. */
40
+ export const isChecked = (rel: string): boolean => isScripted(rel) && !TEST.test(path.posix.basename(rel));
41
+
42
+ /** A root-relative test file whose asserts the script checks read: a TypeScript test outside a fixture or e2e dir. */
43
+ export const isTest = (rel: string): boolean => isScripted(rel) && TEST.test(path.posix.basename(rel));
44
+
45
+ /** `file` with forward slashes, the spelling typescript keys its source files by. */
46
+ export const posix = (file: string): string => file.split(path.sep).join("/");
47
+
48
+ /** The root-relative dir of the tsconfig.json nearest above `rel`, up to the root; null when there is none. */
49
+ export function ownerOf(root: string, rel: string): string | null {
50
+ let dir = path.posix.dirname(rel);
51
+ for (;;) {
52
+ if (fs.existsSync(path.join(root, dir, RUNTIME_CONFIG))) return dir;
53
+ if (dir === TOP) return null;
54
+ dir = path.posix.dirname(dir);
55
+ }
56
+ }
57
+
58
+ /** The setup of `config` in the owner `dir` (root-relative), the defaults for no owner, or why it cannot be read. */
59
+ export function setupOf(root: string, dir: string | null, config: string): Result<Setup> {
60
+ if (dir === null) return { ok: true, value: { options: DEFAULTS, fileNames: [] } };
61
+ const file = posix(path.join(root, dir, config));
62
+ const read = ts.readConfigFile(file, ts.sys.readFile);
63
+ if (read.error !== undefined) {
64
+ const message = ts.flattenDiagnosticMessageText(read.error.messageText, " ");
65
+ return { ok: false, errors: [`${path.posix.join(dir, config)}: ${message}`] };
66
+ }
67
+ const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, path.dirname(file), undefined, file);
68
+ return { ok: true, value: { options: { ...parsed.options, noEmit: true }, fileNames: parsed.fileNames } };
69
+ }
@@ -0,0 +1,8 @@
1
+ /** The file names `repo check` and `repo turn` look for beside each other in a package. */
2
+
3
+ /** The config each package checks its code and tests with. */
4
+ export const RUNTIME_CONFIG = "tsconfig.json";
5
+ /** The strict config beside each `tsconfig.json`, over the folder's non-test sources. */
6
+ export const SRC_CONFIG = "tsconfig.src.json";
7
+ /** The ts-reset file every base lists under `files`. */
8
+ export const RESET = "reset.d.ts";
@@ -0,0 +1,21 @@
1
+ import type { Host, LintSkip, Pack, SkillPack } from "./config.ts";
2
+ import type { KnipIgnoreEntry } from "./knip-schema.ts";
3
+
4
+ /** What workerstack.config.ts default-exports, as `defineConfig` takes it; config.ts checks its schema against it. */
5
+ export type WorkerstackConfig = {
6
+ packs?: readonly Pack[] | undefined;
7
+ agents?: { hosts?: readonly Host[] | undefined; skills?: readonly SkillPack[] | undefined } | undefined;
8
+ commit?: { maxChangedLines?: number | undefined } | undefined;
9
+ coverage?:
10
+ | {
11
+ floors?: string | undefined;
12
+ knownFailures?: string | undefined;
13
+ mutate?: readonly string[] | undefined;
14
+ mutationFloors?: string | undefined;
15
+ }
16
+ | undefined;
17
+ nightly?: { shards?: number | undefined; secrets?: readonly string[] | undefined } | undefined;
18
+ tdd?: boolean | undefined;
19
+ lint?: { skip?: readonly LintSkip[] | undefined } | undefined;
20
+ knip?: { ignore?: readonly KnipIgnoreEntry[] | undefined } | undefined;
21
+ };
@@ -0,0 +1,10 @@
1
+ /** Every variable this repo's code reads, sorted; add a name here to read it. */
2
+ declare const ENV_VARIABLES: readonly ["CI", "CLAUDE_EFFORT", "CLAUDE_PROJECT_DIR", "CLAUDE_SESSION_ID", "ELEVENLABS_API_KEY", "ELEVENLABS_MODEL_ID", "ELEVENLABS_VOICE_ID", "GITHUB_ACTIONS", "GIT_CONFIG_GLOBAL", "GIT_DIR", "GROK_HOOK_EVENT", "GROK_SESSION_ID", "GROK_WORKSPACE_ROOT", "HOME", "LISTEN_DEVICE", "LISTEN_FFMPEG_FORMAT", "LISTEN_MODEL", "LISTEN_SECONDS", "LISTEN_STT", "LISTEN_WHISPER_MODEL", "NODE_PATH", "OPENAI_API_KEY", "OPENAI_TTS_MODEL", "OPENAI_TTS_VOICE", "PATH", "PATHEXT", "REPO_BOARD_HOME", "REPO_BOARD_PROVIDER", "REPO_BOARD_SESSION", "REPO_BROWSER", "REPO_E2E_KEEP", "REPO_E2E_ROOT", "REPO_E2E_TARBALL", "SPEAK_PROVIDER", "TURN_RECORD", "TYPESAFE_API_KEY", "USERPROFILE", "WHISPER_MODEL", "WORKSPACE_ACCOUNT"];
3
+ export type Env = {
4
+ [Name in (typeof ENV_VARIABLES)[number]]?: string | undefined;
5
+ };
6
+ /** The environment parsed against the schema; throws a line per bad variable. */
7
+ export declare function readEnv(source?: Record<string, unknown>): Env;
8
+ /** A copy of the environment with `overrides` applied and every undefined value dropped, for spawn. */
9
+ export declare function childEnv(overrides?: Record<string, string | undefined>): Record<string, string>;
10
+ export {};
@@ -0,0 +1,64 @@
1
+ import { z } from "zod";
2
+ /** Every variable this repo's code reads, sorted; add a name here to read it. */
3
+ const ENV_VARIABLES = [
4
+ "CI",
5
+ "CLAUDE_EFFORT",
6
+ "CLAUDE_PROJECT_DIR",
7
+ "CLAUDE_SESSION_ID",
8
+ "ELEVENLABS_API_KEY",
9
+ "ELEVENLABS_MODEL_ID",
10
+ "ELEVENLABS_VOICE_ID",
11
+ "GITHUB_ACTIONS",
12
+ "GIT_CONFIG_GLOBAL",
13
+ "GIT_DIR",
14
+ "GROK_HOOK_EVENT",
15
+ "GROK_SESSION_ID",
16
+ "GROK_WORKSPACE_ROOT",
17
+ "HOME",
18
+ "LISTEN_DEVICE",
19
+ "LISTEN_FFMPEG_FORMAT",
20
+ "LISTEN_MODEL",
21
+ "LISTEN_SECONDS",
22
+ "LISTEN_STT",
23
+ "LISTEN_WHISPER_MODEL",
24
+ "NODE_PATH",
25
+ "OPENAI_API_KEY",
26
+ "OPENAI_TTS_MODEL",
27
+ "OPENAI_TTS_VOICE",
28
+ "PATH",
29
+ "PATHEXT",
30
+ "REPO_BOARD_HOME",
31
+ "REPO_BOARD_PROVIDER",
32
+ "REPO_BOARD_SESSION",
33
+ "REPO_BROWSER",
34
+ "REPO_E2E_KEEP",
35
+ "REPO_E2E_ROOT",
36
+ "REPO_E2E_TARBALL",
37
+ "SPEAK_PROVIDER",
38
+ "TURN_RECORD",
39
+ "TYPESAFE_API_KEY",
40
+ "USERPROFILE",
41
+ "WHISPER_MODEL",
42
+ "WORKSPACE_ACCOUNT",
43
+ ];
44
+ /** Each variable is an optional string, unless a command cannot run without it. */
45
+ function envSchema() {
46
+ const text = z.string().optional();
47
+ return z.object(Object.fromEntries(ENV_VARIABLES.map((name) => [name, text])));
48
+ }
49
+ /** `repo: environment: <NAME>: <issue>`, one line per bad variable. */
50
+ function describe(error) {
51
+ return error.issues.map((issue) => `repo: environment: ${issue.path.join(".")}: ${issue.message}`).join("\n");
52
+ }
53
+ /** The environment parsed against the schema; throws a line per bad variable. */
54
+ export function readEnv(source = process.env) {
55
+ const parsed = envSchema().safeParse(source);
56
+ if (!parsed.success)
57
+ throw new Error(describe(parsed.error));
58
+ return parsed.data;
59
+ }
60
+ /** A copy of the environment with `overrides` applied and every undefined value dropped, for spawn. */
61
+ export function childEnv(overrides = {}) {
62
+ const merged = { ...process.env, ...overrides };
63
+ return Object.fromEntries(Object.entries(merged).filter((entry) => entry[1] !== undefined));
64
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Where a run's tests get their temp dir: `<real tmpdir>/repo-test-<pid>` of the vitest main process. The config
3
+ * may be evaluated more than once per project, so the path comes from the pid, not from chance.
4
+ */
5
+ export declare function rootPath(pid?: number): string;
6
+ /**
7
+ * The environment that makes the root the temp dir of test processes only (workers and what they spawn); the main
8
+ * process, and the tools it runs in-process such as miniflare, keep the real one.
9
+ */
10
+ export declare function rootEnv(root?: string): Record<string, string>;
@@ -0,0 +1,20 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { ROOT_PREFIX } from "./temp-root.constants.js";
5
+ /**
6
+ * Where a run's tests get their temp dir: `<real tmpdir>/repo-test-<pid>` of the vitest main process. The config
7
+ * may be evaluated more than once per project, so the path comes from the pid, not from chance.
8
+ */
9
+ export function rootPath(pid = process.pid) {
10
+ return path.join(fs.realpathSync(os.tmpdir()), `${ROOT_PREFIX}${pid}`);
11
+ }
12
+ /**
13
+ * The environment that makes the root the temp dir of test processes only (workers and what they spawn); the main
14
+ * process, and the tools it runs in-process such as miniflare, keep the real one.
15
+ */
16
+ export function rootEnv(root = rootPath()) {
17
+ // Node's compile cache defaults to <tmpdir>/node-compile-cache; a worker would put it in the root.
18
+ const cache = process.env.NODE_COMPILE_CACHE ?? path.join(path.dirname(root), "node-compile-cache");
19
+ return { TMPDIR: root, TEMP: root, TMP: root, NODE_COMPILE_CACHE: cache };
20
+ }