@ltm-alpha/alpha-cs 1.6.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 (1107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +972 -0
  3. package/dist/bin/command-supervision.d.ts +49 -0
  4. package/dist/bin/command-supervision.d.ts.map +1 -0
  5. package/dist/bin/command-supervision.js +95 -0
  6. package/dist/bin/command-supervision.js.map +1 -0
  7. package/dist/bin/fatal-handler.d.ts +20 -0
  8. package/dist/bin/fatal-handler.d.ts.map +1 -0
  9. package/dist/bin/fatal-handler.js +118 -0
  10. package/dist/bin/fatal-handler.js.map +1 -0
  11. package/dist/bin/ltm-alpha-cs.d.ts +27 -0
  12. package/dist/bin/ltm-alpha-cs.d.ts.map +1 -0
  13. package/dist/bin/ltm-alpha-cs.js +2643 -0
  14. package/dist/bin/ltm-alpha-cs.js.map +1 -0
  15. package/dist/bin/node-version-check.d.ts +37 -0
  16. package/dist/bin/node-version-check.d.ts.map +1 -0
  17. package/dist/bin/node-version-check.js +79 -0
  18. package/dist/bin/node-version-check.js.map +1 -0
  19. package/dist/bin/uninstall.d.ts +14 -0
  20. package/dist/bin/uninstall.d.ts.map +1 -0
  21. package/dist/bin/uninstall.js +36 -0
  22. package/dist/bin/uninstall.js.map +1 -0
  23. package/dist/context/formatter.d.ts +36 -0
  24. package/dist/context/formatter.d.ts.map +1 -0
  25. package/dist/context/formatter.js +269 -0
  26. package/dist/context/formatter.js.map +1 -0
  27. package/dist/context/index.d.ts +131 -0
  28. package/dist/context/index.d.ts.map +1 -0
  29. package/dist/context/index.js +1378 -0
  30. package/dist/context/index.js.map +1 -0
  31. package/dist/context/markers.d.ts +19 -0
  32. package/dist/context/markers.d.ts.map +1 -0
  33. package/dist/context/markers.js +22 -0
  34. package/dist/context/markers.js.map +1 -0
  35. package/dist/db/index.d.ts +385 -0
  36. package/dist/db/index.d.ts.map +1 -0
  37. package/dist/db/index.js +902 -0
  38. package/dist/db/index.js.map +1 -0
  39. package/dist/db/migrations.d.ts +44 -0
  40. package/dist/db/migrations.d.ts.map +1 -0
  41. package/dist/db/migrations.js +236 -0
  42. package/dist/db/migrations.js.map +1 -0
  43. package/dist/db/queries.d.ts +1095 -0
  44. package/dist/db/queries.d.ts.map +1 -0
  45. package/dist/db/queries.js +3285 -0
  46. package/dist/db/queries.js.map +1 -0
  47. package/dist/db/schema.sql +207 -0
  48. package/dist/db/sqlite-adapter.d.ts +55 -0
  49. package/dist/db/sqlite-adapter.d.ts.map +1 -0
  50. package/dist/db/sqlite-adapter.js +135 -0
  51. package/dist/db/sqlite-adapter.js.map +1 -0
  52. package/dist/db/wal-valve.d.ts +139 -0
  53. package/dist/db/wal-valve.d.ts.map +1 -0
  54. package/dist/db/wal-valve.js +333 -0
  55. package/dist/db/wal-valve.js.map +1 -0
  56. package/dist/directory.d.ts +232 -0
  57. package/dist/directory.d.ts.map +1 -0
  58. package/dist/directory.js +877 -0
  59. package/dist/directory.js.map +1 -0
  60. package/dist/errors.d.ts +150 -0
  61. package/dist/errors.d.ts.map +1 -0
  62. package/dist/errors.js +234 -0
  63. package/dist/errors.js.map +1 -0
  64. package/dist/extraction/astro-extractor.d.ts +79 -0
  65. package/dist/extraction/astro-extractor.d.ts.map +1 -0
  66. package/dist/extraction/astro-extractor.js +320 -0
  67. package/dist/extraction/astro-extractor.js.map +1 -0
  68. package/dist/extraction/cfml-extractor.d.ts +107 -0
  69. package/dist/extraction/cfml-extractor.d.ts.map +1 -0
  70. package/dist/extraction/cfml-extractor.js +505 -0
  71. package/dist/extraction/cfml-extractor.js.map +1 -0
  72. package/dist/extraction/dfm-extractor.d.ts +31 -0
  73. package/dist/extraction/dfm-extractor.d.ts.map +1 -0
  74. package/dist/extraction/dfm-extractor.js +151 -0
  75. package/dist/extraction/dfm-extractor.js.map +1 -0
  76. package/dist/extraction/extraction-version.d.ts +25 -0
  77. package/dist/extraction/extraction-version.d.ts.map +1 -0
  78. package/dist/extraction/extraction-version.js +28 -0
  79. package/dist/extraction/extraction-version.js.map +1 -0
  80. package/dist/extraction/function-ref.d.ts +118 -0
  81. package/dist/extraction/function-ref.d.ts.map +1 -0
  82. package/dist/extraction/function-ref.js +746 -0
  83. package/dist/extraction/function-ref.js.map +1 -0
  84. package/dist/extraction/generated-detection.d.ts +66 -0
  85. package/dist/extraction/generated-detection.d.ts.map +1 -0
  86. package/dist/extraction/generated-detection.js +248 -0
  87. package/dist/extraction/generated-detection.js.map +1 -0
  88. package/dist/extraction/grammars.d.ts +148 -0
  89. package/dist/extraction/grammars.d.ts.map +1 -0
  90. package/dist/extraction/grammars.js +743 -0
  91. package/dist/extraction/grammars.js.map +1 -0
  92. package/dist/extraction/index.d.ts +455 -0
  93. package/dist/extraction/index.d.ts.map +1 -0
  94. package/dist/extraction/index.js +3208 -0
  95. package/dist/extraction/index.js.map +1 -0
  96. package/dist/extraction/kernel/decode.d.ts +9 -0
  97. package/dist/extraction/kernel/decode.d.ts.map +1 -0
  98. package/dist/extraction/kernel/decode.js +162 -0
  99. package/dist/extraction/kernel/decode.js.map +1 -0
  100. package/dist/extraction/kernel/index.d.ts +64 -0
  101. package/dist/extraction/kernel/index.d.ts.map +1 -0
  102. package/dist/extraction/kernel/index.js +279 -0
  103. package/dist/extraction/kernel/index.js.map +1 -0
  104. package/dist/extraction/kernel/layout.d.ts +101 -0
  105. package/dist/extraction/kernel/layout.d.ts.map +1 -0
  106. package/dist/extraction/kernel/layout.js +104 -0
  107. package/dist/extraction/kernel/layout.js.map +1 -0
  108. package/dist/extraction/kernel/loader.d.ts +103 -0
  109. package/dist/extraction/kernel/loader.d.ts.map +1 -0
  110. package/dist/extraction/kernel/loader.js +146 -0
  111. package/dist/extraction/kernel/loader.js.map +1 -0
  112. package/dist/extraction/languages/arkts.d.ts +3 -0
  113. package/dist/extraction/languages/arkts.d.ts.map +1 -0
  114. package/dist/extraction/languages/arkts.js +127 -0
  115. package/dist/extraction/languages/arkts.js.map +1 -0
  116. package/dist/extraction/languages/c-cpp.d.ts +167 -0
  117. package/dist/extraction/languages/c-cpp.d.ts.map +1 -0
  118. package/dist/extraction/languages/c-cpp.js +1850 -0
  119. package/dist/extraction/languages/c-cpp.js.map +1 -0
  120. package/dist/extraction/languages/cfquery.d.ts +12 -0
  121. package/dist/extraction/languages/cfquery.d.ts.map +1 -0
  122. package/dist/extraction/languages/cfquery.js +28 -0
  123. package/dist/extraction/languages/cfquery.js.map +1 -0
  124. package/dist/extraction/languages/cfscript.d.ts +3 -0
  125. package/dist/extraction/languages/cfscript.d.ts.map +1 -0
  126. package/dist/extraction/languages/cfscript.js +73 -0
  127. package/dist/extraction/languages/cfscript.js.map +1 -0
  128. package/dist/extraction/languages/cobol.d.ts +33 -0
  129. package/dist/extraction/languages/cobol.d.ts.map +1 -0
  130. package/dist/extraction/languages/cobol.js +499 -0
  131. package/dist/extraction/languages/cobol.js.map +1 -0
  132. package/dist/extraction/languages/csharp.d.ts +25 -0
  133. package/dist/extraction/languages/csharp.d.ts.map +1 -0
  134. package/dist/extraction/languages/csharp.js +175 -0
  135. package/dist/extraction/languages/csharp.js.map +1 -0
  136. package/dist/extraction/languages/dart.d.ts +3 -0
  137. package/dist/extraction/languages/dart.d.ts.map +1 -0
  138. package/dist/extraction/languages/dart.js +374 -0
  139. package/dist/extraction/languages/dart.js.map +1 -0
  140. package/dist/extraction/languages/erlang.d.ts +3 -0
  141. package/dist/extraction/languages/erlang.d.ts.map +1 -0
  142. package/dist/extraction/languages/erlang.js +386 -0
  143. package/dist/extraction/languages/erlang.js.map +1 -0
  144. package/dist/extraction/languages/go.d.ts +3 -0
  145. package/dist/extraction/languages/go.d.ts.map +1 -0
  146. package/dist/extraction/languages/go.js +111 -0
  147. package/dist/extraction/languages/go.js.map +1 -0
  148. package/dist/extraction/languages/index.d.ts +10 -0
  149. package/dist/extraction/languages/index.d.ts.map +1 -0
  150. package/dist/extraction/languages/index.js +71 -0
  151. package/dist/extraction/languages/index.js.map +1 -0
  152. package/dist/extraction/languages/java.d.ts +3 -0
  153. package/dist/extraction/languages/java.d.ts.map +1 -0
  154. package/dist/extraction/languages/java.js +315 -0
  155. package/dist/extraction/languages/java.js.map +1 -0
  156. package/dist/extraction/languages/javascript.d.ts +3 -0
  157. package/dist/extraction/languages/javascript.d.ts.map +1 -0
  158. package/dist/extraction/languages/javascript.js +106 -0
  159. package/dist/extraction/languages/javascript.js.map +1 -0
  160. package/dist/extraction/languages/kotlin.d.ts +3 -0
  161. package/dist/extraction/languages/kotlin.d.ts.map +1 -0
  162. package/dist/extraction/languages/kotlin.js +512 -0
  163. package/dist/extraction/languages/kotlin.js.map +1 -0
  164. package/dist/extraction/languages/lua.d.ts +3 -0
  165. package/dist/extraction/languages/lua.d.ts.map +1 -0
  166. package/dist/extraction/languages/lua.js +153 -0
  167. package/dist/extraction/languages/lua.js.map +1 -0
  168. package/dist/extraction/languages/luau.d.ts +3 -0
  169. package/dist/extraction/languages/luau.d.ts.map +1 -0
  170. package/dist/extraction/languages/luau.js +37 -0
  171. package/dist/extraction/languages/luau.js.map +1 -0
  172. package/dist/extraction/languages/nix.d.ts +3 -0
  173. package/dist/extraction/languages/nix.d.ts.map +1 -0
  174. package/dist/extraction/languages/nix.js +294 -0
  175. package/dist/extraction/languages/nix.js.map +1 -0
  176. package/dist/extraction/languages/objc.d.ts +3 -0
  177. package/dist/extraction/languages/objc.d.ts.map +1 -0
  178. package/dist/extraction/languages/objc.js +179 -0
  179. package/dist/extraction/languages/objc.js.map +1 -0
  180. package/dist/extraction/languages/pascal.d.ts +3 -0
  181. package/dist/extraction/languages/pascal.d.ts.map +1 -0
  182. package/dist/extraction/languages/pascal.js +77 -0
  183. package/dist/extraction/languages/pascal.js.map +1 -0
  184. package/dist/extraction/languages/php.d.ts +3 -0
  185. package/dist/extraction/languages/php.d.ts.map +1 -0
  186. package/dist/extraction/languages/php.js +196 -0
  187. package/dist/extraction/languages/php.js.map +1 -0
  188. package/dist/extraction/languages/python.d.ts +3 -0
  189. package/dist/extraction/languages/python.d.ts.map +1 -0
  190. package/dist/extraction/languages/python.js +56 -0
  191. package/dist/extraction/languages/python.js.map +1 -0
  192. package/dist/extraction/languages/r.d.ts +3 -0
  193. package/dist/extraction/languages/r.d.ts.map +1 -0
  194. package/dist/extraction/languages/r.js +314 -0
  195. package/dist/extraction/languages/r.js.map +1 -0
  196. package/dist/extraction/languages/ruby.d.ts +3 -0
  197. package/dist/extraction/languages/ruby.d.ts.map +1 -0
  198. package/dist/extraction/languages/ruby.js +149 -0
  199. package/dist/extraction/languages/ruby.js.map +1 -0
  200. package/dist/extraction/languages/rust.d.ts +23 -0
  201. package/dist/extraction/languages/rust.d.ts.map +1 -0
  202. package/dist/extraction/languages/rust.js +172 -0
  203. package/dist/extraction/languages/rust.js.map +1 -0
  204. package/dist/extraction/languages/scala.d.ts +3 -0
  205. package/dist/extraction/languages/scala.d.ts.map +1 -0
  206. package/dist/extraction/languages/scala.js +219 -0
  207. package/dist/extraction/languages/scala.js.map +1 -0
  208. package/dist/extraction/languages/solidity.d.ts +3 -0
  209. package/dist/extraction/languages/solidity.d.ts.map +1 -0
  210. package/dist/extraction/languages/solidity.js +293 -0
  211. package/dist/extraction/languages/solidity.js.map +1 -0
  212. package/dist/extraction/languages/swift.d.ts +3 -0
  213. package/dist/extraction/languages/swift.d.ts.map +1 -0
  214. package/dist/extraction/languages/swift.js +152 -0
  215. package/dist/extraction/languages/swift.js.map +1 -0
  216. package/dist/extraction/languages/terraform.d.ts +3 -0
  217. package/dist/extraction/languages/terraform.d.ts.map +1 -0
  218. package/dist/extraction/languages/terraform.js +641 -0
  219. package/dist/extraction/languages/terraform.js.map +1 -0
  220. package/dist/extraction/languages/typescript.d.ts +16 -0
  221. package/dist/extraction/languages/typescript.d.ts.map +1 -0
  222. package/dist/extraction/languages/typescript.js +174 -0
  223. package/dist/extraction/languages/typescript.js.map +1 -0
  224. package/dist/extraction/languages/vbnet.d.ts +11 -0
  225. package/dist/extraction/languages/vbnet.d.ts.map +1 -0
  226. package/dist/extraction/languages/vbnet.js +141 -0
  227. package/dist/extraction/languages/vbnet.js.map +1 -0
  228. package/dist/extraction/liquid-extractor.d.ts +59 -0
  229. package/dist/extraction/liquid-extractor.d.ts.map +1 -0
  230. package/dist/extraction/liquid-extractor.js +357 -0
  231. package/dist/extraction/liquid-extractor.js.map +1 -0
  232. package/dist/extraction/mybatis-extractor.d.ts +68 -0
  233. package/dist/extraction/mybatis-extractor.d.ts.map +1 -0
  234. package/dist/extraction/mybatis-extractor.js +300 -0
  235. package/dist/extraction/mybatis-extractor.js.map +1 -0
  236. package/dist/extraction/parse-pool.d.ts +167 -0
  237. package/dist/extraction/parse-pool.d.ts.map +1 -0
  238. package/dist/extraction/parse-pool.js +421 -0
  239. package/dist/extraction/parse-pool.js.map +1 -0
  240. package/dist/extraction/parse-worker.d.ts +8 -0
  241. package/dist/extraction/parse-worker.d.ts.map +1 -0
  242. package/dist/extraction/parse-worker.js +138 -0
  243. package/dist/extraction/parse-worker.js.map +1 -0
  244. package/dist/extraction/razor-extractor.d.ts +42 -0
  245. package/dist/extraction/razor-extractor.d.ts.map +1 -0
  246. package/dist/extraction/razor-extractor.js +285 -0
  247. package/dist/extraction/razor-extractor.js.map +1 -0
  248. package/dist/extraction/store-worker.d.ts +24 -0
  249. package/dist/extraction/store-worker.d.ts.map +1 -0
  250. package/dist/extraction/store-worker.js +139 -0
  251. package/dist/extraction/store-worker.js.map +1 -0
  252. package/dist/extraction/store-writer.d.ts +63 -0
  253. package/dist/extraction/store-writer.d.ts.map +1 -0
  254. package/dist/extraction/store-writer.js +174 -0
  255. package/dist/extraction/store-writer.js.map +1 -0
  256. package/dist/extraction/svelte-extractor.d.ts +56 -0
  257. package/dist/extraction/svelte-extractor.d.ts.map +1 -0
  258. package/dist/extraction/svelte-extractor.js +275 -0
  259. package/dist/extraction/svelte-extractor.js.map +1 -0
  260. package/dist/extraction/syntax-tokens.d.ts +103 -0
  261. package/dist/extraction/syntax-tokens.d.ts.map +1 -0
  262. package/dist/extraction/syntax-tokens.js +381 -0
  263. package/dist/extraction/syntax-tokens.js.map +1 -0
  264. package/dist/extraction/tree-sitter-helpers.d.ts +28 -0
  265. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -0
  266. package/dist/extraction/tree-sitter-helpers.js +153 -0
  267. package/dist/extraction/tree-sitter-helpers.js.map +1 -0
  268. package/dist/extraction/tree-sitter-types.d.ts +279 -0
  269. package/dist/extraction/tree-sitter-types.d.ts.map +1 -0
  270. package/dist/extraction/tree-sitter-types.js +10 -0
  271. package/dist/extraction/tree-sitter-types.js.map +1 -0
  272. package/dist/extraction/tree-sitter.d.ts +766 -0
  273. package/dist/extraction/tree-sitter.d.ts.map +1 -0
  274. package/dist/extraction/tree-sitter.js +7138 -0
  275. package/dist/extraction/tree-sitter.js.map +1 -0
  276. package/dist/extraction/vue-extractor.d.ts +51 -0
  277. package/dist/extraction/vue-extractor.d.ts.map +1 -0
  278. package/dist/extraction/vue-extractor.js +254 -0
  279. package/dist/extraction/vue-extractor.js.map +1 -0
  280. package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
  281. package/dist/extraction/wasm/tree-sitter-c.wasm +0 -0
  282. package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  283. package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  284. package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  285. package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  286. package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  287. package/dist/extraction/wasm/tree-sitter-cpp.wasm +0 -0
  288. package/dist/extraction/wasm/tree-sitter-dart.wasm +0 -0
  289. package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  290. package/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
  291. package/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
  292. package/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
  293. package/dist/extraction/wasm/tree-sitter-kotlin.wasm +0 -0
  294. package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
  295. package/dist/extraction/wasm/tree-sitter-luau.wasm +0 -0
  296. package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  297. package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  298. package/dist/extraction/wasm/tree-sitter-php.wasm +0 -0
  299. package/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
  300. package/dist/extraction/wasm/tree-sitter-r.wasm +0 -0
  301. package/dist/extraction/wasm/tree-sitter-ruby.wasm +0 -0
  302. package/dist/extraction/wasm/tree-sitter-rust.wasm +0 -0
  303. package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
  304. package/dist/extraction/wasm/tree-sitter-swift.wasm +0 -0
  305. package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
  306. package/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
  307. package/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
  308. package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  309. package/dist/extraction/wasm-runtime-flags.d.ts +53 -0
  310. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -0
  311. package/dist/extraction/wasm-runtime-flags.js +130 -0
  312. package/dist/extraction/wasm-runtime-flags.js.map +1 -0
  313. package/dist/graph/branch-guards.d.ts +248 -0
  314. package/dist/graph/branch-guards.d.ts.map +1 -0
  315. package/dist/graph/branch-guards.js +2231 -0
  316. package/dist/graph/branch-guards.js.map +1 -0
  317. package/dist/graph/dead-code.d.ts +251 -0
  318. package/dist/graph/dead-code.d.ts.map +1 -0
  319. package/dist/graph/dead-code.js +756 -0
  320. package/dist/graph/dead-code.js.map +1 -0
  321. package/dist/graph/dynamic-boundary-report.d.ts +121 -0
  322. package/dist/graph/dynamic-boundary-report.d.ts.map +1 -0
  323. package/dist/graph/dynamic-boundary-report.js +283 -0
  324. package/dist/graph/dynamic-boundary-report.js.map +1 -0
  325. package/dist/graph/index.d.ts +12 -0
  326. package/dist/graph/index.d.ts.map +1 -0
  327. package/dist/graph/index.js +28 -0
  328. package/dist/graph/index.js.map +1 -0
  329. package/dist/graph/named-symbol-flow.d.ts +125 -0
  330. package/dist/graph/named-symbol-flow.d.ts.map +1 -0
  331. package/dist/graph/named-symbol-flow.js +552 -0
  332. package/dist/graph/named-symbol-flow.js.map +1 -0
  333. package/dist/graph/queries.d.ts +106 -0
  334. package/dist/graph/queries.d.ts.map +1 -0
  335. package/dist/graph/queries.js +341 -0
  336. package/dist/graph/queries.js.map +1 -0
  337. package/dist/graph/symbol-lookup.d.ts +83 -0
  338. package/dist/graph/symbol-lookup.d.ts.map +1 -0
  339. package/dist/graph/symbol-lookup.js +181 -0
  340. package/dist/graph/symbol-lookup.js.map +1 -0
  341. package/dist/graph/traversal.d.ts +127 -0
  342. package/dist/graph/traversal.d.ts.map +1 -0
  343. package/dist/graph/traversal.js +599 -0
  344. package/dist/graph/traversal.js.map +1 -0
  345. package/dist/graph/type-hierarchy.d.ts +155 -0
  346. package/dist/graph/type-hierarchy.d.ts.map +1 -0
  347. package/dist/graph/type-hierarchy.js +382 -0
  348. package/dist/graph/type-hierarchy.js.map +1 -0
  349. package/dist/index.d.ts +874 -0
  350. package/dist/index.d.ts.map +1 -0
  351. package/dist/index.js +2005 -0
  352. package/dist/index.js.map +1 -0
  353. package/dist/installer/config-writer.d.ts +28 -0
  354. package/dist/installer/config-writer.d.ts.map +1 -0
  355. package/dist/installer/config-writer.js +91 -0
  356. package/dist/installer/config-writer.js.map +1 -0
  357. package/dist/installer/index.d.ts +143 -0
  358. package/dist/installer/index.d.ts.map +1 -0
  359. package/dist/installer/index.js +621 -0
  360. package/dist/installer/index.js.map +1 -0
  361. package/dist/installer/instructions-template.d.ts +41 -0
  362. package/dist/installer/instructions-template.d.ts.map +1 -0
  363. package/dist/installer/instructions-template.js +53 -0
  364. package/dist/installer/instructions-template.js.map +1 -0
  365. package/dist/installer/targets/antigravity.d.ts +57 -0
  366. package/dist/installer/targets/antigravity.d.ts.map +1 -0
  367. package/dist/installer/targets/antigravity.js +308 -0
  368. package/dist/installer/targets/antigravity.js.map +1 -0
  369. package/dist/installer/targets/claude.d.ts +65 -0
  370. package/dist/installer/targets/claude.d.ts.map +1 -0
  371. package/dist/installer/targets/claude.js +514 -0
  372. package/dist/installer/targets/claude.js.map +1 -0
  373. package/dist/installer/targets/codex.d.ts +33 -0
  374. package/dist/installer/targets/codex.d.ts.map +1 -0
  375. package/dist/installer/targets/codex.js +212 -0
  376. package/dist/installer/targets/codex.js.map +1 -0
  377. package/dist/installer/targets/copilot-cli.d.ts +32 -0
  378. package/dist/installer/targets/copilot-cli.d.ts.map +1 -0
  379. package/dist/installer/targets/copilot-cli.js +215 -0
  380. package/dist/installer/targets/copilot-cli.js.map +1 -0
  381. package/dist/installer/targets/copilot-jetbrains.d.ts +42 -0
  382. package/dist/installer/targets/copilot-jetbrains.d.ts.map +1 -0
  383. package/dist/installer/targets/copilot-jetbrains.js +238 -0
  384. package/dist/installer/targets/copilot-jetbrains.js.map +1 -0
  385. package/dist/installer/targets/copilot-vscode.d.ts +47 -0
  386. package/dist/installer/targets/copilot-vscode.d.ts.map +1 -0
  387. package/dist/installer/targets/copilot-vscode.js +219 -0
  388. package/dist/installer/targets/copilot-vscode.js.map +1 -0
  389. package/dist/installer/targets/cursor.d.ts +35 -0
  390. package/dist/installer/targets/cursor.d.ts.map +1 -0
  391. package/dist/installer/targets/cursor.js +254 -0
  392. package/dist/installer/targets/cursor.js.map +1 -0
  393. package/dist/installer/targets/gemini.d.ts +26 -0
  394. package/dist/installer/targets/gemini.d.ts.map +1 -0
  395. package/dist/installer/targets/gemini.js +165 -0
  396. package/dist/installer/targets/gemini.js.map +1 -0
  397. package/dist/installer/targets/hermes.d.ts +18 -0
  398. package/dist/installer/targets/hermes.d.ts.map +1 -0
  399. package/dist/installer/targets/hermes.js +359 -0
  400. package/dist/installer/targets/hermes.js.map +1 -0
  401. package/dist/installer/targets/kiro.d.ts +27 -0
  402. package/dist/installer/targets/kiro.d.ts.map +1 -0
  403. package/dist/installer/targets/kiro.js +178 -0
  404. package/dist/installer/targets/kiro.js.map +1 -0
  405. package/dist/installer/targets/opencode.d.ts +49 -0
  406. package/dist/installer/targets/opencode.d.ts.map +1 -0
  407. package/dist/installer/targets/opencode.js +339 -0
  408. package/dist/installer/targets/opencode.js.map +1 -0
  409. package/dist/installer/targets/registry.d.ts +35 -0
  410. package/dist/installer/targets/registry.d.ts.map +1 -0
  411. package/dist/installer/targets/registry.js +97 -0
  412. package/dist/installer/targets/registry.js.map +1 -0
  413. package/dist/installer/targets/shared.d.ts +101 -0
  414. package/dist/installer/targets/shared.d.ts.map +1 -0
  415. package/dist/installer/targets/shared.js +264 -0
  416. package/dist/installer/targets/shared.js.map +1 -0
  417. package/dist/installer/targets/toml.d.ts +53 -0
  418. package/dist/installer/targets/toml.d.ts.map +1 -0
  419. package/dist/installer/targets/toml.js +226 -0
  420. package/dist/installer/targets/toml.js.map +1 -0
  421. package/dist/installer/targets/types.d.ts +108 -0
  422. package/dist/installer/targets/types.d.ts.map +1 -0
  423. package/dist/installer/targets/types.js +16 -0
  424. package/dist/installer/targets/types.js.map +1 -0
  425. package/dist/mcp/daemon-manager.d.ts +42 -0
  426. package/dist/mcp/daemon-manager.d.ts.map +1 -0
  427. package/dist/mcp/daemon-manager.js +141 -0
  428. package/dist/mcp/daemon-manager.js.map +1 -0
  429. package/dist/mcp/daemon-paths.d.ts +81 -0
  430. package/dist/mcp/daemon-paths.d.ts.map +1 -0
  431. package/dist/mcp/daemon-paths.js +228 -0
  432. package/dist/mcp/daemon-paths.js.map +1 -0
  433. package/dist/mcp/daemon-registry.d.ts +57 -0
  434. package/dist/mcp/daemon-registry.d.ts.map +1 -0
  435. package/dist/mcp/daemon-registry.js +323 -0
  436. package/dist/mcp/daemon-registry.js.map +1 -0
  437. package/dist/mcp/daemon.d.ts +294 -0
  438. package/dist/mcp/daemon.d.ts.map +1 -0
  439. package/dist/mcp/daemon.js +897 -0
  440. package/dist/mcp/daemon.js.map +1 -0
  441. package/dist/mcp/dynamic-boundaries.d.ts +32 -0
  442. package/dist/mcp/dynamic-boundaries.d.ts.map +1 -0
  443. package/dist/mcp/dynamic-boundaries.js +323 -0
  444. package/dist/mcp/dynamic-boundaries.js.map +1 -0
  445. package/dist/mcp/early-ppid.d.ts +26 -0
  446. package/dist/mcp/early-ppid.d.ts.map +1 -0
  447. package/dist/mcp/early-ppid.js +29 -0
  448. package/dist/mcp/early-ppid.js.map +1 -0
  449. package/dist/mcp/engine.d.ts +133 -0
  450. package/dist/mcp/engine.d.ts.map +1 -0
  451. package/dist/mcp/engine.js +421 -0
  452. package/dist/mcp/engine.js.map +1 -0
  453. package/dist/mcp/explore-dedup.d.ts +140 -0
  454. package/dist/mcp/explore-dedup.d.ts.map +1 -0
  455. package/dist/mcp/explore-dedup.js +239 -0
  456. package/dist/mcp/explore-dedup.js.map +1 -0
  457. package/dist/mcp/explore-diagnostics.d.ts +289 -0
  458. package/dist/mcp/explore-diagnostics.d.ts.map +1 -0
  459. package/dist/mcp/explore-diagnostics.js +558 -0
  460. package/dist/mcp/explore-diagnostics.js.map +1 -0
  461. package/dist/mcp/explore-session-state.d.ts +217 -0
  462. package/dist/mcp/explore-session-state.d.ts.map +1 -0
  463. package/dist/mcp/explore-session-state.js +322 -0
  464. package/dist/mcp/explore-session-state.js.map +1 -0
  465. package/dist/mcp/index.d.ts +139 -0
  466. package/dist/mcp/index.d.ts.map +1 -0
  467. package/dist/mcp/index.js +636 -0
  468. package/dist/mcp/index.js.map +1 -0
  469. package/dist/mcp/liveness-watchdog.d.ts +35 -0
  470. package/dist/mcp/liveness-watchdog.d.ts.map +1 -0
  471. package/dist/mcp/liveness-watchdog.js +269 -0
  472. package/dist/mcp/liveness-watchdog.js.map +1 -0
  473. package/dist/mcp/ppid-watchdog.d.ts +62 -0
  474. package/dist/mcp/ppid-watchdog.d.ts.map +1 -0
  475. package/dist/mcp/ppid-watchdog.js +64 -0
  476. package/dist/mcp/ppid-watchdog.js.map +1 -0
  477. package/dist/mcp/proxy.d.ts +87 -0
  478. package/dist/mcp/proxy.d.ts.map +1 -0
  479. package/dist/mcp/proxy.js +672 -0
  480. package/dist/mcp/proxy.js.map +1 -0
  481. package/dist/mcp/query-pool.d.ts +108 -0
  482. package/dist/mcp/query-pool.d.ts.map +1 -0
  483. package/dist/mcp/query-pool.js +315 -0
  484. package/dist/mcp/query-pool.js.map +1 -0
  485. package/dist/mcp/query-worker.d.ts +24 -0
  486. package/dist/mcp/query-worker.d.ts.map +1 -0
  487. package/dist/mcp/query-worker.js +87 -0
  488. package/dist/mcp/query-worker.js.map +1 -0
  489. package/dist/mcp/server-instructions.d.ts +34 -0
  490. package/dist/mcp/server-instructions.d.ts.map +1 -0
  491. package/dist/mcp/server-instructions.js +113 -0
  492. package/dist/mcp/server-instructions.js.map +1 -0
  493. package/dist/mcp/session.d.ts +109 -0
  494. package/dist/mcp/session.d.ts.map +1 -0
  495. package/dist/mcp/session.js +380 -0
  496. package/dist/mcp/session.js.map +1 -0
  497. package/dist/mcp/startup-handshake.d.ts +44 -0
  498. package/dist/mcp/startup-handshake.d.ts.map +1 -0
  499. package/dist/mcp/startup-handshake.js +73 -0
  500. package/dist/mcp/startup-handshake.js.map +1 -0
  501. package/dist/mcp/stdin-teardown.d.ts +27 -0
  502. package/dist/mcp/stdin-teardown.d.ts.map +1 -0
  503. package/dist/mcp/stdin-teardown.js +49 -0
  504. package/dist/mcp/stdin-teardown.js.map +1 -0
  505. package/dist/mcp/tools.d.ts +936 -0
  506. package/dist/mcp/tools.d.ts.map +1 -0
  507. package/dist/mcp/tools.js +6611 -0
  508. package/dist/mcp/tools.js.map +1 -0
  509. package/dist/mcp/transport.d.ts +188 -0
  510. package/dist/mcp/transport.d.ts.map +1 -0
  511. package/dist/mcp/transport.js +377 -0
  512. package/dist/mcp/transport.js.map +1 -0
  513. package/dist/mcp/version.d.ts +19 -0
  514. package/dist/mcp/version.d.ts.map +1 -0
  515. package/dist/mcp/version.js +71 -0
  516. package/dist/mcp/version.js.map +1 -0
  517. package/dist/mcp/writer-lock.d.ts +47 -0
  518. package/dist/mcp/writer-lock.d.ts.map +1 -0
  519. package/dist/mcp/writer-lock.js +218 -0
  520. package/dist/mcp/writer-lock.js.map +1 -0
  521. package/dist/project-config.d.ts +117 -0
  522. package/dist/project-config.d.ts.map +1 -0
  523. package/dist/project-config.js +409 -0
  524. package/dist/project-config.js.map +1 -0
  525. package/dist/resolution/alias-binding.d.ts +56 -0
  526. package/dist/resolution/alias-binding.d.ts.map +1 -0
  527. package/dist/resolution/alias-binding.js +118 -0
  528. package/dist/resolution/alias-binding.js.map +1 -0
  529. package/dist/resolution/c-fnptr-synthesizer.d.ts +6 -0
  530. package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -0
  531. package/dist/resolution/c-fnptr-synthesizer.js +1506 -0
  532. package/dist/resolution/c-fnptr-synthesizer.js.map +1 -0
  533. package/dist/resolution/callback-synthesizer.d.ts +78 -0
  534. package/dist/resolution/callback-synthesizer.d.ts.map +1 -0
  535. package/dist/resolution/callback-synthesizer.js +4105 -0
  536. package/dist/resolution/callback-synthesizer.js.map +1 -0
  537. package/dist/resolution/cooperative-yield.d.ts +40 -0
  538. package/dist/resolution/cooperative-yield.d.ts.map +1 -0
  539. package/dist/resolution/cooperative-yield.js +44 -0
  540. package/dist/resolution/cooperative-yield.js.map +1 -0
  541. package/dist/resolution/expo-router-synthesizer.d.ts +32 -0
  542. package/dist/resolution/expo-router-synthesizer.d.ts.map +1 -0
  543. package/dist/resolution/expo-router-synthesizer.js +189 -0
  544. package/dist/resolution/expo-router-synthesizer.js.map +1 -0
  545. package/dist/resolution/frameworks/astro.d.ts +9 -0
  546. package/dist/resolution/frameworks/astro.d.ts.map +1 -0
  547. package/dist/resolution/frameworks/astro.js +169 -0
  548. package/dist/resolution/frameworks/astro.js.map +1 -0
  549. package/dist/resolution/frameworks/cargo-workspace.d.ts +18 -0
  550. package/dist/resolution/frameworks/cargo-workspace.d.ts.map +1 -0
  551. package/dist/resolution/frameworks/cargo-workspace.js +225 -0
  552. package/dist/resolution/frameworks/cargo-workspace.js.map +1 -0
  553. package/dist/resolution/frameworks/cics.d.ts +20 -0
  554. package/dist/resolution/frameworks/cics.d.ts.map +1 -0
  555. package/dist/resolution/frameworks/cics.js +90 -0
  556. package/dist/resolution/frameworks/cics.js.map +1 -0
  557. package/dist/resolution/frameworks/csharp.d.ts +8 -0
  558. package/dist/resolution/frameworks/csharp.d.ts.map +1 -0
  559. package/dist/resolution/frameworks/csharp.js +335 -0
  560. package/dist/resolution/frameworks/csharp.js.map +1 -0
  561. package/dist/resolution/frameworks/drupal.d.ts +51 -0
  562. package/dist/resolution/frameworks/drupal.d.ts.map +1 -0
  563. package/dist/resolution/frameworks/drupal.js +367 -0
  564. package/dist/resolution/frameworks/drupal.js.map +1 -0
  565. package/dist/resolution/frameworks/expo-modules.d.ts +3 -0
  566. package/dist/resolution/frameworks/expo-modules.d.ts.map +1 -0
  567. package/dist/resolution/frameworks/expo-modules.js +148 -0
  568. package/dist/resolution/frameworks/expo-modules.js.map +1 -0
  569. package/dist/resolution/frameworks/expo-router.d.ts +189 -0
  570. package/dist/resolution/frameworks/expo-router.d.ts.map +1 -0
  571. package/dist/resolution/frameworks/expo-router.js +785 -0
  572. package/dist/resolution/frameworks/expo-router.js.map +1 -0
  573. package/dist/resolution/frameworks/express.d.ts +8 -0
  574. package/dist/resolution/frameworks/express.d.ts.map +1 -0
  575. package/dist/resolution/frameworks/express.js +540 -0
  576. package/dist/resolution/frameworks/express.js.map +1 -0
  577. package/dist/resolution/frameworks/fabric.d.ts +3 -0
  578. package/dist/resolution/frameworks/fabric.d.ts.map +1 -0
  579. package/dist/resolution/frameworks/fabric.js +354 -0
  580. package/dist/resolution/frameworks/fabric.js.map +1 -0
  581. package/dist/resolution/frameworks/go.d.ts +8 -0
  582. package/dist/resolution/frameworks/go.d.ts.map +1 -0
  583. package/dist/resolution/frameworks/go.js +184 -0
  584. package/dist/resolution/frameworks/go.js.map +1 -0
  585. package/dist/resolution/frameworks/goframe.d.ts +41 -0
  586. package/dist/resolution/frameworks/goframe.d.ts.map +1 -0
  587. package/dist/resolution/frameworks/goframe.js +112 -0
  588. package/dist/resolution/frameworks/goframe.js.map +1 -0
  589. package/dist/resolution/frameworks/index.d.ts +55 -0
  590. package/dist/resolution/frameworks/index.d.ts.map +1 -0
  591. package/dist/resolution/frameworks/index.js +203 -0
  592. package/dist/resolution/frameworks/index.js.map +1 -0
  593. package/dist/resolution/frameworks/java.d.ts +8 -0
  594. package/dist/resolution/frameworks/java.d.ts.map +1 -0
  595. package/dist/resolution/frameworks/java.js +539 -0
  596. package/dist/resolution/frameworks/java.js.map +1 -0
  597. package/dist/resolution/frameworks/laravel.d.ts +13 -0
  598. package/dist/resolution/frameworks/laravel.d.ts.map +1 -0
  599. package/dist/resolution/frameworks/laravel.js +257 -0
  600. package/dist/resolution/frameworks/laravel.js.map +1 -0
  601. package/dist/resolution/frameworks/nestjs.d.ts +26 -0
  602. package/dist/resolution/frameworks/nestjs.d.ts.map +1 -0
  603. package/dist/resolution/frameworks/nestjs.js +690 -0
  604. package/dist/resolution/frameworks/nestjs.js.map +1 -0
  605. package/dist/resolution/frameworks/nextjs.d.ts +76 -0
  606. package/dist/resolution/frameworks/nextjs.d.ts.map +1 -0
  607. package/dist/resolution/frameworks/nextjs.js +328 -0
  608. package/dist/resolution/frameworks/nextjs.js.map +1 -0
  609. package/dist/resolution/frameworks/object-literal.d.ts +32 -0
  610. package/dist/resolution/frameworks/object-literal.d.ts.map +1 -0
  611. package/dist/resolution/frameworks/object-literal.js +139 -0
  612. package/dist/resolution/frameworks/object-literal.js.map +1 -0
  613. package/dist/resolution/frameworks/package-deps.d.ts +14 -0
  614. package/dist/resolution/frameworks/package-deps.d.ts.map +1 -0
  615. package/dist/resolution/frameworks/package-deps.js +78 -0
  616. package/dist/resolution/frameworks/package-deps.js.map +1 -0
  617. package/dist/resolution/frameworks/play.d.ts +19 -0
  618. package/dist/resolution/frameworks/play.d.ts.map +1 -0
  619. package/dist/resolution/frameworks/play.js +111 -0
  620. package/dist/resolution/frameworks/play.js.map +1 -0
  621. package/dist/resolution/frameworks/python.d.ts +10 -0
  622. package/dist/resolution/frameworks/python.d.ts.map +1 -0
  623. package/dist/resolution/frameworks/python.js +566 -0
  624. package/dist/resolution/frameworks/python.js.map +1 -0
  625. package/dist/resolution/frameworks/react-native.d.ts +29 -0
  626. package/dist/resolution/frameworks/react-native.d.ts.map +1 -0
  627. package/dist/resolution/frameworks/react-native.js +549 -0
  628. package/dist/resolution/frameworks/react-native.js.map +1 -0
  629. package/dist/resolution/frameworks/react-router.d.ts +46 -0
  630. package/dist/resolution/frameworks/react-router.d.ts.map +1 -0
  631. package/dist/resolution/frameworks/react-router.js +187 -0
  632. package/dist/resolution/frameworks/react-router.js.map +1 -0
  633. package/dist/resolution/frameworks/react.d.ts +9 -0
  634. package/dist/resolution/frameworks/react.d.ts.map +1 -0
  635. package/dist/resolution/frameworks/react.js +257 -0
  636. package/dist/resolution/frameworks/react.js.map +1 -0
  637. package/dist/resolution/frameworks/ruby.d.ts +8 -0
  638. package/dist/resolution/frameworks/ruby.d.ts.map +1 -0
  639. package/dist/resolution/frameworks/ruby.js +302 -0
  640. package/dist/resolution/frameworks/ruby.js.map +1 -0
  641. package/dist/resolution/frameworks/rust.d.ts +8 -0
  642. package/dist/resolution/frameworks/rust.d.ts.map +1 -0
  643. package/dist/resolution/frameworks/rust.js +304 -0
  644. package/dist/resolution/frameworks/rust.js.map +1 -0
  645. package/dist/resolution/frameworks/svelte.d.ts +9 -0
  646. package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
  647. package/dist/resolution/frameworks/svelte.js +258 -0
  648. package/dist/resolution/frameworks/svelte.js.map +1 -0
  649. package/dist/resolution/frameworks/sveltekit-router.d.ts +36 -0
  650. package/dist/resolution/frameworks/sveltekit-router.d.ts.map +1 -0
  651. package/dist/resolution/frameworks/sveltekit-router.js +156 -0
  652. package/dist/resolution/frameworks/sveltekit-router.js.map +1 -0
  653. package/dist/resolution/frameworks/swift-objc.d.ts +37 -0
  654. package/dist/resolution/frameworks/swift-objc.d.ts.map +1 -0
  655. package/dist/resolution/frameworks/swift-objc.js +252 -0
  656. package/dist/resolution/frameworks/swift-objc.js.map +1 -0
  657. package/dist/resolution/frameworks/swift.d.ts +10 -0
  658. package/dist/resolution/frameworks/swift.d.ts.map +1 -0
  659. package/dist/resolution/frameworks/swift.js +410 -0
  660. package/dist/resolution/frameworks/swift.js.map +1 -0
  661. package/dist/resolution/frameworks/tanstack-router.d.ts +84 -0
  662. package/dist/resolution/frameworks/tanstack-router.d.ts.map +1 -0
  663. package/dist/resolution/frameworks/tanstack-router.js +437 -0
  664. package/dist/resolution/frameworks/tanstack-router.js.map +1 -0
  665. package/dist/resolution/frameworks/terraform.d.ts +38 -0
  666. package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  667. package/dist/resolution/frameworks/terraform.js +277 -0
  668. package/dist/resolution/frameworks/terraform.js.map +1 -0
  669. package/dist/resolution/frameworks/vue-router.d.ts +82 -0
  670. package/dist/resolution/frameworks/vue-router.d.ts.map +1 -0
  671. package/dist/resolution/frameworks/vue-router.js +377 -0
  672. package/dist/resolution/frameworks/vue-router.js.map +1 -0
  673. package/dist/resolution/frameworks/vue.d.ts +9 -0
  674. package/dist/resolution/frameworks/vue.d.ts.map +1 -0
  675. package/dist/resolution/frameworks/vue.js +303 -0
  676. package/dist/resolution/frameworks/vue.js.map +1 -0
  677. package/dist/resolution/go-module.d.ts +26 -0
  678. package/dist/resolution/go-module.d.ts.map +1 -0
  679. package/dist/resolution/go-module.js +78 -0
  680. package/dist/resolution/go-module.js.map +1 -0
  681. package/dist/resolution/goframe-synthesizer.d.ts +29 -0
  682. package/dist/resolution/goframe-synthesizer.d.ts.map +1 -0
  683. package/dist/resolution/goframe-synthesizer.js +163 -0
  684. package/dist/resolution/goframe-synthesizer.js.map +1 -0
  685. package/dist/resolution/import-resolver.d.ts +122 -0
  686. package/dist/resolution/import-resolver.d.ts.map +1 -0
  687. package/dist/resolution/import-resolver.js +2490 -0
  688. package/dist/resolution/import-resolver.js.map +1 -0
  689. package/dist/resolution/index.d.ts +402 -0
  690. package/dist/resolution/index.d.ts.map +1 -0
  691. package/dist/resolution/index.js +2581 -0
  692. package/dist/resolution/index.js.map +1 -0
  693. package/dist/resolution/js-builtins.d.ts +11 -0
  694. package/dist/resolution/js-builtins.d.ts.map +1 -0
  695. package/dist/resolution/js-builtins.js +23 -0
  696. package/dist/resolution/js-builtins.js.map +1 -0
  697. package/dist/resolution/lru-cache.d.ts +24 -0
  698. package/dist/resolution/lru-cache.d.ts.map +1 -0
  699. package/dist/resolution/lru-cache.js +62 -0
  700. package/dist/resolution/lru-cache.js.map +1 -0
  701. package/dist/resolution/memory-budget.d.ts +48 -0
  702. package/dist/resolution/memory-budget.d.ts.map +1 -0
  703. package/dist/resolution/memory-budget.js +162 -0
  704. package/dist/resolution/memory-budget.js.map +1 -0
  705. package/dist/resolution/name-matcher.d.ts +196 -0
  706. package/dist/resolution/name-matcher.d.ts.map +1 -0
  707. package/dist/resolution/name-matcher.js +3484 -0
  708. package/dist/resolution/name-matcher.js.map +1 -0
  709. package/dist/resolution/next-router-synthesizer.d.ts +27 -0
  710. package/dist/resolution/next-router-synthesizer.d.ts.map +1 -0
  711. package/dist/resolution/next-router-synthesizer.js +113 -0
  712. package/dist/resolution/next-router-synthesizer.js.map +1 -0
  713. package/dist/resolution/path-aliases.d.ts +70 -0
  714. package/dist/resolution/path-aliases.d.ts.map +1 -0
  715. package/dist/resolution/path-aliases.js +346 -0
  716. package/dist/resolution/path-aliases.js.map +1 -0
  717. package/dist/resolution/react-router-synthesizer.d.ts +33 -0
  718. package/dist/resolution/react-router-synthesizer.d.ts.map +1 -0
  719. package/dist/resolution/react-router-synthesizer.js +126 -0
  720. package/dist/resolution/react-router-synthesizer.js.map +1 -0
  721. package/dist/resolution/resolver-pool.d.ts +106 -0
  722. package/dist/resolution/resolver-pool.d.ts.map +1 -0
  723. package/dist/resolution/resolver-pool.js +344 -0
  724. package/dist/resolution/resolver-pool.js.map +1 -0
  725. package/dist/resolution/resolver-worker.d.ts +17 -0
  726. package/dist/resolution/resolver-worker.d.ts.map +1 -0
  727. package/dist/resolution/resolver-worker.js +141 -0
  728. package/dist/resolution/resolver-worker.js.map +1 -0
  729. package/dist/resolution/strip-comments.d.ts +33 -0
  730. package/dist/resolution/strip-comments.d.ts.map +1 -0
  731. package/dist/resolution/strip-comments.js +550 -0
  732. package/dist/resolution/strip-comments.js.map +1 -0
  733. package/dist/resolution/sveltekit-synthesizer.d.ts +58 -0
  734. package/dist/resolution/sveltekit-synthesizer.d.ts.map +1 -0
  735. package/dist/resolution/sveltekit-synthesizer.js +173 -0
  736. package/dist/resolution/sveltekit-synthesizer.js.map +1 -0
  737. package/dist/resolution/swift-objc-bridge.d.ts +134 -0
  738. package/dist/resolution/swift-objc-bridge.d.ts.map +1 -0
  739. package/dist/resolution/swift-objc-bridge.js +256 -0
  740. package/dist/resolution/swift-objc-bridge.js.map +1 -0
  741. package/dist/resolution/synth-utils.d.ts +26 -0
  742. package/dist/resolution/synth-utils.d.ts.map +1 -0
  743. package/dist/resolution/synth-utils.js +75 -0
  744. package/dist/resolution/synth-utils.js.map +1 -0
  745. package/dist/resolution/tanstack-router-synthesizer.d.ts +31 -0
  746. package/dist/resolution/tanstack-router-synthesizer.d.ts.map +1 -0
  747. package/dist/resolution/tanstack-router-synthesizer.js +114 -0
  748. package/dist/resolution/tanstack-router-synthesizer.js.map +1 -0
  749. package/dist/resolution/tier-synthesizer.d.ts +48 -0
  750. package/dist/resolution/tier-synthesizer.d.ts.map +1 -0
  751. package/dist/resolution/tier-synthesizer.js +907 -0
  752. package/dist/resolution/tier-synthesizer.js.map +1 -0
  753. package/dist/resolution/types.d.ts +323 -0
  754. package/dist/resolution/types.d.ts.map +1 -0
  755. package/dist/resolution/types.js +58 -0
  756. package/dist/resolution/types.js.map +1 -0
  757. package/dist/resolution/vue-router-synthesizer.d.ts +30 -0
  758. package/dist/resolution/vue-router-synthesizer.d.ts.map +1 -0
  759. package/dist/resolution/vue-router-synthesizer.js +115 -0
  760. package/dist/resolution/vue-router-synthesizer.js.map +1 -0
  761. package/dist/resolution/workspace-packages.d.ts +58 -0
  762. package/dist/resolution/workspace-packages.d.ts.map +1 -0
  763. package/dist/resolution/workspace-packages.js +346 -0
  764. package/dist/resolution/workspace-packages.js.map +1 -0
  765. package/dist/search/identifier-segments.d.ts +70 -0
  766. package/dist/search/identifier-segments.d.ts.map +1 -0
  767. package/dist/search/identifier-segments.js +205 -0
  768. package/dist/search/identifier-segments.js.map +1 -0
  769. package/dist/search/query-parser.d.ts +57 -0
  770. package/dist/search/query-parser.d.ts.map +1 -0
  771. package/dist/search/query-parser.js +177 -0
  772. package/dist/search/query-parser.js.map +1 -0
  773. package/dist/search/query-paths.d.ts +57 -0
  774. package/dist/search/query-paths.d.ts.map +1 -0
  775. package/dist/search/query-paths.js +298 -0
  776. package/dist/search/query-paths.js.map +1 -0
  777. package/dist/search/query-utils.d.ts +102 -0
  778. package/dist/search/query-utils.d.ts.map +1 -0
  779. package/dist/search/query-utils.js +487 -0
  780. package/dist/search/query-utils.js.map +1 -0
  781. package/dist/sync/git-hooks.d.ts +45 -0
  782. package/dist/sync/git-hooks.d.ts.map +1 -0
  783. package/dist/sync/git-hooks.js +227 -0
  784. package/dist/sync/git-hooks.js.map +1 -0
  785. package/dist/sync/index.d.ts +19 -0
  786. package/dist/sync/index.d.ts.map +1 -0
  787. package/dist/sync/index.js +35 -0
  788. package/dist/sync/index.js.map +1 -0
  789. package/dist/sync/watch-policy.d.ts +48 -0
  790. package/dist/sync/watch-policy.d.ts.map +1 -0
  791. package/dist/sync/watch-policy.js +124 -0
  792. package/dist/sync/watch-policy.js.map +1 -0
  793. package/dist/sync/watcher.d.ts +402 -0
  794. package/dist/sync/watcher.d.ts.map +1 -0
  795. package/dist/sync/watcher.js +978 -0
  796. package/dist/sync/watcher.js.map +1 -0
  797. package/dist/sync/worktree.d.ts +63 -0
  798. package/dist/sync/worktree.d.ts.map +1 -0
  799. package/dist/sync/worktree.js +182 -0
  800. package/dist/sync/worktree.js.map +1 -0
  801. package/dist/telemetry/index.d.ts +145 -0
  802. package/dist/telemetry/index.d.ts.map +1 -0
  803. package/dist/telemetry/index.js +592 -0
  804. package/dist/telemetry/index.js.map +1 -0
  805. package/dist/types.d.ts +473 -0
  806. package/dist/types.d.ts.map +1 -0
  807. package/dist/types.js +118 -0
  808. package/dist/types.js.map +1 -0
  809. package/dist/ui/color.d.ts +21 -0
  810. package/dist/ui/color.d.ts.map +1 -0
  811. package/dist/ui/color.js +42 -0
  812. package/dist/ui/color.js.map +1 -0
  813. package/dist/ui/glyphs.d.ts +67 -0
  814. package/dist/ui/glyphs.d.ts.map +1 -0
  815. package/dist/ui/glyphs.js +125 -0
  816. package/dist/ui/glyphs.js.map +1 -0
  817. package/dist/ui/shimmer-progress.d.ts +11 -0
  818. package/dist/ui/shimmer-progress.d.ts.map +1 -0
  819. package/dist/ui/shimmer-progress.js +164 -0
  820. package/dist/ui/shimmer-progress.js.map +1 -0
  821. package/dist/ui/shimmer-worker.d.ts +2 -0
  822. package/dist/ui/shimmer-worker.d.ts.map +1 -0
  823. package/dist/ui/shimmer-worker.js +126 -0
  824. package/dist/ui/shimmer-worker.js.map +1 -0
  825. package/dist/ui/types.d.ts +15 -0
  826. package/dist/ui/types.d.ts.map +1 -0
  827. package/dist/ui/types.js +3 -0
  828. package/dist/ui/types.js.map +1 -0
  829. package/dist/ui-server/api/deadcode.d.ts +84 -0
  830. package/dist/ui-server/api/deadcode.d.ts.map +1 -0
  831. package/dist/ui-server/api/deadcode.js +136 -0
  832. package/dist/ui-server/api/deadcode.js.map +1 -0
  833. package/dist/ui-server/api/effects.d.ts +87 -0
  834. package/dist/ui-server/api/effects.d.ts.map +1 -0
  835. package/dist/ui-server/api/effects.js +499 -0
  836. package/dist/ui-server/api/effects.js.map +1 -0
  837. package/dist/ui-server/api/entrypoints.d.ts +86 -0
  838. package/dist/ui-server/api/entrypoints.d.ts.map +1 -0
  839. package/dist/ui-server/api/entrypoints.js +279 -0
  840. package/dist/ui-server/api/entrypoints.js.map +1 -0
  841. package/dist/ui-server/api/events.d.ts +183 -0
  842. package/dist/ui-server/api/events.d.ts.map +1 -0
  843. package/dist/ui-server/api/events.js +430 -0
  844. package/dist/ui-server/api/events.js.map +1 -0
  845. package/dist/ui-server/api/file.d.ts +66 -0
  846. package/dist/ui-server/api/file.d.ts.map +1 -0
  847. package/dist/ui-server/api/file.js +235 -0
  848. package/dist/ui-server/api/file.js.map +1 -0
  849. package/dist/ui-server/api/filecode.d.ts +126 -0
  850. package/dist/ui-server/api/filecode.d.ts.map +1 -0
  851. package/dist/ui-server/api/filecode.js +205 -0
  852. package/dist/ui-server/api/filecode.js.map +1 -0
  853. package/dist/ui-server/api/flow.d.ts +226 -0
  854. package/dist/ui-server/api/flow.d.ts.map +1 -0
  855. package/dist/ui-server/api/flow.js +611 -0
  856. package/dist/ui-server/api/flow.js.map +1 -0
  857. package/dist/ui-server/api/hierarchy.d.ts +81 -0
  858. package/dist/ui-server/api/hierarchy.d.ts.map +1 -0
  859. package/dist/ui-server/api/hierarchy.js +92 -0
  860. package/dist/ui-server/api/hierarchy.js.map +1 -0
  861. package/dist/ui-server/api/index.d.ts +90 -0
  862. package/dist/ui-server/api/index.d.ts.map +1 -0
  863. package/dist/ui-server/api/index.js +332 -0
  864. package/dist/ui-server/api/index.js.map +1 -0
  865. package/dist/ui-server/api/map.d.ts +238 -0
  866. package/dist/ui-server/api/map.d.ts.map +1 -0
  867. package/dist/ui-server/api/map.js +681 -0
  868. package/dist/ui-server/api/map.js.map +1 -0
  869. package/dist/ui-server/api/node.d.ts +81 -0
  870. package/dist/ui-server/api/node.d.ts.map +1 -0
  871. package/dist/ui-server/api/node.js +388 -0
  872. package/dist/ui-server/api/node.js.map +1 -0
  873. package/dist/ui-server/api/nodes.d.ts +25 -0
  874. package/dist/ui-server/api/nodes.d.ts.map +1 -0
  875. package/dist/ui-server/api/nodes.js +45 -0
  876. package/dist/ui-server/api/nodes.js.map +1 -0
  877. package/dist/ui-server/api/program.d.ts +139 -0
  878. package/dist/ui-server/api/program.d.ts.map +1 -0
  879. package/dist/ui-server/api/program.js +315 -0
  880. package/dist/ui-server/api/program.js.map +1 -0
  881. package/dist/ui-server/api/respond.d.ts +76 -0
  882. package/dist/ui-server/api/respond.d.ts.map +1 -0
  883. package/dist/ui-server/api/respond.js +186 -0
  884. package/dist/ui-server/api/respond.js.map +1 -0
  885. package/dist/ui-server/api/route-roots.d.ts +39 -0
  886. package/dist/ui-server/api/route-roots.d.ts.map +1 -0
  887. package/dist/ui-server/api/route-roots.js +96 -0
  888. package/dist/ui-server/api/route-roots.js.map +1 -0
  889. package/dist/ui-server/api/routes.d.ts +59 -0
  890. package/dist/ui-server/api/routes.d.ts.map +1 -0
  891. package/dist/ui-server/api/routes.js +112 -0
  892. package/dist/ui-server/api/routes.js.map +1 -0
  893. package/dist/ui-server/api/screens.d.ts +117 -0
  894. package/dist/ui-server/api/screens.d.ts.map +1 -0
  895. package/dist/ui-server/api/screens.js +611 -0
  896. package/dist/ui-server/api/screens.js.map +1 -0
  897. package/dist/ui-server/api/search.d.ts +29 -0
  898. package/dist/ui-server/api/search.d.ts.map +1 -0
  899. package/dist/ui-server/api/search.js +232 -0
  900. package/dist/ui-server/api/search.js.map +1 -0
  901. package/dist/ui-server/api/session.d.ts +52 -0
  902. package/dist/ui-server/api/session.d.ts.map +1 -0
  903. package/dist/ui-server/api/session.js +199 -0
  904. package/dist/ui-server/api/session.js.map +1 -0
  905. package/dist/ui-server/api/source.d.ts +200 -0
  906. package/dist/ui-server/api/source.d.ts.map +1 -0
  907. package/dist/ui-server/api/source.js +419 -0
  908. package/dist/ui-server/api/source.js.map +1 -0
  909. package/dist/ui-server/api/stats.d.ts +27 -0
  910. package/dist/ui-server/api/stats.d.ts.map +1 -0
  911. package/dist/ui-server/api/stats.js +164 -0
  912. package/dist/ui-server/api/stats.js.map +1 -0
  913. package/dist/ui-server/api/steps.d.ts +274 -0
  914. package/dist/ui-server/api/steps.d.ts.map +1 -0
  915. package/dist/ui-server/api/steps.js +1446 -0
  916. package/dist/ui-server/api/steps.js.map +1 -0
  917. package/dist/ui-server/api/trail-store.d.ts +142 -0
  918. package/dist/ui-server/api/trail-store.d.ts.map +1 -0
  919. package/dist/ui-server/api/trail-store.js +323 -0
  920. package/dist/ui-server/api/trail-store.js.map +1 -0
  921. package/dist/ui-server/api/trails.d.ts +163 -0
  922. package/dist/ui-server/api/trails.d.ts.map +1 -0
  923. package/dist/ui-server/api/trails.js +369 -0
  924. package/dist/ui-server/api/trails.js.map +1 -0
  925. package/dist/ui-server/api/when.d.ts +125 -0
  926. package/dist/ui-server/api/when.d.ts.map +1 -0
  927. package/dist/ui-server/api/when.js +251 -0
  928. package/dist/ui-server/api/when.js.map +1 -0
  929. package/dist/ui-server/api/wire.d.ts +179 -0
  930. package/dist/ui-server/api/wire.d.ts.map +1 -0
  931. package/dist/ui-server/api/wire.js +231 -0
  932. package/dist/ui-server/api/wire.js.map +1 -0
  933. package/dist/ui-server/assets.d.ts +40 -0
  934. package/dist/ui-server/assets.d.ts.map +1 -0
  935. package/dist/ui-server/assets.js +110 -0
  936. package/dist/ui-server/assets.js.map +1 -0
  937. package/dist/ui-server/constants.d.ts +32 -0
  938. package/dist/ui-server/constants.d.ts.map +1 -0
  939. package/dist/ui-server/constants.js +35 -0
  940. package/dist/ui-server/constants.js.map +1 -0
  941. package/dist/ui-server/highlight/index.d.ts +113 -0
  942. package/dist/ui-server/highlight/index.d.ts.map +1 -0
  943. package/dist/ui-server/highlight/index.js +307 -0
  944. package/dist/ui-server/highlight/index.js.map +1 -0
  945. package/dist/ui-server/highlight/languages.d.ts +36 -0
  946. package/dist/ui-server/highlight/languages.d.ts.map +1 -0
  947. package/dist/ui-server/highlight/languages.js +50 -0
  948. package/dist/ui-server/highlight/languages.js.map +1 -0
  949. package/dist/ui-server/index.d.ts +92 -0
  950. package/dist/ui-server/index.d.ts.map +1 -0
  951. package/dist/ui-server/index.js +399 -0
  952. package/dist/ui-server/index.js.map +1 -0
  953. package/dist/ui-server/open-browser.d.ts +31 -0
  954. package/dist/ui-server/open-browser.d.ts.map +1 -0
  955. package/dist/ui-server/open-browser.js +79 -0
  956. package/dist/ui-server/open-browser.js.map +1 -0
  957. package/dist/ui-server/security.d.ts +127 -0
  958. package/dist/ui-server/security.d.ts.map +1 -0
  959. package/dist/ui-server/security.js +332 -0
  960. package/dist/ui-server/security.js.map +1 -0
  961. package/dist/ui-server/static.d.ts +45 -0
  962. package/dist/ui-server/static.d.ts.map +1 -0
  963. package/dist/ui-server/static.js +165 -0
  964. package/dist/ui-server/static.js.map +1 -0
  965. package/dist/upgrade/index.d.ts +164 -0
  966. package/dist/upgrade/index.d.ts.map +1 -0
  967. package/dist/upgrade/index.js +649 -0
  968. package/dist/upgrade/index.js.map +1 -0
  969. package/dist/upgrade/remove-binary.d.ts +87 -0
  970. package/dist/upgrade/remove-binary.d.ts.map +1 -0
  971. package/dist/upgrade/remove-binary.js +289 -0
  972. package/dist/upgrade/remove-binary.js.map +1 -0
  973. package/dist/upgrade/update-check.d.ts +92 -0
  974. package/dist/upgrade/update-check.d.ts.map +1 -0
  975. package/dist/upgrade/update-check.js +258 -0
  976. package/dist/upgrade/update-check.js.map +1 -0
  977. package/dist/utils.d.ts +238 -0
  978. package/dist/utils.d.ts.map +1 -0
  979. package/dist/utils.js +601 -0
  980. package/dist/utils.js.map +1 -0
  981. package/dist/viewer/assets/archivo-latin-ext-wght-normal-C4zznr8T.woff2 +0 -0
  982. package/dist/viewer/assets/archivo-latin-wght-normal-E0tuGl4L.woff2 +0 -0
  983. package/dist/viewer/assets/archivo-vietnamese-wght-normal-XAtsl5Q_.woff2 +0 -0
  984. package/dist/viewer/assets/ibm-plex-mono-cyrillic-400-italic-2syK4fUT.woff +0 -0
  985. package/dist/viewer/assets/ibm-plex-mono-cyrillic-400-italic-CBJ8pzag.woff2 +0 -0
  986. package/dist/viewer/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  987. package/dist/viewer/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  988. package/dist/viewer/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  989. package/dist/viewer/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  990. package/dist/viewer/assets/ibm-plex-mono-cyrillic-600-normal-CTOM6hUh.woff2 +0 -0
  991. package/dist/viewer/assets/ibm-plex-mono-cyrillic-600-normal-fLZuRloM.woff +0 -0
  992. package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-400-italic-B4oTjJdl.woff +0 -0
  993. package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-400-italic-CBjNughH.woff2 +0 -0
  994. package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  995. package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  996. package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  997. package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  998. package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-600-normal-9HEixskS.woff +0 -0
  999. package/dist/viewer/assets/ibm-plex-mono-cyrillic-ext-600-normal-V-xxqcpd.woff2 +0 -0
  1000. package/dist/viewer/assets/ibm-plex-mono-latin-400-italic-66oory27.woff +0 -0
  1001. package/dist/viewer/assets/ibm-plex-mono-latin-400-italic-BqAiT5Ww.woff2 +0 -0
  1002. package/dist/viewer/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  1003. package/dist/viewer/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  1004. package/dist/viewer/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  1005. package/dist/viewer/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  1006. package/dist/viewer/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  1007. package/dist/viewer/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  1008. package/dist/viewer/assets/ibm-plex-mono-latin-ext-400-italic-B7_fu1kp.woff2 +0 -0
  1009. package/dist/viewer/assets/ibm-plex-mono-latin-ext-400-italic-Bg0ZHwF4.woff +0 -0
  1010. package/dist/viewer/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  1011. package/dist/viewer/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  1012. package/dist/viewer/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  1013. package/dist/viewer/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  1014. package/dist/viewer/assets/ibm-plex-mono-latin-ext-600-normal-D38SheWl.woff2 +0 -0
  1015. package/dist/viewer/assets/ibm-plex-mono-latin-ext-600-normal-DmB0ttJJ.woff +0 -0
  1016. package/dist/viewer/assets/ibm-plex-mono-vietnamese-400-italic-D6eaYXMU.woff +0 -0
  1017. package/dist/viewer/assets/ibm-plex-mono-vietnamese-400-italic-DRuN92E5.woff2 +0 -0
  1018. package/dist/viewer/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  1019. package/dist/viewer/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  1020. package/dist/viewer/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  1021. package/dist/viewer/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  1022. package/dist/viewer/assets/ibm-plex-mono-vietnamese-600-normal-D2EvbN8M.woff2 +0 -0
  1023. package/dist/viewer/assets/ibm-plex-mono-vietnamese-600-normal-iLQfcSjf.woff +0 -0
  1024. package/dist/viewer/assets/index-2LG7iXtP.js +97 -0
  1025. package/dist/viewer/assets/index-AtMBttuU.css +1 -0
  1026. package/dist/viewer/index.html +16 -0
  1027. package/package.json +69 -0
  1028. package/scripts/add-lang/bench.sh +60 -0
  1029. package/scripts/add-lang/check-grammar.mjs +75 -0
  1030. package/scripts/add-lang/dump-ast.mjs +103 -0
  1031. package/scripts/add-lang/verify-extraction.mjs +70 -0
  1032. package/scripts/agent-eval/ab-adoption.sh +91 -0
  1033. package/scripts/agent-eval/ab-hook.sh +86 -0
  1034. package/scripts/agent-eval/ab-impl.sh +78 -0
  1035. package/scripts/agent-eval/ab-new-vs-baseline.sh +161 -0
  1036. package/scripts/agent-eval/ab-sufficiency.sh +78 -0
  1037. package/scripts/agent-eval/allocation-fixtures.json +344 -0
  1038. package/scripts/agent-eval/arms-F.sh +21 -0
  1039. package/scripts/agent-eval/arms-matrix.sh +37 -0
  1040. package/scripts/agent-eval/audit.sh +68 -0
  1041. package/scripts/agent-eval/bench-readme.sh +48 -0
  1042. package/scripts/agent-eval/bench-why-repo.sh +22 -0
  1043. package/scripts/agent-eval/block-read-hook.sh +19 -0
  1044. package/scripts/agent-eval/compare-arms.mjs +209 -0
  1045. package/scripts/agent-eval/diff-index-drift.mjs +102 -0
  1046. package/scripts/agent-eval/hook-settings.json +15 -0
  1047. package/scripts/agent-eval/itrun.sh +120 -0
  1048. package/scripts/agent-eval/no-cli-shim.sh +96 -0
  1049. package/scripts/agent-eval/offload-eval-3arm.sh +72 -0
  1050. package/scripts/agent-eval/offload-eval-cost.mjs +133 -0
  1051. package/scripts/agent-eval/offload-eval-effort.mjs +108 -0
  1052. package/scripts/agent-eval/offload-eval-frontload-matrix.sh +25 -0
  1053. package/scripts/agent-eval/offload-eval-frontload.sh +47 -0
  1054. package/scripts/agent-eval/offload-eval-ground-truth.json +18 -0
  1055. package/scripts/agent-eval/offload-eval-hook.mjs +84 -0
  1056. package/scripts/agent-eval/offload-eval-judge.mjs +103 -0
  1057. package/scripts/agent-eval/offload-eval-matrix.sh +20 -0
  1058. package/scripts/agent-eval/offload-eval-metrics.mjs +94 -0
  1059. package/scripts/agent-eval/offload-eval-refs1.sh +50 -0
  1060. package/scripts/agent-eval/offload-eval-setup.sh +24 -0
  1061. package/scripts/agent-eval/offload-eval-styles.sh +72 -0
  1062. package/scripts/agent-eval/offload-eval-summarize.mjs +68 -0
  1063. package/scripts/agent-eval/offload-eval.md +76 -0
  1064. package/scripts/agent-eval/parse-arms.mjs +116 -0
  1065. package/scripts/agent-eval/parse-bench-readme.mjs +238 -0
  1066. package/scripts/agent-eval/parse-run.mjs +1401 -0
  1067. package/scripts/agent-eval/parse-session.mjs +130 -0
  1068. package/scripts/agent-eval/probe-allocation.mjs +335 -0
  1069. package/scripts/agent-eval/probe-context.mjs +21 -0
  1070. package/scripts/agent-eval/probe-decl-only.mjs +198 -0
  1071. package/scripts/agent-eval/probe-explore.mjs +40 -0
  1072. package/scripts/agent-eval/probe-factory-closure.mjs +147 -0
  1073. package/scripts/agent-eval/probe-file-spend.mjs +195 -0
  1074. package/scripts/agent-eval/probe-named-symbol.mjs +163 -0
  1075. package/scripts/agent-eval/probe-node.mjs +20 -0
  1076. package/scripts/agent-eval/probe-suite-envelope.mjs +124 -0
  1077. package/scripts/agent-eval/probe-sweep.mjs +119 -0
  1078. package/scripts/agent-eval/probe-trace.mjs +20 -0
  1079. package/scripts/agent-eval/redirect-read-hook.sh +38 -0
  1080. package/scripts/agent-eval/repro-concurrent-explore.mjs +119 -0
  1081. package/scripts/agent-eval/repro-daemon-clients.mjs +125 -0
  1082. package/scripts/agent-eval/run-agent.sh +34 -0
  1083. package/scripts/agent-eval/run-all.sh +149 -0
  1084. package/scripts/agent-eval/run-arms.sh +56 -0
  1085. package/scripts/agent-eval/seq-matrix.mjs +137 -0
  1086. package/scripts/benchmarks/measure-index.cjs +128 -0
  1087. package/scripts/benchmarks/observe-index.py +91 -0
  1088. package/scripts/build-bundle.sh +154 -0
  1089. package/scripts/build-kernel.sh +87 -0
  1090. package/scripts/check-ui-build.mjs +157 -0
  1091. package/scripts/check-ui-package.mjs +192 -0
  1092. package/scripts/clean-remaining.mjs +243 -0
  1093. package/scripts/count-codegraph.mjs +26 -0
  1094. package/scripts/deep-clean.mjs +66 -0
  1095. package/scripts/dump-graph.mjs +57 -0
  1096. package/scripts/extract-release-notes.mjs +130 -0
  1097. package/scripts/final-codegraph-cleanup.mjs +57 -0
  1098. package/scripts/go-rebrand.mjs +114 -0
  1099. package/scripts/kernel-parity.mjs +283 -0
  1100. package/scripts/local-install.sh +41 -0
  1101. package/scripts/npm-sdk.js +75 -0
  1102. package/scripts/npm-shim.js +279 -0
  1103. package/scripts/pack-npm.sh +123 -0
  1104. package/scripts/prepare-release.mjs +270 -0
  1105. package/scripts/rebrand.mjs +129 -0
  1106. package/scripts/sync-ui-version.mjs +47 -0
  1107. package/scripts/try-repo.sh +112 -0
@@ -0,0 +1,3285 @@
1
+ "use strict";
2
+ /**
3
+ * Database Queries
4
+ *
5
+ * Prepared statements for CRUD operations on the knowledge graph.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.QueryBuilder = exports.DEPRIORITIZED_NAME_BONUS_SCALE = void 0;
9
+ const utils_1 = require("../utils");
10
+ const query_utils_1 = require("../search/query-utils");
11
+ const query_parser_1 = require("../search/query-parser");
12
+ const generated_detection_1 = require("../extraction/generated-detection");
13
+ const identifier_segments_1 = require("../search/identifier-segments");
14
+ /**
15
+ * Files that should not be candidates for "dominant file" detection: test/spec
16
+ * files and tool-generated files. Generated files (`*.pb.go`, `*.pulsar.go`,
17
+ * mock outputs, …) often have huge in-file edge counts that dwarf the real
18
+ * source — etcd's `rpc.pb.go` has 4× the in-file edges of `server.go`.
19
+ *
20
+ * Path patterns plus, when the caller passes the indexed set, files whose
21
+ * HEADER declares them generated — a `payroll.go` full of generated CRUD has
22
+ * exactly the same edge-density problem as `rpc.pb.go` and nothing in its name
23
+ * to catch it (#1500).
24
+ */
25
+ function isLowValueFile(filePath, generated) {
26
+ if (generated?.has(filePath))
27
+ return true;
28
+ const lp = filePath.toLowerCase();
29
+ return (/(?:^|\/)(tests?|__tests?__|spec)\//.test(lp) ||
30
+ /_test\.go$/.test(lp) ||
31
+ /(?:^|\/)test_[^/]+\.py$/.test(lp) ||
32
+ /_test\.py$/.test(lp) ||
33
+ /_spec\.rb$/.test(lp) ||
34
+ /_test\.rb$/.test(lp) ||
35
+ /\.(test|spec)\.[jt]sx?$/.test(lp) ||
36
+ /(test|spec|tests)\.(java|kt|scala)$/.test(lp) ||
37
+ /(tests?|spec)\.cs$/.test(lp) ||
38
+ /tests?\.swift$/.test(lp) ||
39
+ /_test\.dart$/.test(lp) ||
40
+ (0, generated_detection_1.isGeneratedFile)(filePath));
41
+ }
42
+ const SQLITE_PARAM_CHUNK_SIZE = 500;
43
+ /**
44
+ * A SQL predicate: is the node aliased `alias` a member an INTERFACE declares?
45
+ *
46
+ * `method_signature` / `property_signature` enter the graph as `method` /
47
+ * `property` nodes hung off their interface by a `contains` edge (#1638). They
48
+ * have no body and originate no behaviour, so for a structural judgement about
49
+ * a FILE they are the interface restated, not an extra thing the file declares.
50
+ * See {@link QueryBuilder.getAmbientDeclarationPathsAmong}, the one caller, for
51
+ * why treating them as opaque would break that rule in three places at once.
52
+ *
53
+ * Seeks `idx_edges_target_kind`, so it costs a key lookup per row rather than a
54
+ * join over the whole edge table.
55
+ */
56
+ const IS_INTERFACE_MEMBER = (alias) => `EXISTS (
57
+ SELECT 1 FROM edges ce JOIN nodes owner ON owner.id = ce.source
58
+ WHERE ce.target = ${alias}.id AND ce.kind = 'contains' AND owner.kind = 'interface'
59
+ )`;
60
+ /**
61
+ * How much of the exact-name bonus a `deprioritize`d path keeps (#982). Damped
62
+ * rather than zeroed: a query that genuinely targets that tree must still rank
63
+ * it, the same "discount, don't erase" rule the path penalty follows.
64
+ *
65
+ * Derived rather than picked. `nameMatchBonus`'s prefix arm tops out below
66
+ * `10 + 30 = 40`, and a de-prioritized node also takes the -15 path penalty, so
67
+ * `80 * SCALE - 15 > 40` is what stops a damped WHOLE-QUERY exact match from
68
+ * losing to a mere prefix match. 0.75 clears it (45). Measured on a 62k-node
69
+ * django index: at 0.25 that invariant breaks in practice — `child`, `parent`
70
+ * and `method` lose rank 1 to `children`, `all_parents` and `method_decorator`
71
+ * — while crowd-out removal is almost flat between 0.75 and 0.5 (39 vs 40 of 88
72
+ * peripheral top-10 slots cleared), so a deeper discount buys little and costs
73
+ * the invariant. Pinned by a test.
74
+ */
75
+ exports.DEPRIORITIZED_NAME_BONUS_SCALE = 0.75;
76
+ /**
77
+ * Last segment of a (possibly dotted/qualified) reference name — the part a
78
+ * new symbol's plain node name could match: 'util.greet' → 'greet',
79
+ * 'mod::fn' → 'fn', 'greet' → 'greet'. Written to unresolved_refs.name_tail
80
+ * when a ref is marked failed, so the #1240 retry lookup can match dotted
81
+ * refs against newly-added node names.
82
+ */
83
+ function referenceNameTail(referenceName) {
84
+ // Erlang refs carry a written arity (`f/1`, `mod::fn/2` — #1610); the tail a
85
+ // new symbol's plain name could match is the arity-less function name.
86
+ const base = referenceName.replace(/\/\d{1,3}$/, '') || referenceName;
87
+ const idx = Math.max(base.lastIndexOf('.'), base.lastIndexOf(':'));
88
+ return idx >= 0 ? base.slice(idx + 1) : base;
89
+ }
90
+ /**
91
+ * Convert database row to Node object
92
+ */
93
+ function rowToNode(row) {
94
+ return {
95
+ id: row.id,
96
+ kind: row.kind,
97
+ name: row.name,
98
+ qualifiedName: row.qualified_name,
99
+ filePath: row.file_path,
100
+ language: row.language,
101
+ startLine: row.start_line,
102
+ endLine: row.end_line,
103
+ startColumn: row.start_column,
104
+ endColumn: row.end_column,
105
+ docstring: row.docstring ?? undefined,
106
+ signature: row.signature ?? undefined,
107
+ visibility: row.visibility,
108
+ isExported: row.is_exported === 1,
109
+ isAsync: row.is_async === 1,
110
+ isStatic: row.is_static === 1,
111
+ isAbstract: row.is_abstract === 1,
112
+ decorators: row.decorators ? (0, utils_1.safeJsonParse)(row.decorators, undefined) : undefined,
113
+ typeParameters: row.type_parameters ? (0, utils_1.safeJsonParse)(row.type_parameters, undefined) : undefined,
114
+ returnType: row.return_type ?? undefined,
115
+ updatedAt: row.updated_at,
116
+ };
117
+ }
118
+ /**
119
+ * Convert database row to Edge object
120
+ */
121
+ function rowToEdge(row) {
122
+ return {
123
+ source: row.source,
124
+ target: row.target,
125
+ kind: row.kind,
126
+ metadata: row.metadata ? (0, utils_1.safeJsonParse)(row.metadata, undefined) : undefined,
127
+ line: row.line ?? undefined,
128
+ column: row.col ?? undefined,
129
+ provenance: row.provenance,
130
+ };
131
+ }
132
+ /**
133
+ * Convert database row to FileRecord object
134
+ */
135
+ function rowToFileRecord(row) {
136
+ return {
137
+ path: row.path,
138
+ contentHash: row.content_hash,
139
+ language: row.language,
140
+ size: row.size,
141
+ modifiedAt: row.modified_at,
142
+ indexedAt: row.indexed_at,
143
+ nodeCount: row.node_count,
144
+ errors: row.errors ? (0, utils_1.safeJsonParse)(row.errors, undefined) : undefined,
145
+ generated: row.generated === 1,
146
+ };
147
+ }
148
+ /**
149
+ * Query builder for the knowledge graph database
150
+ */
151
+ class QueryBuilder {
152
+ db;
153
+ // Project-name tokens (go.mod / package.json / repo dir), normalized. A query
154
+ // word matching one is dropped from path-relevance scoring — it names the
155
+ // whole project, not a symbol, so it carries no discriminative signal (#720).
156
+ // Set once by the Alpha Code Sense instance; empty by default (no down-weighting).
157
+ projectNameTokens = new Set();
158
+ isDeprioritizedPath;
159
+ // FTS5 availability flag — detected once at construction time (#1532)
160
+ _fts5Available;
161
+ // Node cache for frequently accessed nodes (LRU-style, max 1000 entries)
162
+ nodeCache = new Map();
163
+ maxCacheSize = 1000;
164
+ // Prepared statements (lazily initialized)
165
+ stmts = {};
166
+ // Names whose segments were already written this session — skips re-splitting
167
+ // and re-inserting for the same-named nodes that repeat across files ("get",
168
+ // "render", …). Purely a write-path fast path; INSERT OR IGNORE is the
169
+ // correctness backstop. Bounded so a pathological repo can't grow it forever.
170
+ segmentedNames = new Set();
171
+ static MAX_SEGMENTED_NAMES = 65536;
172
+ // Multi-row INSERT statements, cached per (statement kind × row count). The
173
+ // bulk write path decomposes N rows into a few fixed batch sizes so each
174
+ // size's statement is prepared once and reused — one .run() binds a whole
175
+ // chunk instead of one row, which is where the per-call overhead lives.
176
+ // Row order within and across chunks is the input order, so rowid assignment
177
+ // (and therefore resolution's insertion-order disambiguation) is identical
178
+ // to the one-row-per-run path.
179
+ batchStmts = new Map();
180
+ static BATCH_SIZES = [128, 32, 8, 1];
181
+ /**
182
+ * Run `rows` through a multi-row `INSERT` built as `head + (tuple,)*n`,
183
+ * decomposed greedily into the cached batch sizes. Preserves row order.
184
+ */
185
+ runBatched(kind, head, tuple, rows) {
186
+ if (rows.length === 0)
187
+ return;
188
+ let i = 0;
189
+ for (const size of QueryBuilder.BATCH_SIZES) {
190
+ while (rows.length - i >= size) {
191
+ const key = `${kind}:${size}`;
192
+ let stmt = this.batchStmts.get(key);
193
+ if (!stmt) {
194
+ stmt = this.db.prepare(head + new Array(size).fill(tuple).join(','));
195
+ this.batchStmts.set(key, stmt);
196
+ }
197
+ if (size === 1) {
198
+ stmt.run(...rows[i]);
199
+ }
200
+ else {
201
+ const params = [];
202
+ for (let r = 0; r < size; r++) {
203
+ const row = rows[i + r];
204
+ for (let c = 0; c < row.length; c++)
205
+ params.push(row[c]);
206
+ }
207
+ stmt.run(...params);
208
+ }
209
+ i += size;
210
+ }
211
+ }
212
+ }
213
+ constructor(db) {
214
+ this.db = db;
215
+ // Detect FTS5 availability once (#1532)
216
+ try {
217
+ db.prepare("SELECT * FROM nodes_fts LIMIT 0").get();
218
+ this._fts5Available = true;
219
+ }
220
+ catch {
221
+ this._fts5Available = false;
222
+ }
223
+ }
224
+ /**
225
+ * Swap the underlying connection in place. Used by pool workers'
226
+ * connection recycling (plan §7a.6, writes-under-readers): a long-lived
227
+ * read connection pins WAL checkpoint progress, and the deep WAL that
228
+ * accumulates behind it taxes every main-thread B-tree page operation
229
+ * (deletes measured 42.6s → 118.8s from 0 to 4 attached readers on
230
+ * identical hardware). Workers therefore close and reopen their read-only
231
+ * connection at the pool-idle boundary; everything above the connection —
232
+ * this QueryBuilder, the resolver and its warm caches — survives, and only
233
+ * connection-derived state (prepared statements) resets, re-preparing
234
+ * lazily on next use.
235
+ */
236
+ rebind(db) {
237
+ this.db = db;
238
+ this.stmts = {};
239
+ this.batchStmts.clear();
240
+ }
241
+ /** Set the normalized project-name tokens used to down-weight non-discriminative
242
+ * query words in path scoring (#720). Called once when the project opens. */
243
+ setProjectNameTokens(tokens) {
244
+ this.projectNameTokens = tokens;
245
+ }
246
+ /** The normalized project-name tokens (#720); empty if none were derived. */
247
+ getProjectNameTokens() {
248
+ return this.projectNameTokens;
249
+ }
250
+ /**
251
+ * Set the predicate that marks a path as de-prioritized by the project's
252
+ * `codegraph.json` `deprioritize` patterns (#982). Ranking-only: those paths
253
+ * stay indexed and findable, they just stop outranking first-party code.
254
+ * Called once when the project opens; undefined disables the lever.
255
+ */
256
+ setDeprioritizedPathMatcher(matcher) {
257
+ this.isDeprioritizedPath = matcher;
258
+ }
259
+ /** The `deprioritize` predicate (#982), so other rankers apply the same lever. */
260
+ getDeprioritizedPathMatcher() {
261
+ return this.isDeprioritizedPath;
262
+ }
263
+ // ===========================================================================
264
+ // Node Operations
265
+ // ===========================================================================
266
+ /**
267
+ * Insert a new node
268
+ */
269
+ insertNode(node) {
270
+ if (!this.stmts.insertNode) {
271
+ this.stmts.insertNode = this.db.prepare(`
272
+ INSERT OR REPLACE INTO nodes (
273
+ id, kind, name, qualified_name, file_path, language,
274
+ start_line, end_line, start_column, end_column,
275
+ docstring, signature, visibility,
276
+ is_exported, is_async, is_static, is_abstract,
277
+ decorators, type_parameters, return_type, updated_at
278
+ ) VALUES (
279
+ @id, @kind, @name, @qualifiedName, @filePath, @language,
280
+ @startLine, @endLine, @startColumn, @endColumn,
281
+ @docstring, @signature, @visibility,
282
+ @isExported, @isAsync, @isStatic, @isAbstract,
283
+ @decorators, @typeParameters, @returnType, @updatedAt
284
+ )
285
+ `);
286
+ }
287
+ // Validate required fields to prevent SQLite bind errors
288
+ if (!node.id || !node.kind || !node.name || !node.filePath || !node.language) {
289
+ console.error('[Alpha Code Sense] Skipping node with missing required fields:', {
290
+ id: node.id,
291
+ kind: node.kind,
292
+ name: node.name,
293
+ filePath: node.filePath,
294
+ language: node.language,
295
+ });
296
+ return;
297
+ }
298
+ // INSERT OR REPLACE may overwrite a node we have cached. Drop the
299
+ // stale entry so the next getNodeById sees the new row, not the old
300
+ // one (matches the cache-invalidation pattern used by updateNode and
301
+ // deleteNode below).
302
+ this.nodeCache.delete(node.id);
303
+ this.stmts.insertNode.run({
304
+ id: node.id,
305
+ kind: node.kind,
306
+ name: node.name,
307
+ qualifiedName: node.qualifiedName ?? node.name,
308
+ filePath: node.filePath,
309
+ language: node.language,
310
+ startLine: node.startLine ?? 0,
311
+ endLine: node.endLine ?? 0,
312
+ startColumn: node.startColumn ?? 0,
313
+ endColumn: node.endColumn ?? 0,
314
+ docstring: node.docstring ?? null,
315
+ signature: node.signature ?? null,
316
+ visibility: node.visibility ?? null,
317
+ isExported: node.isExported ? 1 : 0,
318
+ isAsync: node.isAsync ? 1 : 0,
319
+ isStatic: node.isStatic ? 1 : 0,
320
+ isAbstract: node.isAbstract ? 1 : 0,
321
+ decorators: node.decorators ? JSON.stringify(node.decorators) : null,
322
+ typeParameters: node.typeParameters ? JSON.stringify(node.typeParameters) : null,
323
+ returnType: node.returnType ?? null,
324
+ updatedAt: node.updatedAt ?? Date.now(),
325
+ });
326
+ // Segment vocabulary rides the same write path (and transaction) so it can
327
+ // never drift ahead of the nodes it describes. Deletes intentionally leave
328
+ // orphans behind — vocab rows are proposals re-verified against nodes
329
+ // before use, and a full index clears the table at its start. File nodes
330
+ // are excluded: a file's basename duplicates the symbols inside it
331
+ // (state-machine.ts / OrderStateMachine), which double-counts every
332
+ // concept and defeats the singleton-vs-cluster rarity statistics. Import
333
+ // nodes are excluded too (#1144): they're named after module specifiers
334
+ // ("external-unindexed-pkg", "./utils/helpers"), not symbols — an
335
+ // import-only name can never be surfaced (getSegmentMatches requires a
336
+ // real definition), so its rows would only inflate the rarity statistics.
337
+ if (this.isSegmentableKind(node.kind))
338
+ this.insertNameSegments(node.name);
339
+ }
340
+ /** Which node kinds contribute their name to the segment vocabulary — the
341
+ * single gate shared by insertNode, updateNode, and the rebuild page query
342
+ * (getDistinctNodeNames), so the write paths can't drift apart. */
343
+ isSegmentableKind(kind) {
344
+ return kind !== 'file' && kind !== 'import';
345
+ }
346
+ /** Write `name`'s segments into name_segment_vocab (idempotent). */
347
+ insertNameSegments(name) {
348
+ const rows = [];
349
+ this.collectNameSegmentRows(name, rows);
350
+ this.runBatched('insertNameSegments', 'INSERT OR IGNORE INTO name_segment_vocab (segment, name) VALUES ', '(?,?)', rows);
351
+ }
352
+ /**
353
+ * Insert multiple nodes in a transaction
354
+ */
355
+ insertNodes(nodes) {
356
+ this.db.transaction(() => {
357
+ // Bulk path: same semantics as insertNode() per row (validation, cache
358
+ // invalidation, segment vocab), but bound as multi-row INSERTs — the
359
+ // per-.run() call overhead dominates the store phase on full indexes.
360
+ const rows = [];
361
+ const segmentRows = [];
362
+ for (const node of nodes) {
363
+ if (!node.id || !node.kind || !node.name || !node.filePath || !node.language) {
364
+ console.error('[Alpha Code Sense] Skipping node with missing required fields:', {
365
+ id: node.id,
366
+ kind: node.kind,
367
+ name: node.name,
368
+ filePath: node.filePath,
369
+ language: node.language,
370
+ });
371
+ continue;
372
+ }
373
+ this.nodeCache.delete(node.id);
374
+ rows.push([
375
+ node.id,
376
+ node.kind,
377
+ node.name,
378
+ node.qualifiedName ?? node.name,
379
+ node.filePath,
380
+ node.language,
381
+ node.startLine ?? 0,
382
+ node.endLine ?? 0,
383
+ node.startColumn ?? 0,
384
+ node.endColumn ?? 0,
385
+ node.docstring ?? null,
386
+ node.signature ?? null,
387
+ node.visibility ?? null,
388
+ node.isExported ? 1 : 0,
389
+ node.isAsync ? 1 : 0,
390
+ node.isStatic ? 1 : 0,
391
+ node.isAbstract ? 1 : 0,
392
+ node.decorators ? JSON.stringify(node.decorators) : null,
393
+ node.typeParameters ? JSON.stringify(node.typeParameters) : null,
394
+ node.returnType ?? null,
395
+ node.updatedAt ?? Date.now(),
396
+ ]);
397
+ if (this.isSegmentableKind(node.kind))
398
+ this.collectNameSegmentRows(node.name, segmentRows);
399
+ }
400
+ this.runBatched('insertNodes', `INSERT OR REPLACE INTO nodes (
401
+ id, kind, name, qualified_name, file_path, language,
402
+ start_line, end_line, start_column, end_column,
403
+ docstring, signature, visibility,
404
+ is_exported, is_async, is_static, is_abstract,
405
+ decorators, type_parameters, return_type, updated_at
406
+ ) VALUES `, '(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', rows);
407
+ this.runBatched('insertNameSegments', 'INSERT OR IGNORE INTO name_segment_vocab (segment, name) VALUES ', '(?,?)', segmentRows);
408
+ })();
409
+ }
410
+ /**
411
+ * Store one file's whole extraction bundle — nodes, edges, unresolved refs,
412
+ * and the file record — in a SINGLE transaction. The bulk-index path calls
413
+ * this once per file instead of opening one transaction per table (#1015
414
+ * file-order commit discipline is unchanged: callers still invoke it in file
415
+ * order, and row order within is input order).
416
+ *
417
+ * Edges MUST already be endpoint-filtered by the caller (the store path
418
+ * filters to the file's own inserted node ids), so the per-file existence
419
+ * SELECT that insertEdges() pays is skipped here.
420
+ */
421
+ storeFileBundle(bundle) {
422
+ this.db.transaction(() => {
423
+ this.insertNodes(bundle.nodes);
424
+ if (bundle.edges.length > 0) {
425
+ const rows = [];
426
+ for (const edge of bundle.edges) {
427
+ rows.push([
428
+ edge.source,
429
+ edge.target,
430
+ edge.kind,
431
+ edge.metadata ? JSON.stringify(edge.metadata) : null,
432
+ edge.line ?? null,
433
+ edge.column ?? null,
434
+ edge.provenance ?? null,
435
+ ]);
436
+ }
437
+ this.runBatched('insertEdges', 'INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance) VALUES ', '(?,?,?,?,?,?,?)', rows);
438
+ }
439
+ if (bundle.refs.length > 0)
440
+ this.insertUnresolvedRefsBatch(bundle.refs);
441
+ this.upsertFile(bundle.file);
442
+ })();
443
+ }
444
+ /**
445
+ * Collect (segment, name) rows for a name, honouring the same session-dedupe
446
+ * semantics as insertNameSegments(). Shared by the bulk write paths.
447
+ */
448
+ collectNameSegmentRows(name, out) {
449
+ if (this.segmentedNames.has(name))
450
+ return;
451
+ if (this.segmentedNames.size >= QueryBuilder.MAX_SEGMENTED_NAMES)
452
+ this.segmentedNames.clear();
453
+ this.segmentedNames.add(name);
454
+ for (const segment of (0, identifier_segments_1.splitIdentifierSegments)(name))
455
+ out.push([segment, name]);
456
+ }
457
+ /**
458
+ * Update an existing node
459
+ */
460
+ updateNode(node) {
461
+ if (!this.stmts.updateNode) {
462
+ this.stmts.updateNode = this.db.prepare(`
463
+ UPDATE nodes SET
464
+ kind = @kind,
465
+ name = @name,
466
+ qualified_name = @qualifiedName,
467
+ file_path = @filePath,
468
+ language = @language,
469
+ start_line = @startLine,
470
+ end_line = @endLine,
471
+ start_column = @startColumn,
472
+ end_column = @endColumn,
473
+ docstring = @docstring,
474
+ signature = @signature,
475
+ visibility = @visibility,
476
+ is_exported = @isExported,
477
+ is_async = @isAsync,
478
+ is_static = @isStatic,
479
+ is_abstract = @isAbstract,
480
+ decorators = @decorators,
481
+ type_parameters = @typeParameters,
482
+ return_type = @returnType,
483
+ updated_at = @updatedAt
484
+ WHERE id = @id
485
+ `);
486
+ }
487
+ // Invalidate cache before update
488
+ this.nodeCache.delete(node.id);
489
+ // Validate required fields
490
+ if (!node.id || !node.kind || !node.name || !node.filePath || !node.language) {
491
+ console.error('[Alpha Code Sense] Skipping node update with missing required fields:', node.id);
492
+ return;
493
+ }
494
+ this.stmts.updateNode.run({
495
+ id: node.id,
496
+ kind: node.kind,
497
+ name: node.name,
498
+ qualifiedName: node.qualifiedName ?? node.name,
499
+ filePath: node.filePath,
500
+ language: node.language,
501
+ startLine: node.startLine ?? 0,
502
+ endLine: node.endLine ?? 0,
503
+ startColumn: node.startColumn ?? 0,
504
+ endColumn: node.endColumn ?? 0,
505
+ docstring: node.docstring ?? null,
506
+ signature: node.signature ?? null,
507
+ visibility: node.visibility ?? null,
508
+ isExported: node.isExported ? 1 : 0,
509
+ isAsync: node.isAsync ? 1 : 0,
510
+ isStatic: node.isStatic ? 1 : 0,
511
+ isAbstract: node.isAbstract ? 1 : 0,
512
+ decorators: node.decorators ? JSON.stringify(node.decorators) : null,
513
+ typeParameters: node.typeParameters ? JSON.stringify(node.typeParameters) : null,
514
+ returnType: node.returnType ?? null,
515
+ updatedAt: node.updatedAt ?? Date.now(),
516
+ });
517
+ // updateNode is a second real write path to `nodes` — framework
518
+ // post-extract passes rewrite names through it (NestJS route prefixing),
519
+ // and a renamed node's new name must reach the segment vocabulary just
520
+ // like an inserted one's (#1141). Without this the rename left the new
521
+ // name permanently unsearchable: the old name's rows became honest-gate
522
+ // orphans and the only backfill is gated on the vocab being EMPTY.
523
+ // insertNameSegments is idempotent (in-memory set + INSERT OR IGNORE),
524
+ // so no name-changed check is needed.
525
+ if (this.isSegmentableKind(node.kind))
526
+ this.insertNameSegments(node.name);
527
+ }
528
+ /**
529
+ * Delete a node by ID
530
+ */
531
+ deleteNode(id) {
532
+ if (!this.stmts.deleteNode) {
533
+ this.stmts.deleteNode = this.db.prepare('DELETE FROM nodes WHERE id = ?');
534
+ }
535
+ // Invalidate cache
536
+ this.nodeCache.delete(id);
537
+ this.stmts.deleteNode.run(id);
538
+ }
539
+ /**
540
+ * Delete all nodes for a file
541
+ */
542
+ deleteNodesByFile(filePath) {
543
+ if (!this.stmts.deleteNodesByFile) {
544
+ this.stmts.deleteNodesByFile = this.db.prepare('DELETE FROM nodes WHERE file_path = ?');
545
+ }
546
+ // Invalidate cache for nodes in this file
547
+ for (const [id, node] of this.nodeCache) {
548
+ if (node.filePath === filePath) {
549
+ this.nodeCache.delete(id);
550
+ }
551
+ }
552
+ this.stmts.deleteNodesByFile.run(filePath);
553
+ }
554
+ // ===========================================================================
555
+ // Name-segment vocabulary (prompt-hook graph-derived gate)
556
+ // ===========================================================================
557
+ /** Wipe the segment vocabulary. A full index calls this at its start; the
558
+ * node write path repopulates it as files (re-)index, so the end state is
559
+ * exactly the current names with no orphan rows. */
560
+ clearNameSegmentVocab() {
561
+ this.db.exec('DELETE FROM name_segment_vocab');
562
+ this.segmentedNames.clear();
563
+ }
564
+ /** True when the vocab has no rows — an index built before the table existed.
565
+ * `sync` uses this to heal such databases (see rebuildNameSegmentVocabFrom). */
566
+ isNameSegmentVocabEmpty() {
567
+ const row = this.db.prepare('SELECT 1 FROM name_segment_vocab LIMIT 1').get();
568
+ return row === undefined;
569
+ }
570
+ /** One page of distinct segmentable node names, for batched vocab rebuilds
571
+ * (file basenames and import specifiers are excluded from the vocab — see
572
+ * insertNode). */
573
+ getDistinctNodeNames(limit, offset) {
574
+ const rows = this.db
575
+ .prepare("SELECT DISTINCT name FROM nodes WHERE kind NOT IN ('file', 'import') ORDER BY name LIMIT ? OFFSET ?")
576
+ .all(limit, offset);
577
+ return rows.map((r) => r.name);
578
+ }
579
+ /** Insert segments for a batch of names in one transaction (vocab heal path). */
580
+ insertNameSegmentsBatch(names) {
581
+ this.db.transaction(() => {
582
+ const rows = [];
583
+ for (const name of names)
584
+ this.collectNameSegmentRows(name, rows);
585
+ this.runBatched('insertNameSegments', 'INSERT OR IGNORE INTO name_segment_vocab (segment, name) VALUES ', '(?,?)', rows);
586
+ })();
587
+ }
588
+ /**
589
+ * Names whose segments cover at least `minWords` distinct PROMPT WORDS —
590
+ * the co-occurrence probe behind the prompt hook's medium tier: the words
591
+ * "state" and "machine" both being segments of `OrderStateMachine` is strong
592
+ * evidence the prompt names that symbol in prose. Ordered by coverage.
593
+ *
594
+ * Takes (segment variant → original word) pairs and folds variants back to
595
+ * their word INSIDE the SQL: a name matching both `service` and `services`
596
+ * counts ONE word, not two. Counting raw variants let plural-variant pairs
597
+ * of a single word tie with genuine two-word matches and — because ORDER
598
+ * BY/LIMIT run here, before any JS-side re-check — crowd a real match past
599
+ * the LIMIT on vocab-heavy repos (#1146).
600
+ */
601
+ getSegmentCoOccurrence(variants, minWords, limit) {
602
+ if (variants.length === 0)
603
+ return [];
604
+ const placeholders = variants.map(() => '?').join(', ');
605
+ const whens = variants.map(() => 'WHEN ? THEN ?').join(' ');
606
+ const rows = this.db
607
+ .prepare(`SELECT name, COUNT(DISTINCT CASE segment ${whens} END) AS matches
608
+ FROM name_segment_vocab
609
+ WHERE segment IN (${placeholders})
610
+ GROUP BY name
611
+ HAVING matches >= ?
612
+ ORDER BY matches DESC, length(name) ASC
613
+ LIMIT ?`)
614
+ .all(...variants.flatMap((v) => [v.segment, v.word]), ...variants.map((v) => v.segment), minWords, limit);
615
+ return rows;
616
+ }
617
+ /** How many distinct names each segment appears in — the rarity signal that
618
+ * separates a discriminative word ("checkout") from a ubiquitous one ("state"). */
619
+ getSegmentNameCounts(segments) {
620
+ if (segments.length === 0)
621
+ return new Map();
622
+ const placeholders = segments.map(() => '?').join(', ');
623
+ const rows = this.db
624
+ .prepare(`SELECT segment, COUNT(*) AS n FROM name_segment_vocab
625
+ WHERE segment IN (${placeholders}) GROUP BY segment`)
626
+ .all(...segments);
627
+ return new Map(rows.map((r) => [r.segment, r.n]));
628
+ }
629
+ /** Names containing the given segment (rare-single-word tier). */
630
+ getNamesForSegment(segment, limit) {
631
+ const rows = this.db
632
+ .prepare('SELECT name FROM name_segment_vocab WHERE segment = ? ORDER BY length(name) ASC LIMIT ?')
633
+ .all(segment, limit);
634
+ return rows.map((r) => r.name);
635
+ }
636
+ /**
637
+ * Get a node by ID
638
+ */
639
+ getNodeById(id) {
640
+ // Check cache first
641
+ if (this.nodeCache.has(id)) {
642
+ const cached = this.nodeCache.get(id);
643
+ // Move to end to implement LRU (delete and re-add)
644
+ this.nodeCache.delete(id);
645
+ this.nodeCache.set(id, cached);
646
+ return cached;
647
+ }
648
+ if (!this.stmts.getNodeById) {
649
+ this.stmts.getNodeById = this.db.prepare('SELECT * FROM nodes WHERE id = ?');
650
+ }
651
+ const row = this.stmts.getNodeById.get(id);
652
+ if (!row) {
653
+ return null;
654
+ }
655
+ const node = rowToNode(row);
656
+ this.cacheNode(node);
657
+ return node;
658
+ }
659
+ /**
660
+ * Batch lookup: fetch many nodes by ID in a single SQL round-trip.
661
+ *
662
+ * Replaces the N+1 pattern in graph traversal where every edge would
663
+ * trigger its own `getNodeById` call. For a function with 50 callers
664
+ * this collapses 50 point reads into one IN-list query (~10-50x
665
+ * faster end-to-end).
666
+ *
667
+ * Returns a Map keyed by id so callers can preserve their own ordering
668
+ * (typically the order edges were returned from the graph). Missing IDs
669
+ * are simply absent from the map.
670
+ *
671
+ * Cache-aware: ids already in the LRU cache are served from memory and
672
+ * the SQL query only touches the misses.
673
+ */
674
+ getNodesByIds(ids) {
675
+ const out = new Map();
676
+ if (ids.length === 0)
677
+ return out;
678
+ // Serve cache hits first; build the miss list for SQL.
679
+ const misses = [];
680
+ for (const id of ids) {
681
+ const cached = this.nodeCache.get(id);
682
+ if (cached !== undefined) {
683
+ // LRU touch
684
+ this.nodeCache.delete(id);
685
+ this.nodeCache.set(id, cached);
686
+ out.set(id, cached);
687
+ }
688
+ else {
689
+ misses.push(id);
690
+ }
691
+ }
692
+ if (misses.length === 0)
693
+ return out;
694
+ // Chunk under SQLite's parameter limit (default 999, raised to 32766
695
+ // in better-sqlite3 builds — chunk at 500 for safety across both
696
+ // backends and to keep the query plan simple).
697
+ for (let i = 0; i < misses.length; i += SQLITE_PARAM_CHUNK_SIZE) {
698
+ const chunk = misses.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
699
+ const placeholders = chunk.map(() => '?').join(',');
700
+ const rows = this.db
701
+ .prepare(`SELECT * FROM nodes WHERE id IN (${placeholders})`)
702
+ .all(...chunk);
703
+ for (const row of rows) {
704
+ const node = rowToNode(row);
705
+ out.set(node.id, node);
706
+ this.cacheNode(node);
707
+ }
708
+ }
709
+ return out;
710
+ }
711
+ getExistingNodeIds(ids) {
712
+ const out = new Set();
713
+ if (ids.length === 0)
714
+ return out;
715
+ const uniqueIds = [...new Set(ids)];
716
+ for (let i = 0; i < uniqueIds.length; i += SQLITE_PARAM_CHUNK_SIZE) {
717
+ const chunk = uniqueIds.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
718
+ const placeholders = chunk.map(() => '?').join(',');
719
+ const rows = this.db
720
+ .prepare(`SELECT id FROM nodes WHERE id IN (${placeholders})`)
721
+ .all(...chunk);
722
+ for (const row of rows) {
723
+ out.add(row.id);
724
+ }
725
+ }
726
+ return out;
727
+ }
728
+ /**
729
+ * Add a node to the cache, evicting oldest if needed
730
+ */
731
+ cacheNode(node) {
732
+ if (this.nodeCache.size >= this.maxCacheSize) {
733
+ // Evict oldest (first) entry
734
+ const firstKey = this.nodeCache.keys().next().value;
735
+ if (firstKey) {
736
+ this.nodeCache.delete(firstKey);
737
+ }
738
+ }
739
+ this.nodeCache.set(node.id, node);
740
+ }
741
+ /**
742
+ * Clear the node cache
743
+ */
744
+ clearCache() {
745
+ this.nodeCache.clear();
746
+ }
747
+ /**
748
+ * Get all nodes in a file
749
+ */
750
+ getNodesByFile(filePath) {
751
+ if (!this.stmts.getNodesByFile) {
752
+ this.stmts.getNodesByFile = this.db.prepare('SELECT * FROM nodes WHERE file_path = ? ORDER BY start_line');
753
+ }
754
+ const rows = this.stmts.getNodesByFile.all(filePath);
755
+ return rows.map(rowToNode);
756
+ }
757
+ /**
758
+ * Find the file that holds the densest concentration of the project's
759
+ * internal call graph — the "core" file. Used by context-builder to
760
+ * boost ranking of symbols in that file's directory (so e.g. sinatra
761
+ * queries surface `lib/sinatra/base.rb`'s `route!` instead of
762
+ * `sinatra-contrib/lib/sinatra/multi_route.rb`'s `route` extension).
763
+ *
764
+ * Returns null if no file has a meaningful concentration (e.g. spread
765
+ * evenly across many files, or empty index).
766
+ *
767
+ * "Internal" = source and target are in the same file. Cross-file
768
+ * edges aren't useful here — they don't tell us which file is the
769
+ * functional center.
770
+ *
771
+ * Excludes test/spec files from candidacy via path-pattern. The agent's
772
+ * typical question is "how does X work", not "how is X tested", so
773
+ * boosting a test file's directory would be a misfire.
774
+ */
775
+ getDominantFile() {
776
+ if (!this.stmts.getDominantFile) {
777
+ // Pull top 20 candidates; we then filter out test/generated files
778
+ // in code (regex-grade matching that SQL LIKE can't express). The
779
+ // generated-file filter is critical — without it, etcd's
780
+ // `api/etcdserverpb/rpc.pb.go` (1916 in-file edges, generated
781
+ // protobuf stub) outranks the real `server/etcdserver/server.go`
782
+ // (470 edges) by 4×, and the boost would push the agent toward
783
+ // generated code.
784
+ this.stmts.getDominantFile = this.db.prepare(`
785
+ SELECT n.file_path AS file_path, COUNT(*) AS edge_count
786
+ FROM edges e
787
+ JOIN nodes n ON e.source = n.id
788
+ JOIN nodes m ON e.target = m.id
789
+ WHERE n.file_path = m.file_path
790
+ GROUP BY n.file_path
791
+ ORDER BY edge_count DESC
792
+ LIMIT 20
793
+ `);
794
+ }
795
+ const rows = this.stmts.getDominantFile.all();
796
+ const generated = this.getGeneratedPathsAmong(rows.map(r => r.file_path));
797
+ const filtered = rows.filter(r => !isLowValueFile(r.file_path, generated));
798
+ if (filtered.length === 0 || filtered[0].edge_count < 20)
799
+ return null;
800
+ return {
801
+ filePath: filtered[0].file_path,
802
+ edgeCount: filtered[0].edge_count,
803
+ nextEdgeCount: filtered[1]?.edge_count ?? 0,
804
+ };
805
+ }
806
+ /**
807
+ * Find the file that holds the densest concentration of the project's
808
+ * `route` nodes (framework-emitted: Express/Gin/Flask/Rails/Drupal/etc.).
809
+ * Used by handleContext on small repos to inline the project's routing
810
+ * config when the agent's query is about request flow — eliminating the
811
+ * "Glob + Read routes.rb" pattern that beats codegraph on tiny realworld
812
+ * template repos.
813
+ *
814
+ * Excludes test/generated files from candidacy. Returns null if there
815
+ * are fewer than 3 non-test routes total, or if no file holds at least
816
+ * 30% of them (diffuse routing → no single answer file).
817
+ */
818
+ getTopRouteFile() {
819
+ if (!this.stmts.getTopRouteFile) {
820
+ this.stmts.getTopRouteFile = this.db.prepare(`
821
+ SELECT file_path, COUNT(*) AS cnt
822
+ FROM nodes
823
+ WHERE kind = 'route'
824
+ GROUP BY file_path
825
+ ORDER BY cnt DESC
826
+ LIMIT 20
827
+ `);
828
+ }
829
+ const rows = this.stmts.getTopRouteFile.all();
830
+ const generated = this.getGeneratedPathsAmong(rows.map(r => r.file_path));
831
+ const filtered = rows.filter(r => !isLowValueFile(r.file_path, generated));
832
+ if (filtered.length === 0)
833
+ return null;
834
+ const totalRoutes = filtered.reduce((sum, r) => sum + r.cnt, 0);
835
+ const top = filtered[0];
836
+ if (totalRoutes < 3 || top.cnt < 3)
837
+ return null;
838
+ if (top.cnt / totalRoutes < 0.30)
839
+ return null;
840
+ return { filePath: top.file_path, routeCount: top.cnt, totalRoutes };
841
+ }
842
+ /**
843
+ * Build a URL → handler manifest from the index. Each route node's
844
+ * `references` edge points at the function/method that handles the
845
+ * request. We join them in one pass; the agent gets the canonical
846
+ * routing answer ("POST /users/login → AuthController#login") without
847
+ * having to parse the framework's route DSL itself.
848
+ *
849
+ * Also returns the file with the most handler endpoints — used as the
850
+ * "top handler file" to inline source for, so the agent has both the
851
+ * mapping AND the handler implementations.
852
+ */
853
+ getRoutingManifest(limit = 40) {
854
+ if (!this.stmts.getRoutingManifest) {
855
+ // Edge kind varies across framework resolvers: Spring/Rails/
856
+ // Laravel/Drupal emit `references`, Express emits `calls`. Accept
857
+ // both — the semantic is the same (route → its handler).
858
+ this.stmts.getRoutingManifest = this.db.prepare(`
859
+ SELECT
860
+ r.name AS url,
861
+ r.id AS route_id,
862
+ r.file_path AS route_file,
863
+ r.start_line AS route_line,
864
+ h.name AS handler,
865
+ h.file_path AS handler_file,
866
+ h.start_line AS handler_line,
867
+ h.kind AS handler_kind
868
+ FROM nodes r
869
+ JOIN edges e ON e.source = r.id
870
+ JOIN nodes h ON e.target = h.id
871
+ WHERE r.kind = 'route'
872
+ AND e.kind IN ('references', 'calls')
873
+ AND h.kind IN ('function', 'method', 'class', 'constant', 'variable')
874
+ ORDER BY r.file_path, r.start_line
875
+ LIMIT ?
876
+ `);
877
+ }
878
+ const rows = this.stmts.getRoutingManifest.all(limit);
879
+ // Drop test/generated handlers — same hygiene as elsewhere.
880
+ const generated = this.getGeneratedPathsAmong(rows.map(r => r.handler_file));
881
+ const filtered = rows.filter(r => !isLowValueFile(r.handler_file, generated));
882
+ if (filtered.length < 3)
883
+ return null;
884
+ // Identify the file holding the most handlers (the "primary handler file").
885
+ const fileCounts = new Map();
886
+ for (const r of filtered) {
887
+ fileCounts.set(r.handler_file, (fileCounts.get(r.handler_file) ?? 0) + 1);
888
+ }
889
+ let topHandlerFile = null;
890
+ let topHandlerFileCount = 0;
891
+ for (const [file, count] of fileCounts) {
892
+ if (count > topHandlerFileCount) {
893
+ topHandlerFile = file;
894
+ topHandlerFileCount = count;
895
+ }
896
+ }
897
+ return {
898
+ entries: filtered.map(r => ({
899
+ url: r.url,
900
+ handler: r.handler,
901
+ handlerFile: r.handler_file,
902
+ handlerLine: r.handler_line,
903
+ handlerKind: r.handler_kind,
904
+ routeId: r.route_id,
905
+ routeFile: r.route_file,
906
+ routeLine: r.route_line,
907
+ })),
908
+ topHandlerFile,
909
+ topHandlerFileCount,
910
+ totalRoutes: filtered.length,
911
+ };
912
+ }
913
+ /**
914
+ * Get all nodes of a specific kind
915
+ */
916
+ getNodesByKind(kind) {
917
+ if (!this.stmts.getNodesByKind) {
918
+ this.stmts.getNodesByKind = this.db.prepare('SELECT * FROM nodes WHERE kind = ?');
919
+ }
920
+ const rows = this.stmts.getNodesByKind.all(kind);
921
+ return rows.map(rowToNode);
922
+ }
923
+ /**
924
+ * Stream every node of a kind one at a time (lazy) instead of materializing
925
+ * them all like {@link getNodesByKind}. For unbounded kinds (`function`,
926
+ * `method`) on a symbol-dense project the full array is gigabytes; the
927
+ * dynamic-edge synthesizers only scan-and-filter, so they iterate to keep
928
+ * memory O(1) in the node count rather than O(nodes) (#610).
929
+ */
930
+ *iterateNodesByKind(kind) {
931
+ // Fresh statement per call (not a cached one): an iterator holds an open
932
+ // cursor, so a shared statement would conflict across overlapping scans.
933
+ const stmt = this.db.prepare('SELECT * FROM nodes WHERE kind = ?');
934
+ for (const row of stmt.iterate(kind)) {
935
+ yield rowToNode(row);
936
+ }
937
+ }
938
+ /**
939
+ * Get all nodes in the database
940
+ */
941
+ getAllNodes() {
942
+ const rows = this.db.prepare('SELECT * FROM nodes').all();
943
+ return rows.map(rowToNode);
944
+ }
945
+ /**
946
+ * Stream nodes of one language whose `decorators` JSON array contains
947
+ * `decorator`. The LIKE on the JSON text is a cheap index-free pre-filter
948
+ * (a decorator name can appear as a substring of another), so callers must
949
+ * still exact-check `node.decorators.includes(decorator)`. Exists so the
950
+ * kotlin expect/actual synthesizer never materializes the whole node table
951
+ * the way `getAllNodes().filter(...)` did — that array alone OOM'd Node's
952
+ * default heap on a 2M-node graph (#1212).
953
+ */
954
+ *iterateNodesByLanguageWithDecorator(language, decorator) {
955
+ // Fresh statement per call — an iterator holds an open cursor (see
956
+ // iterateNodesByKind).
957
+ const stmt = this.db.prepare("SELECT * FROM nodes WHERE language = ? AND decorators LIKE '%' || ? || '%'");
958
+ for (const row of stmt.iterate(language, `"${decorator}"`)) {
959
+ yield rowToNode(row);
960
+ }
961
+ }
962
+ /**
963
+ * Distinct languages present in the files table. One indexed aggregate —
964
+ * lets the dynamic-edge synthesizers skip passes for languages the project
965
+ * doesn't contain at all (a Kotlin pass has no work on a pure-C repo), so
966
+ * their cost is zero rather than a full-graph scan that finds nothing (#1212).
967
+ */
968
+ getDistinctFileLanguages() {
969
+ const rows = this.db.prepare('SELECT DISTINCT language FROM files').all();
970
+ return new Set(rows.map((r) => r.language));
971
+ }
972
+ /**
973
+ * Get nodes by exact name match (uses idx_nodes_name index).
974
+ *
975
+ * This is resolution's candidate list, and the ORDER BY is load-bearing for
976
+ * index correctness, not cosmetic (CG-33). When a reference names a symbol
977
+ * that several files define and nothing disambiguates them, resolution binds
978
+ * to the first candidate — so without an ORDER BY the winner was decided by
979
+ * rowid, i.e. by the order files happened to be WRITTEN. A full index writes
980
+ * them in scan order; an incremental sync appends each file as it changes, so
981
+ * the same tree resolved to different edges depending on how the index was
982
+ * built, and a long-lived synced index drifted away from a rebuild of itself
983
+ * (measured at 4.3% of distinct edges, mostly `calls`).
984
+ *
985
+ * `(file_path, start_line)` is a property of the CODE, so both paths now pick
986
+ * the same candidate. The sort is paid once per distinct name per resolution
987
+ * run — ReferenceResolver memoizes this in its nameCache — and the population
988
+ * is capped by AMBIGUOUS_NAME_CEILING (#999).
989
+ */
990
+ getNodesByName(name) {
991
+ if (!this.stmts.getNodesByName) {
992
+ this.stmts.getNodesByName = this.db.prepare('SELECT * FROM nodes WHERE name = ? ORDER BY file_path, start_line');
993
+ }
994
+ const rows = this.stmts.getNodesByName.all(name);
995
+ return rows.map(rowToNode);
996
+ }
997
+ /**
998
+ * Nodes whose name starts with `prefix`, by index range scan (a LIKE would
999
+ * skip idx_nodes_name under SQLite's default case-insensitive LIKE).
1000
+ */
1001
+ getNodesByNamePrefix(prefix, limit = 20) {
1002
+ if (!this.stmts.getNodesByNamePrefix) {
1003
+ this.stmts.getNodesByNamePrefix = this.db.prepare('SELECT * FROM nodes WHERE name >= ? AND name < ? ORDER BY name LIMIT ?');
1004
+ }
1005
+ const rows = this.stmts.getNodesByNamePrefix.all(prefix, prefix + '￿', limit);
1006
+ return rows.map(rowToNode);
1007
+ }
1008
+ /**
1009
+ * Get nodes by exact qualified name match (uses idx_nodes_qualified_name index)
1010
+ */
1011
+ getNodesByQualifiedNameExact(qualifiedName) {
1012
+ if (!this.stmts.getNodesByQualifiedNameExact) {
1013
+ this.stmts.getNodesByQualifiedNameExact = this.db.prepare('SELECT * FROM nodes WHERE qualified_name = ?');
1014
+ }
1015
+ const rows = this.stmts.getNodesByQualifiedNameExact.all(qualifiedName);
1016
+ return rows.map(rowToNode);
1017
+ }
1018
+ /**
1019
+ * Get nodes by name, case-insensitively (seeks the idx_nodes_lower_name
1020
+ * expression index).
1021
+ *
1022
+ * The parameter is lowered in SQL rather than trusted to arrive lowered, so
1023
+ * the lookup means the same thing whatever casing a caller hands it. Written
1024
+ * as a bare `lower(name) = ?` it silently returned nothing for any input
1025
+ * carrying an uppercase letter, and — because SQLite's `lower()` folds ASCII
1026
+ * only while JavaScript's `.toLowerCase()` folds Unicode — a caller that
1027
+ * pre-lowered in JavaScript could not match a non-ASCII name at all.
1028
+ *
1029
+ * Note this hardens the query, not its one caller: `matchFuzzy` still lowers
1030
+ * in JavaScript before calling, so the non-ASCII gap remains open there.
1031
+ */
1032
+ getNodesByLowerName(name) {
1033
+ if (!this.stmts.getNodesByLowerName) {
1034
+ this.stmts.getNodesByLowerName = this.db.prepare('SELECT * FROM nodes WHERE lower(name) = lower(?)');
1035
+ }
1036
+ const rows = this.stmts.getNodesByLowerName.all(name);
1037
+ return rows.map(rowToNode);
1038
+ }
1039
+ /**
1040
+ * Search nodes by name using FTS with fallback to LIKE for better matching
1041
+ *
1042
+ * Search strategy:
1043
+ * 1. Try FTS5 prefix match (query*) for word-start matching
1044
+ * 2. If no results, try LIKE for substring matching (e.g., "signIn" finds "signInWithGoogle")
1045
+ * 3. Score results based on match quality
1046
+ */
1047
+ searchNodes(query, options = {}) {
1048
+ const { limit = 100, offset = 0 } = options;
1049
+ // Parse field-qualified bits out of the raw query (kind:, lang:,
1050
+ // path:, name:). Anything not recognised stays in `text` and goes
1051
+ // to FTS unchanged. Filters compose with the SearchOptions arg —
1052
+ // both are applied (intersection-style).
1053
+ const parsed = (0, query_parser_1.parseQuery)(query);
1054
+ const mergedKinds = parsed.kinds.length > 0
1055
+ ? Array.from(new Set([...(options.kinds ?? []), ...parsed.kinds]))
1056
+ : options.kinds;
1057
+ const mergedLanguages = parsed.languages.length > 0
1058
+ ? Array.from(new Set([...(options.languages ?? []), ...parsed.languages]))
1059
+ : options.languages;
1060
+ const pathFilters = parsed.pathFilters;
1061
+ const nameFilters = parsed.nameFilters;
1062
+ // The text portion drives FTS/LIKE; if all the user typed was
1063
+ // filters (`kind:function`), we still need *some* candidate set,
1064
+ // so synthesise an empty-text path that returns everything matching
1065
+ // the filters.
1066
+ const text = parsed.text;
1067
+ const kinds = mergedKinds;
1068
+ const languages = mergedLanguages;
1069
+ // First try FTS5 with prefix matching (skip if FTS5 not available, #1532)
1070
+ let results = text
1071
+ ? (this._fts5Available !== false ? this.searchNodesFTS(text, { kinds, languages, limit, offset }) : [])
1072
+ // Over-fetch by 5× when running filter-only (no text). The
1073
+ // post-scoring path: + name: filters can be very selective, so
1074
+ // a smaller multiplier risks returning fewer than `limit`
1075
+ // results despite the DB having plenty of matches.
1076
+ : this.searchAllByFilters({ kinds, languages, limit: limit * 5 });
1077
+ // If no FTS results, try LIKE-based substring search
1078
+ if (results.length === 0 && text.length >= 2) {
1079
+ results = this.searchNodesLike(text, { kinds, languages, limit, offset });
1080
+ }
1081
+ // Final fuzzy fallback: scan all known names and keep those within
1082
+ // a tight Levenshtein distance. Only fires when both FTS and LIKE
1083
+ // returned nothing AND there's a text portion long enough to be
1084
+ // worth fuzzing (1-char queries would match too much).
1085
+ if (results.length === 0 && text.length >= 3) {
1086
+ results = this.searchNodesFuzzy(text, { kinds, languages, limit });
1087
+ }
1088
+ // Supplement: ensure exact name matches are always candidates.
1089
+ // BM25 can bury short exact-match names (e.g. "getBean") under hundreds of
1090
+ // compound names (e.g. "getBeanDescriptor") in large codebases,
1091
+ // pushing them past the FTS fetch limit before post-hoc scoring can help.
1092
+ // Use the max BM25 score as the base so the nameMatchBonus (exact=30 vs
1093
+ // prefix=20) actually differentiates them after rescoring.
1094
+ //
1095
+ // Whole-name equality MUST be written as `lower(name) = lower(?)` so it
1096
+ // seeks `idx_nodes_lower_name`. The equivalent `name = ? COLLATE NOCASE`
1097
+ // matches no index — `idx_nodes_name` is BINARY-collated and the expression
1098
+ // index only matches the same expression — and degrades to a full table
1099
+ // scan. The `LIMIT 20` does not rescue it: SQLite can only stop early once
1100
+ // it has produced 20 rows, and this runs once per query term, most of which
1101
+ // name nothing in the corpus. Measured per term on an unmatched term:
1102
+ // 0.08ms on gin (2.5k nodes), 0.39ms on excalidraw (11k), 2.4ms on django
1103
+ // (62k) — and growing with the corpus, where the seek is flat at ~0.002ms.
1104
+ // Lowering the parameter in SQL rather than in JS is deliberate: SQLite's
1105
+ // `lower()` and NOCASE both fold ASCII only, while JS `.toLowerCase()`
1106
+ // folds Unicode, which would silently stop matching non-ASCII names.
1107
+ if (results.length > 0 && query) {
1108
+ const existingIds = new Set(results.map(r => r.node.id));
1109
+ const maxFtsScore = Math.max(...results.map(r => r.score));
1110
+ const terms = query.split(/\s+/).filter(t => t.length >= 2);
1111
+ for (const term of terms) {
1112
+ let sql = 'SELECT * FROM nodes WHERE lower(name) = lower(?)';
1113
+ const params = [term];
1114
+ if (kinds && kinds.length > 0) {
1115
+ sql += ` AND kind IN (${kinds.map(() => '?').join(',')})`;
1116
+ params.push(...kinds);
1117
+ }
1118
+ if (languages && languages.length > 0) {
1119
+ sql += ` AND language IN (${languages.map(() => '?').join(',')})`;
1120
+ params.push(...languages);
1121
+ }
1122
+ sql += ' LIMIT 20';
1123
+ const rows = this.db.prepare(sql).all(...params);
1124
+ for (const row of rows) {
1125
+ if (!existingIds.has(row.id)) {
1126
+ results.push({ node: rowToNode(row), score: maxFtsScore });
1127
+ existingIds.add(row.id);
1128
+ }
1129
+ }
1130
+ }
1131
+ }
1132
+ // Apply multi-signal scoring
1133
+ if (results.length > 0 && (text || query)) {
1134
+ const scoringQuery = text || query;
1135
+ results = results.map(r => {
1136
+ // A path the project de-prioritized is saying its symbol NAMES are not
1137
+ // the answer, so the exact-name bonus has to be damped too. The -15 path
1138
+ // penalty alone cannot do it: the bonus is additive and larger (measured
1139
+ // on #982's repro, a `usage()` helper sat at 74.8 vs 51.2 for the top
1140
+ // product symbol — -15 lands at 59.8, still ahead). Damped, not zeroed,
1141
+ // so the tree stays findable when it genuinely is what you asked for.
1142
+ // Evaluated once and reused: the predicate stats the config file.
1143
+ const deprioritized = this.isDeprioritizedPath?.(r.node.filePath) ?? false;
1144
+ const nameBonus = (0, query_utils_1.nameMatchBonus)(r.node.name, scoringQuery);
1145
+ return {
1146
+ ...r,
1147
+ score: r.score
1148
+ + (0, query_utils_1.kindBonus)(r.node.kind)
1149
+ + (0, query_utils_1.scorePathRelevance)(r.node.filePath, scoringQuery, this.projectNameTokens, deprioritized)
1150
+ + (deprioritized ? Math.round(nameBonus * exports.DEPRIORITIZED_NAME_BONUS_SCALE) : nameBonus),
1151
+ };
1152
+ });
1153
+ results.sort((a, b) => b.score - a.score);
1154
+ // Trim to requested limit after rescoring
1155
+ if (results.length > limit) {
1156
+ results = results.slice(0, limit);
1157
+ }
1158
+ }
1159
+ // Apply path: + name: filters AFTER scoring. Scoring already uses
1160
+ // path/name as a soft signal; the explicit filters here are a hard
1161
+ // gate. Done last so the FTS limit fetched plenty of candidates to
1162
+ // narrow from.
1163
+ if (pathFilters.length > 0) {
1164
+ const lowered = pathFilters.map((p) => p.toLowerCase());
1165
+ results = results.filter((r) => {
1166
+ const fp = r.node.filePath.toLowerCase();
1167
+ return lowered.some((p) => fp.includes(p));
1168
+ });
1169
+ }
1170
+ if (nameFilters.length > 0) {
1171
+ const lowered = nameFilters.map((n) => n.toLowerCase());
1172
+ results = results.filter((r) => {
1173
+ const nm = r.node.name.toLowerCase();
1174
+ return lowered.some((n) => nm.includes(n));
1175
+ });
1176
+ }
1177
+ return results;
1178
+ }
1179
+ /**
1180
+ * Match-everything path used when the user supplied only field
1181
+ * filters (`kind:function lang:typescript`) with no text. Returns
1182
+ * candidates ordered by name; the caller's filter pass narrows to
1183
+ * what was asked for.
1184
+ */
1185
+ searchAllByFilters(options) {
1186
+ const { kinds, languages, limit } = options;
1187
+ let sql = 'SELECT * FROM nodes WHERE 1=1';
1188
+ const params = [];
1189
+ if (kinds && kinds.length > 0) {
1190
+ sql += ` AND kind IN (${kinds.map(() => '?').join(',')})`;
1191
+ params.push(...kinds);
1192
+ }
1193
+ if (languages && languages.length > 0) {
1194
+ sql += ` AND language IN (${languages.map(() => '?').join(',')})`;
1195
+ params.push(...languages);
1196
+ }
1197
+ sql += ' ORDER BY name LIMIT ?';
1198
+ params.push(limit);
1199
+ const rows = this.db.prepare(sql).all(...params);
1200
+ return rows.map((row) => ({ node: rowToNode(row), score: 1 }));
1201
+ }
1202
+ /**
1203
+ * Fuzzy fallback: when zero FTS/LIKE hits, try an edit-distance
1204
+ * sweep over the distinct symbol-name set. Caps `maxDist` at 2 so
1205
+ * `getUssr` finds `getUser` but `process` doesn't match `prosody`.
1206
+ * Bounded edit distance keeps each comparison cheap; the per-query
1207
+ * scan is O(distinct-name-count) which is far smaller than total
1208
+ * node count on any real codebase.
1209
+ */
1210
+ searchNodesFuzzy(text, options) {
1211
+ const { kinds, languages, limit } = options;
1212
+ const lowered = text.toLowerCase();
1213
+ const maxDist = lowered.length <= 4 ? 1 : 2;
1214
+ // Pull the distinct name list once. The set is cached on QueryBuilder
1215
+ // by getAllNodeNames(); even on a 200k-node project the distinct
1216
+ // name set is typically O(10k) because most names repeat. The
1217
+ // candidate-cap below bounds memory regardless.
1218
+ const allNames = this.getAllNodeNames();
1219
+ const candidates = [];
1220
+ for (const name of allNames) {
1221
+ const dist = (0, query_parser_1.boundedEditDistance)(name.toLowerCase(), lowered, maxDist);
1222
+ if (dist <= maxDist)
1223
+ candidates.push({ name, dist });
1224
+ }
1225
+ candidates.sort((a, b) => a.dist - b.dist);
1226
+ // Cap the per-name follow-up queries. Each survivor triggers a
1227
+ // separate `SELECT * FROM nodes WHERE name = ?`; without this cap
1228
+ // a project with many similar names (`getUser1`, `getUser2`...)
1229
+ // could fan out far beyond `limit` queries before the inner-loop
1230
+ // limit kicks in.
1231
+ const FUZZY_FOLLOWUP_CAP = Math.max(limit * 2, 50);
1232
+ const cappedCandidates = candidates.slice(0, FUZZY_FOLLOWUP_CAP);
1233
+ const results = [];
1234
+ const seen = new Set();
1235
+ for (const c of cappedCandidates) {
1236
+ if (results.length >= limit)
1237
+ break;
1238
+ let sql = 'SELECT * FROM nodes WHERE name = ?';
1239
+ const params = [c.name];
1240
+ if (kinds && kinds.length > 0) {
1241
+ sql += ` AND kind IN (${kinds.map(() => '?').join(',')})`;
1242
+ params.push(...kinds);
1243
+ }
1244
+ if (languages && languages.length > 0) {
1245
+ sql += ` AND language IN (${languages.map(() => '?').join(',')})`;
1246
+ params.push(...languages);
1247
+ }
1248
+ sql += ' LIMIT 5';
1249
+ const rows = this.db.prepare(sql).all(...params);
1250
+ for (const row of rows) {
1251
+ if (seen.has(row.id))
1252
+ continue;
1253
+ seen.add(row.id);
1254
+ // Lower the score for each edit step away from the query so
1255
+ // exact-match fallbacks (dist 0) outrank dist-2 typos.
1256
+ results.push({ node: rowToNode(row), score: 1 / (1 + c.dist) });
1257
+ if (results.length >= limit)
1258
+ break;
1259
+ }
1260
+ }
1261
+ return results;
1262
+ }
1263
+ /**
1264
+ * FTS5 search with prefix matching
1265
+ */
1266
+ searchNodesFTS(query, options) {
1267
+ const { kinds, languages, limit = 100, offset = 0 } = options;
1268
+ // Add prefix wildcard for better matching (e.g., "auth" matches "AuthService", "authenticate")
1269
+ // Escape special FTS5 characters and add prefix wildcard.
1270
+ //
1271
+ // `::` is a qualifier separator in Rust/C++/Ruby, not a token char,
1272
+ // so treat it as whitespace before the strip step. Otherwise queries
1273
+ // like `stage_apply::run` collapse to `stage_applyrun` (the colons
1274
+ // are stripped without splitting) and find nothing. See #173.
1275
+ const ftsQuery = query
1276
+ .replace(/::/g, ' ') // Rust/C++/Ruby qualifier separator
1277
+ .replace(/['"*():^]/g, '') // Remove FTS5 special chars
1278
+ .split(/\s+/)
1279
+ .filter(term => term.length > 0)
1280
+ // Strip FTS5 boolean operators to prevent query manipulation
1281
+ .filter(term => !/^(AND|OR|NOT|NEAR)$/i.test(term))
1282
+ .map(term => `"${term}"*`) // Prefix match each term
1283
+ .join(' OR ');
1284
+ if (!ftsQuery) {
1285
+ return [];
1286
+ }
1287
+ // BM25 column weights: id=0, name=20, qualified_name=5, docstring=1, signature=2
1288
+ // Heavy name weight ensures exact/prefix name matches rank above incidental
1289
+ // mentions in long docstrings or qualified names of nested symbols.
1290
+ // Fetch 5x requested limit so post-hoc rescoring (kindBonus, pathRelevance,
1291
+ // nameMatchBonus) can promote results that BM25 alone undervalues.
1292
+ const ftsLimit = Math.max(limit * 5, 100);
1293
+ let sql = `
1294
+ SELECT nodes.*, bm25(nodes_fts, 0, 20, 5, 1, 2) as score
1295
+ FROM nodes_fts
1296
+ JOIN nodes ON nodes_fts.id = nodes.id
1297
+ WHERE nodes_fts MATCH ?
1298
+ `;
1299
+ const params = [ftsQuery];
1300
+ if (kinds && kinds.length > 0) {
1301
+ sql += ` AND nodes.kind IN (${kinds.map(() => '?').join(',')})`;
1302
+ params.push(...kinds);
1303
+ }
1304
+ if (languages && languages.length > 0) {
1305
+ sql += ` AND nodes.language IN (${languages.map(() => '?').join(',')})`;
1306
+ params.push(...languages);
1307
+ }
1308
+ sql += ' ORDER BY score LIMIT ? OFFSET ?';
1309
+ params.push(ftsLimit, offset);
1310
+ try {
1311
+ const rows = this.db.prepare(sql).all(...params);
1312
+ return rows.map((row) => ({
1313
+ node: rowToNode(row),
1314
+ score: Math.abs(row.score), // bm25 returns negative scores
1315
+ }));
1316
+ }
1317
+ catch {
1318
+ // FTS query failed, return empty
1319
+ return [];
1320
+ }
1321
+ }
1322
+ /**
1323
+ * LIKE-based substring search for cases where FTS doesn't match
1324
+ * Useful for camelCase matching (e.g., "signIn" finds "signInWithGoogle")
1325
+ */
1326
+ searchNodesLike(query, options) {
1327
+ const { kinds, languages, limit = 100, offset = 0 } = options;
1328
+ let sql = `
1329
+ SELECT nodes.*,
1330
+ CASE
1331
+ WHEN name = ? THEN 1.0
1332
+ WHEN name LIKE ? THEN 0.9
1333
+ WHEN name LIKE ? THEN 0.8
1334
+ WHEN qualified_name LIKE ? THEN 0.7
1335
+ ELSE 0.5
1336
+ END as score
1337
+ FROM nodes
1338
+ WHERE (
1339
+ name LIKE ? OR
1340
+ qualified_name LIKE ? OR
1341
+ name LIKE ?
1342
+ )
1343
+ `;
1344
+ // Pattern variants for better matching
1345
+ const exactMatch = query;
1346
+ const startsWith = `${query}%`;
1347
+ const contains = `%${query}%`;
1348
+ const params = [
1349
+ exactMatch, // Exact match score
1350
+ startsWith, // Starts with score
1351
+ contains, // Contains score
1352
+ contains, // Qualified name score
1353
+ contains, // WHERE: name contains
1354
+ contains, // WHERE: qualified_name contains
1355
+ startsWith, // WHERE: name starts with
1356
+ ];
1357
+ if (kinds && kinds.length > 0) {
1358
+ sql += ` AND kind IN (${kinds.map(() => '?').join(',')})`;
1359
+ params.push(...kinds);
1360
+ }
1361
+ if (languages && languages.length > 0) {
1362
+ sql += ` AND language IN (${languages.map(() => '?').join(',')})`;
1363
+ params.push(...languages);
1364
+ }
1365
+ sql += ' ORDER BY score DESC, length(name) ASC LIMIT ? OFFSET ?';
1366
+ params.push(limit, offset);
1367
+ const rows = this.db.prepare(sql).all(...params);
1368
+ return rows.map((row) => ({
1369
+ node: rowToNode(row),
1370
+ score: row.score,
1371
+ }));
1372
+ }
1373
+ /**
1374
+ * Find nodes by exact name match
1375
+ *
1376
+ * Used for hybrid search - looks up symbols by exact name or case-insensitive match.
1377
+ * Returns high-confidence matches for known symbol names extracted from query.
1378
+ *
1379
+ * @param names - Array of symbol names to look up
1380
+ * @param options - Search options (kinds, languages, limit)
1381
+ * @returns SearchResult array with exact matches scored at 1.0
1382
+ */
1383
+ findNodesByExactName(names, options = {}) {
1384
+ if (names.length === 0)
1385
+ return [];
1386
+ const { kinds, languages, limit = 50 } = options;
1387
+ // Two-pass approach to handle common names (e.g., "run" has 40+ matches):
1388
+ // Pass 1: Find which files contain distinctive (rare) symbols from the query.
1389
+ // Pass 2: Query each name, boosting results that co-locate with distinctive symbols.
1390
+ // Pass 1: Find files containing each queried name, identify distinctive names
1391
+ //
1392
+ // Both passes spell whole-name equality as `lower(name) = lower(?)` so they
1393
+ // seek `idx_nodes_lower_name` — see the note in `searchNodes` for why the
1394
+ // `name = ? COLLATE NOCASE` form full-scans instead. This path is the one
1395
+ // that hurts most: it runs both passes for every symbol extracted from the
1396
+ // query, and extraction is generous, so most of those names are absent from
1397
+ // the corpus and never reach either LIMIT.
1398
+ const nameToFiles = new Map();
1399
+ for (const name of names) {
1400
+ let sql = 'SELECT DISTINCT file_path FROM nodes WHERE lower(name) = lower(?)';
1401
+ const params = [name];
1402
+ if (kinds && kinds.length > 0) {
1403
+ sql += ` AND kind IN (${kinds.map(() => '?').join(',')})`;
1404
+ params.push(...kinds);
1405
+ }
1406
+ sql += ' LIMIT 100';
1407
+ const rows = this.db.prepare(sql).all(...params);
1408
+ nameToFiles.set(name.toLowerCase(), new Set(rows.map(r => r.file_path)));
1409
+ }
1410
+ // Distinctive names are those with fewer than 10 file matches (e.g., "scrapeLoop" = 1 file)
1411
+ const distinctiveFiles = new Set();
1412
+ for (const [, files] of nameToFiles) {
1413
+ if (files.size > 0 && files.size < 10) {
1414
+ for (const f of files)
1415
+ distinctiveFiles.add(f);
1416
+ }
1417
+ }
1418
+ // Pass 2: Query each name with per-name limit, scoring by co-location
1419
+ const perNameLimit = Math.max(8, Math.ceil(limit / names.length));
1420
+ const allResults = [];
1421
+ const seenIds = new Set();
1422
+ for (const name of names) {
1423
+ let sql = `
1424
+ SELECT nodes.*, 1.0 as score
1425
+ FROM nodes
1426
+ WHERE lower(name) = lower(?)
1427
+ `;
1428
+ const params = [name];
1429
+ if (kinds && kinds.length > 0) {
1430
+ sql += ` AND kind IN (${kinds.map(() => '?').join(',')})`;
1431
+ params.push(...kinds);
1432
+ }
1433
+ if (languages && languages.length > 0) {
1434
+ sql += ` AND language IN (${languages.map(() => '?').join(',')})`;
1435
+ params.push(...languages);
1436
+ }
1437
+ // Fetch enough to find co-located results among common names
1438
+ sql += ' LIMIT ?';
1439
+ params.push(Math.max(perNameLimit * 3, 50));
1440
+ const rows = this.db.prepare(sql).all(...params);
1441
+ const nameResults = [];
1442
+ for (const row of rows) {
1443
+ const node = rowToNode(row);
1444
+ if (seenIds.has(node.id))
1445
+ continue;
1446
+ // Boost results in files that also contain distinctive symbols
1447
+ const coLocationBoost = distinctiveFiles.has(node.filePath) ? 20 : 0;
1448
+ nameResults.push({ node, score: row.score + coLocationBoost });
1449
+ }
1450
+ // Sort by score (co-located first), take per-name limit
1451
+ nameResults.sort((a, b) => b.score - a.score);
1452
+ for (const r of nameResults.slice(0, perNameLimit)) {
1453
+ seenIds.add(r.node.id);
1454
+ allResults.push(r);
1455
+ }
1456
+ }
1457
+ // Sort all results by score so co-located results bubble up
1458
+ allResults.sort((a, b) => b.score - a.score);
1459
+ return allResults.slice(0, limit);
1460
+ }
1461
+ /**
1462
+ * Find nodes whose name contains a substring (LIKE-based).
1463
+ * Useful for CamelCase-part matching where FTS fails because
1464
+ * e.g. "TransportSearchAction" is one FTS token, not matchable by "Search"*.
1465
+ *
1466
+ * Results are ordered by name length (shorter = more likely to be the core type).
1467
+ */
1468
+ findNodesByNameSubstring(substring, options = {}) {
1469
+ const { kinds, languages, limit = 30, excludePrefix } = options;
1470
+ let sql = `
1471
+ SELECT nodes.*, 1.0 as score
1472
+ FROM nodes
1473
+ WHERE name LIKE ?
1474
+ `;
1475
+ const params = [`%${substring}%`];
1476
+ // Exclude prefix matches (handled by FTS-based prefix search in Step 2b)
1477
+ if (excludePrefix) {
1478
+ sql += ` AND name NOT LIKE ?`;
1479
+ params.push(`${substring}%`);
1480
+ }
1481
+ if (kinds && kinds.length > 0) {
1482
+ sql += ` AND kind IN (${kinds.map(() => '?').join(',')})`;
1483
+ params.push(...kinds);
1484
+ }
1485
+ if (languages && languages.length > 0) {
1486
+ sql += ` AND language IN (${languages.map(() => '?').join(',')})`;
1487
+ params.push(...languages);
1488
+ }
1489
+ sql += ' ORDER BY length(name) ASC LIMIT ?';
1490
+ params.push(limit);
1491
+ const rows = this.db.prepare(sql).all(...params);
1492
+ return rows.map((row) => ({
1493
+ node: rowToNode(row),
1494
+ score: row.score,
1495
+ }));
1496
+ }
1497
+ // ===========================================================================
1498
+ // Edge Operations
1499
+ // ===========================================================================
1500
+ /**
1501
+ * Insert a new edge
1502
+ */
1503
+ insertEdge(edge) {
1504
+ if (!this.stmts.insertEdge) {
1505
+ this.stmts.insertEdge = this.db.prepare(`
1506
+ INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance)
1507
+ VALUES (@source, @target, @kind, @metadata, @line, @col, @provenance)
1508
+ `);
1509
+ }
1510
+ this.stmts.insertEdge.run({
1511
+ source: edge.source,
1512
+ target: edge.target,
1513
+ kind: edge.kind,
1514
+ metadata: edge.metadata ? JSON.stringify(edge.metadata) : null,
1515
+ line: edge.line ?? null,
1516
+ col: edge.column ?? null,
1517
+ provenance: edge.provenance ?? null,
1518
+ });
1519
+ }
1520
+ /**
1521
+ * Insert multiple edges in a transaction
1522
+ */
1523
+ insertEdges(edges) {
1524
+ if (edges.length === 0)
1525
+ return;
1526
+ this.db.transaction(() => {
1527
+ const endpointIds = new Set();
1528
+ for (const edge of edges) {
1529
+ endpointIds.add(edge.source);
1530
+ endpointIds.add(edge.target);
1531
+ }
1532
+ const existingNodeIds = this.getExistingNodeIds([...endpointIds]);
1533
+ const rows = [];
1534
+ for (const edge of edges) {
1535
+ if (!existingNodeIds.has(edge.source) || !existingNodeIds.has(edge.target)) {
1536
+ continue;
1537
+ }
1538
+ rows.push([
1539
+ edge.source,
1540
+ edge.target,
1541
+ edge.kind,
1542
+ edge.metadata ? JSON.stringify(edge.metadata) : null,
1543
+ edge.line ?? null,
1544
+ edge.column ?? null,
1545
+ edge.provenance ?? null,
1546
+ ]);
1547
+ }
1548
+ this.runBatched('insertEdges', 'INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance) VALUES ', '(?,?,?,?,?,?,?)', rows);
1549
+ })();
1550
+ }
1551
+ /**
1552
+ * Delete all edges from a source node
1553
+ */
1554
+ deleteEdgesBySource(sourceId) {
1555
+ if (!this.stmts.deleteEdgesBySource) {
1556
+ this.stmts.deleteEdgesBySource = this.db.prepare('DELETE FROM edges WHERE source = ?');
1557
+ }
1558
+ this.stmts.deleteEdgesBySource.run(sourceId);
1559
+ }
1560
+ /**
1561
+ * Get outgoing edges from a node
1562
+ */
1563
+ getOutgoingEdges(sourceId, kinds, provenance) {
1564
+ if ((kinds && kinds.length > 0) || provenance) {
1565
+ let sql = 'SELECT * FROM edges WHERE source = ?';
1566
+ const params = [sourceId];
1567
+ if (kinds && kinds.length > 0) {
1568
+ sql += ` AND kind IN (${kinds.map(() => '?').join(',')})`;
1569
+ params.push(...kinds);
1570
+ }
1571
+ if (provenance) {
1572
+ sql += ' AND provenance = ?';
1573
+ params.push(provenance);
1574
+ }
1575
+ const rows = this.db.prepare(sql).all(...params);
1576
+ return rows.map(rowToEdge);
1577
+ }
1578
+ if (!this.stmts.getEdgesBySource) {
1579
+ this.stmts.getEdgesBySource = this.db.prepare('SELECT * FROM edges WHERE source = ?');
1580
+ }
1581
+ const rows = this.stmts.getEdgesBySource.all(sourceId);
1582
+ return rows.map(rowToEdge);
1583
+ }
1584
+ /**
1585
+ * Get incoming edges to a node
1586
+ */
1587
+ getIncomingEdges(targetId, kinds) {
1588
+ if (kinds && kinds.length > 0) {
1589
+ const sql = `SELECT * FROM edges WHERE target = ? AND kind IN (${kinds.map(() => '?').join(',')})`;
1590
+ const rows = this.db.prepare(sql).all(targetId, ...kinds);
1591
+ return rows.map(rowToEdge);
1592
+ }
1593
+ if (!this.stmts.getEdgesByTarget) {
1594
+ this.stmts.getEdgesByTarget = this.db.prepare('SELECT * FROM edges WHERE target = ?');
1595
+ }
1596
+ const rows = this.stmts.getEdgesByTarget.all(targetId);
1597
+ return rows.map(rowToEdge);
1598
+ }
1599
+ /**
1600
+ * Outgoing edges for MANY source nodes in one query.
1601
+ *
1602
+ * The batch form of {@link getOutgoingEdges}. Building a nested outline needs
1603
+ * the `contains` edges of every container in a file at once; doing that one
1604
+ * source at a time is a query per symbol on files that have hundreds.
1605
+ */
1606
+ getOutgoingEdgesFrom(sourceIds, kinds) {
1607
+ if (sourceIds.length === 0)
1608
+ return [];
1609
+ const unique = [...new Set(sourceIds)];
1610
+ const out = [];
1611
+ for (let i = 0; i < unique.length; i += SQLITE_PARAM_CHUNK_SIZE) {
1612
+ const chunk = unique.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
1613
+ const placeholders = chunk.map(() => '?').join(',');
1614
+ let sql = `SELECT * FROM edges WHERE source IN (${placeholders})`;
1615
+ const params = [...chunk];
1616
+ if (kinds && kinds.length > 0) {
1617
+ sql += ` AND kind IN (${kinds.map(() => '?').join(',')})`;
1618
+ params.push(...kinds);
1619
+ }
1620
+ const rows = this.db.prepare(sql).all(...params);
1621
+ for (const row of rows)
1622
+ out.push(rowToEdge(row));
1623
+ }
1624
+ return out;
1625
+ }
1626
+ /**
1627
+ * Fan-in (total incoming edge count) for MANY nodes in one query.
1628
+ *
1629
+ * The per-node alternative — `getIncomingEdges(id).length` — is an indexed
1630
+ * lookup each, but a symbol screen rendering a couple of hundred callees
1631
+ * would issue a couple of hundred of them. Ids with no incoming edges are
1632
+ * absent from the map rather than present as 0, so callers can tell "no
1633
+ * edges" from "not asked about".
1634
+ */
1635
+ countIncomingEdges(ids) {
1636
+ const out = new Map();
1637
+ if (ids.length === 0)
1638
+ return out;
1639
+ const unique = [...new Set(ids)];
1640
+ for (let i = 0; i < unique.length; i += SQLITE_PARAM_CHUNK_SIZE) {
1641
+ const chunk = unique.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
1642
+ const placeholders = chunk.map(() => '?').join(',');
1643
+ const rows = this.db
1644
+ .prepare(`SELECT target, COUNT(*) AS count FROM edges WHERE target IN (${placeholders}) GROUP BY target`)
1645
+ .all(...chunk);
1646
+ for (const row of rows)
1647
+ out.set(row.target, row.count);
1648
+ }
1649
+ return out;
1650
+ }
1651
+ /**
1652
+ * Incoming edges for MANY target nodes in one query — the mirror of
1653
+ * {@link getOutgoingEdgesFrom}. Needed wherever a whole file's inbound edges
1654
+ * are wanted at once ("which files import anything in this one?").
1655
+ */
1656
+ getIncomingEdgesTo(targetIds, kinds) {
1657
+ if (targetIds.length === 0)
1658
+ return [];
1659
+ const unique = [...new Set(targetIds)];
1660
+ const out = [];
1661
+ for (let i = 0; i < unique.length; i += SQLITE_PARAM_CHUNK_SIZE) {
1662
+ const chunk = unique.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
1663
+ const placeholders = chunk.map(() => '?').join(',');
1664
+ let sql = `SELECT * FROM edges WHERE target IN (${placeholders})`;
1665
+ const params = [...chunk];
1666
+ if (kinds && kinds.length > 0) {
1667
+ sql += ` AND kind IN (${kinds.map(() => '?').join(',')})`;
1668
+ params.push(...kinds);
1669
+ }
1670
+ const rows = this.db.prepare(sql).all(...params);
1671
+ for (const row of rows)
1672
+ out.push(rowToEdge(row));
1673
+ }
1674
+ return out;
1675
+ }
1676
+ /**
1677
+ * Fan-out (total outgoing edge count) for MANY nodes in one query — the
1678
+ * mirror of {@link countIncomingEdges}. Ids with no outgoing edges are absent
1679
+ * from the map rather than present as 0.
1680
+ */
1681
+ countOutgoingEdges(ids) {
1682
+ const out = new Map();
1683
+ if (ids.length === 0)
1684
+ return out;
1685
+ const unique = [...new Set(ids)];
1686
+ for (let i = 0; i < unique.length; i += SQLITE_PARAM_CHUNK_SIZE) {
1687
+ const chunk = unique.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
1688
+ const placeholders = chunk.map(() => '?').join(',');
1689
+ const rows = this.db
1690
+ .prepare(`SELECT source, COUNT(*) AS count FROM edges WHERE source IN (${placeholders}) GROUP BY source`)
1691
+ .all(...chunk);
1692
+ for (const row of rows)
1693
+ out.set(row.source, row.count);
1694
+ }
1695
+ return out;
1696
+ }
1697
+ /**
1698
+ * Symbols nothing in the index points at — the candidate set behind the dead
1699
+ * code list (`src/graph/dead-code.ts`).
1700
+ *
1701
+ * "Points at" is every edge kind EXCEPT `contains`: a class containing a
1702
+ * method is structure, not use, and counting it would make every member look
1703
+ * reached by its own container. A self-edge is excluded for the same reason
1704
+ * a recursive function is not its own caller.
1705
+ *
1706
+ * One scan, one index probe per candidate. `NOT EXISTS` over
1707
+ * `idx_edges_target_kind` is what keeps it that way — the alternative
1708
+ * (`LEFT JOIN edges … GROUP BY`) builds a row per edge for the whole table
1709
+ * before discarding all but the empty groups. Ordered by position so the
1710
+ * answer is stable across runs and groups by file without a second sort.
1711
+ *
1712
+ * The result is deliberately NOT called dead code: an unreferenced symbol is
1713
+ * a symbol with no STATIC reference, and the caller applies the exclusions
1714
+ * (tests, generated files, overrides, unresolved names) that turn the
1715
+ * candidate set into a claim worth making.
1716
+ */
1717
+ getUnreferencedNodes(kinds, limit) {
1718
+ if (kinds.length === 0 || limit <= 0)
1719
+ return [];
1720
+ const placeholders = kinds.map(() => '?').join(',');
1721
+ const rows = this.db
1722
+ .prepare(`SELECT n.*, COALESCE(f.generated, 0) AS file_generated
1723
+ FROM nodes n
1724
+ LEFT JOIN files f ON f.path = n.file_path
1725
+ WHERE n.kind IN (${placeholders})
1726
+ AND NOT EXISTS (
1727
+ SELECT 1 FROM edges e
1728
+ WHERE e.target = n.id
1729
+ AND e.kind != 'contains'
1730
+ AND e.source != n.id
1731
+ )
1732
+ ORDER BY n.file_path, n.start_line, n.name
1733
+ LIMIT ?`)
1734
+ .all(...kinds, limit);
1735
+ return rows.map((row) => ({ node: rowToNode(row), generated: row.file_generated === 1 }));
1736
+ }
1737
+ /**
1738
+ * Which of `names` the index holds an UNRESOLVED reference to.
1739
+ *
1740
+ * The point is honesty about our own blind spots. A `failed` row in
1741
+ * `unresolved_refs` records that some file referenced a name and the resolver
1742
+ * could not decide what it meant — so a symbol with that name cannot be
1743
+ * called unreferenced, whatever the edge table says. It is deliberately
1744
+ * matched loosely, on the reference name AND on its tail (`util.greet` →
1745
+ * `greet`), because the question being asked is "could this name be the one
1746
+ * we failed to follow", and a maybe has to count as a yes.
1747
+ *
1748
+ * Bounded-lookup like {@link getGeneratedPathsAmong}: the caller holds a
1749
+ * candidate list, so this is a chunked probe over `idx_unresolved_name`, not
1750
+ * a scan of the table.
1751
+ */
1752
+ getUnresolvedNamesAmong(names) {
1753
+ const unique = [...new Set(names)].filter((name) => name.length > 0);
1754
+ const found = new Set();
1755
+ if (unique.length === 0)
1756
+ return found;
1757
+ for (let i = 0; i < unique.length; i += SQLITE_PARAM_CHUNK_SIZE) {
1758
+ const chunk = unique.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
1759
+ const placeholders = chunk.map(() => '?').join(',');
1760
+ const rows = this.db
1761
+ .prepare(`SELECT DISTINCT reference_name AS name FROM unresolved_refs
1762
+ WHERE reference_name IN (${placeholders})
1763
+ UNION
1764
+ SELECT DISTINCT name_tail AS name FROM unresolved_refs
1765
+ WHERE name_tail IN (${placeholders})`)
1766
+ .all(...chunk, ...chunk);
1767
+ for (const row of rows)
1768
+ found.add(row.name);
1769
+ }
1770
+ return found;
1771
+ }
1772
+ /**
1773
+ * Which of `names` are carried by MORE THAN ONE symbol, at least one of which
1774
+ * something points at.
1775
+ *
1776
+ * The false positive this exists to kill: `AlphaCodeSense.getTopRouteFile` calls
1777
+ * `this.queries.getTopRouteFile()`, and the resolver — which prefers a
1778
+ * same-name definition in the call site's own file — attaches that edge to
1779
+ * the *calling* method. One of the two ends up with a self-edge and the other
1780
+ * with nothing at all, and neither is unreferenced. From the edge table the
1781
+ * mis-resolution and a genuinely unused twin are the same picture, so the
1782
+ * claim is not made about either.
1783
+ *
1784
+ * Both halves of the condition are load-bearing. **More than one symbol**:
1785
+ * a uniquely-named function that only calls itself is genuinely dead, and
1786
+ * excluding every recursive function would gut the list. **Self-edges
1787
+ * counted**: the self-edge IS the fingerprint of the mis-resolution above, so
1788
+ * it has to count as evidence that this name resolves somewhere.
1789
+ *
1790
+ * Chunked probe over `idx_nodes_name`, bounded by the caller's candidate list.
1791
+ */
1792
+ getAmbiguousReferencedNames(names) {
1793
+ const unique = [...new Set(names)].filter((name) => name.length > 0);
1794
+ const found = new Set();
1795
+ if (unique.length === 0)
1796
+ return found;
1797
+ for (let i = 0; i < unique.length; i += SQLITE_PARAM_CHUNK_SIZE) {
1798
+ const chunk = unique.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
1799
+ const placeholders = chunk.map(() => '?').join(',');
1800
+ const rows = this.db
1801
+ .prepare(`SELECT name FROM (
1802
+ SELECT n.name AS name,
1803
+ EXISTS (
1804
+ SELECT 1 FROM edges e
1805
+ WHERE e.target = n.id AND e.kind != 'contains'
1806
+ ) AS referenced
1807
+ FROM nodes n
1808
+ WHERE n.name IN (${placeholders})
1809
+ )
1810
+ GROUP BY name
1811
+ HAVING COUNT(*) > 1 AND SUM(referenced) > 0`)
1812
+ .all(...chunk);
1813
+ for (const row of rows)
1814
+ found.add(row.name);
1815
+ }
1816
+ return found;
1817
+ }
1818
+ /**
1819
+ * Which of the given languages the index records an EXPORT marker for.
1820
+ *
1821
+ * A self-measurement, and the honest basis for a whole class of exclusion.
1822
+ * The dead code report's strongest filter is "exported symbols may be reached
1823
+ * from outside this repository" — and that filter silently does nothing for a
1824
+ * language whose exports are not recorded, either because the extractor does
1825
+ * not record them (Rust `pub`) or because the language has no such concept at
1826
+ * all (Python, C, Ruby: the header or the module IS the surface). Rather than
1827
+ * carry a table of which is which, ask the index: if nothing in this language
1828
+ * is marked exported, the filter did not run, and no claim about outside
1829
+ * reachability can be made for it.
1830
+ *
1831
+ * `idx_nodes_language` covers the grouping; the caller passes the handful of
1832
+ * languages its candidates are actually in.
1833
+ */
1834
+ getLanguagesWithExports(languages) {
1835
+ const unique = [...new Set(languages)].filter((language) => language.length > 0);
1836
+ const found = new Set();
1837
+ if (unique.length === 0)
1838
+ return found;
1839
+ for (let i = 0; i < unique.length; i += SQLITE_PARAM_CHUNK_SIZE) {
1840
+ const chunk = unique.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
1841
+ const placeholders = chunk.map(() => '?').join(',');
1842
+ const rows = this.db
1843
+ .prepare(`SELECT language, MAX(is_exported) AS any_exported
1844
+ FROM nodes
1845
+ WHERE language IN (${placeholders})
1846
+ GROUP BY language`)
1847
+ .all(...chunk);
1848
+ for (const row of rows)
1849
+ if (row.any_exported === 1)
1850
+ found.add(row.language);
1851
+ }
1852
+ return found;
1853
+ }
1854
+ /**
1855
+ * The nodes with the most DISTINCT dependents, most first.
1856
+ *
1857
+ * "Distinct" is the difference that matters: a helper called forty times from
1858
+ * one function has a fan-in of 40 but exactly one dependent. This counts the
1859
+ * second thing — the number a reader means by "N callers" — so the top of
1860
+ * this list is the set of symbols a change actually radiates furthest from.
1861
+ *
1862
+ * `contains` is excluded because it is structure, not dependency: counting it
1863
+ * would rank every file and class above the code they hold.
1864
+ */
1865
+ getTopDependedOn(limit) {
1866
+ if (limit <= 0)
1867
+ return [];
1868
+ const rows = this.db
1869
+ .prepare(`SELECT target AS nodeId, COUNT(DISTINCT source) AS dependents
1870
+ FROM edges
1871
+ WHERE kind != 'contains' AND source != target
1872
+ GROUP BY target
1873
+ ORDER BY dependents DESC
1874
+ LIMIT ?`)
1875
+ .all(limit);
1876
+ return rows;
1877
+ }
1878
+ /**
1879
+ * The graph's executable roots — files that RUN something at module level,
1880
+ * ranked by how much of the project they set in motion.
1881
+ *
1882
+ * The engine records a statement at the top level of a file as an edge from
1883
+ * the *file* node, so `src/bin/codegraph.ts` calling `program.parse()` at
1884
+ * module scope is a `calls` edge out of a `file`. That set is what makes the
1885
+ * roots of a dependency graph visible: a library module holds definitions and
1886
+ * runs nothing until someone imports it, while a CLI, a worker entry or a
1887
+ * build script does its work on the way down the file. `instantiates` counts
1888
+ * the same way — `new Server(...)` at module scope is the same act.
1889
+ *
1890
+ * A call made while initializing a module-level `variable` / `constant` —
1891
+ * `const service = new Service()`, `app = FastAPI()` — is attributed to the
1892
+ * declared name (#693), not to the file, so the file's own edges alone would
1893
+ * miss most of what a real entry point runs. Those names are the file's
1894
+ * top-level code too, so `tops` counts them alongside the file node.
1895
+ *
1896
+ * Ranking multiplies the two things an entry point does: it runs (calls), and
1897
+ * it wires the project together (distinct other files its symbols reach). One
1898
+ * alone is misleading — a registration table makes hundreds of module-level
1899
+ * calls into itself, and a barrel file imports everything and runs nothing.
1900
+ * The product puts the file that does both at the top.
1901
+ */
1902
+ getTopCallingFiles(limit) {
1903
+ if (limit <= 0)
1904
+ return [];
1905
+ return this.db
1906
+ .prepare(`WITH tops AS (
1907
+ SELECT n.id AS file_id, n.id AS src
1908
+ FROM nodes n
1909
+ WHERE n.kind = 'file'
1910
+ UNION ALL
1911
+ SELECT c.source AS file_id, c.target AS src
1912
+ FROM edges c
1913
+ JOIN nodes f ON f.id = c.source
1914
+ JOIN nodes v ON v.id = c.target
1915
+ WHERE c.kind = 'contains'
1916
+ AND f.kind = 'file'
1917
+ AND v.kind IN ('variable', 'constant')
1918
+ ),
1919
+ runs AS (
1920
+ SELECT t.file_id AS id, COUNT(*) AS calls
1921
+ FROM tops t
1922
+ JOIN edges e ON e.source = t.src
1923
+ WHERE e.kind IN ('calls', 'instantiates')
1924
+ GROUP BY t.file_id
1925
+ ),
1926
+ cand AS (
1927
+ SELECT r.id AS id, n.file_path AS fp, r.calls AS calls
1928
+ FROM runs r JOIN nodes n ON n.id = r.id
1929
+ ),
1930
+ wires AS (
1931
+ SELECT sn.file_path AS fp, COUNT(DISTINCT tn.file_path) AS reaches
1932
+ FROM edges e
1933
+ JOIN nodes sn ON sn.id = e.source
1934
+ JOIN nodes tn ON tn.id = e.target
1935
+ WHERE e.kind != 'contains'
1936
+ AND sn.file_path <> tn.file_path
1937
+ AND sn.file_path IN (SELECT fp FROM cand)
1938
+ GROUP BY sn.file_path
1939
+ )
1940
+ SELECT c.id AS nodeId,
1941
+ c.fp AS filePath,
1942
+ c.calls AS calls,
1943
+ COALESCE(w.reaches, 0) AS reaches,
1944
+ c.calls * (1 + COALESCE(w.reaches, 0)) AS score
1945
+ FROM cand c LEFT JOIN wires w ON w.fp = c.fp
1946
+ ORDER BY score DESC, calls DESC, filePath
1947
+ LIMIT ?`)
1948
+ .all(limit);
1949
+ }
1950
+ /**
1951
+ * How many OTHER files depend on each of the given files.
1952
+ *
1953
+ * Counted through the symbols, not the file nodes: an `imports` edge points
1954
+ * at the imported symbol, so a file node almost never receives one and
1955
+ * counting edges into it would report every file as depended on by nobody.
1956
+ * Same-file edges are excluded, which is what makes zero mean "nothing else
1957
+ * in the index reaches into this file" — the honest reading of a root.
1958
+ */
1959
+ getFileDependentCounts(filePaths) {
1960
+ if (filePaths.length === 0)
1961
+ return [];
1962
+ return this.db
1963
+ .prepare(`SELECT tn.file_path AS filePath, COUNT(DISTINCT sn.file_path) AS dependents
1964
+ FROM edges e
1965
+ JOIN nodes tn ON tn.id = e.target
1966
+ JOIN nodes sn ON sn.id = e.source
1967
+ WHERE e.kind != 'contains'
1968
+ AND tn.file_path IN (SELECT value FROM json_each(?))
1969
+ AND sn.file_path <> tn.file_path
1970
+ GROUP BY tn.file_path`)
1971
+ .all(JSON.stringify(filePaths));
1972
+ }
1973
+ /**
1974
+ * How far each of the given files reaches OUT: distinct other files its
1975
+ * symbols touch, and how many references that is.
1976
+ *
1977
+ * The mirror of {@link getFileDependentCounts}, and the same reasoning about
1978
+ * `contains` and same-file edges applies. It is driven from `nodes` rather
1979
+ * than from `edges` so the work is proportional to the files asked about —
1980
+ * the entry-points endpoint asks it about every test file in the index, and
1981
+ * an edge-first plan would scan the whole table to answer a question about a
1982
+ * tenth of it.
1983
+ */
1984
+ getFileReachCounts(filePaths) {
1985
+ if (filePaths.length === 0)
1986
+ return [];
1987
+ return this.db
1988
+ .prepare(`SELECT sn.file_path AS filePath,
1989
+ COUNT(DISTINCT tn.file_path) AS reaches,
1990
+ COUNT(*) AS refs
1991
+ FROM nodes sn
1992
+ JOIN edges e ON e.source = sn.id
1993
+ JOIN nodes tn ON tn.id = e.target
1994
+ WHERE sn.file_path IN (SELECT value FROM json_each(?))
1995
+ AND e.kind != 'contains'
1996
+ AND tn.file_path <> sn.file_path
1997
+ GROUP BY sn.file_path`)
1998
+ .all(JSON.stringify(filePaths));
1999
+ }
2000
+ /**
2001
+ * The `file` nodes for the given paths, in one query.
2002
+ *
2003
+ * A file's own node is what makes a file row navigable, and looking it up
2004
+ * with {@link getNodesInFile} means materialising every symbol in the file to
2005
+ * throw all but one away.
2006
+ */
2007
+ getFileNodes(filePaths) {
2008
+ if (filePaths.length === 0)
2009
+ return [];
2010
+ const rows = this.db
2011
+ .prepare(`SELECT * FROM nodes
2012
+ WHERE kind = 'file'
2013
+ AND file_path IN (SELECT value FROM json_each(?))`)
2014
+ .all(JSON.stringify(filePaths));
2015
+ return rows.map(rowToNode);
2016
+ }
2017
+ /**
2018
+ * Roll the whole edge table up to module granularity in one pass.
2019
+ *
2020
+ * The caller decides what a module IS — it hands in a file → module
2021
+ * assignment and gets back the cross-module traffic. That split is
2022
+ * deliberate: naming modules is a *policy* (top-level directories, a façade
2023
+ * file kept separate, a monorepo root) that belongs where the reader lives,
2024
+ * while grouping a million edges by it is *mechanics* that must happen in
2025
+ * SQLite. Doing the fold in JavaScript instead means materialising every
2026
+ * cross-file edge in memory; doing the naming in SQL means a tower of
2027
+ * `instr`/`substr` no one can read.
2028
+ *
2029
+ * The assignment lands in a TEMP table with a primary key, so the join is
2030
+ * indexed and the result set is bounded by modules², not by edges. Temp
2031
+ * tables live in SQLite's own temp database, so this stays valid against a
2032
+ * read-only main.
2033
+ *
2034
+ * Two result sets, because they need two different groupings over the same
2035
+ * join: `links` counts edges per (module, module, kind), and `pairs` names
2036
+ * the busiest symbol pairs behind each link (the map's tooltip). `pairs` is
2037
+ * ranked and cut inside SQLite — the un-cut grouping is the one thing here
2038
+ * that scales with distinct symbol names rather than with modules. Pairs are
2039
+ * ranked by `declared` before raw count, so a link's tooltip names the
2040
+ * symbols the source actually points at rather than whichever `has`/`get`
2041
+ * happened to name-match most often.
2042
+ *
2043
+ * `declared` is the subset of a link's edges that came from something the
2044
+ * source *writes down*: an import, a qualified name, an inheritance clause,
2045
+ * or a call through a typed receiver. It exists because bare name matching
2046
+ * (`resolvedBy: 'exact-match'`) is what invents cross-module links out of
2047
+ * common method names — `run`, `push`, `finish` — and a map that lets those
2048
+ * decide the layering puts the storage layer above the CLI.
2049
+ */
2050
+ aggregateModuleGraph(assignments, options) {
2051
+ if (assignments.length === 0 || options.kinds.length === 0)
2052
+ return { links: [], pairs: [] };
2053
+ const CONFIDENCE = `COALESCE(json_extract(e.metadata, '$.confidence'), 1)`;
2054
+ const DECLARED = `(json_extract(e.metadata, '$.resolvedBy') IN ('import', 'qualified-name')
2055
+ OR e.kind IN ('extends', 'implements')
2056
+ OR (json_extract(e.metadata, '$.resolvedBy') = 'instance-method'
2057
+ AND ${CONFIDENCE} >= 0.9))`;
2058
+ this.db.exec('DROP TABLE IF EXISTS temp.cg_module_map');
2059
+ this.db.exec('CREATE TEMP TABLE cg_module_map (path TEXT PRIMARY KEY, mod TEXT NOT NULL)');
2060
+ try {
2061
+ const insert = this.db.prepare('INSERT OR REPLACE INTO cg_module_map (path, mod) VALUES (?, ?)');
2062
+ this.db.exec('BEGIN');
2063
+ try {
2064
+ for (const row of assignments)
2065
+ insert.run(row.filePath, row.module);
2066
+ this.db.exec('COMMIT');
2067
+ }
2068
+ catch (err) {
2069
+ this.db.exec('ROLLBACK');
2070
+ throw err;
2071
+ }
2072
+ // ONE pass over the edge table. Grouping by the symbol names as well as
2073
+ // the modules costs nothing extra in scan time — the join is what is
2074
+ // expensive — and it buys both results from a single scan. Measured on
2075
+ // this index inflated to 1.6M edges: 1.66s for this query against 3.0s
2076
+ // for the module-level and name-level queries run separately, which is
2077
+ // the difference between meeting and missing the map's cold budget on a
2078
+ // ten-thousand-file repository.
2079
+ const rows = this.db
2080
+ .prepare(`SELECT ms.mod AS source, mt.mod AS target, e.kind AS kind,
2081
+ sn.name AS "from", tn.name AS "to",
2082
+ SUM(CASE WHEN ${CONFIDENCE} >= ? THEN 1 ELSE 0 END) AS count,
2083
+ SUM(CASE WHEN ${CONFIDENCE} >= ? AND ${DECLARED} THEN 1 ELSE 0 END) AS declared,
2084
+ SUM(CASE WHEN ${CONFIDENCE} < ? THEN 1 ELSE 0 END) AS uncertain
2085
+ FROM edges e
2086
+ JOIN nodes sn ON sn.id = e.source
2087
+ JOIN nodes tn ON tn.id = e.target
2088
+ JOIN cg_module_map ms ON ms.path = sn.file_path
2089
+ JOIN cg_module_map mt ON mt.path = tn.file_path
2090
+ WHERE e.kind IN (SELECT value FROM json_each(?))
2091
+ AND ms.mod <> mt.mod
2092
+ GROUP BY ms.mod, mt.mod, e.kind, sn.name, tn.name`)
2093
+ .all(options.minConfidence, options.minConfidence, options.minConfidence, JSON.stringify(options.kinds));
2094
+ return foldModuleRows(rows, options);
2095
+ }
2096
+ finally {
2097
+ this.db.exec('DROP TABLE IF EXISTS temp.cg_module_map');
2098
+ }
2099
+ }
2100
+ /**
2101
+ * Every ordered pair of files where one reaches into the other, once each.
2102
+ *
2103
+ * The input a cycle finder wants: file-level circular dependencies are the
2104
+ * strongly connected components of this graph. One query instead of the
2105
+ * dependency lookup per file that {@link GraphQueryManager.findCircularDependencies}
2106
+ * does — which matters because a cycle report is only interesting on a large
2107
+ * repo, and that is exactly where a query per file stops being affordable.
2108
+ *
2109
+ * `contains` is excluded (a file "contains" its own symbols, which is not a
2110
+ * dependency), and so are same-file edges and low-confidence name matches:
2111
+ * a cycle conjured by a common method name is a false alarm a reader cannot
2112
+ * check.
2113
+ */
2114
+ getCrossFileDependencyPairs(minConfidence) {
2115
+ return this.db
2116
+ .prepare(`SELECT DISTINCT sn.file_path AS source, tn.file_path AS target
2117
+ FROM edges e
2118
+ JOIN nodes sn ON sn.id = e.source
2119
+ JOIN nodes tn ON tn.id = e.target
2120
+ WHERE e.kind <> 'contains'
2121
+ AND sn.file_path <> tn.file_path
2122
+ AND COALESCE(json_extract(e.metadata, '$.confidence'), 1) >= ?`)
2123
+ .all(minConfidence);
2124
+ }
2125
+ /**
2126
+ * Every unresolved reference recorded in one FILE, ordered by line.
2127
+ *
2128
+ * The per-symbol form above answers "what does this body reach that the
2129
+ * index does not hold". A whole-file reader asks the same question of every
2130
+ * line at once, and asking it one symbol at a time is a query per symbol —
2131
+ * 153 of them on this repo's largest file. `unresolved_refs.file_path` is
2132
+ * indexed, so this is one lookup whatever the file holds.
2133
+ *
2134
+ * `limit` bounds the answer rather than the work: the caller draws a marker
2135
+ * per row, and a generated file with fifty thousand of them would ship
2136
+ * megabytes to say something a count already says. Rows come back in line
2137
+ * order, so a cap trims the END of the file, which is at least legible.
2138
+ */
2139
+ getUnresolvedReferencesInFile(filePath, limit = 5000) {
2140
+ if (!this.stmts.getUnresolvedInFile) {
2141
+ this.stmts.getUnresolvedInFile = this.db.prepare('SELECT * FROM unresolved_refs WHERE file_path = ? ORDER BY line, col LIMIT ?');
2142
+ }
2143
+ const rows = this.stmts.getUnresolvedInFile.all(filePath, limit);
2144
+ return rows.map((row) => ({
2145
+ fromNodeId: row.from_node_id,
2146
+ referenceName: row.reference_name,
2147
+ referenceKind: row.reference_kind,
2148
+ line: row.line,
2149
+ column: row.col,
2150
+ candidates: row.candidates ? (0, utils_1.safeJsonParse)(row.candidates, undefined) : undefined,
2151
+ filePath: row.file_path,
2152
+ language: row.language,
2153
+ rowId: row.id,
2154
+ }));
2155
+ }
2156
+ /**
2157
+ * References recorded against a symbol that never resolved to a node — the
2158
+ * calls and type mentions that leave the index (a third-party package, a
2159
+ * runtime builtin, a language construct extraction doesn't model).
2160
+ *
2161
+ * Read-only. It exists so a reader can say "N calls into symbols outside the
2162
+ * index" instead of silently showing a callee list shorter than the body's
2163
+ * call sites, which reads as "nothing else happens here".
2164
+ */
2165
+ getUnresolvedReferencesFrom(fromNodeId) {
2166
+ if (!this.stmts.getUnresolvedFromNode) {
2167
+ this.stmts.getUnresolvedFromNode = this.db.prepare('SELECT * FROM unresolved_refs WHERE from_node_id = ?');
2168
+ }
2169
+ const rows = this.stmts.getUnresolvedFromNode.all(fromNodeId);
2170
+ return rows.map((row) => ({
2171
+ fromNodeId: row.from_node_id,
2172
+ referenceName: row.reference_name,
2173
+ referenceKind: row.reference_kind,
2174
+ line: row.line,
2175
+ column: row.col,
2176
+ candidates: row.candidates ? (0, utils_1.safeJsonParse)(row.candidates, undefined) : undefined,
2177
+ filePath: row.file_path,
2178
+ language: row.language,
2179
+ rowId: row.id,
2180
+ }));
2181
+ }
2182
+ /**
2183
+ * Find all edges where both source and target are in the given node set.
2184
+ * Useful for recovering inter-node connectivity after BFS.
2185
+ */
2186
+ findEdgesBetweenNodes(nodeIds, kinds) {
2187
+ if (nodeIds.length === 0)
2188
+ return [];
2189
+ const idsJson = JSON.stringify(nodeIds);
2190
+ let sql = `SELECT * FROM edges WHERE source IN (SELECT value FROM json_each(?)) AND target IN (SELECT value FROM json_each(?))`;
2191
+ const params = [idsJson, idsJson];
2192
+ if (kinds && kinds.length > 0) {
2193
+ sql += ` AND kind IN (${kinds.map(() => '?').join(',')})`;
2194
+ params.push(...kinds);
2195
+ }
2196
+ const rows = this.db.prepare(sql).all(...params);
2197
+ return rows.map(rowToEdge);
2198
+ }
2199
+ /**
2200
+ * Distinct file paths that DEPEND ON `filePath`: every file containing a
2201
+ * symbol with a cross-file edge (any kind except `contains`) into a symbol
2202
+ * of this file. This is the file-level projection of the symbol dependency
2203
+ * graph and the basis for blast-radius / `affected` test selection.
2204
+ *
2205
+ * It deliberately does NOT restrict to `imports` edges. In this graph an
2206
+ * `imports` edge connects a file to its own local import declarations
2207
+ * (it is always same-file), so an imports-only lookup returns zero
2208
+ * cross-file dependents for every file. The real cross-file dependency
2209
+ * signal is the resolved call/reference graph — calls, references,
2210
+ * instantiates, extends, implements, overrides, type_of, returns,
2211
+ * decorates — exactly what {@link GraphTraverser.getImpactRadius} traverses.
2212
+ * `contains` is excluded: a parent containing a symbol does not *depend* on
2213
+ * it. One indexed query (idx_nodes_file_path + idx_edges_target_kind).
2214
+ */
2215
+ getDependentFilePaths(filePath) {
2216
+ const sql = `SELECT DISTINCT src.file_path AS fp
2217
+ FROM edges e
2218
+ JOIN nodes tgt ON tgt.id = e.target
2219
+ JOIN nodes src ON src.id = e.source
2220
+ WHERE tgt.file_path = ?
2221
+ AND e.kind != 'contains'
2222
+ AND src.file_path != ?`;
2223
+ const rows = this.db.prepare(sql).all(filePath, filePath);
2224
+ return rows.map((r) => r.fp);
2225
+ }
2226
+ /**
2227
+ * Distinct file paths that `filePath` DEPENDS ON — the inverse of
2228
+ * {@link getDependentFilePaths}: every file containing a symbol that a
2229
+ * symbol of this file has a cross-file edge into. Same edge-kind rules
2230
+ * (all kinds except `contains`); same reason imports-only is insufficient.
2231
+ */
2232
+ getDependencyFilePaths(filePath) {
2233
+ const sql = `SELECT DISTINCT tgt.file_path AS fp
2234
+ FROM edges e
2235
+ JOIN nodes src ON src.id = e.source
2236
+ JOIN nodes tgt ON tgt.id = e.target
2237
+ WHERE src.file_path = ?
2238
+ AND e.kind != 'contains'
2239
+ AND tgt.file_path != ?`;
2240
+ const rows = this.db.prepare(sql).all(filePath, filePath);
2241
+ return rows.map((r) => r.fp);
2242
+ }
2243
+ /**
2244
+ * Cross-file edges whose TARGET is a node in `filePath` and whose SOURCE is a
2245
+ * node in a *different* file, paired with the target node's (name, kind) so a
2246
+ * caller can re-resolve the edge to the re-indexed target's new ID (node IDs
2247
+ * are `sha256(filePath:kind:name:line)`, so any line shift in the callee file
2248
+ * changes target IDs and a naive re-insert by old ID silently drops them).
2249
+ * Used by `storeExtractionResult` to preserve incoming edges across a file
2250
+ * re-index (issue #899). Same edge-kind rules as
2251
+ * {@link getDependentFilePaths}: all kinds except `contains`.
2252
+ */
2253
+ getCrossFileIncomingEdgesWithTarget(filePath) {
2254
+ const sql = `SELECT e.*, tgt.name AS target_name, tgt.kind AS target_kind,
2255
+ src.file_path AS source_file_path, src.language AS source_language
2256
+ FROM edges e
2257
+ JOIN nodes tgt ON tgt.id = e.target
2258
+ JOIN nodes src ON src.id = e.source
2259
+ WHERE tgt.file_path = ?
2260
+ AND e.kind != 'contains'
2261
+ AND src.file_path != ?`;
2262
+ const rows = this.db.prepare(sql).all(filePath, filePath);
2263
+ return rows.map(row => ({
2264
+ ...rowToEdge(row),
2265
+ targetName: row.target_name,
2266
+ targetKind: row.target_kind,
2267
+ sourceFilePath: row.source_file_path,
2268
+ sourceLanguage: row.source_language,
2269
+ }));
2270
+ }
2271
+ // ===========================================================================
2272
+ // File Operations
2273
+ // ===========================================================================
2274
+ /**
2275
+ * Insert or update a file record
2276
+ */
2277
+ upsertFile(file) {
2278
+ if (!this.stmts.upsertFile) {
2279
+ this.stmts.upsertFile = this.db.prepare(`
2280
+ INSERT INTO files (path, content_hash, language, size, modified_at, indexed_at, node_count, errors, generated)
2281
+ VALUES (@path, @contentHash, @language, @size, @modifiedAt, @indexedAt, @nodeCount, @errors, @generated)
2282
+ ON CONFLICT(path) DO UPDATE SET
2283
+ content_hash = @contentHash,
2284
+ language = @language,
2285
+ size = @size,
2286
+ modified_at = @modifiedAt,
2287
+ indexed_at = @indexedAt,
2288
+ node_count = @nodeCount,
2289
+ errors = @errors,
2290
+ generated = @generated
2291
+ `);
2292
+ }
2293
+ this.stmts.upsertFile.run({
2294
+ path: file.path,
2295
+ contentHash: file.contentHash,
2296
+ language: file.language,
2297
+ size: file.size,
2298
+ modifiedAt: file.modifiedAt,
2299
+ indexedAt: file.indexedAt,
2300
+ nodeCount: file.nodeCount,
2301
+ errors: file.errors ? JSON.stringify(file.errors) : null,
2302
+ // The upsert always REWRITES the flag: a file that loses its banner in an
2303
+ // edit must lose the flag on the next sync, not keep a stale 1.
2304
+ generated: file.generated ? 1 : 0,
2305
+ });
2306
+ }
2307
+ /**
2308
+ * Which of `filePaths` the index flagged as tool-generated (schema v9+).
2309
+ *
2310
+ * Bounded-lookup by design: every consumer already holds a short candidate
2311
+ * list (a ranked file group, an FTS result page, a LIMIT-20 aggregate), so
2312
+ * this stays a partial-index probe over a handful of paths — no whole-repo
2313
+ * set to materialize, and no cache to invalidate, which means a ranking call
2314
+ * can never serve a verdict the last sync already replaced.
2315
+ *
2316
+ * Returns ONLY the content/index signal; callers union it with
2317
+ * {@link isGeneratedFile} so pre-v9 databases (column present, all zeros
2318
+ * until a re-index) keep the path-only behavior rather than regressing.
2319
+ */
2320
+ getGeneratedPathsAmong(filePaths) {
2321
+ const unique = [...new Set(filePaths)];
2322
+ const found = new Set();
2323
+ if (unique.length === 0)
2324
+ return found;
2325
+ for (let i = 0; i < unique.length; i += SQLITE_PARAM_CHUNK_SIZE) {
2326
+ const chunk = unique.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
2327
+ const placeholders = chunk.map(() => '?').join(',');
2328
+ const rows = this.db
2329
+ .prepare(`SELECT path FROM files WHERE generated = 1 AND path IN (${placeholders})`)
2330
+ .all(...chunk);
2331
+ for (const row of rows)
2332
+ found.add(row.path);
2333
+ }
2334
+ return found;
2335
+ }
2336
+ /**
2337
+ * A reusable `(path) => boolean` over a bounded candidate list, unioning the
2338
+ * indexed flag with the path convention. This is the shape every ranking
2339
+ * comparator wants: one query up front, then O(1) per comparison.
2340
+ */
2341
+ generatedPredicateFor(filePaths) {
2342
+ const flagged = this.getGeneratedPathsAmong(filePaths);
2343
+ return (filePath) => flagged.has(filePath) || (0, generated_detection_1.isGeneratedFile)(filePath);
2344
+ }
2345
+ /**
2346
+ * Which of `filePaths` are AMBIENT DECLARATION files — they declare nothing
2347
+ * but types, and nothing in the index depends on them (CG-28). A hand-written
2348
+ * ambient `.d.ts` of global shims, a vendored typings file, module
2349
+ * augmentation: reachable only by name, structurally attached to nothing.
2350
+ *
2351
+ * Structural, not extension-based, so a hand-written `types.ts` and a `.d.ts`
2352
+ * are judged by the same rule and a `.d.ts` that does declare a class or a
2353
+ * const is (correctly) not caught. Four conditions, all required:
2354
+ *
2355
+ * 1. it declares at least one symbol — an empty or unparsed file is not a
2356
+ * declaration file, it is a file we know nothing about;
2357
+ * 2. EVERY declared symbol is a type-level kind (interface / type alias /
2358
+ * enum / namespace). The narrowness is deliberate and measured: a rule
2359
+ * of "no callables" alone flags 1–18% of a repo, including Kotlin sealed
2360
+ * classes, Rust `mod.rs` re-exports and django's locale constant tables —
2361
+ * real source that must not be demoted. This rule flags 0–4%;
2362
+ * 3. no symbol in it originates a `calls`/`instantiates` edge — the direct
2363
+ * evidence that nothing here has a body;
2364
+ * 4. NOTHING ELSE IN THE INDEX points at it. This is the condition that
2365
+ * separates an ambient shim from a working type module, and it is why
2366
+ * the flag is narrow enough to be safe: `displacement-ts`'s pipeline
2367
+ * `types.ts` passes 1–3 identically but carries 13 inbound imports and
2368
+ * 21 references, so the files that answer a query about the pipeline are
2369
+ * typed BY it — it is part of that answer's structure. An ambient
2370
+ * `declare global` shim has zero. Deliberately index-wide rather than
2371
+ * restricted to the candidate list: the file that imports it is usually
2372
+ * not itself a candidate.
2373
+ *
2374
+ * ### Interface MEMBERS are transparent to all four conditions
2375
+ *
2376
+ * A `method_signature` / `property_signature` inside an interface enters the
2377
+ * graph as a `method` / `property` node (#1638). Read literally that would
2378
+ * break every condition here at once: condition 2 sees non-type kinds and
2379
+ * stops flagging, and — worse, because it is silent — condition 4 starts
2380
+ * seeing inbound `calls` edges the moment a call site through the shim's API
2381
+ * finally has a signature to land on. An ambient `.d.ts` would quietly lose
2382
+ * its damping precisely BECAUSE the platform API it declares is widely used.
2383
+ *
2384
+ * So an interface-owned member is treated the way `parameter` already is: it
2385
+ * neither qualifies, disqualifies, nor counts as inbound dependency. That is
2386
+ * not a new judgement call, it is what keeps the rule measuring what it was
2387
+ * measured on — before #1638 these nodes did not exist, so excluding them
2388
+ * reproduces the 0–4% flag rate the thresholds above were tuned against. It
2389
+ * is also the semantically right answer: a signature with no body is on the
2390
+ * same side of the line as the interface that owns it, and a call edge
2391
+ * landing on one is still not a file that can answer a flow question.
2392
+ *
2393
+ * The interface ITSELF is untouched: the `references` edges an importing
2394
+ * module aims at `UploadStorage` still disqualify the file under (4), which
2395
+ * is what keeps a depended-on `types.ts` out of the flag.
2396
+ *
2397
+ * Bounded-lookup like {@link getGeneratedPathsAmong}: callers hold a ranked
2398
+ * candidate list, so this is a partial-index probe over a handful of paths.
2399
+ */
2400
+ getAmbientDeclarationPathsAmong(filePaths) {
2401
+ const unique = [...new Set(filePaths)];
2402
+ const found = new Set();
2403
+ if (unique.length === 0)
2404
+ return found;
2405
+ for (let i = 0; i < unique.length; i += SQLITE_PARAM_CHUNK_SIZE) {
2406
+ const chunk = unique.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
2407
+ const placeholders = chunk.map(() => '?').join(',');
2408
+ // `file`/`import`/`export`/`parameter` are structural bookkeeping, not
2409
+ // things the file declares, so they neither qualify nor disqualify.
2410
+ const rows = this.db
2411
+ .prepare(`
2412
+ SELECT n.file_path AS file_path,
2413
+ SUM(CASE WHEN n.kind NOT IN ('file','import','export','parameter')
2414
+ AND NOT ${IS_INTERFACE_MEMBER('n')}
2415
+ THEN 1 ELSE 0 END) AS declared,
2416
+ SUM(CASE WHEN n.kind IN ('interface','type_alias','enum','enum_member','namespace')
2417
+ THEN 1 ELSE 0 END) AS typeDeclared
2418
+ FROM nodes n
2419
+ WHERE n.file_path IN (${placeholders})
2420
+ GROUP BY n.file_path
2421
+ `)
2422
+ .all(...chunk);
2423
+ let candidates = rows
2424
+ .filter((r) => r.declared > 0 && r.declared === r.typeDeclared)
2425
+ .map((r) => r.file_path);
2426
+ if (candidates.length === 0)
2427
+ continue;
2428
+ const disqualify = (sql) => {
2429
+ if (candidates.length === 0)
2430
+ return;
2431
+ const hit = new Set(this.db
2432
+ .prepare(sql.replace('$IN$', candidates.map(() => '?').join(',')))
2433
+ .all(...candidates).map((r) => r.file_path));
2434
+ candidates = candidates.filter((p) => !hit.has(p));
2435
+ };
2436
+ // (3) originates behaviour — a signature has no body to originate from,
2437
+ // so an edge attributed to one is not evidence about this file.
2438
+ disqualify(`
2439
+ SELECT DISTINCT n.file_path AS file_path
2440
+ FROM edges e JOIN nodes n ON n.id = e.source
2441
+ WHERE e.kind IN ('calls','instantiates') AND n.file_path IN ($IN$)
2442
+ AND NOT ${IS_INTERFACE_MEMBER('n')}
2443
+ `);
2444
+ // (4) something outside the file depends on it — but a call that lands on
2445
+ // an interface's own signature is a use of the API, not a dependency on
2446
+ // this file's structure. The edges aimed at the interface still count.
2447
+ disqualify(`
2448
+ SELECT DISTINCT t.file_path AS file_path
2449
+ FROM edges e JOIN nodes t ON t.id = e.target JOIN nodes s ON s.id = e.source
2450
+ WHERE t.file_path IN ($IN$) AND s.file_path <> t.file_path
2451
+ AND NOT ${IS_INTERFACE_MEMBER('t')}
2452
+ `);
2453
+ for (const path of candidates)
2454
+ found.add(path);
2455
+ }
2456
+ return found;
2457
+ }
2458
+ /**
2459
+ * A reusable `(path) => boolean` ambient-declaration test over a bounded
2460
+ * candidate list — the shape a ranking comparator wants: one query up front,
2461
+ * O(1) per comparison.
2462
+ */
2463
+ ambientDeclarationPredicateFor(filePaths) {
2464
+ const flagged = this.getAmbientDeclarationPathsAmong(filePaths);
2465
+ return (filePath) => flagged.has(filePath);
2466
+ }
2467
+ /** How many indexed files carry the generated flag. Surfaced by `status`. */
2468
+ countGeneratedFiles() {
2469
+ const row = this.db
2470
+ .prepare('SELECT COUNT(*) AS n FROM files WHERE generated = 1')
2471
+ .get();
2472
+ return row?.n ?? 0;
2473
+ }
2474
+ /**
2475
+ * Delete a file record and its nodes
2476
+ */
2477
+ deleteFile(filePath) {
2478
+ this.db.transaction(() => {
2479
+ this.deleteNodesByFile(filePath);
2480
+ if (!this.stmts.deleteFile) {
2481
+ this.stmts.deleteFile = this.db.prepare('DELETE FROM files WHERE path = ?');
2482
+ }
2483
+ this.stmts.deleteFile.run(filePath);
2484
+ })();
2485
+ }
2486
+ /**
2487
+ * Get a file record by path
2488
+ */
2489
+ getFileByPath(filePath) {
2490
+ if (!this.stmts.getFileByPath) {
2491
+ this.stmts.getFileByPath = this.db.prepare('SELECT * FROM files WHERE path = ?');
2492
+ }
2493
+ const row = this.stmts.getFileByPath.get(filePath);
2494
+ return row ? rowToFileRecord(row) : null;
2495
+ }
2496
+ /**
2497
+ * Get all tracked files
2498
+ */
2499
+ getAllFiles() {
2500
+ if (!this.stmts.getAllFiles) {
2501
+ this.stmts.getAllFiles = this.db.prepare('SELECT * FROM files ORDER BY path');
2502
+ }
2503
+ const rows = this.stmts.getAllFiles.all();
2504
+ return rows.map(rowToFileRecord);
2505
+ }
2506
+ /**
2507
+ * Most recent index timestamp (ms since epoch) across all tracked files, or
2508
+ * null when nothing is indexed yet. One indexed aggregate, no per-row scan. (#329)
2509
+ */
2510
+ getLastIndexedAt() {
2511
+ const row = this.db
2512
+ .prepare('SELECT MAX(indexed_at) AS last FROM files')
2513
+ .get();
2514
+ return row?.last ?? null;
2515
+ }
2516
+ /**
2517
+ * The index's revision marker: how far the last sync got, and how many files
2518
+ * it left behind — one query, both numbers.
2519
+ *
2520
+ * This is the cheapest honest answer to "has the index moved since I last
2521
+ * looked". `MAX(indexed_at)` alone is not enough: a sync that only DELETES
2522
+ * files (a branch checkout that removed a directory) advances nothing, and
2523
+ * the graph the viewer is showing has still changed underneath it. The row
2524
+ * count catches exactly that case.
2525
+ */
2526
+ getIndexRevision() {
2527
+ const row = this.db
2528
+ .prepare('SELECT MAX(indexed_at) AS last, COUNT(*) AS files FROM files')
2529
+ .get();
2530
+ return { lastIndexedAt: row?.last ?? null, fileCount: row?.files ?? 0 };
2531
+ }
2532
+ /**
2533
+ * Files re-indexed strictly after `since` (ms since epoch), newest first.
2534
+ *
2535
+ * `total` is the real count; `paths` is capped at `limit`. Used by the
2536
+ * viewer's live channel to name what a sync just picked up. A file the same
2537
+ * sync DELETED cannot appear here — it has no row left — which is why the
2538
+ * caller compares {@link getIndexRevision} as well rather than treating an
2539
+ * empty list as "nothing happened".
2540
+ */
2541
+ getFilesIndexedSince(since, limit) {
2542
+ const count = this.db
2543
+ .prepare('SELECT COUNT(*) AS n FROM files WHERE indexed_at > ?')
2544
+ .get(since);
2545
+ const rows = this.db
2546
+ .prepare('SELECT path FROM files WHERE indexed_at > ? ORDER BY indexed_at DESC, path LIMIT ?')
2547
+ .all(since, Math.max(0, limit));
2548
+ return { paths: rows.map((r) => r.path), total: count?.n ?? rows.length };
2549
+ }
2550
+ /**
2551
+ * Get files that need re-indexing (hash changed)
2552
+ */
2553
+ getStaleFiles(currentHashes) {
2554
+ const files = this.getAllFiles();
2555
+ return files.filter((f) => {
2556
+ const currentHash = currentHashes.get(f.path);
2557
+ return currentHash && currentHash !== f.contentHash;
2558
+ });
2559
+ }
2560
+ // ===========================================================================
2561
+ // Unresolved References
2562
+ // ===========================================================================
2563
+ /**
2564
+ * Insert an unresolved reference
2565
+ */
2566
+ insertUnresolvedRef(ref) {
2567
+ if (!this.stmts.insertUnresolved) {
2568
+ this.stmts.insertUnresolved = this.db.prepare(`
2569
+ INSERT INTO unresolved_refs (from_node_id, reference_name, reference_kind, line, col, candidates, file_path, language)
2570
+ VALUES (@fromNodeId, @referenceName, @referenceKind, @line, @col, @candidates, @filePath, @language)
2571
+ `);
2572
+ }
2573
+ this.stmts.insertUnresolved.run({
2574
+ fromNodeId: ref.fromNodeId,
2575
+ referenceName: ref.referenceName,
2576
+ referenceKind: ref.referenceKind,
2577
+ line: ref.line,
2578
+ col: ref.column,
2579
+ candidates: ref.candidates ? JSON.stringify(ref.candidates) : null,
2580
+ filePath: ref.filePath ?? '',
2581
+ language: ref.language ?? 'unknown',
2582
+ });
2583
+ }
2584
+ /**
2585
+ * Insert multiple unresolved references in a transaction
2586
+ */
2587
+ insertUnresolvedRefsBatch(refs) {
2588
+ if (refs.length === 0)
2589
+ return;
2590
+ const insert = this.db.transaction(() => {
2591
+ const rows = [];
2592
+ for (const ref of refs) {
2593
+ rows.push([
2594
+ ref.fromNodeId,
2595
+ ref.referenceName,
2596
+ ref.referenceKind,
2597
+ ref.line,
2598
+ ref.column,
2599
+ ref.candidates ? JSON.stringify(ref.candidates) : null,
2600
+ ref.filePath ?? '',
2601
+ ref.language ?? 'unknown',
2602
+ ]);
2603
+ }
2604
+ this.runBatched('insertUnresolvedRefs', 'INSERT INTO unresolved_refs (from_node_id, reference_name, reference_kind, line, col, candidates, file_path, language) VALUES ', '(?,?,?,?,?,?,?,?)', rows);
2605
+ });
2606
+ insert();
2607
+ }
2608
+ /**
2609
+ * Delete unresolved references from a node
2610
+ */
2611
+ deleteUnresolvedByNode(nodeId) {
2612
+ if (!this.stmts.deleteUnresolvedByNode) {
2613
+ this.stmts.deleteUnresolvedByNode = this.db.prepare('DELETE FROM unresolved_refs WHERE from_node_id = ?');
2614
+ }
2615
+ this.stmts.deleteUnresolvedByNode.run(nodeId);
2616
+ }
2617
+ /**
2618
+ * Get unresolved references by name (for resolution)
2619
+ */
2620
+ getUnresolvedByName(name) {
2621
+ if (!this.stmts.getUnresolvedByName) {
2622
+ this.stmts.getUnresolvedByName = this.db.prepare('SELECT * FROM unresolved_refs WHERE reference_name = ?');
2623
+ }
2624
+ const rows = this.stmts.getUnresolvedByName.all(name);
2625
+ return rows.map((row) => ({
2626
+ fromNodeId: row.from_node_id,
2627
+ referenceName: row.reference_name,
2628
+ referenceKind: row.reference_kind,
2629
+ line: row.line,
2630
+ column: row.col,
2631
+ candidates: row.candidates ? (0, utils_1.safeJsonParse)(row.candidates, undefined) : undefined,
2632
+ filePath: row.file_path,
2633
+ language: row.language,
2634
+ rowId: row.id,
2635
+ }));
2636
+ }
2637
+ /**
2638
+ * Get all unresolved references
2639
+ */
2640
+ getUnresolvedReferences() {
2641
+ const rows = this.db.prepare('SELECT * FROM unresolved_refs').all();
2642
+ return rows.map((row) => ({
2643
+ fromNodeId: row.from_node_id,
2644
+ referenceName: row.reference_name,
2645
+ referenceKind: row.reference_kind,
2646
+ line: row.line,
2647
+ column: row.col,
2648
+ candidates: row.candidates ? (0, utils_1.safeJsonParse)(row.candidates, undefined) : undefined,
2649
+ filePath: row.file_path,
2650
+ language: row.language,
2651
+ rowId: row.id,
2652
+ }));
2653
+ }
2654
+ /**
2655
+ * Get the count of PENDING (never-attempted) references without loading
2656
+ * them into memory. Rows marked status='failed' — attempted by a completed
2657
+ * pass, no match — are excluded: they are not outstanding work, only retry
2658
+ * candidates for the #1240 sweep, so they must not trip the #1187 orphan
2659
+ * sweep or the `status` pending-refs warning.
2660
+ */
2661
+ getUnresolvedReferencesCount() {
2662
+ if (!this.stmts.getUnresolvedCount) {
2663
+ this.stmts.getUnresolvedCount = this.db.prepare("SELECT COUNT(*) as count FROM unresolved_refs WHERE status = 'pending'");
2664
+ }
2665
+ const row = this.stmts.getUnresolvedCount.get();
2666
+ return row.count;
2667
+ }
2668
+ /**
2669
+ * Get a batch of PENDING unresolved references using LIMIT/OFFSET
2670
+ * pagination. Used to process references in bounded memory chunks; failed
2671
+ * rows are excluded so the batched drain loop terminates once every row
2672
+ * has been attempted.
2673
+ */
2674
+ getUnresolvedReferencesBatch(offset, limit) {
2675
+ if (!this.stmts.getUnresolvedBatch) {
2676
+ // ORDER BY rowid is load-bearing for the pipelined resolution loop: it
2677
+ // prefetches batch k+1 at OFFSET batch_k.length while batch k's rows are
2678
+ // still pending, which is only exact under a stable enumeration. (A plain
2679
+ // scan and the status index both return rowid order anyway — this pins
2680
+ // it.)
2681
+ this.stmts.getUnresolvedBatch = this.db.prepare("SELECT * FROM unresolved_refs WHERE status = 'pending' ORDER BY rowid LIMIT ? OFFSET ?");
2682
+ }
2683
+ const rows = this.stmts.getUnresolvedBatch.all(limit, offset);
2684
+ return rows.map((row) => ({
2685
+ fromNodeId: row.from_node_id,
2686
+ referenceName: row.reference_name,
2687
+ referenceKind: row.reference_kind,
2688
+ line: row.line,
2689
+ column: row.col,
2690
+ candidates: row.candidates ? (0, utils_1.safeJsonParse)(row.candidates, undefined) : undefined,
2691
+ filePath: row.file_path,
2692
+ language: row.language,
2693
+ rowId: row.id,
2694
+ }));
2695
+ }
2696
+ /**
2697
+ * Keyset variant of {@link getUnresolvedReferencesBatch} for the batched
2698
+ * resolution loop: seek past the last-seen row id instead of OFFSET-walking.
2699
+ * OFFSET reads re-scan the accumulated failed-row prefix on every batch —
2700
+ * O(failed rows) per read, measured at 54.6s of the kernel-scale batch loop
2701
+ * (§7a.2) — while the seek is O(batch) forever. `id` is the rowid alias, so
2702
+ * the enumeration order is identical to the OFFSET reader's.
2703
+ */
2704
+ getUnresolvedReferencesBatchAfter(afterRowId, limit, prerequisites) {
2705
+ // Resolution prerequisites must be committed before dependent calls,
2706
+ // even when an interrupted sync queued their rows in a different order
2707
+ // from a clean index (#1577). Each phase still seeks by row id in bounded
2708
+ // memory; the default preserves the public reader's original enumeration.
2709
+ const key = prerequisites === undefined ? 'getUnresolvedBatchAfter'
2710
+ : prerequisites ? 'getUnresolvedPrerequisitesAfter' : 'getUnresolvedDependentsAfter';
2711
+ if (!this.stmts[key]) {
2712
+ const filter = prerequisites === undefined ? ''
2713
+ : ` AND reference_kind ${prerequisites ? 'IN' : 'NOT IN'} ('imports', 'extends', 'implements')`;
2714
+ this.stmts[key] = this.db.prepare(`SELECT * FROM unresolved_refs WHERE status = 'pending' AND id > ?${filter} ORDER BY id LIMIT ?`);
2715
+ }
2716
+ const rows = this.stmts[key].all(afterRowId, limit);
2717
+ return rows.map((row) => ({
2718
+ fromNodeId: row.from_node_id,
2719
+ referenceName: row.reference_name,
2720
+ referenceKind: row.reference_kind,
2721
+ line: row.line,
2722
+ column: row.col,
2723
+ candidates: row.candidates ? (0, utils_1.safeJsonParse)(row.candidates, undefined) : undefined,
2724
+ filePath: row.file_path,
2725
+ language: row.language,
2726
+ rowId: row.id,
2727
+ }));
2728
+ }
2729
+ /**
2730
+ * Get all tracked file paths (lightweight — no full FileRecord objects)
2731
+ */
2732
+ getAllFilePaths() {
2733
+ if (!this.stmts.getAllFilePaths) {
2734
+ this.stmts.getAllFilePaths = this.db.prepare('SELECT path FROM files ORDER BY path');
2735
+ }
2736
+ const rows = this.stmts.getAllFilePaths.all();
2737
+ return rows.map((r) => r.path);
2738
+ }
2739
+ /**
2740
+ * Get all distinct node names (lightweight — just name strings for pre-filtering)
2741
+ */
2742
+ getAllNodeNames() {
2743
+ if (!this.stmts.getAllNodeNames) {
2744
+ this.stmts.getAllNodeNames = this.db.prepare('SELECT DISTINCT name FROM nodes');
2745
+ }
2746
+ const rows = this.stmts.getAllNodeNames.all();
2747
+ return rows.map((r) => r.name);
2748
+ }
2749
+ /**
2750
+ * Stream the distinct node names one row at a time — the incremental
2751
+ * counterpart to {@link getAllNodeNames} for callers that need to yield
2752
+ * to the event loop mid-scan (resolver cache warm-up on multi-million-node
2753
+ * indexes). Fresh statement per call: the iterator holds an open cursor.
2754
+ */
2755
+ *iterateNodeNames() {
2756
+ const stmt = this.db.prepare('SELECT DISTINCT name FROM nodes');
2757
+ for (const row of stmt.iterate()) {
2758
+ yield row.name;
2759
+ }
2760
+ }
2761
+ /**
2762
+ * Get unresolved references scoped to specific file paths.
2763
+ * Uses the idx_unresolved_file_path index for efficient lookup.
2764
+ */
2765
+ getUnresolvedReferencesByFiles(filePaths) {
2766
+ if (filePaths.length === 0)
2767
+ return [];
2768
+ // Chunk under SQLite's parameter limit: the first sync of a very large repo
2769
+ // passes every changed file here, which an unbounded `IN (...)` would bind
2770
+ // as one parameter each — exceeding MAX_VARIABLE_NUMBER and aborting with
2771
+ // "too many SQL variables". (#540)
2772
+ const rows = [];
2773
+ for (let i = 0; i < filePaths.length; i += SQLITE_PARAM_CHUNK_SIZE) {
2774
+ const chunk = filePaths.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
2775
+ const placeholders = chunk.map(() => '?').join(',');
2776
+ const chunkRows = this.db
2777
+ .prepare(`SELECT * FROM unresolved_refs WHERE status = 'pending' AND file_path IN (${placeholders})`)
2778
+ .all(...chunk);
2779
+ // Append with a loop, never a spread: the INPUT chunk is bounded, but
2780
+ // the RESULT rows per chunk are not — a dense recovery sync (e.g. the
2781
+ // #1541 self-heal re-indexing hundreds of files) returns more rows than
2782
+ // V8 allows as arguments, and `push(...chunkRows)` dies with "Maximum
2783
+ // call stack size exceeded", aborting resolution mid-sync (#1558).
2784
+ for (const row of chunkRows)
2785
+ rows.push(row);
2786
+ }
2787
+ return rows.map((row) => ({
2788
+ fromNodeId: row.from_node_id,
2789
+ referenceName: row.reference_name,
2790
+ referenceKind: row.reference_kind,
2791
+ line: row.line,
2792
+ column: row.col,
2793
+ candidates: row.candidates ? (0, utils_1.safeJsonParse)(row.candidates, undefined) : undefined,
2794
+ filePath: row.file_path,
2795
+ language: row.language,
2796
+ rowId: row.id,
2797
+ }));
2798
+ }
2799
+ /**
2800
+ * Delete all unresolved references (after resolution)
2801
+ */
2802
+ clearUnresolvedReferences() {
2803
+ this.db.exec('DELETE FROM unresolved_refs');
2804
+ }
2805
+ /**
2806
+ * Delete resolved references by their IDs
2807
+ */
2808
+ deleteResolvedReferences(fromNodeIds) {
2809
+ if (fromNodeIds.length === 0)
2810
+ return;
2811
+ // Chunk under SQLite's parameter limit, matching every other IN-list in
2812
+ // this file. The internal resolution path uses deleteSpecificResolvedReferences
2813
+ // instead, but QueryBuilder is part of the public API, so a library consumer
2814
+ // passing more ids than SQLITE_MAX_VARIABLE_NUMBER (32766 on the bundled
2815
+ // node:sqlite) would otherwise hit "too many SQL variables". (#540, #1001)
2816
+ for (let i = 0; i < fromNodeIds.length; i += SQLITE_PARAM_CHUNK_SIZE) {
2817
+ const chunk = fromNodeIds.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
2818
+ const placeholders = chunk.map(() => '?').join(',');
2819
+ this.db.prepare(`DELETE FROM unresolved_refs WHERE from_node_id IN (${placeholders})`).run(...chunk);
2820
+ }
2821
+ }
2822
+ /**
2823
+ * Delete specific resolved references by (fromNodeId, referenceName, referenceKind) tuples.
2824
+ * More precise than deleteResolvedReferences — only removes refs that were actually resolved.
2825
+ */
2826
+ deleteSpecificResolvedReferences(refs) {
2827
+ if (refs.length === 0)
2828
+ return 0;
2829
+ const stmt = this.db.prepare('DELETE FROM unresolved_refs WHERE from_node_id = ? AND reference_name = ? AND reference_kind = ?');
2830
+ // Returns rows actually removed (SQLite `changes`, summed): the batched
2831
+ // resolution loop's non-progress guard keys on this — zero removals from
2832
+ // a batch that claimed work is the direct runaway signal (§7a.2).
2833
+ let changed = 0;
2834
+ const deleteMany = this.db.transaction((items) => {
2835
+ for (const ref of items) {
2836
+ changed += stmt.run(ref.fromNodeId, ref.referenceName, ref.referenceKind).changes;
2837
+ }
2838
+ });
2839
+ deleteMany(refs);
2840
+ return changed;
2841
+ }
2842
+ /**
2843
+ * Delete unresolved-ref rows by row id — the precise cleanup for refs a
2844
+ * resolution pass actually processed. The key-tuple variant above also
2845
+ * deletes SIBLING rows (same caller calling the same callee at other lines)
2846
+ * that a later batch hasn't attempted yet, so when a batch boundary split a
2847
+ * caller's same-named call sites, the later sites' edges were silently never
2848
+ * created (#1269).
2849
+ */
2850
+ deleteReferencesByRowIds(rowIds) {
2851
+ if (rowIds.length === 0)
2852
+ return 0;
2853
+ // One transaction for all chunks (each chunk was previously its own
2854
+ // implicit transaction = its own WAL commit — measurable on 100k+-ref
2855
+ // resolution persists), and the full-size chunk statement is cached so
2856
+ // repeat calls skip the re-prepare; only the final partial chunk (if any)
2857
+ // prepares ad hoc. Returns rows actually removed (summed `changes`) for
2858
+ // the batched loop's non-progress guard (§7a.2).
2859
+ let changed = 0;
2860
+ this.db.transaction(() => {
2861
+ for (let i = 0; i < rowIds.length; i += SQLITE_PARAM_CHUNK_SIZE) {
2862
+ const chunk = rowIds.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
2863
+ if (chunk.length === SQLITE_PARAM_CHUNK_SIZE) {
2864
+ if (!this.stmts.deleteRefsByRowIdsFull) {
2865
+ const placeholders = new Array(SQLITE_PARAM_CHUNK_SIZE).fill('?').join(',');
2866
+ this.stmts.deleteRefsByRowIdsFull = this.db.prepare(`DELETE FROM unresolved_refs WHERE id IN (${placeholders})`);
2867
+ }
2868
+ changed += this.stmts.deleteRefsByRowIdsFull.run(...chunk).changes;
2869
+ }
2870
+ else {
2871
+ const placeholders = chunk.map(() => '?').join(',');
2872
+ changed += this.db.prepare(`DELETE FROM unresolved_refs WHERE id IN (${placeholders})`).run(...chunk).changes;
2873
+ }
2874
+ }
2875
+ })();
2876
+ return changed;
2877
+ }
2878
+ /**
2879
+ * Mark refs a completed resolution pass could not resolve as status='failed'
2880
+ * instead of deleting them (#1240). Failed rows are invisible to the pending
2881
+ * count/batch readers (so drain loops and the #1187 orphan sweep still
2882
+ * terminate) but stay queryable by name_tail so a later sync can retry them
2883
+ * when a changed file introduces a symbol that could satisfy them. name_tail
2884
+ * is (re)written here so rows inserted before the v8 migration get their
2885
+ * tail the first time they're attempted.
2886
+ */
2887
+ markReferencesFailed(refs) {
2888
+ if (refs.length === 0)
2889
+ return 0;
2890
+ const stmt = this.db.prepare("UPDATE unresolved_refs SET status = 'failed', name_tail = ? WHERE from_node_id = ? AND reference_name = ? AND reference_kind = ?");
2891
+ let changed = 0;
2892
+ const markMany = this.db.transaction((items) => {
2893
+ for (const ref of items) {
2894
+ changed += stmt.run(referenceNameTail(ref.referenceName), ref.fromNodeId, ref.referenceName, ref.referenceKind).changes;
2895
+ }
2896
+ });
2897
+ markMany(refs);
2898
+ return changed;
2899
+ }
2900
+ /**
2901
+ * Park refs as status='failed' by row id — the precise counterpart of
2902
+ * markReferencesFailed, for the same reason as deleteReferencesByRowIds:
2903
+ * the key-tuple variant also flips same-key sibling rows in later batches
2904
+ * to 'failed' before they were ever attempted (#1269). Resolution outcome
2905
+ * can differ per call site (receiver-type inference reads the ref's line),
2906
+ * so a sibling must not inherit this row's failure.
2907
+ */
2908
+ markReferencesFailedByRowIds(refs) {
2909
+ if (refs.length === 0)
2910
+ return 0;
2911
+ const stmt = this.db.prepare("UPDATE unresolved_refs SET status = 'failed', name_tail = ? WHERE id = ?");
2912
+ let changed = 0;
2913
+ const markMany = this.db.transaction((items) => {
2914
+ for (const ref of items) {
2915
+ changed += stmt.run(referenceNameTail(ref.referenceName), ref.rowId).changes;
2916
+ }
2917
+ });
2918
+ markMany(refs);
2919
+ return changed;
2920
+ }
2921
+ /**
2922
+ * Failed refs whose name tail matches one of the given symbol names — the
2923
+ * candidates a sync should retry after files carrying those names changed
2924
+ * (#1240). Names matching more than `perNameCeiling` failed refs are
2925
+ * skipped entirely: at that population a name is external/builtin noise
2926
+ * (`get`, `map`, …) that one new definition won't resolve — the same
2927
+ * rationale as resolution's AMBIGUOUS_NAME_CEILING (#999) — and retrying an
2928
+ * arbitrary subset would be both wasted work and incoherent coverage.
2929
+ */
2930
+ getRetryableFailedReferences(names, perNameCeiling = 500) {
2931
+ if (names.length === 0)
2932
+ return [];
2933
+ // Pass 1: per-tail counts, chunked under the SQLite parameter limit.
2934
+ const retryNames = [];
2935
+ for (let i = 0; i < names.length; i += SQLITE_PARAM_CHUNK_SIZE) {
2936
+ const chunk = names.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
2937
+ const placeholders = chunk.map(() => '?').join(',');
2938
+ const counts = this.db
2939
+ .prepare(`SELECT name_tail, COUNT(*) as count FROM unresolved_refs WHERE status = 'failed' AND name_tail IN (${placeholders}) GROUP BY name_tail`)
2940
+ .all(...chunk);
2941
+ for (const row of counts) {
2942
+ if (row.count <= perNameCeiling)
2943
+ retryNames.push(row.name_tail);
2944
+ }
2945
+ }
2946
+ if (retryNames.length === 0)
2947
+ return [];
2948
+ // Pass 2: load the surviving rows.
2949
+ const rows = [];
2950
+ for (let i = 0; i < retryNames.length; i += SQLITE_PARAM_CHUNK_SIZE) {
2951
+ const chunk = retryNames.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
2952
+ const placeholders = chunk.map(() => '?').join(',');
2953
+ const chunkRows = this.db
2954
+ .prepare(`SELECT * FROM unresolved_refs WHERE status = 'failed' AND name_tail IN (${placeholders})`)
2955
+ .all(...chunk);
2956
+ // Loop, not spread — same V8 argument-limit hazard as
2957
+ // getUnresolvedReferencesByFiles (#1558): a large definition delta can
2958
+ // select an unbounded number of failed rows per chunk.
2959
+ for (const row of chunkRows)
2960
+ rows.push(row);
2961
+ }
2962
+ return rows.map((row) => ({
2963
+ fromNodeId: row.from_node_id,
2964
+ referenceName: row.reference_name,
2965
+ referenceKind: row.reference_kind,
2966
+ line: row.line,
2967
+ column: row.col,
2968
+ candidates: row.candidates ? (0, utils_1.safeJsonParse)(row.candidates, undefined) : undefined,
2969
+ filePath: row.file_path,
2970
+ language: row.language,
2971
+ rowId: row.id,
2972
+ }));
2973
+ }
2974
+ /**
2975
+ * Resolution edges whose TARGET symbol is named one of `names` — the edges a
2976
+ * sync must re-resolve after `names` gained or lost a definition (CG-33).
2977
+ *
2978
+ * Resolution binds a reference to a node whose name matches the reference's
2979
+ * tail, and it picks among ALL same-named definitions project-wide. So adding
2980
+ * or removing one definition of `pct` changes the answer for every `pct(...)`
2981
+ * reference in the repo — including references in files this sync never
2982
+ * touches, whose edges nothing else revisits. Those edges' current target is,
2983
+ * by that same rule, a node named `pct`, which is why the target's name is a
2984
+ * sufficient (and index-backed, via idx_nodes_name) way to find them without
2985
+ * a schema change or a scan of edge metadata.
2986
+ *
2987
+ * Returns the source file/language alongside each edge so the caller can
2988
+ * resurrect it as its original reference. Excludes `provenance='heuristic'`
2989
+ * (synthesized dispatch edges are not resolution output and carry no refName
2990
+ * stamp to resurrect from — deleting one would be a permanent loss).
2991
+ *
2992
+ * Names matching more than `perNameCeiling` edges are skipped entirely, same
2993
+ * rationale and same default as {@link getRetryableFailedReferences}: at that
2994
+ * population the name is generic (`get`, `clear`, …), one definition changing
2995
+ * won't flip most of them, and rebinding an arbitrary subset is both wasted
2996
+ * work and incoherent coverage.
2997
+ */
2998
+ getResolutionEdgesByTargetName(names, perNameCeiling = 500) {
2999
+ if (names.length === 0)
3000
+ return [];
3001
+ // Pass 1: per-name edge counts, chunked under the SQLite parameter limit.
3002
+ const keep = [];
3003
+ for (let i = 0; i < names.length; i += SQLITE_PARAM_CHUNK_SIZE) {
3004
+ const chunk = names.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
3005
+ const placeholders = chunk.map(() => '?').join(',');
3006
+ const counts = this.db
3007
+ .prepare(`SELECT tgt.name AS name, COUNT(*) AS count
3008
+ FROM edges e
3009
+ JOIN nodes tgt ON tgt.id = e.target
3010
+ WHERE tgt.name IN (${placeholders})
3011
+ AND (e.provenance IS NULL OR e.provenance != 'heuristic')
3012
+ GROUP BY tgt.name`)
3013
+ .all(...chunk);
3014
+ for (const row of counts) {
3015
+ if (row.count <= perNameCeiling)
3016
+ keep.push(row.name);
3017
+ }
3018
+ }
3019
+ if (keep.length === 0)
3020
+ return [];
3021
+ // Pass 2: load the surviving edges with the source file context a
3022
+ // resurrection needs.
3023
+ const out = [];
3024
+ for (let i = 0; i < keep.length; i += SQLITE_PARAM_CHUNK_SIZE) {
3025
+ const chunk = keep.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
3026
+ const placeholders = chunk.map(() => '?').join(',');
3027
+ const rows = this.db
3028
+ .prepare(`SELECT e.*, src.file_path AS source_file_path, src.language AS source_language
3029
+ FROM edges e
3030
+ JOIN nodes tgt ON tgt.id = e.target
3031
+ JOIN nodes src ON src.id = e.source
3032
+ WHERE tgt.name IN (${placeholders})
3033
+ AND (e.provenance IS NULL OR e.provenance != 'heuristic')`)
3034
+ .all(...chunk);
3035
+ for (const row of rows) {
3036
+ out.push({
3037
+ ...rowToEdge(row),
3038
+ edgeId: row.id,
3039
+ sourceFilePath: row.source_file_path,
3040
+ sourceLanguage: row.source_language,
3041
+ });
3042
+ }
3043
+ }
3044
+ return out;
3045
+ }
3046
+ /** Delete edges by primary key — the rebind pass's half of a re-resolution. */
3047
+ deleteEdgesByIds(edgeIds) {
3048
+ if (edgeIds.length === 0)
3049
+ return 0;
3050
+ let changed = 0;
3051
+ this.db.transaction(() => {
3052
+ for (let i = 0; i < edgeIds.length; i += SQLITE_PARAM_CHUNK_SIZE) {
3053
+ const chunk = edgeIds.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
3054
+ const placeholders = chunk.map(() => '?').join(',');
3055
+ changed += this.db.prepare(`DELETE FROM edges WHERE id IN (${placeholders})`).run(...chunk).changes;
3056
+ }
3057
+ })();
3058
+ return changed;
3059
+ }
3060
+ /**
3061
+ * Replace resolution edges with their original unresolved references as one
3062
+ * transaction. If ref insertion fails, the edge deletion is rolled back.
3063
+ */
3064
+ replaceResolutionEdgesWithUnresolvedRefs(edgeIds, refs) {
3065
+ return this.db.transaction(() => {
3066
+ const changed = this.deleteEdgesByIds(edgeIds);
3067
+ this.insertUnresolvedRefsBatch(refs);
3068
+ return changed;
3069
+ })();
3070
+ }
3071
+ /**
3072
+ * Distinct node names present in the given files — the symbol names a sync
3073
+ * pass uses to look up retryable failed refs after those files changed.
3074
+ */
3075
+ getNodeNamesByFiles(filePaths) {
3076
+ if (filePaths.length === 0)
3077
+ return [];
3078
+ const names = new Set();
3079
+ for (let i = 0; i < filePaths.length; i += SQLITE_PARAM_CHUNK_SIZE) {
3080
+ const chunk = filePaths.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
3081
+ const placeholders = chunk.map(() => '?').join(',');
3082
+ const rows = this.db
3083
+ .prepare(`SELECT DISTINCT name FROM nodes WHERE file_path IN (${placeholders})`)
3084
+ .all(...chunk);
3085
+ for (const row of rows)
3086
+ names.add(row.name);
3087
+ }
3088
+ return [...names];
3089
+ }
3090
+ /**
3091
+ * Distinct `file\0name` pairs defined by the given files — the shape sync's
3092
+ * definition delta needs (CG-33).
3093
+ *
3094
+ * Deliberately NOT `getNodeNamesByFiles`: a bare name set is taken over the
3095
+ * WHOLE changed batch, so a name that moves between two files in one commit
3096
+ * (or exists in one changed file and is newly added to another) appears on
3097
+ * both sides and cancels out of the symmetric difference — even though a
3098
+ * definition genuinely appeared or vanished and every reference to that name
3099
+ * repo-wide may now bind elsewhere. Keying by file makes each definition its
3100
+ * own fact, so the move is seen as one removal plus one addition.
3101
+ */
3102
+ getNodeNamePairsByFiles(filePaths) {
3103
+ const pairs = new Set();
3104
+ if (filePaths.length === 0)
3105
+ return pairs;
3106
+ for (let i = 0; i < filePaths.length; i += SQLITE_PARAM_CHUNK_SIZE) {
3107
+ const chunk = filePaths.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
3108
+ const placeholders = chunk.map(() => '?').join(',');
3109
+ const rows = this.db
3110
+ .prepare(`SELECT DISTINCT file_path, name FROM nodes WHERE file_path IN (${placeholders})`)
3111
+ .all(...chunk);
3112
+ // NUL-joined: a path or a symbol name can contain a space, never a NUL.
3113
+ for (const row of rows)
3114
+ pairs.add(`${row.file_path}\0${row.name}`);
3115
+ }
3116
+ return pairs;
3117
+ }
3118
+ // ===========================================================================
3119
+ // Statistics
3120
+ // ===========================================================================
3121
+ /**
3122
+ * Lightweight (nodes, edges) count snapshot. Used around an index/sync
3123
+ * run to compute true additions across extraction + resolution +
3124
+ * synthesis — the per-phase counter in the orchestrator only sees
3125
+ * extraction's contribution, which is why the CLI summary under-reported
3126
+ * the edge count (resolution + synthesizer edges were invisible).
3127
+ */
3128
+ getNodeAndEdgeCount() {
3129
+ return this.db
3130
+ .prepare('SELECT (SELECT COUNT(*) FROM nodes) AS nodes, (SELECT COUNT(*) FROM edges) AS edges')
3131
+ .get();
3132
+ }
3133
+ /**
3134
+ * Get graph statistics
3135
+ */
3136
+ getStats() {
3137
+ // Single query for all three aggregate counts
3138
+ const counts = this.db.prepare(`
3139
+ SELECT
3140
+ (SELECT COUNT(*) FROM nodes) AS node_count,
3141
+ (SELECT COUNT(*) FROM edges) AS edge_count,
3142
+ (SELECT COUNT(*) FROM files) AS file_count
3143
+ `).get();
3144
+ const nodesByKind = {};
3145
+ const nodeKindRows = this.db
3146
+ .prepare('SELECT kind, COUNT(*) as count FROM nodes GROUP BY kind')
3147
+ .all();
3148
+ for (const row of nodeKindRows) {
3149
+ nodesByKind[row.kind] = row.count;
3150
+ }
3151
+ const edgesByKind = {};
3152
+ const edgeKindRows = this.db
3153
+ .prepare('SELECT kind, COUNT(*) as count FROM edges GROUP BY kind')
3154
+ .all();
3155
+ for (const row of edgeKindRows) {
3156
+ edgesByKind[row.kind] = row.count;
3157
+ }
3158
+ const filesByLanguage = {};
3159
+ const languageRows = this.db
3160
+ .prepare('SELECT language, COUNT(*) as count FROM files GROUP BY language')
3161
+ .all();
3162
+ for (const row of languageRows) {
3163
+ filesByLanguage[row.language] = row.count;
3164
+ }
3165
+ return {
3166
+ nodeCount: counts.node_count,
3167
+ edgeCount: counts.edge_count,
3168
+ fileCount: counts.file_count,
3169
+ nodesByKind,
3170
+ edgesByKind,
3171
+ filesByLanguage,
3172
+ dbSizeBytes: 0, // Set by caller using DatabaseConnection.getSize()
3173
+ walSizeBytes: 0, // Set by caller using DatabaseConnection.getWalSizeBytes()
3174
+ lastUpdated: Date.now(),
3175
+ };
3176
+ }
3177
+ // ===========================================================================
3178
+ // Project Metadata
3179
+ // ===========================================================================
3180
+ /**
3181
+ * Get a metadata value by key
3182
+ */
3183
+ getMetadata(key) {
3184
+ const row = this.db.prepare('SELECT value FROM project_metadata WHERE key = ?').get(key);
3185
+ return row?.value ?? null;
3186
+ }
3187
+ /**
3188
+ * Set a metadata key-value pair (upsert)
3189
+ */
3190
+ setMetadata(key, value) {
3191
+ this.db.prepare('INSERT INTO project_metadata (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at').run(key, value, Date.now());
3192
+ }
3193
+ /**
3194
+ * Get all metadata as a key-value record
3195
+ */
3196
+ getAllMetadata() {
3197
+ const rows = this.db.prepare('SELECT key, value FROM project_metadata').all();
3198
+ const result = {};
3199
+ for (const row of rows) {
3200
+ result[row.key] = row.value;
3201
+ }
3202
+ return result;
3203
+ }
3204
+ /**
3205
+ * Clear all data from the database
3206
+ */
3207
+ clear() {
3208
+ this.nodeCache.clear();
3209
+ this.db.transaction(() => {
3210
+ this.db.exec('DELETE FROM unresolved_refs');
3211
+ this.db.exec('DELETE FROM edges');
3212
+ this.db.exec('DELETE FROM nodes');
3213
+ this.db.exec('DELETE FROM files');
3214
+ })();
3215
+ }
3216
+ }
3217
+ exports.QueryBuilder = QueryBuilder;
3218
+ function foldModuleRows(rows, options) {
3219
+ // A module id is a path and may contain anything printable, so the key
3220
+ // separator has to be something a path cannot hold.
3221
+ const SEP = '\u0000';
3222
+ const links = new Map();
3223
+ const pairKinds = new Set(options.pairKinds);
3224
+ const wantPairs = options.topPairsPerLink > 0 && pairKinds.size > 0;
3225
+ const pairTotals = new Map();
3226
+ for (const row of rows) {
3227
+ const linkKey = `${row.source}${SEP}${row.target}${SEP}${row.kind}`;
3228
+ const link = links.get(linkKey);
3229
+ if (link) {
3230
+ link.count += row.count;
3231
+ link.declared += row.declared;
3232
+ link.uncertain += row.uncertain;
3233
+ }
3234
+ else {
3235
+ links.set(linkKey, {
3236
+ source: row.source,
3237
+ target: row.target,
3238
+ kind: row.kind,
3239
+ count: row.count,
3240
+ declared: row.declared,
3241
+ uncertain: row.uncertain,
3242
+ });
3243
+ }
3244
+ // Only the confident half of a row can be named: an uncertain edge is a
3245
+ // guess, and printing "a to b, 12" for twelve guesses is the map claiming
3246
+ // something it does not know.
3247
+ if (!wantPairs || row.count === 0 || !pairKinds.has(row.kind))
3248
+ continue;
3249
+ const pairKey = `${row.source}${SEP}${row.target}${SEP}${row.from}${SEP}${row.to}`;
3250
+ const pair = pairTotals.get(pairKey);
3251
+ if (pair) {
3252
+ pair.count += row.count;
3253
+ pair.declared += row.declared;
3254
+ }
3255
+ else {
3256
+ pairTotals.set(pairKey, {
3257
+ source: row.source,
3258
+ target: row.target,
3259
+ from: row.from,
3260
+ to: row.to,
3261
+ count: row.count,
3262
+ declared: row.declared,
3263
+ });
3264
+ }
3265
+ }
3266
+ const byLink = new Map();
3267
+ for (const pair of pairTotals.values()) {
3268
+ const key = `${pair.source}${SEP}${pair.target}`;
3269
+ let list = byLink.get(key);
3270
+ if (!list)
3271
+ byLink.set(key, (list = []));
3272
+ list.push(pair);
3273
+ }
3274
+ const pairs = [];
3275
+ for (const list of byLink.values()) {
3276
+ list.sort((a, b) => b.declared - a.declared ||
3277
+ b.count - a.count ||
3278
+ a.from.localeCompare(b.from) ||
3279
+ a.to.localeCompare(b.to));
3280
+ for (const pair of list.slice(0, options.topPairsPerLink))
3281
+ pairs.push(pair);
3282
+ }
3283
+ return { links: [...links.values()], pairs };
3284
+ }
3285
+ //# sourceMappingURL=queries.js.map