@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,16 +11,26 @@ 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 type {
16
+ ArrayCallbackBinding,
17
+ ArrayElemBinding,
15
18
  BetterSqlite3Database,
16
19
  Call,
17
20
  ClassRelation,
18
21
  Definition,
22
+ DynamicKind,
19
23
  ExtractorOutput,
20
24
  FnRefBinding,
25
+ ForOfBinding,
21
26
  Import,
22
27
  NativeAddon,
23
28
  NodeRow,
29
+ ObjectPropBinding,
30
+ ObjectRestParamBinding,
31
+ ParamBinding,
32
+ SpreadArgBinding,
33
+ ThisCallBinding,
24
34
  TypeMapEntry,
25
35
  } from '../../../../types.js';
26
36
  import { computeConfidence } from '../../resolve.js';
@@ -37,12 +47,19 @@ import {
37
47
  import type { ChaContext } from '../cha.js';
38
48
  import { buildChaContext, resolveChaTargets, resolveThisDispatch } from '../cha.js';
39
49
  import type { PipelineContext } from '../context.js';
40
- import { BUILTIN_RECEIVERS, batchInsertEdges, runChaPostPass } from '../helpers.js';
50
+ import {
51
+ BUILTIN_RECEIVERS,
52
+ batchInsertEdges,
53
+ CHA_DISPATCH_PENALTY,
54
+ CHA_TYPED_DISPATCH_CONFIDENCE,
55
+ runChaPostPass,
56
+ } from '../helpers.js';
41
57
  import { getResolved, isBarrelFile, resolveBarrelExportCached } from './resolve-imports.js';
42
58
 
43
59
  // ── Local types ──────────────────────────────────────────────────────────
44
60
 
45
- type EdgeRowTuple = [number, number, string, number, number, string | null];
61
+ type EdgeRowTuple = [number, number, string, number, number, string | null, string | null];
62
+ // src tgt kind conf dyn technique dynamic_kind
46
63
 
47
64
  interface NodeIdStmt {
48
65
  get(name: string, kind: string, file: string, line: number): { id: number } | undefined;
@@ -61,13 +78,27 @@ interface QueryNodeRow {
61
78
  interface NativeFileEntry {
62
79
  file: string;
63
80
  fileNodeId: number;
64
- definitions: Array<{ name: string; kind: string; line: number; endLine: number | null }>;
81
+ definitions: Array<{
82
+ name: string;
83
+ kind: string;
84
+ line: number;
85
+ endLine: number | null;
86
+ params?: string[];
87
+ }>;
65
88
  calls: Call[];
66
89
  importedNames: Array<{ name: string; file: string }>;
67
90
  classes: ClassRelation[];
68
91
  typeMap: Array<{ name: string; typeName: string; confidence: number }>;
69
92
  /** Phase 8.3: function-reference bindings for pts analysis. */
70
93
  fnRefBindings?: Array<{ lhs: string; rhs: string; rhsReceiver?: string }>;
94
+ paramBindings?: ParamBinding[];
95
+ thisCallBindings?: ThisCallBinding[];
96
+ arrayElemBindings?: ArrayElemBinding[];
97
+ spreadArgBindings?: SpreadArgBinding[];
98
+ forOfBindings?: ForOfBinding[];
99
+ arrayCallbackBindings?: ArrayCallbackBinding[];
100
+ objectRestParamBindings?: ObjectRestParamBinding[];
101
+ objectPropBindings?: ObjectPropBinding[];
71
102
  }
72
103
 
73
104
  /** Shape returned by native buildCallEdges. */
@@ -77,11 +108,9 @@ interface NativeEdge {
77
108
  kind: string;
78
109
  confidence: number;
79
110
  dynamic: number;
111
+ dynamic_kind?: string | null;
80
112
  }
81
113
 
82
- /** Phase 8.5: confidence penalty applied to CHA-dispatch edges. */
83
- export const CHA_DISPATCH_PENALTY = 0.1;
84
-
85
114
  // ── Node lookup setup ───────────────────────────────────────────────────
86
115
 
87
116
  function makeGetNodeIdStmt(db: BetterSqlite3Database): NodeIdStmt {
@@ -138,7 +167,7 @@ function emitTypeOnlySymbolEdges(
138
167
  }
139
168
  const candidates = ctx.nodesByNameAndFile.get(`${cleanName}|${targetFile}`);
140
169
  if (candidates && candidates.length > 0) {
141
- allEdgeRows.push([fileNodeId, candidates[0]!.id, 'imports-type', 1.0, 0, null]);
170
+ allEdgeRows.push([fileNodeId, candidates[0]!.id, 'imports-type', 1.0, 0, null, null]);
142
171
  }
143
172
  }
144
173
  }
@@ -160,7 +189,7 @@ function emitEdgesForImport(
160
189
  if (!targetRow) return;
161
190
 
162
191
  const edgeKind = importEdgeKind(imp);
163
- allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null]);
192
+ allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null, null]);
164
193
 
165
194
  if (imp.typeOnly) {
166
195
  emitTypeOnlySymbolEdges(ctx, imp, resolvedPath, fileNodeId, allEdgeRows);
@@ -215,7 +244,7 @@ function buildBarrelEdges(
215
244
  : edgeKind === 'dynamic-imports'
216
245
  ? 'dynamic-imports'
217
246
  : 'imports';
218
- edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null]);
247
+ edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null, null]);
219
248
  }
220
249
  }
221
250
  }
@@ -398,7 +427,7 @@ function buildImportEdgesNative(
398
427
  ) as NativeEdge[];
399
428
 
400
429
  for (const e of nativeEdges) {
401
- allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic, null]);
430
+ allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic, null, null]);
402
431
  }
403
432
  }
404
433
 
@@ -509,17 +538,35 @@ function buildCallEdgesNative(
509
538
  nativeFiles.push({
510
539
  file: relPath,
511
540
  fileNodeId: fileNodeRow.id,
512
- definitions: symbols.definitions.map((d) => ({
513
- name: d.name,
514
- kind: d.kind,
515
- line: d.line,
516
- endLine: d.endLine ?? null,
517
- })),
541
+ definitions: symbols.definitions.map((d) => {
542
+ const params = d.children?.filter((c) => c.kind === 'parameter').map((c) => c.name);
543
+ return {
544
+ name: d.name,
545
+ kind: d.kind,
546
+ line: d.line,
547
+ endLine: d.endLine ?? null,
548
+ params: params?.length ? params : undefined,
549
+ };
550
+ }),
518
551
  calls: symbols.calls,
519
552
  importedNames,
520
553
  classes: symbols.classes,
521
554
  typeMap,
522
555
  fnRefBindings: symbols.fnRefBindings?.length ? symbols.fnRefBindings : undefined,
556
+ paramBindings: symbols.paramBindings?.length ? symbols.paramBindings : undefined,
557
+ thisCallBindings: symbols.thisCallBindings?.length ? symbols.thisCallBindings : undefined,
558
+ arrayElemBindings: symbols.arrayElemBindings?.length ? symbols.arrayElemBindings : undefined,
559
+ spreadArgBindings: symbols.spreadArgBindings?.length ? symbols.spreadArgBindings : undefined,
560
+ forOfBindings: symbols.forOfBindings?.length ? symbols.forOfBindings : undefined,
561
+ arrayCallbackBindings: symbols.arrayCallbackBindings?.length
562
+ ? symbols.arrayCallbackBindings
563
+ : undefined,
564
+ objectRestParamBindings: symbols.objectRestParamBindings?.length
565
+ ? symbols.objectRestParamBindings
566
+ : undefined,
567
+ objectPropBindings: symbols.objectPropBindings?.length
568
+ ? symbols.objectPropBindings
569
+ : undefined,
523
570
  });
524
571
  }
525
572
 
@@ -534,367 +581,11 @@ function buildCallEdgesNative(
534
581
  e.confidence,
535
582
  e.dynamic,
536
583
  e.kind === 'calls' ? 'ts-native' : null,
584
+ e.dynamic_kind ?? null,
537
585
  ]);
538
586
  }
539
587
  }
540
588
 
