@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,34 @@
1
+ import crossSpawn from "cross-spawn";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { execFileSync, spawnSync } from "./spawn.ts";
4
+
5
+ const node = process.execPath;
6
+
7
+ describe("spawnSync", () => {
8
+ it("returns status and output like child_process.spawnSync", () => {
9
+ const r = spawnSync(node, ["-e", "process.stdout.write('hi'); process.exit(3)"], { encoding: "utf8" });
10
+ expect(r.status).toBe(3);
11
+ expect(r.stdout).toBe("hi");
12
+ });
13
+ });
14
+
15
+ describe("execFileSync", () => {
16
+ it("returns stdout on exit 0", () => {
17
+ expect(execFileSync(node, ["-e", "process.stdout.write('ok')"], { encoding: "utf8" })).toBe("ok");
18
+ });
19
+
20
+ it("throws with the exit code on a nonzero exit", () => {
21
+ expect(() => execFileSync(node, ["-e", "process.exit(2)"], {})).toThrow(/exited with 2$/);
22
+ });
23
+
24
+ // Mocked: Windows has no signals, so a real kill reports an exit code there.
25
+ it("throws with the signal when the child is killed", () => {
26
+ const sync = vi.spyOn(crossSpawn, "sync").mockReturnValueOnce({ status: null, signal: "SIGTERM" } as never);
27
+ expect(() => execFileSync("bun", ["install"], {})).toThrow(/^bun install exited with SIGTERM$/);
28
+ sync.mockRestore();
29
+ });
30
+
31
+ it("throws the spawn error for a missing command", () => {
32
+ expect(() => execFileSync("repo-no-such-command-e2e", [], {})).toThrow(/Executable not found in \$PATH|ENOENT/);
33
+ });
34
+ });
package/cli/spawn.ts ADDED
@@ -0,0 +1,27 @@
1
+ import type { SpawnSyncOptions, SpawnSyncOptionsWithStringEncoding, SpawnSyncReturns } from "node:child_process";
2
+ import crossSpawn from "cross-spawn";
3
+
4
+ /**
5
+ * child_process twins for starting bun and the tools it installs. On Windows a package bin is a
6
+ * .cmd shim that child_process cannot start without a shell (spawnSync ENOENT); cross-spawn
7
+ * resolves and escapes it. Elsewhere it is plain spawnSync.
8
+ */
9
+ export function spawnSync(
10
+ cmd: string,
11
+ args: string[],
12
+ opts: SpawnSyncOptionsWithStringEncoding,
13
+ ): SpawnSyncReturns<string>;
14
+ export function spawnSync(cmd: string, args: string[], opts?: SpawnSyncOptions): SpawnSyncReturns<Buffer>;
15
+ export function spawnSync(cmd: string, args: string[], opts?: SpawnSyncOptions): SpawnSyncReturns<string | Buffer> {
16
+ return crossSpawn.sync(cmd, args, opts);
17
+ }
18
+
19
+ /** execFileSync semantics: throw unless the command exits 0. */
20
+ export function execFileSync(cmd: string, args: string[], opts?: SpawnSyncOptions): string | Buffer {
21
+ const r = spawnSync(cmd, args, opts);
22
+ if (r.error) throw r.error;
23
+ if (r.status !== 0) {
24
+ throw new Error(`${cmd} ${args.join(" ")} exited with ${r.status ?? r.signal}`);
25
+ }
26
+ return r.stdout;
27
+ }
@@ -0,0 +1,11 @@
1
+ /** The names `repo` reads from `git diff --numstat` and `git check-attr` output. */
2
+
3
+ /** The tokens `git check-attr -z` prints per path: the path, the attribute and its value. */
4
+ export const ATTR_TOKENS = 3;
5
+ /** Where the value sits in a `git check-attr -z` triple. */
6
+ export const ATTR_VALUE_AT = 2;
7
+ /** The two paths a numstat rename lists after its counts. */
8
+ export const RENAME_PATHS = 2;
9
+ /** The attribute values that mark a path as set. */
10
+ export const ATTR_SET = "set";
11
+ export const ATTR_TRUE = "true";
@@ -0,0 +1,79 @@
1
+ import path from "node:path";
2
+ import { ATTR_SET, ATTR_TOKENS, ATTR_TRUE, ATTR_VALUE_AT, RENAME_PATHS } from "./staged-change.constants.ts";
3
+ import { type GitRun, runGit } from "./sync/standards.ts";
4
+
5
+ /** Files whose size says nothing about the change: lockfiles, by name anywhere, and the lint baseline. */
6
+ const UNCOUNTED_NAMES: readonly string[] = [
7
+ "bun.lock",
8
+ "bun.lockb",
9
+ "package-lock.json",
10
+ "yarn.lock",
11
+ "pnpm-lock.yaml",
12
+ ];
13
+ const UNCOUNTED_PATHS: readonly string[] = [".repo/lint-baseline.json"];
14
+
15
+ /** The staged change: every path, and the changed lines that count toward the size limit. */
16
+ export type StagedChange = { files: string[]; changedLines: number };
17
+
18
+ /** Where numstat output came from: the repo, the git to ask, and the `check-attr` flags that pick the tree. */
19
+ type Reader = { root: string; attrArgs: readonly string[]; git: GitRun };
20
+
21
+ /** One `git diff --numstat -z` entry: its path, and its changed lines, or null for a binary file. */
22
+ type Numstat = { file: string; lines: number | null };
23
+
24
+ /** The entries of `git diff --numstat -z`; a rename is `a\td\t` followed by its old and new path. */
25
+ function parseNumstat(out: string): Numstat[] {
26
+ const tokens = out.split("\0");
27
+ const entries: Numstat[] = [];
28
+ for (let i = 0; i < tokens.length; i++) {
29
+ const match = /^(-|\d+)\t(-|\d+)\t(.*)$/s.exec(tokens[i] /* i < tokens.length */ as string);
30
+ if (match === null) continue;
31
+ const [, added, deleted, named] = match;
32
+ const file = named ? named : (tokens[(i += RENAME_PATHS)] /* a rename lists both paths */ as string);
33
+ entries.push({ file, lines: added === "-" ? null : Number(added) + Number(deleted) });
34
+ }
35
+ return entries;
36
+ }
37
+
38
+ /** The paths among `files` that `.gitattributes` (`attrArgs` picks which: the index, or a commit's) mark `linguist-generated`. */
39
+ function generatedPaths(files: readonly string[], { root, attrArgs, git }: Reader): Set<string> {
40
+ const result = git(root, ["check-attr", ...attrArgs, "-z", "--stdin", "linguist-generated"], files.join("\0"));
41
+ const tokens = result.stdout.split("\0");
42
+ const generated = new Set<string>();
43
+ for (let i = 0; i + ATTR_VALUE_AT < tokens.length; i += ATTR_TOKENS) {
44
+ if (tokens[i + ATTR_VALUE_AT] === ATTR_SET || tokens[i + ATTR_VALUE_AT] === ATTR_TRUE)
45
+ generated.add(tokens[i] /* i < tokens.length */ as string);
46
+ }
47
+ return generated;
48
+ }
49
+
50
+ /** The files and counted lines of `numstat` output, leaving out binaries, lockfiles, the baseline and generated paths. */
51
+ function summarize(numstat: string, reader: Reader): StagedChange {
52
+ const entries = parseNumstat(numstat);
53
+ const files = entries.map((entry) => entry.file);
54
+ const generated = generatedPaths(files, reader);
55
+ const counted = entries.filter(
56
+ ({ file }) =>
57
+ !UNCOUNTED_NAMES.includes(path.posix.basename(file)) && !UNCOUNTED_PATHS.includes(file) && !generated.has(file),
58
+ );
59
+ return { files, changedLines: counted.reduce((sum, entry) => sum + (entry.lines ?? 0), 0) };
60
+ }
61
+
62
+ /**
63
+ * The staged change at `root`: every staged path, and the insertions plus deletions that count toward the size
64
+ * limit, leaving out binaries, lockfiles, the lint baseline and `linguist-generated` paths. Nothing outside a repo.
65
+ */
66
+ export function stagedChange(root: string, git: GitRun = runGit): StagedChange {
67
+ const diff = git(root, ["diff", "--cached", "--numstat", "-z"]);
68
+ return diff.status !== 0
69
+ ? { files: [], changedLines: 0 }
70
+ : summarize(diff.stdout, { root, attrArgs: ["--cached"], git });
71
+ }
72
+
73
+ /** The change commit `sha` made, counted as `stagedChange` counts, with `.gitattributes` read as of that commit. */
74
+ export function commitChange(root: string, sha: string, git: GitRun = runGit): StagedChange {
75
+ const shown = git(root, ["show", "--numstat", "-z", "--format=", sha]);
76
+ return shown.status !== 0
77
+ ? { files: [], changedLines: 0 }
78
+ : summarize(shown.stdout, { root, attrArgs: ["--source", sha], git });
79
+ }
@@ -0,0 +1,50 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { afterEach, expect, it } from "vitest";
5
+ import { spawnSync } from "./spawn.ts";
6
+ import { toolEnv } from "./scripts/lint.ts";
7
+ import { childEnv } from "./env.ts";
8
+
9
+ const PRELOAD = path.join(import.meta.dirname, "stryker-preload.ts");
10
+ const THROWS = "First argument must be an Error object";
11
+ // Babel's stack rewriter wraps the native hook; follow-redirects' CustomError captures a stack on a non-Error.
12
+ const REPRO = `
13
+ const orig = Error.prepareStackTrace;
14
+ Error.prepareStackTrace = (e, t) => (orig ? orig(e, t) : "");
15
+ function C() { Error.captureStackTrace(this, C); }
16
+ C.prototype = new Error();
17
+ new C();
18
+ `;
19
+ let dir = "";
20
+
21
+ afterEach(() => fs.rmSync(dir, { recursive: true, force: true }));
22
+
23
+ function repro(preload: boolean) {
24
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), "stryker-preload-"));
25
+ const file = path.join(dir, "repro.ts");
26
+ fs.writeFileSync(file, REPRO);
27
+ const args = preload ? ["--preload", PRELOAD, file] : [file];
28
+ const run = spawnSync(process.execPath, args, { encoding: "utf8", env: toolEnv(childEnv()) });
29
+ return { code: run.status, text: `${run.stdout}${run.stderr}` };
30
+ }
31
+
32
+ it("bun still throws on a non-Error that captures a stack; delete cli/stryker-preload.ts and its --preload once it does not", () => {
33
+ const bare = repro(false);
34
+ expect(bare.text).toContain(THROWS);
35
+ expect(bare.code).not.toBe(0);
36
+ });
37
+
38
+ it("the preload clears the native hook, so the same code runs", () => {
39
+ expect(repro(true)).toEqual({ code: 0, text: "" });
40
+ });
41
+
42
+ it("sets prepareStackTrace to undefined when loaded", async () => {
43
+ const before = Error.prepareStackTrace;
44
+ try {
45
+ await import("./stryker-preload.ts");
46
+ expect(Error.prepareStackTrace).toBeUndefined();
47
+ } finally {
48
+ Error.prepareStackTrace = before;
49
+ }
50
+ });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Preloaded (`bun --preload`) by `repo mutate` before Stryker. Bun's native `Error.prepareStackTrace` throws "First
3
+ * argument must be an Error object" on an Error-derived non-Error, which babel's stack rewriter and vite's
4
+ * follow-redirects reach while Stryker loads its vitest runner; with no hook, bun's default stack formatting runs.
5
+ * `cli/stryker-preload.test.ts` pins the bun bug: when it fails, delete this file and the `--preload` argument.
6
+ */
7
+ Error.prepareStackTrace = undefined /* the lib types omit undefined; assigning it clears the native hook */ as never;
8
+
9
+ export {};
@@ -0,0 +1,197 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { parse } from "yaml";
4
+ import { describe, expect, it } from "vitest";
5
+ import { emitYaml, finalize, parseDoc, type YamlValue } from "./frontmatter.ts";
6
+
7
+ const GOLDEN = path.join(import.meta.dirname, "..", "fixtures", "golden");
8
+
9
+ describe("parseDoc", () => {
10
+ it("splits frontmatter from a trimmed LF body", () => {
11
+ expect(parseDoc("a.md", "\uFEFF---\r\nname: x\r\n---\r\n\r\n body\r\nline two\r\n\r\n")).toEqual({
12
+ data: { name: "x" },
13
+ body: "body\nline two",
14
+ });
15
+ });
16
+
17
+ it("reads YAML 1.2: yes stays a string", () => {
18
+ expect(parseDoc("a.md", "---\nflag: yes\nnum: 012\n---\n").data).toEqual({ flag: "yes", num: 12 });
19
+ });
20
+
21
+ it("drops null values at every level", () => {
22
+ const text = "---\na:\nb: 1\nc:\n d: ~\n e: 2\nf: [x, null]\n---\n";
23
+ expect(parseDoc("a.md", text).data).toEqual({ b: 1, c: { e: 2 }, f: ["x"] });
24
+ });
25
+
26
+ it("gives an empty mapping for an empty block", () => {
27
+ expect(parseDoc("a.md", "---\n---\nbody")).toEqual({ data: {}, body: "body" });
28
+ });
29
+
30
+ it("names the file when the frontmatter is missing", () => {
31
+ expect(() => parseDoc(".repo/rules/nofm.md", "# no fence\n")).toThrow(
32
+ new Error(".repo/rules/nofm.md: missing frontmatter (start the file with a --- block)"),
33
+ );
34
+ });
35
+
36
+ it("names the file and gives the parser message's first line when the YAML is broken", () => {
37
+ const yaml = "name: [bad";
38
+ let message = "";
39
+ try {
40
+ parse(yaml);
41
+ } catch (error) {
42
+ message = (error as Error).message;
43
+ }
44
+ const firstLine = message.split("\n")[0]!.trim().replace(/:$/, "");
45
+ expect(firstLine).toMatch(/at line 1, column \d+$/);
46
+ expect(() => parseDoc(".repo/subagents/bad.md", `---\n${yaml}\n---\n`)).toThrow(
47
+ new Error(`.repo/subagents/bad.md: invalid frontmatter: ${firstLine}`),
48
+ );
49
+ });
50
+
51
+ it("keeps the invalid-frontmatter message on one line", () => {
52
+ expect(() => parseDoc("a.md", "---\nname: [bad\n---\n")).toThrow(/^a\.md: invalid frontmatter: [^\n]+$/);
53
+ });
54
+
55
+ it.each([
56
+ ["an unclosed block", "---\nname: x\n", "no closing --- line"],
57
+ ["a non-mapping", "---\n- x\n---\n", "expected a mapping"],
58
+ ])("rejects %s", (_label, text, why) => {
59
+ expect(() => parseDoc("a.md", text)).toThrow(new Error(`a.md: invalid frontmatter: ${why}`));
60
+ });
61
+ });
62
+
63
+ describe("emitYaml", () => {
64
+ const roundTrip = (entries: [string, YamlValue][], mode?: "claude") =>
65
+ expect(parse(emitYaml(entries, mode))).toEqual(Object.fromEntries(entries));
66
+
67
+ it("writes lists and nested maps in block style, in the order given", () => {
68
+ const entries: [string, YamlValue][] = [
69
+ ["name", "x"],
70
+ ["tools", ["Read", "Grep"]],
71
+ ["nested", { b: 1, a: true }],
72
+ ];
73
+ expect(emitYaml(entries)).toBe("name: x\ntools:\n - Read\n - Grep\nnested:\n b: 1\n a: true\n");
74
+ roundTrip(entries);
75
+ });
76
+
77
+ it("writes empty lists and maps in flow style", () => {
78
+ expect(
79
+ emitYaml([
80
+ ["a", []],
81
+ ["b", {}],
82
+ ]),
83
+ ).toBe("a: []\nb: {}\n");
84
+ });
85
+
86
+ it("drops nullish values at every level", () => {
87
+ expect(
88
+ emitYaml([
89
+ ["a", null],
90
+ ["b", undefined],
91
+ ["c", { d: null }],
92
+ ["e", ["x", null, undefined]],
93
+ ]),
94
+ ).toBe("c: {}\ne:\n - x\n");
95
+ });
96
+
97
+ const ambiguous = [
98
+ "yes",
99
+ "No",
100
+ "y",
101
+ "on",
102
+ "true",
103
+ "null",
104
+ "~",
105
+ "012",
106
+ "1_000",
107
+ "1:20",
108
+ "0x1f",
109
+ "1e3",
110
+ ".5",
111
+ "2001-12-14",
112
+ "",
113
+ " lead",
114
+ "trail ",
115
+ "- a",
116
+ "-",
117
+ "a: b",
118
+ "a:",
119
+ "a #b",
120
+ "#a",
121
+ "[x]",
122
+ "{x}",
123
+ "@x",
124
+ "%x",
125
+ "`x",
126
+ "!x",
127
+ "&x",
128
+ "*x",
129
+ ">x",
130
+ "|x",
131
+ "'x",
132
+ '"x',
133
+ "---",
134
+ "--- [",
135
+ ];
136
+
137
+ it.each(ambiguous)("quotes %j and reads it back", (value) => {
138
+ expect(emitYaml([["k", value]])).toMatch(/^k: '/);
139
+ roundTrip([["k", value]]);
140
+ });
141
+
142
+ it.each(["a, b", "x[y]", "a:b", "a#b", "-a", "=x", "?x", "Agent(scout, explore, plan)", "grok-4.7", "it's 'quoted'"])(
143
+ "leaves %j bare by default",
144
+ (value) => {
145
+ expect(emitYaml([["k", value]])).toBe(`k: ${value}\n`);
146
+ roundTrip([["k", value]]);
147
+ },
148
+ );
149
+
150
+ it.each(["a, b", "x[y]", "{x", "a:b", "-a", "=x", "?x"])("quotes %j in claude mode", (value) => {
151
+ expect(emitYaml([["k", value]], "claude")).toBe(`k: '${value}'\n`);
152
+ roundTrip([["k", value]], "claude");
153
+ });
154
+
155
+ it("double-quotes strings with newlines or control characters", () => {
156
+ expect(emitYaml([["k", "a\nb\tc"]])).toBe('k: "a\\nb\\tc"\n');
157
+ roundTrip([["k", "a\nb\tc"]]);
158
+ });
159
+
160
+ it("quotes keys like values", () => {
161
+ roundTrip([
162
+ ["yes", "x"],
163
+ ["a: b", 1],
164
+ ]);
165
+ expect(emitYaml([["yes", "x"]])).toBe("'yes': x\n");
166
+ });
167
+
168
+ it("writes special numbers", () => {
169
+ roundTrip([
170
+ ["n", 1.5],
171
+ ["inf", Infinity],
172
+ ["ninf", -Infinity],
173
+ ]);
174
+ expect(parse(emitYaml([["nan", NaN]])).nan).toBeNaN();
175
+ });
176
+
177
+ const agentFiles = ["claude", "agents", "grok", "cursor"].flatMap((host) =>
178
+ fs.readdirSync(path.join(GOLDEN, `.${host}`, "agents")).map((name) => `.${host}/agents/${name}`),
179
+ );
180
+
181
+ it.each(agentFiles)("reproduces the frontmatter of golden %s", (rel) => {
182
+ const text = fs.readFileSync(path.join(GOLDEN, rel), "utf8");
183
+ const block = text.slice(4, text.indexOf("\n---\n", 4) + 1);
184
+ const mode = rel.startsWith(".claude/") ? "claude" : "default";
185
+ expect(emitYaml(Object.entries(parseDoc(rel, text).data) as [string, YamlValue][], mode)).toBe(block);
186
+ });
187
+ });
188
+
189
+ describe("finalize", () => {
190
+ it.each([
191
+ ["a\r\nb\rc", "a\nb\nc\n"],
192
+ ["a\n\n\n ", "a\n"],
193
+ ["", "\n"],
194
+ ])("normalises %j", (text, want) => {
195
+ expect(finalize(text)).toBe(want);
196
+ });
197
+ });
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Markdown-with-frontmatter in and out for the agent generators. Parsing is YAML 1.2 (`yes` stays a string);
3
+ * emitting covers the shapes agent files use: scalars, scalar lists as block sequences and nested maps.
4
+ */
5
+ import { parse } from "yaml";
6
+
7
+ /** A frontmatter document: the YAML mapping with nullish values removed, and the trimmed body. */
8
+ export interface ParsedDoc {
9
+ data: Record<string, unknown>;
10
+ body: string;
11
+ }
12
+
13
+ type YamlScalar = string | number | boolean;
14
+ /** `null` and `undefined` are dropped at every level, as rulesync did. */
15
+ export type YamlValue = YamlScalar | readonly (YamlScalar | null | undefined)[] | YamlMap | null | undefined;
16
+ export interface YamlMap {
17
+ readonly [key: string]: YamlValue;
18
+ }
19
+
20
+ /**
21
+ * How strings are quoted. `default` quotes only what YAML would misread. `claude` also quotes any string holding
22
+ * `,` `[` `]` `{` `}` or `:`, or starting with `-` `?` `=`, as the YAML 1.1 dumper behind the committed
23
+ * `.claude/agents` files did.
24
+ */
25
+ export type YamlMode = "default" | "claude";
26
+
27
+ const FENCE = "---";
28
+
29
+ function toLf(text: string): string {
30
+ return text.replace(/\r\n?/g, "\n");
31
+ }
32
+
33
+ /** Parse `text` (the file at repo-relative POSIX `rel`); throws an Error whose message starts with `rel`. */
34
+ export function parseDoc(rel: string, text: string): ParsedDoc {
35
+ const lines = toLf(text)
36
+ .replace(/^\uFEFF/, "")
37
+ .split("\n");
38
+ if (lines[0]?.trimEnd() !== FENCE) throw new Error(`${rel}: missing frontmatter (start the file with a --- block)`);
39
+ const close = lines.findIndex((line, i) => i > 0 && line.trimEnd() === FENCE);
40
+ if (close === -1) throw new Error(`${rel}: invalid frontmatter: no closing --- line`);
41
+ let raw: unknown;
42
+ try {
43
+ raw = parse(lines.slice(1, close).join("\n"));
44
+ } catch (error) {
45
+ // The first line already names the line and column; the rest is a source excerpt that would split the message.
46
+ const reason = (error as Error).message.split("\n")[0]!.trim().replace(/:$/, "");
47
+ throw new Error(`${rel}: invalid frontmatter: ${reason}`);
48
+ }
49
+ if (raw !== null && !isMap(raw)) throw new Error(`${rel}: invalid frontmatter: expected a mapping`);
50
+ return {
51
+ data: raw === null ? {} : dropNullish(raw),
52
+ body: lines
53
+ .slice(close + 1)
54
+ .join("\n")
55
+ .trim(),
56
+ };
57
+ }
58
+
59
+ function isMap(value: unknown): value is Record<string, unknown> {
60
+ return typeof value === "object" && value !== null && !Array.isArray(value);
61
+ }
62
+
63
+ function dropNullish(map: Record<string, unknown>): Record<string, unknown> {
64
+ const out: Record<string, unknown> = {};
65
+ for (const [key, value] of Object.entries(map)) {
66
+ if (value === null || value === undefined) continue;
67
+ out[key] = Array.isArray(value)
68
+ ? value.filter((item) => item !== null && item !== undefined)
69
+ : isMap(value)
70
+ ? dropNullish(value)
71
+ : value;
72
+ }
73
+ return out;
74
+ }
75
+
76
+ /** Block-style YAML for `entries` in the order given, ending in `\n`; `""` when every value is nullish. */
77
+ export function emitYaml(entries: Iterable<readonly [string, YamlValue]>, mode: YamlMode = "default"): string {
78
+ return emitMap(entries, mode, "");
79
+ }
80
+
81
+ function emitMap(entries: Iterable<readonly [string, YamlValue]>, mode: YamlMode, indent: string): string {
82
+ let out = "";
83
+ for (const [key, value] of entries) {
84
+ if (value === null || value === undefined) continue;
85
+ const head = `${indent}${scalar(key, mode)}:`;
86
+ if (Array.isArray(value)) {
87
+ const items = (value as readonly (YamlScalar | null | undefined)[]).filter(
88
+ (item) => item !== null && item !== undefined,
89
+ );
90
+ out +=
91
+ items.length === 0
92
+ ? `${head} []\n`
93
+ : `${head}\n${items.map((item) => `${indent} - ${scalar(item, mode)}\n`).join("")}`;
94
+ } else if (typeof value === "object") {
95
+ const body = emitMap(Object.entries(value as YamlMap), mode, `${indent} `);
96
+ out += body === "" ? `${head} {}\n` : `${head}\n${body}`;
97
+ } else {
98
+ out += `${head} ${scalar(value as YamlScalar, mode)}\n`;
99
+ }
100
+ }
101
+ return out;
102
+ }
103
+
104
+ function scalar(value: YamlScalar, mode: YamlMode): string {
105
+ if (typeof value === "boolean") return String(value);
106
+ if (typeof value === "number") {
107
+ if (Number.isNaN(value)) return ".nan";
108
+ if (!Number.isFinite(value)) return value > 0 ? ".inf" : "-.inf";
109
+ return String(value);
110
+ }
111
+ // Newlines and control characters need escapes; a JSON string is a valid YAML double-quoted scalar.
112
+ if (/[\u0000-\u001f\u007f\u2028\u2029\uFEFF]/.test(value)) return JSON.stringify(value);
113
+ return needsQuotes(value, mode) ? `'${value.replaceAll("'", "''")}'` : value;
114
+ }
115
+
116
+ /** Words and numbers a YAML 1.1 reader types though YAML 1.2 reads them as strings. */
117
+ const YAML_11_TYPED =
118
+ /^(?:[yYnN]|yes|Yes|YES|no|No|NO|on|On|ON|off|Off|OFF|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?|[-+]?(?:0b[01_]+|0x[0-9a-fA-F_]+|0[0-7_]+|[0-9][0-9_]*(?:\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\.[0-9_]+(?:[eE][-+]?[0-9]+)?)|[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}(?:[Tt ].*)?)$/;
119
+
120
+ function needsQuotes(value: string, mode: YamlMode): boolean {
121
+ if (value === "" || value !== value.trim()) return true;
122
+ if (/^[,[\]{}#&*!|>'"%@`]/.test(value) || /^[-?:](?:\s|$)/.test(value)) return true;
123
+ if (/:(?:\s|$)/.test(value) || /\s#/.test(value)) return true;
124
+ if (mode === "claude" && (/[,[\]{}:]/.test(value) || /^[-?=]/.test(value))) return true;
125
+ if (YAML_11_TYPED.test(value)) return true;
126
+ return readsAsSomethingElse(value);
127
+ }
128
+
129
+ function readsAsSomethingElse(value: string): boolean {
130
+ try {
131
+ return parse(value) !== value;
132
+ } catch {
133
+ return true;
134
+ }
135
+ }
136
+
137
+ /** One file's final text: LF only, trailing whitespace trimmed, exactly one trailing `\n`. */
138
+ export function finalize(text: string): string {
139
+ return `${toLf(text).trimEnd()}\n`;
140
+ }