@optave/codegraph 3.12.0 → 3.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (524) hide show
  1. package/README.md +83 -46
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +38 -40
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/rules/b2.d.ts +7 -0
  6. package/dist/ast-analysis/rules/b2.d.ts.map +1 -0
  7. package/dist/ast-analysis/rules/b2.js +240 -0
  8. package/dist/ast-analysis/rules/b2.js.map +1 -0
  9. package/dist/ast-analysis/rules/b3.d.ts +6 -0
  10. package/dist/ast-analysis/rules/b3.d.ts.map +1 -0
  11. package/dist/ast-analysis/rules/b3.js +105 -0
  12. package/dist/ast-analysis/rules/b3.js.map +1 -0
  13. package/dist/ast-analysis/rules/b4.d.ts +9 -0
  14. package/dist/ast-analysis/rules/b4.d.ts.map +1 -0
  15. package/dist/ast-analysis/rules/b4.js +361 -0
  16. package/dist/ast-analysis/rules/b4.js.map +1 -0
  17. package/dist/ast-analysis/rules/b5.d.ts +4 -0
  18. package/dist/ast-analysis/rules/b5.d.ts.map +1 -0
  19. package/dist/ast-analysis/rules/b5.js +52 -0
  20. package/dist/ast-analysis/rules/b5.js.map +1 -0
  21. package/dist/ast-analysis/rules/c.d.ts +4 -0
  22. package/dist/ast-analysis/rules/c.d.ts.map +1 -0
  23. package/dist/ast-analysis/rules/c.js +143 -0
  24. package/dist/ast-analysis/rules/c.js.map +1 -0
  25. package/dist/ast-analysis/rules/index.d.ts.map +1 -1
  26. package/dist/ast-analysis/rules/index.js +34 -0
  27. package/dist/ast-analysis/rules/index.js.map +1 -1
  28. package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
  29. package/dist/ast-analysis/rules/javascript.js +3 -0
  30. package/dist/ast-analysis/rules/javascript.js.map +1 -1
  31. package/dist/ast-analysis/shared.d.ts.map +1 -1
  32. package/dist/ast-analysis/shared.js +2 -0
  33. package/dist/ast-analysis/shared.js.map +1 -1
  34. package/dist/ast-analysis/visitor-utils.d.ts +1 -0
  35. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  36. package/dist/ast-analysis/visitor-utils.js +5 -0
  37. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  38. package/dist/ast-analysis/visitor.d.ts.map +1 -1
  39. package/dist/ast-analysis/visitor.js +60 -47
  40. package/dist/ast-analysis/visitor.js.map +1 -1
  41. package/dist/ast-analysis/visitors/cfg-visitor.d.ts.map +1 -1
  42. package/dist/ast-analysis/visitors/cfg-visitor.js +126 -76
  43. package/dist/ast-analysis/visitors/cfg-visitor.js.map +1 -1
  44. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  45. package/dist/ast-analysis/visitors/complexity-visitor.js +27 -15
  46. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  47. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  48. package/dist/ast-analysis/visitors/dataflow-visitor.js +54 -21
  49. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  50. package/dist/cli/commands/audit.d.ts.map +1 -1
  51. package/dist/cli/commands/audit.js +2 -1
  52. package/dist/cli/commands/audit.js.map +1 -1
  53. package/dist/cli/commands/batch.d.ts.map +1 -1
  54. package/dist/cli/commands/batch.js +1 -0
  55. package/dist/cli/commands/batch.js.map +1 -1
  56. package/dist/cli/commands/build.d.ts.map +1 -1
  57. package/dist/cli/commands/build.js +6 -1
  58. package/dist/cli/commands/build.js.map +1 -1
  59. package/dist/cli/commands/config.d.ts +3 -0
  60. package/dist/cli/commands/config.d.ts.map +1 -0
  61. package/dist/cli/commands/config.js +275 -0
  62. package/dist/cli/commands/config.js.map +1 -0
  63. package/dist/cli/commands/roles.d.ts.map +1 -1
  64. package/dist/cli/commands/roles.js +6 -1
  65. package/dist/cli/commands/roles.js.map +1 -1
  66. package/dist/cli/commands/triage.js +1 -1
  67. package/dist/cli/commands/triage.js.map +1 -1
  68. package/dist/cli/index.d.ts.map +1 -1
  69. package/dist/cli/index.js +10 -0
  70. package/dist/cli/index.js.map +1 -1
  71. package/dist/cli/shared/options.d.ts +2 -1
  72. package/dist/cli/shared/options.d.ts.map +1 -1
  73. package/dist/cli/shared/options.js +11 -1
  74. package/dist/cli/shared/options.js.map +1 -1
  75. package/dist/cli/types.d.ts +2 -0
  76. package/dist/cli/types.d.ts.map +1 -1
  77. package/dist/db/better-sqlite3.d.ts +2 -1
  78. package/dist/db/better-sqlite3.d.ts.map +1 -1
  79. package/dist/db/better-sqlite3.js.map +1 -1
  80. package/dist/db/connection.d.ts +7 -1
  81. package/dist/db/connection.d.ts.map +1 -1
  82. package/dist/db/connection.js +20 -5
  83. package/dist/db/connection.js.map +1 -1
  84. package/dist/db/index.d.ts +1 -1
  85. package/dist/db/index.d.ts.map +1 -1
  86. package/dist/db/index.js +1 -1
  87. package/dist/db/index.js.map +1 -1
  88. package/dist/db/migrations.d.ts.map +1 -1
  89. package/dist/db/migrations.js +69 -1
  90. package/dist/db/migrations.js.map +1 -1
  91. package/dist/db/repository/build-stmts.d.ts.map +1 -1
  92. package/dist/db/repository/build-stmts.js +18 -0
  93. package/dist/db/repository/build-stmts.js.map +1 -1
  94. package/dist/db/repository/dataflow.d.ts +5 -0
  95. package/dist/db/repository/dataflow.d.ts.map +1 -1
  96. package/dist/db/repository/dataflow.js +14 -0
  97. package/dist/db/repository/dataflow.js.map +1 -1
  98. package/dist/db/repository/index.d.ts +1 -1
  99. package/dist/db/repository/index.d.ts.map +1 -1
  100. package/dist/db/repository/index.js +1 -1
  101. package/dist/db/repository/index.js.map +1 -1
  102. package/dist/db/repository/native-repository.d.ts.map +1 -1
  103. package/dist/db/repository/native-repository.js +47 -34
  104. package/dist/db/repository/native-repository.js.map +1 -1
  105. package/dist/domain/analysis/context.d.ts +2 -2
  106. package/dist/domain/analysis/dependencies.d.ts +2 -2
  107. package/dist/domain/analysis/diff-impact.d.ts +2 -2
  108. package/dist/domain/analysis/fn-impact.d.ts +3 -1
  109. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  110. package/dist/domain/analysis/fn-impact.js +4 -0
  111. package/dist/domain/analysis/fn-impact.js.map +1 -1
  112. package/dist/domain/analysis/implementations.d.ts +2 -2
  113. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  114. package/dist/domain/analysis/module-map.js +32 -5
  115. package/dist/domain/analysis/module-map.js.map +1 -1
  116. package/dist/domain/analysis/roles.d.ts +7 -1
  117. package/dist/domain/analysis/roles.d.ts.map +1 -1
  118. package/dist/domain/analysis/roles.js +16 -0
  119. package/dist/domain/analysis/roles.js.map +1 -1
  120. package/dist/domain/analysis/symbol-lookup.d.ts +4 -4
  121. package/dist/domain/graph/builder/call-resolver.d.ts +29 -13
  122. package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -1
  123. package/dist/domain/graph/builder/call-resolver.js +125 -205
  124. package/dist/domain/graph/builder/call-resolver.js.map +1 -1
  125. package/dist/domain/graph/builder/cha.d.ts +9 -1
  126. package/dist/domain/graph/builder/cha.d.ts.map +1 -1
  127. package/dist/domain/graph/builder/cha.js +17 -2
  128. package/dist/domain/graph/builder/cha.js.map +1 -1
  129. package/dist/domain/graph/builder/context.d.ts +1 -0
  130. package/dist/domain/graph/builder/context.d.ts.map +1 -1
  131. package/dist/domain/graph/builder/context.js.map +1 -1
  132. package/dist/domain/graph/builder/helpers.d.ts +24 -1
  133. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  134. package/dist/domain/graph/builder/helpers.js +174 -65
  135. package/dist/domain/graph/builder/helpers.js.map +1 -1
  136. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  137. package/dist/domain/graph/builder/incremental.js +166 -97
  138. package/dist/domain/graph/builder/incremental.js.map +1 -1
  139. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  140. package/dist/domain/graph/builder/pipeline.js +46 -5
  141. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  142. package/dist/domain/graph/builder/stages/build-edges.d.ts +0 -2
  143. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  144. package/dist/domain/graph/builder/stages/build-edges.js +554 -538
  145. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  146. package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
  147. package/dist/domain/graph/builder/stages/collect-files.js +10 -7
  148. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  149. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  150. package/dist/domain/graph/builder/stages/detect-changes.js +3 -2
  151. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  152. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  153. package/dist/domain/graph/builder/stages/finalize.js +4 -0
  154. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  155. package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -1
  156. package/dist/domain/graph/builder/stages/native-orchestrator.js +952 -343
  157. package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -1
  158. package/dist/domain/graph/builder/stages/resolve-imports.js +1 -1
  159. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  160. package/dist/domain/graph/resolver/points-to.d.ts.map +1 -1
  161. package/dist/domain/graph/resolver/points-to.js +105 -57
  162. package/dist/domain/graph/resolver/points-to.js.map +1 -1
  163. package/dist/domain/graph/resolver/strategy.d.ts +61 -0
  164. package/dist/domain/graph/resolver/strategy.d.ts.map +1 -0
  165. package/dist/domain/graph/resolver/strategy.js +222 -0
  166. package/dist/domain/graph/resolver/strategy.js.map +1 -0
  167. package/dist/domain/graph/watcher.d.ts.map +1 -1
  168. package/dist/domain/graph/watcher.js +16 -9
  169. package/dist/domain/graph/watcher.js.map +1 -1
  170. package/dist/domain/parser.d.ts +16 -5
  171. package/dist/domain/parser.d.ts.map +1 -1
  172. package/dist/domain/parser.js +58 -17
  173. package/dist/domain/parser.js.map +1 -1
  174. package/dist/domain/queries.d.ts +1 -1
  175. package/dist/domain/queries.d.ts.map +1 -1
  176. package/dist/domain/queries.js +1 -1
  177. package/dist/domain/queries.js.map +1 -1
  178. package/dist/domain/wasm-worker-entry.js +13 -2
  179. package/dist/domain/wasm-worker-entry.js.map +1 -1
  180. package/dist/domain/wasm-worker-pool.d.ts.map +1 -1
  181. package/dist/domain/wasm-worker-pool.js +26 -5
  182. package/dist/domain/wasm-worker-pool.js.map +1 -1
  183. package/dist/domain/wasm-worker-protocol.d.ts +8 -0
  184. package/dist/domain/wasm-worker-protocol.d.ts.map +1 -1
  185. package/dist/extractors/cpp.d.ts.map +1 -1
  186. package/dist/extractors/cpp.js +42 -1
  187. package/dist/extractors/cpp.js.map +1 -1
  188. package/dist/extractors/cuda.d.ts.map +1 -1
  189. package/dist/extractors/cuda.js +42 -1
  190. package/dist/extractors/cuda.js.map +1 -1
  191. package/dist/extractors/dart.js +48 -3
  192. package/dist/extractors/dart.js.map +1 -1
  193. package/dist/extractors/groovy.js +62 -3
  194. package/dist/extractors/groovy.js.map +1 -1
  195. package/dist/extractors/helpers.d.ts +15 -2
  196. package/dist/extractors/helpers.d.ts.map +1 -1
  197. package/dist/extractors/helpers.js +45 -1
  198. package/dist/extractors/helpers.js.map +1 -1
  199. package/dist/extractors/java.d.ts.map +1 -1
  200. package/dist/extractors/java.js +85 -8
  201. package/dist/extractors/java.js.map +1 -1
  202. package/dist/extractors/javascript.d.ts.map +1 -1
  203. package/dist/extractors/javascript.js +686 -169
  204. package/dist/extractors/javascript.js.map +1 -1
  205. package/dist/extractors/kotlin.js +58 -3
  206. package/dist/extractors/kotlin.js.map +1 -1
  207. package/dist/extractors/objc.js +25 -2
  208. package/dist/extractors/objc.js.map +1 -1
  209. package/dist/extractors/scala.js +62 -2
  210. package/dist/extractors/scala.js.map +1 -1
  211. package/dist/extractors/swift.js +52 -3
  212. package/dist/extractors/swift.js.map +1 -1
  213. package/dist/features/audit.js +26 -23
  214. package/dist/features/audit.js.map +1 -1
  215. package/dist/features/boundaries.d.ts.map +1 -1
  216. package/dist/features/boundaries.js +12 -9
  217. package/dist/features/boundaries.js.map +1 -1
  218. package/dist/features/cfg.d.ts.map +1 -1
  219. package/dist/features/cfg.js +25 -18
  220. package/dist/features/cfg.js.map +1 -1
  221. package/dist/features/check.d.ts.map +1 -1
  222. package/dist/features/check.js +18 -5
  223. package/dist/features/check.js.map +1 -1
  224. package/dist/features/communities.d.ts +4 -2
  225. package/dist/features/communities.d.ts.map +1 -1
  226. package/dist/features/communities.js +6 -4
  227. package/dist/features/communities.js.map +1 -1
  228. package/dist/features/dataflow.d.ts +60 -0
  229. package/dist/features/dataflow.d.ts.map +1 -1
  230. package/dist/features/dataflow.js +530 -6
  231. package/dist/features/dataflow.js.map +1 -1
  232. package/dist/features/manifesto.d.ts.map +1 -1
  233. package/dist/features/manifesto.js +59 -72
  234. package/dist/features/manifesto.js.map +1 -1
  235. package/dist/features/sequence.d.ts.map +1 -1
  236. package/dist/features/sequence.js +27 -22
  237. package/dist/features/sequence.js.map +1 -1
  238. package/dist/features/snapshot.d.ts.map +1 -1
  239. package/dist/features/snapshot.js +36 -28
  240. package/dist/features/snapshot.js.map +1 -1
  241. package/dist/features/structure-query.d.ts +1 -1
  242. package/dist/features/structure-query.d.ts.map +1 -1
  243. package/dist/features/structure-query.js +6 -6
  244. package/dist/features/structure-query.js.map +1 -1
  245. package/dist/features/structure.d.ts.map +1 -1
  246. package/dist/features/structure.js +150 -62
  247. package/dist/features/structure.js.map +1 -1
  248. package/dist/features/triage.d.ts.map +1 -1
  249. package/dist/features/triage.js +18 -11
  250. package/dist/features/triage.js.map +1 -1
  251. package/dist/graph/algorithms/bfs.d.ts +1 -1
  252. package/dist/graph/algorithms/bfs.d.ts.map +1 -1
  253. package/dist/graph/algorithms/bfs.js +14 -13
  254. package/dist/graph/algorithms/bfs.js.map +1 -1
  255. package/dist/graph/algorithms/tarjan.d.ts.map +1 -1
  256. package/dist/graph/algorithms/tarjan.js +5 -0
  257. package/dist/graph/algorithms/tarjan.js.map +1 -1
  258. package/dist/graph/builders/dependency.js +28 -22
  259. package/dist/graph/builders/dependency.js.map +1 -1
  260. package/dist/graph/classifiers/roles.d.ts +10 -1
  261. package/dist/graph/classifiers/roles.d.ts.map +1 -1
  262. package/dist/graph/classifiers/roles.js +60 -6
  263. package/dist/graph/classifiers/roles.js.map +1 -1
  264. package/dist/index.d.ts +1 -1
  265. package/dist/index.d.ts.map +1 -1
  266. package/dist/index.js +1 -1
  267. package/dist/index.js.map +1 -1
  268. package/dist/infrastructure/config.d.ts +87 -4
  269. package/dist/infrastructure/config.d.ts.map +1 -1
  270. package/dist/infrastructure/config.js +424 -22
  271. package/dist/infrastructure/config.js.map +1 -1
  272. package/dist/infrastructure/registry.d.ts +27 -7
  273. package/dist/infrastructure/registry.d.ts.map +1 -1
  274. package/dist/infrastructure/registry.js +79 -5
  275. package/dist/infrastructure/registry.js.map +1 -1
  276. package/dist/infrastructure/update-check.d.ts.map +1 -1
  277. package/dist/infrastructure/update-check.js +49 -31
  278. package/dist/infrastructure/update-check.js.map +1 -1
  279. package/dist/mcp/server.d.ts +2 -10
  280. package/dist/mcp/server.d.ts.map +1 -1
  281. package/dist/mcp/server.js.map +1 -1
  282. package/dist/mcp/tools/ast-query.d.ts +1 -1
  283. package/dist/mcp/tools/ast-query.d.ts.map +1 -1
  284. package/dist/mcp/tools/audit.d.ts +1 -1
  285. package/dist/mcp/tools/audit.d.ts.map +1 -1
  286. package/dist/mcp/tools/batch-query.d.ts +1 -1
  287. package/dist/mcp/tools/batch-query.d.ts.map +1 -1
  288. package/dist/mcp/tools/branch-compare.d.ts +1 -1
  289. package/dist/mcp/tools/branch-compare.d.ts.map +1 -1
  290. package/dist/mcp/tools/brief.d.ts +1 -1
  291. package/dist/mcp/tools/brief.d.ts.map +1 -1
  292. package/dist/mcp/tools/cfg.d.ts +1 -1
  293. package/dist/mcp/tools/cfg.d.ts.map +1 -1
  294. package/dist/mcp/tools/check.d.ts +1 -1
  295. package/dist/mcp/tools/check.d.ts.map +1 -1
  296. package/dist/mcp/tools/co-changes.d.ts +1 -1
  297. package/dist/mcp/tools/co-changes.d.ts.map +1 -1
  298. package/dist/mcp/tools/code-owners.d.ts +1 -1
  299. package/dist/mcp/tools/code-owners.d.ts.map +1 -1
  300. package/dist/mcp/tools/communities.d.ts +1 -1
  301. package/dist/mcp/tools/communities.d.ts.map +1 -1
  302. package/dist/mcp/tools/complexity.d.ts +1 -1
  303. package/dist/mcp/tools/complexity.d.ts.map +1 -1
  304. package/dist/mcp/tools/context.d.ts +1 -1
  305. package/dist/mcp/tools/context.d.ts.map +1 -1
  306. package/dist/mcp/tools/dataflow.d.ts +1 -1
  307. package/dist/mcp/tools/dataflow.d.ts.map +1 -1
  308. package/dist/mcp/tools/diff-impact.d.ts +1 -1
  309. package/dist/mcp/tools/diff-impact.d.ts.map +1 -1
  310. package/dist/mcp/tools/execution-flow.d.ts +1 -1
  311. package/dist/mcp/tools/execution-flow.d.ts.map +1 -1
  312. package/dist/mcp/tools/export-graph.d.ts +1 -1
  313. package/dist/mcp/tools/export-graph.d.ts.map +1 -1
  314. package/dist/mcp/tools/file-deps.d.ts +1 -1
  315. package/dist/mcp/tools/file-deps.d.ts.map +1 -1
  316. package/dist/mcp/tools/file-exports.d.ts +1 -1
  317. package/dist/mcp/tools/file-exports.d.ts.map +1 -1
  318. package/dist/mcp/tools/find-cycles.d.ts +1 -1
  319. package/dist/mcp/tools/find-cycles.d.ts.map +1 -1
  320. package/dist/mcp/tools/fn-impact.d.ts +1 -1
  321. package/dist/mcp/tools/fn-impact.d.ts.map +1 -1
  322. package/dist/mcp/tools/impact-analysis.d.ts +1 -1
  323. package/dist/mcp/tools/impact-analysis.d.ts.map +1 -1
  324. package/dist/mcp/tools/implementations.d.ts +1 -1
  325. package/dist/mcp/tools/implementations.d.ts.map +1 -1
  326. package/dist/mcp/tools/index.d.ts +2 -5
  327. package/dist/mcp/tools/index.d.ts.map +1 -1
  328. package/dist/mcp/tools/index.js.map +1 -1
  329. package/dist/mcp/tools/interfaces.d.ts +1 -1
  330. package/dist/mcp/tools/interfaces.d.ts.map +1 -1
  331. package/dist/mcp/tools/list-functions.d.ts +1 -1
  332. package/dist/mcp/tools/list-functions.d.ts.map +1 -1
  333. package/dist/mcp/tools/list-repos.d.ts +1 -1
  334. package/dist/mcp/tools/list-repos.d.ts.map +1 -1
  335. package/dist/mcp/tools/module-map.d.ts +1 -1
  336. package/dist/mcp/tools/module-map.d.ts.map +1 -1
  337. package/dist/mcp/tools/node-roles.d.ts +1 -1
  338. package/dist/mcp/tools/node-roles.d.ts.map +1 -1
  339. package/dist/mcp/tools/path.d.ts +1 -1
  340. package/dist/mcp/tools/path.d.ts.map +1 -1
  341. package/dist/mcp/tools/query.d.ts +1 -1
  342. package/dist/mcp/tools/query.d.ts.map +1 -1
  343. package/dist/mcp/tools/semantic-search.d.ts +1 -1
  344. package/dist/mcp/tools/semantic-search.d.ts.map +1 -1
  345. package/dist/mcp/tools/sequence.d.ts +1 -1
  346. package/dist/mcp/tools/sequence.d.ts.map +1 -1
  347. package/dist/mcp/tools/structure.d.ts +1 -1
  348. package/dist/mcp/tools/structure.d.ts.map +1 -1
  349. package/dist/mcp/tools/symbol-children.d.ts +1 -1
  350. package/dist/mcp/tools/symbol-children.d.ts.map +1 -1
  351. package/dist/mcp/tools/triage.d.ts +1 -1
  352. package/dist/mcp/tools/triage.d.ts.map +1 -1
  353. package/dist/mcp/tools/where.d.ts +1 -1
  354. package/dist/mcp/tools/where.d.ts.map +1 -1
  355. package/dist/mcp/types.d.ts +19 -0
  356. package/dist/mcp/types.d.ts.map +1 -0
  357. package/dist/mcp/types.js +6 -0
  358. package/dist/mcp/types.js.map +1 -0
  359. package/dist/presentation/queries-cli/index.d.ts +1 -1
  360. package/dist/presentation/queries-cli/index.d.ts.map +1 -1
  361. package/dist/presentation/queries-cli/index.js +1 -1
  362. package/dist/presentation/queries-cli/index.js.map +1 -1
  363. package/dist/presentation/queries-cli/overview.d.ts +1 -0
  364. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  365. package/dist/presentation/queries-cli/overview.js +20 -1
  366. package/dist/presentation/queries-cli/overview.js.map +1 -1
  367. package/dist/presentation/queries-cli.d.ts +1 -1
  368. package/dist/presentation/queries-cli.d.ts.map +1 -1
  369. package/dist/presentation/queries-cli.js +1 -1
  370. package/dist/presentation/queries-cli.js.map +1 -1
  371. package/dist/presentation/structure.d.ts +1 -1
  372. package/dist/presentation/structure.d.ts.map +1 -1
  373. package/dist/presentation/structure.js +2 -2
  374. package/dist/presentation/structure.js.map +1 -1
  375. package/dist/presentation/viewer.d.ts.map +1 -1
  376. package/dist/presentation/viewer.js +45 -32
  377. package/dist/presentation/viewer.js.map +1 -1
  378. package/dist/shared/constants.d.ts +21 -0
  379. package/dist/shared/constants.d.ts.map +1 -1
  380. package/dist/shared/constants.js +25 -0
  381. package/dist/shared/constants.js.map +1 -1
  382. package/dist/shared/normalize.d.ts.map +1 -1
  383. package/dist/shared/normalize.js +12 -22
  384. package/dist/shared/normalize.js.map +1 -1
  385. package/dist/shared/paginate.d.ts +4 -17
  386. package/dist/shared/paginate.d.ts.map +1 -1
  387. package/dist/shared/paginate.js.map +1 -1
  388. package/dist/types.d.ts +113 -1
  389. package/dist/types.d.ts.map +1 -1
  390. package/grammars/tree-sitter-erlang.wasm +0 -0
  391. package/grammars/tree-sitter-gleam.wasm +0 -0
  392. package/package.json +7 -8
  393. package/src/ast-analysis/engine.ts +43 -63
  394. package/src/ast-analysis/rules/b2.ts +263 -0
  395. package/src/ast-analysis/rules/b3.ts +127 -0
  396. package/src/ast-analysis/rules/b4.ts +378 -0
  397. package/src/ast-analysis/rules/b5.ts +65 -0
  398. package/src/ast-analysis/rules/c.ts +157 -0
  399. package/src/ast-analysis/rules/index.ts +34 -0
  400. package/src/ast-analysis/rules/javascript.ts +3 -0
  401. package/src/ast-analysis/shared.ts +2 -0
  402. package/src/ast-analysis/visitor-utils.ts +5 -0
  403. package/src/ast-analysis/visitor.ts +82 -52
  404. package/src/ast-analysis/visitors/cfg-visitor.ts +198 -84
  405. package/src/ast-analysis/visitors/complexity-visitor.ts +44 -16
  406. package/src/ast-analysis/visitors/dataflow-visitor.ts +68 -29
  407. package/src/cli/commands/audit.ts +2 -1
  408. package/src/cli/commands/batch.ts +1 -0
  409. package/src/cli/commands/build.ts +6 -1
  410. package/src/cli/commands/config.ts +353 -0
  411. package/src/cli/commands/roles.ts +6 -1
  412. package/src/cli/commands/triage.ts +1 -1
  413. package/src/cli/index.ts +10 -0
  414. package/src/cli/shared/options.ts +11 -1
  415. package/src/cli/types.ts +2 -0
  416. package/src/db/better-sqlite3.ts +5 -4
  417. package/src/db/connection.ts +23 -5
  418. package/src/db/index.ts +1 -0
  419. package/src/db/migrations.ts +69 -1
  420. package/src/db/repository/build-stmts.ts +30 -0
  421. package/src/db/repository/dataflow.ts +16 -0
  422. package/src/db/repository/index.ts +1 -1
  423. package/src/db/repository/native-repository.ts +56 -40
  424. package/src/domain/analysis/fn-impact.ts +4 -0
  425. package/src/domain/analysis/module-map.ts +38 -6
  426. package/src/domain/analysis/roles.ts +23 -0
  427. package/src/domain/graph/builder/call-resolver.ts +156 -218
  428. package/src/domain/graph/builder/cha.ts +18 -1
  429. package/src/domain/graph/builder/context.ts +1 -0
  430. package/src/domain/graph/builder/helpers.ts +205 -67
  431. package/src/domain/graph/builder/incremental.ts +249 -119
  432. package/src/domain/graph/builder/pipeline.ts +59 -6
  433. package/src/domain/graph/builder/stages/build-edges.ts +783 -652
  434. package/src/domain/graph/builder/stages/collect-files.ts +12 -6
  435. package/src/domain/graph/builder/stages/detect-changes.ts +4 -2
  436. package/src/domain/graph/builder/stages/finalize.ts +4 -0
  437. package/src/domain/graph/builder/stages/native-orchestrator.ts +1214 -398
  438. package/src/domain/graph/builder/stages/resolve-imports.ts +1 -1
  439. package/src/domain/graph/resolver/points-to.ts +182 -59
  440. package/src/domain/graph/resolver/strategy.ts +265 -0
  441. package/src/domain/graph/watcher.ts +19 -9
  442. package/src/domain/parser.ts +57 -16
  443. package/src/domain/queries.ts +1 -1
  444. package/src/domain/wasm-worker-entry.ts +13 -2
  445. package/src/domain/wasm-worker-pool.ts +29 -4
  446. package/src/domain/wasm-worker-protocol.ts +5 -0
  447. package/src/extractors/cpp.ts +44 -1
  448. package/src/extractors/cuda.ts +44 -1
  449. package/src/extractors/dart.ts +48 -3
  450. package/src/extractors/groovy.ts +62 -2
  451. package/src/extractors/helpers.ts +48 -2
  452. package/src/extractors/java.ts +88 -8
  453. package/src/extractors/javascript.ts +693 -167
  454. package/src/extractors/kotlin.ts +57 -3
  455. package/src/extractors/objc.ts +25 -1
  456. package/src/extractors/scala.ts +63 -1
  457. package/src/extractors/swift.ts +46 -3
  458. package/src/features/audit.ts +43 -34
  459. package/src/features/boundaries.ts +17 -9
  460. package/src/features/cfg.ts +31 -22
  461. package/src/features/check.ts +21 -5
  462. package/src/features/communities.ts +28 -19
  463. package/src/features/dataflow.ts +755 -6
  464. package/src/features/manifesto.ts +76 -75
  465. package/src/features/sequence.ts +29 -23
  466. package/src/features/snapshot.ts +36 -25
  467. package/src/features/structure-query.ts +7 -7
  468. package/src/features/structure.ts +185 -55
  469. package/src/features/triage.ts +28 -15
  470. package/src/graph/algorithms/bfs.ts +13 -12
  471. package/src/graph/algorithms/tarjan.ts +5 -0
  472. package/src/graph/builders/dependency.ts +35 -23
  473. package/src/graph/classifiers/roles.ts +74 -7
  474. package/src/index.ts +5 -1
  475. package/src/infrastructure/config.ts +511 -23
  476. package/src/infrastructure/registry.ts +117 -12
  477. package/src/infrastructure/update-check.ts +55 -33
  478. package/src/mcp/server.ts +2 -8
  479. package/src/mcp/tools/ast-query.ts +1 -1
  480. package/src/mcp/tools/audit.ts +1 -1
  481. package/src/mcp/tools/batch-query.ts +1 -1
  482. package/src/mcp/tools/branch-compare.ts +1 -1
  483. package/src/mcp/tools/brief.ts +1 -1
  484. package/src/mcp/tools/cfg.ts +1 -1
  485. package/src/mcp/tools/check.ts +1 -1
  486. package/src/mcp/tools/co-changes.ts +1 -1
  487. package/src/mcp/tools/code-owners.ts +1 -1
  488. package/src/mcp/tools/communities.ts +1 -1
  489. package/src/mcp/tools/complexity.ts +1 -1
  490. package/src/mcp/tools/context.ts +1 -1
  491. package/src/mcp/tools/dataflow.ts +1 -1
  492. package/src/mcp/tools/diff-impact.ts +1 -1
  493. package/src/mcp/tools/execution-flow.ts +1 -1
  494. package/src/mcp/tools/export-graph.ts +1 -1
  495. package/src/mcp/tools/file-deps.ts +1 -1
  496. package/src/mcp/tools/file-exports.ts +1 -1
  497. package/src/mcp/tools/find-cycles.ts +1 -1
  498. package/src/mcp/tools/fn-impact.ts +1 -1
  499. package/src/mcp/tools/impact-analysis.ts +1 -1
  500. package/src/mcp/tools/implementations.ts +1 -1
  501. package/src/mcp/tools/index.ts +2 -5
  502. package/src/mcp/tools/interfaces.ts +1 -1
  503. package/src/mcp/tools/list-functions.ts +1 -1
  504. package/src/mcp/tools/list-repos.ts +1 -1
  505. package/src/mcp/tools/module-map.ts +1 -1
  506. package/src/mcp/tools/node-roles.ts +1 -1
  507. package/src/mcp/tools/path.ts +1 -1
  508. package/src/mcp/tools/query.ts +1 -1
  509. package/src/mcp/tools/semantic-search.ts +1 -1
  510. package/src/mcp/tools/sequence.ts +1 -1
  511. package/src/mcp/tools/structure.ts +1 -1
  512. package/src/mcp/tools/symbol-children.ts +1 -1
  513. package/src/mcp/tools/triage.ts +1 -1
  514. package/src/mcp/tools/where.ts +1 -1
  515. package/src/mcp/types.ts +21 -0
  516. package/src/presentation/queries-cli/index.ts +1 -1
  517. package/src/presentation/queries-cli/overview.ts +35 -1
  518. package/src/presentation/queries-cli.ts +1 -0
  519. package/src/presentation/structure.ts +3 -3
  520. package/src/presentation/viewer.ts +98 -87
  521. package/src/shared/constants.ts +26 -0
  522. package/src/shared/normalize.ts +13 -22
  523. package/src/shared/paginate.ts +4 -18
  524. package/src/types.ts +127 -1
