@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,62 @@
1
+ /** The `nightly` section of workerstack.config.ts: how many shards the nightly mutation run has, which secrets its live job gets. */
2
+ import { z } from "zod";
3
+ import {
4
+ MAX_SHARDS,
5
+ MIN_SHARDS,
6
+ RESERVED_PREFIX,
7
+ SECRETS_ERROR,
8
+ SECRET_ERROR,
9
+ SECRET_NAME,
10
+ SECTION_ERROR,
11
+ SHARDS_ERROR,
12
+ TWICE,
13
+ } from "./nightly-config.constants.ts";
14
+
15
+ /** What the generated nightly workflow reads. */
16
+ export type ResolvedNightly = { readonly shards: number; readonly secrets: readonly string[] };
17
+
18
+ /** The nightly run a config gets when it omits `nightly` or one of its keys. */
19
+ export const DEFAULT_NIGHTLY: ResolvedNightly = { shards: 1, secrets: [] };
20
+
21
+ type NightlySchema = z.ZodObject<
22
+ {
23
+ shards: z.ZodOptional<z.ZodNumber>;
24
+ secrets: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
25
+ },
26
+ z.core.$strict
27
+ >;
28
+
29
+ function buildSchema(): NightlySchema {
30
+ const secret = z
31
+ .string({ error: SECRET_ERROR })
32
+ .regex(SECRET_NAME, { error: SECRET_ERROR })
33
+ .refine((name) => !name.startsWith(RESERVED_PREFIX), { error: SECRET_ERROR });
34
+ const secrets = z
35
+ .array(secret, { error: SECRETS_ERROR })
36
+ .readonly()
37
+ .superRefine((names, ctx) => {
38
+ const counts = new Map<string, number>();
39
+ for (const name of names) counts.set(name, (counts.get(name) ?? 0) + 1);
40
+ for (const [name, count] of counts) {
41
+ if (count === 1) continue;
42
+ const times = count === TWICE ? "twice" : `${count} times`;
43
+ ctx.addIssue({ code: "custom", message: `${JSON.stringify(name)} is listed ${times}` });
44
+ }
45
+ });
46
+ const shards = z
47
+ .number({ error: SHARDS_ERROR })
48
+ .int({ error: SHARDS_ERROR })
49
+ .min(MIN_SHARDS, { error: SHARDS_ERROR })
50
+ .max(MAX_SHARDS, { error: SHARDS_ERROR });
51
+ return z.strictObject({ shards: shards.optional(), secrets: secrets.optional() }, { error: SECTION_ERROR });
52
+ }
53
+
54
+ export const nightlySchema: NightlySchema = /* @__PURE__ */ buildSchema();
55
+
56
+ /** The parsed config's `nightly` section with every omitted key filled in. */
57
+ export function nightlyOf(parsed: { nightly?: z.output<typeof nightlySchema> | undefined }): {
58
+ nightly: ResolvedNightly;
59
+ } {
60
+ const { shards, secrets } = parsed.nightly ?? {};
61
+ return { nightly: { shards: shards ?? DEFAULT_NIGHTLY.shards, secrets: secrets ?? DEFAULT_NIGHTLY.secrets } };
62
+ }
@@ -0,0 +1,202 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+
3
+ const clackSelect = vi.hoisted(() => vi.fn());
4
+ const clackConfirm = vi.hoisted(() => vi.fn());
5
+ const clackText = vi.hoisted(() => vi.fn());
6
+ const clackPassword = vi.hoisted(() => vi.fn());
7
+ const clackSpinner = vi.hoisted(() => vi.fn());
8
+ const clackIsCancel = vi.hoisted(() => vi.fn());
9
+ const clackCancel = vi.hoisted(() => vi.fn());
10
+
11
+ vi.mock("@clack/prompts", () => ({
12
+ select: clackSelect,
13
+ confirm: clackConfirm,
14
+ text: clackText,
15
+ password: clackPassword,
16
+ spinner: clackSpinner,
17
+ isCancel: clackIsCancel,
18
+ cancel: clackCancel,
19
+ }));
20
+
21
+ import { bailIfCancel, confirm, isInteractive, password, select, spinner, text } from "./prompt.ts";
22
+
23
+ function tty(isTTY: boolean) {
24
+ return { isTTY } as NodeJS.ReadStream & NodeJS.WriteStream;
25
+ }
26
+
27
+ describe("isInteractive", () => {
28
+ it("true when both TTY, CI unset, no yes flags", () => {
29
+ expect(
30
+ isInteractive({
31
+ argv: [],
32
+ env: {},
33
+ stdin: tty(true),
34
+ stdout: tty(true),
35
+ }),
36
+ ).toBe(true);
37
+ });
38
+
39
+ it("true when CI is 0 or false or empty", () => {
40
+ for (const CI of ["0", "false", ""]) {
41
+ expect(
42
+ isInteractive({
43
+ argv: [],
44
+ env: { CI },
45
+ stdin: tty(true),
46
+ stdout: tty(true),
47
+ }),
48
+ ).toBe(true);
49
+ }
50
+ });
51
+
52
+ it("false when stdin not TTY", () => {
53
+ expect(
54
+ isInteractive({
55
+ argv: [],
56
+ env: {},
57
+ stdin: tty(false),
58
+ stdout: tty(true),
59
+ }),
60
+ ).toBe(false);
61
+ });
62
+
63
+ it("false when stdout not TTY", () => {
64
+ expect(
65
+ isInteractive({
66
+ argv: [],
67
+ env: {},
68
+ stdin: tty(true),
69
+ stdout: tty(false),
70
+ }),
71
+ ).toBe(false);
72
+ });
73
+
74
+ it("false when stdin isTTY missing", () => {
75
+ expect(
76
+ isInteractive({
77
+ argv: [],
78
+ env: {},
79
+ stdin: {} as NodeJS.ReadStream,
80
+ stdout: tty(true),
81
+ }),
82
+ ).toBe(false);
83
+ });
84
+
85
+ it.each(["1", "true", "yes", "CI"])("false when CI=%s", (CI) => {
86
+ expect(
87
+ isInteractive({
88
+ argv: [],
89
+ env: { CI },
90
+ stdin: tty(true),
91
+ stdout: tty(true),
92
+ }),
93
+ ).toBe(false);
94
+ });
95
+
96
+ it.each(["--yes", "-y", "--non-interactive"])("false when argv has %s", (flag) => {
97
+ expect(
98
+ isInteractive({
99
+ argv: [flag],
100
+ env: {},
101
+ stdin: tty(true),
102
+ stdout: tty(true),
103
+ }),
104
+ ).toBe(false);
105
+ });
106
+
107
+ it("uses process defaults", () => {
108
+ const stdinDesc = Object.getOwnPropertyDescriptor(process.stdin, "isTTY");
109
+ const stdoutDesc = Object.getOwnPropertyDescriptor(process.stdout, "isTTY");
110
+ const argv = process.argv;
111
+ Object.defineProperty(process.stdin, "isTTY", { configurable: true, value: true });
112
+ Object.defineProperty(process.stdout, "isTTY", { configurable: true, value: true });
113
+ process.argv = ["node", "repo"];
114
+ vi.stubEnv("CI", undefined);
115
+ try {
116
+ expect(isInteractive()).toBe(true);
117
+ process.argv = ["node", "repo", "--yes"];
118
+ expect(isInteractive()).toBe(false);
119
+ } finally {
120
+ process.argv = argv;
121
+ if (stdinDesc) Object.defineProperty(process.stdin, "isTTY", stdinDesc);
122
+ else delete (process.stdin as { isTTY?: boolean }).isTTY;
123
+ if (stdoutDesc) Object.defineProperty(process.stdout, "isTTY", stdoutDesc);
124
+ else delete (process.stdout as { isTTY?: boolean }).isTTY;
125
+ }
126
+ });
127
+ });
128
+
129
+ describe("bailIfCancel", () => {
130
+ afterEach(() => {
131
+ clackIsCancel.mockReset();
132
+ clackCancel.mockReset();
133
+ });
134
+
135
+ it("returns value when not cancel", () => {
136
+ clackIsCancel.mockReturnValue(false);
137
+ expect(bailIfCancel("ok")).toBe("ok");
138
+ expect(clackCancel).not.toHaveBeenCalled();
139
+ });
140
+
141
+ it("cancel Aborted and exit 0", () => {
142
+ clackIsCancel.mockReturnValue(true);
143
+ const exit = vi.spyOn(process, "exit").mockImplementation((() => {
144
+ throw new Error("EXIT");
145
+ }) as typeof process.exit);
146
+ expect(() => bailIfCancel(Symbol("cancel"))).toThrow("EXIT");
147
+ expect(clackCancel).toHaveBeenCalledWith("Aborted");
148
+ expect(exit).toHaveBeenCalledWith(0);
149
+ exit.mockRestore();
150
+ });
151
+ });
152
+
153
+ describe("clack wrappers", () => {
154
+ afterEach(() => {
155
+ clackSelect.mockReset();
156
+ clackConfirm.mockReset();
157
+ clackSpinner.mockReset();
158
+ clackIsCancel.mockReset();
159
+ clackCancel.mockReset();
160
+ });
161
+
162
+ it("select bails then returns", async () => {
163
+ clackSelect.mockResolvedValue("init");
164
+ clackIsCancel.mockReturnValue(false);
165
+ await expect(select({ message: "Command" } as Parameters<typeof select>[0])).resolves.toBe("init");
166
+ expect(clackSelect).toHaveBeenCalledWith({ message: "Command" });
167
+ });
168
+
169
+ it("confirm bails then returns", async () => {
170
+ clackConfirm.mockResolvedValue(true);
171
+ clackIsCancel.mockReturnValue(false);
172
+ await expect(confirm({ message: "Re-init?" })).resolves.toBe(true);
173
+ expect(clackConfirm).toHaveBeenCalledWith({ message: "Re-init?" });
174
+ });
175
+
176
+ it("select cancel exits 0", async () => {
177
+ clackSelect.mockResolvedValue("x");
178
+ clackIsCancel.mockReturnValue(true);
179
+ const exit = vi.spyOn(process, "exit").mockImplementation((() => {
180
+ throw new Error("EXIT");
181
+ }) as typeof process.exit);
182
+ await expect(select({} as Parameters<typeof select>[0])).rejects.toThrow("EXIT");
183
+ expect(clackCancel).toHaveBeenCalledWith("Aborted");
184
+ expect(exit).toHaveBeenCalledWith(0);
185
+ exit.mockRestore();
186
+ });
187
+
188
+ it("spinner passthrough", () => {
189
+ const s = { start: vi.fn(), stop: vi.fn() };
190
+ clackSpinner.mockReturnValue(s);
191
+ expect(spinner()).toBe(s);
192
+ expect(clackSpinner).toHaveBeenCalledOnce();
193
+ });
194
+
195
+ it("text and password bail then return", async () => {
196
+ clackText.mockResolvedValue("proj");
197
+ clackPassword.mockResolvedValue("sk_x");
198
+ clackIsCancel.mockReturnValue(false);
199
+ await expect(text({ message: "Project ID" })).resolves.toBe("proj");
200
+ await expect(password({ message: "API key" })).resolves.toBe("sk_x");
201
+ });
202
+ });
package/cli/prompt.ts ADDED
@@ -0,0 +1 @@
1
+ export { bailIfCancel, confirm, isInteractive, password, select, spinner, text } from "./scripts/prompt.ts";
@@ -0,0 +1,78 @@
1
+ import fs from "node:fs";
2
+ import nodePath from "node:path";
3
+ import { afterEach, expect, it, vi } from "vitest";
4
+ import { git, removePlanted } from "./fixtures/planted-repo.ts";
5
+ import { PASS, plant, problemOf, stage } from "./fixtures/red-proof-repo.ts";
6
+ import { parseNameStatus, redProof, type VitestRun } from "./red-proof.ts";
7
+ import { type GitRun, runGit } from "./sync/standards.ts";
8
+
9
+ /** The real runner's timeout and missing vitest, the outer repo's git env, git failing, and the diff parser. */
10
+ afterEach(removePlanted);
11
+ const change = (...files: [string, string][]) => files.map(([status, file]) => ({ status, path: file }));
12
+ const A_TEST = change(["A", "a.test.ts"]);
13
+ const SLOW = { timeout: 60_000 };
14
+ const never: VitestRun = () => {
15
+ throw new Error("vitest must not run");
16
+ };
17
+ const ENV_TEST = `import { expect, it } from "vitest";
18
+ it("e", () => expect(Object.keys(process.env).filter((k) => k.startsWith("GIT_") && !k.startsWith("GIT_CONFIG_"))).toEqual([]));
19
+ `;
20
+
21
+ it("refuses with the timeout reason when vitest outlives the timeout", SLOW, () => {
22
+ const root = plant({ "s.ts": "head\n" });
23
+ stage(root, { "a.test.ts": PASS });
24
+ expect(problemOf(redProof(root, A_TEST, { timeoutMs: 1 }))).toMatch(
25
+ /^red-proof: vitest wrote no report in \. \(exit 1\): vitest did not finish within 1ms/,
26
+ );
27
+ });
28
+
29
+ it("refuses with the reason when the package has no vitest", SLOW, () => {
30
+ const root = plant({ "s.ts": "head\n" });
31
+ fs.rmSync(nodePath.join(root, "node_modules"));
32
+ stage(root, { "a.test.ts": PASS });
33
+ expect(problemOf(redProof(root, A_TEST))).toMatch(
34
+ /^red-proof: vitest wrote no report in \. \(exit 1\): cannot resolve vitest/,
35
+ );
36
+ });
37
+
38
+ it("strips GIT_* from the nested run and leaves the committed repo's index alone", SLOW, () => {
39
+ const root = plant({ "s.ts": "head\n" });
40
+ stage(root, { "e.test.ts": ENV_TEST, "s.ts": "new\n" });
41
+ const seen = () => ({ index: git(root, "ls-files", "-s"), status: git(root, "status", "--porcelain") });
42
+ const before = seen();
43
+ vi.stubEnv("GIT_DIR", nodePath.join(root, ".git"));
44
+ vi.stubEnv("GIT_INDEX_FILE", nodePath.join(root, ".git", "index"));
45
+ const result = redProof(root, change(["A", "e.test.ts"], ["M", "s.ts"]));
46
+ vi.unstubAllEnvs();
47
+ expect(problemOf(result)).toContain("(e.test.ts passed)");
48
+ expect(seen()).toEqual(before);
49
+ });
50
+
51
+ const failing =
52
+ (word: string, status: number): GitRun =>
53
+ (root, args, input) =>
54
+ args.includes(word) ? { status, stdout: "", stderr: "fatal: nope" } : runGit(root, args, input);
55
+
56
+ it("throws naming the command when write-tree fails, leaving no dir", () => {
57
+ const root = plant({ "s.ts": "head\n" });
58
+ stage(root, { "a.test.ts": PASS });
59
+ const faulty = failing("write-tree", 128);
60
+ expect(() => redProof(root, A_TEST, { git: faulty, vitest: never })).toThrow(
61
+ "red-proof: git write-tree failed: fatal: nope",
62
+ );
63
+ expect(fs.existsSync(nodePath.join(root, ".tmp", "repo", `red-proof-${process.pid}`))).toBe(false);
64
+ });
65
+
66
+ it("throws when the archive cannot be unpacked", () => {
67
+ const root = plant({ "s.ts": "head\n" });
68
+ stage(root, { "a.test.ts": PASS });
69
+ const faulty = failing("archive", 0);
70
+ expect(() => redProof(root, A_TEST, { git: faulty, vitest: never })).toThrow(/^red-proof: tar failed: /);
71
+ });
72
+
73
+ it("reads the NUL-separated name-status entries", () => {
74
+ expect(parseNameStatus("M\0a.ts\0A\0dir/b.test.ts\0D\0c.ts\0")).toEqual(
75
+ change(["M", "a.ts"], ["A", "dir/b.test.ts"], ["D", "c.ts"]),
76
+ );
77
+ expect(parseNameStatus("")).toEqual([]);
78
+ });
@@ -0,0 +1,82 @@
1
+ import fs from "node:fs";
2
+ import { afterEach, expect, it } from "vitest";
3
+ import { removePlanted } from "./fixtures/planted-repo.ts";
4
+ import { PASS, fakeVitest, outputOf, plant, stage } from "./fixtures/red-proof-repo.ts";
5
+ import { redProof, type VitestRun } from "./red-proof.ts";
6
+
7
+ /** Refusals with no test to run, and vitest runs that leave no report, through the injected runner. */
8
+ afterEach(removePlanted);
9
+ const change = (...files: [string, string][]) => files.map(([status, file]) => ({ status, path: file }));
10
+ const never: VitestRun = () => {
11
+ throw new Error("vitest must not run");
12
+ };
13
+ const NO_FILE = {
14
+ ok: false,
15
+ problem: "red-proof: no changed vitest test file outside e2e/ to run on HEAD; add one that fails without the change",
16
+ };
17
+ const A_TEST = change(["A", "a.test.ts"]);
18
+
19
+ it("refuses a change with only e2e tests and source, without running vitest", () => {
20
+ const root = plant({ "s.ts": "head\n" });
21
+ stage(root, { "e2e/a.test.ts": PASS, "s.ts": "new\n" });
22
+ const files = change(["A", "e2e/a.test.ts"], ["M", "s.ts"]);
23
+ expect(redProof(root, files, { vitest: never })).toEqual(NO_FILE);
24
+ });
25
+
26
+ it("refuses a change with no test, a deleted test or nothing, without running vitest", () => {
27
+ const root = plant({ "s.ts": "head\n", "d.test.ts": PASS });
28
+ expect(redProof(root, change(["M", "s.ts"]), { vitest: never })).toEqual(NO_FILE);
29
+ expect(redProof(root, change(["D", "d.test.ts"]), { vitest: never })).toEqual(NO_FILE);
30
+ expect(redProof(root, [], { vitest: never })).toEqual(NO_FILE);
31
+ });
32
+
33
+ it("refuses when vitest crashed, quoting the last stderr lines", () => {
34
+ const root = plant({ "s.ts": "head\n" });
35
+ stage(root, { "a.test.ts": PASS });
36
+ const crash: VitestRun = () => ({ code: 1, stderr: "one\ntwo\nthree\nfour\nfive\n" });
37
+ expect(redProof(root, A_TEST, { vitest: crash })).toEqual({
38
+ ok: false,
39
+ problem: "red-proof: vitest wrote no report in . (exit 1): three | four | five",
40
+ });
41
+ });
42
+
43
+ it("names the package dir when its run timed out", () => {
44
+ const root = plant({ "cli/package.json": { name: "c" } });
45
+ stage(root, { "cli/a.test.ts": PASS });
46
+ const timeout: VitestRun = () => ({ code: 1, stderr: "vitest timed out after 5ms" });
47
+ expect(redProof(root, change(["A", "cli/a.test.ts"]), { vitest: timeout })).toEqual({
48
+ ok: false,
49
+ problem: "red-proof: vitest wrote no report in cli (exit 1): vitest timed out after 5ms",
50
+ });
51
+ });
52
+
53
+ it.each(["{nope", "{}", '{"testResults":[{"name":1}]}'])("refuses a report that reads %s", (text) => {
54
+ const root = plant({ "s.ts": "head\n" });
55
+ stage(root, { "a.test.ts": PASS });
56
+ const writes: VitestRun = (args) => {
57
+ fs.writeFileSync(outputOf(args), text);
58
+ return { code: 3, stderr: "bad\n" };
59
+ };
60
+ expect(redProof(root, A_TEST, { vitest: writes })).toEqual({
61
+ ok: false,
62
+ problem: "red-proof: vitest wrote no report in . (exit 3): bad",
63
+ });
64
+ });
65
+
66
+ it("hands the runner the nested flags, a json report, no coverage and the default timeout", () => {
67
+ const root = plant({ "s.ts": "head\n" });
68
+ stage(root, { "a.test.ts": PASS });
69
+ const seen: { args: string[]; timeout: number }[] = [];
70
+ const run: VitestRun = (args, cwd, timeout) => {
71
+ seen.push({ args, timeout });
72
+ return fakeVitest(() => true)(args, cwd, timeout);
73
+ };
74
+ redProof(root, A_TEST, { vitest: run });
75
+ const joined = seen.map((call) => call.args.join(" ")).join("");
76
+ expect(seen.map((call) => call.args.slice(0, 4))).toEqual([
77
+ ["run", "a.test.ts", "--fsModuleCache=false", "--reporter=json"],
78
+ ]);
79
+ expect(joined).toMatch(/--outputFile=\S*red-proof-\d+\S*\.json$/);
80
+ expect(joined).not.toContain("overage");
81
+ expect(seen.map((call) => call.timeout)).toEqual([300_000]);
82
+ });
@@ -0,0 +1,157 @@
1
+ import fs from "node:fs";
2
+ import nodePath from "node:path";
3
+ import { afterEach, expect, it } from "vitest";
4
+ import { git, removePlanted } from "./fixtures/planted-repo.ts";
5
+ import { PASS, fakeVitest, plant, plantUnborn, stage, staged } from "./fixtures/red-proof-repo.ts";
6
+ import { redProof } from "./red-proof.ts";
7
+
8
+ /** What the HEAD tree holds when vitest runs in it, seen through a stand-in runner on planted repos. */
9
+ afterEach(removePlanted);
10
+ const change = (...files: [string, string][]) => files.map(([status, file]) => ({ status, path: file }));
11
+ const read = (cwd: string, file: string) =>
12
+ fs.existsSync(nodePath.join(cwd, file)) ? fs.readFileSync(nodePath.join(cwd, file), "utf8") : null;
13
+
14
+ const BASE = { "s.ts": "head\n", "gone.ts": "gone\n", "fixtures/f.ts": "old\n", "__snapshots__/a.snap": "old\n" };
15
+ const STAGED = {
16
+ "s.ts": "staged\n",
17
+ "new.ts": "new\n",
18
+ "fixtures/f.ts": "newer\n",
19
+ "fixtures/helper.ts": "h\n",
20
+ "__snapshots__/a.snap": "newer\n",
21
+ "app.config.ts": "cfg\n",
22
+ "a.test.ts": PASS,
23
+ "notes.md": "n\n",
24
+ };
25
+ const MIXED = change(
26
+ ["M", "s.ts"],
27
+ ["A", "new.ts"],
28
+ ["D", "gone.ts"],
29
+ ["M", "fixtures/f.ts"],
30
+ ["A", "fixtures/helper.ts"],
31
+ ["M", "__snapshots__/a.snap"],
32
+ ["A", "app.config.ts"],
33
+ ["A", "a.test.ts"],
34
+ ["A", "notes.md"],
35
+ );
36
+ const WATCHED = ["s.ts", "new.ts", "gone.ts", "fixtures/f.ts", "fixtures/helper.ts", "__snapshots__/a.snap"];
37
+
38
+ it("puts a changed source file back and removes a new one on the HEAD tree", () => {
39
+ const root = plant(BASE);
40
+ stage(root, STAGED);
41
+ git(root, "rm", "-q", "gone.ts");
42
+ const seen: Record<string, string | null> = {};
43
+ const run = fakeVitest(
44
+ () => true,
45
+ (cwd) => {
46
+ for (const file of [...WATCHED, "app.config.ts", "notes.md"]) seen[file] = read(cwd, file);
47
+ },
48
+ );
49
+ expect(redProof(root, MIXED, { vitest: run })).toMatchObject({ ok: true });
50
+ expect(seen).toMatchObject({ "s.ts": "head\n", "new.ts": null, "gone.ts": "gone\n" });
51
+ });
52
+
53
+ it("keeps fixtures, snapshots, configs and other files as staged", () => {
54
+ const root = plant(BASE);
55
+ stage(root, STAGED);
56
+ const seen: Record<string, string | null> = {};
57
+ const run = fakeVitest(
58
+ () => true,
59
+ (cwd) => {
60
+ for (const file of [...WATCHED, "app.config.ts", "notes.md"]) seen[file] = read(cwd, file);
61
+ },
62
+ );
63
+ redProof(root, MIXED, { vitest: run });
64
+ expect(seen).toMatchObject({
65
+ "fixtures/f.ts": "newer\n",
66
+ "fixtures/helper.ts": "h\n",
67
+ "__snapshots__/a.snap": "newer\n",
68
+ "app.config.ts": "cfg\n",
69
+ "notes.md": "n\n",
70
+ });
71
+ expect(git(root, "status", "--porcelain")).toContain("M s.ts");
72
+ });
73
+
74
+ it("puts a renamed file's old path back", () => {
75
+ const root = plant({ "old.ts": "old\n" });
76
+ git(root, "mv", "old.ts", "new.ts");
77
+ stage(root, { "a.test.ts": PASS });
78
+ const seen: (string | null)[] = [];
79
+ const run = fakeVitest(
80
+ () => true,
81
+ (cwd) => seen.push(read(cwd, "old.ts"), read(cwd, "new.ts")),
82
+ );
83
+ const files = change(["D", "old.ts"], ["A", "new.ts"], ["A", "a.test.ts"]);
84
+ expect(redProof(root, files, { vitest: run }).ok).toBe(true);
85
+ expect(seen).toEqual(["old\n", null]);
86
+ });
87
+
88
+ it("runs two packages each in their own HEAD-tree dir with their modules linked", () => {
89
+ const root = plant({ "pkg-a/package.json": { name: "a" }, "pkg-b/package.json": { name: "b" } });
90
+ fs.mkdirSync(nodePath.join(root, "pkg-a", "node_modules"));
91
+ stage(root, { "pkg-a/a.test.ts": PASS, "pkg-b/b.test.ts": PASS, "pkg-b/c.test.ts": PASS });
92
+ const calls: unknown[] = [];
93
+ const run = fakeVitest(
94
+ (file) => file === "a.test.ts",
95
+ (cwd, args) =>
96
+ calls.push({
97
+ cwd: nodePath.relative(root, cwd),
98
+ files: args.filter((arg) => arg.endsWith(".ts")),
99
+ linked: fs.existsSync(nodePath.join(cwd, "node_modules")),
100
+ rootLinked: fs.existsSync(nodePath.join(cwd, "..", "node_modules")),
101
+ }),
102
+ );
103
+ const files = change(["A", "pkg-a/a.test.ts"], ["A", "pkg-b/b.test.ts"], ["A", "pkg-b/c.test.ts"]);
104
+ expect(redProof(root, files, { vitest: run })).toEqual({
105
+ ok: true,
106
+ line: "red-proof: 1 of 3 changed test files failed on HEAD",
107
+ });
108
+ const name = nodePath.join(".tmp", "repo", `red-proof-${process.pid}`);
109
+ expect(calls).toEqual([
110
+ { cwd: nodePath.join(name, "pkg-a"), files: ["a.test.ts"], linked: true, rootLinked: true },
111
+ { cwd: nodePath.join(name, "pkg-b"), files: ["b.test.ts", "c.test.ts"], linked: false, rootLinked: true },
112
+ ]);
113
+ });
114
+
115
+ it("removes its dir after a pass and after a throw", () => {
116
+ const root = plant({ "s.ts": "head\n" });
117
+ stage(root, { "a.test.ts": PASS });
118
+ const tree = nodePath.join(root, ".tmp", "repo", `red-proof-${process.pid}`);
119
+ const seen: boolean[] = [];
120
+ const run = fakeVitest(
121
+ () => true,
122
+ () => seen.push(fs.existsSync(tree)),
123
+ );
124
+ redProof(root, change(["A", "a.test.ts"]), { vitest: run });
125
+ expect(seen).toEqual([true]);
126
+ expect(fs.existsSync(tree)).toBe(false);
127
+ const boom = () => {
128
+ throw new Error("boom");
129
+ };
130
+ expect(() => redProof(root, change(["A", "a.test.ts"]), { vitest: boom })).toThrow("boom");
131
+ expect(fs.existsSync(tree)).toBe(false);
132
+ });
133
+
134
+ it("skips with a line when there is no HEAD", () => {
135
+ const root = plantUnborn({ "a.test.ts": PASS });
136
+ expect(redProof(root, change(["A", "a.test.ts"]), { vitest: fakeVitest(() => true) })).toEqual({
137
+ ok: true,
138
+ line: "red-proof: skipped, no HEAD to run against",
139
+ });
140
+ expect(staged(root)).toBe("A\ta.test.ts\n");
141
+ });
142
+
143
+ it("owns a nested test by the nearest package dir above it", () => {
144
+ const root = plant({ "cli/package.json": { name: "c" } });
145
+ stage(root, { "cli/sub/deep/a.test.ts": PASS, "top/b.test.ts": PASS });
146
+ const calls: unknown[] = [];
147
+ const run = fakeVitest(
148
+ () => false,
149
+ (cwd, args) => calls.push([nodePath.relative(root, cwd), args[1]]),
150
+ );
151
+ redProof(root, change(["A", "cli/sub/deep/a.test.ts"], ["A", "top/b.test.ts"]), { vitest: run });
152
+ const name = nodePath.join(".tmp", "repo", `red-proof-${process.pid}`);
153
+ expect(calls).toEqual([
154
+ [nodePath.join(name, "cli"), "sub/deep/a.test.ts"],
155
+ [name, "top/b.test.ts"],
156
+ ]);
157
+ });
@@ -0,0 +1,44 @@
1
+ import { afterEach, expect, it } from "vitest";
2
+ import { git, removePlanted } from "./fixtures/planted-repo.ts";
3
+ import { FAIL, NEEDS_X, PASS, plant, stage } from "./fixtures/red-proof-repo.ts";
4
+ import { redProof } from "./red-proof.ts";
5
+
6
+ /** The verdict on a real vitest run against HEAD's source in a planted repo. */
7
+ const SLOW = { timeout: 60_000 };
8
+ afterEach(removePlanted);
9
+ const change = (...files: [string, string][]) => files.map(([status, file]) => ({ status, path: file }));
10
+ const readsV = (n: number) =>
11
+ `import { expect, it } from "vitest";\nimport { v } from "./v.ts";\nit("v", () => expect(v).toBe(${n}));\n`;
12
+ const RED = (failed: number, total: number) => ({
13
+ ok: true,
14
+ line: `red-proof: ${failed} of ${total} changed test files failed on HEAD`,
15
+ });
16
+
17
+ it("proves red when a changed test fails on HEAD's source", SLOW, () => {
18
+ const root = plant({ "v.ts": "export const v = 1;\n", "v.test.ts": readsV(1) });
19
+ stage(root, { "v.ts": "export const v = 2;\n", "v.test.ts": readsV(2) });
20
+ expect(redProof(root, change(["M", "v.ts"], ["M", "v.test.ts"]))).toEqual(RED(1, 1));
21
+ expect(git(root, "status", "--porcelain")).toBe("M v.test.ts\nM v.ts\n");
22
+ });
23
+
24
+ it("refuses a changed test that also passes on HEAD, naming it", SLOW, () => {
25
+ const root = plant({ "v.ts": "export const v = 1;\n" });
26
+ stage(root, { "v.ts": "export const v = 2;\n", "a.test.ts": PASS });
27
+ expect(redProof(root, change(["M", "v.ts"], ["A", "a.test.ts"]))).toEqual({
28
+ ok: false,
29
+ problem:
30
+ "red-proof: no changed test file fails on HEAD's source (a.test.ts passed); a feat or fix needs one that fails without the change",
31
+ });
32
+ });
33
+
34
+ it("fails a test of a new source file, which is absent on HEAD", SLOW, () => {
35
+ const root = plant({ "keep.txt": "k\n" });
36
+ stage(root, { "x.ts": "export const x = () => 1;\n", "x.test.ts": NEEDS_X });
37
+ expect(redProof(root, change(["A", "x.ts"], ["A", "x.test.ts"]))).toEqual(RED(1, 1));
38
+ });
39
+
40
+ it("counts a failing test and a passing one apart", SLOW, () => {
41
+ const root = plant({ "keep.txt": "k\n" });
42
+ stage(root, { "a.test.ts": FAIL, "b.test.ts": PASS });
43
+ expect(redProof(root, change(["A", "a.test.ts"], ["A", "b.test.ts"]))).toEqual(RED(1, 2));
44
+ });
@@ -0,0 +1,14 @@
1
+ /** The names `repo red-proof` reads from git and vitest. */
2
+
3
+ /** The `git diff --name-status` letter of a path the change adds. */
4
+ export const STATUS_ADDED = "A";
5
+ /** The `git diff --name-status` letter of a path the change deletes. */
6
+ export const STATUS_DELETED = "D";
7
+ /** The status vitest's json report gives a test file with a failing test. */
8
+ export const REPORT_FAILED = "failed";
9
+ /** The bytes in a kibibyte. */
10
+ const KIB = 1024;
11
+ /** The most output a vitest run may print before it is cut off: 64 MiB. */
12
+ export const MAX_BUFFER: number = 64 * KIB * KIB;
13
+ /** How many trailing stderr lines a missing-report problem quotes. */
14
+ export const STDERR_TAIL_LINES = -3;