541
- /**
542
- * Phase 8.3c pts post-pass for the native call-edge path.
543
- *
544
- * The native Rust engine builds call edges without knowledge of paramBindings,
545
- * so `fn()` calls inside higher-order functions are not resolved to their
546
- * concrete targets. This JS post-pass runs after the native edge pass and adds
547
- * only the parameter-flow pts edges that the native engine missed.
548
- *
549
- * To avoid duplicating edges already emitted by the native engine, the current
550
- * allEdgeRows snapshot is used to seed a seenByPair set before processing each
551
- * file.
552
- */
553
- function buildParamFlowPtsPostPass(
554
- ctx: PipelineContext,
555
- getNodeIdStmt: NodeIdStmt,
556
- allEdgeRows: EdgeRowTuple[],
557
- sharedLookup?: CallNodeLookup,
558
- ): void {
559
- // Only process files that actually have paramBindings (avoid useless work).
560
- const filesWithParams = [...ctx.fileSymbols].filter(
561
- ([, symbols]) => symbols.paramBindings && symbols.paramBindings.length > 0,
562
- );
563
- if (filesWithParams.length === 0) return;
564
-
565
- // Seed seenByPair from the existing rows so we don't duplicate native edges.
566
- // This is O(|allEdgeRows|) once per post-pass, which is acceptable.
567
- const seenByPair = new Set<string>();
568
- for (const [srcId, tgtId] of allEdgeRows) {
569
- seenByPair.add(`${srcId}|${tgtId}`);
570
- }
571
-
572
- const { barrelOnlyFiles, rootDir } = ctx;
573
- const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
574
-
575
- for (const [relPath, symbols] of filesWithParams) {
576
- if (barrelOnlyFiles.has(relPath)) continue;
577
- const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
578
- if (!fileNodeRow) continue;
579
-
580
- const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
581
- const typeMap: Map<string, TypeMapEntry | string> = symbols.typeMap || new Map();
582
- const ptsMap = buildPointsToMapForFile(symbols, importedNames);
583
- if (!ptsMap) continue;
584
-
585
- for (const call of symbols.calls) {
586
- if (call.receiver || call.dynamic) continue; // pts post-pass handles only param-flow (non-dynamic)
587
-
588
- const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
589
- const scopedKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
590
- if (!scopedKey || !ptsMap.has(scopedKey)) continue;
591
-
592
- // Only resolve calls that had no direct targets (same guard as buildFileCallEdges).
593
- const { targets } = resolveCallTargets(
594
- lookup,
595
- call,
596
- relPath,
597
- importedNames,
598
- typeMap as Map<string, unknown>,
599
- );
600
- if (targets.length > 0) continue;
601
-
602
- for (const alias of resolveViaPointsTo(scopedKey, ptsMap)) {
603
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
604
- lookup,
605
- { name: alias },
606
- relPath,
607
- importedNames,
608
- typeMap as Map<string, unknown>,
609
- );
610
- for (const t of aliasTargets) {
611
- const edgeKey = `${caller.id}|${t.id}`;
612
- if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
613
- const conf =
614
- computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
615
- if (conf > 0) {
616
- seenByPair.add(edgeKey);
617
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
618
- }
619
- }
620
- }
621
- }
622
- }
623
- }
624
- }
625
-
626
- /**
627
- * bind/alias pts post-pass for the native call-edge path.
628
- *
629
- * The native Rust engine has no knowledge of JS-layer fnRefBindings (e.g.
630
- * `const f = fn.bind(ctx)`), so calls to bind-created aliases are not resolved
631
- * to their original function on the native path. This JS post-pass runs after
632
- * the native edge pass and adds only the fnRefBindings-seeded pts edges that the
633
- * native engine missed.
634
- *
635
- * Uses the same seenByPair dedup guard as buildParamFlowPtsPostPass to avoid
636
- * duplicating edges already emitted by the native engine.
637
- */
638
- function buildFnRefBindingsPtsPostPass(
639
- ctx: PipelineContext,
640
- getNodeIdStmt: NodeIdStmt,
641
- allEdgeRows: EdgeRowTuple[],
642
- sharedLookup?: CallNodeLookup,
643
- ): void {
644
- // Only process files that actually have fnRefBindings.
645
- const filesWithBindings = [...ctx.fileSymbols].filter(
646
- ([, symbols]) => symbols.fnRefBindings && symbols.fnRefBindings.length > 0,
647
- );
648
- if (filesWithBindings.length === 0) return;
649
-
650
- // Seed seenByPair from the existing rows so we don't duplicate native edges.
651
- const seenByPair = new Set<string>();
652
- for (const [srcId, tgtId] of allEdgeRows) {
653
- seenByPair.add(`${srcId}|${tgtId}`);
654
- }
655
-
656
- const { barrelOnlyFiles, rootDir } = ctx;
657
- const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
658
-
659
- for (const [relPath, symbols] of filesWithBindings) {
660
- if (barrelOnlyFiles.has(relPath)) continue;
661
- const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
662
- if (!fileNodeRow) continue;
663
-
664
- const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
665
- const typeMap: Map<string, TypeMapEntry | string> = symbols.typeMap || new Map();
666
- const ptsMap = buildPointsToMapForFile(symbols, importedNames);
667
- if (!ptsMap) continue;
668
-
669
- // Only resolve calls whose name is an lhs in fnRefBindings — the same
670
- // narrowed guard used in buildFileCallEdges case (c).
671
- const fnRefBindingLhs = new Set(symbols.fnRefBindings!.map((b) => b.lhs));
672
-
673
- for (const call of symbols.calls) {
674
- if (call.receiver || call.dynamic) continue; // bind aliases are flat-keyed, never dynamic
675
- if (!fnRefBindingLhs.has(call.name)) continue;
676
- if (!ptsMap.has(call.name)) continue;
677
-
678
- const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
679
-
680
- // Only resolve calls that had no direct targets (same guard as buildFileCallEdges).
681
- const { targets } = resolveCallTargets(
682
- lookup,
683
- call,
684
- relPath,
685
- importedNames,
686
- typeMap as Map<string, unknown>,
687
- );
688
- if (targets.length > 0) continue;
689
-
690
- for (const alias of resolveViaPointsTo(call.name, ptsMap)) {
691
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
692
- lookup,
693
- { name: alias },
694
- relPath,
695
- importedNames,
696
- typeMap as Map<string, unknown>,
697
- );
698
- for (const t of aliasTargets) {
699
- const edgeKey = `${caller.id}|${t.id}`;
700
- if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
701
- const conf =
702
- computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
703
- if (conf > 0) {
704
- seenByPair.add(edgeKey);
705
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
706
- }
707
- }
708
- }
709
- }
710
- }
711
- }
712
- }
713
-
714
- /**
715
- * this-rebinding post-pass for the native call-edge path.
716
- *
717
- * When `fn.call(namedCtx, ...)` or `fn.apply(namedCtx, ...)` is extracted by the
718
- * WASM layer, `thisCallBindings` records `{ callee: 'fn', thisArg: 'namedCtx' }`.
719
- * The native Rust engine has no knowledge of these bindings, so `this()` calls
720
- * inside `fn` remain unresolved. This JS post-pass adds the missing edges by
721
- * resolving `this()` calls inside each `fn` that has a thisCallBinding.
722
- */
723
- function buildThisCallBindingsPtsPostPass(
724
- ctx: PipelineContext,
725
- getNodeIdStmt: NodeIdStmt,
726
- allEdgeRows: EdgeRowTuple[],
727
- sharedLookup?: CallNodeLookup,
728
- ): void {
729
- const filesWithBindings = [...ctx.fileSymbols].filter(
730
- ([, symbols]) => symbols.thisCallBindings && symbols.thisCallBindings.length > 0,
731
- );
732
- if (filesWithBindings.length === 0) return;
733
-
734
- const seenByPair = new Set<string>();
735
- for (const [srcId, tgtId] of allEdgeRows) {
736
- seenByPair.add(`${srcId}|${tgtId}`);
737
- }
738
-
739
- const { barrelOnlyFiles, rootDir } = ctx;
740
- const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
741
-
742
- for (const [relPath, symbols] of filesWithBindings) {
743
- if (barrelOnlyFiles.has(relPath)) continue;
744
- const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
745
- if (!fileNodeRow) continue;
746
-
747
- const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
748
- const typeMap: Map<string, TypeMapEntry | string> = symbols.typeMap || new Map();
749
- const ptsMap = buildPointsToMapForFile(symbols, importedNames);
750
- if (!ptsMap) continue;
751
-
752
- // Only process calls named 'this' (callee-not-receiver usage)
753
- for (const call of symbols.calls) {
754
- if (call.name !== 'this' || call.receiver) continue;
755
-
756
- const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
757
- if (caller.callerName == null) continue;
758
-
759
- const scopedKey = `${caller.callerName}::this`;
760
- if (!ptsMap.has(scopedKey)) continue;
761
-
762
- for (const alias of resolveViaPointsTo(scopedKey, ptsMap)) {
763
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
764
- lookup,
765
- { name: alias },
766
- relPath,
767
- importedNames,
768
- typeMap as Map<string, unknown>,
769
- );
770
- for (const t of aliasTargets) {
771
- const edgeKey = `${caller.id}|${t.id}`;
772
- if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
773
- const conf =
774
- computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
775
- if (conf > 0) {
776
- seenByPair.add(edgeKey);
777
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
778
- }
779
- }
780
- }
781
- }
782
- }
783
- }
784
- }
785
-
786
- /**
787
- * Phase 8.3f post-pass for the native call-edge path.
788
- *
789
- * The native Rust engine builds call edges without knowledge of
790
- * objectRestParamBindings, so `rest.method()` calls inside functions with
791
- * object-destructuring rest parameters are not resolved via the typeMap chain.
792
- * The Rust engine already resolves same-file and directly-imported callees
793
- * (via steps 1–2 of its resolution logic), so this post-pass only adds edges
794
- * that require the typeMap-chain path:
795
- * typeMap[restName] → argName → typeMap[argName.method] → target
796
- *
797
- * Mirrors the seeding in buildCallEdgesJS (Phase 8.3f) to ensure both engine
798
- * paths produce identical results for receiver-typed rest-param calls.
799
- */
800
- function buildObjectRestParamPostPass(
801
- ctx: PipelineContext,
802
- getNodeIdStmt: NodeIdStmt,
803
- allEdgeRows: EdgeRowTuple[],
804
- sharedLookup?: CallNodeLookup,
805
- ): void {
806
- const filesWithRestBindings = [...ctx.fileSymbols].filter(
807
- ([, symbols]) =>
808
- symbols.objectRestParamBindings &&
809
- symbols.objectRestParamBindings.length > 0 &&
810
- symbols.paramBindings &&
811
- symbols.paramBindings.length > 0,
812
- );
813
- if (filesWithRestBindings.length === 0) return;
814
-
815
- const seenByPair = new Set<string>();
816
- for (const [srcId, tgtId] of allEdgeRows) {
817
- seenByPair.add(`${srcId}|${tgtId}`);
818
- }
819
-
820
- const { barrelOnlyFiles, rootDir } = ctx;
821
- const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
822
-
823
- for (const [relPath, symbols] of filesWithRestBindings) {
824
- if (barrelOnlyFiles.has(relPath)) continue;
825
- const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
826
- if (!fileNodeRow) continue;
827
-
828
- const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
829
- const typeMap: Map<string, TypeMapEntry | string> = new Map(
830
- symbols.typeMap instanceof Map ? symbols.typeMap : [],
831
- );
832
-
833
- // Seed typeMap[callee::restName] = { type: argName } for each matching pair.
834
- // Mirrors the seeding in buildCallEdgesJS Phase 8.3f. Keys are scoped by
835
- // callee so two functions with the same rest-param name (e.g. `...rest`) in
836
- // the same file don't collide (#1358).
837
- // When only one callee uses a given rest name, also seed the unscoped key
838
- // as a null-callerName fallback so edges aren't silently dropped if
839
- // findCaller can't identify the enclosing function (#1358).
840
- const restNameCallees = new Map<string, Set<string>>();
841
- for (const orpb of symbols.objectRestParamBindings!) {
842
- if (!restNameCallees.has(orpb.restName)) restNameCallees.set(orpb.restName, new Set());
843
- restNameCallees.get(orpb.restName)!.add(orpb.callee);
844
- }
845
- const restNames = new Set<string>();
846
- for (const orpb of symbols.objectRestParamBindings!) {
847
- for (const pb of symbols.paramBindings!) {
848
- if (pb.callee === orpb.callee && pb.argIndex === orpb.argIndex) {
849
- const scopedKey = `${orpb.callee}::${orpb.restName}`;
850
- if (!typeMap.has(scopedKey)) {
851
- typeMap.set(scopedKey, { type: pb.argName, confidence: 0.65 });
852
- if (restNameCallees.get(orpb.restName)!.size === 1 && !typeMap.has(orpb.restName)) {
853
- typeMap.set(orpb.restName, { type: pb.argName, confidence: 0.65 });
854
- }
855
- }
856
- // restNames tracks every rest-parameter name found, regardless of whether the
857
- // scoped key was already in typeMap. This ensures the post-pass (below) processes
858
- // all calls whose receiver matches a known rest binding — not just those whose
859
- // typeMap entry was seeded in this iteration.
860
- restNames.add(orpb.restName);
861
- }
862
- }
863
- }
864
- if (restNames.size === 0) continue;
865
-
866
- for (const call of symbols.calls) {
867
- // Only process calls whose receiver is a known rest-binding name.
868
- if (!call.receiver || !restNames.has(call.receiver)) continue;
869
-
870
- const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
871
-
872
- // Resolve with the enriched typeMap. callerName is passed so
873
- // resolveByMethodOrGlobal can look up the scoped key callee::restName (#1358).
874
- // seenByPair deduplicates edges the native engine already emitted.
875
- const { targets, importedFrom } = resolveCallTargets(
876
- lookup,
877
- call,
878
- relPath,
879
- importedNames,
880
- typeMap as Map<string, unknown>,
881
- caller.callerName,
882
- );
883
- for (const t of targets) {
884
- const edgeKey = `${caller.id}|${t.id}`;
885
- if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
886
- const conf =
887
- computeConfidence(relPath, t.file, importedFrom ?? null) - PROPAGATION_HOP_PENALTY;
888
- if (conf > 0) {
889
- seenByPair.add(edgeKey);
890
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
891
- }
892
- }
893
- }
894
- }
895
- }
896
- }
897
-
898
589
  /**
899
590
  * Object.defineProperty accessor post-pass for the native call-edge path.
900
591
  *
@@ -975,7 +666,7 @@ function buildDefinePropertyPostPass(
975
666
  const conf = computeConfidence(relPath, t.file, null);
976
667
  if (conf > 0) {
977
668
  seenByPair.add(edgeKey);
978
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native']);
669
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native', null]);
979
670
  }
980
671
  }
981
672
  }
@@ -988,11 +679,11 @@ function buildDefinePropertyPostPass(
988
679
  *
989
680
  * The native Rust engine has no knowledge of the CHA context, so `this.method()`
990
681
  * calls and interface method dispatches are not expanded to their concrete
991
- * implementations. This JS post-pass runs after the native edges (and the pts
992
- * post-pass) and adds only the CHA-resolved edges that the native engine missed.
682
+ * implementations. This JS post-pass runs after the native edges and adds only
683
+ * the CHA-resolved edges that the native engine missed.
993
684
  *
994
- * Like buildParamFlowPtsPostPass, it seeds seenByPair from the current allEdgeRows
995
- * snapshot to avoid duplicating edges the native engine already produced.
685
+ * Seeds seenByPair from the current allEdgeRows snapshot to avoid duplicating
686
+ * edges the native engine already produced.
996
687
  */
