@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,304 @@
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 { fileURLToPath } from "node:url";
6
+ import * as tar from "tar";
7
+ import { afterEach, describe, expect, it } from "vitest";
8
+ import { VALID_LESSON } from "../scripts/fixtures/persist-bodies.ts";
9
+ import { index, lessons, type Output, type SyncContext } from "./outputs.ts";
10
+ import { mapSections, sectionBody } from "./maps.ts";
11
+
12
+ const here = path.dirname(fileURLToPath(import.meta.url));
13
+ const fixtureWs = path.join(here, "fixtures/maps-ws.tgz");
14
+ const goldenIndex = path.join(here, "fixtures/maps/golden/INDEX.md");
15
+ const INDEX = "references/INDEX.md";
16
+
17
+ const tmpDirs: string[] = [];
18
+ afterEach(() => {
19
+ for (const d of tmpDirs.splice(0)) fs.rmSync(d, { recursive: true, force: true });
20
+ });
21
+
22
+ function tmpDir(prefix: string) {
23
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
24
+ tmpDirs.push(dir);
25
+ return dir;
26
+ }
27
+
28
+ function put(ws: string, rel: string, text: string) {
29
+ const file = path.join(ws, rel);
30
+ fs.mkdirSync(path.dirname(file), { recursive: true });
31
+ fs.writeFileSync(file, text);
32
+ }
33
+
34
+ function selfWs(prefix: string) {
35
+ const ws = tmpDir(prefix);
36
+ put(ws, "package.json", '{ "name": "@workerstack/repo" }\n');
37
+ fs.mkdirSync(path.join(ws, "references"), { recursive: true });
38
+ return ws;
39
+ }
40
+
41
+ function copyFixtureWs() {
42
+ const dest = tmpDir("maps-ws-");
43
+ // packed, so no skill tree sits live in the repo for an agent to discover
44
+ tar.x({ file: fixtureWs, cwd: dest, sync: true });
45
+ put(dest, "node_modules/@workerstack/repo/agents/skills/beta/SKILL.md", "---\nname: beta\n---\n");
46
+ return dest;
47
+ }
48
+
49
+ /** A context over the real files of `ws`, named like this package or not. */
50
+ function context(ws: string, name = "@workerstack/repo"): SyncContext {
51
+ return {
52
+ root: ws,
53
+ config: {
54
+ packs: ["agents", "docs"],
55
+ agents: { hosts: [], skills: [] },
56
+ ...BASE_CONFIG,
57
+ },
58
+ workspace: { root: ws, manifest: { name }, packages: [] },
59
+ read: (rel) => {
60
+ try {
61
+ return fs.readFileSync(path.join(ws, rel), "utf8");
62
+ } catch {
63
+ return null;
64
+ }
65
+ },
66
+ };
67
+ }
68
+
69
+ function indexText(ws: string): string {
70
+ const result = index(context(ws));
71
+ expect(result.errors).toEqual([]);
72
+ const output: Output | undefined = result.outputs[0];
73
+ if (output?.kind !== "file") throw new Error("no index file");
74
+ return output.content;
75
+ }
76
+
77
+ describe("index maps", () => {
78
+ it("fills repo:skills and repo:refs and keeps the hand text around them byte for byte", () => {
79
+ expect(indexText(copyFixtureWs())).toBe(fs.readFileSync(goldenIndex, "utf8"));
80
+ });
81
+
82
+ it("is stable: the written index regenerates to itself", () => {
83
+ const ws = copyFixtureWs();
84
+ put(ws, INDEX, indexText(ws));
85
+ expect(indexText(ws)).toBe(fs.readFileSync(path.join(ws, INDEX), "utf8"));
86
+ });
87
+
88
+ it("refs rows name the stock package path and skip INDEX.md", () => {
89
+ const text = indexText(copyFixtureWs());
90
+ expect(text).toContain("| node_modules/@workerstack/repo/references/alpha.md | Alpha |");
91
+ expect(text).toContain("| node_modules/@workerstack/repo/references/nested/beta.md |");
92
+ expect(text).not.toMatch(/references\/INDEX\.md \|/);
93
+ expect(text).not.toMatch(/Body ignored|Beta body|More text ignored/);
94
+ });
95
+
96
+ it("creates the sections when INDEX.md is missing", () => {
97
+ const ws = copyFixtureWs();
98
+ fs.rmSync(path.join(ws, INDEX));
99
+ const text = indexText(ws);
100
+ expect(text).toContain("<!-- repo:skills -->");
101
+ expect(text).toContain("<!-- repo:refs -->");
102
+ });
103
+
104
+ it("another package gets no skills or refs map", () => {
105
+ const ws = copyFixtureWs();
106
+ const result = index(context(ws, "consumer"));
107
+ const output = result.outputs[0];
108
+ expect(output?.kind === "file" && output.content).not.toContain("alpha-work");
109
+ });
110
+
111
+ it("includes extra refs next to the fixture refs", () => {
112
+ const ws = copyFixtureWs();
113
+ put(ws, "references/stripe.md", "---\ntags: [stripe]\n---\n# Stripe\n\nConsumer payments ref.\n");
114
+ const text = indexText(ws);
115
+ expect(text).toMatch(/references\/stripe\.md \| Stripe \| stripe \| Consumer payments ref/);
116
+ expect(text).toMatch(/alpha\.md/);
117
+ });
118
+ });
119
+
120
+ describe("mapSections skills", () => {
121
+ const rowOf = (ws: string, name: string) =>
122
+ mapSections(ws)
123
+ .skills.split("\n")
124
+ .find((line) => line.startsWith(`| ${name} |`));
125
+
126
+ it("skips a map.task that is an object or a list and keeps a string", () => {
127
+ const ws = selfWs("maps-task-");
128
+ put(ws, "agents/skills/obj/SKILL.md", "---\nname: obj\nmap:\n task:\n nested: yes\n---\n");
129
+ put(ws, "agents/skills/arr/SKILL.md", "---\nname: arr\nmap:\n task:\n - a\n - b\n---\n");
130
+ put(ws, "agents/skills/str/SKILL.md", "---\nname: str\nmap:\n task: keep-me\n---\n");
131
+ const { skills } = mapSections(ws);
132
+ expect(skills).toMatch(/\| str \| local \| \| keep-me \|/);
133
+ expect(skills).not.toMatch(/\[object Object\]|nested/);
134
+ expect(rowOf(ws, "obj")).toBe("| obj | local | | | | |");
135
+ expect(rowOf(ws, "arr")).toBe("| arr | local | | | | |");
136
+ });
137
+
138
+ it("reads dotted map keys, map.refs lists and strings, and commandPath", () => {
139
+ const ws = selfWs("maps-keys-");
140
+ put(
141
+ ws,
142
+ "agents/skills/dotted/SKILL.md",
143
+ "---\nname: dotted\nmap.task: do it\nmap.refs:\n - a.md\n - b.md\ncommandPath: bun repo x\n---\n",
144
+ );
145
+ put(ws, "agents/skills/dotstr/SKILL.md", "---\nname: dotstr\nmap.refs: one.md\n---\n");
146
+ put(ws, "agents/skills/nested/SKILL.md", "---\nname: nested\nmap:\n refs:\n - c.md\n---\n");
147
+ put(ws, "agents/skills/nestedstr/SKILL.md", "---\nname: nestedstr\nmap:\n refs: two.md\n---\n");
148
+ expect(rowOf(ws, "dotted")).toBe("| dotted | local | | do it | bun repo x | a.md, b.md |");
149
+ expect(rowOf(ws, "dotstr")).toBe("| dotstr | local | | | | one.md |");
150
+ expect(rowOf(ws, "nested")).toBe("| nested | local | | | | c.md |");
151
+ expect(rowOf(ws, "nestedstr")).toBe("| nestedstr | local | | | | two.md |");
152
+ });
153
+
154
+ it("falls back to the dir name and the first body line, and reads quoted and folded values", () => {
155
+ const ws = selfWs("maps-fallback-");
156
+ put(ws, "agents/skills/plain/SKILL.md", "# Plain\n\n## Sub\n<!-- note -->\n---\nThe summary line.\n");
157
+ put(ws, "agents/skills/fenced/SKILL.md", "# Fenced\n\n```\n");
158
+ put(ws, "agents/skills/noname/SKILL.md", "---\ndescription: from frontmatter\n---\n");
159
+ put(
160
+ ws,
161
+ "agents/skills/folded/SKILL.md",
162
+ "---\nname: 'it''s'\ndescription: >\n one\n\n two\ncommandPath: \"a b\"\n---\n",
163
+ );
164
+ put(ws, "agents/skills/badquote/SKILL.md", '---\nname: "a\\qb"\ndescription: # comment\n---\n');
165
+ put(ws, "agents/skills/empty/SKILL.md", "");
166
+ put(ws, "agents/skills/hidden/.x", "");
167
+ put(ws, "agents/skills/.dot/SKILL.md", "---\nname: dot\n---\n");
168
+ put(ws, "agents/skills/nofile/README.md", "x");
169
+ expect(rowOf(ws, "plain")).toBe("| plain | local | The summary line. | | | |");
170
+ expect(rowOf(ws, "fenced")).toBe("| fenced | local | | | | |");
171
+ expect(rowOf(ws, "noname")).toBe("| noname | local | from frontmatter | | | |");
172
+ expect(rowOf(ws, "it's")).toBe("| it's | local | one two | | a b | |");
173
+ expect(mapSections(ws).skills).not.toMatch(/dot|hidden|nofile/);
174
+ expect(mapSections(ws).skills).toContain("| empty | local |");
175
+ });
176
+
177
+ it("marks a skill the stock package ships as repo, and truncates long descriptions", () => {
178
+ const ws = selfWs("maps-long-");
179
+ put(ws, "agents/skills/long/SKILL.md", `---\nname: long\ndescription: ${"word ".repeat(80).trim()}\n---\n`);
180
+ put(ws, "node_modules/@workerstack/repo/agents/skills/long/SKILL.md", "x");
181
+ const row = rowOf(ws, "long") ?? "";
182
+ expect(row).toMatch(/^\| long \| repo \| word .*…/);
183
+ expect(row.length).toBeLessThan(200);
184
+ });
185
+
186
+ it("escapes pipes inside a cell and handles every scalar form of the small YAML reader", () => {
187
+ const ws = selfWs("maps-yaml-");
188
+ put(
189
+ ws,
190
+ "agents/skills/yaml/SKILL.md",
191
+ [
192
+ "---",
193
+ "name: yaml",
194
+ "# a comment",
195
+ "",
196
+ "description: a | b",
197
+ "tags: [x, 'y', \"z\"]",
198
+ "block:",
199
+ " inner: value",
200
+ " list:",
201
+ " - one",
202
+ " deeper:",
203
+ " k: v",
204
+ " tilted: 1",
205
+ " over: 2",
206
+ "other: |",
207
+ " literal text",
208
+ "emptyflow: []",
209
+ "bare:",
210
+ "not a key line",
211
+ "---",
212
+ "",
213
+ ].join("\n"),
214
+ );
215
+ expect(rowOf(ws, "yaml")).toBe("| yaml | local | a \\| b | | | |");
216
+ });
217
+ });
218
+
219
+ describe("mapSections refs", () => {
220
+ const rowOf = (ws: string, file: string) =>
221
+ mapSections(ws)
222
+ .refs.split("\n")
223
+ .find((line) => line.includes(`references/${file} |`));
224
+
225
+ it("keeps a bare row for huge, binary and unreadable refs, and skips dotfiles and non-markdown", () => {
226
+ const ws = selfWs("maps-huge-");
227
+ put(ws, "references/huge.md", `# Huge\n\n${"x".repeat(512 * 1024 + 1)}`);
228
+ put(ws, "references/bin.md", "# Bin\0\n");
229
+ put(ws, "references/.hidden.md", "# Hidden\n");
230
+ put(ws, "references/notes.txt", "x");
231
+ put(ws, "references/.dir/skipped.md", "# Skipped\n");
232
+ fs.symlinkSync(path.join(ws, "references", "huge.md"), path.join(ws, "references", "link.md"));
233
+ const { refs } = mapSections(ws);
234
+ expect(rowOf(ws, "huge.md")).toBe("| node_modules/@workerstack/repo/references/huge.md | huge | | |");
235
+ expect(rowOf(ws, "bin.md")).toBe("| node_modules/@workerstack/repo/references/bin.md | bin | | |");
236
+ expect(refs).not.toMatch(/hidden|notes|skipped|link\.md/i);
237
+ expect(refs).not.toMatch(/^x{100,}/m);
238
+ });
239
+
240
+ it("titles a ref from its first heading, falling back to its file name", () => {
241
+ const ws = selfWs("maps-title-");
242
+ put(ws, "references/a.md", "---\ntags: lone\n---\n# Title ##\n\nSummary here.\n");
243
+ put(ws, "references/b.md", "no heading\n");
244
+ put(ws, "references/c.md", "---\ntags: [p, q]\n---\n# C\n");
245
+ put(ws, "references/d.md", '---\ntags: "[r, s]"\n---\n# D\n');
246
+ expect(rowOf(ws, "a.md")).toBe("| node_modules/@workerstack/repo/references/a.md | Title | lone | Summary here. |");
247
+ expect(rowOf(ws, "b.md")).toBe("| node_modules/@workerstack/repo/references/b.md | b | | |");
248
+ expect(rowOf(ws, "c.md")).toBe("| node_modules/@workerstack/repo/references/c.md | C | p, q | |");
249
+ expect(rowOf(ws, "d.md")).toBe("| node_modules/@workerstack/repo/references/d.md | D | r, s | |");
250
+ });
251
+
252
+ it("is empty for a repo with no references or skills", () => {
253
+ const ws = tmpDir("maps-empty-");
254
+ expect(mapSections(ws).refs.split("\n")).toHaveLength(2);
255
+ expect(mapSections(ws).skills.split("\n")).toHaveLength(2);
256
+ });
257
+ });
258
+
259
+ describe("lessons generator", () => {
260
+ function withLesson() {
261
+ const ws = copyFixtureWs();
262
+ put(ws, "src/a.ts", "x\n");
263
+ put(ws, ".repo/lessons/plan-forks.md", VALID_LESSON);
264
+ return ws;
265
+ }
266
+
267
+ it("writes the lessons index, tracked, from the lesson files", () => {
268
+ const result = lessons(context(withLesson()));
269
+ expect(result.errors).toEqual([]);
270
+ expect(result.outputs).toEqual([
271
+ {
272
+ kind: "file",
273
+ path: ".repo/lessons/INDEX.md",
274
+ content: expect.stringContaining(".repo/lessons/plan-forks.md"),
275
+ tracked: true,
276
+ managed: "whole",
277
+ },
278
+ ]);
279
+ });
280
+
281
+ it("reports a lesson whose paths match nothing, and still writes the index", () => {
282
+ const ws = withLesson();
283
+ fs.rmSync(path.join(ws, "src"), { recursive: true });
284
+ const result = lessons(context(ws));
285
+ expect(result.errors).toEqual([expect.stringMatching(/src\/\*\* matches no file/)]);
286
+ expect(result.outputs).toHaveLength(1);
287
+ });
288
+
289
+ it("writes nothing in a repo without lessons", () => {
290
+ expect(lessons(context(copyFixtureWs()))).toEqual({ outputs: [], errors: [] });
291
+ });
292
+ });
293
+
294
+ describe("sectionBody", () => {
295
+ it("returns the trimmed lines between a marker pair", () => {
296
+ expect(sectionBody("a\n<!-- repo:x --> \none \r\ntwo\n<!-- /repo:x -->\nb", "x")).toEqual(["one", "two"]);
297
+ });
298
+
299
+ it("is null when the pair is missing or unclosed", () => {
300
+ expect(sectionBody("a\n", "x")).toBeNull();
301
+ expect(sectionBody("<!-- repo:x -->\nrest", "x")).toBeNull();
302
+ expect(sectionBody("<!-- /repo:x -->\n<!-- repo:x -->\n", "x")).toBeNull();
303
+ });
304
+ });
@@ -0,0 +1,297 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { splitFrontmatter } from "../scripts/emit-agent-shell-tools.ts";
4
+ import { STOCK_SKILLS_REL, listSkillDirNames } from "../scripts/skill-conflicts.ts";
5
+ import { lines } from "../scripts/managed-text.ts";
6
+ import { stockRoot } from "./stock.ts";
7
+
8
+ /** A frontmatter value as the small YAML reader below produces it. */
9
+ type YamlValue = string | string[] | YamlObject;
10
+ interface YamlObject {
11
+ [key: string]: YamlValue;
12
+ }
13
+
14
+ interface RefFile {
15
+ abs: string;
16
+ rel: string;
17
+ }
18
+
19
+ interface SkillFields {
20
+ name: string;
21
+ origin: string;
22
+ descSrc: YamlValue | string;
23
+ commandPath: string;
24
+ refs: string;
25
+ task: string;
26
+ }
27
+
28
+ const isYamlObject = (v: unknown): v is YamlObject => typeof v === "object" && v !== null && !Array.isArray(v);
29
+
30
+ /** How the stock package's reference files are named in the refs table. */
31
+ const STOCK_REFS_PREFIX = "node_modules/@workerstack/repo/references/";
32
+ const REFS_REL = "references";
33
+ const DESC_MAX = 120;
34
+ const HUGE_BYTES = 512 * 1024;
35
+ const SKIP_REF_NAMES = new Set(["INDEX.md"]);
36
+
37
+ function unquoteYaml(raw: unknown): string {
38
+ const s = String(raw).trim();
39
+ if (s.length >= 2 && s[0] === '"' && s[s.length - 1] === '"') {
40
+ try {
41
+ return String(JSON.parse(s));
42
+ } catch {
43
+ return s.slice(1, -1);
44
+ }
45
+ }
46
+ if (s.length >= 2 && s[0] === "'" && s[s.length - 1] === "'") {
47
+ return s.slice(1, -1).replace(/''/g, "'");
48
+ }
49
+ return s;
50
+ }
51
+
52
+ function parseFlowSeq(s: unknown): string[] | null {
53
+ const t = String(s).trim();
54
+ if (!t.startsWith("[") || !t.endsWith("]")) return null;
55
+ const inner = t.slice(1, -1).trim();
56
+ if (!inner) return [];
57
+ return inner
58
+ .split(",")
59
+ .map((x) => unquoteYaml(x.trim()))
60
+ .filter((x) => x !== "");
61
+ }
62
+
63
+ function indentOf(line: unknown): number {
64
+ // The pattern matches the empty string, so a match always exists.
65
+ return String(line).match(/^[ \t]*/)![0].length;
66
+ }
67
+
68
+ function parseYamlDoc(fm: unknown): YamlObject {
69
+ const lines = String(fm).split(/\r?\n/);
70
+ // Every call below is guarded by `i < lines.length`, so the fallback never shows.
71
+ const at = (n: number): string => lines[n]!;
72
+ let i = 0;
73
+ function parseBlock(baseIndent: number): YamlObject {
74
+ const obj: YamlObject = {};
75
+ while (i < lines.length) {
76
+ const line = at(i);
77
+ if (!line.trim() || /^\s*#/.test(line)) {
78
+ i += 1;
79
+ continue;
80
+ }
81
+ const ind = indentOf(line);
82
+ if (ind < baseIndent) break;
83
+ if (baseIndent > 0 && ind > baseIndent) break;
84
+ const trimmed = line.trim();
85
+ const km = trimmed.match(/^([^:#\s][^:]*):\s*(.*?)\s*$/);
86
+ if (!km) {
87
+ i += 1;
88
+ continue;
89
+ }
90
+ // Both groups are unconditional in the pattern, so a match has them.
91
+ const key = km[1]!.trim();
92
+ const val = km[2]!;
93
+ i += 1;
94
+ if (val === ">" || val === "|") {
95
+ const parts: string[] = [];
96
+ while (i < lines.length) {
97
+ const n = at(i);
98
+ if (!n.trim()) {
99
+ parts.push("");
100
+ i += 1;
101
+ continue;
102
+ }
103
+ if (indentOf(n) <= ind) break;
104
+ parts.push(n.trim());
105
+ i += 1;
106
+ }
107
+ obj[key] = parts.filter(Boolean).join(" ");
108
+ continue;
109
+ }
110
+ if (val === "") {
111
+ if (i < lines.length && indentOf(at(i)) > ind) {
112
+ const next = at(i).trim();
113
+ if (next.startsWith("- ")) {
114
+ const arr: string[] = [];
115
+ while (i < lines.length && indentOf(at(i)) > ind && at(i).trim().startsWith("- ")) {
116
+ arr.push(unquoteYaml(at(i).trim().replace(/^- /, "")));
117
+ i += 1;
118
+ }
119
+ obj[key] = arr;
120
+ } else {
121
+ obj[key] = parseBlock(indentOf(at(i)));
122
+ }
123
+ } else {
124
+ obj[key] = {};
125
+ }
126
+ continue;
127
+ }
128
+ const seq = parseFlowSeq(val);
129
+ obj[key] = seq != null ? seq : unquoteYaml(val);
130
+ }
131
+ return obj;
132
+ }
133
+ return parseBlock(0);
134
+ }
135
+
136
+ function oneLine(s: unknown, max: number = DESC_MAX): string {
137
+ const t = String(s).replace(/\s+/g, " ").trim();
138
+ if (t.length <= max) return t;
139
+ return `${t.slice(0, Math.max(0, max - 1)).trimEnd()}…`;
140
+ }
141
+
142
+ function cell(s: unknown): string {
143
+ return oneLine(s).replace(/\|/g, "\\|");
144
+ }
145
+
146
+ function mapRefs(parsed: YamlObject): string[] {
147
+ const dotted = parsed["map.refs"];
148
+ if (Array.isArray(dotted)) return dotted.map(String);
149
+ if (typeof dotted === "string" && dotted) return [dotted];
150
+ const m = parsed.map;
151
+ if (isYamlObject(m) && Array.isArray(m.refs)) return m.refs.map(String);
152
+ if (isYamlObject(m) && typeof m.refs === "string" && m.refs) return [m.refs];
153
+ return [];
154
+ }
155
+
156
+ function scalarTask(v: unknown): string {
157
+ return typeof v === "string" && v !== "" ? v : "";
158
+ }
159
+
160
+ function mapTask(parsed: YamlObject): string {
161
+ const dotted = scalarTask(parsed["map.task"]);
162
+ if (dotted) return dotted;
163
+ const m = parsed.map;
164
+ if (isYamlObject(m)) return scalarTask(m.task);
165
+ return "";
166
+ }
167
+
168
+ function tagsOf(parsed: YamlObject): string[] {
169
+ if (Array.isArray(parsed.tags)) return parsed.tags.map(String);
170
+ if (typeof parsed.tags === "string" && parsed.tags) {
171
+ const seq = parseFlowSeq(parsed.tags);
172
+ if (seq) return seq.map(String);
173
+ return [parsed.tags];
174
+ }
175
+ return [];
176
+ }
177
+
178
+ function firstH1(body: string): string {
179
+ const m = body.match(/^#\s+(.+?)\s*$/m);
180
+ // Group 1 is unconditional in the pattern, so a match has it.
181
+ return m ? m[1]!.replace(/\s+#+\s*$/, "").trim() : "";
182
+ }
183
+
184
+ function firstSummary(body: string): string {
185
+ const lines = body.split(/\r?\n/);
186
+ let pastH1 = false;
187
+ for (const line of lines) {
188
+ const t = line.trim();
189
+ if (!pastH1) {
190
+ if (/^#\s+/.test(t)) pastH1 = true;
191
+ continue;
192
+ }
193
+ if (!t) continue;
194
+ if (/^#/.test(t) || /^```/.test(t) || /^<!--/.test(t) || /^---/.test(t)) continue;
195
+ return oneLine(t);
196
+ }
197
+ return "";
198
+ }
199
+
200
+ function parseMarkdown(src: string): { parsed: YamlObject; body: string } {
201
+ const split = splitFrontmatter(src);
202
+ if (!split.ok) return { parsed: {}, body: src };
203
+ return { parsed: parseYamlDoc(split.fm), body: split.body };
204
+ }
205
+
206
+ function walkRefMd(dir: string, relBase: string, acc: RefFile[]): RefFile[] {
207
+ let entries: fs.Dirent[];
208
+ try {
209
+ entries = fs.readdirSync(dir, { withFileTypes: true });
210
+ } catch {
211
+ return acc;
212
+ }
213
+ for (const ent of entries) {
214
+ if (ent.name.startsWith(".")) continue;
215
+ const abs = path.join(dir, ent.name);
216
+ const rel = `${relBase}/${ent.name}`;
217
+ if (ent.isDirectory()) {
218
+ walkRefMd(abs, rel, acc);
219
+ continue;
220
+ }
221
+ if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
222
+ if (SKIP_REF_NAMES.has(ent.name)) continue;
223
+ acc.push({ abs, rel });
224
+ }
225
+ return acc;
226
+ }
227
+
228
+ function isFile(abs: string): boolean {
229
+ try {
230
+ return fs.statSync(abs).isFile();
231
+ } catch {
232
+ return false;
233
+ }
234
+ }
235
+
236
+ function skillFields(root: string, dir: string): SkillFields {
237
+ const raw = fs.readFileSync(path.join(root, STOCK_SKILLS_REL, dir, "SKILL.md"), "utf8");
238
+ const { parsed, body } = parseMarkdown(raw);
239
+ const name = parsed.name != null && parsed.name !== "" ? String(parsed.name) : dir;
240
+ const descSrc = parsed.description != null && parsed.description !== "" ? parsed.description : firstSummary(body);
241
+ const commandPath = parsed.commandPath != null ? String(parsed.commandPath) : "";
242
+ const refs = mapRefs(parsed).join(", ");
243
+ const task = mapTask(parsed);
244
+ const origin = isFile(path.join(stockRoot(root), STOCK_SKILLS_REL, dir, "SKILL.md")) ? "repo" : "local";
245
+ return { name, origin, descSrc, commandPath, refs, task };
246
+ }
247
+
248
+ function skillRowFromFields(f: SkillFields): string {
249
+ return `| ${cell(f.name)} | ${cell(f.origin)} | ${cell(f.descSrc)} | ${cell(f.task)} | ${cell(f.commandPath)} | ${cell(f.refs)} |`;
250
+ }
251
+
252
+ function refRow(file: RefFile): string {
253
+ const shown = `${STOCK_REFS_PREFIX}${file.rel.slice(REFS_REL.length + 1)}`;
254
+ const fallbackTitle = path.basename(file.rel, ".md");
255
+ const bare = `| ${cell(shown)} | ${cell(fallbackTitle)} | | |`;
256
+ if (fs.statSync(file.abs).size > HUGE_BYTES) return bare;
257
+ const raw = fs.readFileSync(file.abs, "utf8");
258
+ if (raw.includes("\0")) return bare;
259
+ const { parsed, body } = parseMarkdown(raw);
260
+ const title = firstH1(body) || fallbackTitle;
261
+ const tags = tagsOf(parsed).join(", ");
262
+ const summary = firstSummary(body);
263
+ return `| ${cell(shown)} | ${cell(title)} | ${cell(tags)} | ${cell(summary)} |`;
264
+ }
265
+
266
+ function table(header: string[], rows: string[]): string {
267
+ return [`| ${header.join(" | ")} |`, `| ${header.map(() => "---").join(" | ")} |`, ...rows].join("\n");
268
+ }
269
+
270
+ /**
271
+ * The bodies of the `repo:skills` and `repo:refs` sections of references/INDEX.md, from one scan of the skills
272
+ * and references under `root`. Only this package keeps them; the hand-written text of the index is never touched.
273
+ */
274
+ export function mapSections(root: string): { skills: string; refs: string } {
275
+ const skills = listSkillDirNames(path.join(root, STOCK_SKILLS_REL)).map((dir) =>
276
+ skillRowFromFields(skillFields(root, dir)),
277
+ );
278
+ const refs = walkRefMd(path.join(root, REFS_REL), REFS_REL, [])
279
+ .sort((a, b) => a.rel.localeCompare(b.rel))
280
+ .map(refRow);
281
+ return {
282
+ skills: table(["name", "origin", "description", "task", "commandPath", "refs"], skills),
283
+ refs: table(["path", "title", "tags", "summary"], refs),
284
+ };
285
+ }
286
+
287
+ /** Where `repo sync` keeps the generated sections that `repo doctor` reads back. */
288
+ export const INDEX_REL = "references/INDEX.md";
289
+
290
+ /** The lines between `<!-- repo:<name> -->` and `<!-- /repo:<name> -->`, or null when the pair is missing. */
291
+ export function sectionBody(text: string, name: string): string[] | null {
292
+ const all = lines(text);
293
+ const start = all.findIndex((l) => l.trimEnd() === `<!-- repo:${name} -->`);
294
+ if (start === -1) return null;
295
+ const stop = all.findIndex((l, at) => at > start && l.trimEnd() === `<!-- /repo:${name} -->`);
296
+ return stop === -1 ? null : all.slice(start + 1, stop).map((l) => l.trimEnd());
297
+ }