@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
@@ -1,6 +1,7 @@
1
1
  import { Parser } from 'web-tree-sitter';
2
2
  import { GrammarUnavailableError, MissingMarkerPricingError } from '../errors.js';
3
3
  import { profileFor, structuralLanguages } from '../lang/registry.js';
4
+ import { predictOutputBytes, savingBytes } from './budget.js';
4
5
  import { loadGrammar } from './grammar.js';
5
6
  export const STRUCTURAL_PLANNER_ID = 'structural/v1';
6
7
  /**
@@ -51,11 +52,14 @@ export class StructuralPlanner {
51
52
  * reused directly. Deterministic: same text, same language, same focus, same options —
52
53
  * byte-identical plan.
53
54
  *
54
- * `budgetBytes` is a target, not a guarantee, for the same reason it is in the lexical
55
- * planner: once every non-matching sibling run is collapsed there is nothing left to
56
- * cut except the declarations the caller asked to keep, and an optimizer that silently
57
- * drops the thing you searched for is the exact failure this design refuses. Callers
58
- * who need a hard ceiling check `outputBytes` and decide.
55
+ * `budgetBytes` is a target, not a guarantee. The plan is built in two passes: every
56
+ * maximal run of non-matching siblings is collapsed where that pays for its marker,
57
+ * and then only if the plan is still over budget each run the first pass refused
58
+ * is re-asked as its own best profitable sub-run (the budget rung, in
59
+ * {@link planFromTree}). Under the last of those there is nothing left to cut except
60
+ * the declarations the caller asked to keep, and an optimizer that silently drops the
61
+ * thing you searched for is the exact failure this design refuses. Callers who need a
62
+ * hard ceiling check `outputBytes` and decide.
59
63
  *
60
64
  * @throws {GrammarUnavailableError} when the language is not one this planner parses,
61
65
  * when the grammar cannot be loaded, or when the parser produces no tree. Never a
@@ -92,15 +96,32 @@ export async function planStructural(input, options = {}) {
92
96
  }
93
97
  }
94
98
  function assertStructuralLanguage(language) {
95
- for (const supported of STRUCTURAL_LANGUAGES) {
96
- if (language === supported)
97
- return supported;
98
- }
99
+ if (isStructuralLanguage(language))
100
+ return language;
99
101
  const named = `${STRUCTURAL_LANGUAGES.slice(0, -1).join(', ')} and ${STRUCTURAL_LANGUAGES[STRUCTURAL_LANGUAGES.length - 1]}`;
100
102
  throw new GrammarUnavailableError(`smelt: structural planning covers ${named} in this ` +
101
103
  `slice; got "${language}". It does not fall back to the lexical planner — output ` +
102
104
  `labelled structural/v1 that is really line windows would be undetectable from ` +
103
- `the outside. Use the lexical planner explicitly if that is what you want.`);
105
+ `the outside. Use \`strategy: 'auto'\` for a mixed stream that is sometimes not ` +
106
+ `code — it picks the planner from the language and labels the one it ran — or ` +
107
+ `the lexical planner explicitly if that is what you want.`);
108
+ }
109
+ /**
110
+ * Whether this planner has a bundled grammar for the language — the one membership
111
+ * test, shared with the `auto` strategy (`plan/auto.ts`).
112
+ *
113
+ * The refusal above and auto's selection are the same question asked for opposite
114
+ * reasons, and two spellings of it would be two answers: a language auto routed to
115
+ * `structural` that `assertStructuralLanguage` then refused would be a
116
+ * `GrammarUnavailableError` raised by the strategy whose entire job is not raising
117
+ * one.
118
+ */
119
+ export function isStructuralLanguage(language) {
120
+ for (const supported of STRUCTURAL_LANGUAGES) {
121
+ if (language === supported)
122
+ return true;
123
+ }
124
+ return false;
104
125
  }