@@ -11,15 +11,14 @@ import { setTypeMapEntry } from '../../../../extractors/helpers.js';
11
11
  import { PROPAGATION_HOP_PENALTY } from '../../../../extractors/javascript.js';
12
12
  import { debug } from '../../../../infrastructure/logger.js';
13
13
  import { loadNative } from '../../../../infrastructure/native.js';
14
+ import { TS_NATIVE_CONFIDENCE_FLOOR } from '../../../../shared/constants.js';
14
15
  import { computeConfidence } from '../../resolve.js';
15
16
  import { buildPointsToMap, resolveViaPointsTo } from '../../resolver/points-to.js';
16
17
  import { enrichTypeMapWithTsc } from '../../resolver/ts-resolver.js';
17
18
  import { findCaller, isModuleScopedLanguage, resolveCallTargets, resolveReceiverEdge, } from '../call-resolver.js';
18
19
  import { buildChaContext, resolveChaTargets, resolveThisDispatch } from '../cha.js';
19
- import { BUILTIN_RECEIVERS, batchInsertEdges, runChaPostPass } from '../helpers.js';
20
+ import { BUILTIN_RECEIVERS, batchInsertEdges, CHA_DISPATCH_PENALTY, CHA_TYPED_DISPATCH_CONFIDENCE, runChaPostPass, } from '../helpers.js';
20
21
  import { getResolved, isBarrelFile, resolveBarrelExportCached } from './resolve-imports.js';
