@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,371 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { afterEach, describe, expect, it } from "vitest";
6
+ import type { SkillPack } from "../config.ts";
7
+ import { CONSUMER_DIR, STOCK_DIR } from "../../dev/capture-tree-goldens.ts";
8
+ import { loadSources, type Doc, type Sources } from "./agents/sources.ts";
9
+ import { ALWAYS_ON_BUDGET_BYTES, repoDirProblems } from "./repo-dir.ts";
10
+
11
+ const SELF_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
12
+ const ALLOWED =
13
+ "rules/, subagents/, skills/, lessons/, tmp/, global-settings/, settings/claude.json, hooks.jsonc, mcp-policy.jsonc, permissions.json, mcp.json, map.jsonc, lint-baseline.json (read by repo turn and repo check)";
14
+
15
+ const temps: string[] = [];
16
+ afterEach(() => {
17
+ for (const dir of temps.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
18
+ });
19
+
20
+ function repo(files: Record<string, string> = {}): string {
21
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "workerstack-repo-dir-"));
22
+ temps.push(root);
23
+ for (const [rel, text] of Object.entries(files)) {
24
+ fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
25
+ fs.writeFileSync(path.join(root, rel), text);
26
+ }
27
+ return root;
28
+ }
29
+
30
+ function rule(name: string, body: string, paths?: string[]): Doc {
31
+ return {
32
+ rel: `.repo/rules/${name}.md`,
33
+ file: `${name}.md`,
34
+ origin: "repo",
35
+ scope: "",
36
+ data: { description: "d", ...(paths ? { paths } : {}) },
37
+ body,
38
+ };
39
+ }
40
+
41
+ const sourcesOf = (rules: Doc[] = []): Sources => ({
42
+ rules,
43
+ subagents: [],
44
+ hooks: { rel: "agents/hooks.jsonc", value: {} },
45
+ });
46
+
47
+ function check(
48
+ root: string,
49
+ rules: Doc[] = [],
50
+ listed: string[] | null = null,
51
+ skills: readonly SkillPack[] = [],
52
+ packages: string[] = [],
53
+ ): string[] {
54
+ return repoDirProblems(root, { sources: sourcesOf(rules), stock: SELF_ROOT, listed, skills, packages });
55
+ }
56
+
57
+ const skill = (name: string, description = "does a thing", body = "") =>
58
+ `---\nname: ${name}\ndescription: ${description}\n---\n${body}`;
59
+
60
+ describe("the layout", () => {
61
+ it("passes when .repo does not exist", () => {
62
+ expect(check(repo())).toEqual([]);
63
+ });
64
+
65
+ it("passes every entry sync reads, and ignores whatever is in tmp/", () => {
66
+ const root = repo({
67
+ ".repo/rules/a.md": "x",
68
+ ".repo/subagents/a.md": "x",
69
+ ".repo/skills/deploy/SKILL.md": skill("deploy"),
70
+ ".repo/lessons/INDEX.md": "x",
71
+ ".repo/tmp/anything/at/all.bin": "x",
72
+ ".repo/global-settings/claude.json": "{}",
73
+ ".repo/settings/claude.json": "{}",
74
+ ".repo/hooks.jsonc": "{}",
75
+ ".repo/mcp-policy.jsonc": "{}",
76
+ ".repo/permissions.json": "{}",
77
+ ".repo/mcp.json": "{}",
78
+ ".repo/map.jsonc": "{}",
79
+ ".repo/lint-baseline.json": "{}",
80
+ });
81
+ expect(check(root)).toEqual([]);
82
+ });
83
+
84
+ it("names an unknown file with the allowed list and where explanations go", () => {
85
+ expect(check(repo({ ".repo/notes.md": "x" }))).toEqual([
86
+ `.repo/notes.md: not part of .repo; allowed: ${ALLOWED}; explanations go in references/`,
87
+ ]);
88
+ });
89
+
90
+ it("names an unknown folder with a trailing slash", () => {
91
+ expect(check(repo({ ".repo/misc/a.md": "x" }))).toEqual([
92
+ `.repo/misc/: not part of .repo; allowed: ${ALLOWED}; explanations go in references/`,
93
+ ]);
94
+ });
95
+
96
+ it("says nothing reads scripts/ or a settings file other than claude.json, and names any other copied file as not part of .repo", () => {
97
+ const root = repo({
98
+ ".repo/scripts/x.ts": "x",
99
+ ".repo/repo.pipeline.jsonc": "{}",
100
+ ".repo/settings/codex.json": "{}",
101
+ });
102
+ expect(check(root)).toEqual([
103
+ `.repo/repo.pipeline.jsonc: not part of .repo; allowed: ${ALLOWED}; explanations go in references/`,
104
+ ".repo/scripts/: nothing reads it; move what you need out of .repo and delete it",
105
+ ".repo/settings/codex.json: nothing reads it; move what you need out of .repo and delete it",
106
+ ]);
107
+ });
108
+ });
109
+
110
+ describe("skills", () => {
111
+ it("flags a skill folder without SKILL.md", () => {
112
+ expect(check(repo({ ".repo/skills/deploy/notes.md": "x" }))).toEqual([
113
+ ".repo/skills/deploy: SKILL.md is missing; add it or delete the folder",
114
+ ]);
115
+ });
116
+
117
+ it("flags a file straight under skills/", () => {
118
+ expect(check(repo({ ".repo/skills/README.md": "x" }))).toEqual([
119
+ ".repo/skills/README.md: not a skill folder; skills are skills/<name>/SKILL.md",
120
+ ]);
121
+ });
122
+
123
+ it("requires name to equal the folder name", () => {
124
+ expect(check(repo({ ".repo/skills/deploy/SKILL.md": skill("ship") }))).toEqual([
125
+ '.repo/skills/deploy/SKILL.md: name "ship" must equal the folder name "deploy"',
126
+ ]);
127
+ expect(check(repo({ ".repo/skills/deploy/SKILL.md": "---\ndescription: d\n---\n" }))).toEqual([
128
+ '.repo/skills/deploy/SKILL.md: name is required; set it to the folder name "deploy"',
129
+ ]);
130
+ });
131
+
132
+ it("requires a description of at most 1024 characters", () => {
133
+ expect(check(repo({ ".repo/skills/deploy/SKILL.md": "---\nname: deploy\n---\n" }))).toEqual([
134
+ ".repo/skills/deploy/SKILL.md: description is required",
135
+ ]);
136
+ expect(check(repo({ ".repo/skills/deploy/SKILL.md": skill("deploy", "a".repeat(1025)) }))).toEqual([
137
+ ".repo/skills/deploy/SKILL.md: description is 1025 characters, over the 1024 limit; shorten it",
138
+ ]);
139
+ expect(check(repo({ ".repo/skills/deploy/SKILL.md": skill("deploy", "a".repeat(1024)) }))).toEqual([]);
140
+ });
141
+
142
+ it("reports unreadable frontmatter once", () => {
143
+ expect(check(repo({ ".repo/skills/deploy/SKILL.md": "plain\n" }))).toEqual([
144
+ ".repo/skills/deploy/SKILL.md: missing frontmatter (start the file with a --- block)",
145
+ ]);
146
+ });
147
+
148
+ it("flags a dead link, resolved from the skill's folder", () => {
149
+ const body = "See [steps](./steps.md#top) and [more](refs/more.md).\n";
150
+ const root = repo({
151
+ ".repo/skills/deploy/SKILL.md": skill("deploy", "d", body),
152
+ ".repo/skills/deploy/refs/more.md": "x",
153
+ });
154
+ expect(check(root)).toEqual([".repo/skills/deploy/SKILL.md: link ./steps.md does not exist"]);
155
+ fs.writeFileSync(path.join(root, ".repo/skills/deploy/steps.md"), "x");
156
+ expect(check(root)).toEqual([]);
157
+ });
158
+
159
+ it("asks a skill named like a stock skill for overrides, unless its pack is off", () => {
160
+ const root = repo({ ".repo/skills/ask/SKILL.md": skill("ask") });
161
+ expect(check(root)).toEqual([
162
+ ".repo/skills/ask: a stock skill has this name; add `overrides: ask` to its SKILL.md or rename it",
163
+ ]);
164
+ const ok = repo({ ".repo/skills/ask/SKILL.md": `---\nname: ask\ndescription: d\noverrides: ask\n---\n` });
165
+ expect(check(ok)).toEqual([]);
166
+ const bad = repo({ ".repo/skills/mine/SKILL.md": `---\nname: mine\ndescription: d\noverrides: nope\n---\n` });
167
+ expect(check(bad)).toEqual(['.repo/skills/mine/SKILL.md: overrides "nope" but no stock skill has that name']);
168
+ });
169
+
170
+ it("does not ask for overrides when the stock is this repo itself", () => {
171
+ const root = repo({ ".repo/skills/ask/SKILL.md": skill("ask") });
172
+ expect(
173
+ repoDirProblems(root, { sources: sourcesOf(), stock: root, listed: null, skills: [], packages: [] }),
174
+ ).toEqual([]);
175
+ });
176
+ });
177
+
178
+ describe("rule links", () => {
179
+ it("resolves from the repo root, not from the rules folder", () => {
180
+ const root = repo({ "references/a.md": "x", ".repo/rules/b.md": "x" });
181
+ expect(check(root, [rule("r", "[a](references/a.md) and [a](./references/a.md#sec)")])).toEqual([]);
182
+ expect(check(root, [rule("r", "[b](b.md)")])).toEqual([".repo/rules/r.md: link b.md does not exist"]);
183
+ });
184
+
185
+ it("flags a dead link", () => {
186
+ expect(check(repo(), [rule("r", "[gone](references/gone.md)")])).toEqual([
187
+ ".repo/rules/r.md: link references/gone.md does not exist",
188
+ ]);
189
+ });
190
+
191
+ it("skips external links, anchors, absolute paths and code", () => {
192
+ const body = [
193
+ "[a](https://example.com/x) [b](http://example.com) [c](mailto:me@example.com) [d](#top) [e](/abs/path)",
194
+ "`[f](nope.md)`",
195
+ "```",
196
+ "[g](nope.md)",
197
+ "```",
198
+ ].join("\n");
199
+ expect(check(repo(), [rule("r", body)])).toEqual([]);
200
+ });
201
+
202
+ it("does not check a stock rule", () => {
203
+ const stock: Doc = {
204
+ rel: "agents/rules/s.md",
205
+ file: "s.md",
206
+ origin: "stock",
207
+ scope: "",
208
+ data: { description: "d" },
209
+ body: "[x](nope.md)",
210
+ };
211
+ expect(check(repo(), [stock])).toEqual([]);
212
+ });
213
+ });
214
+
215
+ describe("paths globs", () => {
216
+ const scoped = rule("api", "b", ["srv/**"]);
217
+
218
+ it("flags a glob that matches no listed file", () => {
219
+ expect(check(repo(), [scoped], ["src/a.ts"])).toEqual(['.repo/rules/api.md: paths "srv/**" matches no file']);
220
+ });
221
+
222
+ it("passes a glob that matches a listed file and flags only the glob of several that does not", () => {
223
+ expect(check(repo(), [scoped], ["srv/a.ts"])).toEqual([]);
224
+ expect(check(repo(), [rule("api", "b", ["nope/**", "src/*.ts"])], ["src/a.ts"])).toEqual([
225
+ '.repo/rules/api.md: paths "nope/**" matches no file',
226
+ ]);
227
+ });
228
+
229
+ it("does not let .repo/tmp satisfy a glob", () => {
230
+ expect(check(repo(), [rule("api", "b", ["**/*.json"])], [".repo/tmp/sync.json"])).toEqual([
231
+ '.repo/rules/api.md: paths "**/*.json" matches no file',
232
+ ]);
233
+ });
234
+
235
+ it("skips the check outside git", () => {
236
+ expect(check(repo(), [scoped], null)).toEqual([]);
237
+ });
238
+ });
239
+
240
+ describe("the always-on budget", () => {
241
+ it("counts the bytes of every pathless rule body and passes exactly the cap", () => {
242
+ const half = ALWAYS_ON_BUDGET_BYTES / 2;
243
+ expect(ALWAYS_ON_BUDGET_BYTES).toBe(12288);
244
+ expect(check(repo(), [rule("a", "x".repeat(half)), rule("b", "x".repeat(half))])).toEqual([]);
245
+ });
246
+
247
+ it("fails one byte over, naming the size and the way out", () => {
248
+ const half = ALWAYS_ON_BUDGET_BYTES / 2;
249
+ expect(check(repo(), [rule("a", "x".repeat(half)), rule("b", "x".repeat(half + 1))])).toEqual([
250
+ "always-on rules are 12.0 KiB, over the 12 KiB budget; give a rule paths or make it a skill",
251
+ ]);
252
+ expect(check(repo(), [rule("a", "x".repeat(14500))])[0]).toContain("always-on rules are 14.2 KiB");
253
+ });
254
+
255
+ it("counts multi-byte text by bytes, and treats a catch-all glob as pathless", () => {
256
+ const wide = "é".repeat(ALWAYS_ON_BUDGET_BYTES / 2 + 1);
257
+ expect(check(repo(), [rule("a", wide)])).toHaveLength(1);
258
+ expect(check(repo(), [rule("a", "x".repeat(ALWAYS_ON_BUDGET_BYTES + 1), ["**/*"])], ["f"])).toHaveLength(1);
259
+ });
260
+
261
+ it("leaves a scoped rule out", () => {
262
+ expect(check(repo(), [rule("a", "x".repeat(ALWAYS_ON_BUDGET_BYTES * 2), ["src/**"])], ["src/a.ts"])).toEqual([]);
263
+ });
264
+ });
265
+
266
+ describe("the golden consumer", () => {
267
+ function fixtureProblems(withSrc: boolean): string[] {
268
+ const root = repo();
269
+ fs.cpSync(CONSUMER_DIR, root, { recursive: true });
270
+ const listed = fs
271
+ .readdirSync(root, { recursive: true, encoding: "utf8" })
272
+ .map((rel) => rel.replaceAll("\\", "/"))
273
+ .filter((rel) => fs.statSync(path.join(root, rel)).isFile());
274
+ const loaded = loadSources({ stockRoot: STOCK_DIR, consumerRoot: root, packages: [] });
275
+ expect(loaded.problems).toEqual([]);
276
+ expect(fs.existsSync(path.join(root, "src"))).toBe(true);
277
+ return repoDirProblems(root, {
278
+ sources: loaded.sources,
279
+ stock: STOCK_DIR,
280
+ listed: withSrc ? listed : listed.filter((rel) => !rel.startsWith("src/")),
281
+ skills: [],
282
+ packages: [],
283
+ });
284
+ }
285
+
286
+ it("passes, its src/** rule matching the fixture's src file", () => {
287
+ expect(fixtureProblems(true)).toEqual([]);
288
+ });
289
+
290
+ it("would fail without the src file", () => {
291
+ expect(fixtureProblems(false)).toEqual(['.repo/rules/testing.md: paths "src/**" matches no file']);
292
+ });
293
+ });
294
+
295
+ describe("a workspace package's .repo", () => {
296
+ const WEB = "packages/web";
297
+ const pkgRule = (name: string, body: string, paths?: string[], data: Record<string, unknown> = {}): Doc => ({
298
+ rel: `${WEB}/.repo/rules/${name}.md`,
299
+ file: `${name}.md`,
300
+ origin: "repo",
301
+ scope: WEB,
302
+ data: { description: "d", ...(paths ? { paths } : {}), ...data },
303
+ body,
304
+ });
305
+ const inPkg = (rules: Doc[], listed: string[] | null = null, files: Record<string, string> = {}) =>
306
+ check(repo(files), rules, listed, [], [WEB]);
307
+
308
+ it("passes rules and skills, a pathless rule, and a path rule that matches a file under the package", () => {
309
+ const root = repo({
310
+ [`${WEB}/.repo/rules/api.md`]: "x",
311
+ [`${WEB}/.repo/skills/deploy/SKILL.md`]: skill("deploy", "ships it"),
312
+ });
313
+ const rules = [pkgRule("api", "Api.", ["src/api/**"]), pkgRule("all", "All.")];
314
+ expect(check(root, rules, [`${WEB}/src/api/a.ts`], [], [WEB])).toEqual([]);
315
+ });
316
+
317
+ it("refuses every entry but rules/ and skills/, subagents and lessons with where they live", () => {
318
+ const root = repo({
319
+ [`${WEB}/.repo/lessons/INDEX.md`]: "x",
320
+ [`${WEB}/.repo/subagents/a.md`]: "x",
321
+ [`${WEB}/.repo/hooks.jsonc`]: "{}",
322
+ [`${WEB}/.repo/rules/ok.md`]: "x",
323
+ });
324
+ expect(check(root, [], null, [], [WEB])).toEqual([
325
+ `${WEB}/.repo/hooks.jsonc: a package .repo holds only rules/ and skills/`,
326
+ `${WEB}/.repo/lessons/: a package .repo holds only rules/ and skills/; lessons live in the root .repo`,
327
+ `${WEB}/.repo/subagents/: a package .repo holds only rules/ and skills/; subagents live in the root .repo`,
328
+ ]);
329
+ });
330
+
331
+ it("refuses a path that leaves the package, with or without git", () => {
332
+ const rules = [pkgRule("x", "X.", ["../api/**"]), pkgRule("abs", "A.", ["/etc/**"])];
333
+ const expected = [
334
+ `${WEB}/.repo/rules/x.md: paths "../api/**" leaves the package; scope it inside ${WEB}`,
335
+ `${WEB}/.repo/rules/abs.md: paths "/etc/**" leaves the package; scope it inside ${WEB}`,
336
+ ];
337
+ expect(inPkg(rules)).toEqual(expected);
338
+ expect(inPkg(rules, [`${WEB}/a.ts`])).toEqual(expected);
339
+ });
340
+
341
+ it("matches a package glob under the package dir, not at the root", () => {
342
+ const rules = [pkgRule("api", "Api.", ["src/api/**"])];
343
+ expect(inPkg(rules, ["src/api/a.ts"])).toEqual([`${WEB}/.repo/rules/api.md: paths "src/api/**" matches no file`]);
344
+ expect(inPkg(rules, [`${WEB}/src/api/a.ts`])).toEqual([]);
345
+ });
346
+
347
+ it("checks a package skill like a root one but refuses overrides", () => {
348
+ const files = {
349
+ [`${WEB}/.repo/skills/bad/SKILL.md`]: skill("other"),
350
+ [`${WEB}/.repo/skills/ov/SKILL.md`]: `---\nname: ov\ndescription: d\noverrides: ov\n---\n`,
351
+ [`${WEB}/.repo/skills/linky/SKILL.md`]: skill("linky", "d", "[x](nope.md)"),
352
+ };
353
+ expect(inPkg([], null, files)).toEqual([
354
+ `${WEB}/.repo/skills/bad/SKILL.md: name "other" must equal the folder name "bad"`,
355
+ `${WEB}/.repo/skills/linky/SKILL.md: link nope.md does not exist`,
356
+ `${WEB}/.repo/skills/ov/SKILL.md: overrides has no meaning in a package skill; remove it`,
357
+ ]);
358
+ });
359
+
360
+ it("resolves a package rule's links from the package dir", () => {
361
+ const root = repo({ [`${WEB}/docs/a.md`]: "x" });
362
+ const rules = [pkgRule("l", "[ok](docs/a.md) [bad](docs/b.md)")];
363
+ expect(check(root, rules, null, [], [WEB])).toEqual([`${WEB}/.repo/rules/l.md: link docs/b.md does not exist`]);
364
+ });
365
+
366
+ it("leaves package rules out of the always-on budget", () => {
367
+ const big = "a".repeat(ALWAYS_ON_BUDGET_BYTES + 1);
368
+ expect(inPkg([pkgRule("big", big)])).toEqual([]);
369
+ expect(check(repo(), [rule("big", big)])).toHaveLength(1);
370
+ });
371
+ });
@@ -0,0 +1,205 @@
1
+ /**
2
+ * What `.repo/` may hold and whether it holds it right. `.repo/` is user input, so every entry must be one sync
3
+ * reads, or `lint-baseline.json`, which `repo turn` and `repo check` read; nothing there is silently ignored. Checked here: the layout, skills (a `SKILL.md` per folder, a matching
4
+ * `name`, a `description`, `overrides` of a stock skill), relative links in rules and skills, that each rule `paths`
5
+ * glob matches a file, and the budget of the always-on rule text. Rule and subagent frontmatter is checked where
6
+ * they are loaded (`agents/sources.ts`).
7
+ */
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ import { SKILL_PACKS, type SkillPack } from "../config.ts";
11
+ import { STOCK_SKILLS_REL } from "../scripts/skill-conflicts.ts";
12
+ import { scopeOf } from "./agents/rules.ts";
13
+ import { parseDoc } from "./agents/frontmatter.ts";
14
+ import type { Doc, Sources } from "./agents/sources.ts";
15
+
16
+ const REPO_DIR = ".repo";
17
+ const SKILLS_DIR = `${REPO_DIR}/skills`;
18
+ /** The cap on the bytes of all pathless rule bodies, which load into every session through `AGENTS.md`. */
19
+ export const ALWAYS_ON_BUDGET_BYTES: number = 12 * 1024;
20
+ const MAX_DESCRIPTION_CHARS = 1024;
21
+
22
+ const PACKAGE_DIRS = ["rules", "skills"];
23
+ const READ_DIRS = ["rules", "subagents", "skills", "lessons", "tmp", "global-settings"];
24
+ const READ_FILES = ["hooks.jsonc", "mcp-policy.jsonc", "permissions.json", "mcp.json", "map.jsonc"];
25
+ /** Files sync does not read: the lint baseline, which `repo turn` lowers and `repo check` holds the tree to. */
26
+ const GATE_FILES = ["lint-baseline.json"];
27
+ const ALLOWED = [
28
+ ...READ_DIRS.map((dir) => `${dir}/`),
29
+ "settings/claude.json",
30
+ ...READ_FILES,
31
+ ...GATE_FILES.map((file) => `${file} (read by repo turn and repo check)`),
32
+ ].join(", ");
33
+ /** The entry old installs copied into `.repo/` that nothing reads; any other unknown entry gets the allowed list. */
34
+ const UNREAD = ["scripts"];
35
+ const DEAD_ENTRY = "nothing reads it; move what you need out of .repo and delete it";
36
+
37
+ export interface RepoDirInput {
38
+ sources: Sources;
39
+ /** The stock package root: the skills `.repo/skills` may override live under it. */
40
+ stock: string;
41
+ /** Every file git sees, root-relative; null outside git, which skips the `paths` check. */
42
+ listed: readonly string[] | null;
43
+ /** The enabled skill packs; a `.repo/skills` skill named like a stock skill of a pack that is off needs no `overrides`. */
44
+ skills: readonly SkillPack[];
45
+ /** Root-relative dirs of the workspace packages, whose `.repo/` holds only `rules/` and `skills/`. */
46
+ packages: readonly string[];
47
+ }
48
+
49
+ /** One `path: problem; fix` line per fault in `<root>/.repo`, in a stable order. */
50
+ export function repoDirProblems(root: string, { sources, stock, listed, skills, packages }: RepoDirInput): string[] {
51
+ const own = sources.rules.filter((doc) => doc.origin === "repo");
52
+ return [
53
+ ...layoutProblems(root),
54
+ ...skillProblems(root, stock, skills, ""),
55
+ ...packages.flatMap((dir) => [...packageLayoutProblems(root, dir), ...skillProblems(root, stock, skills, dir)]),
56
+ ...own.flatMap((doc) => linkProblems(root, doc.rel, doc.body, doc.scope)),
57
+ ...globProblems(own, listed),
58
+ ...budgetProblems(sources.rules.filter((doc) => doc.scope === "")),
59
+ ];
60
+ }
61
+
62
+ /** A package `.repo/` holds `rules/` and `skills/`; the root-only directories say where they live instead. */
63
+ function packageLayoutProblems(root: string, dir: string): string[] {
64
+ return entriesOf(path.join(root, dir, REPO_DIR))
65
+ .filter((entry) => !(entry.isDirectory() && PACKAGE_DIRS.includes(entry.name)))
66
+ .map((entry) => {
67
+ const dirName = entry.isDirectory();
68
+ const where = dirName && READ_DIRS.includes(entry.name) ? `; ${entry.name} live in the root .repo` : "";
69
+ return `${dir}/${REPO_DIR}/${entry.name}${dirName ? "/" : ""}: a package .repo holds only rules/ and skills/${where}`;
70
+ });
71
+ }
72
+
73
+ function entriesOf(abs: string): fs.Dirent[] {
74
+ return fs.existsSync(abs)
75
+ ? fs.readdirSync(abs, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : 1))
76
+ : [];
77
+ }
78
+
79
+ function layoutProblems(root: string): string[] {
80
+ const problems: string[] = [];
81
+ const dir = path.join(root, REPO_DIR);
82
+ for (const entry of entriesOf(dir)) {
83
+ const known = entry.isDirectory()
84
+ ? READ_DIRS.includes(entry.name)
85
+ : [...READ_FILES, ...GATE_FILES].includes(entry.name);
86
+ const rel = `${REPO_DIR}/${entry.name}${entry.isDirectory() ? "/" : ""}`;
87
+ if (entry.name === "settings" && entry.isDirectory()) {
88
+ for (const file of entriesOf(path.join(dir, "settings"))) {
89
+ if (file.name !== "claude.json") problems.push(`${REPO_DIR}/settings/${file.name}: ${DEAD_ENTRY}`);
90
+ }
91
+ } else if (UNREAD.includes(entry.name)) {
92
+ problems.push(`${rel}: ${DEAD_ENTRY}`);
93
+ } else if (!known) {
94
+ problems.push(`${rel}: not part of .repo; allowed: ${ALLOWED}; explanations go in references/`);
95
+ }
96
+ }
97
+ return problems;
98
+ }
99
+
100
+ /**
101
+ * Each `.repo/skills/<name>` needs a `SKILL.md` whose `name` is the folder and whose `description` fits. One named like a
102
+ * stock skill must say `overrides: <name>`, and an `overrides` must name a stock skill; a new name is asked for nothing more.
103
+ * A package's skills (`scope` its dir) are namespaced by the package: they may not say `overrides` and clash with no stock name.
104
+ */
105
+ function skillProblems(root: string, stock: string, enabled: readonly SkillPack[], scope: string): string[] {
106
+ const problems: string[] = [];
107
+ const stockNames = new Set(
108
+ entriesOf(path.join(stock, STOCK_SKILLS_REL))
109
+ .filter((e) => e.isDirectory())
110
+ .map((e) => e.name),
111
+ );
112
+ const optional = new Set<string>(SKILL_PACKS.filter((pack) => !enabled.includes(pack)));
113
+ // The stock package is this repo's own root, whose skills are not overrides of anything.
114
+ const checkOverrides = scope === "" && path.resolve(stock) !== path.resolve(root);
115
+ const skillsDir = scope === "" ? SKILLS_DIR : `${scope}/${SKILLS_DIR}`;
116
+ for (const entry of entriesOf(path.join(root, skillsDir))) {
117
+ const folder = `${skillsDir}/${entry.name}`;
118
+ const file = `${folder}/SKILL.md`;
119
+ if (!entry.isDirectory()) {
120
+ problems.push(`${folder}: not a skill folder; skills are skills/<name>/SKILL.md`);
121
+ continue;
122
+ }
123
+ if (!fs.existsSync(path.join(root, file))) {
124
+ problems.push(`${folder}: SKILL.md is missing; add it or delete the folder`);
125
+ continue;
126
+ }
127
+ const text = fs.readFileSync(path.join(root, file), "utf8");
128
+ let data: Record<string, unknown>;
129
+ try {
130
+ data = parseDoc(file, text).data;
131
+ } catch (error) {
132
+ problems.push((error as Error).message);
133
+ continue;
134
+ }
135
+ const { name, description, overrides } = data;
136
+ if (name === undefined) problems.push(`${file}: name is required; set it to the folder name "${entry.name}"`);
137
+ else if (name !== entry.name)
138
+ problems.push(`${file}: name "${String(name)}" must equal the folder name "${entry.name}"`);
139
+ if (typeof description !== "string" || description.trim() === "") problems.push(`${file}: description is required`);
140
+ else if (description.length > MAX_DESCRIPTION_CHARS) {
141
+ problems.push(
142
+ `${file}: description is ${description.length} characters, over the ${MAX_DESCRIPTION_CHARS} limit; shorten it`,
143
+ );
144
+ }
145
+ if (scope !== "" && overrides !== undefined) {
146
+ problems.push(`${file}: overrides has no meaning in a package skill; remove it`);
147
+ } else if (checkOverrides) {
148
+ if (typeof overrides === "string" && !stockNames.has(overrides)) {
149
+ problems.push(`${file}: overrides "${overrides}" but no stock skill has that name`);
150
+ } else if (stockNames.has(entry.name) && !optional.has(entry.name) && overrides !== entry.name) {
151
+ problems.push(
152
+ `${folder}: a stock skill has this name; add \`overrides: ${entry.name}\` to its SKILL.md or rename it`,
153
+ );
154
+ }
155
+ }
156
+ problems.push(...linkProblems(root, file, text, path.posix.dirname(file)));
157
+ }
158
+ return problems;
159
+ }
160
+
161
+ const LINK = /\[[^\]]*\]\(\s*<?([^)\s>]+)>?(?:\s+"[^"]*")?\s*\)/g;
162
+
163
+ /** One line per relative link of `text` (the file `rel`) that points at nothing, resolved from `base` under `root`. */
164
+ function linkProblems(root: string, rel: string, text: string, base: string): string[] {
165
+ const prose = text.replace(/^(```|~~~)[^\n]*\n[\s\S]*?^\1[^\n]*$/gm, "").replace(/`[^`\n]*`/g, "");
166
+ const problems: string[] = [];
167
+ for (const match of prose.matchAll(LINK)) {
168
+ const target = match[1]!;
169
+ const bare = target.split("#")[0]!;
170
+ if (bare === "" || bare.startsWith("/") || /^[a-z][a-z0-9+.-]*:/i.test(bare)) continue;
171
+ if (!fs.existsSync(path.join(root, base, bare))) problems.push(`${rel}: link ${bare} does not exist`);
172
+ }
173
+ return problems;
174
+ }
175
+
176
+ /**
177
+ * A package rule's globs are relative to its dir: one starting with `../` or `/` leaves it (always refused), the rest are
178
+ * prefixed with the dir and, with git, must match a listed file. Root rule globs are used as written.
179
+ */
180
+ function globProblems(rules: readonly Doc[], listed: readonly string[] | null): string[] {
181
+ const files = listed?.filter((rel) => !rel.startsWith(`${REPO_DIR}/tmp/`));
182
+ return rules.flatMap((doc) =>
183
+ scopeOf(doc).flatMap((glob) => {
184
+ if (doc.scope !== "" && (glob.startsWith("../") || glob.startsWith("/"))) {
185
+ return [`${doc.rel}: paths "${glob}" leaves the package; scope it inside ${doc.scope}`];
186
+ }
187
+ const full = doc.scope === "" ? glob : `${doc.scope}/${glob}`;
188
+ return files === undefined || files.some((file) => new Bun.Glob(full).match(file))
189
+ ? []
190
+ : [`${doc.rel}: paths "${glob}" matches no file`];
191
+ }),
192
+ );
193
+ }
194
+
195
+ /** The bytes of every pathless rule body, stock included, which `AGENTS.md` holds in every session. */
196
+ function budgetProblems(rules: readonly Doc[]): string[] {
197
+ const bytes = rules
198
+ .filter((doc) => scopeOf(doc).length === 0)
199
+ .reduce((sum, doc) => sum + Buffer.byteLength(doc.body.trim()), 0);
200
+ if (bytes <= ALWAYS_ON_BUDGET_BYTES) return [];
201
+ const kib = (value: number) => (value / 1024).toFixed(1);
202
+ return [
203
+ `always-on rules are ${kib(bytes)} KiB, over the ${ALWAYS_ON_BUDGET_BYTES / 1024} KiB budget; give a rule paths or make it a skill`,
204
+ ];
205
+ }
@@ -0,0 +1,8 @@
1
+ /** The platform whose files git cannot give an executable bit. */
2
+ export const WINDOWS_PLATFORM = "win32";
3
+
4
+ /** The index mode of an executable file; a file written on win32 is added as `100644` since NTFS has no mode bits. */
5
+ export const EXECUTABLE_MODE = "100755";
6
+
7
+ /** The fields of a `git ls-files -s` line: mode, blob, stage and path. */
8
+ export const INDEX_FIELDS = 4;