105
126
  function planFromTree(tree, input, options, language) {
106
127
  const profile = profileFor(language);
@@ -118,51 +139,177 @@ function planFromTree(tree, input, options, language) {
118
139
  const matched = matchUnits(units, input, options);
119
140
  const minSiblings = options.minSiblings ?? 1;
120
141
  const toByte = utf8OffsetIndex(input.text, units.flatMap((unit) => [unit.start, unit.end]));
121
- const elisions = [];
122
- let run = [];
123
- const flush = () => {
124
- if (run.length === 0)
125
- return;
126
- const group = run;
127
- run = [];
128
- if (group.length < minSiblings)
129
- return;
130
- // A line-comment marker swallows the rest of its line. When the run's last unit
142
+ /**
143
+ * One contiguous group of siblings as an elision — or nothing, when collapsing it
144
+ * would be illegal or would not pay for its marker. Every cut in this planner,
145
+ * first pass and budget rung alike, is minted here: one legality rule, one price,
146
+ * one explanation, so the rung cannot cut something the first pass would refuse.
147
+ */
148
+ const collapse = (group) => {
149
+ if (group.length === 0 || group.length < minSiblings)
150
+ return undefined;
151
+ // A line-comment marker swallows the rest of its line. When the group's last unit
131
152
  // ends mid-line — python's `stmt_a(); stmt_b()` puts two top-level statements on
132
153
  // one line — the marker's `# ` leader would comment out the *kept* code after it.
133
154
  // Refusing the collapse is the honest move; extending the range would elide code
134
155
  // no unit accounted for.
135
156
  if (markerIsLineComment && !restOfLineIsBlank(input.text, group[group.length - 1].end)) {
136
- return;
157
+ return undefined;
137
158
  }
138
159
  const start = toByte.get(group[0].start);
139
160
  const end = toByte.get(group[group.length - 1].end);
140
- const cutBytes = end - start;
141
- const explanation = explain(group);
161
+ const candidate = {
162
+ range: { start, end },
163
+ reason: { rule: SIBLING_COLLAPSE_RULE, explanation: explain(group) },
164
+ };
142
165
  // Profitability, priced rather than estimated: ask the MarkerPricing seam for the
143
166
  // exact cost of the marker this cut would earn — the explanation's length varies
144
167
  // with kind diversity, and the pricing carries the language's comment leader (or a
145
168
  // caller's custom builder), so a fixed estimate can pass a cut whose marker is
146
169
  // bigger than what it removes, and a marker that costs more than it removes grows
147
170
  // the output.
148
- const markerBytes = pricing.costBytes({ rule: SIBLING_COLLAPSE_RULE, explanation }, cutBytes);
149
- if (cutBytes <= markerBytes)
150
- return;
151
- elisions.push({
152
- range: { start, end },
153
- reason: { rule: SIBLING_COLLAPSE_RULE, explanation },
154
- });
171
+ return savingBytes(candidate, pricing) > 0 ? candidate : undefined;
155
172
  };
173
+ /**
174
+ * The UTF-8 bytes a sub-run `[from, to)` spans — read off the same offset index
175
+ * `collapse` mints ranges from, so it is the exact upper bound on what collapsing
176
+ * that sub-run could save, in O(1) and without slicing. The budget rung's span
177
+ * bound depends on it being both exact and cheap.
178
+ */
179
+ const spanBytes = (group, from, to) => toByte.get(group[to - 1].end) - toByte.get(group[from].start);
180
+ // The first pass: every maximal run of siblings no focus term matched and no rule
181
+ // pinned, collapsed whole. A run that earns no cut is remembered, not forgotten —
182
+ // the budget rung below is the only reader of that list.
183
+ const elisions = [];
184
+ const refused = [];
185
+ for (const group of maximalRuns(units, matched)) {
186
+ const cut = collapse(group);
187
+ if (cut === undefined)
188
+ refused.push(group);
189
+ else
190
+ elisions.push(cut);
191
+ }
192
+ // THE BUDGET RUNG — the structural answer to the lexical planner's context ladder.
193
+ //
194
+ // Until this existed, `planStructural` never read `input.budgetBytes` at all: it
195
+ // took every profitable maximal run and stopped, which is right until the plan
196
+ // comes back over budget with a profitable cut still on the table. The review's
197
+ // case: 158 bytes, a budget of 120, and a run of three sibling classes worth 92
198
+ // bytes left whole — because the *maximal* run also swallowed a `VERSION = 1`
199
+ // statement above them, and a mixed-kind explanation ("1 statement, 3 classes")
200
+ // priced a 106-byte marker against a 105-byte cut. The planner returned over budget
201
+ // having elided nothing, while the three classes alone priced an 82-byte marker
202
+ // against 92 bytes — a real saving of 10 bytes nobody was offered.
203
+ //
204
+ // So, and only when the plan is still over budget, each refused run is re-asked as
205
+ // its own best profitable sub-run, earliest run first, stopping the moment the plan
206
+ // fits. Four properties hold by construction rather than by care:
207
+ //
208
+ // - **A focus match is never cut.** A matched unit is not in any run, so no
209
+ // sub-run of any run can contain one. Same for a pinned unit.
210
+ // - **The output never grows.** Every candidate goes through `collapse`, which
211
+ // mints nothing whose marker is not strictly cheaper than the bytes it removes.
212
+ // - **It stays deterministic.** Sub-runs are enumerated start-ascending and
213
+ // length-descending, and a tie in saving keeps the one already held — the
214
+ // earliest start, then the longest. Both of the rung's bounds read that same
215
+ // order, so a bounded sweep and an exhaustive one answer alike.
216
+ // - **It stays bounded.** A refused run can be the whole file — the mid-line
217
+ // refusal is about one character, not about size — so the sweep stops at the
218
+ // first cut that fits the budget and skips every candidate whose span already
219
+ // proves it cannot win. See {@link bestSubRun}.
220
+ //
221
+ // Budget pressure is the trigger, not profitability, because a maximal run is the
222
+ // better *explanation*: one marker naming everything it hid beats two naming halves
223
+ // of it. Trading that away is worth it to fit a budget and not worth it otherwise.
224
+ const inputBytes = Buffer.byteLength(input.text, 'utf8');
225
+ for (const group of refused) {
226
+ const currentBytes = predictOutputBytes(inputBytes, elisions, pricing);
227
+ if (currentBytes <= input.budgetBytes)
228
+ break;
229
+ // What this run would have to save to land the plan under budget — the sweep's
230
+ // stopping condition, so a run that can fit the budget is priced a handful of
231
+ // times instead of exhaustively. See {@link bestSubRun}.
232
+ const enough = currentBytes - input.budgetBytes;
233
+ const cut = bestSubRun(group, collapse, pricing, spanBytes, enough);
234
+ if (cut !== undefined)
235
+ elisions.push(cut);
236
+ }
237
+ return elisions.toSorted((a, b) => a.range.start - b.range.start);
238
+ }
239
+ /**
240
+ * The runs the first pass collapses: every maximal contiguous group of units that no
241
+ * focus term matched and no rule pinned. A matched or pinned unit ends the run before
242
+ * it and starts none — which is what makes "never cut a focus-matched declaration" a
243
+ * property of the *shape* of the data rather than a check anyone has to remember.
244
+ */
245
+ function maximalRuns(units, matched) {
246
+ const runs = [];
247
+ let run = [];
156
248
  for (let i = 0; i < units.length; i += 1) {
157
249
  if (matched[i] === true || units[i].pinned === true) {
158
- flush();
250
+ if (run.length > 0)
251
+ runs.push(run);
252
+ run = [];
159
253
  }
160
254
  else {
161
255
  run.push(units[i]);
162
256
  }
163
257
  }
164
- flush();
165
- return elisions;
258
+ if (run.length > 0)
259
+ runs.push(run);
260
+ return runs;
261
+ }
262
+ /**
263
+ * The best cut available inside one refused run: the contiguous sub-run whose real
264
+ * marker is cheapest against the bytes it removes.
265
+ *
266
+ * The enumeration is bounded, and it has to be. A run reaches here refused for any of
267
+ * three reasons, and only one of them is "it lost to a ~100-byte marker": a run is also
268
+ * refused when it holds fewer than `minSiblings` units, and — the one that bites —
269
+ * when its last unit ends mid-line, because a line-comment marker would swallow the
270
+ * kept code after it. That last rule is a property of one character in the file, not of
271
+ * the run's size, so a *whole-file* run can land here: one bash script whose last line
272
+ * is `cleanup_tmp; deploy_release --now` refuses its only maximal run, and an
273
+ * unbounded `(from, to)` sweep over its thousands of units — each candidate slicing,
274
+ * explaining and pricing — is cubic. Measured, before this bound: 19.5 KB / 373 ms,
275
+ * 39 KB / 2.3 s, 79 KB / 17.2 s, 159 KB / 131 s. That is a CPU loop on tool-controlled
276
+ * input, on the path the caller takes precisely when it is over budget — the normal
277
+ * case. Two bounds, neither of which changes which cut a bounded sweep would have
278
+ * found by exhaustion:
279
+ *
280
+ * - **`enough`** — the saving that lands the plan under budget. The rung exists to
281
+ * fit a budget; the first candidate that does is the answer, and enumeration order
282
+ * (start-ascending, length-descending) makes it the earliest, longest such cut.
283
+ * This is what turns the whole-file case into two priced candidates.
284
+ * - **The span bound** — a cut can never save more than the bytes it spans, so once
285
+ * a candidate's span has shrunk to `bestSaving` or below, neither it nor any
286
+ * shorter candidate from the same start can win, and once the *longest* candidate
287
+ * from a start is that small, no later start can win either. Spans shrink
288
+ * monotonically along both loops, so both breaks are exact, not heuristic: they
289
+ * skip only candidates already proven to lose.
290
+ */
291
+ function bestSubRun(group, collapse, pricing, spanBytes, enough) {
292
+ let best;
293
+ let bestSaving = 0;
294
+ for (let from = 0; from < group.length; from += 1) {
295
+ if (spanBytes(group, from, group.length) <= bestSaving)
296
+ break;
297
+ for (let to = group.length; to > from; to -= 1) {
298
+ if (spanBytes(group, from, to) <= bestSaving)
299
+ break;
300
+ const candidate = collapse(group.slice(from, to));
301
+ if (candidate === undefined)
302
+ continue;
303
+ const saving = savingBytes(candidate, pricing);
304
+ if (saving > bestSaving) {
305
+ best = candidate;
306
+ bestSaving = saving;
307
+ if (saving >= enough)
308
+ return best;
309
+ }
310
+ }
311
+ }
312
+ return best;
166
313
  }
167
314
  /** True when nothing but whitespace follows `index` on its own line. */
168
315
  function restOfLineIsBlank(text, index) {
@@ -1 +1 @@
1
- {"version":3,"file":"structural.js","sourceRoot":"","sources":["../../src/plan/structural.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAGzC,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAElF,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAUtE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,CAAC,MAAM,qBAAqB,GAAG,eAAe,CAAC;AAErD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAA0B,mBAAmB,EAAE,CAAC;AASjF;;;GAGG;AACH,MAAM,qBAAqB,GAAG,kBAAkB,CAAC;AAyCjD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,iBAAiB;IACnB,EAAE,GAAG,qBAAqB,CAAC;IAC3B,OAAO,CAA2B;IAE3C,YAAY,OAAO,GAA6B,EAAE;QAChD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,IAAI,CAAC,KAAgB;QACnB,OAAO,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;CACF;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,KAAgB,EAChB,OAAO,GAA6B,EAAE;IAEtC,MAAM,QAAQ,GAAG,wBAAwB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC1D,kFAAkF;IAClF,kFAAkF;IAClF,2EAA2E;IAC3E,MAAM,OAAO,GAA8B,KAAK,CAAC,OAAO,CAAC;IACzD,IAAI,OAAO,KAAK,SAAS;QAAE,MAAM,IAAI,yBAAyB,CAAC,qBAAqB,CAAC,CAAC;IACtF,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAC;IAE5C,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAC5B,IAAI,IAAI,GAAgB,IAAI,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAC5B,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,MAAM,IAAI,uBAAuB,CAC/B,cAAc,QAAQ,uDAAuD;gBAC3E,yEAAyE;gBACzE,kCAAkC,CACrC,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO,EAAE,qBAAqB;YAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC;SACvD,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,MAAM,CAAC,MAAM,EAAE,CAAC;IAClB,CAAC;AACH,CAAC;AAED,SAAS,wBAAwB,CAAC,QAA+B;IAC/D,KAAK,MAAM,SAAS,IAAI,oBAAoB,EAAE,CAAC;QAC7C,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;IAC/C,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAC3D,oBAAoB,CAAC,oBAAoB,CAAC,MAAM,GAAG,CAAC,CACtD,EAAE,CAAC;IACH,MAAM,IAAI,uBAAuB,CAC/B,qCAAqC,KAAK,WAAW;QACnD,eAAe,QAAQ,2DAA2D;QAClF,gFAAgF;QAChF,2EAA2E,CAC9E,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CACnB,IAAU,EACV,KAAgB,EAChB,OAAiC,EACjC,QAA4B;IAE5B,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACrC,yEAAyE;IACzE,iFAAiF;IACjF,mDAAmD;IACnD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAU,CAAC;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,iFAAiF;IACjF,8EAA8E;IAC9E,gFAAgF;IAChF,iCAAiC;IACjC,MAAM,mBAAmB,GAAG,OAAO,CAAC,YAAY,KAAK,SAAS,CAAC;IAC/D,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAC5D,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAClD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,eAAe,CAC5B,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAChD,CAAC;IAEF,MAAM,QAAQ,GAAqB,EAAE,CAAC;IACtC,IAAI,GAAG,GAAW,EAAE,CAAC;IAErB,MAAM,KAAK,GAAG,GAAS,EAAE;QACvB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC7B,MAAM,KAAK,GAAG,GAAG,CAAC;QAClB,GAAG,GAAG,EAAE,CAAC;QACT,IAAI,KAAK,CAAC,MAAM,GAAG,WAAW;YAAE,OAAO;QACvC,gFAAgF;QAChF,iFAAiF;QACjF,kFAAkF;QAClF,iFAAiF;QACjF,yBAAyB;QACzB,IAAI,mBAAmB,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,GAAG,CAAC,EAAE,CAAC;YACxF,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,KAAK,CAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,GAAG,CAAE,CAAC;QACtD,MAAM,QAAQ,GAAG,GAAG,GAAG,KAAK,CAAC;QAC7B,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QACnC,kFAAkF;QAClF,iFAAiF;QACjF,mFAAmF;QACnF,+EAA+E;QAC/E,kFAAkF;QAClF,cAAc;QACd,MAAM,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,WAAW,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC9F,IAAI,QAAQ,IAAI,WAAW;YAAE,OAAO;QACpC,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;YACrB,MAAM,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,WAAW,EAAE;SACrD,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAE,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;YACrD,KAAK,EAAE,CAAC;QACV,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IACD,KAAK,EAAE,CAAC;IACR,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,wEAAwE;AACxE,SAAS,iBAAiB,CAAC,IAAY,EAAE,KAAa;IACpD,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACpB,IAAI,EAAE,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,OAAO,KAAK,CAAC;IACnC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,OAAO,CAAC,IAAU,EAAE,IAAY,EAAE,SAA4B;IACrE,MAAM,KAAK,GAAW,EAAE,CAAC;IACzB,yEAAyE;IACzE,IAAI,OAAO,GAAW,EAAE,CAAC;IACzB,8EAA8E;IAC9E,IAAI,mBAAmB,GAAG,KAAK,CAAC;IAEhC,MAAM,YAAY,GAAG,GAAS,EAAE;QAC9B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACjC,KAAK,CAAC,IAAI,CAAC;YACT,KAAK,EAAE,OAAO,CAAC,CAAC,CAAE,CAAC,UAAU;YAC7B,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,QAAQ;YAC1C,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;YACnD,MAAM,EAAE,CAAC,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;SAC7F,CAAC,CAAC;QACH,OAAO,GAAG,EAAE,CAAC;QACb,mBAAmB,GAAG,KAAK,CAAC;IAC9B,CAAC,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACvC,IAAI,KAAK,KAAK,IAAI;YAAE,SAAS;QAC7B,IAAI,SAAS,CAAC,kBAAkB,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/E,6EAA6E;YAC7E,6EAA6E;YAC7E,4EAA4E;YAC5E,2EAA2E;YAC3E,2EAA2E;YAC3E,0EAA0E;YAC1E,yCAAyC;YACzC,YAAY,EAAE,CAAC;YACf,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;YACtC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;YAC3D,SAAS;QACX,CAAC;QACD,IACE,SAAS,CAAC,WAAW,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI;YAC/C,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,EACvC,CAAC;YACD,6EAA6E;YAC7E,yEAAyE;YACzE,cAAc;YACd,YAAY,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,CAAC;gBACT,KAAK,EAAE,KAAK,CAAC,UAAU;gBACvB,GAAG,EAAE,KAAK,CAAC,QAAQ;gBACnB,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC;gBAC9B,MAAM,EAAE,IAAI;aACb,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,MAAM,SAAS,GAAG,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzD,MAAM,WAAW,GAAG,SAAS,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7D,IAAI,SAAS,IAAI,WAAW,EAAE,CAAC;YAC7B,IACE,OAAO,CAAC,MAAM,GAAG,CAAC;gBAClB,CAAC,mBAAmB;gBACpB,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,EACxE,CAAC;gBACD,YAAY,EAAE,CAAC;YACjB,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpB,mBAAmB,KAAK,WAAW,CAAC;YACpC,SAAS;QACX,CAAC;QAED,0EAA0E;QAC1E,gFAAgF;QAChF,iFAAiF;QACjF,2EAA2E;QAC3E,iFAAiF;QACjF,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAC/B,SAAS,CAAC,yBAAyB,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI;YAC3D,CAAC,CAAC,qBAAqB,CAAC,KAAK,EAAE,SAAS,CAAC;YACzC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAE5C,MAAM,QAAQ,GACZ,OAAO,CAAC,MAAM,GAAG,CAAC;YAClB,CAAC,mBAAmB;gBAClB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;QAC7E,IAAI,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC;YAC7E,2EAA2E;YAC3E,0EAA0E;YAC1E,YAAY,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,CAAC;gBACT,KAAK,EAAE,KAAK,CAAC,UAAU;gBACvB,GAAG,EAAE,QAAQ;gBACb,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC;aAC/B,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,QAAQ,EAAE,CAAC;YACpB,KAAK,CAAC,IAAI,CAAC;gBACT,KAAK,EAAE,OAAO,CAAC,CAAC,CAAE,CAAC,UAAU;gBAC7B,GAAG,EAAE,QAAQ;gBACb,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC;aAC/B,CAAC,CAAC;YACH,OAAO,GAAG,EAAE,CAAC;YACb,mBAAmB,GAAG,KAAK,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,YAAY,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,CAAC;gBACT,KAAK,EAAE,KAAK,CAAC,UAAU;gBACvB,GAAG,EAAE,QAAQ;gBACb,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC;aAC/B,CAAC,CAAC;QACL,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,QAAQ;YAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxD,CAAC;IACD,YAAY,EAAE,CAAC;IACf,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wFAAwF;AACxF,SAAS,aAAa,CAAC,IAAU,EAAE,IAAY,EAAE,SAA4B;IAC3E,IAAI,SAAS,CAAC,oBAAoB,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC/D,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACzD,OAAO,SAAS,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AACzF,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,IAAU,EAAE,IAAY,EAAE,SAA4B;IAC7E,MAAM,OAAO,GAAG,SAAS,CAAC,oBAAoB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5D,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AAClE,CAAC;AAED;;;;GAIG;AACH,SAAS,qBAAqB,CAC5B,IAAU,EACV,SAA4B;IAE5B,MAAM,QAAQ,GAAW,EAAE,CAAC;IAC5B,IAAI,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC;IAC1B,MAAM,IAAI,GAAG,CAAC,OAAa,EAAQ,EAAE;QACnC,IAAI,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,OAAO;QACT,CAAC;QACD,IAAI,WAAW,GAAG,KAAK,CAAC;QACxB,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrC,IAAI,KAAK,KAAK,IAAI;gBAAE,SAAS;YAC7B,WAAW,GAAG,IAAI,CAAC;YACnB,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;QACD,IAAI,CAAC,WAAW,IAAI,OAAO,CAAC,QAAQ,GAAG,GAAG;YAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC;IACrE,CAAC,CAAC;IACF,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClC,IAAI,KAAK,KAAK,IAAI;YAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC,EAAE,CAAC;AACpF,CAAC;AAED,+EAA+E;AAC/E,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW,EAAE,KAAa;IACxD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACvC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IACzC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,UAAU,CACjB,KAAsB,EACtB,KAAgB,EAChB,OAAiC;IAEjC,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,KAAK,CAAC;IACrD,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAEhF,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACxB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACvC,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACzD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,OAAO,CAAC,KAAsB;IACrC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAElF,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;IAC3B,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC;QAC5B,OAAO,aAAa,MAAM,CAAC,KAAK,CAAC,YAAY,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;IACxE,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CACrC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAChE,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,qBAAqB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClF,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC,OAAO,CAAC;IACZ,OAAO,aAAa,MAAM,CAAC,KAAK,CAAC,YAAY,OAAO,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAC/E,CAAC;AAED,sFAAsF;AACtF,MAAM,qBAAqB,GAAwB,IAAI,GAAG,CAAC;IACzD,WAAW;IACX,SAAS;IACT,iBAAiB;IACjB,gBAAgB;IAChB,gBAAgB;IAChB,qBAAqB;IACrB,WAAW;IACX,SAAS;IACT,qBAAqB;IACrB,mBAAmB;IACnB,SAAS;IACT,SAAS;IACT,cAAc;IACd,cAAc;IACd,wBAAwB;IACxB,0BAA0B;CAC3B,CAAC,CAAC;AAEH,SAAS,SAAS,CAAC,IAAY,EAAE,KAAa;IAC5C,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAC9D,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,SAAS,MAAM,CAAC,IAAU,EAAE,SAA4B;IACtD,MAAM,eAAe,GAAG,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1D,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QAClC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvC,IAAI,KAAK,KAAK,IAAI,IAAI,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,SAAS;YACvE,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/C,IAAI,KAAK,KAAK,SAAS;gBAAE,OAAO,KAAK,CAAC;QACxC,CAAC;QACD,OAAO,eAAe,CAAC;IACzB,CAAC;IACD,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,iBAAiB,CAAC;IACpD,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB;QAAE,OAAO,WAAW,CAAC;IAC5F,OAAO,aAAa,CAAC;AACvB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,eAAe,CAAC,IAAY,EAAE,OAA0B;IAC/D,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/D,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,YAAY,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;QAC5E,aAAa,GAAG,KAAK,CAAC;QACtB,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
1
+ {"version":3,"file":"structural.js","sourceRoot":"","sources":["../../src/plan/structural.ts"],"names":[],"mappings":"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","sourcesContent":["import { Parser } from 'web-tree-sitter';\nimport type { Node, Tree } from 'web-tree-sitter';\n\nimport { GrammarUnavailableError, MissingMarkerPricingError } from '../errors.ts';\nimport type { LanguageStructure } from '../lang/profile.ts';\nimport { profileFor, structuralLanguages } from '../lang/registry.ts';\nimport type {\n ElisionPlan,\n LanguageId,\n MarkerPricing,\n PlanInput,\n PlannedElision,\n Planner,\n} from '../types.ts';\n\nimport { predictOutputBytes, savingBytes } from './budget.ts';\nimport { loadGrammar } from './grammar.ts';\n\nexport const STRUCTURAL_PLANNER_ID = 'structural/v1';\n\n/**\n * The languages this planner actually parses — a derived view of the registry: every\n * {@link LanguageProfile} that carries a `structure` section, in registry order. A\n * language appears here only once its node kinds are mapped in its profile\n * (`src/lang/<id>.ts`), because claiming a language before that would produce markers\n * that mislabel what they collapsed.\n *\n * Exported for the totality guard (`test/guards/structural-totality.test.ts`): every\n * language named here must have a fixture, a snapshot and a doc-comment case, so a\n * language cannot be claimed without tests.\n */\nexport const STRUCTURAL_LANGUAGES: readonly LanguageId[] = structuralLanguages();\n\n/**\n * Historically the union of the claimed ids; every `LanguageId` carries a profile\n * now, so the compile-time totality this type enforced lives on the registry\n * (`Record<LanguageId, LanguageProfile>`) instead.\n */\nexport type StructuralLanguage = LanguageId;\n\n/**\n * Every elision this planner produces carries this rule id, and the profitability\n * check below prices the marker that rule would earn — so the two must not drift.\n */\nconst SIBLING_COLLAPSE_RULE = 'sibling-collapse';\n\nexport interface StructuralPlannerOptions {\n /**\n * Never collapse a sibling group smaller than this. Defaults to 1 — the byte\n * profitability check already refuses collapses that would not pay for their marker.\n */\n readonly minSiblings?: number;\n /** Focus matching is substring, case-insensitive by default — same as lexical. */\n readonly caseSensitive?: boolean;\n}\n\n/**\n * One top-level declaration, together with the doc comment attached to it. The unit is\n * the atom of every decision here: a unit is kept whole or collapsed whole, which is\n * what makes \"a kept declaration keeps its signature line and attached doc comment\"\n * true by construction rather than by patching ranges afterwards.\n *\n * All positions are UTF-16 code-unit indices (what web-tree-sitter reports for a JS\n * string); they are converted to UTF-8 byte offsets in one place, at the end.\n */\ninterface Unit {\n /** Start of the unit — the first attached comment or attribute if there is one. */\n readonly start: number;\n /** End of the declaration node. */\n readonly end: number;\n /** Human word for the declaration's kind, e.g. `'function'`. */\n readonly kind: string;\n /**\n * A unit the planner must never collapse, matched or not. Every pin follows one\n * law: collapsing it would silently change what the survivor *is*, not what it\n * contains. Go's `//go:build` constraint governs which builds see the whole file;\n * shebang lines (bash, ruby, python as comments; javascript, typescript, tsx,\n * kotlin and swift as their grammars' own shebang nodes) and ruby's\n * `# frozen_string_literal:` magic comment govern how the file is executed at all;\n * php's `<?php` open tag is what makes the rest of the file php; and c/c++'s\n * `#pragma once` governs what including the file means.\n */\n readonly pinned?: boolean;\n}\n\n/**\n * The structural planner — the reason smelt exists.\n *\n * It parses with the language's tree-sitter grammar, finds the top-level declarations\n * whose text matches the caller's focus, keeps each match whole — signature line,\n * attached doc comment, body — and collapses each contiguous run of non-matching\n * *siblings* into one marker that names them from the parse tree: `collapsed 3 sibling\n * functions`. Structure is what makes that explanation possible; a line window can only\n * ever say \"collapsed 40 lines\".\n *\n * It throws {@link GrammarUnavailableError} rather than falling back to the lexical\n * planner, and that is the deliberate part. A silent fallback would mean a caller who\n * asked for structural planning, and whose grammar failed to load, gets line-window\n * output labelled `structural/v1` — plausible, wrong, and undetectable from the\n * outside. A caller who wants the fallback asks for it, by planning lexically itself.\n */\nexport class StructuralPlanner implements Planner {\n readonly id = STRUCTURAL_PLANNER_ID;\n readonly options: StructuralPlannerOptions;\n\n constructor(options: StructuralPlannerOptions = {}) {\n this.options = options;\n }\n\n plan(input: PlanInput): Promise<ElisionPlan> {\n return planStructural(input, this.options);\n }\n}\n\n/**\n * The planner as a function, exported like {@link planLexical} so it can be tested and\n * reused directly. Deterministic: same text, same language, same focus, same options —\n * byte-identical plan.\n *\n * `budgetBytes` is a target, not a guarantee. The plan is built in two passes: every\n * maximal run of non-matching siblings is collapsed where that pays for its marker,\n * and then — only if the plan is still over budget — each run the first pass refused\n * is re-asked as its own best profitable sub-run (the budget rung, in\n * {@link planFromTree}). Under the last of those there is nothing left to cut except\n * the declarations the caller asked to keep, and an optimizer that silently drops the\n * thing you searched for is the exact failure this design refuses. Callers who need a\n * hard ceiling check `outputBytes` and decide.\n *\n * @throws {GrammarUnavailableError} when the language is not one this planner parses,\n * when the grammar cannot be loaded, or when the parser produces no tree. Never a\n * lexical fallback.\n */\nexport async function planStructural(\n input: PlanInput,\n options: StructuralPlannerOptions = {},\n): Promise<ElisionPlan> {\n const language = assertStructuralLanguage(input.language);\n // The runtime backstop for JS callers: TypeScript makes `pricing` required, but a\n // JS caller can omit it, and the honest answer is a named refusal rather than the\n // planner quietly pricing markers itself — the inversion the seam removed.\n const pricing: MarkerPricing | undefined = input.pricing;\n if (pricing === undefined) throw new MissingMarkerPricingError(STRUCTURAL_PLANNER_ID);\n const grammar = await loadGrammar(language);\n\n const parser = new Parser();\n let tree: Tree | null = null;\n try {\n parser.setLanguage(grammar);\n tree = parser.parse(input.text);\n if (tree === null) {\n throw new GrammarUnavailableError(\n `smelt: the ${language} parser returned no tree. Structural planning cannot ` +\n `proceed, and it does not fall back to lexical — a caller who wants the ` +\n `fallback plans lexically itself.`,\n );\n }\n return {\n planner: STRUCTURAL_PLANNER_ID,\n language: input.language,\n elisions: planFromTree(tree, input, options, language),\n };\n } finally {\n tree?.delete();\n parser.delete();\n }\n}\n\nfunction assertStructuralLanguage(language: PlanInput['language']): StructuralLanguage {\n if (isStructuralLanguage(language)) return language;\n const named = `${STRUCTURAL_LANGUAGES.slice(0, -1).join(', ')} and ${\n STRUCTURAL_LANGUAGES[STRUCTURAL_LANGUAGES.length - 1]\n }`;\n throw new GrammarUnavailableError(\n `smelt: structural planning covers ${named} in this ` +\n `slice; got \"${language}\". It does not fall back to the lexical planner — output ` +\n `labelled structural/v1 that is really line windows would be undetectable from ` +\n `the outside. Use \\`strategy: 'auto'\\` for a mixed stream that is sometimes not ` +\n `code — it picks the planner from the language and labels the one it ran — or ` +\n `the lexical planner explicitly if that is what you want.`,\n );\n}\n\n/**\n * Whether this planner has a bundled grammar for the language — the one membership\n * test, shared with the `auto` strategy (`plan/auto.ts`).\n *\n * The refusal above and auto's selection are the same question asked for opposite\n * reasons, and two spellings of it would be two answers: a language auto routed to\n * `structural` that `assertStructuralLanguage` then refused would be a\n * `GrammarUnavailableError` raised by the strategy whose entire job is not raising\n * one.\n */\nexport function isStructuralLanguage(\n language: PlanInput['language'],\n): language is StructuralLanguage {\n for (const supported of STRUCTURAL_LANGUAGES) {\n if (language === supported) return true;\n }\n return false;\n}\n\nfunction planFromTree(\n tree: Tree,\n input: PlanInput,\n options: StructuralPlannerOptions,\n language: StructuralLanguage,\n): readonly PlannedElision[] {\n const profile = profileFor(language);\n // assertStructuralLanguage admits only languages whose profile carries a\n // `structure` section — that is the definition of a structural language — so the\n // assertion states a fact the seam already proved.\n const structure = profile.structure!;\n const pricing = input.pricing;\n // The marker lands as a line comment in every structural language (the profile's\n // markerLeader), and a line comment comments out everything to the end of its\n // line — so a collapse is only legal where nothing kept follows on the marker's\n // own line. See the flush below.\n const markerIsLineComment = profile.markerLeader !== undefined;\n const units = unitsOf(tree.rootNode, input.text, structure);\n const matched = matchUnits(units, input, options);\n const minSiblings = options.minSiblings ?? 1;\n const toByte = utf8OffsetIndex(\n input.text,\n units.flatMap((unit) => [unit.start, unit.end]),\n );\n\n /**\n * One contiguous group of siblings as an elision — or nothing, when collapsing it\n * would be illegal or would not pay for its marker. Every cut in this planner,\n * first pass and budget rung alike, is minted here: one legality rule, one price,\n * one explanation, so the rung cannot cut something the first pass would refuse.\n */\n const collapse = (group: readonly Unit[]): PlannedElision | undefined => {\n if (group.length === 0 || group.length < minSiblings) return undefined;\n // A line-comment marker swallows the rest of its line. When the group's last unit\n // ends mid-line — python's `stmt_a(); stmt_b()` puts two top-level statements on\n // one line — the marker's `# ` leader would comment out the *kept* code after it.\n // Refusing the collapse is the honest move; extending the range would elide code\n // no unit accounted for.\n if (markerIsLineComment && !restOfLineIsBlank(input.text, group[group.length - 1]!.end)) {\n return undefined;\n }\n const start = toByte.get(group[0]!.start)!;\n const end = toByte.get(group[group.length - 1]!.end)!;\n const candidate: PlannedElision = {\n range: { start, end },\n reason: { rule: SIBLING_COLLAPSE_RULE, explanation: explain(group) },\n };\n // Profitability, priced rather than estimated: ask the MarkerPricing seam for the\n // exact cost of the marker this cut would earn — the explanation's length varies\n // with kind diversity, and the pricing carries the language's comment leader (or a\n // caller's custom builder), so a fixed estimate can pass a cut whose marker is\n // bigger than what it removes, and a marker that costs more than it removes grows\n // the output.\n return savingBytes(candidate, pricing) > 0 ? candidate : undefined;\n };\n\n /**\n * The UTF-8 bytes a sub-run `[from, to)` spans — read off the same offset index\n * `collapse` mints ranges from, so it is the exact upper bound on what collapsing\n * that sub-run could save, in O(1) and without slicing. The budget rung's span\n * bound depends on it being both exact and cheap.\n */\n const spanBytes = (group: readonly Unit[], from: number, to: number): number =>\n toByte.get(group[to - 1]!.end)! - toByte.get(group[from]!.start)!;\n\n // The first pass: every maximal run of siblings no focus term matched and no rule\n // pinned, collapsed whole. A run that earns no cut is remembered, not forgotten —\n // the budget rung below is the only reader of that list.\n const elisions: PlannedElision[] = [];\n const refused: (readonly Unit[])[] = [];\n for (const group of maximalRuns(units, matched)) {\n const cut = collapse(group);\n if (cut === undefined) refused.push(group);\n else elisions.push(cut);\n }\n\n // THE BUDGET RUNG — the structural answer to the lexical planner's context ladder.\n //\n // Until this existed, `planStructural` never read `input.budgetBytes` at all: it\n // took every profitable maximal run and stopped, which is right until the plan\n // comes back over budget with a profitable cut still on the table. The review's\n // case: 158 bytes, a budget of 120, and a run of three sibling classes worth 92\n // bytes left whole — because the *maximal* run also swallowed a `VERSION = 1`\n // statement above them, and a mixed-kind explanation (\"1 statement, 3 classes\")\n // priced a 106-byte marker against a 105-byte cut. The planner returned over budget\n // having elided nothing, while the three classes alone priced an 82-byte marker\n // against 92 bytes — a real saving of 10 bytes nobody was offered.\n //\n // So, and only when the plan is still over budget, each refused run is re-asked as\n // its own best profitable sub-run, earliest run first, stopping the moment the plan\n // fits. Four properties hold by construction rather than by care:\n //\n // - **A focus match is never cut.** A matched unit is not in any run, so no\n // sub-run of any run can contain one. Same for a pinned unit.\n // - **The output never grows.** Every candidate goes through `collapse`, which\n // mints nothing whose marker is not strictly cheaper than the bytes it removes.\n // - **It stays deterministic.** Sub-runs are enumerated start-ascending and\n // length-descending, and a tie in saving keeps the one already held — the\n // earliest start, then the longest. Both of the rung's bounds read that same\n // order, so a bounded sweep and an exhaustive one answer alike.\n // - **It stays bounded.** A refused run can be the whole file — the mid-line\n // refusal is about one character, not about size — so the sweep stops at the\n // first cut that fits the budget and skips every candidate whose span already\n // proves it cannot win. See {@link bestSubRun}.\n //\n // Budget pressure is the trigger, not profitability, because a maximal run is the\n // better *explanation*: one marker naming everything it hid beats two naming halves\n // of it. Trading that away is worth it to fit a budget and not worth it otherwise.\n const inputBytes = Buffer.byteLength(input.text, 'utf8');\n for (const group of refused) {\n const currentBytes = predictOutputBytes(inputBytes, elisions, pricing);\n if (currentBytes <= input.budgetBytes) break;\n // What this run would have to save to land the plan under budget — the sweep's\n // stopping condition, so a run that can fit the budget is priced a handful of\n // times instead of exhaustively. See {@link bestSubRun}.\n const enough = currentBytes - input.budgetBytes;\n const cut = bestSubRun(group, collapse, pricing, spanBytes, enough);\n if (cut !== undefined) elisions.push(cut);\n }\n\n return elisions.toSorted((a, b) => a.range.start - b.range.start);\n}\n\n/**\n * The runs the first pass collapses: every maximal contiguous group of units that no\n * focus term matched and no rule pinned. A matched or pinned unit ends the run before\n * it and starts none — which is what makes \"never cut a focus-matched declaration\" a\n * property of the *shape* of the data rather than a check anyone has to remember.\n */\nfunction maximalRuns(\n units: readonly Unit[],\n matched: readonly boolean[],\n): readonly (readonly Unit[])[] {\n const runs: Unit[][] = [];\n let run: Unit[] = [];\n for (let i = 0; i < units.length; i += 1) {\n if (matched[i] === true || units[i]!.pinned === true) {\n if (run.length > 0) runs.push(run);\n run = [];\n } else {\n run.push(units[i]!);\n }\n }\n if (run.length > 0) runs.push(run);\n return runs;\n}\n\n/**\n * The best cut available inside one refused run: the contiguous sub-run whose real\n * marker is cheapest against the bytes it removes.\n *\n * The enumeration is bounded, and it has to be. A run reaches here refused for any of\n * three reasons, and only one of them is \"it lost to a ~100-byte marker\": a run is also\n * refused when it holds fewer than `minSiblings` units, and — the one that bites —\n * when its last unit ends mid-line, because a line-comment marker would swallow the\n * kept code after it. That last rule is a property of one character in the file, not of\n * the run's size, so a *whole-file* run can land here: one bash script whose last line\n * is `cleanup_tmp; deploy_release --now` refuses its only maximal run, and an\n * unbounded `(from, to)` sweep over its thousands of units — each candidate slicing,\n * explaining and pricing — is cubic. Measured, before this bound: 19.5 KB / 373 ms,\n * 39 KB / 2.3 s, 79 KB / 17.2 s, 159 KB / 131 s. That is a CPU loop on tool-controlled\n * input, on the path the caller takes precisely when it is over budget — the normal\n * case. Two bounds, neither of which changes which cut a bounded sweep would have\n * found by exhaustion:\n *\n * - **`enough`** — the saving that lands the plan under budget. The rung exists to\n * fit a budget; the first candidate that does is the answer, and enumeration order\n * (start-ascending, length-descending) makes it the earliest, longest such cut.\n * This is what turns the whole-file case into two priced candidates.\n * - **The span bound** — a cut can never save more than the bytes it spans, so once\n * a candidate's span has shrunk to `bestSaving` or below, neither it nor any\n * shorter candidate from the same start can win, and once the *longest* candidate\n * from a start is that small, no later start can win either. Spans shrink\n * monotonically along both loops, so both breaks are exact, not heuristic: they\n * skip only candidates already proven to lose.\n */\nfunction bestSubRun(\n group: readonly Unit[],\n collapse: (group: readonly Unit[]) => PlannedElision | undefined,\n pricing: MarkerPricing,\n spanBytes: (group: readonly Unit[], from: number, to: number) => number,\n enough: number,\n): PlannedElision | undefined {\n let best: PlannedElision | undefined;\n let bestSaving = 0;\n for (let from = 0; from < group.length; from += 1) {\n if (spanBytes(group, from, group.length) <= bestSaving) break;\n for (let to = group.length; to > from; to -= 1) {\n if (spanBytes(group, from, to) <= bestSaving) break;\n const candidate = collapse(group.slice(from, to));\n if (candidate === undefined) continue;\n const saving = savingBytes(candidate, pricing);\n if (saving > bestSaving) {\n best = candidate;\n bestSaving = saving;\n if (saving >= enough) return best;\n }\n }\n }\n return best;\n}\n\n/** True when nothing but whitespace follows `index` on its own line. */\nfunction restOfLineIsBlank(text: string, index: number): boolean {\n for (let i = index; i < text.length; i += 1) {\n const ch = text[i]!;\n if (ch === '\\n') return true;\n if (!/\\s/.test(ch)) return false;\n }\n return true;\n}\n\n/**\n * Group the root's named children into units: each declaration plus the comment block\n * — and, in rust, the outer attributes — attached to it. A comment attaches when only\n * blank-free whitespace separates it from what follows — one newline at most — so a doc\n * comment travels with its declaration, while a comment left floating above a blank\n * line stands alone.\n *\n * Attributes attach *unconditionally*: tree-sitter-rust parses `#[inline]` as a\n * top-level sibling of the item it decorates, but the language's own rule is that an\n * outer attribute modifies the next item, blank lines or not. So once an attribute is\n * pending, the whole pending prefix (doc comments included) rides forward to the next\n * declaration — treating it as its own unit would let a collapse strip `#[derive(…)]`\n * and the doc comment above it off a kept declaration.\n */\nfunction unitsOf(root: Node, text: string, structure: LanguageStructure): readonly Unit[] {\n const units: Unit[] = [];\n /** Comments and attributes waiting to attach to the next declaration. */\n let pending: Node[] = [];\n /** Once true, blank lines no longer detach the pending prefix — see above. */\n let pendingHasAttribute = false;\n\n const flushPending = (): void => {\n if (pending.length === 0) return;\n units.push({\n start: pending[0]!.startIndex,\n end: pending[pending.length - 1]!.endIndex,\n kind: pendingHasAttribute ? 'attribute' : 'comment',\n pinned: !pendingHasAttribute && pending.some((node) => pinnedComment(node, text, structure)),\n });\n pending = [];\n pendingHasAttribute = false;\n };\n\n for (const child of root.namedChildren) {\n if (child === null) continue;\n if (structure.ridesBackwardTypes?.has(child.type) === true && units.length > 0) {\n // A node that is grammatically part of the *previous* statement, parsed as a\n // top-level sibling: tree-sitter-ruby emits a heredoc's body as a sibling of\n // the statement holding its `<<~SQL` opener. Splitting them lets a collapse\n // keep the opener and cut the body — an unterminated heredoc that swallows\n // every kept declaration after it, without a single ERROR node to show for\n // it. The body extends the preceding unit instead, so opener and body are\n // kept whole or collapsed whole, always.\n flushPending();\n const last = units[units.length - 1]!;\n units[units.length - 1] = { ...last, end: child.endIndex };\n continue;\n }\n if (\n structure.pinnedTypes?.has(child.type) === true ||\n pinnedDirective(child, text, structure)\n ) {\n // A pinned node (php's `<?php` tag, a `#!` shebang line, c's `#pragma once`)\n // is its own uncollapsible unit — nothing attaches to it, and no run may\n // swallow it.\n flushPending();\n units.push({\n start: child.startIndex,\n end: child.endIndex,\n kind: kindOf(child, structure),\n pinned: true,\n });\n continue;\n }\n const isComment = structure.commentTypes.has(child.type);\n const isAttribute = structure.attributeTypes.has(child.type);\n if (isComment || isAttribute) {\n if (\n pending.length > 0 &&\n !pendingHasAttribute &&\n !adjacent(text, pending[pending.length - 1]!.endIndex, child.startIndex)\n ) {\n flushPending();\n }\n pending.push(child);\n pendingHasAttribute ||= isAttribute;\n continue;\n }\n\n // tree-sitter-kotlin extends the import_list node over a doc comment that\n // follows it, so the KDoc of the first documented declaration after the imports\n // would be collapsed *with the imports* — a kept declaration losing its attached\n // doc comment. Split such trailing comments off: the unit ends at its last\n // non-comment token, and the comments ride forward like any other pending block.\n const { end: childEnd, trailing } =\n structure.trailingCommentSplitTypes?.has(child.type) === true\n ? splitTrailingComments(child, structure)\n : { end: child.endIndex, trailing: [] };\n\n const attached =\n pending.length > 0 &&\n (pendingHasAttribute ||\n adjacent(text, pending[pending.length - 1]!.endIndex, child.startIndex));\n if (attached && pending.some((node) => pinnedComment(node, text, structure))) {\n // A pinned comment (`//go:build`) governs the file, not the declaration it\n // happens to touch — keep it a standalone, uncollapsible unit either way.\n flushPending();\n units.push({\n start: child.startIndex,\n end: childEnd,\n kind: kindOf(child, structure),\n });\n } else if (attached) {\n units.push({\n start: pending[0]!.startIndex,\n end: childEnd,\n kind: kindOf(child, structure),\n });\n pending = [];\n pendingHasAttribute = false;\n } else {\n flushPending();\n units.push({\n start: child.startIndex,\n end: childEnd,\n kind: kindOf(child, structure),\n });\n }\n for (const comment of trailing) pending.push(comment);\n }\n flushPending();\n return units;\n}\n\n/** Whether this comment node is one the language pins to the file. See {@link Unit}. */\nfunction pinnedComment(node: Node, text: string, structure: LanguageStructure): boolean {\n if (structure.pinnedCommentPattern === undefined) return false;\n if (!structure.commentTypes.has(node.type)) return false;\n return structure.pinnedCommentPattern.test(text.slice(node.startIndex, node.endIndex));\n}\n\n/**\n * Whether this non-comment node is pinned by a per-type text pattern. C's\n * `#pragma once` is a `preproc_call` like any other pragma, but collapsing it\n * silently changes header inclusion semantics — the same class as `//go:build`, on a\n * node kind a comment pattern cannot reach.\n */\nfunction pinnedDirective(node: Node, text: string, structure: LanguageStructure): boolean {\n const pattern = structure.pinnedPatternsByType?.[node.type];\n if (pattern === undefined) return false;\n return pattern.test(text.slice(node.startIndex, node.endIndex));\n}\n\n/**\n * The end of `node`'s last non-comment token, and every comment node after it. Used\n * for node types the grammar extends over comments that belong to what follows —\n * kotlin's import_list swallowing the next declaration's KDoc.\n */\nfunction splitTrailingComments(\n node: Node,\n structure: LanguageStructure,\n): { readonly end: number; readonly trailing: readonly Node[] } {\n const comments: Node[] = [];\n let end = node.startIndex;\n const walk = (current: Node): void => {\n if (structure.commentTypes.has(current.type)) {\n comments.push(current);\n return;\n }\n let hasChildren = false;\n for (const child of current.children) {\n if (child === null) continue;\n hasChildren = true;\n walk(child);\n }\n if (!hasChildren && current.endIndex > end) end = current.endIndex;\n };\n for (const child of node.children) {\n if (child !== null) walk(child);\n }\n return { end, trailing: comments.filter((comment) => comment.startIndex >= end) };\n}\n\n/** Nothing but whitespace between the two indices, and at most one newline. */\nfunction adjacent(text: string, end: number, start: number): boolean {\n const between = text.slice(end, start);\n if (!/^\\s*$/.test(between)) return false;\n return (between.match(/\\n/g) ?? []).length <= 1;\n}\n\nfunction matchUnits(\n units: readonly Unit[],\n input: PlanInput,\n options: StructuralPlannerOptions,\n): readonly boolean[] {\n const caseSensitive = options.caseSensitive ?? false;\n const focus = (input.focus ?? []).filter((term) => term.length > 0);\n const needles = caseSensitive ? focus : focus.map((term) => term.toLowerCase());\n\n return units.map((unit) => {\n if (needles.length === 0) return false;\n const raw = input.text.slice(unit.start, unit.end);\n const haystack = caseSensitive ? raw : raw.toLowerCase();\n return needles.some((needle) => haystack.includes(needle));\n });\n}\n\n/**\n * Law 2, for this rule: the explanation names the *kind* and the *count*, read off the\n * parse tree — `collapsed 3 sibling functions` — never a line count as the claim. A\n * mixed run says what it mixed: `collapsed 4 sibling declarations (3 functions,\n * 1 class)` — and when the run holds anything that is not a declaration (a statement,\n * a floating comment, an unparsed region), the heading says `nodes`, because calling\n * a parse error a declaration would be the marker lying about the tree.\n */\nfunction explain(group: readonly Unit[]): string {\n const counts = new Map<string, number>();\n for (const unit of group) counts.set(unit.kind, (counts.get(unit.kind) ?? 0) + 1);\n\n const total = group.length;\n if (counts.size === 1) {\n const kind = group[0]!.kind;\n return `collapsed ${String(total)} sibling ${countNoun(kind, total)}`;\n }\n const parts = [...counts.entries()].map(\n ([kind, count]) => `${String(count)} ${countNoun(kind, count)}`,\n );\n const heading = [...counts.keys()].every((kind) => !NON_DECLARATION_KINDS.has(kind))\n ? 'declarations'\n : 'nodes';\n return `collapsed ${String(total)} sibling ${heading} (${parts.join(', ')})`;\n}\n\n/** The kind labels that are not declarations, so a mixed heading never overclaims. */\nconst NON_DECLARATION_KINDS: ReadonlySet<string> = new Set([\n 'statement',\n 'comment',\n 'unparsed region',\n 'package clause',\n 'package header',\n 'package declaration',\n 'attribute',\n 'command',\n 'variable assignment',\n 'include directive',\n 'shebang',\n 'php tag',\n 'html section',\n 'heredoc body',\n 'preprocessor directive',\n 'preprocessor conditional',\n]);\n\nfunction countNoun(kind: string, count: number): string {\n if (count === 1) return kind;\n if (/[^aeiou]y$/.test(kind)) return `${kind.slice(0, -1)}ies`;\n return /(?:s|sh|ch)$/.test(kind) ? `${kind}es` : `${kind}s`;\n}\n\n/**\n * The kind of a declaration, unwrapping wrappers (`export …`, `@decorator`) so the\n * marker names what it hides. What the language's map does not name is still labelled\n * honestly — see {@link LanguageStructure.kindLabels} for the labelling rules.\n */\nfunction kindOf(node: Node, structure: LanguageStructure): string {\n const wrapperFallback = structure.wrapperTypes[node.type];\n if (wrapperFallback !== undefined) {\n for (const child of node.namedChildren) {\n if (child === null || structure.commentTypes.has(child.type)) continue;\n const label = structure.kindLabels[child.type];\n if (label !== undefined) return label;\n }\n return wrapperFallback;\n }\n const label = structure.kindLabels[node.type];\n if (label !== undefined) return label;\n if (node.type === 'ERROR') return 'unparsed region';\n if (node.type.endsWith('_statement') || node.type === 'statement_block') return 'statement';\n return 'declaration';\n}\n\n/**\n *\n * web-tree-sitter reports positions in code units of the JS string it parsed;\n * {@link ElisionPlan} ranges are UTF-8 bytes. Because every converted index is a parse\n * node boundary, a range can never split a multi-byte character — a node boundary is\n * always a character boundary. One forward pass, so the conversion is linear.\n */\nfunction utf8OffsetIndex(text: string, indices: readonly number[]): ReadonlyMap<number, number> {\n const sorted = [...new Set(indices)].toSorted((a, b) => a - b);\n const map = new Map<number, number>();\n let previousIndex = 0;\n let previousByte = 0;\n for (const index of sorted) {\n previousByte += Buffer.byteLength(text.slice(previousIndex, index), 'utf8');\n previousIndex = index;\n map.set(index, previousByte);\n }\n return map;\n}\n"]}
@@ -18,6 +18,32 @@ import type { LanguageId } from '../types.ts';
18
18
  * source, and the caller's result carries a warning naming the file. Trusting a
19
19
  * damaged cache would silently drop symbols from the map, which is this project's
20
20
  * signature failure mode.
21
+ *
22
+ * **It is bounded, and the bound is a sweep.** The key is a content hash, so an edit
23
+ * does not replace an entry — it mints a new one and orphans the old, which is
24
+ * invisible and permanent: every pre-edit version of every file the caller ever mapped
25
+ * stays on disk for as long as the directory does. A long session therefore grew this
26
+ * cache without limit while never reading most of it. So {@link TagsCache.sweep}
27
+ * deletes every entry the build it just finished did not use, leaving exactly the tags
28
+ * of the tree as it now stands. The policy is stated as a bound anyone can check: **an
29
+ * entry survives a build only if that build used it**, so the cache is at most one
30
+ * entry per mappable file in the tree — plus whatever a crash left mid-write, which
31
+ * no sweep can safely reclaim; see {@link ENTRY_FILE}.
32
+ *
33
+ * Why that is safe, and the rule any other policy would also have to meet: **a miss
34
+ * can only make a map slower, never wrong.** A missing entry is re-extracted from the
35
+ * file's own bytes, and a present entry is only ever served for the exact content that
36
+ * hashed to its key — so sweeping too much costs a re-parse and sweeping too little
37
+ * costs disk, and neither can change a single symbol in the emitted map. The same rule
38
+ * is why the sweep never throws: it runs after the map is finished, so a cache
39
+ * directory that will not list postpones the bound to the next build rather than
40
+ * turning a computed map into no map at all.
41
+ *
42
+ * The cost is paid by a caller who points *one* cache directory at *several* trees:
43
+ * each build sweeps the others' entries, and every build then re-parses. That is a
44
+ * slower map, not a wrong one, and it is measured rather than hidden — the sweep's
45
+ * count rides back in `RepoMap.cache.pruned` and is printed in `smelt map`'s report.
46
+ * One cache directory per tree is the shape this is tuned for.
21
47
  */
22
48
  /** The format name every cache entry carries. */
23
49
  export declare const TAGS_CACHE_FORMAT = "smelt-repomap-tags";
@@ -49,5 +75,27 @@ export declare class TagsCache {
49
75
  * source, so losing one to a crash costs a re-parse, not a broken promise.
50
76
  */
51
77
  write(key: string, tags: FileTags): void;
78
+ /**
79
+ * Delete every entry that is not in `live` — the bound on this cache, described in
80
+ * full in the module comment above. `live` is the set of keys the build that just
81
+ * finished actually used, so what survives is exactly the tags of the tree as it
82
+ * now stands, and the superseded pre-edit entries a content-hash key would otherwise
83
+ * accumulate forever are gone.
84
+ *
85
+ * Returns how many entries were removed, so the caller can report a measured number
86
+ * rather than a claim.
87
+ *
88
+ * **The whole sweep is best effort, listing included.** A directory that will not
89
+ * list — an unreadable cache directory, or one an external cleaner removed between
90
+ * the last write and this call — reports `0` pruned and leaves the map alone. It
91
+ * runs after the tree has been walked, ranked and rendered, so a throw here would
92
+ * destroy a finished map over housekeeping smelt only wanted to do: no map at all
93
+ * instead of a slower one, which is the trade this cache is forbidden to make.
94
+ * Deleting one entry is best effort for the same reason. Nothing is lost by
95
+ * skipping a sweep — the entries are offered again on the next build, and until
96
+ * then nothing reads them, because nothing will ever look up a key no file hashes
97
+ * to.
98
+ */
99
+ sweep(live: ReadonlySet<string>): number;
52
100
  }
53
101
  //# sourceMappingURL=cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/repomap/cache.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAiB,QAAQ,EAAgB,MAAM,WAAW,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;;;;;;;;;;;;;GAkBG;AAEH,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,uBAAuB,CAAC;AAEtD,yFAAyF;AACzF,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED,8CAA8C;AAC9C,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAE/D,qBAAa,SAAS;;IAGpB,qFAAqF;IACrF,YAAY,GAAG,EAAE,MAAM,EAGtB;IAED;;;;OAIG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAqBjC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAWvC;CAeF"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/repomap/cache.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAiB,QAAQ,EAAgB,MAAM,WAAW,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,uBAAuB,CAAC;AAEtD,yFAAyF;AACzF,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED,8CAA8C;AAC9C,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAkB/D,qBAAa,SAAS;;IAGpB,qFAAqF;IACrF,YAAY,GAAG,EAAE,MAAM,EAKtB;IAED;;;;OAIG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CA2BjC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAavC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,KAAK,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,CAqBvC;CAeF","sourcesContent":["import {\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport { join } from 'node:path';\nimport process from 'node:process';\n\nimport { contentHash } from '../hash.ts';\nimport { fsCall } from './io.ts';\nimport type { DefinitionTag, FileTags, ReferenceTag } from './tags.ts';\nimport type { LanguageId } from '../types.ts';\n\n/**\n * The repo map's disk cache — per-file tags, keyed by content hash, JSON on disk.\n *\n * Modelled on Aider's repo-map tags cache (https://aider.chat/docs/repomap.html),\n * with one deliberate substitution: Aider persists through SQLite; this repo ships\n * zero new runtime dependencies, so entries are plain JSON files under a directory\n * **the caller explicitly hands in**. smelt never writes outside a store or cache it\n * was handed — no default location, no home-directory guessing.\n *\n * The key is a content hash over the tag-format version, the language, and the file's\n * exact text. That is the whole invalidation story: edit a file and its key changes,\n * so the stale entry is simply never looked up again. Nothing needs a timestamp.\n *\n * A corrupt entry — unparseable JSON, or JSON of the wrong shape — is **discarded\n * loudly, never trusted**: the entry file is deleted, the tags are re-extracted from\n * source, and the caller's result carries a warning naming the file. Trusting a\n * damaged cache would silently drop symbols from the map, which is this project's\n * signature failure mode.\n *\n * **It is bounded, and the bound is a sweep.** The key is a content hash, so an edit\n * does not replace an entry — it mints a new one and orphans the old, which is\n * invisible and permanent: every pre-edit version of every file the caller ever mapped\n * stays on disk for as long as the directory does. A long session therefore grew this\n * cache without limit while never reading most of it. So {@link TagsCache.sweep}\n * deletes every entry the build it just finished did not use, leaving exactly the tags\n * of the tree as it now stands. The policy is stated as a bound anyone can check: **an\n * entry survives a build only if that build used it**, so the cache is at most one\n * entry per mappable file in the tree — plus whatever a crash left mid-write, which\n * no sweep can safely reclaim; see {@link ENTRY_FILE}.\n *\n * Why that is safe, and the rule any other policy would also have to meet: **a miss\n * can only make a map slower, never wrong.** A missing entry is re-extracted from the\n * file's own bytes, and a present entry is only ever served for the exact content that\n * hashed to its key — so sweeping too much costs a re-parse and sweeping too little\n * costs disk, and neither can change a single symbol in the emitted map. The same rule\n * is why the sweep never throws: it runs after the map is finished, so a cache\n * directory that will not list postpones the bound to the next build rather than\n * turning a computed map into no map at all.\n *\n * The cost is paid by a caller who points *one* cache directory at *several* trees:\n * each build sweeps the others' entries, and every build then re-parses. That is a\n * slower map, not a wrong one, and it is measured rather than hidden — the sweep's\n * count rides back in `RepoMap.cache.pruned` and is printed in `smelt map`'s report.\n * One cache directory per tree is the shape this is tuned for.\n */\n\n/** The format name every cache entry carries. */\nexport const TAGS_CACHE_FORMAT = 'smelt-repomap-tags';\n\n/** Bump this when the tag shape changes: old entries then miss instead of misleading. */\nexport const TAGS_CACHE_VERSION = 1;\n\n/**\n * The cache key for one file's tags. The version and language are part of the hashed\n * material, so a format bump or a re-detected language can never resurrect an entry\n * extracted under different rules.\n */\nexport function tagsCacheKey(language: LanguageId, content: string): string {\n return contentHash(`${TAGS_CACHE_FORMAT}/${String(TAGS_CACHE_VERSION)}\\0${language}\\0${content}`);\n}\n\n/** What `read()` reports about one lookup. */\nexport type TagsCacheLookup = FileTags | 'corrupt' | undefined;\n\n/**\n * An entry file's name, and the key inside it. Only a name of exactly this shape is\n * ever swept: a temp file from a write in flight (`<key>.json.tmp-<pid>`) does not\n * match, so a sweep cannot delete the file another process is at that moment renaming\n * into place.\n *\n * The price of that exclusion, stated rather than hidden: the bound above is over\n * *entries*, and a process killed between the `writeFileSync` and the `renameSync` in\n * {@link TagsCache.write} leaves a temp file no later sweep reclaims. Reclaiming one\n * safely needs a liveness test this cache cannot make — a pid can be reused, and an\n * mtime cutoff is a number smelt would have invented — and the alternative, deleting\n * a temp file a live writer is about to rename, fails that writer's build. So a\n * crash's leftovers are counted as the known cost of never racing a concurrent write.\n */\nconst ENTRY_FILE = /^([0-9a-f]+)\\.json$/;\n\nexport class TagsCache {\n readonly #entriesDir: string;\n\n /** `dir` is the directory the caller handed in; entries live under `<dir>/tags/`. */\n constructor(dir: string) {\n this.#entriesDir = join(dir, 'tags');\n fsCall('create the tags cache directory', this.#entriesDir, () => {\n mkdirSync(this.#entriesDir, { recursive: true });\n });\n }\n\n /**\n * The cached tags under `key`, `undefined` on a miss, `'corrupt'` when an entry\n * existed but could not be trusted — in which case it has already been deleted, so\n * the corruption is reported exactly once and never re-read.\n */\n read(key: string): TagsCacheLookup {\n const path = this.#entryPath(key);\n const raw = fsCall('read the cache entry', path, (): string | undefined => {\n try {\n return readFileSync(path, 'utf8');\n } catch (error) {\n // Structural, not `NodeJS.ErrnoException`: this type reaches the shipped\n // declarations, and an ambient namespace there breaks a consumer compiling\n // with `skipLibCheck: false`.\n if ((error as { code?: string }).code === 'ENOENT') return undefined;\n throw error;\n }\n });\n if (raw === undefined) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n this.#discard(key);\n return 'corrupt';\n }\n const tags = validateEntry(parsed);\n if (tags === undefined) {\n this.#discard(key);\n return 'corrupt';\n }\n return tags;\n }\n\n /**\n * Persist one file's tags. Written to a temp name and renamed into place, so a\n * concurrent reader never sees a half-written entry; both writers of the same key\n * are writing identical bytes (the key covers the content), so last-rename-wins is\n * harmless. No fsync: unlike the elision store, every entry here is derivable from\n * source, so losing one to a crash costs a re-parse, not a broken promise.\n */\n write(key: string, tags: FileTags): void {\n const body = `${JSON.stringify({\n format: TAGS_CACHE_FORMAT,\n version: TAGS_CACHE_VERSION,\n defs: tags.defs,\n refs: tags.refs,\n })}\\n`;\n const target = this.#entryPath(key);\n const temp = `${target}.tmp-${String(process.pid)}`;\n fsCall('write the cache entry', target, () => {\n writeFileSync(temp, body, 'utf8');\n renameSync(temp, target);\n });\n }\n\n /**\n * Delete every entry that is not in `live` — the bound on this cache, described in\n * full in the module comment above. `live` is the set of keys the build that just\n * finished actually used, so what survives is exactly the tags of the tree as it\n * now stands, and the superseded pre-edit entries a content-hash key would otherwise\n * accumulate forever are gone.\n *\n * Returns how many entries were removed, so the caller can report a measured number\n * rather than a claim.\n *\n * **The whole sweep is best effort, listing included.** A directory that will not\n * list — an unreadable cache directory, or one an external cleaner removed between\n * the last write and this call — reports `0` pruned and leaves the map alone. It\n * runs after the tree has been walked, ranked and rendered, so a throw here would\n * destroy a finished map over housekeeping smelt only wanted to do: no map at all\n * instead of a slower one, which is the trade this cache is forbidden to make.\n * Deleting one entry is best effort for the same reason. Nothing is lost by\n * skipping a sweep — the entries are offered again on the next build, and until\n * then nothing reads them, because nothing will ever look up a key no file hashes\n * to.\n */\n sweep(live: ReadonlySet<string>): number {\n let names: string[];\n try {\n names = readdirSync(this.#entriesDir);\n } catch {\n return 0;\n }\n let pruned = 0;\n for (const name of names.toSorted()) {\n const key = ENTRY_FILE.exec(name)?.[1];\n if (key === undefined || live.has(key)) continue;\n try {\n unlinkSync(join(this.#entriesDir, name));\n pruned += 1;\n } catch {\n // Already gone, or undeletable. Either way it is one stale entry that will be\n // offered for sweeping again on the next build; nothing reads it meanwhile,\n // because nothing will ever look up a key no file hashes to.\n }\n }\n return pruned;\n }\n\n #entryPath(key: string): string {\n return join(this.#entriesDir, `${key}.json`);\n }\n\n /** Delete a corrupt entry so it can never be re-read as truth. Best effort. */\n #discard(key: string): void {\n try {\n unlinkSync(this.#entryPath(key));\n } catch {\n // Already gone, or undeletable — either way it will be overwritten by the\n // rewrite that follows every discard.\n }\n }\n}\n\n/** The parsed entry as `FileTags`, or `undefined` when its shape cannot be trusted. */\nfunction validateEntry(parsed: unknown): FileTags | undefined {\n if (typeof parsed !== 'object' || parsed === null) return undefined;\n const entry = parsed as { format?: unknown; version?: unknown; defs?: unknown; refs?: unknown };\n if (entry.format !== TAGS_CACHE_FORMAT || entry.version !== TAGS_CACHE_VERSION) return undefined;\n if (!Array.isArray(entry.defs) || !Array.isArray(entry.refs)) return undefined;\n\n const defs: DefinitionTag[] = [];\n for (const item of entry.defs as unknown[]) {\n const def = item as { name?: unknown; kind?: unknown; line?: unknown };\n if (typeof def.name !== 'string' || def.name === '') return undefined;\n if (typeof def.kind !== 'string' || def.kind === '') return undefined;\n if (typeof def.line !== 'number' || !Number.isInteger(def.line) || def.line < 1) {\n return undefined;\n }\n defs.push({ name: def.name, kind: def.kind, line: def.line });\n }\n const refs: ReferenceTag[] = [];\n for (const item of entry.refs as unknown[]) {\n const ref = item as { name?: unknown; count?: unknown };\n if (typeof ref.name !== 'string' || ref.name === '') return undefined;\n if (typeof ref.count !== 'number' || !Number.isInteger(ref.count) || ref.count < 1) {\n return undefined;\n }\n refs.push({ name: ref.name, count: ref.count });\n }\n return { defs, refs };\n}\n"]}