@optave/codegraph 3.13.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 (458) hide show
  1. package/README.md +35 -34
  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/config.d.ts.map +1 -1
  51. package/dist/cli/commands/config.js +137 -134
  52. package/dist/cli/commands/config.js.map +1 -1
  53. package/dist/cli/commands/roles.d.ts.map +1 -1
  54. package/dist/cli/commands/roles.js +6 -1
  55. package/dist/cli/commands/roles.js.map +1 -1
  56. package/dist/db/better-sqlite3.d.ts +2 -1
  57. package/dist/db/better-sqlite3.d.ts.map +1 -1
  58. package/dist/db/better-sqlite3.js.map +1 -1
  59. package/dist/db/connection.d.ts +7 -1
  60. package/dist/db/connection.d.ts.map +1 -1
  61. package/dist/db/connection.js +20 -5
  62. package/dist/db/connection.js.map +1 -1
  63. package/dist/db/index.d.ts +1 -1
  64. package/dist/db/index.d.ts.map +1 -1
  65. package/dist/db/index.js +1 -1
  66. package/dist/db/index.js.map +1 -1
  67. package/dist/db/migrations.d.ts.map +1 -1
  68. package/dist/db/migrations.js +68 -0
  69. package/dist/db/migrations.js.map +1 -1
  70. package/dist/db/repository/build-stmts.d.ts.map +1 -1
  71. package/dist/db/repository/build-stmts.js +18 -0
  72. package/dist/db/repository/build-stmts.js.map +1 -1
  73. package/dist/db/repository/dataflow.d.ts +5 -0
  74. package/dist/db/repository/dataflow.d.ts.map +1 -1
  75. package/dist/db/repository/dataflow.js +14 -0
  76. package/dist/db/repository/dataflow.js.map +1 -1
  77. package/dist/db/repository/index.d.ts +1 -1
  78. package/dist/db/repository/index.d.ts.map +1 -1
  79. package/dist/db/repository/index.js +1 -1
  80. package/dist/db/repository/index.js.map +1 -1
  81. package/dist/db/repository/native-repository.d.ts.map +1 -1
  82. package/dist/db/repository/native-repository.js +47 -34
  83. package/dist/db/repository/native-repository.js.map +1 -1
  84. package/dist/domain/analysis/context.d.ts +2 -2
  85. package/dist/domain/analysis/dependencies.d.ts +2 -2
  86. package/dist/domain/analysis/diff-impact.d.ts +2 -2
  87. package/dist/domain/analysis/fn-impact.d.ts +3 -1
  88. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  89. package/dist/domain/analysis/fn-impact.js +4 -0
  90. package/dist/domain/analysis/fn-impact.js.map +1 -1
  91. package/dist/domain/analysis/implementations.d.ts +2 -2
  92. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  93. package/dist/domain/analysis/module-map.js +32 -5
  94. package/dist/domain/analysis/module-map.js.map +1 -1
  95. package/dist/domain/analysis/roles.d.ts +7 -1
  96. package/dist/domain/analysis/roles.d.ts.map +1 -1
  97. package/dist/domain/analysis/roles.js +16 -0
  98. package/dist/domain/analysis/roles.js.map +1 -1
  99. package/dist/domain/analysis/symbol-lookup.d.ts +4 -4
  100. package/dist/domain/graph/builder/call-resolver.d.ts +17 -5
  101. package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -1
  102. package/dist/domain/graph/builder/call-resolver.js +85 -220
  103. package/dist/domain/graph/builder/call-resolver.js.map +1 -1
  104. package/dist/domain/graph/builder/context.d.ts +1 -0
  105. package/dist/domain/graph/builder/context.d.ts.map +1 -1
  106. package/dist/domain/graph/builder/context.js.map +1 -1
  107. package/dist/domain/graph/builder/helpers.d.ts +16 -1
  108. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  109. package/dist/domain/graph/builder/helpers.js +162 -72
  110. package/dist/domain/graph/builder/helpers.js.map +1 -1
  111. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  112. package/dist/domain/graph/builder/incremental.js +166 -97
  113. package/dist/domain/graph/builder/incremental.js.map +1 -1
  114. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  115. package/dist/domain/graph/builder/pipeline.js +10 -4
  116. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  117. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  118. package/dist/domain/graph/builder/stages/build-edges.js +496 -250
  119. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  120. package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
  121. package/dist/domain/graph/builder/stages/collect-files.js +10 -7
  122. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  123. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  124. package/dist/domain/graph/builder/stages/detect-changes.js +2 -1
  125. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  126. package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -1
  127. package/dist/domain/graph/builder/stages/native-orchestrator.js +895 -545
  128. package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -1
  129. package/dist/domain/graph/resolver/points-to.d.ts.map +1 -1
  130. package/dist/domain/graph/resolver/points-to.js +105 -57
  131. package/dist/domain/graph/resolver/points-to.js.map +1 -1
  132. package/dist/domain/graph/resolver/strategy.d.ts +61 -0
  133. package/dist/domain/graph/resolver/strategy.d.ts.map +1 -0
  134. package/dist/domain/graph/resolver/strategy.js +222 -0
  135. package/dist/domain/graph/resolver/strategy.js.map +1 -0
  136. package/dist/domain/graph/watcher.d.ts.map +1 -1
  137. package/dist/domain/graph/watcher.js +16 -9
  138. package/dist/domain/graph/watcher.js.map +1 -1
  139. package/dist/domain/parser.d.ts +12 -0
  140. package/dist/domain/parser.d.ts.map +1 -1
  141. package/dist/domain/parser.js +12 -2
  142. package/dist/domain/parser.js.map +1 -1
  143. package/dist/domain/queries.d.ts +1 -1
  144. package/dist/domain/queries.d.ts.map +1 -1
  145. package/dist/domain/queries.js +1 -1
  146. package/dist/domain/queries.js.map +1 -1
  147. package/dist/domain/wasm-worker-entry.js +3 -0
  148. package/dist/domain/wasm-worker-entry.js.map +1 -1
  149. package/dist/domain/wasm-worker-pool.d.ts.map +1 -1
  150. package/dist/domain/wasm-worker-pool.js +24 -5
  151. package/dist/domain/wasm-worker-pool.js.map +1 -1
  152. package/dist/domain/wasm-worker-protocol.d.ts +7 -0
  153. package/dist/domain/wasm-worker-protocol.d.ts.map +1 -1
  154. package/dist/extractors/dart.js +48 -3
  155. package/dist/extractors/dart.js.map +1 -1
  156. package/dist/extractors/groovy.js +62 -3
  157. package/dist/extractors/groovy.js.map +1 -1
  158. package/dist/extractors/helpers.d.ts +4 -2
  159. package/dist/extractors/helpers.d.ts.map +1 -1
  160. package/dist/extractors/helpers.js +5 -1
  161. package/dist/extractors/helpers.js.map +1 -1
  162. package/dist/extractors/java.js +77 -1
  163. package/dist/extractors/java.js.map +1 -1
  164. package/dist/extractors/javascript.d.ts.map +1 -1
  165. package/dist/extractors/javascript.js +549 -163
  166. package/dist/extractors/javascript.js.map +1 -1
  167. package/dist/extractors/kotlin.js +58 -3
  168. package/dist/extractors/kotlin.js.map +1 -1
  169. package/dist/extractors/objc.js +25 -2
  170. package/dist/extractors/objc.js.map +1 -1
  171. package/dist/extractors/scala.js +62 -2
  172. package/dist/extractors/scala.js.map +1 -1
  173. package/dist/extractors/swift.js +52 -3
  174. package/dist/extractors/swift.js.map +1 -1
  175. package/dist/features/audit.js +26 -23
  176. package/dist/features/audit.js.map +1 -1
  177. package/dist/features/boundaries.d.ts.map +1 -1
  178. package/dist/features/boundaries.js +12 -9
  179. package/dist/features/boundaries.js.map +1 -1
  180. package/dist/features/cfg.d.ts.map +1 -1
  181. package/dist/features/cfg.js +25 -18
  182. package/dist/features/cfg.js.map +1 -1
  183. package/dist/features/check.d.ts.map +1 -1
  184. package/dist/features/check.js +18 -5
  185. package/dist/features/check.js.map +1 -1
  186. package/dist/features/communities.d.ts +4 -2
  187. package/dist/features/communities.d.ts.map +1 -1
  188. package/dist/features/communities.js +6 -4
  189. package/dist/features/communities.js.map +1 -1
  190. package/dist/features/dataflow.d.ts +60 -0
  191. package/dist/features/dataflow.d.ts.map +1 -1
  192. package/dist/features/dataflow.js +530 -6
  193. package/dist/features/dataflow.js.map +1 -1
  194. package/dist/features/manifesto.d.ts.map +1 -1
  195. package/dist/features/manifesto.js +59 -72
  196. package/dist/features/manifesto.js.map +1 -1
  197. package/dist/features/sequence.d.ts.map +1 -1
  198. package/dist/features/sequence.js +27 -22
  199. package/dist/features/sequence.js.map +1 -1
  200. package/dist/features/snapshot.d.ts.map +1 -1
  201. package/dist/features/snapshot.js +36 -28
  202. package/dist/features/snapshot.js.map +1 -1
  203. package/dist/features/structure.d.ts.map +1 -1
  204. package/dist/features/structure.js +150 -62
  205. package/dist/features/structure.js.map +1 -1
  206. package/dist/features/triage.d.ts.map +1 -1
  207. package/dist/features/triage.js +18 -11
  208. package/dist/features/triage.js.map +1 -1
  209. package/dist/graph/algorithms/bfs.d.ts +1 -1
  210. package/dist/graph/algorithms/bfs.d.ts.map +1 -1
  211. package/dist/graph/algorithms/bfs.js +14 -13
  212. package/dist/graph/algorithms/bfs.js.map +1 -1
  213. package/dist/graph/algorithms/tarjan.d.ts.map +1 -1
  214. package/dist/graph/algorithms/tarjan.js +5 -0
  215. package/dist/graph/algorithms/tarjan.js.map +1 -1
  216. package/dist/graph/builders/dependency.js +28 -22
  217. package/dist/graph/builders/dependency.js.map +1 -1
  218. package/dist/graph/classifiers/roles.d.ts +10 -1
  219. package/dist/graph/classifiers/roles.d.ts.map +1 -1
  220. package/dist/graph/classifiers/roles.js +60 -6
  221. package/dist/graph/classifiers/roles.js.map +1 -1
  222. package/dist/infrastructure/config.d.ts +10 -0
  223. package/dist/infrastructure/config.d.ts.map +1 -1
  224. package/dist/infrastructure/config.js +31 -3
  225. package/dist/infrastructure/config.js.map +1 -1
  226. package/dist/infrastructure/registry.d.ts +0 -7
  227. package/dist/infrastructure/registry.d.ts.map +1 -1
  228. package/dist/infrastructure/registry.js +29 -13
  229. package/dist/infrastructure/registry.js.map +1 -1
  230. package/dist/infrastructure/update-check.d.ts.map +1 -1
  231. package/dist/infrastructure/update-check.js +49 -31
  232. package/dist/infrastructure/update-check.js.map +1 -1
  233. package/dist/mcp/server.d.ts +2 -10
  234. package/dist/mcp/server.d.ts.map +1 -1
  235. package/dist/mcp/server.js.map +1 -1
  236. package/dist/mcp/tools/ast-query.d.ts +1 -1
  237. package/dist/mcp/tools/ast-query.d.ts.map +1 -1
  238. package/dist/mcp/tools/audit.d.ts +1 -1
  239. package/dist/mcp/tools/audit.d.ts.map +1 -1
  240. package/dist/mcp/tools/batch-query.d.ts +1 -1
  241. package/dist/mcp/tools/batch-query.d.ts.map +1 -1
  242. package/dist/mcp/tools/branch-compare.d.ts +1 -1
  243. package/dist/mcp/tools/branch-compare.d.ts.map +1 -1
  244. package/dist/mcp/tools/brief.d.ts +1 -1
  245. package/dist/mcp/tools/brief.d.ts.map +1 -1
  246. package/dist/mcp/tools/cfg.d.ts +1 -1
  247. package/dist/mcp/tools/cfg.d.ts.map +1 -1
  248. package/dist/mcp/tools/check.d.ts +1 -1
  249. package/dist/mcp/tools/check.d.ts.map +1 -1
  250. package/dist/mcp/tools/co-changes.d.ts +1 -1
  251. package/dist/mcp/tools/co-changes.d.ts.map +1 -1
  252. package/dist/mcp/tools/code-owners.d.ts +1 -1
  253. package/dist/mcp/tools/code-owners.d.ts.map +1 -1
  254. package/dist/mcp/tools/communities.d.ts +1 -1
  255. package/dist/mcp/tools/communities.d.ts.map +1 -1
  256. package/dist/mcp/tools/complexity.d.ts +1 -1
  257. package/dist/mcp/tools/complexity.d.ts.map +1 -1
  258. package/dist/mcp/tools/context.d.ts +1 -1
  259. package/dist/mcp/tools/context.d.ts.map +1 -1
  260. package/dist/mcp/tools/dataflow.d.ts +1 -1
  261. package/dist/mcp/tools/dataflow.d.ts.map +1 -1
  262. package/dist/mcp/tools/diff-impact.d.ts +1 -1
  263. package/dist/mcp/tools/diff-impact.d.ts.map +1 -1
  264. package/dist/mcp/tools/execution-flow.d.ts +1 -1
  265. package/dist/mcp/tools/execution-flow.d.ts.map +1 -1
  266. package/dist/mcp/tools/export-graph.d.ts +1 -1
  267. package/dist/mcp/tools/export-graph.d.ts.map +1 -1
  268. package/dist/mcp/tools/file-deps.d.ts +1 -1
  269. package/dist/mcp/tools/file-deps.d.ts.map +1 -1
  270. package/dist/mcp/tools/file-exports.d.ts +1 -1
  271. package/dist/mcp/tools/file-exports.d.ts.map +1 -1
  272. package/dist/mcp/tools/find-cycles.d.ts +1 -1
  273. package/dist/mcp/tools/find-cycles.d.ts.map +1 -1
  274. package/dist/mcp/tools/fn-impact.d.ts +1 -1
  275. package/dist/mcp/tools/fn-impact.d.ts.map +1 -1
  276. package/dist/mcp/tools/impact-analysis.d.ts +1 -1
  277. package/dist/mcp/tools/impact-analysis.d.ts.map +1 -1
  278. package/dist/mcp/tools/implementations.d.ts +1 -1
  279. package/dist/mcp/tools/implementations.d.ts.map +1 -1
  280. package/dist/mcp/tools/index.d.ts +2 -5
  281. package/dist/mcp/tools/index.d.ts.map +1 -1
  282. package/dist/mcp/tools/index.js.map +1 -1
  283. package/dist/mcp/tools/interfaces.d.ts +1 -1
  284. package/dist/mcp/tools/interfaces.d.ts.map +1 -1
  285. package/dist/mcp/tools/list-functions.d.ts +1 -1
  286. package/dist/mcp/tools/list-functions.d.ts.map +1 -1
  287. package/dist/mcp/tools/list-repos.d.ts +1 -1
  288. package/dist/mcp/tools/list-repos.d.ts.map +1 -1
  289. package/dist/mcp/tools/module-map.d.ts +1 -1
  290. package/dist/mcp/tools/module-map.d.ts.map +1 -1
  291. package/dist/mcp/tools/node-roles.d.ts +1 -1
  292. package/dist/mcp/tools/node-roles.d.ts.map +1 -1
  293. package/dist/mcp/tools/path.d.ts +1 -1
  294. package/dist/mcp/tools/path.d.ts.map +1 -1
  295. package/dist/mcp/tools/query.d.ts +1 -1
  296. package/dist/mcp/tools/query.d.ts.map +1 -1
  297. package/dist/mcp/tools/semantic-search.d.ts +1 -1
  298. package/dist/mcp/tools/semantic-search.d.ts.map +1 -1
  299. package/dist/mcp/tools/sequence.d.ts +1 -1
  300. package/dist/mcp/tools/sequence.d.ts.map +1 -1
  301. package/dist/mcp/tools/structure.d.ts +1 -1
  302. package/dist/mcp/tools/structure.d.ts.map +1 -1
  303. package/dist/mcp/tools/symbol-children.d.ts +1 -1
  304. package/dist/mcp/tools/symbol-children.d.ts.map +1 -1
  305. package/dist/mcp/tools/triage.d.ts +1 -1
  306. package/dist/mcp/tools/triage.d.ts.map +1 -1
  307. package/dist/mcp/tools/where.d.ts +1 -1
  308. package/dist/mcp/tools/where.d.ts.map +1 -1
  309. package/dist/mcp/types.d.ts +19 -0
  310. package/dist/mcp/types.d.ts.map +1 -0
  311. package/dist/mcp/types.js +6 -0
  312. package/dist/mcp/types.js.map +1 -0
  313. package/dist/presentation/queries-cli/index.d.ts +1 -1
  314. package/dist/presentation/queries-cli/index.d.ts.map +1 -1
  315. package/dist/presentation/queries-cli/index.js +1 -1
  316. package/dist/presentation/queries-cli/index.js.map +1 -1
  317. package/dist/presentation/queries-cli/overview.d.ts +1 -0
  318. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  319. package/dist/presentation/queries-cli/overview.js +20 -1
  320. package/dist/presentation/queries-cli/overview.js.map +1 -1
  321. package/dist/presentation/queries-cli.d.ts +1 -1
  322. package/dist/presentation/queries-cli.d.ts.map +1 -1
  323. package/dist/presentation/queries-cli.js +1 -1
  324. package/dist/presentation/queries-cli.js.map +1 -1
  325. package/dist/presentation/viewer.d.ts.map +1 -1
  326. package/dist/presentation/viewer.js +45 -32
  327. package/dist/presentation/viewer.js.map +1 -1
  328. package/dist/shared/constants.d.ts +21 -0
  329. package/dist/shared/constants.d.ts.map +1 -1
  330. package/dist/shared/constants.js +25 -0
  331. package/dist/shared/constants.js.map +1 -1
  332. package/dist/shared/normalize.d.ts.map +1 -1
  333. package/dist/shared/normalize.js +12 -22
  334. package/dist/shared/normalize.js.map +1 -1
  335. package/dist/shared/paginate.d.ts +4 -17
  336. package/dist/shared/paginate.d.ts.map +1 -1
  337. package/dist/shared/paginate.js.map +1 -1
  338. package/dist/types.d.ts +76 -1
  339. package/dist/types.d.ts.map +1 -1
  340. package/grammars/tree-sitter-erlang.wasm +0 -0
  341. package/package.json +7 -7
  342. package/src/ast-analysis/engine.ts +43 -63
  343. package/src/ast-analysis/rules/b2.ts +263 -0
  344. package/src/ast-analysis/rules/b3.ts +127 -0
  345. package/src/ast-analysis/rules/b4.ts +378 -0
  346. package/src/ast-analysis/rules/b5.ts +65 -0
  347. package/src/ast-analysis/rules/c.ts +157 -0
  348. package/src/ast-analysis/rules/index.ts +34 -0
  349. package/src/ast-analysis/rules/javascript.ts +3 -0
  350. package/src/ast-analysis/shared.ts +2 -0
  351. package/src/ast-analysis/visitor-utils.ts +5 -0
  352. package/src/ast-analysis/visitor.ts +82 -52
  353. package/src/ast-analysis/visitors/cfg-visitor.ts +198 -84
  354. package/src/ast-analysis/visitors/complexity-visitor.ts +44 -16
  355. package/src/ast-analysis/visitors/dataflow-visitor.ts +68 -29
  356. package/src/cli/commands/config.ts +184 -184
  357. package/src/cli/commands/roles.ts +6 -1
  358. package/src/db/better-sqlite3.ts +5 -4
  359. package/src/db/connection.ts +23 -5
  360. package/src/db/index.ts +1 -0
  361. package/src/db/migrations.ts +68 -0
  362. package/src/db/repository/build-stmts.ts +30 -0
  363. package/src/db/repository/dataflow.ts +16 -0
  364. package/src/db/repository/index.ts +1 -1
  365. package/src/db/repository/native-repository.ts +56 -40
  366. package/src/domain/analysis/fn-impact.ts +4 -0
  367. package/src/domain/analysis/module-map.ts +38 -6
  368. package/src/domain/analysis/roles.ts +23 -0
  369. package/src/domain/graph/builder/call-resolver.ts +112 -232
  370. package/src/domain/graph/builder/context.ts +1 -0
  371. package/src/domain/graph/builder/helpers.ts +190 -72
  372. package/src/domain/graph/builder/incremental.ts +249 -120
  373. package/src/domain/graph/builder/pipeline.ts +11 -5
  374. package/src/domain/graph/builder/stages/build-edges.ts +696 -296
  375. package/src/domain/graph/builder/stages/collect-files.ts +12 -6
  376. package/src/domain/graph/builder/stages/detect-changes.ts +3 -1
  377. package/src/domain/graph/builder/stages/native-orchestrator.ts +1102 -590
  378. package/src/domain/graph/resolver/points-to.ts +182 -59
  379. package/src/domain/graph/resolver/strategy.ts +265 -0
  380. package/src/domain/graph/watcher.ts +19 -9
  381. package/src/domain/parser.ts +12 -2
  382. package/src/domain/queries.ts +1 -1
  383. package/src/domain/wasm-worker-entry.ts +3 -0
  384. package/src/domain/wasm-worker-pool.ts +28 -4
  385. package/src/domain/wasm-worker-protocol.ts +4 -0
  386. package/src/extractors/dart.ts +48 -3
  387. package/src/extractors/groovy.ts +62 -2
  388. package/src/extractors/helpers.ts +5 -2
  389. package/src/extractors/java.ts +80 -1
  390. package/src/extractors/javascript.ts +566 -161
  391. package/src/extractors/kotlin.ts +57 -3
  392. package/src/extractors/objc.ts +25 -1
  393. package/src/extractors/scala.ts +63 -1
  394. package/src/extractors/swift.ts +46 -3
  395. package/src/features/audit.ts +43 -34
  396. package/src/features/boundaries.ts +17 -9
  397. package/src/features/cfg.ts +31 -22
  398. package/src/features/check.ts +21 -5
  399. package/src/features/communities.ts +28 -19
  400. package/src/features/dataflow.ts +755 -6
  401. package/src/features/manifesto.ts +76 -75
  402. package/src/features/sequence.ts +29 -23
  403. package/src/features/snapshot.ts +36 -25
  404. package/src/features/structure.ts +185 -55
  405. package/src/features/triage.ts +28 -15
  406. package/src/graph/algorithms/bfs.ts +13 -12
  407. package/src/graph/algorithms/tarjan.ts +5 -0
  408. package/src/graph/builders/dependency.ts +35 -23
  409. package/src/graph/classifiers/roles.ts +74 -7
  410. package/src/infrastructure/config.ts +32 -3
  411. package/src/infrastructure/registry.ts +44 -20
  412. package/src/infrastructure/update-check.ts +55 -33
  413. package/src/mcp/server.ts +2 -8
  414. package/src/mcp/tools/ast-query.ts +1 -1
  415. package/src/mcp/tools/audit.ts +1 -1
  416. package/src/mcp/tools/batch-query.ts +1 -1
  417. package/src/mcp/tools/branch-compare.ts +1 -1
  418. package/src/mcp/tools/brief.ts +1 -1
  419. package/src/mcp/tools/cfg.ts +1 -1
  420. package/src/mcp/tools/check.ts +1 -1
  421. package/src/mcp/tools/co-changes.ts +1 -1
  422. package/src/mcp/tools/code-owners.ts +1 -1
  423. package/src/mcp/tools/communities.ts +1 -1
  424. package/src/mcp/tools/complexity.ts +1 -1
  425. package/src/mcp/tools/context.ts +1 -1
  426. package/src/mcp/tools/dataflow.ts +1 -1
  427. package/src/mcp/tools/diff-impact.ts +1 -1
  428. package/src/mcp/tools/execution-flow.ts +1 -1
  429. package/src/mcp/tools/export-graph.ts +1 -1
  430. package/src/mcp/tools/file-deps.ts +1 -1
  431. package/src/mcp/tools/file-exports.ts +1 -1
  432. package/src/mcp/tools/find-cycles.ts +1 -1
  433. package/src/mcp/tools/fn-impact.ts +1 -1
  434. package/src/mcp/tools/impact-analysis.ts +1 -1
  435. package/src/mcp/tools/implementations.ts +1 -1
  436. package/src/mcp/tools/index.ts +2 -5
  437. package/src/mcp/tools/interfaces.ts +1 -1
  438. package/src/mcp/tools/list-functions.ts +1 -1
  439. package/src/mcp/tools/list-repos.ts +1 -1
  440. package/src/mcp/tools/module-map.ts +1 -1
  441. package/src/mcp/tools/node-roles.ts +1 -1
  442. package/src/mcp/tools/path.ts +1 -1
  443. package/src/mcp/tools/query.ts +1 -1
  444. package/src/mcp/tools/semantic-search.ts +1 -1
  445. package/src/mcp/tools/sequence.ts +1 -1
  446. package/src/mcp/tools/structure.ts +1 -1
  447. package/src/mcp/tools/symbol-children.ts +1 -1
  448. package/src/mcp/tools/triage.ts +1 -1
  449. package/src/mcp/tools/where.ts +1 -1
  450. package/src/mcp/types.ts +21 -0
  451. package/src/presentation/queries-cli/index.ts +1 -1
  452. package/src/presentation/queries-cli/overview.ts +35 -1
  453. package/src/presentation/queries-cli.ts +1 -0
  454. package/src/presentation/viewer.ts +98 -87
  455. package/src/shared/constants.ts +26 -0
  456. package/src/shared/normalize.ts +13 -22
  457. package/src/shared/paginate.ts +4 -18
  458. package/src/types.ts +86 -1
