@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,233 @@
1
+ /**
2
+ * `.repo/lint-baseline.json`: per file, per rule, how many lint problems the repo still carries. Only ever lowered
3
+ * to the real counts (by `repo turn` for the files it lints, by `repo lint --baseline` for all); `--baseline` also
4
+ * seeds a rule a file has no entry for. `repo check` fails on a count above its entry, an entry above its count and
5
+ * an entry for a gone file. Worktrees each hold a copy; a merge conflict resolves either way, then `--baseline`
6
+ * lowers it to the truth.
7
+ */
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ import { z } from "zod";
11
+ import { findRoot, loadConfig } from "../config.ts";
12
+ import { OXLINT_CONFIG_PATH } from "../sync/oxlint-config.ts";
13
+ import { captureTool } from "./lint.ts";
14
+ import { REPORT_START } from "./lint.constants.ts";
15
+
16
+ export const BASELINE_PATH = ".repo/lint-baseline.json";
17
+ /** File → rule → problem count; a file or rule left out counts 0. */
18
+ export type Counts = Record<string, Record<string, number>>;
19
+ /** One rule of one file over its baseline entry. */
20
+ export type Excess = { file: string; rule: string; has: number; allows: number };
21
+ /** One entry `repo turn` lowered; `to` 0 drops it. */
22
+ export type Lowered = { file: string; rule: string; from: number; to: number };
23
+ export type Diagnostic = { file: string; rule: string; line: string };
24
+ export type Result<T> = { ok: true; value: T } | { ok: false; errors: string[] };
25
+
26
+ const LOWER = "run `bun repo lint --baseline`";
27
+ const PARSE_ERROR = "parse-error";
28
+ const count = z.number().int().positive({ error: "must be a whole number above 0" });
29
+ const baselineSchema = z.record(z.string(), z.record(z.string(), count, { error: "must be an object" }), {
30
+ error: "must be an object",
31
+ });
32
+ const reportSchema = z.object({
33
+ diagnostics: z.array(
34
+ z.object({
35
+ message: z.string(),
36
+ code: z.string().optional(),
37
+ severity: z.string(),
38
+ filename: z.string(),
39
+ labels: z.array(z.object({ span: z.object({ line: z.number(), column: z.number() }) })).optional(),
40
+ }),
41
+ ),
42
+ });
43
+
44
+ const byKey = (a: string, b: string) => Number(a > b) - Number(a < b);
45
+ const plural = (n: number, noun: string) => `${n} ${noun}${n === 1 ? "" : "s"}`;
46
+ const countOf = (counts: Counts, file: string, rule: string) => counts[file]?.[rule] ?? 0;
47
+
48
+ export const excessText = ({ file, rule, has, allows }: Excess): string =>
49
+ `${file} has ${has} ${rule}, the baseline allows ${allows}`;
50
+
51
+ /** Every rule of `files` whose count in `actual` is above `baseline`, in file then rule order. */
52
+ function overOf(baseline: Counts, actual: Counts, files: readonly string[]): Excess[] {
53
+ return [...files].sort(byKey).flatMap((file) =>
54
+ Object.keys(actual[file] ?? {}).flatMap((rule) => {
55
+ const has = countOf(actual, file, rule);
56
+ const allows = countOf(baseline, file, rule);
57
+ return has > allows ? [{ file, rule, has, allows }] : [];
58
+ }),
59
+ );
60
+ }
61
+
62
+ /** `counts` with empty files and zero rules dropped and keys sorted, as the file holds them. */
63
+ function normalized(counts: Counts): Counts {
64
+ const entries = Object.entries(counts).map(([file, rules]) => {
65
+ const kept = Object.entries(rules).filter(([, value]) => value > 0);
66
+ return [file, Object.fromEntries(kept.sort(([a], [b]) => byKey(a, b)))] as const;
67
+ });
68
+ const sorted = entries.sort(([a], [b]) => byKey(a, b));
69
+ return Object.fromEntries(sorted.filter(([, rules]) => Object.keys(rules).length > 0));
70
+ }
71
+
72
+ /**
73
+ * The baseline gate over `files` (linted or deleted): what is over, the baseline with each of their entries lowered
74
+ * to the real count, and what was lowered. Other files' entries stay as they are.
75
+ */
76
+ export function gate(
77
+ baseline: Counts,
78
+ actual: Counts,
79
+ files: readonly string[],
80
+ ): {
81
+ over: Excess[];
82
+ next: Counts;
83
+ lowered: Lowered[];
84
+ } {
85
+ const next: Counts = { ...baseline };
86
+ const lowered: Lowered[] = [];
87
+ for (const file of [...files].sort(byKey)) {
88
+ const entry = baseline[file];
89
+ if (entry === undefined) continue;
90
+ const rules = Object.keys(entry).sort(byKey);
91
+ next[file] = Object.fromEntries(
92
+ Object.entries(entry).map(([rule, allows]) => [rule, Math.min(allows, countOf(actual, file, rule))]),
93
+ );
94
+ for (const rule of rules) {
95
+ const [from, to] = [countOf(baseline, file, rule), countOf(next, file, rule)];
96
+ if (to < from) lowered.push({ file, rule, from, to });
97
+ }
98
+ }
99
+ return { over: overOf(baseline, actual, files), next: normalized(next), lowered };
100
+ }
101
+
102
+ /** `repo lint --baseline`: every entry lowered to its real count, a gone file dropped, a rule a file lacks seeded. */
103
+ function reseeded(baseline: Counts, actual: Counts): Counts {
104
+ const next: Counts = gate(baseline, actual, Object.keys(baseline)).next;
105
+ for (const [file, rules] of Object.entries(actual)) {
106
+ for (const [rule, value] of Object.entries(rules)) {
107
+ if (baseline[file]?.[rule] === undefined) next[file] = { ...next[file], [rule]: value };
108
+ }
109
+ }
110
+ return normalized(next);
111
+ }
112
+
113
+ /** What `repo check` reports: a count over its entry, an entry over its count, an entry for a file that is gone. */
114
+ function drift(baseline: Counts, actual: Counts, exists: (file: string) => boolean): string[] {
115
+ const files = [...new Set([...Object.keys(baseline), ...Object.keys(actual)])].sort(byKey);
116
+ return files.flatMap((file) => {
117
+ if (!exists(file)) return [`${file} is gone; ${LOWER}`];
118
+ const over = overOf(baseline, actual, [file]).map(excessText);
119
+ const high = Object.keys(baseline[file] ?? {})
120
+ .filter((rule) => countOf(actual, file, rule) < countOf(baseline, file, rule))
121
+ .map((rule) => {
122
+ const allows = countOf(baseline, file, rule);
123
+ return `${excessText({ file, rule, has: countOf(actual, file, rule), allows })}; ${LOWER} to lower it`;
124
+ });
125
+ return [...over, ...high];
126
+ });
127
+ }
128
+
129
+ /** The baseline under `root`, empty when the file is absent. */
130
+ export function readBaseline(root: string): Result<Counts> {
131
+ const file = path.join(root, BASELINE_PATH);
132
+ if (!fs.existsSync(file)) return { ok: true, value: {} };
133
+ const fail = (lines: string[]): Result<Counts> => ({ ok: false, errors: lines.map((l) => `${BASELINE_PATH}: ${l}`) });
134
+ let raw: unknown;
135
+ try {
136
+ raw = JSON.parse(fs.readFileSync(file, "utf8"));
137
+ } catch (error) {
138
+ return fail([`not valid JSON: ${(error as Error).message}; fix it, or delete it and ${LOWER}`]);
139
+ }
140
+ const parsed = baselineSchema.safeParse(raw);
141
+ if (parsed.success) return { ok: true, value: parsed.data };
142
+ return fail(
143
+ parsed.error.issues.map(
144
+ (issue) => `${[...issue.path, ""].join(": ")}${issue.message}; fix it, or delete it and ${LOWER}`,
145
+ ),
146
+ );
147
+ }
148
+
149
+ /** Writes `counts` sorted, as `JSON.stringify` indents it, which oxfmt leaves alone; through a rename, so no reader sees half. */
150
+ export function writeBaseline(root: string, counts: Counts): void {
151
+ const file = path.join(root, BASELINE_PATH);
152
+ fs.mkdirSync(path.dirname(file), { recursive: true });
153
+ const temp = `${file}.${process.pid}.tmp`;
154
+ fs.writeFileSync(temp, `${JSON.stringify(normalized(counts), null, 2)}\n`);
155
+ fs.renameSync(temp, file);
156
+ }
157
+
158
+ /**
159
+ * oxlint's json report on `args` in `root` (no paths: the whole tree), one diagnostic per problem; `run` runs oxlint,
160
+ * `captureTool` unless a caller injects another.
161
+ */
162
+ export function lintReport(
163
+ root: string,
164
+ args: readonly string[],
165
+ run: typeof captureTool = captureTool,
166
+ ): Result<Diagnostic[]> {
167
+ const ran = run("oxlint", ["-f", "json", ...args], root);
168
+ let raw: unknown;
169
+ try {
170
+ raw = JSON.parse(ran.stdout.slice(Math.max(ran.stdout.search(REPORT_START), 0)));
171
+ } catch {
172
+ return { ok: false, errors: [`oxlint exited ${ran.code}: ${`${ran.stdout}${ran.stderr}`.trim()}`] };
173
+ }
174
+ const report = reportSchema.parse(raw);
175
+ const diagnostics = report.diagnostics.map((d) => {
176
+ const rule = d.code ?? PARSE_ERROR;
177
+ const span = d.labels?.[0]?.span;
178
+ // A diagnostic without a label is placed at its file alone.
179
+ const at = [d.filename, span?.line, span?.column].filter((part) => part !== undefined).join(":");
180
+ return { file: d.filename, rule, line: `${at}: ${d.severity} ${rule}: ${d.message}` };
181
+ });
182
+ return { ok: true, value: diagnostics };
183
+ }
184
+
185
+ export function countsOf(diagnostics: readonly Diagnostic[]): Counts {
186
+ const counts: Counts = {};
187
+ for (const { file, rule } of diagnostics) counts[file] = { ...counts[file], [rule]: countOf(counts, file, rule) + 1 };
188
+ return counts;
189
+ }
190
+
191
+ /** The whole tree's counts, oxlint's and the `scripts` hits no skip turns off, and the baseline under `root`. */
192
+ function measure(root: string, scripts: readonly Diagnostic[]): Result<{ baseline: Counts; actual: Counts }> {
193
+ const baseline = readBaseline(root);
194
+ if (!baseline.ok) return baseline;
195
+ const report = lintReport(root, []);
196
+ if (!report.ok) return report;
197
+ return { ok: true, value: { baseline: baseline.value, actual: countsOf([...report.value, ...scripts]) } };
198
+ }
199
+
200
+ /**
201
+ * `repo lint --baseline` in `root`: every entry lowered to the real count, oxlint's and the `scripts` hits no skip turns
202
+ * off, a gone file dropped and a rule a file lacks seeded; the exit code.
203
+ */
204
+ export function seedBaseline(root: string, scripts: readonly Diagnostic[]): number {
205
+ const measured = measure(root, scripts);
206
+ if (!measured.ok) {
207
+ for (const line of measured.errors) process.stderr.write(`repo lint: ${line}\n`);
208
+ return 1;
209
+ }
210
+ const next = reseeded(measured.value.baseline, measured.value.actual);
211
+ writeBaseline(root, next);
212
+ const problems = Object.values(next).reduce((sum, rules) => sum + Object.values(rules).reduce((a, b) => a + b, 0), 0);
213
+ const counted = `${plural(Object.keys(next).length, "file")}, ${plural(problems, "problem")}`;
214
+ process.stdout.write(`repo lint: baseline written: ${counted}\n`);
215
+ return 0;
216
+ }
217
+
218
+ /**
219
+ * `repo check`'s lint lines over oxlint's and the `scripts` hits no skip turns off, each prefixed with the baseline
220
+ * path: none when the repo has no config, the standards pack is off or sync has not written the oxlint config (sync
221
+ * --check reports that).
222
+ */
223
+ export async function checkLintBaseline(cwd: string, scripts: readonly Diagnostic[]): Promise<string[]> {
224
+ const root = findRoot(cwd);
225
+ if (root === null) return [];
226
+ const loaded = await loadConfig(root);
227
+ if (!loaded.ok || !loaded.config.packs.includes("standards")) return [];
228
+ if (!fs.existsSync(path.join(root, OXLINT_CONFIG_PATH))) return [];
229
+ const measured = measure(root, scripts);
230
+ if (!measured.ok) return measured.errors;
231
+ const exists = (file: string) => fs.existsSync(path.join(root, file));
232
+ return drift(measured.value.baseline, measured.value.actual, exists).map((line) => `${BASELINE_PATH}: ${line}`);
233
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `repo lint`: oxlint with the arguments given, or with `--baseline` alone, the baseline lowered and seeded from
3
+ * oxlint's and the script checks' hits. The baseline and the script checks load only for `--baseline`, so a lint of
4
+ * paths never loads typescript.
5
+ */
6
+ import { lint, runTool } from "./lint.ts";
7
+
8
+ export async function lintMain(argv: string[]): Promise<number> {
9
+ if (!argv.includes("--baseline")) return lint(argv);
10
+ if (argv.length > 1) {
11
+ process.stderr.write("repo lint: --baseline takes no other arguments; it lints the whole repo\n");
12
+ return 1;
13
+ }
14
+ const [{ seedBaseline }, { checkScriptsAt }] = await Promise.all([
15
+ import("./lint-baseline.ts"),
16
+ import("../script-diagnostics.ts"),
17
+ ]);
18
+ const scripts = await checkScriptsAt(process.cwd());
19
+ if (scripts.problems.length > 0) {
20
+ for (const line of scripts.problems) process.stderr.write(`repo lint: ${line}\n`);
21
+ return 1;
22
+ }
23
+ return runTool("oxlint", [], { run: (_tool, _args, root) => seedBaseline(root, scripts.kept) });
24
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * `repo check`'s view of `lint.skip`: every skip with its rule, path, hits and reason, a count per rule, and the
3
+ * entries gone stale. The hits come from one more lint of the skipped files under the generated config without its
4
+ * skips, written beside `.oxlintrc.json` for the run (oxlint reads override globs against the config's own dir) and
5
+ * named for the process, so two checks never remove each other's copy.
6
+ */
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import { findRoot, loadConfig, type LintSkip } from "../config.ts";
10
+ import { sourceFiles } from "../check-comments.ts";
11
+ import { oxlintConfig, unskippedConfigPath } from "../sync/oxlint-config.ts";
12
+ import { readWorkspace, type Workspace } from "../sync/workspace.ts";
13
+ import { type Diagnostic, lintReport } from "./lint-baseline.ts";
14
+ import { captureTool } from "./lint.ts";
15
+
16
+ /** What `repo check` prints of the skips: report lines that pass, problem lines that fail it. */
17
+ export type SkipCheck = { report: string[]; problems: string[] };
18
+ /** The script checks' hits, no skip applied, and the oxlint runner (`captureTool` unless a test injects another). */
19
+ export type SkipDeps = { scripts: readonly Diagnostic[]; run?: typeof captureTool };
20
+ /** One `lint.skip` entry with the source files its globs match. */
21
+ type Matched = { skip: LintSkip; index: number; files: Set<string> };
22
+
23
+ const NONE: SkipCheck = { report: [], problems: [] };
24
+ const byKey = (a: string, b: string) => Number(a > b) - Number(a < b);
25
+ const hits = (n: number) => `${n} hit${n === 1 ? "" : "s"}`;
26
+ /** `repo/no-module-state` is reported as `repo(no-module-state)`. */
27
+ export const codeOf = (rule: string): string => rule.replace("/", "(").concat(")");
28
+
29
+ /**
30
+ * oxlint's override match: a glob with no slash matches a file's name in any dir, a leading `./` anchors it at the
31
+ * root, and any other glob matches the root-relative path.
32
+ */
33
+ function matcherOf(glob: string): Bun.Glob {
34
+ if (glob.startsWith("./")) return new Bun.Glob(glob.slice(2));
35
+ return new Bun.Glob(glob.includes("/") ? glob : `**/${glob}`);
36
+ }
37
+
38
+ /** The `files` (root-relative POSIX paths) any of `globs` matches, as oxlint's overrides match them, in their order. */
39
+ export function skipFiles(globs: readonly string[], files: readonly string[]): string[] {
40
+ const matchers = globs.map(matcherOf);
41
+ return files.filter((file) => matchers.some((matcher) => matcher.match(file)));
42
+ }
43
+
44
+ const noFile = (index: number, glob: string) =>
45
+ `lint.skip[${index}]: ${JSON.stringify(glob)} matches no file; fix the path or delete the entry`;
46
+ const noHit = ({ skip, index }: Matched, rule: string) =>
47
+ `lint.skip[${index}]: ${rule} has no hit in ${skip.files.map((glob) => JSON.stringify(glob)).join(", ")}; delete it from the entry`;
48
+
49
+ /** The diagnostics of `rule` in `files`. */
50
+ const hitsOf = (diagnostics: readonly Diagnostic[], rule: string, files: ReadonlySet<string>) =>
51
+ diagnostics.filter((d) => d.rule === codeOf(rule) && files.has(d.file));
52
+
53
+ /** Each entry's report lines, one per rule and glob with hits, and a problem per rule with none. */
54
+ function entryLines(entry: Matched, diagnostics: readonly Diagnostic[], sources: readonly string[]): SkipCheck {
55
+ const { skip } = entry;
56
+ const report: string[] = [];
57
+ const problems: string[] = [];
58
+ for (const rule of skip.rules) {
59
+ if (hitsOf(diagnostics, rule, entry.files).length === 0) problems.push(noHit(entry, rule));
60
+ for (const glob of skip.files) {
61
+ const count = hitsOf(diagnostics, rule, new Set(skipFiles([glob], sources))).length;
62
+ if (count > 0) report.push(`skip ${rule} in ${glob}: ${hits(count)} (${skip.reason})`);
63
+ }
64
+ }
65
+ return { report, problems };
66
+ }
67
+
68
+ /** `skips: <rule> <n>, …`: per rule, the hits any entry turns off, each counted once; null when none. */
69
+ function countLine(entries: readonly Matched[], diagnostics: readonly Diagnostic[]): string | null {
70
+ const counted = new Map<string, Set<Diagnostic>>();
71
+ for (const entry of entries) {
72
+ for (const rule of entry.skip.rules) {
73
+ const found = counted.get(rule) ?? new Set();
74
+ for (const hit of hitsOf(diagnostics, rule, entry.files)) found.add(hit);
75
+ if (found.size > 0) counted.set(rule, found);
76
+ }
77
+ }
78
+ const rules = [...counted.keys()].sort(byKey);
79
+ return rules.length === 0 ? null : `skips: ${rules.map((rule) => `${rule} ${counted.get(rule)?.size}`).join(", ")}`;
80
+ }
81
+
82
+ /** The diagnostics of `files` under the generated config without skips, written to the root for the run only. */
83
+ function unskippedReport(workspace: Workspace, files: readonly string[], run: typeof captureTool) {
84
+ // Only an unknown skip rule fails the build, and this one has no skips.
85
+ const built = oxlintConfig(workspace, []) /* no skips, so the build cannot fail */ as { ok: true; value: string };
86
+ const rel = unskippedConfigPath(process.pid);
87
+ const file = path.join(workspace.root, rel);
88
+ fs.writeFileSync(file, built.value);
89
+ try {
90
+ return lintReport(workspace.root, ["-c", rel, ...files], run);
91
+ } finally {
92
+ fs.rmSync(file, { force: true });
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Every `lint.skip` entry checked against the repo: a report line per rule and glob with its hits and reason, a count
98
+ * per rule, and a problem for a glob that matches no source file and for a rule with no hit in its entry's files. The
99
+ * hits are oxlint's without skips and the script checks' ones `deps` hands over.
100
+ */
101
+ export function checkLintSkips(workspace: Workspace, skips: readonly LintSkip[], deps: SkipDeps): SkipCheck {
102
+ const { scripts, run = captureTool } = deps;
103
+ if (skips.length === 0) return NONE;
104
+ const listed = sourceFiles(workspace.root);
105
+ if (!listed.ok) return { report: [], problems: listed.errors };
106
+ const entries = skips.map((skip, index) => ({ skip, index, files: new Set(skipFiles(skip.files, listed.files)) }));
107
+ const files = [...new Set(entries.flatMap((entry) => [...entry.files]))].sort(byKey);
108
+ const linted = files.length === 0 ? { ok: true as const, value: [] } : unskippedReport(workspace, files, run);
109
+ if (!linted.ok) return { report: [], problems: linted.errors };
110
+ const diagnostics = [...linted.value, ...scripts];
111
+ const lines = entries.map((entry) => {
112
+ const missing = entry.skip.files.filter((glob) => skipFiles([glob], listed.files).length === 0);
113
+ // An entry that matches nothing is stale on its paths alone; its rules cannot hit.
114
+ const checked = entry.files.size === 0 ? NONE : entryLines(entry, diagnostics, listed.files);
115
+ return {
116
+ report: checked.report,
117
+ problems: [...missing.map((glob) => noFile(entry.index, glob)), ...checked.problems],
118
+ };
119
+ });
120
+ const count = countLine(entries, diagnostics);
121
+ return {
122
+ report: [...lines.flatMap((line) => line.report), ...(count === null ? [] : [count])],
123
+ problems: lines.flatMap((line) => line.problems),
124
+ };
125
+ }
126
+
127
+ /**
128
+ * `repo check`'s skip lines for the repo at `cwd`, given its script checks' hits with no skip applied: none without a config,
129
+ * the standards pack or a readable workspace.
130
+ */
131
+ export async function checkLintSkipsAt(cwd: string, scripts: readonly Diagnostic[]): Promise<SkipCheck> {
132
+ const root = findRoot(cwd);
133
+ if (root === null) return NONE;
134
+ const loaded = await loadConfig(root);
135
+ if (!loaded.ok || !loaded.config.packs.includes("standards")) return NONE;
136
+ // An unreadable manifest is already reported by sync --check.
137
+ const read = readWorkspace(root);
138
+ return read.ok ? checkLintSkips(read.workspace, loaded.config.lint.skip, { scripts }) : NONE;
139
+ }
@@ -0,0 +1,6 @@
1
+ /** The prefix of the variables git hands a hook (GIT_DIR, GIT_INDEX_FILE, ...). */
2
+ export const GIT_ENV_PREFIX = "GIT_";
3
+ /** Env-config variables: they set config, not the repo, so a tool keeps them. */
4
+ export const GIT_CONFIG_ENV: RegExp = /^GIT_CONFIG_(COUNT|KEY_\d+|VALUE_\d+)$/;
5
+ /** Where oxlint's json report starts when a note ("No files found to lint.") precedes it: a line opening with `{`. */
6
+ export const REPORT_START: RegExp = /^\{/m;
@@ -0,0 +1,142 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { CONFIG_FILE, findRoot, loadConfig } from "../config.ts";
6
+ import { OXLINT_CONFIG_PATH } from "../sync/oxlint-config.ts";
7
+ import { OXFMT_CONFIG_PATH } from "../sync/standards.ts";
8
+ import { WINDOWS_PLATFORM } from "../actionlint.constants.ts";
9
+ import { childEnv, readEnv } from "../env.ts";
10
+ import { GIT_CONFIG_ENV, GIT_ENV_PREFIX } from "./lint.constants.ts";
11
+
12
+ /** The pinned tools the standards pack runs. */
13
+ export type Tool = "oxlint" | "oxfmt";
14
+ /** Runs `tool` with `args` in `root` and returns its exit code. */
15
+ export type RunTool = (tool: Tool, args: string[], root: string) => number;
16
+ export type ToolDeps = { cwd?: string; stderr?: (s: string) => void; run?: RunTool };
17
+
18
+ const COMMAND: Record<Tool, string> = { oxlint: "lint", oxfmt: "fmt" };
19
+ const CONFIG: Record<Tool, string> = { oxlint: OXLINT_CONFIG_PATH, oxfmt: OXFMT_CONFIG_PATH };
20
+
21
+ /**
22
+ * `repo lint` and `repo fmt`: the pinned tool, run in the repo root with the arguments given, once the config
23
+ * loads with the standards pack on and sync has written the tool's config. Its exit code is returned as is.
24
+ */
25
+ export async function runTool(tool: Tool, args: string[], deps: ToolDeps = {}): Promise<number> {
26
+ const { cwd = process.cwd(), stderr = (s: string) => process.stderr.write(s), run = spawnTool } = deps;
27
+ const fail = (lines: string[]): number => {
28
+ for (const line of lines) stderr(`repo ${COMMAND[tool]}: ${line}\n`);
29
+ return 1;
30
+ };
31
+ const root = findRoot(cwd);
32
+ if (root === null) return fail([`no ${CONFIG_FILE} in ${cwd} or any parent; run \`bun repo init\``]);
33
+ const loaded = await loadConfig(root);
34
+ if (!loaded.ok) return fail(loaded.errors);
35
+ if (!loaded.config.packs.includes("standards"))
36
+ return fail([`the standards pack is off; add "standards" to packs in ${CONFIG_FILE}`]);
37
+ if (!fs.existsSync(path.join(root, CONFIG[tool])))
38
+ return fail([`no ${CONFIG[tool]}; run \`bun repo sync\` to write it`]);
39
+ return run(tool, args, root);
40
+ }
41
+
42
+ export const lint = (argv: string[]): Promise<number> => runTool("oxlint", argv);
43
+ export const fmt = (argv: string[]): Promise<number> => runTool("oxfmt", argv);
44
+
45
+ /** The file a package export resolves to, from this package's install. */
46
+ function resolved(specifier: string): string {
47
+ // Bun.resolveSync, not import.meta.resolve: vitest's transform drops the latter, and tests run this in-process.
48
+ return Bun.resolveSync(specifier, import.meta.dirname);
49
+ }
50
+
51
+ /** The tool's bin script, read from its package.json; it is a node shim, so bun runs it. */
52
+ function binOf(tool: Tool): string {
53
+ const manifest = resolved(`${tool}/package.json`);
54
+ const { bin } = JSON.parse(fs.readFileSync(manifest, "utf8")) as { bin: Record<string, string> };
55
+ return path.join(path.dirname(manifest), bin[tool] as string);
56
+ }
57
+
58
+ /** What a captured run printed and its exit code. */
59
+ export type Captured = { code: number; stdout: string; stderr: string };
60
+
61
+ /**
62
+ * The node_modules dir holding `@workerstack/repo` for this package at `packageRoot`: the one its scope dir sits in
63
+ * when installed (bunx, a consumer), else the root's own, which a dev checkout fills with a self-link.
64
+ */
65
+ export function pluginModules(packageRoot: string): string {
66
+ const modules = path.dirname(path.dirname(packageRoot));
67
+ return path.basename(modules) === "node_modules" ? modules : path.join(packageRoot, "node_modules");
68
+ }
69
+
70
+ /** The package export of the native tsgolint binary for a platform and arch; Windows' ends in `.exe`. */
71
+ export function tsgolintSpecifier(platform: NodeJS.Platform, arch: string): string {
72
+ return `@oxlint-tsgolint/${platform}-${arch}/tsgolint${platform === WINDOWS_PLATFORM ? ".exe" : ""}`;
73
+ }
74
+
75
+ /**
76
+ * What oxlint needs beyond the inherited environment: the native tsgolint binary, which its own lookup would reach
77
+ * through a node shim, and a NODE_PATH led by the node_modules holding this package, so the config's
78
+ * `@workerstack/repo/lint` plugin loads in a repo with no install of its own.
79
+ */
80
+ function oxlintEnv(): NodeJS.ProcessEnv {
81
+ const modules = pluginModules(path.resolve(import.meta.dirname, "..", ".."));
82
+ const inherited = readEnv().NODE_PATH;
83
+ return {
84
+ OXLINT_TSGOLINT_PATH: resolved(tsgolintSpecifier(process.platform, process.arch)),
85
+ NODE_PATH: inherited ? `${modules}${path.delimiter}${inherited}` : modules,
86
+ };
87
+ }
88
+
89
+ /** The bun command line and environment that run `tool`. */
90
+ function commandOf(tool: Tool): { args: string[]; env: NodeJS.ProcessEnv } {
91
+ return { args: [binOf(tool)], env: childEnv(tool === "oxlint" ? oxlintEnv() : {}) };
92
+ }
93
+
94
+ /** Spawns bun on the tool's bin, output straight to the terminal. */
95
+ function spawnTool(tool: Tool, args: string[], root: string): number {
96
+ const command = commandOf(tool);
97
+ const result = spawnSync(process.execPath, [...command.args, ...args], {
98
+ cwd: root,
99
+ stdio: "inherit",
100
+ env: toolEnv(command.env),
101
+ });
102
+ return result.status ?? 1;
103
+ }
104
+
105
+ /**
106
+ * `env` without any `GIT_*` variable. Git hands a hook GIT_DIR, GIT_INDEX_FILE and more; a tool run from one that
107
+ * kept them would aim every git call it makes, a test's `git init` in its own temp dir included, at the repo being
108
+ * committed. `GIT_CONFIG_COUNT`, `GIT_CONFIG_KEY_<n>` and `GIT_CONFIG_VALUE_<n>` stay: they set config, not the repo
109
+ * (the test setup uses them to switch off background git), and git does not hand them to hooks.
110
+ */
111
+ export function toolEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
112
+ return Object.fromEntries(
113
+ Object.entries(env).filter(([name]) => !name.startsWith(GIT_ENV_PREFIX) || GIT_CONFIG_ENV.test(name)),
114
+ );
115
+ }
116
+
117
+ /**
118
+ * Runs bun with `args` in `cwd`, its output captured instead of printed, with no `GIT_*` variable (`toolEnv`). The
119
+ * output goes to files, not pipes: a node script that exits right after writing (oxlint's json report) loses what a
120
+ * pipe had not yet taken.
121
+ */
122
+ export function captureBun(args: string[], cwd: string, env: NodeJS.ProcessEnv = childEnv()): Captured {
123
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "workerstack-capture-"));
124
+ const files = { stdout: path.join(dir, "stdout"), stderr: path.join(dir, "stderr") };
125
+ const out = fs.openSync(files.stdout, "w");
126
+ const err = fs.openSync(files.stderr, "w");
127
+ try {
128
+ const result = spawnSync(process.execPath, args, { cwd, env: toolEnv(env), stdio: ["ignore", out, err] });
129
+ const read = (file: string) => fs.readFileSync(file, "utf8");
130
+ return { code: result.status ?? 1, stdout: read(files.stdout), stderr: read(files.stderr) };
131
+ } finally {
132
+ fs.closeSync(out);
133
+ fs.closeSync(err);
134
+ fs.rmSync(dir, { recursive: true, force: true });
135
+ }
136
+ }
137
+
138
+ /** The tool run as `repo lint` or `repo fmt` runs it, in `root`, its output captured instead of printed. */
139
+ export function captureTool(tool: Tool, args: string[], root: string): Captured {
140
+ const command = commandOf(tool);
141
+ return captureBun([...command.args, ...args], root, command.env);
142
+ }