21
- /** Phase 8.5: confidence penalty applied to CHA-dispatch edges. */
22
- export const CHA_DISPATCH_PENALTY = 0.1;
23
22
  // ── Node lookup setup ───────────────────────────────────────────────────
24
23
  function makeGetNodeIdStmt(db) {
25
24
  return {
@@ -72,7 +71,7 @@ function emitTypeOnlySymbolEdges(ctx, imp, resolvedPath, fileNodeId, allEdgeRows
72
71
  }
73
72
  const candidates = ctx.nodesByNameAndFile.get(`${cleanName}|${targetFile}`);
74
73
  if (candidates && candidates.length > 0) {
75
- allEdgeRows.push([fileNodeId, candidates[0].id, 'imports-type', 1.0, 0, null]);
74
+ allEdgeRows.push([fileNodeId, candidates[0].id, 'imports-type', 1.0, 0, null, null]);
76
75
  }
77
76
  }
78
77
  }
@@ -86,7 +85,7 @@ function emitEdgesForImport(ctx, imp, fileNodeId, relPath, getNodeIdStmt, allEdg
86
85
  if (!targetRow)
87
86
  return;
88
87
  const edgeKind = importEdgeKind(imp);
89
- allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null]);
88
+ allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null, null]);
90
89
  if (imp.typeOnly) {
91
90
  emitTypeOnlySymbolEdges(ctx, imp, resolvedPath, fileNodeId, allEdgeRows);
92
91
  }
@@ -124,7 +123,7 @@ function buildBarrelEdges(ctx, imp, resolvedPath, fileNodeId, edgeKind, getNodeI
124
123
  : edgeKind === 'dynamic-imports'
125
124
  ? 'dynamic-imports'
126
125
  : 'imports';
127
- edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null]);
126
+ edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null, null]);
128
127
  }
129
128
  }
130
129
  }
@@ -239,7 +238,7 @@ function buildImportEdgesNative(ctx, getNodeIdStmt, allEdgeRows, native) {
239
238
  const symbolNodes = collectSymbolNodes(ctx);
240
239
  const nativeEdges = native.buildImportEdges(files, resolvedImports, fileReexports, registry.ids, barrelFiles, ctx.rootDir, symbolNodes);
241
240
  for (const e of nativeEdges) {
242
- allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic, null]);
241
+ allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic, null, null]);
243
242
  }
244
243
  }
245
244
  // ── Phase 8.2: Cross-file return-type propagation ───────────────────────
@@ -337,17 +336,35 @@ function buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodes, native)
337
336
  nativeFiles.push({
338
337
  file: relPath,
339
338
  fileNodeId: fileNodeRow.id,
340
- definitions: symbols.definitions.map((d) => ({
341
- name: d.name,
342
- kind: d.kind,
343
- line: d.line,
344
- endLine: d.endLine ?? null,
345
- })),
339
+ definitions: symbols.definitions.map((d) => {
340
+ const params = d.children?.filter((c) => c.kind === 'parameter').map((c) => c.name);
341
+ return {
342
+ name: d.name,
343
+ kind: d.kind,
344
+ line: d.line,
345
+ endLine: d.endLine ?? null,
346
+ params: params?.length ? params : undefined,
347
+ };
348
+ }),
346
349
  calls: symbols.calls,
347
350
  importedNames,
348
351
  classes: symbols.classes,
349
352
  typeMap,
350
353
  fnRefBindings: symbols.fnRefBindings?.length ? symbols.fnRefBindings : undefined,
354
+ paramBindings: symbols.paramBindings?.length ? symbols.paramBindings : undefined,
355
+ thisCallBindings: symbols.thisCallBindings?.length ? symbols.thisCallBindings : undefined,
356
+ arrayElemBindings: symbols.arrayElemBindings?.length ? symbols.arrayElemBindings : undefined,
357
+ spreadArgBindings: symbols.spreadArgBindings?.length ? symbols.spreadArgBindings : undefined,
358
+ forOfBindings: symbols.forOfBindings?.length ? symbols.forOfBindings : undefined,
359
+ arrayCallbackBindings: symbols.arrayCallbackBindings?.length
360
+ ? symbols.arrayCallbackBindings
361
+ : undefined,
362
+ objectRestParamBindings: symbols.objectRestParamBindings?.length
363
+ ? symbols.objectRestParamBindings
364
+ : undefined,
365
+ objectPropBindings: symbols.objectPropBindings?.length
366
+ ? symbols.objectPropBindings
367
+ : undefined,
351
368
  });
352
369
  }
353
370
  const nativeEdges = native.buildCallEdges(nativeFiles, allNodes, [
@@ -361,285 +378,10 @@ function buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodes, native)
361
378
  e.confidence,
362
379
  e.dynamic,
363
380
  e.kind === 'calls' ? 'ts-native' : null,
381
+ e.dynamic_kind ?? null,
364
382
  ]);
365
383
  }
366
384
  }
