@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,401 @@
1
+ /**
2
+ * The four hook files, from the merged `hooks.jsonc`: `.claude/settings.json`, `.codex/hooks.json`,
3
+ * `.agents/hooks.json` and `.cursor/hooks.json`, in the shapes rulesync 43.7.0 wrote. Grok's hook configs are
4
+ * static stock files. Each host sees the shared events it supports in source order, then its own override block
5
+ * (`claudecode`, `codexcli`, `antigravity-cli`, `cursor`): a same event is replaced in place, new ones go last.
6
+ * `D:<n>` cites a line of rulesync 43.7.0's bundled `dist/import-*.js`.
7
+ */
8
+ import type { Host } from "../../config.ts";
9
+ import { expandHookCommand } from "../../scripts/hook-command.ts";
10
+ import type { Sources } from "./sources.ts";
11
+
12
+ type Json = Record<string, unknown>;
13
+ type Def = Json;
14
+ type Group = { matcher?: string; hooks: Json[] };
15
+
16
+ /** Canonical event to Claude event, D:6011-6044; also the events Claude supports. */
17
+ const CLAUDE_EVENTS: Record<string, string> = {
18
+ sessionStart: "SessionStart",
19
+ sessionEnd: "SessionEnd",
20
+ preToolUse: "PreToolUse",
21
+ postToolUse: "PostToolUse",
22
+ beforeSubmitPrompt: "UserPromptSubmit",
23
+ stop: "Stop",
24
+ subagentStop: "SubagentStop",
25
+ preCompact: "PreCompact",
26
+ permissionRequest: "PermissionRequest",
27
+ notification: "Notification",
28
+ setup: "Setup",
29
+ worktreeCreate: "WorktreeCreate",
30
+ worktreeRemove: "WorktreeRemove",
31
+ messageDisplay: "MessageDisplay",
32
+ instructionsLoaded: "InstructionsLoaded",
33
+ userPromptExpansion: "UserPromptExpansion",
34
+ postToolUseFailure: "PostToolUseFailure",
35
+ postToolBatch: "PostToolBatch",
36
+ permissionDenied: "PermissionDenied",
37
+ subagentStart: "SubagentStart",
38
+ taskCreated: "TaskCreated",
39
+ taskCompleted: "TaskCompleted",
40
+ stopFailure: "StopFailure",
41
+ teammateIdle: "TeammateIdle",
42
+ configChange: "ConfigChange",
43
+ cwdChanged: "CwdChanged",
44
+ fileChanged: "FileChanged",
45
+ directoryAdded: "DirectoryAdded",
46
+ postCompact: "PostCompact",
47
+ elicitation: "Elicitation",
48
+ elicitationResult: "ElicitationResult",
49
+ preModelSwitch: "PreModelSwitch",
50
+ postModelSwitch: "PostModelSwitch",
51
+ };
52
+ /** D:6525-6540. */
53
+ const CODEX_EVENTS: Record<string, string> = {
54
+ sessionStart: "SessionStart",
55
+ sessionEnd: "SessionEnd",
56
+ preToolUse: "PreToolUse",
57
+ postToolUse: "PostToolUse",
58
+ beforeSubmitPrompt: "UserPromptSubmit",
59
+ stop: "Stop",
60
+ stopCancelled: "Interrupt",
61
+ permissionRequest: "PermissionRequest",
62
+ subagentStart: "SubagentStart",
63
+ subagentStop: "SubagentStop",
64
+ preCompact: "PreCompact",
65
+ postCompact: "PostCompact",
66
+ preModelInvocation: "PreModelRequest",
67
+ postModelInvocation: "PostModelRequest",
68
+ };
69
+ /** D:6135-6142. */
70
+ const ANTIGRAVITY_EVENTS: Record<string, string> = {
71
+ sessionStart: "SessionStart",
72
+ preToolUse: "PreToolUse",
73
+ postToolUse: "PostToolUse",
74
+ preModelInvocation: "PreInvocation",
75
+ postModelInvocation: "PostInvocation",
76
+ stop: "Stop",
77
+ };
78
+ /** D:6162-6184: cursor keeps the canonical names. */
79
+ const CURSOR_EVENTS = Object.fromEntries(
80
+ [
81
+ "sessionStart",
82
+ "sessionEnd",
83
+ "preToolUse",
84
+ "postToolUse",
85
+ "beforeSubmitPrompt",
86
+ "stop",
87
+ "subagentStop",
88
+ "preCompact",
89
+ "postToolUseFailure",
90
+ "subagentStart",
91
+ "beforeShellExecution",
92
+ "afterShellExecution",
93
+ "beforeMCPExecution",
94
+ "afterMCPExecution",
95
+ "beforeReadFile",
96
+ "afterFileEdit",
97
+ "afterAgentResponse",
98
+ "afterAgentThought",
99
+ "beforeTabFileRead",
100
+ "afterTabFileEdit",
101
+ "workspaceOpen",
102
+ ].map((event) => [event, event]),
103
+ );
104
+ /** Whole matcher tokens Antigravity names differently, D:54345-54351. */
105
+ const ANTIGRAVITY_TOOLS: Record<string, string> = {
106
+ Bash: "run_command",
107
+ Read: "(?:read_file|view_file)",
108
+ Write: "(?:write_file|write_to_file)",
109
+ Edit: "(?:edit_file|replace_file_content)",
110
+ WebSearch: "search_web",
111
+ };
112
+ /** A name token outside escapes and character classes, D:52398. */
113
+ const MATCHER_TOKEN = /\\.|\[(?:\\.|[^\]\\])*\]|(?<=^|[^\w\\]|\\\w)[A-Za-z_]\w*(?!\w)/gs;
114
+ const CURSOR_TYPES = new Set(["command", "prompt"]);
115
+ const CLAUDE_SCHEMA = "https://json.schemastore.org/claude-code-settings.json";
116
+
117
+ /** How one matcher-group host converts a definition (the converter configs at D:55604-55631, D:56372-56378, D:54389-54398). */
118
+ /**
119
+ * One passthrough field (D:53298-53339): written when valid for its kind (D:53378-53385, `isStringArray` D:4415),
120
+ * and only on command hooks when `commandOnly`. `onFailure` is claude's keyword field (D:55667-55678, D:53527-53572):
121
+ * `failClosed: true` becomes `onFailure: "block"` on command and http hooks, except on the events claude ignores
122
+ * it on and on command hooks that set `async` or `asyncRewake`.
123
+ */
124
+ type Passthrough =
125
+ | { key: string; kind: "boolean" | "number" | "string" | "array"; commandOnly?: true }
126
+ | { key: "onFailure"; kind: "keyword" };
127
+
128
+ const VALID: Record<Exclude<Passthrough["kind"], "keyword">, (value: unknown) => boolean> = {
129
+ boolean: (value) => typeof value === "boolean",
130
+ number: (value) => Number.isFinite(value),
131
+ string: (value) => typeof value === "string" && value !== "",
132
+ array: (value) => Array.isArray(value) && value.every((item) => typeof item === "string"),
133
+ };
134
+ const ON_FAILURE_IGNORED = new Set(["stop", "subagentStop", "taskCompleted", "teammateIdle"]);
135
+
136
+ interface Target {
137
+ overrideKey: string;
138
+ names: Record<string, string>;
139
+ /** Events whose groups carry no matcher; translation is skipped there too (D:53195, D:53796). */
140
+ noMatcher: ReadonlySet<string>;
141
+ /** Hook types written; others are skipped (D:53520, D:53681). */
142
+ types: ReadonlySet<string>;
143
+ command?: (command: string, def: Def) => string;
144
+ /** Hook fields copied through, in the order rulesync writes them, ahead of `type` (D:53582-53640). */
145
+ passthrough: readonly Passthrough[];
146
+ matcher?: (matcher: string) => string;
147
+ /** Whether prompt and agent hooks carry `model` (D:55632 `emitsPromptModel`). */
148
+ promptModel: boolean;
149
+ /** Whether `name` and `description` are written (codex `passthroughFields`, D:56378). */
150
+ named: boolean;
151
+ }
152
+
153
+ const CLAUDE: Target = {
154
+ overrideKey: "claudecode",
155
+ names: CLAUDE_EVENTS,
156
+ noMatcher: new Set([
157
+ "worktreeCreate",
158
+ "worktreeRemove",
159
+ "messageDisplay",
160
+ "postToolBatch",
161
+ "taskCreated",
162
+ "taskCompleted",
163
+ "teammateIdle",
164
+ "cwdChanged",
165
+ "beforeSubmitPrompt",
166
+ "stop",
167
+ ]),
168
+ types: new Set(["command", "prompt", "http", "mcp_tool", "agent"]),
169
+ command: prefixProjectDir,
170
+ // D:55633-55684, in emit order: booleans, keyword, strings, arrays.
171
+ passthrough: [
172
+ { key: "once", kind: "boolean" },
173
+ { key: "async", kind: "boolean", commandOnly: true },
174
+ { key: "asyncRewake", kind: "boolean", commandOnly: true },
175
+ { key: "continueOnBlock", kind: "boolean" },
176
+ { key: "onFailure", kind: "keyword" },
177
+ { key: "if", kind: "string" },
178
+ { key: "statusMessage", kind: "string" },
179
+ { key: "shell", kind: "string", commandOnly: true },
180
+ { key: "args", kind: "array", commandOnly: true },
181
+ ],
182
+ promptModel: true,
183
+ named: false,
184
+ };
185
+ const CODEX: Target = {
186
+ overrideKey: "codexcli",
187
+ names: CODEX_EVENTS,
188
+ noMatcher: new Set(),
189
+ types: new Set(["command", "mcp_tool"]),
190
+ // D:56377-56394, in emit order: booleans, numbers, strings.
191
+ passthrough: [
192
+ { key: "async", kind: "boolean", commandOnly: true },
193
+ { key: "additionalContextLimit", kind: "number", commandOnly: true },
194
+ { key: "commandWindows", kind: "string", commandOnly: true },
195
+ { key: "statusMessage", kind: "string" },
196
+ ],
197
+ promptModel: false,
198
+ named: true,
199
+ };
200
+ /** D:54322-54327; these events are also written as bare handler lists (D:54405-54410). */
201
+ const ANTIGRAVITY_NO_MATCHER = new Set(["sessionStart", "preModelInvocation", "postModelInvocation", "stop"]);
202
+ const ANTIGRAVITY_FLAT = new Set([...ANTIGRAVITY_NO_MATCHER].map((event) => ANTIGRAVITY_EVENTS[event]));
203
+ const ANTIGRAVITY: Target = {
204
+ overrideKey: "antigravity-cli",
205
+ names: ANTIGRAVITY_EVENTS,
206
+ noMatcher: ANTIGRAVITY_NO_MATCHER,
207
+ types: new Set(["command"]),
208
+ passthrough: [],
209
+ matcher: (matcher) =>
210
+ matcher.replace(MATCHER_TOKEN, (token) =>
211
+ Object.hasOwn(ANTIGRAVITY_TOOLS, token) ? ANTIGRAVITY_TOOLS[token]! : token,
212
+ ),
213
+ promptModel: false,
214
+ named: false,
215
+ };
216
+
217
+ /** Path to text for each enabled host's hook file; `claudeBase` is the stock `agents/settings/claude.json`. */
218
+ export function renderHooks(sources: Sources, hosts: readonly Host[], claudeBase: string): Map<string, string> {
219
+ const config = sources.hooks.value;
220
+ const files = new Map<string, string>();
221
+ if (hosts.includes("claude")) {
222
+ files.set(".claude/settings.json", text(claudeSettings(claudeBase, toolHooks(config, CLAUDE))));
223
+ }
224
+ if (hosts.includes("codex"))
225
+ files.set(".codex/hooks.json", text({ hooks: filterCodexHandlers(toolHooks(config, CODEX)) }));
226
+ if (hosts.includes("antigravity")) {
227
+ const flat = Object.entries(toolHooks(config, ANTIGRAVITY)).map(([event, groups]) => [
228
+ event,
229
+ ANTIGRAVITY_FLAT.has(event) ? groups.flatMap((group) => group.hooks) : groups,
230
+ ]);
231
+ files.set(".agents/hooks.json", text(flat.length === 0 ? {} : { rulesync: Object.fromEntries(flat) }));
232
+ }
233
+ if (hosts.includes("cursor"))
234
+ files.set(".cursor/hooks.json", text({ version: config.version ?? 1, hooks: cursorHooks(config) }));
235
+ return files;
236
+ }
237
+
238
+ function text(value: unknown): string {
239
+ return `${JSON.stringify(value, null, 2)}\n`;
240
+ }
241
+
242
+ /** The host's name for an event; an override event it has none for keeps its own (`lookupOwn … ?? eventName`, D:53765-53768). */
243
+ function nameOf(names: Record<string, string>, event: string): string {
244
+ return Object.hasOwn(names, event) ? names[event]! : event;
245
+ }
246
+
247
+ /** Shared events the host names, in source order, then `{...shared, ...override}`; overrides are unfiltered (D:53170-53178). */
248
+ function effectiveEvents(config: Json, overrideKey: string, names: Record<string, string>): [string, Def[]][] {
249
+ const shared = Object.entries((config.hooks as Record<string, Def[]> | undefined) ?? {}).filter(([event]) =>
250
+ Object.hasOwn(names, event),
251
+ );
252
+ const override = (config[overrideKey] as { hooks?: Record<string, Def[]> } | undefined)?.hooks ?? {};
253
+ return Object.entries({ ...Object.fromEntries(shared), ...override });
254
+ }
255
+
256
+ /**
257
+ * Per event, defs grouped by (translated) matcher in first-appearance order (D:53186-53215): `{matcher, hooks}`,
258
+ * or `{hooks}` when the matcher is empty or the event takes none. Groups left without a writable hook, and events
259
+ * left without a group, are dropped (D:53756-53800).
260
+ */
261
+ function toolHooks(config: Json, target: Target): Record<string, Group[]> {
262
+ const out: Record<string, Group[]> = {};
263
+ for (const [event, defs] of effectiveEvents(config, target.overrideKey, target.names)) {
264
+ const noMatcher = target.noMatcher.has(event);
265
+ const groups = new Map<string, Def[]>();
266
+ for (const def of defs) {
267
+ const raw = (def.matcher as string | undefined) ?? "";
268
+ const matcher = raw === "" || noMatcher || target.matcher === undefined ? raw : target.matcher(raw);
269
+ groups.set(matcher, [...(groups.get(matcher) ?? []), def]);
270
+ }
271
+ const entries: Group[] = [];
272
+ for (const [matcher, group] of groups) {
273
+ const hooks = group
274
+ .filter((def) => target.types.has((def.type as string | undefined) ?? "command"))
275
+ .map((def) => hookObject(def, event, target));
276
+ if (hooks.length === 0) continue;
277
+ entries.push(matcher === "" || noMatcher ? { hooks } : { matcher, hooks });
278
+ }
279
+ if (entries.length > 0) out[nameOf(target.names, event)] = entries;
280
+ }
281
+ return out;
282
+ }
283
+
284
+ /**
285
+ * Passthrough fields, `type`, `command`, `timeout`, `prompt`, the type's payload, then codex's `name`/`description`
286
+ * (D:53698-53735, D:53598-53612).
287
+ */
288
+ function hookObject(def: Def, event: string, target: Target): Json {
289
+ const type = (def.type as string | undefined) ?? "command";
290
+ const source = typeof def.command === "string" ? expandHookCommand(def.command) : def.command;
291
+ const command = typeof source === "string" && target.command !== undefined ? target.command(source, def) : source;
292
+ const payload: Record<string, readonly string[]> = {
293
+ http: ["url", "headers", "allowedEnvVars"],
294
+ mcp_tool: ["server", "tool", "input"],
295
+ prompt: target.promptModel ? ["model"] : [],
296
+ agent: target.promptModel ? ["model"] : [],
297
+ };
298
+ const fields = {
299
+ ...passthroughFields(def, type, event, target),
300
+ type,
301
+ command,
302
+ timeout: def.timeout,
303
+ prompt: def.prompt,
304
+ } as Json;
305
+ for (const key of [...(payload[type] ?? []), ...(target.named ? ["name", "description"] : [])])
306
+ fields[key] = def[key];
307
+ return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined && value !== null));
308
+ }
309
+
310
+ function passthroughFields(def: Def, type: string, event: string, target: Target): Json {
311
+ const out: Json = {};
312
+ for (const field of target.passthrough) {
313
+ if (field.kind === "keyword") {
314
+ const blocked = type === "command" && (def.async === true || def.asyncRewake === true);
315
+ const applies = (type === "command" || type === "http") && !ON_FAILURE_IGNORED.has(event) && !blocked;
316
+ if (def.failClosed === true && applies) out.onFailure = "block";
317
+ } else if (VALID[field.kind](def[field.key]) && (field.commandOnly !== true || type === "command")) {
318
+ out[field.key] = def[field.key];
319
+ }
320
+ }
321
+ return out;
322
+ }
323
+
324
+ /**
325
+ * Deliberate divergence from rulesync: only a leading `./` (optionally quoted) gets the project dir; script
326
+ * operands later in the command are left alone. A shell command gets `"$CLAUDE_PROJECT_DIR"/`; an exec-form
327
+ * hook (`args` is an array) gets `${CLAUDE_PROJECT_DIR}/` with the surrounding quotes stripped (D:53245-53266).
328
+ */
329
+ function prefixProjectDir(command: string, def: Def): string {
330
+ const lead = /^(['"]?)\.\/(.*)$/s.exec(command);
331
+ if (lead === null) return command;
332
+ const relative = `${lead[1]}${lead[2]}`;
333
+ if (!Array.isArray(def.args)) return `"$CLAUDE_PROJECT_DIR"/${relative}`;
334
+ return `\${CLAUDE_PROJECT_DIR}/${relative.replace(/^(["'])(.*)\1$/, "$2").replace(/^["']/, "")}`;
335
+ }
336
+
337
+ /** Why Codex would refuse a handler, or undefined (`describeRejectedHandler`, D:56419-56429). */
338
+ function codexRejects(hook: Json): boolean {
339
+ const { timeout } = hook;
340
+ if (timeout !== undefined && !(Number.isSafeInteger(timeout) && (timeout as number) >= 0)) return true;
341
+ if (hook.type !== "mcp_tool") return false;
342
+ return (
343
+ [hook.server, hook.tool].some((value) => typeof value !== "string" || value === "") || containsNull(hook.input)
344
+ );
345
+ }
346
+
347
+ function containsNull(value: unknown): boolean {
348
+ if (value === null) return true;
349
+ return typeof value === "object" && Object.values(value as object).some(containsNull);
350
+ }
351
+
352
+ /** Drops handlers Codex rejects, then emptied groups and events (`filterCodexHandlers`, D:56436-56462). */
353
+ function filterCodexHandlers(hooks: Record<string, Group[]>): Record<string, Group[]> {
354
+ const out: Record<string, Group[]> = {};
355
+ for (const [event, groups] of Object.entries(hooks)) {
356
+ const kept = groups
357
+ .map((group) => ({ ...group, hooks: group.hooks.filter((hook) => !codexRejects(hook)) }))
358
+ .filter((group) => group.hooks.length > 0);
359
+ if (kept.length > 0) out[event] = kept;
360
+ }
361
+ return out;
362
+ }
363
+
364
+ /** The stock settings with `hooks` replaced in place (else appended) and `$schema` first when missing. */
365
+ function claudeSettings(base: string, hooks: Json): Json {
366
+ const settings: Json = { ...(JSON.parse(base) as Json), hooks };
367
+ return "$schema" in settings ? settings : { $schema: CLAUDE_SCHEMA, ...settings };
368
+ }
369
+
370
+ /**
371
+ * Cursor's flat entries, no grouping (D:57912-57936): command and prompt handlers only, `type` only when the
372
+ * source has one, `model` only on prompt handlers; events left empty are dropped.
373
+ */
374
+ function cursorHooks(config: Json): Json {
375
+ const out: Json = {};
376
+ for (const [event, defs] of effectiveEvents(config, "cursor", CURSOR_EVENTS)) {
377
+ const mapped = defs
378
+ .filter((def) => CURSOR_TYPES.has((def.type as string | undefined) ?? "command"))
379
+ .map((def) => {
380
+ const { type, command, timeout, loop_limit, matcher, prompt, model, failClosed } = def;
381
+ const entry = {
382
+ type,
383
+ command: typeof command === "string" ? expandHookCommand(command) : command,
384
+ timeout,
385
+ loop_limit,
386
+ matcher,
387
+ prompt,
388
+ model: type === "prompt" ? model : undefined,
389
+ failClosed,
390
+ };
391
+ // `loop_limit` alone is written when null.
392
+ return Object.fromEntries(
393
+ Object.entries(entry).filter(
394
+ ([key, value]) => value !== undefined && (value !== null || key === "loop_limit"),
395
+ ),
396
+ );
397
+ });
398
+ if (mapped.length > 0) out[nameOf(CURSOR_EVENTS, event)] = mapped;
399
+ }
400
+ return out;
401
+ }
@@ -0,0 +1,229 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { describe, expect, it } from "vitest";
5
+ import { HOSTS, type Host } from "../../config.ts";
6
+ import { readGoldens } from "../../../dev/capture-tree-goldens.ts";
7
+ import { renderRules } from "./rules.ts";
8
+ import { renderSubagents } from "./subagents.ts";
9
+ import { type Doc, type Sources, loadSources } from "./sources.ts";
10
+
11
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
12
+ const FIXTURES = path.join(ROOT, "cli", "sync", "fixtures");
13
+
14
+ const isRulesPath = (rel: string) =>
15
+ rel === "AGENTS.md" ||
16
+ rel.startsWith(".agents/rules/") ||
17
+ rel.startsWith(".claude/rules/") ||
18
+ rel.startsWith(".grok/rules/");
19
+
20
+ function fixtureSources(): Sources {
21
+ return loadSources({
22
+ stockRoot: path.join(FIXTURES, "consumer-stock"),
23
+ consumerRoot: path.join(FIXTURES, "consumer"),
24
+ packages: [],
25
+ }).sources;
26
+ }
27
+
28
+ function rule(file: string, data: Record<string, unknown>, body: string, dir = ".repo"): Doc {
29
+ return {
30
+ rel: `${dir}/rules/${file}`,
31
+ file,
32
+ origin: dir === "agents" ? "stock" : "repo",
33
+ scope: "",
34
+ data: { description: "d", ...data },
35
+ body,
36
+ };
37
+ }
38
+
39
+ function sourcesOf(rules: Doc[]): Sources {
40
+ return { rules, subagents: [], hooks: { rel: "agents/hooks.jsonc", value: {} } };
41
+ }
42
+
43
+ /** The text of every file `renderRules` plans for the root scope, or for `scope`. */
44
+ function renderText(sources: Sources, hosts: readonly Host[], scope = ""): Map<string, string> {
45
+ return new Map([...renderRules(sources.rules, hosts, scope)].map(([rel, file]) => [rel, file.text]));
46
+ }
47
+
48
+ const BASE = rule("overview.md", {}, "Base body.", "agents");
49
+ const SCOPED = rule("testing.md", { paths: ["src/**", "lib/**"] }, "Test body.");
50
+
51
+ describe("renderRules", () => {
52
+ it("matches the consumer fixture goldens byte for byte, path set included", () => {
53
+ const goldens = new Map([...readGoldens()].filter(([rel]) => isRulesPath(rel)));
54
+ const out = renderText(fixtureSources(), HOSTS);
55
+ expect([...out.keys()].sort()).toEqual([...goldens.keys()].sort());
56
+ for (const [rel, text] of goldens) expect(out.get(rel), rel).toBe(text);
57
+ });
58
+
59
+ it("renders this repo's committed AGENTS.md from its stock alone", () => {
60
+ const out = renderText(loadSources({ stockRoot: ROOT, consumerRoot: null, packages: [] }).sources, HOSTS);
61
+ expect(out.get("AGENTS.md")).toBe(fs.readFileSync(path.join(ROOT, "AGENTS.md"), "utf8"));
62
+ });
63
+
64
+ it("emits nothing with no host on", () => {
65
+ expect(renderText(fixtureSources(), []).size).toBe(0);
66
+ });
67
+
68
+ it("folds pathless rules into AGENTS.md in source order and writes no file for them", () => {
69
+ const out = renderText(sourcesOf([BASE, rule("b.md", {}, "B."), rule("a.md", {}, "A.")]), HOSTS);
70
+ expect([...out.keys()]).toEqual(["AGENTS.md"]);
71
+ expect(out.get("AGENTS.md")).toBe("Base body.\n\nB.\n\nA.\n");
72
+ });
73
+
74
+ it.each([[["**/*"]], [["*"]], [[]]])("treats paths %j as pathless", (paths) => {
75
+ const out = renderText(sourcesOf([BASE, rule("p.md", { paths }, "P.")]), HOSTS);
76
+ expect([...out.keys()]).toEqual(["AGENTS.md"]);
77
+ expect(out.get("AGENTS.md")).toBe("Base body.\n\nP.\n");
78
+ });
79
+
80
+ it("renders a path rule for claude with a paths list and nothing else", () => {
81
+ const out = renderText(
82
+ sourcesOf([BASE, rule("testing.md", { paths: ["src/**", "lib/**"], overrides: "x" }, "Test body.")]),
83
+ ["claude"],
84
+ );
85
+ expect(out.get(".claude/rules/testing.md")).toBe("---\npaths:\n - src/**\n - lib/**\n---\nTest body.\n");
86
+ expect([...out.keys()].sort()).toEqual([".claude/rules/testing.md", "AGENTS.md"]);
87
+ });
88
+
89
+ it("renders a path rule for antigravity as a glob trigger", () => {
90
+ const out = renderText(sourcesOf([BASE, SCOPED]), ["antigravity"]);
91
+ expect(out.get(".agents/rules/testing.md")).toBe("---\ntrigger: glob\nglobs: src/**,lib/**\n---\nTest body.\n");
92
+ expect([...out.keys()].sort()).toEqual([".agents/rules/testing.md", "AGENTS.md"]);
93
+ });
94
+
95
+ it("keeps nested rule paths", () => {
96
+ const out = renderText(sourcesOf([BASE, rule("x/y.md", { paths: ["x/**"] }, "Y.")]), ["claude", "antigravity"]);
97
+ expect(out.has(".claude/rules/x/y.md")).toBe(true);
98
+ expect(out.has(".agents/rules/x/y.md")).toBe(true);
99
+ });
100
+
101
+ it.each<Host>(["codex", "grok", "cursor"])(
102
+ "lists a path rule in AGENTS.md when %s is on, without a rule file",
103
+ (host) => {
104
+ const out = renderText(sourcesOf([BASE, SCOPED]), [host]);
105
+ expect([...out.keys()]).toEqual(["AGENTS.md"]);
106
+ expect(out.get("AGENTS.md")).toBe(
107
+ "Base body.\n\n## Rules by path\n- `src/** lib/**`: read `.repo/rules/testing.md`\n",
108
+ );
109
+ },
110
+ );
111
+
112
+ it("points a stock path rule into the installed package", () => {
113
+ const stock = rule("style.md", { paths: ["**/*.ts"] }, "S.", "agents");
114
+ const out = renderText(sourcesOf([BASE, stock]), ["codex"]);
115
+ expect(out.get("AGENTS.md")).toContain(
116
+ "- `**/*.ts`: read `node_modules/@workerstack/repo/agents/rules/style.md`\n",
117
+ );
118
+ });
119
+
120
+ it("leaves the pointer list out when only claude or antigravity is on", () => {
121
+ for (const host of ["claude", "antigravity"] as const) {
122
+ expect(renderText(sourcesOf([BASE, SCOPED]), [host]).get("AGENTS.md")).toBe("Base body.\n");
123
+ }
124
+ });
125
+
126
+ it("leaves the pointer list out when no rule has paths", () => {
127
+ expect(renderText(sourcesOf([BASE]), ["codex"]).get("AGENTS.md")).toBe("Base body.\n");
128
+ });
129
+
130
+ it("writes no .grok/rules for any host set", () => {
131
+ expect([...renderText(sourcesOf([BASE, SCOPED]), HOSTS).keys()].filter((rel) => rel.startsWith(".grok/"))).toEqual(
132
+ [],
133
+ );
134
+ });
135
+
136
+ it("lists every path rule once, in source order", () => {
137
+ const out = renderText(sourcesOf([BASE, SCOPED, rule("b.md", { paths: ["b/**"] }, "B.")]), HOSTS);
138
+ const list = out.get("AGENTS.md")!.split("## Rules by path\n")[1]!;
139
+ expect(list).toBe("- `src/** lib/**`: read `.repo/rules/testing.md`\n- `b/**`: read `.repo/rules/b.md`\n");
140
+ });
141
+ });
142
+
143
+ describe("overrides in rendered output", () => {
144
+ it("is stripped from every host's subagent file", () => {
145
+ const scout: Doc = {
146
+ rel: ".repo/subagents/scout.md",
147
+ file: "scout.md",
148
+ origin: "repo",
149
+ scope: "",
150
+ data: { name: "scout", description: "Scout.", overrides: "scout" },
151
+ body: "Body.",
152
+ };
153
+ const out = renderSubagents({ ...sourcesOf([]), subagents: [scout] }, HOSTS);
154
+ expect(out.size).toBe(HOSTS.length);
155
+ for (const [rel, text] of out) expect(text, rel).not.toContain("overrides");
156
+ });
157
+ });
158
+
159
+ describe("renderRules for a package scope", () => {
160
+ const WEB = "packages/web";
161
+ const pkgRule = (file: string, data: Record<string, unknown>, body: string): Doc => ({
162
+ rel: `${WEB}/.repo/rules/${file}`,
163
+ file,
164
+ origin: "repo",
165
+ scope: WEB,
166
+ data: { description: "d", ...data },
167
+ body,
168
+ });
169
+ const POINTER = "Start at references/INDEX.md in this package.";
170
+ const COMPONENTS = pkgRule("components.md", {}, "Components live in src/components and use the ui kit.");
171
+ const API = pkgRule("api.md", { paths: ["src/api/**"] }, "Api body.");
172
+ const sources = sourcesOf([BASE, SCOPED, COMPONENTS, API]);
173
+
174
+ it("writes the package AGENTS.md: the pointer line, its pathless rules, then a pointer per path rule", () => {
175
+ const out = renderText(sources, ["codex"], WEB);
176
+ expect(out.get(`${WEB}/AGENTS.md`)).toBe(
177
+ `${POINTER}\n\nComponents live in src/components and use the ui kit.\n\n## Rules by path\n- \`packages/web/src/api/**\`: read \`packages/web/.repo/rules/api.md\`\n`,
178
+ );
179
+ expect([...out.keys()]).toEqual([`${WEB}/AGENTS.md`]);
180
+ });
181
+
182
+ it("leaves the pointer list out when no host that needs it is on, and the root AGENTS.md has no package rule", () => {
183
+ expect(renderText(sources, ["claude"], WEB).get(`${WEB}/AGENTS.md`)).toBe(
184
+ `${POINTER}\n\nComponents live in src/components and use the ui kit.\n`,
185
+ );
186
+ const root = renderText(sources, HOSTS, "");
187
+ for (const [rel, text] of root) expect(`${rel}\n${text}`, rel).not.toMatch(/Components live|packages\/web/);
188
+ });
189
+
190
+ it("writes CLAUDE.md with the pointer line alone, only when claude is on", () => {
191
+ expect(renderText(sources, ["claude"], WEB).get(`${WEB}/CLAUDE.md`)).toBe(`${POINTER}\n`);
192
+ expect(renderText(sources, ["codex", "antigravity", "grok", "cursor"], WEB).has(`${WEB}/CLAUDE.md`)).toBe(false);
193
+ });
194
+
195
+ it("renders every package rule for claude under its dir, a pathless one scoped to the whole package", () => {
196
+ const out = renderText(sources, ["claude"], WEB);
197
+ expect(out.get(".claude/rules/packages/web/components.md")).toBe(
198
+ "---\npaths:\n - packages/web/**\n---\nComponents live in src/components and use the ui kit.\n",
199
+ );
200
+ expect(out.get(".claude/rules/packages/web/api.md")).toBe(
201
+ "---\npaths:\n - packages/web/src/api/**\n---\nApi body.\n",
202
+ );
203
+ });
204
+
205
+ it("renders every package rule for antigravity under its dir as a glob trigger", () => {
206
+ const out = renderText(pkgSources(), ["antigravity"], WEB);
207
+ expect(out.get(".agents/rules/packages/web/components.md")).toBe(
208
+ "---\ntrigger: glob\nglobs: packages/web/**\n---\nComponents live in src/components and use the ui kit.\n",
209
+ );
210
+ expect(out.get(".agents/rules/packages/web/multi.md")).toBe(
211
+ "---\ntrigger: glob\nglobs: packages/web/a/**,packages/web/b/**\n---\nM.\n",
212
+ );
213
+ expect(out.has(`${WEB}/CLAUDE.md`)).toBe(false);
214
+ });
215
+
216
+ function pkgSources(): Sources {
217
+ return sourcesOf([COMPONENTS, pkgRule("multi.md", { paths: ["a/**", "b/**"] }, "M.")]);
218
+ }
219
+
220
+ it("names the rule file's source, and renders a package without rules as the pointer line", () => {
221
+ const rendered = renderRules(sources.rules, ["claude"], WEB);
222
+ expect(rendered.get(".claude/rules/packages/web/api.md")?.source).toBe(`${WEB}/.repo/rules/api.md`);
223
+ expect(renderText(sources, ["claude"], "packages/empty").get("packages/empty/AGENTS.md")).toBe(`${POINTER}\n`);
224
+ });
225
+
226
+ it("emits nothing with no host on", () => {
227
+ expect(renderRules(sources.rules, [], WEB).size).toBe(0);
228
+ });
229
+ });