@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 { computeConfidence } from '../../resolve.js';
15
16
  import { buildPointsToMap, resolveViaPointsTo } from '../../resolver/points-to.js';
16
17
  import { enrichTypeMapWithTsc } from '../../resolver/ts-resolver.js';
@@ -70,7 +71,7 @@ function emitTypeOnlySymbolEdges(ctx, imp, resolvedPath, fileNodeId, allEdgeRows
70
71
  }
71
72
  const candidates = ctx.nodesByNameAndFile.get(`${cleanName}|${targetFile}`);
72
73
  if (candidates && candidates.length > 0) {
73
- allEdgeRows.push([fileNodeId, candidates[0].id, 'imports-type', 1.0, 0, null]);
74
+ allEdgeRows.push([fileNodeId, candidates[0].id, 'imports-type', 1.0, 0, null, null]);
74
75
  }
75
76
  }
76
77
  }
@@ -84,7 +85,7 @@ function emitEdgesForImport(ctx, imp, fileNodeId, relPath, getNodeIdStmt, allEdg
84
85
  if (!targetRow)
85
86
  return;
86
87
  const edgeKind = importEdgeKind(imp);
87
- allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null]);
88
+ allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0, null, null]);
88
89
  if (imp.typeOnly) {
89
90
  emitTypeOnlySymbolEdges(ctx, imp, resolvedPath, fileNodeId, allEdgeRows);
90
91
  }
@@ -122,7 +123,7 @@ function buildBarrelEdges(ctx, imp, resolvedPath, fileNodeId, edgeKind, getNodeI
122
123
  : edgeKind === 'dynamic-imports'
123
124
  ? 'dynamic-imports'
124
125
  : 'imports';
125
- edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null]);
126
+ edgeRows.push([fileNodeId, actualRow.id, kind, 0.9, 0, null, null]);
126
127
  }
127
128
  }
128
129
  }
@@ -237,7 +238,7 @@ function buildImportEdgesNative(ctx, getNodeIdStmt, allEdgeRows, native) {
237
238
  const symbolNodes = collectSymbolNodes(ctx);
238
239
  const nativeEdges = native.buildImportEdges(files, resolvedImports, fileReexports, registry.ids, barrelFiles, ctx.rootDir, symbolNodes);
239
240
  for (const e of nativeEdges) {
240
- allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic, null]);
241
+ allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic, null, null]);
241
242
  }
242
243
  }
243
244
  // ── Phase 8.2: Cross-file return-type propagation ───────────────────────
@@ -377,6 +378,7 @@ function buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodes, native)
377
378
  e.confidence,
378
379
  e.dynamic,
379
380
  e.kind === 'calls' ? 'ts-native' : null,
381
+ e.dynamic_kind ?? null,
380
382
  ]);
381
383
  }
382
384
  }
@@ -443,7 +445,7 @@ function buildDefinePropertyPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLook
443
445
  const conf = computeConfidence(relPath, t.file, null);
444
446
  if (conf > 0) {
445
447
  seenByPair.add(edgeKey);
446
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native']);
448
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'ts-native', null]);
447
449
  }
448
450
  }
449
451
  }
@@ -519,7 +521,7 @@ function buildChaPostPass(ctx, getNodeIdStmt, allEdgeRows, chaCtx) {
519
521
  // Tag super-dispatch edges distinctly so runChaPostPass can exclude them
520
522
  // from further CHA expansion (super calls are not virtual dispatch).
521
523
  const technique = call.receiver === 'super' ? 'super-dispatch' : 'cha';
522
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, technique]);
524
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, technique, null]);
523
525
  }
524
526
  }
525
527
  }
@@ -594,7 +596,11 @@ function buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows, chaCtx) {
594
596
  }
595
597
  const seenCallEdges = new Set();
596
598
  const ptsMap = buildPointsToMapForFile(symbols, importedNames);
597
- buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCallEdges, lookup, allEdgeRows, typeMap, ptsMap, chaCtx);
599
+ // Build the import-artifact name set: importedNames plus CJS require bindings.
600
+ // Used only by resolveReceiverEdge to distinguish local definitions from CJS
601
+ // import shadows — does NOT affect call-target resolution or DB edges (#1661).
602
+ const importArtifactNames = buildImportArtifactNames(importedNames, symbols, ctx, relPath, rootDir);
603
+ buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCallEdges, lookup, allEdgeRows, typeMap, ptsMap, chaCtx, importArtifactNames);
598
604
  buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows);
599
605
  }
600
606
  }
@@ -634,6 +640,33 @@ function buildImportedNamesMap(ctx, relPath, symbols, rootDir) {
634
640
  }
635
641
  return importedNames;
636
642
  }