@@ -11,6 +11,7 @@ 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 {
15
16
  ArrayCallbackBinding,
16
17
  ArrayElemBinding,
@@ -18,6 +19,7 @@ import type {
18
19
  Call,
19
20
  ClassRelation,
20
21
  Definition,
22
+ DynamicKind,
21
23
  ExtractorOutput,
22
24
  FnRefBinding,
23
25
  ForOfBinding,
@@ -56,7 +58,8 @@ import { getResolved, isBarrelFile, resolveBarrelExportCached } from './resolve-
56
58
 
57
59
  // ── Local types ──────────────────────────────────────────────────────────
58
60
 
59
- 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
60
63
 
61
64
  interface NodeIdStmt {
62
65
  get(name: string, kind: string, file: string, line: number): { id: number } | undefined;
@@ -105,6 +108,7 @@ interface NativeEdge {
105
108
  kind: string;
106
109
  confidence: number;
107
110
  dynamic: number;
111
+ dynamic_kind?: string | null;
108
112
  }
109
113
 
110
114
  // ── Node lookup setup ───────────────────────────────────────────────────
@@ -163,7 +167,7 @@ function emitTypeOnlySymbolEdges(
163
167
  }
164
168
  const candidates = ctx.nodesByNameAndFile.get(`${cleanName}|${targetFile}`);
165
169
  if (candidates && candidates.length > 0) {
166
- 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]);
167
171
  }
168
172
  }
