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

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 +97 -28
  3. package/dist/bin/vat.js +97 -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 +165 -0
  41. package/dist/commands/ard/emit.d.ts.map +1 -0
  42. package/dist/commands/ard/emit.js +338 -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 +84 -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 +1150 -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 +372 -0
  97. package/dist/commands/claude/context.d.ts.map +1 -0
  98. package/dist/commands/claude/context.js +1115 -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 +116 -0
  226. package/dist/commands/okf/index.js.map +1 -0
  227. package/dist/commands/okf/validate.d.ts +195 -0
  228. package/dist/commands/okf/validate.d.ts.map +1 -0
  229. package/dist/commands/okf/validate.js +199 -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 +167 -0
  259. package/dist/commands/resources/check-progress.d.ts.map +1 -0
  260. package/dist/commands/resources/check-progress.js +250 -0
  261. package/dist/commands/resources/check-progress.js.map +1 -0
  262. package/dist/commands/resources/check-supervisor.d.ts +479 -0
  263. package/dist/commands/resources/check-supervisor.d.ts.map +1 -0
  264. package/dist/commands/resources/check-supervisor.js +587 -0
  265. package/dist/commands/resources/check-supervisor.js.map +1 -0
  266. package/dist/commands/resources/check.d.ts +620 -0
  267. package/dist/commands/resources/check.d.ts.map +1 -0
  268. package/dist/commands/resources/check.js +1452 -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 +430 -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 +100 -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 +8 -6
  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 +143 -20
  374. package/dist/utils/command-error.d.ts.map +1 -1
  375. package/dist/utils/command-error.js +179 -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 +72 -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 +134 -2
  444. package/dist/utils/resource-loader.d.ts.map +1 -1
  445. package/dist/utils/resource-loader.js +290 -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 +143 -39
  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 +36 -27
  497. package/schemas/ard-emit.json +152 -0
  498. package/schemas/okf-validate.json +170 -0
  499. package/schemas/resources-check.json +257 -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,1115 @@
