@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,118 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { nextVersion, type Mode } from "./version.ts";
3
+
4
+ const commit = (subject: string, body = "") => ({ subject, body });
5
+ const AUTO: Mode = { kind: "auto" };
6
+ type Options = { mode?: Mode; first?: boolean };
7
+ const next = (current: string, subjects: string[], { mode = AUTO, first = false }: Options = {}) =>
8
+ nextVersion({ current, commits: subjects.map((subject) => commit(subject)), mode, first });
9
+
10
+ describe("below 1.0", () => {
11
+ it("makes a breaking commit a minor and everything else a patch", () => {
12
+ expect(next("0.1.0", ["feat!: drop x"])).toEqual({ ok: true, version: "0.2.0" });
13
+ expect(next("0.1.3", ["fix(cli)!: rename y"])).toEqual({ ok: true, version: "0.2.0" });
14
+ expect(next("0.1.0", ["feat: add x"])).toEqual({ ok: true, version: "0.1.1" });
15
+ expect(next("0.1.0", ["fix: x"])).toEqual({ ok: true, version: "0.1.1" });
16
+ expect(next("0.1.0", ["perf: x"])).toEqual({ ok: true, version: "0.1.1" });
17
+ expect(next("0.1.0", ["revert: x"])).toEqual({ ok: true, version: "0.1.1" });
18
+ });
19
+
20
+ it("reads a BREAKING CHANGE footer as breaking", () => {
21
+ const got = nextVersion({
22
+ current: "0.4.2",
23
+ commits: [commit("feat: x", "why\n\nBREAKING CHANGE: the flag is gone")],
24
+ mode: AUTO,
25
+ first: false,
26
+ });
27
+ expect(got).toEqual({ ok: true, version: "0.5.0" });
28
+ });
29
+ });
30
+
31
+ describe("from 1.0", () => {
32
+ it("bumps major for breaking, minor for feat and patch for fix, perf and revert", () => {
33
+ expect(next("1.2.3", ["feat!: x"])).toEqual({ ok: true, version: "2.0.0" });
34
+ expect(next("1.2.3", ["feat: x"])).toEqual({ ok: true, version: "1.3.0" });
35
+ expect(next("1.2.3", ["fix: x"])).toEqual({ ok: true, version: "1.2.4" });
36
+ expect(next("1.2.3", ["perf(a): x"])).toEqual({ ok: true, version: "1.2.4" });
37
+ expect(next("1.2.3", ["revert: x"])).toEqual({ ok: true, version: "1.2.4" });
38
+ });
39
+
40
+ it("reads the footer and lets the highest commit win", () => {
41
+ const footer = commit("fix: x", "BREAKING-CHANGE: gone");
42
+ expect(nextVersion({ current: "1.2.3", commits: [footer], mode: AUTO, first: false })).toEqual({
43
+ ok: true,
44
+ version: "2.0.0",
45
+ });
46
+ expect(next("1.2.3", ["fix: a", "feat: b", "docs: c"])).toEqual({ ok: true, version: "1.3.0" });
47
+ });
48
+
49
+ it("ignores a body line that only mentions the footer", () => {
50
+ const mention = commit("fix: x", "see BREAKING CHANGE: in the docs");
51
+ expect(nextVersion({ current: "1.2.3", commits: [mention], mode: AUTO, first: false })).toEqual({
52
+ ok: true,
53
+ version: "1.2.4",
54
+ });
55
+ });
56
+
57
+ it("finds nothing to release in chore, docs, refactor, test and unparsable subjects", () => {
58
+ const none = { ok: false, reason: "nothing" };
59
+ expect(next("1.2.3", ["chore: x", "docs: y", "refactor: z", "test: w", "not conventional"])).toEqual(none);
60
+ expect(next("1.2.3", [])).toEqual(none);
61
+ });
62
+ });
63
+
64
+ describe("first release", () => {
65
+ it("releases 0.1.0 from 0.0.0 with no tag, whatever the commits", () => {
66
+ expect(next("0.0.0", ["chore: x"], { first: true })).toEqual({ ok: true, version: "0.1.0" });
67
+ expect(next("0.0.0", [], { mode: { kind: "pre", id: "rc" }, first: true })).toEqual({
68
+ ok: true,
69
+ version: "0.1.0-rc.0",
70
+ });
71
+ expect(next("0.0.0", [], { mode: { kind: "stable" }, first: true })).toEqual({ ok: true, version: "1.0.0" });
72
+ });
73
+ });
74
+
75
+ describe("--stable", () => {
76
+ it("moves a 0.x version to 1.0.0 even with nothing else to release", () => {
77
+ expect(next("0.4.2", ["fix: x"], { mode: { kind: "stable" } })).toEqual({ ok: true, version: "1.0.0" });
78
+ expect(next("0.4.2", [], { mode: { kind: "stable" } })).toEqual({ ok: true, version: "1.0.0" });
79
+ expect(next("0.9.0-rc.1", [], { mode: { kind: "stable" } })).toEqual({ ok: true, version: "1.0.0" });
80
+ });
81
+
82
+ it("refuses a 1.x version", () => {
83
+ expect(next("1.2.0", ["feat: x"], { mode: { kind: "stable" } })).toEqual({ ok: false, reason: "stable" });
84
+ });
85
+ });
86
+
87
+ describe("--pre", () => {
88
+ const rc: Mode = { kind: "pre", id: "rc" };
89
+
90
+ it("starts a prerelease on the computed version", () => {
91
+ expect(next("1.0.0", ["feat: x"], { mode: rc })).toEqual({ ok: true, version: "1.1.0-rc.0" });
92
+ });
93
+
94
+ it("counts up on the same base and id, and starts again on a new id", () => {
95
+ expect(next("1.1.0-rc.0", ["fix: x"], { mode: rc })).toEqual({ ok: true, version: "1.1.0-rc.1" });
96
+ expect(next("1.1.0-rc.1", ["fix: x"], { mode: { kind: "pre", id: "beta" } })).toEqual({
97
+ ok: true,
98
+ version: "1.1.0-beta.0",
99
+ });
100
+ });
101
+
102
+ it("finds nothing to release without a releasable commit", () => {
103
+ expect(next("1.1.0-rc.0", ["chore: x"], { mode: rc })).toEqual({ ok: false, reason: "nothing" });
104
+ });
105
+ });
106
+
107
+ describe("graduation", () => {
108
+ it("releases the base of a prerelease with a plain release, whatever the commits", () => {
109
+ expect(next("1.1.0-rc.1", [])).toEqual({ ok: true, version: "1.1.0" });
110
+ expect(next("0.2.0-rc.0", ["chore: x"])).toEqual({ ok: true, version: "0.2.0" });
111
+ });
112
+ });
113
+
114
+ describe("a version that is not semver", () => {
115
+ it("is nothing to bump from", () => {
116
+ expect(next("latest", ["feat: x"])).toEqual({ ok: false, reason: "version" });
117
+ });
118
+ });
@@ -0,0 +1,69 @@
1
+ import { HEADER, isBreaking } from "../conventional.ts";
2
+ import { FIRST_VERSION, MINOR_TYPES, PATCH_TYPES, SEMVER, STABLE_MAJOR, STABLE_VERSION } from "./release.constants.ts";
3
+
4
+ /** One commit since the last release: its subject line and the rest of its message. */
5
+ export type Commit = { subject: string; body: string };
6
+ /** What the person asked for: the next version by the commits, the 1.0.0 move, or a prerelease with an id. */
7
+ export type Mode = { kind: "auto" } | { kind: "stable" } | { kind: "pre"; id: string };
8
+ export type BumpInput = { current: string; commits: readonly Commit[]; mode: Mode; first: boolean };
9
+ /** The next version, or why there is none: nothing releasable, a 1.x `--stable`, a version that is not semver. */
10
+ export type Next = { ok: true; version: string } | { ok: false; reason: "nothing" | "stable" | "version" };
11
+
12
+ type Level = "major" | "minor" | "patch";
13
+ export type Parts = { major: number; minor: number; patch: number; id: string | undefined; n: number };
14
+
15
+ /** The numbers of a semver version and its prerelease id and number; null for any other text. */
16
+ export function parseSemver(version: string): Parts | null {
17
+ const match = SEMVER.exec(version);
18
+ if (match === null) return null;
19
+ const [, major, minor, patch, id, n] = match;
20
+ return { major: Number(major), minor: Number(minor), patch: Number(patch), id, n: Number(n ?? 0) };
21
+ }
22
+
23
+ /** The highest bump the commits ask for, as 1.0 and later read them; null when none is releasable. */
24
+ function levelOf(commits: readonly Commit[]): Level | null {
25
+ let level: Level | null = null;
26
+ for (const { subject, body } of commits) {
27
+ const type = HEADER.exec(subject)?.groups?.["type"] ?? "";
28
+ if (isBreaking(subject, body.split("\n"))) return "major";
29
+ if (MINOR_TYPES.includes(type)) level = "minor";
30
+ else if (PATCH_TYPES.includes(type) && level === null) level = "patch";
31
+ }
32
+ return level;
33
+ }
34
+
35
+ function bump({ major, minor, patch }: Parts, level: Level): string {
36
+ // Below 1.0 a minor carries the breaking changes, so a breaking commit is a minor and the rest are patches.
37
+ const stable = major >= STABLE_MAJOR;
38
+ if (level === "major" && stable) return `${major + 1}.0.0`;
39
+ if (level === "major" || (level === "minor" && stable)) return `${major}.${minor + 1}.0`;
40
+ return `${major}.${minor}.${patch + 1}`;
41
+ }
42
+
43
+ const base = ({ major, minor, patch }: Parts): string => `${major}.${minor}.${patch}`;
44
+
45
+ const ok = (version: string): Next => ({ ok: true, version });
46
+ const fail = (reason: "nothing" | "stable" | "version"): Next => ({ ok: false, reason });
47
+
48
+ /** The release version before any prerelease suffix: `from`'s own base when `keepBase`, else the bump `level` asks for. */
49
+ function targetOf(from: Parts, level: Level | null, keepBase: boolean): string | null {
50
+ if (level === null) return null;
51
+ return keepBase ? base(from) : bump(from, level);
52
+ }
53
+
54
+ /** The version `--stable` or a graduation sets whatever the commits say; null when neither applies. */
55
+ function fixedVersion(from: Parts, mode: Mode): Next | null {
56
+ if (mode.kind === "stable") return from.major === 0 ? ok(STABLE_VERSION) : fail("stable");
57
+ return mode.kind === "auto" && from.id !== undefined ? ok(base(from)) : null;
58
+ }
59
+
60
+ /** The version after `current`, per the commits since its release and the mode. */
61
+ export function nextVersion({ current, commits, mode, first }: BumpInput): Next {
62
+ const from = parseSemver(current);
63
+ if (from === null) return fail("version");
64
+ const fixed = fixedVersion(from, mode);
65
+ if (fixed !== null) return fixed;
66
+ const target = first ? FIRST_VERSION : targetOf(from, levelOf(commits), mode.kind === "pre" && from.id !== undefined);
67
+ if (target === null) return fail("nothing");
68
+ return mode.kind === "pre" ? ok(`${target}-${mode.id}.${from.id === mode.id ? from.n + 1 : 0}`) : ok(target);
69
+ }
@@ -0,0 +1,101 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { releaseWait } from "./wait.ts";
3
+
4
+ type Answer = () => Response | Error;
5
+ const doc = (versions: string[], tags: Record<string, string>): Answer => {
6
+ const body = { versions: Object.fromEntries(versions.map((v) => [v, {}])), "dist-tags": tags };
7
+ return () => new Response(JSON.stringify(body));
8
+ };
9
+ const MISSING: Answer = () => new Response("{}", { status: 404 });
10
+ const SPEC = "@workerstack/repo@1.1.0-rc.0";
11
+
12
+ /** Runs `release wait`; `answers` are the registry's replies in order, the last repeating. */
13
+ async function wait(argv: string[], answers: Answer[]) {
14
+ const out: string[] = [];
15
+ const err: string[] = [];
16
+ const urls: string[] = [];
17
+ const sleeps: number[] = [];
18
+ let clock = 0;
19
+ const fetcher = (url: string): Promise<Response> => {
20
+ urls.push(url);
21
+ const got = (answers.length > 1 ? answers.shift() : answers[0])?.();
22
+ return got instanceof Response ? Promise.resolve(got) : Promise.reject(got);
23
+ };
24
+ const sleep = (ms: number): Promise<void> => {
25
+ clock += ms;
26
+ sleeps.push(ms);
27
+ return Promise.resolve();
28
+ };
29
+ const io = {
30
+ stdout: (s: string) => out.push(s),
31
+ stderr: (s: string) => err.push(s),
32
+ fetch: fetcher,
33
+ sleep,
34
+ now: () => clock,
35
+ };
36
+ const code = await releaseWait(argv, io);
37
+ return { code, out: out.join(""), err: err.join(""), urls, sleeps };
38
+ }
39
+
40
+ describe("repo release wait finds the version", () => {
41
+ it("prints the version and its dist-tag when the registry has it at once", async () => {
42
+ const got = await wait([SPEC], [doc(["1.0.0", "1.1.0-rc.0"], { latest: "1.0.0", next: "1.1.0-rc.0" })]);
43
+ expect(got).toEqual({
44
+ code: 0,
45
+ out: "published 1.1.0-rc.0 under next\n",
46
+ err: "",
47
+ urls: ["https://registry.npmjs.org/@workerstack%2frepo"],
48
+ sleeps: [],
49
+ });
50
+ });
51
+
52
+ it("prints just the version when no dist-tag points at it", async () => {
53
+ const got = await wait(["@workerstack/database@0.22.0"], [doc(["0.22.0", "0.23.0"], { latest: "0.23.0" })]);
54
+ expect(got).toMatchObject({ code: 0, out: "published 0.22.0\n" });
55
+ expect(got.urls).toEqual(["https://registry.npmjs.org/@workerstack%2fdatabase"]);
56
+ });
57
+
58
+ it("looks again every 10 seconds, through a miss, a failure and a bad body", async () => {
59
+ const bad: Answer = () => new Response("not json");
60
+ const offline: Answer = () => new Error("offline");
61
+ const got = await wait([SPEC], [MISSING, offline, bad, doc(["1.1.0-rc.0"], { next: "1.1.0-rc.0" })]);
62
+ expect(got).toMatchObject({ code: 0, out: "published 1.1.0-rc.0 under next\n" });
63
+ expect(got.sleeps).toEqual([10_000, 10_000, 10_000]);
64
+ });
65
+ });
66
+
67
+ describe("repo release wait through a registry that does not answer", () => {
68
+ it("keeps polling past a server error and an answer that is not a package", async () => {
69
+ const down: Answer = () => new Response("down", { status: 503 });
70
+ const odd: Answer = () => new Response('{"x":1}');
71
+ const got = await wait([SPEC], [down, odd, doc(["1.1.0-rc.0"], { next: "1.1.0-rc.0" })]);
72
+ expect(got).toMatchObject({ code: 0, out: "published 1.1.0-rc.0 under next\n", sleeps: [10_000, 10_000] });
73
+ });
74
+
75
+ it("gives up after 15 minutes of server errors", async () => {
76
+ const got = await wait([SPEC], [() => new Response("down", { status: 503 })]);
77
+ expect(got).toMatchObject({ code: 1, out: "" });
78
+ expect(got.sleeps).toHaveLength(90);
79
+ });
80
+ });
81
+
82
+ describe("repo release wait gives up", () => {
83
+ it("after 15 minutes without the version", async () => {
84
+ const got = await wait([SPEC], [doc(["1.0.0"], { latest: "1.0.0" })]);
85
+ expect(got.code).toBe(1);
86
+ expect(got.out).toBe("");
87
+ expect(got.err).toBe(
88
+ "repo release wait: @workerstack/repo@1.1.0-rc.0 is not on npm after 15 minutes; see gh run list --workflow release\n",
89
+ );
90
+ expect(got.sleeps).toHaveLength(90);
91
+ expect(got.urls).toHaveLength(91);
92
+ });
93
+
94
+ it.each([[[]], [["@workerstack/repo"]], [["repo@"]], [["a", "b"]]])(
95
+ "asks for <name>@<version> for the arguments %j",
96
+ async (argv) => {
97
+ const line = "repo release wait: name the tag as <name>@<version>, e.g. @workerstack/repo@1.0.0\n";
98
+ expect(await wait(argv, [MISSING])).toMatchObject({ code: 1, out: "", err: line, urls: [] });
99
+ },
100
+ );
101
+ });
@@ -0,0 +1,95 @@
1
+ import { z } from "zod";
2
+ import {
3
+ ENCODED_SLASH,
4
+ NOT_A_DOCUMENT,
5
+ NOT_FOUND,
6
+ REGISTRY,
7
+ SCOPE_SLASH,
8
+ WAIT_GIVE_UP_MINUTES,
9
+ WAIT_GIVE_UP_MS,
10
+ WAIT_POLL_MS,
11
+ } from "./release.constants.ts";
12
+ import { WAIT_PREFIX, WAIT_USAGE, notOnNpm, published } from "./release-messages.constants.ts";
13
+
14
+ /** Where `repo release wait` prints and what it reaches the registry, the clock and the sleep through. */
15
+ export type WaitIo = {
16
+ stdout: (text: string) => void;
17
+ stderr: (text: string) => void;
18
+ fetch: (url: string) => Promise<Response>;
19
+ now: () => number;
20
+ sleep: (ms: number) => Promise<void>;
21
+ };
22
+
23
+ /** The part of the registry's document for a package that a release reads. */
24
+ type RegistryDoc = { versions: Record<string, unknown>; "dist-tags": Record<string, string> };
25
+
26
+ const documentSchema = (): z.ZodType<RegistryDoc> =>
27
+ z.object({ versions: z.record(z.string(), z.unknown()), "dist-tags": z.record(z.string(), z.string()) });
28
+
29
+ /** A package name and the version of it a tag or `wait` names. */
30
+ export type Spec = { name: string; version: string };
31
+
32
+ /** `<name>@<version>` split at its last `@` (a scope's `@` leads the name); null when there is no version. */
33
+ export function splitSpec(spec: string): Spec | null {
34
+ const at = spec.lastIndexOf("@");
35
+ return at > 0 && at < spec.length - 1 ? { name: spec.slice(0, at), version: spec.slice(at + 1) } : null;
36
+ }
37
+
38
+ /** What the registry said about a package: its document, that it is not there (404), or why it did not answer. */
39
+ export type RegistryAnswer =
40
+ | { kind: "found"; doc: RegistryDoc }
41
+ | { kind: "missing" }
42
+ | { kind: "unreachable"; why: string };
43
+
44
+ /** Asks the registry for `name`; any answer but a document or a 404 is `unreachable` with its status or error. */
45
+ export async function registryDoc(fetcher: WaitIo["fetch"], name: string): Promise<RegistryAnswer> {
46
+ try {
47
+ const response = await fetcher(`${REGISTRY}${name.replace(SCOPE_SLASH, ENCODED_SLASH)}`);
48
+ if (response.status === NOT_FOUND) return { kind: "missing" };
49
+ if (!response.ok) return { kind: "unreachable", why: `HTTP ${response.status}` };
50
+ const parsed = documentSchema().safeParse(await response.json());
51
+ return parsed.success ? { kind: "found", doc: parsed.data } : { kind: "unreachable", why: NOT_A_DOCUMENT };
52
+ } catch (error) {
53
+ return {
54
+ kind: "unreachable",
55
+ why: (error /* a failed fetch or a body that is not JSON throws an Error */ as Error).message,
56
+ };
57
+ }
58
+ }
59
+
60
+ /** The dist-tag pointing at `version` when the answer lists it, `{}` when it lists it with no tag, null otherwise. */
61
+ export function listing(answer: RegistryAnswer, version: string): { tag?: string } | null {
62
+ if (answer.kind !== "found" || !(version in answer.doc.versions)) return null;
63
+ const tag = Object.entries(answer.doc["dist-tags"]).find(([, pointed]) => pointed === version)?.[0];
64
+ return tag === undefined ? {} : { tag };
65
+ }
66
+
67
+ /** Where `poll` reads the clock and sleeps. */
68
+ export type PollIo = Pick<WaitIo, "now" | "sleep">;
69
+
70
+ /** Looks with `probe` until it answers, every `WAIT_POLL_MS`, for up to `WAIT_GIVE_UP_MS` after `started`; null on giving up. */
71
+ export async function poll<T>(io: PollIo, probe: () => Promise<T | null>, started: number): Promise<T | null> {
72
+ const found = await probe();
73
+ if (found !== null) return found;
74
+ if (io.now() - started >= WAIT_GIVE_UP_MS) return null;
75
+ await io.sleep(WAIT_POLL_MS);
76
+ return poll(io, probe, started);
77
+ }
78
+
79
+ /** `repo release wait <name>@<version>`: polls npm every 10 seconds for up to 15 minutes. */
80
+ export async function releaseWait(argv: readonly string[], io: WaitIo): Promise<number> {
81
+ const spec = argv[0] ?? "";
82
+ const parts = argv.length === 1 ? splitSpec(spec) : null;
83
+ if (parts === null) {
84
+ io.stderr(`${WAIT_PREFIX}${WAIT_USAGE}\n`);
85
+ return 1;
86
+ }
87
+ const probe = async () => listing(await registryDoc(io.fetch, parts.name), parts.version);
88
+ const found = await poll(io, probe, io.now());
89
+ if (found === null) {
90
+ io.stderr(`${WAIT_PREFIX}${notOnNpm(spec, WAIT_GIVE_UP_MINUTES)}\n`);
91
+ return 1;
92
+ }
93
+ io.stdout(`${published(parts.version, found.tag)}\n`);
94
+ return 0;
95
+ }
@@ -0,0 +1,76 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { afterEach, expect, it } from "vitest";
3
+ import { cleanTemps, hit, NAMES_NOTHING, NUMBER, plant, SLOW } from "./magic/fixtures/magic-repo.ts";
4
+ import { checkRejectionsAt, checkScriptsAt, scriptDiagnostics } from "./script-diagnostics.ts";
5
+
6
+ afterEach(cleanTemps);
7
+
8
+ const GUARD = "repo(guard-rejects)";
9
+ const UNTESTED = (name: string) =>
10
+ `${name} is never asserted false; add expect(${name}(<an input it must reject>)).toBe(false) to a test`;
11
+
12
+ it("scriptDiagnostics joins the magic hits and the untested guards, in file and line order", { timeout: SLOW }, () => {
13
+ const source =
14
+ "export const isList = (value: unknown): value is unknown[] => Array.isArray(value);\nexport const n = (): number => 7;\n";
15
+ const root = plant({ "src/a.ts": source });
16
+ expect(scriptDiagnostics(root, ["src/a.ts"])).toEqual({
17
+ ok: true,
18
+ value: [hit("src/a.ts:1:14", GUARD, UNTESTED("isList")), hit("src/a.ts:2:32", NUMBER, NAMES_NOTHING("7"))],
19
+ });
20
+ });
21
+
22
+ it("scriptDiagnostics reports a tsconfig.json that only the guards read, over a test file", { timeout: SLOW }, () => {
23
+ const root = plant({ "pkg/tsconfig.json": "{", "pkg/a.test.ts": "export {};\n" });
24
+ const found = scriptDiagnostics(root, ["pkg/a.test.ts"]);
25
+ expect(found.ok ? [] : found.errors).toEqual([expect.stringMatching(/^pkg\/tsconfig\.json: .+/)]);
26
+ });
27
+
28
+ /** A git checkout with a config of `packs` and `skip`, and `files`. */
29
+ function checkout(files: Record<string, string>, config: object): string {
30
+ const root = plant({ ...files, "workerstack.config.ts": `export default ${JSON.stringify(config)};\n` });
31
+ execFileSync("git", ["init", "-q"], { cwd: root });
32
+ return root;
33
+ }
34
+
35
+ it("checkScriptsAt gives every hit of the checkout and those its skips leave", async () => {
36
+ const skip = { files: ["src/a.ts"], rules: ["repo/magic-number"], reason: "tuning lives here" };
37
+ const root = checkout(
38
+ { "src/a.ts": "export const f = () => 5000;\n", "src/b.ts": "export const g = () => 7;\n" },
39
+ { packs: ["standards"], lint: { skip: [skip] } },
40
+ );
41
+ const a = hit("src/a.ts:1:24", NUMBER, NAMES_NOTHING("5000"));
42
+ const b = hit("src/b.ts:1:24", NUMBER, NAMES_NOTHING("7"));
43
+ expect(await checkScriptsAt(root)).toEqual({ all: [a, b], kept: [b], problems: [] });
44
+ });
45
+
46
+ it("checkScriptsAt checks nothing without a config or the standards pack, and reports what stops it", async () => {
47
+ const none = { all: [], kept: [], problems: [] };
48
+ expect(await checkScriptsAt(plant({ "src/a.ts": "export const f = () => 5;\n" }))).toEqual(none);
49
+ const docs = checkout({ "src/a.ts": "export const f = () => 5;\n" }, { packs: ["docs"] });
50
+ expect(await checkScriptsAt(docs)).toEqual(none);
51
+ const broken = checkout(
52
+ { "pkg/tsconfig.json": "{", "pkg/a.ts": "export const f = () => 5;\n" },
53
+ { packs: ["standards"] },
54
+ );
55
+ expect((await checkScriptsAt(broken)).problems).toEqual([expect.stringMatching(/^pkg\/tsconfig\.json: .+/)]);
56
+ const plain = plant({ "workerstack.config.ts": 'export default { packs: ["standards"] };\n' });
57
+ expect((await checkScriptsAt(plain)).problems).toEqual([expect.stringMatching(/^git ls-files failed: /)]);
58
+ });
59
+
60
+ it(
61
+ "checkRejectionsAt checks every reject file of a standards checkout, and nothing without the pack",
62
+ { timeout: SLOW },
63
+ async () => {
64
+ const files = {
65
+ "src/a.reject.ts": "export const a: number = 'x';\n",
66
+ "src/b.ts": "export const b: number = 'x';\n",
67
+ };
68
+ expect(await checkRejectionsAt(checkout(files, { packs: ["standards"] }))).toEqual([
69
+ "src/a.reject.ts: line 1 fails unmarked: TS2322: Type 'string' is not assignable to type 'number'.",
70
+ ]);
71
+ expect(await checkRejectionsAt(checkout(files, { packs: ["docs"] }))).toEqual([]);
72
+ expect(await checkRejectionsAt(plant(files))).toEqual([]);
73
+ const plain = plant({ "workerstack.config.ts": 'export default { packs: ["standards"] };\n' });
74
+ expect(await checkRejectionsAt(plain)).toEqual([expect.stringMatching(/^git ls-files failed: /)]);
75
+ },
76
+ );
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The checks run as scripts on the TypeScript compiler API, joined for `repo check`, `repo turn` and `repo lint
3
+ * --baseline`: the magic literals and the untested guards as lint diagnostics the baseline counts, and the reject files
4
+ * check prints. Apart from `check-magic.ts` and `check-guards.ts`, which this imports, so neither imports the other.
5
+ */
6
+ import { sourceFiles } from "./check-comments.ts";
7
+ import { guardDiagnostics } from "./guard-diagnostics.ts";
8
+ import { magicDiagnostics, withoutSkipped } from "./check-magic.ts";
9
+ import { rejectionProblems } from "./check-rejections.ts";
10
+ import { findRoot, loadConfig, type LintSkip } from "./config.ts";
11
+ import type { Diagnostic, Result } from "./scripts/lint-baseline.ts";
12
+
13
+ /** `repo check`'s script hits: all of them for the skip report, those no skip turns off for the baseline. */
14
+ export type ScriptCheck = { all: Diagnostic[]; kept: Diagnostic[]; problems: string[] };
15
+ /** A standards checkout's root, `lint.skip` and source files; null without a config or the pack. */
16
+ type Checkout = { root: string; skips: readonly LintSkip[]; files: Result<string[]> } | null;
17
+
18
+ const NONE: ScriptCheck = { all: [], kept: [], problems: [] };
19
+
20
+ const byKey = (a: string, b: string) => Number(a > b) - Number(a < b);
21
+ /** File order, then line and column: within one file the lines differ from the position on, read as numbers. */
22
+ const byPlace = (a: Diagnostic, b: Diagnostic): number =>
23
+ byKey(a.file, b.file) || a.line.localeCompare(b.line, "en", { numeric: true });
24
+
25
+ /**
26
+ * The magic hits and untested guards of `files` (root-relative POSIX paths), in file then position order; the guards
27
+ * of the sources a test among them imports too. Or why a tsconfig.json could not be read.
28
+ */
29
+ export function scriptDiagnostics(root: string, files: readonly string[]): Result<Diagnostic[]> {
30
+ const magic = magicDiagnostics(root, files);
31
+ if (!magic.ok) return magic;
32
+ const guards = guardDiagnostics(root, files);
33
+ if (!guards.ok) return guards;
34
+ return { ok: true, value: [...magic.value, ...guards.value].sort(byPlace) };
35
+ }
36
+
37
+ async function checkoutAt(cwd: string): Promise<Checkout> {
38
+ const root = findRoot(cwd);
39
+ if (root === null) return null;
40
+ const loaded = await loadConfig(root);
41
+ if (!loaded.ok || !loaded.config.packs.includes("standards")) return null;
42
+ const listed = sourceFiles(root);
43
+ const files: Result<string[]> = listed.ok ? { ok: true, value: listed.files } : listed;
44
+ return { root, skips: loaded.config.lint.skip, files };
45
+ }
46
+
47
+ /** `repo check`'s script hits for the repo at `cwd`: none without a config or the standards pack. */
48
+ export async function checkScriptsAt(cwd: string): Promise<ScriptCheck> {
49
+ const checkout = await checkoutAt(cwd);
50
+ if (checkout === null) return NONE;
51
+ if (!checkout.files.ok) return { ...NONE, problems: checkout.files.errors };
52
+ const found = scriptDiagnostics(checkout.root, checkout.files.value);
53
+ if (!found.ok) return { ...NONE, problems: found.errors };
54
+ return { all: found.value, kept: withoutSkipped(found.value, checkout.skips, checkout.files.value), problems: [] };
55
+ }
56
+
57
+ /** `repo check`'s reject file problems for the repo at `cwd`: none without a config or the standards pack. */
58
+ export async function checkRejectionsAt(cwd: string): Promise<string[]> {
59
+ const checkout = await checkoutAt(cwd);
60
+ if (checkout === null) return [];
61
+ return checkout.files.ok ? rejectionProblems(checkout.root, checkout.files.value) : checkout.files.errors;
62
+ }