@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,157 @@
1
+ /**
2
+ * `repo turn [--staged] [<path>]`: gates a change set (the working tree, the index, or one package) with fmt, disable
3
+ * comments, lint and its baseline, typecheck, reject files, related tests, and sync and check when their inputs
4
+ * changed, then prints one summary line. Only `--staged` restages, and only what turn itself rewrote.
5
+ */
6
+ import { spawnSync } from "node:child_process";
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import { CONFIG_FILE, findRoot, loadConfig } from "../config.ts";
10
+ import { readWorkspace } from "../sync/workspace.ts";
11
+ import { changeSet, type Scope } from "./change-set.ts";
12
+ import { commentsStep } from "./comments-step.ts";
13
+ import { captureBun } from "./lint.ts";
14
+ import { rejectionsStep } from "./rejections-step.ts";
15
+ import {
16
+ checkStep,
17
+ fmtStep,
18
+ lintStep,
19
+ syncStep,
20
+ testsStep,
21
+ type Bins,
22
+ type Step,
23
+ type StepResult,
24
+ type Turn,
25
+ } from "./turn-steps.ts";
26
+ import { typecheckStep } from "./typecheck-step.ts";
27
+ import { childEnv } from "../env.ts";
28
+ import { NESTED_VITEST_FLAGS } from "../nested-vitest-flags.ts";
29
+
30
+ export type TurnDeps = { cwd?: string; stdout?: (s: string) => void; stderr?: (s: string) => void; bins?: Bins };
31
+ type Parsed = { ok: true; scope: Scope } | { ok: false; errors: string[] };
32
+
33
+ const STEPS: readonly Step[] = [
34
+ fmtStep,
35
+ commentsStep,
36
+ lintStep,
37
+ typecheckStep,
38
+ rejectionsStep,
39
+ testsStep,
40
+ syncStep,
41
+ checkStep,
42
+ ];
43
+ const USE = "use repo turn [--staged] [<path>]";
44
+
45
+ /** The bin of `name` in the package.json `manifest` resolves to. */
46
+ function binOf(manifest: string, name: string): string {
47
+ const { bin } = JSON.parse(fs.readFileSync(manifest, "utf8")) as { bin: Record<string, string> };
48
+ return path.join(path.dirname(manifest), bin[name] as string);
49
+ }
50
+
51
+ /** typescript from this package's install; vitest from the package being tested, as its own tests would run. */
52
+ export const BINS: Bins = {
53
+ tsc: (args, cwd) =>
54
+ captureBun([binOf(Bun.resolveSync("typescript/package.json", import.meta.dirname), "tsc"), ...args], cwd),
55
+ vitest: (args, cwd) => {
56
+ let manifest: string;
57
+ try {
58
+ manifest = Bun.resolveSync("vitest/package.json", cwd);
59
+ } catch {
60
+ return null;
61
+ }
62
+ return captureBun(
63
+ ["--bun", binOf(manifest, "vitest"), ...NESTED_VITEST_FLAGS, ...args],
64
+ cwd,
65
+ childEnv({ NO_COLOR: "1" }),
66
+ );
67
+ },
68
+ };
69
+
70
+ function parseArgs(argv: readonly string[], cwd: string, root: string): Parsed {
71
+ const flags = argv.filter((arg) => arg.startsWith("-"));
72
+ const paths = argv.filter((arg) => !arg.startsWith("-"));
73
+ const unknown = flags.find((flag) => flag !== "--staged");
74
+ if (unknown !== undefined) return { ok: false, errors: [`unknown flag ${unknown}; ${USE}`] };
75
+ if (paths.length > 1) return { ok: false, errors: [`one path at most; ${USE}`] };
76
+ if (flags.length > 0 && paths.length > 0)
77
+ return { ok: false, errors: ["--staged gates the index, a path gates a package; pass one"] };
78
+ if (flags.length > 0) return { ok: true, scope: { kind: "staged" } };
79
+ if (paths[0] === undefined) return { ok: true, scope: { kind: "tree" } };
80
+ const rel = path.relative(root, path.resolve(cwd, paths[0])).split(path.sep).join("/");
81
+ if (rel.startsWith("..") || path.isAbsolute(rel)) return { ok: false, errors: [`${paths[0]} is outside ${root}`] };
82
+ return { ok: true, scope: { kind: "package", rel } };
83
+ }
84
+
85
+ /** The root, its packages and the change set, or why turn cannot start. */
86
+ async function prepare(
87
+ argv: readonly string[],
88
+ cwd: string,
89
+ bins: Bins,
90
+ ): Promise<{ ok: true; turn: Turn; staged: boolean } | { ok: false; errors: string[] }> {
91
+ const root = findRoot(cwd);
92
+ if (root === null) return { ok: false, errors: [`no ${CONFIG_FILE} in ${cwd} or any parent; run \`bun repo init\``] };
93
+ const parsed = parseArgs(argv, cwd, root);
94
+ if (!parsed.ok) return parsed;
95
+ const loaded = await loadConfig(root);
96
+ if (!loaded.ok) return loaded;
97
+ if (!loaded.config.packs.includes("standards"))
98
+ return { ok: false, errors: [`the standards pack is off; add "standards" to packs in ${CONFIG_FILE}`] };
99
+ const workspace = readWorkspace(root);
100
+ if (!workspace.ok) return workspace;
101
+ const packages = workspace.workspace.packages.map((pkg) => pkg.dir);
102
+ const changes = changeSet(root, parsed.scope, packages);
103
+ if (!changes.ok) return changes;
104
+ const turn = { root, changes: changes.changes, packages, bins, skips: loaded.config.lint.skip };
105
+ return { ok: true, turn, staged: parsed.scope.kind === "staged" };
106
+ }
107
+
108
+ /**
109
+ * Stages the files turn rewrote, so the commit carries them as turn left them; when git refuses (a held index
110
+ * lock), says so, since the index would otherwise go out with the content turn fixed only on disk.
111
+ */
112
+ function restage(root: string, touched: readonly string[], stderr: (s: string) => void): boolean {
113
+ if (touched.length === 0) return true;
114
+ if (spawnSync("git", ["add", "-A", "--", ...touched], { cwd: root }).status === 0) return true;
115
+ stderr(`repo turn: git add failed for ${touched.join(" ")}; stage it yourself and commit again\n`);
116
+ return false;
117
+ }
118
+
119
+ /** Runs every step whose inputs changed, printing a failed step's output as it ends. */
120
+ function runSteps(turn: Turn, stderr: (s: string) => void): StepResult[] {
121
+ return STEPS.flatMap((step) => {
122
+ const result = step(turn);
123
+ if (result === null) return [];
124
+ for (const chunk of result.output) stderr(chunk);
125
+ return [result];
126
+ });
127
+ }
128
+
129
+ /** The deps with the process's own for each one left out. */
130
+ function withDefaults(deps: TurnDeps): Required<TurnDeps> {
131
+ return {
132
+ cwd: deps.cwd ?? process.cwd(),
133
+ stdout: deps.stdout ?? ((s: string) => process.stdout.write(s)),
134
+ stderr: deps.stderr ?? ((s: string) => process.stderr.write(s)),
135
+ bins: deps.bins ?? BINS,
136
+ };
137
+ }
138
+
139
+ export async function main(argv: string[], deps: TurnDeps = {}): Promise<number> {
140
+ const { cwd, stdout, stderr, bins } = withDefaults(deps);
141
+ const prepared = await prepare(argv, cwd, bins);
142
+ if (!prepared.ok) {
143
+ for (const line of prepared.errors) stderr(`repo turn: ${line}\n`);
144
+ return 1;
145
+ }
146
+ const { turn, staged } = prepared;
147
+ if (turn.changes.length === 0) {
148
+ stdout("turn: no changes\n");
149
+ return 0;
150
+ }
151
+ const results = runSteps(turn, stderr);
152
+ const touched = [...new Set(results.flatMap((result) => result.touched))];
153
+ const restaged = !staged || restage(turn.root, touched, stderr);
154
+ const files = `${turn.changes.length} file${turn.changes.length === 1 ? "" : "s"}`;
155
+ stdout(`turn: ${files} · ${results.map((result) => result.label).join(" · ")}\n`);
156
+ return restaged && results.every((result) => result.ok) ? 0 : 1;
157
+ }
@@ -0,0 +1,43 @@
1
+ /** The typecheck step of `repo turn`: each owner's tsconfig.json, then its strict tsconfig.src.json. */
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { missingSrcConfig } from "../check-tsconfig.ts";
5
+ import { RUNTIME_CONFIG, SRC_CONFIG } from "../tsconfig.constants.ts";
6
+ import type { Captured } from "./lint.ts";
7
+ import { dirName, failed, owners, passed, type Step, type Turn } from "./turn-steps.ts";
8
+
9
+ /** A config typecheck ran or found missing: its root-relative path as the label names it, and what to print. */
10
+ type ConfigRun = { name: string; ok: boolean; output: string };
11
+
12
+ const configRun = (name: string, run: Captured): ConfigRun => ({
13
+ name,
14
+ ok: run.code === 0,
15
+ output: `${run.stdout}${run.stderr}`,
16
+ });
17
+
18
+ /** The owner's tsconfig.json, then its tsconfig.src.json, which is a failure with check's message when missing. */
19
+ function typecheckOwner(turn: Turn, owner: string): ConfigRun[] {
20
+ const tsc = (project: string) => turn.bins.tsc(["--noEmit", "-p", project], turn.root);
21
+ const runtime = configRun(path.posix.join(owner, RUNTIME_CONFIG), tsc(dirName(owner)));
22
+ const src = path.posix.join(owner, SRC_CONFIG);
23
+ if (!fs.existsSync(path.join(turn.root, src))) {
24
+ return [runtime, { name: `${src} missing`, ok: false, output: `repo turn: ${missingSrcConfig(dirName(owner))}\n` }];
25
+ }
26
+ return [runtime, configRun(src, tsc(src))];
27
+ }
28
+
29
+ /**
30
+ * `tsc --noEmit -p` on the tsconfig.json and the strict tsconfig.src.json of each owning package with a
31
+ * tsconfig.json, the root for files outside packages. A failure names each failing config by path.
32
+ */
33
+ export const typecheckStep: Step = (turn) => {
34
+ const checked = owners(turn).filter((owner) => fs.existsSync(path.join(turn.root, owner, RUNTIME_CONFIG)));
35
+ if (checked.length === 0) return passed("typecheck skipped (no tsconfig.json)");
36
+ const bad = checked.flatMap((owner) => typecheckOwner(turn, owner)).filter((run) => !run.ok);
37
+ if (bad.length === 0) return passed(`typecheck ${checked.map(dirName).join(", ")}`);
38
+ const names = bad.map(({ name }) => name).sort();
39
+ return failed(
40
+ `typecheck failed (${names.join(", ")})`,
41
+ bad.map(({ output }) => output),
42
+ );
43
+ };
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Write .vscode/tasks.json from package scripts (VS Code and Cursor: Tasks: Run Task).
4
+ *
5
+ * One `bun run <script>` shell task per root script, and one per script of every workspace package (root
6
+ * `package.json` `workspaces` globs), labelled `<scope>:<name>:<script>` with a `${workspaceFolder}` cwd.
7
+ * A task `isGenerated` rejects (hand-written, extra args, `dependsOn`) is kept, in order, after the generated block.
8
+ */
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import { isRecord } from "./hook-input.ts";
12
+ import { stripJsonc } from "./jsonc.ts";
13
+ import { resolveRoots } from "./workspace-root.ts";
14
+
15
+ const TASKS_REL = ".vscode/tasks.json";
16
+ const PRESENTATION = { panel: "new", reveal: "always" };
17
+
18
+ /** A tasks.json entry; hand-written ones may carry any other field. */
19
+ export interface VsTask {
20
+ label?: string;
21
+ type?: string;
22
+ command?: string;
23
+ dependsOn?: unknown;
24
+ options?: { cwd?: string };
25
+ problemMatcher?: unknown[];
26
+ presentation?: { panel: string; reveal: string };
27
+ [key: string]: unknown;
28
+ }
29
+
30
+ export interface TasksFile {
31
+ tasks: VsTask[];
32
+ version: string;
33
+ }
34
+
35
+ type PackageJson = Record<string, unknown>;
36
+ const RUN_RE = /^bun run \S+$/;
37
+
38
+ /** True for a task this script writes: a plain `bun run <script>` shell task with no `dependsOn`. */
39
+ export function isGenerated(task: VsTask | null | undefined): boolean {
40
+ return task != null && task.type === "shell" && !task.dependsOn && RUN_RE.test(String(task.command));
41
+ }
42
+
43
+ const slash = (p: string): string => p.split(path.sep).join("/");
44
+
45
+ function readPackage(file: string): PackageJson | null {
46
+ try {
47
+ const pkg: unknown = JSON.parse(fs.readFileSync(file, "utf8"));
48
+ return isRecord(pkg) ? pkg : null;
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ function globDirs(root: string, pattern: string): string[] {
55
+ const hits = fs.globSync(pattern, {
56
+ cwd: root,
57
+ exclude: (entry: string) => path.basename(entry) === "node_modules",
58
+ });
59
+ return hits.filter((hit) => !slash(hit).split("/").includes("node_modules")).map((hit) => path.join(root, hit));
60
+ }
61
+
62
+ function workspaceDirs(root: string, pkg: PackageJson): string[] {
63
+ const ws: unknown = Array.isArray(pkg.workspaces)
64
+ ? pkg.workspaces
65
+ : isRecord(pkg.workspaces)
66
+ ? pkg.workspaces.packages
67
+ : undefined;
68
+ const patterns = (Array.isArray(ws) ? ws : []).filter((p): p is string => typeof p === "string");
69
+ const dropped = new Set(patterns.filter((p) => p.startsWith("!")).flatMap((p) => globDirs(root, p.slice(1))));
70
+ const dirs = new Set(patterns.filter((p) => !p.startsWith("!")).flatMap((p) => globDirs(root, p)));
71
+ return [...dirs]
72
+ .filter((dir) => !dropped.has(dir) && path.relative(root, dir) !== "")
73
+ .filter((dir) => fs.existsSync(path.join(dir, "package.json")))
74
+ .sort();
75
+ }
76
+
77
+ /** `@acme/api` -> `acme:api`; `api` -> `api`. */
78
+ function labelPrefix(name: string): string {
79
+ return name.replace(/^@/, "").replace(/\//g, ":");
80
+ }
81
+
82
+ function shellTask(label: string, script: string, cwd?: string): VsTask {
83
+ return {
84
+ label,
85
+ type: "shell",
86
+ command: `bun run ${script}`,
87
+ ...(cwd ? { options: { cwd } } : {}),
88
+ problemMatcher: [],
89
+ presentation: { ...PRESENTATION },
90
+ };
91
+ }
92
+
93
+ function scriptsOf(pkg: PackageJson): string[] {
94
+ return isRecord(pkg.scripts) ? Object.keys(pkg.scripts) : [];
95
+ }
96
+
97
+ /**
98
+ * @param root checkout root holding the package.json
99
+ * @param existing the tasks array of the current tasks.json
100
+ * @returns generated tasks sorted by label, then the kept ones in their order
101
+ */
102
+ export function buildTasks(root: string, existing: VsTask[]): TasksFile {
103
+ const pkg = readPackage(path.join(root, "package.json")) ?? {};
104
+ const kept = existing.filter((task) => !isGenerated(task));
105
+ const taken = new Set(kept.map((task) => task.label));
106
+ const generated = new Map<string, VsTask>();
107
+ const add = (label: string, task: VsTask): void => {
108
+ if (taken.has(label)) return;
109
+ taken.add(label);
110
+ generated.set(label, task);
111
+ };
112
+ for (const script of scriptsOf(pkg)) add(script, shellTask(script, script));
113
+ for (const dir of workspaceDirs(root, pkg)) {
114
+ const workspace = readPackage(path.join(dir, "package.json"));
115
+ if (!workspace || typeof workspace.name !== "string" || !workspace.name) continue;
116
+ const cwd = `\${workspaceFolder}/${slash(path.relative(root, dir))}`;
117
+ for (const script of scriptsOf(workspace)) {
118
+ const label = `${labelPrefix(workspace.name)}:${script}`;
119
+ add(label, shellTask(label, script, cwd));
120
+ }
121
+ }
122
+ // Labels are unique in `generated`, so two never compare equal.
123
+ const sorted = [...generated].sort(([a], [b]) => (a < b ? -1 : 1)).map(([, task]) => task);
124
+ return { tasks: [...sorted, ...kept], version: "2.0.0" };
125
+ }
126
+
127
+ export type RenderedTasks = { ok: true; text: string; generated: number; kept: number } | { ok: false; error: string };
128
+
129
+ /**
130
+ * The text of `.vscode/tasks.json` for `root`: the generated tasks, then the ones kept from `existing` (the
131
+ * current file's text, null when there is none).
132
+ */
133
+ export function renderTasks(root: string, existing: string | null): RenderedTasks {
134
+ let tasks: VsTask[] = [];
135
+ if (existing !== null) {
136
+ try {
137
+ const parsed: unknown = JSON.parse(stripJsonc(existing));
138
+ if (isRecord(parsed) && Array.isArray(parsed.tasks)) tasks = parsed.tasks.filter(isRecord);
139
+ } catch (err) {
140
+ return {
141
+ ok: false,
142
+ error: `cannot parse ${TASKS_REL}: ${(err /* JSON.parse and stripJsonc throw only Error */ as Error).message}`,
143
+ };
144
+ }
145
+ }
146
+ const result = buildTasks(root, tasks);
147
+ const kept = result.tasks.filter((task) => !isGenerated(task)).length;
148
+ return { ok: true, text: `${JSON.stringify(result, null, 2)}\n`, generated: result.tasks.length - kept, kept };
149
+ }
150
+
151
+ /** Writes `.vscode/tasks.json` under `root`; prints the count line; returns the exit code. */
152
+ export function run(root: string): number {
153
+ const file = path.join(root, TASKS_REL);
154
+ const pkgFile = path.join(root, "package.json");
155
+ const current = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : null;
156
+ const rendered = renderTasks(root, current);
157
+ if (!rendered.ok) {
158
+ console.error(`tasks: ${rendered.error.replace(TASKS_REL, file)}`);
159
+ return 1;
160
+ }
161
+ if (!readPackage(pkgFile)) {
162
+ console.error(`tasks: cannot read ${pkgFile}`);
163
+ return 1;
164
+ }
165
+ fs.mkdirSync(path.dirname(file), { recursive: true });
166
+ fs.writeFileSync(file, rendered.text);
167
+ console.log(
168
+ `Wrote ${rendered.generated + rendered.kept} tasks to ${TASKS_REL} (${rendered.generated} generated, ${rendered.kept} kept)`,
169
+ );
170
+ return 0;
171
+ }
172
+
173
+ export function main(): number {
174
+ return run(resolveRoots().checkout);
175
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Where repo state lives for a hook or script call (roadmap D1, D2).
3
+ *
4
+ * root `.tmp/repo` scratch: the main checkout, so subdirs and worktrees share one
5
+ * checkout tracked files (lessons, `cli/scripts`): the checkout the agent works in
6
+ * cwd paths the agent typed and child-process cwds: the hook's cwd, else the sent workspace
7
+ *
8
+ * A repo install is the nearest dir holding INSTALL_MARKER, up to the git top level, so a
9
+ * repo install in a subpackage keeps its own root. All three share the `realpath.native` spelling, so a
10
+ * symlink, a junction or a Windows 8.3 short name compares equal to the path git prints. Node builtins only.
11
+ */
12
+ import { execFileSync } from "node:child_process";
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ import type { HookInput } from "./hook-input.ts";
17
+ import { readEnv } from "../env.ts";
18
+ import type { Env } from "../env.ts";
19
+
20
+ /** The file that marks a repo install: the consumer's `workerstack.config.ts`, which also exists with no `agents/` dir. */
21
+ export const INSTALL_MARKER = "workerstack.config.ts";
22
+
23
+ export interface GitFacts {
24
+ top: string;
25
+ gitDir: string;
26
+ commonDir: string;
27
+ }
28
+
29
+ /** Root, checkout and cwd spellings for one hook or script call. */
30
+ export interface Roots {
31
+ root: string;
32
+ checkout: string;
33
+ cwd: string;
34
+ }
35
+
36
+ interface RootsContext {
37
+ git: (dir: string) => GitFacts | null;
38
+ p: typeof path;
39
+ exists: (abs: string) => boolean;
40
+ realpath: (abs: string, p?: typeof path) => string;
41
+ }
42
+
43
+ const revParseMemo = new Map<string, GitFacts | null>();
44
+
45
+ /** `{ top, gitDir, commonDir }` for `dir`, or null outside git. Memoized for this process only. */
46
+ export function gitRevParse(dir: string, run: typeof execFileSync = execFileSync): GitFacts | null {
47
+ if (revParseMemo.has(dir)) return revParseMemo.get(dir) ?? null;
48
+ let facts: GitFacts | null = null;
49
+ try {
50
+ const out = run(
51
+ "git",
52
+ ["-C", dir, "rev-parse", "--path-format=absolute", "--show-toplevel", "--git-dir", "--git-common-dir"],
53
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
54
+ );
55
+ const [top, gitDir, commonDir] = out.split(/\r?\n/);
56
+ if (top && gitDir && commonDir) facts = { top, gitDir, commonDir };
57
+ } catch {
58
+ facts = null;
59
+ }
60
+ revParseMemo.set(dir, facts);
61
+ return facts;
62
+ }
63
+
64
+ /** The filesystem calls `canonicalPath` makes, replaceable by a test. */
65
+ interface CanonicalEdges {
66
+ exists: (abs: string) => boolean;
67
+ real: (abs: string) => string;
68
+ }
69
+
70
+ const NATIVE_EDGES: CanonicalEdges = { exists: fs.existsSync, real: fs.realpathSync.native };
71
+
72
+ /**
73
+ * `abs` with its nearest existing ancestor in the `realpath.native` spelling. A relative path, a non-string, or a
74
+ * path for a foreign `p` (a `path.win32` test on Linux) comes back unchanged.
75
+ */
76
+ export function canonicalPath(abs: string, p: typeof path = path, edges: CanonicalEdges = NATIVE_EDGES): string {
77
+ const { exists, real } = edges;
78
+ if (p !== path || typeof abs !== "string" || !path.isAbsolute(abs)) return abs;
79
+ let dir = abs;
80
+ while (!exists(dir)) {
81
+ const up = path.dirname(dir);
82
+ if (up === dir) return abs;
83
+ dir = up;
84
+ }
85
+ try {
86
+ return path.join(real(dir), path.relative(dir, abs));
87
+ } catch {
88
+ return abs;
89
+ }
90
+ }
91
+
92
+ /** A path an agent typed, resolved against the roots' cwd, in the roots' spelling. */
93
+ export function resolveTyped(roots: { cwd: string }, raw: string, p: typeof path = path): string {
94
+ return canonicalPath(p.resolve(roots.cwd, raw), p);
95
+ }
96
+
97
+ function firstString(...values: unknown[]): string {
98
+ for (const v of values) if (typeof v === "string" && v.trim()) return v.trim();
99
+ return "";
100
+ }
101
+
102
+ function asDir(value: string): string {
103
+ return value.startsWith("file:") ? fileURLToPath(value) : value;
104
+ }
105
+
106
+ function within(p: typeof path, top: string, dir: string): boolean {
107
+ const rel = p.relative(top, dir);
108
+ return !rel.startsWith("..") && !p.isAbsolute(rel);
109
+ }
110
+
111
+ /** Nearest dir from `dir` up to the git top level that holds the repo marker, else that top level. */
112
+ function checkoutIn(dir: string, facts: GitFacts | null, { p, exists, realpath }: RootsContext): string {
113
+ if (!facts) return dir;
114
+ const top = realpath(p.resolve(facts.top));
115
+ if (!within(p, top, dir)) return top;
116
+ for (let d = dir; ; d = p.dirname(d)) {
117
+ if (exists(p.join(d, INSTALL_MARKER))) return d;
118
+ if (d === top) return top;
119
+ }
120
+ }
121
+
122
+ function rootsFor(start: string, ctx: RootsContext): { checkout: string; root: string } {
123
+ const facts = ctx.git(start);
124
+ const checkout = checkoutIn(start, facts, ctx);
125
+ if (!facts) return { checkout, root: checkout };
126
+ const { p } = ctx;
127
+ const gitDir = ctx.realpath(p.resolve(facts.gitDir));
128
+ const commonDir = ctx.realpath(p.resolve(facts.commonDir));
129
+ if (gitDir === commonDir || p.basename(commonDir) !== ".git") return { checkout, root: checkout };
130
+ const main = p.dirname(commonDir);
131
+ return { checkout, root: p.join(main, p.relative(ctx.realpath(p.resolve(facts.top)), checkout)) };
132
+ }
133
+
134
+ /** Resolves roots from a hook input; reads workspaceRoot, workspace_root, workspacePaths, cwd and toolCall.args.Cwd. */
135
+ export function resolveRoots(
136
+ input: HookInput | null = {},
137
+ {
138
+ env = readEnv(),
139
+ cwd = process.cwd(),
140
+ git = gitRevParse,
141
+ p = path,
142
+ exists = fs.existsSync,
143
+ realpath = canonicalPath,
144
+ }: Partial<RootsContext> & { env?: Env; cwd?: string } = {},
145
+ ): Roots {
146
+ const data: HookInput = input && typeof input === "object" ? input : {};
147
+ const args = data.toolCall?.args ?? {};
148
+ const ctx: RootsContext = { git, p, exists, realpath };
149
+ const paths = Array.isArray(data.workspacePaths) ? data.workspacePaths : [];
150
+ const given = firstString(data.workspaceRoot, data.workspace_root, paths[0]);
151
+ // A workspace the harness sent beats the hook process cwd as where the agent works.
152
+ const hookCwd = realpath(p.resolve(cwd, asDir(firstString(data.cwd, args.Cwd, given) || ".")));
153
+ const candidate = firstString(given, env.GROK_WORKSPACE_ROOT, env.CLAUDE_PROJECT_DIR);
154
+ const start = candidate ? realpath(p.resolve(cwd, asDir(candidate))) : hookCwd;
155
+ return {
156
+ root: rootsFor(start, ctx).root,
157
+ checkout: rootsFor(hookCwd, ctx).checkout,
158
+ cwd: hookCwd,
159
+ };
160
+ }