@smeltjs/core 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/README.md +3 -3
  2. package/dist/agents/guide.d.ts +68 -0
  3. package/dist/agents/guide.d.ts.map +1 -0
  4. package/dist/agents/guide.js +72 -0
  5. package/dist/agents/guide.js.map +1 -0
  6. package/dist/agents/instructions.d.ts +133 -0
  7. package/dist/agents/instructions.d.ts.map +1 -0
  8. package/dist/agents/instructions.js +212 -0
  9. package/dist/agents/instructions.js.map +1 -0
  10. package/dist/agents/lint.d.ts +154 -0
  11. package/dist/agents/lint.d.ts.map +1 -0
  12. package/dist/agents/lint.js +761 -0
  13. package/dist/agents/lint.js.map +1 -0
  14. package/dist/agents/split.d.ts +113 -0
  15. package/dist/agents/split.d.ts.map +1 -0
  16. package/dist/agents/split.js +234 -0
  17. package/dist/agents/split.js.map +1 -0
  18. package/dist/apply.d.ts.map +1 -1
  19. package/dist/apply.js.map +1 -1
  20. package/dist/cache/prefix.d.ts.map +1 -1
  21. package/dist/cache/prefix.js.map +1 -1
  22. package/dist/cli/agents.d.ts +39 -0
  23. package/dist/cli/agents.d.ts.map +1 -0
  24. package/dist/cli/agents.js +121 -0
  25. package/dist/cli/agents.js.map +1 -0
  26. package/dist/cli/args.d.ts +17 -70
  27. package/dist/cli/args.d.ts.map +1 -1
  28. package/dist/cli/args.js +18 -338
  29. package/dist/cli/args.js.map +1 -1
  30. package/dist/cli/bin.d.ts.map +1 -1
  31. package/dist/cli/bin.js.map +1 -1
  32. package/dist/cli/config.d.ts +60 -0
  33. package/dist/cli/config.d.ts.map +1 -1
  34. package/dist/cli/config.js +89 -2
  35. package/dist/cli/config.js.map +1 -1
  36. package/dist/cli/hooks.d.ts +55 -51
  37. package/dist/cli/hooks.d.ts.map +1 -1
  38. package/dist/cli/hooks.js +206 -658
  39. package/dist/cli/hooks.js.map +1 -1
  40. package/dist/cli/init.d.ts +30 -15
  41. package/dist/cli/init.d.ts.map +1 -1
  42. package/dist/cli/init.js +167 -38
  43. package/dist/cli/init.js.map +1 -1
  44. package/dist/cli/report.d.ts +23 -0
  45. package/dist/cli/report.d.ts.map +1 -1
  46. package/dist/cli/report.js +142 -4
  47. package/dist/cli/report.js.map +1 -1
  48. package/dist/cli/run.d.ts +29 -90
  49. package/dist/cli/run.d.ts.map +1 -1
  50. package/dist/cli/run.js +34 -342
  51. package/dist/cli/run.js.map +1 -1
  52. package/dist/cli/shell.d.ts +106 -0
  53. package/dist/cli/shell.d.ts.map +1 -0
  54. package/dist/cli/shell.js +100 -0
  55. package/dist/cli/shell.js.map +1 -0
  56. package/dist/cli/subcommands/agents.d.ts +72 -0
  57. package/dist/cli/subcommands/agents.d.ts.map +1 -0
  58. package/dist/cli/subcommands/agents.js +162 -0
  59. package/dist/cli/subcommands/agents.js.map +1 -0
  60. package/dist/cli/subcommands/flags.d.ts +144 -0
  61. package/dist/cli/subcommands/flags.d.ts.map +1 -0
  62. package/dist/cli/subcommands/flags.js +220 -0
  63. package/dist/cli/subcommands/flags.js.map +1 -0
  64. package/dist/cli/subcommands/hooks.d.ts +18 -0
  65. package/dist/cli/subcommands/hooks.d.ts.map +1 -0
  66. package/dist/cli/subcommands/hooks.js +80 -0
  67. package/dist/cli/subcommands/hooks.js.map +1 -0
  68. package/dist/cli/subcommands/init.d.ts +19 -0
  69. package/dist/cli/subcommands/init.d.ts.map +1 -0
  70. package/dist/cli/subcommands/init.js +44 -0
  71. package/dist/cli/subcommands/init.js.map +1 -0
  72. package/dist/cli/subcommands/map.d.ts +75 -0
  73. package/dist/cli/subcommands/map.d.ts.map +1 -0
  74. package/dist/cli/subcommands/map.js +132 -0
  75. package/dist/cli/subcommands/map.js.map +1 -0
  76. package/dist/cli/subcommands/registry.d.ts +81 -0
  77. package/dist/cli/subcommands/registry.d.ts.map +1 -0
  78. package/dist/cli/subcommands/registry.js +141 -0
  79. package/dist/cli/subcommands/registry.js.map +1 -0
  80. package/dist/cli/subcommands/retrieve.d.ts +74 -0
  81. package/dist/cli/subcommands/retrieve.d.ts.map +1 -0
  82. package/dist/cli/subcommands/retrieve.js +84 -0
  83. package/dist/cli/subcommands/retrieve.js.map +1 -0
  84. package/dist/cli/subcommands/smelt.d.ts +107 -0
  85. package/dist/cli/subcommands/smelt.d.ts.map +1 -0
  86. package/dist/cli/subcommands/smelt.js +304 -0
  87. package/dist/cli/subcommands/smelt.js.map +1 -0
  88. package/dist/cli/subcommands/stats.d.ts +43 -0
  89. package/dist/cli/subcommands/stats.d.ts.map +1 -0
  90. package/dist/cli/subcommands/stats.js +62 -0
  91. package/dist/cli/subcommands/stats.js.map +1 -0
  92. package/dist/cli/subcommands/subcommand.d.ts +113 -0
  93. package/dist/cli/subcommands/subcommand.d.ts.map +1 -0
  94. package/dist/cli/subcommands/subcommand.js +3 -0
  95. package/dist/cli/subcommands/subcommand.js.map +1 -0
  96. package/dist/cli/usage.d.ts +3 -0
  97. package/dist/cli/usage.d.ts.map +1 -0
  98. package/dist/cli/usage.js +95 -0
  99. package/dist/cli/usage.js.map +1 -0
  100. package/dist/detect.d.ts.map +1 -1
  101. package/dist/detect.js.map +1 -1
  102. package/dist/errors.d.ts +21 -1
  103. package/dist/errors.d.ts.map +1 -1
  104. package/dist/errors.js +33 -1
  105. package/dist/errors.js.map +1 -1
  106. package/dist/harness/aider.d.ts +10 -0
  107. package/dist/harness/aider.d.ts.map +1 -0
  108. package/dist/harness/aider.js +21 -0
  109. package/dist/harness/aider.js.map +1 -0
  110. package/dist/harness/claude-code.d.ts +3 -0
  111. package/dist/harness/claude-code.d.ts.map +1 -0
  112. package/dist/harness/claude-code.js +68 -0
  113. package/dist/harness/claude-code.js.map +1 -0
  114. package/dist/harness/cline.d.ts +3 -0
  115. package/dist/harness/cline.d.ts.map +1 -0
  116. package/dist/harness/cline.js +54 -0
  117. package/dist/harness/cline.js.map +1 -0
  118. package/dist/harness/codex.d.ts +3 -0
  119. package/dist/harness/codex.d.ts.map +1 -0
  120. package/dist/harness/codex.js +92 -0
  121. package/dist/harness/codex.js.map +1 -0
  122. package/dist/harness/cursor.d.ts +3 -0
  123. package/dist/harness/cursor.d.ts.map +1 -0
  124. package/dist/harness/cursor.js +54 -0
  125. package/dist/harness/cursor.js.map +1 -0
  126. package/dist/harness/gemini.d.ts +3 -0
  127. package/dist/harness/gemini.d.ts.map +1 -0
  128. package/dist/harness/gemini.js +60 -0
  129. package/dist/harness/gemini.js.map +1 -0
  130. package/dist/harness/grok.d.ts +3 -0
  131. package/dist/harness/grok.d.ts.map +1 -0
  132. package/dist/harness/grok.js +47 -0
  133. package/dist/harness/grok.js.map +1 -0
  134. package/dist/harness/hermes.d.ts +3 -0
  135. package/dist/harness/hermes.d.ts.map +1 -0
  136. package/dist/harness/hermes.js +72 -0
  137. package/dist/harness/hermes.js.map +1 -0
  138. package/dist/harness/kilocode.d.ts +3 -0
  139. package/dist/harness/kilocode.d.ts.map +1 -0
  140. package/dist/harness/kilocode.js +35 -0
  141. package/dist/harness/kilocode.js.map +1 -0
  142. package/dist/harness/opencode.d.ts +3 -0
  143. package/dist/harness/opencode.d.ts.map +1 -0
  144. package/dist/harness/opencode.js +97 -0
  145. package/dist/harness/opencode.js.map +1 -0
  146. package/dist/harness/paths.d.ts +17 -0
  147. package/dist/harness/paths.d.ts.map +1 -0
  148. package/dist/harness/paths.js +45 -0
  149. package/dist/harness/paths.js.map +1 -0
  150. package/dist/harness/profile.d.ts +209 -0
  151. package/dist/harness/profile.d.ts.map +1 -0
  152. package/dist/harness/profile.js +41 -0
  153. package/dist/harness/profile.js.map +1 -0
  154. package/dist/harness/registry.d.ts +100 -0
  155. package/dist/harness/registry.d.ts.map +1 -0
  156. package/dist/harness/registry.js +144 -0
  157. package/dist/harness/registry.js.map +1 -0
  158. package/dist/harness/snippet.d.ts +19 -0
  159. package/dist/harness/snippet.d.ts.map +1 -0
  160. package/dist/harness/snippet.js +41 -0
  161. package/dist/harness/snippet.js.map +1 -0
  162. package/dist/hash.d.ts.map +1 -1
  163. package/dist/hash.js.map +1 -1
  164. package/dist/hooks/guard-core.d.ts +14 -14
  165. package/dist/hooks/guard-core.d.ts.map +1 -1
  166. package/dist/hooks/guard-core.js +15 -37
  167. package/dist/hooks/guard-core.js.map +1 -1
  168. package/dist/hooks/shim.d.ts +95 -4
  169. package/dist/hooks/shim.d.ts.map +1 -1
  170. package/dist/hooks/shim.js +87 -2
  171. package/dist/hooks/shim.js.map +1 -1
  172. package/dist/hooks/shims/claude-code.d.ts +5 -18
  173. package/dist/hooks/shims/claude-code.d.ts.map +1 -1
  174. package/dist/hooks/shims/claude-code.js +9 -56
  175. package/dist/hooks/shims/claude-code.js.map +1 -1
  176. package/dist/hooks/shims/cline.d.ts +5 -12
  177. package/dist/hooks/shims/cline.d.ts.map +1 -1
  178. package/dist/hooks/shims/cline.js +9 -34
  179. package/dist/hooks/shims/cline.js.map +1 -1
  180. package/dist/hooks/shims/codex.d.ts +5 -18
  181. package/dist/hooks/shims/codex.d.ts.map +1 -1
  182. package/dist/hooks/shims/codex.js +9 -51
  183. package/dist/hooks/shims/codex.js.map +1 -1
  184. package/dist/hooks/shims/cursor.d.ts +5 -14
  185. package/dist/hooks/shims/cursor.d.ts.map +1 -1
  186. package/dist/hooks/shims/cursor.js +9 -42
  187. package/dist/hooks/shims/cursor.js.map +1 -1
  188. package/dist/hooks/shims/gemini.d.ts +5 -18
  189. package/dist/hooks/shims/gemini.d.ts.map +1 -1
  190. package/dist/hooks/shims/gemini.js +9 -48
  191. package/dist/hooks/shims/gemini.js.map +1 -1
  192. package/dist/hooks/shims/grok.d.ts +5 -13
  193. package/dist/hooks/shims/grok.d.ts.map +1 -1
  194. package/dist/hooks/shims/grok.js +9 -32
  195. package/dist/hooks/shims/grok.js.map +1 -1
  196. package/dist/hooks/shims/hermes.d.ts +5 -17
  197. package/dist/hooks/shims/hermes.d.ts.map +1 -1
  198. package/dist/hooks/shims/hermes.js +9 -45
  199. package/dist/hooks/shims/hermes.js.map +1 -1
  200. package/dist/index.d.ts +43 -76
  201. package/dist/index.d.ts.map +1 -1
  202. package/dist/index.js +21 -67
  203. package/dist/index.js.map +1 -1
  204. package/dist/lang/bash.d.ts.map +1 -1
  205. package/dist/lang/bash.js.map +1 -1
  206. package/dist/lang/c.d.ts.map +1 -1
  207. package/dist/lang/c.js.map +1 -1
  208. package/dist/lang/c_sharp.d.ts.map +1 -1
  209. package/dist/lang/c_sharp.js.map +1 -1
  210. package/dist/lang/cpp.d.ts.map +1 -1
  211. package/dist/lang/cpp.js.map +1 -1
  212. package/dist/lang/go.d.ts.map +1 -1
  213. package/dist/lang/go.js.map +1 -1
  214. package/dist/lang/java.d.ts.map +1 -1
  215. package/dist/lang/java.js.map +1 -1
  216. package/dist/lang/javascript.d.ts.map +1 -1
  217. package/dist/lang/javascript.js.map +1 -1
  218. package/dist/lang/kotlin.d.ts.map +1 -1
  219. package/dist/lang/kotlin.js.map +1 -1
  220. package/dist/lang/php.d.ts.map +1 -1
  221. package/dist/lang/php.js.map +1 -1
  222. package/dist/lang/profile.d.ts.map +1 -1
  223. package/dist/lang/profile.js.map +1 -1
  224. package/dist/lang/python.d.ts.map +1 -1
  225. package/dist/lang/python.js.map +1 -1
  226. package/dist/lang/registry.d.ts.map +1 -1
  227. package/dist/lang/registry.js.map +1 -1
  228. package/dist/lang/ruby.d.ts.map +1 -1
  229. package/dist/lang/ruby.js.map +1 -1
  230. package/dist/lang/rust.d.ts.map +1 -1
  231. package/dist/lang/rust.js.map +1 -1
  232. package/dist/lang/swift.d.ts.map +1 -1
  233. package/dist/lang/swift.js.map +1 -1
  234. package/dist/lang/tsx.d.ts.map +1 -1
  235. package/dist/lang/tsx.js.map +1 -1
  236. package/dist/lang/typescript.d.ts.map +1 -1
  237. package/dist/lang/typescript.js.map +1 -1
  238. package/dist/net/policy.d.ts +18 -1
  239. package/dist/net/policy.d.ts.map +1 -1
  240. package/dist/net/policy.js +6 -4
  241. package/dist/net/policy.js.map +1 -1
  242. package/dist/ops/index.d.ts +39 -0
  243. package/dist/ops/index.d.ts.map +1 -0
  244. package/dist/ops/index.js +37 -0
  245. package/dist/ops/index.js.map +1 -0
  246. package/dist/ops/inputs.d.ts +170 -0
  247. package/dist/ops/inputs.d.ts.map +1 -0
  248. package/dist/ops/inputs.js +142 -0
  249. package/dist/ops/inputs.js.map +1 -0
  250. package/dist/ops/verbs.d.ts +136 -0
  251. package/dist/ops/verbs.d.ts.map +1 -0
  252. package/dist/ops/verbs.js +76 -0
  253. package/dist/ops/verbs.js.map +1 -0
  254. package/dist/plan/auto.d.ts +69 -0
  255. package/dist/plan/auto.d.ts.map +1 -0
  256. package/dist/plan/auto.js +70 -0
  257. package/dist/plan/auto.js.map +1 -0
  258. package/dist/plan/budget.d.ts +21 -0
  259. package/dist/plan/budget.d.ts.map +1 -0
  260. package/dist/plan/budget.js +26 -0
  261. package/dist/plan/budget.js.map +1 -0
  262. package/dist/plan/grammar.d.ts +14 -0
  263. package/dist/plan/grammar.d.ts.map +1 -1
  264. package/dist/plan/grammar.js +47 -6
  265. package/dist/plan/grammar.js.map +1 -1
  266. package/dist/plan/lexical.d.ts.map +1 -1
  267. package/dist/plan/lexical.js +1 -7
  268. package/dist/plan/lexical.js.map +1 -1
  269. package/dist/plan/planners.d.ts +35 -2
  270. package/dist/plan/planners.d.ts.map +1 -1
  271. package/dist/plan/planners.js +35 -2
  272. package/dist/plan/planners.js.map +1 -1
  273. package/dist/plan/structural.d.ts +19 -5
  274. package/dist/plan/structural.d.ts.map +1 -1
  275. package/dist/plan/structural.js +180 -33
  276. package/dist/plan/structural.js.map +1 -1
  277. package/dist/repomap/cache.d.ts +48 -0
  278. package/dist/repomap/cache.d.ts.map +1 -1
  279. package/dist/repomap/cache.js +112 -13
  280. package/dist/repomap/cache.js.map +1 -1
  281. package/dist/repomap/io.d.ts +24 -0
  282. package/dist/repomap/io.d.ts.map +1 -0
  283. package/dist/repomap/io.js +34 -0
  284. package/dist/repomap/io.js.map +1 -0
  285. package/dist/repomap/map.d.ts +69 -6
  286. package/dist/repomap/map.d.ts.map +1 -1
  287. package/dist/repomap/map.js +104 -22
  288. package/dist/repomap/map.js.map +1 -1
  289. package/dist/repomap/rank.d.ts +26 -2
  290. package/dist/repomap/rank.d.ts.map +1 -1
  291. package/dist/repomap/rank.js +20 -0
  292. package/dist/repomap/rank.js.map +1 -1
  293. package/dist/repomap/reader.d.ts +73 -0
  294. package/dist/repomap/reader.d.ts.map +1 -0
  295. package/dist/repomap/reader.js +30 -0
  296. package/dist/repomap/reader.js.map +1 -0
  297. package/dist/repomap/tags.d.ts.map +1 -1
  298. package/dist/repomap/tags.js.map +1 -1
  299. package/dist/retrieve.d.ts +8 -0
  300. package/dist/retrieve.d.ts.map +1 -1
  301. package/dist/retrieve.js +12 -1
  302. package/dist/retrieve.js.map +1 -1
  303. package/dist/smelter.d.ts +79 -0
  304. package/dist/smelter.d.ts.map +1 -0
  305. package/dist/smelter.js +65 -0
  306. package/dist/smelter.js.map +1 -0
  307. package/dist/stages.d.ts.map +1 -1
  308. package/dist/stages.js +2 -2
  309. package/dist/stages.js.map +1 -1
  310. package/dist/stats.d.ts +2 -1
  311. package/dist/stats.d.ts.map +1 -1
  312. package/dist/stats.js +2 -1
  313. package/dist/stats.js.map +1 -1
  314. package/dist/store-dir.d.ts +43 -4
  315. package/dist/store-dir.d.ts.map +1 -1
  316. package/dist/store-dir.js +48 -5
  317. package/dist/store-dir.js.map +1 -1
  318. package/dist/store.d.ts +6 -0
  319. package/dist/store.d.ts.map +1 -1
  320. package/dist/store.js +6 -0
  321. package/dist/store.js.map +1 -1
  322. package/dist/text/json-edit.d.ts +71 -0
  323. package/dist/text/json-edit.d.ts.map +1 -0
  324. package/dist/text/json-edit.js +219 -0
  325. package/dist/text/json-edit.js.map +1 -0
  326. package/dist/types.d.ts +35 -14
  327. package/dist/types.d.ts.map +1 -1
  328. package/dist/types.js.map +1 -1
  329. package/grammars/tree-sitter-bash.wasm +0 -0
  330. package/grammars/tree-sitter-c.wasm +0 -0
  331. package/grammars/tree-sitter-c_sharp.wasm +0 -0
  332. package/grammars/tree-sitter-cpp.wasm +0 -0
  333. package/grammars/tree-sitter-go.wasm +0 -0
  334. package/grammars/tree-sitter-java.wasm +0 -0
  335. package/grammars/tree-sitter-javascript.wasm +0 -0
  336. package/grammars/tree-sitter-kotlin.wasm +0 -0
  337. package/grammars/tree-sitter-php.wasm +0 -0
  338. package/grammars/tree-sitter-python.wasm +0 -0
  339. package/grammars/tree-sitter-ruby.wasm +0 -0
  340. package/grammars/tree-sitter-rust.wasm +0 -0
  341. package/grammars/tree-sitter-swift.wasm +0 -0
  342. package/grammars/tree-sitter-tsx.wasm +0 -0
  343. package/grammars/tree-sitter-typescript.wasm +0 -0
  344. package/package.json +11 -11
  345. package/dist/cli/resolve.d.ts +0 -103
  346. package/dist/cli/resolve.d.ts.map +0 -1
  347. package/dist/cli/resolve.js +0 -106
  348. package/dist/cli/resolve.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/cli/shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,gEAAgE;AAChE,MAAM,CAAC,MAAM,QAAQ,GAAG,OAAO,CAAC;AAiChC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,YAAY,CAAC,KAAmB;IAC9C,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;IAC/C,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,KAAK,GAAG,KAAK,CAAC;IAElB,MAAM,QAAQ,GAAG,GAAuB,EAAE;QACxC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,OAAO,KAAK,CAAC,CAAC;YAAE,OAAO,SAAS,CAAC;QACrC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QACvC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,CAAC,IAAI;YACR,SAAS,CAAC;gBACR,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;gBACxB,IAAI,IAAI,KAAK,SAAS;oBAAE,OAAO,IAAI,CAAC;gBACpC,IAAI,KAAK,EAAE,CAAC;oBACV,IAAI,OAAO,KAAK,EAAE;wBAAE,OAAO,SAAS,CAAC;oBACrC,MAAM,IAAI,GAAG,OAAO,CAAC;oBACrB,OAAO,GAAG,EAAE,CAAC;oBACb,OAAO,IAAI,CAAC;gBACd,CAAC;gBACD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnC,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;oBACvB,KAAK,GAAG,IAAI,CAAC;oBACb,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,wCAAwC;oBACrE,SAAS;gBACX,CAAC;gBACD,OAAO;oBACL,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;wBAC5B,CAAC,CAAC,IAAI,CAAC,KAAK;wBACZ,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QACD,KAAK,CAAC,OAAO;YACX,KAAK,GAAG,IAAI,CAAC;YACb,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QAC5B,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,EAAE,EAAE,CAAC;IACL,UAAU,EAAE,CAAC;IACb,KAAK,EAAE,CAAC;IACR,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,CAAC;CACL,CAAC","sourcesContent":["/**\n * The CLI's edge, in one module: the name people type, where its bytes come from and\n * go, and the codes it hands back to the shell.\n *\n * These three used to live in `args.ts` and `run.ts` — the two modules that now read\n * the subcommand registry. A verb file needs all three, so leaving them there would\n * make `args.ts → subcommands/* → args.ts` a cycle, which is exactly the shape that\n * once forced the `--harness` help list to be hand-typed (see\n * `src/harness/registry.ts`). **This file imports nothing**, so every module under\n * `cli/` can read it and nothing has to be written twice to avoid a loop.\n */\n\n/** The command people type. Independent of the package name. */\nexport const CLI_NAME = 'smelt';\n\n/**\n * Where a wizard's answers come from: lines of text arriving over time, and nothing\n * more. `process.stdin` is one, a scripted `Readable.from([...])` in a test is\n * another, an async generator is a third.\n *\n * **Stated structurally, never as `NodeJS.ReadableStream`.** A `.d.ts` that names an\n * ambient namespace only typechecks inside a compilation that pulled `@types/node`\n * into its *global* scope, and a consumer building with `skipLibCheck: false` and a\n * narrowed `types` array (or no `@types/node` at its own root — the ordinary case\n * under pnpm) fails on smelt's declarations rather than on their own code. Naming the\n * node type by import does not help: TypeScript resolves `node:stream` — and bare\n * `stream` — only through the same globally-included `@types/node`. So the published\n * surface describes the shape smelt actually consumes, which needs no node types at\n * all, and {@link answerReader} is the one adapter that reads it.\n * `test/guards/packaging.test.ts` holds the shipped declarations to it.\n */\nexport type AnswerStream = AsyncIterable<string | Uint8Array>;\n\n/**\n * One wizard's answers, one line at a time, plus the release that ends the process.\n *\n * `release` is not housekeeping — it is the difference between a wizard that exits and\n * one the user has to Ctrl-C. See {@link answerReader}.\n */\nexport interface AnswerReader {\n /** The next line, without its terminator, or `undefined` once input has ended. */\n next(): Promise<string | undefined>;\n /** Stop reading and let go of the source, so nothing it owns keeps the loop alive. */\n release(): Promise<void>;\n}\n\n/**\n * Read {@link AnswerStream} as lines — and, when the wizard is done, **let go of it**.\n *\n * This exists because the obvious adapter is a trap. Wrapping the answer stream in\n * `Readable.from(...)` and handing that to `readline` reads the source through *its*\n * async iterator, and closing the readline interface or destroying the wrapper ends\n * only the wrapper: the source is left mid-`next()`, still subscribed, still holding\n * its handle. On the real CLI that source is `process.stdin`, so `smelt init` wrote\n * every file, printed `Done.` and then sat there forever — a hang only visible on an\n * open pipe or a TTY, because EOF happens to end the iteration by itself, and EOF is\n * what every scripted test hands it.\n *\n * So the source's own iterator is held here and nothing else touches it. `release`\n * calls its `return()`, which is the contract an async iterable already has for \"I am\n * finished with you\": `process.stdin`'s destroys the stream and unrefs the handle, an\n * async generator runs its `finally`, and a plain array iterator does nothing at all.\n * Crucially `release` is called between reads, never during one — an iterator awaiting\n * `next()` cannot be returned out of, which is the very state the `Readable.from`\n * wrapper left the source in.\n *\n * Bytes are decoded as UTF-8 across chunk boundaries, so a multi-byte character split\n * across two reads survives; `\\r\\n` and a final line with no terminator both behave as\n * readline did.\n */\nexport function answerReader(input: AnswerStream): AnswerReader {\n const iterator = input[Symbol.asyncIterator]();\n const decoder = new TextDecoder('utf-8');\n let pending = '';\n let ended = false;\n\n const takeLine = (): string | undefined => {\n const newline = pending.indexOf('\\n');\n if (newline === -1) return undefined;\n const line = pending.slice(0, newline);\n pending = pending.slice(newline + 1);\n return line.endsWith('\\r') ? line.slice(0, -1) : line;\n };\n\n return {\n async next(): Promise<string | undefined> {\n for (;;) {\n const line = takeLine();\n if (line !== undefined) return line;\n if (ended) {\n if (pending === '') return undefined;\n const last = pending;\n pending = '';\n return last;\n }\n const step = await iterator.next();\n if (step.done === true) {\n ended = true;\n pending += decoder.decode(); // flush a truncated multi-byte sequence\n continue;\n }\n pending +=\n typeof step.value === 'string'\n ? step.value\n : decoder.decode(step.value, { stream: true });\n }\n },\n async release(): Promise<void> {\n ended = true;\n pending = '';\n await iterator.return?.();\n },\n };\n}\n\n/**\n * Exit codes, and why there are five of them.\n *\n * A CLI that returns 0 whatever happens is the shell-level version of a stub that\n * returns `[]`: the caller cannot tell success from failure, so a pipeline built on it\n * fails silently. **`overBudget` is the load-bearing one.** A plan that did not fit is\n * not an error — smelt refused to cut the regions the caller asked to keep, which is\n * correct — but it is also not success, and a script must be able to see the\n * difference without parsing prose.\n */\nexport const EXIT = {\n ok: 0,\n overBudget: 1,\n usage: 2,\n refused: 3,\n unexpected: 4,\n} as const;\n\n/** Where the CLI's bytes come from and go. Injected so `runCli` is testable in-process. */\nexport interface CliIo {\n readonly stdout: (text: string) => void;\n readonly stderr: (text: string) => void;\n /** All of stdin, as UTF-8. @throws {CliUsageError} when nothing is piped. */\n readonly stdin: () => string;\n /** The package version, for `--version`. */\n readonly version: string;\n /**\n * Where `smelt.config.json` discovery starts, and where `init` writes. Defaults to\n * the process working directory; tests pass a temp directory to stay hermetic.\n */\n readonly cwd?: string;\n /**\n * Interactive input for `smelt init` — the wizard reads answers line by line, which\n * the one-shot `stdin()` above cannot provide. `bin.ts` passes the real stdin\n * stream; tests pass a scripted one. Absent means `init` is a usage error.\n * See {@link AnswerStream} for why the type is structural.\n */\n readonly initInput?: AnswerStream;\n}\n"]}
@@ -0,0 +1,72 @@
1
+ import type { AgentsLintReport } from '../../agents/lint.ts';
2
+ import type { LoadedConfig } from '../config.ts';
3
+ import type { Subcommand } from './subcommand.ts';
4
+ /**
5
+ * `smelt agents` — the instruction files an agent loads on **every** request.
6
+ *
7
+ * The verb exists because `AGENTS.md` is the one blob in a repository that is paid for
8
+ * on every single request, relevant or not, and nothing measures it. That is smelt's
9
+ * own subject, so the fit is exact — and the two actions divide along the line smelt
10
+ * divides everything else along:
11
+ *
12
+ * - **`lint`** measures and explains. It never edits.
13
+ * - **`split`** edits, mechanically, under `smelt init`'s consent discipline — and
14
+ * says plainly which half of the refactor it refused to do.
15
+ *
16
+ * There is no `smelt agents init`, and there will not be one: the guide this verb
17
+ * lints against says in as many words never to auto-generate an AGENTS.md, and a tool
18
+ * that built the thing its own source warns against would be worth less than no tool
19
+ * (ruling R1).
20
+ */
21
+ /** `smelt agents <lint|split> [path]` — parsed. */
22
+ export interface AgentsInvocation {
23
+ readonly mode: 'agents';
24
+ readonly action: 'lint' | 'split';
25
+ /** The directory to read. Defaults to `.`; always a directory, never a file. */
26
+ readonly dir: string;
27
+ /** `lint` only: any finding exits 1. */
28
+ readonly strict: boolean;
29
+ /** `lint` only: the versioned envelope instead of the text report. */
30
+ readonly json: boolean;
31
+ }
32
+ /** Everything one `smelt agents lint` run needs, fully merged. */
33
+ export interface ResolvedAgentsRun {
34
+ readonly action: 'lint' | 'split';
35
+ readonly dir: string;
36
+ readonly strict: boolean;
37
+ readonly json: boolean;
38
+ /**
39
+ * The user's byte ceiling for the merged set, from `smelt.config.json`.
40
+ *
41
+ * **Absent means unbudgeted, and there is no built-in fallback** (ruling R2). This
42
+ * field carries no `budgetSource`, unlike {@link ResolvedRun}'s, precisely because
43
+ * there is only one source it can have come from: a budget here is always the
44
+ * user's, never a flag and never smelt's.
45
+ */
46
+ readonly budgetBytes?: number;
47
+ }
48
+ /**
49
+ * The `smelt agents lint --json` envelope format. Its own version line, like the map
50
+ * and stats envelopes: a lint report carries rule ids and byte counts per level, a
51
+ * structure that has to be able to move without dragging the other two with it.
52
+ */
53
+ export declare const CLI_AGENTS_JSON_FORMAT = "smelt-agents-cli/v1";
54
+ /** What `smelt agents lint --json` prints. */
55
+ export interface CliAgentsJsonEnvelope {
56
+ readonly format: string;
57
+ /** The {@link AgentsLintReport} exactly as `lintAgents` returned it. */
58
+ readonly report: AgentsLintReport;
59
+ }
60
+ export declare const agentsCommand: Subcommand<AgentsInvocation, ResolvedAgentsRun>;
61
+ /**
62
+ * Merge one `'agents'` invocation with the loaded config.
63
+ *
64
+ * The whole merge is one key, and it has **no flag** on purpose. A `--budget` here
65
+ * would be a ceiling typed on the command line, which is a ceiling nobody in the
66
+ * repository has agreed to; the number that matters is the one committed in
67
+ * `smelt.config.json` where CI and every contributor read the same value. So unlike
68
+ * every other verb with a budget, this one cannot refuse for a missing budget: absent
69
+ * is a legitimate state, and it means "measure, do not fail".
70
+ */
71
+ export declare function resolveAgentsRun(invocation: AgentsInvocation, config: LoadedConfig | undefined): ResolvedAgentsRun;
72
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../../src/cli/subcommands/agents.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAK7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAMjD,OAAO,KAAK,EAAgB,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEhE;;;;;;;;;;;;;;;;GAgBG;AAEH,mDAAmD;AACnD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,gFAAgF;IAChF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,kEAAkE;AAClE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,wBAAwB,CAAC;AAE5D,8CAA8C;AAC9C,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;CACnC;AAED,eAAO,MAAM,aAAa,EAAE,UAAU,CAAC,gBAAgB,EAAE,iBAAiB,CAsFzE,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,YAAY,GAAG,SAAS,GAC/B,iBAAiB,CASnB","sourcesContent":["import process from 'node:process';\n\nimport { lintAgents, overBudgetBytes } from '../../agents/lint.ts';\nimport type { AgentsLintReport } from '../../agents/lint.ts';\nimport { CliUsageError } from '../../errors.ts';\nimport { readTree } from '../../ops/inputs.ts';\nimport { runAgentsSplit } from '../agents.ts';\nimport { CONFIG_FILE_NAME } from '../config.ts';\nimport type { LoadedConfig } from '../config.ts';\nimport { formatAgentsReport } from '../report.ts';\nimport { CLI_NAME, EXIT } from '../shell.ts';\nimport type { CliIo } from '../shell.ts';\n\nimport type { FlagValues } from './flags.ts';\nimport type { ConfigSource, Subcommand } from './subcommand.ts';\n\n/**\n * `smelt agents` — the instruction files an agent loads on **every** request.\n *\n * The verb exists because `AGENTS.md` is the one blob in a repository that is paid for\n * on every single request, relevant or not, and nothing measures it. That is smelt's\n * own subject, so the fit is exact — and the two actions divide along the line smelt\n * divides everything else along:\n *\n * - **`lint`** measures and explains. It never edits.\n * - **`split`** edits, mechanically, under `smelt init`'s consent discipline — and\n * says plainly which half of the refactor it refused to do.\n *\n * There is no `smelt agents init`, and there will not be one: the guide this verb\n * lints against says in as many words never to auto-generate an AGENTS.md, and a tool\n * that built the thing its own source warns against would be worth less than no tool\n * (ruling R1).\n */\n\n/** `smelt agents <lint|split> [path]` — parsed. */\nexport interface AgentsInvocation {\n readonly mode: 'agents';\n readonly action: 'lint' | 'split';\n /** The directory to read. Defaults to `.`; always a directory, never a file. */\n readonly dir: string;\n /** `lint` only: any finding exits 1. */\n readonly strict: boolean;\n /** `lint` only: the versioned envelope instead of the text report. */\n readonly json: boolean;\n}\n\n/** Everything one `smelt agents lint` run needs, fully merged. */\nexport interface ResolvedAgentsRun {\n readonly action: 'lint' | 'split';\n readonly dir: string;\n readonly strict: boolean;\n readonly json: boolean;\n /**\n * The user's byte ceiling for the merged set, from `smelt.config.json`.\n *\n * **Absent means unbudgeted, and there is no built-in fallback** (ruling R2). This\n * field carries no `budgetSource`, unlike {@link ResolvedRun}'s, precisely because\n * there is only one source it can have come from: a budget here is always the\n * user's, never a flag and never smelt's.\n */\n readonly budgetBytes?: number;\n}\n\n/**\n * The `smelt agents lint --json` envelope format. Its own version line, like the map\n * and stats envelopes: a lint report carries rule ids and byte counts per level, a\n * structure that has to be able to move without dragging the other two with it.\n */\nexport const CLI_AGENTS_JSON_FORMAT = 'smelt-agents-cli/v1';\n\n/** What `smelt agents lint --json` prints. */\nexport interface CliAgentsJsonEnvelope {\n readonly format: string;\n /** The {@link AgentsLintReport} exactly as `lintAgents` returned it. */\n readonly report: AgentsLintReport;\n}\n\nexport const agentsCommand: Subcommand<AgentsInvocation, ResolvedAgentsRun> = {\n name: 'agents',\n flags: ['strict', 'json'],\n refusal:\n `agents reads the instruction files in a tree and reports on them — it plans no ` +\n `elisions, stores nothing, and has no budget of its own to take: the only budget ` +\n `it honours is agents.budgetBytes in ${CONFIG_FILE_NAME}, which is yours.`,\n usage: {\n synopsis: ['agents lint [dir] [--strict] [--json]'],\n occasional: ['agents split [dir]'],\n section: {\n heading: 'AGENTS',\n body:\n ` ${CLI_NAME} agents lint audits the instruction files an agent loads on every\\n` +\n ` request — every AGENTS.md, CLAUDE.md and GEMINI.md in the tree, because a\\n` +\n ` nested one merges with the root. A merge runs up the tree and never across\\n` +\n ` it, so it reports bytes per level, the per-request worst case (the heaviest\\n` +\n ` level plus its ancestors — what one agent actually loads) and the whole-tree\\n` +\n ` surface, plus an imperative count labelled a heuristic. Then eight advisory\\n` +\n ` rules: dead-path and dead-link (path-like tokens and links resolved against\\n` +\n ` the real tree — the check nobody else makes, and the reason to run this in\\n` +\n ` CI), forcing-language, structure-dump, generated-boilerplate, language-rule,\\n` +\n ` mirror-drift and restated-at-level. Every finding carries a stable rule id\\n` +\n ` and a sentence citing the guide it applies\\n` +\n ` (aihero.dev/a-complete-guide-to-agents-md). Findings exit 0; --strict makes\\n` +\n ` any finding exit 1. There is no built-in size limit: set agents.budgetBytes\\n` +\n ` in ${CONFIG_FILE_NAME} and exceeding it exits 1, as every other ${CLI_NAME}\\n` +\n ` budget does. The guide's own \"~150-200 instructions\" figure is printed as a\\n` +\n ` citation and compared to nothing.\\n` +\n `\\n` +\n ` ${CLI_NAME} agents split does the MECHANICAL half of the guide's refactor:\\n` +\n ` partition the root file by ## heading into one Markdown file per section\\n` +\n ` under docs/, rewrite the relative links that moved a directory deeper, and\\n` +\n ` leave a link list behind. Same discipline as init and hooks — every file\\n` +\n ` listed, one confirm, an existing file never overwritten without a per-file\\n` +\n ` yes. It does NOT decide which sections are essential: that is a reading of\\n` +\n ` your project, so it needs a model, and ${CLI_NAME} has none by law. Instead it\\n` +\n ` prints the guide's own refactor prompt with your real section headings filled\\n` +\n ` in, for you to hand to your own agent. That seam is the point, not an\\n` +\n ` omission. There is no \\`${CLI_NAME} agents init\\`: the guide says never to\\n` +\n ` auto-generate an AGENTS.md.`,\n },\n },\n\n /**\n * An action, and at most one directory. `lint` defaults to `.` because the everyday\n * invocation is \"lint here\" — unlike `map`, which is always aimed somewhere.\n */\n parse(values: FlagValues, positionals: readonly string[]): AgentsInvocation {\n const action = positionals[1];\n if (action !== 'lint' && action !== 'split') {\n throw new CliUsageError(\n `${CLI_NAME}: agents needs an action — lint or split.\\n` +\n ` ${CLI_NAME} agents lint [dir] [--strict] [--json]\\n` +\n ` ${CLI_NAME} agents split [dir]`,\n );\n }\n if (positionals.length > 3) {\n throw new CliUsageError(\n `${CLI_NAME}: agents ${action} takes at most one directory, got ` +\n `${String(positionals.length - 2)} (${positionals.slice(2).join(', ')}).`,\n );\n }\n if (action === 'split' && (values.strict === true || values.json === true)) {\n throw new CliUsageError(\n `${CLI_NAME}: --strict and --json belong to \\`${CLI_NAME} agents lint\\`. ` +\n `split is interactive and writes files; a report format and a CI exit code ` +\n `have nothing to act on there.`,\n );\n }\n return {\n mode: 'agents',\n action,\n dir: positionals[2] ?? '.',\n strict: values.strict === true,\n json: values.json === true,\n };\n },\n\n resolve(invocation: AgentsInvocation, config: ConfigSource): ResolvedAgentsRun {\n return resolveAgentsRun(invocation, config());\n },\n\n run(resolved: ResolvedAgentsRun, io: CliIo): number | Promise<number> {\n return resolved.action === 'lint' ? runLint(resolved, io) : runSplit(resolved, io);\n },\n};\n\n/**\n * Merge one `'agents'` invocation with the loaded config.\n *\n * The whole merge is one key, and it has **no flag** on purpose. A `--budget` here\n * would be a ceiling typed on the command line, which is a ceiling nobody in the\n * repository has agreed to; the number that matters is the one committed in\n * `smelt.config.json` where CI and every contributor read the same value. So unlike\n * every other verb with a budget, this one cannot refuse for a missing budget: absent\n * is a legitimate state, and it means \"measure, do not fail\".\n */\nexport function resolveAgentsRun(\n invocation: AgentsInvocation,\n config: LoadedConfig | undefined,\n): ResolvedAgentsRun {\n const budgetBytes = config?.config.agents?.budgetBytes;\n return {\n action: invocation.action,\n dir: invocation.dir,\n strict: invocation.strict,\n json: invocation.json,\n ...(budgetBytes === undefined ? {} : { budgetBytes }),\n };\n}\n\n/**\n * One `smelt agents lint` run: prove the target is a tree, lint it, render, exit.\n *\n * Three exit codes, and the reasoning for each:\n *\n * - **1 over the user's budget.** Identical to a `smelt` run that did not fit, and\n * identical for the same reason: not an error, not a success, and a script must be\n * able to tell without parsing prose. It applies with or without `--strict`,\n * because the number came from the repository rather than from smelt.\n * - **1 with `--strict` and any finding.** The CI switch. Off by default because the\n * rules are advisory heuristics.\n * - **0** otherwise, findings and all.\n */\nfunction runLint(run: ResolvedAgentsRun, io: CliIo): number {\n const tree = readTree(run.dir, run.dir, {\n tree: 'agents lint',\n file: `\\`${CLI_NAME} <file>\\``,\n });\n if (!tree.ok) throw new CliUsageError(`${CLI_NAME}: ${tree.refusal}`);\n\n const report = lintAgents({\n root: tree.value,\n ...(run.budgetBytes === undefined ? {} : { budgetBytes: run.budgetBytes }),\n });\n\n if (run.json) {\n const envelope: CliAgentsJsonEnvelope = { format: CLI_AGENTS_JSON_FORMAT, report };\n io.stdout(`${JSON.stringify(envelope, null, 2)}\\n`);\n } else {\n io.stdout(formatAgentsReport(report, { source: run.dir, strict: run.strict }));\n }\n\n if (overBudgetBytes(report) !== undefined) return EXIT.overBudget;\n if (run.strict && report.findings.length > 0) return EXIT.overBudget;\n return EXIT.ok;\n}\n\n/** `smelt agents split` is interactive like `init` and `hooks`, and refuses without a stream. */\nasync function runSplit(run: ResolvedAgentsRun, io: CliIo): Promise<number> {\n if (io.initInput === undefined) {\n throw new CliUsageError(\n `${CLI_NAME}: agents split is interactive — it lists every file it would write ` +\n `and asks before each one — and this invocation has no interactive input ` +\n `stream. Run \\`${CLI_NAME} agents split\\` from a terminal.`,\n );\n }\n return await runAgentsSplit({\n input: io.initInput,\n output: io.stdout,\n cwd: io.cwd ?? process.cwd(),\n dir: run.dir,\n });\n}\n"]}
@@ -0,0 +1,162 @@
1
+ import process from 'node:process';
2
+ import { lintAgents, overBudgetBytes } from '../../agents/lint.js';
3
+ import { CliUsageError } from '../../errors.js';
4
+ import { readTree } from '../../ops/inputs.js';
5
+ import { runAgentsSplit } from '../agents.js';
6
+ import { CONFIG_FILE_NAME } from '../config.js';
7
+ import { formatAgentsReport } from '../report.js';
8
+ import { CLI_NAME, EXIT } from '../shell.js';
9
+ /**
10
+ * The `smelt agents lint --json` envelope format. Its own version line, like the map
11
+ * and stats envelopes: a lint report carries rule ids and byte counts per level, a
12
+ * structure that has to be able to move without dragging the other two with it.
13
+ */
14
+ export const CLI_AGENTS_JSON_FORMAT = 'smelt-agents-cli/v1';
15
+ export const agentsCommand = {
16
+ name: 'agents',
17
+ flags: ['strict', 'json'],
18
+ refusal: `agents reads the instruction files in a tree and reports on them — it plans no ` +
19
+ `elisions, stores nothing, and has no budget of its own to take: the only budget ` +
20
+ `it honours is agents.budgetBytes in ${CONFIG_FILE_NAME}, which is yours.`,
21
+ usage: {
22
+ synopsis: ['agents lint [dir] [--strict] [--json]'],
23
+ occasional: ['agents split [dir]'],
24
+ section: {
25
+ heading: 'AGENTS',
26
+ body: ` ${CLI_NAME} agents lint audits the instruction files an agent loads on every\n` +
27
+ ` request — every AGENTS.md, CLAUDE.md and GEMINI.md in the tree, because a\n` +
28
+ ` nested one merges with the root. A merge runs up the tree and never across\n` +
29
+ ` it, so it reports bytes per level, the per-request worst case (the heaviest\n` +
30
+ ` level plus its ancestors — what one agent actually loads) and the whole-tree\n` +
31
+ ` surface, plus an imperative count labelled a heuristic. Then eight advisory\n` +
32
+ ` rules: dead-path and dead-link (path-like tokens and links resolved against\n` +
33
+ ` the real tree — the check nobody else makes, and the reason to run this in\n` +
34
+ ` CI), forcing-language, structure-dump, generated-boilerplate, language-rule,\n` +
35
+ ` mirror-drift and restated-at-level. Every finding carries a stable rule id\n` +
36
+ ` and a sentence citing the guide it applies\n` +
37
+ ` (aihero.dev/a-complete-guide-to-agents-md). Findings exit 0; --strict makes\n` +
38
+ ` any finding exit 1. There is no built-in size limit: set agents.budgetBytes\n` +
39
+ ` in ${CONFIG_FILE_NAME} and exceeding it exits 1, as every other ${CLI_NAME}\n` +
40
+ ` budget does. The guide's own "~150-200 instructions" figure is printed as a\n` +
41
+ ` citation and compared to nothing.\n` +
42
+ `\n` +
43
+ ` ${CLI_NAME} agents split does the MECHANICAL half of the guide's refactor:\n` +
44
+ ` partition the root file by ## heading into one Markdown file per section\n` +
45
+ ` under docs/, rewrite the relative links that moved a directory deeper, and\n` +
46
+ ` leave a link list behind. Same discipline as init and hooks — every file\n` +
47
+ ` listed, one confirm, an existing file never overwritten without a per-file\n` +
48
+ ` yes. It does NOT decide which sections are essential: that is a reading of\n` +
49
+ ` your project, so it needs a model, and ${CLI_NAME} has none by law. Instead it\n` +
50
+ ` prints the guide's own refactor prompt with your real section headings filled\n` +
51
+ ` in, for you to hand to your own agent. That seam is the point, not an\n` +
52
+ ` omission. There is no \`${CLI_NAME} agents init\`: the guide says never to\n` +
53
+ ` auto-generate an AGENTS.md.`,
54
+ },
55
+ },
56
+ /**
57
+ * An action, and at most one directory. `lint` defaults to `.` because the everyday
58
+ * invocation is "lint here" — unlike `map`, which is always aimed somewhere.
59
+ */
60
+ parse(values, positionals) {
61
+ const action = positionals[1];
62
+ if (action !== 'lint' && action !== 'split') {
63
+ throw new CliUsageError(`${CLI_NAME}: agents needs an action — lint or split.\n` +
64
+ ` ${CLI_NAME} agents lint [dir] [--strict] [--json]\n` +
65
+ ` ${CLI_NAME} agents split [dir]`);
66
+ }
67
+ if (positionals.length > 3) {
68
+ throw new CliUsageError(`${CLI_NAME}: agents ${action} takes at most one directory, got ` +
69
+ `${String(positionals.length - 2)} (${positionals.slice(2).join(', ')}).`);
70
+ }
71
+ if (action === 'split' && (values.strict === true || values.json === true)) {
72
+ throw new CliUsageError(`${CLI_NAME}: --strict and --json belong to \`${CLI_NAME} agents lint\`. ` +
73
+ `split is interactive and writes files; a report format and a CI exit code ` +
74
+ `have nothing to act on there.`);
75
+ }
76
+ return {
77
+ mode: 'agents',
78
+ action,
79
+ dir: positionals[2] ?? '.',
80
+ strict: values.strict === true,
81
+ json: values.json === true,
82
+ };
83
+ },
84
+ resolve(invocation, config) {
85
+ return resolveAgentsRun(invocation, config());
86
+ },
87
+ run(resolved, io) {
88
+ return resolved.action === 'lint' ? runLint(resolved, io) : runSplit(resolved, io);
89
+ },
90
+ };
91
+ /**
92
+ * Merge one `'agents'` invocation with the loaded config.
93
+ *
94
+ * The whole merge is one key, and it has **no flag** on purpose. A `--budget` here
95
+ * would be a ceiling typed on the command line, which is a ceiling nobody in the
96
+ * repository has agreed to; the number that matters is the one committed in
97
+ * `smelt.config.json` where CI and every contributor read the same value. So unlike
98
+ * every other verb with a budget, this one cannot refuse for a missing budget: absent
99
+ * is a legitimate state, and it means "measure, do not fail".
100
+ */
101
+ export function resolveAgentsRun(invocation, config) {
102
+ const budgetBytes = config?.config.agents?.budgetBytes;
103
+ return {
104
+ action: invocation.action,
105
+ dir: invocation.dir,
106
+ strict: invocation.strict,
107
+ json: invocation.json,
108
+ ...(budgetBytes === undefined ? {} : { budgetBytes }),
109
+ };
110
+ }
111
+ /**
112
+ * One `smelt agents lint` run: prove the target is a tree, lint it, render, exit.
113
+ *
114
+ * Three exit codes, and the reasoning for each:
115
+ *
116
+ * - **1 over the user's budget.** Identical to a `smelt` run that did not fit, and
117
+ * identical for the same reason: not an error, not a success, and a script must be
118
+ * able to tell without parsing prose. It applies with or without `--strict`,
119
+ * because the number came from the repository rather than from smelt.
120
+ * - **1 with `--strict` and any finding.** The CI switch. Off by default because the
121
+ * rules are advisory heuristics.
122
+ * - **0** otherwise, findings and all.
123
+ */
124
+ function runLint(run, io) {
125
+ const tree = readTree(run.dir, run.dir, {
126
+ tree: 'agents lint',
127
+ file: `\`${CLI_NAME} <file>\``,
128
+ });
129
+ if (!tree.ok)
130
+ throw new CliUsageError(`${CLI_NAME}: ${tree.refusal}`);
131
+ const report = lintAgents({
132
+ root: tree.value,
133
+ ...(run.budgetBytes === undefined ? {} : { budgetBytes: run.budgetBytes }),
134
+ });
135
+ if (run.json) {
136
+ const envelope = { format: CLI_AGENTS_JSON_FORMAT, report };
137
+ io.stdout(`${JSON.stringify(envelope, null, 2)}\n`);
138
+ }
139
+ else {
140
+ io.stdout(formatAgentsReport(report, { source: run.dir, strict: run.strict }));
141
+ }
142
+ if (overBudgetBytes(report) !== undefined)
143
+ return EXIT.overBudget;
144
+ if (run.strict && report.findings.length > 0)
145
+ return EXIT.overBudget;
146
+ return EXIT.ok;
147
+ }
148
+ /** `smelt agents split` is interactive like `init` and `hooks`, and refuses without a stream. */
149
+ async function runSplit(run, io) {
150
+ if (io.initInput === undefined) {
151
+ throw new CliUsageError(`${CLI_NAME}: agents split is interactive — it lists every file it would write ` +
152
+ `and asks before each one — and this invocation has no interactive input ` +
153
+ `stream. Run \`${CLI_NAME} agents split\` from a terminal.`);
154
+ }
155
+ return await runAgentsSplit({
156
+ input: io.initInput,
157
+ output: io.stdout,
158
+ cwd: io.cwd ?? process.cwd(),
159
+ dir: run.dir,
160
+ });
161
+ }
162
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.js","sourceRoot":"","sources":["../../../src/cli/subcommands/agents.ts"],"names":[],"mappings":"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","sourcesContent":["import process from 'node:process';\n\nimport { lintAgents, overBudgetBytes } from '../../agents/lint.ts';\nimport type { AgentsLintReport } from '../../agents/lint.ts';\nimport { CliUsageError } from '../../errors.ts';\nimport { readTree } from '../../ops/inputs.ts';\nimport { runAgentsSplit } from '../agents.ts';\nimport { CONFIG_FILE_NAME } from '../config.ts';\nimport type { LoadedConfig } from '../config.ts';\nimport { formatAgentsReport } from '../report.ts';\nimport { CLI_NAME, EXIT } from '../shell.ts';\nimport type { CliIo } from '../shell.ts';\n\nimport type { FlagValues } from './flags.ts';\nimport type { ConfigSource, Subcommand } from './subcommand.ts';\n\n/**\n * `smelt agents` — the instruction files an agent loads on **every** request.\n *\n * The verb exists because `AGENTS.md` is the one blob in a repository that is paid for\n * on every single request, relevant or not, and nothing measures it. That is smelt's\n * own subject, so the fit is exact — and the two actions divide along the line smelt\n * divides everything else along:\n *\n * - **`lint`** measures and explains. It never edits.\n * - **`split`** edits, mechanically, under `smelt init`'s consent discipline — and\n * says plainly which half of the refactor it refused to do.\n *\n * There is no `smelt agents init`, and there will not be one: the guide this verb\n * lints against says in as many words never to auto-generate an AGENTS.md, and a tool\n * that built the thing its own source warns against would be worth less than no tool\n * (ruling R1).\n */\n\n/** `smelt agents <lint|split> [path]` — parsed. */\nexport interface AgentsInvocation {\n readonly mode: 'agents';\n readonly action: 'lint' | 'split';\n /** The directory to read. Defaults to `.`; always a directory, never a file. */\n readonly dir: string;\n /** `lint` only: any finding exits 1. */\n readonly strict: boolean;\n /** `lint` only: the versioned envelope instead of the text report. */\n readonly json: boolean;\n}\n\n/** Everything one `smelt agents lint` run needs, fully merged. */\nexport interface ResolvedAgentsRun {\n readonly action: 'lint' | 'split';\n readonly dir: string;\n readonly strict: boolean;\n readonly json: boolean;\n /**\n * The user's byte ceiling for the merged set, from `smelt.config.json`.\n *\n * **Absent means unbudgeted, and there is no built-in fallback** (ruling R2). This\n * field carries no `budgetSource`, unlike {@link ResolvedRun}'s, precisely because\n * there is only one source it can have come from: a budget here is always the\n * user's, never a flag and never smelt's.\n */\n readonly budgetBytes?: number;\n}\n\n/**\n * The `smelt agents lint --json` envelope format. Its own version line, like the map\n * and stats envelopes: a lint report carries rule ids and byte counts per level, a\n * structure that has to be able to move without dragging the other two with it.\n */\nexport const CLI_AGENTS_JSON_FORMAT = 'smelt-agents-cli/v1';\n\n/** What `smelt agents lint --json` prints. */\nexport interface CliAgentsJsonEnvelope {\n readonly format: string;\n /** The {@link AgentsLintReport} exactly as `lintAgents` returned it. */\n readonly report: AgentsLintReport;\n}\n\nexport const agentsCommand: Subcommand<AgentsInvocation, ResolvedAgentsRun> = {\n name: 'agents',\n flags: ['strict', 'json'],\n refusal:\n `agents reads the instruction files in a tree and reports on them — it plans no ` +\n `elisions, stores nothing, and has no budget of its own to take: the only budget ` +\n `it honours is agents.budgetBytes in ${CONFIG_FILE_NAME}, which is yours.`,\n usage: {\n synopsis: ['agents lint [dir] [--strict] [--json]'],\n occasional: ['agents split [dir]'],\n section: {\n heading: 'AGENTS',\n body:\n ` ${CLI_NAME} agents lint audits the instruction files an agent loads on every\\n` +\n ` request — every AGENTS.md, CLAUDE.md and GEMINI.md in the tree, because a\\n` +\n ` nested one merges with the root. A merge runs up the tree and never across\\n` +\n ` it, so it reports bytes per level, the per-request worst case (the heaviest\\n` +\n ` level plus its ancestors — what one agent actually loads) and the whole-tree\\n` +\n ` surface, plus an imperative count labelled a heuristic. Then eight advisory\\n` +\n ` rules: dead-path and dead-link (path-like tokens and links resolved against\\n` +\n ` the real tree — the check nobody else makes, and the reason to run this in\\n` +\n ` CI), forcing-language, structure-dump, generated-boilerplate, language-rule,\\n` +\n ` mirror-drift and restated-at-level. Every finding carries a stable rule id\\n` +\n ` and a sentence citing the guide it applies\\n` +\n ` (aihero.dev/a-complete-guide-to-agents-md). Findings exit 0; --strict makes\\n` +\n ` any finding exit 1. There is no built-in size limit: set agents.budgetBytes\\n` +\n ` in ${CONFIG_FILE_NAME} and exceeding it exits 1, as every other ${CLI_NAME}\\n` +\n ` budget does. The guide's own \"~150-200 instructions\" figure is printed as a\\n` +\n ` citation and compared to nothing.\\n` +\n `\\n` +\n ` ${CLI_NAME} agents split does the MECHANICAL half of the guide's refactor:\\n` +\n ` partition the root file by ## heading into one Markdown file per section\\n` +\n ` under docs/, rewrite the relative links that moved a directory deeper, and\\n` +\n ` leave a link list behind. Same discipline as init and hooks — every file\\n` +\n ` listed, one confirm, an existing file never overwritten without a per-file\\n` +\n ` yes. It does NOT decide which sections are essential: that is a reading of\\n` +\n ` your project, so it needs a model, and ${CLI_NAME} has none by law. Instead it\\n` +\n ` prints the guide's own refactor prompt with your real section headings filled\\n` +\n ` in, for you to hand to your own agent. That seam is the point, not an\\n` +\n ` omission. There is no \\`${CLI_NAME} agents init\\`: the guide says never to\\n` +\n ` auto-generate an AGENTS.md.`,\n },\n },\n\n /**\n * An action, and at most one directory. `lint` defaults to `.` because the everyday\n * invocation is \"lint here\" — unlike `map`, which is always aimed somewhere.\n */\n parse(values: FlagValues, positionals: readonly string[]): AgentsInvocation {\n const action = positionals[1];\n if (action !== 'lint' && action !== 'split') {\n throw new CliUsageError(\n `${CLI_NAME}: agents needs an action — lint or split.\\n` +\n ` ${CLI_NAME} agents lint [dir] [--strict] [--json]\\n` +\n ` ${CLI_NAME} agents split [dir]`,\n );\n }\n if (positionals.length > 3) {\n throw new CliUsageError(\n `${CLI_NAME}: agents ${action} takes at most one directory, got ` +\n `${String(positionals.length - 2)} (${positionals.slice(2).join(', ')}).`,\n );\n }\n if (action === 'split' && (values.strict === true || values.json === true)) {\n throw new CliUsageError(\n `${CLI_NAME}: --strict and --json belong to \\`${CLI_NAME} agents lint\\`. ` +\n `split is interactive and writes files; a report format and a CI exit code ` +\n `have nothing to act on there.`,\n );\n }\n return {\n mode: 'agents',\n action,\n dir: positionals[2] ?? '.',\n strict: values.strict === true,\n json: values.json === true,\n };\n },\n\n resolve(invocation: AgentsInvocation, config: ConfigSource): ResolvedAgentsRun {\n return resolveAgentsRun(invocation, config());\n },\n\n run(resolved: ResolvedAgentsRun, io: CliIo): number | Promise<number> {\n return resolved.action === 'lint' ? runLint(resolved, io) : runSplit(resolved, io);\n },\n};\n\n/**\n * Merge one `'agents'` invocation with the loaded config.\n *\n * The whole merge is one key, and it has **no flag** on purpose. A `--budget` here\n * would be a ceiling typed on the command line, which is a ceiling nobody in the\n * repository has agreed to; the number that matters is the one committed in\n * `smelt.config.json` where CI and every contributor read the same value. So unlike\n * every other verb with a budget, this one cannot refuse for a missing budget: absent\n * is a legitimate state, and it means \"measure, do not fail\".\n */\nexport function resolveAgentsRun(\n invocation: AgentsInvocation,\n config: LoadedConfig | undefined,\n): ResolvedAgentsRun {\n const budgetBytes = config?.config.agents?.budgetBytes;\n return {\n action: invocation.action,\n dir: invocation.dir,\n strict: invocation.strict,\n json: invocation.json,\n ...(budgetBytes === undefined ? {} : { budgetBytes }),\n };\n}\n\n/**\n * One `smelt agents lint` run: prove the target is a tree, lint it, render, exit.\n *\n * Three exit codes, and the reasoning for each:\n *\n * - **1 over the user's budget.** Identical to a `smelt` run that did not fit, and\n * identical for the same reason: not an error, not a success, and a script must be\n * able to tell without parsing prose. It applies with or without `--strict`,\n * because the number came from the repository rather than from smelt.\n * - **1 with `--strict` and any finding.** The CI switch. Off by default because the\n * rules are advisory heuristics.\n * - **0** otherwise, findings and all.\n */\nfunction runLint(run: ResolvedAgentsRun, io: CliIo): number {\n const tree = readTree(run.dir, run.dir, {\n tree: 'agents lint',\n file: `\\`${CLI_NAME} <file>\\``,\n });\n if (!tree.ok) throw new CliUsageError(`${CLI_NAME}: ${tree.refusal}`);\n\n const report = lintAgents({\n root: tree.value,\n ...(run.budgetBytes === undefined ? {} : { budgetBytes: run.budgetBytes }),\n });\n\n if (run.json) {\n const envelope: CliAgentsJsonEnvelope = { format: CLI_AGENTS_JSON_FORMAT, report };\n io.stdout(`${JSON.stringify(envelope, null, 2)}\\n`);\n } else {\n io.stdout(formatAgentsReport(report, { source: run.dir, strict: run.strict }));\n }\n\n if (overBudgetBytes(report) !== undefined) return EXIT.overBudget;\n if (run.strict && report.findings.length > 0) return EXIT.overBudget;\n return EXIT.ok;\n}\n\n/** `smelt agents split` is interactive like `init` and `hooks`, and refuses without a stream. */\nasync function runSplit(run: ResolvedAgentsRun, io: CliIo): Promise<number> {\n if (io.initInput === undefined) {\n throw new CliUsageError(\n `${CLI_NAME}: agents split is interactive — it lists every file it would write ` +\n `and asks before each one — and this invocation has no interactive input ` +\n `stream. Run \\`${CLI_NAME} agents split\\` from a terminal.`,\n );\n }\n return await runAgentsSplit({\n input: io.initInput,\n output: io.stdout,\n cwd: io.cwd ?? process.cwd(),\n dir: run.dir,\n });\n}\n"]}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Every flag the CLI accepts, once.
3
+ *
4
+ * The table is the companion the {@link Subcommand} registry needs: a verb declares
5
+ * the flags it owns as `FlagName[]`, so the type of that list — and therefore whether
6
+ * a verb can claim a flag that does not exist — comes from here. It carries three
7
+ * things that used to be written in three places:
8
+ *
9
+ * 1. **How `node:util.parseArgs` reads the flag.** The option table used to be a
10
+ * literal inside `parseSmeltArgs`.
11
+ * 2. **The name.** `FlagName` is `keyof typeof CLI_FLAGS`, so an eleventh flag is
12
+ * spelled once and every verb's ownership list typechecks against it.
13
+ * 3. **Its OPTIONS entry in `--help`.** Including the `map only.` / `hooks only.`
14
+ * prefix, which is *not* stored here: it is generated from the registry's
15
+ * ownership (see `renderOptions` in `cli/usage.ts`), because "which verb owns
16
+ * this flag" is a fact the registry already holds and prose that restates it is
17
+ * prose that can go stale.
18
+ *
19
+ * Key order is meaningful: it is the order the OPTIONS block renders and the order a
20
+ * refusal lists flags in, so keep it stable and append new flags where they read best.
21
+ */
22
+ export declare const CLI_FLAGS: {
23
+ readonly budget: {
24
+ readonly type: 'string';
25
+ };
26
+ readonly focus: {
27
+ readonly type: 'string';
28
+ readonly multiple: true;
29
+ };
30
+ readonly language: {
31
+ readonly type: 'string';
32
+ };
33
+ readonly strategy: {
34
+ readonly type: 'string';
35
+ };
36
+ readonly ignore: {
37
+ readonly type: 'string';
38
+ readonly multiple: true;
39
+ };
40
+ readonly cache: {
41
+ readonly type: 'string';
42
+ };
43
+ readonly harness: {
44
+ readonly type: 'string';
45
+ };
46
+ readonly strict: {
47
+ readonly type: 'boolean';
48
+ };
49
+ readonly json: {
50
+ readonly type: 'boolean';
51
+ };
52
+ readonly reconstruct: {
53
+ readonly type: 'boolean';
54
+ };
55
+ readonly help: {
56
+ readonly type: 'boolean';
57
+ readonly short: 'h';
58
+ };
59
+ readonly version: {
60
+ readonly type: 'boolean';
61
+ };
62
+ };
63
+ /** Every flag name, as a type. A verb cannot claim a flag that is not in the table. */
64
+ export type FlagName = keyof typeof CLI_FLAGS;
65
+ /**
66
+ * The flags answered *before* any verb, so no verb owns them and no verb may refuse
67
+ * them: `smelt map --help` prints the help, exactly as it always has. Every other flag
68
+ * belongs to at least one verb — `test/guards/subcommand-registry.test.ts` pins that.
69
+ */
70
+ export declare const GLOBAL_FLAGS: readonly ["help", "version"];
71
+ /** A flag a verb can own — everything but the two global ones. */
72
+ export type VerbFlag = Exclude<FlagName, (typeof GLOBAL_FLAGS)[number]>;
73
+ /** Every ownable flag, in table order: the list a refusal checks a verb against. */
74
+ export declare const VERB_FLAGS: readonly VerbFlag[];
75
+ /**
76
+ * `--a`, `--a and --b`, `--a, --b and --c` — in flag-table order, always.
77
+ *
78
+ * Every refusal that names more than one flag spells the list this way: the
79
+ * ownership refusal in `./registry.ts`, and the default verb's `--reconstruct`
80
+ * refusal in `./smelt.ts`. It lives with the table because the *order* is the
81
+ * table's — a refusal that listed flags in the order the user happened to type them
82
+ * would read differently every time, and two refusals disagreeing about how to spell
83
+ * the same pair is the kind of drift this file exists to remove.
84
+ */
85
+ export declare function flagList(flags: readonly VerbFlag[]): string;
86
+ /** What one flag's parsed value looks like, derived from how `parseArgs` was told to read it. */
87
+ type FlagValue<F> = F extends {
88
+ readonly type: 'boolean';
89
+ } ? boolean : F extends {
90
+ readonly multiple: true;
91
+ } ? readonly string[] : string;
92
+ /**
93
+ * The parsed flags, as every `Subcommand.parse` sees them: one optional field per
94
+ * flag, typed by the table above. Absent means the user did not type it — which is
95
+ * the only thing a refusal needs to know, and the only thing a verb may act on.
96
+ */
97
+ export type FlagValues = {
98
+ readonly [K in FlagName]?: FlagValue<(typeof CLI_FLAGS)[K]>;
99
+ };
100
+ /** How one flag appears in the OPTIONS block of `--help`. */
101
+ export interface FlagHelp {
102
+ /** The left column, e.g. `--budget <bytes>` or `-h, --help`. */
103
+ readonly label: string;
104
+ /**
105
+ * The description, already wrapped to the OPTIONS column — a function because three
106
+ * entries render a registry (`SUPPORTED_LANGUAGES`, `STRATEGIES` +
107
+ * `STRUCTURAL_LANGUAGES`, `HARNESS_IDS`) rather than a hand-typed list. The
108
+ * ownership prefix is not here; it is generated.
109
+ */
110
+ body(): readonly string[];
111
+ }
112
+ /**
113
+ * A comma-separated list under an OPTIONS entry's hanging indent, wrapped where the
114
+ * hand-typed version wrapped. `--strategy` and `--language` render their registries on
115
+ * one line because they fit; the harness ids do not, and a list long enough to wrap is
116
+ * exactly the list nobody keeps in sync by hand.
117
+ */
118
+ export declare function optionList(items: readonly string[], width: number): readonly string[];
119
+ /**
120
+ * The OPTIONS entry for every flag. `Record<FlagName, FlagHelp>` on purpose: a flag
121
+ * added to {@link CLI_FLAGS} without a help entry is a compile error, so the help text
122
+ * cannot fall behind what the parser accepts.
123
+ */
124
+ export declare const FLAG_HELP: Readonly<Record<FlagName, FlagHelp>>;
125
+ /**
126
+ * `--budget` has no built-in default, for the same reason `smelt()` has none: a budget
127
+ * smelt invented would be smelt deciding how much of the caller's context to throw
128
+ * away, silently, at a number nobody chose. A *missing* flag is not an error here,
129
+ * though — `smelt.config.json` may carry a `defaultBudgetBytes` the user chose
130
+ * explicitly, and the verb's `resolve` errors only when neither exists. A malformed
131
+ * value is always an error.
132
+ *
133
+ * It lives with the flag rather than with a verb because two verbs own `--budget`, and
134
+ * the two of them agreeing on what "4kb" means is not something to leave to chance.
135
+ *
136
+ * The *lexing* is the CLI's own and stays here — argv carries strings, so `4kb` and a
137
+ * leading `-` are answered by a digits-only test before anything numeric happens. The
138
+ * *rule* and *the sentence that refuses it* come from `ops/inputs.ts`, which is also
139
+ * where the `smelt_file` tool gets them: two surfaces, one law, spelled `--budget`
140
+ * here and `"budgetBytes"` there.
141
+ */
142
+ export declare function parseBudget(raw: string | undefined): number | undefined;
143
+ export {};
144
+ //# sourceMappingURL=flags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flags.d.ts","sourceRoot":"","sources":["../../../src/cli/subcommands/flags.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,SAAS;aACpB,MAAM;iBAAI,IAAI,EAAE,QAAQ;;aACxB,KAAK;iBAAI,IAAI,EAAE,QAAQ;iBAAE,QAAQ;;aACjC,QAAQ;iBAAI,IAAI,EAAE,QAAQ;;aAC1B,QAAQ;iBAAI,IAAI,EAAE,QAAQ;;aAC1B,MAAM;iBAAI,IAAI,EAAE,QAAQ;iBAAE,QAAQ;;aAClC,KAAK;iBAAI,IAAI,EAAE,QAAQ;;aACvB,OAAO;iBAAI,IAAI,EAAE,QAAQ;;aACzB,MAAM;iBAAI,IAAI,EAAE,SAAS;;aACzB,IAAI;iBAAI,IAAI,EAAE,SAAS;;aACvB,WAAW;iBAAI,IAAI,EAAE,SAAS;;aAC9B,IAAI;iBAAI,IAAI,EAAE,SAAS;iBAAE,KAAK,EAAE,GAAG;;aACnC,OAAO;iBAAI,IAAI,EAAE,SAAS;;CAClB,CAAC;AAEX,uFAAuF;AACvF,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,SAAS,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,YAAY,8BAA6D,CAAC;AAEvF,kEAAkE;AAClE,MAAM,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAExE,oFAAoF;AACpF,eAAO,MAAM,UAAU,EAAE,SAAS,QAAQ,EAEzC,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,MAAM,CAI3D;AAED,iGAAiG;AACjG,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACtD,OAAO,GACP,CAAC,SAAS;IAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,GACnC,SAAS,MAAM,EAAE,GACjB,MAAM,CAAC;AAEb;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,EAAE,CAAC,IAAI,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC;AAEzF,6DAA6D;AAC7D,MAAM,WAAW,QAAQ;IACvB,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,IAAI,IAAI,SAAS,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAerF;AAKD;;;;GAIG;AACH,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAyF1D,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAOvE","sourcesContent":["import { SUPPORTED_LANGUAGES } from '../../detect.ts';\nimport { CliUsageError } from '../../errors.ts';\nimport { HARNESS_IDS } from '../../harness/registry.ts';\nimport { budgetFault, budgetMalformed } from '../../ops/inputs.ts';\nimport type { BudgetFault } from '../../ops/inputs.ts';\nimport { STRATEGIES, DEFAULT_STRATEGY } from '../../plan/planners.ts';\nimport { STRUCTURAL_LANGUAGES } from '../../plan/structural.ts';\nimport { DEFAULT_REPO_IGNORE } from '../../repomap/map.ts';\nimport { CLI_NAME } from '../shell.ts';\n\n/**\n * Every flag the CLI accepts, once.\n *\n * The table is the companion the {@link Subcommand} registry needs: a verb declares\n * the flags it owns as `FlagName[]`, so the type of that list — and therefore whether\n * a verb can claim a flag that does not exist — comes from here. It carries three\n * things that used to be written in three places:\n *\n * 1. **How `node:util.parseArgs` reads the flag.** The option table used to be a\n * literal inside `parseSmeltArgs`.\n * 2. **The name.** `FlagName` is `keyof typeof CLI_FLAGS`, so an eleventh flag is\n * spelled once and every verb's ownership list typechecks against it.\n * 3. **Its OPTIONS entry in `--help`.** Including the `map only.` / `hooks only.`\n * prefix, which is *not* stored here: it is generated from the registry's\n * ownership (see `renderOptions` in `cli/usage.ts`), because \"which verb owns\n * this flag\" is a fact the registry already holds and prose that restates it is\n * prose that can go stale.\n *\n * Key order is meaningful: it is the order the OPTIONS block renders and the order a\n * refusal lists flags in, so keep it stable and append new flags where they read best.\n */\nexport const CLI_FLAGS = {\n budget: { type: 'string' },\n focus: { type: 'string', multiple: true },\n language: { type: 'string' },\n strategy: { type: 'string' },\n ignore: { type: 'string', multiple: true },\n cache: { type: 'string' },\n harness: { type: 'string' },\n strict: { type: 'boolean' },\n json: { type: 'boolean' },\n reconstruct: { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean' },\n} as const;\n\n/** Every flag name, as a type. A verb cannot claim a flag that is not in the table. */\nexport type FlagName = keyof typeof CLI_FLAGS;\n\n/**\n * The flags answered *before* any verb, so no verb owns them and no verb may refuse\n * them: `smelt map --help` prints the help, exactly as it always has. Every other flag\n * belongs to at least one verb — `test/guards/subcommand-registry.test.ts` pins that.\n */\nexport const GLOBAL_FLAGS = ['help', 'version'] as const satisfies readonly FlagName[];\n\n/** A flag a verb can own — everything but the two global ones. */\nexport type VerbFlag = Exclude<FlagName, (typeof GLOBAL_FLAGS)[number]>;\n\n/** Every ownable flag, in table order: the list a refusal checks a verb against. */\nexport const VERB_FLAGS: readonly VerbFlag[] = Object.keys(CLI_FLAGS).filter(\n (name): name is VerbFlag => !(GLOBAL_FLAGS as readonly string[]).includes(name),\n);\n\n/**\n * `--a`, `--a and --b`, `--a, --b and --c` — in flag-table order, always.\n *\n * Every refusal that names more than one flag spells the list this way: the\n * ownership refusal in `./registry.ts`, and the default verb's `--reconstruct`\n * refusal in `./smelt.ts`. It lives with the table because the *order* is the\n * table's — a refusal that listed flags in the order the user happened to type them\n * would read differently every time, and two refusals disagreeing about how to spell\n * the same pair is the kind of drift this file exists to remove.\n */\nexport function flagList(flags: readonly VerbFlag[]): string {\n const spelled = VERB_FLAGS.filter((flag) => flags.includes(flag)).map((flag) => `--${flag}`);\n if (spelled.length <= 1) return spelled.join('');\n return `${spelled.slice(0, -1).join(', ')} and ${spelled.at(-1) ?? ''}`;\n}\n\n/** What one flag's parsed value looks like, derived from how `parseArgs` was told to read it. */\ntype FlagValue<F> = F extends { readonly type: 'boolean' }\n ? boolean\n : F extends { readonly multiple: true }\n ? readonly string[]\n : string;\n\n/**\n * The parsed flags, as every `Subcommand.parse` sees them: one optional field per\n * flag, typed by the table above. Absent means the user did not type it — which is\n * the only thing a refusal needs to know, and the only thing a verb may act on.\n */\nexport type FlagValues = { readonly [K in FlagName]?: FlagValue<(typeof CLI_FLAGS)[K]> };\n\n/** How one flag appears in the OPTIONS block of `--help`. */\nexport interface FlagHelp {\n /** The left column, e.g. `--budget <bytes>` or `-h, --help`. */\n readonly label: string;\n /**\n * The description, already wrapped to the OPTIONS column — a function because three\n * entries render a registry (`SUPPORTED_LANGUAGES`, `STRATEGIES` +\n * `STRUCTURAL_LANGUAGES`, `HARNESS_IDS`) rather than a hand-typed list. The\n * ownership prefix is not here; it is generated.\n */\n body(): readonly string[];\n}\n\n/**\n * A comma-separated list under an OPTIONS entry's hanging indent, wrapped where the\n * hand-typed version wrapped. `--strategy` and `--language` render their registries on\n * one line because they fit; the harness ids do not, and a list long enough to wrap is\n * exactly the list nobody keeps in sync by hand.\n */\nexport function optionList(items: readonly string[], width: number): readonly string[] {\n const lines: string[] = [];\n let line = '';\n items.forEach((item, index) => {\n const word = index === items.length - 1 ? `${item}.` : `${item},`;\n const candidate = line === '' ? word : `${line} ${word}`;\n if (line !== '' && candidate.length > width) {\n lines.push(line);\n line = word;\n } else {\n line = candidate;\n }\n });\n lines.push(line);\n return lines;\n}\n\n/** The width an OPTIONS description wraps within, once its 23-column indent is removed. */\nconst OPTION_BODY_WIDTH = 65;\n\n/**\n * The OPTIONS entry for every flag. `Record<FlagName, FlagHelp>` on purpose: a flag\n * added to {@link CLI_FLAGS} without a help entry is a compile error, so the help text\n * cannot fall behind what the parser accepts.\n */\nexport const FLAG_HELP: Readonly<Record<FlagName, FlagHelp>> = {\n budget: {\n label: '--budget <bytes>',\n body: () => [\n 'Required, unless smelt.config.json sets defaultBudgetBytes.',\n 'Soft ceiling for the output, in UTF-8 bytes (for map: a hard',\n 'ceiling, met by construction). No built-in default: a budget',\n `${CLI_NAME} invented would decide for you.`,\n ],\n },\n focus: {\n label: '--focus <term>',\n body: () => [\n 'What you were looking for. Repeatable. Matching regions and',\n 'their context survive; the runs between them collapse. For',\n 'map: symbols matching a term (by name or path) are promoted',\n 'to the front of the fill order, ranks unchanged.',\n ],\n },\n language: {\n label: '--language <id>',\n body: () => [`Override detection. One of: ${[...SUPPORTED_LANGUAGES, 'unknown'].join(', ')}.`],\n },\n strategy: {\n label: '--strategy <id>',\n body: () => [\n `${STRATEGIES.join(', ')}. Defaults to ${DEFAULT_STRATEGY}, unless`,\n 'smelt.config.json says otherwise. structural parses',\n `${STRUCTURAL_LANGUAGES.join(', ')};`,\n 'any other language is refused, never approximated. auto picks',\n 'structural for those languages and lexical for the rest, and',\n 'the report names whichever one actually ran.',\n ],\n },\n ignore: {\n label: '--ignore <entry>',\n body: () => [\n 'Repeatable. Replaces the default ignore list',\n // Read off DEFAULT_REPO_IGNORE, never re-typed: help text that lists a default\n // by hand is help text that will one day describe a different default.\n `(${DEFAULT_REPO_IGNORE.join(', ')}): a bare name matches any path segment,`,\n 'an entry containing / is a root-relative prefix.',\n ],\n },\n cache: {\n label: '--cache <dir>',\n body: () => [\n 'Directory for the tags cache, keyed by content',\n 'hash. Only when given does the map write to disk at all.',\n ],\n },\n harness: {\n label: '--harness <id>',\n body: () => [\n 'Skip harness detection and target one id:',\n ...optionList(HARNESS_IDS, OPTION_BODY_WIDTH),\n ],\n },\n strict: {\n label: '--strict',\n body: () => [\n 'Turn any lint finding into exit 1, for CI. Findings are',\n 'advisory by default: the rules are heuristics about somebody',\n \"else's house style, and enforcing them uninvited would be\",\n `${CLI_NAME} deciding it. A budget you set yourself is different —`,\n 'exceeding agents.budgetBytes exits 1 with or without --strict.',\n ],\n },\n json: {\n label: '--json',\n body: () => [\n 'Print a JSON envelope on stdout instead of the text:',\n '{ format, result, elided } for a smelt run — `result` is',\n 'the SmeltResult verbatim, `elided` carries the bytes, so',\n 'the envelope can be reconstructed; feed it back with',\n '--reconstruct. For map: { format, map }, the RepoMap',\n 'structure verbatim. For agents lint: { format, report },',\n 'the measured levels and every finding with its rule id.',\n ],\n },\n reconstruct: {\n label: '--reconstruct',\n body: () => [\n 'Read a --json envelope and print the original text, byte for',\n 'byte. This is Law 3 you can run from a shell.',\n ],\n },\n help: { label: '-h, --help', body: () => ['This text.'] },\n version: { label: '--version', body: () => ['The package version.'] },\n};\n\n/**\n * `--budget` has no built-in default, for the same reason `smelt()` has none: a budget\n * smelt invented would be smelt deciding how much of the caller's context to throw\n * away, silently, at a number nobody chose. A *missing* flag is not an error here,\n * though — `smelt.config.json` may carry a `defaultBudgetBytes` the user chose\n * explicitly, and the verb's `resolve` errors only when neither exists. A malformed\n * value is always an error.\n *\n * It lives with the flag rather than with a verb because two verbs own `--budget`, and\n * the two of them agreeing on what \"4kb\" means is not something to leave to chance.\n *\n * The *lexing* is the CLI's own and stays here — argv carries strings, so `4kb` and a\n * leading `-` are answered by a digits-only test before anything numeric happens. The\n * *rule* and *the sentence that refuses it* come from `ops/inputs.ts`, which is also\n * where the `smelt_file` tool gets them: two surfaces, one law, spelled `--budget`\n * here and `\"budgetBytes\"` there.\n */\nexport function parseBudget(raw: string | undefined): number | undefined {\n if (raw === undefined) return undefined;\n if (!/^\\d+$/.test(raw)) throw refuseBudget('not-an-integer', raw);\n const value = Number(raw);\n const fault = budgetFault(value);\n if (fault !== undefined) throw refuseBudget(fault, raw);\n return value;\n}\n\n/** The malformed-budget refusal, in the CLI's currency: prefixed, and exit 2. */\nfunction refuseBudget(fault: BudgetFault, raw: string): CliUsageError {\n return new CliUsageError(`${CLI_NAME}: ${budgetMalformed(fault, '--budget', raw)}`);\n}\n"]}