1
+ /**
2
+ * `vat claude context [path]` — what loads into an agent's context here, and
3
+ * what it costs.
4
+ *
5
+ * The first ANALYSIS verb under `vat claude`, which until now held only
6
+ * management verbs (plugins, marketplaces, org administration). It answers a
7
+ * question, changes nothing, and never gates: there is no threshold anywhere in
8
+ * this file, and the only non-zero exit is {@link handleCommandError}'s 2 for a
9
+ * system failure. A number that fails a build teaches people to stop reading it.
10
+ *
11
+ * ## Everything below is orchestration and formatting — deliberately
12
+ *
13
+ * Which files load, why, and what they cost are all decided in
14
+ * `@vibe-agent-toolkit/resources` (`whatLoadsAt` and `account`). This module
15
+ * populates, calls them, and renders. It computes nothing about context loading
16
+ * of its own, which is the CLI-stays-dumb rule applied to a lane where the
17
+ * temptation is strong: the totals are right there and a "just one more derived
18
+ * field" would put a second, unowned model of the vendor's behaviour in the
19
+ * package no other package can depend on.
20
+ *
21
+ * ## The limits ride WITH the run — ONCE — in every format
22
+ *
23
+ * Spec §11 puts the stated limits in the command's own output rather than in a
24
+ * doc, because a limit a reader has to go and find is a limit that never reaches
25
+ * the person acting on the number. So {@link CLAUDE_CONTEXT_LIMITS} and
26
+ * {@link CLAUDE_CONTEXT_MODELLED_BEHAVIOURS} are fields of the yaml/json
27
+ * ENVELOPE and a printed section of the text rendering, and
28
+ * {@link CLAUDE_CONTEXT_BOUNDS_STATEMENT} — *neither a floor nor a ceiling* —
29
+ * appears in all three. All three are imported, never restated: the sentence is
30
+ * domain content owned beside the list it frames, so every other consumer of the
31
+ * list reaches it too.
32
+ *
33
+ * ⛔ They belong to the ENVELOPE, never to an answer or a region, and every
34
+ * rendering obeys that identically — see {@link ContextEnvelope} for why.
35
+ * Attaching them per answer was not a formatting preference: on the whole-tree
36
+ * sweep `--all` used to be (6,224 answers on this repository) `--format json`
37
+ * measured 76,877,016 bytes and measures 6,581,468 with the block hoisted — 70.3
38
+ * MB of ONE byte-identical paragraph, repeated. That is the JSON spelling of the
39
+ * very burial {@link renderEnvelopeText} refuses to do in text, and it is why
40
+ * {@link ContextCostMapEnvelope} hoists the same block rather than growing one
41
+ * per region.
42
+ *
43
+ * ## `--all` is a MAP, not a sweep
44
+ *
45
+ * ⛔ `--all` emits no per-path documents in any format. It used to answer for
46
+ * every realized path — 10,438 documents and 205,918 lines on a large adopter
47
+ * monorepo, ~491 s — which is not a report anyone reads. It now emits the
48
+ * whole-tree cost map {@link buildContextCostMap} builds, which answers the
49
+ * question the flag exists for: where in this tree is it expensive to work.
50
+ * Naming paths is the drill-down, and is the only way to get a per-path answer.
51
+ *
52
+ * ⛔ Nothing here may read as "complete". Many of the stated limits are signed
53
+ * `over-report`/`under-report` and apply whether or not `unknownTokenRows`,
54
+ * `skippedOversizeRows` and `prunedRows` are all zero, so zeroed counters are not
55
+ * a licence for the words "total cost", "full" or "all context".
56
+ *
57
+ * ⛔ A `null` token or byte count is an UNKNOWN SIZE and is never rendered as 0 —
58
+ * see {@link chargeText}. A confident zero is indistinguishable from a measured
59
+ * one, and the same reasoning is why an unrealized path answers `unknown` rather
60
+ * than an empty table.
61
+ */
62
+ import { account, buildClaudeContextPopulation, buildContextCostMap, CLAUDE_CONTEXT_BOUNDS_STATEMENT, CLAUDE_CONTEXT_LIMITS, CLAUDE_CONTEXT_MODELLED_BEHAVIOURS, discoverableFrom, HarnessFactsAbsentError, whatLoadsAt, } from '@vibe-agent-toolkit/resources';
63
+ import { ExitCode } from '@vibe-agent-toolkit/schema';
64
+ import { findProjectRoot, isVatError } from '@vibe-agent-toolkit/utils';
65
+ import { Command, Option } from 'commander';
66
+ import { handleCommandError } from '../../utils/command-error.js';
67
+ import { targetPathWithin } from '../../utils/corpus-target.js';
68
+ import { createLogger } from '../../utils/logger.js';
69
+ import { writeJsonOutput, writeStdoutSync, writeYamlOutput } from '../../utils/output.js';
70
+ import { populationWiring } from '../../utils/population-wiring.js';
71
+ import { withPopulationCache } from '../../utils/projection-store.js';
72
+ import { gitTrackerForProjectRoot } from '../audit/distributed-tree.js';
73
+ /** Soft wrap width for the printed limit statements. */
74
+ const WRAP_COLUMNS = 96;
75
+ /** How this command names itself in a refusal. */
76
+ const COMMAND_NAME = 'vat claude context';
77
+ /**
78
+ * How many rows each RANKED section of the `--all` cost map prints.
79
+ *
80
+ * ⛔ A cap exists because the previous shape of `--all` had none: it answered for
81
+ * every realized path, which on a large adopter monorepo was 10,438 documents and
82
+ * 205,918 lines — an output nobody, human or agent, can act on. The map collapses
83
+ * that to two ranked lists, and a ranked list is only useful if its head is
84
+ * readable in one screen. 20 is that head: enough that a real hot spot is
85
+ * somewhere in it, few enough that the whole report stays scannable.
86
+ *
87
+ * ⛔ It bounds the TEXT rendering ONLY. `--format json` carries the map whole —
88
+ * see {@link ContextCostMapEnvelope} — because a program asked for the map, not
89
+ * for a summary of it, and a silently-truncated array is a defect a consumer
90
+ * cannot see.
91
+ *
92
+ * ⛔ Whenever it fires, {@link omittedNotice} says how many rows were left out. A
93
+ * silent cap reads as "this is everything", which is the one thing this report
94
+ * must never say. Exported so a test pins the notice's arithmetic against the
95
+ * same number the renderer slices at, rather than against a literal that is free
96
+ * to disagree with it.
97
+ */
98
+ export const COST_MAP_ROW_LIMIT = 20;
99
+ /**
100
+ * What the two halves of the cost map mean, and which one is exact.
101
+ *
102
+ * Printed under the heading because the numbers are unreadable without it: a
103
+ * reader who takes the per-region figure for an average, or borrows a
104
+ * region-mate's on-demand figure, has read the report backwards. This paraphrases
105
+ * the contracts `RegionCost.alwaysTokens` and `DirectoryCost.onDemandTokens`
106
+ * state in `@vibe-agent-toolkit/resources` — it asserts nothing this module
107
+ * decided, and computes nothing.
108
+ */
109
+ const COST_MAP_METHOD_STATEMENT = 'Directories are ranked by what it costs to work in them: the at-launch floor PLUS what fires'
110
+ + ' on demand there, because a cheap rule under a heavy instruction chain is still an expensive'
111
+ + ' place to work.'
112
+ + ' Every working location in a region loads the same files at launch, so the at-launch figure is'
113
+ + ' EXACT for the whole region rather than an average over it. On-demand cost is reported per'
114
+ + ' DIRECTORY instead, because a path-scoped rule is admitted where some file under that'
115
+ + " directory matches its globs and nowhere else — a region-mate's on-demand figure is never a"
116
+ + " substitute for a directory's own. Only a FILE query is exact for path-scoped rules: name a"
117
+ + ' path (vat claude context src/index.ts) to get one.';
118
+ /**
119
+ * Create the `vat claude context [path]` command.
120
+ *
121
+ * @returns The configured Commander command
122
+ */
123
+ export function createContextCommand() {
124
+ const command = new Command('context');
125
+ command
126
+ .description('Report what Claude Code loads into context at a path, why, and what it costs')
127
+ .argument('[paths...]', 'Files or directories to answer for (default: the current directory)')
128
+ .addOption(new Option('--format <format>', 'Output format: text (default), yaml, or json').choices([
129
+ 'text',
130
+ 'yaml',
131
+ 'json',
132
+ ]).default('text'))
133
+ .option('--all', 'Report the whole-tree cost map — launch cost per region, total cost per directory —'
134
+ + ' instead of answering for named paths')
135
+ .option('--discoverable', 'Also report what the loaded files LINK TO in one hop and the harness does not load')
136
+ .option('--debug', 'Verbose logging to stderr')
137
+ .action(claudeContextCommand)
138
+ .addHelpText('after', `
139
+ Description:
140
+ Report which CLAUDE.md files, .claude/rules files and @-imported files load
141
+ into an agent's context at a path, why each one is there, and what it costs.
142
+ A FILE argument is exact. A directory argument classifies each path-scoped
143
+ rule as ∀ (its glob covers every file there, so it is a second CLAUDE.md for
144
+ that directory) or ∃ (some file there matches, and the answer names it); a
145
+ rule no file there can match is left out rather than charged.
146
+
147
+ Several paths may be named at once. The tree is enumerated ONCE and every
148
+ path answered from that one population, so asking about ten paths together
149
+ costs what asking about one costs — not ten times it. No paths means the
150
+ current directory.
151
+
152
+ --all answers a different question: where in this tree is it expensive to
153
+ work. It reports launch cost once per REGION (every directory inheriting one
154
+ CLAUDE.md chain — exact for all of them, not an average) and, per DIRECTORY,
155
+ the TOTAL of that launch floor and what fires on demand there (a path-scoped
156
+ rule fires for some directories and not others, so the on-demand half is
157
+ never borrowed from a region-mate). Directories are ranked by that total. It
158
+ emits no per-path documents; name paths for those. --discoverable applies to
159
+ named paths only.
160
+
161
+ Output:
162
+ - kind: context-answers for named paths, context-cost-map for --all —
163
+ the two are different documents, so never guess which you hold
164
+ - root: the corpus root that was enumerated
165
+ - answers: (context-answers) one document per requested path, in the order
166
+ requested — always a list, even for one path, so consumers never
167
+ branch on count; with totals, rows, and every admitting predicate
168
+ - costMap: (context-cost-map) regions worst-first by launch cost, directories
169
+ worst-first by total cost (launch + on demand), the tree-level
170
+ roll-up of rows nothing could be measured for, plus the counts of
171
+ locations evaluated, queried, and left out for want of an answer
172
+ - limits: what this run deliberately does not settle, in both directions.
173
+ On the ENVELOPE beside root, never on an answer or a region — they
174
+ bound the method rather than any one path, so they are stated
175
+ exactly once however much was measured
176
+
177
+ Counts of rows whose size is unknown, skipped by the 4 MiB cliff, or pruned
178
+ behind one are reported beside every estimate and NEVER summed into it. The
179
+ text rendering prints the top ${COST_MAP_ROW_LIMIT} of each ranked list and says how many it
180
+ left out; --format json carries the map whole.
181
+
182
+ A path the projection never realized answers kind: unknown — never zero.
183
+ Diagnostics and blob-stage refusals go to stderr; stdout is the document.
184
+
185
+ Exit Codes:
186
+ 0 - An answer was produced (there is no threshold and no gate)
187
+ 1 - Invalid usage (unknown option, or an unsupported --format value)
188
+ 2 - System error (a path outside the corpus root, unreadable tree, or a
189
+ VAT bug: a memory file reached with no derived harness facts)
190
+
191
+ Example:
192
+ $ vat claude context src/index.ts docs/ README.md # one scan, three answers
193
+ `);
194
+ return command;
195
+ }
196
+ /**
197
+ * Action handler for `vat claude context [path]`.
198
+ *
199
+ * ⛔ The two branches produce DIFFERENT documents and there is no third mode: a
200
+ * `--per-path` escape hatch back to the old sweep is exactly the backward
201
+ * compatibility shim this project forbids pre-1.0, and naming paths explicitly
202
+ * already gives the per-path answer — now from a population the caller pays for
203
+ * once instead of ten thousand times.
204
+ *
205
+ * @param pathArgs - The paths to answer for; empty means the current directory
206
+ * @param options - The command's flags
207
+ */
208
+ export async function claudeContextCommand(pathArgs, options) {
209
+ const logger = createLogger(options.debug === true ? { debug: true } : {});
210
+ const startTime = Date.now();
211
+ const sweep = options.all === true;
212
+ try {
213
+ const root = findProjectRoot(process.cwd()) ?? process.cwd();
214
+ // 🔑 Every argument is resolved BEFORE the population, not inside the map.
215
+ // A path outside the corpus is a usage error, and finding it afterwards
216
+ // would charge the caller a full population — minutes on a cold cache — to
217
+ // be told they mistyped. `--all` has no arguments to check.
218
+ const requested = sweep ? [] : targetsWithin(root, pathArgs);
219
+ const projection = await populateContext(root, logger);
220
+ const format = options.format ?? 'text';
221
+ if (sweep) {
222
+ // Every number in the map is decided in `@vibe-agent-toolkit/resources`.
223
+ // This branch calls one function and formats what it returns.
224
+ const map = buildContextCostMap(projection);
225
+ emit(costMapEnvelope(root, map), renderCostMapText(map), format);
226
+ }
227
+ else {
228
+ // ONE population, N queries. `whatLoadsAt` is a pure read of materialised
229
+ // tables, so the marginal cost of another path is a map lookup — which is
230
+ // the whole reason this command takes a list rather than being run twice.
231
+ const answers = requested.map((target) => documentFor(target, projection, root, options.discoverable === true));
232
+ emit(contextEnvelope(root, answers), renderEnvelopeText(answers), format);
233
+ }
234
+ // ⛔ Always 0. There is no threshold in this command and there is not going
235
+ // to be one — a number that fails a build is a number people learn to stop
236
+ // reading. The explicit exit matches every other leaf here and guarantees the
237
+ // process ends rather than waiting on whatever the population left behind.
238
+ process.exit(ExitCode.OK);
239
+ }
240
+ catch (error) {
241
+ const label = failureLabel(error);
242
+ handleCommandError(error, logger, startTime, label, options.format);
243
+ }
244
+ }
245
+ /**
246
+ * How a failure names itself on stderr — with its `code` when it is the
247
+ * producer bug the query refuses to answer through.
248
+ *
249
+ * ⛔ Recognised by `code`, never by message. A reached memory file with no
250
+ * derived harness facts means the answer would be wrong, so there is no answer:
251
+ * it takes the same exit-2 ending as every other failure, and the code in the
252
+ * label is what tells an operator (and a bug report) it is VAT's, not the tree's.
253
+ *
254
+ * @param error - The value that was thrown
255
+ * @returns The command name `handleCommandError` prints before the message
256
+ */
257
+ function failureLabel(error) {
258
+ return isVatError(error, HarnessFactsAbsentError.code) ? `claude context (${error.code})` : 'claude context';
259
+ }
260
+ /**
261
+ * The answer document for one query.
262
+ *
263
+ * Split from the handler so the handler stays a straight line: the accounting
264
+ * call needs the `claude-md` identity set, and threading that through the
265
+ * handler body put three statements between the query and its document.
266
+ *
267
+ * Returns the document alone — rendering moved to {@link renderEnvelopeText},
268
+ * which needs every document at once to print the limits exactly once, and the
269
+ * limits themselves are attached by {@link contextEnvelope}, never here.
270
+ *
271
+ * ⛔ Exported so it can be TESTED. What has to be pinned is an ABSENCE — that no
272
+ * limit field is on the returned document — and an absence is only observable by
273
+ * calling the function that would have added it.
274
+ *
275
+ * @param answer - The query's answer
276
+ * @param projection - The populated projection, for the `claude-md` tag rows
277
+ * @param discoverable - Whether to compute the one-hop reachable set
278
+ * @returns The answer document
279
+ */
280
+ export function answerDocument(answer, projection, discoverable) {
281
+ // The cliff applies to every memory file the harness reads, so the
282
+ // accounting needs no tag vocabulary from here.
283
+ const accounted = account(answer);
284
+ // 🔑 `conditions`, `overBudgetRules` and `unattributedImports` live on the
285
+ // ORIGINAL answer — `AccountedContext` is `{rows, totals}` and nothing else.
286
+ // Reading them off the accounting result silently drops every warning.
287
+ const document = {
288
+ kind: 'answer',
289
+ input: answer.input,
290
+ directory: answer.directory,
291
+ file: answer.file,
292
+ totals: accounted.totals,
293
+ rows: accounted.rows,
294
+ conditions: answer.conditions,
295
+ overBudgetRules: answer.overBudgetRules,
296
+ unattributedImports: answer.unattributedImports,
297
+ // The ANSWER is passed, not recomputed: it is the authority for what is
298
+ // already loaded, and the two lenses partitioning depends on both reading
299
+ // one loaded set.
300
+ discoverable: discoverable ? discoverableFrom(projection, answer) : null,
301
+ };
302
+ return document;
303
+ }
304
+ /**
305
+ * Wrap the run's documents in the envelope, with the limits attached ONCE.
306
+ *
307
+ * The one place {@link CLAUDE_CONTEXT_LIMITS} and its two companions enter the
308
+ * machine-readable output. Keeping that a single assignment is what makes the
309
+ * "stated once" property structural rather than a habit: there is no per-answer
310
+ * site left where a copy could be reintroduced.
311
+ *
312
+ * ⛔ Exported so it can be TESTED alongside {@link answerDocument} — the
313
+ * regression this guards (an answer growing its own copy of the block) is a
314
+ * property of the two functions TOGETHER, and a test that can reach only one of
315
+ * them cannot see it.
316
+ *
317
+ * @param root - The corpus root that was enumerated
318
+ * @param answers - One document per requested path, in the order requested
319
+ * @returns The envelope, ready to serialize
320
+ */
321
+ export function contextEnvelope(root, answers) {
322
+ return {
323
+ kind: 'context-answers',
324
+ root,
325
+ answers,
326
+ boundsStatement: CLAUDE_CONTEXT_BOUNDS_STATEMENT,
327
+ limits: CLAUDE_CONTEXT_LIMITS,
328
+ modelledBehaviours: CLAUDE_CONTEXT_MODELLED_BEHAVIOURS,
329
+ };
330
+ }
331
+ /**
332
+ * Wrap the `--all` cost map in its envelope, with the limits attached ONCE.
333
+ *
334
+ * The mirror of {@link contextEnvelope}, and deliberately its own function rather
335
+ * than a generic over both: the two envelopes differ in more than one field, and
336
+ * a shared builder taking a payload would make `kind` a parameter — which is how
337
+ * a discriminator stops discriminating.
338
+ *
339
+ * ⛔ Exported so a test can pin, over a many-region map, that the limit block
340
+ * appears exactly ONCE in the serialized document. Presence passes on a copy per
341
+ * region, which is the 70.3 MB defect this command already shipped once.
342
+ *
343
+ * @param root - The corpus root that was enumerated
344
+ * @param costMap - The whole-tree cost map, carried entire
345
+ * @returns The envelope, ready to serialize
346
+ */
347
+ export function costMapEnvelope(root, costMap) {
348
+ return {
349
+ kind: 'context-cost-map',
350
+ root,
351
+ costMap,
352
+ boundsStatement: CLAUDE_CONTEXT_BOUNDS_STATEMENT,
353
+ limits: CLAUDE_CONTEXT_LIMITS,
354
+ modelledBehaviours: CLAUDE_CONTEXT_MODELLED_BEHAVIOURS,
355
+ };
356
+ }
357
+ /**
358
+ * Resolve every requested path, defaulting to the current directory.
359
+ *
360
+ * No arguments means the current directory — NOT the whole corpus. `--all` is
361
+ * the sweep, spelled explicitly, because a bare `vat claude context` answering
362
+ * for thousands of paths would turn the friendliest invocation into the most
363
+ * expensive one.
364
+ *
365
+ * @param root - The discovered project root
366
+ * @param pathArgs - The path arguments, possibly empty
367
+ * @returns One root-relative target per argument, in argument order
368
+ */
369
+ function targetsWithin(root, pathArgs) {
370
+ if (pathArgs.length === 0)
371
+ return [targetPathWithin(root, undefined, COMMAND_NAME)];
372
+ return pathArgs.map((pathArg) => targetPathWithin(root, pathArg, COMMAND_NAME));
373
+ }
374
+ /**
375
+ * The document for one target, answer or non-answer.
376
+ *
377
+ * @param target - The root-relative path to answer for
378
+ * @param projection - The populated projection
379
+ * @param root - The corpus root, for the non-answer's explanation
380
+ * @param discoverable - Whether to compute the one-hop reachable set
381
+ * @returns The answer document, or the structurally-different unknown document
382
+ */
383
+ function documentFor(target, projection, root, discoverable) {
384
+ const answer = whatLoadsAt(projection, target);
385
+ if (answer.kind === 'unknown')
386
+ return unknownDocumentFor(answer.input, root);
387
+ return answerDocument(answer, projection, discoverable);
388
+ }
389
+ /**
390
+ * Render every answer for a person, with the limits stated exactly once.
391
+ *
392
+ * ⛔ The limits are printed ONCE, after the last answer, rather than per answer.
393
+ * They are properties of the MEASUREMENT METHOD, not of any one path, so
394
+ * repeating them per path would read as though each answer carried its own
395
+ * caveats — and across a many-path run would bury the answers under identical
396
+ * paragraphs. A run with no answered path prints no limits at all, for the same
397
+ * reason {@link unknownDocumentFor} omits them: nothing was measured, so there is
398
+ * no measurement to bound.
399
+ *
400
+ * @param documents - The answers, in requested order
401
+ * @returns The text rendering, newline-terminated
402
+ */
403
+ export function renderEnvelopeText(documents) {
404
+ const bodies = documents.map((document) => document.kind === 'unknown' ? renderUnknownText(document) : renderAnswerText(document));
405
+ const measured = documents.some((document) => document.kind === 'answer');
406
+ if (!measured)
407
+ return bodies.join('\n');
408
+ return `${bodies.join('\n')}${limitSection().join('\n')}\n`;
409
+ }
410
+ /**
411
+ * Render the `--all` cost map for a person: where in this tree it is expensive
412
+ * to work.
413
+ *
414
+ * Four sections, in the order a reader needs them — the region table (what each
415
+ * part of the tree costs at launch), the regions' own files (what they are paying
416
+ * for), the directories that cost the most to work in overall, and what the map
417
+ * looked at — then the limits, ONCE.
418
+ *
419
+ * ⛔ Every ranked section is capped at {@link COST_MAP_ROW_LIMIT} and every cap
420
+ * that fires announces itself. ⛔ Nothing here sums an unknown into a total or
421
+ * calls any figure complete: the counted-not-summed rows are printed beside each
422
+ * estimate, and the stated limits apply whether or not those counters are zero.
423
+ *
424
+ * ⛔ Exported so the properties above can be TESTED with inputs a real tree never
425
+ * produces — a null token count, more directories than the cap. On this repository
426
+ * every file has a measured blob, so an assertion driven by a real run compares
427
+ * `false === false` and would keep passing against a `?? 0`.
428
+ *
429
+ * @param map - The whole-tree cost map from `buildContextCostMap`
430
+ * @returns The text rendering, newline-terminated
431
+ */
432
+ export function renderCostMapText(map) {
433
+ const lines = [
434
+ 'Context cost by region — what it costs to work in each part of this tree',
435
+ '',
436
+ ...wrapStatement(COST_MAP_METHOD_STATEMENT, ' '),
437
+ '',
438
+ ...regionTable(map.regions),
439
+ ...map.regions.slice(0, COST_MAP_ROW_LIMIT).flatMap(regionDetail),
440
+ ...directoryTable(map.directories),
441
+ ...coverageLines(map),
442
+ ];
443
+ return `${lines.join('\n')}\n${limitSection().join('\n')}\n`;
444
+ }
445
+ /**
446
+ * The ranked region table: what a session pays at launch in each part of the tree.
447
+ *
448
+ * @param regions - The regions, already worst-first by launch cost
449
+ * @returns The section's lines, blank-terminated
450
+ */
451
+ function regionTable(regions) {
452
+ const shown = regions.slice(0, COST_MAP_ROW_LIMIT);
453
+ const lines = [' at launch locations region'];
454
+ for (const region of shown) {
455
+ lines.push(` ${padCount(region.alwaysTokens, 9)} ${padCount(region.locationCount, 9)}`
456
+ + ` ${displayPath(region.representative)}`);
457
+ }
458
+ lines.push(...omittedNotice(shown.length, regions.length, 'region', 'regions'), '');
459
+ return lines;
460
+ }
461
+ /**
462
+ * One region's launch-time bill, itemised.
463
+ *
464
+ * 🔑 The rows are rendered by {@link rowSection}, the same function the per-path
465
+ * answer uses, so a file reads identically in both reports and there is exactly
466
+ * one admission-describer. Its `loadClass` filter is a no-op here — `alwaysRows`
467
+ * is already that class — and passing it anyway keeps the call honest rather than
468
+ * relying on the caller's guarantee.
469
+ *
470
+ * @param region - The region to itemise
471
+ * @returns The section's lines, blank-terminated
472
+ */
473
+ function regionDetail(region) {
474
+ return [
475
+ `Region ${displayPath(region.representative)} — ${groupDigits(region.alwaysTokens)} tokens`
476
+ + ` at launch, ${countOf(region.locationCount, 'location', 'locations')}`,
477
+ ...quietCounterLines(region),
478
+ ...nestBlock(rowSection('Loaded at launch', region.alwaysRows, 'always')),
479
+ '',
480
+ ];
481
+ }
482
+ /**
483
+ * A region's three counters, printed only when one of them has something to say.
484
+ *
485
+ * ⛔ **This suppression is safe ONLY because the tree-level roll-up in
486
+ * {@link coverageLines} prints UNCONDITIONALLY, zeros included.** The honesty
487
+ * rule in this lane is that a reader must never be able to mistake "nothing here
488
+ * was unmeasurable" for "nobody counted", and after this suppression the roll-up
489
+ * is the single thing in the report carrying that. Silence a region's zeros only
490
+ * while a tree-level zero is still printed somewhere; drop the roll-up and this
491
+ * function has to go back to unconditional.
492
+ *
493
+ * ⛔ Deliberately NOT applied to {@link estimateLines}. A single-path answer has
494
+ * no roll-up to fall back on, so its counters are the only statement that the
495
+ * rows were counted at all and must keep printing whatever their values.
496
+ *
497
+ * The measured motivation: nine regions on this repository printed 27 lines of
498
+ * pure zeros, burying the launch bills the section exists to show.
499
+ *
500
+ * @param region - The region whose counters to print
501
+ * @returns The three lines, or none when all three are zero
502
+ */
503
+ function quietCounterLines(region) {
504
+ const anything = region.unknownTokenRows + region.skippedOversizeRows + region.prunedRows;
505
+ if (anything === 0)
506
+ return [];
507
+ // Whole block or nothing: a reader comparing two regions must be comparing the
508
+ // same three lines, not one region's selected non-zeros.
509
+ return counterLines(region);
510
+ }
511
+ /**
512
+ * Indent a borrowed section one level, and drop the blank line it terminates with.
513
+ *
514
+ * {@link rowSection} is written for the per-path answer, where it sits at the top
515
+ * level and separates itself from what follows. Nested under a region heading it
516
+ * needs a level of indent, and its terminator would double the region's own — so
517
+ * this drops every blank rather than the last one specifically, which is exact
518
+ * because that section emits no interior blanks.
519
+ *
520
+ * @param lines - The borrowed section's lines
521
+ * @returns The same lines, indented, with blanks removed
522
+ */
523
+ function nestBlock(lines) {
524
+ return lines.filter((line) => line !== '').map((line) => ` ${line}`);
525
+ }
526
+ /**
527
+ * The ranked directory table: where in this tree it is most expensive to work.
528
+ *
529
+ * ⛔ Ordered and headed by TOTAL cost — the launch floor plus the on-demand
530
+ * burden — which is the key `buildContextCostMap` sorts by. Ranking on a key the
531
+ * producer did not sort on would make the column descend non-monotonically and
532
+ * read as a bug; ranking on the on-demand half alone would answer "where does a
533
+ * rule fire", which is not the question the map is for.
534
+ *
535
+ * ⛔ Every one of the three figures is READ. The CLI does not add
536
+ * `alwaysTokens` to `onDemandTokens` here — that sum is `DirectoryCost.
537
+ * totalTokens`, computed where both halves are known to be charged-only totals.
538
+ * A second sum in this module would be a second, unowned model of what "cost"
539
+ * means, free to disagree with the ranking beside it. Both halves stay in the
540
+ * table because they are acted on differently: the floor by moving instructions,
541
+ * the burden by scoping rules.
542
+ *
543
+ * @param directories - The directories, already worst-first by total cost
544
+ * @returns The section's lines, blank-terminated, or none when there are none
545
+ */
546
+ function directoryTable(directories) {
547
+ if (directories.length === 0)
548
+ return [];
549
+ const shown = directories.slice(0, COST_MAP_ROW_LIMIT);
550
+ const lines = [
551
+ 'Most expensive directories to work in — at launch PLUS what fires on demand there',
552
+ ' total at launch on demand directory',
553
+ ];
554
+ for (const directory of shown) {
555
+ lines.push(` ${padCount(directory.totalTokens, 9)} ${padCount(directory.alwaysTokens, 9)}`
556
+ + ` ${padCount(directory.onDemandTokens, 9)} ${displayPath(directory.directory)}`
557
+ // ⛔ The unknown-row suffix appears only when there is one, for the same
558
+ // reason and under the same condition as {@link quietCounterLines}: the
559
+ // tree-level roll-up in {@link coverageLines} states unconditionally that
560
+ // these rows were counted, so twenty repetitions of "0 rows of unknown
561
+ // size" buy nothing and crowd out the paths the table is ranking. Remove
562
+ // the roll-up and this suffix has to become unconditional again.
563
+ + unknownRowSuffix(directory.unknownTokenRows));
564
+ }
565
+ lines.push(...omittedNotice(shown.length, directories.length, 'directory', 'directories'), '');
566
+ return lines;
567
+ }
568
+ /**
569
+ * The note that a directory's on-demand cost is an under-report, when it is.
570
+ *
571
+ * @param unknownTokenRows - On-demand rows at that directory whose size is unknown
572
+ * @returns The suffix, or nothing at all when every row was measured
573
+ */
574
+ function unknownRowSuffix(unknownTokenRows) {
575
+ if (unknownTokenRows === 0)
576
+ return '';
577
+ return ` · ${countOf(unknownTokenRows, 'row', 'rows')} of unknown size, counted not summed`;
578
+ }
579
+ /**
580
+ * What the map looked at, including the locations it could not answer for.
581
+ *
582
+ * ⛔ `skippedUnknownLocations` is PRINTED, not folded away. A location left out
583
+ * because a query it needed answered `unknown` is a hole in the map, and a report
584
+ * that quietly dropped it would be indistinguishable from one where every
585
+ * location answered.
586
+ *
587
+ * ⛔ The tree-level roll-up prints UNCONDITIONALLY, all-zero included, and that
588
+ * is load-bearing: {@link quietCounterLines} suppresses the per-region zeros, so
589
+ * this is the only place left saying the rows were counted at all. A zero here is
590
+ * a measurement; an absent line would be indistinguishable from nobody looking.
591
+ *
592
+ * @param map - The cost map
593
+ * @returns The section's lines, blank-terminated
594
+ */
595
+ function coverageLines(map) {
596
+ return [
597
+ 'What this map looked at',
598
+ ` working locations evaluated ${groupDigits(map.evaluatedDirectories)}`,
599
+ ` queries issued ${groupDigits(map.queriedDirectories)}`,
600
+ ` no answer of their own ${countOf(map.skippedUnknownLocations, 'location', 'locations')}`
601
+ + ' (left out of the table above, never counted as zero)',
602
+ ' rows this map could not measure, over every region and directory above:',
603
+ ...counterLines(map.unmeasuredRows),
604
+ '',
605
+ ];
606
+ }
607
+ /**
608
+ * Say how many ranked rows were left unprinted, or say nothing when none were.
609
+ *
610
+ * ⛔ The COUNT is the point. "Some rows were omitted" leaves a reader unable to
611
+ * tell a table that dropped three rows from one that dropped three thousand, and
612
+ * a cap with no notice at all reads as "this is everything" — which is the claim
613
+ * this whole report is built not to make.
614
+ *
615
+ * @param shown - How many rows were printed
616
+ * @param total - How many there were
617
+ * @param singular - What one omitted row is, for the `total - shown === 1` case
618
+ * @param plural - What several of them are
619
+ * @returns The notice line, or none when nothing was truncated
620
+ */
621
+ function omittedNotice(shown, total, singular, plural) {
622
+ if (total <= shown)
623
+ return [];
624
+ const omitted = total - shown;
625
+ const noun = omitted === 1 ? singular : plural;
626
+ return [
627
+ ` ... and ${groupDigits(omitted)} more ${noun} not shown —`
628
+ + ` this is the ${groupDigits(shown)} most expensive, not the whole tree`,
629
+ ];
630
+ }
631
+ /**
632
+ * The three row counters that keep an estimate from reading as settled.
633
+ *
634
+ * Shared by the per-path answer and by every region of the cost map, because it
635
+ * is one rule stated in one place: a row whose size is unknown, whose file the
636
+ * 4 MiB cliff skipped, or which sits behind such a file is COUNTED here and
637
+ * summed into nothing. Two copies of these three lines would be two places for a
638
+ * `?? 0` to appear in.
639
+ *
640
+ * @param counters - Anything carrying the three counts — `ContextTotals`, or a
641
+ * region's own always-row counts
642
+ * @returns The three lines, unterminated
643
+ */
644
+ function counterLines(counters) {
645
+ return [
646
+ ` size unknown ${countOf(counters.unknownTokenRows, 'row', 'rows')}`
647
+ + ' (counted, never summed as zero)',
648
+ ` skipped over 4 MiB ${countOf(counters.skippedOversizeRows, 'row', 'rows')}`,
649
+ ` pruned behind a skip ${countOf(counters.prunedRows, 'row', 'rows')}`,
650
+ ];
651
+ }
652
+ /**
653
+ * A count with its noun, digit-grouped and agreeing in number.
654
+ *
655
+ * The alternative — a bare `${n} ${plural}` — prints "1 locations" whenever a
656
+ * region has exactly one, which is the common case on a tree with many small
657
+ * packages and reads as a rendering bug the moment anyone sees it. Both forms are
658
+ * PARAMETERS rather than a suffixed `s`, because the nouns this report uses are
659
+ * not all regular ("directory" is the counter-example living two functions away).
660
+ *
661
+ * @param value - The count
662
+ * @param singular - The noun for exactly one
663
+ * @param plural - The noun for any other count, zero included
664
+ * @returns The count and its noun
665
+ */
666
+ function countOf(value, singular, plural) {
667
+ return `${groupDigits(value)} ${value === 1 ? singular : plural}`;
668
+ }
669
+ /**
670
+ * A count, digit-grouped and right-aligned to a column width.
671
+ *
672
+ * @param value - The count
673
+ * @param width - The column width; a longer number overflows rather than truncates
674
+ * @returns The padded text
675
+ */
676
+ function padCount(value, width) {
677
+ return groupDigits(value).padStart(width);
678
+ }
679
+ /**
680
+ * A non-negative integer with thousands separators.
681
+ *
682
+ * ⚠️ Written out by hand rather than through `toLocaleString` or `Intl`: those
683
+ * are ICU- and locale-dependent, so the same tree would render `38,412` on one
684
+ * machine and `38 412` on another, and this report is exactly the kind of output
685
+ * people diff between machines. Every number reaching it is a token estimate or a
686
+ * row count, so the negative case cannot arise and is not invented for.
687
+ *
688
+ * @param value - The count
689
+ * @returns The grouped digits
690
+ */
691
+ function groupDigits(value) {
692
+ const digits = String(value);
693
+ let grouped = '';
694
+ for (const [index, digit] of [...digits].entries()) {
695
+ if (index > 0 && (digits.length - index) % 3 === 0)
696
+ grouped += ',';
697
+ grouped += digit;
698
+ }
699
+ return grouped;
700
+ }
701
+ /**
702
+ * Populate the Claude-context lane for one tree.
703
+ *
704
+ * @param root - The absolute corpus root
705
+ * @param logger - Where blob-stage refusals go. **stderr**, never stdout: this
706
+ * command's stdout is the document a caller parses, and a diagnostic in the
707
+ * middle of it breaks every consumer
708
+ * @returns The populated projection
709
+ */
710
+ async function populateContext(root, logger) {
711
+ return withPopulationCache({ root }, async (cache) => {
712
+ const gitTracker = await gitTrackerForProjectRoot(root);
713
+ return buildClaudeContextPopulation({ root, ...populationWiring(logger, gitTracker, cache, root) });
714
+ });
715
+ }
716
+ /**
717
+ * Write the document in the requested format.
718
+ *
719
+ * @param document - The yaml/json payload
720
+ * @param text - The text rendering of the same answer
721
+ * @param format - The selected format
722
+ */
723
+ function emit(document, text, format) {
724
+ if (format === 'json') {
725
+ writeJsonOutput(document);
726
+ return;
727
+ }
728
+ if (format === 'yaml') {
729
+ writeYamlOutput(document);
730
+ return;
731
+ }
732
+ writeStdoutSync(text);
733
+ }
734
+ /**
735
+ * The non-answer, spelled so it cannot be mistaken for an empty answer.
736
+ *
737
+ * ⛔ **A non-answer publishes no measurement, in every format.** No `totals`, no
738
+ * `rows`: nothing was measured here, so anything readable as a figure would
739
+ * assert that a measurement happened, which is the exact confusion `kind:
740
+ * 'unknown'` exists to prevent. The limits are one level up on the ENVELOPE and
741
+ * bound the run's method, not this path — a sweep whose every path was
742
+ * unrealized still measured nothing, and the `explanation` carries the only
743
+ * caveat that applies to a non-answer, which is that there is no answer.
744
+ *
745
+ * @param input - The path that was asked about
746
+ * @param root - The corpus root that was enumerated
747
+ * @returns The unknown document
748
+ */
749
+ function unknownDocumentFor(input, root) {
750
+ return {
751
+ kind: 'unknown',
752
+ input,
753
+ reason: 'path-not-realized',
754
+ explanation: `${displayPath(input)} is not a path this projection realized under ${root}, so VAT did not`
755
+ + ' look at it. This is NOT an answer of "nothing loads here" — no context was measured at'
756
+ + ' all. Check the path exists and is not excluded from enumeration.',
757
+ };
758
+ }
759
+ /**
760
+ * Render the non-answer for a person.
761
+ *
762
+ * @param document - The unknown document
763
+ * @returns The text rendering, newline-terminated
764
+ */
765
+ function renderUnknownText(document) {
766
+ return [
767
+ `NO ANSWER for ${displayPath(document.input)} — reason: ${document.reason}`,
768
+ '',
769
+ ...wrapStatement(document.explanation, ' '),
770
+ '',
771
+ ].join('\n');
772
+ }
773
+ /**
774
+ * Render the answer for a person.
775
+ *
776
+ * @param document - The answer document
777
+ * @returns The text rendering, newline-terminated
778
+ */
779
+ function renderAnswerText(document) {
780
+ const lines = [
781
+ ...headingLines(document),
782
+ ...estimateLines(document.totals),
783
+ ...rowSection('Loaded at launch (always)', document.rows, 'always'),
784
+ ...rowSection('Loaded on demand', document.rows, 'on-demand'),
785
+ ...listSection('Conditions', document.conditions.map(conditionLine)),
786
+ ...listSection('Rules with a paths: pattern the vendor expansion budget refused (their other patterns still apply)', document.overBudgetRules),
787
+ ...listSection('Imported files no importer could be attributed to', document.unattributedImports),
788
+ ...discoverySection(document.discoverable),
789
+ ];
790
+ return `${lines.join('\n')}\n`;
791
+ }
792
+ /**
793
+ * What the loaded files point at and the harness does not load.
794
+ *
795
+ * ⛔ Printed under its own heading with its own total, never merged into the
796
+ * token estimate. Nothing loads a markdown link, so this is an upper bound on a
797
+ * VOLUNTARY cost — what following every link once would add — and adding it to a
798
+ * context-budget figure would charge a session for documents it may never open.
799
+ * The heading says "not loaded" for a reader who sees only this section.
800
+ *
801
+ * A `null` argument means the lens was not asked for and prints NOTHING. An
802
+ * empty `rows` prints the heading with a zero, because "this file links nowhere"
803
+ * is a real and useful answer that must not be confused with "nobody looked".
804
+ *
805
+ * @param discoverable - The lens's answer, or null when `--discoverable` was off
806
+ * @returns The section's lines, or none when the lens did not run
807
+ */
808
+ function discoverySection(discoverable) {
809
+ if (discoverable === null)
810
+ return [];
811
+ const { rows, totals } = discoverable;
812
+ const lines = [
813
+ 'Discoverable in one hop — LINKED from the loaded files, NOT loaded',
814
+ ` ${groupDigits(totals.discoverableTokens)} tokens if every link were followed once`
815
+ + ' (a ceiling, never a charge)',
816
+ ` ${groupDigits(totals.unknownTokenRows)} size unknown`
817
+ + ` · ${groupDigits(totals.unrealizedRows)} not in this tree`
818
+ + ` · ${groupDigits(totals.outsideRootRows)} outside the root`,
819
+ ];
820
+ for (const row of rows) {
821
+ lines.push(` ${displayPath(row.path)} — ${discoverableCost(row)}`);
822
+ for (const citation of row.citedBy) {
823
+ const text = citation.text === null ? '' : ` as "${citation.text}"`;
824
+ lines.push(` linked from ${displayPath(citation.fromPath)}:${citation.line}${text}`);
825
+ }
826
+ }
827
+ lines.push('');
828
+ return lines;
829
+ }
830
+ /**
831
+ * What one discoverable row would cost, or why VAT cannot say.
832
+ *
833
+ * ⛔ Never `0` for an unmeasured row, the same rule {@link chargeText} enforces
834
+ * one section up. `unrealized` says what it is — an absence in THIS projection —
835
+ * rather than "broken": adjudicating a link is `vat resources validate`'s lane.
836
+ *
837
+ * @param row - The discoverable row
838
+ * @returns The cost phrase
839
+ */
840
+ function discoverableCost(row) {
841
+ if (row.reach === 'outside-root')
842
+ return 'outside the corpus root, so its size is unknowable here';
843
+ if (row.reach === 'unrealized')
844
+ return 'not realized in this projection, so nothing is known of it';
845
+ if (row.tokens === null)
846
+ return 'size unknown: no measured blob, so it is counted, not summed';
847
+ return `${groupDigits(row.tokens)} tokens if opened`;
848
+ }
849
+ /**
850
+ * The heading, which states up front whether the query was exact.
851
+ *
852
+ * @param document - The answer document
853
+ * @returns The heading lines, blank-terminated
854
+ */
855
+ function headingLines(document) {
856
+ const exactness = document.file === null
857
+ ? 'DIRECTORY query — a path-scoped rule is reported as ∀ (covers every file here)'
858
+ + ' or ∃ (some file here matches); only a FILE query is exact'
859
+ : 'FILE query — path-scoped rules are matched exactly';
860
+ return [`Claude Code context at ${displayPath(document.input)}`, ` ${exactness}`, ''];
861
+ }
862
+ /**
863
+ * The token estimate and the three counters that keep it from reading as settled.
864
+ *
865
+ * @param totals - The accounting totals
866
+ * @returns The section's lines, blank-terminated
867
+ */
868
+ function estimateLines(totals) {
869
+ return [
870
+ 'Token estimate',
871
+ ` always-loaded ${groupDigits(totals.alwaysTokens)} tokens`,
872
+ ` on-demand ${groupDigits(totals.onDemandTokens)} tokens`,
873
+ // Already folded into the two lines above — every row's own header, and the
874
+ // once-per-launch preamble on top of them — printed separately so a reader
875
+ // sees how much of the total is RENDERING rather than content.
876
+ ` of which: headers ${groupDigits(totals.headerTokens)} tokens`
877
+ + ` · preamble ${groupDigits(totals.preambleTokens)} tokens (both already counted above)`,
878
+ // ⛔ UNCONDITIONAL here, unlike a region's — see {@link quietCounterLines}.
879
+ // A single-path answer has no tree-level roll-up to fall back on, so these
880
+ // three lines are its only statement that the rows were counted at all.
881
+ ...counterLines(totals),
882
+ '',
883
+ ];
884
+ }
885
+ /**
886
+ * One load class's rows, each with every predicate that admitted it.
887
+ *
888
+ * @param title - The section heading
889
+ * @param rows - Every accounted row
890
+ * @param loadClass - The class this section reports
891
+ * @returns The section's lines, or none when the class is empty
892
+ */
893
+ function rowSection(title, rows, loadClass) {
894
+ const selected = rows.filter((row) => row.loadClass === loadClass);
895
+ if (selected.length === 0)
896
+ return [];
897
+ const lines = [title];
898
+ for (const row of selected) {
899
+ lines.push(` ${row.path} — ${chargeText(row)}`);
900
+ for (const admission of row.admissions) {
901
+ lines.push(` ${describeAdmission(admission)}`);
902
+ }
903
+ }
904
+ lines.push('');
905
+ return lines;
906
+ }
907
+ /**
908
+ * What one row costs, or why it costs nothing.
909
+ *
910
+ * ⛔ A null token count renders as "size unknown", never as `0`. The `charged`
911
+ * branch cannot be reached with a null — `sizeCliffOf` classifies those
912
+ * `unmeasured` first — but the check is written as a value test rather than a
913
+ * `?? 0`, so if that ever changed the output would say what it knows instead of
914
+ * asserting a measurement it does not have.
915
+ *
916
+ * **Exported solely so that rule can be pinned by a test that would FAIL if a
917
+ * `?? 0` were introduced.** The property lives in the RENDERER and nowhere else:
918
+ * an assertion over the json rows only re-states what `account` already
919
+ * guarantees, and on any real tree every row carries a blob, so such an
920
+ * assertion compares `false === false` and passes without ever reaching the code
921
+ * that could print a zero. It is not part of this module's command surface and
922
+ * nothing outside its own test may call it.
923
+ *
924
+ * @param row - The accounted row
925
+ * @returns The cost phrase
926
+ */
927
+ export function chargeText(row) {
928
+ if (row.sizeCliff === 'oversize-skipped') {
929
+ return 'skipped: past the 4 MiB CLAUDE.md cliff, so none of it loads';
930
+ }
931
+ if (row.sizeCliff === 'pruned-by-oversize') {
932
+ return 'not reached: every import route into it passes through a skipped file';
933
+ }
934
+ if (row.tokens === null)
935
+ return 'size unknown: no measured blob, so it is counted, not summed';
936
+ // ⚠️ Grouped through the SAME helper the tables use. Two spellings of one
937
+ // quantity — `14,396` in a column and `8385` on the row ten lines below it —
938
+ // read as a bug in the measurement rather than as a formatting choice. This
939
+ // changes only how the number is written, never whether one is written: the
940
+ // null branch above is what keeps an unknown size out of the digits entirely.
941
+ return `${groupDigits(row.tokens)} tokens`;
942
+ }
943
+ /**
944
+ * One admission, in prose.
945
+ *
946
+ * @param admission - Why the row is in the answer
947
+ * @returns The prose form
948
+ */
949
+ function describeAdmission(admission) {
950
+ switch (admission.kind) {
951
+ case 'ancestry':
952
+ return `CLAUDE.md chain, from ${displayPath(admission.dir)}`;
953
+ case 'root-rule':
954
+ return 'root-scope rules file';
955
+ case 'nested-rule':
956
+ return `nested rules file, under ${displayPath(admission.under)}`;
957
+ case 'glob-rule':
958
+ return `rule matched ${admission.pattern}`;
959
+ case 'glob-rule-covers-dir':
960
+ return `∀ rule covers EVERY file here via ${admission.pattern}`
961
+ + ' — a second CLAUDE.md for this directory in all but name';
962
+ case 'glob-rule-may-fire':
963
+ return `∃ rule may fire here — ${admission.pattern} matches ${displayPath(admission.examplePath)}`
964
+ + ', among possibly others; ask about a FILE for an exact answer';
965
+ // Spelled out rather than left to a `default`, so that adding a seventh
966
+ // admission kind upstream is a lint error here instead of an import
967
+ // description silently applied to something that is not an import.
968
+ case 'import':
969
+ return describeImport(admission);
970
+ }
971
+ }
972
+ /**
973
+ * One import admission, in prose.
974
+ *
975
+ * An unattributed member says so rather than naming a plausible importer: the
976
+ * query reports `viaPath: null` precisely when it could not attribute a parent,
977
+ * and inventing one here would launder that into a fact.
978
+ *
979
+ * @param admission - The import admission
980
+ * @returns The prose form
981
+ */
982
+ function describeImport(admission) {
983
+ if (admission.viaPath === null || admission.depth === null) {
984
+ return `@-imported into the closure rooted at ${admission.rootPath}, importer unattributed`;
985
+ }
986
+ return `imported by ${admission.viaPath} at depth ${admission.depth}`
987
+ + ` (closure rooted at ${admission.rootPath})`;
988
+ }
989
+ /**
990
+ * One condition — the compact `severity code at location: message` line, an
991
+ * indented `subject:` naming the refused or escaping TARGET when one differs
992
+ * from `location` (never for `CLOSURE_REFERENCE_UNRESOLVED`, which has none),
993
+ * an indented `why:` explaining which harness loader rule makes it so, and,
994
+ * when {@link GradedCondition.affects} names one, a `reached from:` line
995
+ * tracing the import chain that reaches it.
996
+ *
997
+ * Exported for `context-condition-line.test.ts`, the same reason
998
+ * {@link chargeText} is: the only code that renders these lines, so it is what
999
+ * a unit test pins rather than a system assertion over a real tree's own
1000
+ * conditions (which may carry none of the shapes this needs to distinguish).
1001
+ *
1002
+ * @param condition - The graded condition
1003
+ * @returns The lines, newline-joined — one {@link listSection} entry
1004
+ */
1005
+ export function conditionLine(condition) {
1006
+ const reference = condition.ref === null ? '' : ` [${condition.ref}]`;
1007
+ const lines = [
1008
+ ` ${condition.severity.padEnd(7)} ${condition.code}`
1009
+ + ` at ${conditionLocation(condition)}${reference}: ${condition.message}`,
1010
+ ];
1011
+ if (condition.subject !== null) {
1012
+ lines.push(` subject: ${condition.subject}`);
1013
+ }
1014
+ lines.push(` why: ${condition.why}`);
1015
+ if (condition.affects !== null) {
1016
+ lines.push(` reached from: ${condition.affects.chain.join(' → ')} (hop ${condition.affects.hop})`);
1017
+ }
1018
+ return lines.join('\n');
1019
+ }
1020
+ /**
1021
+ * Where a condition points.
1022
+ *
1023
+ * @param condition - The graded condition
1024
+ * @returns A path, or a `path:line`
1025
+ */
1026
+ function conditionLocation(condition) {
1027
+ return condition.line === null ? condition.path : `${condition.path}:${condition.line}`;
1028
+ }
1029
+ /**
1030
+ * A heading plus its lines, or nothing at all when there are none.
1031
+ *
1032
+ * @param title - The section heading
1033
+ * @param entries - The already-formatted lines, or bare strings to indent
1034
+ * @returns The section's lines, blank-terminated, or none
1035
+ */
1036
+ function listSection(title, entries) {
1037
+ if (entries.length === 0)
1038
+ return [];
1039
+ return [title, ...entries.map((entry) => (entry.startsWith(' ') ? entry : ` ${entry}`)), ''];
1040
+ }
1041
+ /**
1042
+ * The limits section — the one part of this rendering that is never omitted.
1043
+ *
1044
+ * Printed even when there is nothing else to say, because the limits are a
1045
+ * property of the METHOD rather than of this particular tree: an answer with no
1046
+ * conditions and no unknown rows is subject to exactly the same signed
1047
+ * over/under-report bounds as a messy one.
1048
+ *
1049
+ * ⛔ Reads the imported constants, NOT an answer document — the same source
1050
+ * {@link contextEnvelope} reads. It took no argument's worth of per-answer data
1051
+ * even when the fields were on the answer, and threading one through was what
1052
+ * made "the limits belong to an answer" look true.
1053
+ *
1054
+ * @returns The section's lines
1055
+ */
1056
+ function limitSection() {
1057
+ const lines = [
1058
+ 'What this answer does not settle',
1059
+ ...wrapStatement(CLAUDE_CONTEXT_BOUNDS_STATEMENT, ' '),
1060
+ '',
1061
+ ];
1062
+ for (const limit of CLAUDE_CONTEXT_LIMITS) {
1063
+ lines.push(` ${limit.direction}: ${limit.id}`);
1064
+ lines.push(...wrapStatement(limit.statement, ' '));
1065
+ }
1066
+ lines.push('', 'Modelled Claude Code behaviours (vendor versions, each cited)');
1067
+ for (const behaviour of CLAUDE_CONTEXT_MODELLED_BEHAVIOURS) {
1068
+ lines.push(` ${behaviour.behaviour}`);
1069
+ lines.push(` ${behaviour.introducedIn} — ${behaviour.citedFrom}`);
1070
+ }
1071
+ return lines;
1072
+ }
1073
+ /**
1074
+ * Soft-wrap a statement to {@link WRAP_COLUMNS}, under a fixed indent.
1075
+ *
1076
+ * The limit statements are two and three sentences long and are the part of this
1077
+ * output people actually have to read; unwrapped they arrive as one 400-column
1078
+ * line that a terminal breaks mid-word.
1079
+ *
1080
+ * @param text - The statement
1081
+ * @param indent - Leading whitespace for every produced line
1082
+ * @returns One or more indented lines
1083
+ */
1084
+ function wrapStatement(text, indent) {
1085
+ const width = Math.max(WRAP_COLUMNS - indent.length, 20);
1086
+ const lines = [];
1087
+ let pending = [];
1088
+ let used = 0;
1089
+ for (const word of text.split(' ')) {
1090
+ if (pending.length > 0 && used + pending.length + word.length > width) {
1091
+ lines.push(indent + pending.join(' '));
1092
+ pending = [];
1093
+ used = 0;
1094
+ }
1095
+ pending.push(word);
1096
+ used += word.length;
1097
+ }
1098
+ if (pending.length > 0)
1099
+ lines.push(indent + pending.join(' '));
1100
+ return lines;
1101
+ }
1102
+ /**
1103
+ * A root-relative path as a reader should see it.
1104
+ *
1105
+ * The corpus root itself is `''`, which renders as nothing at all and reads like
1106
+ * a bug. Only the empty case is special-cased — every other path is printed
1107
+ * exactly as the projection holds it, so a reader can paste it straight back in.
1108
+ *
1109
+ * @param path - The root-relative path
1110
+ * @returns The display form
1111
+ */
1112
+ function displayPath(path) {
1113
+ return path === '' ? '<corpus root>' : path;
1114
+ }
1115
+ //# sourceMappingURL=context.js.map