169
173
  }
@@ -185,7 +189,7 @@ function emitEdgesForImport(
185
189
  if (!targetRow) return;
186
190
 
187
191
  const edgeKind = importEdgeKind(imp);
188
- allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null]);
192
+ allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null, null]);
189
193
 
190
194
  if (imp.typeOnly) {
191
195
  emitTypeOnlySymbolEdges(ctx, imp, resolvedPath, fileNodeId, allEdgeRows);
@@ -240,7 +244,7 @@ function buildBarrelEdges(
240
244
  : edgeKind === 'dynamic-imports'
241
245
  ? 'dynamic-imports'
242
246
  : 'imports';
243
- edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null]);
247
+ edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null, null]);
244
248
  }
245
249
  }
246
250
  }
@@ -423,7 +427,7 @@ function buildImportEdgesNative(
423
427
  ) as NativeEdge[];
424
428
 
425
429
  for (const e of nativeEdges) {
426
- 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]);
427
431
  }
428
432
  }
429
433
 
@@ -577,6 +581,7 @@ function buildCallEdgesNative(
577
581
  e.confidence,
578
582
  e.dynamic,
579
583
  e.kind === 'calls' ? 'ts-native' : null,
584
+ e.dynamic_kind ?? null,
580
585
  ]);
581
586
  }