367
- /**
368
- * Phase 8.3c pts post-pass for the native call-edge path.
369
- *
370
- * The native Rust engine builds call edges without knowledge of paramBindings,
371
- * so `fn()` calls inside higher-order functions are not resolved to their
372
- * concrete targets. This JS post-pass runs after the native edge pass and adds
373
- * only the parameter-flow pts edges that the native engine missed.
374
- *
375
- * To avoid duplicating edges already emitted by the native engine, the current
376
- * allEdgeRows snapshot is used to seed a seenByPair set before processing each
377
- * file.
378
- */
379
- function buildParamFlowPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup) {
380
- // Only process files that actually have paramBindings (avoid useless work).
381
- const filesWithParams = [...ctx.fileSymbols].filter(([, symbols]) => symbols.paramBindings && symbols.paramBindings.length > 0);
382
- if (filesWithParams.length === 0)
383
- return;
384
- // Seed seenByPair from the existing rows so we don't duplicate native edges.
385
- // This is O(|allEdgeRows|) once per post-pass, which is acceptable.
386
- const seenByPair = new Set();
387
- for (const [srcId, tgtId] of allEdgeRows) {
388
- seenByPair.add(`${srcId}|${tgtId}`);
389
- }
390
- const { barrelOnlyFiles, rootDir } = ctx;
391
- const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
392
- for (const [relPath, symbols] of filesWithParams) {
393
- if (barrelOnlyFiles.has(relPath))
394
- continue;
395
- const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
396
- if (!fileNodeRow)
397
- continue;
398
- const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
399
- const typeMap = symbols.typeMap || new Map();
400
- const ptsMap = buildPointsToMapForFile(symbols, importedNames);
401
- if (!ptsMap)
402
- continue;
403
- for (const call of symbols.calls) {
404
- if (call.receiver || call.dynamic)
405
- continue; // pts post-pass handles only param-flow (non-dynamic)
406
- const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
407
- const scopedKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
408
- if (!scopedKey || !ptsMap.has(scopedKey))
409
- continue;
410
- // Only resolve calls that had no direct targets (same guard as buildFileCallEdges).
411
- const { targets } = resolveCallTargets(lookup, call, relPath, importedNames, typeMap);
412
- if (targets.length > 0)
413
- continue;
414
- for (const alias of resolveViaPointsTo(scopedKey, ptsMap)) {
415
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
416
- for (const t of aliasTargets) {
417
- const edgeKey = `${caller.id}|${t.id}`;
418
- if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
419
- const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
420
- if (conf > 0) {
421
- seenByPair.add(edgeKey);
422
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
423
- }
424
- }
425
- }
426
- }
427
- }
428
- }
429
- }
430
- /**
431
- * bind/alias pts post-pass for the native call-edge path.
432
- *
433
- * The native Rust engine has no knowledge of JS-layer fnRefBindings (e.g.
434
- * `const f = fn.bind(ctx)`), so calls to bind-created aliases are not resolved
435
- * to their original function on the native path. This JS post-pass runs after
436
- * the native edge pass and adds only the fnRefBindings-seeded pts edges that the
437
- * native engine missed.
438
- *
439
- * Uses the same seenByPair dedup guard as buildParamFlowPtsPostPass to avoid
440
- * duplicating edges already emitted by the native engine.
441
- */
442
- function buildFnRefBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup) {
443
- // Only process files that actually have fnRefBindings.
444
- const filesWithBindings = [...ctx.fileSymbols].filter(([, symbols]) => symbols.fnRefBindings && symbols.fnRefBindings.length > 0);
445
- if (filesWithBindings.length === 0)
446
- return;
447
- // Seed seenByPair from the existing rows so we don't duplicate native edges.
448
- const seenByPair = new Set();
449
- for (const [srcId, tgtId] of allEdgeRows) {
450
- seenByPair.add(`${srcId}|${tgtId}`);
451
- }
452
- const { barrelOnlyFiles, rootDir } = ctx;
453
- const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
454
- for (const [relPath, symbols] of filesWithBindings) {
455
- if (barrelOnlyFiles.has(relPath))
456
- continue;
457
- const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
458
- if (!fileNodeRow)
459
- continue;
460
- const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
461
- const typeMap = symbols.typeMap || new Map();
462
- const ptsMap = buildPointsToMapForFile(symbols, importedNames);
463
- if (!ptsMap)
464
- continue;
465
- // Only resolve calls whose name is an lhs in fnRefBindings — the same
466
- // narrowed guard used in buildFileCallEdges case (c).
467
- const fnRefBindingLhs = new Set(symbols.fnRefBindings.map((b) => b.lhs));
468
- for (const call of symbols.calls) {
469
- if (call.receiver || call.dynamic)
470
- continue; // bind aliases are flat-keyed, never dynamic
471
- if (!fnRefBindingLhs.has(call.name))
472
- continue;
473
- if (!ptsMap.has(call.name))
474
- continue;
475
- const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
476
- // Only resolve calls that had no direct targets (same guard as buildFileCallEdges).
477
- const { targets } = resolveCallTargets(lookup, call, relPath, importedNames, typeMap);
478
- if (targets.length > 0)
479
- continue;
480
- for (const alias of resolveViaPointsTo(call.name, ptsMap)) {
481
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
482
- for (const t of aliasTargets) {
483
- const edgeKey = `${caller.id}|${t.id}`;
484
- if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
485
- const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
486
- if (conf > 0) {
487
- seenByPair.add(edgeKey);
488
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
489
- }
490
- }
491
- }
492
- }
493
- }
494
- }
495
- }
496
- /**
497
- * this-rebinding post-pass for the native call-edge path.
498
- *
499
- * When `fn.call(namedCtx, ...)` or `fn.apply(namedCtx, ...)` is extracted by the
500
- * WASM layer, `thisCallBindings` records `{ callee: 'fn', thisArg: 'namedCtx' }`.
501
- * The native Rust engine has no knowledge of these bindings, so `this()` calls
502
- * inside `fn` remain unresolved. This JS post-pass adds the missing edges by
503
- * resolving `this()` calls inside each `fn` that has a thisCallBinding.
504
- */
505
- function buildThisCallBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup) {
506
- const filesWithBindings = [...ctx.fileSymbols].filter(([, symbols]) => symbols.thisCallBindings && symbols.thisCallBindings.length > 0);
507
- if (filesWithBindings.length === 0)
508
- return;
509
- const seenByPair = new Set();
510
- for (const [srcId, tgtId] of allEdgeRows) {
511
- seenByPair.add(`${srcId}|${tgtId}`);
512
- }
513
- const { barrelOnlyFiles, rootDir } = ctx;
514
- const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
515
- for (const [relPath, symbols] of filesWithBindings) {
516
- if (barrelOnlyFiles.has(relPath))
517
- continue;
518
- const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
519
- if (!fileNodeRow)
520
- continue;
521
- const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
522
- const typeMap = symbols.typeMap || new Map();
523
- const ptsMap = buildPointsToMapForFile(symbols, importedNames);
524
- if (!ptsMap)
525
- continue;
526
- // Only process calls named 'this' (callee-not-receiver usage)
527
- for (const call of symbols.calls) {
528
- if (call.name !== 'this' || call.receiver)
529
- continue;
530
- const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
531
- if (caller.callerName == null)
532
- continue;
533
- const scopedKey = `${caller.callerName}::this`;
534
- if (!ptsMap.has(scopedKey))
535
- continue;
536
- for (const alias of resolveViaPointsTo(scopedKey, ptsMap)) {
537
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
538
- for (const t of aliasTargets) {
539
- const edgeKey = `${caller.id}|${t.id}`;
540
- if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
541
- const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
542
- if (conf > 0) {
543
- seenByPair.add(edgeKey);
544
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
545
- }
546
- }
547
- }
548
- }
549
- }
550
- }
551
- }
552
- /**
553
- * Phase 8.3f post-pass for the native call-edge path.
554
- *
555
- * The native Rust engine builds call edges without knowledge of
556
- * objectRestParamBindings, so `rest.method()` calls inside functions with
557
- * object-destructuring rest parameters are not resolved via the typeMap chain.
558
- * The Rust engine already resolves same-file and directly-imported callees
559
- * (via steps 1–2 of its resolution logic), so this post-pass only adds edges
560
- * that require the typeMap-chain path:
561
- * typeMap[restName] → argName → typeMap[argName.method] → target
562
- *
563
- * Mirrors the seeding in buildCallEdgesJS (Phase 8.3f) to ensure both engine
564
- * paths produce identical results for receiver-typed rest-param calls.
565
- */
566
- function buildObjectRestParamPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup) {
567
- const filesWithRestBindings = [...ctx.fileSymbols].filter(([, symbols]) => symbols.objectRestParamBindings &&
568
- symbols.objectRestParamBindings.length > 0 &&
569
- symbols.paramBindings &&
570
- symbols.paramBindings.length > 0);
571
- if (filesWithRestBindings.length === 0)
572
- return;
573
- const seenByPair = new Set();
574
- for (const [srcId, tgtId] of allEdgeRows) {
575
- seenByPair.add(`${srcId}|${tgtId}`);
576
- }
577
- const { barrelOnlyFiles, rootDir } = ctx;
578
- const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
579
- for (const [relPath, symbols] of filesWithRestBindings) {
580
- if (barrelOnlyFiles.has(relPath))
581
- continue;
582
- const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
583
- if (!fileNodeRow)
584
- continue;
585
- const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
586
- const typeMap = new Map(symbols.typeMap instanceof Map ? symbols.typeMap : []);
587
- // Seed typeMap[callee::restName] = { type: argName } for each matching pair.
588
- // Mirrors the seeding in buildCallEdgesJS Phase 8.3f. Keys are scoped by
589
- // callee so two functions with the same rest-param name (e.g. `...rest`) in
590
- // the same file don't collide (#1358).
591
- // When only one callee uses a given rest name, also seed the unscoped key
592
- // as a null-callerName fallback so edges aren't silently dropped if
593
- // findCaller can't identify the enclosing function (#1358).
594
- const restNameCallees = new Map();
595
- for (const orpb of symbols.objectRestParamBindings) {
596
- if (!restNameCallees.has(orpb.restName))
597
- restNameCallees.set(orpb.restName, new Set());
598
- restNameCallees.get(orpb.restName).add(orpb.callee);
599
- }
600
- const restNames = new Set();
601
- for (const orpb of symbols.objectRestParamBindings) {
602
- for (const pb of symbols.paramBindings) {
603
- if (pb.callee === orpb.callee && pb.argIndex === orpb.argIndex) {
604
- const scopedKey = `${orpb.callee}::${orpb.restName}`;
605
- if (!typeMap.has(scopedKey)) {
606
- typeMap.set(scopedKey, { type: pb.argName, confidence: 0.65 });
607
- if (restNameCallees.get(orpb.restName).size === 1 && !typeMap.has(orpb.restName)) {
608
- typeMap.set(orpb.restName, { type: pb.argName, confidence: 0.65 });
609
- }
610
- }
611
- // restNames tracks every rest-parameter name found, regardless of whether the
612
- // scoped key was already in typeMap. This ensures the post-pass (below) processes
613
- // all calls whose receiver matches a known rest binding — not just those whose
614
- // typeMap entry was seeded in this iteration.
615
- restNames.add(orpb.restName);
616
- }
617
- }
618
- }
619
- if (restNames.size === 0)
620
- continue;
621
- for (const call of symbols.calls) {
622
- // Only process calls whose receiver is a known rest-binding name.
623
- if (!call.receiver || !restNames.has(call.receiver))
624
- continue;
625
- const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
626
- // Resolve with the enriched typeMap. callerName is passed so
627
- // resolveByMethodOrGlobal can look up the scoped key callee::restName (#1358).
628
- // seenByPair deduplicates edges the native engine already emitted.
629
- const { targets, importedFrom } = resolveCallTargets(lookup, call, relPath, importedNames, typeMap, caller.callerName);
630
- for (const t of targets) {
631
- const edgeKey = `${caller.id}|${t.id}`;
632
- if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
633
- const conf = computeConfidence(relPath, t.file, importedFrom ?? null) - PROPAGATION_HOP_PENALTY;
634
- if (conf > 0) {
635
- seenByPair.add(edgeKey);
636
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
637
- }
638
- }
639
- }
640
- }
641
- }
642
- }
643
385
  /**
644
386
  * Object.defineProperty accessor post-pass for the native call-edge path.
645
387
  *
@@ -703,7 +445,7 @@ function buildDefinePropertyPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLook
703
445
  const conf = computeConfidence(relPath, t.file, null);
704
446
  if (conf > 0) {
705
447
  seenByPair.add(edgeKey);
706
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native']);
448
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native', null]);
707
449
  }
708
450
  }
709
451
  }
@@ -715,11 +457,11 @@ function buildDefinePropertyPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLook
715
457
  *
716
458
  * The native Rust engine has no knowledge of the CHA context, so `this.method()`
717
459
  * calls and interface method dispatches are not expanded to their concrete
718
- * implementations. This JS post-pass runs after the native edges (and the pts
719
- * post-pass) and adds only the CHA-resolved edges that the native engine missed.
460
+ * implementations. This JS post-pass runs after the native edges and adds only
461
+ * the CHA-resolved edges that the native engine missed.
720
462
  *
721
- * Like buildParamFlowPtsPostPass, it seeds seenByPair from the current allEdgeRows
722
- * snapshot to avoid duplicating edges the native engine already produced.
463
+ * Seeds seenByPair from the current allEdgeRows snapshot to avoid duplicating
464
+ * edges the native engine already produced.
723
465
  */
