@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,4105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SYNTH_PROGRESS_STEPS = exports.SYNTH_PASSES = void 0;
4
+ exports.synthesizeCallbackEdges = synthesizeCallbackEdges;
5
+ const generated_detection_1 = require("../extraction/generated-detection");
6
+ const strip_comments_1 = require("./strip-comments");
7
+ const c_fnptr_synthesizer_1 = require("./c-fnptr-synthesizer");
8
+ const goframe_synthesizer_1 = require("./goframe-synthesizer");
9
+ const expo_router_synthesizer_1 = require("./expo-router-synthesizer");
10
+ const next_router_synthesizer_1 = require("./next-router-synthesizer");
11
+ const react_router_synthesizer_1 = require("./react-router-synthesizer");
12
+ const tanstack_router_synthesizer_1 = require("./tanstack-router-synthesizer");
13
+ const vue_router_synthesizer_1 = require("./vue-router-synthesizer");
14
+ const sveltekit_synthesizer_1 = require("./sveltekit-synthesizer");
15
+ const cooperative_yield_1 = require("./cooperative-yield");
16
+ const tier_synthesizer_1 = require("./tier-synthesizer");
17
+ const synth_utils_1 = require("./synth-utils");
18
+ const import_resolver_1 = require("./import-resolver");
19
+ const REGISTRAR_NAME = /^(on[A-Z]\w*|subscribe|addListener|addEventListener|register|watch|listen|addCallback)$/;
20
+ const DISPATCHER_NAME = /(emit|trigger|notify|dispatch|fire|publish|flush)/i;
21
+ const MAX_CALLBACKS_PER_CHANNEL = 40;
22
+ const EVENT_FANOUT_CAP = 6; // skip events with more handlers/dispatchers than this (too generic without type info)
23
+ const ON_RE = /\.(?:on|once|addListener)\(\s*['"]([^'"]+)['"]\s*,\s*(?:function\s+(\w+)|(?:this\.)?(\w+))/g;
24
+ const EMIT_RE = /\.(?:emit|fire|dispatchEvent)\(\s*['"]([^'"]+)['"]/g;
25
+ const SETSTATE_RE = /this\.setState\s*\(/;
26
+ const FLUTTER_SETSTATE_RE = /\bsetState\s*\(/; // Flutter: setState((){…}) / this.setState
27
+ const JS_FAMILY = ['typescript', 'javascript', 'tsx', 'jsx'];
28
+ const JSX_TAG_RE = /<([A-Z][A-Za-z0-9_]*)[\s/>]/g;
29
+ const MAX_JSX_CHILDREN = 30;
30
+ // Vue SFC templates: kebab-case child components (<el-button> → ElButton) and
31
+ // event bindings (@click="fn" / v-on:click="fn"). PascalCase children (<VPNav/>)
32
+ // are already caught by JSX_TAG_RE via the SFC component node.
33
+ const VUE_KEBAB_RE = /<([a-z][a-z0-9]*(?:-[a-z0-9]+)+)[\s/>]/g;
34
+ // PascalCase component tags — `<MediaCard ...>`, `<NavBar/>`. HTML elements are
35
+ // lowercase, so an uppercase-initial tag is a component usage; built-ins
36
+ // (`<NuxtLink>`, `<Transition>`) simply resolve to nothing and emit no edge.
37
+ const VUE_PASCAL_RE = /<([A-Z][A-Za-z0-9]*)[\s/>]/g;
38
+ const VUE_HANDLER_RE = /(?:@|v-on:)([a-zA-Z][\w-]*)(?:\.[\w]+)*\s*=\s*"([^"]+)"/g;
39
+ // Composable/hook destructure: `const { close: closeSidebar } = useSidebarControl()`.
40
+ // Captures the destructure body + the called composable; only `use*` calls qualify.
41
+ const VUE_DESTRUCTURE_RE = /(?:const|let|var)\s*\{([^}]+)\}\s*=\s*(\w+)\s*\(/g;
42
+ // Closure-collection dynamic dispatch (language-agnostic, Swift-first). A method
43
+ // appends a closure to a collection property; another method iterates that
44
+ // property *invoking each element* (`coll.forEach { $0() }` / `{ it() }`). The
45
+ // element-invoke (`$0(` / `it(`) PROVES the collection holds closures, so pairing
46
+ // a dispatcher to same-named registrars (`.append`/`.add`/`.push`/`.insert`,
47
+ // incl. Swift `prop.write { $0.append }`) is high-precision. Cross-file/class by
48
+ // design: Alamofire appends in `DataRequest.validate` but iterates in the base
49
+ // `Request.didCompleteTask` — neither same-file nor same-class pairing reaches it.
50
+ const CC_DISPATCH_RE = /(\w+)\.forEach\s*\{\s*(?:\$0|it)\s*\(/g;
51
+ const CC_APPEND_WRITE_RE = /(\w+)\.write\s*\{\s*\$0(?:\.(\w+))?\.(?:append|add|push|insert)\s*\(/g;
52
+ const CC_APPEND_DIRECT_RE = /(\w+)\.(?:append|add|push|insert)\s*\(/g;
53
+ const CC_FANOUT_CAP = 8; // skip a field name with more dispatchers/registrars than this (too generic to pair confidently)
54
+ // The dispatcher gate — `{ $0( ` / `{ it( ` element-invocation — is Swift/Kotlin
55
+ // trailing-closure syntax, so ONLY those languages can ever contribute a
56
+ // dispatcher, and a cross-language registrar pairing (a JS `.push(` against a
57
+ // Swift dispatcher's field name) would be a wrong edge, not a missed one.
58
+ // Gating both sides here isn't just precision: `.push(`/`.add(` is everywhere
59
+ // in JS/PHP, so an ungated scan slices + regexes nearly every function on repos
60
+ // where the pass cannot emit a single edge — on a 12k-file PHP/JS app that was
61
+ // 20+ minutes of the "Resolving refs" tail and a #850 watchdog kill (#1235).
62
+ const CC_LANGUAGES = new Set(['swift', 'kotlin']);
63
+ function kebabToPascal(s) {
64
+ return s.split('-').map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('');
65
+ }
66
+ /**
67
+ * Nuxt auto-import name for a component, derived from its path UNDER `components/`:
68
+ * `components/media/Card.vue` → `MediaCard`, `components/base/foo/Bar.vue` →
69
+ * `BaseFooBar`. Each directory segment and the filename is PascalCased and
70
+ * concatenated; a directory whose PascalCase name prefixes the next segment is
71
+ * collapsed (Nuxt's de-dup: `base/BaseButton.vue` → `BaseButton`, not
72
+ * `BaseBaseButton`). Returns null for a flat component (`components/NavBar.vue`)
73
+ * — its node is already named by basename, so a direct tag match finds it.
74
+ */
75
+ function nuxtComponentName(filePath) {
76
+ const marker = filePath.lastIndexOf('components/');
77
+ if (marker === -1)
78
+ return null;
79
+ const rel = filePath.slice(marker + 'components/'.length).replace(/\.(vue|ts|tsx|js|jsx)$/i, '');
80
+ const segs = rel.split('/').filter(Boolean).map(kebabToPascal);
81
+ if (segs.length < 2)
82
+ return null;
83
+ const out = [];
84
+ for (const s of segs) {
85
+ const prev = out[out.length - 1];
86
+ if (prev && s.startsWith(prev))
87
+ out[out.length - 1] = s;
88
+ else
89
+ out.push(s);
90
+ }
91
+ return out.join('');
92
+ }
93
+ function sliceLines(content, startLine, endLine) {
94
+ if (!startLine || !endLine)
95
+ return null;
96
+ return content.split('\n').slice(startLine - 1, endLine).join('\n');
97
+ }
98
+ function registrarField(src) {
99
+ const m = src.match(/this\.(\w+)\.(?:add|push|set)\(/);
100
+ return m ? m[1] : null;
101
+ }
102
+ function dispatcherField(src) {
103
+ const forOf = src.match(/\bof\s+(?:Array\.from\(\s*)?this\.(\w+)/);
104
+ if (forOf && /\b\w+\s*\(/.test(src))
105
+ return forOf[1];
106
+ const forEach = src.match(/this\.(\w+)\.forEach\(/);
107
+ if (forEach)
108
+ return forEach[1];
109
+ return null;
110
+ }
111
+ /**
112
+ * Stream method + function nodes lazily. The synthesizers only scan-and-filter
113
+ * down to a tiny matched subset, so materializing every function/method (which
114
+ * is gigabytes on a symbol-dense project) just to iterate it once is what OOM'd
115
+ * #610. Iterating keeps memory O(1) in the node count.
116
+ */
117
+ function* methodAndFunctionNodes(queries) {
118
+ yield* queries.iterateNodesByKind('method');
119
+ yield* queries.iterateNodesByKind('function');
120
+ }
121
+ /** Phase 1: field-backed observer channels (registrar/dispatcher share a store). */
122
+ async function fieldChannelEdges(queries, ctx, onYield) {
123
+ const registrars = [];
124
+ const dispatchers = [];
125
+ let scanned = 0;
126
+ for (const m of methodAndFunctionNodes(queries)) {
127
+ if ((++scanned & 255) === 0)
128
+ await onYield(); // #1091: yield mid-scan on huge graphs
129
+ const isReg = REGISTRAR_NAME.test(m.name);
130
+ const isDisp = DISPATCHER_NAME.test(m.name);
131
+ if (!isReg && !isDisp)
132
+ continue;
133
+ const content = ctx.readFile(m.filePath);
134
+ const src = content && sliceLines(content, m.startLine, m.endLine);
135
+ if (!src)
136
+ continue;
137
+ if (isReg) {
138
+ const f = registrarField(src);
139
+ if (f)
140
+ registrars.push({ node: m, field: f });
141
+ }
142
+ if (isDisp) {
143
+ const f = dispatcherField(src);
144
+ if (f)
145
+ dispatchers.push({ node: m, field: f });
146
+ }
147
+ }
148
+ const edges = [];
149
+ const seen = new Set();
150
+ for (const reg of registrars) {
151
+ const chDispatchers = dispatchers.filter((d) => d.node.filePath === reg.node.filePath && d.field === reg.field);
152
+ if (chDispatchers.length === 0)
153
+ continue;
154
+ const argRe = new RegExp(`${reg.node.name}\\s*\\(\\s*(?:this\\.)?(\\w+)`);
155
+ let added = 0;
156
+ for (const e of queries.getIncomingEdges(reg.node.id, ['calls'])) {
157
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
158
+ break;
159
+ if (!e.line)
160
+ continue;
161
+ const caller = queries.getNodeById(e.source);
162
+ if (!caller)
163
+ continue;
164
+ const line = ctx.readFile(caller.filePath)?.split('\n')[e.line - 1];
165
+ const am = line?.match(argRe);
166
+ if (!am)
167
+ continue;
168
+ const fn = ctx.getNodesByName(am[1]).find((n) => n.kind === 'method' || n.kind === 'function');
169
+ if (!fn)
170
+ continue;
171
+ for (const disp of chDispatchers) {
172
+ if (disp.node.id === fn.id)
173
+ continue;
174
+ const key = `${disp.node.id}>${fn.id}`;
175
+ if (seen.has(key))
176
+ continue;
177
+ seen.add(key);
178
+ edges.push({
179
+ source: disp.node.id, target: fn.id, kind: 'calls', line: disp.node.startLine,
180
+ provenance: 'heuristic',
181
+ metadata: {
182
+ synthesizedBy: 'callback', via: reg.node.name, field: reg.field,
183
+ // Where the callback was wired up (`scene.onUpdate(this.triggerRender)`).
184
+ // This is the #1 thing an agent reads/greps to explain the flow — surface
185
+ // it so node/trace/context can show it without a callers() + Read round-trip.
186
+ registeredAt: `${caller.filePath}:${e.line}`,
187
+ },
188
+ });
189
+ added++;
190
+ }
191
+ }
192
+ }
193
+ return edges;
194
+ }
195
+ /**
196
+ * Closure-collection dispatch: dispatcher iterates a closure-collection property
197
+ * invoking each element; registrar appends a closure to the same-named property.
198
+ * Emits dispatcher → registrar so a flow reaches the registration site (where the
199
+ * appended closure's body — and its callers — live). High-precision: the
200
+ * dispatcher's element-invoke is the gate (a `.forEach` that does NOT invoke its
201
+ * element is ignored), so a repo with no closure-collection dispatch yields zero
202
+ * edges regardless of how many `.append`/`.push` sites it has.
203
+ *
204
+ * Pairs globally by field name (cross-file/class is required — see Alamofire's
205
+ * base-class `Request.didCompleteTask` iterating `validators` appended by the
206
+ * subclass `DataRequest.validate`), bounded by a fan-out cap so a generic field
207
+ * name shared across unrelated classes can't fan out into noise.
208
+ */
209
+ async function closureCollectionEdges(queries, ctx, onYield) {
210
+ const dispatchers = new Map(); // field → dispatcher methods + forEach line
211
+ const registrars = new Map(); // field → registrar methods + append line
212
+ const addReg = (field, node, absLine) => {
213
+ if (!field || /^\d+$/.test(field))
214
+ return; // `$0.append` mis-captures the `0`; the write-RE owns that field
215
+ const arr = registrars.get(field) ?? [];
216
+ if (!arr.some((r) => r.node.id === node.id))
217
+ arr.push({ node, line: absLine });
218
+ registrars.set(field, arr);
219
+ };
220
+ // Slices EVERY Swift/Kotlin method/function's source (no cheap name-gate), so
221
+ // on a repo with a huge file this is the heaviest synthesis pass — yield
222
+ // mid-scan (and mid-match-loop below: a single generated function dense with
223
+ // matches must not starve the watchdog either) so it can't wedge the #850
224
+ // watchdog on its own (#1091, #1235).
225
+ let scanned = 0;
226
+ let matchTick = 0;
227
+ for (const m of methodAndFunctionNodes(queries)) {
228
+ if ((++scanned & 127) === 0)
229
+ await onYield();
230
+ if (!CC_LANGUAGES.has(m.language))
231
+ continue;
232
+ const content = ctx.readFile(m.filePath);
233
+ const src = content && sliceLines(content, m.startLine, m.endLine);
234
+ if (!src)
235
+ continue;
236
+ const hasForEach = src.includes('.forEach');
237
+ const hasAppend = src.includes('.append(') || src.includes('.add(') || src.includes('.push(') || src.includes('.insert(');
238
+ if (!hasForEach && !hasAppend)
239
+ continue;
240
+ const lineAt = (0, synth_utils_1.makeLineAt)(src, m.startLine ?? 1);
241
+ if (hasForEach) {
242
+ CC_DISPATCH_RE.lastIndex = 0;
243
+ let d;
244
+ while ((d = CC_DISPATCH_RE.exec(src))) {
245
+ if ((++matchTick & 255) === 0)
246
+ await onYield();
247
+ const arr = dispatchers.get(d[1]) ?? [];
248
+ if (!arr.some((n) => n.node.id === m.id))
249
+ arr.push({ node: m, line: lineAt(d.index) });
250
+ dispatchers.set(d[1], arr);
251
+ }
252
+ }
253
+ if (hasAppend) {
254
+ CC_APPEND_WRITE_RE.lastIndex = 0;
255
+ let w;
256
+ while ((w = CC_APPEND_WRITE_RE.exec(src))) {
257
+ if ((++matchTick & 255) === 0)
258
+ await onYield();
259
+ addReg(w[2] || w[1], m, lineAt(w.index)); // nested `$0.streams` else the `.write` receiver
260
+ }
261
+ CC_APPEND_DIRECT_RE.lastIndex = 0;
262
+ let a;
263
+ while ((a = CC_APPEND_DIRECT_RE.exec(src))) {
264
+ if ((++matchTick & 255) === 0)
265
+ await onYield();
266
+ addReg(a[1], m, lineAt(a.index));
267
+ }
268
+ }
269
+ }
270
+ const edges = [];
271
+ const seen = new Set();
272
+ for (const [field, disps] of dispatchers) {
273
+ const regs = registrars.get(field);
274
+ if (!regs || regs.length === 0)
275
+ continue;
276
+ if (disps.length > CC_FANOUT_CAP || regs.length > CC_FANOUT_CAP)
277
+ continue; // generic field — can't pair confidently
278
+ for (const disp of disps)
279
+ for (const reg of regs) {
280
+ if (disp.node.id === reg.node.id)
281
+ continue;
282
+ const key = `${disp.node.id}>${reg.node.id}`;
283
+ if (seen.has(key))
284
+ continue;
285
+ seen.add(key);
286
+ edges.push({
287
+ source: disp.node.id, target: reg.node.id, kind: 'calls', line: disp.line,
288
+ provenance: 'heuristic',
289
+ metadata: { synthesizedBy: 'closure-collection', field, registeredAt: `${reg.node.filePath}:${reg.line}` },
290
+ });
291
+ }
292
+ }
293
+ return edges;
294
+ }
295
+ /** Phase 2: string-keyed EventEmitter channels (on('e', fn) ↔ emit('e')). */
296
+ async function eventEmitterEdges(ctx, onYield) {
297
+ let scannedFiles = 0;
298
+ const emitsByEvent = new Map(); // event → dispatcher node ids
299
+ const handlersByEvent = new Map(); // event → handler id → registration site (file:line)
300
+ let scanned = 0;
301
+ for (const file of ctx.getAllFiles()) {
302
+ if ((++scannedFiles & 15) === 0)
303
+ await onYield();
304
+ if ((++scanned & 255) === 0)
305
+ await onYield(); // #1091: yield mid-scan on huge graphs
306
+ const content = ctx.readFile(file);
307
+ if (!content)
308
+ continue;
309
+ const hasEmit = content.includes('.emit(') || content.includes('.fire(') || content.includes('.dispatchEvent(');
310
+ const hasOn = content.includes('.on(') || content.includes('.once(') || content.includes('.addListener(');
311
+ if (!hasEmit && !hasOn)
312
+ continue;
313
+ const nodesInFile = ctx.getNodesInFile(file);
314
+ const lineOf = (0, synth_utils_1.makeLineAt)(content, 1);
315
+ if (hasEmit) {
316
+ EMIT_RE.lastIndex = 0;
317
+ let m;
318
+ while ((m = EMIT_RE.exec(content))) {
319
+ const disp = (0, synth_utils_1.enclosingFn)(nodesInFile, lineOf(m.index));
320
+ if (!disp)
321
+ continue;
322
+ const set = emitsByEvent.get(m[1]) ?? new Set();
323
+ set.add(disp.id);
324
+ emitsByEvent.set(m[1], set);
325
+ }
326
+ }
327
+ if (hasOn) {
328
+ ON_RE.lastIndex = 0;
329
+ let m;
330
+ while ((m = ON_RE.exec(content))) {
331
+ const handlerName = m[2] || m[3];
332
+ if (!handlerName)
333
+ continue;
334
+ const handler = ctx.getNodesByName(handlerName).find((n) => n.kind === 'function' || n.kind === 'method');
335
+ if (!handler)
336
+ continue;
337
+ const map = handlersByEvent.get(m[1]) ?? new Map();
338
+ map.set(handler.id, `${file}:${lineOf(m.index)}`);
339
+ handlersByEvent.set(m[1], map);
340
+ }
341
+ }
342
+ }
343
+ const edges = [];
344
+ const seen = new Set();
345
+ for (const [event, dispatchers] of emitsByEvent) {
346
+ const handlers = handlersByEvent.get(event);
347
+ if (!handlers)
348
+ continue;
349
+ // Precision guard: a generic event name with many handlers/dispatchers can't
350
+ // be matched without receiver-type info (Phase 3) — skip rather than over-link.
351
+ if (dispatchers.size > EVENT_FANOUT_CAP || handlers.size > EVENT_FANOUT_CAP)
352
+ continue;
353
+ for (const d of dispatchers)
354
+ for (const [h, registeredAt] of handlers) {
355
+ if (d === h)
356
+ continue;
357
+ const key = `${d}>${h}`;
358
+ if (seen.has(key))
359
+ continue;
360
+ seen.add(key);
361
+ edges.push({ source: d, target: h, kind: 'calls', provenance: 'heuristic', metadata: { synthesizedBy: 'event-emitter', event, registeredAt } });
362
+ }
363
+ }
364
+ return edges;
365
+ }
366
+ /**
367
+ * Phase 4: React class-component re-render. `this.setState(...)` re-runs the
368
+ * component's `render()`, but that hop is React-internal — no static edge — so a
369
+ * flow like "mutation → setState → canvas repaint" dead-ends at setState even
370
+ * though `render → getRenderableElements → …` is fully call-connected after it.
371
+ * Bridge it: for each class that has a `render` method, link every sibling method
372
+ * whose body calls `this.setState(` → `render`. The setState gate keeps this to
373
+ * React class components (a non-React class with a `render` method won't call
374
+ * `this.setState`). Over-approximation (all setState methods reach render) is
375
+ * accepted — it's reachability-correct, like the callback channels.
376
+ */
377
+ async function reactRenderEdges(queries, ctx, onYield) {
378
+ let scanned255 = 0;
379
+ const edges = [];
380
+ const seen = new Set();
381
+ // A class can only emit here if it CONTAINS a method named `render` — so one
382
+ // indexed name lookup bounds the candidate set up front, and the class scan
383
+ // below skips everything else before its per-class edge/node queries. On a
384
+ // repo with few/no render methods (any non-React codebase) this collapses
385
+ // the pass from every-class fan-out to ~zero DB work, with identical output:
386
+ // the skipped classes fail the same `render` check today, just after paying
387
+ // for their children. (Not a language gate: `render` + `this.setState(` in
388
+ // Java — e.g. Litho — legitimately matches today and still does.)
389
+ const renderOwners = new Set();
390
+ for (const n of ctx.getNodesByName('render')) {
391
+ if (n.kind !== 'method')
392
+ continue;
393
+ for (const e of queries.getIncomingEdges(n.id, ['contains']))
394
+ renderOwners.add(e.source);
395
+ }
396
+ if (renderOwners.size === 0)
397
+ return edges;
398
+ for (const cls of queries.iterateNodesByKind('class')) {
399
+ if ((++scanned255 & 63) === 0)
400
+ await onYield();
401
+ if (!renderOwners.has(cls.id))
402
+ continue;
403
+ const children = queries.getOutgoingEdges(cls.id, ['contains'])
404
+ .map((e) => queries.getNodeById(e.target))
405
+ .filter((n) => !!n && n.kind === 'method');
406
+ const render = children.find((n) => n.name === 'render');
407
+ if (!render)
408
+ continue;
409
+ let added = 0;
410
+ for (const m of children) {
411
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
412
+ break;
413
+ if (m.id === render.id)
414
+ continue;
415
+ const content = ctx.readFile(m.filePath);
416
+ const src = content && sliceLines(content, m.startLine, m.endLine);
417
+ if (!src || !SETSTATE_RE.test(src))
418
+ continue;
419
+ const key = `${m.id}>${render.id}`;
420
+ if (seen.has(key))
421
+ continue;
422
+ seen.add(key);
423
+ edges.push({
424
+ source: m.id, target: render.id, kind: 'calls', line: m.startLine,
425
+ provenance: 'heuristic',
426
+ metadata: { synthesizedBy: 'react-render', via: 'setState', registeredAt: `${render.filePath}:${render.startLine}` },
427
+ });
428
+ added++;
429
+ }
430
+ }
431
+ return edges;
432
+ }
433
+ /**
434
+ * Phase 4b: Flutter setState → build (the Dart analog of react-render). In a
435
+ * StatefulWidget's State class, `setState(() {…})` re-runs `build(context)`, but
436
+ * that hop is framework-internal (Flutter calls build), so a flow like
437
+ * "onPressed → _increment → setState → rebuilt UI" dead-ends at setState. Bridge
438
+ * it: for each Dart class with a `build` method, link every sibling method whose
439
+ * body calls `setState(` → `build`. The setState gate + `.dart` file keep this to
440
+ * Flutter State classes. Over-approximation accepted (reachability-correct).
441
+ */
442
+ async function flutterBuildEdges(queries, ctx, onYield) {
443
+ let scanned255 = 0;
444
+ const edges = [];
445
+ const seen = new Set();
446
+ for (const cls of queries.iterateNodesByKind('class')) {
447
+ if ((++scanned255 & 63) === 0)
448
+ await onYield();
449
+ const children = queries.getOutgoingEdges(cls.id, ['contains'])
450
+ .map((e) => queries.getNodeById(e.target))
451
+ .filter((n) => !!n && n.kind === 'method');
452
+ const build = children.find((n) => n.name === 'build');
453
+ if (!build || !build.filePath.endsWith('.dart'))
454
+ continue;
455
+ let added = 0;
456
+ for (const m of children) {
457
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
458
+ break;
459
+ if (m.id === build.id)
460
+ continue;
461
+ const content = ctx.readFile(m.filePath);
462
+ const src = content && sliceLines(content, m.startLine, m.endLine);
463
+ if (!src || !FLUTTER_SETSTATE_RE.test(src))
464
+ continue;
465
+ const key = `${m.id}>${build.id}`;
466
+ if (seen.has(key))
467
+ continue;
468
+ seen.add(key);
469
+ edges.push({
470
+ source: m.id, target: build.id, kind: 'calls', line: m.startLine,
471
+ provenance: 'heuristic',
472
+ metadata: { synthesizedBy: 'flutter-build', via: 'setState', registeredAt: `${build.filePath}:${build.startLine}` },
473
+ });
474
+ added++;
475
+ }
476
+ }
477
+ return edges;
478
+ }
479
+ /**
480
+ * Reactive ArkUI property decorators: assigning a property carrying one of
481
+ * these re-runs the owning struct's `build()`. Covers both state models —
482
+ * V1 (`@Component`: State/Prop/Link/Provide/Consume/Storage*) and V2
483
+ * (`@ComponentV2`: Local/Provider/Consumer; `@Param` is read-only in V2 so
484
+ * the assignment gate never fires on it, and `@Trace` lives on `@ObservedV2`
485
+ * data classes, not struct properties).
486
+ */
487
+ const ARKUI_REACTIVE_DECORATORS = new Set([
488
+ 'State', 'Prop', 'Link', 'Provide', 'Consume', 'StorageLink', 'StorageProp',
489
+ 'LocalStorageLink', 'LocalStorageProp', 'ObjectLink',
490
+ 'Local', 'Provider', 'Consumer',
491
+ ]);
492
+ /** ArkUI-observed array mutators — `this.todos.push(x)` re-renders like an assignment. */
493
+ const ARKUI_ARRAY_MUTATORS = 'push|pop|shift|unshift|splice|sort|reverse|fill';
494
+ /**
495
+ * Phase 4b-ets: ArkUI state → build (the ArkTS analog of react-render /
496
+ * flutter-build). Assigning a reactive-decorated property (`@State count`,
497
+ * `@Link selected`, …) re-runs the `@Component struct`'s `build()`, but that
498
+ * hop is framework-internal — no static edge — so "onClick → markAllDone →
499
+ * this.todos = […] → rebuilt list" dead-ends at the assignment. Bridge it:
500
+ * for each arkts struct with a `build()` method and at least one reactive
501
+ * property, link every sibling method whose body ASSIGNS (or array-mutates)
502
+ * one of those properties → `build`. Assignment-gated on the struct's OWN
503
+ * reactive property names — a method that merely reads state, or a struct
504
+ * with no reactive properties, gets nothing (this is the precision line the
505
+ * all-sibling-methods design would erase).
506
+ */
507
+ async function arkuiStateBuildEdges(queries, ctx, onYield) {
508
+ let scanned255 = 0;
509
+ const edges = [];
510
+ const seen = new Set();
511
+ for (const struct of queries.iterateNodesByKind('struct')) {
512
+ if ((++scanned255 & 63) === 0)
513
+ await onYield();
514
+ if (struct.language !== 'arkts')
515
+ continue;
516
+ const children = queries.getOutgoingEdges(struct.id, ['contains'])
517
+ .map((e) => queries.getNodeById(e.target))
518
+ .filter((n) => !!n);
519
+ const build = children.find((n) => n.kind === 'method' && n.name === 'build');
520
+ if (!build)
521
+ continue;
522
+ const reactiveProps = children.filter((n) => n.kind === 'property' && (n.decorators ?? []).some((d) => ARKUI_REACTIVE_DECORATORS.has(d)));
523
+ if (reactiveProps.length === 0)
524
+ continue;
525
+ const propAlternation = reactiveProps
526
+ .map((p) => p.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
527
+ .join('|');
528
+ // `this.count = …` / `+=` / `++` / `--` / `this.todos.push(…)`. The
529
+ // `=(?!=)` keeps `this.done == x` comparisons out.
530
+ const mutationRe = new RegExp(`this\\.(?:${propAlternation})\\s*(?:=(?!=)|\\+\\+|--|[+\\-*/%&|^]=|\\.(?:${ARKUI_ARRAY_MUTATORS})\\s*\\()`);
531
+ let added = 0;
532
+ for (const m of children) {
533
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
534
+ break;
535
+ if (m.kind !== 'method' || m.id === build.id)
536
+ continue;
537
+ const content = ctx.readFile(m.filePath);
538
+ const src = content && sliceLines(content, m.startLine, m.endLine);
539
+ if (!src || !mutationRe.test((0, strip_comments_1.stripCommentsForRegex)(src, 'typescript')))
540
+ continue;
541
+ const key = `${m.id}>${build.id}`;
542
+ if (seen.has(key))
543
+ continue;
544
+ seen.add(key);
545
+ edges.push({
546
+ source: m.id, target: build.id, kind: 'calls', line: m.startLine,
547
+ provenance: 'heuristic',
548
+ metadata: { synthesizedBy: 'arkui-state', via: 'state assignment', registeredAt: `${build.filePath}:${build.startLine}` },
549
+ });
550
+ added++;
551
+ }
552
+ }
553
+ return edges;
554
+ }
555
+ /** Emit/subscribe call sites of HarmonyOS's `@ohos.events.emitter` bus. */
556
+ const ARKUI_EMITTER_CALL_RE = /\bemitter\s*\.\s*(emit|on|once)\s*\(\s*([A-Za-z_$][\w$.]*|\{[^)]{0,120}?\beventId\s*:\s*[^,}]+[^)]*?\})/g;
557
+ /** Cap per event bucket — a generic key with many parties is dynamic routing, not a static pair. */
558
+ const ARKUI_EMITTER_FANOUT_CAP = 8;
559
+ /**
560
+ * Phase 4b-ets2: HarmonyOS `@ohos.events.emitter` bridge. The cross-component
561
+ * bus — `emitter.emit(eventId)` fires `emitter.on(eventId, cb)` — is
562
+ * framework-internal, so an order flow riding it (OrangeShopping's
563
+ * add-to-cart) dead-ends at the emit. Link emit-site enclosing
564
+ * function/method → on/once-site enclosing function/method when both
565
+ * reference the SAME statically-recoverable event key.
566
+ *
567
+ * Key recovery, per call site (comment-stripped enclosing-file source): the
568
+ * first argument is an `{ eventId: K }` literal, a `Names.Dotted` constant, or
569
+ * a local whose same-file declaration is `new EventsId(K)` / `= K` — chase one
570
+ * level. Precision scoping learned from the samples monorepo (thousands of
571
+ * unrelated samples, most using eventId 1): NUMERIC keys pair within the same
572
+ * FILE only; NAMED keys pair within the same workspace module directory (or
573
+ * the whole project when it declares no modules — the single-app case), both
574
+ * behind a fan-out cap. Inline `on(id, (e) => {…})` arrows need no special
575
+ * handling — their bodies' calls already attribute to the registering method,
576
+ * so targeting that method keeps the chain connected.
577
+ */
578
+ async function arkuiEmitterEdges(ctx, onYield) {
579
+ let scannedFiles = 0;
580
+ // bucket key -> emit sites / handler sites
581
+ const emits = new Map();
582
+ const handlers = new Map();
583
+ const moduleDirs = (() => {
584
+ const ws = ctx.getWorkspacePackages?.();
585
+ return ws ? [...new Set(ws.byName.values())].sort((a, b) => b.length - a.length) : [];
586
+ })();
587
+ const moduleScopeOf = (file) => {
588
+ for (const dir of moduleDirs) {
589
+ if (file === dir || file.startsWith(dir + '/'))
590
+ return dir;
591
+ }
592
+ return '';
593
+ };
594
+ for (const file of ctx.getAllFiles()) {
595
+ if ((++scannedFiles & 15) === 0)
596
+ await onYield();
597
+ if (!file.endsWith('.ets'))
598
+ continue;
599
+ const content = ctx.readFile(file);
600
+ if (!content || !content.includes('emitter.'))
601
+ continue;
602
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'typescript');
603
+ const nodes = ctx.getNodesInFile(file)
604
+ .filter((n) => n.kind === 'method' || n.kind === 'function');
605
+ ARKUI_EMITTER_CALL_RE.lastIndex = 0;
606
+ let m;
607
+ while ((m = ARKUI_EMITTER_CALL_RE.exec(safe))) {
608
+ const verb = m[1];
609
+ const arg = m[2].trim();
610
+ const line = safe.slice(0, m.index).split('\n').length;
611
+ const encl = nodes
612
+ .filter((n) => n.startLine <= line && n.endLine >= line)
613
+ .sort((a, b) => (a.endLine - a.startLine) - (b.endLine - b.startLine))[0];
614
+ if (!encl)
615
+ continue;
616
+ // Recover the event key from the first argument.
617
+ let key = null;
618
+ const idLit = arg.startsWith('{') ? arg.match(/\beventId\s*:\s*([\w$.]+)/)?.[1] : undefined;
619
+ const token = idLit ?? arg;
620
+ if (token !== undefined) {
621
+ if (/^\d+$/.test(token)) {
622
+ key = `num:${file}:${token}`; // numeric: same-file only
623
+ }
624
+ else if (token.includes('.')) {
625
+ key = `name:${moduleScopeOf(file)}:${token}`;
626
+ }
627
+ else {
628
+ // Local variable — chase its same-file declaration one level:
629
+ // `let x = new EventsId(K)` / `const x = K`.
630
+ const declRe = new RegExp(`\\b${token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b\\s*(?::[^=\\n]+)?=\\s*(?:new\\s+[\\w$.]+\\(\\s*([^)\\n]+?)\\s*\\)|([\\w$.]+))`);
631
+ const decl = safe.match(declRe);
632
+ const inner = (decl?.[1] ?? decl?.[2])?.trim();
633
+ if (inner && /^\d+$/.test(inner))
634
+ key = `num:${file}:${inner}`;
635
+ else if (inner && /^[\w$.]+$/.test(inner))
636
+ key = `name:${moduleScopeOf(file)}:${inner}`;
637
+ }
638
+ }
639
+ if (!key)
640
+ continue;
641
+ const site = { nodeId: encl.id, file, line };
642
+ if (verb === 'emit') {
643
+ (emits.get(key) ?? emits.set(key, []).get(key)).push(site);
644
+ }
645
+ else {
646
+ (handlers.get(key) ?? handlers.set(key, []).get(key)).push(site);
647
+ }
648
+ }
649
+ }
650
+ const edges = [];
651
+ const seen = new Set();
652
+ for (const [key, emitSites] of emits) {
653
+ const handlerSites = handlers.get(key);
654
+ if (!handlerSites)
655
+ continue;
656
+ if (emitSites.length > ARKUI_EMITTER_FANOUT_CAP || handlerSites.length > ARKUI_EMITTER_FANOUT_CAP)
657
+ continue;
658
+ const eventLabel = key.slice(key.lastIndexOf(':') + 1);
659
+ for (const e of emitSites)
660
+ for (const h of handlerSites) {
661
+ if (e.nodeId === h.nodeId)
662
+ continue;
663
+ const dedupe = `${e.nodeId}>${h.nodeId}`;
664
+ if (seen.has(dedupe))
665
+ continue;
666
+ seen.add(dedupe);
667
+ edges.push({
668
+ source: e.nodeId, target: h.nodeId, kind: 'calls', line: e.line,
669
+ provenance: 'heuristic',
670
+ metadata: { synthesizedBy: 'arkui-emitter', event: eventLabel, registeredAt: `${h.file}:${h.line}` },
671
+ });
672
+ }
673
+ }
674
+ return edges;
675
+ }
676
+ /** `router.pushUrl({ url: 'pages/Detail' })` / replaceUrl — literal urls only. */
677
+ const ARKUI_ROUTER_RE = /\brouter\s*\.\s*(?:pushUrl|replaceUrl)\s*\(\s*\{[^)]{0,200}?\burl\s*:\s*['"]([\w\-./]+)['"]/g;
678
+ /**
679
+ * Phase 4b-ets3: HarmonyOS page navigation. `router.pushUrl({ url:
680
+ * 'pages/Detail' })` reaches the `@Entry struct` of
681
+ * `<module>/src/main/ets/pages/Detail.ets`, but the hop is a string — no
682
+ * static edge — so "tap → openDetail → ???" ends at the router call. Bridge
683
+ * literal urls to the page struct: the url resolves against the standard
684
+ * `src/main/ets/` layout (what main_pages.json entries name); candidates
685
+ * prefer the caller's own workspace module (routes are module-scoped), and
686
+ * anything still ambiguous is dropped rather than guessed. Only `@Entry`
687
+ * structs qualify as targets — the decorator is what makes a file a page.
688
+ */
689
+ async function arkuiRouterEdges(ctx, onYield) {
690
+ const edges = [];
691
+ const seen = new Set();
692
+ const allFiles = ctx.getAllFiles();
693
+ const moduleDirs = (() => {
694
+ const ws = ctx.getWorkspacePackages?.();
695
+ return ws ? [...new Set(ws.byName.values())].sort((a, b) => b.length - a.length) : [];
696
+ })();
697
+ const moduleScopeOf = (file) => {
698
+ for (const dir of moduleDirs) {
699
+ if (file === dir || file.startsWith(dir + '/'))
700
+ return dir;
701
+ }
702
+ return '';
703
+ };
704
+ let scannedFiles = 0;
705
+ for (const file of allFiles) {
706
+ if ((++scannedFiles & 15) === 0)
707
+ await onYield();
708
+ if (!file.endsWith('.ets'))
709
+ continue;
710
+ const content = ctx.readFile(file);
711
+ if (!content || !content.includes('router.'))
712
+ continue;
713
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'typescript');
714
+ const nodes = ctx.getNodesInFile(file)
715
+ .filter((n) => n.kind === 'method' || n.kind === 'function');
716
+ ARKUI_ROUTER_RE.lastIndex = 0;
717
+ let m;
718
+ while ((m = ARKUI_ROUTER_RE.exec(safe))) {
719
+ const url = m[1];
720
+ const line = safe.slice(0, m.index).split('\n').length;
721
+ const encl = nodes
722
+ .filter((n) => n.startLine <= line && n.endLine >= line)
723
+ .sort((a, b) => (a.endLine - a.startLine) - (b.endLine - b.startLine))[0];
724
+ if (!encl)
725
+ continue;
726
+ const suffix = `/src/main/ets/${url}.ets`;
727
+ let candidates = allFiles.filter((f) => f.endsWith(suffix));
728
+ if (candidates.length > 1) {
729
+ const scope = moduleScopeOf(file);
730
+ const sameModule = candidates.filter((f) => moduleScopeOf(f) === scope);
731
+ if (sameModule.length > 0)
732
+ candidates = sameModule;
733
+ }
734
+ if (candidates.length !== 1)
735
+ continue; // ambiguous or unresolved — never guess
736
+ const page = ctx.getNodesInFile(candidates[0]).find((n) => n.kind === 'struct' && (n.decorators ?? []).includes('Entry'));
737
+ if (!page)
738
+ continue;
739
+ const key = `${encl.id}>${page.id}`;
740
+ if (seen.has(key))
741
+ continue;
742
+ seen.add(key);
743
+ edges.push({
744
+ source: encl.id, target: page.id, kind: 'calls', line,
745
+ provenance: 'heuristic',
746
+ metadata: { synthesizedBy: 'arkui-route', event: url, registeredAt: `${candidates[0]}:${page.startLine}` },
747
+ });
748
+ }
749
+ }
750
+ return edges;
751
+ }
752
+ /**
753
+ * Phase 4c: C++ virtual override. A call through a base/interface pointer
754
+ * (`db->Get(...)`, `iter->Next()`) dispatches at runtime to a subclass override,
755
+ * but that hop is a vtable indirection — no static call edge — so a flow stops at
756
+ * the abstract base method. Bridge it like react-render: for each C++ class that
757
+ * `extends` a base, link each base method → the subclass method of the same name
758
+ * (the override), so trace/callees from the interface method reach the
759
+ * implementation(s). Over-approximation accepted (reachability-correct); capped
760
+ * per class and gated to C++ to avoid touching other languages' dispatch.
761
+ */
762
+ async function cppOverrideEdges(queries, onYield) {
763
+ let scanned255 = 0;
764
+ const edges = [];
765
+ const seen = new Set();
766
+ const methodsOf = (classId) => queries
767
+ .getOutgoingEdges(classId, ['contains'])
768
+ .map((e) => queries.getNodeById(e.target))
769
+ .filter((n) => !!n && n.kind === 'method');
770
+ for (const cls of queries.iterateNodesByKind('class')) {
771
+ if ((++scanned255 & 63) === 0)
772
+ await onYield();
773
+ const subMethods = methodsOf(cls.id).filter((n) => n.language === 'cpp');
774
+ if (subMethods.length === 0)
775
+ continue;
776
+ for (const ext of queries.getOutgoingEdges(cls.id, ['extends'])) {
777
+ const base = queries.getNodeById(ext.target);
778
+ if (!base || base.language !== 'cpp' || base.id === cls.id)
779
+ continue;
780
+ const baseMethods = new Map(methodsOf(base.id).map((m) => [m.name, m]));
781
+ let added = 0;
782
+ for (const m of subMethods) {
783
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
784
+ break;
785
+ const bm = baseMethods.get(m.name);
786
+ if (!bm || bm.id === m.id)
787
+ continue;
788
+ const key = `${bm.id}>${m.id}`;
789
+ if (seen.has(key))
790
+ continue;
791
+ seen.add(key);
792
+ edges.push({
793
+ source: bm.id,
794
+ target: m.id,
795
+ kind: 'calls',
796
+ line: bm.startLine,
797
+ provenance: 'heuristic',
798
+ metadata: { synthesizedBy: 'cpp-override', via: m.name, registeredAt: `${m.filePath}:${m.startLine}` },
799
+ });
800
+ added++;
801
+ }
802
+ }
803
+ }
804
+ return edges;
805
+ }
806
+ /**
807
+ * Phase 5.5: interface / abstract dispatch (Java, Kotlin). A call through an
808
+ * injected interface (`@Autowired FooService svc; svc.list()`) or an abstract
809
+ * base dispatches at runtime to the implementing class's override — a vtable
810
+ * indirection with no static call edge — so a request→service flow stops at the
811
+ * interface method. Bridge it like cpp-override: for each class that
812
+ * `implements` an interface (or `extends` an abstract base), link each
813
+ * base/interface method → the class's same-name method (the override) so
814
+ * trace/callees reach the implementation. Over-approximation accepted
815
+ * (reachability-correct); capped per class, gated to JVM languages.
816
+ */
817
+ // Languages whose static `implements`/`extends` edges should bridge an
818
+ // interface (or abstract base) method to the matching concrete-class method.
819
+ // The set is "languages with explicit nominal subtyping and a single class
820
+ // kind that holds methods" — i.e. the shape this loop expects. Swift and
821
+ // Scala fit shape-wise (Swift `protocol`/`class`, Scala `trait`/`class`)
822
+ // and are added below; their concrete-side nodes can be a `struct` (Swift)
823
+ // or an `object` (Scala) so the loop also iterates those kinds.
824
+ const IFACE_OVERRIDE_LANGS = new Set([
825
+ 'java', 'kotlin', 'csharp', 'typescript', 'javascript', 'swift', 'scala', 'go', 'rust',
826
+ 'arkts',
827
+ ]);
828
+ /**
829
+ * Go implicit interface satisfaction (#584). Go has no `implements` keyword — a
830
+ * struct satisfies an interface structurally when its method set covers the
831
+ * interface's. Synthesize the missing `implements` edge (struct → interface) by
832
+ * matching method-NAME sets, so impl-navigation works and the interface-dispatch
833
+ * bridge ({@link interfaceOverrideEdges}, now 'go'-enabled) can link an interface
834
+ * method call to the concrete overrides.
835
+ *
836
+ * Name-only matching (signatures ignored) — over-approximation accepted, in line
837
+ * with the other dispatch synthesizers; capped per interface. Empty interfaces
838
+ * (`any`) are skipped so they don't match every struct.
839
+ */
840
+ async function goImplementsEdges(queries, onYield) {
841
+ let scanned255 = 0;
842
+ const edges = [];
843
+ const seen = new Set();
844
+ const methodNameSet = (id) => new Set(queries
845
+ .getOutgoingEdges(id, ['contains'])
846
+ .map((e) => queries.getNodeById(e.target))
847
+ .filter((n) => !!n && n.kind === 'method')
848
+ .map((n) => n.name));
849
+ // Materializes GO structs only (the pass is language-gated by the caller),
850
+ // never the whole struct kind — that array is O(nodes) on struct-heavy
851
+ // repos like the Linux kernel (#1212).
852
+ const goStructs = [];
853
+ for (const s of queries.iterateNodesByKind('struct')) {
854
+ if ((++scanned255 & 63) === 0)
855
+ await onYield();
856
+ if (s.language === 'go')
857
+ goStructs.push(s);
858
+ }
859
+ const structMethods = new Map();
860
+ for (const s of goStructs)
861
+ structMethods.set(s.id, methodNameSet(s.id));
862
+ for (const iface of queries.iterateNodesByKind('interface')) {
863
+ if ((++scanned255 & 63) === 0)
864
+ await onYield();
865
+ if ((++scanned255 & 63) === 0)
866
+ await onYield();
867
+ if (iface.language !== 'go')
868
+ continue;
869
+ const want = methodNameSet(iface.id);
870
+ if (want.size === 0)
871
+ continue; // empty interface (`any`) — would match everything
872
+ let added = 0;
873
+ for (const s of goStructs) {
874
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
875
+ break;
876
+ const have = structMethods.get(s.id);
877
+ if (!have || have.size < want.size)
878
+ continue;
879
+ let all = true;
880
+ for (const m of want) {
881
+ if (!have.has(m)) {
882
+ all = false;
883
+ break;
884
+ }
885
+ }
886
+ if (!all)
887
+ continue;
888
+ const key = `${s.id}>${iface.id}`;
889
+ if (seen.has(key))
890
+ continue;
891
+ seen.add(key);
892
+ edges.push({
893
+ source: s.id,
894
+ target: iface.id,
895
+ kind: 'implements',
896
+ line: s.startLine,
897
+ provenance: 'heuristic',
898
+ metadata: { synthesizedBy: 'go-implements', via: iface.name, registeredAt: `${s.filePath}:${s.startLine}` },
899
+ });
900
+ added++;
901
+ }
902
+ }
903
+ return edges;
904
+ }
905
+ /**
906
+ * Cross-file Go method → receiver-type `contains` edges. In Go a type's methods
907
+ * are commonly declared in a different file from the `type` declaration itself
908
+ * (`type User struct{…}` in `user.go`, `func (u *User) Save()` in
909
+ * `user_store.go`). Extraction attaches the struct→method `contains` edge only
910
+ * when the receiver type is in the SAME file — the owner lookup in
911
+ * `tree-sitter.ts` is scoped to the file being parsed — so a cross-file method
912
+ * is left orphaned from its type (it's still `contains`ed by its file, just not
913
+ * its struct). That breaks `codegraph_node` member outlines, any
914
+ * callers/callees/impact traversal that goes through the type's `contains`
915
+ * edges, and the {@link goImplementsEdges} method-set computation (which derives
916
+ * a struct's method set from those same edges, so it under-counts interfaces a
917
+ * cross-file struct satisfies).
918
+ *
919
+ * Go guarantees a method's receiver type is declared in the SAME PACKAGE as the
920
+ * method, and a Go package is a single directory — so this is a deterministic
921
+ * structural link, not a heuristic: find the same-named type in the method's own
922
+ * directory and add the missing `contains` edge (no `provenance: 'heuristic'`,
923
+ * matching the same-file edges extraction already emits). Skips methods that
924
+ * already have a type parent (the same-file case). (#583, cross-file half)
925
+ */
926
+ async function goCrossFileMethodContainsEdges(queries, onYield) {
927
+ let scanned255 = 0;
928
+ const edges = [];
929
+ const seen = new Set();
930
+ const TYPE_KINDS = new Set(['struct', 'class', 'interface', 'enum', 'type_alias']);
931
+ const dirOf = (p) => {
932
+ const i = p.replace(/\\/g, '/').lastIndexOf('/');
933
+ return i >= 0 ? p.slice(0, i) : '';
934
+ };
935
+ for (const method of queries.iterateNodesByKind('method')) {
936
+ if ((++scanned255 & 63) === 0)
937
+ await onYield();
938
+ if ((++scanned255 & 63) === 0)
939
+ await onYield();
940
+ if (method.language !== 'go')
941
+ continue;
942
+ // The receiver type is encoded in the method's qualifiedName as `Recv::name`
943
+ // (extraction sets `${receiverType}::${name}` for receiver methods).
944
+ const qn = method.qualifiedName;
945
+ if (!qn)
946
+ continue;
947
+ const sep = qn.lastIndexOf('::');
948
+ if (sep <= 0)
949
+ continue;
950
+ const receiver = qn.slice(0, sep);
951
+ if (!receiver)
952
+ continue;
953
+ // Already attached to its type (same-file case handled at extraction)?
954
+ const hasTypeParent = queries
955
+ .getIncomingEdges(method.id, ['contains'])
956
+ .some((e) => {
957
+ const src = queries.getNodeById(e.source);
958
+ return src != null && TYPE_KINDS.has(src.kind);
959
+ });
960
+ if (hasTypeParent)
961
+ continue;
962
+ // Find the receiver type in the SAME directory (= same Go package). Go forbids
963
+ // duplicate type names within a package, so a same-name same-dir match is
964
+ // unambiguous; scoping to the directory avoids linking to a same-named type
965
+ // in another package.
966
+ const dir = dirOf(method.filePath);
967
+ const owner = queries
968
+ .getNodesByName(receiver)
969
+ .find((n) => n.language === 'go' && TYPE_KINDS.has(n.kind) && dirOf(n.filePath) === dir);
970
+ if (!owner)
971
+ continue;
972
+ const key = `${owner.id}>${method.id}`;
973
+ if (seen.has(key))
974
+ continue;
975
+ seen.add(key);
976
+ edges.push({ source: owner.id, target: method.id, kind: 'contains', line: method.startLine });
977
+ }
978
+ return edges;
979
+ }
980
+ /**
981
+ * Kotlin Multiplatform `expect`/`actual` linking. A `common` source set declares
982
+ * `expect fun foo()` / `expect class Bar`; each platform source set (jvm, native,
983
+ * js, …) provides an `actual` implementation with the IDENTICAL fully-qualified
984
+ * name in a different file. Callers in common code resolve to the `expect`
985
+ * declaration, so every `actual` impl ends up with zero dependents — invisible to
986
+ * impact/affected even though editing it can break every caller of the API.
987
+ *
988
+ * Synthesize a `calls` edge from the common declaration to each platform `actual`
989
+ * (mirroring the interface-impl bridge: abstract → concrete), so editing a
990
+ * platform impl surfaces the common `expect` and its callers, and the impl file
991
+ * participates in the graph.
992
+ *
993
+ * `expect`/`actual` are captured onto the node's `decorators` list at extraction
994
+ * (kotlin.ts `extractModifiers`). Members of an `expect class` are NOT themselves
995
+ * keyword-marked, so the declaration side is matched as the same-FQN, same-kind
996
+ * node that is NOT marked `actual`. Requiring an `actual`-marked counterpart also
997
+ * gates out plain cross-file overloads (neither side is marked).
998
+ */
999
+ // Kinds that an `expect`/`actual` pair may legitimately straddle. `expect class`
1000
+ // is routinely fulfilled by an `actual typealias` (e.g. `actual typealias
1001
+ // CancellationException = …`, `actual typealias SchedulerTask = Task`), so a
1002
+ // strict kind match would miss those one-line alias files. Same-FQN + the
1003
+ // `actual` marker already gates out unrelated symbols, so widening to the
1004
+ // type-like kinds is safe.
1005
+ const KMP_TYPE_KINDS = new Set(['class', 'interface', 'struct', 'enum', 'type_alias']);
1006
+ function kmpKindsCompatible(a, b) {
1007
+ return a === b || (KMP_TYPE_KINDS.has(a) && KMP_TYPE_KINDS.has(b));
1008
+ }
1009
+ async function kotlinExpectActualEdges(queries, onYield) {
1010
+ let scanned255 = 0;
1011
+ const edges = [];
1012
+ const seen = new Set();
1013
+ // SQL-side language+decorator pre-filter, streamed. The old
1014
+ // `getAllNodes().filter(...)` hydrated the ENTIRE node table into one array
1015
+ // just to find kotlin `actual` declarations — on a 2M-node graph that alone
1016
+ // exceeded Node's default heap and killed the index (#1212). The LIKE
1017
+ // pre-filter can over-match (substring), so the exact decorator check stays.
1018
+ for (const act of queries.iterateNodesByLanguageWithDecorator('kotlin', 'actual')) {
1019
+ if ((++scanned255 & 63) === 0)
1020
+ await onYield();
1021
+ if (!act.decorators?.includes('actual'))
1022
+ continue;
1023
+ let added = 0;
1024
+ for (const cand of queries.getNodesByQualifiedNameExact(act.qualifiedName)) {
1025
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
1026
+ break;
1027
+ // The declaration side: same FQN + compatible kind, a different file, NOT
1028
+ // itself an `actual` (that would be a sibling platform impl, not the decl).
1029
+ if (cand.language !== 'kotlin' || cand.id === act.id)
1030
+ continue;
1031
+ if (!kmpKindsCompatible(cand.kind, act.kind) || cand.filePath === act.filePath)
1032
+ continue;
1033
+ if (cand.decorators?.includes('actual'))
1034
+ continue;
1035
+ const key = `${cand.id}>${act.id}`;
1036
+ if (seen.has(key))
1037
+ continue;
1038
+ seen.add(key);
1039
+ edges.push({
1040
+ source: cand.id,
1041
+ target: act.id,
1042
+ kind: 'calls',
1043
+ line: cand.startLine,
1044
+ provenance: 'heuristic',
1045
+ metadata: {
1046
+ synthesizedBy: 'kotlin-expect-actual',
1047
+ via: act.name,
1048
+ registeredAt: `${act.filePath}:${act.startLine}`,
1049
+ },
1050
+ });
1051
+ added++;
1052
+ }
1053
+ }
1054
+ return edges;
1055
+ }
1056
+ async function interfaceOverrideEdges(queries, onYield) {
1057
+ let scanned255 = 0;
1058
+ const edges = [];
1059
+ const seen = new Set();
1060
+ // Memoized: a popular base interface's method list is otherwise re-fetched
1061
+ // once per implementer (dubbo-style hub interfaces have hundreds), and the
1062
+ // memo only ever serves reads. Same rows, same order — byte-identical.
1063
+ const methodsMemo = new Map();
1064
+ const methodsOf = (classId) => {
1065
+ const hit = methodsMemo.get(classId);
1066
+ if (hit)
1067
+ return hit;
1068
+ const methods = queries
1069
+ .getOutgoingEdges(classId, ['contains'])
1070
+ .map((e) => queries.getNodeById(e.target))
1071
+ .filter((n) => !!n && n.kind === 'method');
1072
+ methodsMemo.set(classId, methods);
1073
+ return methods;
1074
+ };
1075
+ // Concrete-side kinds vary by language: `class` covers Java / Kotlin /
1076
+ // C# / TS / Swift-classes / Scala-classes; `struct` covers Swift value
1077
+ // types that conform to protocols. Iterate both.
1078
+ const concreteKinds = ['class', 'struct', 'union'];
1079
+ for (const kind of concreteKinds) {
1080
+ for (const cls of queries.iterateNodesByKind(kind)) {
1081
+ if ((++scanned255 & 63) === 0)
1082
+ await onYield();
1083
+ // A class can only emit here if it HAS a supertype edge — check that
1084
+ // (one edge query) before materializing its methods: most classes in a
1085
+ // typical graph extend/implement nothing and skip in one hop.
1086
+ const sups = queries.getOutgoingEdges(cls.id, ['implements', 'extends']);
1087
+ if (sups.length === 0)
1088
+ continue;
1089
+ const implMethods = methodsOf(cls.id).filter((n) => IFACE_OVERRIDE_LANGS.has(n.language));
1090
+ if (implMethods.length === 0)
1091
+ continue;
1092
+ for (const sup of sups) {
1093
+ const base = queries.getNodeById(sup.target);
1094
+ if (!base || !IFACE_OVERRIDE_LANGS.has(base.language) || base.id === cls.id)
1095
+ continue;
1096
+ // Group impl methods by name to handle OVERLOADS: an interface `list()` and
1097
+ // `list(params)` are distinct nodes and a call may resolve to either, so
1098
+ // link every base overload → every same-name impl overload (keying by name
1099
+ // alone would drop all but one and miss the resolved overload).
1100
+ const implByName = new Map();
1101
+ for (const m of implMethods) {
1102
+ const arr = implByName.get(m.name);
1103
+ if (arr)
1104
+ arr.push(m);
1105
+ else
1106
+ implByName.set(m.name, [m]);
1107
+ }
1108
+ let added = 0;
1109
+ for (const bm of methodsOf(base.id)) {
1110
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
1111
+ break;
1112
+ for (const m of implByName.get(bm.name) ?? []) {
1113
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
1114
+ break;
1115
+ if (bm.id === m.id)
1116
+ continue;
1117
+ const key = `${bm.id}>${m.id}`;
1118
+ if (seen.has(key))
1119
+ continue;
1120
+ seen.add(key);
1121
+ edges.push({
1122
+ source: bm.id,
1123
+ target: m.id,
1124
+ kind: 'calls',
1125
+ line: bm.startLine,
1126
+ provenance: 'heuristic',
1127
+ metadata: { synthesizedBy: 'interface-impl', via: m.name, registeredAt: `${m.filePath}:${m.startLine}` },
1128
+ });
1129
+ added++;
1130
+ }
1131
+ }
1132
+ }
1133
+ }
1134
+ }
1135
+ return edges;
1136
+ }
1137
+ /**
1138
+ * Go gRPC stub → impl bridge. The protoc-gen-go-grpc codegen emits an
1139
+ * `UnimplementedXxxServer` struct in `*_grpc.pb.go` carrying one method
1140
+ * per service RPC; the real handler is a hand-written struct in another
1141
+ * file (`x/bank/keeper/msg_server.go::msgServer.Send` in cosmos-sdk).
1142
+ * Go's structural typing means no `implements` edge exists for our
1143
+ * resolver to follow, so `trace("Send","SendCoins")` lands on the
1144
+ * empty stub and reports "no path" (validated empirically — the cosmos
1145
+ * Q1 r1 trace failure that drove this work).
1146
+ *
1147
+ * Bridge: for each `UnimplementedXxxServer` whose RPC-method names are
1148
+ * a SUBSET of some other Go struct's method names, emit `calls` edges
1149
+ * `stub.method → impl.method` (paired by name). Excludes the gRPC
1150
+ * internal markers `mustEmbedUnimplementedXxxServer` and
1151
+ * `testEmbeddedByValue`, and skips candidate impls that themselves
1152
+ * live in a generated file (their `xxxClient` / sibling stubs would
1153
+ * otherwise look like impls).
1154
+ *
1155
+ * Multiple candidates is allowed and capped at MAX_CALLBACKS_PER_CHANNEL —
1156
+ * a service often has both a production impl and one or more test
1157
+ * mocks; linking to all preserves trace utility without false-favoring.
1158
+ *
1159
+ * Provenance: `heuristic`, `synthesizedBy: 'go-grpc-stub-impl'`. The
1160
+ * stub's source line is the wiring site shown in the trace trail.
1161
+ */
1162
+ async function goGrpcStubImplEdges(queries, onYield) {
1163
+ let scanned255 = 0;
1164
+ const edges = [];
1165
+ const seen = new Set();
1166
+ const STUB_RE = /^Unimplemented.*Server$/;
1167
+ // gRPC internal-helper methods that appear on every Unimplemented*Server;
1168
+ // not part of the service contract, so exclude when computing the RPC-method
1169
+ // signature used to match impls.
1170
+ const isInternalMarker = (n) => n.startsWith('mustEmbed') || n === 'testEmbeddedByValue';
1171
+ // Methods directly contained by each Go struct, name-only. Built once.
1172
+ const methodNamesByStruct = new Map();
1173
+ const methodNodesByStruct = new Map();
1174
+ const goStructs = [];
1175
+ for (const s of queries.iterateNodesByKind('struct')) {
1176
+ if ((++scanned255 & 63) === 0)
1177
+ await onYield();
1178
+ if (s.language !== 'go')
1179
+ continue;
1180
+ goStructs.push(s);
1181
+ const ms = queries
1182
+ .getOutgoingEdges(s.id, ['contains'])
1183
+ .map((e) => queries.getNodeById(e.target))
1184
+ .filter((n) => !!n && n.kind === 'method');
1185
+ methodNodesByStruct.set(s.id, ms);
1186
+ methodNamesByStruct.set(s.id, new Set(ms.map((m) => m.name)));
1187
+ }
1188
+ for (const stub of goStructs) {
1189
+ if (!STUB_RE.test(stub.name))
1190
+ continue;
1191
+ // The stub MUST live in a generated file — that's what tells us this is
1192
+ // a protoc-emitted scaffold rather than someone naming a struct
1193
+ // `UnimplementedXxxServer` by hand. Without this gate we'd also bridge
1194
+ // such hand-written structs and create misleading edges.
1195
+ if (!(0, generated_detection_1.isGeneratedFile)(stub.filePath))
1196
+ continue;
1197
+ const stubMethods = (methodNodesByStruct.get(stub.id) ?? []).filter((m) => !isInternalMarker(m.name));
1198
+ if (stubMethods.length === 0)
1199
+ continue;
1200
+ const stubMethodNames = stubMethods.map((m) => m.name);
1201
+ for (const cand of goStructs) {
1202
+ if (cand.id === stub.id)
1203
+ continue;
1204
+ // Skip generated-file candidates — they're siblings (msgClient,
1205
+ // UnsafeMsgServer, …) whose method sets coincidentally match.
1206
+ if ((0, generated_detection_1.isGeneratedFile)(cand.filePath))
1207
+ continue;
1208
+ const candNames = methodNamesByStruct.get(cand.id);
1209
+ if (!candNames)
1210
+ continue;
1211
+ // Subset: every RPC method must exist on the candidate by name.
1212
+ // Signature-level match would tighten this further, but name-match
1213
+ // alone already gives one-to-one pairing in real codebases because
1214
+ // gRPC method-name sets are highly distinctive (Send + MultiSend +
1215
+ // UpdateParams + SetSendEnabled is unique to bank's MsgServer).
1216
+ if (!stubMethodNames.every((n) => candNames.has(n)))
1217
+ continue;
1218
+ const candMethods = methodNodesByStruct.get(cand.id) ?? [];
1219
+ let added = 0;
1220
+ for (const sm of stubMethods) {
1221
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
1222
+ break;
1223
+ for (const cm of candMethods) {
1224
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
1225
+ break;
1226
+ if (cm.name !== sm.name)
1227
+ continue;
1228
+ const key = `${sm.id}>${cm.id}`;
1229
+ if (seen.has(key))
1230
+ continue;
1231
+ seen.add(key);
1232
+ edges.push({
1233
+ source: sm.id,
1234
+ target: cm.id,
1235
+ kind: 'calls',
1236
+ line: sm.startLine,
1237
+ provenance: 'heuristic',
1238
+ metadata: {
1239
+ synthesizedBy: 'go-grpc-stub-impl',
1240
+ via: cm.name,
1241
+ registeredAt: `${cm.filePath}:${cm.startLine}`,
1242
+ },
1243
+ });
1244
+ added++;
1245
+ }
1246
+ }
1247
+ }
1248
+ }
1249
+ return edges;
1250
+ }
1251
+ /** Kinds a JSX tag can name. A tag that resolves only to a type is markup we drop. */
1252
+ const JSX_CHILD_KINDS = new Set(['component', 'function', 'class']);
1253
+ /**
1254
+ * The languages a JSX tag can plausibly name a component in. Preferred over a
1255
+ * same-named symbol in another language, never required — a React Native tag
1256
+ * whose only match is the native class it bridges to (`requireNativeComponent`)
1257
+ * still links there.
1258
+ */
1259
+ const JSX_CHILD_LANGUAGES = [...JS_FAMILY, 'vue', 'svelte'];
1260
+ /** `localName` → the project file it is imported from, for one file's imports. */
1261
+ function importedFrom(ctx, file, language) {
1262
+ const out = new Map();
1263
+ for (const im of ctx.getImportMappings(file, language)) {
1264
+ // The mappings name the module as written; the file it is comes from the
1265
+ // same resolution the import resolver uses (aliases, extensions, index files).
1266
+ const resolved = im.resolvedPath ?? (0, import_resolver_1.resolveImportPath)(im.source, file, language, ctx);
1267
+ if (resolved)
1268
+ out.set(im.localName, resolved);
1269
+ }
1270
+ return out;
1271
+ }
1272
+ /**
1273
+ * The component a JSX tag names, among every node that shares the name.
1274
+ *
1275
+ * A tag is written in one file, and that file already says which `FrameCard` it
1276
+ * means: the one it declares, or the one it imports. Taking the FIRST node of
1277
+ * that name — which is what this did — is a coin flip once a name repeats, and
1278
+ * it costs twice over. The parent gets an edge to a component it never renders,
1279
+ * and the component it does render is left with no caller at all, so every walk
1280
+ * back from that subtree dead-ends: on an Expo app whose `<FrameCard/>` was one
1281
+ * of two, the folder sheet's `openBackgroundNoiseDetail` stood alone on Screens
1282
+ * with no screen behind it, while the edge pointed at an unrelated card in
1283
+ * another sheet.
1284
+ *
1285
+ * Same file first — a small component declared beside its use is the commonest
1286
+ * shape, and the one an import can never disambiguate. Then the file the name
1287
+ * is imported from. Then the language, which only decides a tie: a `.tsx` tag
1288
+ * naming both a TS component and a same-named Swift class means the TS one.
1289
+ */
1290
+ function jsxChild(ctx, name, file, importsOf) {
1291
+ const candidates = ctx.getNodesByName(name).filter((n) => JSX_CHILD_KINDS.has(n.kind));
1292
+ if (candidates.length <= 1)
1293
+ return candidates[0];
1294
+ const local = candidates.find((n) => n.filePath === file);
1295
+ if (local)
1296
+ return local;
1297
+ const from = importsOf().get(name);
1298
+ if (from) {
1299
+ const imported = candidates.find((n) => n.filePath === from);
1300
+ if (imported)
1301
+ return imported;
1302
+ }
1303
+ return candidates.find((n) => JSX_CHILD_LANGUAGES.includes(n.language)) ?? candidates[0];
1304
+ }
1305
+ /**
1306
+ * Phase 5: React JSX child rendering. A component that returns `<Child .../>`
1307
+ * mounts Child — React calls it — but JSX instantiation isn't a static call edge,
1308
+ * so a render tree (App.render → StaticCanvas → renderStaticScene) breaks at the
1309
+ * JSX hop. Link parent → each capitalized JSX child it renders. File-oriented
1310
+ * (read each JSX file once). Precision gate: the child name must resolve to a
1311
+ * component/function/class node — TS generics like `Array<Foo>` resolve to a type
1312
+ * (or nothing) and are dropped.
1313
+ */
1314
+ async function reactJsxChildEdges(ctx, onYield) {
1315
+ let scannedFiles = 0;
1316
+ const edges = [];
1317
+ const seen = new Set();
1318
+ const PARENT_KINDS = new Set(['method', 'function', 'component']);
1319
+ let scanned = 0;
1320
+ for (const file of ctx.getAllFiles()) {
1321
+ if ((++scannedFiles & 15) === 0)
1322
+ await onYield();
1323
+ if ((++scanned & 255) === 0)
1324
+ await onYield(); // #1091: yield mid-scan on huge graphs
1325
+ const content = ctx.readFile(file);
1326
+ if (!content || (!content.includes('</') && !content.includes('/>')))
1327
+ continue; // JSX-file gate
1328
+ // File-level language gate, not merely a project-level one: mixed C/JS
1329
+ // monorepos must not interpret `"<Foo/>"` inside C as JSX (#1560).
1330
+ const parents = ctx.getNodesInFile(file).filter((n) => PARENT_KINDS.has(n.kind) && JS_FAMILY.includes(n.language));
1331
+ if (parents.length === 0)
1332
+ continue;
1333
+ // Read once per file, and only when a name actually turns out ambiguous.
1334
+ let imports = null;
1335
+ const importsOf = () => (imports ??= importedFrom(ctx, file, parents[0].language));
1336
+ for (const parent of parents) {
1337
+ const src = sliceLines(content, parent.startLine, parent.endLine);
1338
+ if (!src || (!src.includes('</') && !src.includes('/>')))
1339
+ continue;
1340
+ const names = new Set();
1341
+ JSX_TAG_RE.lastIndex = 0;
1342
+ let m;
1343
+ while ((m = JSX_TAG_RE.exec(src)))
1344
+ names.add(m[1]);
1345
+ let added = 0;
1346
+ for (const name of names) {
1347
+ if (added >= MAX_JSX_CHILDREN)
1348
+ break;
1349
+ const child = jsxChild(ctx, name, file, importsOf);
1350
+ if (!child || child.id === parent.id)
1351
+ continue;
1352
+ const key = `${parent.id}>${child.id}`;
1353
+ if (seen.has(key))
1354
+ continue;
1355
+ seen.add(key);
1356
+ edges.push({
1357
+ source: parent.id, target: child.id, kind: 'calls', line: parent.startLine,
1358
+ provenance: 'heuristic',
1359
+ metadata: { synthesizedBy: 'jsx-render', via: name },
1360
+ });
1361
+ added++;
1362
+ }
1363
+ }
1364
+ }
1365
+ return edges;
1366
+ }
1367
+ /**
1368
+ * Phase 6: Vue SFC templates. The `.vue` extractor only parses `<script>`, so
1369
+ * template usage is invisible — child components and event handlers used ONLY in
1370
+ * the template have no edge to them. PascalCase children (`<VPNav/>`) are already
1371
+ * caught by reactJsxChildEdges (which scans the SFC component node), so this adds
1372
+ * the two Vue-specific shapes:
1373
+ * - kebab-case children: `<el-button>` → `ElButton` component (renders).
1374
+ * - event bindings: `@click="onClick"` / `v-on:submit="save"` → handler method.
1375
+ * Scoped to the `<template>` block of `.vue` files; resolution gate (kebab→
1376
+ * component, handler→function/method) keeps precision; inline arrows / `$emit`
1377
+ * skipped.
1378
+ */
1379
+ async function vueTemplateEdges(ctx, onYield) {
1380
+ let scannedFiles = 0;
1381
+ let scanned255 = 0;
1382
+ const edges = [];
1383
+ const seen = new Set();
1384
+ const COMPONENT_KINDS = new Set(['component', 'function', 'class']);
1385
+ const HANDLER_KINDS = new Set(['method', 'function']);
1386
+ // A composable's returned member may be a fn (`function close(){}`) or an
1387
+ // arrow assigned to a const (`const close = () => {}`).
1388
+ const RETURN_KINDS = new Set(['method', 'function', 'variable', 'constant']);
1389
+ // Nuxt auto-imports nested components by a DIRECTORY-PREFIXED name —
1390
+ // `components/media/Card.vue` is used as `<MediaCard/>`, not `<Card/>` — but
1391
+ // the component node is named by basename (`Card`), so a direct tag match
1392
+ // misses it (flat components match by basename and don't need this). Map each
1393
+ // nested component's Nuxt name → node so those template usages resolve.
1394
+ const nuxtComponents = new Map();
1395
+ for (const c of (ctx.iterateNodesByKind?.('component') ?? ctx.getNodesByKind('component'))) {
1396
+ if ((++scanned255 & 63) === 0)
1397
+ await onYield();
1398
+ const nn = nuxtComponentName(c.filePath);
1399
+ if (nn && !nuxtComponents.has(nn))
1400
+ nuxtComponents.set(nn, c);
1401
+ }
1402
+ for (const file of ctx.getAllFiles()) {
1403
+ if ((++scannedFiles & 15) === 0)
1404
+ await onYield();
1405
+ if (!file.endsWith('.vue'))
1406
+ continue;
1407
+ const content = ctx.readFile(file);
1408
+ const tpl = content && content.match(/<template[^>]*>([\s\S]*)<\/template>/i)?.[1];
1409
+ if (!tpl)
1410
+ continue;
1411
+ const comp = ctx.getNodesInFile(file).find((n) => n.kind === 'component');
1412
+ if (!comp)
1413
+ continue;
1414
+ // Composable-destructure map: alias → { composable, key }. Lets us resolve a
1415
+ // template handler that isn't a local function but a destructured composable
1416
+ // return (`@click="closeSidebar"` ← `const { close: closeSidebar } = useSidebarControl()`).
1417
+ const script = content.match(/<script[^>]*>([\s\S]*?)<\/script>/i)?.[1] ?? '';
1418
+ const destructured = new Map();
1419
+ VUE_DESTRUCTURE_RE.lastIndex = 0;
1420
+ let dm;
1421
+ while ((dm = VUE_DESTRUCTURE_RE.exec(script))) {
1422
+ if (!/^use[A-Z]/.test(dm[2]))
1423
+ continue; // composables / hooks only
1424
+ for (const part of dm[1].split(',')) {
1425
+ const pm = part.trim().match(/^(\w+)\s*(?::\s*(\w+))?$/); // key | key: alias
1426
+ if (pm)
1427
+ destructured.set(pm[2] || pm[1], { composable: dm[2], key: pm[1] });
1428
+ }
1429
+ }
1430
+ let added = 0;
1431
+ const addEdge = (target, meta) => {
1432
+ if (added >= MAX_JSX_CHILDREN || !target || target.id === comp.id)
1433
+ return;
1434
+ const k = `${comp.id}>${target.id}>${meta.synthesizedBy}`;
1435
+ if (seen.has(k))
1436
+ return;
1437
+ seen.add(k);
1438
+ edges.push({ source: comp.id, target: target.id, kind: 'calls', line: comp.startLine, provenance: 'heuristic', metadata: meta });
1439
+ added++;
1440
+ };
1441
+ // Prefer a target in THIS SFC (handlers live in the same file's script) —
1442
+ // avoids cross-file mis-match when a name repeats across a monorepo.
1443
+ const resolve = (name, kinds) => {
1444
+ const matches = ctx.getNodesByName(name).filter((n) => kinds.has(n.kind));
1445
+ return matches.find((n) => n.filePath === file) ?? matches[0];
1446
+ };
1447
+ let m;
1448
+ VUE_KEBAB_RE.lastIndex = 0;
1449
+ while ((m = VUE_KEBAB_RE.exec(tpl))) {
1450
+ const tag = kebabToPascal(m[1]);
1451
+ addEdge(resolve(tag, COMPONENT_KINDS) ?? nuxtComponents.get(tag), { synthesizedBy: 'jsx-render', via: m[1] });
1452
+ }
1453
+ // PascalCase component tags. Try a direct name match first (flat components
1454
+ // and explicit registrations), then the Nuxt dir-prefixed auto-import name
1455
+ // (`<MediaCard>` → components/media/Card.vue). Built-ins match neither → no edge.
1456
+ VUE_PASCAL_RE.lastIndex = 0;
1457
+ while ((m = VUE_PASCAL_RE.exec(tpl))) {
1458
+ const tag = m[1];
1459
+ addEdge(resolve(tag, COMPONENT_KINDS) ?? nuxtComponents.get(tag), { synthesizedBy: 'jsx-render', via: tag });
1460
+ }
1461
+ VUE_HANDLER_RE.lastIndex = 0;
1462
+ while ((m = VUE_HANDLER_RE.exec(tpl))) {
1463
+ const event = m[1];
1464
+ const expr = m[2].trim();
1465
+ if (expr.includes('=>') || expr.startsWith('$'))
1466
+ continue; // inline arrow / $emit
1467
+ const name = expr.match(/^([A-Za-z_]\w*)/)?.[1];
1468
+ if (!name)
1469
+ continue;
1470
+ const direct = resolve(name, HANDLER_KINDS);
1471
+ if (direct) {
1472
+ addEdge(direct, { synthesizedBy: 'vue-handler', event });
1473
+ continue;
1474
+ }
1475
+ // Composable-destructure handler → resolve to the composable's returned fn.
1476
+ const d = destructured.get(name);
1477
+ if (!d)
1478
+ continue;
1479
+ const composable = resolve(d.composable, HANDLER_KINDS);
1480
+ // Resolve to the SPECIFIC returned member (e.g. `close`) defined in the
1481
+ // composable's file. No fallback to the composable itself — the component
1482
+ // already has a static `useX()` call edge, so that would just be redundant
1483
+ // and less precise.
1484
+ const keyFn = composable
1485
+ ? ctx.getNodesByName(d.key).find((n) => RETURN_KINDS.has(n.kind) && n.filePath === composable.filePath)
1486
+ : undefined;
1487
+ if (keyFn)
1488
+ addEdge(keyFn, { synthesizedBy: 'vue-handler', event, via: d.composable });
1489
+ }
1490
+ }
1491
+ return edges;
1492
+ }
1493
+ /**
1494
+ * React Native cross-language event channel (Phase 3 of the mixed-iOS/RN
1495
+ * bridging effort). Same shape as `eventEmitterEdges` but cross-language:
1496
+ *
1497
+ * Native (ObjC, on RCTEventEmitter subclass):
1498
+ * [self sendEventWithName:@"locationUpdate" body:@{...}];
1499
+ *
1500
+ * Native (Java/Kotlin, via the JS module dispatcher):
1501
+ * emitter.emit("locationUpdate", body);
1502
+ * reactContext.getJSModule(RCTDeviceEventEmitter.class).emit("locationUpdate", body);
1503
+ *
1504
+ * JS (subscriber):
1505
+ * new NativeEventEmitter(NativeModules.Geo).addListener("locationUpdate", handler);
1506
+ * DeviceEventEmitter.addListener("locationUpdate", handler);
1507
+ *
1508
+ * Synthesize: native dispatch site → JS handler, keyed by the literal
1509
+ * event name. A NAMED handler (`addListener('x', handleX)`) is the target
1510
+ * when it is a node; an unnamed one — a parameter passed through, or an
1511
+ * inline `(data) => {…}` written in a `useEffect` — is attributed to the
1512
+ * enclosing function, where the event demonstrably lands. (The in-language
1513
+ * synthesizer stays named-only; this channel pairs across a language
1514
+ * boundary on a literal, which is the evidence that makes the wider
1515
+ * attribution safe.)
1516
+ *
1517
+ * Provenance `'heuristic'`, synthesizedBy `'rn-event-channel'`.
1518
+ */
1519
+ // ObjC's `[self sendEventWithName:@"X" body:...]` shape (bracket syntax,
1520
+ // `@` string literals).
1521
+ const RN_OBJC_SEND_RE = /\bsendEventWithName\s*:\s*@"([^"]+)"/g;
1522
+ // Swift's `sendEvent(withName: "X", body: ...)` shape — same RCTEventEmitter
1523
+ // method, different call syntax. Both Objective-C and Swift subclass
1524
+ // RCTEventEmitter so this catches the Swift-side equivalent emission sites
1525
+ // (e.g. RNFusedLocation.swift's `sendEvent(withName: "geolocationDidChange",
1526
+ // body: locationData)`).
1527
+ const RN_SWIFT_SEND_RE = /\bsendEvent\s*\(\s*withName\s*:\s*"([^"]+)"/g;
1528
+ // JVM-side emitter calls: `emitter.emit("X", body)`. Matches both Java
1529
+ // and Kotlin syntax because the call form is identical. Restricted to
1530
+ // JVM source files in the consumer so we don't re-process JS emits
1531
+ // (which `eventEmitterEdges` already handles).
1532
+ const RN_JVM_EMIT_RE = /\.emit\s*\(\s*"([^"]+)"\s*,/g;
1533
+ // Custom `sendEvent(reactContext, "X", body)` wrapper — extremely common
1534
+ // (react-native-device-info and many libs wrap `DeviceEventManagerModule…emit`
1535
+ // behind a helper whose `.emit(eventName, …)` uses a VARIABLE, so RN_JVM_EMIT_RE
1536
+ // misses it; the literal lives in the wrapper CALL instead). Captures the first
1537
+ // string literal inside a `sendEvent(...)` call. `[^;{}]*?` keeps it on one
1538
+ // statement and stops at a block boundary, so the wrapper DEFINITION (whose `(`
1539
+ // is followed by `… ) {`) never matches. Multi-line tolerant. (java/kotlin/swift)
1540
+ const RN_NATIVE_SENDEVENT_RE = /\bsendEvent\s*\([^;{}]*?"([^"]+)"/g;
1541
+ async function rnEventEdges(ctx, onYield) {
1542
+ let scannedFiles = 0;
1543
+ // Native dispatchers (source = the native method whose body sends the
1544
+ // event) and JS handlers (target = the function/method registered as
1545
+ // the listener) keyed by event name.
1546
+ const nativeDispatchersByEvent = new Map();
1547
+ const jsHandlersByEvent = new Map();
1548
+ for (const file of ctx.getAllFiles()) {
1549
+ if ((++scannedFiles & 15) === 0)
1550
+ await onYield();
1551
+ const content = ctx.readFile(file);
1552
+ if (!content)
1553
+ continue;
1554
+ const nodesInFile = ctx.getNodesInFile(file);
1555
+ const lineOf = (0, synth_utils_1.makeLineAt)(content, 1);
1556
+ const addDispatcher = (event, line) => {
1557
+ const disp = (0, synth_utils_1.enclosingFn)(nodesInFile, line);
1558
+ if (!disp)
1559
+ return;
1560
+ const set = nativeDispatchersByEvent.get(event) ?? new Set();
1561
+ set.add(disp.id);
1562
+ nativeDispatchersByEvent.set(event, set);
1563
+ };
1564
+ // ObjC side: `sendEventWithName:@"X"` only fires inside `.m`/`.mm`
1565
+ // files (RCTEventEmitter subclasses).
1566
+ if (file.endsWith('.m') || file.endsWith('.mm')) {
1567
+ RN_OBJC_SEND_RE.lastIndex = 0;
1568
+ let m;
1569
+ while ((m = RN_OBJC_SEND_RE.exec(content))) {
1570
+ if (m[1])
1571
+ addDispatcher(m[1], lineOf(m.index));
1572
+ }
1573
+ }
1574
+ // Swift side: same RCTEventEmitter method, parens/named-args syntax.
1575
+ if (file.endsWith('.swift')) {
1576
+ RN_SWIFT_SEND_RE.lastIndex = 0;
1577
+ let m;
1578
+ while ((m = RN_SWIFT_SEND_RE.exec(content))) {
1579
+ if (m[1])
1580
+ addDispatcher(m[1], lineOf(m.index));
1581
+ }
1582
+ RN_NATIVE_SENDEVENT_RE.lastIndex = 0;
1583
+ while ((m = RN_NATIVE_SENDEVENT_RE.exec(content))) {
1584
+ if (m[1])
1585
+ addDispatcher(m[1], lineOf(m.index));
1586
+ }
1587
+ }
1588
+ // JVM side: `.emit("X", …)` in Java/Kotlin, plus the common
1589
+ // `sendEvent(ctx, "X", body)` wrapper. (We pattern-match anywhere in the
1590
+ // file; the JS in-language path uses a separate emitter object pattern and
1591
+ // is already handled by eventEmitterEdges.)
1592
+ if (file.endsWith('.java') || file.endsWith('.kt')) {
1593
+ let m;
1594
+ RN_JVM_EMIT_RE.lastIndex = 0;
1595
+ while ((m = RN_JVM_EMIT_RE.exec(content))) {
1596
+ if (m[1])
1597
+ addDispatcher(m[1], lineOf(m.index));
1598
+ }
1599
+ RN_NATIVE_SENDEVENT_RE.lastIndex = 0;
1600
+ while ((m = RN_NATIVE_SENDEVENT_RE.exec(content))) {
1601
+ if (m[1])
1602
+ addDispatcher(m[1], lineOf(m.index));
1603
+ }
1604
+ }
1605
+ // JS subscribers (.addListener("X", handler)). Restrict to JS-family
1606
+ // files so a native file's `addListener:` (the ObjC method) doesn't
1607
+ // get mistaken for a JS subscription — they're entirely different
1608
+ // things despite sharing a name.
1609
+ if (file.endsWith('.js') ||
1610
+ file.endsWith('.jsx') ||
1611
+ file.endsWith('.ts') ||
1612
+ file.endsWith('.tsx') ||
1613
+ file.endsWith('.mjs') ||
1614
+ file.endsWith('.cjs')) {
1615
+ // Match BOTH the named-handler form (`.addListener('x', fn)`) and
1616
+ // an unnamed-handler form (`.addListener('x', listener)` where
1617
+ // `listener` is a parameter — common in RN wrapper APIs like
1618
+ // RNFirebase's `messaging().onMessageReceived(listener)`). For the
1619
+ // unnamed case we attribute the subscription to the ENCLOSING JS
1620
+ // function (the abstraction layer), giving a reachability-correct
1621
+ // hop even when the actual user-side handler lives one call up.
1622
+ const ADDLISTENER_ANY = /\.(?:on|once|addListener)\(\s*['"]([^'"]+)['"]\s*,\s*([A-Za-z_][\w.]*)/g;
1623
+ // The inline form: `.addListener('x', (data) => {…})` / `function () {…}`.
1624
+ const ADDLISTENER_INLINE = /\.(?:on|once|addListener)\(\s*['"]([^'"]+)['"]\s*,\s*(?:async\s*)?(?:\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>|function\s*\()/g;
1625
+ ADDLISTENER_ANY.lastIndex = 0;
1626
+ let m;
1627
+ while ((m = ADDLISTENER_ANY.exec(content))) {
1628
+ const event = m[1];
1629
+ const arg = m[2];
1630
+ if (!event || !arg)
1631
+ continue;
1632
+ const bareName = arg.includes('.') ? arg.slice(arg.lastIndexOf('.') + 1) : arg;
1633
+ // Try a named-symbol match first (matches the in-language semantic).
1634
+ const namedHandler = ctx
1635
+ .getNodesByName(bareName)
1636
+ .find((n) => n.kind === 'function' || n.kind === 'method');
1637
+ let targetId = namedHandler?.id ?? null;
1638
+ if (!targetId) {
1639
+ // Fall back to the enclosing function — the subscribe-wrapper
1640
+ // pattern means the event fires THROUGH this function on its
1641
+ // way to user code. Reachability-correct attribution.
1642
+ const enclosing = (0, synth_utils_1.enclosingFn)(nodesInFile, lineOf(m.index));
1643
+ targetId = enclosing?.id ?? null;
1644
+ }
1645
+ if (!targetId) {
1646
+ // Broader fallback for JS object-literal API shape
1647
+ // (`const Foo = { watchX(...) { … addListener(...) … } }`):
1648
+ // method shorthand inside an object literal isn't extracted
1649
+ // as a method node, so enclosingFn returns null. Attribute to
1650
+ // the smallest enclosing `constant` / `variable` node — that's
1651
+ // the API surface a downstream caller would `import` and
1652
+ // invoke. Reachability-correct.
1653
+ const line = lineOf(m.index);
1654
+ let smallest = null;
1655
+ for (const n of nodesInFile) {
1656
+ if (n.kind !== 'constant' && n.kind !== 'variable')
1657
+ continue;
1658
+ const end = n.endLine ?? n.startLine;
1659
+ if (n.startLine <= line && end >= line) {
1660
+ if (!smallest || n.startLine >= smallest.startLine)
1661
+ smallest = n;
1662
+ }
1663
+ }
1664
+ targetId = smallest?.id ?? null;
1665
+ }
1666
+ if (!targetId)
1667
+ continue;
1668
+ const map = jsHandlersByEvent.get(event) ?? new Map();
1669
+ map.set(targetId, `${file}:${lineOf(m.index)}`);
1670
+ jsHandlersByEvent.set(event, map);
1671
+ }
1672
+ // Inline listeners — the shape a React component registers inside a
1673
+ // `useEffect`: `nativeEmitter.addListener('onCaptureComplete', (data) =>
1674
+ // { … router.push('/review') })`. There is no handler symbol to name,
1675
+ // so the subscription is attributed to the enclosing function exactly
1676
+ // as the unnamed-argument form above is: the native event lands in that
1677
+ // component, and its body is where a reader looks next. This channel
1678
+ // only — the in-language synthesizer keeps its named-handler policy.
1679
+ ADDLISTENER_INLINE.lastIndex = 0;
1680
+ while ((m = ADDLISTENER_INLINE.exec(content))) {
1681
+ const event = m[1];
1682
+ if (!event)
1683
+ continue;
1684
+ const enclosing = (0, synth_utils_1.enclosingFn)(nodesInFile, lineOf(m.index));
1685
+ if (!enclosing)
1686
+ continue;
1687
+ const map = jsHandlersByEvent.get(event) ?? new Map();
1688
+ if (!map.has(enclosing.id))
1689
+ map.set(enclosing.id, `${file}:${lineOf(m.index)}`);
1690
+ jsHandlersByEvent.set(event, map);
1691
+ }
1692
+ }
1693
+ }
1694
+ const edges = [];
1695
+ const seen = new Set();
1696
+ for (const [event, dispatchers] of nativeDispatchersByEvent) {
1697
+ const handlers = jsHandlersByEvent.get(event);
1698
+ if (!handlers)
1699
+ continue;
1700
+ // Same fan-out guard as the in-language channel: generic event names
1701
+ // (e.g. 'change', 'error', 'data') with many handlers/dispatchers
1702
+ // can't be matched precisely without receiver-type info.
1703
+ if (dispatchers.size > EVENT_FANOUT_CAP || handlers.size > EVENT_FANOUT_CAP)
1704
+ continue;
1705
+ for (const d of dispatchers) {
1706
+ for (const [h, registeredAt] of handlers) {
1707
+ if (d === h)
1708
+ continue;
1709
+ const key = `${d}>${h}`;
1710
+ if (seen.has(key))
1711
+ continue;
1712
+ seen.add(key);
1713
+ edges.push({
1714
+ source: d,
1715
+ target: h,
1716
+ kind: 'calls',
1717
+ provenance: 'heuristic',
1718
+ metadata: { synthesizedBy: 'rn-event-channel', event, registeredAt },
1719
+ });
1720
+ }
1721
+ }
1722
+ }
1723
+ return edges;
1724
+ }
1725
+ /**
1726
+ * Phase 6 — React Native Fabric/Codegen view component bridge.
1727
+ *
1728
+ * The Fabric framework extractor (`frameworks/fabric.ts`) emits
1729
+ * `component` nodes named after the JS-visible component (e.g.
1730
+ * `RNSScreenStack`) from each `codegenNativeComponent<Props>('Name')`
1731
+ * spec declaration. The native implementation lives in an ObjC++/.mm or
1732
+ * Kotlin/Java class whose name follows one of RN's conventions:
1733
+ *
1734
+ * - Exact: `RNSScreenStack`
1735
+ * - With suffix: `RNSScreenStackView`, `RNSScreenStackViewManager`,
1736
+ * `RNSScreenStackComponentView`, `RNSScreenStackManager`
1737
+ *
1738
+ * This synthesizer walks every Fabric component node and looks for a
1739
+ * native class matching one of those names; when found, emits a
1740
+ * `calls` edge `component → native class` (provenance `'heuristic'`,
1741
+ * `synthesizedBy:'fabric-native-impl'`) so trace from JSX usage of the
1742
+ * component continues into native.
1743
+ *
1744
+ * The convention-based suffix lookup is precise: there's no name
1745
+ * collision in RN view-manager codebases by design (Codegen output would
1746
+ * conflict otherwise).
1747
+ */
1748
+ const FABRIC_NATIVE_SUFFIXES = ['', 'View', 'ViewManager', 'ComponentView', 'Manager'];
1749
+ /**
1750
+ * Expo Modules cross-platform pairing. An Expo Module exposes the SAME
1751
+ * JS-visible method (`AsyncFunction("getBatteryLevelAsync")`) from BOTH an iOS
1752
+ * (Swift) and an Android (Kotlin) implementation. A JS callsite name-resolves to
1753
+ * only ONE of them, so the other platform's impl looked like nothing called it
1754
+ * (and editing it showed no blast radius). Link the iOS and Android impls of the
1755
+ * same `<module>.<method>` to each other (both directions), so a JS call that
1756
+ * reaches one platform reaches the other, and editing either surfaces the JS
1757
+ * caller. The Expo method nodes are id-prefixed `expo-module:` and qualified
1758
+ * `<file>::<module>.<method>` by the framework extractor.
1759
+ */
1760
+ async function expoCrossPlatformEdges(queries, onYield) {
1761
+ let scanned255 = 0;
1762
+ const edges = [];
1763
+ const seen = new Set();
1764
+ const byKey = new Map();
1765
+ for (const m of queries.iterateNodesByKind('method')) {
1766
+ if ((++scanned255 & 63) === 0)
1767
+ await onYield();
1768
+ if (!m.id.startsWith('expo-module:'))
1769
+ continue;
1770
+ const key = m.qualifiedName.split('::').pop(); // `<module>.<method>`
1771
+ if (!key)
1772
+ continue;
1773
+ const arr = byKey.get(key);
1774
+ if (arr)
1775
+ arr.push(m);
1776
+ else
1777
+ byKey.set(key, [m]);
1778
+ }
1779
+ for (const group of byKey.values()) {
1780
+ if (group.length < 2)
1781
+ continue;
1782
+ for (const a of group) {
1783
+ for (const b of group) {
1784
+ if (a.id === b.id || a.language === b.language)
1785
+ continue; // cross-platform only
1786
+ const key = `${a.id}>${b.id}`;
1787
+ if (seen.has(key))
1788
+ continue;
1789
+ seen.add(key);
1790
+ edges.push({
1791
+ source: a.id,
1792
+ target: b.id,
1793
+ kind: 'calls',
1794
+ line: a.startLine,
1795
+ provenance: 'heuristic',
1796
+ metadata: { synthesizedBy: 'expo-cross-platform', via: a.name },
1797
+ });
1798
+ }
1799
+ }
1800
+ }
1801
+ return edges;
1802
+ }
1803
+ /**
1804
+ * Classic React Native NativeModules cross-platform pairing. A native module
1805
+ * method (`@ReactMethod` on Android, `RCT_EXPORT_METHOD` on iOS) is implemented
1806
+ * on BOTH platforms, but a JS callsite name-resolves to only ONE — so the other
1807
+ * platform's impl looked like nothing called it. A native method that HAS a JS
1808
+ * caller is a confirmed bridge method; link it to the same-named native method
1809
+ * in another language (the other platform's impl) so a JS call reaching one
1810
+ * platform reaches the other, and editing either surfaces the JS caller.
1811
+ *
1812
+ * Names are normalized to the first selector keyword (`getFreeDiskStorage:` →
1813
+ * `getFreeDiskStorage`) — that's the JS-visible name, and how the iOS selector
1814
+ * lines up with the bare Android method name.
1815
+ */
1816
+ async function rnCrossPlatformEdges(queries, onYield) {
1817
+ let scanned255 = 0;
1818
+ const edges = [];
1819
+ const seen = new Set();
1820
+ const NATIVE = new Set(['java', 'kotlin', 'objc', 'cpp']);
1821
+ const JS = new Set(['typescript', 'tsx', 'javascript', 'jsx']);
1822
+ // RN module INFRASTRUCTURE methods exist on every native module (called by the
1823
+ // RN runtime, not user JS), so pairing them by name would cross-link unrelated
1824
+ // modules in a multi-module repo. Skip them — they aren't user-facing methods.
1825
+ const RN_INFRA = new Set([
1826
+ 'addListener', 'removeListeners', 'getConstants', 'constantsToExport', 'getName',
1827
+ 'invalidate', 'initialize', 'getDefaultEventTypes', 'supportedEvents',
1828
+ 'requiresMainQueueSetup', 'methodQueue',
1829
+ ]);
1830
+ const norm = (name) => {
1831
+ const i = name.indexOf(':');
1832
+ return i >= 0 ? name.slice(0, i) : name;
1833
+ };
1834
+ // Index native methods by their JS-visible (normalized) name. Only names with
1835
+ // impls in ≥2 native languages can pair, so the per-method JS-caller check
1836
+ // below only runs for genuine cross-platform candidates.
1837
+ const byName = new Map();
1838
+ for (const m of queries.iterateNodesByKind('method')) {
1839
+ if ((++scanned255 & 63) === 0)
1840
+ await onYield();
1841
+ if (!NATIVE.has(m.language))
1842
+ continue;
1843
+ const key = norm(m.name);
1844
+ const arr = byName.get(key);
1845
+ if (arr)
1846
+ arr.push(m);
1847
+ else
1848
+ byName.set(key, [m]);
1849
+ }
1850
+ for (const [groupName, group] of byName) {
1851
+ if (RN_INFRA.has(groupName))
1852
+ continue;
1853
+ const langs = new Set(group.map((m) => m.language));
1854
+ if (langs.size < 2)
1855
+ continue; // single-platform — nothing to pair
1856
+ for (const m of group) {
1857
+ // Is m a bridge method? (a JS-language `calls` edge points at it)
1858
+ const incoming = queries.getIncomingEdges(m.id, ['calls']);
1859
+ if (incoming.length === 0)
1860
+ continue;
1861
+ const sources = queries.getNodesByIds(incoming.map((e) => e.source));
1862
+ const isBridge = incoming.some((e) => {
1863
+ const s = sources.get(e.source);
1864
+ return !!s && JS.has(s.language);
1865
+ });
1866
+ if (!isBridge)
1867
+ continue;
1868
+ // Link to the other-platform impls (both directions).
1869
+ for (const sib of group) {
1870
+ if (sib.id === m.id || sib.language === m.language)
1871
+ continue;
1872
+ for (const [a, b] of [[m, sib], [sib, m]]) {
1873
+ const key = `${a.id}>${b.id}`;
1874
+ if (seen.has(key))
1875
+ continue;
1876
+ seen.add(key);
1877
+ edges.push({
1878
+ source: a.id,
1879
+ target: b.id,
1880
+ kind: 'calls',
1881
+ line: a.startLine,
1882
+ provenance: 'heuristic',
1883
+ metadata: { synthesizedBy: 'rn-cross-platform', via: norm(m.name) },
1884
+ });
1885
+ }
1886
+ }
1887
+ }
1888
+ }
1889
+ return edges;
1890
+ }
1891
+ async function fabricNativeImplEdges(ctx, onYield) {
1892
+ let scanned255 = 0;
1893
+ const edges = [];
1894
+ const seen = new Set();
1895
+ // The Fabric extractor IDs are prefixed `fabric-component:` so we can
1896
+ // filter to just those while streaming — never materializing the whole
1897
+ // `component` kind (#1212).
1898
+ const components = [];
1899
+ for (const n of (ctx.iterateNodesByKind?.('component') ?? ctx.getNodesByKind('component'))) {
1900
+ if ((++scanned255 & 63) === 0)
1901
+ await onYield();
1902
+ if (n.id.startsWith('fabric-component:'))
1903
+ components.push(n);
1904
+ }
1905
+ if (components.length === 0)
1906
+ return edges;
1907
+ // Pre-index native classes by name for O(1) lookup.
1908
+ const nativeClassesByName = new Map();
1909
+ for (const n of (ctx.iterateNodesByKind?.('class') ?? ctx.getNodesByKind('class'))) {
1910
+ if ((++scanned255 & 63) === 0)
1911
+ await onYield();
1912
+ if (n.language !== 'objc' && n.language !== 'kotlin' && n.language !== 'java' && n.language !== 'cpp')
1913
+ continue;
1914
+ const arr = nativeClassesByName.get(n.name);
1915
+ if (arr)
1916
+ arr.push(n);
1917
+ else
1918
+ nativeClassesByName.set(n.name, [n]);
1919
+ }
1920
+ for (const component of components) {
1921
+ for (const suffix of FABRIC_NATIVE_SUFFIXES) {
1922
+ const candidate = component.name + suffix;
1923
+ const matches = nativeClassesByName.get(candidate);
1924
+ if (!matches || matches.length === 0)
1925
+ continue;
1926
+ // Link the component node to every matching native class (iOS +
1927
+ // Android each have one).
1928
+ for (const native of matches) {
1929
+ const key = `${component.id}>${native.id}`;
1930
+ if (seen.has(key))
1931
+ continue;
1932
+ seen.add(key);
1933
+ edges.push({
1934
+ source: component.id,
1935
+ target: native.id,
1936
+ kind: 'calls',
1937
+ provenance: 'heuristic',
1938
+ metadata: {
1939
+ synthesizedBy: 'fabric-native-impl',
1940
+ viaSuffix: suffix || '(exact)',
1941
+ componentName: component.name,
1942
+ },
1943
+ });
1944
+ }
1945
+ }
1946
+ }
1947
+ return edges;
1948
+ }
1949
+ /**
1950
+ * MyBatis: link a Java mapper interface method to the XML statement that holds
1951
+ * its SQL. The XML extractor (`src/extraction/mybatis-extractor.ts`) qualifies
1952
+ * each `<select|insert|update|delete|sql id="X">` as `<namespace>::<id>` where
1953
+ * `<namespace>` is the Java FQN of the mapper interface. A Java method's
1954
+ * qualifiedName ends with `<ClassName>::<methodName>`, so we suffix-match the
1955
+ * last two segments of the XML qualified name to find a unique Java method by
1956
+ * `<ClassName>::<methodName>` (`ClassName` = last dotted segment of the XML
1957
+ * namespace). Cross-mapper `<include refid="other.X">` references go through
1958
+ * the normal qualified-name resolver — only the Java↔XML bridge is synthetic.
1959
+ *
1960
+ * Precision over recall: ambiguous mappers (multiple Java classes with the
1961
+ * same simple name) are dropped. We need-not bridge by package because Java
1962
+ * mapper interfaces are typically uniquely named within a project.
1963
+ */
1964
+ async function mybatisJavaXmlEdges(queries, onYield) {
1965
+ let scanned255 = 0;
1966
+ const edges = [];
1967
+ const seen = new Set();
1968
+ // Collect the XML side FIRST (mapper `<select id=…>` statements extracted as
1969
+ // xml-language method nodes): if the project has none — every Java+XML repo
1970
+ // that doesn't use MyBatis — return before paying the full java-method
1971
+ // stream below. Same rowid stream order as matching inline, so the edge
1972
+ // output is byte-identical when mappers do exist.
1973
+ const xmlMethods = [];
1974
+ for (const m of queries.iterateNodesByKind('method')) {
1975
+ if ((++scanned255 & 63) === 0)
1976
+ await onYield();
1977
+ if (m.language === 'xml')
1978
+ xmlMethods.push(m);
1979
+ }
1980
+ if (xmlMethods.length === 0)
1981
+ return edges;
1982
+ // Index Java methods by `<ClassName>::<methodName>` for O(1) lookup.
1983
+ const javaIndex = new Map();
1984
+ for (const m of queries.iterateNodesByKind('method')) {
1985
+ if ((++scanned255 & 63) === 0)
1986
+ await onYield();
1987
+ if (m.language !== 'java' && m.language !== 'kotlin')
1988
+ continue;
1989
+ const parts = m.qualifiedName.split('::');
1990
+ const last = parts[parts.length - 1];
1991
+ const cls = parts[parts.length - 2];
1992
+ if (!last || !cls)
1993
+ continue;
1994
+ const key = `${cls}::${last}`;
1995
+ const arr = javaIndex.get(key);
1996
+ if (arr)
1997
+ arr.push(m);
1998
+ else
1999
+ javaIndex.set(key, [m]);
2000
+ }
2001
+ for (const xml of xmlMethods) {
2002
+ if ((++scanned255 & 63) === 0)
2003
+ await onYield();
2004
+ // Qualified name: `<namespace>::<id>`. Extract the simple class name.
2005
+ const colonIdx = xml.qualifiedName.lastIndexOf('::');
2006
+ if (colonIdx < 0)
2007
+ continue;
2008
+ const namespace = xml.qualifiedName.slice(0, colonIdx);
2009
+ const id = xml.qualifiedName.slice(colonIdx + 2);
2010
+ if (!namespace || !id)
2011
+ continue;
2012
+ const dotIdx = namespace.lastIndexOf('.');
2013
+ const className = dotIdx >= 0 ? namespace.slice(dotIdx + 1) : namespace;
2014
+ const candidates = javaIndex.get(`${className}::${id}`);
2015
+ if (!candidates || candidates.length === 0)
2016
+ continue;
2017
+ // Drop ambiguous matches (multiple same-name classes); the user can
2018
+ // disambiguate by adding the package-suffix match in a future enhancement.
2019
+ if (candidates.length > 1)
2020
+ continue;
2021
+ const java = candidates[0];
2022
+ const key = `${java.id}>${xml.id}`;
2023
+ if (seen.has(key))
2024
+ continue;
2025
+ seen.add(key);
2026
+ edges.push({
2027
+ source: java.id,
2028
+ target: xml.id,
2029
+ kind: 'calls',
2030
+ line: java.startLine,
2031
+ provenance: 'heuristic',
2032
+ metadata: {
2033
+ synthesizedBy: 'mybatis-java-xml',
2034
+ via: `${className}.${id}`,
2035
+ registeredAt: `${xml.filePath}:${xml.startLine}`,
2036
+ },
2037
+ });
2038
+ }
2039
+ return edges;
2040
+ }
2041
+ /**
2042
+ * Gin middleware chain. Gin runs its entire handler chain through one dynamic
2043
+ * line in `(*Context).Next`:
2044
+ * for c.index < len(c.handlers) { c.handlers[c.index](c); c.index++ }
2045
+ * `c.handlers` is a `HandlersChain` (`[]HandlerFunc`) assembled at registration
2046
+ * time by `combineHandlers` from the funcs passed to `r.Use(...)` /
2047
+ * `r.GET("/path", h...)` / `r.Handle(...)`. Because the call is a computed index
2048
+ * into a runtime-built slice, tree-sitter resolves `c.handlers[c.index](c)` to
2049
+ * NOTHING — so `callees(Next)` is just the `len()` helper and the flow
2050
+ * `ServeHTTP → handleHTTPRequest → Next` dead-ends at the exact symbol the
2051
+ * "how do requests flow through the middleware chain" question is about. The
2052
+ * agent then re-queries Next and falls back to Read/grep (validated: the gin
2053
+ * WITH-arm rabbit-holed on precisely this dead-end).
2054
+ *
2055
+ * Bridge it: find the chain DISPATCHER (a Go method whose body invokes a
2056
+ * `handlers` slice by index) and link it → every HandlerFunc registered via a
2057
+ * gin registration call, so `callees(Next)` and `trace(ServeHTTP, <handler>)`
2058
+ * connect end-to-end. Named handlers only (`gin.Logger()` → `Logger`,
2059
+ * `authMiddleware`); inline closures are anonymous and skipped. Like
2060
+ * react-render / interface-impl this is a deliberate over-approximation —
2061
+ * reachability-correct (any registered handler CAN run for some route), capped,
2062
+ * and gated on the dispatcher existing so it never runs on non-gin Go repos.
2063
+ * Provenance `heuristic`, `synthesizedBy:'gin-middleware-chain'`; `registeredAt`
2064
+ * is the `.Use`/`.GET` site an agent would otherwise grep for.
2065
+ */
2066
+ const GIN_DISPATCH_RE = /\.handlers\s*\[[^\]]*\]\s*\(/; // c.handlers[c.index](c)
2067
+ const GIN_REG_RE = /\.(?:Use|GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|Any|Handle)\s*\(/g;
2068
+ /** Balanced `(...)` body starting at the '(' index; null if unbalanced. */
2069
+ function goBalancedArgs(s, openIdx) {
2070
+ let depth = 0;
2071
+ for (let i = openIdx; i < s.length; i++) {
2072
+ const c = s[i];
2073
+ if (c === '(')
2074
+ depth++;
2075
+ else if (c === ')') {
2076
+ depth--;
2077
+ if (depth === 0)
2078
+ return s.slice(openIdx + 1, i);
2079
+ }
2080
+ }
2081
+ return null;
2082
+ }
2083
+ /** Split a top-level comma list, respecting nested () [] {}. */
2084
+ function goSplitArgs(args) {
2085
+ const out = [];
2086
+ let depth = 0, cur = '';
2087
+ for (const c of args) {
2088
+ if (c === '(' || c === '[' || c === '{') {
2089
+ depth++;
2090
+ cur += c;
2091
+ }
2092
+ else if (c === ')' || c === ']' || c === '}') {
2093
+ depth--;
2094
+ cur += c;
2095
+ }
2096
+ else if (c === ',' && depth === 0) {
2097
+ out.push(cur);
2098
+ cur = '';
2099
+ }
2100
+ else
2101
+ cur += c;
2102
+ }
2103
+ if (cur.trim())
2104
+ out.push(cur);
2105
+ return out;
2106
+ }
2107
+ /** Tail ident of a handler arg: `gin.Logger()`→`Logger`, `mw`→`mw`; null for string paths / closures. */
2108
+ function goHandlerIdent(expr) {
2109
+ const cleaned = expr.trim().replace(/\(\s*\)$/, ''); // drop a trailing call ()
2110
+ if (!cleaned || cleaned.startsWith('"') || cleaned.startsWith('`') || cleaned.startsWith('func'))
2111
+ return null;
2112
+ const m = cleaned.match(/(?:\.|^)([A-Za-z_]\w*)$/);
2113
+ return m ? m[1] : null;
2114
+ }
2115
+ async function ginMiddlewareChainEdges(queries, ctx, onYield) {
2116
+ let scanned255 = 0;
2117
+ let scannedFiles = 0;
2118
+ // 1. Find the chain dispatcher(s): a Go method that invokes a `handlers` slice by index.
2119
+ const dispatchers = [];
2120
+ for (const n of queries.iterateNodesByKind('method')) {
2121
+ if ((++scanned255 & 63) === 0)
2122
+ await onYield();
2123
+ if (n.language !== 'go')
2124
+ continue;
2125
+ const content = ctx.readFile(n.filePath);
2126
+ const src = content && sliceLines(content, n.startLine, n.endLine);
2127
+ if (src && GIN_DISPATCH_RE.test(src))
2128
+ dispatchers.push(n);
2129
+ }
2130
+ if (dispatchers.length === 0)
2131
+ return []; // not a gin repo — bail
2132
+ // 2. Collect handler identifiers registered via gin registration calls
2133
+ // (.Use / .GET / … / .Handle). String args (paths/methods) and inline
2134
+ // closures are dropped by goHandlerIdent; the rest are HandlerFuncs.
2135
+ const registered = new Map(); // name → registeredAt (file:line)
2136
+ for (const file of ctx.getAllFiles()) {
2137
+ if ((++scannedFiles & 15) === 0)
2138
+ await onYield();
2139
+ if (!file.endsWith('.go'))
2140
+ continue;
2141
+ const content = ctx.readFile(file);
2142
+ if (!content || (!content.includes('.Use(') && !/\.(?:GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|Any|Handle)\(/.test(content)))
2143
+ continue;
2144
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'go');
2145
+ GIN_REG_RE.lastIndex = 0;
2146
+ let m;
2147
+ while ((m = GIN_REG_RE.exec(safe))) {
2148
+ const parenIdx = m.index + m[0].length - 1;
2149
+ const argStr = goBalancedArgs(safe, parenIdx);
2150
+ if (!argStr)
2151
+ continue;
2152
+ const line = safe.slice(0, m.index).split('\n').length;
2153
+ for (const arg of goSplitArgs(argStr)) {
2154
+ const name = goHandlerIdent(arg);
2155
+ if (name && !registered.has(name))
2156
+ registered.set(name, `${file}:${line}`);
2157
+ }
2158
+ }
2159
+ }
2160
+ if (registered.size === 0)
2161
+ return [];
2162
+ // 3. Link each dispatcher → each registered handler node (dedup, capped).
2163
+ const edges = [];
2164
+ const seen = new Set();
2165
+ for (const disp of dispatchers) {
2166
+ let added = 0;
2167
+ for (const [name, registeredAt] of registered) {
2168
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
2169
+ break;
2170
+ const handler = ctx.getNodesByName(name).find((n) => (n.kind === 'function' || n.kind === 'method') && n.language === 'go');
2171
+ if (!handler || handler.id === disp.id)
2172
+ continue;
2173
+ const key = `${disp.id}>${handler.id}`;
2174
+ if (seen.has(key))
2175
+ continue;
2176
+ seen.add(key);
2177
+ edges.push({
2178
+ source: disp.id, target: handler.id, kind: 'calls', line: disp.startLine,
2179
+ provenance: 'heuristic',
2180
+ metadata: { synthesizedBy: 'gin-middleware-chain', via: name, registeredAt },
2181
+ });
2182
+ added++;
2183
+ }
2184
+ }
2185
+ return edges;
2186
+ }
2187
+ /**
2188
+ * Delphi form code-behind: a form unit `UFRMAbout.pas` owns its visual form
2189
+ * definition `UFRMAbout.dfm` (VCL) / `.fmx` (FireMonkey) — paired by basename in
2190
+ * the same directory, wired by the `{$R *.dfm}` directive rather than a `uses`
2191
+ * clause. Link the unit → its form so a `.dfm`/`.fmx` used only as a form
2192
+ * definition isn't orphaned, and editing the form surfaces its code-behind unit.
2193
+ */
2194
+ async function pascalFormEdges(ctx, onYield) {
2195
+ let scannedFiles = 0;
2196
+ const edges = [];
2197
+ const allFiles = new Set(ctx.getAllFiles());
2198
+ for (const file of allFiles) {
2199
+ if ((++scannedFiles & 255) === 0)
2200
+ await onYield();
2201
+ if (!/\.(dfm|fmx)$/i.test(file))
2202
+ continue;
2203
+ const pasFile = file.replace(/\.(dfm|fmx)$/i, '.pas');
2204
+ if (!allFiles.has(pasFile))
2205
+ continue;
2206
+ const formNode = ctx.getNodesInFile(file).find((n) => n.kind === 'file');
2207
+ const unitNode = ctx.getNodesInFile(pasFile).find((n) => n.kind === 'file');
2208
+ if (!formNode || !unitNode)
2209
+ continue;
2210
+ edges.push({
2211
+ source: unitNode.id,
2212
+ target: formNode.id,
2213
+ kind: 'references',
2214
+ line: unitNode.startLine,
2215
+ provenance: 'heuristic',
2216
+ metadata: { synthesizedBy: 'pascal-form', registeredAt: pasFile },
2217
+ });
2218
+ }
2219
+ return edges;
2220
+ }
2221
+ /**
2222
+ * SvelteKit file-convention data flow. A route directory's `+page.svelte` (a
2223
+ * `component` node) receives its `data` from the sibling `+page.server.{ts,js}`
2224
+ * / `+page.{ts,js}` `load` function and posts forms to its `actions` — wired by
2225
+ * the framework BY FILE PATH, with no static import between them. So editing a
2226
+ * `load` shows no impact on the page it feeds, and the page looks like it has no
2227
+ * server-side dependency. Link the page component to its sibling loader's
2228
+ * `load` / `actions` (same for `+layout`). The pairing is path-deterministic
2229
+ * (same directory, matching `+page`/`+layout` prefix), so it's precise — but
2230
+ * it's a framework-convention edge, so provenance stays `heuristic`.
2231
+ *
2232
+ * Direction: page → load, so `getImpactRadius(load)` surfaces the page (editing
2233
+ * a loader's data shows the page it feeds) and the page's dependencies include
2234
+ * its loader.
2235
+ */
2236
+ async function svelteKitLoadEdges(ctx, onYield) {
2237
+ let scannedFiles = 0;
2238
+ const edges = [];
2239
+ const allFiles = new Set(ctx.getAllFiles());
2240
+ const HOOKS = new Set(['load', 'actions']);
2241
+ const HOOK_KINDS = new Set(['function', 'method', 'constant', 'variable']);
2242
+ for (const file of allFiles) {
2243
+ if ((++scannedFiles & 255) === 0)
2244
+ await onYield();
2245
+ const m = file.match(/(.*\/)(\+(?:page|layout))\.svelte$/);
2246
+ if (!m)
2247
+ continue;
2248
+ const dir = m[1];
2249
+ const prefix = m[2];
2250
+ const page = ctx.getNodesInFile(file).find((n) => n.kind === 'component');
2251
+ if (!page)
2252
+ continue;
2253
+ for (const ext of ['.server.ts', '.server.js', '.ts', '.js']) {
2254
+ const loaderFile = `${dir}${prefix}${ext}`;
2255
+ if (!allFiles.has(loaderFile))
2256
+ continue;
2257
+ for (const hook of ctx.getNodesInFile(loaderFile)) {
2258
+ // `load` and `actions` by name, and every function the loader file
2259
+ // declares — a form action is an arrow inside `actions`, and it is a
2260
+ // node of its own (`default`, `logout`), where the redirect that ends
2261
+ // the submission is actually written.
2262
+ const named = HOOK_KINDS.has(hook.kind) && HOOKS.has(hook.name);
2263
+ if (!named && hook.kind !== 'function' && hook.kind !== 'method')
2264
+ continue;
2265
+ edges.push({
2266
+ source: page.id,
2267
+ target: hook.id,
2268
+ kind: 'calls',
2269
+ line: page.startLine,
2270
+ provenance: 'heuristic',
2271
+ metadata: {
2272
+ synthesizedBy: 'sveltekit-load',
2273
+ via: hook.name,
2274
+ registeredAt: `${loaderFile}:${hook.startLine ?? 0}`,
2275
+ },
2276
+ });
2277
+ }
2278
+ }
2279
+ }
2280
+ return edges;
2281
+ }
2282
+ /**
2283
+ * Redux-thunk dispatch chain. `export const X = createAsyncThunk(prefix, async (a, api) => {...})`
2284
+ * (or a wrapper like trezor's `createThunk(...)`) passes the async body as an ARGUMENT, so
2285
+ * tree-sitter never extracts it as a function node: `X` is a `constant` whose body's calls are
2286
+ * ORPHANED. The `dispatch(nextThunk(...))` calls that drive a thunk chain forward therefore produce
2287
+ * no edges, so `callees(X)` is empty and a flow `dispatch(X(...)) → X → nextThunk` dead-ends at the
2288
+ * constant (validated on trezor-suite: the signXxxThunk constants had ZERO outgoing edges). Bridge
2289
+ * it: body-scan each thunk constant for `dispatch(Y(...))` and link `X → Y`, so the dispatch chain
2290
+ * connects. High-precision — the `dispatch(` keyword plus `Y` must resolve to a function/constant/
2291
+ * method node; capped; gated on thunk constants existing so it never runs on non-RTK repos.
2292
+ * Cross-file by design (a suite thunk dispatches a wallet-core thunk). Provenance `heuristic`,
2293
+ * `synthesizedBy:'redux-thunk'`; `registeredAt` is the dispatch site.
2294
+ */
2295
+ const THUNK_DECL_RE = /create(?:Async)?Thunk/;
2296
+ const THUNK_DISPATCH_RE = /\bdispatch\s*\(\s*([A-Za-z_]\w*)\s*[(),]/g;
2297
+ const THUNK_FANOUT_CAP = 24;
2298
+ async function reduxThunkEdges(queries, ctx, onYield) {
2299
+ let scanned255 = 0;
2300
+ const edges = [];
2301
+ const seen = new Set();
2302
+ for (const node of queries.iterateNodesByKind('constant')) {
2303
+ if ((++scanned255 & 63) === 0)
2304
+ await onYield();
2305
+ // Cheap gate: the initializer (captured in `signature`) must be a create(Async)Thunk call —
2306
+ // avoids reading every constant's body on a large repo.
2307
+ if (!node.signature || !THUNK_DECL_RE.test(node.signature))
2308
+ continue;
2309
+ const content = ctx.readFile(node.filePath);
2310
+ const src = content && sliceLines(content, node.startLine, node.endLine);
2311
+ if (!src)
2312
+ continue;
2313
+ // Thunks are TS/JS-family (same // and /* */ comment syntax); map to a CommentLang.
2314
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(src, node.language === 'javascript' || node.language === 'jsx' ? 'javascript' : 'typescript');
2315
+ THUNK_DISPATCH_RE.lastIndex = 0;
2316
+ let m;
2317
+ let added = 0;
2318
+ while ((m = THUNK_DISPATCH_RE.exec(safe)) && added < THUNK_FANOUT_CAP) {
2319
+ const name = m[1];
2320
+ if (name === node.name)
2321
+ continue; // self-dispatch (recursive thunk) — skip
2322
+ // Resolve the dispatched name, PREFERRING the thunk/action-creator over a same-named
2323
+ // service function. `dispatch(X(...))` dispatches a thunk or an action-creator (both
2324
+ // `constant`s) — never an unrelated helper that merely shares the name. On octo-call,
2325
+ // `leaveCall` is BOTH a `createAsyncThunk` const AND a service function, and the bare
2326
+ // `.find()` picked the function (wrong). Order: thunk const > other const > same-file
2327
+ // callable > first match. A single candidate (no collision) is unaffected.
2328
+ const cands = ctx
2329
+ .getNodesByName(name)
2330
+ .filter((n) => n.kind === 'constant' || n.kind === 'function' || n.kind === 'method');
2331
+ const target = cands.find((n) => !!n.signature && THUNK_DECL_RE.test(n.signature)) ??
2332
+ cands.find((n) => n.kind === 'constant') ??
2333
+ cands.find((n) => n.filePath === node.filePath) ??
2334
+ cands[0];
2335
+ if (!target || target.id === node.id)
2336
+ continue;
2337
+ const key = `${node.id}>${target.id}`;
2338
+ if (seen.has(key))
2339
+ continue;
2340
+ seen.add(key);
2341
+ const line = node.startLine + safe.slice(0, m.index).split('\n').length - 1;
2342
+ edges.push({
2343
+ source: node.id,
2344
+ target: target.id,
2345
+ kind: 'calls',
2346
+ line,
2347
+ provenance: 'heuristic',
2348
+ metadata: { synthesizedBy: 'redux-thunk', via: name, registeredAt: `${node.filePath}:${line}` },
2349
+ });
2350
+ added++;
2351
+ }
2352
+ }
2353
+ return edges;
2354
+ }
2355
+ // ── Object-literal registry dispatch ─────────────────────────────────────────
2356
+ // A command/handler registry maps string keys → handler class/function symbols in an
2357
+ // object literal, then dispatches by a RUNTIME key static parsing can't follow:
2358
+ // this.commands = { [Cmd.ADD]: AddObjectCommand, ... } // registration
2359
+ // new this.commands[command](args).execute() // dynamic dispatch
2360
+ // Bridge it like gin-middleware-chain: link each dispatching function → each registered
2361
+ // handler's callable entry (a class's execute/run/handle/… method — preferring the method
2362
+ // chained at the dispatch site — or the function value). Scoped to a registry + dispatch in
2363
+ // the SAME file (the cross-file barrel-namespace variant, e.g. trezor's getMethod, is
2364
+ // deferred). Gated on a real object literal with ≥2 entries that RESOLVE to callables (a
2365
+ // `{ width: 5 }` literal resolves to nothing → no edges); fan-out capped.
2366
+ const REGISTRY_ASSIGN_RE = /(?:(?:const|let|var)\s+([A-Za-z_$][\w$]*)|((?:this\.)?[A-Za-z_$][\w$]*))\s*=\s*\{/g;
2367
+ const REGISTRY_DISPATCH_RE = /(?:\bnew\s+)?((?:this\.)?[A-Za-z_$][\w$]*)\s*\[\s*([A-Za-z_$][\w$.]*)\s*\]\s*(?:\(|\.[A-Za-z_$])/g;
2368
+ const REGISTRY_MIN_ENTRIES = 2;
2369
+ const REGISTRY_FANOUT_CAP = 40;
2370
+ const REGISTRY_CLASS_ENTRY = new Set(['execute', 'run', 'handle', 'perform', 'process', 'call', 'apply', 'dispatch']);
2371
+ const REGISTRY_JS_EXT = /\.(?:ts|tsx|js|jsx|mjs|cjs)$/;
2372
+ /** From the index of an opening `{`, return the brace-balanced body up to its matching `}`. */
2373
+ function braceBody(src, openIdx) {
2374
+ let depth = 0;
2375
+ for (let i = openIdx; i < src.length; i++) {
2376
+ if (src[i] === '{')
2377
+ depth++;
2378
+ else if (src[i] === '}' && --depth === 0)
2379
+ return src.slice(openIdx + 1, i);
2380
+ }
2381
+ return null;
2382
+ }
2383
+ /** Top-level `key: Identifier` entries of an object-literal body. DEPTH-AWARE: only depth-0
2384
+ * segments are considered, so method-shorthand bodies (`number(a,b){…}`), arrow values
2385
+ * (`x: () => …`), and nested objects (`x: { … }`) don't leak their inner `k: v` pairs as
2386
+ * bogus handlers. The per-segment anchor (`^… key: Ident …$`) keeps only pure identifier
2387
+ * values — a data value (`x: 5`), call, or arrow fails to match. */
2388
+ function registryEntryNames(body) {
2389
+ const segs = [];
2390
+ let depth = 0;
2391
+ let start = 0;
2392
+ for (let i = 0; i < body.length; i++) {
2393
+ const c = body[i];
2394
+ if (c === '{' || c === '(' || c === '[')
2395
+ depth++;
2396
+ else if (c === '}' || c === ')' || c === ']')
2397
+ depth--;
2398
+ else if (c === ',' && depth === 0) {
2399
+ segs.push(body.slice(start, i));
2400
+ start = i + 1;
2401
+ }
2402
+ }
2403
+ segs.push(body.slice(start));
2404
+ const names = [];
2405
+ for (const seg of segs) {
2406
+ const m = /^\s*(?:\[[^\]]+\]|['"]?[\w$]+['"]?)\s*:\s*([A-Za-z_$][\w$]*)\s*$/.exec(seg);
2407
+ if (m && m[1].length >= 3 && !names.includes(m[1]))
2408
+ names.push(m[1]);
2409
+ }
2410
+ return names;
2411
+ }
2412
+ /** Resolve a registered handler name to its callable entry: a function value, or a class's
2413
+ * `execute`-like method (preferring the method chained at the dispatch site), else the class. */
2414
+ function resolveRegistryHandler(ctx, name, chained) {
2415
+ const cands = ctx.getNodesByName(name);
2416
+ const fn = cands.find((n) => n.kind === 'function');
2417
+ if (fn)
2418
+ return fn;
2419
+ const cls = cands.find((n) => n.kind === 'class' || n.kind === 'struct');
2420
+ if (cls) {
2421
+ const methods = ctx
2422
+ .getNodesInFile(cls.filePath)
2423
+ .filter((n) => n.kind === 'method' && n.startLine >= cls.startLine && n.startLine <= (cls.endLine ?? cls.startLine));
2424
+ const want = chained && REGISTRY_CLASS_ENTRY.has(chained) ? chained : null;
2425
+ const entry = (want && methods.find((m) => m.name === want)) ||
2426
+ methods.find((m) => REGISTRY_CLASS_ENTRY.has(m.name)) ||
2427
+ methods.find((m) => m.name === 'constructor');
2428
+ return entry ?? cls;
2429
+ }
2430
+ // Require a CALLABLE target — a registry dispatched as `reg[k](…)` invokes a function/
2431
+ // method, never a data `constant` (dropping it removes false positives like a `{ x: URL }`
2432
+ // entry resolving to the global URL constant).
2433
+ return cands.find((n) => n.kind === 'method') ?? null;
2434
+ }
2435
+ async function objectRegistryEdges(ctx, onYield) {
2436
+ let scannedFiles = 0;
2437
+ const edges = [];
2438
+ const seen = new Set();
2439
+ let scanned = 0;
2440
+ for (const file of ctx.getAllFiles()) {
2441
+ if ((++scannedFiles & 15) === 0)
2442
+ await onYield();
2443
+ if ((++scanned & 255) === 0)
2444
+ await onYield(); // #1091: yield mid-scan on huge graphs
2445
+ if (!REGISTRY_JS_EXT.test(file))
2446
+ continue;
2447
+ const content = ctx.readFile(file);
2448
+ // Cheap pre-filter: a computed member access BY NAME (`ident[ident`) — the dispatch shape.
2449
+ if (!content || !/[\w$]\s*\[\s*[A-Za-z_$]/.test(content))
2450
+ continue;
2451
+ // Skip minified/generated bundles (draco, three.min, base64…): their pervasive `h[x](...)`
2452
+ // calls + single-letter `{a:b}` literals are a false-positive minefield. Average line
2453
+ // length is the reliable tell — real source ~30–80, minified in the hundreds/thousands.
2454
+ const newlines = (content.match(/\n/g)?.length ?? 0) + 1;
2455
+ if (content.length / newlines > 200)
2456
+ continue;
2457
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, /\.(?:jsx?|mjs|cjs)$/.test(file) ? 'javascript' : 'typescript');
2458
+ // 1. Dispatch sites: `(new )?<ref>[<ident-key>]` followed by a call or a chained method.
2459
+ // A quoted-string key (`['save']`) does NOT match — that's a static access, not dispatch.
2460
+ REGISTRY_DISPATCH_RE.lastIndex = 0;
2461
+ const dispatches = [];
2462
+ let dm;
2463
+ while ((dm = REGISTRY_DISPATCH_RE.exec(safe))) {
2464
+ const win = safe.slice(dm.index, dm.index + 160);
2465
+ const cm = /\]\s*\([^)]*\)\s*\.\s*([A-Za-z_$][\w$]*)/.exec(win) || /\]\s*\.\s*([A-Za-z_$][\w$]*)/.exec(win);
2466
+ dispatches.push({ ref: dm[1], line: safe.slice(0, dm.index).split('\n').length, chained: cm ? cm[1] : null });
2467
+ }
2468
+ if (!dispatches.length)
2469
+ continue;
2470
+ // Normalize a leading `this.` so a class FIELD-INITIALIZER registry (`commands = {…}`)
2471
+ // matches a `this.commands[k]` dispatch, not just the constructor form `this.commands = {…}`.
2472
+ const norm = (r) => r.replace(/^this\./, '');
2473
+ const refs = new Set(dispatches.map((d) => norm(d.ref)));
2474
+ // 2. Registries: an object literal assigned to a dispatched ref, ≥2 entries resolving to callables.
2475
+ REGISTRY_ASSIGN_RE.lastIndex = 0;
2476
+ const registries = new Map();
2477
+ let am;
2478
+ while ((am = REGISTRY_ASSIGN_RE.exec(safe))) {
2479
+ const lhs = norm(am[1] ?? am[2]);
2480
+ if (!refs.has(lhs) || registries.has(lhs))
2481
+ continue;
2482
+ const body = braceBody(safe, am.index + am[0].length - 1);
2483
+ if (!body)
2484
+ continue;
2485
+ const names = registryEntryNames(body); // depth-0 `key: Identifier` entries only
2486
+ if (names.length >= REGISTRY_MIN_ENTRIES) {
2487
+ registries.set(lhs, { names, line: safe.slice(0, am.index).split('\n').length });
2488
+ }
2489
+ }
2490
+ if (!registries.size)
2491
+ continue;
2492
+ // 3. Link each dispatcher → each registered handler's callable entry.
2493
+ const nodesInFile = ctx.getNodesInFile(file);
2494
+ for (const d of dispatches) {
2495
+ const reg = registries.get(norm(d.ref));
2496
+ if (!reg)
2497
+ continue;
2498
+ const disp = (0, synth_utils_1.enclosingFn)(nodesInFile, d.line);
2499
+ if (!disp)
2500
+ continue;
2501
+ let added = 0;
2502
+ for (const name of reg.names) {
2503
+ if (added >= REGISTRY_FANOUT_CAP)
2504
+ break;
2505
+ const target = resolveRegistryHandler(ctx, name, d.chained);
2506
+ if (!target || target.id === disp.id)
2507
+ continue;
2508
+ const key = `${disp.id}>${target.id}`;
2509
+ if (seen.has(key))
2510
+ continue;
2511
+ seen.add(key);
2512
+ edges.push({
2513
+ source: disp.id,
2514
+ target: target.id,
2515
+ kind: 'calls',
2516
+ line: d.line,
2517
+ provenance: 'heuristic',
2518
+ metadata: { synthesizedBy: 'object-registry', via: name, registeredAt: `${file}:${reg.line}` },
2519
+ });
2520
+ added++;
2521
+ }
2522
+ }
2523
+ }
2524
+ return edges;
2525
+ }
2526
+ // ── RTK Query generated-hook → endpoint ──────────────────────────────────────
2527
+ // RTK Query generates one `useGetXQuery`/`useUpdateYMutation` hook per endpoint
2528
+ // (`createApi({ endpoints: b => ({ getX: b.query(...) }) })`). Components call the
2529
+ // hook; the fetch logic lives in the endpoint's queryFn. The hook↔endpoint link is
2530
+ // pure NAMING CONVENTION (no static edge): strip `use` + the optional `Lazy`
2531
+ // variant + the `Query|Mutation` suffix, lowercase the head → the endpoint key.
2532
+ // Both are extracted as function nodes (the hook from its `export const {…}=api`
2533
+ // binding, carrying a sentinel signature; the endpoint from the createApi object),
2534
+ // so bridging hook→endpoint connects `component → useGetXQuery → getX → queryFn`.
2535
+ // Gated on the extraction sentinel so it only ever fires on genuinely-generated
2536
+ // hooks (never a hand-written `useFooQuery`), and on a SAME-FILE endpoint (RTK
2537
+ // colocates the hooks and their api in one module) — 0 on any non-RTK repo.
2538
+ const RTK_HOOK_DERIVE_RE = /^use([A-Z][A-Za-z0-9]*?)(?:Query|Mutation)$/;
2539
+ // MUST match the signature set in tree-sitter.ts `extractRtkHookBindings`.
2540
+ const RTK_GENERATED_HOOK_SIGNATURE = '= RTK Query generated hook';
2541
+ /** Derive the endpoint key from a generated-hook name (`useLazyGetRecordsQuery`
2542
+ * → `getRecords`), or null if it doesn't fit the convention. */
2543
+ function rtkEndpointNameFromHook(hook) {
2544
+ const m = RTK_HOOK_DERIVE_RE.exec(hook);
2545
+ if (!m)
2546
+ return null;
2547
+ let mid = m[1];
2548
+ if (mid.startsWith('Lazy'))
2549
+ mid = mid.slice(4); // useLazyGetX → getX (same endpoint)
2550
+ if (!mid)
2551
+ return null;
2552
+ return mid.charAt(0).toLowerCase() + mid.slice(1);
2553
+ }
2554
+ async function rtkQueryEdges(queries, ctx, onYield) {
2555
+ let scanned255 = 0;
2556
+ const edges = [];
2557
+ const seen = new Set();
2558
+ for (const hook of queries.iterateNodesByKind('function')) {
2559
+ if ((++scanned255 & 63) === 0)
2560
+ await onYield();
2561
+ // Only our extracted generated-hook bindings (sentinel) — not a real hook fn.
2562
+ if (hook.signature !== RTK_GENERATED_HOOK_SIGNATURE)
2563
+ continue;
2564
+ const endpointName = rtkEndpointNameFromHook(hook.name);
2565
+ if (!endpointName)
2566
+ continue;
2567
+ // The endpoint is a same-file function by the derived name (RTK colocates the
2568
+ // api definition and its generated-hook exports in one module).
2569
+ const target = ctx
2570
+ .getNodesByName(endpointName)
2571
+ .find((n) => n.kind === 'function' && n.filePath === hook.filePath);
2572
+ if (!target || target.id === hook.id)
2573
+ continue;
2574
+ const key = `${hook.id}>${target.id}`;
2575
+ if (seen.has(key))
2576
+ continue;
2577
+ seen.add(key);
2578
+ edges.push({
2579
+ source: hook.id,
2580
+ target: target.id,
2581
+ kind: 'calls',
2582
+ line: hook.startLine,
2583
+ provenance: 'heuristic',
2584
+ metadata: { synthesizedBy: 'rtk-query', via: endpointName, registeredAt: `${hook.filePath}:${hook.startLine}` },
2585
+ });
2586
+ }
2587
+ return edges;
2588
+ }
2589
+ // ── Pinia useStore().action() dispatch bridge ────────────────────────────────
2590
+ // A Pinia store factory `export const useXStore = defineStore(...)` exposes its
2591
+ // actions as methods on the store instance; a consumer does `const s = useXStore()`
2592
+ // then `s.action()`. The call is a method-on-instance with no static edge to the
2593
+ // action (which lives in the store's module). Bridge it: map each factory → its
2594
+ // file, bind `const <var> = useXStore()` per consumer file, and link the enclosing
2595
+ // function → the `<var>.method()` action node IN THE STORE'S FILE. The same-store-
2596
+ // file gate keeps it precise (a Pinia built-in like `$patch` or an unrelated
2597
+ // same-named method resolves to nothing). Covers both the options and setup store
2598
+ // forms uniformly (the action is a function node in the store file either way).
2599
+ const PINIA_CONSUMER_EXT = /\.(?:ts|tsx|js|jsx|mjs|cjs|vue)$/;
2600
+ const PINIA_FACTORY_RE = /\b(?:export\s+)?const\s+(\w+)\s*=\s*defineStore\s*\(/g;
2601
+ const PINIA_BIND_RE = /\bconst\s+(\w+)\s*=\s*(?:await\s+)?(\w+)\s*\(/g;
2602
+ const PINIA_CALL_RE = /(\w+)\s*\.\s*(\w+)\s*\(/g;
2603
+ const PINIA_FANOUT_CAP = 80;
2604
+ async function piniaStoreEdges(ctx, onYield) {
2605
+ let scannedFiles = 0;
2606
+ // 1. Map each `const useXStore = defineStore(...)` factory → its store file.
2607
+ const factoryFile = new Map();
2608
+ for (const file of ctx.getAllFiles()) {
2609
+ if ((++scannedFiles & 15) === 0)
2610
+ await onYield();
2611
+ if (!PINIA_CONSUMER_EXT.test(file))
2612
+ continue;
2613
+ const content = ctx.readFile(file);
2614
+ if (!content || !content.includes('defineStore'))
2615
+ continue;
2616
+ PINIA_FACTORY_RE.lastIndex = 0;
2617
+ let m;
2618
+ while ((m = PINIA_FACTORY_RE.exec(content)))
2619
+ factoryFile.set(m[1], file);
2620
+ }
2621
+ if (!factoryFile.size)
2622
+ return [];
2623
+ const edges = [];
2624
+ const seen = new Set();
2625
+ for (const file of ctx.getAllFiles()) {
2626
+ if ((++scannedFiles & 15) === 0)
2627
+ await onYield();
2628
+ if (!PINIA_CONSUMER_EXT.test(file))
2629
+ continue;
2630
+ const content = ctx.readFile(file);
2631
+ if (!content || !content.includes('Store'))
2632
+ continue;
2633
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, /\.(?:jsx?|mjs|cjs)$/.test(file) ? 'javascript' : 'typescript');
2634
+ // 2. Bind store vars in this file: `const <var> = <known-factory>(...)`.
2635
+ const varStore = new Map();
2636
+ PINIA_BIND_RE.lastIndex = 0;
2637
+ let bm;
2638
+ while ((bm = PINIA_BIND_RE.exec(safe))) {
2639
+ const sf = factoryFile.get(bm[2]);
2640
+ if (sf)
2641
+ varStore.set(bm[1], sf);
2642
+ }
2643
+ if (!varStore.size)
2644
+ continue;
2645
+ // 3. Link `<var>.<method>(` → the action function node in the store's file.
2646
+ const nodesInFile = ctx.getNodesInFile(file);
2647
+ const fallbackDispatcher = nodesInFile.find((n) => n.kind === 'component'); // .vue top-level setup
2648
+ PINIA_CALL_RE.lastIndex = 0;
2649
+ let cm;
2650
+ let added = 0;
2651
+ while ((cm = PINIA_CALL_RE.exec(safe)) && added < PINIA_FANOUT_CAP) {
2652
+ const storeFile = varStore.get(cm[1]);
2653
+ if (!storeFile)
2654
+ continue;
2655
+ const method = cm[2];
2656
+ const line = safe.slice(0, cm.index).split('\n').length;
2657
+ const disp = (0, synth_utils_1.enclosingFn)(nodesInFile, line) ?? fallbackDispatcher;
2658
+ if (!disp)
2659
+ continue;
2660
+ const target = ctx
2661
+ .getNodesByName(method)
2662
+ .find((n) => n.kind === 'function' && n.filePath === storeFile);
2663
+ if (!target || target.id === disp.id)
2664
+ continue;
2665
+ const key = `${disp.id}>${target.id}`;
2666
+ if (seen.has(key))
2667
+ continue;
2668
+ seen.add(key);
2669
+ edges.push({
2670
+ source: disp.id,
2671
+ target: target.id,
2672
+ kind: 'calls',
2673
+ line,
2674
+ provenance: 'heuristic',
2675
+ metadata: { synthesizedBy: 'pinia-store', via: method, registeredAt: `${file}:${line}` },
2676
+ });
2677
+ added++;
2678
+ }
2679
+ }
2680
+ return edges;
2681
+ }
2682
+ // ── Vuex string-keyed dispatch / commit bridge ───────────────────────────────
2683
+ // Vuex dispatches actions/mutations by a runtime STRING key: `dispatch('user/login')`
2684
+ // / `commit('SET_TOKEN')` / `this.$store.dispatch('app/toggleDevice')`. The action
2685
+ // & mutation definitions are object-literal methods in store module files (now
2686
+ // extracted as function nodes). Bridge the string key to its node: the LAST `/`
2687
+ // segment is the action/mutation name; the preceding segment is the namespace
2688
+ // (≈ the store module's file). Resolve the name to a function node IN A STORE FILE
2689
+ // (the store-file gate excludes a same-named `api/` helper — `getInfo`/`login`
2690
+ // commonly collide), disambiguated by the namespace appearing in the path (or, for
2691
+ // a root key, the same file — Vuex's local-module `commit('M')` inside an action).
2692
+ const VUEX_DISPATCH_RE = /\b(?:dispatch|commit)\s*\(\s*['"]([A-Za-z][\w/]*)['"]/g;
2693
+ const VUEX_STORE_SIGNAL = /\bdefineStore\b|\bcreateStore\b|\bVuex\b|\bmutations\b|\bactions\b|\bgetters\b|\bnamespaced\b/g;
2694
+ const VUEX_FANOUT_CAP = 120;
2695
+ /** A path segment (dir or filename stem) equals `seg` — `…/modules/user.js` has
2696
+ * the segment `user` for namespace `user`. */
2697
+ function pathHasSegment(filePath, seg) {
2698
+ return new RegExp('[\\\\/]' + seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[\\\\/.]').test(filePath);
2699
+ }
2700
+ async function vuexDispatchEdges(ctx, onYield) {
2701
+ let scannedFiles = 0;
2702
+ const storeFileCache = new Map();
2703
+ const isStoreFile = (file) => {
2704
+ let v = storeFileCache.get(file);
2705
+ if (v === undefined) {
2706
+ const c = ctx.readFile(file);
2707
+ const seen = new Set();
2708
+ if (c) {
2709
+ VUEX_STORE_SIGNAL.lastIndex = 0;
2710
+ let sm;
2711
+ while ((sm = VUEX_STORE_SIGNAL.exec(c))) {
2712
+ seen.add(sm[0]);
2713
+ if (seen.size >= 2)
2714
+ break;
2715
+ }
2716
+ }
2717
+ v = seen.size >= 2;
2718
+ storeFileCache.set(file, v);
2719
+ }
2720
+ return v;
2721
+ };
2722
+ const resolve = (key, dispatchFile) => {
2723
+ const segs = key.split('/');
2724
+ const action = segs[segs.length - 1];
2725
+ const cands = ctx.getNodesByName(action).filter((n) => n.kind === 'function' && isStoreFile(n.filePath));
2726
+ if (!cands.length)
2727
+ return null;
2728
+ if (segs.length > 1) {
2729
+ const mod = segs[segs.length - 2]; // immediate namespace ≈ the module file
2730
+ return cands.find((c) => pathHasSegment(c.filePath, mod)) ?? (cands.length === 1 ? cands[0] : null);
2731
+ }
2732
+ // Root key: a local `commit('M')` inside an action targets the same module file;
2733
+ // otherwise accept only an unambiguous single store-wide match.
2734
+ return cands.find((c) => c.filePath === dispatchFile) ?? (cands.length === 1 ? cands[0] : null);
2735
+ };
2736
+ const edges = [];
2737
+ const seen = new Set();
2738
+ for (const file of ctx.getAllFiles()) {
2739
+ if ((++scannedFiles & 15) === 0)
2740
+ await onYield();
2741
+ if (!PINIA_CONSUMER_EXT.test(file))
2742
+ continue;
2743
+ const content = ctx.readFile(file);
2744
+ if (!content || (!content.includes('dispatch(') && !content.includes('commit(')))
2745
+ continue;
2746
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, /\.(?:jsx?|mjs|cjs)$/.test(file) ? 'javascript' : 'typescript');
2747
+ const nodesInFile = ctx.getNodesInFile(file);
2748
+ const fallback = nodesInFile.find((n) => n.kind === 'component'); // .vue top-level
2749
+ VUEX_DISPATCH_RE.lastIndex = 0;
2750
+ let m;
2751
+ let added = 0;
2752
+ while ((m = VUEX_DISPATCH_RE.exec(safe)) && added < VUEX_FANOUT_CAP) {
2753
+ const key = m[1];
2754
+ const line = safe.slice(0, m.index).split('\n').length;
2755
+ const disp = (0, synth_utils_1.enclosingFn)(nodesInFile, line) ?? fallback;
2756
+ if (!disp)
2757
+ continue;
2758
+ const target = resolve(key, file);
2759
+ if (!target || target.id === disp.id)
2760
+ continue;
2761
+ const edgeKey = `${disp.id}>${target.id}`;
2762
+ if (seen.has(edgeKey))
2763
+ continue;
2764
+ seen.add(edgeKey);
2765
+ edges.push({
2766
+ source: disp.id,
2767
+ target: target.id,
2768
+ kind: 'calls',
2769
+ line,
2770
+ provenance: 'heuristic',
2771
+ metadata: { synthesizedBy: 'vuex-dispatch', via: key, registeredAt: `${file}:${line}` },
2772
+ });
2773
+ added++;
2774
+ }
2775
+ }
2776
+ return edges;
2777
+ }
2778
+ // ── Celery task dispatch (Python) ─────────────────────────────────────────────
2779
+ // Celery decouples a task's call site from its body through async dispatch:
2780
+ // # tasks.py
2781
+ // @shared_task # also @app.task / @celery_app.task / @<app>.task / @task
2782
+ // def process(account_ids): ...
2783
+ // # views.py — a DIFFERENT module
2784
+ // process.apply_async(kwargs={...}) # or process.delay(...) — dynamic, no static edge
2785
+ // Bridge it: link the enclosing function/method at each `.delay(`/`.apply_async(` site → the
2786
+ // task function body. Precision rests on the DECORATOR gate — the dispatched name must resolve
2787
+ // to a Python function carrying a celery task decorator (read from the source lines above its
2788
+ // `def`, since the def's own startLine excludes the decorator). A `.delay()` on a non-task
2789
+ // object resolves to no task node → no edge, so a Celery-free repo yields 0. Same-file /
2790
+ // unique-candidate disambiguation like vuex. (Canvas forms — `group(t).delay()`, `t.s()`/`.si()`
2791
+ // — have no single identifier before `.delay`/`.apply_async`, so they're skipped, not mis-bridged.)
2792
+ const CELERY_DISPATCH_RE = /\b([A-Za-z_]\w*)\s*\.\s*(?:delay|apply_async)\s*\(/g;
2793
+ // A task decorator: bare `@shared_task`/`@task` or attribute `@app.task`/`@celery_app.task`,
2794
+ // each optionally called with args. `\b`-bounded and `@`-anchored so `@mytask`, or a symbol
2795
+ // merely named `task`, can't match. No `/g`, so `.test()` is stateless across reuse.
2796
+ const CELERY_TASK_DECORATOR_RE = /@\s*(?:[A-Za-z_][\w.]*\.)?(?:shared_task|task)\b/;
2797
+ const CELERY_PY_EXT = /\.py$/;
2798
+ const CELERY_FANOUT_CAP = 80;
2799
+ const CELERY_DECORATOR_LOOKBACK = 12; // max lines above a `def` to scan for its decorators
2800
+ async function celeryDispatchEdges(ctx, onYield) {
2801
+ let scannedFiles = 0;
2802
+ // Memoize the decorator check per task-candidate node: it reads the file and scans a few
2803
+ // lines above the def. Only called on names that are actually `.delay`/`.apply_async`
2804
+ // receivers, so the candidate set stays small.
2805
+ const taskCache = new Map();
2806
+ const isCeleryTask = (node) => {
2807
+ let v = taskCache.get(node.id);
2808
+ if (v !== undefined)
2809
+ return v;
2810
+ v = false;
2811
+ if (node.kind === 'function' && CELERY_PY_EXT.test(node.filePath)) {
2812
+ const content = ctx.readFile(node.filePath);
2813
+ if (content) {
2814
+ const lines = content.split('\n');
2815
+ // startLine is the `def` line (decorators sit ABOVE it). Walk upward, stopping at the
2816
+ // previous declaration so a non-task def can never inherit the prior def's decorator.
2817
+ const stop = Math.max(0, node.startLine - 1 - CELERY_DECORATOR_LOOKBACK);
2818
+ for (let i = node.startLine - 2; i >= stop; i--) {
2819
+ const t = (lines[i] ?? '').trim();
2820
+ if (/^(?:async\s+def|def|class)\b/.test(t))
2821
+ break; // previous decl → stop
2822
+ if (CELERY_TASK_DECORATOR_RE.test(t)) {
2823
+ v = true;
2824
+ break;
2825
+ }
2826
+ }
2827
+ }
2828
+ }
2829
+ taskCache.set(node.id, v);
2830
+ return v;
2831
+ };
2832
+ const resolve = (name, dispatchFile) => {
2833
+ const cands = ctx.getNodesByName(name).filter((n) => n.kind === 'function' && isCeleryTask(n));
2834
+ if (!cands.length)
2835
+ return null;
2836
+ if (cands.length === 1)
2837
+ return cands[0];
2838
+ // Cross-module name collision: prefer a task defined in the dispatching file, else bail
2839
+ // (ambiguous — precision over recall, like vuex's root-key resolution).
2840
+ return cands.find((c) => c.filePath === dispatchFile) ?? null;
2841
+ };
2842
+ const edges = [];
2843
+ const seen = new Set();
2844
+ for (const file of ctx.getAllFiles()) {
2845
+ if ((++scannedFiles & 15) === 0)
2846
+ await onYield();
2847
+ if (!CELERY_PY_EXT.test(file))
2848
+ continue;
2849
+ const content = ctx.readFile(file);
2850
+ if (!content || (!content.includes('.delay(') && !content.includes('.apply_async(')))
2851
+ continue;
2852
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'python');
2853
+ const nodesInFile = ctx.getNodesInFile(file);
2854
+ CELERY_DISPATCH_RE.lastIndex = 0;
2855
+ let m;
2856
+ let added = 0;
2857
+ while ((m = CELERY_DISPATCH_RE.exec(safe)) && added < CELERY_FANOUT_CAP) {
2858
+ const name = m[1];
2859
+ const line = safe.slice(0, m.index).split('\n').length;
2860
+ const disp = (0, synth_utils_1.enclosingFn)(nodesInFile, line);
2861
+ if (!disp)
2862
+ continue; // module-level dispatch — no source symbol to attribute
2863
+ const target = resolve(name, file);
2864
+ if (!target || target.id === disp.id)
2865
+ continue;
2866
+ const key = `${disp.id}>${target.id}`;
2867
+ if (seen.has(key))
2868
+ continue;
2869
+ seen.add(key);
2870
+ edges.push({
2871
+ source: disp.id,
2872
+ target: target.id,
2873
+ kind: 'calls',
2874
+ line,
2875
+ provenance: 'heuristic',
2876
+ metadata: { synthesizedBy: 'celery-dispatch', via: name, registeredAt: `${file}:${line}` },
2877
+ });
2878
+ added++;
2879
+ }
2880
+ }
2881
+ return edges;
2882
+ }
2883
+ // ── Spring application events (Java) ──────────────────────────────────────────
2884
+ // Spring decouples an event PUBLISHER from its LISTENER(s) through the application
2885
+ // event bus, linked by the EVENT TYPE (not a name):
2886
+ // // SomeService.java
2887
+ // eventPublisher.publishEvent(new PasswordChangedEvent(this, username)); // publish
2888
+ // // RememberMeTokenRevoker.java — a DIFFERENT file
2889
+ // @EventListener(PasswordChangedEvent.class) // listen
2890
+ // public void onPasswordChanged(PasswordChangedEvent event) { ... }
2891
+ // Bridge it: link the enclosing method at each `publishEvent(new XEvent(...))` site →
2892
+ // every listener method of XEvent. Listeners are `@EventListener` / `@TransactionalEventListener`
2893
+ // methods (event type = the first param type, or the `@EventListener(X.class)` value form) and
2894
+ // the older `class … implements ApplicationListener<X> { void onApplicationEvent(X e) }`. Keyed
2895
+ // by exact type name, usually cross-file. A repo with no `@EventListener`/`publishEvent` yields 0.
2896
+ // (Java method nodes INCLUDE their leading annotations in the range — startLine is the first
2897
+ // `@…` line — so the annotation block is scanned DOWNWARD from startLine, bounded to consecutive
2898
+ // `@`-lines so it can't bleed into an adjacent method.)
2899
+ const SPRING_PUBLISH_RE = /\.publishEvent\s*\(\s*new\s+([A-Z][A-Za-z0-9_]*)/g;
2900
+ const SPRING_LISTENER_ANNO_RE = /@(?:EventListener|TransactionalEventListener)\b/;
2901
+ const SPRING_ANNO_TYPE_RE = /@(?:EventListener|TransactionalEventListener)\s*\(\s*([A-Z][A-Za-z0-9_]*)\.class/;
2902
+ const SPRING_APP_LISTENER_RE = /\bApplicationListener\s*</;
2903
+ const SPRING_JAVA_EXT = /\.java$/;
2904
+ const SPRING_FANOUT_CAP = 80;
2905
+ /** The first parameter's type from a Java method `signature` (`"void (XEvent e)"` → `XEvent`).
2906
+ * Skips a leading `final`/`@Anno`, strips generics, and requires a PascalCase class name (event
2907
+ * types are classes) — so a no-arg or primitive-param method yields null. */
2908
+ function springFirstParamType(sig) {
2909
+ if (!sig)
2910
+ return null;
2911
+ const open = sig.indexOf('(');
2912
+ if (open < 0)
2913
+ return null;
2914
+ const close = sig.indexOf(')', open);
2915
+ const inner = sig.slice(open + 1, close < 0 ? sig.length : close).trim();
2916
+ if (!inner)
2917
+ return null;
2918
+ const first = inner.split(',')[0].trim();
2919
+ const toks = first.split(/\s+/).filter((t) => t && t !== 'final' && !t.startsWith('@'));
2920
+ if (toks.length < 2)
2921
+ return null; // need `Type name`
2922
+ const type = toks[toks.length - 2].replace(/<.*$/, ''); // drop generic args
2923
+ return /^[A-Z][A-Za-z0-9_]*$/.test(type) ? type : null;
2924
+ }
2925
+ async function springEventEdges(ctx, onYield) {
2926
+ let scannedFiles = 0;
2927
+ // Pass 1 — event-type → listener methods, scanning only event-relevant files.
2928
+ // This is the ONLY full read sweep: publisher files are recorded here so
2929
+ // pass 2 re-reads just those instead of every .java file again (#1212 —
2930
+ // the double full-repo read was one of the tail's longest unyielded spans).
2931
+ const listeners = new Map();
2932
+ const publisherFiles = [];
2933
+ for (const file of ctx.getAllFiles()) {
2934
+ if ((++scannedFiles & 15) === 0)
2935
+ await onYield();
2936
+ if (!SPRING_JAVA_EXT.test(file))
2937
+ continue;
2938
+ const content = ctx.readFile(file);
2939
+ if (!content)
2940
+ continue;
2941
+ if (content.includes('.publishEvent('))
2942
+ publisherFiles.push(file);
2943
+ const hasAnno = content.includes('@EventListener') || content.includes('@TransactionalEventListener');
2944
+ const hasAppListener = SPRING_APP_LISTENER_RE.test(content);
2945
+ if (!hasAnno && !hasAppListener)
2946
+ continue;
2947
+ const lines = content.split('\n');
2948
+ for (const node of ctx.getNodesInFile(file)) {
2949
+ if (node.kind !== 'method')
2950
+ continue;
2951
+ // Collect this method's own leading annotation block (consecutive `@`-lines from startLine).
2952
+ const annoLines = [];
2953
+ for (let i = node.startLine - 1; i < lines.length && i < node.startLine + 7; i++) {
2954
+ const t = (lines[i] ?? '').trim();
2955
+ if (!t.startsWith('@'))
2956
+ break; // reached the declaration → stop (no bleed into next method)
2957
+ annoLines.push(t);
2958
+ }
2959
+ const head = annoLines.join('\n');
2960
+ const annotated = hasAnno && SPRING_LISTENER_ANNO_RE.test(head);
2961
+ const isAppListener = hasAppListener && node.name === 'onApplicationEvent';
2962
+ if (!annotated && !isAppListener)
2963
+ continue;
2964
+ let type = springFirstParamType(node.signature);
2965
+ if (!type && annotated) {
2966
+ const m = SPRING_ANNO_TYPE_RE.exec(head);
2967
+ if (m)
2968
+ type = m[1];
2969
+ }
2970
+ if (!type)
2971
+ continue;
2972
+ let arr = listeners.get(type);
2973
+ if (!arr) {
2974
+ arr = [];
2975
+ listeners.set(type, arr);
2976
+ }
2977
+ arr.push(node);
2978
+ }
2979
+ }
2980
+ if (!listeners.size)
2981
+ return [];
2982
+ // Pass 2 — link each publishEvent(new XEvent(...)) site → every listener of
2983
+ // XEvent. Only the publisher files recorded in pass 1 are (re-)read.
2984
+ const edges = [];
2985
+ const seen = new Set();
2986
+ for (const file of publisherFiles) {
2987
+ if ((++scannedFiles & 15) === 0)
2988
+ await onYield();
2989
+ const content = ctx.readFile(file);
2990
+ if (!content || !content.includes('.publishEvent('))
2991
+ continue;
2992
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'java');
2993
+ const nodesInFile = ctx.getNodesInFile(file);
2994
+ SPRING_PUBLISH_RE.lastIndex = 0;
2995
+ let m;
2996
+ let added = 0;
2997
+ while ((m = SPRING_PUBLISH_RE.exec(safe)) && added < SPRING_FANOUT_CAP) {
2998
+ const targets = listeners.get(m[1]);
2999
+ if (!targets || !targets.length)
3000
+ continue;
3001
+ const line = safe.slice(0, m.index).split('\n').length;
3002
+ const disp = (0, synth_utils_1.enclosingFn)(nodesInFile, line);
3003
+ if (!disp)
3004
+ continue;
3005
+ for (const target of targets) {
3006
+ if (target.id === disp.id)
3007
+ continue;
3008
+ const key = `${disp.id}>${target.id}`;
3009
+ if (seen.has(key))
3010
+ continue;
3011
+ seen.add(key);
3012
+ edges.push({
3013
+ source: disp.id,
3014
+ target: target.id,
3015
+ kind: 'calls',
3016
+ line,
3017
+ provenance: 'heuristic',
3018
+ metadata: { synthesizedBy: 'spring-event', via: m[1], registeredAt: `${file}:${line}` },
3019
+ });
3020
+ added++;
3021
+ }
3022
+ }
3023
+ }
3024
+ return edges;
3025
+ }
3026
+ // ── MediatR request/notification dispatch (C#/.NET) ───────────────────────────
3027
+ // MediatR decouples a Send/Publish call site from its Handle method through a mediator,
3028
+ // linked by the request/notification TYPE (the IRequestHandler<T,…> generic):
3029
+ // // CancelOrderCommandHandler.cs — the handler
3030
+ // public class CancelOrderCommandHandler : IRequestHandler<CancelOrderCommand, bool> {
3031
+ // public async Task<bool> Handle(CancelOrderCommand request, CancellationToken ct) { … }
3032
+ // // some controller — the dispatch (usually a DIFFERENT file)
3033
+ // var command = new CancelOrderCommand(orderId); await _mediator.Send(command);
3034
+ // Bridge it: link the enclosing method at each mediator `.Send(x)`/`.Publish(x)` site → the
3035
+ // `Handle` method of the handler for x's type. The sent type is resolved from the argument —
3036
+ // inline `new X(…)`, a local `var v = new X(…)`, or a parameter/local declared `X v` — bounded
3037
+ // to the enclosing method. Precision rests on TWO gates: the receiver must be mediator-ish
3038
+ // (`mediator`/`sender`/`publisher`, so MAUI `MessagingCenter.Send` is ignored) AND the resolved
3039
+ // type must be a known handler request type (so a same-named non-request DTO is never bridged).
3040
+ // C# has no `signature` on method nodes, so the handler's request type is read from the class
3041
+ // base-list source (`: IRequestHandler<X,…>`), not a param signature.
3042
+ const MEDIATR_HANDLER_BASE_RE = /(?:IRequestHandler|INotificationHandler)\s*<\s*([A-Za-z_]\w*)/;
3043
+ const MEDIATR_DISPATCH_RE = /([A-Za-z_][\w.]*)\s*\.\s*(?:Send|Publish)\s*\(\s*(new\s+[A-Z]\w*|[A-Za-z_]\w*)/g;
3044
+ const MEDIATR_RECEIVER_RE = /(?:mediator|sender|publisher)/i;
3045
+ const MEDIATR_CS_EXT = /\.cs$/;
3046
+ const MEDIATR_FANOUT_CAP = 80;
3047
+ const MEDIATR_HANDLER_DECL_LOOKAHEAD = 4; // lines from a class startLine to find a wrapped base list
3048
+ /** The type sent at a MediatR `.Send(arg)`/`.Publish(arg)` site: an inline `new X(…)`, else
3049
+ * `arg` as an identifier resolved within the enclosing method — a `… arg = new X(…)` assignment
3050
+ * (wins), or a parameter/local declared `X arg`. Returns null when the type can't be seen. */
3051
+ function resolveMediatrArgType(arg, lines, methodStart, dispatchLine) {
3052
+ const inl = /^new\s+([A-Z]\w*)/.exec(arg);
3053
+ if (inl)
3054
+ return inl[1];
3055
+ if (!/^[A-Za-z_]\w*$/.test(arg))
3056
+ return null;
3057
+ const assignRe = new RegExp(`\\b${arg}\\b\\s*=\\s*new\\s+([A-Z]\\w*)`);
3058
+ const declRe = new RegExp(`\\b([A-Z]\\w*)\\b\\s+${arg}\\b`);
3059
+ let declType = null;
3060
+ for (let i = Math.max(0, methodStart - 1); i < dispatchLine && i < lines.length; i++) {
3061
+ const ln = lines[i] ?? '';
3062
+ const a = assignRe.exec(ln);
3063
+ if (a)
3064
+ return a[1]; // an explicit `arg = new X` is the most specific — take it
3065
+ if (!declType) {
3066
+ const d = declRe.exec(ln);
3067
+ if (d)
3068
+ declType = d[1]; // a `X arg` declaration — remember, but keep scanning for an assignment
3069
+ }
3070
+ }
3071
+ return declType;
3072
+ }
3073
+ async function mediatrDispatchEdges(ctx, onYield) {
3074
+ let scannedFiles = 0;
3075
+ // Pass 1 — request/notification type → the Handle method of each handler class.
3076
+ const handlers = new Map();
3077
+ for (const file of ctx.getAllFiles()) {
3078
+ if ((++scannedFiles & 15) === 0)
3079
+ await onYield();
3080
+ if (!MEDIATR_CS_EXT.test(file))
3081
+ continue;
3082
+ const content = ctx.readFile(file);
3083
+ if (!content || (!content.includes('IRequestHandler<') && !content.includes('INotificationHandler<')))
3084
+ continue;
3085
+ const lines = content.split('\n');
3086
+ const nodesInFile = ctx.getNodesInFile(file);
3087
+ for (const cls of nodesInFile) {
3088
+ if (cls.kind !== 'class')
3089
+ continue;
3090
+ const decl = lines.slice(cls.startLine - 1, cls.startLine - 1 + MEDIATR_HANDLER_DECL_LOOKAHEAD).join('\n');
3091
+ const m = MEDIATR_HANDLER_BASE_RE.exec(decl);
3092
+ if (!m)
3093
+ continue;
3094
+ const type = m[1];
3095
+ const end = cls.endLine ?? cls.startLine;
3096
+ const handle = nodesInFile.find((n) => n.kind === 'method' && n.name === 'Handle' && n.startLine >= cls.startLine && n.startLine <= end);
3097
+ if (!handle)
3098
+ continue;
3099
+ let arr = handlers.get(type);
3100
+ if (!arr) {
3101
+ arr = [];
3102
+ handlers.set(type, arr);
3103
+ }
3104
+ arr.push(handle);
3105
+ }
3106
+ }
3107
+ if (!handlers.size)
3108
+ return [];
3109
+ // Pass 2 — link each mediator-ish .Send(x)/.Publish(x) → the handler of x's type.
3110
+ const edges = [];
3111
+ const seen = new Set();
3112
+ for (const file of ctx.getAllFiles()) {
3113
+ if ((++scannedFiles & 15) === 0)
3114
+ await onYield();
3115
+ if (!MEDIATR_CS_EXT.test(file))
3116
+ continue;
3117
+ const content = ctx.readFile(file);
3118
+ if (!content || (!content.includes('.Send(') && !content.includes('.Publish(')))
3119
+ continue;
3120
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'csharp');
3121
+ const safeLines = safe.split('\n');
3122
+ const nodesInFile = ctx.getNodesInFile(file);
3123
+ MEDIATR_DISPATCH_RE.lastIndex = 0;
3124
+ let m;
3125
+ let added = 0;
3126
+ while ((m = MEDIATR_DISPATCH_RE.exec(safe)) && added < MEDIATR_FANOUT_CAP) {
3127
+ if (!MEDIATR_RECEIVER_RE.test(m[1]))
3128
+ continue; // not a mediator (MessagingCenter, HttpClient, …)
3129
+ const line = safe.slice(0, m.index).split('\n').length;
3130
+ const disp = (0, synth_utils_1.enclosingFn)(nodesInFile, line);
3131
+ if (!disp)
3132
+ continue;
3133
+ const type = resolveMediatrArgType(m[2], safeLines, disp.startLine, line);
3134
+ if (!type)
3135
+ continue;
3136
+ const targets = handlers.get(type);
3137
+ if (!targets)
3138
+ continue;
3139
+ for (const target of targets) {
3140
+ if (target.id === disp.id)
3141
+ continue;
3142
+ const key = `${disp.id}>${target.id}`;
3143
+ if (seen.has(key))
3144
+ continue;
3145
+ seen.add(key);
3146
+ edges.push({
3147
+ source: disp.id,
3148
+ target: target.id,
3149
+ kind: 'calls',
3150
+ line,
3151
+ provenance: 'heuristic',
3152
+ metadata: { synthesizedBy: 'mediatr-dispatch', via: type, registeredAt: `${file}:${line}` },
3153
+ });
3154
+ added++;
3155
+ }
3156
+ }
3157
+ }
3158
+ return edges;
3159
+ }
3160
+ // ── Sidekiq job dispatch (Ruby) ───────────────────────────────────────────────
3161
+ // Sidekiq decouples a job's enqueue site from the worker's `perform`, linked by the WORKER
3162
+ // CLASS NAME:
3163
+ // # app/workers/destroy_user_worker.rb
3164
+ // class DestroyUserWorker
3165
+ // include Sidekiq::Worker # or Sidekiq::Job (the modern alias)
3166
+ // def perform(user_id) … end
3167
+ // # app/services/… — a DIFFERENT file
3168
+ // DestroyUserWorker.perform_async(user.id) # also .perform_in(t, …) / .perform_at(t, …)
3169
+ // Bridge it: link the enclosing method at each `Worker.perform_async/_in/_at(…)` site → that
3170
+ // worker's instance `perform`. Name-keyed (like Celery): the receiver class must be a Sidekiq
3171
+ // worker — gated by reading `include Sidekiq::Job|Worker` from the class body, since that mixin
3172
+ // is an external gem module that forms no resolvable edge. ActiveJob's `perform_later`/`_now` is
3173
+ // a different shape and deliberately not matched, so an ActiveJob-only app yields 0.
3174
+ const SIDEKIQ_DISPATCH_RE = /([A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)\s*\.\s*perform_(?:async|in|at)\b/g;
3175
+ const SIDEKIQ_WORKER_RE = /\binclude\s+Sidekiq::(?:Job|Worker)\b/;
3176
+ const SIDEKIQ_RB_EXT = /\.rb$/;
3177
+ const SIDEKIQ_FANOUT_CAP = 80;
3178
+ async function sidekiqDispatchEdges(ctx, onYield) {
3179
+ let scannedFiles = 0;
3180
+ // class node id → its instance `perform` method (null if the class isn't a Sidekiq worker),
3181
+ // memoized. Reads the class body for the mixin; only consulted for actual dispatch receivers.
3182
+ const performCache = new Map();
3183
+ const performOf = (cls) => {
3184
+ let v = performCache.get(cls.id);
3185
+ if (v !== undefined)
3186
+ return v;
3187
+ v = null;
3188
+ const content = ctx.readFile(cls.filePath);
3189
+ if (content) {
3190
+ const end = cls.endLine ?? cls.startLine;
3191
+ const body = content.split('\n').slice(cls.startLine - 1, end).join('\n');
3192
+ if (SIDEKIQ_WORKER_RE.test(body)) {
3193
+ v = ctx.getNodesInFile(cls.filePath).find((n) => n.kind === 'method' && n.name === 'perform' && n.startLine >= cls.startLine && n.startLine <= end) ?? null;
3194
+ }
3195
+ }
3196
+ performCache.set(cls.id, v);
3197
+ return v;
3198
+ };
3199
+ // Resolve a (possibly namespaced) worker reference to its `perform`. A namespaced ref is
3200
+ // matched by EXACT qualified name first, so same-named workers in different namespaces
3201
+ // (forem has four `SendEmailNotificationWorker`s) resolve to the right one; an unqualified
3202
+ // ref falls back to the simple name and links only when a single worker bears it — an
3203
+ // ambiguous collision bails (precision over recall).
3204
+ const resolve = (ref) => {
3205
+ if (ref.includes('::')) {
3206
+ const q = ctx.getNodesByQualifiedName(ref).find((n) => n.kind === 'class' && performOf(n));
3207
+ if (q)
3208
+ return performOf(q);
3209
+ }
3210
+ const workers = ctx.getNodesByName(ref.split('::').pop()).filter((n) => n.kind === 'class' && performOf(n));
3211
+ return workers.length === 1 ? performOf(workers[0]) : null;
3212
+ };
3213
+ const edges = [];
3214
+ const seen = new Set();
3215
+ for (const file of ctx.getAllFiles()) {
3216
+ if ((++scannedFiles & 15) === 0)
3217
+ await onYield();
3218
+ if (!SIDEKIQ_RB_EXT.test(file))
3219
+ continue;
3220
+ const content = ctx.readFile(file);
3221
+ if (!content || !/\.perform_(?:async|in|at)\b/.test(content))
3222
+ continue;
3223
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'ruby');
3224
+ const nodesInFile = ctx.getNodesInFile(file);
3225
+ SIDEKIQ_DISPATCH_RE.lastIndex = 0;
3226
+ let m;
3227
+ let added = 0;
3228
+ while ((m = SIDEKIQ_DISPATCH_RE.exec(safe)) && added < SIDEKIQ_FANOUT_CAP) {
3229
+ const line = safe.slice(0, m.index).split('\n').length;
3230
+ const disp = (0, synth_utils_1.enclosingFn)(nodesInFile, line);
3231
+ if (!disp)
3232
+ continue;
3233
+ const target = resolve(m[1]);
3234
+ if (!target || target.id === disp.id)
3235
+ continue;
3236
+ const key = `${disp.id}>${target.id}`;
3237
+ if (seen.has(key))
3238
+ continue;
3239
+ seen.add(key);
3240
+ edges.push({
3241
+ source: disp.id,
3242
+ target: target.id,
3243
+ kind: 'calls',
3244
+ line,
3245
+ provenance: 'heuristic',
3246
+ metadata: { synthesizedBy: 'sidekiq-dispatch', via: m[1], registeredAt: `${file}:${line}` },
3247
+ });
3248
+ added++;
3249
+ }
3250
+ }
3251
+ return edges;
3252
+ }
3253
+ // ── Erlang behaviour-callback dispatch ────────────────────────────────────────
3254
+ // An Erlang behaviour is a compile-checked callback contract: the behaviour
3255
+ // module declares `-callback init(...) -> ...`, implementers declare
3256
+ // `-behaviour(B)` and export the callbacks, and the framework side dispatches
3257
+ // through a VARIABLE module — cowboy's `Handler:init(Req, Opts)` and
3258
+ // `Middleware:execute(Req, Env)` folds, ejabberd's `Mod:start/2`. Extraction
3259
+ // deliberately leaves var-module calls silent (no static target), so the flow
3260
+ // breaks at exactly the hop agents ask about (request → handler init). Bridge:
3261
+ //
3262
+ // dispatch site `Var:fn(args…)` → every in-repo implementer of the behaviour
3263
+ // declaring `fn` with the SITE's arity — provided exactly ONE in-repo
3264
+ // behaviour declares (fn, arity); a name+arity collision across behaviours
3265
+ // bails (silent beats wrong) — and the implementer defines and exports `fn`.
3266
+ //
3267
+ // Behaviours are discovered by scanning every Erlang file for `-callback`
3268
+ // declarations (not just `implements` targets), so a behaviour with zero
3269
+ // implementers still participates in the ambiguity gate. Fan-out control: a
3270
+ // mega-behaviour (ejabberd's gen_mod, ~200 mod_* implementers) would mint
3271
+ // hundreds of edges per site that READ as complete coverage while being
3272
+ // arbitrary — above the cap the site is skipped entirely and the boundary
3273
+ // stays visibly dynamic (explore's boundary announcer covers it) instead of
3274
+ // silently truncated.
3275
+ const ERLANG_EXT = /\.(?:erl|hrl)$/;
3276
+ // `Var:fn(` — variable (capitalized) module, lowercase function, immediate
3277
+ // open-paren. The leading char class rejects `?MODULE:fn(` (macro), `a:b(`
3278
+ // (static remote call, already linked), and mid-word matches.
3279
+ const ERLANG_DISPATCH_RE = /(^|[^?\w@'])([A-Z][A-Za-z0-9_@]*):([a-z][A-Za-z0-9_@]*)\(/g;
3280
+ const ERLANG_CALLBACK_DECL_RE = /(^|\n)\s*-callback\s+('[^'\n]+'|[a-z][A-Za-z0-9_@]*)\s*\(/g;
3281
+ const ERLANG_BEHAVIOUR_FANOUT_CAP = 24;
3282
+ /**
3283
+ * Argument count of the call/declaration whose `(` sits at `openIdx` —
3284
+ * top-level commas + 1, `()` → 0, unbalanced/oversized → -1. Skips nested
3285
+ * (), [], {}, <<>> content, `"strings"`, `'atoms'`, and `$c` char literals,
3286
+ * so `-callback init(fun((a, b) -> ok), #{k => v}) -> ok.` counts 2.
3287
+ */
3288
+ function erlangArityAt(src, openIdx) {
3289
+ let depth = 1;
3290
+ // `<<1,2,3>>` binary literals: commas inside are element separators, not
3291
+ // argument separators. Tracked separately from bracket depth because the
3292
+ // single-char `<`/`>` comparison operators must stay inert (#1358).
3293
+ let binDepth = 0;
3294
+ let commas = 0;
3295
+ let sawArg = false;
3296
+ const limit = Math.min(src.length, openIdx + 4000);
3297
+ for (let i = openIdx + 1; i < limit; i++) {
3298
+ const ch = src[i];
3299
+ if (ch === '"' || ch === "'") {
3300
+ i++;
3301
+ while (i < limit && src[i] !== ch) {
3302
+ if (src[i] === '\\')
3303
+ i++;
3304
+ i++;
3305
+ }
3306
+ sawArg = true;
3307
+ continue;
3308
+ }
3309
+ if (ch === '$') {
3310
+ i++;
3311
+ if (src[i] === '\\')
3312
+ i++;
3313
+ sawArg = true;
3314
+ continue;
3315
+ }
3316
+ if (ch === '<' && src[i + 1] === '<') {
3317
+ binDepth++;
3318
+ i++;
3319
+ sawArg = true;
3320
+ continue;
3321
+ }
3322
+ if (ch === '>' && src[i + 1] === '>' && binDepth > 0) {
3323
+ binDepth--;
3324
+ i++;
3325
+ continue;
3326
+ }
3327
+ if (ch === '(' || ch === '[' || ch === '{') {
3328
+ depth++;
3329
+ sawArg = true;
3330
+ continue;
3331
+ }
3332
+ if (ch === ')' || ch === ']' || ch === '}') {
3333
+ depth--;
3334
+ if (depth === 0)
3335
+ return sawArg ? commas + 1 : 0;
3336
+ continue;
3337
+ }
3338
+ if (ch === ',' && depth === 1 && binDepth === 0) {
3339
+ commas++;
3340
+ continue;
3341
+ }
3342
+ if (!/\s/.test(ch))
3343
+ sawArg = true;
3344
+ }
3345
+ return -1;
3346
+ }
3347
+ /**
3348
+ * Nix module-system option wiring. A NixOS/home-manager/nix-darwin option is
3349
+ * DECLARED in one module (`options.launchd.user.agents = mkOption { ... }`)
3350
+ * and SET in others (`launchd.user.agents.yabai = { ... }` inside a module's
3351
+ * config) — the connection happens by option-path unification inside the
3352
+ * module-system evaluator, so there is no static call/import edge to follow
3353
+ * and flow questions ("how does services.yabai.enable become a launchd
3354
+ * service?") go dark at the module boundary.
3355
+ *
3356
+ * This pass links each config-write binding to the option declaration whose
3357
+ * path is the longest static-segment prefix of the write path. Precision gates:
3358
+ * - only STATIC segments participate: plain identifiers, plus quoted segments
3359
+ * (`"git/config"`, `"com.apple.dock"`) as opaque verbatim tokens that match
3360
+ * only quote-exactly; an interpolated (`${name}`) segment ends the prefix,
3361
+ * so dynamic paths never match beyond their static head;
3362
+ * - matched prefixes must be ≥2 segments: 1-segment paths would wrongly link
3363
+ * every package's `meta = { ... }` attrset to nixos's `options.meta`;
3364
+ * - a prefix declared in more than one file is ambiguous → no edge (a wrong
3365
+ * edge is worse than none);
3366
+ * - writes physically inside an options block are declaration internals
3367
+ * (types, defaults, examples), never config writes → excluded.
3368
+ * Both declaration spellings register: flat (`options.a.b = ...`) by name, and
3369
+ * nested (`options = { a.b = ...; }`) by line-span containment.
3370
+ */
3371
+ function nixLeadingPlainSegments(name) {
3372
+ const segs = [];
3373
+ let i = 0;
3374
+ const n = name.length;
3375
+ while (i < n) {
3376
+ if (name[i] === '"') {
3377
+ // Quoted segment — an opaque verbatim token (quotes kept, so it can
3378
+ // never collide with a plain identifier). `NSGlobalDomain."com.apple.
3379
+ // mouse.tapBehavior"` must match ITS OWN quoted declaration, not
3380
+ // whichever sibling registered the shared plain prefix first.
3381
+ let j = i + 1;
3382
+ while (j < n && name[j] !== '"') {
3383
+ if (name[j] === '\\')
3384
+ j++;
3385
+ j++;
3386
+ }
3387
+ if (j >= n)
3388
+ return segs; // unterminated — stop at the static head
3389
+ const tok = name.slice(i, j + 1);
3390
+ if (tok.includes('${'))
3391
+ return segs; // interpolated → dynamic → stop
3392
+ segs.push(tok);
3393
+ i = j + 1;
3394
+ if (i >= n)
3395
+ break;
3396
+ if (name[i] !== '.')
3397
+ return segs;
3398
+ i++;
3399
+ continue;
3400
+ }
3401
+ let j = i;
3402
+ while (j < n && name[j] !== '.') {
3403
+ if (name[j] === '"' || (name[j] === '$' && name[j + 1] === '{'))
3404
+ return segs;
3405
+ j++;
3406
+ }
3407
+ const seg = name.slice(i, j);
3408
+ if (!/^[A-Za-z_][A-Za-z0-9_'-]*$/.test(seg))
3409
+ return segs;
3410
+ segs.push(seg);
3411
+ i = j + 1;
3412
+ }
3413
+ return segs;
3414
+ }
3415
+ async function nixOptionPathEdges(queries, onYield) {
3416
+ let scanned255 = 0;
3417
+ // One streaming pass over nix bindings (variables + the odd function-valued
3418
+ // option); memory stays O(bindings-kept), not O(all nodes) (#610).
3419
+ const byFile = new Map();
3420
+ let scanned = 0;
3421
+ for (const kind of ['variable', 'function']) {
3422
+ for (const node of queries.iterateNodesByKind(kind)) {
3423
+ if ((++scanned255 & 63) === 0)
3424
+ await onYield();
3425
+ if ((++scanned & 0x3fff) === 0 && onYield)
3426
+ await onYield();
3427
+ if (node.language !== 'nix')
3428
+ continue;
3429
+ const segs = nixLeadingPlainSegments(node.name);
3430
+ if (segs.length === 0)
3431
+ continue;
3432
+ const rec = {
3433
+ id: node.id,
3434
+ filePath: node.filePath,
3435
+ startLine: node.startLine,
3436
+ endLine: node.endLine,
3437
+ segs,
3438
+ };
3439
+ const arr = byFile.get(node.filePath);
3440
+ if (arr)
3441
+ arr.push(rec);
3442
+ else
3443
+ byFile.set(node.filePath, [rec]);
3444
+ }
3445
+ }
3446
+ // Per file: walk bindings outermost-first with a stack of active option
3447
+ // spans, composing nested declaration paths (`options = { services.foo = {
3448
+ // enable = mkOption ...; }; }` registers services.foo AND services.foo.enable).
3449
+ // An `options` binding nested inside another option span is a SUBMODULE's
3450
+ // own namespace (`attrsOf (submodule { options = ...; })`) — its internals
3451
+ // are not globally addressable, so the sentinel blocks registration below it
3452
+ // while still excluding the region from write candidates.
3453
+ const SUBMODULE = '\u0000submodule';
3454
+ const decls = new Map();
3455
+ const writes = [];
3456
+ const register = (path, rec) => {
3457
+ if (path.length < 2 || path.includes(SUBMODULE))
3458
+ return;
3459
+ const key = path.join('.');
3460
+ const arr = decls.get(key);
3461
+ if (arr)
3462
+ arr.push(rec);
3463
+ else
3464
+ decls.set(key, [rec]);
3465
+ };
3466
+ for (const recs of byFile.values()) {
3467
+ recs.sort((a, b) => a.startLine - b.startLine || b.endLine - a.endLine);
3468
+ const stack = [];
3469
+ for (const rec of recs) {
3470
+ while (stack.length > 0 && stack[stack.length - 1].end < rec.startLine)
3471
+ stack.pop();
3472
+ // Strict containment at line granularity: a one-line nested binding is
3473
+ // indistinguishable from its container, so it stays unclassified (rare
3474
+ // in module code, where option blocks are multi-line).
3475
+ const enclosing = stack.length > 0 &&
3476
+ rec.startLine >= stack[stack.length - 1].start &&
3477
+ rec.endLine <= stack[stack.length - 1].end &&
3478
+ !(rec.startLine === stack[stack.length - 1].start && rec.endLine === stack[stack.length - 1].end)
3479
+ ? stack[stack.length - 1]
3480
+ : null;
3481
+ if (rec.segs[0] === 'options') {
3482
+ const ownPath = rec.segs.slice(1); // [] for the bare `options = { ... }` spelling
3483
+ const prefix = enclosing ? [SUBMODULE] : ownPath;
3484
+ register(prefix, rec);
3485
+ stack.push({ start: rec.startLine, end: rec.endLine, prefix });
3486
+ continue;
3487
+ }
3488
+ if (enclosing) {
3489
+ const composed = [...enclosing.prefix, ...rec.segs];
3490
+ register(composed, rec);
3491
+ stack.push({ start: rec.startLine, end: rec.endLine, prefix: composed });
3492
+ continue;
3493
+ }
3494
+ if (rec.segs.length >= 2) {
3495
+ writes.push(rec);
3496
+ }
3497
+ }
3498
+ }
3499
+ if (decls.size === 0 || writes.length === 0)
3500
+ return [];
3501
+ const edges = [];
3502
+ for (const w of writes) {
3503
+ // `config.services.x = ...` spells the same write with an explicit prefix.
3504
+ const segs = w.segs[0] === 'config' ? w.segs.slice(1) : w.segs;
3505
+ if (segs.length < 2)
3506
+ continue;
3507
+ // Longest prefix wins; an ambiguous longest match does NOT fall back to a
3508
+ // shorter one (that would link `services.nginx.virtualHosts.x` to
3509
+ // `options.services.nginx` when virtualHosts is the contested path).
3510
+ for (let len = Math.min(segs.length, 6); len >= 2; len--) {
3511
+ const candidates = decls.get(segs.slice(0, len).join('.'));
3512
+ if (!candidates || candidates.length === 0)
3513
+ continue;
3514
+ const files = new Set(candidates.map((c) => c.filePath));
3515
+ if (files.size === 1) {
3516
+ const target = candidates[0];
3517
+ if (target.id !== w.id) {
3518
+ edges.push({
3519
+ source: w.id,
3520
+ target: target.id,
3521
+ kind: 'references',
3522
+ line: w.startLine,
3523
+ provenance: 'heuristic',
3524
+ metadata: {
3525
+ synthesizedBy: 'nix-option-path',
3526
+ optionPath: segs.slice(0, len).join('.'),
3527
+ registeredAt: `${target.filePath}:${target.startLine}`,
3528
+ },
3529
+ });
3530
+ }
3531
+ }
3532
+ break; // longest hit decides, matched or ambiguous
3533
+ }
3534
+ }
3535
+ return edges;
3536
+ }
3537
+ async function erlangBehaviourDispatchEdges(queries, ctx, onYield) {
3538
+ let scannedFiles = 0;
3539
+ let scanned255 = 0;
3540
+ // Cheap language gate: no Erlang modules → no cost beyond one streamed
3541
+ // kind scan (never a materialized array of every namespace — #1212).
3542
+ const erlangModules = [];
3543
+ for (const n of queries.iterateNodesByKind('namespace')) {
3544
+ if ((++scanned255 & 63) === 0)
3545
+ await onYield();
3546
+ if (n.language === 'erlang')
3547
+ erlangModules.push(n);
3548
+ }
3549
+ if (erlangModules.length === 0)
3550
+ return [];
3551
+ // Pass 1 — scan every Erlang file with `-callback` decls: behaviour module →
3552
+ // its (name, arity) callback set, and the global `name/arity` → declaring
3553
+ // behaviours map that drives the ambiguity gate.
3554
+ const moduleByFile = new Map();
3555
+ for (const ns of erlangModules) {
3556
+ if (!moduleByFile.has(ns.filePath))
3557
+ moduleByFile.set(ns.filePath, ns);
3558
+ }
3559
+ const declaringBehaviours = new Map(); // `fn/arity` → behaviour namespaces
3560
+ const callbackNames = new Set();
3561
+ for (const file of ctx.getAllFiles()) {
3562
+ if ((++scannedFiles & 15) === 0)
3563
+ await onYield();
3564
+ if (!ERLANG_EXT.test(file))
3565
+ continue;
3566
+ const behaviour = moduleByFile.get(file);
3567
+ if (!behaviour)
3568
+ continue; // a .hrl or module-less file can't be a behaviour
3569
+ const content = ctx.readFile(file);
3570
+ if (!content || !content.includes('-callback'))
3571
+ continue;
3572
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'erlang');
3573
+ ERLANG_CALLBACK_DECL_RE.lastIndex = 0;
3574
+ let m;
3575
+ while ((m = ERLANG_CALLBACK_DECL_RE.exec(safe))) {
3576
+ const name = m[2].replace(/^'|'$/g, '');
3577
+ const arity = erlangArityAt(safe, m.index + m[0].length - 1);
3578
+ if (arity < 0)
3579
+ continue;
3580
+ const key = `${name}/${arity}`;
3581
+ const arr = declaringBehaviours.get(key);
3582
+ if (arr) {
3583
+ if (!arr.some((b) => b.id === behaviour.id))
3584
+ arr.push(behaviour);
3585
+ }
3586
+ else {
3587
+ declaringBehaviours.set(key, [behaviour]);
3588
+ }
3589
+ callbackNames.add(name);
3590
+ }
3591
+ }
3592
+ if (declaringBehaviours.size === 0)
3593
+ return [];
3594
+ // Implementer target lookup, lazy per (behaviour, fn, arity): implementers
3595
+ // come from the `implements` edges extraction resolved, and the target is
3596
+ // the implementer module's own exported `fn` node OF THE SITE'S ARITY —
3597
+ // function qualifiedNames carry arity (`mod::fn/2`, #1610), so the arity the
3598
+ // dispatch site used selects among same-named definitions.
3599
+ const targetCache = new Map();
3600
+ const qnArity = (qn) => {
3601
+ const m = /\/(\d{1,3})$/.exec(qn);
3602
+ return m ? Number(m[1]) : -1;
3603
+ };
3604
+ const targetsOf = (behaviour, fn, arity) => {
3605
+ const cacheKey = `${behaviour.id}#${fn}/${arity}`;
3606
+ let targets = targetCache.get(cacheKey);
3607
+ if (targets)
3608
+ return targets;
3609
+ targets = [];
3610
+ for (const e of queries.getIncomingEdges(behaviour.id, ['implements'])) {
3611
+ const impl = queries.getNodeById(e.source);
3612
+ if (!impl || impl.language !== 'erlang' || impl.kind !== 'namespace')
3613
+ continue;
3614
+ const fnNode = ctx
3615
+ .getNodesInFile(impl.filePath)
3616
+ .find((n) => n.kind === 'function' &&
3617
+ n.name === fn &&
3618
+ qnArity(n.qualifiedName) === arity &&
3619
+ n.isExported !== false);
3620
+ if (fnNode)
3621
+ targets.push(fnNode);
3622
+ }
3623
+ targetCache.set(cacheKey, targets);
3624
+ return targets;
3625
+ };
3626
+ // Pass 2 — dispatch sites. Only files containing a var-module call shape are
3627
+ // scanned in full.
3628
+ const edges = [];
3629
+ const seen = new Set();
3630
+ for (const file of ctx.getAllFiles()) {
3631
+ if ((++scannedFiles & 15) === 0)
3632
+ await onYield();
3633
+ if (!ERLANG_EXT.test(file))
3634
+ continue;
3635
+ const content = ctx.readFile(file);
3636
+ if (!content || !/[A-Z][A-Za-z0-9_@]*:[a-z]/.test(content))
3637
+ continue;
3638
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'erlang');
3639
+ const nodesInFile = ctx.getNodesInFile(file);
3640
+ ERLANG_DISPATCH_RE.lastIndex = 0;
3641
+ let m;
3642
+ while ((m = ERLANG_DISPATCH_RE.exec(safe))) {
3643
+ const fn = m[3];
3644
+ if (!callbackNames.has(fn))
3645
+ continue;
3646
+ const openIdx = m.index + m[0].length - 1;
3647
+ const arity = erlangArityAt(safe, openIdx);
3648
+ if (arity < 0)
3649
+ continue;
3650
+ const behaviours = declaringBehaviours.get(`${fn}/${arity}`);
3651
+ if (!behaviours || behaviours.length !== 1)
3652
+ continue; // unknown or ambiguous
3653
+ const behaviour = behaviours[0];
3654
+ const targets = targetsOf(behaviour, fn, arity);
3655
+ if (targets.length === 0 || targets.length > ERLANG_BEHAVIOUR_FANOUT_CAP)
3656
+ continue;
3657
+ const line = safe.slice(0, m.index).split('\n').length;
3658
+ const disp = (0, synth_utils_1.enclosingFn)(nodesInFile, line);
3659
+ if (!disp)
3660
+ continue;
3661
+ for (const target of targets) {
3662
+ if (target.id === disp.id)
3663
+ continue;
3664
+ const key = `${disp.id}>${target.id}`;
3665
+ if (seen.has(key))
3666
+ continue;
3667
+ seen.add(key);
3668
+ edges.push({
3669
+ source: disp.id,
3670
+ target: target.id,
3671
+ kind: 'calls',
3672
+ line,
3673
+ provenance: 'heuristic',
3674
+ metadata: {
3675
+ synthesizedBy: 'erlang-behaviour',
3676
+ via: `${behaviour.name}:${fn}/${arity}`,
3677
+ registeredAt: `${file}:${line}`,
3678
+ },
3679
+ });
3680
+ }
3681
+ }
3682
+ }
3683
+ return edges;
3684
+ }
3685
+ // ── Laravel events (PHP) ──────────────────────────────────────────────────────
3686
+ // Laravel decouples an event dispatch from its listener(s), linked by the EVENT CLASS:
3687
+ // // app/Events/PlaybackStarted.php + app/Listeners/UpdateLastfmNowPlaying.php
3688
+ // class UpdateLastfmNowPlaying { public function handle(PlaybackStarted $event) { … } }
3689
+ // // a controller / service — a DIFFERENT file
3690
+ // event(new PlaybackStarted($song, $user));
3691
+ // Bridge it: link the enclosing method at each `event(new XEvent(...))` site → every listener's
3692
+ // `handle` for XEvent. Listeners come from TWO registration mechanisms (both real, both needed):
3693
+ // (A) auto-discovery — a `handle(EventType $e)` typed first param (also splits a union A|B);
3694
+ // (B) the `protected $listen = [ XEvent::class => [Listener::class, …] ]` map in an
3695
+ // EventServiceProvider, which also covers a listener whose `handle()` is UNTYPED.
3696
+ // Only `event(new X)` is matched — queued JOBS dispatch via `::dispatch()` and their `handle()`
3697
+ // takes an injected service, never an event type, so jobs are excluded by construction.
3698
+ const LARAVEL_DISPATCH_RE = /\bevent\s*\(\s*new\s+\\?([A-Za-z_][\w\\]*)/g;
3699
+ const LARAVEL_PHP_EXT = /\.php$/;
3700
+ const LARAVEL_FANOUT_CAP = 200;
3701
+ // A `$listen` entry: `Event::class => [Listener::class, …]`, key/values as `::class` or strings.
3702
+ const LISTEN_ENTRY_RE = /(?:([A-Za-z_\\][\w\\]*)::class|'([^']+)'|"([^"]+)")\s*=>\s*\[([^\]]*)\]/g;
3703
+ const LISTEN_CLASS_RE = /(?:([A-Za-z_\\][\w\\]*)::class|'([^']+)'|"([^"]+)")/g;
3704
+ /** Short class name from a PHP reference: `\App\Events\Foo` / `App\Events::Foo` → `Foo`. */
3705
+ function phpSimpleName(s) {
3706
+ return s.replace(/^\\/, '').split('\\').pop().split('::').pop().trim();
3707
+ }
3708
+ /** The first-parameter class type(s) of a `handle(...)` declaration — union-split, short-named,
3709
+ * primitives dropped. `handle(A|B $e)` → [A, B]; `handle(string $x)` / `handle()` → []. */
3710
+ function laravelHandleEventTypes(decl) {
3711
+ const m = /function\s+handle\s*\(\s*(?:\.\.\.\s*)?(\??[A-Za-z_\\][\w\\|]*)\s+&?\s*(?:\.\.\.\s*)?\$/.exec(decl);
3712
+ if (!m)
3713
+ return [];
3714
+ return m[1]
3715
+ .replace(/^\?/, '')
3716
+ .split('|')
3717
+ .map((t) => phpSimpleName(t))
3718
+ .filter((t) => /^[A-Z]\w*$/.test(t));
3719
+ }
3720
+ /** From an opening `[`, the bracket-balanced body up to its matching `]`. */
3721
+ function phpArrayBody(src, openIdx) {
3722
+ let depth = 0;
3723
+ for (let i = openIdx; i < src.length; i++) {
3724
+ if (src[i] === '[')
3725
+ depth++;
3726
+ else if (src[i] === ']' && --depth === 0)
3727
+ return src.slice(openIdx + 1, i);
3728
+ }
3729
+ return null;
3730
+ }
3731
+ async function laravelEventEdges(ctx, onYield) {
3732
+ let scannedFiles = 0;
3733
+ // event short name → its listener `handle` methods (deduped by node id).
3734
+ const listeners = new Map();
3735
+ const add = (event, handle) => {
3736
+ let m = listeners.get(event);
3737
+ if (!m) {
3738
+ m = new Map();
3739
+ listeners.set(event, m);
3740
+ }
3741
+ m.set(handle.id, handle);
3742
+ };
3743
+ const handleOf = (cls) => ctx
3744
+ .getNodesInFile(cls.filePath)
3745
+ .find((n) => n.kind === 'method' && n.name === 'handle'
3746
+ && n.startLine >= cls.startLine && n.startLine <= (cls.endLine ?? cls.startLine)) ?? null;
3747
+ // Pass 1 — build the event→handle map from both registration mechanisms.
3748
+ for (const file of ctx.getAllFiles()) {
3749
+ if ((++scannedFiles & 15) === 0)
3750
+ await onYield();
3751
+ if (!LARAVEL_PHP_EXT.test(file))
3752
+ continue;
3753
+ const content = ctx.readFile(file);
3754
+ if (!content)
3755
+ continue;
3756
+ // (A) typed listener handles — node-driven, so a commented-out method can't leak in.
3757
+ if (content.includes('function handle')) {
3758
+ const lines = content.split('\n');
3759
+ for (const node of ctx.getNodesInFile(file)) {
3760
+ if (node.kind !== 'method' || node.name !== 'handle')
3761
+ continue;
3762
+ const decl = lines.slice(node.startLine - 1, node.startLine + 2).join('\n');
3763
+ for (const ev of laravelHandleEventTypes(decl))
3764
+ add(ev, node);
3765
+ }
3766
+ }
3767
+ // (B) the EventServiceProvider `$listen` map — parsed from comment-stripped source so a
3768
+ // fully-commented map (firefly's, on auto-discovery) contributes nothing.
3769
+ if (content.includes('$listen')) {
3770
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'php');
3771
+ const decl = safe.search(/\$listen\s*=\s*\[/);
3772
+ const body = decl >= 0 ? phpArrayBody(safe, safe.indexOf('[', decl)) : null;
3773
+ if (body) {
3774
+ LISTEN_ENTRY_RE.lastIndex = 0;
3775
+ let em;
3776
+ while ((em = LISTEN_ENTRY_RE.exec(body))) {
3777
+ const event = phpSimpleName(em[1] ?? em[2] ?? em[3] ?? '');
3778
+ LISTEN_CLASS_RE.lastIndex = 0;
3779
+ let lm;
3780
+ while ((lm = LISTEN_CLASS_RE.exec(em[4]))) {
3781
+ const ln = phpSimpleName(lm[1] ?? lm[2] ?? lm[3] ?? '');
3782
+ const cls = ctx.getNodesByName(ln).find((n) => n.kind === 'class' && handleOf(n));
3783
+ if (cls)
3784
+ add(event, handleOf(cls));
3785
+ }
3786
+ }
3787
+ }
3788
+ }
3789
+ }
3790
+ if (!listeners.size)
3791
+ return [];
3792
+ // Pass 2 — link each event(new X(...)) site → every listener of X.
3793
+ const edges = [];
3794
+ const seen = new Set();
3795
+ for (const file of ctx.getAllFiles()) {
3796
+ if ((++scannedFiles & 15) === 0)
3797
+ await onYield();
3798
+ if (!LARAVEL_PHP_EXT.test(file))
3799
+ continue;
3800
+ const content = ctx.readFile(file);
3801
+ if (!content || !content.includes('event('))
3802
+ continue;
3803
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'php');
3804
+ const nodesInFile = ctx.getNodesInFile(file);
3805
+ LARAVEL_DISPATCH_RE.lastIndex = 0;
3806
+ let m;
3807
+ let added = 0;
3808
+ while ((m = LARAVEL_DISPATCH_RE.exec(safe)) && added < LARAVEL_FANOUT_CAP) {
3809
+ const targets = listeners.get(phpSimpleName(m[1]));
3810
+ if (!targets)
3811
+ continue;
3812
+ const line = safe.slice(0, m.index).split('\n').length;
3813
+ const disp = (0, synth_utils_1.enclosingFn)(nodesInFile, line);
3814
+ if (!disp)
3815
+ continue;
3816
+ for (const target of targets.values()) {
3817
+ if (target.id === disp.id)
3818
+ continue;
3819
+ const key = `${disp.id}>${target.id}`;
3820
+ if (seen.has(key))
3821
+ continue;
3822
+ seen.add(key);
3823
+ edges.push({
3824
+ source: disp.id,
3825
+ target: target.id,
3826
+ kind: 'calls',
3827
+ line,
3828
+ provenance: 'heuristic',
3829
+ metadata: { synthesizedBy: 'laravel-event', via: phpSimpleName(m[1]), registeredAt: `${file}:${line}` },
3830
+ });
3831
+ added++;
3832
+ }
3833
+ }
3834
+ }
3835
+ return edges;
3836
+ }
3837
+ const ALWAYS = () => true;
3838
+ /**
3839
+ * The independent passes, in MERGE ORDER — the first-seen dedup in
3840
+ * synthesizeCallbackEdges follows this array, so reordering entries changes
3841
+ * which duplicate edge wins. The two Go pre-passes (cross-file method
3842
+ * `contains`, implicit `implements`) are NOT here: they persist before these
3843
+ * run because interfaceOverrideEdges reads their edges from the DB.
3844
+ */
3845
+ exports.SYNTH_PASSES = [
3846
+ { name: 'fieldEdges', gate: ALWAYS, run: (q, c, y) => fieldChannelEdges(q, c, y) },
3847
+ { name: 'closureCollEdges', gate: ALWAYS, run: (q, c, y) => closureCollectionEdges(q, c, y) },
3848
+ // Cross-tier channels — a client's `fetch('/api/x')` onto its own route,
3849
+ // a queue job onto its consumer, a bus / socket event onto its handler.
3850
+ // Before the in-process emitter pass: the same (source, target) pair
3851
+ // keeps the more specific edge — the one that says which tier it crosses.
3852
+ { name: 'tierEdges', gate: (has) => has(...JS_FAMILY), run: (_q, c, y) => (0, tier_synthesizer_1.crossTierEdges)(c, y) },
3853
+ { name: 'emitterEdges', gate: ALWAYS, run: (_q, c, y) => eventEmitterEdges(c, y) },
3854
+ { name: 'renderEdges', gate: ALWAYS, run: (q, c, y) => reactRenderEdges(q, c, y) },
3855
+ { name: 'jsxEdges', gate: (has) => has(...JS_FAMILY), run: (_q, c, y) => reactJsxChildEdges(c, y) },
3856
+ { name: 'vueEdges', gate: (has) => has('vue'), run: (_q, c, y) => vueTemplateEdges(c, y) },
3857
+ { name: 'svelteKitEdges', gate: (has) => has('svelte'), run: (_q, c, y) => svelteKitLoadEdges(c, y) },
3858
+ { name: 'pascalEdges', gate: ALWAYS, run: (_q, c, y) => pascalFormEdges(c, y) },
3859
+ { name: 'flutterEdges', gate: (has) => has('dart'), run: (q, c, y) => flutterBuildEdges(q, c, y) },
3860
+ { name: 'arkuiStateEdges', gate: (has) => has('arkts'), run: (q, c, y) => arkuiStateBuildEdges(q, c, y) },
3861
+ { name: 'arkuiEmitter', gate: (has) => has('arkts'), run: (_q, c, y) => arkuiEmitterEdges(c, y) },
3862
+ { name: 'arkuiRoutes', gate: (has) => has('arkts'), run: (_q, c, y) => arkuiRouterEdges(c, y) },
3863
+ { name: 'cppEdges', gate: (has) => has('cpp'), run: (q, _c, y) => cppOverrideEdges(q, y) },
3864
+ {
3865
+ name: 'ifaceEdges',
3866
+ gate: (has) => has('java', 'kotlin', 'csharp', 'swift', 'scala', 'go', 'rust', 'arkts', ...JS_FAMILY),
3867
+ run: (q, _c, y) => interfaceOverrideEdges(q, y),
3868
+ },
3869
+ { name: 'kotlinExpectActual', gate: (has) => has('kotlin'), run: (q, _c, y) => kotlinExpectActualEdges(q, y) },
3870
+ { name: 'goGrpcEdges', gate: (has) => has('go'), run: (q, _c, y) => goGrpcStubImplEdges(q, y) },
3871
+ { name: 'rnEventEdgesList', gate: (has) => has(...JS_FAMILY), run: (_q, c, y) => rnEventEdges(c, y) },
3872
+ { name: 'fabricNativeEdges', gate: ALWAYS, run: (_q, c, y) => fabricNativeImplEdges(c, y) },
3873
+ // Expo module nodes (`expo-module:` ids) are emitted only from .swift/.kt
3874
+ // files, and a pair needs BOTH platforms — so without both languages the
3875
+ // pass's only collection loop is provably empty (it was streaming every
3876
+ // method row on pure-Java repos to find nothing).
3877
+ { name: 'expoXPlatEdges', gate: (has) => has('swift') && has('kotlin'), run: (q, _c, y) => expoCrossPlatformEdges(q, y) },
3878
+ // An RN cross-platform edge requires a JS-language caller on the native
3879
+ // method (`isBridge`) — no JS-family files means no JS-language nodes, so
3880
+ // the result is provably empty.
3881
+ { name: 'rnXPlatEdges', gate: (has) => has(...JS_FAMILY), run: (q, _c, y) => rnCrossPlatformEdges(q, y) },
3882
+ {
3883
+ name: 'mybatisEdges',
3884
+ gate: (has) => has('java', 'kotlin') && has('xml'),
3885
+ run: (q, _c, y) => mybatisJavaXmlEdges(q, y),
3886
+ },
3887
+ { name: 'ginEdges', gate: (has) => has('go'), run: (q, c, y) => ginMiddlewareChainEdges(q, c, y) },
3888
+ { name: 'thunkEdges', gate: (has) => has(...JS_FAMILY), run: (q, c, y) => reduxThunkEdges(q, c, y) },
3889
+ { name: 'registryEdges', gate: ALWAYS, run: (_q, c, y) => objectRegistryEdges(c, y) },
3890
+ { name: 'rtkEdges', gate: (has) => has(...JS_FAMILY), run: (q, c, y) => rtkQueryEdges(q, c, y) },
3891
+ { name: 'piniaEdges', gate: (has) => has('vue', ...JS_FAMILY), run: (_q, c, y) => piniaStoreEdges(c, y) },
3892
+ { name: 'vuexEdges', gate: (has) => has('vue', ...JS_FAMILY), run: (_q, c, y) => vuexDispatchEdges(c, y) },
3893
+ { name: 'celeryEdges', gate: (has) => has('python'), run: (_q, c, y) => celeryDispatchEdges(c, y) },
3894
+ { name: 'springEdges', gate: (has) => has('java'), run: (_q, c, y) => springEventEdges(c, y) },
3895
+ { name: 'mediatrEdges', gate: (has) => has('csharp'), run: (_q, c, y) => mediatrDispatchEdges(c, y) },
3896
+ { name: 'sidekiqEdges', gate: (has) => has('ruby'), run: (_q, c, y) => sidekiqDispatchEdges(c, y) },
3897
+ {
3898
+ name: 'erlangBehaviourEdges',
3899
+ gate: (has) => has('erlang'),
3900
+ run: (q, c, y) => erlangBehaviourDispatchEdges(q, c, y),
3901
+ },
3902
+ { name: 'laravelEdges', gate: (has) => has('php'), run: (_q, c, y) => laravelEventEdges(c, y) },
3903
+ {
3904
+ name: 'cFnPtrEdges',
3905
+ gate: (has) => has('c', 'cpp'),
3906
+ run: (q, c, y, sub) => (0, c_fnptr_synthesizer_1.cFnPointerDispatchEdges)(q, c, y, sub),
3907
+ },
3908
+ { name: 'goframeEdges', gate: (has) => has('go'), run: (_q, c, y) => (0, goframe_synthesizer_1.goframeRouteEdges)(c, y) },
3909
+ // `router.push(await helper())` — the helper's return literals are the screens.
3910
+ { name: 'expoRouterReturnEdges', gate: (has) => has(...JS_FAMILY), run: (_q, c, y) => (0, expo_router_synthesizer_1.expoRouterReturnEdges)(c, y) },
3911
+ // `<Link href="/x">` / an internal `<a href>` — markup, not a call; the component navigates.
3912
+ { name: 'nextLinkEdges', gate: (has) => has(...JS_FAMILY), run: (_q, c, y) => (0, next_router_synthesizer_1.nextLinkEdges)(c, y) },
3913
+ { name: 'reactRouterLinkEdges', gate: (has) => has(...JS_FAMILY), run: (_q, c, y) => (0, react_router_synthesizer_1.reactRouterLinkEdges)(c, y) },
3914
+ { name: 'tanstackLinkEdges', gate: (has) => has(...JS_FAMILY), run: (_q, c, y) => (0, tanstack_router_synthesizer_1.tanstackLinkEdges)(c, y) },
3915
+ { name: 'vueRouterLinkEdges', gate: (has) => has('vue', ...JS_FAMILY), run: (_q, c, y) => (0, vue_router_synthesizer_1.vueRouterLinkEdges)(c, y) },
3916
+ { name: 'svelteKitPageEdges', gate: (has) => has('svelte'), run: (_q, c, y) => (0, sveltekit_synthesizer_1.svelteKitPageComponentEdges)(c, y) },
3917
+ { name: 'svelteKitLinkEdges', gate: (has) => has('svelte'), run: (_q, c, y) => (0, sveltekit_synthesizer_1.svelteKitLinkEdges)(c, y) },
3918
+ { name: 'nixOptionEdges', gate: (has) => has('nix'), run: (q, _c, y) => nixOptionPathEdges(q, y) },
3919
+ ];
3920
+ /** Fixed non-registry steps: goMethodContains, goImplements, dedupe-merge, insertMergedEdges. */
3921
+ const FIXED_SYNTH_STEPS = 4;
3922
+ exports.SYNTH_PROGRESS_STEPS = exports.SYNTH_PASSES.length + FIXED_SYNTH_STEPS;
3923
+ async function synthesizeCallbackEdges(queries, ctx, onProgress,
3924
+ // A live resolver pool to fan the independent passes across (structural type
3925
+ // so this file never imports the pool — resolver-worker imports THIS file).
3926
+ // Null/omitted → the sequential path, byte-identical to the pool path.
3927
+ pool,
3928
+ // WAL-valve writer backstop (WalCheckpointValve.backpressure), called at
3929
+ // pool-idle points in the edge-insert loops below — the passes themselves
3930
+ // only read; every write in this function happens with the pool idle.
3931
+ backpressure) {
3932
+ // Each sub-pass below is a whole-graph scan, and there are ~30 of them, all
3933
+ // running synchronously on the indexer's main thread. Their AGGREGATE can run
3934
+ // for well over a minute on a large repo — long enough for the #850 liveness
3935
+ // watchdog to SIGKILL the process mid-index (#1091), since its heartbeat lives
3936
+ // on this same thread. Yield between passes so the heartbeat can fire; a pass
3937
+ // that itself hangs (a real wedge) never reaches the next yield, so the
3938
+ // watchdog still catches that. See ./cooperative-yield.
3939
+ const yieldToLoop = (0, cooperative_yield_1.createYielder)();
3940
+ // Synthesis runs AFTER the resolution progress bar reaches 100%, so without
3941
+ // its own progress the UI freezes at "Resolving refs 100%" for the whole
3942
+ // tail — long enough on big repos that users conclude the index hung and
3943
+ // kill it. Report each completed pass; the caller surfaces it as its own
3944
+ // progress phase. Emit 0/total up front so the phase flips immediately.
3945
+ // Emissions are throttled to whole-percent movement (each consumes a UI
3946
+ // message); values may be fractional steps from within-pass reporting.
3947
+ let passesDone = 0;
3948
+ let lastPct = -1;
3949
+ const emit = (value) => {
3950
+ if (!onProgress)
3951
+ return;
3952
+ const v = Math.min(value, exports.SYNTH_PROGRESS_STEPS);
3953
+ const pct = Math.floor((v / exports.SYNTH_PROGRESS_STEPS) * 100);
3954
+ if (pct === lastPct)
3955
+ return;
3956
+ lastPct = pct;
3957
+ onProgress(v, exports.SYNTH_PROGRESS_STEPS);
3958
+ };
3959
+ // A single long pass otherwise parks the bar between steps; a pass that
3960
+ // takes this callback reports a 0..1 fraction of its own work, surfaced
3961
+ // here as fractional progress within its step.
3962
+ const subProgress = (fraction) => emit(passesDone + Math.max(0, Math.min(fraction, 1)));
3963
+ emit(0);
3964
+ // Per-pass wall-clock timing to stderr, opt-in via CODEGRAPH_SYNTH_TIMINGS
3965
+ // (=1: passes over 250ms; =all: every pass). This is the diagnostic that
3966
+ // located both the #1091/#1122 watchdog stalls and the #1212 OOM — keep it.
3967
+ const markT = { t: Date.now() };
3968
+ const __mark = (label) => {
3969
+ const now = Date.now();
3970
+ const dt = now - markT.t;
3971
+ markT.t = now;
3972
+ if (process.env.CODEGRAPH_SYNTH_TIMINGS && (dt > 250 || process.env.CODEGRAPH_SYNTH_TIMINGS === 'all')) {
3973
+ console.error(`[synth-timing] ${label}: ${dt}ms`);
3974
+ }
3975
+ passesDone++;
3976
+ emit(passesDone);
3977
+ };
3978
+ // Language gating: one indexed DISTINCT over the files table lets a pass
3979
+ // whose own filters reference a specific language/extension be skipped
3980
+ // outright when the project has no such files — its result is provably
3981
+ // empty, so skipping is behavior-identical and the cost drops to zero
3982
+ // (the Kotlin pass was the OOM culprit on the pure-C Linux kernel, #1212).
3983
+ // Passes without an explicit language filter always run.
3984
+ const langs = queries.getDistinctFileLanguages();
3985
+ const has = (...ls) => ls.some((l) => langs.has(l));
3986
+ const NONE = [];
3987
+ // Cross-file Go method→type `contains` edges must be synthesized AND persisted
3988
+ // FIRST: a method declared in a different file from its receiver type is
3989
+ // otherwise orphaned from the struct, and goImplementsEdges (next) derives a
3990
+ // struct's method set from its `contains` edges — so without this it would
3991
+ // under-count the interfaces a cross-file struct satisfies. (#583)
3992
+ // Writer-side WAL backstop for the insert loops here (see the param doc):
3993
+ // one fstat when under the valve's hard cap, a parked full backfill past it.
3994
+ const foldIfOver = async () => {
3995
+ const bp = backpressure?.();
3996
+ if (bp)
3997
+ await bp;
3998
+ };
3999
+ const goMethodContains = has('go') ? await goCrossFileMethodContainsEdges(queries, yieldToLoop) : NONE;
4000
+ for (let i = 0; i < goMethodContains.length; i += 2000) {
4001
+ queries.insertEdges(goMethodContains.slice(i, i + 2000));
4002
+ await yieldToLoop();
4003
+ await foldIfOver();
4004
+ }
4005
+ await yieldToLoop();
4006
+ __mark('goMethodContains');
4007
+ // Go implicit `implements` edges must be synthesized AND persisted next: the
4008
+ // interface-dispatch bridge below reads `implements` edges from the DB, and
4009
+ // Go has none statically. (Other languages already have static implements
4010
+ // edges from extraction, so they don't need this pre-pass.)
4011
+ const goImpl = has('go') ? await goImplementsEdges(queries, yieldToLoop) : NONE;
4012
+ for (let i = 0; i < goImpl.length; i += 2000) {
4013
+ queries.insertEdges(goImpl.slice(i, i + 2000));
4014
+ await yieldToLoop();
4015
+ await foldIfOver();
4016
+ }
4017
+ await yieldToLoop();
4018
+ __mark('goImplements');
4019
+ // Run the independent passes (see SYNTH_PASSES). Their results are merged in
4020
+ // REGISTRY ORDER below regardless of execution order, and none of their edges
4021
+ // persist until that merge — so every pass sees the same committed
4022
+ // post-resolution DB state whether it runs sequentially here or on a resolver
4023
+ // pool worker. With a live pool (already booted on ≥150k-ref repos), passes
4024
+ // fan out across its read-only workers and the per-pass wall-clock comes from
4025
+ // the worker; a pass that fails on a worker falls back to running on the main
4026
+ // thread, so a worker crash isolates to a retry instead of failing synthesis.
4027
+ const passEdges = new Array(exports.SYNTH_PASSES.length).fill(NONE);
4028
+ const markPass = (label, dt) => {
4029
+ if (process.env.CODEGRAPH_SYNTH_TIMINGS && (dt > 250 || process.env.CODEGRAPH_SYNTH_TIMINGS === 'all')) {
4030
+ console.error(`[synth-timing] ${label}: ${dt}ms`);
4031
+ }
4032
+ passesDone++;
4033
+ emit(passesDone);
4034
+ };
4035
+ const runPassOnMain = async (i) => {
4036
+ const pass = exports.SYNTH_PASSES[i];
4037
+ const t0 = Date.now();
4038
+ passEdges[i] = await pass.run(queries, ctx, yieldToLoop, subProgress);
4039
+ await yieldToLoop();
4040
+ markPass(pass.name, Date.now() - t0);
4041
+ };
4042
+ const gatedIn = [];
4043
+ for (let i = 0; i < exports.SYNTH_PASSES.length; i++) {
4044
+ if (exports.SYNTH_PASSES[i].gate(has))
4045
+ gatedIn.push(i);
4046
+ else
4047
+ markPass(exports.SYNTH_PASSES[i].name, 0);
4048
+ }
4049
+ // Above this node count, a pass that OOM-killed its worker must NOT be
4050
+ // retried on the main thread — the retry would OOM the whole process and
4051
+ // take the index with it (the #1212 failure class). Below it, a worker
4052
+ // failure is more likely a transient crash than a memory ceiling, and the
4053
+ // main-thread retry keeps coverage. Skipping loses only that pass's
4054
+ // synthesized edges; the index still completes.
4055
+ const MAIN_RETRY_MAX_NODES = 1_500_000;
4056
+ const graphNodes = queries.getNodeAndEdgeCount().nodes;
4057
+ if (pool && gatedIn.length > 1) {
4058
+ await Promise.all(gatedIn.map(async (i) => {
4059
+ const pass = exports.SYNTH_PASSES[i];
4060
+ try {
4061
+ const out = await pool.runSynthPass(pass.name);
4062
+ passEdges[i] = out.edges;
4063
+ markPass(pass.name, out.ms);
4064
+ }
4065
+ catch (err) {
4066
+ if (graphNodes > MAIN_RETRY_MAX_NODES) {
4067
+ // Worker died at a scale where the main-thread retry is a process
4068
+ // OOM risk: skip the pass, keep the index alive, and say so.
4069
+ console.error(`[synthesis] pass '${pass.name}' failed on a worker at ${graphNodes} nodes — skipped (edges from this pass are absent): ${err instanceof Error ? err.message : String(err)}`);
4070
+ markPass(`${pass.name} (skipped at scale)`, 0);
4071
+ return;
4072
+ }
4073
+ // Worker-side failure (crash, OOM, unknown pass after a version
4074
+ // mismatch): retry this one pass on the main thread.
4075
+ await runPassOnMain(i);
4076
+ }
4077
+ }));
4078
+ }
4079
+ else {
4080
+ for (const i of gatedIn) {
4081
+ await runPassOnMain(i);
4082
+ }
4083
+ }
4084
+ const merged = [];
4085
+ const seen = new Set();
4086
+ for (const e of passEdges.flat()) {
4087
+ const key = `${e.source}>${e.target}`;
4088
+ if (seen.has(key))
4089
+ continue;
4090
+ seen.add(key);
4091
+ merged.push(e);
4092
+ }
4093
+ __mark('dedupe-merge');
4094
+ // Chunked insert with yields: on the Linux kernel the merged synthesized
4095
+ // edge set is ~275k rows, and one transaction for all of them was a 20s
4096
+ // unyielded main-thread span (#1212 follow-up) — the last one in the tail.
4097
+ for (let i = 0; i < merged.length; i += 2000) {
4098
+ queries.insertEdges(merged.slice(i, i + 2000));
4099
+ await yieldToLoop();
4100
+ await foldIfOver();
4101
+ }
4102
+ __mark('insertMergedEdges');
4103
+ return merged.length + goImpl.length + goMethodContains.length;
4104
+ }
4105
+ //# sourceMappingURL=callback-synthesizer.js.map