582
587
  }
@@ -661,7 +666,7 @@ function buildDefinePropertyPostPass(
661
666
  const conf = computeConfidence(relPath, t.file, null);
662
667
  if (conf > 0) {
663
668
  seenByPair.add(edgeKey);
664
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native']);
669
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native', null]);
665
670
  }
666
671
  }
667
672
  }
@@ -751,7 +756,7 @@ function buildChaPostPass(
751
756
  // Tag super-dispatch edges distinctly so runChaPostPass can exclude them
752
757
  // from further CHA expansion (super calls are not virtual dispatch).
753
758
  const technique = call.receiver === 'super' ? 'super-dispatch' : 'cha';
754
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, technique]);
759
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, technique, null]);
755
760
  }
756
761
  }
757
762
  }
@@ -839,6 +844,16 @@ function buildCallEdgesJS(
839
844
 
840
845
  const seenCallEdges = new Set<string>();
841
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
+ );
842
857
 
843
858
  buildFileCallEdges(
844
859
  relPath,
@@ -851,6 +866,7 @@ function buildCallEdgesJS(
851
866
  typeMap,
852
867
  ptsMap,
853
868
  chaCtx,
869
+ importArtifactNames,
854
870
  );
855
871
  buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows);
856
872
  }
@@ -895,6 +911,38 @@ function buildImportedNamesMap(
895
911
  return importedNames;
896
912
  }
