homegraph 0.9.9

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 (549) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +709 -0
  3. package/dist/bin/codegraph.d.ts +26 -0
  4. package/dist/bin/codegraph.d.ts.map +1 -0
  5. package/dist/bin/codegraph.js +1572 -0
  6. package/dist/bin/codegraph.js.map +1 -0
  7. package/dist/bin/homegraph.d.ts +26 -0
  8. package/dist/bin/homegraph.d.ts.map +1 -0
  9. package/dist/bin/homegraph.js +1572 -0
  10. package/dist/bin/homegraph.js.map +1 -0
  11. package/dist/bin/node-version-check.d.ts +37 -0
  12. package/dist/bin/node-version-check.d.ts.map +1 -0
  13. package/dist/bin/node-version-check.js +79 -0
  14. package/dist/bin/node-version-check.js.map +1 -0
  15. package/dist/bin/uninstall.d.ts +14 -0
  16. package/dist/bin/uninstall.d.ts.map +1 -0
  17. package/dist/bin/uninstall.js +36 -0
  18. package/dist/bin/uninstall.js.map +1 -0
  19. package/dist/context/formatter.d.ts +30 -0
  20. package/dist/context/formatter.d.ts.map +1 -0
  21. package/dist/context/formatter.js +263 -0
  22. package/dist/context/formatter.js.map +1 -0
  23. package/dist/context/index.d.ts +119 -0
  24. package/dist/context/index.d.ts.map +1 -0
  25. package/dist/context/index.js +1289 -0
  26. package/dist/context/index.js.map +1 -0
  27. package/dist/context/markers.d.ts +19 -0
  28. package/dist/context/markers.d.ts.map +1 -0
  29. package/dist/context/markers.js +22 -0
  30. package/dist/context/markers.js.map +1 -0
  31. package/dist/db/index.d.ts +101 -0
  32. package/dist/db/index.d.ts.map +1 -0
  33. package/dist/db/index.js +250 -0
  34. package/dist/db/index.js.map +1 -0
  35. package/dist/db/migrations.d.ts +44 -0
  36. package/dist/db/migrations.d.ts.map +1 -0
  37. package/dist/db/migrations.js +131 -0
  38. package/dist/db/migrations.js.map +1 -0
  39. package/dist/db/queries.d.ts +386 -0
  40. package/dist/db/queries.d.ts.map +1 -0
  41. package/dist/db/queries.js +1562 -0
  42. package/dist/db/queries.js.map +1 -0
  43. package/dist/db/schema.sql +151 -0
  44. package/dist/db/sqlite-adapter.d.ts +53 -0
  45. package/dist/db/sqlite-adapter.d.ts.map +1 -0
  46. package/dist/db/sqlite-adapter.js +117 -0
  47. package/dist/db/sqlite-adapter.js.map +1 -0
  48. package/dist/directory.d.ts +57 -0
  49. package/dist/directory.d.ts.map +1 -0
  50. package/dist/directory.js +253 -0
  51. package/dist/directory.js.map +1 -0
  52. package/dist/errors.d.ts +136 -0
  53. package/dist/errors.d.ts.map +1 -0
  54. package/dist/errors.js +219 -0
  55. package/dist/errors.js.map +1 -0
  56. package/dist/extraction/arkanalyzer-extractor.d.ts +6 -0
  57. package/dist/extraction/arkanalyzer-extractor.d.ts.map +1 -0
  58. package/dist/extraction/arkanalyzer-extractor.js +11 -0
  59. package/dist/extraction/arkanalyzer-extractor.js.map +1 -0
  60. package/dist/extraction/arkanalyzer-project.d.ts +20 -0
  61. package/dist/extraction/arkanalyzer-project.d.ts.map +1 -0
  62. package/dist/extraction/arkanalyzer-project.js +596 -0
  63. package/dist/extraction/arkanalyzer-project.js.map +1 -0
  64. package/dist/extraction/context.d.ts +12 -0
  65. package/dist/extraction/context.d.ts.map +1 -0
  66. package/dist/extraction/context.js +29 -0
  67. package/dist/extraction/context.js.map +1 -0
  68. package/dist/extraction/dfm-extractor.d.ts +31 -0
  69. package/dist/extraction/dfm-extractor.d.ts.map +1 -0
  70. package/dist/extraction/dfm-extractor.js +151 -0
  71. package/dist/extraction/dfm-extractor.js.map +1 -0
  72. package/dist/extraction/extraction-version.d.ts +25 -0
  73. package/dist/extraction/extraction-version.d.ts.map +1 -0
  74. package/dist/extraction/extraction-version.js +28 -0
  75. package/dist/extraction/extraction-version.js.map +1 -0
  76. package/dist/extraction/generated-detection.d.ts +30 -0
  77. package/dist/extraction/generated-detection.d.ts.map +1 -0
  78. package/dist/extraction/generated-detection.js +80 -0
  79. package/dist/extraction/generated-detection.js.map +1 -0
  80. package/dist/extraction/grammars.d.ts +108 -0
  81. package/dist/extraction/grammars.d.ts.map +1 -0
  82. package/dist/extraction/grammars.js +465 -0
  83. package/dist/extraction/grammars.js.map +1 -0
  84. package/dist/extraction/index.d.ts +138 -0
  85. package/dist/extraction/index.d.ts.map +1 -0
  86. package/dist/extraction/index.js +1396 -0
  87. package/dist/extraction/index.js.map +1 -0
  88. package/dist/extraction/languages/arkts.d.ts +18 -0
  89. package/dist/extraction/languages/arkts.d.ts.map +1 -0
  90. package/dist/extraction/languages/arkts.js +878 -0
  91. package/dist/extraction/languages/arkts.js.map +1 -0
  92. package/dist/extraction/languages/c-cpp.d.ts +4 -0
  93. package/dist/extraction/languages/c-cpp.d.ts.map +1 -0
  94. package/dist/extraction/languages/c-cpp.js +173 -0
  95. package/dist/extraction/languages/c-cpp.js.map +1 -0
  96. package/dist/extraction/languages/csharp.d.ts +25 -0
  97. package/dist/extraction/languages/csharp.d.ts.map +1 -0
  98. package/dist/extraction/languages/csharp.js +120 -0
  99. package/dist/extraction/languages/csharp.js.map +1 -0
  100. package/dist/extraction/languages/dart.d.ts +3 -0
  101. package/dist/extraction/languages/dart.d.ts.map +1 -0
  102. package/dist/extraction/languages/dart.js +192 -0
  103. package/dist/extraction/languages/dart.js.map +1 -0
  104. package/dist/extraction/languages/go.d.ts +3 -0
  105. package/dist/extraction/languages/go.d.ts.map +1 -0
  106. package/dist/extraction/languages/go.js +74 -0
  107. package/dist/extraction/languages/go.js.map +1 -0
  108. package/dist/extraction/languages/index.d.ts +10 -0
  109. package/dist/extraction/languages/index.d.ts.map +1 -0
  110. package/dist/extraction/languages/index.js +51 -0
  111. package/dist/extraction/languages/index.js.map +1 -0
  112. package/dist/extraction/languages/java.d.ts +3 -0
  113. package/dist/extraction/languages/java.d.ts.map +1 -0
  114. package/dist/extraction/languages/java.js +74 -0
  115. package/dist/extraction/languages/java.js.map +1 -0
  116. package/dist/extraction/languages/javascript.d.ts +3 -0
  117. package/dist/extraction/languages/javascript.d.ts.map +1 -0
  118. package/dist/extraction/languages/javascript.js +90 -0
  119. package/dist/extraction/languages/javascript.js.map +1 -0
  120. package/dist/extraction/languages/kotlin.d.ts +3 -0
  121. package/dist/extraction/languages/kotlin.d.ts.map +1 -0
  122. package/dist/extraction/languages/kotlin.js +285 -0
  123. package/dist/extraction/languages/kotlin.js.map +1 -0
  124. package/dist/extraction/languages/lua.d.ts +3 -0
  125. package/dist/extraction/languages/lua.d.ts.map +1 -0
  126. package/dist/extraction/languages/lua.js +150 -0
  127. package/dist/extraction/languages/lua.js.map +1 -0
  128. package/dist/extraction/languages/luau.d.ts +3 -0
  129. package/dist/extraction/languages/luau.d.ts.map +1 -0
  130. package/dist/extraction/languages/luau.js +37 -0
  131. package/dist/extraction/languages/luau.js.map +1 -0
  132. package/dist/extraction/languages/objc.d.ts +3 -0
  133. package/dist/extraction/languages/objc.d.ts.map +1 -0
  134. package/dist/extraction/languages/objc.js +133 -0
  135. package/dist/extraction/languages/objc.js.map +1 -0
  136. package/dist/extraction/languages/pascal.d.ts +3 -0
  137. package/dist/extraction/languages/pascal.d.ts.map +1 -0
  138. package/dist/extraction/languages/pascal.js +66 -0
  139. package/dist/extraction/languages/pascal.js.map +1 -0
  140. package/dist/extraction/languages/php.d.ts +3 -0
  141. package/dist/extraction/languages/php.d.ts.map +1 -0
  142. package/dist/extraction/languages/php.js +120 -0
  143. package/dist/extraction/languages/php.js.map +1 -0
  144. package/dist/extraction/languages/python.d.ts +3 -0
  145. package/dist/extraction/languages/python.d.ts.map +1 -0
  146. package/dist/extraction/languages/python.js +56 -0
  147. package/dist/extraction/languages/python.js.map +1 -0
  148. package/dist/extraction/languages/ruby.d.ts +3 -0
  149. package/dist/extraction/languages/ruby.d.ts.map +1 -0
  150. package/dist/extraction/languages/ruby.js +149 -0
  151. package/dist/extraction/languages/ruby.js.map +1 -0
  152. package/dist/extraction/languages/rust.d.ts +3 -0
  153. package/dist/extraction/languages/rust.d.ts.map +1 -0
  154. package/dist/extraction/languages/rust.js +113 -0
  155. package/dist/extraction/languages/rust.js.map +1 -0
  156. package/dist/extraction/languages/scala.d.ts +3 -0
  157. package/dist/extraction/languages/scala.d.ts.map +1 -0
  158. package/dist/extraction/languages/scala.js +174 -0
  159. package/dist/extraction/languages/scala.js.map +1 -0
  160. package/dist/extraction/languages/swift.d.ts +3 -0
  161. package/dist/extraction/languages/swift.d.ts.map +1 -0
  162. package/dist/extraction/languages/swift.js +91 -0
  163. package/dist/extraction/languages/swift.js.map +1 -0
  164. package/dist/extraction/languages/typescript.d.ts +3 -0
  165. package/dist/extraction/languages/typescript.d.ts.map +1 -0
  166. package/dist/extraction/languages/typescript.js +129 -0
  167. package/dist/extraction/languages/typescript.js.map +1 -0
  168. package/dist/extraction/liquid-extractor.d.ts +59 -0
  169. package/dist/extraction/liquid-extractor.d.ts.map +1 -0
  170. package/dist/extraction/liquid-extractor.js +354 -0
  171. package/dist/extraction/liquid-extractor.js.map +1 -0
  172. package/dist/extraction/mybatis-extractor.d.ts +48 -0
  173. package/dist/extraction/mybatis-extractor.d.ts.map +1 -0
  174. package/dist/extraction/mybatis-extractor.js +198 -0
  175. package/dist/extraction/mybatis-extractor.js.map +1 -0
  176. package/dist/extraction/parse-worker.d.ts +8 -0
  177. package/dist/extraction/parse-worker.d.ts.map +1 -0
  178. package/dist/extraction/parse-worker.js +94 -0
  179. package/dist/extraction/parse-worker.js.map +1 -0
  180. package/dist/extraction/razor-extractor.d.ts +42 -0
  181. package/dist/extraction/razor-extractor.d.ts.map +1 -0
  182. package/dist/extraction/razor-extractor.js +285 -0
  183. package/dist/extraction/razor-extractor.js.map +1 -0
  184. package/dist/extraction/svelte-extractor.d.ts +56 -0
  185. package/dist/extraction/svelte-extractor.d.ts.map +1 -0
  186. package/dist/extraction/svelte-extractor.js +272 -0
  187. package/dist/extraction/svelte-extractor.js.map +1 -0
  188. package/dist/extraction/tree-sitter-helpers.d.ts +28 -0
  189. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -0
  190. package/dist/extraction/tree-sitter-helpers.js +103 -0
  191. package/dist/extraction/tree-sitter-helpers.js.map +1 -0
  192. package/dist/extraction/tree-sitter-types.d.ts +210 -0
  193. package/dist/extraction/tree-sitter-types.d.ts.map +1 -0
  194. package/dist/extraction/tree-sitter-types.js +10 -0
  195. package/dist/extraction/tree-sitter-types.js.map +1 -0
  196. package/dist/extraction/tree-sitter.d.ts +455 -0
  197. package/dist/extraction/tree-sitter.d.ts.map +1 -0
  198. package/dist/extraction/tree-sitter.js +4089 -0
  199. package/dist/extraction/tree-sitter.js.map +1 -0
  200. package/dist/extraction/vue-extractor.d.ts +51 -0
  201. package/dist/extraction/vue-extractor.d.ts.map +1 -0
  202. package/dist/extraction/vue-extractor.js +251 -0
  203. package/dist/extraction/vue-extractor.js.map +1 -0
  204. package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  205. package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
  206. package/dist/extraction/wasm/tree-sitter-luau.wasm +0 -0
  207. package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  208. package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
  209. package/dist/extraction/wasm-runtime-flags.d.ts +38 -0
  210. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -0
  211. package/dist/extraction/wasm-runtime-flags.js +106 -0
  212. package/dist/extraction/wasm-runtime-flags.js.map +1 -0
  213. package/dist/graph/index.d.ts +8 -0
  214. package/dist/graph/index.d.ts.map +1 -0
  215. package/dist/graph/index.js +13 -0
  216. package/dist/graph/index.js.map +1 -0
  217. package/dist/graph/queries.d.ts +106 -0
  218. package/dist/graph/queries.d.ts.map +1 -0
  219. package/dist/graph/queries.js +339 -0
  220. package/dist/graph/queries.js.map +1 -0
  221. package/dist/graph/traversal.d.ts +127 -0
  222. package/dist/graph/traversal.d.ts.map +1 -0
  223. package/dist/graph/traversal.js +531 -0
  224. package/dist/graph/traversal.js.map +1 -0
  225. package/dist/index.d.ts +522 -0
  226. package/dist/index.d.ts.map +1 -0
  227. package/dist/index.js +947 -0
  228. package/dist/index.js.map +1 -0
  229. package/dist/installer/config-writer.d.ts +28 -0
  230. package/dist/installer/config-writer.d.ts.map +1 -0
  231. package/dist/installer/config-writer.js +91 -0
  232. package/dist/installer/config-writer.js.map +1 -0
  233. package/dist/installer/index.d.ts +100 -0
  234. package/dist/installer/index.d.ts.map +1 -0
  235. package/dist/installer/index.js +497 -0
  236. package/dist/installer/index.js.map +1 -0
  237. package/dist/installer/instructions-template.d.ts +18 -0
  238. package/dist/installer/instructions-template.d.ts.map +1 -0
  239. package/dist/installer/instructions-template.js +21 -0
  240. package/dist/installer/instructions-template.js.map +1 -0
  241. package/dist/installer/targets/antigravity.d.ts +57 -0
  242. package/dist/installer/targets/antigravity.d.ts.map +1 -0
  243. package/dist/installer/targets/antigravity.js +308 -0
  244. package/dist/installer/targets/antigravity.js.map +1 -0
  245. package/dist/installer/targets/claude.d.ts +56 -0
  246. package/dist/installer/targets/claude.d.ts.map +1 -0
  247. package/dist/installer/targets/claude.js +388 -0
  248. package/dist/installer/targets/claude.js.map +1 -0
  249. package/dist/installer/targets/codex.d.ts +18 -0
  250. package/dist/installer/targets/codex.d.ts.map +1 -0
  251. package/dist/installer/targets/codex.js +187 -0
  252. package/dist/installer/targets/codex.js.map +1 -0
  253. package/dist/installer/targets/cursor.d.ts +35 -0
  254. package/dist/installer/targets/cursor.d.ts.map +1 -0
  255. package/dist/installer/targets/cursor.js +254 -0
  256. package/dist/installer/targets/cursor.js.map +1 -0
  257. package/dist/installer/targets/gemini.d.ts +26 -0
  258. package/dist/installer/targets/gemini.d.ts.map +1 -0
  259. package/dist/installer/targets/gemini.js +167 -0
  260. package/dist/installer/targets/gemini.js.map +1 -0
  261. package/dist/installer/targets/hermes.d.ts +18 -0
  262. package/dist/installer/targets/hermes.d.ts.map +1 -0
  263. package/dist/installer/targets/hermes.js +359 -0
  264. package/dist/installer/targets/hermes.js.map +1 -0
  265. package/dist/installer/targets/kiro.d.ts +27 -0
  266. package/dist/installer/targets/kiro.d.ts.map +1 -0
  267. package/dist/installer/targets/kiro.js +178 -0
  268. package/dist/installer/targets/kiro.js.map +1 -0
  269. package/dist/installer/targets/opencode.d.ts +30 -0
  270. package/dist/installer/targets/opencode.d.ts.map +1 -0
  271. package/dist/installer/targets/opencode.js +237 -0
  272. package/dist/installer/targets/opencode.js.map +1 -0
  273. package/dist/installer/targets/registry.d.ts +35 -0
  274. package/dist/installer/targets/registry.d.ts.map +1 -0
  275. package/dist/installer/targets/registry.js +91 -0
  276. package/dist/installer/targets/registry.js.map +1 -0
  277. package/dist/installer/targets/shared.d.ts +77 -0
  278. package/dist/installer/targets/shared.d.ts.map +1 -0
  279. package/dist/installer/targets/shared.js +247 -0
  280. package/dist/installer/targets/shared.js.map +1 -0
  281. package/dist/installer/targets/toml.d.ts +52 -0
  282. package/dist/installer/targets/toml.d.ts.map +1 -0
  283. package/dist/installer/targets/toml.js +147 -0
  284. package/dist/installer/targets/toml.js.map +1 -0
  285. package/dist/installer/targets/types.d.ts +101 -0
  286. package/dist/installer/targets/types.d.ts.map +1 -0
  287. package/dist/installer/targets/types.js +16 -0
  288. package/dist/installer/targets/types.js.map +1 -0
  289. package/dist/mcp/daemon-paths.d.ts +46 -0
  290. package/dist/mcp/daemon-paths.d.ts.map +1 -0
  291. package/dist/mcp/daemon-paths.js +125 -0
  292. package/dist/mcp/daemon-paths.js.map +1 -0
  293. package/dist/mcp/daemon.d.ts +220 -0
  294. package/dist/mcp/daemon.d.ts.map +1 -0
  295. package/dist/mcp/daemon.js +616 -0
  296. package/dist/mcp/daemon.js.map +1 -0
  297. package/dist/mcp/engine.d.ts +105 -0
  298. package/dist/mcp/engine.d.ts.map +1 -0
  299. package/dist/mcp/engine.js +270 -0
  300. package/dist/mcp/engine.js.map +1 -0
  301. package/dist/mcp/index.d.ts +112 -0
  302. package/dist/mcp/index.d.ts.map +1 -0
  303. package/dist/mcp/index.js +478 -0
  304. package/dist/mcp/index.js.map +1 -0
  305. package/dist/mcp/ppid-watchdog.d.ts +44 -0
  306. package/dist/mcp/ppid-watchdog.d.ts.map +1 -0
  307. package/dist/mcp/ppid-watchdog.js +27 -0
  308. package/dist/mcp/ppid-watchdog.js.map +1 -0
  309. package/dist/mcp/proxy.d.ts +81 -0
  310. package/dist/mcp/proxy.d.ts.map +1 -0
  311. package/dist/mcp/proxy.js +584 -0
  312. package/dist/mcp/proxy.js.map +1 -0
  313. package/dist/mcp/server-instructions.d.ts +19 -0
  314. package/dist/mcp/server-instructions.d.ts.map +1 -0
  315. package/dist/mcp/server-instructions.js +74 -0
  316. package/dist/mcp/server-instructions.js.map +1 -0
  317. package/dist/mcp/session.d.ts +77 -0
  318. package/dist/mcp/session.d.ts.map +1 -0
  319. package/dist/mcp/session.js +294 -0
  320. package/dist/mcp/session.js.map +1 -0
  321. package/dist/mcp/tools.d.ts +413 -0
  322. package/dist/mcp/tools.d.ts.map +1 -0
  323. package/dist/mcp/tools.js +3049 -0
  324. package/dist/mcp/tools.js.map +1 -0
  325. package/dist/mcp/transport.d.ts +188 -0
  326. package/dist/mcp/transport.d.ts.map +1 -0
  327. package/dist/mcp/transport.js +343 -0
  328. package/dist/mcp/transport.js.map +1 -0
  329. package/dist/mcp/version.d.ts +19 -0
  330. package/dist/mcp/version.d.ts.map +1 -0
  331. package/dist/mcp/version.js +71 -0
  332. package/dist/mcp/version.js.map +1 -0
  333. package/dist/resolution/callback-synthesizer.d.ts +10 -0
  334. package/dist/resolution/callback-synthesizer.d.ts.map +1 -0
  335. package/dist/resolution/callback-synthesizer.js +1818 -0
  336. package/dist/resolution/callback-synthesizer.js.map +1 -0
  337. package/dist/resolution/frameworks/cargo-workspace.d.ts +18 -0
  338. package/dist/resolution/frameworks/cargo-workspace.d.ts.map +1 -0
  339. package/dist/resolution/frameworks/cargo-workspace.js +225 -0
  340. package/dist/resolution/frameworks/cargo-workspace.js.map +1 -0
  341. package/dist/resolution/frameworks/csharp.d.ts +8 -0
  342. package/dist/resolution/frameworks/csharp.d.ts.map +1 -0
  343. package/dist/resolution/frameworks/csharp.js +241 -0
  344. package/dist/resolution/frameworks/csharp.js.map +1 -0
  345. package/dist/resolution/frameworks/drupal.d.ts +51 -0
  346. package/dist/resolution/frameworks/drupal.d.ts.map +1 -0
  347. package/dist/resolution/frameworks/drupal.js +367 -0
  348. package/dist/resolution/frameworks/drupal.js.map +1 -0
  349. package/dist/resolution/frameworks/expo-modules.d.ts +3 -0
  350. package/dist/resolution/frameworks/expo-modules.d.ts.map +1 -0
  351. package/dist/resolution/frameworks/expo-modules.js +148 -0
  352. package/dist/resolution/frameworks/expo-modules.js.map +1 -0
  353. package/dist/resolution/frameworks/express.d.ts +8 -0
  354. package/dist/resolution/frameworks/express.d.ts.map +1 -0
  355. package/dist/resolution/frameworks/express.js +308 -0
  356. package/dist/resolution/frameworks/express.js.map +1 -0
  357. package/dist/resolution/frameworks/fabric.d.ts +3 -0
  358. package/dist/resolution/frameworks/fabric.d.ts.map +1 -0
  359. package/dist/resolution/frameworks/fabric.js +354 -0
  360. package/dist/resolution/frameworks/fabric.js.map +1 -0
  361. package/dist/resolution/frameworks/go.d.ts +8 -0
  362. package/dist/resolution/frameworks/go.d.ts.map +1 -0
  363. package/dist/resolution/frameworks/go.js +161 -0
  364. package/dist/resolution/frameworks/go.js.map +1 -0
  365. package/dist/resolution/frameworks/index.d.ts +48 -0
  366. package/dist/resolution/frameworks/index.d.ts.map +1 -0
  367. package/dist/resolution/frameworks/index.js +161 -0
  368. package/dist/resolution/frameworks/index.js.map +1 -0
  369. package/dist/resolution/frameworks/java.d.ts +8 -0
  370. package/dist/resolution/frameworks/java.d.ts.map +1 -0
  371. package/dist/resolution/frameworks/java.js +504 -0
  372. package/dist/resolution/frameworks/java.js.map +1 -0
  373. package/dist/resolution/frameworks/laravel.d.ts +13 -0
  374. package/dist/resolution/frameworks/laravel.d.ts.map +1 -0
  375. package/dist/resolution/frameworks/laravel.js +257 -0
  376. package/dist/resolution/frameworks/laravel.js.map +1 -0
  377. package/dist/resolution/frameworks/nestjs.d.ts +26 -0
  378. package/dist/resolution/frameworks/nestjs.d.ts.map +1 -0
  379. package/dist/resolution/frameworks/nestjs.js +698 -0
  380. package/dist/resolution/frameworks/nestjs.js.map +1 -0
  381. package/dist/resolution/frameworks/play.d.ts +19 -0
  382. package/dist/resolution/frameworks/play.d.ts.map +1 -0
  383. package/dist/resolution/frameworks/play.js +111 -0
  384. package/dist/resolution/frameworks/play.js.map +1 -0
  385. package/dist/resolution/frameworks/python.d.ts +10 -0
  386. package/dist/resolution/frameworks/python.d.ts.map +1 -0
  387. package/dist/resolution/frameworks/python.js +400 -0
  388. package/dist/resolution/frameworks/python.js.map +1 -0
  389. package/dist/resolution/frameworks/react-native.d.ts +3 -0
  390. package/dist/resolution/frameworks/react-native.d.ts.map +1 -0
  391. package/dist/resolution/frameworks/react-native.js +410 -0
  392. package/dist/resolution/frameworks/react-native.js.map +1 -0
  393. package/dist/resolution/frameworks/react.d.ts +8 -0
  394. package/dist/resolution/frameworks/react.d.ts.map +1 -0
  395. package/dist/resolution/frameworks/react.js +365 -0
  396. package/dist/resolution/frameworks/react.js.map +1 -0
  397. package/dist/resolution/frameworks/ruby.d.ts +8 -0
  398. package/dist/resolution/frameworks/ruby.d.ts.map +1 -0
  399. package/dist/resolution/frameworks/ruby.js +302 -0
  400. package/dist/resolution/frameworks/ruby.js.map +1 -0
  401. package/dist/resolution/frameworks/rust.d.ts +8 -0
  402. package/dist/resolution/frameworks/rust.d.ts.map +1 -0
  403. package/dist/resolution/frameworks/rust.js +304 -0
  404. package/dist/resolution/frameworks/rust.js.map +1 -0
  405. package/dist/resolution/frameworks/svelte.d.ts +9 -0
  406. package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
  407. package/dist/resolution/frameworks/svelte.js +249 -0
  408. package/dist/resolution/frameworks/svelte.js.map +1 -0
  409. package/dist/resolution/frameworks/swift-objc.d.ts +37 -0
  410. package/dist/resolution/frameworks/swift-objc.d.ts.map +1 -0
  411. package/dist/resolution/frameworks/swift-objc.js +252 -0
  412. package/dist/resolution/frameworks/swift-objc.js.map +1 -0
  413. package/dist/resolution/frameworks/swift.d.ts +10 -0
  414. package/dist/resolution/frameworks/swift.d.ts.map +1 -0
  415. package/dist/resolution/frameworks/swift.js +400 -0
  416. package/dist/resolution/frameworks/swift.js.map +1 -0
  417. package/dist/resolution/frameworks/vue.d.ts +9 -0
  418. package/dist/resolution/frameworks/vue.d.ts.map +1 -0
  419. package/dist/resolution/frameworks/vue.js +306 -0
  420. package/dist/resolution/frameworks/vue.js.map +1 -0
  421. package/dist/resolution/go-module.d.ts +26 -0
  422. package/dist/resolution/go-module.d.ts.map +1 -0
  423. package/dist/resolution/go-module.js +78 -0
  424. package/dist/resolution/go-module.js.map +1 -0
  425. package/dist/resolution/import-resolver.d.ts +68 -0
  426. package/dist/resolution/import-resolver.d.ts.map +1 -0
  427. package/dist/resolution/import-resolver.js +1726 -0
  428. package/dist/resolution/import-resolver.js.map +1 -0
  429. package/dist/resolution/index.d.ts +159 -0
  430. package/dist/resolution/index.d.ts.map +1 -0
  431. package/dist/resolution/index.js +1025 -0
  432. package/dist/resolution/index.js.map +1 -0
  433. package/dist/resolution/lru-cache.d.ts +24 -0
  434. package/dist/resolution/lru-cache.d.ts.map +1 -0
  435. package/dist/resolution/lru-cache.js +62 -0
  436. package/dist/resolution/lru-cache.js.map +1 -0
  437. package/dist/resolution/name-matcher.d.ts +52 -0
  438. package/dist/resolution/name-matcher.d.ts.map +1 -0
  439. package/dist/resolution/name-matcher.js +707 -0
  440. package/dist/resolution/name-matcher.js.map +1 -0
  441. package/dist/resolution/path-aliases.d.ts +68 -0
  442. package/dist/resolution/path-aliases.d.ts.map +1 -0
  443. package/dist/resolution/path-aliases.js +238 -0
  444. package/dist/resolution/path-aliases.js.map +1 -0
  445. package/dist/resolution/strip-comments.d.ts +27 -0
  446. package/dist/resolution/strip-comments.d.ts.map +1 -0
  447. package/dist/resolution/strip-comments.js +441 -0
  448. package/dist/resolution/strip-comments.js.map +1 -0
  449. package/dist/resolution/swift-objc-bridge.d.ts +134 -0
  450. package/dist/resolution/swift-objc-bridge.d.ts.map +1 -0
  451. package/dist/resolution/swift-objc-bridge.js +256 -0
  452. package/dist/resolution/swift-objc-bridge.js.map +1 -0
  453. package/dist/resolution/types.d.ts +216 -0
  454. package/dist/resolution/types.d.ts.map +1 -0
  455. package/dist/resolution/types.js +8 -0
  456. package/dist/resolution/types.js.map +1 -0
  457. package/dist/resolution/workspace-packages.d.ts +48 -0
  458. package/dist/resolution/workspace-packages.d.ts.map +1 -0
  459. package/dist/resolution/workspace-packages.js +208 -0
  460. package/dist/resolution/workspace-packages.js.map +1 -0
  461. package/dist/search/query-parser.d.ts +57 -0
  462. package/dist/search/query-parser.d.ts.map +1 -0
  463. package/dist/search/query-parser.js +177 -0
  464. package/dist/search/query-parser.js.map +1 -0
  465. package/dist/search/query-utils.d.ts +71 -0
  466. package/dist/search/query-utils.d.ts.map +1 -0
  467. package/dist/search/query-utils.js +380 -0
  468. package/dist/search/query-utils.js.map +1 -0
  469. package/dist/sync/git-hooks.d.ts +45 -0
  470. package/dist/sync/git-hooks.d.ts.map +1 -0
  471. package/dist/sync/git-hooks.js +225 -0
  472. package/dist/sync/git-hooks.js.map +1 -0
  473. package/dist/sync/index.d.ts +19 -0
  474. package/dist/sync/index.d.ts.map +1 -0
  475. package/dist/sync/index.js +35 -0
  476. package/dist/sync/index.js.map +1 -0
  477. package/dist/sync/watch-policy.d.ts +48 -0
  478. package/dist/sync/watch-policy.d.ts.map +1 -0
  479. package/dist/sync/watch-policy.js +124 -0
  480. package/dist/sync/watch-policy.js.map +1 -0
  481. package/dist/sync/watcher.d.ts +283 -0
  482. package/dist/sync/watcher.d.ts.map +1 -0
  483. package/dist/sync/watcher.js +606 -0
  484. package/dist/sync/watcher.js.map +1 -0
  485. package/dist/sync/worktree.d.ts +54 -0
  486. package/dist/sync/worktree.d.ts.map +1 -0
  487. package/dist/sync/worktree.js +137 -0
  488. package/dist/sync/worktree.js.map +1 -0
  489. package/dist/types.d.ts +377 -0
  490. package/dist/types.d.ts.map +1 -0
  491. package/dist/types.js +80 -0
  492. package/dist/types.js.map +1 -0
  493. package/dist/ui/glyphs.d.ts +42 -0
  494. package/dist/ui/glyphs.d.ts.map +1 -0
  495. package/dist/ui/glyphs.js +78 -0
  496. package/dist/ui/glyphs.js.map +1 -0
  497. package/dist/ui/shimmer-progress.d.ts +11 -0
  498. package/dist/ui/shimmer-progress.d.ts.map +1 -0
  499. package/dist/ui/shimmer-progress.js +90 -0
  500. package/dist/ui/shimmer-progress.js.map +1 -0
  501. package/dist/ui/shimmer-worker.d.ts +2 -0
  502. package/dist/ui/shimmer-worker.d.ts.map +1 -0
  503. package/dist/ui/shimmer-worker.js +118 -0
  504. package/dist/ui/shimmer-worker.js.map +1 -0
  505. package/dist/ui/types.d.ts +17 -0
  506. package/dist/ui/types.d.ts.map +1 -0
  507. package/dist/ui/types.js +3 -0
  508. package/dist/ui/types.js.map +1 -0
  509. package/dist/upgrade/index.d.ts +132 -0
  510. package/dist/upgrade/index.d.ts.map +1 -0
  511. package/dist/upgrade/index.js +462 -0
  512. package/dist/upgrade/index.js.map +1 -0
  513. package/dist/utils.d.ts +205 -0
  514. package/dist/utils.d.ts.map +1 -0
  515. package/dist/utils.js +549 -0
  516. package/dist/utils.js.map +1 -0
  517. package/package.json +57 -0
  518. package/scripts/add-lang/bench.sh +60 -0
  519. package/scripts/add-lang/check-grammar.mjs +75 -0
  520. package/scripts/add-lang/dump-ast.mjs +103 -0
  521. package/scripts/add-lang/verify-extraction.mjs +70 -0
  522. package/scripts/agent-eval/arms-F.sh +21 -0
  523. package/scripts/agent-eval/arms-matrix.sh +37 -0
  524. package/scripts/agent-eval/audit.sh +68 -0
  525. package/scripts/agent-eval/bench-readme.sh +28 -0
  526. package/scripts/agent-eval/bench-why-repo.sh +22 -0
  527. package/scripts/agent-eval/block-read-hook.sh +19 -0
  528. package/scripts/agent-eval/hook-settings.json +15 -0
  529. package/scripts/agent-eval/itrun.sh +120 -0
  530. package/scripts/agent-eval/parse-arms.mjs +116 -0
  531. package/scripts/agent-eval/parse-bench-readme.mjs +84 -0
  532. package/scripts/agent-eval/parse-run.mjs +45 -0
  533. package/scripts/agent-eval/parse-session.mjs +93 -0
  534. package/scripts/agent-eval/probe-context.mjs +21 -0
  535. package/scripts/agent-eval/probe-explore.mjs +40 -0
  536. package/scripts/agent-eval/probe-node.mjs +20 -0
  537. package/scripts/agent-eval/probe-sweep.mjs +119 -0
  538. package/scripts/agent-eval/probe-trace.mjs +20 -0
  539. package/scripts/agent-eval/run-agent.sh +34 -0
  540. package/scripts/agent-eval/run-all.sh +67 -0
  541. package/scripts/agent-eval/run-arms.sh +56 -0
  542. package/scripts/agent-eval/seq-matrix.mjs +137 -0
  543. package/scripts/build-bundle.sh +118 -0
  544. package/scripts/extract-release-notes.mjs +130 -0
  545. package/scripts/local-install.sh +41 -0
  546. package/scripts/npm-sdk.js +75 -0
  547. package/scripts/npm-shim.js +246 -0
  548. package/scripts/pack-npm.sh +119 -0
  549. package/scripts/prepare-release.mjs +270 -0
