@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,84 @@
1
+ /**
2
+ * Rule outputs. A pathless rule folds into `AGENTS.md`, which every host reads. A rule with `paths` is written where
3
+ * the host scopes it: `.claude/rules/` with a `paths:` list for claude and `.agents/rules/` with a glob trigger for
4
+ * antigravity. Codex, grok and cursor cannot scope, so when any of them is on, `AGENTS.md` ends with a
5
+ * `## Rules by path` list that points at each path rule's source file. `description` and `overrides` never render.
6
+ */
7
+ import type { Host } from "../../config.ts";
8
+ import { PACKAGE_NAME } from "../stock.ts";
9
+ import { emitYaml, finalize } from "./frontmatter.ts";
10
+ import type { Doc } from "./sources.ts";
11
+
12
+ /** Hosts that read only `AGENTS.md` and so need the path rules listed there. */
13
+ const UNSCOPED_HOSTS: readonly Host[] = ["codex", "grok", "cursor"];
14
+
15
+ /** The globs a rule applies to; none when it has no `paths` or they cover every file. */
16
+ export function scopeOf(doc: Doc): string[] {
17
+ const paths = (doc.data["paths"] as string[] | undefined) ?? [];
18
+ return paths.length === 1 && (paths[0] === "**/*" || paths[0] === "*") ? [] : paths;
19
+ }
20
+
21
+ /** Where a reader finds the rule's source from the repo root: stock lives in the installed package. */
22
+ const sourcePath = (doc: Doc) => (doc.origin === "repo" ? doc.rel : `node_modules/${PACKAGE_NAME}/${doc.rel}`);
23
+
24
+ /** One planned rule file: its text and the source it comes from, for a message when two scopes plan one path. */
25
+ export interface RuleFile {
26
+ text: string;
27
+ source: string;
28
+ }
29
+
30
+ /** The line that opens every package `AGENTS.md`, and is all of its `CLAUDE.md`. */
31
+ const PACKAGE_POINTER = "Start at references/INDEX.md in this package.";
32
+
33
+ /**
34
+ * The files of one scope's rules (`""` for the root and stock, a package dir for a package), given the hosts that are
35
+ * on, by POSIX path. The root `AGENTS.md` holds the pathless rules and a pointer per path rule; a package's holds the
36
+ * pointer line first, and its globs are prefixed with the dir. A package also gets a `CLAUDE.md` with that line alone
37
+ * when claude is on (claude then skips the package `AGENTS.md`, which its `.claude/rules` files already cover), and
38
+ * every one of its rules, pathless ones as `<dir>/**`, is written for claude and antigravity under `<dir>/`.
39
+ */
40
+ export function renderRules(rules: readonly Doc[], hosts: readonly Host[], scope: string): Map<string, RuleFile> {
41
+ const out = new Map<string, RuleFile>();
42
+ if (hosts.length === 0) return out;
43
+
44
+ const own = rules.filter((doc) => doc.scope === scope);
45
+ const globsOf = (doc: Doc) => scopeOf(doc).map((glob) => (scope === "" ? glob : `${scope}/${glob}`));
46
+ const pathless = own.filter((doc) => scopeOf(doc).length === 0);
47
+ const scoped = own.filter((doc) => scopeOf(doc).length > 0);
48
+ const sections = pathless.map((doc) => doc.body.trim()).filter((body) => body !== "");
49
+ if (scoped.length > 0 && hosts.some((host) => UNSCOPED_HOSTS.includes(host))) {
50
+ const lines = scoped.map((doc) => `- \`${globsOf(doc).join(" ")}\`: read \`${sourcePath(doc)}\``);
51
+ sections.push(`## Rules by path\n${lines.join("\n")}`);
52
+ }
53
+ const home = scope === "" ? "" : `${scope}/`;
54
+ const source = `${home}.repo/rules`;
55
+ out.set(`${home}AGENTS.md`, {
56
+ text: finalize((scope === "" ? sections : [PACKAGE_POINTER, ...sections]).join("\n\n")),
57
+ source,
58
+ });
59
+ if (scope !== "" && hosts.includes("claude"))
60
+ out.set(`${home}CLAUDE.md`, { text: finalize(PACKAGE_POINTER), source });
61
+
62
+ for (const doc of scope === "" ? scoped : own) {
63
+ const globs = scopeOf(doc).length === 0 ? [`${scope}/**`] : globsOf(doc);
64
+ const nested = scope === "" ? doc.file : `${scope}/${doc.file}`;
65
+ if (hosts.includes("claude")) {
66
+ out.set(`.claude/rules/${nested}`, {
67
+ text: finalize(`---\n${emitYaml([["paths", globs]])}---\n${doc.body}`),
68
+ source: doc.rel,
69
+ });
70
+ }
71
+ if (hosts.includes("antigravity")) {
72
+ out.set(`.agents/rules/${nested}`, {
73
+ text: finalize(
74
+ `---\n${emitYaml([
75
+ ["trigger", "glob"],
76
+ ["globs", globs.join(",")],
77
+ ])}---\n${doc.body}`,
78
+ ),
79
+ source: doc.rel,
80
+ });
81
+ }
82
+ }
83
+ return out;
84
+ }
@@ -0,0 +1,375 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { loadSources } from "./sources.ts";
6
+
7
+ const temps: string[] = [];
8
+ afterEach(() => {
9
+ for (const dir of temps.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
10
+ });
11
+
12
+ /** A temp root holding `files` (POSIX relative path → text). */
13
+ function tree(files: Record<string, string>): string {
14
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "workerstack-sources-"));
15
+ temps.push(root);
16
+ for (const [rel, text] of Object.entries(files)) {
17
+ fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
18
+ fs.writeFileSync(path.join(root, rel), text);
19
+ }
20
+ return root;
21
+ }
22
+
23
+ const doc = (name: string, extra = "") => `---\ndescription: ${name} desc\n${extra}---\n${name} body\n`;
24
+
25
+ type Roots = Parameters<typeof loadSources>[0];
26
+
27
+ /** The docs that loaded; any problem fails the test, so a case about sources cannot pass on a half load. */
28
+ function loaded(roots: Roots) {
29
+ const result = loadSources(roots);
30
+ expect(result.problems).toEqual([]);
31
+ return result.sources;
32
+ }
33
+
34
+ function problems(roots: Roots): string[] {
35
+ const result = loadSources(roots);
36
+ expect(result.problems).not.toEqual([]);
37
+ return result.problems;
38
+ }
39
+ const HOOKS = '{\n // stock\n "version": 1, "hooks": {}\n}\n';
40
+
41
+ function stock(extra: Record<string, string> = {}): string {
42
+ return tree({
43
+ "agents/hooks.jsonc": HOOKS,
44
+ "agents/rules/foo.md": doc("foo"),
45
+ "agents/rules/overview.md": doc("overview"),
46
+ "agents/subagents/scout.md": doc("scout"),
47
+ "agents/subagents/worker.md": doc("worker"),
48
+ ...extra,
49
+ });
50
+ }
51
+
52
+ describe("loadSources", () => {
53
+ it("reads stock alone when there is no consumer", () => {
54
+ const sources = loaded({ stockRoot: stock(), consumerRoot: null, packages: [] });
55
+ expect(sources.rules.map((d) => [d.rel, d.file, d.data, d.body])).toEqual([
56
+ ["agents/rules/foo.md", "foo.md", { description: "foo desc" }, "foo body"],
57
+ ["agents/rules/overview.md", "overview.md", { description: "overview desc" }, "overview body"],
58
+ ]);
59
+ expect(sources.subagents.map((d) => d.rel)).toEqual(["agents/subagents/scout.md", "agents/subagents/worker.md"]);
60
+ expect(sources.hooks).toEqual({ rel: "agents/hooks.jsonc", value: { version: 1, hooks: {} } });
61
+ });
62
+
63
+ it("replaces a stock file in place when it says overrides, case-folded, and appends new files sorted", () => {
64
+ const consumer = tree({
65
+ ".repo/rules/zeta.md": doc("zeta"),
66
+ ".repo/rules/Foo.md": doc("consumer-foo", "overrides: foo\n"),
67
+ ".repo/rules/alpha.md": doc("alpha"),
68
+ ".repo/subagents/scout.md": doc("consumer-scout", "overrides: scout\n"),
69
+ });
70
+ const sources = loaded({ stockRoot: stock(), consumerRoot: consumer, packages: [] });
71
+ expect(sources.rules.map((d) => [d.rel, d.file, d.body])).toEqual([
72
+ [".repo/rules/Foo.md", "Foo.md", "consumer-foo body"],
73
+ ["agents/rules/overview.md", "overview.md", "overview body"],
74
+ [".repo/rules/alpha.md", "alpha.md", "alpha body"],
75
+ [".repo/rules/zeta.md", "zeta.md", "zeta body"],
76
+ ]);
77
+ expect(sources.subagents.map((d) => [d.rel, d.body])).toEqual([
78
+ [".repo/subagents/scout.md", "consumer-scout body"],
79
+ ["agents/subagents/worker.md", "worker body"],
80
+ ]);
81
+ });
82
+
83
+ it("fails a same-name rule or subagent that does not say overrides", () => {
84
+ const consumer = tree({
85
+ ".repo/rules/Foo.md": doc("a"),
86
+ ".repo/subagents/worker.md": doc("b"),
87
+ ".repo/rules/fresh.md": doc("fresh"),
88
+ });
89
+ expect(problems({ stockRoot: stock(), consumerRoot: consumer, packages: [] })).toEqual([
90
+ ".repo/rules/Foo.md: a stock rule has this name; add `overrides: Foo` or rename it",
91
+ ".repo/subagents/worker.md: a stock subagent has this name; add `overrides: worker` or rename it",
92
+ ]);
93
+ });
94
+
95
+ it("names the nested path in the same-name error", () => {
96
+ const roots = {
97
+ stockRoot: stock({ "agents/rules/a/b.md": doc("ab") }),
98
+ consumerRoot: tree({ ".repo/rules/a/b.md": doc("x") }),
99
+ packages: [],
100
+ };
101
+ expect(problems(roots)).toEqual([
102
+ ".repo/rules/a/b.md: a stock rule has this name; add `overrides: a/b` or rename it",
103
+ ]);
104
+ });
105
+
106
+ it("fails an overrides that names nothing", () => {
107
+ const consumer = tree({
108
+ ".repo/rules/x.md": doc("x", "overrides: nope\n"),
109
+ ".repo/subagents/y.md": doc("y", "overrides: nada\n"),
110
+ });
111
+ expect(problems({ stockRoot: stock(), consumerRoot: consumer, packages: [] })).toEqual([
112
+ '.repo/rules/x.md: overrides "nope" but no stock rule has that name',
113
+ '.repo/subagents/y.md: overrides "nada" but no stock subagent has that name',
114
+ ]);
115
+ });
116
+
117
+ it("fails an overrides that is not a string", () => {
118
+ const consumer = tree({ ".repo/rules/x.md": doc("x", "overrides: [a]\n") });
119
+ expect(problems({ stockRoot: stock(), consumerRoot: consumer, packages: [] })).toEqual([
120
+ ".repo/rules/x.md: invalid frontmatter: overrides must be a string",
121
+ ]);
122
+ });
123
+
124
+ it("keeps nested paths POSIX and sorts them as plain strings", () => {
125
+ const sources = loaded({
126
+ stockRoot: stock({ "agents/rules/a/b.md": doc("ab"), "agents/rules/a.md": doc("a") }),
127
+ consumerRoot: tree({ ".repo/subagents/team/lead.md": doc("lead") }),
128
+ packages: [],
129
+ });
130
+ expect(sources.rules.map((d) => d.file)).toEqual(["a.md", "a/b.md", "foo.md", "overview.md"]);
131
+ expect(sources.subagents.at(-1)).toMatchObject({ rel: ".repo/subagents/team/lead.md", file: "team/lead.md" });
132
+ for (const d of [...sources.rules, ...sources.subagents]) expect(d.rel).not.toContain("\\");
133
+ });
134
+
135
+ it("skips non-markdown files and dot entries", () => {
136
+ const sources = loaded({
137
+ stockRoot: stock({ "agents/rules/notes.txt": "x", "agents/rules/.curated/c.md": doc("c") }),
138
+ consumerRoot: tree({ ".repo/rules/.hidden.md": "no frontmatter" }),
139
+ packages: [],
140
+ });
141
+ expect(sources.rules.map((d) => d.file)).toEqual(["foo.md", "overview.md"]);
142
+ });
143
+
144
+ it("takes .repo/hooks.jsonc whole over the stock one", () => {
145
+ const consumer = tree({ ".repo/hooks.jsonc": '{ "hooks": { "Stop": [] } /* consumer */ }' });
146
+ expect(loaded({ stockRoot: stock(), consumerRoot: consumer, packages: [] }).hooks).toEqual({
147
+ rel: ".repo/hooks.jsonc",
148
+ value: { hooks: { Stop: [] } },
149
+ });
150
+ });
151
+
152
+ it("uses the stock hooks when the consumer has none", () => {
153
+ expect(loaded({ stockRoot: stock(), consumerRoot: tree({}), packages: [] }).hooks.rel).toBe("agents/hooks.jsonc");
154
+ });
155
+
156
+ it.each([
157
+ ["broken JSONC", '{ "hooks": ', /^\.repo\/hooks\.jsonc: invalid JSONC: /],
158
+ ["trailing commas, which are unsupported", '{ "hooks": {}, }', /^\.repo\/hooks\.jsonc: invalid JSONC: /],
159
+ ["a non-object", "[]", /^\.repo\/hooks\.jsonc: invalid JSONC: expected an object$/],
160
+ ])("rejects %s in hooks", (_label, text, message) => {
161
+ const consumer = tree({ ".repo/hooks.jsonc": text });
162
+ const [first, ...rest] = problems({ stockRoot: stock(), consumerRoot: consumer, packages: [] });
163
+ expect(first).toMatch(message);
164
+ expect(rest).toEqual([]);
165
+ });
166
+
167
+ it("names the consumer file in a frontmatter error", () => {
168
+ const consumer = tree({ ".repo/rules/nofm.md": "no fence\n" });
169
+ expect(problems({ stockRoot: stock(), consumerRoot: consumer, packages: [] })).toEqual([
170
+ ".repo/rules/nofm.md: missing frontmatter (start the file with a --- block)",
171
+ ]);
172
+ });
173
+
174
+ it("collects every problem across files, rules and hooks instead of stopping at the first", () => {
175
+ const consumer = tree({
176
+ ".repo/rules/nofm.md": "no fence\n",
177
+ ".repo/rules/Foo.md": doc("foo"),
178
+ ".repo/rules/x.md": doc("x", "overrides: nope\n"),
179
+ ".repo/hooks.jsonc": "[]",
180
+ });
181
+ expect(problems({ stockRoot: stock(), consumerRoot: consumer, packages: [] })).toEqual([
182
+ ".repo/rules/nofm.md: missing frontmatter (start the file with a --- block)",
183
+ ".repo/rules/Foo.md: a stock rule has this name; add `overrides: Foo` or rename it",
184
+ '.repo/rules/x.md: overrides "nope" but no stock rule has that name',
185
+ ".repo/hooks.jsonc: invalid JSONC: expected an object",
186
+ ]);
187
+ });
188
+
189
+ it("reports a broken stock file too", () => {
190
+ expect(
191
+ problems({ stockRoot: stock({ "agents/rules/broken.md": "no fence\n" }), consumerRoot: null, packages: [] }),
192
+ ).toEqual(["agents/rules/broken.md: missing frontmatter (start the file with a --- block)"]);
193
+ });
194
+
195
+ it("requires a description on a rule but not on a subagent", () => {
196
+ const bare = "---\npaths: []\n---\nbody\n";
197
+ const consumer = tree({ ".repo/rules/bare.md": bare, ".repo/subagents/bare.md": "---\nname: bare\n---\nbody\n" });
198
+ expect(problems({ stockRoot: stock(), consumerRoot: consumer, packages: [] })).toEqual([
199
+ ".repo/rules/bare.md: invalid frontmatter: description is required",
200
+ ]);
201
+ });
202
+
203
+ it.each([
204
+ ["a string", "paths: src/**\n"],
205
+ ["a map", "paths:\n a: b\n"],
206
+ ["a list holding a non-string", "paths:\n - 3\n"],
207
+ ])("rejects paths that are %s", (_label, extra) => {
208
+ const consumer = tree({ ".repo/rules/p.md": doc("p", extra) });
209
+ expect(problems({ stockRoot: stock(), consumerRoot: consumer, packages: [] })).toEqual([
210
+ ".repo/rules/p.md: invalid frontmatter: paths must be a list of strings",
211
+ ]);
212
+ });
213
+
214
+ it("accepts paths as a list of strings", () => {
215
+ const consumer = tree({ ".repo/rules/p.md": doc("p", "paths:\n - src/**\n - lib/**\n") });
216
+ const sources = loaded({ stockRoot: stock(), consumerRoot: consumer, packages: [] });
217
+ expect(sources.rules.find((d) => d.file === "p.md")?.data["paths"]).toEqual(["src/**", "lib/**"]);
218
+ });
219
+
220
+ it("does not parse a stock file the consumer replaces", () => {
221
+ const sources = loaded({
222
+ stockRoot: stock({ "agents/rules/broken.md": "no fence\n" }),
223
+ consumerRoot: tree({ ".repo/rules/BROKEN.md": doc("fixed", "overrides: broken\n") }),
224
+ packages: [],
225
+ });
226
+ expect(sources.rules[0]!.rel).toBe(".repo/rules/BROKEN.md");
227
+ });
228
+
229
+ describe("frontmatter keys and body size", () => {
230
+ const rule = (extra = "", body = "body\n") => `---\ndescription: d\n${extra}---\n${body}`;
231
+ const agent = (extra = "", body = "body\n") => `---\ndescription: d\n${extra}---\n${body}`;
232
+ const withConsumer = (files: Record<string, string>) =>
233
+ problems({ stockRoot: stock(), consumerRoot: tree(files), packages: [] });
234
+
235
+ it("rejects a rule key outside description, paths and overrides, naming the keys to use", () => {
236
+ expect(withConsumer({ ".repo/rules/x.md": rule("glob: src/**\n") })).toEqual([
237
+ '.repo/rules/x.md: unknown frontmatter key "glob"; use description, paths, overrides',
238
+ ]);
239
+ });
240
+
241
+ it("rejects a name on a rule", () => {
242
+ expect(withConsumer({ ".repo/rules/x.md": rule("name: x\n") })).toEqual([
243
+ '.repo/rules/x.md: unknown frontmatter key "name"; use description, paths, overrides',
244
+ ]);
245
+ });
246
+
247
+ it("rejects an unknown subagent key and lists the known ones", () => {
248
+ const [problem] = withConsumer({ ".repo/subagents/x.md": agent("colour: red\n") });
249
+ expect(problem).toMatch(
250
+ /^\.repo\/subagents\/x\.md: unknown frontmatter key "colour"; use name, description, targets, overrides, tier, exec, claudecode, /,
251
+ );
252
+ expect(problem).toContain("cursor");
253
+ });
254
+
255
+ it("accepts the subagent keys and every host block, nested too", () => {
256
+ const extra =
257
+ "name: x\ntargets: [claude]\ntier: deep\nexec: fast\nclaudecode:\n model: opus\ncodexcli:\n a: b\n";
258
+ const consumer = tree({ ".repo/subagents/team/x.md": agent(extra) });
259
+ expect(
260
+ loaded({ stockRoot: stock(), consumerRoot: consumer, packages: [] }).subagents.some(
261
+ (d) => d.file === "team/x.md",
262
+ ),
263
+ ).toBe(true);
264
+ });
265
+
266
+ it("reports a missing description on a rule", () => {
267
+ expect(withConsumer({ ".repo/rules/x.md": "---\npaths: []\n---\nbody\n" })).toEqual([
268
+ ".repo/rules/x.md: invalid frontmatter: description is required",
269
+ ]);
270
+ });
271
+
272
+ it.each([
273
+ ["rule", ".repo/rules/x.md"],
274
+ ["subagent", ".repo/subagents/x.md"],
275
+ ])("rejects an empty %s body", (_kind, rel) => {
276
+ expect(withConsumer({ [rel]: rule("", " \n") })).toEqual([
277
+ `${rel}: body is empty; write the instructions below the frontmatter`,
278
+ ]);
279
+ });
280
+
281
+ it.each([
282
+ ["rule", ".repo/rules/x.md"],
283
+ ["subagent", ".repo/subagents/x.md"],
284
+ ])("rejects a %s body over 8 KiB and accepts exactly 8 KiB", (_kind, rel) => {
285
+ expect(withConsumer({ [rel]: rule("", `${"a".repeat(8193)}\n`) })).toEqual([
286
+ `${rel}: body is 8193 bytes, over the 8 KiB (8192 byte) limit; move detail into a skill or a reference`,
287
+ ]);
288
+ const ok = loaded({
289
+ stockRoot: stock(),
290
+ consumerRoot: tree({ [rel]: rule("", "a".repeat(8192)) }),
291
+ packages: [],
292
+ });
293
+ expect(ok.rules.length + ok.subagents.length).toBeGreaterThan(0);
294
+ });
295
+ });
296
+
297
+ describe("a load with problems", () => {
298
+ it("returns the docs that loaded beside the problems, leaving out a doc whose frontmatter failed and the hooks that did not parse", () => {
299
+ const consumer = tree({
300
+ ".repo/rules/good.md": doc("good"),
301
+ ".repo/rules/bad.md": "---\ncolour: red\n---\nbody\n",
302
+ ".repo/hooks.jsonc": "{ nope",
303
+ });
304
+ const result = loadSources({ stockRoot: stock(), consumerRoot: consumer, packages: [] });
305
+ expect(result.sources.rules.map((d) => d.file)).toEqual(["foo.md", "overview.md", "good.md"]);
306
+ expect(result.sources.hooks).toEqual({ rel: ".repo/hooks.jsonc", value: {} });
307
+ expect(result.problems).toHaveLength(2);
308
+ expect(result.problems[0]).toMatch(/^\.repo\/rules\/bad\.md: unknown frontmatter key "colour"/);
309
+ expect(result.problems[1]).toMatch(/^\.repo\/hooks\.jsonc: invalid JSONC/);
310
+ });
311
+ });
312
+ });
313
+
314
+ describe("package sources", () => {
315
+ const packageTree = () =>
316
+ tree({
317
+ ".repo/rules/root.md": doc("root"),
318
+ "packages/web/.repo/rules/ui/button.md": doc("button"),
319
+ "packages/web/.repo/rules/api.md": doc("api", "paths:\n - src/**\n"),
320
+ "packages/web/.repo/subagents/x.md": doc("x"),
321
+ "packages/api/.repo/rules/core.md": doc("core"),
322
+ });
323
+
324
+ it("gives every doc an origin and a scope: stock and root are scope empty, a package rule carries its dir", () => {
325
+ const consumer = packageTree();
326
+ const sources = loaded({ stockRoot: stock(), consumerRoot: consumer, packages: ["packages/web", "packages/api"] });
327
+ expect(sources.rules.map((d) => [d.rel, d.file, d.origin, d.scope])).toEqual([
328
+ ["agents/rules/foo.md", "foo.md", "stock", ""],
329
+ ["agents/rules/overview.md", "overview.md", "stock", ""],
330
+ [".repo/rules/root.md", "root.md", "repo", ""],
331
+ ["packages/web/.repo/rules/api.md", "api.md", "repo", "packages/web"],
332
+ ["packages/web/.repo/rules/ui/button.md", "ui/button.md", "repo", "packages/web"],
333
+ ["packages/api/.repo/rules/core.md", "core.md", "repo", "packages/api"],
334
+ ]);
335
+ expect(sources.subagents.map((d) => [d.origin, d.scope])).toEqual([
336
+ ["stock", ""],
337
+ ["stock", ""],
338
+ ]);
339
+ });
340
+
341
+ it("never merges a package rule with a root or stock rule of the same name", () => {
342
+ const consumer = tree({ "packages/web/.repo/rules/foo.md": doc("pkg-foo") });
343
+ const sources = loaded({ stockRoot: stock(), consumerRoot: consumer, packages: ["packages/web"] });
344
+ expect(sources.rules.map((d) => [d.rel, d.body])).toEqual([
345
+ ["agents/rules/foo.md", "foo body"],
346
+ ["agents/rules/overview.md", "overview body"],
347
+ ["packages/web/.repo/rules/foo.md", "pkg-foo body"],
348
+ ]);
349
+ });
350
+
351
+ it("reads packages from the stock root when there is no consumer root", () => {
352
+ const root = stock({ "packages/web/.repo/rules/p.md": doc("p") });
353
+ const sources = loaded({ stockRoot: root, consumerRoot: null, packages: ["packages/web"] });
354
+ expect(sources.rules.map((d) => d.scope)).toEqual(["", "", "packages/web"]);
355
+ });
356
+
357
+ it("refuses overrides in a package rule and leaves that rule out", () => {
358
+ const consumer = tree({
359
+ "packages/web/.repo/rules/y.md": doc("y", "overrides: foo\n"),
360
+ "packages/web/.repo/rules/z.md": doc("z"),
361
+ });
362
+ const result = loadSources({ stockRoot: stock(), consumerRoot: consumer, packages: ["packages/web"] });
363
+ expect(result.problems).toEqual([
364
+ "packages/web/.repo/rules/y.md: overrides has no meaning in a package rule; remove it",
365
+ ]);
366
+ expect(result.sources.rules.map((d) => d.file)).toEqual(["foo.md", "overview.md", "z.md"]);
367
+ });
368
+
369
+ it("reports a package rule with bad frontmatter", () => {
370
+ const consumer = tree({ "packages/web/.repo/rules/bad.md": "no fence\n" });
371
+ expect(problems({ stockRoot: stock(), consumerRoot: consumer, packages: ["packages/web"] })).toEqual([
372
+ "packages/web/.repo/rules/bad.md: missing frontmatter (start the file with a --- block)",
373
+ ]);
374
+ });
375
+ });