643
+ /**
644
+ * Build a map of all names that are import artifacts in this file — includes
645
+ * both ES module imports (already in importedNames) and CJS require destructuring
646
+ * bindings (`const { X } = require('./path')`). Used exclusively by resolveReceiverEdge
647
+ * to classify same-file function-kind nodes as import artifacts vs. local definitions.
648
+ * Does NOT affect call resolution or DB edge creation (#1661).
649
+ */
650
+ function buildImportArtifactNames(importedNames, symbols, ctx, relPath, rootDir) {
651
+ if (!symbols.cjsRequireBindings?.length)
652
+ return importedNames;
653
+ const combined = new Map(importedNames);
654
+ const traceBarrel = (resolvedPath, cleanName) => {
655
+ if (!isBarrelFile(ctx, resolvedPath))
656
+ return resolvedPath;
657
+ const actual = resolveBarrelExportCached(ctx, resolvedPath, cleanName);
658
+ return actual ?? resolvedPath;
659
+ };
660
+ for (const binding of symbols.cjsRequireBindings) {
661
+ const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), binding.source);
662
+ for (const name of binding.names) {
663
+ if (!combined.has(name)) {
664
+ combined.set(name, traceBarrel(resolvedPath, name));
665
+ }
666
+ }
667
+ }
668
+ return combined;
669
+ }
637
670
  function makeContextLookup(ctx, getNodeIdStmt) {
638
671
  return {
639
672
  byNameAndFile: (name, file) => ctx.nodesByNameAndFile.get(`${name}|${file}`) ?? [],
@@ -702,209 +735,419 @@ function buildDefinitionParamsMap(definitions) {
702
735
  }
703
736
  return map;
704
737
  }
705
- function buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCallEdges, lookup, allEdgeRows, typeMap, ptsMap, chaCtx) {
706
- // Tracks edges that were inserted by the pts fallback (edgeKey → allEdgeRows index).
707
- // Kept separate from seenCallEdges so that a subsequent direct-call edge for the same
708
- // caller→target pair can upgrade the confidence in-place rather than being silently
709
- // dropped by the dedup guard. Once upgraded, the key moves to seenCallEdges and is
710
- // no longer tracked here.
711
- const ptsEdgeRows = new Map();
712
- // Pre-compute the set of names that appear as lhs in fnRefBindings so that
713
- // case (c) of the pts gate below only fires for names that are genuine
714
- // bind/alias entries, not for every locally-defined function or import that
715
- // buildPointsToMap seeds with a self-pointing entry.
716
- const fnRefBindingLhs = new Set(symbols.fnRefBindings?.map((b) => b.lhs) ?? []);
717
- for (const call of symbols.calls) {
718
- if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver))
719
- continue;
720
- const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
721
- const isDynamic = call.dynamic ? 1 : 0;
722
- let { targets, importedFrom } = resolveCallTargets(lookup, call, relPath, importedNames, typeMap, caller.callerName);
723
- // Same-class `this.method()` fallback: when the call receiver is `this` and
724
- // resolveCallTargets found nothing, derive the enclosing class name from the
725
- // caller (e.g. `Logger.info` → class prefix `Logger`) and retry with the
726
- // qualified method name `Logger._write`. This mirrors what the native Rust
727
- // engine does implicitly via its class-scoped symbol table.
728
- // NOTE: restricted to `this` only — `super.method()` targets a parent class,
729
- // not the enclosing class, so qualifying with the child class name would
730
- // produce a false edge when the child also defines a same-named method.
731
- if (targets.length === 0 && call.receiver === 'this' && caller.callerName != null) {
738
+ // ── Per-call resolution helpers ─────────────────────────────────────────
739
+ /**
740
+ * Resolve targets for a single call site with all JS-path fallbacks applied.
741
+ *
742
+ * Runs in order:
743
+ * 1. Primary resolution via `resolveCallTargets` (importedNames + typeMap).
744
+ * 2. Same-class `this.method()` fallback (non-super receivers only).
745
+ * 3. Same-class bare-call fallback for non-JS/TS class-scoped languages.
746
+ * 4. Object.defineProperty accessor fallback (this-calls inside getter/setter).
747
+ *
748
+ * Returns the resolved targets array and the importedFrom hint for confidence scoring.
749
+ */
750
+ function resolveFallbackTargets(call, caller, relPath, importedNames, lookup, typeMap, definePropertyReceivers) {
751
+ // RES-4: Kotlin member callable reference — `Greeter::greet` emits
752
+ // { name: 'greet', receiver: 'Greeter', dynamicKind: 'reflection' }.
753
+ // The receiver is the class qualifier (not a typeMap variable), so
754
+ // resolveCallTargets would find a same-named top-level function via
755
+ // byNameAndFile('greet', relPath) before the qualified form is tried.
756
+ // Prefer `Greeter.greet` in the same file first; fall through to the
757
+ // normal path only when no qualified match exists.
758
+ let preQualifiedTargets = [];
759
+ if (call.dynamicKind === 'reflection' &&
760
+ call.receiver &&
761
+ !call.keyExpr &&
762
+ !isModuleScopedLanguage(relPath)) {
763
+ preQualifiedTargets = lookup
764
+ .byNameAndFile(`${call.receiver}.${call.name}`, relPath)
765
+ .filter((n) => n.kind === 'method' || n.kind === 'function');
766
+ }
767
+ let { targets, importedFrom } = preQualifiedTargets.length > 0
768
+ ? {
769
+ targets: preQualifiedTargets,
770
+ importedFrom: undefined,
771
+ }
772
+ : resolveCallTargets(lookup, call, relPath, importedNames, typeMap, caller.callerName);
773
+ // Same-class `this.method()` fallback: when the call receiver is `this` and
774
+ // resolveCallTargets found nothing, derive the enclosing class name from the
775
+ // caller (e.g. `Logger.info` → class prefix `Logger`) and retry with the
776
+ // qualified method name `Logger._write`. This mirrors what the native Rust
777
+ // engine does implicitly via its class-scoped symbol table.
778
+ // NOTE: restricted to `this` only — `super.method()` targets a parent class,
779
+ // not the enclosing class, so qualifying with the child class name would
780
+ // produce a false edge when the child also defines a same-named method.
781
+ if (targets.length === 0 && call.receiver === 'this' && caller.callerName != null) {
782
+ const lastDot = caller.callerName.lastIndexOf('.');
783
+ if (lastDot > 0) {
784
+ const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
785
+ const className = caller.callerName.slice(prevDot + 1, lastDot);
786
+ const qualified = lookup
787
+ .byNameAndFile(`${className}.${call.name}`, relPath)
788
+ .filter((n) => n.kind === 'method');
789
+ if (qualified.length > 0)
790
+ targets = qualified;
791
+ }
792
+ }
793
+ // Same-class bare-call fallback: when a no-receiver call can't be resolved
794
+ // globally, try the caller's own class as a qualifier. Handles C# static
795
+ // sibling calls: `IsValidEmail()` inside `Validators.ValidateUser` resolves
796
+ // to `Validators.IsValidEmail`. Skipped for JS/TS where bare calls are
797
+ // module-scoped, not class-scoped.
798
+ if (targets.length === 0 &&
799
+ !call.receiver &&
800
+ caller.callerName != null &&
801
+ !isModuleScopedLanguage(relPath)) {
802
+ const lastDot = caller.callerName.lastIndexOf('.');
803
+ if (lastDot > 0) {
804
+ const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
805
+ const className = caller.callerName.slice(prevDot + 1, lastDot);
806
+ const qualified = lookup
807
+ .byNameAndFile(`${className}.${call.name}`, relPath)
808
+ .filter((n) => n.kind === 'method');
809
+ if (qualified.length > 0)
810
+ targets = qualified;
811
+ }
812
+ }
813
+ // RES-3: reflection with literal method name — JVM getMethod("name") / invokeMethod("name").
814
+ // Java/Scala/Groovy methods are stored as class-qualified names (e.g. Reflection.greet),
815
+ // so lookup.byNameAndFile('greet', relPath) finds nothing. When dynamicKind='reflection'
816
+ // and keyExpr is set (a string-literal method name was captured), try the qualified form:
817
+ // 1. typeMap[receiver] → resolvedType → lookup `resolvedType.keyExpr` (type-annotated local)
818
+ // 2. callerName class prefix → `CallerClass.keyExpr` (same-class sibling, e.g. Groovy obj)
819
+ // Scoped to non-JS/TS files to avoid interfering with the JS reflection path.
820
+ if (targets.length === 0 &&
821
+ call.dynamicKind === 'reflection' &&
822
+ call.keyExpr &&
823
+ call.receiver &&
824
+ !isModuleScopedLanguage(relPath)) {
825
+ const typeEntry = typeMap.get(call.receiver);
826
+ const resolvedType = typeEntry
827
+ ? typeof typeEntry === 'string'
828
+ ? typeEntry
829
+ : typeEntry.type
830
+ : null;
831
+ if (resolvedType) {
832
+ const qualified = lookup
833
+ .byNameAndFile(`${resolvedType}.${call.keyExpr}`, relPath)
834
+ .filter((n) => n.kind === 'method' || n.kind === 'function');
835
+ if (qualified.length > 0)
836
+ targets = qualified;
837
+ }
838
+ if (targets.length === 0 && caller.callerName != null) {
732
839
  const lastDot = caller.callerName.lastIndexOf('.');
733
840
  if (lastDot > 0) {
734
841
  const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
735
- const className = caller.callerName.slice(prevDot + 1, lastDot);
736
- const qualifiedName = `${className}.${call.name}`;
842
+ const callerClass = caller.callerName.slice(prevDot + 1, lastDot);
737
843
  const qualified = lookup
738
- .byNameAndFile(qualifiedName, relPath)
739
- .filter((n) => n.kind === 'method');
740
- if (qualified.length > 0) {
844
+ .byNameAndFile(`${callerClass}.${call.keyExpr}`, relPath)
845
+ .filter((n) => n.kind === 'method' || n.kind === 'function');
846
+ if (qualified.length > 0)
741
847
  targets = qualified;
742
- }
743
848
  }
744
849
  }
745
- // Same-class bare-call fallback: when a no-receiver call can't be resolved
746
- // globally, try the caller's own class as a qualifier. Handles C# static
747
- // sibling calls: `IsValidEmail()` inside `Validators.ValidateUser` resolves
748
- // to `Validators.IsValidEmail`. Skipped for JS/TS where bare calls are
749
- // module-scoped, not class-scoped.
750
- if (targets.length === 0 &&
751
- !call.receiver &&
752
- caller.callerName != null &&
753
- !isModuleScopedLanguage(relPath)) {
754
- const lastDot = caller.callerName.lastIndexOf('.');
755
- if (lastDot > 0) {
756
- const prevDot = caller.callerName.lastIndexOf('.', lastDot - 1);
757
- const className = caller.callerName.slice(prevDot + 1, lastDot);
758
- const qualifiedName = `${className}.${call.name}`;
759
- const qualified = lookup
760
- .byNameAndFile(qualifiedName, relPath)
761
- .filter((n) => n.kind === 'method');
762
- if (qualified.length > 0) {
763
- targets = qualified;
764
- }
850
+ }
851
+ // Object.defineProperty accessor fallback: when a function is registered as
852
+ // a getter/setter via `Object.defineProperty(obj, "bar", { get: getter })`,
853
+ // calls to `this.X()` inside `getter` resolve against `obj` (this === obj
854
+ // when the accessor is invoked). If the same-class fallback above found
855
+ // nothing, try treating `obj` as the receiver and look up `obj.X` in the
856
+ // typeMap, or fall back to a same-file lookup of any definition named X
857
+ // that belongs to the object literal or its type.
858
+ if (targets.length === 0 &&
859
+ call.receiver === 'this' &&
860
+ caller.callerName != null &&
861
+ definePropertyReceivers) {
862
+ const receiverVarName = definePropertyReceivers.get(caller.callerName);
863
+ if (receiverVarName) {
864
+ const typeEntry = typeMap.get(receiverVarName);
865
+ const typeName = typeEntry
866
+ ? typeof typeEntry === 'string'
867
+ ? typeEntry
868
+ : typeEntry.type
869
+ : null;
870
+ if (typeName) {
871
+ const qualified = lookup.byNameAndFile(`${typeName}.${call.name}`, relPath);
872
+ if (qualified.length > 0)
873
+ targets = [...qualified];
874
+ }
875
+ // If still no targets, search for any definition named `call.name` in
876
+ // the same file — handles plain object literals where the method isn't
877
+ // qualified (e.g. `const obj = { baz() {} }` defines `baz` directly).
878
+ // Note: this is intentionally broad — it matches any same-file definition
879
+ // with the called name, not just members of the receiver object. This is
880
+ // the same behaviour used by the native post-pass path (buildDefinePropertyPostPass).
881
+ if (targets.length === 0) {
882
+ const sameFile = lookup.byNameAndFile(call.name, relPath);
883
+ if (sameFile.length > 0)
884
+ targets = [...sameFile];
765
885
  }
766
886
  }
767
- // Object.defineProperty accessor fallback: when a function is registered as
768
- // a getter/setter via `Object.defineProperty(obj, "bar", { get: getter })`,
769
- // calls to `this.X()` inside `getter` resolve against `obj` (this === obj
770
- // when the accessor is invoked). If the same-class fallback above found
771
- // nothing, try treating `obj` as the receiver and look up `obj.X` in the
772
- // typeMap, or fall back to a same-file lookup of any definition named X
773
- // that belongs to the object literal or its type.
774
- if (targets.length === 0 &&
775
- call.receiver === 'this' &&
776
- caller.callerName != null &&
777
- symbols.definePropertyReceivers) {
778
- const receiverVarName = symbols.definePropertyReceivers.get(caller.callerName);
779
- if (receiverVarName) {
780
- // Try typeMap lookup for receiver.methodName
781
- const typeEntry = typeMap.get(receiverVarName);
782
- const typeName = typeEntry
783
- ? typeof typeEntry === 'string'
784
- ? typeEntry
785
- : typeEntry.type
786
- : null;
787
- if (typeName) {
788
- const qualifiedName = `${typeName}.${call.name}`;
789
- const qualified = lookup.byNameAndFile(qualifiedName, relPath);
790
- if (qualified.length > 0) {
791
- targets = [...qualified];
792
- }
793
- }
794
- // If still no targets, search for any definition named `call.name` in
795
- // the same file — handles plain object literals where the method isn't
796
- // qualified (e.g. `const obj = { baz() {} }` defines `baz` directly).
797
- // Note: this is intentionally broad — it matches any same-file definition
798
- // with the called name, not just members of the receiver object. This is
799
- // the same behaviour used by the native post-pass path (buildDefinePropertyPostPass).
800
- if (targets.length === 0) {
801
- const sameFile = lookup.byNameAndFile(call.name, relPath);
802
- if (sameFile.length > 0) {
803
- targets = [...sameFile];
804
- }
887
+ }
888
+ return { targets, importedFrom };
889
+ }
890
+ /**
891
+ * Emit direct-call edges for the resolved targets of a single call site.
892
+ *
893
+ * Sorts targets by confidence descending first, then for each target:
894
+ * - Skips self-edges and already-seen edges.
895
+ * - If a pts edge already exists for this pair, upgrades it in-place to
896
+ * direct-call confidence and promotes to seenCallEdges.
897
+ * - If a dyn=0 edge already exists and the incoming call has an explicit
898
+ * dynamicKind (e.g. 'reflection' for bare decorators), upgrades the
899
+ * existing row to dyn=1 in-place so the semantic classification wins.
900
+ * - Otherwise records a new `calls` edge with `ts-native` technique.
901
+ */
902
+ function emitDirectCallEdgesForCall(caller, targets, importedFrom, isDynamic, hasDynamicKind, relPath, seenCallEdges, ptsEdgeRows, allEdgeRows, dynZeroEdgeRows) {
903
+ // Sort targets by confidence descending before emitting edges.
904
+ // For multi-target calls with duplicate (source_id, target_id) pairs the
905
+ // stored confidence depends on which duplicate is processed last — sorting
906
+ // here guarantees the highest-confidence target wins on dedup, matching the
907
+ // native engine's sort_targets_by_confidence call in build_edges.rs.
908
+ const sorted = targets.length > 1
909
+ ? [...targets].sort((a, b) => computeConfidence(relPath, b.file, importedFrom ?? null) -
910
+ computeConfidence(relPath, a.file, importedFrom ?? null))
911
+ : targets;
912
+ for (const t of sorted) {
913
+ const edgeKey = `${caller.id}|${t.id}`;
914
+ if (t.id === caller.id)
915
+ continue;
916
+ const confidence = computeConfidence(relPath, t.file, importedFrom ?? null);
917
+ if (seenCallEdges.has(edgeKey)) {
918
+ // Edge already emitted. If the incoming call carries an explicit semantic
919
+ // dynamic classification (dynamicKind set e.g. 'reflection' for bare
920
+ // decorators) and the existing edge was recorded with dyn=0, upgrade it
921
+ // in-place so the more specific classification wins.
922
+ // Generic dynamic=true without dynamicKind (alias/callback calls) does
923
+ // NOT override dyn=0 to avoid false positives on f.call/f.bind patterns.
924
+ if (isDynamic === 1 && hasDynamicKind && dynZeroEdgeRows) {
925
+ const dynZeroIdx = dynZeroEdgeRows.get(edgeKey);
926
+ if (dynZeroIdx !== undefined) {
927
+ const row = allEdgeRows[dynZeroIdx];
928
+ if (row)
929
+ row[4] = 1;
930
+ dynZeroEdgeRows.delete(edgeKey);
805
931
  }
806
932
  }
933
+ continue;
807
934
  }
808
- // Sort targets by confidence descending before emitting edges.
809
- // For multi-target calls with duplicate (source_id, target_id) pairs the
810
- // stored confidence depends on which duplicate is processed last sorting
811
- // here guarantees the highest-confidence target wins on dedup, matching the
812
- // native engine's sort_targets_by_confidence call in build_edges.rs.
813
- if (targets.length > 1) {
814
- targets = [...targets].sort((a, b) => computeConfidence(relPath, b.file, importedFrom ?? null) -
815
- computeConfidence(relPath, a.file, importedFrom ?? null));
935
+ const ptsIdx = ptsEdgeRows.get(edgeKey);
936
+ if (ptsIdx !== undefined) {
937
+ // A pts-resolved edge already exists for this caller→target pair with a
938
+ // penalised confidence. Upgrade it to the direct-call confidence in-place,
939
+ // then promote to seenCallEdges so no further processing is needed.
940
+ const ptsRow = allEdgeRows[ptsIdx];
941
+ if (ptsRow) {
942
+ ptsRow[3] = confidence;
943
+ ptsRow[4] = isDynamic; // upgrade is_dynamic: direct call overrides the pts-alias dynamic flag
944
+ ptsRow[5] = 'ts-native'; // promoted from pts to direct-call resolution
945
+ }
946
+ ptsEdgeRows.delete(edgeKey);
947
+ seenCallEdges.add(edgeKey);
816
948
  }
817
- for (const t of targets) {
949
+ else {
950
+ seenCallEdges.add(edgeKey);
951
+ const newIdx = allEdgeRows.length;
952
+ allEdgeRows.push([caller.id, t.id, 'calls', confidence, isDynamic, 'ts-native', null]);
953
+ // Track dyn=0 edges so a later dyn=1+dynamicKind call for the same pair
954
+ // can upgrade them (e.g. bare decorator after call-expression decorator).
955
+ if (isDynamic === 0 && dynZeroEdgeRows) {
956
+ dynZeroEdgeRows.set(edgeKey, newIdx);
957
+ }
958
+ }
959
+ }
960
+ }
961
+ /**
962
+ * Phase 8.3 / 8.3c / bind: emit pts-resolved edges for unresolved no-receiver calls.
963
+ *
964
+ * Fires for three cases:
965
+ * (a) dynamic=true: alias calls emitted by extractCallbackReferenceCalls.
966
+ * Looks up `call.name` directly (alias entries are flat-keyed).
967
+ * (b) non-dynamic: parameter variable calls (fn() where fn is a param).
968
+ * Looks up the scoped key `callerName::call.name` to avoid spurious
969
+ * edges from same-named parameters across different functions.
970
+ * (c) non-dynamic: module-level alias bindings — `f = fn.bind(ctx)` or
971
+ * `const f = handler` — where pts('f') was seeded by fnRefBindings.
972
+ * Checked against fnRefBindingLhs so case (c) only fires for genuine
973
+ * bind/alias entries and never for self-seeded local definitions.
974
+ *
975
+ * Pts edges are added to ptsEdgeRows (not seenCallEdges) so that a later
976
+ * direct call to the same target can upgrade confidence rather than being
977
+ * silently dropped by the dedup guard.
978
+ */
979
+ function emitPtsNoReceiverEdges(call, caller, isDynamic, relPath, importedNames, lookup, typeMap, ptsMap, fnRefBindingLhs, seenCallEdges, ptsEdgeRows, allEdgeRows) {
980
+ const scopedPtsKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
981
+ // Module-level calls (callerName === null) use the '<module>' sentinel emitted by
982
+ // extractSpreadForOfWalk for top-level for-of loops. Look it up as a fallback so
983
+ // that `for (const f of arr) { f(); }` at module scope resolves correctly.
984
+ const modulePtsKey = caller.callerName === null && ptsMap.has(`<module>::${call.name}`)
985
+ ? `<module>::${call.name}`
986
+ : null;
987
+ const flatPtsKey = !call.dynamic && fnRefBindingLhs.has(call.name) && ptsMap.has(call.name) ? call.name : null;
988
+ if (!(call.dynamic ||
989
+ (scopedPtsKey != null && ptsMap.has(scopedPtsKey)) ||
990
+ modulePtsKey != null ||
991
+ flatPtsKey != null))
992
+ return;
993
+ const ptsLookupName = call.dynamic
994
+ ? call.name
995
+ : scopedPtsKey != null && ptsMap.has(scopedPtsKey)
996
+ ? scopedPtsKey
997
+ : modulePtsKey != null
998
+ ? modulePtsKey
999
+ : // flatPtsKey != null is guaranteed: if neither call.dynamic nor scopedPtsKey
1000
+ // nor modulePtsKey matched, flatPtsKey must be non-null.
1001
+ flatPtsKey;
1002
+ for (const alias of resolveViaPointsTo(ptsLookupName, ptsMap)) {
1003
+ // Resolve the concrete alias target. Only `name` is needed here — receiver
1004
+ // and line are not relevant for alias resolution (we are looking up the
1005
+ // aliased function by name, not dispatching a method call).
1006
+ const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
1007
+ const sortedAliasTargets = aliasTargets.length > 1
1008
+ ? [...aliasTargets].sort((a, b) => computeConfidence(relPath, b.file, aliasFrom ?? null) -
1009
+ computeConfidence(relPath, a.file, aliasFrom ?? null))
1010
+ : aliasTargets;
1011
+ for (const t of sortedAliasTargets) {
818
1012
  const edgeKey = `${caller.id}|${t.id}`;
819
- if (t.id !== caller.id) {
820
- const confidence = computeConfidence(relPath, t.file, importedFrom ?? null);
821
- if (seenCallEdges.has(edgeKey))
822
- continue;
823
- const ptsIdx = ptsEdgeRows.get(edgeKey);
824
- if (ptsIdx !== undefined) {
825
- // A pts-resolved edge already exists for this caller→target pair with a
826
- // penalised confidence. Upgrade it to the direct-call confidence in-place,
827
- // then promote to seenCallEdges so no further processing is needed.
828
- const ptsRow = allEdgeRows[ptsIdx];
829
- if (ptsRow) {
830
- ptsRow[3] = confidence;
831
- ptsRow[4] = isDynamic; // upgrade is_dynamic: direct call overrides the pts-alias dynamic flag
832
- ptsRow[5] = 'ts-native'; // promoted from pts to direct-call resolution
833
- }
834
- ptsEdgeRows.delete(edgeKey);
835
- seenCallEdges.add(edgeKey);
1013
+ if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1014
+ const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
1015
+ if (conf > 0) {
1016
+ ptsEdgeRows.set(edgeKey, allEdgeRows.length);
1017
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to', null]);
836
1018
  }
837
- else {
838
- seenCallEdges.add(edgeKey);
839
- allEdgeRows.push([caller.id, t.id, 'calls', confidence, isDynamic, 'ts-native']);
1019
+ }
1020
+ }
1021
+ }
1022
+ }
1023
+ /**
1024
+ * Phase 8.3f: emit pts-resolved edges for unresolved receiver calls via
1025
+ * object-rest param bindings.
1026
+ *
1027
+ * Fires when `rest.prop()` is encountered and `rest` was seeded as
1028
+ * `pts["rest.prop"]` by the object-rest dispatch chain
1029
+ * (ObjectRestParamBinding + paramBinding + ObjectPropBinding).
1030
+ */
1031
+ function emitPtsReceiverEdges(call, caller, isDynamic, relPath, importedNames, lookup, typeMap, ptsMap, seenCallEdges, ptsEdgeRows, allEdgeRows) {
1032
+ const receiverKey = `${call.receiver}.${call.name}`;
1033
+ if (!ptsMap.has(receiverKey))
1034
+ return;
1035
+ for (const alias of resolveViaPointsTo(receiverKey, ptsMap)) {
1036
+ const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
1037
+ const sortedAliasTargets = aliasTargets.length > 1
1038
+ ? [...aliasTargets].sort((a, b) => computeConfidence(relPath, b.file, aliasFrom ?? null) -
1039
+ computeConfidence(relPath, a.file, aliasFrom ?? null))
1040
+ : aliasTargets;
1041
+ for (const t of sortedAliasTargets) {
1042
+ const edgeKey = `${caller.id}|${t.id}`;
1043
+ if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1044
+ const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
1045
+ if (conf > 0) {
1046
+ ptsEdgeRows.set(edgeKey, allEdgeRows.length);
1047
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to', null]);
840
1048
  }
841
1049
  }
842
1050
  }
843
- // Phase 8.3 / 8.3c / bind: points-to fallback for unresolved calls.
844
- // Fires for three cases:
845
- // (a) dynamic=true: alias calls emitted by extractCallbackReferenceCalls.
846
- // Looks up `call.name` directly (alias entries are flat-keyed).
847
- // (b) non-dynamic: parameter variable calls (fn() where fn is a param).
848
- // Looks up the scoped key `callerName::call.name` to avoid spurious
849
- // edges from same-named parameters across different functions.
850
- // (c) non-dynamic: module-level alias bindings — `f = fn.bind(ctx)` or
851
- // `const f = handler` where pts('f') was seeded by fnRefBindings.
852
- // Checked against fnRefBindingLhs (the pre-computed set of lhs names from
853
- // fnRefBindings) rather than the full ptsMap, so case (c) only fires for
854
- // genuine bind/alias entries and never for self-seeded local definitions.
855
- // Confidence is penalised by one hop to reflect the extra indirection.
856
- //
857
- // Note: pts edges are added to ptsEdgeRows (not seenCallEdges) so that a later
858
- // direct call to the same target in the same function body can upgrade confidence
859
- // rather than being silently dropped by the dedup guard.
860
- const scopedPtsKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
861
- // Module-level calls (callerName === null) use the '<module>' sentinel emitted by
862
- // extractSpreadForOfWalk for top-level for-of loops. Look it up as a fallback so
863
- // that `for (const f of arr) { f(); }` at module scope resolves correctly.
864
- const modulePtsKey = caller.callerName === null && ptsMap?.has(`<module>::${call.name}`)
865
- ? `<module>::${call.name}`
1051
+ }
1052
+ }
1053
+ /**
1054
+ * Phase 8.5: emit CHA + RTA dispatch edges for a single call site.
1055
+ *
1056
+ * For `this`/`self`/`super` calls: resolve through the class hierarchy.
1057
+ * For typed receiver calls: expand to all instantiated concrete implementations.
1058
+ */
1059
+ function emitChaCallEdgesForCall(call, caller, relPath, typeMap, lookup, chaCtx, seenCallEdges, ptsEdgeRows, allEdgeRows) {
1060
+ let chaTargets = [];
1061
+ let isTypedReceiverDispatch = false;
1062
+ if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
1063
+ chaTargets = resolveThisDispatch(call.name, caller.callerName, call.receiver, chaCtx, lookup, relPath);
1064
+ }
1065
+ else if (!BUILTIN_RECEIVERS.has(call.receiver)) {
1066
+ const typeEntry = typeMap.get(call.receiver);
1067
+ const typeName = typeEntry
1068
+ ? typeof typeEntry === 'string'
1069
+ ? typeEntry
1070
+ : typeEntry.type
866
1071
  : null;
867
- const flatPtsKey = !call.dynamic && fnRefBindingLhs.has(call.name) && ptsMap?.has(call.name) ? call.name : null;
868
- if (targets.length === 0 &&
869
- !call.receiver &&
870
- ptsMap &&
871
- (call.dynamic ||
872
- (scopedPtsKey != null && ptsMap.has(scopedPtsKey)) ||
873
- modulePtsKey != null ||
874
- flatPtsKey != null)) {
875
- const ptsLookupName = call.dynamic
876
- ? call.name
877
- : scopedPtsKey != null && ptsMap.has(scopedPtsKey)
878
- ? scopedPtsKey
879
- : modulePtsKey != null
880
- ? modulePtsKey
881
- : // flatPtsKey != null is guaranteed by the outer if condition: if neither
882
- // call.dynamic nor scopedPtsKey nor modulePtsKey matched, flatPtsKey must be non-null.
883
- flatPtsKey;
884
- for (const alias of resolveViaPointsTo(ptsLookupName, ptsMap)) {
885
- // Resolve the concrete alias target. Only `name` is needed here — receiver
886
- // and line are not relevant for alias resolution (we are looking up the
887
- // aliased function by name, not dispatching a method call).
888
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
889
- const sortedAliasTargets = aliasTargets.length > 1
890
- ? [...aliasTargets].sort((a, b) => computeConfidence(relPath, b.file, aliasFrom ?? null) -
891
- computeConfidence(relPath, a.file, aliasFrom ?? null))
892
- : aliasTargets;
893
- for (const t of sortedAliasTargets) {
894
- const edgeKey = `${caller.id}|${t.id}`;
895
- if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
896
- const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
897
- if (conf > 0) {
898
- ptsEdgeRows.set(edgeKey, allEdgeRows.length);
899
- allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
900
- }
901
- }
902
- }
1072
+ if (typeName) {
1073
+ chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
1074
+ isTypedReceiverDispatch = true;
1075
+ }
1076
+ }
1077
+ for (const t of chaTargets) {
1078
+ const edgeKey = `${caller.id}|${t.id}`;
1079
+ if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
1080
+ // Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
1081
+ // — file proximity is not meaningful for virtual dispatch confidence.
1082
+ // this/super dispatch keeps computeConfidence-based proximity scoring to
1083
+ // match runPostNativeThisDispatch (native-orchestrator.ts).
1084
+ const conf = isTypedReceiverDispatch
1085
+ ? CHA_TYPED_DISPATCH_CONFIDENCE
1086
+ : computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
1087
+ if (conf > 0) {
1088
+ seenCallEdges.add(edgeKey);
1089
+ allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha', null]);
903
1090
  }
904
1091
  }
905
- // Phase 8.3f: pts fallback for receiver calls via object-rest param bindings.
906
- // Fires when `rest.prop()` is encountered and `rest` was seeded as `pts["rest.prop"]`
907
- // by the object-rest dispatch chain (ObjectRestParamBinding + paramBinding + ObjectPropBinding).
1092
+ }
1093
+ }
1094
+ /**
1095
+ * Dynamic kinds that cannot be resolved statically — emit a sink edge to the
1096
+ * file node instead of silently dropping the call site. confidence=0.0 keeps
1097
+ * these below DEFAULT_MIN_CONFIDENCE so they never appear in normal query results.
1098
+ * Includes reflection so that Reflect.apply/getMethod/callable-ref calls whose
1099
+ * target is not found in the codebase still produce a visible sink edge.
1100
+ */
1101
+ const FLAG_ONLY_KINDS = new Set([
1102
+ 'eval',
1103
+ 'computed-key',
1104
+ 'reflection',
1105
+ 'unresolved-dynamic',
1106
+ ]);
1107
+ /**
1108
+ * Build call edges for all calls in a single file (WASM/JS engine path).
1109
+ *
1110
+ * Iterates over `symbols.calls` and dispatches each call through the full
1111
+ * JS resolution cascade:
1112
+ * 1. `resolveFallbackTargets` — primary + class-fallback + defineProperty fallback
1113
+ * 2. `emitDirectCallEdgesForCall` — emit direct-call edges (upgrading any pts pair)
1114
+ * 3. `emitPtsNoReceiverEdges` — Phase 8.3/8.3c pts fallback for no-receiver calls
1115
+ * 4. `emitPtsReceiverEdges` — Phase 8.3f pts fallback for rest-param receiver calls
1116
+ * 5. Inline `resolveReceiverEdge` — emit `receiver` edge for external receivers
1117
+ * 6. `emitChaCallEdgesForCall` — Phase 8.5 CHA + RTA dispatch expansion
1118
+ * 7. Sink edge for flag-only dynamic kinds (eval, computed-key, reflection, unresolved-dynamic)
1119
+ */
1120
+ function buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCallEdges, lookup, allEdgeRows, typeMap, ptsMap, chaCtx, importArtifactNames) {
1121
+ // Tracks edges that were inserted by the pts fallback (edgeKey → allEdgeRows index).
1122
+ // Kept separate from seenCallEdges so that a subsequent direct-call edge for the same
1123
+ // caller→target pair can upgrade the confidence in-place rather than being silently
1124
+ // dropped by the dedup guard. Once upgraded, the key moves to seenCallEdges and is
1125
+ // no longer tracked here.
1126
+ const ptsEdgeRows = new Map();
1127
+ // Tracks direct-call edges emitted with dyn=0 (edgeKey → allEdgeRows index).
1128
+ // When a later call to the same target has dyn=1 (e.g. a bare decorator `@Log`
1129
+ // processed after the call-expression `@Log()` in the query path), the existing
1130
+ // dyn=0 row is upgraded in-place so the more specific dynamic classification wins.
1131
+ const dynZeroEdgeRows = new Map();
1132
+ // Pre-compute the set of names that appear as lhs in fnRefBindings so that
1133
+ // case (c) of the pts gate below only fires for names that are genuine
1134
+ // bind/alias entries, not for every locally-defined function or import that
1135
+ // buildPointsToMap seeds with a self-pointing entry.
1136
+ const fnRefBindingLhs = new Set(symbols.fnRefBindings?.map((b) => b.lhs) ?? []);
1137
+ for (const call of symbols.calls) {
1138
+ if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver))
1139
+ continue;
1140
+ const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
1141
+ const isDynamic = call.dynamic ? 1 : 0;
1142
+ // Step 1: Resolve targets with all JS-path fallbacks.
1143
+ const { targets, importedFrom } = resolveFallbackTargets(call, caller, relPath, importedNames, lookup, typeMap, symbols.definePropertyReceivers);
1144
+ // Step 2: Emit direct-call edges (upgrades any pending pts edge in-place).
1145
+ emitDirectCallEdgesForCall(caller, targets, importedFrom, isDynamic, !!call.dynamicKind, relPath, seenCallEdges, ptsEdgeRows, allEdgeRows, dynZeroEdgeRows);
1146
+ // Step 3: Phase 8.3/8.3c pts fallback for unresolved no-receiver calls.
1147
+ if (targets.length === 0 && !call.receiver && ptsMap) {
1148
+ emitPtsNoReceiverEdges(call, caller, isDynamic, relPath, importedNames, lookup, typeMap, ptsMap, fnRefBindingLhs, seenCallEdges, ptsEdgeRows, allEdgeRows);
1149
+ }
1150
+ // Step 4: Phase 8.3f pts fallback for unresolved receiver calls (rest params).
908
1151
  if (targets.length === 0 &&
909
1152
  call.receiver &&
910
1153
  !BUILTIN_RECEIVERS.has(call.receiver) &&
@@ -912,74 +1155,40 @@ function buildFileCallEdges(relPath, symbols, fileNodeRow, importedNames, seenCa
912
1155
  call.receiver !== 'self' &&
913
1156
  call.receiver !== 'super' &&
914
1157
  ptsMap) {
915
- const receiverKey = `${call.receiver}.${call.name}`;
916
- if (ptsMap.has(receiverKey)) {
917
- for (const alias of resolveViaPointsTo(receiverKey, ptsMap)) {
918
- const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(lookup, { name: alias }, relPath, importedNames, typeMap);
919
- const sortedAliasTargets = aliasTargets.length > 1
920
- ? [...aliasTargets].sort((a, b) => computeConfidence(relPath, b.file, aliasFrom ?? null) -
921
- computeConfidence(relPath, a.file, aliasFrom ?? null))
922
- : aliasTargets;
923
- for (const t of sortedAliasTargets) {
924
- const edgeKey = `${caller.id}|${t.id}`;
925
- if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
926
- const conf = computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
927
- if (conf > 0) {
928
- ptsEdgeRows.set(edgeKey, allEdgeRows.length);
929
- allEdgeRows.push([caller.id, t.id, 'calls', conf, isDynamic, 'points-to']);
930
- }
931
- }
932
- }
933
- }
934
- }
1158
+ emitPtsReceiverEdges(call, caller, isDynamic, relPath, importedNames, lookup, typeMap, ptsMap, seenCallEdges, ptsEdgeRows, allEdgeRows);
935
1159
  }
1160
+ // Step 5: Emit receiver edge for external (non-this/self/super) receivers.
936
1161
  if (call.receiver &&
937
1162
  !BUILTIN_RECEIVERS.has(call.receiver) &&
938
1163
  call.receiver !== 'this' &&
939
1164
  call.receiver !== 'self' &&
940
1165
  call.receiver !== 'super') {
941
- const recv = resolveReceiverEdge(lookup, { name: call.name, receiver: call.receiver }, caller, relPath, typeMap, seenCallEdges, importedNames);
1166
+ const recv = resolveReceiverEdge(lookup, { name: call.name, receiver: call.receiver }, caller, relPath, typeMap, seenCallEdges, importArtifactNames ?? importedNames);
942
1167
  if (recv) {
943
- allEdgeRows.push([recv.callerId, recv.receiverId, 'receiver', recv.confidence, 0, null]);
1168
+ allEdgeRows.push([
1169
+ recv.callerId,
1170
+ recv.receiverId,
1171
+ 'receiver',
1172
+ recv.confidence,
1173
+ 0,
1174
+ null,
1175
+ null,
1176
+ ]);
944
1177
  }
945
1178
  }
946
- // Phase 8.5: CHA + RTA dispatch expansion.
947
- // For `this`/`self`/`super` calls: resolve through the class hierarchy instead
948
- // of relying solely on global name matching.
949
- // For typed receiver calls: expand to all instantiated concrete implementations.
1179
+ // Step 6: Phase 8.5 CHA + RTA dispatch expansion.
950
1180
  if (chaCtx && call.receiver) {
951
- let chaTargets = [];
952
- let isTypedReceiverDispatch = false;
953
- if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
954
- chaTargets = resolveThisDispatch(call.name, caller.callerName, call.receiver, chaCtx, lookup, relPath);
955
- }
956
- else if (!BUILTIN_RECEIVERS.has(call.receiver)) {
957
- const typeEntry = typeMap.get(call.receiver);
958
- const typeName = typeEntry
959
- ? typeof typeEntry === 'string'
960
- ? typeEntry
961
- : typeEntry.type
962
- : null;
963
- if (typeName) {
964
- chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
965
- isTypedReceiverDispatch = true;
966
- }
967
- }
968
- for (const t of chaTargets) {
969
- const edgeKey = `${caller.id}|${t.id}`;
970
- if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
971
- // Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
972
- // — file proximity is not meaningful for virtual dispatch confidence.
973
- // this/super dispatch keeps computeConfidence-based proximity scoring to
974
- // match runPostNativeThisDispatch (native-orchestrator.ts).
975
- const conf = isTypedReceiverDispatch
976
- ? CHA_TYPED_DISPATCH_CONFIDENCE
977
- : computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
978
- if (conf > 0) {
979
- seenCallEdges.add(edgeKey);
980
- allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha']);
981
- }
982
- }
1181
+ emitChaCallEdgesForCall(call, caller, relPath, typeMap, lookup, chaCtx, seenCallEdges, ptsEdgeRows, allEdgeRows);
1182
+ }
1183
+ // Step 7: Flag-only dynamic kinds with no resolved target sink edge to the
1184
+ // file node. confidence=0.0 keeps it below DEFAULT_MIN_CONFIDENCE so it never
1185
+ // appears in normal query results, but is queryable via `codegraph roles --dynamic`.
1186
+ if (targets.length === 0 && call.dynamicKind && FLAG_ONLY_KINDS.has(call.dynamicKind)) {
1187
+ // Key per (caller, file, kind) so each kind gets at most one sink edge per caller.
1188
+ const sinkKey = `${caller.id}:${fileNodeRow.id}:${call.dynamicKind}`;
1189
+ if (!seenCallEdges.has(sinkKey)) {
1190
+ seenCallEdges.add(sinkKey);
1191
+ allEdgeRows.push([caller.id, fileNodeRow.id, 'calls', 0.0, 1, null, call.dynamicKind]);
983
1192
  }
984
1193
  }
985
1194
  }
@@ -995,7 +1204,7 @@ function buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows) {
995
1204
  const targetRows = (ctx.nodesByName.get(cls.extends) || []).filter((n) => EXTENDS_TARGET_KINDS.has(n.kind));
996
1205
  if (sourceRow) {
997
1206
  for (const t of targetRows) {
998
- allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null]);
1207
+ allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0, null, null]);
999
1208
  }
1000
1209
  }
1001
1210
  }