897
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
+
898
946
  function makeContextLookup(ctx: PipelineContext, getNodeIdStmt: NodeIdStmt): CallNodeLookup {
899
947
  return {
900
948
  byNameAndFile: (name, file) => ctx.nodesByNameAndFile.get(`${name}|${file}`) ?? [],
@@ -991,6 +1039,509 @@ function buildDefinitionParamsMap(
991
1039
  return map;
992
1040
  }
993
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
+ */
994
1545
  function buildFileCallEdges(
995
1546
  relPath: string,
996
1547
  symbols: ExtractorOutput,
@@ -1002,6 +1553,7 @@ function buildFileCallEdges(
1002
1553
  typeMap: Map<string, TypeMapEntry | string>,
1003
1554
  ptsMap?: PointsToMap | null,
1004
1555
  chaCtx?: ChaContext,
1556
+ importArtifactNames?: ReadonlyMap<string, string>,
1005
1557
  ): void {
1006
1558
  // Tracks edges that were inserted by the pts fallback (edgeKey → allEdgeRows index).
1007
1559
  // Kept separate from seenCallEdges so that a subsequent direct-call edge for the same
@@ -1010,236 +1562,68 @@ function buildFileCallEdges(
1010
1562
  // no longer tracked here.
1011
1563
  const ptsEdgeRows = new Map<string, number>();
1012
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
+
1013
1571
  // Pre-compute the set of names that appear as lhs in fnRefBindings so that
1014
1572
  // case (c) of the pts gate below only fires for names that are genuine
1015
1573
  // bind/alias entries, not for every locally-defined function or import that
1016
1574
  // buildPointsToMap seeds with a self-pointing entry.
1017
1575
  const fnRefBindingLhs = new Set(symbols.fnRefBindings?.map((b) => b.lhs) ?? []);
1576
+
1018
1577
  for (const call of symbols.calls) {
1019
1578
  if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver)) continue;
1020
1579
 
1021
1580
  const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
1022
1581
  const isDynamic: number = call.dynamic ? 1 : 0;
1023
- let { targets, importedFrom } = resolveCallTargets(
1024
- lookup,
1582
+
1583
+ // Step 1: Resolve targets with all JS-path fallbacks.
1584
+ const { targets, importedFrom } = resolveFallbackTargets(
1025
1585
  call,
1586
+ caller,
1026
1587
  relPath,
1027
1588
  importedNames,
1028
- typeMap as Map<string, unknown>,
1029
- caller.callerName,
1589
+ lookup,
1590
+ typeMap,
1591
+ symbols.definePropertyReceivers,
1030
1592
  );
1031
1593
 
1032
- // Same-class `this.method()` fallback: when the call receiver is `this` and
1033
- // resolveCallTargets found nothing, derive the enclosing class name from the
1034
- // caller (e.g. `Logger.info` → class prefix `Logger`) and retry with the
1035
- // qualified method name `Logger._write`. This mirrors what the native Rust
1036
- // engine does implicitly via its class-scoped symbol table.
1037
- // NOTE: restricted to `this` only — `super.method()` targets a parent class,
1038
- // not the enclosing class, so qualifying with the child class name would
1039
- // produce a false edge when the child also defines a same-named method.
1040
- if (targets.length === 0 && call.receiver === 'this' && caller.callerName != null) {
1041
- const lastDot = caller.callerName.lastIndexOf('.');
1042
- if (lastDot > 0) {
1043
- const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
1044
- const className = caller.callerName.slice(prevDot + 1, lastDot);
1045
- const qualifiedName = `${className}.${call.name}`;
1046
- const qualified = lookup
1047
- .byNameAndFile(qualifiedName, relPath)
1048
- .filter((n) => n.kind === 'method');
1049
- if (qualified.length > 0) {
1050
- targets = qualified;
1051
- }
1052
- }
1053
- }
1054
-
1055
- // Same-class bare-call fallback: when a no-receiver call can't be resolved
1056
- // globally, try the caller's own class as a qualifier. Handles C# static
1057
- // sibling calls: `IsValidEmail()` inside `Validators.ValidateUser` resolves
1058
- // to `Validators.IsValidEmail`. Skipped for JS/TS where bare calls are
1059
- // module-scoped, not class-scoped.
1060
- if (
1061
- targets.length === 0 &&
1062
- !call.receiver &&
1063
- caller.callerName != null &&
1064
- !isModuleScopedLanguage(relPath)
1065
- ) {
1066
- const lastDot = caller.callerName.lastIndexOf('.');
1067
- if (lastDot > 0) {
1068
- const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
1069
- const className = caller.callerName.slice(prevDot + 1, lastDot);
1070
- const qualifiedName = `${className}.${call.name}`;
1071
- const qualified = lookup
1072
- .byNameAndFile(qualifiedName, relPath)
1073
- .filter((n) => n.kind === 'method');
1074
- if (qualified.length > 0) {
1075
- targets = qualified;
1076
- }
1077
- }
1078
- }
1079
-
1080
- // Object.defineProperty accessor fallback: when a function is registered as
1081
- // a getter/setter via `Object.defineProperty(obj, "bar", { get: getter })`,
1082
- // calls to `this.X()` inside `getter` resolve against `obj` (this === obj
1083
- // when the accessor is invoked). If the same-class fallback above found
1084
- // nothing, try treating `obj` as the receiver and look up `obj.X` in the
1085
- // typeMap, or fall back to a same-file lookup of any definition named X
1086
- // that belongs to the object literal or its type.
1087
- if (
1088
- targets.length === 0 &&
1089
- call.receiver === 'this' &&
1090
- caller.callerName != null &&
1091
- symbols.definePropertyReceivers
1092
- ) {
1093
- const receiverVarName = symbols.definePropertyReceivers.get(caller.callerName);
1094
- if (receiverVarName) {
1095
- // Try typeMap lookup for receiver.methodName
1096
- const typeEntry = typeMap.get(receiverVarName);
1097
- const typeName = typeEntry
1098
- ? typeof typeEntry === 'string'
1099
- ? typeEntry
1100
- : (typeEntry as { type?: string }).type
1101
- : null;
1102
- if (typeName) {
1103
- const qualifiedName = `${typeName}.${call.name}`;
1104
- const qualified = lookup.byNameAndFile(qualifiedName, relPath);
1105
- if (qualified.length > 0) {
1106
- targets = [...qualified];
1107
- }
1108
- }
1109
- // If still no targets, search for any definition named `call.name` in
1110
- // the same file — handles plain object literals where the method isn't
1111
- // qualified (e.g. `const obj = { baz() {} }` defines `baz` directly).
1112
- // Note: this is intentionally broad — it matches any same-file definition
1113
- // with the called name, not just members of the receiver object. This is
1114
- // the same behaviour used by the native post-pass path (buildDefinePropertyPostPass).
1115
- if (targets.length === 0) {
1116
- const sameFile = lookup.byNameAndFile(call.name, relPath);
1117
- if (sameFile.length > 0) {
1118
- targets = [...sameFile];
1119
- }
1120
- }
1121
- }
1122
- }
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
+ );
1123
1607
 
1124
- // Sort targets by confidence descending before emitting edges.
1125
- // For multi-target calls with duplicate (source_id, target_id) pairs the
1126
- // stored confidence depends on which duplicate is processed last — sorting
1127
- // here guarantees the highest-confidence target wins on dedup, matching the
1128
- // native engine's sort_targets_by_confidence call in build_edges.rs.
1129
- if (targets.length > 1) {
1130
- targets = [...targets].sort(
1131
- (a, b) =>
1132
- computeConfidence(relPath, b.file, importedFrom ?? null) -
1133
- computeConfidence(relPath, a.file, importedFrom ?? null),
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,
1134
1623
  );
1135
1624
  }
1136
1625
 
1137
- for (const t of targets) {
1138
- const edgeKey = `${caller.id}|${t.id}`;
1139
- if (t.id !== caller.id) {
1140
- const confidence = computeConfidence(relPath, t.file, importedFrom ?? null);
1141
- if (seenCallEdges.has(edgeKey)) continue;
1142
- const ptsIdx = ptsEdgeRows.get(edgeKey);
1143
- if (ptsIdx !== undefined) {
1144
- // A pts-resolved edge already exists for this caller→target pair with a
1145
- // penalised confidence. Upgrade it to the direct-call confidence in-place,
1146
- // then promote to seenCallEdges so no further processing is needed.
1147
- const ptsRow = allEdgeRows[ptsIdx];
1148
- if (ptsRow) {
1149
- ptsRow[3] = confidence;
1150
- ptsRow[4] = isDynamic; // upgrade is_dynamic: direct call overrides the pts-alias dynamic flag
1151
- ptsRow[5] = 'ts-native'; // promoted from pts to direct-call resolution
1152
- }
1153
- ptsEdgeRows.delete(edgeKey);
1154
- seenCallEdges.add(edgeKey);
1155
- } else {
1156
- seenCallEdges.add(edgeKey);
1157
- allEdgeRows.push([caller.id, t.id, 'calls', confidence, isDynamic, 'ts-native']);
1158
- }
1159
- }
1160
- }
1161
-
1162
- // Phase 8.3 / 8.3c / bind: points-to fallback for unresolved calls.
1163
- // Fires for three cases:
1164
- // (a) dynamic=true: alias calls emitted by extractCallbackReferenceCalls.
1165
- // Looks up `call.name` directly (alias entries are flat-keyed).
1166
- // (b) non-dynamic: parameter variable calls (fn() where fn is a param).
1167
- // Looks up the scoped key `callerName::call.name` to avoid spurious
1168
- // edges from same-named parameters across different functions.
1169
- // (c) non-dynamic: module-level alias bindings — `f = fn.bind(ctx)` or
1170
- // `const f = handler` — where pts('f') was seeded by fnRefBindings.
1171
- // Checked against fnRefBindingLhs (the pre-computed set of lhs names from
1172
- // fnRefBindings) rather than the full ptsMap, so case (c) only fires for
1173
- // genuine bind/alias entries and never for self-seeded local definitions.
1174
- // Confidence is penalised by one hop to reflect the extra indirection.
1175
- //
1176
- // Note: pts edges are added to ptsEdgeRows (not seenCallEdges) so that a later
1177
- // direct call to the same target in the same function body can upgrade confidence
1178
- // rather than being silently dropped by the dedup guard.
1179
- const scopedPtsKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
1180
- // Module-level calls (callerName === null) use the '<module>' sentinel emitted by
1181
- // extractSpreadForOfWalk for top-level for-of loops. Look it up as a fallback so
1182
- // that `for (const f of arr) { f(); }` at module scope resolves correctly.
1183
- const modulePtsKey =
1184
- caller.callerName === null && ptsMap?.has(`<module>::${call.name}`)
1185
- ? `<module>::${call.name}`
1186
- : null;
1187
- const flatPtsKey =
1188
- !call.dynamic && fnRefBindingLhs.has(call.name) && ptsMap?.has(call.name) ? call.name : null;
1189
- if (
1190
- targets.length === 0 &&
1191
- !call.receiver &&
1192
- ptsMap &&
1193
- (call.dynamic ||
1194
- (scopedPtsKey != null && ptsMap.has(scopedPtsKey)) ||
1195
- modulePtsKey != null ||
1196
- flatPtsKey != null)
1197
- ) {
1198
- const ptsLookupName = call.dynamic
1199
- ? call.name
1200
- : scopedPtsKey != null && ptsMap.has(scopedPtsKey)
1201
- ? scopedPtsKey
1202
- : modulePtsKey != null
1203
- ? modulePtsKey
1204
- : // flatPtsKey != null is guaranteed by the outer if condition: if neither
1205
- // call.dynamic nor scopedPtsKey nor modulePtsKey matched, flatPtsKey must be non-null.
1206
- flatPtsKey!;
1207
- for (const alias of resolveViaPointsTo(ptsLookupName, ptsMap)) {
1208
- // Resolve the concrete alias target. Only `name` is needed here — receiver
1209
- // and line are not relevant for alias resolution (we are looking up the
1210
- // aliased function by name, not dispatching a method call).
1211
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
1212
- lookup,
1213
- { name: alias },
1214
- relPath,
1215
- importedNames,
1216
- typeMap as Map<string, unknown>,
1217
- );
1218
- const sortedAliasTargets =
1219
- aliasTargets.length > 1
1220
- ? [...aliasTargets].sort(
1221
- (a, b) =>
1222
- computeConfidence(relPath, b.file, aliasFrom ?? null) -
1223
- computeConfidence(relPath, a.file, aliasFrom ?? null),
1224
- )
1225
- : aliasTargets;
1226
- for (const t of sortedAliasTargets) {
1227
- const edgeKey = `${caller.id}|${t.id}`;
1228
- if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1229
- const conf =
1230
- computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
1231
- if (conf > 0) {
1232
- ptsEdgeRows.set(edgeKey, allEdgeRows.length);
1233
- allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
1234
- }
1235
- }
1236
- }
1237
- }
1238
- }
1239
-
1240
- // Phase 8.3f: pts fallback for receiver calls via object-rest param bindings.
1241
- // Fires when `rest.prop()` is encountered and `rest` was seeded as `pts["rest.prop"]`
1242
- // by the object-rest dispatch chain (ObjectRestParamBinding + paramBinding + ObjectPropBinding).
1626
+ // Step 4: Phase 8.3f pts fallback for unresolved receiver calls (rest params).
1243
1627
  if (
1244
1628
  targets.length === 0 &&
1245
1629
  call.receiver &&
@@ -1249,39 +1633,22 @@ function buildFileCallEdges(
1249
1633
  call.receiver !== 'super' &&
1250
1634
  ptsMap
1251
1635
  ) {
1252
- const receiverKey = `${call.receiver}.${call.name}`;
1253
- if (ptsMap.has(receiverKey)) {
1254
- for (const alias of resolveViaPointsTo(receiverKey, ptsMap)) {
1255
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
1256
- lookup,
1257
- { name: alias },
1258
- relPath,
1259
- importedNames,
1260
- typeMap as Map<string, unknown>,
1261
- );
1262
- const sortedAliasTargets =
1263
- aliasTargets.length > 1
1264
- ? [...aliasTargets].sort(
1265
- (a, b) =>
1266
- computeConfidence(relPath, b.file, aliasFrom ?? null) -
1267
- computeConfidence(relPath, a.file, aliasFrom ?? null),
1268
- )
1269
- : aliasTargets;
1270
- for (const t of sortedAliasTargets) {
1271
- const edgeKey = `${caller.id}|${t.id}`;
1272
- if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1273
- const conf =
1274
- computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
1275
- if (conf > 0) {
1276
- ptsEdgeRows.set(edgeKey, allEdgeRows.length);
1277
- allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
1278
- }
1279
- }
1280
- }
1281
- }
1282
- }
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
+ );
1283
1649
  }