@@ -0,0 +1,1818 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.synthesizeCallbackEdges = synthesizeCallbackEdges;
4
+ const generated_detection_1 = require("../extraction/generated-detection");
5
+ const strip_comments_1 = require("./strip-comments");
6
+ const REGISTRAR_NAME = /^(on[A-Z]\w*|subscribe|addListener|addEventListener|register|watch|listen|addCallback)$/;
7
+ const DISPATCHER_NAME = /(emit|trigger|notify|dispatch|fire|publish|flush)/i;
8
+ const MAX_CALLBACKS_PER_CHANNEL = 40;
9
+ const EVENT_FANOUT_CAP = 6; // skip events with more handlers/dispatchers than this (too generic without type info)
10
+ const ON_RE = /\.(?:on|once|addListener)\(\s*['"]([^'"]+)['"]\s*,\s*(?:function\s+(\w+)|(?:this\.)?(\w+))/g;
11
+ const EMIT_RE = /\.(?:emit|fire|dispatchEvent)\(\s*['"]([^'"]+)['"]/g;
12
+ const SETSTATE_RE = /this\.setState\s*\(/;
13
+ const FLUTTER_SETSTATE_RE = /\bsetState\s*\(/; // Flutter: setState((){…}) / this.setState
14
+ const JSX_TAG_RE = /<([A-Z][A-Za-z0-9_]*)[\s/>]/g;
15
+ const MAX_JSX_CHILDREN = 30;
16
+ // Vue SFC templates: kebab-case child components (<el-button> → ElButton) and
17
+ // event bindings (@click="fn" / v-on:click="fn"). PascalCase children (<VPNav/>)
18
+ // are already caught by JSX_TAG_RE via the SFC component node.
19
+ const VUE_KEBAB_RE = /<([a-z][a-z0-9]*(?:-[a-z0-9]+)+)[\s/>]/g;
20
+ // PascalCase component tags — `<MediaCard ...>`, `<NavBar/>`. HTML elements are
21
+ // lowercase, so an uppercase-initial tag is a component usage; built-ins
22
+ // (`<NuxtLink>`, `<Transition>`) simply resolve to nothing and emit no edge.
23
+ const VUE_PASCAL_RE = /<([A-Z][A-Za-z0-9]*)[\s/>]/g;
24
+ const VUE_HANDLER_RE = /(?:@|v-on:)([a-zA-Z][\w-]*)(?:\.[\w]+)*\s*=\s*"([^"]+)"/g;
25
+ // Composable/hook destructure: `const { close: closeSidebar } = useSidebarControl()`.
26
+ // Captures the destructure body + the called composable; only `use*` calls qualify.
27
+ const VUE_DESTRUCTURE_RE = /(?:const|let|var)\s*\{([^}]+)\}\s*=\s*(\w+)\s*\(/g;
28
+ // Closure-collection dynamic dispatch (language-agnostic, Swift-first). A method
29
+ // appends a closure to a collection property; another method iterates that
30
+ // property *invoking each element* (`coll.forEach { $0() }` / `{ it() }`). The
31
+ // element-invoke (`$0(` / `it(`) PROVES the collection holds closures, so pairing
32
+ // a dispatcher to same-named registrars (`.append`/`.add`/`.push`/`.insert`,
33
+ // incl. Swift `prop.write { $0.append }`) is high-precision. Cross-file/class by
34
+ // design: Alamofire appends in `DataRequest.validate` but iterates in the base
35
+ // `Request.didCompleteTask` — neither same-file nor same-class pairing reaches it.
36
+ const CC_DISPATCH_RE = /(\w+)\.forEach\s*\{\s*(?:\$0|it)\s*\(/g;
37
+ const CC_APPEND_WRITE_RE = /(\w+)\.write\s*\{\s*\$0(?:\.(\w+))?\.(?:append|add|push|insert)\s*\(/g;
38
+ const CC_APPEND_DIRECT_RE = /(\w+)\.(?:append|add|push|insert)\s*\(/g;
39
+ const CC_FANOUT_CAP = 8; // skip a field name with more dispatchers/registrars than this (too generic to pair confidently)
40
+ function kebabToPascal(s) {
41
+ return s.split('-').map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('');
42
+ }
43
+ /**
44
+ * Nuxt auto-import name for a component, derived from its path UNDER `components/`:
45
+ * `components/media/Card.vue` → `MediaCard`, `components/base/foo/Bar.vue` →
46
+ * `BaseFooBar`. Each directory segment and the filename is PascalCased and
47
+ * concatenated; a directory whose PascalCase name prefixes the next segment is
48
+ * collapsed (Nuxt's de-dup: `base/BaseButton.vue` → `BaseButton`, not
49
+ * `BaseBaseButton`). Returns null for a flat component (`components/NavBar.vue`)
50
+ * — its node is already named by basename, so a direct tag match finds it.
51
+ */
52
+ function nuxtComponentName(filePath) {
53
+ const marker = filePath.lastIndexOf('components/');
54
+ if (marker === -1)
55
+ return null;
56
+ const rel = filePath.slice(marker + 'components/'.length).replace(/\.(vue|ts|tsx|js|jsx)$/i, '');
57
+ const segs = rel.split('/').filter(Boolean).map(kebabToPascal);
58
+ if (segs.length < 2)
59
+ return null;
60
+ const out = [];
61
+ for (const s of segs) {
62
+ const prev = out[out.length - 1];
63
+ if (prev && s.startsWith(prev))
64
+ out[out.length - 1] = s;
65
+ else
66
+ out.push(s);
67
+ }
68
+ return out.join('');
69
+ }
70
+ function sliceLines(content, startLine, endLine) {
71
+ if (!startLine || !endLine)
72
+ return null;
73
+ return content.split('\n').slice(startLine - 1, endLine).join('\n');
74
+ }
75
+ function registrarField(src) {
76
+ const m = src.match(/this\.(\w+)\.(?:add|push|set)\(/);
77
+ return m ? m[1] : null;
78
+ }
79
+ function dispatcherField(src) {
80
+ const forOf = src.match(/\bof\s+(?:Array\.from\(\s*)?this\.(\w+)/);
81
+ if (forOf && /\b\w+\s*\(/.test(src))
82
+ return forOf[1];
83
+ const forEach = src.match(/this\.(\w+)\.forEach\(/);
84
+ if (forEach)
85
+ return forEach[1];
86
+ return null;
87
+ }
88
+ const FN_KINDS = new Set(['method', 'function', 'component']);
89
+ /** Innermost function/method node whose line range contains `line`. */
90
+ function enclosingFn(nodesInFile, line) {
91
+ let best = null;
92
+ for (const n of nodesInFile) {
93
+ if (!FN_KINDS.has(n.kind))
94
+ continue;
95
+ const end = n.endLine ?? n.startLine;
96
+ if (n.startLine <= line && end >= line) {
97
+ if (!best || n.startLine >= best.startLine)
98
+ best = n; // prefer the tightest (latest-starting) encloser
99
+ }
100
+ }
101
+ return best;
102
+ }
103
+ /**
104
+ * Stream method + function nodes lazily. The synthesizers only scan-and-filter
105
+ * down to a tiny matched subset, so materializing every function/method (which
106
+ * is gigabytes on a symbol-dense project) just to iterate it once is what OOM'd
107
+ * #610. Iterating keeps memory O(1) in the node count.
108
+ */
109
+ function* methodAndFunctionNodes(queries) {
110
+ yield* queries.iterateNodesByKind('method');
111
+ yield* queries.iterateNodesByKind('function');
112
+ }
113
+ /** Phase 1: field-backed observer channels (registrar/dispatcher share a store). */
114
+ function fieldChannelEdges(queries, ctx) {
115
+ const registrars = [];
116
+ const dispatchers = [];
117
+ for (const m of methodAndFunctionNodes(queries)) {
118
+ const isReg = REGISTRAR_NAME.test(m.name);
119
+ const isDisp = DISPATCHER_NAME.test(m.name);
120
+ if (!isReg && !isDisp)
121
+ continue;
122
+ const content = ctx.readFile(m.filePath);
123
+ const src = content && sliceLines(content, m.startLine, m.endLine);
124
+ if (!src)
125
+ continue;
126
+ if (isReg) {
127
+ const f = registrarField(src);
128
+ if (f)
129
+ registrars.push({ node: m, field: f });
130
+ }
131
+ if (isDisp) {
132
+ const f = dispatcherField(src);
133
+ if (f)
134
+ dispatchers.push({ node: m, field: f });
135
+ }
136
+ }
137
+ const edges = [];
138
+ const seen = new Set();
139
+ for (const reg of registrars) {
140
+ const chDispatchers = dispatchers.filter((d) => d.node.filePath === reg.node.filePath && d.field === reg.field);
141
+ if (chDispatchers.length === 0)
142
+ continue;
143
+ const argRe = new RegExp(`${reg.node.name}\\s*\\(\\s*(?:this\\.)?(\\w+)`);
144
+ let added = 0;
145
+ for (const e of queries.getIncomingEdges(reg.node.id, ['calls'])) {
146
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
147
+ break;
148
+ if (!e.line)
149
+ continue;
150
+ const caller = queries.getNodeById(e.source);
151
+ if (!caller)
152
+ continue;
153
+ const line = ctx.readFile(caller.filePath)?.split('\n')[e.line - 1];
154
+ const am = line?.match(argRe);
155
+ if (!am)
156
+ continue;
157
+ const fn = ctx.getNodesByName(am[1]).find((n) => n.kind === 'method' || n.kind === 'function');
158
+ if (!fn)
159
+ continue;
160
+ for (const disp of chDispatchers) {
161
+ if (disp.node.id === fn.id)
162
+ continue;
163
+ const key = `${disp.node.id}>${fn.id}`;
164
+ if (seen.has(key))
165
+ continue;
166
+ seen.add(key);
167
+ edges.push({
168
+ source: disp.node.id, target: fn.id, kind: 'calls', line: disp.node.startLine,
169
+ provenance: 'heuristic',
170
+ metadata: {
171
+ synthesizedBy: 'callback', via: reg.node.name, field: reg.field,
172
+ // Where the callback was wired up (`scene.onUpdate(this.triggerRender)`).
173
+ // This is the #1 thing an agent reads/greps to explain the flow — surface
174
+ // it so node/trace/context can show it without a callers() + Read round-trip.
175
+ registeredAt: `${caller.filePath}:${e.line}`,
176
+ },
177
+ });
178
+ added++;
179
+ }
180
+ }
181
+ }
182
+ return edges;
183
+ }
184
+ /**
185
+ * Closure-collection dispatch: dispatcher iterates a closure-collection property
186
+ * invoking each element; registrar appends a closure to the same-named property.
187
+ * Emits dispatcher → registrar so a flow reaches the registration site (where the
188
+ * appended closure's body — and its callers — live). High-precision: the
189
+ * dispatcher's element-invoke is the gate (a `.forEach` that does NOT invoke its
190
+ * element is ignored), so a repo with no closure-collection dispatch yields zero
191
+ * edges regardless of how many `.append`/`.push` sites it has.
192
+ *
193
+ * Pairs globally by field name (cross-file/class is required — see Alamofire's
194
+ * base-class `Request.didCompleteTask` iterating `validators` appended by the
195
+ * subclass `DataRequest.validate`), bounded by a fan-out cap so a generic field
196
+ * name shared across unrelated classes can't fan out into noise.
197
+ */
198
+ function closureCollectionEdges(queries, ctx) {
199
+ const dispatchers = new Map(); // field → dispatcher methods + forEach line
200
+ const registrars = new Map(); // field → registrar methods + append line
201
+ const addReg = (field, node, absLine) => {
202
+ if (!field || /^\d+$/.test(field))
203
+ return; // `$0.append` mis-captures the `0`; the write-RE owns that field
204
+ const arr = registrars.get(field) ?? [];
205
+ if (!arr.some((r) => r.node.id === node.id))
206
+ arr.push({ node, line: absLine });
207
+ registrars.set(field, arr);
208
+ };
209
+ for (const m of methodAndFunctionNodes(queries)) {
210
+ const content = ctx.readFile(m.filePath);
211
+ const src = content && sliceLines(content, m.startLine, m.endLine);
212
+ if (!src)
213
+ continue;
214
+ const hasForEach = src.includes('.forEach');
215
+ const hasAppend = src.includes('.append(') || src.includes('.add(') || src.includes('.push(') || src.includes('.insert(');
216
+ if (!hasForEach && !hasAppend)
217
+ continue;
218
+ const lineAt = (idx) => (m.startLine ?? 1) + src.slice(0, idx).split('\n').length - 1;
219
+ if (hasForEach) {
220
+ CC_DISPATCH_RE.lastIndex = 0;
221
+ let d;
222
+ while ((d = CC_DISPATCH_RE.exec(src))) {
223
+ const arr = dispatchers.get(d[1]) ?? [];
224
+ if (!arr.some((n) => n.node.id === m.id))
225
+ arr.push({ node: m, line: lineAt(d.index) });
226
+ dispatchers.set(d[1], arr);
227
+ }
228
+ }
229
+ if (hasAppend) {
230
+ CC_APPEND_WRITE_RE.lastIndex = 0;
231
+ let w;
232
+ while ((w = CC_APPEND_WRITE_RE.exec(src)))
233
+ addReg(w[2] || w[1], m, lineAt(w.index)); // nested `$0.streams` else the `.write` receiver
234
+ CC_APPEND_DIRECT_RE.lastIndex = 0;
235
+ let a;
236
+ while ((a = CC_APPEND_DIRECT_RE.exec(src)))
237
+ addReg(a[1], m, lineAt(a.index));
238
+ }
239
+ }
240
+ const edges = [];
241
+ const seen = new Set();
242
+ for (const [field, disps] of dispatchers) {
243
+ const regs = registrars.get(field);
244
+ if (!regs || regs.length === 0)
245
+ continue;
246
+ if (disps.length > CC_FANOUT_CAP || regs.length > CC_FANOUT_CAP)
247
+ continue; // generic field — can't pair confidently
248
+ for (const disp of disps)
249
+ for (const reg of regs) {
250
+ if (disp.node.id === reg.node.id)
251
+ continue;
252
+ const key = `${disp.node.id}>${reg.node.id}`;
253
+ if (seen.has(key))
254
+ continue;
255
+ seen.add(key);
256
+ edges.push({
257
+ source: disp.node.id, target: reg.node.id, kind: 'calls', line: disp.line,
258
+ provenance: 'heuristic',
259
+ metadata: { synthesizedBy: 'closure-collection', field, registeredAt: `${reg.node.filePath}:${reg.line}` },
260
+ });
261
+ }
262
+ }
263
+ return edges;
264
+ }
265
+ /** Phase 2: string-keyed EventEmitter channels (on('e', fn) ↔ emit('e')). */
266
+ function eventEmitterEdges(ctx) {
267
+ const emitsByEvent = new Map(); // event → dispatcher node ids
268
+ const handlersByEvent = new Map(); // event → handler id → registration site (file:line)
269
+ for (const file of ctx.getAllFiles()) {
270
+ const content = ctx.readFile(file);
271
+ if (!content)
272
+ continue;
273
+ const hasEmit = content.includes('.emit(') || content.includes('.fire(') || content.includes('.dispatchEvent(');
274
+ const hasOn = content.includes('.on(') || content.includes('.once(') || content.includes('.addListener(');
275
+ if (!hasEmit && !hasOn)
276
+ continue;
277
+ const nodesInFile = ctx.getNodesInFile(file);
278
+ const lineOf = (idx) => content.slice(0, idx).split('\n').length;
279
+ if (hasEmit) {
280
+ EMIT_RE.lastIndex = 0;
281
+ let m;
282
+ while ((m = EMIT_RE.exec(content))) {
283
+ const disp = enclosingFn(nodesInFile, lineOf(m.index));
284
+ if (!disp)
285
+ continue;
286
+ const set = emitsByEvent.get(m[1]) ?? new Set();
287
+ set.add(disp.id);
288
+ emitsByEvent.set(m[1], set);
289
+ }
290
+ }
291
+ if (hasOn) {
292
+ ON_RE.lastIndex = 0;
293
+ let m;
294
+ while ((m = ON_RE.exec(content))) {
295
+ const handlerName = m[2] || m[3];
296
+ if (!handlerName)
297
+ continue;
298
+ const handler = ctx.getNodesByName(handlerName).find((n) => n.kind === 'function' || n.kind === 'method');
299
+ if (!handler)
300
+ continue;
301
+ const map = handlersByEvent.get(m[1]) ?? new Map();
302
+ map.set(handler.id, `${file}:${lineOf(m.index)}`);
303
+ handlersByEvent.set(m[1], map);
304
+ }
305
+ }
306
+ }
307
+ const edges = [];
308
+ const seen = new Set();
309
+ for (const [event, dispatchers] of emitsByEvent) {
310
+ const handlers = handlersByEvent.get(event);
311
+ if (!handlers)
312
+ continue;
313
+ // Precision guard: a generic event name with many handlers/dispatchers can't
314
+ // be matched without receiver-type info (Phase 3) — skip rather than over-link.
315
+ if (dispatchers.size > EVENT_FANOUT_CAP || handlers.size > EVENT_FANOUT_CAP)
316
+ continue;
317
+ for (const d of dispatchers)
318
+ for (const [h, registeredAt] of handlers) {
319
+ if (d === h)
320
+ continue;
321
+ const key = `${d}>${h}`;
322
+ if (seen.has(key))
323
+ continue;
324
+ seen.add(key);
325
+ edges.push({ source: d, target: h, kind: 'calls', provenance: 'heuristic', metadata: { synthesizedBy: 'event-emitter', event, registeredAt } });
326
+ }
327
+ }
328
+ return edges;
329
+ }
330
+ /**
331
+ * Phase 4: React class-component re-render. `this.setState(...)` re-runs the
332
+ * component's `render()`, but that hop is React-internal — no static edge — so a
333
+ * flow like "mutation → setState → canvas repaint" dead-ends at setState even
334
+ * though `render → getRenderableElements → …` is fully call-connected after it.
335
+ * Bridge it: for each class that has a `render` method, link every sibling method
336
+ * whose body calls `this.setState(` → `render`. The setState gate keeps this to
337
+ * React class components (a non-React class with a `render` method won't call
338
+ * `this.setState`). Over-approximation (all setState methods reach render) is
339
+ * accepted — it's reachability-correct, like the callback channels.
340
+ */
341
+ function reactRenderEdges(queries, ctx) {
342
+ const edges = [];
343
+ const seen = new Set();
344
+ for (const cls of queries.getNodesByKind('class')) {
345
+ const children = queries.getOutgoingEdges(cls.id, ['contains'])
346
+ .map((e) => queries.getNodeById(e.target))
347
+ .filter((n) => !!n && n.kind === 'method');
348
+ const render = children.find((n) => n.name === 'render');
349
+ if (!render)
350
+ continue;
351
+ let added = 0;
352
+ for (const m of children) {
353
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
354
+ break;
355
+ if (m.id === render.id)
356
+ continue;
357
+ const content = ctx.readFile(m.filePath);
358
+ const src = content && sliceLines(content, m.startLine, m.endLine);
359
+ if (!src || !SETSTATE_RE.test(src))
360
+ continue;
361
+ const key = `${m.id}>${render.id}`;
362
+ if (seen.has(key))
363
+ continue;
364
+ seen.add(key);
365
+ edges.push({
366
+ source: m.id, target: render.id, kind: 'calls', line: m.startLine,
367
+ provenance: 'heuristic',
368
+ metadata: { synthesizedBy: 'react-render', via: 'setState', registeredAt: `${render.filePath}:${render.startLine}` },
369
+ });
370
+ added++;
371
+ }
372
+ }
373
+ return edges;
374
+ }
375
+ /**
376
+ * Phase 4b: Flutter setState → build (the Dart analog of react-render). In a
377
+ * StatefulWidget's State class, `setState(() {…})` re-runs `build(context)`, but
378
+ * that hop is framework-internal (Flutter calls build), so a flow like
379
+ * "onPressed → _increment → setState → rebuilt UI" dead-ends at setState. Bridge
380
+ * it: for each Dart class with a `build` method, link every sibling method whose
381
+ * body calls `setState(` → `build`. The setState gate + `.dart` file keep this to
382
+ * Flutter State classes. Over-approximation accepted (reachability-correct).
383
+ */
384
+ function flutterBuildEdges(queries, ctx) {
385
+ const edges = [];
386
+ const seen = new Set();
387
+ for (const cls of queries.getNodesByKind('class')) {
388
+ const children = queries.getOutgoingEdges(cls.id, ['contains'])
389
+ .map((e) => queries.getNodeById(e.target))
390
+ .filter((n) => !!n && n.kind === 'method');
391
+ const build = children.find((n) => n.name === 'build');
392
+ if (!build || !build.filePath.endsWith('.dart'))
393
+ continue;
394
+ let added = 0;
395
+ for (const m of children) {
396
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
397
+ break;
398
+ if (m.id === build.id)
399
+ continue;
400
+ const content = ctx.readFile(m.filePath);
401
+ const src = content && sliceLines(content, m.startLine, m.endLine);
402
+ if (!src || !FLUTTER_SETSTATE_RE.test(src))
403
+ continue;
404
+ const key = `${m.id}>${build.id}`;
405
+ if (seen.has(key))
406
+ continue;
407
+ seen.add(key);
408
+ edges.push({
409
+ source: m.id, target: build.id, kind: 'calls', line: m.startLine,
410
+ provenance: 'heuristic',
411
+ metadata: { synthesizedBy: 'flutter-build', via: 'setState', registeredAt: `${build.filePath}:${build.startLine}` },
412
+ });
413
+ added++;
414
+ }
415
+ }
416
+ return edges;
417
+ }
418
+ /**
419
+ * Phase 4c: C++ virtual override. A call through a base/interface pointer
420
+ * (`db->Get(...)`, `iter->Next()`) dispatches at runtime to a subclass override,
421
+ * but that hop is a vtable indirection — no static call edge — so a flow stops at
422
+ * the abstract base method. Bridge it like react-render: for each C++ class that
423
+ * `extends` a base, link each base method → the subclass method of the same name
424
+ * (the override), so trace/callees from the interface method reach the
425
+ * implementation(s). Over-approximation accepted (reachability-correct); capped
426
+ * per class and gated to C++ to avoid touching other languages' dispatch.
427
+ */
428
+ function cppOverrideEdges(queries) {
429
+ const edges = [];
430
+ const seen = new Set();
431
+ const methodsOf = (classId) => queries
432
+ .getOutgoingEdges(classId, ['contains'])
433
+ .map((e) => queries.getNodeById(e.target))
434
+ .filter((n) => !!n && n.kind === 'method');
435
+ for (const cls of queries.getNodesByKind('class')) {
436
+ const subMethods = methodsOf(cls.id).filter((n) => n.language === 'cpp');
437
+ if (subMethods.length === 0)
438
+ continue;
439
+ for (const ext of queries.getOutgoingEdges(cls.id, ['extends'])) {
440
+ const base = queries.getNodeById(ext.target);
441
+ if (!base || base.language !== 'cpp' || base.id === cls.id)
442
+ continue;
443
+ const baseMethods = new Map(methodsOf(base.id).map((m) => [m.name, m]));
444
+ let added = 0;
445
+ for (const m of subMethods) {
446
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
447
+ break;
448
+ const bm = baseMethods.get(m.name);
449
+ if (!bm || bm.id === m.id)
450
+ continue;
451
+ const key = `${bm.id}>${m.id}`;
452
+ if (seen.has(key))
453
+ continue;
454
+ seen.add(key);
455
+ edges.push({
456
+ source: bm.id,
457
+ target: m.id,
458
+ kind: 'calls',
459
+ line: bm.startLine,
460
+ provenance: 'heuristic',
461
+ metadata: { synthesizedBy: 'cpp-override', via: m.name, registeredAt: `${m.filePath}:${m.startLine}` },
462
+ });
463
+ added++;
464
+ }
465
+ }
466
+ }
467
+ return edges;
468
+ }
469
+ /**
470
+ * Phase 5.5: interface / abstract dispatch (Java, Kotlin). A call through an
471
+ * injected interface (`@Autowired FooService svc; svc.list()`) or an abstract
472
+ * base dispatches at runtime to the implementing class's override — a vtable
473
+ * indirection with no static call edge — so a request→service flow stops at the
474
+ * interface method. Bridge it like cpp-override: for each class that
475
+ * `implements` an interface (or `extends` an abstract base), link each
476
+ * base/interface method → the class's same-name method (the override) so
477
+ * trace/callees reach the implementation. Over-approximation accepted
478
+ * (reachability-correct); capped per class, gated to JVM languages.
479
+ */
480
+ // Languages whose static `implements`/`extends` edges should bridge an
481
+ // interface (or abstract base) method to the matching concrete-class method.
482
+ // The set is "languages with explicit nominal subtyping and a single class
483
+ // kind that holds methods" — i.e. the shape this loop expects. Swift and
484
+ // Scala fit shape-wise (Swift `protocol`/`class`, Scala `trait`/`class`)
485
+ // and are added below; their concrete-side nodes can be a `struct` (Swift)
486
+ // or an `object` (Scala) so the loop also iterates those kinds.
487
+ const IFACE_OVERRIDE_LANGS = new Set([
488
+ 'java', 'kotlin', 'csharp', 'typescript', 'javascript', 'swift', 'scala', 'go', 'rust',
489
+ ]);
490
+ /**
491
+ * Go implicit interface satisfaction (#584). Go has no `implements` keyword — a
492
+ * struct satisfies an interface structurally when its method set covers the
493
+ * interface's. Synthesize the missing `implements` edge (struct → interface) by
494
+ * matching method-NAME sets, so impl-navigation works and the interface-dispatch
495
+ * bridge ({@link interfaceOverrideEdges}, now 'go'-enabled) can link an interface
496
+ * method call to the concrete overrides.
497
+ *
498
+ * Name-only matching (signatures ignored) — over-approximation accepted, in line
499
+ * with the other dispatch synthesizers; capped per interface. Empty interfaces
500
+ * (`any`) are skipped so they don't match every struct.
501
+ */
502
+ function goImplementsEdges(queries) {
503
+ const edges = [];
504
+ const seen = new Set();
505
+ const methodNameSet = (id) => new Set(queries
506
+ .getOutgoingEdges(id, ['contains'])
507
+ .map((e) => queries.getNodeById(e.target))
508
+ .filter((n) => !!n && n.kind === 'method')
509
+ .map((n) => n.name));
510
+ const goStructs = queries.getNodesByKind('struct').filter((s) => s.language === 'go');
511
+ const structMethods = new Map();
512
+ for (const s of goStructs)
513
+ structMethods.set(s.id, methodNameSet(s.id));
514
+ for (const iface of queries.getNodesByKind('interface')) {
515
+ if (iface.language !== 'go')
516
+ continue;
517
+ const want = methodNameSet(iface.id);
518
+ if (want.size === 0)
519
+ continue; // empty interface (`any`) — would match everything
520
+ let added = 0;
521
+ for (const s of goStructs) {
522
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
523
+ break;
524
+ const have = structMethods.get(s.id);
525
+ if (!have || have.size < want.size)
526
+ continue;
527
+ let all = true;
528
+ for (const m of want) {
529
+ if (!have.has(m)) {
530
+ all = false;
531
+ break;
532
+ }
533
+ }
534
+ if (!all)
535
+ continue;
536
+ const key = `${s.id}>${iface.id}`;
537
+ if (seen.has(key))
538
+ continue;
539
+ seen.add(key);
540
+ edges.push({
541
+ source: s.id,
542
+ target: iface.id,
543
+ kind: 'implements',
544
+ line: s.startLine,
545
+ provenance: 'heuristic',
546
+ metadata: { synthesizedBy: 'go-implements', via: iface.name, registeredAt: `${s.filePath}:${s.startLine}` },
547
+ });
548
+ added++;
549
+ }
550
+ }
551
+ return edges;
552
+ }
553
+ /**
554
+ * Cross-file Go method → receiver-type `contains` edges. In Go a type's methods
555
+ * are commonly declared in a different file from the `type` declaration itself
556
+ * (`type User struct{…}` in `user.go`, `func (u *User) Save()` in
557
+ * `user_store.go`). Extraction attaches the struct→method `contains` edge only
558
+ * when the receiver type is in the SAME file — the owner lookup in
559
+ * `tree-sitter.ts` is scoped to the file being parsed — so a cross-file method
560
+ * is left orphaned from its type (it's still `contains`ed by its file, just not
561
+ * its struct). That breaks `homegraph_node` member outlines, any
562
+ * callers/callees/impact traversal that goes through the type's `contains`
563
+ * edges, and the {@link goImplementsEdges} method-set computation (which derives
564
+ * a struct's method set from those same edges, so it under-counts interfaces a
565
+ * cross-file struct satisfies).
566
+ *
567
+ * Go guarantees a method's receiver type is declared in the SAME PACKAGE as the
568
+ * method, and a Go package is a single directory — so this is a deterministic
569
+ * structural link, not a heuristic: find the same-named type in the method's own
570
+ * directory and add the missing `contains` edge (no `provenance: 'heuristic'`,
571
+ * matching the same-file edges extraction already emits). Skips methods that
572
+ * already have a type parent (the same-file case). (#583, cross-file half)
573
+ */
574
+ function goCrossFileMethodContainsEdges(queries) {
575
+ const edges = [];
576
+ const seen = new Set();
577
+ const TYPE_KINDS = new Set(['struct', 'class', 'interface', 'enum', 'type_alias']);
578
+ const dirOf = (p) => {
579
+ const i = p.replace(/\\/g, '/').lastIndexOf('/');
580
+ return i >= 0 ? p.slice(0, i) : '';
581
+ };
582
+ for (const method of queries.getNodesByKind('method')) {
583
+ if (method.language !== 'go')
584
+ continue;
585
+ // The receiver type is encoded in the method's qualifiedName as `Recv::name`
586
+ // (extraction sets `${receiverType}::${name}` for receiver methods).
587
+ const qn = method.qualifiedName;
588
+ if (!qn)
589
+ continue;
590
+ const sep = qn.lastIndexOf('::');
591
+ if (sep <= 0)
592
+ continue;
593
+ const receiver = qn.slice(0, sep);
594
+ if (!receiver)
595
+ continue;
596
+ // Already attached to its type (same-file case handled at extraction)?
597
+ const hasTypeParent = queries
598
+ .getIncomingEdges(method.id, ['contains'])
599
+ .some((e) => {
600
+ const src = queries.getNodeById(e.source);
601
+ return src != null && TYPE_KINDS.has(src.kind);
602
+ });
603
+ if (hasTypeParent)
604
+ continue;
605
+ // Find the receiver type in the SAME directory (= same Go package). Go forbids
606
+ // duplicate type names within a package, so a same-name same-dir match is
607
+ // unambiguous; scoping to the directory avoids linking to a same-named type
608
+ // in another package.
609
+ const dir = dirOf(method.filePath);
610
+ const owner = queries
611
+ .getNodesByName(receiver)
612
+ .find((n) => n.language === 'go' && TYPE_KINDS.has(n.kind) && dirOf(n.filePath) === dir);
613
+ if (!owner)
614
+ continue;
615
+ const key = `${owner.id}>${method.id}`;
616
+ if (seen.has(key))
617
+ continue;
618
+ seen.add(key);
619
+ edges.push({ source: owner.id, target: method.id, kind: 'contains', line: method.startLine });
620
+ }
621
+ return edges;
622
+ }
623
+ /**
624
+ * Kotlin Multiplatform `expect`/`actual` linking. A `common` source set declares
625
+ * `expect fun foo()` / `expect class Bar`; each platform source set (jvm, native,
626
+ * js, …) provides an `actual` implementation with the IDENTICAL fully-qualified
627
+ * name in a different file. Callers in common code resolve to the `expect`
628
+ * declaration, so every `actual` impl ends up with zero dependents — invisible to
629
+ * impact/affected even though editing it can break every caller of the API.
630
+ *
631
+ * Synthesize a `calls` edge from the common declaration to each platform `actual`
632
+ * (mirroring the interface-impl bridge: abstract → concrete), so editing a
633
+ * platform impl surfaces the common `expect` and its callers, and the impl file
634
+ * participates in the graph.
635
+ *
636
+ * `expect`/`actual` are captured onto the node's `decorators` list at extraction
637
+ * (kotlin.ts `extractModifiers`). Members of an `expect class` are NOT themselves
638
+ * keyword-marked, so the declaration side is matched as the same-FQN, same-kind
639
+ * node that is NOT marked `actual`. Requiring an `actual`-marked counterpart also
640
+ * gates out plain cross-file overloads (neither side is marked).
641
+ */
642
+ // Kinds that an `expect`/`actual` pair may legitimately straddle. `expect class`
643
+ // is routinely fulfilled by an `actual typealias` (e.g. `actual typealias
644
+ // CancellationException = …`, `actual typealias SchedulerTask = Task`), so a
645
+ // strict kind match would miss those one-line alias files. Same-FQN + the
646
+ // `actual` marker already gates out unrelated symbols, so widening to the
647
+ // type-like kinds is safe.
648
+ const KMP_TYPE_KINDS = new Set(['class', 'interface', 'struct', 'enum', 'type_alias']);
649
+ function kmpKindsCompatible(a, b) {
650
+ return a === b || (KMP_TYPE_KINDS.has(a) && KMP_TYPE_KINDS.has(b));
651
+ }
652
+ function kotlinExpectActualEdges(queries) {
653
+ const edges = [];
654
+ const seen = new Set();
655
+ const actuals = queries
656
+ .getAllNodes()
657
+ .filter((n) => n.language === 'kotlin' && !!n.decorators?.includes('actual'));
658
+ for (const act of actuals) {
659
+ let added = 0;
660
+ for (const cand of queries.getNodesByQualifiedNameExact(act.qualifiedName)) {
661
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
662
+ break;
663
+ // The declaration side: same FQN + compatible kind, a different file, NOT
664
+ // itself an `actual` (that would be a sibling platform impl, not the decl).
665
+ if (cand.language !== 'kotlin' || cand.id === act.id)
666
+ continue;
667
+ if (!kmpKindsCompatible(cand.kind, act.kind) || cand.filePath === act.filePath)
668
+ continue;
669
+ if (cand.decorators?.includes('actual'))
670
+ continue;
671
+ const key = `${cand.id}>${act.id}`;
672
+ if (seen.has(key))
673
+ continue;
674
+ seen.add(key);
675
+ edges.push({
676
+ source: cand.id,
677
+ target: act.id,
678
+ kind: 'calls',
679
+ line: cand.startLine,
680
+ provenance: 'heuristic',
681
+ metadata: {
682
+ synthesizedBy: 'kotlin-expect-actual',
683
+ via: act.name,
684
+ registeredAt: `${act.filePath}:${act.startLine}`,
685
+ },
686
+ });
687
+ added++;
688
+ }
689
+ }
690
+ return edges;
691
+ }
692
+ function interfaceOverrideEdges(queries) {
693
+ const edges = [];
694
+ const seen = new Set();
695
+ const methodsOf = (classId) => queries
696
+ .getOutgoingEdges(classId, ['contains'])
697
+ .map((e) => queries.getNodeById(e.target))
698
+ .filter((n) => !!n && n.kind === 'method');
699
+ // Concrete-side kinds vary by language: `class` covers Java / Kotlin /
700
+ // C# / TS / Swift-classes / Scala-classes; `struct` covers Swift value
701
+ // types that conform to protocols. Iterate both.
702
+ const concreteKinds = ['class', 'struct'];
703
+ for (const kind of concreteKinds) {
704
+ for (const cls of queries.getNodesByKind(kind)) {
705
+ const implMethods = methodsOf(cls.id).filter((n) => IFACE_OVERRIDE_LANGS.has(n.language));
706
+ if (implMethods.length === 0)
707
+ continue;
708
+ for (const sup of queries.getOutgoingEdges(cls.id, ['implements', 'extends'])) {
709
+ const base = queries.getNodeById(sup.target);
710
+ if (!base || !IFACE_OVERRIDE_LANGS.has(base.language) || base.id === cls.id)
711
+ continue;
712
+ // Group impl methods by name to handle OVERLOADS: an interface `list()` and
713
+ // `list(params)` are distinct nodes and a call may resolve to either, so
714
+ // link every base overload → every same-name impl overload (keying by name
715
+ // alone would drop all but one and miss the resolved overload).
716
+ const implByName = new Map();
717
+ for (const m of implMethods) {
718
+ const arr = implByName.get(m.name);
719
+ if (arr)
720
+ arr.push(m);
721
+ else
722
+ implByName.set(m.name, [m]);
723
+ }
724
+ let added = 0;
725
+ for (const bm of methodsOf(base.id)) {
726
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
727
+ break;
728
+ for (const m of implByName.get(bm.name) ?? []) {
729
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
730
+ break;
731
+ if (bm.id === m.id)
732
+ continue;
733
+ const key = `${bm.id}>${m.id}`;
734
+ if (seen.has(key))
735
+ continue;
736
+ seen.add(key);
737
+ edges.push({
738
+ source: bm.id,
739
+ target: m.id,
740
+ kind: 'calls',
741
+ line: bm.startLine,
742
+ provenance: 'heuristic',
743
+ metadata: { synthesizedBy: 'interface-impl', via: m.name, registeredAt: `${m.filePath}:${m.startLine}` },
744
+ });
745
+ added++;
746
+ }
747
+ }
748
+ }
749
+ }
750
+ }
751
+ return edges;
752
+ }
753
+ /**
754
+ * Go gRPC stub → impl bridge. The protoc-gen-go-grpc codegen emits an
755
+ * `UnimplementedXxxServer` struct in `*_grpc.pb.go` carrying one method
756
+ * per service RPC; the real handler is a hand-written struct in another
757
+ * file (`x/bank/keeper/msg_server.go::msgServer.Send` in cosmos-sdk).
758
+ * Go's structural typing means no `implements` edge exists for our
759
+ * resolver to follow, so `trace("Send","SendCoins")` lands on the
760
+ * empty stub and reports "no path" (validated empirically — the cosmos
761
+ * Q1 r1 trace failure that drove this work).
762
+ *
763
+ * Bridge: for each `UnimplementedXxxServer` whose RPC-method names are
764
+ * a SUBSET of some other Go struct's method names, emit `calls` edges
765
+ * `stub.method → impl.method` (paired by name). Excludes the gRPC
766
+ * internal markers `mustEmbedUnimplementedXxxServer` and
767
+ * `testEmbeddedByValue`, and skips candidate impls that themselves
768
+ * live in a generated file (their `xxxClient` / sibling stubs would
769
+ * otherwise look like impls).
770
+ *
771
+ * Multiple candidates is allowed and capped at MAX_CALLBACKS_PER_CHANNEL —
772
+ * a service often has both a production impl and one or more test
773
+ * mocks; linking to all preserves trace utility without false-favoring.
774
+ *
775
+ * Provenance: `heuristic`, `synthesizedBy: 'go-grpc-stub-impl'`. The
776
+ * stub's source line is the wiring site shown in the trace trail.
777
+ */
778
+ function goGrpcStubImplEdges(queries) {
779
+ const edges = [];
780
+ const seen = new Set();
781
+ const STUB_RE = /^Unimplemented.*Server$/;
782
+ // gRPC internal-helper methods that appear on every Unimplemented*Server;
783
+ // not part of the service contract, so exclude when computing the RPC-method
784
+ // signature used to match impls.
785
+ const isInternalMarker = (n) => n.startsWith('mustEmbed') || n === 'testEmbeddedByValue';
786
+ // Methods directly contained by each Go struct, name-only. Built once.
787
+ const methodNamesByStruct = new Map();
788
+ const methodNodesByStruct = new Map();
789
+ const goStructs = [];
790
+ for (const s of queries.getNodesByKind('struct')) {
791
+ if (s.language !== 'go')
792
+ continue;
793
+ goStructs.push(s);
794
+ const ms = queries
795
+ .getOutgoingEdges(s.id, ['contains'])
796
+ .map((e) => queries.getNodeById(e.target))
797
+ .filter((n) => !!n && n.kind === 'method');
798
+ methodNodesByStruct.set(s.id, ms);
799
+ methodNamesByStruct.set(s.id, new Set(ms.map((m) => m.name)));
800
+ }
801
+ for (const stub of goStructs) {
802
+ if (!STUB_RE.test(stub.name))
803
+ continue;
804
+ // The stub MUST live in a generated file — that's what tells us this is
805
+ // a protoc-emitted scaffold rather than someone naming a struct
806
+ // `UnimplementedXxxServer` by hand. Without this gate we'd also bridge
807
+ // such hand-written structs and create misleading edges.
808
+ if (!(0, generated_detection_1.isGeneratedFile)(stub.filePath))
809
+ continue;
810
+ const stubMethods = (methodNodesByStruct.get(stub.id) ?? []).filter((m) => !isInternalMarker(m.name));
811
+ if (stubMethods.length === 0)
812
+ continue;
813
+ const stubMethodNames = stubMethods.map((m) => m.name);
814
+ for (const cand of goStructs) {
815
+ if (cand.id === stub.id)
816
+ continue;
817
+ // Skip generated-file candidates — they're siblings (msgClient,
818
+ // UnsafeMsgServer, …) whose method sets coincidentally match.
819
+ if ((0, generated_detection_1.isGeneratedFile)(cand.filePath))
820
+ continue;
821
+ const candNames = methodNamesByStruct.get(cand.id);
822
+ if (!candNames)
823
+ continue;
824
+ // Subset: every RPC method must exist on the candidate by name.
825
+ // Signature-level match would tighten this further, but name-match
826
+ // alone already gives one-to-one pairing in real codebases because
827
+ // gRPC method-name sets are highly distinctive (Send + MultiSend +
828
+ // UpdateParams + SetSendEnabled is unique to bank's MsgServer).
829
+ if (!stubMethodNames.every((n) => candNames.has(n)))
830
+ continue;
831
+ const candMethods = methodNodesByStruct.get(cand.id) ?? [];
832
+ let added = 0;
833
+ for (const sm of stubMethods) {
834
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
835
+ break;
836
+ for (const cm of candMethods) {
837
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
838
+ break;
839
+ if (cm.name !== sm.name)
840
+ continue;
841
+ const key = `${sm.id}>${cm.id}`;
842
+ if (seen.has(key))
843
+ continue;
844
+ seen.add(key);
845
+ edges.push({
846
+ source: sm.id,
847
+ target: cm.id,
848
+ kind: 'calls',
849
+ line: sm.startLine,
850
+ provenance: 'heuristic',
851
+ metadata: {
852
+ synthesizedBy: 'go-grpc-stub-impl',
853
+ via: cm.name,
854
+ registeredAt: `${cm.filePath}:${cm.startLine}`,
855
+ },
856
+ });
857
+ added++;
858
+ }
859
+ }
860
+ }
861
+ }
862
+ return edges;
863
+ }
864
+ /**
865
+ * Phase 5: React JSX child rendering. A component that returns `<Child .../>`
866
+ * mounts Child — React calls it — but JSX instantiation isn't a static call edge,
867
+ * so a render tree (App.render → StaticCanvas → renderStaticScene) breaks at the
868
+ * JSX hop. Link parent → each capitalized JSX child it renders. File-oriented
869
+ * (read each JSX file once). Precision gate: the child name must resolve to a
870
+ * component/function/class node — TS generics like `Array<Foo>` resolve to a type
871
+ * (or nothing) and are dropped.
872
+ */
873
+ function reactJsxChildEdges(ctx) {
874
+ const edges = [];
875
+ const seen = new Set();
876
+ const PARENT_KINDS = new Set(['method', 'function', 'component']);
877
+ for (const file of ctx.getAllFiles()) {
878
+ const content = ctx.readFile(file);
879
+ if (!content || (!content.includes('</') && !content.includes('/>')))
880
+ continue; // JSX-file gate
881
+ const parents = ctx.getNodesInFile(file).filter((n) => PARENT_KINDS.has(n.kind));
882
+ for (const parent of parents) {
883
+ const src = sliceLines(content, parent.startLine, parent.endLine);
884
+ if (!src || (!src.includes('</') && !src.includes('/>')))
885
+ continue;
886
+ const names = new Set();
887
+ JSX_TAG_RE.lastIndex = 0;
888
+ let m;
889
+ while ((m = JSX_TAG_RE.exec(src)))
890
+ names.add(m[1]);
891
+ let added = 0;
892
+ for (const name of names) {
893
+ if (added >= MAX_JSX_CHILDREN)
894
+ break;
895
+ const child = ctx.getNodesByName(name).find((n) => n.kind === 'component' || n.kind === 'function' || n.kind === 'class');
896
+ if (!child || child.id === parent.id)
897
+ continue;
898
+ const key = `${parent.id}>${child.id}`;
899
+ if (seen.has(key))
900
+ continue;
901
+ seen.add(key);
902
+ edges.push({
903
+ source: parent.id, target: child.id, kind: 'calls', line: parent.startLine,
904
+ provenance: 'heuristic',
905
+ metadata: { synthesizedBy: 'jsx-render', via: name },
906
+ });
907
+ added++;
908
+ }
909
+ }
910
+ }
911
+ return edges;
912
+ }
913
+ /**
914
+ * Phase 6: Vue SFC templates. The `.vue` extractor only parses `<script>`, so
915
+ * template usage is invisible — child components and event handlers used ONLY in
916
+ * the template have no edge to them. PascalCase children (`<VPNav/>`) are already
917
+ * caught by reactJsxChildEdges (which scans the SFC component node), so this adds
918
+ * the two Vue-specific shapes:
919
+ * - kebab-case children: `<el-button>` → `ElButton` component (renders).
920
+ * - event bindings: `@click="onClick"` / `v-on:submit="save"` → handler method.
921
+ * Scoped to the `<template>` block of `.vue` files; resolution gate (kebab→
922
+ * component, handler→function/method) keeps precision; inline arrows / `$emit`
923
+ * skipped.
924
+ */
925
+ function vueTemplateEdges(ctx) {
926
+ const edges = [];
927
+ const seen = new Set();
928
+ const COMPONENT_KINDS = new Set(['component', 'function', 'class']);
929
+ const HANDLER_KINDS = new Set(['method', 'function']);
930
+ // A composable's returned member may be a fn (`function close(){}`) or an
931
+ // arrow assigned to a const (`const close = () => {}`).
932
+ const RETURN_KINDS = new Set(['method', 'function', 'variable', 'constant']);
933
+ // Nuxt auto-imports nested components by a DIRECTORY-PREFIXED name —
934
+ // `components/media/Card.vue` is used as `<MediaCard/>`, not `<Card/>` — but
935
+ // the component node is named by basename (`Card`), so a direct tag match
936
+ // misses it (flat components match by basename and don't need this). Map each
937
+ // nested component's Nuxt name → node so those template usages resolve.
938
+ const nuxtComponents = new Map();
939
+ for (const c of ctx.getNodesByKind('component')) {
940
+ const nn = nuxtComponentName(c.filePath);
941
+ if (nn && !nuxtComponents.has(nn))
942
+ nuxtComponents.set(nn, c);
943
+ }
944
+ for (const file of ctx.getAllFiles()) {
945
+ if (!file.endsWith('.vue'))
946
+ continue;
947
+ const content = ctx.readFile(file);
948
+ const tpl = content && content.match(/<template[^>]*>([\s\S]*)<\/template>/i)?.[1];
949
+ if (!tpl)
950
+ continue;
951
+ const comp = ctx.getNodesInFile(file).find((n) => n.kind === 'component');
952
+ if (!comp)
953
+ continue;
954
+ // Composable-destructure map: alias → { composable, key }. Lets us resolve a
955
+ // template handler that isn't a local function but a destructured composable
956
+ // return (`@click="closeSidebar"` ← `const { close: closeSidebar } = useSidebarControl()`).
957
+ const script = content.match(/<script[^>]*>([\s\S]*?)<\/script>/i)?.[1] ?? '';
958
+ const destructured = new Map();
959
+ VUE_DESTRUCTURE_RE.lastIndex = 0;
960
+ let dm;
961
+ while ((dm = VUE_DESTRUCTURE_RE.exec(script))) {
962
+ if (!/^use[A-Z]/.test(dm[2]))
963
+ continue; // composables / hooks only
964
+ for (const part of dm[1].split(',')) {
965
+ const pm = part.trim().match(/^(\w+)\s*(?::\s*(\w+))?$/); // key | key: alias
966
+ if (pm)
967
+ destructured.set(pm[2] || pm[1], { composable: dm[2], key: pm[1] });
968
+ }
969
+ }
970
+ let added = 0;
971
+ const addEdge = (target, meta) => {
972
+ if (added >= MAX_JSX_CHILDREN || !target || target.id === comp.id)
973
+ return;
974
+ const k = `${comp.id}>${target.id}>${meta.synthesizedBy}`;
975
+ if (seen.has(k))
976
+ return;
977
+ seen.add(k);
978
+ edges.push({ source: comp.id, target: target.id, kind: 'calls', line: comp.startLine, provenance: 'heuristic', metadata: meta });
979
+ added++;
980
+ };
981
+ // Prefer a target in THIS SFC (handlers live in the same file's script) —
982
+ // avoids cross-file mis-match when a name repeats across a monorepo.
983
+ const resolve = (name, kinds) => {
984
+ const matches = ctx.getNodesByName(name).filter((n) => kinds.has(n.kind));
985
+ return matches.find((n) => n.filePath === file) ?? matches[0];
986
+ };
987
+ let m;
988
+ VUE_KEBAB_RE.lastIndex = 0;
989
+ while ((m = VUE_KEBAB_RE.exec(tpl))) {
990
+ const tag = kebabToPascal(m[1]);
991
+ addEdge(resolve(tag, COMPONENT_KINDS) ?? nuxtComponents.get(tag), { synthesizedBy: 'jsx-render', via: m[1] });
992
+ }
993
+ // PascalCase component tags. Try a direct name match first (flat components
994
+ // and explicit registrations), then the Nuxt dir-prefixed auto-import name
995
+ // (`<MediaCard>` → components/media/Card.vue). Built-ins match neither → no edge.
996
+ VUE_PASCAL_RE.lastIndex = 0;
997
+ while ((m = VUE_PASCAL_RE.exec(tpl))) {
998
+ const tag = m[1];
999
+ addEdge(resolve(tag, COMPONENT_KINDS) ?? nuxtComponents.get(tag), { synthesizedBy: 'jsx-render', via: tag });
1000
+ }
1001
+ VUE_HANDLER_RE.lastIndex = 0;
1002
+ while ((m = VUE_HANDLER_RE.exec(tpl))) {
1003
+ const event = m[1];
1004
+ const expr = m[2].trim();
1005
+ if (expr.includes('=>') || expr.startsWith('$'))
1006
+ continue; // inline arrow / $emit
1007
+ const name = expr.match(/^([A-Za-z_]\w*)/)?.[1];
1008
+ if (!name)
1009
+ continue;
1010
+ const direct = resolve(name, HANDLER_KINDS);
1011
+ if (direct) {
1012
+ addEdge(direct, { synthesizedBy: 'vue-handler', event });
1013
+ continue;
1014
+ }
1015
+ // Composable-destructure handler → resolve to the composable's returned fn.
1016
+ const d = destructured.get(name);
1017
+ if (!d)
1018
+ continue;
1019
+ const composable = resolve(d.composable, HANDLER_KINDS);
1020
+ // Resolve to the SPECIFIC returned member (e.g. `close`) defined in the
1021
+ // composable's file. No fallback to the composable itself — the component
1022
+ // already has a static `useX()` call edge, so that would just be redundant
1023
+ // and less precise.
1024
+ const keyFn = composable
1025
+ ? ctx.getNodesByName(d.key).find((n) => RETURN_KINDS.has(n.kind) && n.filePath === composable.filePath)
1026
+ : undefined;
1027
+ if (keyFn)
1028
+ addEdge(keyFn, { synthesizedBy: 'vue-handler', event, via: d.composable });
1029
+ }
1030
+ }
1031
+ return edges;
1032
+ }
1033
+ /**
1034
+ * React Native cross-language event channel (Phase 3 of the mixed-iOS/RN
1035
+ * bridging effort). Same shape as `eventEmitterEdges` but cross-language:
1036
+ *
1037
+ * Native (ObjC, on RCTEventEmitter subclass):
1038
+ * [self sendEventWithName:@"locationUpdate" body:@{...}];
1039
+ *
1040
+ * Native (Java/Kotlin, via the JS module dispatcher):
1041
+ * emitter.emit("locationUpdate", body);
1042
+ * reactContext.getJSModule(RCTDeviceEventEmitter.class).emit("locationUpdate", body);
1043
+ *
1044
+ * JS (subscriber):
1045
+ * new NativeEventEmitter(NativeModules.Geo).addListener("locationUpdate", handler);
1046
+ * DeviceEventEmitter.addListener("locationUpdate", handler);
1047
+ *
1048
+ * Synthesize: native dispatch site → JS handler, keyed by the literal
1049
+ * event name. Only matches NAMED handlers (the existing `ON_RE` named-
1050
+ * capture form). Inline arrow handlers like `addListener('x', d => …)`
1051
+ * aren't named at extraction time and would need link-through-body
1052
+ * support; matches the deliberate scope of the in-language synthesizer.
1053
+ *
1054
+ * Provenance `'heuristic'`, synthesizedBy `'rn-event-channel'`.
1055
+ */
1056
+ // ObjC's `[self sendEventWithName:@"X" body:...]` shape (bracket syntax,
1057
+ // `@` string literals).
1058
+ const RN_OBJC_SEND_RE = /\bsendEventWithName\s*:\s*@"([^"]+)"/g;
1059
+ // Swift's `sendEvent(withName: "X", body: ...)` shape — same RCTEventEmitter
1060
+ // method, different call syntax. Both Objective-C and Swift subclass
1061
+ // RCTEventEmitter so this catches the Swift-side equivalent emission sites
1062
+ // (e.g. RNFusedLocation.swift's `sendEvent(withName: "geolocationDidChange",
1063
+ // body: locationData)`).
1064
+ const RN_SWIFT_SEND_RE = /\bsendEvent\s*\(\s*withName\s*:\s*"([^"]+)"/g;
1065
+ // JVM-side emitter calls: `emitter.emit("X", body)`. Matches both Java
1066
+ // and Kotlin syntax because the call form is identical. Restricted to
1067
+ // JVM source files in the consumer so we don't re-process JS emits
1068
+ // (which `eventEmitterEdges` already handles).
1069
+ const RN_JVM_EMIT_RE = /\.emit\s*\(\s*"([^"]+)"\s*,/g;
1070
+ // Custom `sendEvent(reactContext, "X", body)` wrapper — extremely common
1071
+ // (react-native-device-info and many libs wrap `DeviceEventManagerModule…emit`
1072
+ // behind a helper whose `.emit(eventName, …)` uses a VARIABLE, so RN_JVM_EMIT_RE
1073
+ // misses it; the literal lives in the wrapper CALL instead). Captures the first
1074
+ // string literal inside a `sendEvent(...)` call. `[^;{}]*?` keeps it on one
1075
+ // statement and stops at a block boundary, so the wrapper DEFINITION (whose `(`
1076
+ // is followed by `… ) {`) never matches. Multi-line tolerant. (java/kotlin/swift)
1077
+ const RN_NATIVE_SENDEVENT_RE = /\bsendEvent\s*\([^;{}]*?"([^"]+)"/g;
1078
+ function rnEventEdges(ctx) {
1079
+ // Native dispatchers (source = the native method whose body sends the
1080
+ // event) and JS handlers (target = the function/method registered as
1081
+ // the listener) keyed by event name.
1082
+ const nativeDispatchersByEvent = new Map();
1083
+ const jsHandlersByEvent = new Map();
1084
+ for (const file of ctx.getAllFiles()) {
1085
+ const content = ctx.readFile(file);
1086
+ if (!content)
1087
+ continue;
1088
+ const nodesInFile = ctx.getNodesInFile(file);
1089
+ const lineOf = (idx) => content.slice(0, idx).split('\n').length;
1090
+ const addDispatcher = (event, line) => {
1091
+ const disp = enclosingFn(nodesInFile, line);
1092
+ if (!disp)
1093
+ return;
1094
+ const set = nativeDispatchersByEvent.get(event) ?? new Set();
1095
+ set.add(disp.id);
1096
+ nativeDispatchersByEvent.set(event, set);
1097
+ };
1098
+ // ObjC side: `sendEventWithName:@"X"` only fires inside `.m`/`.mm`
1099
+ // files (RCTEventEmitter subclasses).
1100
+ if (file.endsWith('.m') || file.endsWith('.mm')) {
1101
+ RN_OBJC_SEND_RE.lastIndex = 0;
1102
+ let m;
1103
+ while ((m = RN_OBJC_SEND_RE.exec(content))) {
1104
+ if (m[1])
1105
+ addDispatcher(m[1], lineOf(m.index));
1106
+ }
1107
+ }
1108
+ // Swift side: same RCTEventEmitter method, parens/named-args syntax.
1109
+ if (file.endsWith('.swift')) {
1110
+ RN_SWIFT_SEND_RE.lastIndex = 0;
1111
+ let m;
1112
+ while ((m = RN_SWIFT_SEND_RE.exec(content))) {
1113
+ if (m[1])
1114
+ addDispatcher(m[1], lineOf(m.index));
1115
+ }
1116
+ RN_NATIVE_SENDEVENT_RE.lastIndex = 0;
1117
+ while ((m = RN_NATIVE_SENDEVENT_RE.exec(content))) {
1118
+ if (m[1])
1119
+ addDispatcher(m[1], lineOf(m.index));
1120
+ }
1121
+ }
1122
+ // JVM side: `.emit("X", …)` in Java/Kotlin, plus the common
1123
+ // `sendEvent(ctx, "X", body)` wrapper. (We pattern-match anywhere in the
1124
+ // file; the JS in-language path uses a separate emitter object pattern and
1125
+ // is already handled by eventEmitterEdges.)
1126
+ if (file.endsWith('.java') || file.endsWith('.kt')) {
1127
+ let m;
1128
+ RN_JVM_EMIT_RE.lastIndex = 0;
1129
+ while ((m = RN_JVM_EMIT_RE.exec(content))) {
1130
+ if (m[1])
1131
+ addDispatcher(m[1], lineOf(m.index));
1132
+ }
1133
+ RN_NATIVE_SENDEVENT_RE.lastIndex = 0;
1134
+ while ((m = RN_NATIVE_SENDEVENT_RE.exec(content))) {
1135
+ if (m[1])
1136
+ addDispatcher(m[1], lineOf(m.index));
1137
+ }
1138
+ }
1139
+ // JS subscribers (.addListener("X", handler)). Restrict to JS-family
1140
+ // files so a native file's `addListener:` (the ObjC method) doesn't
1141
+ // get mistaken for a JS subscription — they're entirely different
1142
+ // things despite sharing a name.
1143
+ if (file.endsWith('.js') ||
1144
+ file.endsWith('.jsx') ||
1145
+ file.endsWith('.ts') ||
1146
+ file.endsWith('.tsx') ||
1147
+ file.endsWith('.mjs') ||
1148
+ file.endsWith('.cjs')) {
1149
+ // Match BOTH the named-handler form (`.addListener('x', fn)`) and
1150
+ // an unnamed-handler form (`.addListener('x', listener)` where
1151
+ // `listener` is a parameter — common in RN wrapper APIs like
1152
+ // RNFirebase's `messaging().onMessageReceived(listener)`). For the
1153
+ // unnamed case we attribute the subscription to the ENCLOSING JS
1154
+ // function (the abstraction layer), giving a reachability-correct
1155
+ // hop even when the actual user-side handler lives one call up.
1156
+ const ADDLISTENER_ANY = /\.(?:on|once|addListener)\(\s*['"]([^'"]+)['"]\s*,\s*([A-Za-z_][\w.]*)/g;
1157
+ ADDLISTENER_ANY.lastIndex = 0;
1158
+ let m;
1159
+ while ((m = ADDLISTENER_ANY.exec(content))) {
1160
+ const event = m[1];
1161
+ const arg = m[2];
1162
+ if (!event || !arg)
1163
+ continue;
1164
+ const bareName = arg.includes('.') ? arg.slice(arg.lastIndexOf('.') + 1) : arg;
1165
+ // Try a named-symbol match first (matches the in-language semantic).
1166
+ const namedHandler = ctx
1167
+ .getNodesByName(bareName)
1168
+ .find((n) => n.kind === 'function' || n.kind === 'method');
1169
+ let targetId = namedHandler?.id ?? null;
1170
+ if (!targetId) {
1171
+ // Fall back to the enclosing function — the subscribe-wrapper
1172
+ // pattern means the event fires THROUGH this function on its
1173
+ // way to user code. Reachability-correct attribution.
1174
+ const enclosing = enclosingFn(nodesInFile, lineOf(m.index));
1175
+ targetId = enclosing?.id ?? null;
1176
+ }
1177
+ if (!targetId) {
1178
+ // Broader fallback for JS object-literal API shape
1179
+ // (`const Foo = { watchX(...) { … addListener(...) … } }`):
1180
+ // method shorthand inside an object literal isn't extracted
1181
+ // as a method node, so enclosingFn returns null. Attribute to
1182
+ // the smallest enclosing `constant` / `variable` node — that's
1183
+ // the API surface a downstream caller would `import` and
1184
+ // invoke. Reachability-correct.
1185
+ const line = lineOf(m.index);
1186
+ let smallest = null;
1187
+ for (const n of nodesInFile) {
1188
+ if (n.kind !== 'constant' && n.kind !== 'variable')
1189
+ continue;
1190
+ const end = n.endLine ?? n.startLine;
1191
+ if (n.startLine <= line && end >= line) {
1192
+ if (!smallest || n.startLine >= smallest.startLine)
1193
+ smallest = n;
1194
+ }
1195
+ }
1196
+ targetId = smallest?.id ?? null;
1197
+ }
1198
+ if (!targetId)
1199
+ continue;
1200
+ const map = jsHandlersByEvent.get(event) ?? new Map();
1201
+ map.set(targetId, `${file}:${lineOf(m.index)}`);
1202
+ jsHandlersByEvent.set(event, map);
1203
+ }
1204
+ }
1205
+ }
1206
+ const edges = [];
1207
+ const seen = new Set();
1208
+ for (const [event, dispatchers] of nativeDispatchersByEvent) {
1209
+ const handlers = jsHandlersByEvent.get(event);
1210
+ if (!handlers)
1211
+ continue;
1212
+ // Same fan-out guard as the in-language channel: generic event names
1213
+ // (e.g. 'change', 'error', 'data') with many handlers/dispatchers
1214
+ // can't be matched precisely without receiver-type info.
1215
+ if (dispatchers.size > EVENT_FANOUT_CAP || handlers.size > EVENT_FANOUT_CAP)
1216
+ continue;
1217
+ for (const d of dispatchers) {
1218
+ for (const [h, registeredAt] of handlers) {
1219
+ if (d === h)
1220
+ continue;
1221
+ const key = `${d}>${h}`;
1222
+ if (seen.has(key))
1223
+ continue;
1224
+ seen.add(key);
1225
+ edges.push({
1226
+ source: d,
1227
+ target: h,
1228
+ kind: 'calls',
1229
+ provenance: 'heuristic',
1230
+ metadata: { synthesizedBy: 'rn-event-channel', event, registeredAt },
1231
+ });
1232
+ }
1233
+ }
1234
+ }
1235
+ return edges;
1236
+ }
1237
+ /**
1238
+ * Phase 6 — React Native Fabric/Codegen view component bridge.
1239
+ *
1240
+ * The Fabric framework extractor (`frameworks/fabric.ts`) emits
1241
+ * `component` nodes named after the JS-visible component (e.g.
1242
+ * `RNSScreenStack`) from each `codegenNativeComponent<Props>('Name')`
1243
+ * spec declaration. The native implementation lives in an ObjC++/.mm or
1244
+ * Kotlin/Java class whose name follows one of RN's conventions:
1245
+ *
1246
+ * - Exact: `RNSScreenStack`
1247
+ * - With suffix: `RNSScreenStackView`, `RNSScreenStackViewManager`,
1248
+ * `RNSScreenStackComponentView`, `RNSScreenStackManager`
1249
+ *
1250
+ * This synthesizer walks every Fabric component node and looks for a
1251
+ * native class matching one of those names; when found, emits a
1252
+ * `calls` edge `component → native class` (provenance `'heuristic'`,
1253
+ * `synthesizedBy:'fabric-native-impl'`) so trace from JSX usage of the
1254
+ * component continues into native.
1255
+ *
1256
+ * The convention-based suffix lookup is precise: there's no name
1257
+ * collision in RN view-manager codebases by design (Codegen output would
1258
+ * conflict otherwise).
1259
+ */
1260
+ const FABRIC_NATIVE_SUFFIXES = ['', 'View', 'ViewManager', 'ComponentView', 'Manager'];
1261
+ /**
1262
+ * Expo Modules cross-platform pairing. An Expo Module exposes the SAME
1263
+ * JS-visible method (`AsyncFunction("getBatteryLevelAsync")`) from BOTH an iOS
1264
+ * (Swift) and an Android (Kotlin) implementation. A JS callsite name-resolves to
1265
+ * only ONE of them, so the other platform's impl looked like nothing called it
1266
+ * (and editing it showed no blast radius). Link the iOS and Android impls of the
1267
+ * same `<module>.<method>` to each other (both directions), so a JS call that
1268
+ * reaches one platform reaches the other, and editing either surfaces the JS
1269
+ * caller. The Expo method nodes are id-prefixed `expo-module:` and qualified
1270
+ * `<file>::<module>.<method>` by the framework extractor.
1271
+ */
1272
+ function expoCrossPlatformEdges(queries) {
1273
+ const edges = [];
1274
+ const seen = new Set();
1275
+ const byKey = new Map();
1276
+ for (const m of queries.getNodesByKind('method')) {
1277
+ if (!m.id.startsWith('expo-module:'))
1278
+ continue;
1279
+ const key = m.qualifiedName.split('::').pop(); // `<module>.<method>`
1280
+ if (!key)
1281
+ continue;
1282
+ const arr = byKey.get(key);
1283
+ if (arr)
1284
+ arr.push(m);
1285
+ else
1286
+ byKey.set(key, [m]);
1287
+ }
1288
+ for (const group of byKey.values()) {
1289
+ if (group.length < 2)
1290
+ continue;
1291
+ for (const a of group) {
1292
+ for (const b of group) {
1293
+ if (a.id === b.id || a.language === b.language)
1294
+ continue; // cross-platform only
1295
+ const key = `${a.id}>${b.id}`;
1296
+ if (seen.has(key))
1297
+ continue;
1298
+ seen.add(key);
1299
+ edges.push({
1300
+ source: a.id,
1301
+ target: b.id,
1302
+ kind: 'calls',
1303
+ line: a.startLine,
1304
+ provenance: 'heuristic',
1305
+ metadata: { synthesizedBy: 'expo-cross-platform', via: a.name },
1306
+ });
1307
+ }
1308
+ }
1309
+ }
1310
+ return edges;
1311
+ }
1312
+ /**
1313
+ * Classic React Native NativeModules cross-platform pairing. A native module
1314
+ * method (`@ReactMethod` on Android, `RCT_EXPORT_METHOD` on iOS) is implemented
1315
+ * on BOTH platforms, but a JS callsite name-resolves to only ONE — so the other
1316
+ * platform's impl looked like nothing called it. A native method that HAS a JS
1317
+ * caller is a confirmed bridge method; link it to the same-named native method
1318
+ * in another language (the other platform's impl) so a JS call reaching one
1319
+ * platform reaches the other, and editing either surfaces the JS caller.
1320
+ *
1321
+ * Names are normalized to the first selector keyword (`getFreeDiskStorage:` →
1322
+ * `getFreeDiskStorage`) — that's the JS-visible name, and how the iOS selector
1323
+ * lines up with the bare Android method name.
1324
+ */
1325
+ function rnCrossPlatformEdges(queries) {
1326
+ const edges = [];
1327
+ const seen = new Set();
1328
+ const NATIVE = new Set(['java', 'kotlin', 'objc', 'cpp']);
1329
+ const JS = new Set(['typescript', 'tsx', 'javascript', 'jsx']);
1330
+ // RN module INFRASTRUCTURE methods exist on every native module (called by the
1331
+ // RN runtime, not user JS), so pairing them by name would cross-link unrelated
1332
+ // modules in a multi-module repo. Skip them — they aren't user-facing methods.
1333
+ const RN_INFRA = new Set([
1334
+ 'addListener', 'removeListeners', 'getConstants', 'constantsToExport', 'getName',
1335
+ 'invalidate', 'initialize', 'getDefaultEventTypes', 'supportedEvents',
1336
+ 'requiresMainQueueSetup', 'methodQueue',
1337
+ ]);
1338
+ const norm = (name) => {
1339
+ const i = name.indexOf(':');
1340
+ return i >= 0 ? name.slice(0, i) : name;
1341
+ };
1342
+ // Index native methods by their JS-visible (normalized) name. Only names with
1343
+ // impls in ≥2 native languages can pair, so the per-method JS-caller check
1344
+ // below only runs for genuine cross-platform candidates.
1345
+ const byName = new Map();
1346
+ for (const m of queries.iterateNodesByKind('method')) {
1347
+ if (!NATIVE.has(m.language))
1348
+ continue;
1349
+ const key = norm(m.name);
1350
+ const arr = byName.get(key);
1351
+ if (arr)
1352
+ arr.push(m);
1353
+ else
1354
+ byName.set(key, [m]);
1355
+ }
1356
+ for (const [groupName, group] of byName) {
1357
+ if (RN_INFRA.has(groupName))
1358
+ continue;
1359
+ const langs = new Set(group.map((m) => m.language));
1360
+ if (langs.size < 2)
1361
+ continue; // single-platform — nothing to pair
1362
+ for (const m of group) {
1363
+ // Is m a bridge method? (a JS-language `calls` edge points at it)
1364
+ const incoming = queries.getIncomingEdges(m.id, ['calls']);
1365
+ if (incoming.length === 0)
1366
+ continue;
1367
+ const sources = queries.getNodesByIds(incoming.map((e) => e.source));
1368
+ const isBridge = incoming.some((e) => {
1369
+ const s = sources.get(e.source);
1370
+ return !!s && JS.has(s.language);
1371
+ });
1372
+ if (!isBridge)
1373
+ continue;
1374
+ // Link to the other-platform impls (both directions).
1375
+ for (const sib of group) {
1376
+ if (sib.id === m.id || sib.language === m.language)
1377
+ continue;
1378
+ for (const [a, b] of [[m, sib], [sib, m]]) {
1379
+ const key = `${a.id}>${b.id}`;
1380
+ if (seen.has(key))
1381
+ continue;
1382
+ seen.add(key);
1383
+ edges.push({
1384
+ source: a.id,
1385
+ target: b.id,
1386
+ kind: 'calls',
1387
+ line: a.startLine,
1388
+ provenance: 'heuristic',
1389
+ metadata: { synthesizedBy: 'rn-cross-platform', via: norm(m.name) },
1390
+ });
1391
+ }
1392
+ }
1393
+ }
1394
+ }
1395
+ return edges;
1396
+ }
1397
+ function fabricNativeImplEdges(ctx) {
1398
+ const edges = [];
1399
+ const seen = new Set();
1400
+ // The Fabric extractor IDs are prefixed `fabric-component:` so we can
1401
+ // filter to just those without iterating all `component` nodes.
1402
+ const components = ctx.getNodesByKind('component').filter((n) => n.id.startsWith('fabric-component:'));
1403
+ if (components.length === 0)
1404
+ return edges;
1405
+ // Pre-index native classes by name for O(1) lookup.
1406
+ const nativeClassesByName = new Map();
1407
+ for (const n of ctx.getNodesByKind('class')) {
1408
+ if (n.language !== 'objc' && n.language !== 'kotlin' && n.language !== 'java' && n.language !== 'cpp')
1409
+ continue;
1410
+ const arr = nativeClassesByName.get(n.name);
1411
+ if (arr)
1412
+ arr.push(n);
1413
+ else
1414
+ nativeClassesByName.set(n.name, [n]);
1415
+ }
1416
+ for (const component of components) {
1417
+ for (const suffix of FABRIC_NATIVE_SUFFIXES) {
1418
+ const candidate = component.name + suffix;
1419
+ const matches = nativeClassesByName.get(candidate);
1420
+ if (!matches || matches.length === 0)
1421
+ continue;
1422
+ // Link the component node to every matching native class (iOS +
1423
+ // Android each have one).
1424
+ for (const native of matches) {
1425
+ const key = `${component.id}>${native.id}`;
1426
+ if (seen.has(key))
1427
+ continue;
1428
+ seen.add(key);
1429
+ edges.push({
1430
+ source: component.id,
1431
+ target: native.id,
1432
+ kind: 'calls',
1433
+ provenance: 'heuristic',
1434
+ metadata: {
1435
+ synthesizedBy: 'fabric-native-impl',
1436
+ viaSuffix: suffix || '(exact)',
1437
+ componentName: component.name,
1438
+ },
1439
+ });
1440
+ }
1441
+ }
1442
+ }
1443
+ return edges;
1444
+ }
1445
+ /**
1446
+ * MyBatis: link a Java mapper interface method to the XML statement that holds
1447
+ * its SQL. The XML extractor (`src/extraction/mybatis-extractor.ts`) qualifies
1448
+ * each `<select|insert|update|delete|sql id="X">` as `<namespace>::<id>` where
1449
+ * `<namespace>` is the Java FQN of the mapper interface. A Java method's
1450
+ * qualifiedName ends with `<ClassName>::<methodName>`, so we suffix-match the
1451
+ * last two segments of the XML qualified name to find a unique Java method by
1452
+ * `<ClassName>::<methodName>` (`ClassName` = last dotted segment of the XML
1453
+ * namespace). Cross-mapper `<include refid="other.X">` references go through
1454
+ * the normal qualified-name resolver — only the Java↔XML bridge is synthetic.
1455
+ *
1456
+ * Precision over recall: ambiguous mappers (multiple Java classes with the
1457
+ * same simple name) are dropped. We need-not bridge by package because Java
1458
+ * mapper interfaces are typically uniquely named within a project.
1459
+ */
1460
+ function mybatisJavaXmlEdges(queries) {
1461
+ const edges = [];
1462
+ const seen = new Set();
1463
+ // Index Java methods by `<ClassName>::<methodName>` for O(1) lookup.
1464
+ const javaIndex = new Map();
1465
+ for (const m of queries.iterateNodesByKind('method')) {
1466
+ if (m.language !== 'java' && m.language !== 'kotlin')
1467
+ continue;
1468
+ const parts = m.qualifiedName.split('::');
1469
+ const last = parts[parts.length - 1];
1470
+ const cls = parts[parts.length - 2];
1471
+ if (!last || !cls)
1472
+ continue;
1473
+ const key = `${cls}::${last}`;
1474
+ const arr = javaIndex.get(key);
1475
+ if (arr)
1476
+ arr.push(m);
1477
+ else
1478
+ javaIndex.set(key, [m]);
1479
+ }
1480
+ for (const xml of queries.iterateNodesByKind('method')) {
1481
+ if (xml.language !== 'xml')
1482
+ continue;
1483
+ // Qualified name: `<namespace>::<id>`. Extract the simple class name.
1484
+ const colonIdx = xml.qualifiedName.lastIndexOf('::');
1485
+ if (colonIdx < 0)
1486
+ continue;
1487
+ const namespace = xml.qualifiedName.slice(0, colonIdx);
1488
+ const id = xml.qualifiedName.slice(colonIdx + 2);
1489
+ if (!namespace || !id)
1490
+ continue;
1491
+ const dotIdx = namespace.lastIndexOf('.');
1492
+ const className = dotIdx >= 0 ? namespace.slice(dotIdx + 1) : namespace;
1493
+ const candidates = javaIndex.get(`${className}::${id}`);
1494
+ if (!candidates || candidates.length === 0)
1495
+ continue;
1496
+ // Drop ambiguous matches (multiple same-name classes); the user can
1497
+ // disambiguate by adding the package-suffix match in a future enhancement.
1498
+ if (candidates.length > 1)
1499
+ continue;
1500
+ const java = candidates[0];
1501
+ const key = `${java.id}>${xml.id}`;
1502
+ if (seen.has(key))
1503
+ continue;
1504
+ seen.add(key);
1505
+ edges.push({
1506
+ source: java.id,
1507
+ target: xml.id,
1508
+ kind: 'calls',
1509
+ line: java.startLine,
1510
+ provenance: 'heuristic',
1511
+ metadata: {
1512
+ synthesizedBy: 'mybatis-java-xml',
1513
+ via: `${className}.${id}`,
1514
+ registeredAt: `${xml.filePath}:${xml.startLine}`,
1515
+ },
1516
+ });
1517
+ }
1518
+ return edges;
1519
+ }
1520
+ /**
1521
+ * Gin middleware chain. Gin runs its entire handler chain through one dynamic
1522
+ * line in `(*Context).Next`:
1523
+ * for c.index < len(c.handlers) { c.handlers[c.index](c); c.index++ }
1524
+ * `c.handlers` is a `HandlersChain` (`[]HandlerFunc`) assembled at registration
1525
+ * time by `combineHandlers` from the funcs passed to `r.Use(...)` /
1526
+ * `r.GET("/path", h...)` / `r.Handle(...)`. Because the call is a computed index
1527
+ * into a runtime-built slice, tree-sitter resolves `c.handlers[c.index](c)` to
1528
+ * NOTHING — so `callees(Next)` is just the `len()` helper and the flow
1529
+ * `ServeHTTP → handleHTTPRequest → Next` dead-ends at the exact symbol the
1530
+ * "how do requests flow through the middleware chain" question is about. The
1531
+ * agent then re-queries Next and falls back to Read/grep (validated: the gin
1532
+ * WITH-arm rabbit-holed on precisely this dead-end).
1533
+ *
1534
+ * Bridge it: find the chain DISPATCHER (a Go method whose body invokes a
1535
+ * `handlers` slice by index) and link it → every HandlerFunc registered via a
1536
+ * gin registration call, so `callees(Next)` and `trace(ServeHTTP, <handler>)`
1537
+ * connect end-to-end. Named handlers only (`gin.Logger()` → `Logger`,
1538
+ * `authMiddleware`); inline closures are anonymous and skipped. Like
1539
+ * react-render / interface-impl this is a deliberate over-approximation —
1540
+ * reachability-correct (any registered handler CAN run for some route), capped,
1541
+ * and gated on the dispatcher existing so it never runs on non-gin Go repos.
1542
+ * Provenance `heuristic`, `synthesizedBy:'gin-middleware-chain'`; `registeredAt`
1543
+ * is the `.Use`/`.GET` site an agent would otherwise grep for.
1544
+ */
1545
+ const GIN_DISPATCH_RE = /\.handlers\s*\[[^\]]*\]\s*\(/; // c.handlers[c.index](c)
1546
+ const GIN_REG_RE = /\.(?:Use|GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|Any|Handle)\s*\(/g;
1547
+ /** Balanced `(...)` body starting at the '(' index; null if unbalanced. */
1548
+ function goBalancedArgs(s, openIdx) {
1549
+ let depth = 0;
1550
+ for (let i = openIdx; i < s.length; i++) {
1551
+ const c = s[i];
1552
+ if (c === '(')
1553
+ depth++;
1554
+ else if (c === ')') {
1555
+ depth--;
1556
+ if (depth === 0)
1557
+ return s.slice(openIdx + 1, i);
1558
+ }
1559
+ }
1560
+ return null;
1561
+ }
1562
+ /** Split a top-level comma list, respecting nested () [] {}. */
1563
+ function goSplitArgs(args) {
1564
+ const out = [];
1565
+ let depth = 0, cur = '';
1566
+ for (const c of args) {
1567
+ if (c === '(' || c === '[' || c === '{') {
1568
+ depth++;
1569
+ cur += c;
1570
+ }
1571
+ else if (c === ')' || c === ']' || c === '}') {
1572
+ depth--;
1573
+ cur += c;
1574
+ }
1575
+ else if (c === ',' && depth === 0) {
1576
+ out.push(cur);
1577
+ cur = '';
1578
+ }
1579
+ else
1580
+ cur += c;
1581
+ }
1582
+ if (cur.trim())
1583
+ out.push(cur);
1584
+ return out;
1585
+ }
1586
+ /** Tail ident of a handler arg: `gin.Logger()`→`Logger`, `mw`→`mw`; null for string paths / closures. */
1587
+ function goHandlerIdent(expr) {
1588
+ const cleaned = expr.trim().replace(/\(\s*\)$/, ''); // drop a trailing call ()
1589
+ if (!cleaned || cleaned.startsWith('"') || cleaned.startsWith('`') || cleaned.startsWith('func'))
1590
+ return null;
1591
+ const m = cleaned.match(/(?:\.|^)([A-Za-z_]\w*)$/);
1592
+ return m ? m[1] : null;
1593
+ }
1594
+ function ginMiddlewareChainEdges(queries, ctx) {
1595
+ // 1. Find the chain dispatcher(s): a Go method that invokes a `handlers` slice by index.
1596
+ const dispatchers = [];
1597
+ for (const n of queries.iterateNodesByKind('method')) {
1598
+ if (n.language !== 'go')
1599
+ continue;
1600
+ const content = ctx.readFile(n.filePath);
1601
+ const src = content && sliceLines(content, n.startLine, n.endLine);
1602
+ if (src && GIN_DISPATCH_RE.test(src))
1603
+ dispatchers.push(n);
1604
+ }
1605
+ if (dispatchers.length === 0)
1606
+ return []; // not a gin repo — bail
1607
+ // 2. Collect handler identifiers registered via gin registration calls
1608
+ // (.Use / .GET / … / .Handle). String args (paths/methods) and inline
1609
+ // closures are dropped by goHandlerIdent; the rest are HandlerFuncs.
1610
+ const registered = new Map(); // name → registeredAt (file:line)
1611
+ for (const file of ctx.getAllFiles()) {
1612
+ if (!file.endsWith('.go'))
1613
+ continue;
1614
+ const content = ctx.readFile(file);
1615
+ if (!content || (!content.includes('.Use(') && !/\.(?:GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|Any|Handle)\(/.test(content)))
1616
+ continue;
1617
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'go');
1618
+ GIN_REG_RE.lastIndex = 0;
1619
+ let m;
1620
+ while ((m = GIN_REG_RE.exec(safe))) {
1621
+ const parenIdx = m.index + m[0].length - 1;
1622
+ const argStr = goBalancedArgs(safe, parenIdx);
1623
+ if (!argStr)
1624
+ continue;
1625
+ const line = safe.slice(0, m.index).split('\n').length;
1626
+ for (const arg of goSplitArgs(argStr)) {
1627
+ const name = goHandlerIdent(arg);
1628
+ if (name && !registered.has(name))
1629
+ registered.set(name, `${file}:${line}`);
1630
+ }
1631
+ }
1632
+ }
1633
+ if (registered.size === 0)
1634
+ return [];
1635
+ // 3. Link each dispatcher → each registered handler node (dedup, capped).
1636
+ const edges = [];
1637
+ const seen = new Set();
1638
+ for (const disp of dispatchers) {
1639
+ let added = 0;
1640
+ for (const [name, registeredAt] of registered) {
1641
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
1642
+ break;
1643
+ const handler = ctx.getNodesByName(name).find((n) => (n.kind === 'function' || n.kind === 'method') && n.language === 'go');
1644
+ if (!handler || handler.id === disp.id)
1645
+ continue;
1646
+ const key = `${disp.id}>${handler.id}`;
1647
+ if (seen.has(key))
1648
+ continue;
1649
+ seen.add(key);
1650
+ edges.push({
1651
+ source: disp.id, target: handler.id, kind: 'calls', line: disp.startLine,
1652
+ provenance: 'heuristic',
1653
+ metadata: { synthesizedBy: 'gin-middleware-chain', via: name, registeredAt },
1654
+ });
1655
+ added++;
1656
+ }
1657
+ }
1658
+ return edges;
1659
+ }
1660
+ /**
1661
+ * Delphi form code-behind: a form unit `UFRMAbout.pas` owns its visual form
1662
+ * definition `UFRMAbout.dfm` (VCL) / `.fmx` (FireMonkey) — paired by basename in
1663
+ * the same directory, wired by the `{$R *.dfm}` directive rather than a `uses`
1664
+ * clause. Link the unit → its form so a `.dfm`/`.fmx` used only as a form
1665
+ * definition isn't orphaned, and editing the form surfaces its code-behind unit.
1666
+ */
1667
+ function pascalFormEdges(ctx) {
1668
+ const edges = [];
1669
+ const allFiles = new Set(ctx.getAllFiles());
1670
+ for (const file of allFiles) {
1671
+ if (!/\.(dfm|fmx)$/i.test(file))
1672
+ continue;
1673
+ const pasFile = file.replace(/\.(dfm|fmx)$/i, '.pas');
1674
+ if (!allFiles.has(pasFile))
1675
+ continue;
1676
+ const formNode = ctx.getNodesInFile(file).find((n) => n.kind === 'file');
1677
+ const unitNode = ctx.getNodesInFile(pasFile).find((n) => n.kind === 'file');
1678
+ if (!formNode || !unitNode)
1679
+ continue;
1680
+ edges.push({
1681
+ source: unitNode.id,
1682
+ target: formNode.id,
1683
+ kind: 'references',
1684
+ line: unitNode.startLine,
1685
+ provenance: 'heuristic',
1686
+ metadata: { synthesizedBy: 'pascal-form', registeredAt: pasFile },
1687
+ });
1688
+ }
1689
+ return edges;
1690
+ }
1691
+ /**
1692
+ * SvelteKit file-convention data flow. A route directory's `+page.svelte` (a
1693
+ * `component` node) receives its `data` from the sibling `+page.server.{ts,js}`
1694
+ * / `+page.{ts,js}` `load` function and posts forms to its `actions` — wired by
1695
+ * the framework BY FILE PATH, with no static import between them. So editing a
1696
+ * `load` shows no impact on the page it feeds, and the page looks like it has no
1697
+ * server-side dependency. Link the page component to its sibling loader's
1698
+ * `load` / `actions` (same for `+layout`). The pairing is path-deterministic
1699
+ * (same directory, matching `+page`/`+layout` prefix), so it's precise — but
1700
+ * it's a framework-convention edge, so provenance stays `heuristic`.
1701
+ *
1702
+ * Direction: page → load, so `getImpactRadius(load)` surfaces the page (editing
1703
+ * a loader's data shows the page it feeds) and the page's dependencies include
1704
+ * its loader.
1705
+ */
1706
+ function svelteKitLoadEdges(ctx) {
1707
+ const edges = [];
1708
+ const allFiles = new Set(ctx.getAllFiles());
1709
+ const HOOKS = new Set(['load', 'actions']);
1710
+ const HOOK_KINDS = new Set(['function', 'method', 'constant', 'variable']);
1711
+ for (const file of allFiles) {
1712
+ const m = file.match(/(.*\/)(\+(?:page|layout))\.svelte$/);
1713
+ if (!m)
1714
+ continue;
1715
+ const dir = m[1];
1716
+ const prefix = m[2];
1717
+ const page = ctx.getNodesInFile(file).find((n) => n.kind === 'component');
1718
+ if (!page)
1719
+ continue;
1720
+ for (const ext of ['.server.ts', '.server.js', '.ts', '.js']) {
1721
+ const loaderFile = `${dir}${prefix}${ext}`;
1722
+ if (!allFiles.has(loaderFile))
1723
+ continue;
1724
+ for (const hook of ctx.getNodesInFile(loaderFile)) {
1725
+ if (!HOOK_KINDS.has(hook.kind) || !HOOKS.has(hook.name))
1726
+ continue;
1727
+ edges.push({
1728
+ source: page.id,
1729
+ target: hook.id,
1730
+ kind: 'references',
1731
+ line: page.startLine,
1732
+ provenance: 'heuristic',
1733
+ metadata: {
1734
+ synthesizedBy: 'sveltekit-load',
1735
+ via: hook.name,
1736
+ registeredAt: `${loaderFile}:${hook.startLine ?? 0}`,
1737
+ },
1738
+ });
1739
+ }
1740
+ }
1741
+ }
1742
+ return edges;
1743
+ }
1744
+ /**
1745
+ * Synthesize dispatcher→callback edges (field observers + EventEmitters +
1746
+ * React re-render + JSX children + Vue templates + SvelteKit load + RN event
1747
+ * channel + Fabric native-impl + MyBatis Java↔XML + Gin middleware chain).
1748
+ * Returns the count added. Never throws into indexing — callers wrap in try/catch.
1749
+ */
1750
+ function synthesizeCallbackEdges(queries, ctx) {
1751
+ // Cross-file Go method→type `contains` edges must be synthesized AND persisted
1752
+ // FIRST: a method declared in a different file from its receiver type is
1753
+ // otherwise orphaned from the struct, and goImplementsEdges (next) derives a
1754
+ // struct's method set from its `contains` edges — so without this it would
1755
+ // under-count the interfaces a cross-file struct satisfies. (#583)
1756
+ const goMethodContains = goCrossFileMethodContainsEdges(queries);
1757
+ if (goMethodContains.length > 0)
1758
+ queries.insertEdges(goMethodContains);
1759
+ // Go implicit `implements` edges must be synthesized AND persisted next: the
1760
+ // interface-dispatch bridge below reads `implements` edges from the DB, and
1761
+ // Go has none statically. (Other languages already have static implements
1762
+ // edges from extraction, so they don't need this pre-pass.)
1763
+ const goImpl = goImplementsEdges(queries);
1764
+ if (goImpl.length > 0)
1765
+ queries.insertEdges(goImpl);
1766
+ const fieldEdges = fieldChannelEdges(queries, ctx);
1767
+ const closureCollEdges = closureCollectionEdges(queries, ctx);
1768
+ const emitterEdges = eventEmitterEdges(ctx);
1769
+ const renderEdges = reactRenderEdges(queries, ctx);
1770
+ const jsxEdges = reactJsxChildEdges(ctx);
1771
+ const vueEdges = vueTemplateEdges(ctx);
1772
+ const svelteKitEdges = svelteKitLoadEdges(ctx);
1773
+ const pascalEdges = pascalFormEdges(ctx);
1774
+ const flutterEdges = flutterBuildEdges(queries, ctx);
1775
+ const cppEdges = cppOverrideEdges(queries);
1776
+ const ifaceEdges = interfaceOverrideEdges(queries);
1777
+ const kotlinExpectActual = kotlinExpectActualEdges(queries);
1778
+ const goGrpcEdges = goGrpcStubImplEdges(queries);
1779
+ const rnEventEdgesList = rnEventEdges(ctx);
1780
+ const fabricNativeEdges = fabricNativeImplEdges(ctx);
1781
+ const expoXPlatEdges = expoCrossPlatformEdges(queries);
1782
+ const rnXPlatEdges = rnCrossPlatformEdges(queries);
1783
+ const mybatisEdges = mybatisJavaXmlEdges(queries);
1784
+ const ginEdges = ginMiddlewareChainEdges(queries, ctx);
1785
+ const merged = [];
1786
+ const seen = new Set();
1787
+ for (const e of [
1788
+ ...fieldEdges,
1789
+ ...closureCollEdges,
1790
+ ...emitterEdges,
1791
+ ...renderEdges,
1792
+ ...jsxEdges,
1793
+ ...vueEdges,
1794
+ ...svelteKitEdges,
1795
+ ...pascalEdges,
1796
+ ...flutterEdges,
1797
+ ...cppEdges,
1798
+ ...ifaceEdges,
1799
+ ...kotlinExpectActual,
1800
+ ...goGrpcEdges,
1801
+ ...rnEventEdgesList,
1802
+ ...fabricNativeEdges,
1803
+ ...expoXPlatEdges,
1804
+ ...rnXPlatEdges,
1805
+ ...mybatisEdges,
1806
+ ...ginEdges,
1807
+ ]) {
1808
+ const key = `${e.source}>${e.target}`;
1809
+ if (seen.has(key))
1810
+ continue;
1811
+ seen.add(key);
1812
+ merged.push(e);
1813
+ }
1814
+ if (merged.length > 0)
1815
+ queries.insertEdges(merged);
1816
+ return merged.length + goImpl.length + goMethodContains.length;
1817
+ }
1818
+ //# sourceMappingURL=callback-synthesizer.js.map