@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,63 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { context } from "../fixtures/release-context.ts";
3
+ import { nightly } from "./nightly.ts";
4
+ import type { SyncContext } from "./outputs.ts";
5
+
6
+ /** The nightly.yml text for `shards`, `secrets` and a root manifest with or without `test:live`. */
7
+ function render(shards: number, secrets: string[] = [], live = false): string {
8
+ const base = context(["release"], live ? { scripts: { "test:live": "vitest run -c live.config.ts" } } : {});
9
+ const ctx: SyncContext = { ...base, config: { ...base.config, nightly: { shards, secrets } } };
10
+ const [output] = nightly(ctx).outputs;
11
+ if (output?.kind !== "file") throw new Error("no nightly.yml");
12
+ return output.content;
13
+ }
14
+
15
+ const liveJob = (text: string) => text.slice(text.indexOf(" live:"));
16
+
17
+ describe("nightly jobs", () => {
18
+ it("fans the mutate job out over every shard of the config", () => {
19
+ const text = render(8);
20
+ expect(text).toContain("name: mutate (${{ matrix.shard }}/8)");
21
+ expect(text).toContain("shard: [1, 2, 3, 4, 5, 6, 7, 8]");
22
+ expect(text).toContain('run: bun repo mutate --shard "$SHARD/8" --report "mutation-$SHARD.json"');
23
+ });
24
+
25
+ it("names each upload by its shard and downloads by the same prefix", () => {
26
+ const text = render(3);
27
+ expect(text).toContain("name: mutation-${{ matrix.shard }}\n path: mutation-${{ matrix.shard }}.json");
28
+ expect(text).toContain("pattern: mutation-*\n merge-multiple: true");
29
+ });
30
+ });
31
+
32
+ describe("nightly live job", () => {
33
+ it("has no live job without a test:live script, secrets or not", () => {
34
+ expect(render(1)).not.toContain("live");
35
+ expect(render(1, ["API_TOKEN"])).not.toContain("live");
36
+ });
37
+
38
+ it("adds a live job with a 40-minute limit that runs test:live", () => {
39
+ const job = liveJob(render(1, [], true));
40
+ expect(job).toContain("timeout-minutes: 40");
41
+ expect(job).toContain(" - run: bun run test:live\n");
42
+ expect(job).not.toContain("env:");
43
+ expect(job).not.toContain("needs:");
44
+ });
45
+
46
+ it("maps each secret by name in the live job's env, never inside run", () => {
47
+ const job = liveJob(render(1, ["API_TOKEN", "OTHER_KEY"], true));
48
+ expect(job).toContain(
49
+ " env:\n API_TOKEN: ${{ secrets.API_TOKEN }}\n OTHER_KEY: ${{ secrets.OTHER_KEY }}\n",
50
+ );
51
+ expect(job).not.toMatch(/run:.*secrets/);
52
+ });
53
+
54
+ it("never triggers on a push or a pull request", () => {
55
+ expect(render(1)).not.toMatch(/push|pull_request/);
56
+ });
57
+
58
+ it("pins every action to a full commit SHA", () => {
59
+ const uses = render(2, [], true).match(/uses: \S+/g) ?? [];
60
+ expect(uses.length).toBeGreaterThan(0);
61
+ for (const line of uses) expect(line).toMatch(/@[0-9a-f]{40}$/);
62
+ });
63
+ });
@@ -0,0 +1,48 @@
1
+ import { CI_HEADER, DOWNLOAD, UPLOAD } from "./release.constants.ts";
2
+
3
+ /** The generated header, which must be the first line of a nightly.yml sync owns. */
4
+ export const NIGHTLY_HEADER: string = CI_HEADER;
5
+ export const NIGHTLY_PATH: string = ".github/workflows/nightly.yml";
6
+ export const LIVE_SCRIPT: string = "test:live";
7
+ /** A mutation shard runs about 30 to 40 minutes on 8 shards; one shard is the whole run. */
8
+ export const MUTATE_TIMEOUT_MINUTES: number = 180;
9
+ /** The report a shard writes, named by its number; the floors job downloads them all by the prefix. */
10
+ const REPORT_PREFIX: string = "mutation-";
11
+ const REPORTS_DIR: string = "reports";
12
+
13
+ /** The workflow text before the jobs: name, triggers, concurrency and permissions. */
14
+ export const PREAMBLE: string = `name: nightly
15
+
16
+ on:
17
+ schedule:
18
+ - cron: "0 3 * * *"
19
+ workflow_dispatch:
20
+
21
+ concurrency:
22
+ group: nightly
23
+ cancel-in-progress: false
24
+
25
+ permissions:
26
+ contents: read
27
+
28
+ jobs:`;
29
+
30
+ /** The mutate job's steps after the install; `%TOTAL%` is the shard count. */
31
+ export const MUTATE_STEPS: string = ` - name: Mutate this shard
32
+ env:
33
+ SHARD: \${{ matrix.shard }}
34
+ run: bun repo mutate --shard "$SHARD/%TOTAL%" --report "${REPORT_PREFIX}$SHARD.json"
35
+ - uses: ${UPLOAD}
36
+ if: always()
37
+ with:
38
+ name: ${REPORT_PREFIX}\${{ matrix.shard }}
39
+ path: ${REPORT_PREFIX}\${{ matrix.shard }}.json
40
+ if-no-files-found: error`;
41
+
42
+ /** The floors job's steps after the install: every shard's report is downloaded into one directory and scored. */
43
+ export const FLOORS_STEPS: string = ` - uses: ${DOWNLOAD}
44
+ with:
45
+ pattern: ${REPORT_PREFIX}*
46
+ merge-multiple: true
47
+ path: ${REPORTS_DIR}
48
+ - run: bun repo mutate check ${REPORTS_DIR}`;
@@ -0,0 +1,100 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { HEADER, context } from "../fixtures/release-context.ts";
3
+ import { GENERATORS } from "./outputs.ts";
4
+ import { nightly } from "./nightly.ts";
5
+
6
+ const PATH = ".github/workflows/nightly.yml";
7
+ const CHECKOUT = "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1";
8
+ const SETUP_BUN = "oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0";
9
+ const UPLOAD = "actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2";
10
+ const DOWNLOAD = "actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2";
11
+
12
+ const ONE_SHARD = `${HEADER}
13
+ name: nightly
14
+
15
+ on:
16
+ schedule:
17
+ - cron: "0 3 * * *"
18
+ workflow_dispatch:
19
+
20
+ concurrency:
21
+ group: nightly
22
+ cancel-in-progress: false
23
+
24
+ permissions:
25
+ contents: read
26
+
27
+ jobs:
28
+ mutate:
29
+ name: mutate (\${{ matrix.shard }}/1)
30
+ runs-on: ubuntu-latest
31
+ timeout-minutes: 180
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ shard: [1]
36
+ steps:
37
+ - uses: ${CHECKOUT}
38
+ - uses: ${SETUP_BUN}
39
+ with:
40
+ bun-version-file: package.json
41
+ - run: bun install --frozen-lockfile
42
+ - name: Mutate this shard
43
+ env:
44
+ SHARD: \${{ matrix.shard }}
45
+ run: bun repo mutate --shard "$SHARD/1" --report "mutation-$SHARD.json"
46
+ - uses: ${UPLOAD}
47
+ if: always()
48
+ with:
49
+ name: mutation-\${{ matrix.shard }}
50
+ path: mutation-\${{ matrix.shard }}.json
51
+ if-no-files-found: error
52
+
53
+ mutate-floors:
54
+ name: mutate-floors
55
+ needs: mutate
56
+ runs-on: ubuntu-latest
57
+ timeout-minutes: 30
58
+ steps:
59
+ - uses: ${CHECKOUT}
60
+ - uses: ${SETUP_BUN}
61
+ with:
62
+ bun-version-file: package.json
63
+ - run: bun install --frozen-lockfile
64
+ - uses: ${DOWNLOAD}
65
+ with:
66
+ pattern: mutation-*
67
+ merge-multiple: true
68
+ path: reports
69
+ - run: bun repo mutate check reports
70
+ `;
71
+
72
+ describe("nightly", () => {
73
+ it("is a generator", () => {
74
+ expect(GENERATORS).toContain(nightly);
75
+ });
76
+
77
+ it("writes nothing with the pack off", () => {
78
+ expect(nightly(context(["agents", "standards", "docs"]))).toEqual({ outputs: [], errors: [] });
79
+ });
80
+
81
+ it("writes the whole nightly.yml for one shard and no live script", () => {
82
+ expect(nightly(context(["release"]))).toEqual({
83
+ outputs: [{ kind: "file", path: PATH, content: ONE_SHARD, tracked: true, managed: "whole" }],
84
+ errors: [],
85
+ });
86
+ });
87
+
88
+ it("rewrites a nightly.yml that has the generated header on its first line", () => {
89
+ const result = nightly(context(["release"], {}, { [PATH]: `${HEADER}\nname: old\n` }));
90
+ expect(result.errors).toEqual([]);
91
+ expect(result.outputs).toHaveLength(1);
92
+ });
93
+
94
+ it("refuses a hand-written nightly.yml", () => {
95
+ expect(nightly(context(["release"], {}, { [PATH]: "name: nightly\n" }))).toEqual({
96
+ outputs: [],
97
+ errors: [".github/workflows/nightly.yml is hand-written; delete it so repo sync can own it"],
98
+ });
99
+ });
100
+ });
@@ -0,0 +1,75 @@
1
+ import { lines } from "../scripts/managed-text.ts";
2
+ import type { Generator, SyncContext } from "./outputs.ts";
3
+ import {
4
+ FLOORS_STEPS,
5
+ LIVE_SCRIPT,
6
+ MUTATE_STEPS,
7
+ MUTATE_TIMEOUT_MINUTES,
8
+ NIGHTLY_HEADER,
9
+ NIGHTLY_PATH,
10
+ PREAMBLE,
11
+ } from "./nightly.constants.ts";
12
+ import { hasScript } from "./release.ts";
13
+ import {
14
+ CHECKOUT_STEP,
15
+ INSTALL_STEP,
16
+ SETUP_BUN_STEP,
17
+ SLOW_TIMEOUT_MINUTES,
18
+ TEST_TIMEOUT_MINUTES,
19
+ } from "./release.constants.ts";
20
+
21
+ const SETUP = [CHECKOUT_STEP, SETUP_BUN_STEP, INSTALL_STEP];
22
+
23
+ const mutateJob = (total: number) =>
24
+ [
25
+ " mutate:",
26
+ " name: mutate (${{ matrix.shard }}/" + `${total})`,
27
+ " runs-on: ubuntu-latest",
28
+ ` timeout-minutes: ${MUTATE_TIMEOUT_MINUTES}`,
29
+ " strategy:",
30
+ " fail-fast: false",
31
+ " matrix:",
32
+ ` shard: [${Array.from({ length: total }, (_, at) => at + 1).join(", ")}]`,
33
+ " steps:",
34
+ ...SETUP,
35
+ MUTATE_STEPS.replace("%TOTAL%", String(total)),
36
+ ].join("\n");
37
+
38
+ const floorsJob = () =>
39
+ [
40
+ " mutate-floors:",
41
+ " name: mutate-floors",
42
+ " needs: mutate",
43
+ " runs-on: ubuntu-latest",
44
+ ` timeout-minutes: ${TEST_TIMEOUT_MINUTES}`,
45
+ " steps:",
46
+ ...SETUP,
47
+ FLOORS_STEPS,
48
+ ].join("\n");
49
+
50
+ /** The live job: each secret is mapped by name from the repository's secrets, in `env` and nowhere else. */
51
+ const liveJob = (secrets: readonly string[]) =>
52
+ [
53
+ " live:",
54
+ " name: live",
55
+ " runs-on: ubuntu-latest",
56
+ ` timeout-minutes: ${SLOW_TIMEOUT_MINUTES}`,
57
+ ...(secrets.length > 0 ? [" env:", ...secrets.map((name) => ` ${name}: \${{ secrets.${name} }}`)] : []),
58
+ " steps:",
59
+ ...SETUP,
60
+ ` - run: bun run ${LIVE_SCRIPT}`,
61
+ ].join("\n");
62
+
63
+ /** Under the release pack, the tracked, whole-file `.github/workflows/nightly.yml`; a hand-written one is refused. */
64
+ export const nightly: Generator = (ctx: SyncContext) => {
65
+ if (!ctx.config.packs.includes("release")) return { outputs: [], errors: [] };
66
+ const existing = ctx.read(NIGHTLY_PATH);
67
+ if (existing !== null && lines(existing)[0] !== NIGHTLY_HEADER) {
68
+ return { outputs: [], errors: [`${NIGHTLY_PATH} is hand-written; delete it so repo sync can own it`] };
69
+ }
70
+ const { shards, secrets } = ctx.config.nightly;
71
+ const live = hasScript(ctx, LIVE_SCRIPT) ? [liveJob(secrets)] : [];
72
+ const jobs = [mutateJob(shards), floorsJob(), ...live];
73
+ const content = `${NIGHTLY_HEADER}\n${PREAMBLE}\n${jobs.join("\n\n")}\n`;
74
+ return { outputs: [{ kind: "file", path: NIGHTLY_PATH, content, tracked: true, managed: "whole" }], errors: [] };
75
+ };
@@ -0,0 +1,351 @@
1
+ import { BASE_CONFIG } from "../fixtures/base-config.ts";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { describe, expect, it } from "vitest";
6
+ import type { Pack } from "../config.ts";
7
+ import type { Host } from "../config.ts";
8
+ import { nightly } from "./nightly.ts";
9
+ import { release } from "./release.ts";
10
+ import { releaseWorkflow } from "./release-workflow.ts";
11
+ import { bunfig, GENERATORS, index, lessons, trees, type Output, type SyncContext } from "./outputs.ts";
12
+ import type { PackageJson, WorkspacePackage } from "./workspace.ts";
13
+
14
+ const BUNFIG = '# Generated by repo sync. Do not edit.\n[install]\nlinker = "hoisted"\nauto = "disable"\n';
15
+
16
+ type Fixture = {
17
+ hosts?: Host[];
18
+ packs?: Pack[];
19
+ manifest?: PackageJson;
20
+ packages?: WorkspacePackage[];
21
+ files?: Record<string, string>;
22
+ };
23
+
24
+ /** A sync context over in-memory files; `read` sees only `files`. */
25
+ function context({
26
+ hosts = [],
27
+ packs = ["agents", "docs"],
28
+ manifest = {},
29
+ packages = [],
30
+ files = {},
31
+ }: Fixture = {}): SyncContext {
32
+ return {
33
+ root: "/repo",
34
+ config: {
35
+ packs,
36
+ agents: { hosts, skills: [] },
37
+ ...BASE_CONFIG,
38
+ },
39
+ workspace: { root: "/repo", manifest, packages },
40
+ read: (rel) => files[rel] ?? null,
41
+ };
42
+ }
43
+
44
+ /** The text of a file output. */
45
+ function contentOf(output: Output | undefined): string | undefined {
46
+ return output?.kind === "file" ? output.content : undefined;
47
+ }
48
+
49
+ const API: WorkspacePackage = { dir: "packages/api", manifest: { name: "@acme/api" } };
50
+ const WEB: WorkspacePackage = { dir: "apps/web", manifest: { name: "web" } };
51
+ const PACKAGE_INDEXES = {
52
+ "packages/api/references/INDEX.md": "\n\n# API worker\n\nMore.\n",
53
+ "apps/web/references/INDEX.md": "Web app\r\n",
54
+ };
55
+ const PACKAGES_SECTION =
56
+ "<!-- repo:packages -->\n" +
57
+ "- [web](../apps/web/references/INDEX.md): Web app\n" +
58
+ "- [@acme/api](../packages/api/references/INDEX.md): API worker\n" +
59
+ "<!-- /repo:packages -->";
60
+ const REPO_STACK =
61
+ "<!-- repo:stack -->\n" +
62
+ "- [@workerstack/repo](../node_modules/@workerstack/repo/references/INDEX.md)\n" +
63
+ "<!-- /repo:stack -->";
64
+ const EMPTY_STACK = "<!-- repo:stack -->\n<!-- /repo:stack -->";
65
+
66
+ describe("GENERATORS", () => {
67
+ it("runs bunfig, index, lessons, trees, release, nightly and the release workflow in that order", () => {
68
+ expect(GENERATORS).toEqual([bunfig, index, lessons, trees, release, nightly, releaseWorkflow]);
69
+ });
70
+ });
71
+
72
+ describe("bunfig", () => {
73
+ it("writes the generated bunfig.toml when none exists", () => {
74
+ expect(bunfig(context())).toEqual({
75
+ outputs: [{ kind: "file", path: "bunfig.toml", content: BUNFIG, tracked: true, managed: "whole" }],
76
+ errors: [],
77
+ });
78
+ });
79
+
80
+ it("returns the same content when the file is current", () => {
81
+ expect(bunfig(context({ files: { "bunfig.toml": BUNFIG } }))).toEqual({
82
+ outputs: [{ kind: "file", path: "bunfig.toml", content: BUNFIG, tracked: true, managed: "whole" }],
83
+ errors: [],
84
+ });
85
+ });
86
+
87
+ it("owns a stale file that still starts with the generated header", () => {
88
+ const stale = '# Generated by repo sync. Do not edit.\r\n[install]\r\nlinker = "isolated"\r\n';
89
+ expect(bunfig(context({ files: { "bunfig.toml": stale } })).outputs).toEqual([
90
+ { kind: "file", path: "bunfig.toml", content: BUNFIG, tracked: true, managed: "whole" },
91
+ ]);
92
+ });
93
+
94
+ it("refuses a hand-written bunfig.toml", () => {
95
+ expect(bunfig(context({ files: { "bunfig.toml": '[test]\nroot = "x"\n' } }))).toEqual({
96
+ outputs: [],
97
+ errors: ["bunfig.toml is hand-written; delete it so repo sync can own it"],
98
+ });
99
+ });
100
+ });
101
+
102
+ describe("trees", () => {
103
+ it("writes nothing without the agents pack, whatever the hosts", () => {
104
+ expect(trees(context({ hosts: ["claude"], packs: ["docs"] }))).toEqual({ outputs: [], errors: [] });
105
+ expect(trees(context({ hosts: ["claude"], packs: [] }))).toEqual({ outputs: [], errors: [] });
106
+ });
107
+
108
+ it("writes nothing with the agents pack and no hosts", () => {
109
+ expect(trees(context())).toEqual({ outputs: [], errors: [] });
110
+ });
111
+
112
+ it("turns a failed render into one error", () => {
113
+ const result = trees(context({ hosts: ["claude"] }));
114
+ expect(result.outputs).toEqual([]);
115
+ expect(result.errors).toEqual([expect.stringContaining("bun install")]);
116
+ });
117
+ });
118
+
119
+ describe("lessons", () => {
120
+ /** A real root with a lessons dir, so the pack alone decides whether the index is written. */
121
+ const withLessonsDir = (packs: Pack[]) => {
122
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "workerstack-lessons-"));
123
+ try {
124
+ fs.mkdirSync(path.join(root, ".repo", "lessons"), { recursive: true });
125
+ return lessons({ ...context({ packs }), root });
126
+ } finally {
127
+ fs.rmSync(root, { recursive: true, force: true });
128
+ }
129
+ };
130
+
131
+ it("writes nothing without the agents pack", () => {
132
+ expect(withLessonsDir(["docs"])).toEqual({ outputs: [], errors: [] });
133
+ expect(withLessonsDir([])).toEqual({ outputs: [], errors: [] });
134
+ });
135
+
136
+ it("writes the lessons index with the agents pack", () => {
137
+ expect(withLessonsDir(["agents"]).outputs.map((o) => o.path)).toEqual([".repo/lessons/INDEX.md"]);
138
+ });
139
+ });
140
+
141
+ describe("index", () => {
142
+ const STACK_ROOT: PackageJson = { dependencies: { "@workerstack/repo": "^1.0.0" } };
143
+
144
+ it("does nothing when the docs pack is off", () => {
145
+ expect(index(context({ packs: ["agents"], packages: [API], files: {} }))).toEqual({ outputs: [], errors: [] });
146
+ });
147
+
148
+ it("creates the file with both sections when it is missing, then each package INDEX with an empty deps section", () => {
149
+ const ctx = context({ manifest: STACK_ROOT, packages: [WEB, API], files: PACKAGE_INDEXES });
150
+ const block = (path: string, content: string): Output => ({
151
+ kind: "file",
152
+ path,
153
+ content,
154
+ tracked: true,
155
+ managed: "block",
156
+ });
157
+ expect(index(ctx)).toEqual({
158
+ outputs: [
159
+ block("references/INDEX.md", `# References\n\n${PACKAGES_SECTION}\n\n${REPO_STACK}\n`),
160
+ block("apps/web/references/INDEX.md", "Web app\n\n<!-- repo:deps -->\n<!-- /repo:deps -->\n"),
161
+ block(
162
+ "packages/api/references/INDEX.md",
163
+ "\n\n# API worker\n\nMore.\n\n<!-- repo:deps -->\n<!-- /repo:deps -->\n",
164
+ ),
165
+ ],
166
+ errors: [],
167
+ });
168
+ });
169
+
170
+ it("writes no packages section in a single-package repo, and keeps empty stack markers", () => {
171
+ expect(index(context()).outputs).toEqual([
172
+ {
173
+ kind: "file",
174
+ path: "references/INDEX.md",
175
+ content: `# References\n\n${EMPTY_STACK}\n`,
176
+ tracked: true,
177
+ managed: "block",
178
+ },
179
+ ]);
180
+ });
181
+
182
+ it("replaces present sections in place and keeps the hand-written text around them", () => {
183
+ const text =
184
+ "# Docs\n\nIntro.\n\n<!-- repo:packages -->\n- stale\n<!-- /repo:packages -->\n\nMiddle.\n\n" +
185
+ "<!-- repo:stack -->\n- stale\n<!-- /repo:stack -->\n\nFooter.\n";
186
+ const ctx = context({
187
+ manifest: STACK_ROOT,
188
+ packages: [WEB, API],
189
+ files: { ...PACKAGE_INDEXES, "references/INDEX.md": text },
190
+ });
191
+ expect(index(ctx).outputs.slice(0, 1)).toEqual([
192
+ {
193
+ kind: "file",
194
+ path: "references/INDEX.md",
195
+ content: `# Docs\n\nIntro.\n\n${PACKAGES_SECTION}\n\nMiddle.\n\n${REPO_STACK}\n\nFooter.\n`,
196
+ tracked: true,
197
+ managed: "block",
198
+ },
199
+ ]);
200
+ });
201
+
202
+ it("appends absent sections at the end after a blank line", () => {
203
+ const ctx = context({
204
+ manifest: STACK_ROOT,
205
+ packages: [WEB, API],
206
+ files: { ...PACKAGE_INDEXES, "references/INDEX.md": "# Docs\n\nHand-written." },
207
+ });
208
+ expect(index(ctx).outputs.slice(0, 1)).toEqual([
209
+ {
210
+ kind: "file",
211
+ path: "references/INDEX.md",
212
+ content: `# Docs\n\nHand-written.\n\n${PACKAGES_SECTION}\n\n${REPO_STACK}\n`,
213
+ tracked: true,
214
+ managed: "block",
215
+ },
216
+ ]);
217
+ });
218
+
219
+ it("describes a package by its first non-empty INDEX line and links it by name, else by dir", () => {
220
+ const ctx = context({
221
+ packages: [
222
+ { dir: "libs/blank", manifest: { name: "" } },
223
+ { dir: "libs/heading", manifest: { name: "heading" } },
224
+ ],
225
+ files: { "libs/blank/references/INDEX.md": "\n \n", "libs/heading/references/INDEX.md": " ## Sub \n# Top\n" },
226
+ });
227
+ expect(contentOf(index(ctx).outputs[0])).toContain(
228
+ "<!-- repo:packages -->\n" +
229
+ "- [libs/blank](../libs/blank/references/INDEX.md)\n" +
230
+ "- [heading](../libs/heading/references/INDEX.md): ## Sub\n" +
231
+ "<!-- /repo:packages -->\n",
232
+ );
233
+ });
234
+
235
+ it("fails naming each workspace package that has no references/INDEX.md", () => {
236
+ const ctx = context({ packages: [WEB, API], files: {} });
237
+ expect(index(ctx)).toEqual({
238
+ outputs: [],
239
+ errors: [
240
+ "apps/web has no references/INDEX.md; every workspace package needs one",
241
+ "packages/api has no references/INDEX.md; every workspace package needs one",
242
+ ],
243
+ });
244
+ });
245
+
246
+ it("lists every @workerstack/* dependency of the root and packages once, sorted", () => {
247
+ const ctx = context({
248
+ manifest: {
249
+ dependencies: { "@workerstack/repo": "^1.0.0", zod: "^4.0.0" },
250
+ devDependencies: { "@workerstack/lint": "^1.0.0" },
251
+ },
252
+ packages: [
253
+ {
254
+ dir: "packages/api",
255
+ manifest: {
256
+ peerDependencies: { "@workerstack/worker": "^1.0.0", "@workerstack/repo": "^1.0.0" },
257
+ optionalDependencies: { "@workerstack/ai": "^1.0.0", "@workerstackx/no": "1.0.0" },
258
+ },
259
+ },
260
+ ],
261
+ files: { "packages/api/references/INDEX.md": "API\n" },
262
+ });
263
+ const link = (name: string) => `- [${name}](../node_modules/${name}/references/INDEX.md)\n`;
264
+ expect(contentOf(index(ctx).outputs[0])).toContain(
265
+ "<!-- repo:stack -->\n" +
266
+ link("@workerstack/ai") +
267
+ link("@workerstack/lint") +
268
+ link("@workerstack/repo") +
269
+ link("@workerstack/worker") +
270
+ "<!-- /repo:stack -->\n",
271
+ );
272
+ });
273
+
274
+ it("fails on a section opener with no closer", () => {
275
+ const ctx = context({ files: { "references/INDEX.md": "# Docs\n\n<!-- repo:stack -->\n- stale\n" } });
276
+ expect(index(ctx)).toEqual({
277
+ outputs: [],
278
+ errors: ['references/INDEX.md: "<!-- repo:stack -->" has no closing "<!-- /repo:stack -->"'],
279
+ });
280
+ });
281
+
282
+ it("reports a missing package INDEX and an unclosed section together", () => {
283
+ const ctx = context({ packages: [API], files: { "references/INDEX.md": "<!-- repo:packages -->\n" } });
284
+ expect(index(ctx).errors).toEqual([
285
+ "packages/api has no references/INDEX.md; every workspace package needs one",
286
+ 'references/INDEX.md: "<!-- repo:packages -->" has no closing "<!-- /repo:packages -->"',
287
+ ]);
288
+ });
289
+ });
290
+
291
+ describe("index repo:deps", () => {
292
+ const pkg = (dir: string, name: string, deps: Partial<PackageJson> = {}): WorkspacePackage => ({
293
+ dir,
294
+ manifest: { name, ...deps },
295
+ });
296
+ const WEB_PKG = pkg("packages/web", "@acme/web", {
297
+ dependencies: { "@acme/api": "1.0.0", react: "19.0.0", "@workerstack/ui": "1.0.0" },
298
+ devDependencies: { "@acme/core": "1.0.0", "@workerstack/lint": "1.0.0", "@workerstack/shared": "1.0.0" },
299
+ peerDependencies: { "@acme/api": "1.0.0" },
300
+ optionalDependencies: { "@workerstack/ai": "1.0.0" },
301
+ });
302
+ const files = {
303
+ "packages/web/references/INDEX.md": "# Web\n",
304
+ "packages/api/references/INDEX.md": "# Api\n",
305
+ "packages/core/references/INDEX.md": "# Core\n",
306
+ "packages/shared/references/INDEX.md": "# Shared\n",
307
+ };
308
+ const packages = [
309
+ WEB_PKG,
310
+ pkg("packages/api", "@acme/api"),
311
+ pkg("packages/core", "@acme/core"),
312
+ pkg("packages/shared", "@workerstack/shared"),
313
+ ];
314
+ const depsOf = (rel: string, ctx = context({ packages, files })) => {
315
+ const content = contentOf(index(ctx).outputs.find((o) => o.path === rel));
316
+ return content?.split("<!-- repo:deps -->\n")[1]?.split("<!-- /repo:deps -->")[0];
317
+ };
318
+
319
+ it("links the INDEX of each workspace dependency first, then each other @workerstack/* one, both sorted and deduped", () => {
320
+ expect(depsOf("packages/web/references/INDEX.md")).toBe(
321
+ "- [@acme/api](../../api/references/INDEX.md)\n" +
322
+ "- [@acme/core](../../core/references/INDEX.md)\n" +
323
+ "- [@workerstack/shared](../../shared/references/INDEX.md)\n" +
324
+ "- [@workerstack/ai](../../../node_modules/@workerstack/ai/references/INDEX.md)\n" +
325
+ "- [@workerstack/lint](../../../node_modules/@workerstack/lint/references/INDEX.md)\n" +
326
+ "- [@workerstack/ui](../../../node_modules/@workerstack/ui/references/INDEX.md)\n",
327
+ );
328
+ });
329
+
330
+ it("gives a package with no such dependency an empty section, and replaces a stale one in place", () => {
331
+ expect(depsOf("packages/api/references/INDEX.md")).toBe("");
332
+ const stale = {
333
+ ...files,
334
+ "packages/web/references/INDEX.md": "# Web\n\n<!-- repo:deps -->\n- stale\n<!-- /repo:deps -->\n\nTail.\n",
335
+ };
336
+ const content = contentOf(
337
+ index(context({ packages, files: stale })).outputs.find((o) => o.path === "packages/web/references/INDEX.md"),
338
+ );
339
+ expect(content).toMatch(/^# Web\n\n<!-- repo:deps -->\n- \[@acme\/api\]/);
340
+ expect(content).toMatch(/<!-- \/repo:deps -->\n\nTail\.\n$/);
341
+ expect(content).not.toContain("stale");
342
+ });
343
+
344
+ it("fails on an unclosed deps section in a package INDEX", () => {
345
+ const broken = { ...files, "packages/api/references/INDEX.md": "# Api\n<!-- repo:deps -->\n" };
346
+ expect(index(context({ packages, files: broken }))).toEqual({
347
+ outputs: [],
348
+ errors: ['packages/api/references/INDEX.md: "<!-- repo:deps -->" has no closing "<!-- /repo:deps -->"'],
349
+ });
350
+ });
351
+ });