1284
1650
 
1651
+ // Step 5: Emit receiver edge for external (non-this/self/super) receivers.
1285
1652
  if (
1286
1653
  call.receiver &&
1287
1654
  !BUILTIN_RECEIVERS.has(call.receiver) &&
@@ -1296,56 +1663,45 @@ function buildFileCallEdges(
1296
1663
  relPath,
1297
1664
  typeMap as Map<string, unknown>,
1298
1665
  seenCallEdges,
1299
- importedNames,
1666
+ importArtifactNames ?? importedNames,
1300
1667
  );
1301
1668
  if (recv) {
1302
- 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
+ ]);
1303
1678
  }
1304
1679
  }
1305
1680
 
1306
- // Phase 8.5: CHA + RTA dispatch expansion.
1307
- // For `this`/`self`/`super` calls: resolve through the class hierarchy instead
1308
- // of relying solely on global name matching.
1309
- // For typed receiver calls: expand to all instantiated concrete implementations.
1681
+ // Step 6: Phase 8.5 CHA + RTA dispatch expansion.
1310
1682
  if (chaCtx && call.receiver) {
1311
- let chaTargets: ReadonlyArray<{ id: number; file: string }> = [];
1312
- let isTypedReceiverDispatch = false;
1313
- if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
1314
- chaTargets = resolveThisDispatch(
1315
- call.name,
1316
- caller.callerName,
1317
- call.receiver,
1318
- chaCtx,
1319
- lookup,
1320
- relPath,
1321
- );
1322
- } else if (!BUILTIN_RECEIVERS.has(call.receiver)) {
1323
- const typeEntry = typeMap.get(call.receiver);
1324
- const typeName = typeEntry
1325
- ? typeof typeEntry === 'string'
1326
- ? typeEntry
1327
- : (typeEntry as { type?: string }).type
1328
- : null;
1329
- if (typeName) {
1330
- chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
1331
- isTypedReceiverDispatch = true;
1332
- }
1333
- }
1334
- for (const t of chaTargets) {
1335
- const edgeKey = `${caller.id}|${t.id}`;
1336
- if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1337
- // Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
1338
- // — file proximity is not meaningful for virtual dispatch confidence.
1339
- // this/super dispatch keeps computeConfidence-based proximity scoring to
1340
- // match runPostNativeThisDispatch (native-orchestrator.ts).
1341
- const conf = isTypedReceiverDispatch
1342
- ? CHA_TYPED_DISPATCH_CONFIDENCE
1343
- : computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
1344
- if (conf > 0) {
1345
- seenCallEdges.add(edgeKey);
1346
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha']);
1347
- }
1348
- }
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]);
1349
1705
  }
