@vibe-agent-toolkit/cli 0.2.0-rc.1 → 0.2.0-rc.10

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 (504) hide show
  1. package/README.md +4 -5
  2. package/dist/bin/vat +95 -28
  3. package/dist/bin/vat.js +95 -28
  4. package/dist/bin/vat.js.map +1 -1
  5. package/dist/bin.js +115 -76
  6. package/dist/bin.js.map +1 -1
  7. package/dist/command-loaders.d.ts +44 -0
  8. package/dist/command-loaders.d.ts.map +1 -0
  9. package/dist/command-loaders.js +60 -0
  10. package/dist/command-loaders.js.map +1 -0
  11. package/dist/commands/agent/build.d.ts.map +1 -1
  12. package/dist/commands/agent/build.js +2 -1
  13. package/dist/commands/agent/build.js.map +1 -1
  14. package/dist/commands/agent/import.d.ts.map +1 -1
  15. package/dist/commands/agent/import.js +3 -2
  16. package/dist/commands/agent/import.js.map +1 -1
  17. package/dist/commands/agent/index.js +1 -1
  18. package/dist/commands/agent/install-path.d.ts +36 -0
  19. package/dist/commands/agent/install-path.d.ts.map +1 -0
  20. package/dist/commands/agent/install-path.js +44 -0
  21. package/dist/commands/agent/install-path.js.map +1 -0
  22. package/dist/commands/agent/install.d.ts.map +1 -1
  23. package/dist/commands/agent/install.js +66 -13
  24. package/dist/commands/agent/install.js.map +1 -1
  25. package/dist/commands/agent/installed.d.ts.map +1 -1
  26. package/dist/commands/agent/installed.js +22 -12
  27. package/dist/commands/agent/installed.js.map +1 -1
  28. package/dist/commands/agent/list.d.ts.map +1 -1
  29. package/dist/commands/agent/list.js +2 -1
  30. package/dist/commands/agent/list.js.map +1 -1
  31. package/dist/commands/agent/run.d.ts.map +1 -1
  32. package/dist/commands/agent/run.js +2 -1
  33. package/dist/commands/agent/run.js.map +1 -1
  34. package/dist/commands/agent/uninstall.d.ts.map +1 -1
  35. package/dist/commands/agent/uninstall.js +13 -6
  36. package/dist/commands/agent/uninstall.js.map +1 -1
  37. package/dist/commands/agent/validate.d.ts.map +1 -1
  38. package/dist/commands/agent/validate.js +3 -2
  39. package/dist/commands/agent/validate.js.map +1 -1
  40. package/dist/commands/ard/emit.d.ts +153 -0
  41. package/dist/commands/ard/emit.d.ts.map +1 -0
  42. package/dist/commands/ard/emit.js +276 -0
  43. package/dist/commands/ard/emit.js.map +1 -0
  44. package/dist/commands/ard/index.d.ts +12 -0
  45. package/dist/commands/ard/index.d.ts.map +1 -0
  46. package/dist/commands/ard/index.js +80 -0
  47. package/dist/commands/ard/index.js.map +1 -0
  48. package/dist/commands/ard/surfaces.d.ts +56 -0
  49. package/dist/commands/ard/surfaces.d.ts.map +1 -0
  50. package/dist/commands/ard/surfaces.js +150 -0
  51. package/dist/commands/ard/surfaces.js.map +1 -0
  52. package/dist/commands/audit/distributed-tree.d.ts +2 -2
  53. package/dist/commands/audit/distributed-tree.d.ts.map +1 -1
  54. package/dist/commands/audit/distributed-tree.js +11 -7
  55. package/dist/commands/audit/distributed-tree.js.map +1 -1
  56. package/dist/commands/audit/git-url-clone.d.ts +1 -1
  57. package/dist/commands/audit/git-url-clone.d.ts.map +1 -1
  58. package/dist/commands/audit/git-url-clone.js.map +1 -1
  59. package/dist/commands/audit/hierarchical-output.d.ts +1 -1
  60. package/dist/commands/audit/hierarchical-output.d.ts.map +1 -1
  61. package/dist/commands/audit/hierarchical-output.js +1 -1
  62. package/dist/commands/audit/hierarchical-output.js.map +1 -1
  63. package/dist/commands/audit/provenance.d.ts.map +1 -1
  64. package/dist/commands/audit/provenance.js +4 -3
  65. package/dist/commands/audit/provenance.js.map +1 -1
  66. package/dist/commands/audit/scan-population.d.ts +158 -0
  67. package/dist/commands/audit/scan-population.d.ts.map +1 -0
  68. package/dist/commands/audit/scan-population.js +292 -0
  69. package/dist/commands/audit/scan-population.js.map +1 -0
  70. package/dist/commands/audit-settings.d.ts.map +1 -1
  71. package/dist/commands/audit-settings.js +6 -5
  72. package/dist/commands/audit-settings.js.map +1 -1
  73. package/dist/commands/audit.d.ts +98 -8
  74. package/dist/commands/audit.d.ts.map +1 -1
  75. package/dist/commands/audit.js +1126 -394
  76. package/dist/commands/audit.js.map +1 -1
  77. package/dist/commands/build.d.ts +1 -1
  78. package/dist/commands/build.d.ts.map +1 -1
  79. package/dist/commands/build.js +40 -36
  80. package/dist/commands/build.js.map +1 -1
  81. package/dist/commands/cache/cache-control.d.ts +82 -0
  82. package/dist/commands/cache/cache-control.d.ts.map +1 -0
  83. package/dist/commands/cache/cache-control.js +86 -0
  84. package/dist/commands/cache/cache-control.js.map +1 -0
  85. package/dist/commands/cache/clear.d.ts +34 -6
  86. package/dist/commands/cache/clear.d.ts.map +1 -1
  87. package/dist/commands/cache/clear.js +101 -15
  88. package/dist/commands/cache/clear.js.map +1 -1
  89. package/dist/commands/cache/index.d.ts +13 -63
  90. package/dist/commands/cache/index.d.ts.map +1 -1
  91. package/dist/commands/cache/index.js +47 -79
  92. package/dist/commands/cache/index.js.map +1 -1
  93. package/dist/commands/claude/claude-config.d.ts.map +1 -1
  94. package/dist/commands/claude/claude-config.js +6 -0
  95. package/dist/commands/claude/claude-config.js.map +1 -1
  96. package/dist/commands/claude/context.d.ts +340 -0
  97. package/dist/commands/claude/context.d.ts.map +1 -0
  98. package/dist/commands/claude/context.js +1080 -0
  99. package/dist/commands/claude/context.js.map +1 -0
  100. package/dist/commands/claude/index.d.ts +14 -0
  101. package/dist/commands/claude/index.d.ts.map +1 -1
  102. package/dist/commands/claude/index.js +22 -2
  103. package/dist/commands/claude/index.js.map +1 -1
  104. package/dist/commands/claude/marketplace/changelog-utils.d.ts +1 -1
  105. package/dist/commands/claude/marketplace/changelog-utils.d.ts.map +1 -1
  106. package/dist/commands/claude/marketplace/changelog-utils.js +1 -2
  107. package/dist/commands/claude/marketplace/changelog-utils.js.map +1 -1
  108. package/dist/commands/claude/marketplace/git-publish.d.ts.map +1 -1
  109. package/dist/commands/claude/marketplace/git-publish.js +40 -31
  110. package/dist/commands/claude/marketplace/git-publish.js.map +1 -1
  111. package/dist/commands/claude/marketplace/license-utils.d.ts.map +1 -1
  112. package/dist/commands/claude/marketplace/license-utils.js +0 -1
  113. package/dist/commands/claude/marketplace/license-utils.js.map +1 -1
  114. package/dist/commands/claude/marketplace/publish-tree.d.ts.map +1 -1
  115. package/dist/commands/claude/marketplace/publish-tree.js +0 -6
  116. package/dist/commands/claude/marketplace/publish-tree.js.map +1 -1
  117. package/dist/commands/claude/marketplace/publish.d.ts.map +1 -1
  118. package/dist/commands/claude/marketplace/publish.js +2 -1
  119. package/dist/commands/claude/marketplace/publish.js.map +1 -1
  120. package/dist/commands/claude/marketplace/validate.d.ts +73 -22
  121. package/dist/commands/claude/marketplace/validate.d.ts.map +1 -1
  122. package/dist/commands/claude/marketplace/validate.js +407 -72
  123. package/dist/commands/claude/marketplace/validate.js.map +1 -1
  124. package/dist/commands/claude/org/api-keys.d.ts +0 -3
  125. package/dist/commands/claude/org/api-keys.d.ts.map +1 -1
  126. package/dist/commands/claude/org/api-keys.js +2 -1
  127. package/dist/commands/claude/org/api-keys.js.map +1 -1
  128. package/dist/commands/claude/org/helpers.d.ts +54 -1
  129. package/dist/commands/claude/org/helpers.d.ts.map +1 -1
  130. package/dist/commands/claude/org/helpers.js +42 -7
  131. package/dist/commands/claude/org/helpers.js.map +1 -1
  132. package/dist/commands/claude/org/index.d.ts.map +1 -1
  133. package/dist/commands/claude/org/index.js +24 -16
  134. package/dist/commands/claude/org/index.js.map +1 -1
  135. package/dist/commands/claude/org/invites.d.ts +0 -3
  136. package/dist/commands/claude/org/invites.d.ts.map +1 -1
  137. package/dist/commands/claude/org/invites.js +3 -2
  138. package/dist/commands/claude/org/invites.js.map +1 -1
  139. package/dist/commands/claude/org/skills.d.ts +527 -0
  140. package/dist/commands/claude/org/skills.d.ts.map +1 -1
  141. package/dist/commands/claude/org/skills.js +1414 -108
  142. package/dist/commands/claude/org/skills.js.map +1 -1
  143. package/dist/commands/claude/org/users.d.ts +0 -3
  144. package/dist/commands/claude/org/users.d.ts.map +1 -1
  145. package/dist/commands/claude/org/users.js +6 -3
  146. package/dist/commands/claude/org/users.js.map +1 -1
  147. package/dist/commands/claude/org/workspaces.d.ts +0 -3
  148. package/dist/commands/claude/org/workspaces.d.ts.map +1 -1
  149. package/dist/commands/claude/org/workspaces.js +11 -7
  150. package/dist/commands/claude/org/workspaces.js.map +1 -1
  151. package/dist/commands/claude/plugin/build.d.ts +41 -3
  152. package/dist/commands/claude/plugin/build.d.ts.map +1 -1
  153. package/dist/commands/claude/plugin/build.js +147 -80
  154. package/dist/commands/claude/plugin/build.js.map +1 -1
  155. package/dist/commands/claude/plugin/helpers.d.ts.map +1 -1
  156. package/dist/commands/claude/plugin/helpers.js +2 -7
  157. package/dist/commands/claude/plugin/helpers.js.map +1 -1
  158. package/dist/commands/claude/plugin/install.d.ts +12 -0
  159. package/dist/commands/claude/plugin/install.d.ts.map +1 -1
  160. package/dist/commands/claude/plugin/install.js +93 -62
  161. package/dist/commands/claude/plugin/install.js.map +1 -1
  162. package/dist/commands/claude/plugin/list.d.ts.map +1 -1
  163. package/dist/commands/claude/plugin/list.js +3 -2
  164. package/dist/commands/claude/plugin/list.js.map +1 -1
  165. package/dist/commands/claude/plugin/marketplace-json.d.ts +24 -9
  166. package/dist/commands/claude/plugin/marketplace-json.d.ts.map +1 -1
  167. package/dist/commands/claude/plugin/marketplace-json.js +10 -9
  168. package/dist/commands/claude/plugin/marketplace-json.js.map +1 -1
  169. package/dist/commands/claude/plugin/plugin-changelog.d.ts.map +1 -1
  170. package/dist/commands/claude/plugin/plugin-changelog.js +0 -2
  171. package/dist/commands/claude/plugin/plugin-changelog.js.map +1 -1
  172. package/dist/commands/claude/plugin/plugin-files.d.ts.map +1 -1
  173. package/dist/commands/claude/plugin/plugin-files.js +3 -6
  174. package/dist/commands/claude/plugin/plugin-files.js.map +1 -1
  175. package/dist/commands/claude/plugin/plugin-validators.d.ts.map +1 -1
  176. package/dist/commands/claude/plugin/plugin-validators.js +3 -6
  177. package/dist/commands/claude/plugin/plugin-validators.js.map +1 -1
  178. package/dist/commands/claude/plugin/tree-copy.d.ts +73 -0
  179. package/dist/commands/claude/plugin/tree-copy.d.ts.map +1 -1
  180. package/dist/commands/claude/plugin/tree-copy.js +221 -14
  181. package/dist/commands/claude/plugin/tree-copy.js.map +1 -1
  182. package/dist/commands/claude/plugin/uninstall.d.ts.map +1 -1
  183. package/dist/commands/claude/plugin/uninstall.js +4 -4
  184. package/dist/commands/claude/plugin/uninstall.js.map +1 -1
  185. package/dist/commands/consistency-check.d.ts +17 -15
  186. package/dist/commands/consistency-check.d.ts.map +1 -1
  187. package/dist/commands/consistency-check.js +152 -106
  188. package/dist/commands/consistency-check.js.map +1 -1
  189. package/dist/commands/corpus/index.d.ts.map +1 -1
  190. package/dist/commands/corpus/index.js +3 -0
  191. package/dist/commands/corpus/index.js.map +1 -1
  192. package/dist/commands/corpus/report.d.ts +1 -2
  193. package/dist/commands/corpus/report.d.ts.map +1 -1
  194. package/dist/commands/corpus/report.js +1 -3
  195. package/dist/commands/corpus/report.js.map +1 -1
  196. package/dist/commands/corpus/runner.d.ts +58 -1
  197. package/dist/commands/corpus/runner.d.ts.map +1 -1
  198. package/dist/commands/corpus/runner.js +95 -41
  199. package/dist/commands/corpus/runner.js.map +1 -1
  200. package/dist/commands/corpus/scan.d.ts.map +1 -1
  201. package/dist/commands/corpus/scan.js +20 -12
  202. package/dist/commands/corpus/scan.js.map +1 -1
  203. package/dist/commands/corpus/seed.d.ts +4 -4
  204. package/dist/commands/corpus/seed.d.ts.map +1 -1
  205. package/dist/commands/corpus/seed.js +1 -3
  206. package/dist/commands/corpus/seed.js.map +1 -1
  207. package/dist/commands/doctor.d.ts +50 -0
  208. package/dist/commands/doctor.d.ts.map +1 -1
  209. package/dist/commands/doctor.js +245 -52
  210. package/dist/commands/doctor.js.map +1 -1
  211. package/dist/commands/inventory.d.ts +13 -1
  212. package/dist/commands/inventory.d.ts.map +1 -1
  213. package/dist/commands/inventory.js +121 -17
  214. package/dist/commands/inventory.js.map +1 -1
  215. package/dist/commands/mcp/collections.d.ts +1 -1
  216. package/dist/commands/mcp/collections.d.ts.map +1 -1
  217. package/dist/commands/mcp/list-collections.d.ts.map +1 -1
  218. package/dist/commands/mcp/list-collections.js +2 -1
  219. package/dist/commands/mcp/list-collections.js.map +1 -1
  220. package/dist/commands/mcp/serve.d.ts.map +1 -1
  221. package/dist/commands/mcp/serve.js +3 -2
  222. package/dist/commands/mcp/serve.js.map +1 -1
  223. package/dist/commands/okf/index.d.ts +11 -0
  224. package/dist/commands/okf/index.d.ts.map +1 -0
  225. package/dist/commands/okf/index.js +114 -0
  226. package/dist/commands/okf/index.js.map +1 -0
  227. package/dist/commands/okf/validate.d.ts +191 -0
  228. package/dist/commands/okf/validate.d.ts.map +1 -0
  229. package/dist/commands/okf/validate.js +190 -0
  230. package/dist/commands/okf/validate.js.map +1 -0
  231. package/dist/commands/phase-utils.d.ts +182 -114
  232. package/dist/commands/phase-utils.d.ts.map +1 -1
  233. package/dist/commands/phase-utils.js +154 -163
  234. package/dist/commands/phase-utils.js.map +1 -1
  235. package/dist/commands/positional-args.d.ts.map +1 -1
  236. package/dist/commands/positional-args.js +3 -2
  237. package/dist/commands/positional-args.js.map +1 -1
  238. package/dist/commands/rag/clear-command.d.ts.map +1 -1
  239. package/dist/commands/rag/clear-command.js +2 -1
  240. package/dist/commands/rag/clear-command.js.map +1 -1
  241. package/dist/commands/rag/index-command.d.ts +64 -0
  242. package/dist/commands/rag/index-command.d.ts.map +1 -1
  243. package/dist/commands/rag/index-command.js +72 -6
  244. package/dist/commands/rag/index-command.js.map +1 -1
  245. package/dist/commands/rag/index.d.ts.map +1 -1
  246. package/dist/commands/rag/index.js +23 -10
  247. package/dist/commands/rag/index.js.map +1 -1
  248. package/dist/commands/rag/query-command.d.ts.map +1 -1
  249. package/dist/commands/rag/query-command.js +2 -1
  250. package/dist/commands/rag/query-command.js.map +1 -1
  251. package/dist/commands/rag/stats-command.d.ts.map +1 -1
  252. package/dist/commands/rag/stats-command.js +2 -1
  253. package/dist/commands/rag/stats-command.js.map +1 -1
  254. package/dist/commands/resources/builtin-help.d.ts +27 -0
  255. package/dist/commands/resources/builtin-help.d.ts.map +1 -0
  256. package/dist/commands/resources/builtin-help.js +46 -0
  257. package/dist/commands/resources/builtin-help.js.map +1 -0
  258. package/dist/commands/resources/check-progress.d.ts +154 -0
  259. package/dist/commands/resources/check-progress.d.ts.map +1 -0
  260. package/dist/commands/resources/check-progress.js +222 -0
  261. package/dist/commands/resources/check-progress.js.map +1 -0
  262. package/dist/commands/resources/check-supervisor.d.ts +465 -0
  263. package/dist/commands/resources/check-supervisor.d.ts.map +1 -0
  264. package/dist/commands/resources/check-supervisor.js +573 -0
  265. package/dist/commands/resources/check-supervisor.js.map +1 -0
  266. package/dist/commands/resources/check.d.ts +593 -0
  267. package/dist/commands/resources/check.d.ts.map +1 -0
  268. package/dist/commands/resources/check.js +1365 -0
  269. package/dist/commands/resources/check.js.map +1 -0
  270. package/dist/commands/resources/index.d.ts.map +1 -1
  271. package/dist/commands/resources/index.js +418 -4
  272. package/dist/commands/resources/index.js.map +1 -1
  273. package/dist/commands/resources/query.d.ts +106 -0
  274. package/dist/commands/resources/query.d.ts.map +1 -0
  275. package/dist/commands/resources/query.js +177 -0
  276. package/dist/commands/resources/query.js.map +1 -0
  277. package/dist/commands/resources/scan.d.ts +24 -0
  278. package/dist/commands/resources/scan.d.ts.map +1 -1
  279. package/dist/commands/resources/scan.js +17 -8
  280. package/dist/commands/resources/scan.js.map +1 -1
  281. package/dist/commands/resources/validate.d.ts +42 -2
  282. package/dist/commands/resources/validate.d.ts.map +1 -1
  283. package/dist/commands/resources/validate.js +208 -55
  284. package/dist/commands/resources/validate.js.map +1 -1
  285. package/dist/commands/skill/review-checklist.d.ts +3 -2
  286. package/dist/commands/skill/review-checklist.d.ts.map +1 -1
  287. package/dist/commands/skill/review-checklist.js +40 -2
  288. package/dist/commands/skill/review-checklist.js.map +1 -1
  289. package/dist/commands/skill/review.d.ts +247 -0
  290. package/dist/commands/skill/review.d.ts.map +1 -1
  291. package/dist/commands/skill/review.js +99 -62
  292. package/dist/commands/skill/review.js.map +1 -1
  293. package/dist/commands/skill/test/configure.d.ts +56 -0
  294. package/dist/commands/skill/test/configure.d.ts.map +1 -1
  295. package/dist/commands/skill/test/configure.js +75 -16
  296. package/dist/commands/skill/test/configure.js.map +1 -1
  297. package/dist/commands/skill/test/run.d.ts +63 -10
  298. package/dist/commands/skill/test/run.d.ts.map +1 -1
  299. package/dist/commands/skill/test/run.js +258 -77
  300. package/dist/commands/skill/test/run.js.map +1 -1
  301. package/dist/commands/skills/build.d.ts +94 -4
  302. package/dist/commands/skills/build.d.ts.map +1 -1
  303. package/dist/commands/skills/build.js +325 -97
  304. package/dist/commands/skills/build.js.map +1 -1
  305. package/dist/commands/skills/install.d.ts +2 -1
  306. package/dist/commands/skills/install.d.ts.map +1 -1
  307. package/dist/commands/skills/install.js +20 -31
  308. package/dist/commands/skills/install.js.map +1 -1
  309. package/dist/commands/skills/list.d.ts +2 -1
  310. package/dist/commands/skills/list.d.ts.map +1 -1
  311. package/dist/commands/skills/list.js +44 -26
  312. package/dist/commands/skills/list.js.map +1 -1
  313. package/dist/commands/skills/package.d.ts +1 -1
  314. package/dist/commands/skills/package.d.ts.map +1 -1
  315. package/dist/commands/skills/package.js +6 -8
  316. package/dist/commands/skills/package.js.map +1 -1
  317. package/dist/commands/skills/scope-guard.d.ts.map +1 -1
  318. package/dist/commands/skills/scope-guard.js +3 -5
  319. package/dist/commands/skills/scope-guard.js.map +1 -1
  320. package/dist/commands/skills/shared.d.ts.map +1 -1
  321. package/dist/commands/skills/shared.js +2 -4
  322. package/dist/commands/skills/shared.js.map +1 -1
  323. package/dist/commands/skills/skill-discovery.d.ts +47 -6
  324. package/dist/commands/skills/skill-discovery.d.ts.map +1 -1
  325. package/dist/commands/skills/skill-discovery.js +61 -11
  326. package/dist/commands/skills/skill-discovery.js.map +1 -1
  327. package/dist/commands/skills/source-resolvers.d.ts +15 -0
  328. package/dist/commands/skills/source-resolvers.d.ts.map +1 -1
  329. package/dist/commands/skills/source-resolvers.js +25 -4
  330. package/dist/commands/skills/source-resolvers.js.map +1 -1
  331. package/dist/commands/skills/validate-command.d.ts.map +1 -1
  332. package/dist/commands/skills/validate-command.js +11 -3
  333. package/dist/commands/skills/validate-command.js.map +1 -1
  334. package/dist/commands/skills/validate.d.ts +140 -4
  335. package/dist/commands/skills/validate.d.ts.map +1 -1
  336. package/dist/commands/skills/validate.js +224 -67
  337. package/dist/commands/skills/validate.js.map +1 -1
  338. package/dist/commands/validate.d.ts +1 -1
  339. package/dist/commands/validate.d.ts.map +1 -1
  340. package/dist/commands/validate.js +63 -27
  341. package/dist/commands/validate.js.map +1 -1
  342. package/dist/commands/verify.d.ts +119 -15
  343. package/dist/commands/verify.d.ts.map +1 -1
  344. package/dist/commands/verify.js +357 -142
  345. package/dist/commands/verify.js.map +1 -1
  346. package/dist/index.d.ts +0 -1
  347. package/dist/index.d.ts.map +1 -1
  348. package/dist/index.js +5 -1
  349. package/dist/index.js.map +1 -1
  350. package/dist/report-schemas.d.ts +45 -0
  351. package/dist/report-schemas.d.ts.map +1 -0
  352. package/dist/report-schemas.js +100 -0
  353. package/dist/report-schemas.js.map +1 -0
  354. package/dist/skill-resolution/classify.js +2 -2
  355. package/dist/skill-resolution/classify.js.map +1 -1
  356. package/dist/skill-resolution/packaging-config.d.ts +49 -5
  357. package/dist/skill-resolution/packaging-config.d.ts.map +1 -1
  358. package/dist/skill-resolution/packaging-config.js +59 -19
  359. package/dist/skill-resolution/packaging-config.js.map +1 -1
  360. package/dist/skill-resolution/resolve-skill-reference.d.ts.map +1 -1
  361. package/dist/skill-resolution/resolve-skill-reference.js +55 -57
  362. package/dist/skill-resolution/resolve-skill-reference.js.map +1 -1
  363. package/dist/utils/agent-discovery.d.ts.map +1 -1
  364. package/dist/utils/agent-discovery.js +22 -11
  365. package/dist/utils/agent-discovery.js.map +1 -1
  366. package/dist/utils/agent-runner.d.ts.map +1 -1
  367. package/dist/utils/agent-runner.js +7 -4
  368. package/dist/utils/agent-runner.js.map +1 -1
  369. package/dist/utils/claude-context-lens.d.ts +34 -0
  370. package/dist/utils/claude-context-lens.d.ts.map +1 -0
  371. package/dist/utils/claude-context-lens.js +39 -0
  372. package/dist/utils/claude-context-lens.js.map +1 -0
  373. package/dist/utils/command-error.d.ts +138 -20
  374. package/dist/utils/command-error.d.ts.map +1 -1
  375. package/dist/utils/command-error.js +172 -36
  376. package/dist/utils/command-error.js.map +1 -1
  377. package/dist/utils/config-loader.d.ts +2 -1
  378. package/dist/utils/config-loader.d.ts.map +1 -1
  379. package/dist/utils/config-loader.js +40 -14
  380. package/dist/utils/config-loader.js.map +1 -1
  381. package/dist/utils/corpus-target.d.ts +26 -0
  382. package/dist/utils/corpus-target.d.ts.map +1 -0
  383. package/dist/utils/corpus-target.js +35 -0
  384. package/dist/utils/corpus-target.js.map +1 -0
  385. package/dist/utils/edge-lens-evaluation.d.ts +83 -0
  386. package/dist/utils/edge-lens-evaluation.d.ts.map +1 -0
  387. package/dist/utils/edge-lens-evaluation.js +110 -0
  388. package/dist/utils/edge-lens-evaluation.js.map +1 -0
  389. package/dist/utils/help-loader.d.ts.map +1 -1
  390. package/dist/utils/help-loader.js +0 -1
  391. package/dist/utils/help-loader.js.map +1 -1
  392. package/dist/utils/issue-anchor.d.ts +2 -2
  393. package/dist/utils/issue-anchor.d.ts.map +1 -1
  394. package/dist/utils/issue-anchor.js +1 -1
  395. package/dist/utils/issue-rendering.d.ts +3 -3
  396. package/dist/utils/issue-rendering.d.ts.map +1 -1
  397. package/dist/utils/issue-rendering.js +3 -3
  398. package/dist/utils/issue-rendering.js.map +1 -1
  399. package/dist/utils/issue-severity.d.ts +12 -5
  400. package/dist/utils/issue-severity.d.ts.map +1 -1
  401. package/dist/utils/issue-severity.js +1 -1
  402. package/dist/utils/issue-severity.js.map +1 -1
  403. package/dist/utils/optional-backend.d.ts +78 -0
  404. package/dist/utils/optional-backend.d.ts.map +1 -0
  405. package/dist/utils/optional-backend.js +117 -0
  406. package/dist/utils/optional-backend.js.map +1 -0
  407. package/dist/utils/output.d.ts +26 -0
  408. package/dist/utils/output.d.ts.map +1 -1
  409. package/dist/utils/output.js +35 -0
  410. package/dist/utils/output.js.map +1 -1
  411. package/dist/utils/package-json.d.ts +24 -0
  412. package/dist/utils/package-json.d.ts.map +1 -0
  413. package/dist/utils/package-json.js +49 -0
  414. package/dist/utils/package-json.js.map +1 -0
  415. package/dist/utils/population-wiring.d.ts +100 -0
  416. package/dist/utils/population-wiring.d.ts.map +1 -0
  417. package/dist/utils/population-wiring.js +98 -0
  418. package/dist/utils/population-wiring.js.map +1 -0
  419. package/dist/utils/project-root-policy.d.ts +20 -0
  420. package/dist/utils/project-root-policy.d.ts.map +1 -1
  421. package/dist/utils/project-root-policy.js +30 -0
  422. package/dist/utils/project-root-policy.js.map +1 -1
  423. package/dist/utils/projection-lenses.d.ts +98 -0
  424. package/dist/utils/projection-lenses.d.ts.map +1 -0
  425. package/dist/utils/projection-lenses.js +203 -0
  426. package/dist/utils/projection-lenses.js.map +1 -0
  427. package/dist/utils/projection-query.d.ts +324 -0
  428. package/dist/utils/projection-query.d.ts.map +1 -0
  429. package/dist/utils/projection-query.js +357 -0
  430. package/dist/utils/projection-query.js.map +1 -0
  431. package/dist/utils/projection-store.d.ts +335 -0
  432. package/dist/utils/projection-store.d.ts.map +1 -0
  433. package/dist/utils/projection-store.js +522 -0
  434. package/dist/utils/projection-store.js.map +1 -0
  435. package/dist/utils/relation-limits.d.ts +50 -0
  436. package/dist/utils/relation-limits.d.ts.map +1 -0
  437. package/dist/utils/relation-limits.js +50 -0
  438. package/dist/utils/relation-limits.js.map +1 -0
  439. package/dist/utils/repeatable-option.d.ts +37 -0
  440. package/dist/utils/repeatable-option.d.ts.map +1 -0
  441. package/dist/utils/repeatable-option.js +39 -0
  442. package/dist/utils/repeatable-option.js.map +1 -0
  443. package/dist/utils/resource-loader.d.ts +132 -2
  444. package/dist/utils/resource-loader.d.ts.map +1 -1
  445. package/dist/utils/resource-loader.js +288 -27
  446. package/dist/utils/resource-loader.js.map +1 -1
  447. package/dist/utils/root-argv.d.ts +53 -0
  448. package/dist/utils/root-argv.d.ts.map +1 -0
  449. package/dist/utils/root-argv.js +215 -0
  450. package/dist/utils/root-argv.js.map +1 -0
  451. package/dist/utils/run-integrity.d.ts +95 -0
  452. package/dist/utils/run-integrity.d.ts.map +1 -0
  453. package/dist/utils/run-integrity.js +102 -0
  454. package/dist/utils/run-integrity.js.map +1 -0
  455. package/dist/utils/skill-discovery.d.ts.map +1 -1
  456. package/dist/utils/skill-discovery.js +2 -1
  457. package/dist/utils/skill-discovery.js.map +1 -1
  458. package/dist/utils/skill-packaging-config.d.ts +63 -1
  459. package/dist/utils/skill-packaging-config.d.ts.map +1 -1
  460. package/dist/utils/skill-packaging-config.js +56 -1
  461. package/dist/utils/skill-packaging-config.js.map +1 -1
  462. package/dist/utils/sqlite-experimental-warning.d.ts +69 -0
  463. package/dist/utils/sqlite-experimental-warning.d.ts.map +1 -0
  464. package/dist/utils/sqlite-experimental-warning.js +144 -0
  465. package/dist/utils/sqlite-experimental-warning.js.map +1 -0
  466. package/dist/utils/url-redact.d.ts.map +1 -1
  467. package/dist/utils/url-redact.js +4 -6
  468. package/dist/utils/url-redact.js.map +1 -1
  469. package/dist/utils/user-context-scanner.d.ts +8 -0
  470. package/dist/utils/user-context-scanner.d.ts.map +1 -1
  471. package/dist/utils/user-context-scanner.js +4 -2
  472. package/dist/utils/user-context-scanner.js.map +1 -1
  473. package/dist/utils/validate-help-files.d.ts.map +1 -1
  474. package/dist/utils/validate-help-files.js +12 -6
  475. package/dist/utils/validate-help-files.js.map +1 -1
  476. package/dist/utils/vat-bin-path.d.ts +36 -0
  477. package/dist/utils/vat-bin-path.d.ts.map +1 -0
  478. package/dist/utils/vat-bin-path.js +48 -0
  479. package/dist/utils/vat-bin-path.js.map +1 -0
  480. package/dist/utils/verdict-helpers.d.ts +1 -1
  481. package/dist/utils/verdict-helpers.d.ts.map +1 -1
  482. package/dist/utils/verdict-helpers.js +1 -1
  483. package/dist/utils/verdict-helpers.js.map +1 -1
  484. package/dist/version.d.ts.map +1 -1
  485. package/dist/version.js +1 -3
  486. package/dist/version.js.map +1 -1
  487. package/docs/agent.md +2 -2
  488. package/docs/audit.md +141 -37
  489. package/docs/doctor.md +7 -6
  490. package/docs/index.md +19 -11
  491. package/docs/mcp.md +2 -2
  492. package/docs/rag.md +3 -2
  493. package/docs/resources.md +13 -3
  494. package/docs/skill-test.md +10 -9
  495. package/docs/skills.md +53 -69
  496. package/package.json +35 -26
  497. package/schemas/ard-emit.json +149 -0
  498. package/schemas/okf-validate.json +170 -0
  499. package/schemas/resources-check.json +229 -0
  500. package/schemas/skill-review.json +230 -0
  501. package/dist/commands/resources/command-helpers.d.ts +0 -5
  502. package/dist/commands/resources/command-helpers.d.ts.map +0 -1
  503. package/dist/commands/resources/command-helpers.js +0 -6
  504. package/dist/commands/resources/command-helpers.js.map +0 -1