997
688
  function buildChaPostPass(
998
689
  ctx: PipelineContext,
@@ -1026,6 +717,7 @@ function buildChaPostPass(
1026
717
 
1027
718
  const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
1028
719
  let chaTargets: ReadonlyArray<{ id: number; file: string }> = [];
720
+ let isTypedReceiverDispatch = false;
1029
721
 
1030
722
  if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
1031
723
  chaTargets = resolveThisDispatch(
@@ -1034,6 +726,7 @@ function buildChaPostPass(
1034
726
  call.receiver,
1035
727
  chaCtx,
1036
728
  lookup,
729
+ relPath,
1037
730
  );
1038
731
  } else {
1039
732
  const typeEntry = typeMap.get(call.receiver);
@@ -1044,16 +737,26 @@ function buildChaPostPass(
1044
737
  : null;
1045
738
  if (typeName) {
1046
739
  chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
740
+ isTypedReceiverDispatch = true;
1047
741
  }
1048
742
  }
1049
743
 
1050
744
  for (const t of chaTargets) {
1051
745
  const edgeKey = `${caller.id}|${t.id}`;
1052
746
  if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
1053
- const conf = computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
747
+ // Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
748
+ // — file proximity is not meaningful for virtual dispatch confidence.
749
+ // this/super dispatch keeps computeConfidence-based proximity scoring to
750
+ // match runPostNativeThisDispatch (native-orchestrator.ts).
751
+ const conf = isTypedReceiverDispatch
752
+ ? CHA_TYPED_DISPATCH_CONFIDENCE
753
+ : computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
1054
754
  if (conf > 0) {
1055
755
  seenByPair.add(edgeKey);
1056
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha']);
756
+ // Tag super-dispatch edges distinctly so runChaPostPass can exclude them
757
+ // from further CHA expansion (super calls are not virtual dispatch).
758
+ const technique = call.receiver === 'super' ? 'super-dispatch' : 'cha';
759
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, technique, null]);
1057
760
  }
1058
761
  }
1059
762
  }
@@ -1141,6 +844,16 @@ function buildCallEdgesJS(
1141
844
 
1142
845
  const seenCallEdges = new Set<string>();
1143
846
  const ptsMap = buildPointsToMapForFile(symbols, importedNames);
847
+ // Build the import-artifact name set: importedNames plus CJS require bindings.
848
+ // Used only by resolveReceiverEdge to distinguish local definitions from CJS
849
+ // import shadows — does NOT affect call-target resolution or DB edges (#1661).
850
+ const importArtifactNames = buildImportArtifactNames(
851
+ importedNames,
852
+ symbols,
853
+ ctx,
854
+ relPath,
855
+ rootDir,
856
+ );
1144
857
 
1145
858
  buildFileCallEdges(
1146
859
  relPath,
@@ -1153,6 +866,7 @@ function buildCallEdgesJS(
1153
866
  typeMap,
1154
867
  ptsMap,
1155
868
  chaCtx,
869
+ importArtifactNames,
1156
870
  );
1157
871
  buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows);
1158
872
  }
@@ -1197,6 +911,38 @@ function buildImportedNamesMap(
1197
911
  return importedNames;
1198
912
  }
1199
913
 