724
466
  function buildChaPostPass(ctx, getNodeIdStmt, allEdgeRows, chaCtx) {
725
467
  // Fast-exit when the CHA context is empty (no class hierarchy in the project)
@@ -748,8 +490,9 @@ function buildChaPostPass(ctx, getNodeIdStmt, allEdgeRows, chaCtx) {
748
490
  continue;
749
491
  const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
750
492
  let chaTargets = [];
493
+ let isTypedReceiverDispatch = false;
751
494
  if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
752
- chaTargets = resolveThisDispatch(call.name, caller.callerName, call.receiver, chaCtx, lookup);
495
+ chaTargets = resolveThisDispatch(call.name, caller.callerName, call.receiver, chaCtx, lookup, relPath);
753
496
  }
754
497
  else {
755
498
  const typeEntry = typeMap.get(call.receiver);
@@ -760,15 +503,25 @@ function buildChaPostPass(ctx, getNodeIdStmt, allEdgeRows, chaCtx) {
760
503
  : null;
761
504
  if (typeName) {
762
505
  chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
506
+ isTypedReceiverDispatch = true;
763
507
  }
764
508
  }
765
509
  for (const t of chaTargets) {
766
510
  const edgeKey = `${caller.id}|${t.id}`;
767
511
  if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
768
- const conf = computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
512
+ // Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
513
+ // — file proximity is not meaningful for virtual dispatch confidence.
514
+ // this/super dispatch keeps computeConfidence-based proximity scoring to
515
+ // match runPostNativeThisDispatch (native-orchestrator.ts).
516
+ const conf = isTypedReceiverDispatch
517
+ ? CHA_TYPED_DISPATCH_CONFIDENCE
518
+ : computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
769
519
  if (conf > 0) {
770
520
  seenByPair.add(edgeKey);
771
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha']);
521
+ // Tag super-dispatch edges distinctly so runChaPostPass can exclude them
522
+ // from further CHA expansion (super calls are not virtual dispatch).
523
+ const technique = call.receiver === 'super' ? 'super-dispatch' : 'cha';
524
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, technique, null]);
772
525
  }
773
526
  }
774
527
  }
@@ -843,7 +596,11 @@ function buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows, chaCtx) {
843
596
  }
844
597
  const seenCallEdges = new Set();
845
598
  const ptsMap = buildPointsToMapForFile(symbols, importedNames);
846
- buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCallEdges, lookup, allEdgeRows, typeMap, ptsMap, chaCtx);
599
+ // Build the import-artifact name set: importedNames plus CJS require bindings.
600
+ // Used only by resolveReceiverEdge to distinguish local definitions from CJS
601
+ // import shadows — does NOT affect call-target resolution or DB edges (#1661).
602
+ const importArtifactNames = buildImportArtifactNames(importedNames, symbols, ctx, relPath, rootDir);
603
+ buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCallEdges, lookup, allEdgeRows, typeMap, ptsMap, chaCtx, importArtifactNames);
847
604
  buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows);
848
605
  }
849
606
  }
@@ -883,6 +640,33 @@ function buildImportedNamesMap(ctx, relPath, symbols, rootDir) {
883
640
  }
884
641
  return importedNames;
885
642
  }