@@ -1004,7 +1213,7 @@ function buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows) {
1004
1213
  const targetRows = (ctx.nodesByName.get(cls.implements) || []).filter((n) => IMPLEMENTS_TARGET_KINDS.has(n.kind));
1005
1214
  if (sourceRow) {
1006
1215
  for (const t of targetRows) {
1007
- allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null]);
1216
+ allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0, null, null]);
1008
1217
  }
1009
1218
  }
1010
1219
  }
@@ -1013,7 +1222,8 @@ function buildClassHierarchyEdges(ctx, relPath, symbols, allEdgeRows) {
1013
1222
  // ── Native bulk-insert technique back-fill ──────────────────────────────
1014
1223
  /**
1015
1224
  * After native bulkInsertEdges (which does not write the technique column),
1016
- * apply technique values from the in-memory row array back to the DB.
1225
+ * apply technique values from the in-memory row array back to the DB, and lift
1226
+ * any resolved ts-native edge below TS_NATIVE_CONFIDENCE_FLOOR to that floor.
1017
1227
  *
1018
1228
  * Rows with an explicit technique get a targeted UPDATE by (source_id, target_id).
1019
1229
  * The catch-all 'ts-native' tag is scoped to only the source_ids present in this
@@ -1030,6 +1240,8 @@ function applyEdgeTechniquesAfterNativeInsert(db, rows) {
1030
1240
  const sourceIds = [...new Set(callRows.map((r) => r[0]))];
1031
1241
  // Chunk to stay within SQLite's SQLITE_LIMIT_VARIABLE_NUMBER (999 on older builds).
1032
1242
  const CHUNK_SIZE = 500;
1243
+ // Rows that carry an explicit dynamic_kind (sink edges for flagged dynamic calls).
1244
+ const dynamicKindRows = callRows.filter((r) => r[6] != null);
1033
1245
  const tx = db.transaction(() => {
1034
1246
  if (taggedRows.length > 0) {
1035
1247
  const stmt = db.prepare("UPDATE edges SET technique = ? WHERE kind = 'calls' AND source_id = ? AND target_id = ? AND technique IS NULL");
@@ -1040,6 +1252,24 @@ function applyEdgeTechniquesAfterNativeInsert(db, rows) {
1040
1252
  const chunk = sourceIds.slice(i, i + CHUNK_SIZE);
1041
1253
  const placeholders = chunk.map(() => '?').join(',');
1042
1254
  db.prepare(`UPDATE edges SET technique = 'ts-native' WHERE kind = 'calls' AND technique IS NULL AND source_id IN (${placeholders})`).run(...chunk);
1255
+ // Lift resolved ts-native edges below the confidence floor for this chunk.
1256
+ db.prepare(`UPDATE edges SET confidence = ?
1257
+ WHERE kind = 'calls' AND technique = 'ts-native'
1258
+ AND confidence > 0 AND confidence < ?
1259
+ AND source_id IN (${placeholders})`).run(TS_NATIVE_CONFIDENCE_FLOOR, TS_NATIVE_CONFIDENCE_FLOOR, ...chunk);
1260
+ }
1261
+ // Back-fill dynamic_kind for flagged sink edges emitted by the native engine.
1262
+ // Native bulkInsertEdges uses INSERT OR IGNORE and does not write dynamic_kind, so
1263
+ // this UPDATE is the only way to set it for natively-inserted sink edges.
1264
+ //
1265
+ // Scope to confidence=0.0 AND dynamic=1 so we only touch sink edges (never normal
1266
+ // call edges that happen to share the same (source_id, target_id) pair).
1267
+ // Include dynamic_kind in the WHERE so two sink edges from the same caller to the
1268
+ // same file with different kinds don't clobber each other across incremental runs.
1269
+ if (dynamicKindRows.length > 0) {
1270
+ const stmt = db.prepare("UPDATE edges SET dynamic_kind = ? WHERE kind = 'calls' AND source_id = ? AND target_id = ? AND confidence = 0.0 AND dynamic = 1 AND (dynamic_kind IS NULL OR dynamic_kind = ?)");
1271
+ for (const r of dynamicKindRows)
1272
+ stmt.run(r[6], r[0], r[1], r[6]);
1043
1273
  }
1044
1274
  });
1045
1275
  tx();
@@ -1068,6 +1298,7 @@ function reconnectReverseDepEdges(ctx) {
1068
1298
  saved.confidence,
1069
1299
  saved.dynamic,
1070
1300
  saved.technique,
1301
+ saved.dynamicKind ?? null,
1071
1302
  ]);
1072
1303
  }
1073
1304
  else {
@@ -1255,6 +1486,21 @@ export async function buildEdges(ctx) {
1255
1486
  else {
1256
1487
  buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows, chaCtx);
1257
1488
  }
1489
+ // Apply ts-native confidence floor to allEdgeRows in-memory. The proximity
1490
+ // heuristic returns 0.3 for cross-module calls with no import-path evidence,
1491
+ // but both WASM and native engines perform actual name-based symbol lookup,
1492
+ // which is stronger evidence than pure proximity. Clamping to
1493
+ // TS_NATIVE_CONFIDENCE_FLOOR (0.5) avoids unfairly dragging down the
1494
+ // call-confidence metric. Sink edges (confidence = 0.0) are excluded so
1495
+ // they remain below DEFAULT_MIN_CONFIDENCE.
1496
+ for (const r of allEdgeRows) {
1497
+ if (r[2] === 'calls' &&
1498
+ r[5] === 'ts-native' &&
1499
+ r[3] > 0 &&
1500
+ r[3] < TS_NATIVE_CONFIDENCE_FLOOR) {
1501
+ r[3] = TS_NATIVE_CONFIDENCE_FLOOR;
1502
+ }
1503
+ }
1258
1504
  // When using native edge insert, skip JS insert here — do it after tx commits.
1259
1505
  // Otherwise insert edges within this transaction for atomicity.
1260
1506
  const useNativeEdgeInsert = ctx.engineName === 'native' && !!ctx.nativeDb?.bulkInsertEdges;