1350
1706
  }
1351
1707
  }
@@ -1373,7 +1729,7 @@ function buildClassHierarchyEdges(
1373
1729
  );
1374
1730
  if (sourceRow) {
1375
1731
  for (const t of targetRows) {
1376
- allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null]);
1732
+ allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null, null]);
1377
1733
  }
1378
1734
  }
1379
1735
  }
@@ -1387,7 +1743,7 @@ function buildClassHierarchyEdges(
1387
1743
  );
1388
1744
  if (sourceRow) {
1389
1745
  for (const t of targetRows) {
1390
- allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null]);
1746
+ allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null, null]);
1391
1747
  }
1392
1748
  }
1393
1749
  }
@@ -1398,7 +1754,8 @@ function buildClassHierarchyEdges(
1398
1754
 
1399
1755
  /**
1400
1756
  * After native bulkInsertEdges (which does not write the technique column),
1401
- * 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.
1402
1759
  *
1403
1760
  * Rows with an explicit technique get a targeted UPDATE by (source_id, target_id).
1404
1761
  * The catch-all 'ts-native' tag is scoped to only the source_ids present in this
@@ -1419,6 +1776,9 @@ function applyEdgeTechniquesAfterNativeInsert(
1419
1776
  // Chunk to stay within SQLite's SQLITE_LIMIT_VARIABLE_NUMBER (999 on older builds).
1420
1777
  const CHUNK_SIZE = 500;
1421
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
+
1422
1782
  const tx = db.transaction(() => {
1423
1783
  if (taggedRows.length > 0) {
1424
1784
  const stmt = db.prepare(
@@ -1432,6 +1792,27 @@ function applyEdgeTechniquesAfterNativeInsert(
1432
1792
  db.prepare(
1433
1793
  `UPDATE edges SET technique = 'ts-native' WHERE kind = 'calls' AND technique IS NULL AND source_id IN (${placeholders})`,
1434
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]);
1435
1816
  }
1436
1817
  });
1437
1818
  tx();
@@ -1470,6 +1851,7 @@ function reconnectReverseDepEdges(ctx: PipelineContext): void {
1470
1851
  saved.confidence,
1471
1852
  saved.dynamic,
1472
1853
  saved.technique,
1854
+ saved.dynamicKind ?? null,
1473
1855
  ]);
1474
1856
  } else {
1475
1857
  // Target was removed or renamed in the changed file — edge is stale
@@ -1683,6 +2065,24 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
1683
2065
  buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows, chaCtx);
1684
2066
  }
1685
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
+
1686
2086
  // When using native edge insert, skip JS insert here — do it after tx commits.
1687
2087
  // Otherwise insert edges within this transaction for atomicity.
1688
2088
  const useNativeEdgeInsert = ctx.engineName === 'native' && !!ctx.nativeDb?.bulkInsertEdges;