643
+ /**
644
+ * Build a map of all names that are import artifacts in this file — includes
645
+ * both ES module imports (already in importedNames) and CJS require destructuring
646
+ * bindings (`const { X } = require('./path')`). Used exclusively by resolveReceiverEdge
647
+ * to classify same-file function-kind nodes as import artifacts vs. local definitions.
648
+ * Does NOT affect call resolution or DB edge creation (#1661).
649
+ */
650
+ function buildImportArtifactNames(importedNames, symbols, ctx, relPath, rootDir) {
651
+ if (!symbols.cjsRequireBindings?.length)
652
+ return importedNames;
653
+ const combined = new Map(importedNames);
654
+ const traceBarrel = (resolvedPath, cleanName) => {
655
+ if (!isBarrelFile(ctx, resolvedPath))
656
+ return resolvedPath;
657
+ const actual = resolveBarrelExportCached(ctx, resolvedPath, cleanName);
658
+ return actual ?? resolvedPath;
659
+ };
660
+ for (const binding of symbols.cjsRequireBindings) {
661
+ const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), binding.source);
662
+ for (const name of binding.names) {
663
+ if (!combined.has(name)) {
664
+ combined.set(name, traceBarrel(resolvedPath, name));
665
+ }
666
+ }
667
+ }
668
+ return combined;
669
+ }
886
670
  function makeContextLookup(ctx, getNodeIdStmt) {
887
671
  return {
888
672
  byNameAndFile: (name, file) => ctx.nodesByNameAndFile.get(`${name}|${file}`) ?? [],
@@ -951,196 +735,419 @@ function buildDefinitionParamsMap(definitions) {
951
735
  }
952
736
  return map;
953
737
  }
954
- function buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCallEdges, lookup, allEdgeRows, typeMap, ptsMap, chaCtx) {
955
- // Tracks edges that were inserted by the pts fallback (edgeKey → allEdgeRows index).
956
- // Kept separate from seenCallEdges so that a subsequent direct-call edge for the same
957
- // caller→target pair can upgrade the confidence in-place rather than being silently
958
- // dropped by the dedup guard. Once upgraded, the key moves to seenCallEdges and is
959
- // no longer tracked here.
960
- const ptsEdgeRows = new Map();
961
- // Pre-compute the set of names that appear as lhs in fnRefBindings so that
962
- // case (c) of the pts gate below only fires for names that are genuine
963
- // bind/alias entries, not for every locally-defined function or import that
964
- // buildPointsToMap seeds with a self-pointing entry.
965
- const fnRefBindingLhs = new Set(symbols.fnRefBindings?.map((b) => b.lhs) ?? []);
966
- for (const call of symbols.calls) {
967
- if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver))
968
- continue;
969
- const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
970
- const isDynamic = call.dynamic ? 1 : 0;
971
- let { targets, importedFrom } = resolveCallTargets(lookup, call, relPath, importedNames, typeMap, caller.callerName);
972
- // Same-class `this.method()` fallback: when the call receiver is `this` and
973
- // resolveCallTargets found nothing, derive the enclosing class name from the
974
- // caller (e.g. `Logger.info` → class prefix `Logger`) and retry with the
975
- // qualified method name `Logger._write`. This mirrors what the native Rust
976
- // engine does implicitly via its class-scoped symbol table.
977
- // NOTE: restricted to `this` only — `super.method()` targets a parent class,
978
- // not the enclosing class, so qualifying with the child class name would
979
- // produce a false edge when the child also defines a same-named method.
980
- if (targets.length === 0 && call.receiver === 'this' && caller.callerName != null) {
738
+ // ── Per-call resolution helpers ─────────────────────────────────────────
739
+ /**
740
+ * Resolve targets for a single call site with all JS-path fallbacks applied.
741
+ *
742
+ * Runs in order:
743
+ * 1. Primary resolution via `resolveCallTargets` (importedNames + typeMap).
744
+ * 2. Same-class `this.method()` fallback (non-super receivers only).
745
+ * 3. Same-class bare-call fallback for non-JS/TS class-scoped languages.
746
+ * 4. Object.defineProperty accessor fallback (this-calls inside getter/setter).
747
+ *
748
+ * Returns the resolved targets array and the importedFrom hint for confidence scoring.
749
+ */
750
+ function resolveFallbackTargets(call, caller, relPath, importedNames, lookup, typeMap, definePropertyReceivers) {
751
+ // RES-4: Kotlin member callable reference — `Greeter::greet` emits
752
+ // { name: 'greet', receiver: 'Greeter', dynamicKind: 'reflection' }.
753
+ // The receiver is the class qualifier (not a typeMap variable), so
754
+ // resolveCallTargets would find a same-named top-level function via
755
+ // byNameAndFile('greet', relPath) before the qualified form is tried.
756
+ // Prefer `Greeter.greet` in the same file first; fall through to the
757
+ // normal path only when no qualified match exists.
758
+ let preQualifiedTargets = [];
759
+ if (call.dynamicKind === 'reflection' &&
760
+ call.receiver &&
761
+ !call.keyExpr &&
762
+ !isModuleScopedLanguage(relPath)) {
763
+ preQualifiedTargets = lookup
764
+ .byNameAndFile(`${call.receiver}.${call.name}`, relPath)
765
+ .filter((n) => n.kind === 'method' || n.kind === 'function');
766
+ }
767
+ let { targets, importedFrom } = preQualifiedTargets.length > 0
768
+ ? {
769
+ targets: preQualifiedTargets,
770
+ importedFrom: undefined,
771
+ }
772
+ : resolveCallTargets(lookup, call, relPath, importedNames, typeMap, caller.callerName);
773
+ // Same-class `this.method()` fallback: when the call receiver is `this` and
774
+ // resolveCallTargets found nothing, derive the enclosing class name from the
775
+ // caller (e.g. `Logger.info` → class prefix `Logger`) and retry with the
776
+ // qualified method name `Logger._write`. This mirrors what the native Rust
777
+ // engine does implicitly via its class-scoped symbol table.
778
+ // NOTE: restricted to `this` only — `super.method()` targets a parent class,
779
+ // not the enclosing class, so qualifying with the child class name would
780
+ // produce a false edge when the child also defines a same-named method.
781
+ if (targets.length === 0 && call.receiver === 'this' && caller.callerName != null) {
782
+ const lastDot = caller.callerName.lastIndexOf('.');
783
+ if (lastDot > 0) {
784
+ const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
785
+ const className = caller.callerName.slice(prevDot + 1, lastDot);
786
+ const qualified = lookup
787
+ .byNameAndFile(`${className}.${call.name}`, relPath)
788
+ .filter((n) => n.kind === 'method');
789
+ if (qualified.length > 0)
790
+ targets = qualified;
791
+ }
792
+ }
793
+ // Same-class bare-call fallback: when a no-receiver call can't be resolved
794
+ // globally, try the caller's own class as a qualifier. Handles C# static
795
+ // sibling calls: `IsValidEmail()` inside `Validators.ValidateUser` resolves
796
+ // to `Validators.IsValidEmail`. Skipped for JS/TS where bare calls are
797
+ // module-scoped, not class-scoped.
798
+ if (targets.length === 0 &&
799
+ !call.receiver &&
800
+ caller.callerName != null &&
801
+ !isModuleScopedLanguage(relPath)) {
802
+ const lastDot = caller.callerName.lastIndexOf('.');
803
+ if (lastDot > 0) {
804
+ const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
805
+ const className = caller.callerName.slice(prevDot + 1, lastDot);
806
+ const qualified = lookup
807
+ .byNameAndFile(`${className}.${call.name}`, relPath)
808
+ .filter((n) => n.kind === 'method');
809
+ if (qualified.length > 0)
810
+ targets = qualified;
811
+ }
812
+ }
813
+ // RES-3: reflection with literal method name — JVM getMethod("name") / invokeMethod("name").
814
+ // Java/Scala/Groovy methods are stored as class-qualified names (e.g. Reflection.greet),
815
+ // so lookup.byNameAndFile('greet', relPath) finds nothing. When dynamicKind='reflection'
816
+ // and keyExpr is set (a string-literal method name was captured), try the qualified form:
817
+ // 1. typeMap[receiver] → resolvedType → lookup `resolvedType.keyExpr` (type-annotated local)
818
+ // 2. callerName class prefix → `CallerClass.keyExpr` (same-class sibling, e.g. Groovy obj)
819
+ // Scoped to non-JS/TS files to avoid interfering with the JS reflection path.
820
+ if (targets.length === 0 &&
821
+ call.dynamicKind === 'reflection' &&
822
+ call.keyExpr &&
823
+ call.receiver &&
824
+ !isModuleScopedLanguage(relPath)) {
825
+ const typeEntry = typeMap.get(call.receiver);
826
+ const resolvedType = typeEntry
827
+ ? typeof typeEntry === 'string'
828
+ ? typeEntry
829
+ : typeEntry.type
830
+ : null;
831
+ if (resolvedType) {
832
+ const qualified = lookup
833
+ .byNameAndFile(`${resolvedType}.${call.keyExpr}`, relPath)
834
+ .filter((n) => n.kind === 'method' || n.kind === 'function');
835
+ if (qualified.length > 0)
836
+ targets = qualified;
837
+ }
838
+ if (targets.length === 0 && caller.callerName != null) {
981
839
  const lastDot = caller.callerName.lastIndexOf('.');
982
840
  if (lastDot > 0) {
983
841
  const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
984
- const className = caller.callerName.slice(prevDot + 1, lastDot);
985
- const qualifiedName = `${className}.${call.name}`;
842
+ const callerClass = caller.callerName.slice(prevDot + 1, lastDot);
986
843
  const qualified = lookup
987
- .byNameAndFile(qualifiedName, relPath)
988
- .filter((n) => n.kind === 'method');
989
- if (qualified.length > 0) {
844
+ .byNameAndFile(`${callerClass}.${call.keyExpr}`, relPath)
845
+ .filter((n) => n.kind === 'method' || n.kind === 'function');
846
+ if (qualified.length > 0)
990
847
  targets = qualified;
991
- }
992
848
  }
993
849
  }
994
- // Same-class bare-call fallback: when a no-receiver call can't be resolved
995
- // globally, try the caller's own class as a qualifier. Handles C# static
996
- // sibling calls: `IsValidEmail()` inside `Validators.ValidateUser` resolves
997
- // to `Validators.IsValidEmail`. Skipped for JS/TS where bare calls are
998
- // module-scoped, not class-scoped.
999
- if (targets.length === 0 &&
1000
- !call.receiver &&
1001
- caller.callerName != null &&
1002
- !isModuleScopedLanguage(relPath)) {
1003
- const lastDot = caller.callerName.lastIndexOf('.');
1004
- if (lastDot > 0) {
1005
- const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
1006
- const className = caller.callerName.slice(prevDot + 1, lastDot);
1007
- const qualifiedName = `${className}.${call.name}`;
1008
- const qualified = lookup
1009
- .byNameAndFile(qualifiedName, relPath)
1010
- .filter((n) => n.kind === 'method');
1011
- if (qualified.length > 0) {
1012
- targets = qualified;
1013
- }
850
+ }
851
+ // Object.defineProperty accessor fallback: when a function is registered as
852
+ // a getter/setter via `Object.defineProperty(obj, "bar", { get: getter })`,
853
+ // calls to `this.X()` inside `getter` resolve against `obj` (this === obj
854
+ // when the accessor is invoked). If the same-class fallback above found
855
+ // nothing, try treating `obj` as the receiver and look up `obj.X` in the
856
+ // typeMap, or fall back to a same-file lookup of any definition named X
857
+ // that belongs to the object literal or its type.
858
+ if (targets.length === 0 &&
859
+ call.receiver === 'this' &&
860
+ caller.callerName != null &&
861
+ definePropertyReceivers) {
862
+ const receiverVarName = definePropertyReceivers.get(caller.callerName);
863
+ if (receiverVarName) {
864
+ const typeEntry = typeMap.get(receiverVarName);
865
+ const typeName = typeEntry
866
+ ? typeof typeEntry === 'string'
867
+ ? typeEntry
868
+ : typeEntry.type
869
+ : null;
870
+ if (typeName) {
871
+ const qualified = lookup.byNameAndFile(`${typeName}.${call.name}`, relPath);
872
+ if (qualified.length > 0)
873
+ targets = [...qualified];
874
+ }
875
+ // If still no targets, search for any definition named `call.name` in
876
+ // the same file — handles plain object literals where the method isn't
877
+ // qualified (e.g. `const obj = { baz() {} }` defines `baz` directly).
878
+ // Note: this is intentionally broad — it matches any same-file definition
879
+ // with the called name, not just members of the receiver object. This is
880
+ // the same behaviour used by the native post-pass path (buildDefinePropertyPostPass).
881
+ if (targets.length === 0) {
882
+ const sameFile = lookup.byNameAndFile(call.name, relPath);
883
+ if (sameFile.length > 0)
884
+ targets = [...sameFile];
1014
885
  }
1015
886
  }
1016
- // Object.defineProperty accessor fallback: when a function is registered as
1017
- // a getter/setter via `Object.defineProperty(obj, "bar", { get: getter })`,
1018
- // calls to `this.X()` inside `getter` resolve against `obj` (this === obj
1019
- // when the accessor is invoked). If the same-class fallback above found
1020
- // nothing, try treating `obj` as the receiver and look up `obj.X` in the
1021
- // typeMap, or fall back to a same-file lookup of any definition named X
1022
- // that belongs to the object literal or its type.
1023
- if (targets.length === 0 &&
1024
- call.receiver === 'this' &&
1025
- caller.callerName != null &&
1026
- symbols.definePropertyReceivers) {
1027
- const receiverVarName = symbols.definePropertyReceivers.get(caller.callerName);
1028
- if (receiverVarName) {
1029
- // Try typeMap lookup for receiver.methodName
1030
- const typeEntry = typeMap.get(receiverVarName);
1031
- const typeName = typeEntry
1032
- ? typeof typeEntry === 'string'
1033
- ? typeEntry
1034
- : typeEntry.type
1035
- : null;
1036
- if (typeName) {
1037
- const qualifiedName = `${typeName}.${call.name}`;
1038
- const qualified = lookup.byNameAndFile(qualifiedName, relPath);
1039
- if (qualified.length > 0) {
1040
- targets = [...qualified];
1041
- }
1042
- }
1043
- // If still no targets, search for any definition named `call.name` in
1044
- // the same file — handles plain object literals where the method isn't
1045
- // qualified (e.g. `const obj = { baz() {} }` defines `baz` directly).
1046
- // Note: this is intentionally broad — it matches any same-file definition
1047
- // with the called name, not just members of the receiver object. This is
1048
- // the same behaviour used by the native post-pass path (buildDefinePropertyPostPass).
1049
- if (targets.length === 0) {
1050
- const sameFile = lookup.byNameAndFile(call.name, relPath);
1051
- if (sameFile.length > 0) {
1052
- targets = [...sameFile];
1053
- }
887
+ }
888
+ return { targets, importedFrom };
889
+ }
890
+ /**
891
+ * Emit direct-call edges for the resolved targets of a single call site.
892
+ *
893
+ * Sorts targets by confidence descending first, then for each target:
894
+ * - Skips self-edges and already-seen edges.
895
+ * - If a pts edge already exists for this pair, upgrades it in-place to
896
+ * direct-call confidence and promotes to seenCallEdges.
897
+ * - If a dyn=0 edge already exists and the incoming call has an explicit
898
+ * dynamicKind (e.g. 'reflection' for bare decorators), upgrades the
899
+ * existing row to dyn=1 in-place so the semantic classification wins.
900
+ * - Otherwise records a new `calls` edge with `ts-native` technique.
901
+ */
902
+ function emitDirectCallEdgesForCall(caller, targets, importedFrom, isDynamic, hasDynamicKind, relPath, seenCallEdges, ptsEdgeRows, allEdgeRows, dynZeroEdgeRows) {
903
+ // Sort targets by confidence descending before emitting edges.
904
+ // For multi-target calls with duplicate (source_id, target_id) pairs the
905
+ // stored confidence depends on which duplicate is processed last — sorting
906
+ // here guarantees the highest-confidence target wins on dedup, matching the
907
+ // native engine's sort_targets_by_confidence call in build_edges.rs.
908
+ const sorted = targets.length > 1
909
+ ? [...targets].sort((a, b) => computeConfidence(relPath, b.file, importedFrom ?? null) -
910
+ computeConfidence(relPath, a.file, importedFrom ?? null))
911
+ : targets;
912
+ for (const t of sorted) {
913
+ const edgeKey = `${caller.id}|${t.id}`;
914
+ if (t.id === caller.id)
915
+ continue;
916
+ const confidence = computeConfidence(relPath, t.file, importedFrom ?? null);
917
+ if (seenCallEdges.has(edgeKey)) {
918
+ // Edge already emitted. If the incoming call carries an explicit semantic
919
+ // dynamic classification (dynamicKind set e.g. 'reflection' for bare
920
+ // decorators) and the existing edge was recorded with dyn=0, upgrade it
921
+ // in-place so the more specific classification wins.
922
+ // Generic dynamic=true without dynamicKind (alias/callback calls) does
923
+ // NOT override dyn=0 to avoid false positives on f.call/f.bind patterns.
924
+ if (isDynamic === 1 && hasDynamicKind && dynZeroEdgeRows) {
925
+ const dynZeroIdx = dynZeroEdgeRows.get(edgeKey);
926
+ if (dynZeroIdx !== undefined) {
927
+ const row = allEdgeRows[dynZeroIdx];
928
+ if (row)
929
+ row[4] = 1;
930
+ dynZeroEdgeRows.delete(edgeKey);
1054
931
  }
1055
932
  }
933
+ continue;
934
+ }
935
+ const ptsIdx = ptsEdgeRows.get(edgeKey);
936
+ if (ptsIdx !== undefined) {
937
+ // A pts-resolved edge already exists for this caller→target pair with a
938
+ // penalised confidence. Upgrade it to the direct-call confidence in-place,
939
+ // then promote to seenCallEdges so no further processing is needed.
940
+ const ptsRow = allEdgeRows[ptsIdx];
941
+ if (ptsRow) {
942
+ ptsRow[3] = confidence;
943
+ ptsRow[4] = isDynamic; // upgrade is_dynamic: direct call overrides the pts-alias dynamic flag
944
+ ptsRow[5] = 'ts-native'; // promoted from pts to direct-call resolution
945
+ }
946
+ ptsEdgeRows.delete(edgeKey);
947
+ seenCallEdges.add(edgeKey);
1056
948
  }
1057
- for (const t of targets) {
949
+ else {
950
+ seenCallEdges.add(edgeKey);
951
+ const newIdx = allEdgeRows.length;
952
+ allEdgeRows.push([caller.id, t.id, 'calls', confidence, isDynamic, 'ts-native', null]);
953
+ // Track dyn=0 edges so a later dyn=1+dynamicKind call for the same pair
954
+ // can upgrade them (e.g. bare decorator after call-expression decorator).
955
+ if (isDynamic === 0 && dynZeroEdgeRows) {
956
+ dynZeroEdgeRows.set(edgeKey, newIdx);
957
+ }
958
+ }
959
+ }
960
+ }
961
+ /**
962
+ * Phase 8.3 / 8.3c / bind: emit pts-resolved edges for unresolved no-receiver calls.
963
+ *
964
+ * Fires for three cases:
965
+ * (a) dynamic=true: alias calls emitted by extractCallbackReferenceCalls.
966
+ * Looks up `call.name` directly (alias entries are flat-keyed).
967
+ * (b) non-dynamic: parameter variable calls (fn() where fn is a param).
968
+ * Looks up the scoped key `callerName::call.name` to avoid spurious
969
+ * edges from same-named parameters across different functions.
970
+ * (c) non-dynamic: module-level alias bindings — `f = fn.bind(ctx)` or
971
+ * `const f = handler` — where pts('f') was seeded by fnRefBindings.
972
+ * Checked against fnRefBindingLhs so case (c) only fires for genuine
973
+ * bind/alias entries and never for self-seeded local definitions.
974
+ *
975
+ * Pts edges are added to ptsEdgeRows (not seenCallEdges) so that a later
976
+ * direct call to the same target can upgrade confidence rather than being
977
+ * silently dropped by the dedup guard.
978
+ */
979
+ function emitPtsNoReceiverEdges(call, caller, isDynamic, relPath, importedNames, lookup, typeMap, ptsMap, fnRefBindingLhs, seenCallEdges, ptsEdgeRows, allEdgeRows) {
980
+ const scopedPtsKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
981
+ // Module-level calls (callerName === null) use the '<module>' sentinel emitted by
982
+ // extractSpreadForOfWalk for top-level for-of loops. Look it up as a fallback so
983
+ // that `for (const f of arr) { f(); }` at module scope resolves correctly.
984
+ const modulePtsKey = caller.callerName === null && ptsMap.has(`<module>::${call.name}`)
985
+ ? `<module>::${call.name}`
986
+ : null;
987
+ const flatPtsKey = !call.dynamic && fnRefBindingLhs.has(call.name) && ptsMap.has(call.name) ? call.name : null;
988
+ if (!(call.dynamic ||
989
+ (scopedPtsKey != null && ptsMap.has(scopedPtsKey)) ||
990
+ modulePtsKey != null ||
991
+ flatPtsKey != null))
992
+ return;
993
+ const ptsLookupName = call.dynamic
994
+ ? call.name
995
+ : scopedPtsKey != null && ptsMap.has(scopedPtsKey)
996
+ ? scopedPtsKey
997
+ : modulePtsKey != null
998
+ ? modulePtsKey
999
+ : // flatPtsKey != null is guaranteed: if neither call.dynamic nor scopedPtsKey
1000
+ // nor modulePtsKey matched, flatPtsKey must be non-null.
1001
+ flatPtsKey;
1002
+ for (const alias of resolveViaPointsTo(ptsLookupName, ptsMap)) {
1003
+ // Resolve the concrete alias target. Only `name` is needed here — receiver
1004
+ // and line are not relevant for alias resolution (we are looking up the
1005
+ // aliased function by name, not dispatching a method call).
1006
+ const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
1007
+ const sortedAliasTargets = aliasTargets.length > 1
1008
+ ? [...aliasTargets].sort((a, b) => computeConfidence(relPath, b.file, aliasFrom ?? null) -
1009
+ computeConfidence(relPath, a.file, aliasFrom ?? null))
1010
+ : aliasTargets;
1011
+ for (const t of sortedAliasTargets) {
1058
1012
  const edgeKey = `${caller.id}|${t.id}`;
1059
- if (t.id !== caller.id) {
1060
- const confidence = computeConfidence(relPath, t.file, importedFrom ?? null);
1061
- if (seenCallEdges.has(edgeKey))
1062
- continue;
1063
- const ptsIdx = ptsEdgeRows.get(edgeKey);
1064
- if (ptsIdx !== undefined) {
1065
- // A pts-resolved edge already exists for this caller→target pair with a
1066
- // penalised confidence. Upgrade it to the direct-call confidence in-place,
1067
- // then promote to seenCallEdges so no further processing is needed.
1068
- const ptsRow = allEdgeRows[ptsIdx];
1069
- if (ptsRow) {
1070
- ptsRow[3] = confidence;
1071
- ptsRow[4] = isDynamic; // upgrade is_dynamic: direct call overrides the pts-alias dynamic flag
1072
- ptsRow[5] = 'ts-native'; // promoted from pts to direct-call resolution
1073
- }
1074
- ptsEdgeRows.delete(edgeKey);
1075
- seenCallEdges.add(edgeKey);
1013
+ if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1014
+ const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
1015
+ if (conf > 0) {
1016
+ ptsEdgeRows.set(edgeKey, allEdgeRows.length);
1017
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to', null]);
1076
1018
  }
1077
- else {
1078
- seenCallEdges.add(edgeKey);
1079
- allEdgeRows.push([caller.id, t.id, 'calls', confidence, isDynamic, 'ts-native']);
1019
+ }
1020
+ }
1021
+ }
1022
+ }
1023
+ /**
1024
+ * Phase 8.3f: emit pts-resolved edges for unresolved receiver calls via
1025
+ * object-rest param bindings.
1026
+ *
1027
+ * Fires when `rest.prop()` is encountered and `rest` was seeded as
1028
+ * `pts["rest.prop"]` by the object-rest dispatch chain
1029
+ * (ObjectRestParamBinding + paramBinding + ObjectPropBinding).
1030
+ */
1031
+ function emitPtsReceiverEdges(call, caller, isDynamic, relPath, importedNames, lookup, typeMap, ptsMap, seenCallEdges, ptsEdgeRows, allEdgeRows) {
1032
+ const receiverKey = `${call.receiver}.${call.name}`;
1033
+ if (!ptsMap.has(receiverKey))
1034
+ return;
1035
+ for (const alias of resolveViaPointsTo(receiverKey, ptsMap)) {
1036
+ const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
1037
+ const sortedAliasTargets = aliasTargets.length > 1
1038
+ ? [...aliasTargets].sort((a, b) => computeConfidence(relPath, b.file, aliasFrom ?? null) -
1039
+ computeConfidence(relPath, a.file, aliasFrom ?? null))
1040
+ : aliasTargets;
1041
+ for (const t of sortedAliasTargets) {
1042
+ const edgeKey = `${caller.id}|${t.id}`;
1043
+ if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1044
+ const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
1045
+ if (conf > 0) {
1046
+ ptsEdgeRows.set(edgeKey, allEdgeRows.length);
1047
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to', null]);
1080
1048
  }
1081
1049
  }
1082
1050
  }
1083
- // Phase 8.3 / 8.3c / bind: points-to fallback for unresolved calls.
1084
- // Fires for three cases:
1085
- // (a) dynamic=true: alias calls emitted by extractCallbackReferenceCalls.
1086
- // Looks up `call.name` directly (alias entries are flat-keyed).
1087
- // (b) non-dynamic: parameter variable calls (fn() where fn is a param).
1088
- // Looks up the scoped key `callerName::call.name` to avoid spurious
1089
- // edges from same-named parameters across different functions.
1090
- // (c) non-dynamic: module-level alias bindings — `f = fn.bind(ctx)` or
1091
- // `const f = handler` where pts('f') was seeded by fnRefBindings.
1092
- // Checked against fnRefBindingLhs (the pre-computed set of lhs names from
1093
- // fnRefBindings) rather than the full ptsMap, so case (c) only fires for
1094
- // genuine bind/alias entries and never for self-seeded local definitions.
1095
- // Confidence is penalised by one hop to reflect the extra indirection.
1096
- //
1097
- // Note: pts edges are added to ptsEdgeRows (not seenCallEdges) so that a later
1098
- // direct call to the same target in the same function body can upgrade confidence
1099
- // rather than being silently dropped by the dedup guard.
1100
- const scopedPtsKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
1101
- // Module-level calls (callerName === null) use the '<module>' sentinel emitted by
1102
- // extractSpreadForOfWalk for top-level for-of loops. Look it up as a fallback so
1103
- // that `for (const f of arr) { f(); }` at module scope resolves correctly.
1104
- const modulePtsKey = caller.callerName === null && ptsMap?.has(`<module>::${call.name}`)
1105
- ? `<module>::${call.name}`
1051
+ }
1052
+ }
1053
+ /**
1054
+ * Phase 8.5: emit CHA + RTA dispatch edges for a single call site.
1055
+ *
1056
+ * For `this`/`self`/`super` calls: resolve through the class hierarchy.
1057
+ * For typed receiver calls: expand to all instantiated concrete implementations.
1058
+ */
1059
+ function emitChaCallEdgesForCall(call, caller, relPath, typeMap, lookup, chaCtx, seenCallEdges, ptsEdgeRows, allEdgeRows) {
1060
+ let chaTargets = [];
1061
+ let isTypedReceiverDispatch = false;
1062
+ if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
1063
+ chaTargets = resolveThisDispatch(call.name, caller.callerName, call.receiver, chaCtx, lookup, relPath);
1064
+ }
1065
+ else if (!BUILTIN_RECEIVERS.has(call.receiver)) {
1066
+ const typeEntry = typeMap.get(call.receiver);
1067
+ const typeName = typeEntry
1068
+ ? typeof typeEntry === 'string'
1069
+ ? typeEntry
1070
+ : typeEntry.type
1106
1071
  : null;
1107
- const flatPtsKey = !call.dynamic && fnRefBindingLhs.has(call.name) && ptsMap?.has(call.name) ? call.name : null;
1108
- if (targets.length === 0 &&
1109
- !call.receiver &&
1110
- ptsMap &&
1111
- (call.dynamic ||
1112
- (scopedPtsKey != null && ptsMap.has(scopedPtsKey)) ||
1113
- modulePtsKey != null ||
1114
- flatPtsKey != null)) {
1115
- const ptsLookupName = call.dynamic
1116
- ? call.name
1117
- : scopedPtsKey != null && ptsMap.has(scopedPtsKey)
1118
- ? scopedPtsKey
1119
- : modulePtsKey != null
1120
- ? modulePtsKey
1121
- : // flatPtsKey != null is guaranteed by the outer if condition: if neither
1122
- // call.dynamic nor scopedPtsKey nor modulePtsKey matched, flatPtsKey must be non-null.
1123
- flatPtsKey;
1124
- for (const alias of resolveViaPointsTo(ptsLookupName, ptsMap)) {
1125
- // Resolve the concrete alias target. Only `name` is needed here — receiver
1126
- // and line are not relevant for alias resolution (we are looking up the
1127
- // aliased function by name, not dispatching a method call).
1128
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
1129
- for (const t of aliasTargets) {
1130
- const edgeKey = `${caller.id}|${t.id}`;
1131
- if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1132
- const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
1133
- if (conf > 0) {
1134
- ptsEdgeRows.set(edgeKey, allEdgeRows.length);
1135
- allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
1136
- }
1137
- }
1138
- }
1072
+ if (typeName) {
1073
+ chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
1074
+ isTypedReceiverDispatch = true;
1075
+ }
1076
+ }
1077
+ for (const t of chaTargets) {
1078
+ const edgeKey = `${caller.id}|${t.id}`;
1079
+ if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1080
+ // Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
1081
+ // — file proximity is not meaningful for virtual dispatch confidence.
1082
+ // this/super dispatch keeps computeConfidence-based proximity scoring to
1083
+ // match runPostNativeThisDispatch (native-orchestrator.ts).
1084
+ const conf = isTypedReceiverDispatch
1085
+ ? CHA_TYPED_DISPATCH_CONFIDENCE
1086
+ : computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
1087
+ if (conf > 0) {
1088
+ seenCallEdges.add(edgeKey);
1089
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha', null]);
1139
1090
  }
1140
1091
  }
1141
- // Phase 8.3f: pts fallback for receiver calls via object-rest param bindings.
1142
- // Fires when `rest.prop()` is encountered and `rest` was seeded as `pts["rest.prop"]`
1143
- // by the object-rest dispatch chain (ObjectRestParamBinding + paramBinding + ObjectPropBinding).
1092
+ }
1093
+ }
1094
+ /**
1095
+ * Dynamic kinds that cannot be resolved statically — emit a sink edge to the
1096
+ * file node instead of silently dropping the call site. confidence=0.0 keeps
1097
+ * these below DEFAULT_MIN_CONFIDENCE so they never appear in normal query results.
1098
+ * Includes reflection so that Reflect.apply/getMethod/callable-ref calls whose
1099
+ * target is not found in the codebase still produce a visible sink edge.
1100
+ */
1101
+ const FLAG_ONLY_KINDS = new Set([
1102
+ 'eval',
1103
+ 'computed-key',
1104
+ 'reflection',
1105
+ 'unresolved-dynamic',
1106
+ ]);
1107
+ /**
1108
+ * Build call edges for all calls in a single file (WASM/JS engine path).
1109
+ *
1110
+ * Iterates over `symbols.calls` and dispatches each call through the full
1111
+ * JS resolution cascade:
1112
+ * 1. `resolveFallbackTargets` — primary + class-fallback + defineProperty fallback
1113
+ * 2. `emitDirectCallEdgesForCall` — emit direct-call edges (upgrading any pts pair)
1114
+ * 3. `emitPtsNoReceiverEdges` — Phase 8.3/8.3c pts fallback for no-receiver calls
1115
+ * 4. `emitPtsReceiverEdges` — Phase 8.3f pts fallback for rest-param receiver calls
1116
+ * 5. Inline `resolveReceiverEdge` — emit `receiver` edge for external receivers
1117
+ * 6. `emitChaCallEdgesForCall` — Phase 8.5 CHA + RTA dispatch expansion
1118
+ * 7. Sink edge for flag-only dynamic kinds (eval, computed-key, reflection, unresolved-dynamic)
1119
+ */
1120
+ function buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCallEdges, lookup, allEdgeRows, typeMap, ptsMap, chaCtx, importArtifactNames) {
1121
+ // Tracks edges that were inserted by the pts fallback (edgeKey → allEdgeRows index).
1122
+ // Kept separate from seenCallEdges so that a subsequent direct-call edge for the same
1123
+ // caller→target pair can upgrade the confidence in-place rather than being silently
1124
+ // dropped by the dedup guard. Once upgraded, the key moves to seenCallEdges and is
1125
+ // no longer tracked here.
1126
+ const ptsEdgeRows = new Map();
1127
+ // Tracks direct-call edges emitted with dyn=0 (edgeKey → allEdgeRows index).
1128
+ // When a later call to the same target has dyn=1 (e.g. a bare decorator `@Log`
1129
+ // processed after the call-expression `@Log()` in the query path), the existing
1130
+ // dyn=0 row is upgraded in-place so the more specific dynamic classification wins.
1131
+ const dynZeroEdgeRows = new Map();
1132
+ // Pre-compute the set of names that appear as lhs in fnRefBindings so that
1133
+ // case (c) of the pts gate below only fires for names that are genuine
1134
+ // bind/alias entries, not for every locally-defined function or import that
1135
+ // buildPointsToMap seeds with a self-pointing entry.
1136
+ const fnRefBindingLhs = new Set(symbols.fnRefBindings?.map((b) => b.lhs) ?? []);
1137
+ for (const call of symbols.calls) {
1138
+ if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver))
1139
+ continue;
1140
+ const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
1141
+ const isDynamic = call.dynamic ? 1 : 0;
1142
+ // Step 1: Resolve targets with all JS-path fallbacks.
1143
+ const { targets, importedFrom } = resolveFallbackTargets(call, caller, relPath, importedNames, lookup, typeMap, symbols.definePropertyReceivers);
1144
+ // Step 2: Emit direct-call edges (upgrades any pending pts edge in-place).
1145
+ emitDirectCallEdgesForCall(caller, targets, importedFrom, isDynamic, !!call.dynamicKind, relPath, seenCallEdges, ptsEdgeRows, allEdgeRows, dynZeroEdgeRows);
1146
+ // Step 3: Phase 8.3/8.3c pts fallback for unresolved no-receiver calls.
1147
+ if (targets.length === 0 && !call.receiver && ptsMap) {
1148
+ emitPtsNoReceiverEdges(call, caller, isDynamic, relPath, importedNames, lookup, typeMap, ptsMap, fnRefBindingLhs, seenCallEdges, ptsEdgeRows, allEdgeRows);
1149
+ }
1150
+ // Step 4: Phase 8.3f pts fallback for unresolved receiver calls (rest params).
1144
1151
  if (targets.length === 0 &&
1145
1152
  call.receiver &&
1146
1153
  !BUILTIN_RECEIVERS.has(call.receiver) &&
@@ -1148,62 +1155,40 @@ function buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCa
1148
1155
  call.receiver !== 'self' &&
1149
1156
  call.receiver !== 'super' &&
1150
1157
  ptsMap) {
1151
- const receiverKey = `${call.receiver}.${call.name}`;
1152
- if (ptsMap.has(receiverKey)) {
1153
- for (const alias of resolveViaPointsTo(receiverKey, ptsMap)) {
1154
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
1155
- for (const t of aliasTargets) {
1156
- const edgeKey = `${caller.id}|${t.id}`;
1157
- if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1158
- const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
1159
- if (conf > 0) {
1160
- ptsEdgeRows.set(edgeKey, allEdgeRows.length);
1161
- allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
1162
- }
1163
- }
1164
- }
1165
- }
1166
- }
1158
+ emitPtsReceiverEdges(call, caller, isDynamic, relPath, importedNames, lookup, typeMap, ptsMap, seenCallEdges, ptsEdgeRows, allEdgeRows);
1167
1159
  }
1160
+ // Step 5: Emit receiver edge for external (non-this/self/super) receivers.
1168
1161
  if (call.receiver &&
1169
1162
  !BUILTIN_RECEIVERS.has(call.receiver) &&
1170
1163
  call.receiver !== 'this' &&
1171
1164
  call.receiver !== 'self' &&
1172
1165
  call.receiver !== 'super') {
1173
- const recv = resolveReceiverEdge(lookup, { name: call.name, receiver: call.receiver }, caller, relPath, typeMap, seenCallEdges);
1166
+ const recv = resolveReceiverEdge(lookup, { name: call.name, receiver: call.receiver }, caller, relPath, typeMap, seenCallEdges, importArtifactNames ?? importedNames);
1174
1167
  if (recv) {
1175
- allEdgeRows.push([recv.callerId, recv.receiverId, 'receiver', recv.confidence, 0, null]);
1168
+ allEdgeRows.push([
1169
+ recv.callerId,
1170
+ recv.receiverId,
1171
+ 'receiver',
1172
+ recv.confidence,
1173
+ 0,
1174
+ null,
1175
+ null,
1176
+ ]);
1176
1177
  }
1177
1178
  }
1178
- // Phase 8.5: CHA + RTA dispatch expansion.
1179
- // For `this`/`self`/`super` calls: resolve through the class hierarchy instead
1180
- // of relying solely on global name matching.
1181
- // For typed receiver calls: expand to all instantiated concrete implementations.
1179
+ // Step 6: Phase 8.5 CHA + RTA dispatch expansion.
1182
1180
  if (chaCtx && call.receiver) {
1183
- let chaTargets = [];
1184
- if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
1185
- chaTargets = resolveThisDispatch(call.name, caller.callerName, call.receiver, chaCtx, lookup);
1186
- }
1187
- else if (!BUILTIN_RECEIVERS.has(call.receiver)) {
1188
- const typeEntry = typeMap.get(call.receiver);
1189
- const typeName = typeEntry
1190
- ? typeof typeEntry === 'string'
1191
- ? typeEntry
1192
- : typeEntry.type
1193
- : null;
1194
- if (typeName) {
1195
- chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
1196
- }
1197
- }
1198
- for (const t of chaTargets) {
1199
- const edgeKey = `${caller.id}|${t.id}`;
1200
- if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1201
- const conf = computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
1202
- if (conf > 0) {
1203
- seenCallEdges.add(edgeKey);
1204
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha']);
1205
- }
1206
- }
1181
+ emitChaCallEdgesForCall(call, caller, relPath, typeMap, lookup, chaCtx, seenCallEdges, ptsEdgeRows, allEdgeRows);
1182
+ }
1183
+ // Step 7: Flag-only dynamic kinds with no resolved target → sink edge to the
1184
+ // file node. confidence=0.0 keeps it below DEFAULT_MIN_CONFIDENCE so it never
1185
+ // appears in normal query results, but is queryable via `codegraph roles --dynamic`.
1186
+ if (targets.length === 0 && call.dynamicKind && FLAG_ONLY_KINDS.has(call.dynamicKind)) {
1187
+ // Key per (caller, file, kind) so each kind gets at most one sink edge per caller.
1188
+ const sinkKey = `${caller.id}:${fileNodeRow.id}:${call.dynamicKind}`;
1189
+ if (!seenCallEdges.has(sinkKey)) {
1190
+ seenCallEdges.add(sinkKey);
1191
+ allEdgeRows.push([caller.id, fileNodeRow.id, 'calls', 0.0, 1, null, call.dynamicKind]);
1207
1192
  }
1208
1193
  }
1209
1194
  }
@@ -1219,7 +1204,7 @@ function buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows) {
1219
1204
  const targetRows = (ctx.nodesByName.get(cls.extends) || []).filter((n) => EXTENDS_TARGET_KINDS.has(n.kind));
1220
1205
  if (sourceRow) {
1221
1206
  for (const t of targetRows) {
1222
- allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null]);
1207
+ allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null, null]);
1223
1208
  }