@@ -0,0 +1,1365 @@
1
+ /**
2
+ * `vat resources check` — run the project's own assertions over its resource
3
+ * projection, as a gate.
4
+ *
5
+ * The invariants this file implements are written down once, in
6
+ * `docs/architecture/cli.md` under "`vat resources check [path]`". Read that
7
+ * before changing anything here; the headlines, so a reader knows when to:
8
+ *
9
+ * - 🔑 **Four ways to check nothing are all refusals**, at error severity under
10
+ * the non-overridable `RESOURCE_CHECK_BROKEN`: a statement that will not run
11
+ * (the `catch` in `runUnits`), a corpus that enumerated empty
12
+ * ({@link emptyCorpusFinding}), a `--check` name nothing declares
13
+ * (`requireKnownCheck`), and not one check executed at all
14
+ * ({@link noCheckRanFinding}, derived in {@link buildCheckOutputData} so an
15
+ * emptied `BUILTIN_CHECKS` cannot publish a clean document).
16
+ * - ⚠️ That last one does NOT make the step always able to fail: the only
17
+ * built-in ships at `info`. Do not invent a second refusal for it — that
18
+ * would refuse the no-config run the default set exists to serve.
19
+ * - 🔑 **No built-in check may be SQL.** {@link checkCommand} reads
20
+ * `config?.resources?.checks` and nothing else for statements; built-ins are
21
+ * TypeScript predicates over the row model, and a built-in's `sqlTwin` is
22
+ * documentation an adopter copies, never executed here.
23
+ * - 🔑 **The default set belongs to the pipeline, not the config file.**
24
+ * `BUILTIN_CHECKS` reaches {@link runProjectChecks} as a REQUIRED `builtins`
25
+ * argument via {@link runOutcome}; nothing on that path reads `config`.
26
+ * - 🚨 **Adopter SQL runs unsandboxed on purpose, and contributed SQL is not
27
+ * adopter SQL** — a contributed statement ships as a reviewed example.
28
+ * - ⛔ A TypeScript/SQL twin is documentation and a differential oracle, never
29
+ * a runtime optimizer to schedule between.
30
+ */
31
+ import { performance } from 'node:perf_hooks';
32
+ import { bindBuiltinChecks, BUILTIN_CHECK_NAMES, issuesFromCheckRows, LIMIT_DIRECTIONS, } from '@vibe-agent-toolkit/resources';
33
+ import { buildReport, CUSTOM_CHECK_CODE_PREFIX, ExitCode, isCustomCheckCode, reportSchema, toFindings, } from '@vibe-agent-toolkit/schema';
34
+ import { safePath } from '@vibe-agent-toolkit/utils';
35
+ import { z } from 'zod';
36
+ import { handleReportCommandError } from '../../utils/command-error.js';
37
+ import { loadConfigCached } from '../../utils/config-loader.js';
38
+ import { formatDurationSecs } from '../../utils/duration.js';
39
+ import { resolveIssueSeverity } from '../../utils/issue-severity.js';
40
+ import { createLogger } from '../../utils/logger.js';
41
+ import { writeStdoutSync, writeStructuredOutput } from '../../utils/output.js';
42
+ import { assertDirectoryArgument, projectRootOrLoudCwd, projectRootOrNull } from '../../utils/project-root-policy.js';
43
+ import { withQueriedProjection, } from '../../utils/projection-query.js';
44
+ import { relationBoundsFor } from '../../utils/relation-limits.js';
45
+ import { nothingCheckedFinding, runIntegrityFinding } from '../../utils/run-integrity.js';
46
+ import { createProgressWriter, parseProgressLog, unitInFlight, } from './check-progress.js';
47
+ import { parseBudgetSeconds, requireSupervisableFlags, runsInThisProcess, superviseCheck, withProgressLog, } from './check-supervisor.js';
48
+ /** What one rule cost, as the document publishes it. */
49
+ const PublishedCheckCostSchema = z.object({
50
+ name: z.string(),
51
+ /** Three significant figures, so a 0.4 ms rule serializes as 0.0004 rather than a zero that reads as "not measured". */
52
+ durationSecs: z.number().nonnegative(),
53
+ /** Rows the statement returned. ABSENT, never 0, when the statement did not complete. */
54
+ rows: z.number().int().nonnegative().optional(),
55
+ broken: z.literal(true).optional(),
56
+ /** Present and `true` only for a rule VAT supplied — see {@link CheckCost.builtin}. */
57
+ builtin: z.literal(true).optional(),
58
+ }).strict();
59
+ /**
60
+ * The published copy of {@link relationBoundsFor}'s answer.
61
+ *
62
+ * The registry lists are `readonly` and this document is handed to a serializer,
63
+ * so the arrays are copied rather than referenced — the same rule
64
+ * `lensesEvaluated` follows one field above: a published document owns no
65
+ * reference into the run's own state.
66
+ *
67
+ * @param lensesEvaluated - The lenses this run evaluated
68
+ * @returns The bounds keys to spread, or nothing
69
+ */
70
+ function structuredBounds(lensesEvaluated) {
71
+ const bounds = relationBoundsFor(lensesEvaluated);
72
+ if (bounds.boundsStatement === undefined || bounds.limits === undefined)
73
+ return {};
74
+ return { boundsStatement: bounds.boundsStatement, limits: bounds.limits.map((limit) => ({ ...limit })) };
75
+ }
76
+ /**
77
+ * A signed bound on what the derived rows settle, as `@vibe-agent-toolkit/resources`
78
+ * states it.
79
+ *
80
+ * ⛔ The direction vocabulary is IMPORTED, never respelled: `LIMIT_DIRECTIONS`
81
+ * is the one list, tied to `StatedLimit['direction']` by `satisfies`, so a
82
+ * fifth direction cannot leave this schema quietly rejecting it.
83
+ */
84
+ const StatedLimitSchema = z.object({
85
+ id: z.string(),
86
+ direction: z.enum(LIMIT_DIRECTIONS),
87
+ statement: z.string(),
88
+ }).strict();
89
+ /**
90
+ * What the check run reports beyond its findings.
91
+ *
92
+ * The envelope's `examined` is `membersEnumerated` — what the rules ran
93
+ * AGAINST. `checksRun` is the other denominator, the number of RULES, and both
94
+ * are needed: zero findings is the pass condition and either number at zero
95
+ * makes that pass vacuous.
96
+ */
97
+ export const CheckDataSchema = z.object({
98
+ root: z.string(),
99
+ /** Whether the projection was derived this run or read from the store. */
100
+ population: z.enum(['derived', 'store']),
101
+ /** What the population cost — charged to no check, see `CheckCost`. */
102
+ populationSecs: z.number().nonnegative(),
103
+ /** What evaluating the lenses cost, paid before the first statement ran. */
104
+ lensSecs: z.number().nonnegative(),
105
+ /**
106
+ * Which lenses that covers — the derived relations this run's checks could
107
+ * actually read. 🔑 An empty list says "no check asked for a derived relation"
108
+ * rather than "a lens stopped running"; without it `lensSecs: 0` is the same
109
+ * document either way, and a gate whose rules silently read empty relations is
110
+ * a gate that cannot fail.
111
+ */
112
+ lensesEvaluated: z.array(z.string()),
113
+ /**
114
+ * The prose frame {@link CheckDataSchema.limits} is read under. Present
115
+ * exactly when a lens with stated bounds was evaluated.
116
+ */
117
+ boundsStatement: z.string().optional(),
118
+ /**
119
+ * What the derived rows this run could read do NOT settle — signed and
120
+ * directional, the same list `vat claude context` publishes beside its own
121
+ * answer. Absent when no bounded lens ran: an empty list would claim nothing
122
+ * bounds the answer, which is stronger than "the answer holds no such row".
123
+ */
124
+ limits: z.array(StatedLimitSchema).optional(),
125
+ /** The number of rules that EXECUTED. Derived from `checks`, never carried beside it. */
126
+ checksRun: z.number().int().nonnegative(),
127
+ /** What each rule cost, directly under the denominator it is the breakdown of. */
128
+ checks: z.array(PublishedCheckCostSchema),
129
+ }).strict();
130
+ /** The document this command publishes. */
131
+ export const CHECK_REPORT_SCHEMA = reportSchema(CheckDataSchema);
132
+ /**
133
+ * The refusal for a run in which NO check executed.
134
+ *
135
+ * 🚨 **The defect this closes shipped, and it is the shape by which a gate gets
136
+ * silently deleted.** A project with a valid `vibe-agent-toolkit.config.yaml`
137
+ * and no `checks:` block produced `status: success`, `checksRun: 0`, exit 0 — a
138
+ * passing gate document. Delete the `checks:` block and the CI step keeps going
139
+ * green forever; the only thing that said otherwise was a stderr warning nothing
140
+ * parses. **A gate that checked nothing is indistinguishable from a gate that
141
+ * was removed, and the whole argument for having these gates is that you never
142
+ * let those two look alike.**
143
+ *
144
+ * It is the third instance of the shape in this release — `vat claude budget`
145
+ * answered `success` on a path that matched no working location, and the repo's
146
+ * audit quality gate had a green path over zero parsed findings — so it is
147
+ * closed the way those were rather than a third way: a non-overridable
148
+ * `RUN_INTEGRITY_CODE` at `error`, derived in the report builder.
149
+ *
150
+ * 🔑 **Derived in {@link buildCheckOutputData}, not asserted by the handler.**
151
+ * That placement is the guarantee. The builder is where `checksRun` is computed
152
+ * and every lane's document goes through it — the completed run, the recovered
153
+ * document of an interrupted one, the child a supervisor forwards — so "the
154
+ * denominator is zero and the status is clean" is unrepresentable by
155
+ * construction, and a fourth lane added later inherits the refusal instead of
156
+ * having to remember it. It also lands after `resolveIssueSeverity`, which is
157
+ * belt and braces: the code is already unreachable from a `severity` key.
158
+ *
159
+ * The mechanism — ask the DENOMINATOR never the config, one report per
160
+ * situation, stand down behind an existing run-integrity finding — is the shared
161
+ * {@link nothingCheckedFinding}; this wrapper owns only the message. The
162
+ * stand-down matters here for an interrupted run, which recovers zero costs when
163
+ * the kill landed before the first statement finished and already carries
164
+ * {@link interruptedRunFinding}, the more specific of the two claims. A `--check`
165
+ * filter that matched nothing is refused earlier by {@link requireKnownCheck},
166
+ * and must stay refused even if that guard is ever widened.
167
+ *
168
+ * 🔑 **An absent `checks:` block does not empty the denominator** — the built-in
169
+ * set is in the pipeline, so reaching zero here means not even a default ran and
170
+ * the remedy is not "declare some checks". The guard stays because it is the
171
+ * mechanism-level guarantee that `checksRun: 0` can never be published clean,
172
+ * whatever empties it.
173
+ *
174
+ * @param costs - One record per check that EXECUTED; its length is `checksRun`
175
+ * @param issues - What the run already found, so an existing run-integrity
176
+ * report is not duplicated
177
+ * @returns The finding, or nothing when at least one check ran
178
+ */
179
+ function noCheckRanFinding(costs, issues) {
180
+ return nothingCheckedFinding(costs.length, issues, () => 'No check ran at all, so this document is not a verdict: a gate that checked nothing'
181
+ + ' produces the same report as a gate that was removed, and this run cannot tell'
182
+ + ' you which happened.'
183
+ + ' This is not an absent `resources.checks` block — VAT\'s own built-in checks run'
184
+ + ' whether or not a project declares any, so reaching zero means not even those'
185
+ + ' executed. Re-run with --debug and report it; if the run was filtered, check that'
186
+ + ' --check named a rule this build still ships.');
187
+ }
188
+ /**
189
+ * Build the check report.
190
+ *
191
+ * Pure: no file system, no clock, no `process.exit` — the same contract every
192
+ * other payload builder here follows, so the document's shape stays under unit
193
+ * test rather than only under a CLI spawn.
194
+ *
195
+ * `checksRun` is published beside the counts and is not decoration: zero
196
+ * findings from four checks and zero findings from **no** checks are the same
197
+ * document without it, and they mean opposite things. A project whose config key
198
+ * is misspelled would otherwise read as passing. It is DERIVED from the `checks`
199
+ * list rather than carried alongside it, so the denominator and the entries that
200
+ * account for it cannot disagree.
201
+ *
202
+ * 🚨 `populationSecs` and the per-check `durationSecs` do not overlap and do not
203
+ * sum to `durationMs`. The population is paid once and shared by every check;
204
+ * see {@link CheckCost} for why it is charged to none of them.
205
+ *
206
+ * 🔑 A denominator of zero is refused HERE rather than published — see
207
+ * {@link noCheckRanFinding}. This is the one function every lane's document
208
+ * passes through, so deriving the refusal in it is what makes "checked nothing,
209
+ * status clean" unrepresentable rather than merely unwritten.
210
+ *
211
+ * 🪤 The findings are NOT run through `relativizePathEntries`. Every other
212
+ * payload builder re-bases here because its producer keeps absolute paths
213
+ * internally; these arrive project-relative instead, and re-basing an
214
+ * already-relative path would resolve it against the wrong base and silently
215
+ * corrupt it. The guarantee is `locationOf` in `sql-checks.ts` — the one place
216
+ * a projection column becomes a finding's `location`, and which refuses an
217
+ * absolute or backslashed value there. Nothing on this path parses findings
218
+ * through the schema at runtime, and do not "fix" that by parsing at this
219
+ * boundary: it would convert a slip in `locationOf` from "this finding loses
220
+ * its anchor" into "the whole run throws", which is strictly worse for a gate.
221
+ *
222
+ * @param input - The findings and the provenance of the run behind them
223
+ * @returns The document to serialize
224
+ */
225
+ export function buildCheckOutputData(input) {
226
+ const issues = [...noCheckRanFinding(input.costs, input.issues), ...input.issues];
227
+ return buildReport({
228
+ // The OTHER denominator, and the one whose absence shipped a green gate over
229
+ // an empty repository. `checksRun` counts rules; this counts what they ran
230
+ // against.
231
+ examined: input.membersEnumerated,
232
+ findings: toFindings(issues),
233
+ durationMs: input.durationMs,
234
+ data: {
235
+ root: input.root,
236
+ population: input.population,
237
+ // Beside the origin, because a `population: store` a reader cannot price is
238
+ // a label taken on faith. Charged to no check — see {@link CheckCost}.
239
+ populationSecs: formatDurationSecs(input.populationMs),
240
+ // Published here too, and not only by `query`, because this verb pays it
241
+ // identically — the lens is evaluated before the first statement runs. A
242
+ // document that priced the population but not the lens would attribute the
243
+ // lens's cost to whichever rule the reader happened to be looking at, which
244
+ // is the same defect `populationSecs` exists to prevent.
245
+ lensSecs: formatDurationSecs(input.lensMs),
246
+ // What that number covers — see `CheckDataSchema.lensesEvaluated`. Copied
247
+ // into a plain array so the published document owns no reference into the
248
+ // run's own state.
249
+ lensesEvaluated: [...input.lensesEvaluated],
250
+ // The bounds of the rows those lenses produced, stated ONCE and only when
251
+ // a bounded lens ran. This is the verb an adopter makes GATE, so it is the
252
+ // one that most owes them — `utils/relation-limits.ts` carries why.
253
+ // Copied out of the readonly registry lists: the published document owns
254
+ // no reference into module state a later render could mutate.
255
+ ...structuredBounds(input.lensesEvaluated),
256
+ // The denominator of rules. Derived from `checks`, never carried beside it.
257
+ checksRun: input.costs.length,
258
+ // What each rule cost, directly under the denominator it is a breakdown
259
+ // of. A SQL surface is an unbounded cost — a project can declare a
260
+ // statement that scans every row of every table — and a single total
261
+ // attributes nothing: ten seconds is one expensive rule, a slow
262
+ // population, or forty cheap rules, and only this list tells them apart.
263
+ //
264
+ // 🪤 `rows` and `broken` are spread conditionally rather than defaulted.
265
+ // `rows: 0` on a statement that never returned would read as a clean pass,
266
+ // which is the exact confusion `RESOURCE_CHECK_BROKEN` exists to prevent —
267
+ // so a check that threw publishes no row count at all.
268
+ checks: input.costs.map((cost) => ({
269
+ name: cost.name,
270
+ // Three significant figures, so a 0.4 ms rule serializes as 0.0004 rather
271
+ // than rounding to a zero that reads as "not measured".
272
+ durationSecs: formatDurationSecs(cost.durationMs),
273
+ ...(cost.rows === undefined ? {} : { rows: cost.rows }),
274
+ ...(cost.broken === undefined ? {} : { broken: cost.broken }),
275
+ // Absent, never `false`, on a declared rule — so the key's presence is
276
+ // the whole claim and a reader never has to tell `false` from unset.
277
+ ...(cost.builtin === undefined ? {} : { builtin: cost.builtin }),
278
+ })),
279
+ },
280
+ });
281
+ }
282
+ /**
283
+ * Run a thunk between two readings of the clock, and report both.
284
+ *
285
+ * ## 🚨 Why this is a helper and not a stopwatch inline in the loop
286
+ *
287
+ * The loop used to read the clock into a `startedAt`, call `ask`, and subtract
288
+ * two lines later — and the claim below that "the span is `ask(check.sql)` and
289
+ * nothing else" had NO guard behind it. A reviewer moved the subtraction below
290
+ * the row-to-finding mapping, widening the measured span to cover work that is
291
+ * not the statement, and **the entire unit suite stayed green (36/36)**. That is
292
+ * measured, not assumed.
293
+ *
294
+ * No cleverer fake clock closes it. The injected clock advances per READING, so
295
+ * every mutation that keeps two readings per check yields the same number; a
296
+ * clock that advanced only inside `ask` could not tell the two positions apart
297
+ * either, because the real mapping never touches the clock — and making the
298
+ * mapping slow in real time would buy a flake, not a guard.
299
+ *
300
+ * So the span is a THUNK. What is timed is what is passed in, and widening it
301
+ * stops being a one-line move of a subtraction and becomes a visible rewrite of
302
+ * the call site.
303
+ *
304
+ * 🪤 The throw is CAUGHT rather than propagated, so the failing path has a
305
+ * duration too. Keep it that way: a `try` in the caller around a helper that
306
+ * only times the happy path re-opens the two-read stopwatch this closes.
307
+ *
308
+ * @param now - The clock, in milliseconds
309
+ * @param run - The work whose duration is wanted, and nothing else
310
+ * @returns What `run` produced or threw, and how long that took
311
+ */
312
+ function timed(now, run) {
313
+ const startedAt = now();
314
+ try {
315
+ return { ok: true, value: run(), durationMs: now() - startedAt };
316
+ }
317
+ catch (error) {
318
+ return { ok: false, error, durationMs: now() - startedAt };
319
+ }
320
+ }
321
+ /**
322
+ * The finding for a statement that would not run.
323
+ *
324
+ * 🔑 A finding, not a skip, at `error`, and under its OWN code.
325
+ *
326
+ * The check's declared severity describes how bad a VIOLATION is; it says
327
+ * nothing about how bad it is that the check cannot run, and a `warning` check
328
+ * whose SQL broke would otherwise fail nothing while asserting nothing. That
329
+ * much was always true of the DECLARATION.
330
+ *
331
+ * 🪤 It was never true of an adopter OVERRIDE, and this finding used to carry
332
+ * `customCheckCode(name)` — the same code as a violation of that very check. So
333
+ * `severity: { 'CUSTOM:foo': 'ignore' }`, the documented way to stand down a
334
+ * check you inherited, also silenced "foo could not run", and `'warning'`
335
+ * demoted it below the exit threshold. A renamed projection column then produced
336
+ * exit 0 from a gate. `RESOURCE_CHECK_BROKEN` is a non-overridable code
337
+ * precisely so that config line cannot reach here.
338
+ *
339
+ * 🪤 Only `ask` reaches this. Turning the selected rows INTO findings is not a
340
+ * statement failure, and reporting it as one told the operator a working rule
341
+ * was broken — see the append in {@link runUnits}.
342
+ *
343
+ * @param name - The check's key in `resources.checks`
344
+ * @param error - Whatever the statement threw
345
+ * @returns The run-integrity finding
346
+ */
347
+ function brokenCheckFinding(name, error) {
348
+ return runIntegrityFinding(`The check "${name}" could not run, so it is asserting nothing: `
349
+ + (error instanceof Error ? error.message : String(error)));
350
+ }
351
+ /**
352
+ * One declared statement, as a unit.
353
+ *
354
+ * @param name - The check's key in `resources.checks`
355
+ * @param check - Its declaration
356
+ * @param ask - Runs one statement against the populated projection
357
+ * @returns The runnable unit
358
+ */
359
+ function sqlCheckUnit(name, check, ask) {
360
+ return {
361
+ name,
362
+ select: () => {
363
+ const rows = ask(check.sql);
364
+ return { rows: rows.length, report: () => issuesFromCheckRows(name, check, rows) };
365
+ },
366
+ };
367
+ }
368
+ /**
369
+ * One built-in predicate, as a unit.
370
+ *
371
+ * 🪤 Its `rows` is the finding count, and that is the same quantity a statement
372
+ * reports: one finding per violating row. There is no conversion step to leave
373
+ * outside the clock, so the whole predicate is timed.
374
+ *
375
+ * @param builtin - The built-in, already bound to this run's projection
376
+ * @returns The runnable unit
377
+ */
378
+ function builtinCheckUnit(builtin) {
379
+ return {
380
+ name: builtin.name,
381
+ builtin: true,
382
+ select: () => {
383
+ const issues = builtin.run();
384
+ return { rows: issues.length, report: () => issues };
385
+ },
386
+ };
387
+ }
388
+ /**
389
+ * Every check this run will attempt, in the order it will attempt them.
390
+ *
391
+ * 🔑 **Built-ins FIRST, and never interleaved with the declared ones**, so the
392
+ * document's `checks[]` opens with the same rules in the same order for every
393
+ * project and a reader comparing two runs compares like with like.
394
+ *
395
+ * 🪤 A project MAY declare a check whose key equals a built-in's name. Both run,
396
+ * both are priced, `builtin` on the cost record tells them apart, and
397
+ * `--check <name>` selects both. Not refused: a new config error is a breaking
398
+ * change and nothing about the collision is unsafe.
399
+ *
400
+ * @param builtins - The default set, bound to this run's projection
401
+ * @param checks - The project's `resources.checks`
402
+ * @param ask - Runs one statement against the populated projection
403
+ * @returns The units, built-ins first
404
+ */
405
+ function checkUnits(builtins, checks, ask) {
406
+ return [
407
+ ...builtins.map((builtin) => builtinCheckUnit(builtin)),
408
+ ...Object.entries(checks).map(([name, check]) => sqlCheckUnit(name, check, ask)),
409
+ ];
410
+ }
411
+ /**
412
+ * Run every unit, collect its findings, and price it.
413
+ *
414
+ * 🚨 **The timed span is `unit.select()` and nothing else** — structurally,
415
+ * because it is the thunk handed to {@link timed}. 🚨 **And the loop announces a
416
+ * check BEFORE it can be stopped**, because a runaway statement can only be
417
+ * ended by an external `SIGKILL` that publishes nothing of the killed process's
418
+ * memory. Both, with the clock-granularity and one-record-per-check rules they
419
+ * rest on: `docs/architecture/cli.md`, "The population is charged to NOBODY" and
420
+ * "Why the loop announces a check before it can be stopped".
421
+ *
422
+ * @param units - Every check this run will attempt, built-ins first
423
+ * @param only - A single check name to run, or undefined for all of them
424
+ * @param now - The clock, in milliseconds; injected so a test can assert an
425
+ * exact duration rather than a range that passes on a timer that never started
426
+ * @param onProgress - Told what is about to run and what it cost, or undefined
427
+ * when nothing is watching (the in-process lane, and `--budget 0`)
428
+ * @returns The findings, and one cost record per check that ran
429
+ */
430
+ function runUnits(units, only, now, onProgress) {
431
+ const issues = [];
432
+ const costs = [];
433
+ /** File the cost with the observer as well as with the caller — one place. */
434
+ const price = (cost) => {
435
+ costs.push(cost);
436
+ onProgress?.({ kind: 'check', ...cost });
437
+ };
438
+ for (const unit of units) {
439
+ const { name } = unit;
440
+ if (only !== undefined && name !== only)
441
+ continue;
442
+ // Spread onto every cost record this unit files, so the two arms below
443
+ // cannot disagree about which set the rule came from.
444
+ const origin = unit.builtin === undefined ? {} : { builtin: unit.builtin };
445
+ // 🪤 BEFORE `timed`, and not one line later. `ask` is where a runaway
446
+ // statement never returns from, so an announcement below it is filed by
447
+ // every check except the one whose name the operator needs.
448
+ onProgress?.({ kind: 'start', name });
449
+ const outcome = timed(now, unit.select);
450
+ if (!outcome.ok) {
451
+ // Priced up to the throw, and with NO row count: a check that did not
452
+ // complete selected nothing, and `rows: 0` would say it selected nothing
453
+ // and passed. A built-in reaches this arm too — a defect in VAT's own
454
+ // predicate must fail the gate rather than vanish.
455
+ price({ name, durationMs: outcome.durationMs, broken: true, ...origin });
456
+ issues.push(brokenCheckFinding(name, outcome.error));
457
+ continue;
458
+ }
459
+ price({ name, durationMs: outcome.durationMs, rows: outcome.value.rows, ...origin });
460
+ // 🪤 Appended one at a time, NEVER `issues.push(...findings)`. A spread
461
+ // becomes an ARGUMENT LIST, which throws `RangeError: Maximum call stack
462
+ // size exceeded` past roughly 125,000 elements on the main thread — and a
463
+ // check's result set is unbounded by construction. VAT's own
464
+ // `blob_references` table already holds 29,645 rows, so `SELECT * FROM
465
+ // blob_references` over a repository a few times this size reached it, and
466
+ // the throw landed in the arm above: a rule that ran perfectly was reported
467
+ // as one that "could not run, so it is asserting nothing". A loop does not
468
+ // put the argument limit in play at all.
469
+ for (const issue of outcome.value.report())
470
+ issues.push(issue);
471
+ }
472
+ return { issues, costs };
473
+ }
474
+ /**
475
+ * Refuse a `--check` name that names neither a built-in nor a declared check.
476
+ *
477
+ * 🔑 **An unknown name is otherwise a silent green.** With nothing comparing the
478
+ * flag against the known names, `--check nope` filters every check away, runs
479
+ * none, and exits 0 with `checksRun: 0` and `issues: []` on empty stderr — and
480
+ * the "no checks are declared" warning does not fire, because it asks whether
481
+ * the `checks` MAP is empty, never whether the FILTER matched anything. Not a
482
+ * hypothetical typo: rename or delete a check and the CI step keeps passing
483
+ * forever while asserting nothing.
484
+ *
485
+ * Thrown rather than reported as a finding, because it is an OPERATOR error and
486
+ * not a content violation — the two have different exit codes in this command's
487
+ * contract (2 versus 1) and different audiences. Thrown before the projection is
488
+ * populated, so a mistyped flag costs no crawl.
489
+ *
490
+ * 🔑 **A built-in is nameable here exactly like a declared one.** A guard that
491
+ * only knew the config would refuse the very rules that run when there is no
492
+ * config at all. The refusal names both sets.
493
+ *
494
+ * @param builtinNames - The default set's names — {@link BUILTIN_CHECK_NAMES} in
495
+ * production, and passed in rather than imported here so a test can prove the
496
+ * guard rather than restate the constant
497
+ * @param checks - The project's `resources.checks`
498
+ * @param only - The `--check` value, or undefined
499
+ * @throws When `only` names no check this run would attempt
500
+ */
501
+ export function requireKnownCheck(builtinNames, checks, only) {
502
+ // 🪤 `Object.hasOwn`, not `only in checks`. `in` walks the prototype chain, so
503
+ // `--check toString` would have looked declared, matched nothing, and restored
504
+ // the exact silent green this guard closes.
505
+ if (only === undefined || builtinNames.includes(only) || Object.hasOwn(checks, only))
506
+ return;
507
+ const declared = Object.keys(checks);
508
+ throw new Error(`No check named "${only}" exists. `
509
+ + `Built in: ${builtinNames.join(', ')}. `
510
+ + (declared.length === 0
511
+ ? 'This project declares none of its own — add them under `resources.checks` in'
512
+ + ' vibe-agent-toolkit.config.yaml.'
513
+ : `Declared under \`resources.checks\`: ${declared.join(', ')}.`));
514
+ }
515
+ /**
516
+ * Name every `CUSTOM:` severity override that overrides nothing.
517
+ *
518
+ * 🔑 **The same silent-green shape as an unknown `--check` name, one surface
519
+ * over.** {@link requireKnownCheck} refuses `--check nope` because a filter
520
+ * that matched nothing ran nothing and exited 0. A `validation.severity` entry
521
+ * keyed `CUSTOM:<name>` for a check that is not declared is the mirror image: it
522
+ * parses (the schema validates the KEY's shape and cannot see
523
+ * `resources.checks`), applies to no finding, and says nothing. An adopter
524
+ * renames a check and leaves the override behind, or mistypes it while standing
525
+ * down an inherited one — and in the second direction the check they meant to
526
+ * silence keeps failing their build with no clue why.
527
+ *
528
+ * ⚠️ **A warning, not a refusal, and the asymmetry with `--check` is
529
+ * deliberate.** `--check` is a flag typed for THIS run, so a mistake is an
530
+ * operator error and exiting 2 costs nothing. A `severity` entry lives in a
531
+ * committed config that may legitimately be shared across repositories or
532
+ * outlive a check through a rename in flight; refusing it would be a new
533
+ * breaking config error, which is exactly the change this release already
534
+ * decided not to make twice (see `ResourcesConfigSchema`). Stderr, so the
535
+ * document on stdout stays parseable — the same channel and the same reasoning
536
+ * as the "no checks are declared" warning in {@link checkCommand}.
537
+ *
538
+ * 📌 The registry half of the key space needs no equivalent: a misspelled
539
+ * REGISTRY code is already refused by `SeverityOverrideCodeSchema`, whose
540
+ * `SEVERITY_KEY_PATTERN` alternates every registry code name with the `CUSTOM:`
541
+ * namespace — so a key has to BE one of the shipped codes to match that half.
542
+ * Only the open `CUSTOM:` half can name something that does not exist, because
543
+ * only it matches on shape rather than on identity.
544
+ *
545
+ * @param checks - The project's `resources.checks`
546
+ * @param validation - The project's `resources.validation`, or undefined
547
+ * @param logger - Where the warning goes (stderr)
548
+ */
549
+ export function warnUndeclaredOverrides(checks, validation, logger) {
550
+ const stale = Object.keys(validation?.severity ?? {})
551
+ .filter((code) => isCustomCheckCode(code))
552
+ // 🪤 `Object.hasOwn`, not `in` — same reason {@link requireKnownCheck}
553
+ // uses it: `in` walks the prototype chain, so an override keyed
554
+ // `CUSTOM:toString` would look declared and stay silent.
555
+ .filter((code) => !Object.hasOwn(checks, code.slice(CUSTOM_CHECK_CODE_PREFIX.length)));
556
+ if (stale.length === 0)
557
+ return;
558
+ const declared = Object.keys(checks);
559
+ logger.warn(`resources.validation.severity names ${stale.length} check(s) this project does not declare,`
560
+ + ` so ${stale.length === 1 ? 'it overrides' : 'they override'} nothing: ${stale.join(', ')}.`
561
+ + (declared.length === 0
562
+ ? ' No checks are declared at all.'
563
+ : ` Declared under \`resources.checks\`: ${declared.join(', ')}.`)
564
+ + ' Correct the spelling, or delete the entry — a severity override for a check that no longer'
565
+ + ' exists is not silencing anything, and a MISTYPED one leaves the check you meant to stand'
566
+ + ' down still failing the build.');
567
+ }
568
+ /**
569
+ * The finding for a run whose checks executed over an empty corpus.
570
+ *
571
+ * 🔑 **The defect this closes shipped.** A scratch repository with
572
+ * `.gitignore = *` and two declared checks reported `checksRun: 2`,
573
+ * `status: success`, exit 0 and empty stderr over a corpus of ZERO files. The
574
+ * rules reported nothing because there was nothing to report on, and the
575
+ * document could not say so: `checksRun` is the denominator of rules, not of
576
+ * rows, so a gate over eight thousand files and a gate over none serialized
577
+ * identically. A broad ignore pattern, a shallow or sparse CI checkout, a root
578
+ * that resolved to the wrong directory, or an extent source that enumerated
579
+ * nothing all produce it — and because population DECLINES ignored members
580
+ * rather than flagging them, the tables carry no trace of the difference.
581
+ *
582
+ * The precedent is `population-wiring.ts`, which makes `onBlobPopulation`
583
+ * mandatory because "a tree whose every document was declined as binary
584
+ * otherwise populates as empty and reports success". That guard covers blob
585
+ * refusals only; this one covers the extent.
586
+ *
587
+ * 🪤 **Only when checks actually RAN.** A run with no rules at all is refused
588
+ * too — it is the same class one level further out — but by
589
+ * {@link noCheckRanFinding}, whose message is about the missing RULES rather
590
+ * than about the missing rows. Firing both would hand the operator two reports
591
+ * about one situation, and this is the less specific of the two: "the 0 checks
592
+ * that ran asserted nothing over 0 members" sends them to inspect a
593
+ * `.gitignore` when what they are missing is a `checks:` block.
594
+ *
595
+ * 🪤 **`RUN_INTEGRITY_CODE`, not a code of its own** — see that constant
596
+ * for why all four refusals in this verb share one.
597
+ *
598
+ * @param checksRun - How many checks EXECUTED. Under `--check` that is fewer
599
+ * than the project declares, which is why the message says "ran" and not
600
+ * "declared": the number is the size of the filtered run
601
+ * @param membersEnumerated - How many members the population enumerated
602
+ * @returns The finding, or nothing when the run had a corpus (or no checks)
603
+ */
604
+ function emptyCorpusFinding(checksRun, membersEnumerated) {
605
+ if (checksRun === 0 || membersEnumerated > 0)
606
+ return [];
607
+ return [runIntegrityFinding(`The projection enumerated 0 members, so the ${checksRun} check(s) that ran`
608
+ + ' asserted nothing: there were no rows for any statement to select and'
609
+ + ' zero findings means only that the corpus was empty.'
610
+ + ' Look at `.gitignore` (one broad pattern declines every file), at whether'
611
+ + ' the checkout is complete rather than shallow or sparse, and at whether'
612
+ + ' `root` in this report is the directory you meant.'
613
+ + ' `vat resources scan` over the same path lists what an enumeration finds.')];
614
+ }
615
+ /**
616
+ * Where the run was when it stopped, as a phrase.
617
+ *
618
+ * @param inFlight - What the log's last state says
619
+ * @returns A clause naming the unit
620
+ */
621
+ function inFlightPhrase(inFlight) {
622
+ if (inFlight.kind === 'check')
623
+ return `while the check "${inFlight.name}" was running`;
624
+ if (inFlight.kind === 'reporting') {
625
+ return 'after the last check had finished, while its document was being assembled';
626
+ }
627
+ // 🪤 The HEDGE is load-bearing and was once deleted on the strength of
628
+ // `checks-complete`. It does not cover this window. `price()` files the last
629
+ // check's cost BEFORE `issuesFromCheckRows` turns its rows into issues, and
630
+ // `checks-complete` is written after that conversion — so a kill in between
631
+ // leaves a log of nothing but completed checks, which reads as `idle`. The
632
+ // unhedged sentence then told an operator whose run had finished every rule
633
+ // that it died before the first statement. The window is narrow (the
634
+ // conversion runs ~0.74x the statement it follows, so a budget that survived
635
+ // the statement survives the conversion) but a narrow window is not a closed
636
+ // one, and the sentence costs nothing to keep honest.
637
+ return 'while no check was running — either between the population and the first'
638
+ + ' statement, or after the last one had filed its cost';
639
+ }
640
+ /**
641
+ * What ended the child, as a phrase.
642
+ *
643
+ * @param death - The ending the supervisor resolved
644
+ * @returns A clause naming the mechanism
645
+ */
646
+ function deathPhrase(death) {
647
+ if (death.kind === 'signal')
648
+ return `the child process was terminated by ${death.signal}`;
649
+ if (death.kind === 'spawn-failed') {
650
+ return `the child process could not be started at all (${death.binary}: ${death.detail})`;
651
+ }
652
+ if (death.kind === 'kill-failed') {
653
+ return 'the budget was blown and the SIGKILL that should have ended the run was REFUSED'
654
+ + ` (${death.detail})`;
655
+ }
656
+ if (death.kind === 'no-output') {
657
+ return `the child process exited ${death.code} and published no report at all`;
658
+ }
659
+ return 'the child process ended with neither an exit code nor a signal';
660
+ }
661
+ /**
662
+ * What to do about a child that never started.
663
+ *
664
+ * 🚨 **"Check your PATH" is the wrong thing to tell a saturated runner.**
665
+ * `spawn` fails with `EAGAIN` when the machine has no free process slot and
666
+ * `ENOMEM` when it has no free memory — which is precisely the memory-pressured
667
+ * environment this whole feature exists for, and precisely where an operator
668
+ * least deserves to be sent hunting an installation problem that is not there.
669
+ *
670
+ * @param detail - What `spawn` reported
671
+ * @returns The remedy sentence
672
+ */
673
+ function spawnFailedRemedy(detail) {
674
+ if (/\b(?:EAGAIN|ENOMEM)\b/.test(detail)) {
675
+ return ' The child could not be started because the RUNNER had nothing left to start it'
676
+ + ' with — EAGAIN is "no free process slot" and ENOMEM is "no free memory" — so this is'
677
+ + ' about how loaded the machine is, not about the installation and not about the SQL.'
678
+ + ' Give the job a less contended runner, or reduce what else is running beside it.';
679
+ }
680
+ return ' Nothing ran at all, so this is an installation or PATH problem rather than'
681
+ + ' anything about the SQL.';
682
+ }
683
+ /**
684
+ * What to do about a kill the kernel refused.
685
+ *
686
+ * ⛔ This must never be composed from the spawn-failure sentence, however
687
+ * similar the plumbing is. The two are opposites: one child never existed, and
688
+ * this one is, as far as this process can tell, STILL RUNNING.
689
+ *
690
+ * @param death - The refused kill, and the pid it left behind
691
+ * @returns The remedy sentence
692
+ */
693
+ function killFailedRemedy(detail, pid) {
694
+ const handle = pid === undefined
695
+ ? ' The child\'s pid is not known to this process.'
696
+ : ` The child was process ${pid}.`;
697
+ return ' The run started, ran past its budget, and then REFUSED TO DIE: the watchdog sent'
698
+ + ` SIGKILL and the kernel would not deliver it (${detail}), which normally means EPERM.`
699
+ + handle
700
+ + ' It may still be running and still holding whatever the statement was holding, so'
701
+ + ' check for it and end it by hand before starting another run. Nothing about the SQL'
702
+ + ' or the installation is implicated by the refusal itself — but the bound could not be'
703
+ + ' enforced, so treat this run as unsupervised.'
704
+ // 🚨 Its OWN budget-role sentence, because the generic one below is false
705
+ // here: this is the single abnormal death the watchdog causes. Saying "the
706
+ // watchdog never fired" would repeat, in the report, the exact confusion
707
+ // that produced the defect.
708
+ + ' The watchdog DID fire — the budget is what decided this run should stop — but the'
709
+ + ' kill was refused, so raising the budget only moves when the refusal happens. It does'
710
+ + ' not make the run stoppable.';
711
+ }
712
+ /**
713
+ * What to do about a particular signal.
714
+ *
715
+ * 🚨 **The defect this closes: the fallback contradicted the sentence beside
716
+ * it.** Only `SIGABRT` and `SIGKILL` were partitioned on and everything else
717
+ * fell through to a sentence written for `no-status` — so a real `kill -TERM`
718
+ * produced "terminated by SIGTERM … The cause is outside anything this command
719
+ * can observe; the child left no exit code to report". It names the signal, and
720
+ * then says it can see nothing and was told nothing. SIGSEGV, SIGBUS, SIGHUP and
721
+ * SIGXCPU read the same way.
722
+ *
723
+ * ⚠️ **SIGTERM is not exotic; it is how CI kills things** — `timeout(1)`, a
724
+ * cancelled job, a container stopping. The old claim that "SIGABRT and SIGKILL
725
+ * are the two that actually happen" was wrong about the commonest one.
726
+ *
727
+ * @param signal - The signal `close` reported
728
+ * @returns The remedy sentence
729
+ */
730
+ function signalRemedy(signal) {
731
+ if (signal === 'SIGABRT') {
732
+ return ' SIGABRT here is Node aborting on its own heap limit: the run exhausted memory'
733
+ + ' MATERIALISING a result set, which is what an unbounded statement does as soon as it'
734
+ + ' selects rows rather than an aggregate. The remedy is a narrower statement or a'
735
+ + ' `LIMIT`, not a bigger machine.';
736
+ }
737
+ if (signal === 'SIGKILL') {
738
+ return ' Nothing inside this run sends SIGKILL except the budget watchdog, and the watchdog'
739
+ + ' did not fire — so something OUTSIDE killed the child: a kernel OOM killer on a'
740
+ + ' memory-capped runner (it picks the largest process, which is this child and never the'
741
+ + ' idle parent), a container memory limit, or an operator. Give the job more memory, or'
742
+ + ' narrow the statement so it needs less.';
743
+ }
744
+ if (signal === 'SIGTERM' || signal === 'SIGINT' || signal === 'SIGHUP') {
745
+ return ` Nothing inside this run sends ${signal}: something OUTSIDE asked the child to stop.`
746
+ + ' On CI that is almost always a step or job timeout (`timeout(1)`, a workflow'
747
+ + ' `timeout-minutes`), a cancelled pipeline, or a container being shut down. Look at the'
748
+ + ' job\'s own time limit and at whether the run was cancelled — not at the SQL, which was'
749
+ + ' still working when it was interrupted.';
750
+ }
751
+ if (signal === 'SIGSEGV' || signal === 'SIGBUS') {
752
+ return ` ${signal} is a CRASH inside native code, not anything an adopter's SQL can express:`
753
+ + ' the process was killed by the kernel for touching memory it should not have. That'
754
+ + ' points at a native module (`node:sqlite` is one) or at the platform, and it is worth'
755
+ + ' reporting with the Node version, the platform and the statement that was running.';
756
+ }
757
+ return ` ${signal} was not sent by anything inside this run, and this command has no specific`
758
+ + ' remedy for it: look at what else on this machine signals its child processes — a'
759
+ + ' supervisor, a resource limit, or an operator.';
760
+ }
761
+ /**
762
+ * The exit code Node uses for its own fatal abort, on EVERY platform.
763
+ *
764
+ * 🚨 **This is measured, and it is NOT the local platform's signal number.** On
765
+ * macOS and Linux 134 is the shell encoding `128 + SIGABRT`, where `SIGABRT` is
766
+ * 6. On Windows `os.constants.signals.SIGABRT` is **22** — MSVC numbers it
767
+ * differently — so deriving the code from that table gives 150 and matches
768
+ * nothing Node ever emits. A `windows-latest` run of the heap-abort case
769
+ * reported exactly **134**, the same as Unix: the number comes from Node's own
770
+ * abort path, not from the platform's signal table.
771
+ *
772
+ * 🪤 An earlier version of this file DID derive it from `os.constants.signals`,
773
+ * and its unit test derived the same way — so the test agreed with the code, the
774
+ * round-trip passed on every machine, and the pair was wrong together on the one
775
+ * platform the branch exists for. The value is therefore pinned as a measured
776
+ * fact, and a test asserts the number itself rather than re-deriving it.
777
+ *
778
+ * ⛔ Not a version constant: it decides nothing about whether stored data is
779
+ * still valid. It is an external protocol value, like an API header.
780
+ */
781
+ export const NODE_FATAL_ABORT_EXIT_CODE = 134;
782
+ /**
783
+ * What to do about a child that exited with a code and said nothing.
784
+ *
785
+ * 🚨 **This is the SAME event as a signal death, seen on a platform without
786
+ * signals.** Windows has none, so Node reports its own fatal abort — the heap
787
+ * limit among them — as an exit code with `signal === null`, where macOS and
788
+ * Linux report `SIGABRT` with no code at all. An operator hitting the heap limit
789
+ * on Windows CI deserves the identical remedy as one hitting it on Linux, so the
790
+ * abort is recognised by its code and {@link signalRemedy} answers both.
791
+ *
792
+ * ⚠️ Every OTHER code gets a deliberately non-committal sentence. This command
793
+ * cannot tell an external termination it has no name for from a bug of its own,
794
+ * and claiming either would send half its readers to the wrong place — the exact
795
+ * defect {@link signalRemedy}'s fall-through was written to fix.
796
+ *
797
+ * @param code - The exit code the child reported
798
+ * @returns The remedy sentence
799
+ */
800
+ function noOutputRemedy(code) {
801
+ if (code === NODE_FATAL_ABORT_EXIT_CODE) {
802
+ return ` Exit ${code} is Node's own fatal abort. A platform with no signal to report — Windows`
803
+ + ' — surfaces as an exit code what macOS and Linux report as SIGABRT itself, and it is the'
804
+ + ' same event.' + signalRemedy('SIGABRT');
805
+ }
806
+ return ` Exit ${code} is not Node's abort code, and the child published none of the document`
807
+ + ' every path through this command ends by writing — so this is either a termination from'
808
+ + ' outside that left an exit code instead of a signal, or a defect in vat. The child\'s stderr'
809
+ + ' was inherited, so whatever it managed to say is above this report; if there is nothing'
810
+ + ' there either, report this exit code with the platform and the statement that was running.';
811
+ }
812
+ /**
813
+ * What to DO about it — and the reason the death is carried this far as a union
814
+ * rather than as a string.
815
+ *
816
+ * 🔑 Every kind wants a DIFFERENT file looked at: SIGABRT is the statement,
817
+ * SIGKILL is the runner's memory, SIGTERM is the job's own timeout, a failed
818
+ * spawn is either the installation or the runner's load, and a refused kill is a
819
+ * process still running. A message that conflated any two of them would send
820
+ * half its readers to the wrong place.
821
+ *
822
+ * ⛔ **The last line is for `no-status` and nothing else.** It is the only
823
+ * ending about which this command genuinely knows nothing, and it used to be
824
+ * the fall-through for every unpartitioned signal — which produced messages
825
+ * that named a signal and then denied having any information about it.
826
+ *
827
+ * @param death - The ending the supervisor resolved
828
+ * @returns The remedy sentence
829
+ */
830
+ function deathRemedy(death) {
831
+ if (death.kind === 'kill-failed')
832
+ return killFailedRemedy(death.detail, death.pid);
833
+ if (death.kind === 'spawn-failed')
834
+ return spawnFailedRemedy(death.detail) + WATCHDOG_UNINVOLVED;
835
+ if (death.kind === 'signal')
836
+ return signalRemedy(death.signal) + WATCHDOG_UNINVOLVED;
837
+ if (death.kind === 'no-output')
838
+ return noOutputRemedy(death.code) + WATCHDOG_UNINVOLVED;
839
+ return ' The cause is outside anything this command can observe; the child left no exit code'
840
+ + ' to report.' + WATCHDOG_UNINVOLVED;
841
+ }
842
+ /**
843
+ * The sentence that keeps an abnormal death from being blamed on the bound.
844
+ *
845
+ * ⛔ Appended by every arm of {@link deathRemedy} EXCEPT the refused kill, which
846
+ * writes its own — because there the watchdog is exactly what fired, and this
847
+ * sentence would repeat in the report the confusion that produced the defect:
848
+ * an `error` event read as a spawn failure, so `killed` was never consulted.
849
+ */
850
+ const WATCHDOG_UNINVOLVED = ' The budget was not what ended this run — the watchdog never fired — so raising it'
851
+ + ' would not help.';
852
+ /**
853
+ * The sentence that says what this document does NOT contain.
854
+ *
855
+ * 🚨 Load-bearing, and shared by both endings. The progress log records COSTS,
856
+ * not findings: a check that completed contributes its `rows` (how many rows its
857
+ * statement selected) but not the violations those rows would have become,
858
+ * because the child never got to turn them into issues. A reader who took the
859
+ * issue list as the complete account would conclude the finished rules found
860
+ * nothing.
861
+ */
862
+ const INCOMPLETE_NOTICE = ' ⚠️ The checks listed under `checks` DID complete and `rows` is what each'
863
+ + ' statement selected, but their individual violations are NOT in `issues`: the'
864
+ + ' progress log records costs, not findings. Read that list as incomplete.';
865
+ /**
866
+ * The finding for a run that was interrupted, however it was interrupted.
867
+ *
868
+ * 🪤 **`RUN_INTEGRITY_CODE`, for the reason that constant gives.** An
869
+ * interrupted run must never be silenceable by the config of the very project
870
+ * whose SQL hung.
871
+ *
872
+ * 🔑 **This is the MORE SPECIFIC of two claims that can be true at once.** A run
873
+ * killed before its first statement completed recovers zero costs, which
874
+ * {@link noCheckRanFinding} also refuses — so that one stands down when this
875
+ * one is present. The dedupe there is order-independent (`issues.some(code ===
876
+ * RUN_INTEGRITY_CODE)`), so this finding only has to be IN the issue list of an
877
+ * interrupted document, not first in it.
878
+ *
879
+ * @param inFlight - What the run was doing when the log stopped growing
880
+ * @param ending - Which way the run ended, and what the operator can do
881
+ * @returns The run-integrity finding
882
+ */
883
+ function interruptedRunFinding(inFlight, ending) {
884
+ const where = inFlightPhrase(inFlight);
885
+ const message = ending.kind === 'budget'
886
+ ? budgetKillMessage(inFlight, ending.budgetSecs, where)
887
+ : `This run DIED before it finished: ${deathPhrase(ending.death)} ${where}, so the checks`
888
+ + ' after it never executed and this document is not a verdict.'
889
+ + deathRemedy(ending.death);
890
+ return runIntegrityFinding(message + INCOMPLETE_NOTICE);
891
+ }
892
+ /**
893
+ * What a WATCHDOG kill means, which depends entirely on what was in flight.
894
+ *
895
+ * 🚨 **The defect this closes: the `reporting` state got the statement-hang
896
+ * body.** Creating that state adapted only the *where* clause, so a run whose
897
+ * ten checks had ALL completed and whose 1,000,000-issue document blew the
898
+ * budget was told "the checks after it never executed" and "a statement that
899
+ * will not finish cannot be stopped from inside the process — the query is
900
+ * synchronous and holds the event loop". Measured on that run: `checksRun: 10`,
901
+ * all ten costs present, no statement running, nothing holding the loop for any
902
+ * SQL reason. The operator was sent to hunt a hanging rule that does not exist,
903
+ * and the message never mentioned the thing that actually killed the run.
904
+ *
905
+ * ⚠️ It is easy to reach on a real gate, not a curiosity: serialising 2,000,000
906
+ * issues takes ~17 s after `checks-complete` is filed, which is one budget
907
+ * window on its own.
908
+ *
909
+ * @param inFlight - What the log's last state says
910
+ * @param budgetSecs - The bound that was blown
911
+ * @param where - The clause naming the unit, from {@link inFlightPhrase}
912
+ * @returns The message body
913
+ */
914
+ function budgetKillMessage(inFlight, budgetSecs, where) {
915
+ if (inFlight.kind === 'reporting') {
916
+ return `This run made no progress for ${budgetSecs}s and was killed ${where}.`
917
+ + ' Every declared check had already finished and filed its cost, so no statement was'
918
+ + ' running and there was nothing left to execute: the cause is the SIZE of the document'
919
+ + ' being assembled, not a rule that would not return. Serialising a large result is'
920
+ + ' linear in how much there is — 2,000,000 issues take about 17 seconds — so look at'
921
+ + ' the rules under `checks` that produced the most rows and narrow them (a `LIMIT`, or'
922
+ + ' one row per violation rather than one per member), or raise the bound with'
923
+ + ' `--budget <seconds>` so the document has time to be written.';
924
+ }
925
+ return `This run made no progress for ${budgetSecs}s and was killed ${where}, so the`
926
+ + ' checks after it never executed and this document is not a verdict.'
927
+ + ' A statement that will not finish cannot be stopped from inside the process —'
928
+ + ' the query is synchronous and holds the event loop — so the bound is enforced by'
929
+ + ' killing the run. Raise it with `--budget <seconds>` if the work is legitimately'
930
+ + ' slow, or `--budget 0` to remove it (the run can then hang forever).';
931
+ }
932
+ /**
933
+ * The refusal when there is no population line to build a document from.
934
+ *
935
+ * Separate wording per ending for the same reason the finding is: telling an
936
+ * operator whose child was OOM-killed to raise `--budget` sends them to change
937
+ * the one thing that had nothing to do with it.
938
+ *
939
+ * @param ending - Which way the run ended
940
+ * @returns The operator-error message
941
+ */
942
+ function noPopulationMessage(ending) {
943
+ const tail = ' There is no projection to report on and no honest document to publish.';
944
+ if (ending.kind === 'abnormal') {
945
+ return `This run DIED before its population completed: ${deathPhrase(ending.death)}.`
946
+ + tail + deathRemedy(ending.death);
947
+ }
948
+ return `The run made no progress for ${ending.budgetSecs}s and was killed before its`
949
+ + ' population completed.' + tail
950
+ + ' Population is legitimately slow on a large tree, and it reports progress only when it'
951
+ + ' FINISHES, so the budget is a total bound for that one unit — raise it with'
952
+ + ' `--budget <seconds>` before assuming the crawl is stuck.'
953
+ + ' For scale, two MEASURED trees, neither of them yours: VAT\'s own repository is ~1.2s'
954
+ + ' warm and 33-35s with a cold parse cache; a 10,000-file adopter tree is ~5s warm and'
955
+ + ' 16.5s cold. Your tree is a third number.';
956
+ }
957
+ /**
958
+ * Rebuild the document from what an INTERRUPTED run left on disk.
959
+ *
960
+ * Both interruptions come here — the watchdog's kill and an abnormal death —
961
+ * because everything below this line is identical for them: the same recovered
962
+ * costs, the same non-overridable finding, the same refusal when there is no
963
+ * population. Only the sentences differ, and {@link CheckRunEnding} carries that.
964
+ *
965
+ * 🔑 It feeds {@link buildCheckOutputData}, the SAME builder a completed run
966
+ * uses. A second payload builder for this lane would be a second shape to
967
+ * keep in step with every future field — and the failure mode is silent, because
968
+ * nobody reads an interrupted run's document until the day they need it.
969
+ *
970
+ * 🚨 **A run interrupted during POPULATION gets no document at all.** There is no
971
+ * projection, so `population`, `populationSecs` and `membersEnumerated` have no
972
+ * honest value — and inventing `membersEnumerated: 0` would be worse than a
973
+ * blank, because that value already MEANS "this gate ran over an empty corpus",
974
+ * which is a different and wrong claim. The throw becomes an operator error
975
+ * (exit 2) through the same `handleReportCommandError` path everything else here uses.
976
+ *
977
+ * @param options - The wreckage
978
+ * @param options.entries - What `parseProgressLog` recovered
979
+ * @param options.root - The corpus root the parent resolved
980
+ * @param options.ending - Which way the run was interrupted
981
+ * @param options.durationMs - Wall time from spawn to ending
982
+ * @returns The input to {@link buildCheckOutputData}
983
+ * @throws When the child never reported a completed population
984
+ */
985
+ export function buildInterruptedCheckInput(options) {
986
+ const { entries, root, ending, durationMs } = options;
987
+ const population = entries.find((entry) => entry.kind === 'population');
988
+ if (population === undefined)
989
+ throw new Error(noPopulationMessage(ending));
990
+ return {
991
+ root,
992
+ durationMs,
993
+ population: population.population,
994
+ populationMs: population.populationMs,
995
+ lensMs: population.lensMs,
996
+ lensesEvaluated: population.lensesEvaluated,
997
+ membersEnumerated: population.membersEnumerated,
998
+ issues: [interruptedRunFinding(unitInFlight(entries), ending)],
999
+ // 🪤 Rebuilt field by field rather than passed through. The log's check
1000
+ // entry carries a `kind` discriminator that `CheckCost` does not, and the
1001
+ // conditional spreads keep `rows`/`broken` ABSENT rather than `undefined` —
1002
+ // which is what makes `rows: 0` on a statement that never returned
1003
+ // impossible, exactly as it is on the completed path.
1004
+ costs: entries.filter((entry) => entry.kind === 'check').map((entry) => ({
1005
+ name: entry.name,
1006
+ durationMs: entry.durationMs,
1007
+ ...(entry.rows === undefined ? {} : { rows: entry.rows }),
1008
+ ...(entry.broken === undefined ? {} : { broken: entry.broken }),
1009
+ ...(entry.builtin === undefined ? {} : { builtin: entry.builtin }),
1010
+ })),
1011
+ };
1012
+ }
1013
+ /**
1014
+ * Run this project's checks — VAT's default set plus whatever the project
1015
+ * declared — and resolve their severities: the whole of this verb's logic, with
1016
+ * the database taken out.
1017
+ *
1018
+ * 🔑 **`builtins` is a required argument beside `checks`, never a default inside
1019
+ * this function.** That is what makes "the default set belongs to the pipeline,
1020
+ * not to the config file" a property of the signature — nobody can get the
1021
+ * defaults by omitting an argument or lose them by supplying no config.
1022
+ *
1023
+ * Exported and pure so the loop is provable without a spawn. It was not: every
1024
+ * spawned case declared ONE check (the `--check` case declares two and filters
1025
+ * to one, which the loop cannot distinguish), so `checksRun` was never observed
1026
+ * above 1 and a `break` at the end of the loop would have left the suite green.
1027
+ *
1028
+ * Pure in the same sense the payload builder is, with ONE seam: it reads a
1029
+ * clock. That is why the clock is a parameter — the measurement is the point of
1030
+ * the `costs` it returns, and a duration nothing can pin is a number nobody can
1031
+ * trust.
1032
+ *
1033
+ * @param options - The run
1034
+ * @param options.builtins - The default set, already bound to this run's
1035
+ * projection. Required, and `[]` is a deliberate statement rather than an
1036
+ * omission
1037
+ * @param options.checks - The declared checks
1038
+ * @param options.only - A single check name, or undefined for all
1039
+ * @param options.ask - Runs one statement against the populated projection
1040
+ * @param options.validation - The project's severity overrides, or undefined
1041
+ * @param options.membersEnumerated - How many members the population enumerated.
1042
+ * Told rather than asked, because no answer can reveal it: an `ask` over an
1043
+ * empty projection and an `ask` over a clean one both return no rows
1044
+ * @param options.now - The measurement clock in milliseconds, defaulting to
1045
+ * `performance.now()`. Injected only so a unit test can pin an exact per-check
1046
+ * duration; nothing in production passes it
1047
+ * @param options.onProgress - Where each check's `start` and cost are announced
1048
+ * so an outside supervisor can see them, or undefined when the run is not
1049
+ * supervised. Optional rather than required because `--budget 0` and every
1050
+ * unit case run with nobody watching
1051
+ * @returns The resolved findings, and one cost record per check that ran
1052
+ */
1053
+ export function runProjectChecks(options) {
1054
+ const now = options.now ?? (() => performance.now());
1055
+ const { issues, costs } = runUnits(checkUnits(options.builtins, options.checks, options.ask), options.only, now, options.onProgress);
1056
+ // 🚨 HERE, and this line moved to get here. `checks-complete` buys the phase
1057
+ // after the last statement a fresh watchdog window, and three places
1058
+ // documented that phase as "resolving severities and serialising". It was
1059
+ // emitted a level up, by `runOutcome`, AFTER this function returned — so
1060
+ // `resolveIssueSeverity` below ran inside the LAST CHECK's window and the
1061
+ // documented claim was false for the half of the phase that is quadratic in
1062
+ // the issue count. Emitting it before that call is what makes the claim true,
1063
+ // which is the useful direction: the alternative was to weaken three docs.
1064
+ //
1065
+ // ⚠️ It does NOT cover the last check's row-to-issue conversion, which
1066
+ // `runUnits` does after filing that check's cost — see {@link inFlightPhrase}
1067
+ // for why the `idle` sentence still has to hedge for that window.
1068
+ options.onProgress?.({ kind: 'checks-complete' });
1069
+ // The run-integrity report leads. An aggregate check selects a row whatever
1070
+ // the corpus is, so its findings can outnumber this one — and every one of
1071
+ // them is noise until the operator knows the gate ran over nothing.
1072
+ const reported = [...emptyCorpusFinding(costs.length, options.membersEnumerated), ...issues];
1073
+ return {
1074
+ // The adopter's own `resources.validation.severity` still applies, so a
1075
+ // check inherited from a shared config can be downgraded or ignored without
1076
+ // editing it. Applied AFTER the run, never before: a check that is going to
1077
+ // be ignored must still be EXECUTED, or "ignored" would quietly become
1078
+ // "never checked" and those are different claims.
1079
+ //
1080
+ // It reaches a check's VIOLATIONS only. `RESOURCE_CHECK_BROKEN` is not an
1081
+ // overridable code, so the news that a check stopped checking survives every
1082
+ // override an adopter can write about the check itself.
1083
+ //
1084
+ // 🔑 A BUILT-IN's findings pass through the identical call, under their
1085
+ // ordinary registry code: `severity: { CLAUDE_RULE_GLOB_INERT: ignore }`
1086
+ // silences the default exactly as it silences any other code VAT emits, with
1087
+ // no `CUSTOM:` spelling and no second override surface. That the two lanes
1088
+ // share this one line is the reason there is nothing to keep in step.
1089
+ issues: resolveIssueSeverity(reported, options.validation),
1090
+ costs,
1091
+ };
1092
+ }
1093
+ /**
1094
+ * Serialize the document in the format the operator asked for.
1095
+ *
1096
+ * Extracted because BOTH endings publish one — a completed run and a killed one
1097
+ * — and two copies of a two-branch format switch is how one of them ends up
1098
+ * ignoring `--format json`.
1099
+ *
1100
+ * @param payload - The document
1101
+ * @param format - `json`, or anything else for YAML
1102
+ */
1103
+ function emitCheckDocument(payload, format) {
1104
+ writeStructuredOutput(payload, format);
1105
+ }
1106
+ /**
1107
+ * The child's argv — this very command, plus the hidden flag that stops it
1108
+ * spawning a child of its own.
1109
+ *
1110
+ * 🚨 `--cost-log` is ALWAYS appended, and that is the recursion guard. Its
1111
+ * presence is the sole discriminator between "supervise a child" and "do the
1112
+ * work"; a path that built these arguments without it would fork bomb.
1113
+ *
1114
+ * @param pathArg - The corpus root the operator named, or undefined
1115
+ * @param options - What the operator passed
1116
+ * @param logPath - The progress log both sides watch
1117
+ * @returns The child's arguments after the CLI entry point
1118
+ */
1119
+ function childArgs(pathArg, options, logPath) {
1120
+ return [
1121
+ 'resources',
1122
+ 'check',
1123
+ ...(pathArg === undefined ? [] : [pathArg]),
1124
+ ...(options.check === undefined ? [] : ['--check', options.check]),
1125
+ ...(options.format === undefined ? [] : ['--format', options.format]),
1126
+ ...(options.debug === true ? ['--debug'] : []),
1127
+ '--cost-log',
1128
+ logPath,
1129
+ ];
1130
+ }
1131
+ /**
1132
+ * Run the checks in a child bounded by `budgetSecs`, and report either what it
1133
+ * said or what killing it revealed.
1134
+ *
1135
+ * 🪤 The endings are RETURNED rather than exited on from inside
1136
+ * `withProgressLog`. `process.exit` skips a `finally`, so exiting in there would
1137
+ * leak the temp directory on every single run — and it is the successful runs,
1138
+ * which are all of them but one, that would accumulate.
1139
+ *
1140
+ * @param options - The run
1141
+ * @param options.pathArg - The corpus root the operator named, or undefined
1142
+ * @param options.options - What the operator passed
1143
+ * @param options.budgetSecs - The bound, in seconds
1144
+ * @returns What to publish, and with what exit code
1145
+ * @throws When the child was killed before its population completed
1146
+ */
1147
+ async function superviseCheckRun(options) {
1148
+ const startDir = options.pathArg ?? process.cwd();
1149
+ // 🪤 The SILENT resolution, deliberately. `projectRootOrLoudCwd` warns on
1150
+ // stderr when there is no project ancestor, and the CHILD runs that policy for
1151
+ // real — a parent that ran it too would print the same warning twice for one
1152
+ // run. The parent needs a root only to label a KILLED run's document.
1153
+ const root = projectRootOrNull(startDir) ?? safePath.resolve(startDir);
1154
+ return withProgressLog(async (logPath) => {
1155
+ const run = await superviseCheck({
1156
+ args: childArgs(options.pathArg, options.options, logPath),
1157
+ logPath,
1158
+ budgetMs: options.budgetSecs * 1000,
1159
+ });
1160
+ if (run.outcome === 'completed')
1161
+ return { forward: run.stdout, code: run.code };
1162
+ return {
1163
+ payload: buildCheckOutputData(buildInterruptedCheckInput({
1164
+ entries: parseProgressLog(run.log),
1165
+ root,
1166
+ // 🚨 The abnormal ending reaches the SAME fail-closed document the
1167
+ // watchdog's kill does. It used to reach `{ outcome: 'completed', code:
1168
+ // 0 }` instead, because the close handler never read `close`'s signal
1169
+ // argument — so a child that aborted on its heap limit printed nothing
1170
+ // and exited 0, on precisely the runaway shape the bound exists for.
1171
+ ending: run.outcome === 'killed'
1172
+ ? { kind: 'budget', budgetSecs: options.budgetSecs }
1173
+ : { kind: 'abnormal', death: run.death },
1174
+ durationMs: run.elapsedMs,
1175
+ })),
1176
+ };
1177
+ });
1178
+ }
1179
+ /**
1180
+ * Run the project's declared checks against its projection.
1181
+ *
1182
+ * ## 🚨 Why this verb spawns a child and `vat resources query` does not
1183
+ *
1184
+ * Both verbs run SQL an adopter wrote, over the same projection, through the
1185
+ * same `withQueriedProjection` — which does NOT diverge here, and must not. The
1186
+ * fork is this function and nothing below it.
1187
+ *
1188
+ * `query`'s author is at the keyboard, and Ctrl-C already works for them. It
1189
+ * works for one reason worth stating, because it is fragile: NO signal handler
1190
+ * is installed anywhere on this path. A process blocked in synchronous
1191
+ * `node:sqlite` dies instantly on SIGINT while that remains true, and SURVIVES
1192
+ * SIGINT and SIGTERM the moment a handler exists — the handler is a JS callback
1193
+ * and the event loop that would schedule it is the blocked resource. Adding one
1194
+ * would take Ctrl-C away from `query` without adding anything to `check`.
1195
+ *
1196
+ * `check` runs unattended in CI, where nobody is there to press it. So it gets a
1197
+ * bound enforced from outside itself, and a hang becomes a bounded failure
1198
+ * instead of a job that burns its runner minutes and reports nothing.
1199
+ *
1200
+ * ## `[path]` locates the project; a `WHERE` clause is the only scope a check has
1201
+ *
1202
+ * Same contract as `vat resources query`, for the same reason (see
1203
+ * `queryCommand`): the projection is the whole tracked tree, so the argument is
1204
+ * where root discovery starts and nothing more. A locator that names no
1205
+ * directory is refused HERE, before the fork below — the parent would otherwise
1206
+ * resolve it silently to the cwd's project and spawn a child to run every check
1207
+ * over a tree the operator did not name, at exit 0.
1208
+ *
1209
+ * @param pathArg - Where to look for the project, or omitted for the current directory
1210
+ * @param options - Parsed command-line options
1211
+ */
1212
+ export async function checkCommand(pathArg, options) {
1213
+ const logger = createLogger({ debug: options.debug ?? false });
1214
+ const startTime = Date.now();
1215
+ try {
1216
+ if (pathArg !== undefined)
1217
+ assertDirectoryArgument(pathArg);
1218
+ const budgetSecs = parseBudgetSeconds(options.budget);
1219
+ // Before anything is spawned: a budget the fork below would silently ignore
1220
+ // is an operator error, not a bound.
1221
+ requireSupervisableFlags({
1222
+ costLog: options.costLog,
1223
+ budgetRaw: options.budget,
1224
+ budgetSecs,
1225
+ });
1226
+ // The presence of `--cost-log` says "you ARE the child". `--budget 0` says
1227
+ // the operator declined the bound; both run the work here, and the second
1228
+ // can therefore hang forever, which its help text says.
1229
+ if (!runsInThisProcess({ costLog: options.costLog, budgetSecs })) {
1230
+ const ending = await superviseCheckRun({ pathArg, options, budgetSecs });
1231
+ if ('forward' in ending) {
1232
+ // Verbatim. The child already built the document the operator's
1233
+ // `--format` asked for, and re-serializing it here would be a second
1234
+ // place for that shape to live.
1235
+ //
1236
+ // ⚠️ `durationSecs` in it is therefore the CHILD's wall time and does
1237
+ // not include this process's own startup and spawn (~0.15 s measured).
1238
+ // Deliberate: the field is a breakdown that `populationSecs` and the
1239
+ // per-rule costs have to reconcile against, and folding in a supervisor
1240
+ // overhead none of them can account for would leave a remainder a
1241
+ // reader would attribute to whichever rule they were looking at — the
1242
+ // exact defect `CheckCost` documents.
1243
+ writeStdoutSync(ending.forward);
1244
+ process.exit(ending.code);
1245
+ }
1246
+ emitCheckDocument(ending.payload, options.format);
1247
+ // ⛔ Never OK. A run that did not finish — killed by the watchdog OR dead of
1248
+ // its own memory — must not look like a pass. FINDINGS, because the
1249
+ // document it just published says `status: findings` with the killed
1250
+ // check named as one: the checks that completed are kept, and the exit
1251
+ // code follows the document, as everywhere else.
1252
+ process.exit(ExitCode.FINDINGS);
1253
+ }
1254
+ const projectRoot = projectRootOrLoudCwd(pathArg ?? process.cwd(), logger);
1255
+ const config = loadConfigCached(projectRoot);
1256
+ const checks = config?.resources?.checks;
1257
+ if (checks === undefined || Object.keys(checks).length === 0) {
1258
+ // 🪤 Stderr only, and it decides NOTHING — the document is what the exit
1259
+ // code is computed from and what CI parses. ⚠️ It does not stand beside a
1260
+ // refusal: the built-in set runs here, so this run DID assert something.
1261
+ // What the operator needs to know is that the project is relying entirely
1262
+ // on VAT's defaults, which is what a deleted `checks:` block looks like.
1263
+ logger.warn(`No checks of this project's own are declared, so this run asserted only VAT's`
1264
+ + ` built-in set (${BUILTIN_CHECK_NAMES.join(', ')}).`
1265
+ + ' Add your own under `resources.checks` in vibe-agent-toolkit.config.yaml;'
1266
+ + ' each is a description plus one SQL statement selecting the rows that VIOLATE it.'
1267
+ + ' `vat resources check --help` prints the SQL each built-in is equivalent to,'
1268
+ + ' as a starting point.');
1269
+ }
1270
+ // Before the crawl: a mistyped flag is an operator error and pays nothing.
1271
+ requireKnownCheck(BUILTIN_CHECK_NAMES, checks ?? {}, options.check);
1272
+ // Also before the crawl, and for the same reason: an override that overrides
1273
+ // nothing is knowable from the config alone, and the operator should read it
1274
+ // beside the other config news rather than after a full population.
1275
+ warnUndeclaredOverrides(checks ?? {}, config?.resources?.validation, logger);
1276
+ const outcome = await runOutcome({
1277
+ root: projectRoot,
1278
+ checks: checks ?? {},
1279
+ only: options.check,
1280
+ logger,
1281
+ validation: config?.resources?.validation,
1282
+ onProgress: options.costLog === undefined
1283
+ ? undefined
1284
+ : createProgressWriter(options.costLog),
1285
+ });
1286
+ const payload = buildCheckOutputData({
1287
+ ...outcome,
1288
+ root: projectRoot,
1289
+ durationMs: Date.now() - startTime,
1290
+ });
1291
+ emitCheckDocument(payload, options.format);
1292
+ process.exit(payload.summary.errors > 0 ? ExitCode.FINDINGS : ExitCode.OK);
1293
+ }
1294
+ catch (error) {
1295
+ handleReportCommandError(error, logger, startTime, 'Check', options.format);
1296
+ }
1297
+ }
1298
+ /**
1299
+ * Populate once, run the checks, resolve their severities.
1300
+ *
1301
+ * @param options - The run
1302
+ * @param options.root - Absolute corpus root
1303
+ * @param options.checks - The declared checks
1304
+ * @param options.only - A single check key, or undefined for all
1305
+ * @param options.logger - Where blob-stage refusals are reported
1306
+ * @param options.validation - The project's severity overrides, or undefined
1307
+ * @param options.onProgress - Where each unit is announced for a supervisor
1308
+ * outside this process, or undefined when nothing is watching
1309
+ * @returns The findings and the provenance of the population behind them
1310
+ */
1311
+ async function runOutcome(options) {
1312
+ const { root, checks, only, logger, validation, onProgress } = options;
1313
+ // 🪤 **No `preflight` here, deliberately.** `withQueriedProjection` can compile
1314
+ // statements against the empty schema before populating — `vat resources query`
1315
+ // passes its one statement and gets a typo back in milliseconds instead of
1316
+ // after a full population. This verb must NOT, because a check that cannot run
1317
+ // is not an operator error here: it is a `RESOURCE_CHECK_BROKEN` finding naming
1318
+ // the check, at `error`, in a document that also reports how big the corpus was
1319
+ // and what the population cost. Failing early would trade that document for a
1320
+ // bare throw, and the finding exists precisely so a renamed column cannot end a
1321
+ // gate quietly. The population is the price of an honest report, and it is
1322
+ // charged once for every check rather than per broken one.
1323
+ // 🔑 `statements` IS passed, and it is a different question from `preflight`:
1324
+ // it only decides which derived relations are evaluated. Every declared
1325
+ // statement goes in, including one `--check` narrows away — the lens is per
1326
+ // RUN, and narrowing it would make `--check foo` and a full run disagree about
1327
+ // which relations exist.
1328
+ const statements = Object.values(checks).map((check) => check.sql);
1329
+ return withQueriedProjection({ root, logger, statements }, (ask, provenance, extent, projection) => {
1330
+ // 🔑 Emitted HERE, inside the callback, because this is the one place where
1331
+ // the provenance and the extent are both exactly in hand — and emitted the
1332
+ // INSTANT population completes, because its arrival is what tells a
1333
+ // supervisor that a kill has a projection to report on. It also resets the
1334
+ // watchdog, so the population is bounded by the budget without being charged
1335
+ // the first check's share of it.
1336
+ onProgress?.({
1337
+ kind: 'population',
1338
+ population: provenance.population,
1339
+ populationMs: provenance.populationMs,
1340
+ lensMs: provenance.lensMs,
1341
+ lensesEvaluated: [...provenance.lensesEvaluated],
1342
+ membersEnumerated: extent.membersEnumerated,
1343
+ });
1344
+ // 🪤 `checks-complete` is NOT emitted here. It used to be, and that put
1345
+ // `resolveIssueSeverity` — which runs inside `runProjectChecks` — on the
1346
+ // wrong side of the line, still charged to the last check's budget window
1347
+ // while three separate docs said otherwise. It is emitted by
1348
+ // `runProjectChecks` itself now, the instant the last statement is done.
1349
+ //
1350
+ // 🔑 The DEFAULT SET enters HERE, from a module constant bound to the
1351
+ // projection — never from `config`, which this line does not read. That is
1352
+ // the placement the "defaults belong to the pipeline" invariant is about.
1353
+ const ran = runProjectChecks({
1354
+ builtins: bindBuiltinChecks(projection),
1355
+ checks,
1356
+ only,
1357
+ ask,
1358
+ validation,
1359
+ ...extent,
1360
+ onProgress,
1361
+ });
1362
+ return { ...ran, ...provenance, ...extent };
1363
+ });
1364
+ }
1365
+ //# sourceMappingURL=check.js.map