914
+ /**
915
+ * Build a map of all names that are import artifacts in this file — includes
916
+ * both ES module imports (already in importedNames) and CJS require destructuring
917
+ * bindings (`const { X } = require('./path')`). Used exclusively by resolveReceiverEdge
918
+ * to classify same-file function-kind nodes as import artifacts vs. local definitions.
919
+ * Does NOT affect call resolution or DB edge creation (#1661).
920
+ */
921
+ function buildImportArtifactNames(
922
+ importedNames: Map<string, string>,
923
+ symbols: ExtractorOutput,
924
+ ctx: PipelineContext,
925
+ relPath: string,
926
+ rootDir: string,
927
+ ): ReadonlyMap<string, string> {
928
+ if (!symbols.cjsRequireBindings?.length) return importedNames;
929
+ const combined = new Map(importedNames);
930
+ const traceBarrel = (resolvedPath: string, cleanName: string): string => {
931
+ if (!isBarrelFile(ctx, resolvedPath)) return resolvedPath;
932
+ const actual = resolveBarrelExportCached(ctx, resolvedPath, cleanName);
933
+ return actual ?? resolvedPath;
934
+ };
935
+ for (const binding of symbols.cjsRequireBindings) {
936
+ const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), binding.source);
937
+ for (const name of binding.names) {
938
+ if (!combined.has(name)) {
939
+ combined.set(name, traceBarrel(resolvedPath, name));
940
+ }
941
+ }
942
+ }
943
+ return combined;
944
+ }
945
+
1200
946
  function makeContextLookup(ctx: PipelineContext, getNodeIdStmt: NodeIdStmt): CallNodeLookup {
1201
947
  return {
1202
948
  byNameAndFile: (name, file) => ctx.nodesByNameAndFile.get(`${name}|${file}`) ?? [],
@@ -1293,6 +1039,509 @@ function buildDefinitionParamsMap(
1293
1039
  return map;
1294
1040
  }
1295
1041
 
1042
+ // ── Per-call resolution helpers ─────────────────────────────────────────
1043
+
1044
+ /**
1045
+ * Resolve targets for a single call site with all JS-path fallbacks applied.
1046
+ *
1047
+ * Runs in order:
1048
+ * 1. Primary resolution via `resolveCallTargets` (importedNames + typeMap).
1049
+ * 2. Same-class `this.method()` fallback (non-super receivers only).
1050
+ * 3. Same-class bare-call fallback for non-JS/TS class-scoped languages.
1051
+ * 4. Object.defineProperty accessor fallback (this-calls inside getter/setter).
1052
+ *
1053
+ * Returns the resolved targets array and the importedFrom hint for confidence scoring.
1054
+ */
1055
+ function resolveFallbackTargets(
1056
+ call: Call,
1057
+ caller: { id: number; callerName: string | null },
1058
+ relPath: string,
1059
+ importedNames: Map<string, string>,
1060
+ lookup: CallNodeLookup,
1061
+ typeMap: Map<string, TypeMapEntry | string>,
1062
+ definePropertyReceivers: Map<string, string> | undefined,
1063
+ ): {
1064
+ targets: ReadonlyArray<{ id: number; file: string; kind?: string }>;
1065
+ importedFrom: string | null | undefined;
1066
+ } {
1067
+ // RES-4: Kotlin member callable reference — `Greeter::greet` emits
1068
+ // { name: 'greet', receiver: 'Greeter', dynamicKind: 'reflection' }.
1069
+ // The receiver is the class qualifier (not a typeMap variable), so
1070
+ // resolveCallTargets would find a same-named top-level function via
1071
+ // byNameAndFile('greet', relPath) before the qualified form is tried.
1072
+ // Prefer `Greeter.greet` in the same file first; fall through to the
1073
+ // normal path only when no qualified match exists.
1074
+ let preQualifiedTargets: ReadonlyArray<{ id: number; file: string; kind?: string }> = [];
1075
+ if (
1076
+ call.dynamicKind === 'reflection' &&
1077
+ call.receiver &&
1078
+ !call.keyExpr &&
1079
+ !isModuleScopedLanguage(relPath)
1080
+ ) {
1081
+ preQualifiedTargets = lookup
1082
+ .byNameAndFile(`${call.receiver}.${call.name}`, relPath)
1083
+ .filter((n) => n.kind === 'method' || n.kind === 'function');
1084
+ }
1085
+
1086
+ let { targets, importedFrom } =
1087
+ preQualifiedTargets.length > 0
1088
+ ? {
1089
+ targets: preQualifiedTargets as Array<{ id: number; file: string; kind?: string }>,
1090
+ importedFrom: undefined as string | undefined,
1091
+ }
1092
+ : resolveCallTargets(
1093
+ lookup,
1094
+ call,
1095
+ relPath,
1096
+ importedNames,
1097
+ typeMap as Map<string, unknown>,
1098
+ caller.callerName,
1099
+ );
1100
+
1101
+ // Same-class `this.method()` fallback: when the call receiver is `this` and
1102
+ // resolveCallTargets found nothing, derive the enclosing class name from the
1103
+ // caller (e.g. `Logger.info` → class prefix `Logger`) and retry with the
1104
+ // qualified method name `Logger._write`. This mirrors what the native Rust
1105
+ // engine does implicitly via its class-scoped symbol table.
1106
+ // NOTE: restricted to `this` only — `super.method()` targets a parent class,
1107
+ // not the enclosing class, so qualifying with the child class name would
1108
+ // produce a false edge when the child also defines a same-named method.
1109
+ if (targets.length === 0 && call.receiver === 'this' && caller.callerName != null) {
1110
+ const lastDot = caller.callerName.lastIndexOf('.');
1111
+ if (lastDot > 0) {
1112
+ const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
1113
+ const className = caller.callerName.slice(prevDot + 1, lastDot);
1114
+ const qualified = lookup
1115
+ .byNameAndFile(`${className}.${call.name}`, relPath)
1116
+ .filter((n) => n.kind === 'method');
1117
+ if (qualified.length > 0) targets = qualified;
1118
+ }
1119
+ }
1120
+
1121
+ // Same-class bare-call fallback: when a no-receiver call can't be resolved
1122
+ // globally, try the caller's own class as a qualifier. Handles C# static
1123
+ // sibling calls: `IsValidEmail()` inside `Validators.ValidateUser` resolves
1124
+ // to `Validators.IsValidEmail`. Skipped for JS/TS where bare calls are
1125
+ // module-scoped, not class-scoped.
1126
+ if (
1127
+ targets.length === 0 &&
1128
+ !call.receiver &&
1129
+ caller.callerName != null &&
1130
+ !isModuleScopedLanguage(relPath)
1131
+ ) {
1132
+ const lastDot = caller.callerName.lastIndexOf('.');
1133
+ if (lastDot > 0) {
1134
+ const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
1135
+ const className = caller.callerName.slice(prevDot + 1, lastDot);
1136
+ const qualified = lookup
1137
+ .byNameAndFile(`${className}.${call.name}`, relPath)
1138
+ .filter((n) => n.kind === 'method');
1139
+ if (qualified.length > 0) targets = qualified;
1140
+ }
1141
+ }
1142
+
1143
+ // RES-3: reflection with literal method name — JVM getMethod("name") / invokeMethod("name").
1144
+ // Java/Scala/Groovy methods are stored as class-qualified names (e.g. Reflection.greet),
1145
+ // so lookup.byNameAndFile('greet', relPath) finds nothing. When dynamicKind='reflection'
1146
+ // and keyExpr is set (a string-literal method name was captured), try the qualified form:
1147
+ // 1. typeMap[receiver] → resolvedType → lookup `resolvedType.keyExpr` (type-annotated local)
1148
+ // 2. callerName class prefix → `CallerClass.keyExpr` (same-class sibling, e.g. Groovy obj)
1149
+ // Scoped to non-JS/TS files to avoid interfering with the JS reflection path.
1150
+ if (
1151
+ targets.length === 0 &&
1152
+ call.dynamicKind === 'reflection' &&
1153
+ call.keyExpr &&
1154
+ call.receiver &&
1155
+ !isModuleScopedLanguage(relPath)
1156
+ ) {
1157
+ const typeEntry = typeMap.get(call.receiver);
1158
+ const resolvedType = typeEntry
1159
+ ? typeof typeEntry === 'string'
1160
+ ? typeEntry
1161
+ : (typeEntry as { type?: string }).type
1162
+ : null;
1163
+ if (resolvedType) {
1164
+ const qualified = lookup
1165
+ .byNameAndFile(`${resolvedType}.${call.keyExpr}`, relPath)
1166
+ .filter((n) => n.kind === 'method' || n.kind === 'function');
1167
+ if (qualified.length > 0) targets = qualified;
1168
+ }
1169
+ if (targets.length === 0 && caller.callerName != null) {
1170
+ const lastDot = caller.callerName.lastIndexOf('.');
1171
+ if (lastDot > 0) {
1172
+ const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
1173
+ const callerClass = caller.callerName.slice(prevDot + 1, lastDot);
1174
+ const qualified = lookup
1175
+ .byNameAndFile(`${callerClass}.${call.keyExpr}`, relPath)
1176
+ .filter((n) => n.kind === 'method' || n.kind === 'function');
1177
+ if (qualified.length > 0) targets = qualified;
1178
+ }
1179
+ }
1180
+ }
1181
+
1182
+ // Object.defineProperty accessor fallback: when a function is registered as
1183
+ // a getter/setter via `Object.defineProperty(obj, "bar", { get: getter })`,
1184
+ // calls to `this.X()` inside `getter` resolve against `obj` (this === obj
1185
+ // when the accessor is invoked). If the same-class fallback above found
1186
+ // nothing, try treating `obj` as the receiver and look up `obj.X` in the
1187
+ // typeMap, or fall back to a same-file lookup of any definition named X
1188
+ // that belongs to the object literal or its type.
1189
+ if (
1190
+ targets.length === 0 &&
1191
+ call.receiver === 'this' &&
1192
+ caller.callerName != null &&
1193
+ definePropertyReceivers
1194
+ ) {
1195
+ const receiverVarName = definePropertyReceivers.get(caller.callerName);
1196
+ if (receiverVarName) {
1197
+ const typeEntry = typeMap.get(receiverVarName);
1198
+ const typeName = typeEntry
1199
+ ? typeof typeEntry === 'string'
1200
+ ? typeEntry
1201
+ : (typeEntry as { type?: string }).type
1202
+ : null;
1203
+ if (typeName) {
1204
+ const qualified = lookup.byNameAndFile(`${typeName}.${call.name}`, relPath);
1205
+ if (qualified.length > 0) targets = [...qualified];
1206
+ }
1207
+ // If still no targets, search for any definition named `call.name` in
1208
+ // the same file — handles plain object literals where the method isn't
1209
+ // qualified (e.g. `const obj = { baz() {} }` defines `baz` directly).
1210
+ // Note: this is intentionally broad — it matches any same-file definition
1211
+ // with the called name, not just members of the receiver object. This is
1212
+ // the same behaviour used by the native post-pass path (buildDefinePropertyPostPass).
1213
+ if (targets.length === 0) {
1214
+ const sameFile = lookup.byNameAndFile(call.name, relPath);
1215
+ if (sameFile.length > 0) targets = [...sameFile];
1216
+ }
1217
+ }
1218
+ }
1219
+
1220
+ return { targets, importedFrom };
1221
+ }
1222
+
1223
+ /**
1224
+ * Emit direct-call edges for the resolved targets of a single call site.
1225
+ *
1226
+ * Sorts targets by confidence descending first, then for each target:
1227
+ * - Skips self-edges and already-seen edges.
1228
+ * - If a pts edge already exists for this pair, upgrades it in-place to
1229
+ * direct-call confidence and promotes to seenCallEdges.
1230
+ * - If a dyn=0 edge already exists and the incoming call has an explicit
1231
+ * dynamicKind (e.g. 'reflection' for bare decorators), upgrades the
1232
+ * existing row to dyn=1 in-place so the semantic classification wins.
1233
+ * - Otherwise records a new `calls` edge with `ts-native` technique.
1234
+ */
1235
+ function emitDirectCallEdgesForCall(
1236
+ caller: { id: number },
1237
+ targets: ReadonlyArray<{ id: number; file: string }>,
1238
+ importedFrom: string | null | undefined,
1239
+ isDynamic: number,
1240
+ hasDynamicKind: boolean,
1241
+ relPath: string,
1242
+ seenCallEdges: Set<string>,
1243
+ ptsEdgeRows: Map<string, number>,
1244
+ allEdgeRows: EdgeRowTuple[],
1245
+ dynZeroEdgeRows?: Map<string, number>,
1246
+ ): void {
1247
+ // Sort targets by confidence descending before emitting edges.
1248
+ // For multi-target calls with duplicate (source_id, target_id) pairs the
1249
+ // stored confidence depends on which duplicate is processed last — sorting
1250
+ // here guarantees the highest-confidence target wins on dedup, matching the
1251
+ // native engine's sort_targets_by_confidence call in build_edges.rs.
1252
+ const sorted =
1253
+ targets.length > 1
1254
+ ? [...targets].sort(
1255
+ (a, b) =>
1256
+ computeConfidence(relPath, b.file, importedFrom ?? null) -
1257
+ computeConfidence(relPath, a.file, importedFrom ?? null),
1258
+ )
1259
+ : targets;
1260
+
1261
+ for (const t of sorted) {
1262
+ const edgeKey = `${caller.id}|${t.id}`;
1263
+ if (t.id === caller.id) continue;
1264
+ const confidence = computeConfidence(relPath, t.file, importedFrom ?? null);
1265
+ if (seenCallEdges.has(edgeKey)) {
1266
+ // Edge already emitted. If the incoming call carries an explicit semantic
1267
+ // dynamic classification (dynamicKind set — e.g. 'reflection' for bare
1268
+ // decorators) and the existing edge was recorded with dyn=0, upgrade it
1269
+ // in-place so the more specific classification wins.
1270
+ // Generic dynamic=true without dynamicKind (alias/callback calls) does
1271
+ // NOT override dyn=0 to avoid false positives on f.call/f.bind patterns.
1272
+ if (isDynamic === 1 && hasDynamicKind && dynZeroEdgeRows) {
1273
+ const dynZeroIdx = dynZeroEdgeRows.get(edgeKey);
1274
+ if (dynZeroIdx !== undefined) {
1275
+ const row = allEdgeRows[dynZeroIdx];
1276
+ if (row) row[4] = 1;
1277
+ dynZeroEdgeRows.delete(edgeKey);
1278
+ }
1279
+ }
1280
+ continue;
1281
+ }
1282
+ const ptsIdx = ptsEdgeRows.get(edgeKey);
1283
+ if (ptsIdx !== undefined) {
1284
+ // A pts-resolved edge already exists for this caller→target pair with a
1285
+ // penalised confidence. Upgrade it to the direct-call confidence in-place,
1286
+ // then promote to seenCallEdges so no further processing is needed.
1287
+ const ptsRow = allEdgeRows[ptsIdx];
1288
+ if (ptsRow) {
1289
+ ptsRow[3] = confidence;
1290
+ ptsRow[4] = isDynamic; // upgrade is_dynamic: direct call overrides the pts-alias dynamic flag
1291
+ ptsRow[5] = 'ts-native'; // promoted from pts to direct-call resolution
1292
+ }
1293
+ ptsEdgeRows.delete(edgeKey);
1294
+ seenCallEdges.add(edgeKey);
1295
+ } else {
1296
+ seenCallEdges.add(edgeKey);
1297
+ const newIdx = allEdgeRows.length;
1298
+ allEdgeRows.push([caller.id, t.id, 'calls', confidence, isDynamic, 'ts-native', null]);
1299
+ // Track dyn=0 edges so a later dyn=1+dynamicKind call for the same pair
1300
+ // can upgrade them (e.g. bare decorator after call-expression decorator).
1301
+ if (isDynamic === 0 && dynZeroEdgeRows) {
1302
+ dynZeroEdgeRows.set(edgeKey, newIdx);
1303
+ }
1304
+ }
1305
+ }
1306
+ }
1307
+
1308
+ /**
1309
+ * Phase 8.3 / 8.3c / bind: emit pts-resolved edges for unresolved no-receiver calls.
1310
+ *
1311
+ * Fires for three cases:
1312
+ * (a) dynamic=true: alias calls emitted by extractCallbackReferenceCalls.
1313
+ * Looks up `call.name` directly (alias entries are flat-keyed).
1314
+ * (b) non-dynamic: parameter variable calls (fn() where fn is a param).
1315
+ * Looks up the scoped key `callerName::call.name` to avoid spurious
1316
+ * edges from same-named parameters across different functions.
1317
+ * (c) non-dynamic: module-level alias bindings — `f = fn.bind(ctx)` or
1318
+ * `const f = handler` — where pts('f') was seeded by fnRefBindings.
1319
+ * Checked against fnRefBindingLhs so case (c) only fires for genuine
1320
+ * bind/alias entries and never for self-seeded local definitions.
1321
+ *
1322
+ * Pts edges are added to ptsEdgeRows (not seenCallEdges) so that a later
1323
+ * direct call to the same target can upgrade confidence rather than being
1324
+ * silently dropped by the dedup guard.
1325
+ */
1326
+ function emitPtsNoReceiverEdges(
1327
+ call: Call,
1328
+ caller: { id: number; callerName: string | null },
1329
+ isDynamic: number,
1330
+ relPath: string,
1331
+ importedNames: Map<string, string>,
1332
+ lookup: CallNodeLookup,
1333
+ typeMap: Map<string, TypeMapEntry | string>,
1334
+ ptsMap: PointsToMap,
1335
+ fnRefBindingLhs: Set<string>,
1336
+ seenCallEdges: Set<string>,
1337
+ ptsEdgeRows: Map<string, number>,
1338
+ allEdgeRows: EdgeRowTuple[],
1339
+ ): void {
1340
+ const scopedPtsKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
1341
+ // Module-level calls (callerName === null) use the '<module>' sentinel emitted by
1342
+ // extractSpreadForOfWalk for top-level for-of loops. Look it up as a fallback so
1343
+ // that `for (const f of arr) { f(); }` at module scope resolves correctly.
1344
+ const modulePtsKey =
1345
+ caller.callerName === null && ptsMap.has(`<module>::${call.name}`)
1346
+ ? `<module>::${call.name}`
1347
+ : null;
1348
+ const flatPtsKey =
1349
+ !call.dynamic && fnRefBindingLhs.has(call.name) && ptsMap.has(call.name) ? call.name : null;
1350
+
1351
+ if (
1352
+ !(
1353
+ call.dynamic ||
1354
+ (scopedPtsKey != null && ptsMap.has(scopedPtsKey)) ||
1355
+ modulePtsKey != null ||
1356
+ flatPtsKey != null
1357
+ )
1358
+ )
1359
+ return;
1360
+
1361
+ const ptsLookupName = call.dynamic
1362
+ ? call.name
1363
+ : scopedPtsKey != null && ptsMap.has(scopedPtsKey)
1364
+ ? scopedPtsKey
1365
+ : modulePtsKey != null
1366
+ ? modulePtsKey
1367
+ : // flatPtsKey != null is guaranteed: if neither call.dynamic nor scopedPtsKey
1368
+ // nor modulePtsKey matched, flatPtsKey must be non-null.
1369
+ flatPtsKey!;
1370
+
1371
+ for (const alias of resolveViaPointsTo(ptsLookupName, ptsMap)) {
1372
+ // Resolve the concrete alias target. Only `name` is needed here — receiver
1373
+ // and line are not relevant for alias resolution (we are looking up the
1374
+ // aliased function by name, not dispatching a method call).
1375
+ const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
1376
+ lookup,
1377
+ { name: alias },
1378
+ relPath,
1379
+ importedNames,
1380
+ typeMap as Map<string, unknown>,
1381
+ );
1382
+ const sortedAliasTargets =
1383
+ aliasTargets.length > 1
1384
+ ? [...aliasTargets].sort(
1385
+ (a, b) =>
1386
+ computeConfidence(relPath, b.file, aliasFrom ?? null) -
1387
+ computeConfidence(relPath, a.file, aliasFrom ?? null),
1388
+ )
1389
+ : aliasTargets;
1390
+ for (const t of sortedAliasTargets) {
1391
+ const edgeKey = `${caller.id}|${t.id}`;
1392
+ if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1393
+ const conf =
1394
+ computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
1395
+ if (conf > 0) {
1396
+ ptsEdgeRows.set(edgeKey, allEdgeRows.length);
1397
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to', null]);
1398
+ }
1399
+ }
1400
+ }
1401
+ }
1402
+ }
1403
+
1404
+ /**
1405
+ * Phase 8.3f: emit pts-resolved edges for unresolved receiver calls via
1406
+ * object-rest param bindings.
1407
+ *
1408
+ * Fires when `rest.prop()` is encountered and `rest` was seeded as
1409
+ * `pts["rest.prop"]` by the object-rest dispatch chain
1410
+ * (ObjectRestParamBinding + paramBinding + ObjectPropBinding).
1411
+ */
1412
+ function emitPtsReceiverEdges(
1413
+ call: Call,
1414
+ caller: { id: number; callerName: string | null },
1415
+ isDynamic: number,
1416
+ relPath: string,
1417
+ importedNames: Map<string, string>,
1418
+ lookup: CallNodeLookup,
1419
+ typeMap: Map<string, TypeMapEntry | string>,
1420
+ ptsMap: PointsToMap,
1421
+ seenCallEdges: Set<string>,
1422
+ ptsEdgeRows: Map<string, number>,
1423
+ allEdgeRows: EdgeRowTuple[],
1424
+ ): void {
1425
+ const receiverKey = `${call.receiver}.${call.name}`;
1426
+ if (!ptsMap.has(receiverKey)) return;
1427
+
1428
+ for (const alias of resolveViaPointsTo(receiverKey, ptsMap)) {
1429
+ const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
1430
+ lookup,
1431
+ { name: alias },
1432
+ relPath,
1433
+ importedNames,
1434
+ typeMap as Map<string, unknown>,
1435
+ );
1436
+ const sortedAliasTargets =
1437
+ aliasTargets.length > 1
1438
+ ? [...aliasTargets].sort(
1439
+ (a, b) =>
1440
+ computeConfidence(relPath, b.file, aliasFrom ?? null) -
1441
+ computeConfidence(relPath, a.file, aliasFrom ?? null),
1442
+ )
1443
+ : aliasTargets;
1444
+ for (const t of sortedAliasTargets) {
1445
+ const edgeKey = `${caller.id}|${t.id}`;
1446
+ if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1447
+ const conf =
1448
+ computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
1449
+ if (conf > 0) {
1450
+ ptsEdgeRows.set(edgeKey, allEdgeRows.length);
1451
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to', null]);
1452
+ }
1453
+ }
1454
+ }
1455
+ }
1456
+ }
1457
+
1458
+ /**
1459
+ * Phase 8.5: emit CHA + RTA dispatch edges for a single call site.
1460
+ *
1461
+ * For `this`/`self`/`super` calls: resolve through the class hierarchy.
1462
+ * For typed receiver calls: expand to all instantiated concrete implementations.
1463
+ */
1464
+ function emitChaCallEdgesForCall(
1465
+ call: Call,
1466
+ caller: { id: number; callerName: string | null },
1467
+ relPath: string,
1468
+ typeMap: Map<string, TypeMapEntry | string>,
1469
+ lookup: CallNodeLookup,
1470
+ chaCtx: ChaContext,
1471
+ seenCallEdges: Set<string>,
1472
+ ptsEdgeRows: Map<string, number>,
1473
+ allEdgeRows: EdgeRowTuple[],
1474
+ ): void {
1475
+ let chaTargets: ReadonlyArray<{ id: number; file: string }> = [];
1476
+ let isTypedReceiverDispatch = false;
1477
+
1478
+ if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
1479
+ chaTargets = resolveThisDispatch(
1480
+ call.name,
1481
+ caller.callerName,
1482
+ call.receiver,
1483
+ chaCtx,
1484
+ lookup,
1485
+ relPath,
1486
+ );
1487
+ } else if (!BUILTIN_RECEIVERS.has(call.receiver!)) {
1488
+ const typeEntry = typeMap.get(call.receiver!);
1489
+ const typeName = typeEntry
1490
+ ? typeof typeEntry === 'string'
1491
+ ? typeEntry
1492
+ : (typeEntry as { type?: string }).type
1493
+ : null;
1494
+ if (typeName) {
1495
+ chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
1496
+ isTypedReceiverDispatch = true;
1497
+ }
1498
+ }
1499
+
1500
+ for (const t of chaTargets) {
1501
+ const edgeKey = `${caller.id}|${t.id}`;
1502
+ if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1503
+ // Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
1504
+ // — file proximity is not meaningful for virtual dispatch confidence.
1505
+ // this/super dispatch keeps computeConfidence-based proximity scoring to
1506
+ // match runPostNativeThisDispatch (native-orchestrator.ts).
1507
+ const conf = isTypedReceiverDispatch
1508
+ ? CHA_TYPED_DISPATCH_CONFIDENCE
1509
+ : computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
1510
+ if (conf > 0) {
1511
+ seenCallEdges.add(edgeKey);
1512
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha', null]);
1513
+ }
1514
+ }
1515
+ }
1516
+ }
1517
+
1518
+ /**
1519
+ * Dynamic kinds that cannot be resolved statically — emit a sink edge to the
1520
+ * file node instead of silently dropping the call site. confidence=0.0 keeps
1521
+ * these below DEFAULT_MIN_CONFIDENCE so they never appear in normal query results.
1522
+ * Includes reflection so that Reflect.apply/getMethod/callable-ref calls whose
1523
+ * target is not found in the codebase still produce a visible sink edge.
1524
+ */
1525
+ const FLAG_ONLY_KINDS: ReadonlySet<DynamicKind> = new Set([
1526
+ 'eval',
1527
+ 'computed-key',
1528
+ 'reflection',
1529
+ 'unresolved-dynamic',
1530
+ ]);
1531
+
1532
+ /**
1533
+ * Build call edges for all calls in a single file (WASM/JS engine path).
1534
+ *
1535
+ * Iterates over `symbols.calls` and dispatches each call through the full
1536
+ * JS resolution cascade:
1537
+ * 1. `resolveFallbackTargets` — primary + class-fallback + defineProperty fallback
1538
+ * 2. `emitDirectCallEdgesForCall` — emit direct-call edges (upgrading any pts pair)
1539
+ * 3. `emitPtsNoReceiverEdges` — Phase 8.3/8.3c pts fallback for no-receiver calls
1540
+ * 4. `emitPtsReceiverEdges` — Phase 8.3f pts fallback for rest-param receiver calls
1541
+ * 5. Inline `resolveReceiverEdge` — emit `receiver` edge for external receivers
1542
+ * 6. `emitChaCallEdgesForCall` — Phase 8.5 CHA + RTA dispatch expansion
1543
+ * 7. Sink edge for flag-only dynamic kinds (eval, computed-key, reflection, unresolved-dynamic)
1544
+ */
1296
1545
  function buildFileCallEdges(
1297
1546
  relPath: string,
1298
1547
  symbols: ExtractorOutput,
@@ -1304,6 +1553,7 @@ function buildFileCallEdges(
1304
1553
  typeMap: Map<string, TypeMapEntry | string>,
1305
1554
  ptsMap?: PointsToMap | null,
1306
1555
  chaCtx?: ChaContext,
1556
+ importArtifactNames?: ReadonlyMap<string, string>,
1307
1557
  ): void {
1308
1558
  // Tracks edges that were inserted by the pts fallback (edgeKey → allEdgeRows index).
1309
1559
  // Kept separate from seenCallEdges so that a subsequent direct-call edge for the same
@@ -1312,6 +1562,12 @@ function buildFileCallEdges(
1312
1562
  // no longer tracked here.
1313
1563
  const ptsEdgeRows = new Map<string, number>();
1314
1564
 
1565
+ // Tracks direct-call edges emitted with dyn=0 (edgeKey → allEdgeRows index).
1566
+ // When a later call to the same target has dyn=1 (e.g. a bare decorator `@Log`
1567
+ // processed after the call-expression `@Log()` in the query path), the existing
1568
+ // dyn=0 row is upgraded in-place so the more specific dynamic classification wins.
1569
+ const dynZeroEdgeRows = new Map<string, number>();
1570
+
1315
1571
  // Pre-compute the set of names that appear as lhs in fnRefBindings so that
1316
1572
  // case (c) of the pts gate below only fires for names that are genuine
1317
1573
  // bind/alias entries, not for every locally-defined function or import that
@@ -1323,205 +1579,51 @@ function buildFileCallEdges(
1323
1579
 
1324
1580
  const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
1325
1581
  const isDynamic: number = call.dynamic ? 1 : 0;
1326
- let { targets, importedFrom } = resolveCallTargets(
1327
- lookup,
1582
+
1583
+ // Step 1: Resolve targets with all JS-path fallbacks.
1584
+ const { targets, importedFrom } = resolveFallbackTargets(
1328
1585
  call,
1586
+ caller,
1329
1587
  relPath,
1330
1588
  importedNames,
1331
- typeMap as Map<string, unknown>,
1332
- caller.callerName,
1589
+ lookup,
1590
+ typeMap,
1591
+ symbols.definePropertyReceivers,
1333
1592
  );
1334
1593
 
1335
- // Same-class `this.method()` fallback: when the call receiver is `this` and
1336
- // resolveCallTargets found nothing, derive the enclosing class name from the
1337
- // caller (e.g. `Logger.info` → class prefix `Logger`) and retry with the
1338
- // qualified method name `Logger._write`. This mirrors what the native Rust
1339
- // engine does implicitly via its class-scoped symbol table.
1340
- // NOTE: restricted to `this` only — `super.method()` targets a parent class,
1341
- // not the enclosing class, so qualifying with the child class name would
1342
- // produce a false edge when the child also defines a same-named method.
1343
- if (targets.length === 0 && call.receiver === 'this' && caller.callerName != null) {
1344
- const lastDot = caller.callerName.lastIndexOf('.');
1345
- if (lastDot > 0) {
1346
- const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
1347
- const className = caller.callerName.slice(prevDot + 1, lastDot);
1348
- const qualifiedName = `${className}.${call.name}`;
1349
- const qualified = lookup
1350
- .byNameAndFile(qualifiedName, relPath)
1351
- .filter((n) => n.kind === 'method');
1352
- if (qualified.length > 0) {
1353
- targets = qualified;
1354
- }
1355
- }
1356
- }
1357
-
1358
- // Same-class bare-call fallback: when a no-receiver call can't be resolved
1359
- // globally, try the caller's own class as a qualifier. Handles C# static
1360
- // sibling calls: `IsValidEmail()` inside `Validators.ValidateUser` resolves
1361
- // to `Validators.IsValidEmail`. Skipped for JS/TS where bare calls are
1362
- // module-scoped, not class-scoped.
1363
- if (
1364
- targets.length === 0 &&
1365
- !call.receiver &&
1366
- caller.callerName != null &&
1367
- !isModuleScopedLanguage(relPath)
1368
- ) {
1369
- const lastDot = caller.callerName.lastIndexOf('.');
1370
- if (lastDot > 0) {
1371
- const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
1372
- const className = caller.callerName.slice(prevDot + 1, lastDot);
1373
- const qualifiedName = `${className}.${call.name}`;
1374
- const qualified = lookup
1375
- .byNameAndFile(qualifiedName, relPath)
1376
- .filter((n) => n.kind === 'method');
1377
- if (qualified.length > 0) {
1378
- targets = qualified;
1379
- }
1380
- }
1381
- }
1382
-
1383
- // Object.defineProperty accessor fallback: when a function is registered as
1384
- // a getter/setter via `Object.defineProperty(obj, "bar", { get: getter })`,
1385
- // calls to `this.X()` inside `getter` resolve against `obj` (this === obj
1386
- // when the accessor is invoked). If the same-class fallback above found
1387
- // nothing, try treating `obj` as the receiver and look up `obj.X` in the
1388
- // typeMap, or fall back to a same-file lookup of any definition named X
1389
- // that belongs to the object literal or its type.
1390
- if (
1391
- targets.length === 0 &&
1392
- call.receiver === 'this' &&
1393
- caller.callerName != null &&
1394
- symbols.definePropertyReceivers
1395
- ) {
1396
- const receiverVarName = symbols.definePropertyReceivers.get(caller.callerName);
1397
- if (receiverVarName) {
1398
- // Try typeMap lookup for receiver.methodName
1399
- const typeEntry = typeMap.get(receiverVarName);
1400
- const typeName = typeEntry
1401
- ? typeof typeEntry === 'string'
1402
- ? typeEntry
1403
- : (typeEntry as { type?: string }).type
1404
- : null;
1405
- if (typeName) {
1406
- const qualifiedName = `${typeName}.${call.name}`;
1407
- const qualified = lookup.byNameAndFile(qualifiedName, relPath);
1408
- if (qualified.length > 0) {
1409
- targets = [...qualified];
1410
- }
1411
- }
1412
- // If still no targets, search for any definition named `call.name` in
1413
- // the same file — handles plain object literals where the method isn't
1414
- // qualified (e.g. `const obj = { baz() {} }` defines `baz` directly).
1415
- // Note: this is intentionally broad — it matches any same-file definition
1416
- // with the called name, not just members of the receiver object. This is
1417
- // the same behaviour used by the native post-pass path (buildDefinePropertyPostPass).
1418
- if (targets.length === 0) {
1419
- const sameFile = lookup.byNameAndFile(call.name, relPath);
1420
- if (sameFile.length > 0) {
1421
- targets = [...sameFile];
1422
- }
1423
- }
1424
- }
1425
- }
1426
-
1427
- for (const t of targets) {
1428
- const edgeKey = `${caller.id}|${t.id}`;
1429
- if (t.id !== caller.id) {
1430
- const confidence = computeConfidence(relPath, t.file, importedFrom ?? null);
1431
- if (seenCallEdges.has(edgeKey)) continue;
1432
- const ptsIdx = ptsEdgeRows.get(edgeKey);
1433
- if (ptsIdx !== undefined) {
1434
- // A pts-resolved edge already exists for this caller→target pair with a
1435
- // penalised confidence. Upgrade it to the direct-call confidence in-place,
1436
- // then promote to seenCallEdges so no further processing is needed.
1437
- const ptsRow = allEdgeRows[ptsIdx];
1438
- if (ptsRow) {
1439
- ptsRow[3] = confidence;
1440
- ptsRow[4] = isDynamic; // upgrade is_dynamic: direct call overrides the pts-alias dynamic flag
1441
- ptsRow[5] = 'ts-native'; // promoted from pts to direct-call resolution
1442
- }
1443
- ptsEdgeRows.delete(edgeKey);
1444
- seenCallEdges.add(edgeKey);
1445
- } else {
1446
- seenCallEdges.add(edgeKey);
1447
- allEdgeRows.push([caller.id, t.id, 'calls', confidence, isDynamic, 'ts-native']);
1448
- }
1449
- }
1450
- }
1594
+ // Step 2: Emit direct-call edges (upgrades any pending pts edge in-place).
1595
+ emitDirectCallEdgesForCall(
1596
+ caller,
1597
+ targets,
1598
+ importedFrom,
1599
+ isDynamic,
1600
+ !!call.dynamicKind,
1601
+ relPath,
1602
+ seenCallEdges,
1603
+ ptsEdgeRows,
1604
+ allEdgeRows,
1605
+ dynZeroEdgeRows,
1606
+ );
1451
1607
 
1452
- // Phase 8.3 / 8.3c / bind: points-to fallback for unresolved calls.
1453
- // Fires for three cases:
1454
- // (a) dynamic=true: alias calls emitted by extractCallbackReferenceCalls.
1455
- // Looks up `call.name` directly (alias entries are flat-keyed).
1456
- // (b) non-dynamic: parameter variable calls (fn() where fn is a param).
1457
- // Looks up the scoped key `callerName::call.name` to avoid spurious
1458
- // edges from same-named parameters across different functions.
1459
- // (c) non-dynamic: module-level alias bindings — `f = fn.bind(ctx)` or
1460
- // `const f = handler` — where pts('f') was seeded by fnRefBindings.
1461
- // Checked against fnRefBindingLhs (the pre-computed set of lhs names from
1462
- // fnRefBindings) rather than the full ptsMap, so case (c) only fires for
1463
- // genuine bind/alias entries and never for self-seeded local definitions.
1464
- // Confidence is penalised by one hop to reflect the extra indirection.
1465
- //
1466
- // Note: pts edges are added to ptsEdgeRows (not seenCallEdges) so that a later
1467
- // direct call to the same target in the same function body can upgrade confidence
1468
- // rather than being silently dropped by the dedup guard.
1469
- const scopedPtsKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
1470
- // Module-level calls (callerName === null) use the '<module>' sentinel emitted by
1471
- // extractSpreadForOfWalk for top-level for-of loops. Look it up as a fallback so
1472
- // that `for (const f of arr) { f(); }` at module scope resolves correctly.
1473
- const modulePtsKey =
1474
- caller.callerName === null && ptsMap?.has(`<module>::${call.name}`)
1475
- ? `<module>::${call.name}`
1476
- : null;
1477
- const flatPtsKey =
1478
- !call.dynamic && fnRefBindingLhs.has(call.name) && ptsMap?.has(call.name) ? call.name : null;
1479
- if (
1480
- targets.length === 0 &&
1481
- !call.receiver &&
1482
- ptsMap &&
1483
- (call.dynamic ||
1484
- (scopedPtsKey != null && ptsMap.has(scopedPtsKey)) ||
1485
- modulePtsKey != null ||
1486
- flatPtsKey != null)
1487
- ) {
1488
- const ptsLookupName = call.dynamic
1489
- ? call.name
1490
- : scopedPtsKey != null && ptsMap.has(scopedPtsKey)
1491
- ? scopedPtsKey
1492
- : modulePtsKey != null
1493
- ? modulePtsKey
1494
- : // flatPtsKey != null is guaranteed by the outer if condition: if neither
1495
- // call.dynamic nor scopedPtsKey nor modulePtsKey matched, flatPtsKey must be non-null.
1496
- flatPtsKey!;
1497
- for (const alias of resolveViaPointsTo(ptsLookupName, ptsMap)) {
1498
- // Resolve the concrete alias target. Only `name` is needed here — receiver
1499
- // and line are not relevant for alias resolution (we are looking up the
1500
- // aliased function by name, not dispatching a method call).
1501
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
1502
- lookup,
1503
- { name: alias },
1504
- relPath,
1505
- importedNames,
1506
- typeMap as Map<string, unknown>,
1507
- );
1508
- for (const t of aliasTargets) {
1509
- const edgeKey = `${caller.id}|${t.id}`;
1510
- if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1511
- const conf =
1512
- computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
1513
- if (conf > 0) {
1514
- ptsEdgeRows.set(edgeKey, allEdgeRows.length);
1515
- allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
1516
- }
1517
- }
1518
- }
1519
- }
1608
+ // Step 3: Phase 8.3/8.3c pts fallback for unresolved no-receiver calls.
1609
+ if (targets.length === 0 && !call.receiver && ptsMap) {
1610
+ emitPtsNoReceiverEdges(
1611
+ call,
1612
+ caller,
1613
+ isDynamic,
1614
+ relPath,
1615
+ importedNames,
1616
+ lookup,
1617
+ typeMap,
1618
+ ptsMap,
1619
+ fnRefBindingLhs,
1620
+ seenCallEdges,
1621
+ ptsEdgeRows,
1622
+ allEdgeRows,
1623
+ );
1520
1624
  }
1521
1625
 
1522
- // Phase 8.3f: pts fallback for receiver calls via object-rest param bindings.
1523
- // Fires when `rest.prop()` is encountered and `rest` was seeded as `pts["rest.prop"]`
1524
- // by the object-rest dispatch chain (ObjectRestParamBinding + paramBinding + ObjectPropBinding).
1626
+ // Step 4: Phase 8.3f pts fallback for unresolved receiver calls (rest params).
1525
1627
  if (
1526
1628
  targets.length === 0 &&
1527
1629
  call.receiver &&
@@ -1531,31 +1633,22 @@ function buildFileCallEdges(
1531
1633
  call.receiver !== 'super' &&
1532
1634
  ptsMap
1533
1635
  ) {
1534
- const receiverKey = `${call.receiver}.${call.name}`;
1535
- if (ptsMap.has(receiverKey)) {
1536
- for (const alias of resolveViaPointsTo(receiverKey, ptsMap)) {
1537
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
1538
- lookup,
1539
- { name: alias },
1540
- relPath,
1541
- importedNames,
1542
- typeMap as Map<string, unknown>,
1543
- );
1544
- for (const t of aliasTargets) {
1545
- const edgeKey = `${caller.id}|${t.id}`;
1546
- if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1547
- const conf =
1548
- computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
1549
- if (conf > 0) {
1550
- ptsEdgeRows.set(edgeKey, allEdgeRows.length);
1551
- allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
1552
- }
1553
- }
1554
- }
1555
- }
1556
- }
1636
+ emitPtsReceiverEdges(
1637
+ call,
1638
+ caller,
1639
+ isDynamic,
1640
+ relPath,
1641
+ importedNames,
1642
+ lookup,
1643
+ typeMap,
1644
+ ptsMap,
1645
+ seenCallEdges,
1646
+ ptsEdgeRows,
1647
+ allEdgeRows,
1648
+ );
1557
1649
  }
1558
1650
 
1651
+ // Step 5: Emit receiver edge for external (non-this/self/super) receivers.
1559
1652
  if (
1560
1653
  call.receiver &&
1561
1654
  !BUILTIN_RECEIVERS.has(call.receiver) &&
@@ -1570,46 +1663,45 @@ function buildFileCallEdges(
1570
1663
  relPath,
1571
1664
  typeMap as Map<string, unknown>,
1572
1665
  seenCallEdges,
1666
+ importArtifactNames ?? importedNames,
1573
1667
  );
1574
1668
  if (recv) {
1575
- allEdgeRows.push([recv.callerId, recv.receiverId, 'receiver', recv.confidence, 0, null]);
1669
+ allEdgeRows.push([
1670
+ recv.callerId,
1671
+ recv.receiverId,
1672
+ 'receiver',
1673
+ recv.confidence,
1674
+ 0,
1675
+ null,
1676
+ null,
1677
+ ]);
1576
1678
  }
1577
1679
  }
1578
1680
 
1579
- // Phase 8.5: CHA + RTA dispatch expansion.
1580
- // For `this`/`self`/`super` calls: resolve through the class hierarchy instead
1581
- // of relying solely on global name matching.
1582
- // For typed receiver calls: expand to all instantiated concrete implementations.
1681
+ // Step 6: Phase 8.5 CHA + RTA dispatch expansion.
1583
1682
  if (chaCtx && call.receiver) {
1584
- let chaTargets: ReadonlyArray<{ id: number; file: string }> = [];
1585
- if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
1586
- chaTargets = resolveThisDispatch(
1587
- call.name,
1588
- caller.callerName,
1589
- call.receiver,
1590
- chaCtx,
1591
- lookup,
1592
- );
1593
- } else if (!BUILTIN_RECEIVERS.has(call.receiver)) {
1594
- const typeEntry = typeMap.get(call.receiver);
1595
- const typeName = typeEntry
1596
- ? typeof typeEntry === 'string'
1597
- ? typeEntry
1598
- : (typeEntry as { type?: string }).type
1599
- : null;
1600
- if (typeName) {
1601
- chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
1602
- }
1603
- }
1604
- for (const t of chaTargets) {
1605
- const edgeKey = `${caller.id}|${t.id}`;
1606
- if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1607
- const conf = computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
1608
- if (conf > 0) {
1609
- seenCallEdges.add(edgeKey);
1610
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha']);
1611
- }
1612
- }
1683
+ emitChaCallEdgesForCall(
1684
+ call,
1685
+ caller,
1686
+ relPath,
1687
+ typeMap,
1688
+ lookup,
1689
+ chaCtx,
1690
+ seenCallEdges,
1691
+ ptsEdgeRows,
1692
+ allEdgeRows,
1693
+ );
1694
+ }
1695
+
1696
+ // Step 7: Flag-only dynamic kinds with no resolved target → sink edge to the
1697
+ // file node. confidence=0.0 keeps it below DEFAULT_MIN_CONFIDENCE so it never
1698
+ // appears in normal query results, but is queryable via `codegraph roles --dynamic`.
1699
+ if (targets.length === 0 && call.dynamicKind && FLAG_ONLY_KINDS.has(call.dynamicKind)) {
1700
+ // Key per (caller, file, kind) so each kind gets at most one sink edge per caller.
1701
+ const sinkKey = `${caller.id}:${fileNodeRow.id}:${call.dynamicKind}`;
1702
+ if (!seenCallEdges.has(sinkKey)) {
1703
+ seenCallEdges.add(sinkKey);
1704
+ allEdgeRows.push([caller.id, fileNodeRow.id, 'calls', 0.0, 1, null, call.dynamicKind]);
1613
1705
  }
1614
1706
  }
1615
1707
  }
@@ -1637,7 +1729,7 @@ function buildClassHierarchyEdges(
1637
1729
  );
1638
1730
  if (sourceRow) {
1639
1731
  for (const t of targetRows) {
1640
- allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null]);
1732
+ allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null, null]);
1641
1733
  }