1224
1209
  }
1225
1210
  }
@@ -1228,7 +1213,7 @@ function buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows) {
1228
1213
  const targetRows = (ctx.nodesByName.get(cls.implements) || []).filter((n) => IMPLEMENTS_TARGET_KINDS.has(n.kind));
1229
1214
  if (sourceRow) {
1230
1215
  for (const t of targetRows) {
1231
- allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null]);
1216
+ allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null, null]);
1232
1217
  }
1233
1218
  }
1234
1219
  }
@@ -1237,7 +1222,8 @@ function buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows) {
1237
1222
  // ── Native bulk-insert technique back-fill ──────────────────────────────
1238
1223
  /**
1239
1224
  * After native bulkInsertEdges (which does not write the technique column),
1240
- * apply technique values from the in-memory row array back to the DB.
1225
+ * apply technique values from the in-memory row array back to the DB, and lift
1226
+ * any resolved ts-native edge below TS_NATIVE_CONFIDENCE_FLOOR to that floor.
1241
1227
  *
1242
1228
  * Rows with an explicit technique get a targeted UPDATE by (source_id, target_id).
1243
1229
  * The catch-all 'ts-native' tag is scoped to only the source_ids present in this
@@ -1254,6 +1240,8 @@ function applyEdgeTechniquesAfterNativeInsert(db, rows) {
1254
1240
  const sourceIds = [...new Set(callRows.map((r) => r[0]))];
1255
1241
  // Chunk to stay within SQLite's SQLITE_LIMIT_VARIABLE_NUMBER (999 on older builds).
1256
1242
  const CHUNK_SIZE = 500;
1243
+ // Rows that carry an explicit dynamic_kind (sink edges for flagged dynamic calls).
1244
+ const dynamicKindRows = callRows.filter((r) => r[6] != null);
1257
1245
  const tx = db.transaction(() => {
1258
1246
  if (taggedRows.length > 0) {
1259
1247
  const stmt = db.prepare("UPDATE edges SET technique = ? WHERE kind = 'calls' AND source_id = ? AND target_id = ? AND technique IS NULL");
@@ -1264,6 +1252,24 @@ function applyEdgeTechniquesAfterNativeInsert(db, rows) {
1264
1252
  const chunk = sourceIds.slice(i, i + CHUNK_SIZE);
1265
1253
  const placeholders = chunk.map(() => '?').join(',');
1266
1254
  db.prepare(`UPDATE edges SET technique = 'ts-native' WHERE kind = 'calls' AND technique IS NULL AND source_id IN (${placeholders})`).run(...chunk);
1255
+ // Lift resolved ts-native edges below the confidence floor for this chunk.
1256
+ db.prepare(`UPDATE edges SET confidence = ?
1257
+ WHERE kind = 'calls' AND technique = 'ts-native'
1258
+ AND confidence > 0 AND confidence < ?
1259
+ AND source_id IN (${placeholders})`).run(TS_NATIVE_CONFIDENCE_FLOOR, TS_NATIVE_CONFIDENCE_FLOOR, ...chunk);
1260
+ }
1261
+ // Back-fill dynamic_kind for flagged sink edges emitted by the native engine.
1262
+ // Native bulkInsertEdges uses INSERT OR IGNORE and does not write dynamic_kind, so
1263
+ // this UPDATE is the only way to set it for natively-inserted sink edges.
1264
+ //
1265
+ // Scope to confidence=0.0 AND dynamic=1 so we only touch sink edges (never normal
1266
+ // call edges that happen to share the same (source_id, target_id) pair).
1267
+ // Include dynamic_kind in the WHERE so two sink edges from the same caller to the
1268
+ // same file with different kinds don't clobber each other across incremental runs.
1269
+ if (dynamicKindRows.length > 0) {
1270
+ const stmt = db.prepare("UPDATE edges SET dynamic_kind = ? WHERE kind = 'calls' AND source_id = ? AND target_id = ? AND confidence = 0.0 AND dynamic = 1 AND (dynamic_kind IS NULL OR dynamic_kind = ?)");
1271
+ for (const r of dynamicKindRows)
1272
+ stmt.run(r[6], r[0], r[1], r[6]);
1267
1273
  }
1268
1274
  });
1269
1275
  tx();
@@ -1292,6 +1298,7 @@ function reconnectReverseDepEdges(ctx) {
1292
1298
  saved.confidence,
1293
1299
  saved.dynamic,
1294
1300
  saved.technique,
1301
+ saved.dynamicKind ?? null,
1295
1302
  ]);
1296
1303
  }
1297
1304
  else {
@@ -1330,7 +1337,7 @@ function reconnectReverseDepEdges(ctx) {
1330
1337
  * their import targets. Falls back to loading ALL nodes for full builds or
1331
1338
  * larger incremental changes.
1332
1339
  */
1333
- const NODE_KIND_FILTER_SQL = `kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant')`;
1340
+ const NODE_KIND_FILTER_SQL = `kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant','variable')`;
1334
1341
  function loadNodes(ctx) {
1335
1342
  const { db, fileSymbols, isFullBuild, batchResolved } = ctx;
1336
1343
  const nodeKindFilter = NODE_KIND_FILTER_SQL;
@@ -1399,7 +1406,15 @@ export async function buildEdges(ctx) {
1399
1406
  // Enrich typeMap for .ts/.tsx files using the TypeScript compiler API.
1400
1407
  // Runs before call-edge construction so the accurate types are available
1401
1408
  // for method-call resolution. Gated on config so users can opt out.
1402
- if (ctx.config.build.typescriptResolver) {
1409
+ //
1410
+ // Skip for small incremental builds: TypeScript program creation requires
1411
+ // loading the entire tsconfig file list (~700ms startup on the codegraph
1412
+ // corpus), which dominates the 1-file rebuild time. Native engine bypasses
1413
+ // this entirely via the Rust orchestrator; WASM/JS engines need this gate
1414
+ // to match native's effective behaviour on tiny incremental changes.
1415
+ // Mirrors the smallFilesThreshold gates for nativeDb and native call-edges.
1416
+ const isSmallIncremental = !ctx.isFullBuild && ctx.fileSymbols.size <= ctx.config.build.smallFilesThreshold;
1417
+ if (ctx.config.build.typescriptResolver && !isSmallIncremental) {
1403
1418
  await enrichTypeMapWithTsc(ctx.rootDir, ctx.fileSymbols);
1404
1419
  }
1405
1420
  const native = engineName === 'native' ? loadNative() : null;
@@ -1454,26 +1469,12 @@ export async function buildEdges(ctx) {
1454
1469
  (ctx.isFullBuild || ctx.fileSymbols.size > ctx.config.build.smallFilesThreshold);
1455
1470
  if (useNativeCallEdges) {
1456
1471
  buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodesBefore, native);
1457
- // Build the shared lookup once — both pts post-passes use it, avoiding
1458
- // redundant construction of the same context closure.
1472
+ // The native engine receives all pts bindings (paramBindings,
1473
+ // fnRefBindings, thisCallBindings, objectRestParamBindings, …) through
1474
+ // NativeFileEntry and runs the same points-to solver as the JS path, so
1475
+ // no pts post-passes are needed here. Only capabilities that remain
1476
+ // JS-only run as post-passes below.
1459
1477
  const sharedLookup = makeContextLookup(ctx, getNodeIdStmt);
1460
- // Phase 8.3c post-pass: augment native call edges with parameter-flow pts
1461
- // edges. The native Rust engine has no knowledge of paramBindings, so any
1462
- // `fn()` call inside a higher-order function would be missed. This JS pass
1463
- // runs on top of the native edges and adds only the pts-resolved edges that
1464
- // the native engine could not produce.
1465
- buildParamFlowPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
1466
- // bind/alias post-pass: augment native call edges with fnRefBindings-seeded
1467
- // pts edges. The native Rust engine has no knowledge of JS fnRefBindings
1468
- // (e.g. `const f = fn.bind(ctx)`), so calls to bind-created aliases are
1469
- // not resolved to their original function on the native path.
1470
- buildFnRefBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
1471
- // this-rebinding post-pass: resolve `this()` calls inside functions that
1472
- // were invoked via `.call(namedCtx, ...)` / `.apply(namedCtx, ...)`.
1473
- buildThisCallBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
1474
- // Phase 8.3f post-pass: augment native call edges with object rest-param
1475
- // receiver resolution — typeMap[restName] → argName → typeMap[argName.method].
1476
- buildObjectRestParamPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
1477
1478
  // Object.defineProperty accessor post-pass: resolve this-dispatch inside
1478
1479
  // getter/setter functions registered via Object.defineProperty.
1479
1480
  buildDefinePropertyPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
@@ -1485,6 +1486,21 @@ export async function buildEdges(ctx) {
1485
1486
  else {
1486
1487
  buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows, chaCtx);
1487
1488
  }
1489
+ // Apply ts-native confidence floor to allEdgeRows in-memory. The proximity
1490
+ // heuristic returns 0.3 for cross-module calls with no import-path evidence,
1491
+ // but both WASM and native engines perform actual name-based symbol lookup,
1492
+ // which is stronger evidence than pure proximity. Clamping to
1493
+ // TS_NATIVE_CONFIDENCE_FLOOR (0.5) avoids unfairly dragging down the
1494
+ // call-confidence metric. Sink edges (confidence = 0.0) are excluded so
1495
+ // they remain below DEFAULT_MIN_CONFIDENCE.
1496
+ for (const r of allEdgeRows) {
1497
+ if (r[2] === 'calls' &&
1498
+ r[5] === 'ts-native' &&
1499
+ r[3] > 0 &&
1500
+ r[3] < TS_NATIVE_CONFIDENCE_FLOOR) {
1501
+ r[3] = TS_NATIVE_CONFIDENCE_FLOOR;
1502
+ }
1503
+ }
1488
1504
  // When using native edge insert, skip JS insert here — do it after tx commits.
1489
1505
  // Otherwise insert edges within this transaction for atomicity.
1490
1506
  const useNativeEdgeInsert = ctx.engineName === 'native' && !!ctx.nativeDb?.bulkInsertEdges;