1642
1734
  }
1643
1735
  }
@@ -1651,7 +1743,7 @@ function buildClassHierarchyEdges(
1651
1743
  );
1652
1744
  if (sourceRow) {
1653
1745
  for (const t of targetRows) {
1654
- allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null]);
1746
+ allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null, null]);
1655
1747
  }
1656
1748
  }
1657
1749
  }
@@ -1662,7 +1754,8 @@ function buildClassHierarchyEdges(
1662
1754
 
1663
1755
  /**
1664
1756
  * After native bulkInsertEdges (which does not write the technique column),
1665
- * apply technique values from the in-memory row array back to the DB.
1757
+ * apply technique values from the in-memory row array back to the DB, and lift
1758
+ * any resolved ts-native edge below TS_NATIVE_CONFIDENCE_FLOOR to that floor.
1666
1759
  *
1667
1760
  * Rows with an explicit technique get a targeted UPDATE by (source_id, target_id).
1668
1761
  * The catch-all 'ts-native' tag is scoped to only the source_ids present in this
@@ -1683,6 +1776,9 @@ function applyEdgeTechniquesAfterNativeInsert(
1683
1776
  // Chunk to stay within SQLite's SQLITE_LIMIT_VARIABLE_NUMBER (999 on older builds).
1684
1777
  const CHUNK_SIZE = 500;
1685
1778
 
1779
+ // Rows that carry an explicit dynamic_kind (sink edges for flagged dynamic calls).
1780
+ const dynamicKindRows = callRows.filter((r) => r[6] != null);
1781
+
1686
1782
  const tx = db.transaction(() => {
1687
1783
  if (taggedRows.length > 0) {
1688
1784
  const stmt = db.prepare(
@@ -1696,6 +1792,27 @@ function applyEdgeTechniquesAfterNativeInsert(
1696
1792
  db.prepare(
1697
1793
  `UPDATE edges SET technique = 'ts-native' WHERE kind = 'calls' AND technique IS NULL AND source_id IN (${placeholders})`,
1698
1794
  ).run(...chunk);
1795
+ // Lift resolved ts-native edges below the confidence floor for this chunk.
1796
+ db.prepare(
1797
+ `UPDATE edges SET confidence = ?
1798
+ WHERE kind = 'calls' AND technique = 'ts-native'
1799
+ AND confidence > 0 AND confidence < ?
1800
+ AND source_id IN (${placeholders})`,
1801
+ ).run(TS_NATIVE_CONFIDENCE_FLOOR, TS_NATIVE_CONFIDENCE_FLOOR, ...chunk);
1802
+ }
1803
+ // Back-fill dynamic_kind for flagged sink edges emitted by the native engine.
1804
+ // Native bulkInsertEdges uses INSERT OR IGNORE and does not write dynamic_kind, so
1805
+ // this UPDATE is the only way to set it for natively-inserted sink edges.
1806
+ //
1807
+ // Scope to confidence=0.0 AND dynamic=1 so we only touch sink edges (never normal
1808
+ // call edges that happen to share the same (source_id, target_id) pair).
1809
+ // Include dynamic_kind in the WHERE so two sink edges from the same caller to the
1810
+ // same file with different kinds don't clobber each other across incremental runs.
1811
+ if (dynamicKindRows.length > 0) {
1812
+ const stmt = db.prepare(
1813
+ "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 = ?)",
1814
+ );
1815
+ for (const r of dynamicKindRows) stmt.run(r[6], r[0], r[1], r[6]);
1699
1816
  }
1700
1817
  });
1701
1818
  tx();
@@ -1734,6 +1851,7 @@ function reconnectReverseDepEdges(ctx: PipelineContext): void {
1734
1851
  saved.confidence,
1735
1852
  saved.dynamic,
1736
1853
  saved.technique,
1854
+ saved.dynamicKind ?? null,
1737
1855
  ]);
1738
1856
  } else {
1739
1857
  // Target was removed or renamed in the changed file — edge is stale
@@ -1775,7 +1893,7 @@ function reconnectReverseDepEdges(ctx: PipelineContext): void {
1775
1893
  * their import targets. Falls back to loading ALL nodes for full builds or
1776
1894
  * larger incremental changes.
1777
1895
  */
1778
- const NODE_KIND_FILTER_SQL = `kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant')`;
1896
+ const NODE_KIND_FILTER_SQL = `kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant','variable')`;
1779
1897
 
1780
1898
  function loadNodes(ctx: PipelineContext): { rows: QueryNodeRow[]; scoped: boolean } {
1781
1899
  const { db, fileSymbols, isFullBuild, batchResolved } = ctx;
@@ -1858,7 +1976,16 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
1858
1976
  // Enrich typeMap for .ts/.tsx files using the TypeScript compiler API.
1859
1977
  // Runs before call-edge construction so the accurate types are available
1860
1978
  // for method-call resolution. Gated on config so users can opt out.
1861
- if (ctx.config.build.typescriptResolver) {
1979
+ //
1980
+ // Skip for small incremental builds: TypeScript program creation requires
1981
+ // loading the entire tsconfig file list (~700ms startup on the codegraph
1982
+ // corpus), which dominates the 1-file rebuild time. Native engine bypasses
1983
+ // this entirely via the Rust orchestrator; WASM/JS engines need this gate
1984
+ // to match native's effective behaviour on tiny incremental changes.
1985
+ // Mirrors the smallFilesThreshold gates for nativeDb and native call-edges.
1986
+ const isSmallIncremental =
1987
+ !ctx.isFullBuild && ctx.fileSymbols.size <= ctx.config.build.smallFilesThreshold;
1988
+ if (ctx.config.build.typescriptResolver && !isSmallIncremental) {
1862
1989
  await enrichTypeMapWithTsc(ctx.rootDir, ctx.fileSymbols);
1863
1990
  }
1864
1991
 
@@ -1921,26 +2048,12 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
1921
2048
  (ctx.isFullBuild || ctx.fileSymbols.size > ctx.config.build.smallFilesThreshold);
1922
2049
  if (useNativeCallEdges) {
1923
2050
  buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodesBefore, native!);
1924
- // Build the shared lookup once — both pts post-passes use it, avoiding
1925
- // redundant construction of the same context closure.
2051
+ // The native engine receives all pts bindings (paramBindings,
2052
+ // fnRefBindings, thisCallBindings, objectRestParamBindings, …) through
2053
+ // NativeFileEntry and runs the same points-to solver as the JS path, so
2054
+ // no pts post-passes are needed here. Only capabilities that remain
2055
+ // JS-only run as post-passes below.
1926
2056
  const sharedLookup = makeContextLookup(ctx, getNodeIdStmt);
1927
- // Phase 8.3c post-pass: augment native call edges with parameter-flow pts
1928
- // edges. The native Rust engine has no knowledge of paramBindings, so any
1929
- // `fn()` call inside a higher-order function would be missed. This JS pass
1930
- // runs on top of the native edges and adds only the pts-resolved edges that
1931
- // the native engine could not produce.
1932
- buildParamFlowPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
1933
- // bind/alias post-pass: augment native call edges with fnRefBindings-seeded
1934
- // pts edges. The native Rust engine has no knowledge of JS fnRefBindings
1935
- // (e.g. `const f = fn.bind(ctx)`), so calls to bind-created aliases are
1936
- // not resolved to their original function on the native path.
1937
- buildFnRefBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
1938
- // this-rebinding post-pass: resolve `this()` calls inside functions that
1939
- // were invoked via `.call(namedCtx, ...)` / `.apply(namedCtx, ...)`.
1940
- buildThisCallBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
1941
- // Phase 8.3f post-pass: augment native call edges with object rest-param
1942
- // receiver resolution — typeMap[restName] → argName → typeMap[argName.method].
1943
- buildObjectRestParamPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
1944
2057
  // Object.defineProperty accessor post-pass: resolve this-dispatch inside
1945
2058
  // getter/setter functions registered via Object.defineProperty.
1946
2059
  buildDefinePropertyPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
@@ -1952,6 +2065,24 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
1952
2065
  buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows, chaCtx);
1953
2066
  }
1954
2067
 
2068
+ // Apply ts-native confidence floor to allEdgeRows in-memory. The proximity
2069
+ // heuristic returns 0.3 for cross-module calls with no import-path evidence,
2070
+ // but both WASM and native engines perform actual name-based symbol lookup,
2071
+ // which is stronger evidence than pure proximity. Clamping to
2072
+ // TS_NATIVE_CONFIDENCE_FLOOR (0.5) avoids unfairly dragging down the
2073
+ // call-confidence metric. Sink edges (confidence = 0.0) are excluded so
2074
+ // they remain below DEFAULT_MIN_CONFIDENCE.
2075
+ for (const r of allEdgeRows) {
2076
+ if (
2077
+ r[2] === 'calls' &&
2078
+ r[5] === 'ts-native' &&
2079
+ (r[3] as number) > 0 &&
2080
+ (r[3] as number) < TS_NATIVE_CONFIDENCE_FLOOR
2081
+ ) {
2082
+ r[3] = TS_NATIVE_CONFIDENCE_FLOOR;
2083
+ }
2084
+ }
2085
+
1955
2086
  // When using native edge insert, skip JS insert here — do it after tx commits.
1956
2087
  // Otherwise insert edges within this transaction for atomicity.
1957
2088
  const useNativeEdgeInsert = ctx.engineName === 'native' && !!ctx.nativeDb?.bulkInsertEdges;