cotx-engine 0.1.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 (501) hide show
  1. package/LICENSE +94 -0
  2. package/README.md +103 -0
  3. package/dist/commands/compile.d.ts +3 -0
  4. package/dist/commands/compile.js +93 -0
  5. package/dist/commands/compile.js.map +1 -0
  6. package/dist/commands/context.d.ts +1 -0
  7. package/dist/commands/context.js +98 -0
  8. package/dist/commands/context.js.map +1 -0
  9. package/dist/commands/diff.d.ts +19 -0
  10. package/dist/commands/diff.js +127 -0
  11. package/dist/commands/diff.js.map +1 -0
  12. package/dist/commands/impact.d.ts +3 -0
  13. package/dist/commands/impact.js +91 -0
  14. package/dist/commands/impact.js.map +1 -0
  15. package/dist/commands/init.d.ts +1 -0
  16. package/dist/commands/init.js +13 -0
  17. package/dist/commands/init.js.map +1 -0
  18. package/dist/commands/lint.d.ts +13 -0
  19. package/dist/commands/lint.js +290 -0
  20. package/dist/commands/lint.js.map +1 -0
  21. package/dist/commands/map.d.ts +6 -0
  22. package/dist/commands/map.js +409 -0
  23. package/dist/commands/map.js.map +1 -0
  24. package/dist/commands/migrate.d.ts +16 -0
  25. package/dist/commands/migrate.js +150 -0
  26. package/dist/commands/migrate.js.map +1 -0
  27. package/dist/commands/query.d.ts +3 -0
  28. package/dist/commands/query.js +47 -0
  29. package/dist/commands/query.js.map +1 -0
  30. package/dist/commands/rename.d.ts +5 -0
  31. package/dist/commands/rename.js +163 -0
  32. package/dist/commands/rename.js.map +1 -0
  33. package/dist/commands/snapshot.d.ts +6 -0
  34. package/dist/commands/snapshot.js +48 -0
  35. package/dist/commands/snapshot.js.map +1 -0
  36. package/dist/commands/status.d.ts +1 -0
  37. package/dist/commands/status.js +72 -0
  38. package/dist/commands/status.js.map +1 -0
  39. package/dist/commands/update.d.ts +8 -0
  40. package/dist/commands/update.js +163 -0
  41. package/dist/commands/update.js.map +1 -0
  42. package/dist/commands/write.d.ts +6 -0
  43. package/dist/commands/write.js +221 -0
  44. package/dist/commands/write.js.map +1 -0
  45. package/dist/compiler/auto-describe.d.ts +13 -0
  46. package/dist/compiler/auto-describe.js +91 -0
  47. package/dist/compiler/auto-describe.js.map +1 -0
  48. package/dist/compiler/concept-compiler.d.ts +21 -0
  49. package/dist/compiler/concept-compiler.js +125 -0
  50. package/dist/compiler/concept-compiler.js.map +1 -0
  51. package/dist/compiler/contract-compiler.d.ts +16 -0
  52. package/dist/compiler/contract-compiler.js +90 -0
  53. package/dist/compiler/contract-compiler.js.map +1 -0
  54. package/dist/compiler/delta-detector.d.ts +8 -0
  55. package/dist/compiler/delta-detector.js +34 -0
  56. package/dist/compiler/delta-detector.js.map +1 -0
  57. package/dist/compiler/flow-compiler.d.ts +18 -0
  58. package/dist/compiler/flow-compiler.js +69 -0
  59. package/dist/compiler/flow-compiler.js.map +1 -0
  60. package/dist/compiler/module-compiler.d.ts +18 -0
  61. package/dist/compiler/module-compiler.js +420 -0
  62. package/dist/compiler/module-compiler.js.map +1 -0
  63. package/dist/compiler/stale-detector.d.ts +22 -0
  64. package/dist/compiler/stale-detector.js +79 -0
  65. package/dist/compiler/stale-detector.js.map +1 -0
  66. package/dist/config/ignore-service.d.ts +26 -0
  67. package/dist/config/ignore-service.js +366 -0
  68. package/dist/config/ignore-service.js.map +1 -0
  69. package/dist/core/analysis/cluster-enricher.d.ts +38 -0
  70. package/dist/core/analysis/cluster-enricher.js +169 -0
  71. package/dist/core/analysis/cluster-enricher.js.map +1 -0
  72. package/dist/core/analysis/community-processor.d.ts +39 -0
  73. package/dist/core/analysis/community-processor.js +319 -0
  74. package/dist/core/analysis/community-processor.js.map +1 -0
  75. package/dist/core/analysis/process-processor.d.ts +51 -0
  76. package/dist/core/analysis/process-processor.js +318 -0
  77. package/dist/core/analysis/process-processor.js.map +1 -0
  78. package/dist/core/bridge.d.ts +15 -0
  79. package/dist/core/bridge.js +63 -0
  80. package/dist/core/bridge.js.map +1 -0
  81. package/dist/core/export/json-exporter.d.ts +43 -0
  82. package/dist/core/export/json-exporter.js +13 -0
  83. package/dist/core/export/json-exporter.js.map +1 -0
  84. package/dist/core/graph/graph.d.ts +2 -0
  85. package/dist/core/graph/graph.js +79 -0
  86. package/dist/core/graph/graph.js.map +1 -0
  87. package/dist/core/graph/types.d.ts +25 -0
  88. package/dist/core/graph/types.js +2 -0
  89. package/dist/core/graph/types.js.map +1 -0
  90. package/dist/core/parser/ast-cache.d.ts +11 -0
  91. package/dist/core/parser/ast-cache.js +36 -0
  92. package/dist/core/parser/ast-cache.js.map +1 -0
  93. package/dist/core/parser/call-processor.d.ts +105 -0
  94. package/dist/core/parser/call-processor.js +1807 -0
  95. package/dist/core/parser/call-processor.js.map +1 -0
  96. package/dist/core/parser/call-routing.d.ts +55 -0
  97. package/dist/core/parser/call-routing.js +113 -0
  98. package/dist/core/parser/call-routing.js.map +1 -0
  99. package/dist/core/parser/call-sites/extract-language-call-site.d.ts +10 -0
  100. package/dist/core/parser/call-sites/extract-language-call-site.js +23 -0
  101. package/dist/core/parser/call-sites/extract-language-call-site.js.map +1 -0
  102. package/dist/core/parser/call-sites/java.d.ts +9 -0
  103. package/dist/core/parser/call-sites/java.js +31 -0
  104. package/dist/core/parser/call-sites/java.js.map +1 -0
  105. package/dist/core/parser/cluster-enricher.d.ts +38 -0
  106. package/dist/core/parser/cluster-enricher.js +169 -0
  107. package/dist/core/parser/cluster-enricher.js.map +1 -0
  108. package/dist/core/parser/community-processor.d.ts +39 -0
  109. package/dist/core/parser/community-processor.js +321 -0
  110. package/dist/core/parser/community-processor.js.map +1 -0
  111. package/dist/core/parser/constants.d.ts +16 -0
  112. package/dist/core/parser/constants.js +17 -0
  113. package/dist/core/parser/constants.js.map +1 -0
  114. package/dist/core/parser/entry-point-scoring.d.ts +57 -0
  115. package/dist/core/parser/entry-point-scoring.js +377 -0
  116. package/dist/core/parser/entry-point-scoring.js.map +1 -0
  117. package/dist/core/parser/export-detection.d.ts +57 -0
  118. package/dist/core/parser/export-detection.js +234 -0
  119. package/dist/core/parser/export-detection.js.map +1 -0
  120. package/dist/core/parser/field-extractor.d.ts +34 -0
  121. package/dist/core/parser/field-extractor.js +33 -0
  122. package/dist/core/parser/field-extractor.js.map +1 -0
  123. package/dist/core/parser/field-extractors/configs/c-cpp.d.ts +16 -0
  124. package/dist/core/parser/field-extractors/configs/c-cpp.js +129 -0
  125. package/dist/core/parser/field-extractors/configs/c-cpp.js.map +1 -0
  126. package/dist/core/parser/field-extractors/configs/csharp.d.ts +15 -0
  127. package/dist/core/parser/field-extractors/configs/csharp.js +129 -0
  128. package/dist/core/parser/field-extractors/configs/csharp.js.map +1 -0
  129. package/dist/core/parser/field-extractors/configs/dart.d.ts +12 -0
  130. package/dist/core/parser/field-extractors/configs/dart.js +93 -0
  131. package/dist/core/parser/field-extractors/configs/dart.js.map +1 -0
  132. package/dist/core/parser/field-extractors/configs/go.d.ts +12 -0
  133. package/dist/core/parser/field-extractors/configs/go.js +66 -0
  134. package/dist/core/parser/field-extractors/configs/go.js.map +1 -0
  135. package/dist/core/parser/field-extractors/configs/helpers.d.ts +40 -0
  136. package/dist/core/parser/field-extractors/configs/helpers.js +118 -0
  137. package/dist/core/parser/field-extractors/configs/helpers.js.map +1 -0
  138. package/dist/core/parser/field-extractors/configs/jvm.d.ts +17 -0
  139. package/dist/core/parser/field-extractors/configs/jvm.js +139 -0
  140. package/dist/core/parser/field-extractors/configs/jvm.js.map +1 -0
  141. package/dist/core/parser/field-extractors/configs/php.d.ts +12 -0
  142. package/dist/core/parser/field-extractors/configs/php.js +69 -0
  143. package/dist/core/parser/field-extractors/configs/php.js.map +1 -0
  144. package/dist/core/parser/field-extractors/configs/python.d.ts +15 -0
  145. package/dist/core/parser/field-extractors/configs/python.js +92 -0
  146. package/dist/core/parser/field-extractors/configs/python.js.map +1 -0
  147. package/dist/core/parser/field-extractors/configs/ruby.d.ts +15 -0
  148. package/dist/core/parser/field-extractors/configs/ruby.js +68 -0
  149. package/dist/core/parser/field-extractors/configs/ruby.js.map +1 -0
  150. package/dist/core/parser/field-extractors/configs/rust.d.ts +12 -0
  151. package/dist/core/parser/field-extractors/configs/rust.js +58 -0
  152. package/dist/core/parser/field-extractors/configs/rust.js.map +1 -0
  153. package/dist/core/parser/field-extractors/configs/swift.d.ts +15 -0
  154. package/dist/core/parser/field-extractors/configs/swift.js +75 -0
  155. package/dist/core/parser/field-extractors/configs/swift.js.map +1 -0
  156. package/dist/core/parser/field-extractors/configs/typescript-javascript.d.ts +14 -0
  157. package/dist/core/parser/field-extractors/configs/typescript-javascript.js +72 -0
  158. package/dist/core/parser/field-extractors/configs/typescript-javascript.js.map +1 -0
  159. package/dist/core/parser/field-extractors/generic.d.ts +61 -0
  160. package/dist/core/parser/field-extractors/generic.js +170 -0
  161. package/dist/core/parser/field-extractors/generic.js.map +1 -0
  162. package/dist/core/parser/field-extractors/typescript.d.ts +16 -0
  163. package/dist/core/parser/field-extractors/typescript.js +167 -0
  164. package/dist/core/parser/field-extractors/typescript.js.map +1 -0
  165. package/dist/core/parser/field-types.d.ts +46 -0
  166. package/dist/core/parser/field-types.js +2 -0
  167. package/dist/core/parser/field-types.js.map +1 -0
  168. package/dist/core/parser/filesystem-walker.d.ts +28 -0
  169. package/dist/core/parser/filesystem-walker.js +82 -0
  170. package/dist/core/parser/filesystem-walker.js.map +1 -0
  171. package/dist/core/parser/framework-detection.d.ts +149 -0
  172. package/dist/core/parser/framework-detection.js +782 -0
  173. package/dist/core/parser/framework-detection.js.map +1 -0
  174. package/dist/core/parser/heritage-processor.d.ts +52 -0
  175. package/dist/core/parser/heritage-processor.js +339 -0
  176. package/dist/core/parser/heritage-processor.js.map +1 -0
  177. package/dist/core/parser/import-processor.d.ts +33 -0
  178. package/dist/core/parser/import-processor.js +382 -0
  179. package/dist/core/parser/import-processor.js.map +1 -0
  180. package/dist/core/parser/import-resolvers/csharp.d.ts +19 -0
  181. package/dist/core/parser/import-resolvers/csharp.js +132 -0
  182. package/dist/core/parser/import-resolvers/csharp.js.map +1 -0
  183. package/dist/core/parser/import-resolvers/dart.d.ts +7 -0
  184. package/dist/core/parser/import-resolvers/dart.js +45 -0
  185. package/dist/core/parser/import-resolvers/dart.js.map +1 -0
  186. package/dist/core/parser/import-resolvers/go.d.ts +18 -0
  187. package/dist/core/parser/import-resolvers/go.js +62 -0
  188. package/dist/core/parser/import-resolvers/go.js.map +1 -0
  189. package/dist/core/parser/import-resolvers/jvm.d.ts +32 -0
  190. package/dist/core/parser/import-resolvers/jvm.js +160 -0
  191. package/dist/core/parser/import-resolvers/jvm.js.map +1 -0
  192. package/dist/core/parser/import-resolvers/php.d.ts +25 -0
  193. package/dist/core/parser/import-resolvers/php.js +81 -0
  194. package/dist/core/parser/import-resolvers/php.js.map +1 -0
  195. package/dist/core/parser/import-resolvers/python.d.ts +25 -0
  196. package/dist/core/parser/import-resolvers/python.js +85 -0
  197. package/dist/core/parser/import-resolvers/python.js.map +1 -0
  198. package/dist/core/parser/import-resolvers/ruby.d.ts +15 -0
  199. package/dist/core/parser/import-resolvers/ruby.js +21 -0
  200. package/dist/core/parser/import-resolvers/ruby.js.map +1 -0
  201. package/dist/core/parser/import-resolvers/rust.d.ts +18 -0
  202. package/dist/core/parser/import-resolvers/rust.js +119 -0
  203. package/dist/core/parser/import-resolvers/rust.js.map +1 -0
  204. package/dist/core/parser/import-resolvers/standard.d.ts +36 -0
  205. package/dist/core/parser/import-resolvers/standard.js +144 -0
  206. package/dist/core/parser/import-resolvers/standard.js.map +1 -0
  207. package/dist/core/parser/import-resolvers/swift.d.ts +7 -0
  208. package/dist/core/parser/import-resolvers/swift.js +25 -0
  209. package/dist/core/parser/import-resolvers/swift.js.map +1 -0
  210. package/dist/core/parser/import-resolvers/types.d.ts +44 -0
  211. package/dist/core/parser/import-resolvers/types.js +7 -0
  212. package/dist/core/parser/import-resolvers/types.js.map +1 -0
  213. package/dist/core/parser/import-resolvers/utils.d.ts +35 -0
  214. package/dist/core/parser/import-resolvers/utils.js +150 -0
  215. package/dist/core/parser/import-resolvers/utils.js.map +1 -0
  216. package/dist/core/parser/import-resolvers/vue.d.ts +8 -0
  217. package/dist/core/parser/import-resolvers/vue.js +10 -0
  218. package/dist/core/parser/import-resolvers/vue.js.map +1 -0
  219. package/dist/core/parser/language-config.d.ts +52 -0
  220. package/dist/core/parser/language-config.js +182 -0
  221. package/dist/core/parser/language-config.js.map +1 -0
  222. package/dist/core/parser/language-provider.d.ts +126 -0
  223. package/dist/core/parser/language-provider.js +25 -0
  224. package/dist/core/parser/language-provider.js.map +1 -0
  225. package/dist/core/parser/languages/c-cpp.d.ts +12 -0
  226. package/dist/core/parser/languages/c-cpp.js +312 -0
  227. package/dist/core/parser/languages/c-cpp.js.map +1 -0
  228. package/dist/core/parser/languages/csharp.d.ts +8 -0
  229. package/dist/core/parser/languages/csharp.js +127 -0
  230. package/dist/core/parser/languages/csharp.js.map +1 -0
  231. package/dist/core/parser/languages/dart.d.ts +12 -0
  232. package/dist/core/parser/languages/dart.js +91 -0
  233. package/dist/core/parser/languages/dart.js.map +1 -0
  234. package/dist/core/parser/languages/go.d.ts +11 -0
  235. package/dist/core/parser/languages/go.js +32 -0
  236. package/dist/core/parser/languages/go.js.map +1 -0
  237. package/dist/core/parser/languages/index.d.ts +38 -0
  238. package/dist/core/parser/languages/index.js +63 -0
  239. package/dist/core/parser/languages/index.js.map +1 -0
  240. package/dist/core/parser/languages/java.d.ts +9 -0
  241. package/dist/core/parser/languages/java.js +33 -0
  242. package/dist/core/parser/languages/java.js.map +1 -0
  243. package/dist/core/parser/languages/kotlin.d.ts +9 -0
  244. package/dist/core/parser/languages/kotlin.js +112 -0
  245. package/dist/core/parser/languages/kotlin.js.map +1 -0
  246. package/dist/core/parser/languages/php.d.ts +8 -0
  247. package/dist/core/parser/languages/php.js +226 -0
  248. package/dist/core/parser/languages/php.js.map +1 -0
  249. package/dist/core/parser/languages/python.d.ts +12 -0
  250. package/dist/core/parser/languages/python.js +66 -0
  251. package/dist/core/parser/languages/python.js.map +1 -0
  252. package/dist/core/parser/languages/ruby.d.ts +9 -0
  253. package/dist/core/parser/languages/ruby.js +109 -0
  254. package/dist/core/parser/languages/ruby.js.map +1 -0
  255. package/dist/core/parser/languages/rust.d.ts +12 -0
  256. package/dist/core/parser/languages/rust.js +121 -0
  257. package/dist/core/parser/languages/rust.js.map +1 -0
  258. package/dist/core/parser/languages/swift.d.ts +12 -0
  259. package/dist/core/parser/languages/swift.js +233 -0
  260. package/dist/core/parser/languages/swift.js.map +1 -0
  261. package/dist/core/parser/languages/typescript.d.ts +11 -0
  262. package/dist/core/parser/languages/typescript.js +169 -0
  263. package/dist/core/parser/languages/typescript.js.map +1 -0
  264. package/dist/core/parser/languages/vue.d.ts +13 -0
  265. package/dist/core/parser/languages/vue.js +65 -0
  266. package/dist/core/parser/languages/vue.js.map +1 -0
  267. package/dist/core/parser/markdown-processor.d.ts +17 -0
  268. package/dist/core/parser/markdown-processor.js +125 -0
  269. package/dist/core/parser/markdown-processor.js.map +1 -0
  270. package/dist/core/parser/method-extractors/configs/c-cpp.d.ts +3 -0
  271. package/dist/core/parser/method-extractors/configs/c-cpp.js +276 -0
  272. package/dist/core/parser/method-extractors/configs/c-cpp.js.map +1 -0
  273. package/dist/core/parser/method-extractors/configs/csharp.d.ts +2 -0
  274. package/dist/core/parser/method-extractors/configs/csharp.js +243 -0
  275. package/dist/core/parser/method-extractors/configs/csharp.js.map +1 -0
  276. package/dist/core/parser/method-extractors/configs/dart.d.ts +2 -0
  277. package/dist/core/parser/method-extractors/configs/dart.js +263 -0
  278. package/dist/core/parser/method-extractors/configs/dart.js.map +1 -0
  279. package/dist/core/parser/method-extractors/configs/go.d.ts +2 -0
  280. package/dist/core/parser/method-extractors/configs/go.js +120 -0
  281. package/dist/core/parser/method-extractors/configs/go.js.map +1 -0
  282. package/dist/core/parser/method-extractors/configs/jvm.d.ts +3 -0
  283. package/dist/core/parser/method-extractors/configs/jvm.js +309 -0
  284. package/dist/core/parser/method-extractors/configs/jvm.js.map +1 -0
  285. package/dist/core/parser/method-extractors/configs/php.d.ts +2 -0
  286. package/dist/core/parser/method-extractors/configs/php.js +243 -0
  287. package/dist/core/parser/method-extractors/configs/php.js.map +1 -0
  288. package/dist/core/parser/method-extractors/configs/python.d.ts +2 -0
  289. package/dist/core/parser/method-extractors/configs/python.js +219 -0
  290. package/dist/core/parser/method-extractors/configs/python.js.map +1 -0
  291. package/dist/core/parser/method-extractors/configs/ruby.d.ts +2 -0
  292. package/dist/core/parser/method-extractors/configs/ruby.js +201 -0
  293. package/dist/core/parser/method-extractors/configs/ruby.js.map +1 -0
  294. package/dist/core/parser/method-extractors/configs/rust.d.ts +2 -0
  295. package/dist/core/parser/method-extractors/configs/rust.js +120 -0
  296. package/dist/core/parser/method-extractors/configs/rust.js.map +1 -0
  297. package/dist/core/parser/method-extractors/configs/swift.d.ts +2 -0
  298. package/dist/core/parser/method-extractors/configs/swift.js +191 -0
  299. package/dist/core/parser/method-extractors/configs/swift.js.map +1 -0
  300. package/dist/core/parser/method-extractors/configs/typescript-javascript.d.ts +3 -0
  301. package/dist/core/parser/method-extractors/configs/typescript-javascript.js +231 -0
  302. package/dist/core/parser/method-extractors/configs/typescript-javascript.js.map +1 -0
  303. package/dist/core/parser/method-extractors/generic.d.ts +11 -0
  304. package/dist/core/parser/method-extractors/generic.js +162 -0
  305. package/dist/core/parser/method-extractors/generic.js.map +1 -0
  306. package/dist/core/parser/method-types.d.ts +110 -0
  307. package/dist/core/parser/method-types.js +2 -0
  308. package/dist/core/parser/method-types.js.map +1 -0
  309. package/dist/core/parser/mro-processor.d.ts +46 -0
  310. package/dist/core/parser/mro-processor.js +677 -0
  311. package/dist/core/parser/mro-processor.js.map +1 -0
  312. package/dist/core/parser/named-binding-processor.d.ts +18 -0
  313. package/dist/core/parser/named-binding-processor.js +43 -0
  314. package/dist/core/parser/named-binding-processor.js.map +1 -0
  315. package/dist/core/parser/named-bindings/csharp.d.ts +3 -0
  316. package/dist/core/parser/named-bindings/csharp.js +38 -0
  317. package/dist/core/parser/named-bindings/csharp.js.map +1 -0
  318. package/dist/core/parser/named-bindings/java.d.ts +3 -0
  319. package/dist/core/parser/named-bindings/java.js +30 -0
  320. package/dist/core/parser/named-bindings/java.js.map +1 -0
  321. package/dist/core/parser/named-bindings/kotlin.d.ts +3 -0
  322. package/dist/core/parser/named-bindings/kotlin.js +37 -0
  323. package/dist/core/parser/named-bindings/kotlin.js.map +1 -0
  324. package/dist/core/parser/named-bindings/php.d.ts +3 -0
  325. package/dist/core/parser/named-bindings/php.js +62 -0
  326. package/dist/core/parser/named-bindings/php.js.map +1 -0
  327. package/dist/core/parser/named-bindings/python.d.ts +3 -0
  328. package/dist/core/parser/named-bindings/python.js +50 -0
  329. package/dist/core/parser/named-bindings/python.js.map +1 -0
  330. package/dist/core/parser/named-bindings/rust.d.ts +3 -0
  331. package/dist/core/parser/named-bindings/rust.js +67 -0
  332. package/dist/core/parser/named-bindings/rust.js.map +1 -0
  333. package/dist/core/parser/named-bindings/types.d.ts +16 -0
  334. package/dist/core/parser/named-bindings/types.js +7 -0
  335. package/dist/core/parser/named-bindings/types.js.map +1 -0
  336. package/dist/core/parser/named-bindings/typescript.d.ts +3 -0
  337. package/dist/core/parser/named-bindings/typescript.js +59 -0
  338. package/dist/core/parser/named-bindings/typescript.js.map +1 -0
  339. package/dist/core/parser/parsing-processor.d.ts +23 -0
  340. package/dist/core/parser/parsing-processor.js +464 -0
  341. package/dist/core/parser/parsing-processor.js.map +1 -0
  342. package/dist/core/parser/pipeline.d.ts +17 -0
  343. package/dist/core/parser/pipeline.js +1405 -0
  344. package/dist/core/parser/pipeline.js.map +1 -0
  345. package/dist/core/parser/process-processor.d.ts +51 -0
  346. package/dist/core/parser/process-processor.js +318 -0
  347. package/dist/core/parser/process-processor.js.map +1 -0
  348. package/dist/core/parser/resolution-context.d.ts +58 -0
  349. package/dist/core/parser/resolution-context.js +136 -0
  350. package/dist/core/parser/resolution-context.js.map +1 -0
  351. package/dist/core/parser/route-extractors/expo.d.ts +1 -0
  352. package/dist/core/parser/route-extractors/expo.js +37 -0
  353. package/dist/core/parser/route-extractors/expo.js.map +1 -0
  354. package/dist/core/parser/route-extractors/middleware.d.ts +47 -0
  355. package/dist/core/parser/route-extractors/middleware.js +168 -0
  356. package/dist/core/parser/route-extractors/middleware.js.map +1 -0
  357. package/dist/core/parser/route-extractors/nextjs.d.ts +3 -0
  358. package/dist/core/parser/route-extractors/nextjs.js +77 -0
  359. package/dist/core/parser/route-extractors/nextjs.js.map +1 -0
  360. package/dist/core/parser/route-extractors/php.d.ts +7 -0
  361. package/dist/core/parser/route-extractors/php.js +23 -0
  362. package/dist/core/parser/route-extractors/php.js.map +1 -0
  363. package/dist/core/parser/route-extractors/response-shapes.d.ts +20 -0
  364. package/dist/core/parser/route-extractors/response-shapes.js +295 -0
  365. package/dist/core/parser/route-extractors/response-shapes.js.map +1 -0
  366. package/dist/core/parser/structure-processor.d.ts +2 -0
  367. package/dist/core/parser/structure-processor.js +37 -0
  368. package/dist/core/parser/structure-processor.js.map +1 -0
  369. package/dist/core/parser/symbol-table.d.ts +79 -0
  370. package/dist/core/parser/symbol-table.js +116 -0
  371. package/dist/core/parser/symbol-table.js.map +1 -0
  372. package/dist/core/parser/tree-sitter-queries.d.ts +16 -0
  373. package/dist/core/parser/tree-sitter-queries.js +1180 -0
  374. package/dist/core/parser/tree-sitter-queries.js.map +1 -0
  375. package/dist/core/parser/type-env.d.ts +81 -0
  376. package/dist/core/parser/type-env.js +1048 -0
  377. package/dist/core/parser/type-env.js.map +1 -0
  378. package/dist/core/parser/type-extractors/c-cpp.d.ts +7 -0
  379. package/dist/core/parser/type-extractors/c-cpp.js +533 -0
  380. package/dist/core/parser/type-extractors/c-cpp.js.map +1 -0
  381. package/dist/core/parser/type-extractors/csharp.d.ts +2 -0
  382. package/dist/core/parser/type-extractors/csharp.js +584 -0
  383. package/dist/core/parser/type-extractors/csharp.js.map +1 -0
  384. package/dist/core/parser/type-extractors/dart.d.ts +15 -0
  385. package/dist/core/parser/type-extractors/dart.js +370 -0
  386. package/dist/core/parser/type-extractors/dart.js.map +1 -0
  387. package/dist/core/parser/type-extractors/go.d.ts +2 -0
  388. package/dist/core/parser/type-extractors/go.js +514 -0
  389. package/dist/core/parser/type-extractors/go.js.map +1 -0
  390. package/dist/core/parser/type-extractors/jvm.d.ts +3 -0
  391. package/dist/core/parser/type-extractors/jvm.js +857 -0
  392. package/dist/core/parser/type-extractors/jvm.js.map +1 -0
  393. package/dist/core/parser/type-extractors/php.d.ts +2 -0
  394. package/dist/core/parser/type-extractors/php.js +535 -0
  395. package/dist/core/parser/type-extractors/php.js.map +1 -0
  396. package/dist/core/parser/type-extractors/python.d.ts +2 -0
  397. package/dist/core/parser/type-extractors/python.js +475 -0
  398. package/dist/core/parser/type-extractors/python.js.map +1 -0
  399. package/dist/core/parser/type-extractors/ruby.d.ts +2 -0
  400. package/dist/core/parser/type-extractors/ruby.js +378 -0
  401. package/dist/core/parser/type-extractors/ruby.js.map +1 -0
  402. package/dist/core/parser/type-extractors/rust.d.ts +2 -0
  403. package/dist/core/parser/type-extractors/rust.js +516 -0
  404. package/dist/core/parser/type-extractors/rust.js.map +1 -0
  405. package/dist/core/parser/type-extractors/shared.d.ts +131 -0
  406. package/dist/core/parser/type-extractors/shared.js +797 -0
  407. package/dist/core/parser/type-extractors/shared.js.map +1 -0
  408. package/dist/core/parser/type-extractors/swift.d.ts +2 -0
  409. package/dist/core/parser/type-extractors/swift.js +485 -0
  410. package/dist/core/parser/type-extractors/swift.js.map +1 -0
  411. package/dist/core/parser/type-extractors/types.d.ts +172 -0
  412. package/dist/core/parser/type-extractors/types.js +2 -0
  413. package/dist/core/parser/type-extractors/types.js.map +1 -0
  414. package/dist/core/parser/type-extractors/typescript.d.ts +2 -0
  415. package/dist/core/parser/type-extractors/typescript.js +662 -0
  416. package/dist/core/parser/type-extractors/typescript.js.map +1 -0
  417. package/dist/core/parser/utils/ast-helpers.d.ts +73 -0
  418. package/dist/core/parser/utils/ast-helpers.js +415 -0
  419. package/dist/core/parser/utils/ast-helpers.js.map +1 -0
  420. package/dist/core/parser/utils/call-analysis.d.ts +75 -0
  421. package/dist/core/parser/utils/call-analysis.js +575 -0
  422. package/dist/core/parser/utils/call-analysis.js.map +1 -0
  423. package/dist/core/parser/utils/event-loop.d.ts +5 -0
  424. package/dist/core/parser/utils/event-loop.js +6 -0
  425. package/dist/core/parser/utils/event-loop.js.map +1 -0
  426. package/dist/core/parser/utils/method-props.d.ts +8 -0
  427. package/dist/core/parser/utils/method-props.js +39 -0
  428. package/dist/core/parser/utils/method-props.js.map +1 -0
  429. package/dist/core/parser/utils/verbose.d.ts +1 -0
  430. package/dist/core/parser/utils/verbose.js +8 -0
  431. package/dist/core/parser/utils/verbose.js.map +1 -0
  432. package/dist/core/parser/vue-sfc-extractor.d.ts +44 -0
  433. package/dist/core/parser/vue-sfc-extractor.js +95 -0
  434. package/dist/core/parser/vue-sfc-extractor.js.map +1 -0
  435. package/dist/core/parser/workers/parse-worker.d.ts +171 -0
  436. package/dist/core/parser/workers/parse-worker.js +1724 -0
  437. package/dist/core/parser/workers/parse-worker.js.map +1 -0
  438. package/dist/core/parser/workers/worker-pool.d.ts +16 -0
  439. package/dist/core/parser/workers/worker-pool.js +124 -0
  440. package/dist/core/parser/workers/worker-pool.js.map +1 -0
  441. package/dist/core/shared/graph-types.d.ts +61 -0
  442. package/dist/core/shared/graph-types.js +5 -0
  443. package/dist/core/shared/graph-types.js.map +1 -0
  444. package/dist/core/shared/index.d.ts +4 -0
  445. package/dist/core/shared/index.js +4 -0
  446. package/dist/core/shared/index.js.map +1 -0
  447. package/dist/core/shared/language-detection.d.ts +22 -0
  448. package/dist/core/shared/language-detection.js +137 -0
  449. package/dist/core/shared/language-detection.js.map +1 -0
  450. package/dist/core/shared/languages.d.ts +23 -0
  451. package/dist/core/shared/languages.js +25 -0
  452. package/dist/core/shared/languages.js.map +1 -0
  453. package/dist/core/shared/pipeline.d.ts +15 -0
  454. package/dist/core/shared/pipeline.js +5 -0
  455. package/dist/core/shared/pipeline.js.map +1 -0
  456. package/dist/core/tree-sitter/parser-loader.d.ts +5 -0
  457. package/dist/core/tree-sitter/parser-loader.js +71 -0
  458. package/dist/core/tree-sitter/parser-loader.js.map +1 -0
  459. package/dist/index.d.ts +2 -0
  460. package/dist/index.js +132 -0
  461. package/dist/index.js.map +1 -0
  462. package/dist/lib/hash.d.ts +1 -0
  463. package/dist/lib/hash.js +6 -0
  464. package/dist/lib/hash.js.map +1 -0
  465. package/dist/lib/naming.d.ts +12 -0
  466. package/dist/lib/naming.js +28 -0
  467. package/dist/lib/naming.js.map +1 -0
  468. package/dist/lib/utils.d.ts +1 -0
  469. package/dist/lib/utils.js +4 -0
  470. package/dist/lib/utils.js.map +1 -0
  471. package/dist/mcp/server.d.ts +26 -0
  472. package/dist/mcp/server.js +282 -0
  473. package/dist/mcp/server.js.map +1 -0
  474. package/dist/mcp/tools.d.ts +37 -0
  475. package/dist/mcp/tools.js +650 -0
  476. package/dist/mcp/tools.js.map +1 -0
  477. package/dist/query/bm25.d.ts +19 -0
  478. package/dist/query/bm25.js +60 -0
  479. package/dist/query/bm25.js.map +1 -0
  480. package/dist/query/graph-index.d.ts +40 -0
  481. package/dist/query/graph-index.js +178 -0
  482. package/dist/query/graph-index.js.map +1 -0
  483. package/dist/store/derived-index.d.ts +4 -0
  484. package/dist/store/derived-index.js +68 -0
  485. package/dist/store/derived-index.js.map +1 -0
  486. package/dist/store/meta.d.ts +1 -0
  487. package/dist/store/meta.js +3 -0
  488. package/dist/store/meta.js.map +1 -0
  489. package/dist/store/schema.d.ts +135 -0
  490. package/dist/store/schema.js +2 -0
  491. package/dist/store/schema.js.map +1 -0
  492. package/dist/store/store.d.ts +49 -0
  493. package/dist/store/store.js +254 -0
  494. package/dist/store/store.js.map +1 -0
  495. package/dist/types/pipeline.d.ts +12 -0
  496. package/dist/types/pipeline.js +2 -0
  497. package/dist/types/pipeline.js.map +1 -0
  498. package/package.json +69 -0
  499. package/skills/cotx-enrich/SKILL.md +59 -0
  500. package/vendor/leiden/index.cjs +355 -0
  501. package/vendor/leiden/utils.cjs +392 -0
@@ -0,0 +1,1807 @@
1
+ import Parser from 'tree-sitter';
2
+ import { TIER_CONFIDENCE } from './resolution-context.js';
3
+ import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
4
+ import { getProvider } from './languages/index.js';
5
+ import { generateId } from '../../lib/utils.js';
6
+ import { getLanguageFromFilename, SupportedLanguages } from '../shared/index.js';
7
+ import { isVerboseIngestionEnabled } from './utils/verbose.js';
8
+ import { yieldToEventLoop } from './utils/event-loop.js';
9
+ import { FUNCTION_NODE_TYPES, findEnclosingClassId, findEnclosingClassInfo, genericFuncName, inferFunctionLabel, } from './utils/ast-helpers.js';
10
+ import { countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, extractMixedChain, extractCallArgTypes, } from './utils/call-analysis.js';
11
+ import { buildTypeEnv, isSubclassOf } from './type-env.js';
12
+ import { resolveExtendsType } from './heritage-processor.js';
13
+ import { getTreeSitterBufferSize } from './constants.js';
14
+ import { normalizeFetchURL, routeMatches } from './route-extractors/nextjs.js';
15
+ import { extractTemplateComponents } from './vue-sfc-extractor.js';
16
+ import { extractReturnTypeName, stripNullable } from './type-extractors/shared.js';
17
+ import { extractParsedCallSite } from './call-sites/extract-language-call-site.js';
18
+ const MAX_EXPORTS_PER_FILE = 500;
19
+ const MAX_TYPE_NAME_LENGTH = 256;
20
+ /** Build a map of imported callee names → return types for cross-file call-result binding.
21
+ * Consulted ONLY when SymbolTable has no unambiguous local match (local-first principle). */
22
+ export function buildImportedReturnTypes(filePath, namedImportMap, symbolTable) {
23
+ const result = new Map();
24
+ const fileImports = namedImportMap.get(filePath);
25
+ if (!fileImports)
26
+ return result;
27
+ for (const [localName, binding] of fileImports) {
28
+ const def = symbolTable.lookupExactFull(binding.sourcePath, binding.exportedName);
29
+ if (!def?.returnType)
30
+ continue;
31
+ const simpleReturn = extractReturnTypeName(def.returnType);
32
+ if (simpleReturn)
33
+ result.set(localName, simpleReturn);
34
+ }
35
+ return result;
36
+ }
37
+ /** Build cross-file RAW return types for imported callables.
38
+ * Unlike buildImportedReturnTypes (which stores extractReturnTypeName output),
39
+ * this stores the raw declared return type string (e.g., 'User[]', 'List<User>').
40
+ * Used by lookupRawReturnType for for-loop element extraction via extractElementTypeFromString. */
41
+ export function buildImportedRawReturnTypes(filePath, namedImportMap, symbolTable) {
42
+ const result = new Map();
43
+ const fileImports = namedImportMap.get(filePath);
44
+ if (!fileImports)
45
+ return result;
46
+ for (const [localName, binding] of fileImports) {
47
+ const def = symbolTable.lookupExactFull(binding.sourcePath, binding.exportedName);
48
+ if (!def?.returnType)
49
+ continue;
50
+ result.set(localName, def.returnType);
51
+ }
52
+ return result;
53
+ }
54
+ /** Collect resolved type bindings for exported file-scope symbols.
55
+ * Uses graph node isExported flag — does NOT require isExported on SymbolDefinition. */
56
+ function collectExportedBindings(typeEnv, filePath, symbolTable, graph) {
57
+ const fileScope = typeEnv.fileScope();
58
+ if (!fileScope || fileScope.size === 0)
59
+ return null;
60
+ const exported = new Map();
61
+ for (const [varName, typeName] of fileScope) {
62
+ if (exported.size >= MAX_EXPORTS_PER_FILE)
63
+ break;
64
+ if (!typeName || typeName.length > MAX_TYPE_NAME_LENGTH)
65
+ continue;
66
+ const nodeId = symbolTable.lookupExact(filePath, varName);
67
+ if (!nodeId)
68
+ continue;
69
+ const node = graph.getNode(nodeId);
70
+ if (node?.properties?.isExported) {
71
+ exported.set(varName, typeName);
72
+ }
73
+ }
74
+ return exported.size > 0 ? exported : null;
75
+ }
76
+ /** Build ExportedTypeMap from graph nodes — used for worker path where TypeEnv
77
+ * is not available in the main thread. Collects returnType/declaredType from
78
+ * exported symbols that have callables with known return types. */
79
+ export function buildExportedTypeMapFromGraph(graph, symbolTable) {
80
+ const result = new Map();
81
+ graph.forEachNode((node) => {
82
+ if (!node.properties?.isExported)
83
+ return;
84
+ if (!node.properties?.filePath || !node.properties?.name)
85
+ return;
86
+ const filePath = node.properties.filePath;
87
+ const name = node.properties.name;
88
+ if (!name || name.length > MAX_TYPE_NAME_LENGTH)
89
+ return;
90
+ // For callable symbols, use returnType; for properties/variables, use declaredType.
91
+ // Use lookupExactAll + nodeId match to handle same-name methods in different classes.
92
+ const defs = symbolTable.lookupExactAll(filePath, name);
93
+ const def = defs.find((d) => d.nodeId === node.id) ?? defs[0];
94
+ if (!def)
95
+ return;
96
+ const typeName = def.returnType ?? def.declaredType;
97
+ if (!typeName || typeName.length > MAX_TYPE_NAME_LENGTH)
98
+ return;
99
+ // Extract simple type name (strip Promise<>, etc.) — reuse shared utility
100
+ const simpleType = extractReturnTypeName(typeName) ?? typeName;
101
+ if (!simpleType)
102
+ return;
103
+ let fileExports = result.get(filePath);
104
+ if (!fileExports) {
105
+ fileExports = new Map();
106
+ result.set(filePath, fileExports);
107
+ }
108
+ if (fileExports.size < MAX_EXPORTS_PER_FILE) {
109
+ fileExports.set(name, simpleType);
110
+ }
111
+ });
112
+ return result;
113
+ }
114
+ /** Seed cross-file receiver types into pre-extracted call records.
115
+ * Fills missing receiverTypeName for single-hop imported variables
116
+ * using ExportedTypeMap + namedImportMap — zero disk I/O, zero AST re-parsing.
117
+ * Mutates calls in-place. Runs BEFORE processCallsFromExtracted. */
118
+ export function seedCrossFileReceiverTypes(calls, namedImportMap, exportedTypeMap) {
119
+ if (namedImportMap.size === 0 || exportedTypeMap.size === 0) {
120
+ return { enrichedCount: 0 };
121
+ }
122
+ let enrichedCount = 0;
123
+ for (const call of calls) {
124
+ if (call.receiverTypeName || !call.receiverName)
125
+ continue;
126
+ if (call.callForm !== 'member')
127
+ continue;
128
+ const fileImports = namedImportMap.get(call.filePath);
129
+ if (!fileImports)
130
+ continue;
131
+ const binding = fileImports.get(call.receiverName);
132
+ if (!binding)
133
+ continue;
134
+ const upstream = exportedTypeMap.get(binding.sourcePath);
135
+ if (!upstream)
136
+ continue;
137
+ const type = upstream.get(binding.exportedName);
138
+ if (type) {
139
+ call.receiverTypeName = type;
140
+ enrichedCount++;
141
+ }
142
+ }
143
+ return { enrichedCount };
144
+ }
145
+ // Stdlib methods that preserve the receiver's type identity. When TypeEnv already
146
+ // strips nullable wrappers (Option<User> → User), these chain steps are no-ops
147
+ // for type resolution — the current type passes through unchanged.
148
+ const TYPE_PRESERVING_METHODS = new Set([
149
+ 'unwrap',
150
+ 'expect',
151
+ 'unwrap_or',
152
+ 'unwrap_or_default',
153
+ 'unwrap_or_else', // Rust Option/Result
154
+ 'clone',
155
+ 'to_owned',
156
+ 'as_ref',
157
+ 'as_mut',
158
+ 'borrow',
159
+ 'borrow_mut', // Rust clone/borrow
160
+ 'get', // Kotlin/Java Optional.get()
161
+ 'orElseThrow', // Java Optional
162
+ ]);
163
+ /**
164
+ * Walk up the AST from a node to find the enclosing function/method.
165
+ * Returns null if the call is at module/file level (top-level code).
166
+ */
167
+ const findEnclosingFunction = (node, filePath, ctx, provider) => {
168
+ let current = node.parent;
169
+ while (current) {
170
+ if (FUNCTION_NODE_TYPES.has(current.type)) {
171
+ const efnResult = provider.methodExtractor?.extractFunctionName?.(current);
172
+ const funcName = efnResult?.funcName ?? genericFuncName(current);
173
+ const label = efnResult?.label ?? inferFunctionLabel(current.type);
174
+ if (funcName) {
175
+ const resolved = ctx.resolve(funcName, filePath);
176
+ if (resolved?.tier === 'same-file' && resolved.candidates.length > 0) {
177
+ // Disambiguate by enclosing class when multiple candidates
178
+ if (resolved.candidates.length === 1) {
179
+ return resolved.candidates[0].nodeId;
180
+ }
181
+ const classInfo = findEnclosingClassInfo(current, filePath);
182
+ if (classInfo) {
183
+ const match = resolved.candidates.find((c) => c.ownerId === classInfo.classId);
184
+ if (match)
185
+ return match.nodeId;
186
+ }
187
+ if (process.env.NODE_ENV === 'development' && classInfo) {
188
+ console.warn(`[CallProcessor] Enclosing class '${classInfo.className}' found but no candidate matched — falling back to ${resolved.candidates[0].nodeId}`);
189
+ }
190
+ return resolved.candidates[0].nodeId;
191
+ }
192
+ // Fallback: qualify the generated ID to match definition-phase node IDs
193
+ let finalLabel = label;
194
+ if (provider.labelOverride) {
195
+ const override = provider.labelOverride(current, label);
196
+ if (override !== null)
197
+ finalLabel = override;
198
+ }
199
+ const classInfo = findEnclosingClassInfo(current, filePath);
200
+ const qualifiedName = classInfo ? `${classInfo.className}.${funcName}` : funcName;
201
+ // Include #<arity> suffix to match definition-phase Method/Constructor IDs.
202
+ // Use provider.methodExtractor.extractFromNode — same extractor as definition phase.
203
+ let arity;
204
+ if (finalLabel === 'Method' || finalLabel === 'Constructor') {
205
+ const language = getLanguageFromFilename(filePath);
206
+ const info = language
207
+ ? provider.methodExtractor?.extractFromNode?.(current, { filePath, language })
208
+ : undefined;
209
+ if (info) {
210
+ arity = info.parameters.some((p) => p.isVariadic) ? undefined : info.parameters.length;
211
+ }
212
+ }
213
+ const arityTag = arity !== undefined ? `#${arity}` : '';
214
+ return generateId(finalLabel, `${filePath}:${qualifiedName}${arityTag}`);
215
+ }
216
+ }
217
+ // Language-specific enclosing function resolution (e.g., Dart where
218
+ // function_body is a sibling of function_signature, not a child).
219
+ if (provider.enclosingFunctionFinder) {
220
+ const customResult = provider.enclosingFunctionFinder(current);
221
+ if (customResult) {
222
+ const resolved = ctx.resolve(customResult.funcName, filePath);
223
+ if (resolved?.tier === 'same-file' && resolved.candidates.length > 0) {
224
+ if (resolved.candidates.length === 1) {
225
+ return resolved.candidates[0].nodeId;
226
+ }
227
+ const classInfo = findEnclosingClassInfo(current.previousSibling ?? current, filePath);
228
+ if (classInfo) {
229
+ const match = resolved.candidates.find((c) => c.ownerId === classInfo.classId);
230
+ if (match)
231
+ return match.nodeId;
232
+ }
233
+ if (process.env.NODE_ENV === 'development' && classInfo) {
234
+ console.warn(`[CallProcessor] Enclosing class '${classInfo.className}' found but no candidate matched — falling back to ${resolved.candidates[0].nodeId}`);
235
+ }
236
+ return resolved.candidates[0].nodeId;
237
+ }
238
+ let finalLabel = customResult.label;
239
+ if (provider.labelOverride) {
240
+ const override = provider.labelOverride(current.previousSibling, finalLabel);
241
+ if (override !== null)
242
+ finalLabel = override;
243
+ }
244
+ const classInfo = findEnclosingClassInfo(current.previousSibling ?? current, filePath);
245
+ const qualifiedName = classInfo
246
+ ? `${classInfo.className}.${customResult.funcName}`
247
+ : customResult.funcName;
248
+ // Include #<arity> suffix to match definition-phase Method/Constructor IDs.
249
+ const sigNode = current.previousSibling ?? current;
250
+ let arity2;
251
+ if (finalLabel === 'Method' || finalLabel === 'Constructor') {
252
+ const language = getLanguageFromFilename(filePath);
253
+ const info = language
254
+ ? provider.methodExtractor?.extractFromNode?.(sigNode, { filePath, language })
255
+ : undefined;
256
+ if (info) {
257
+ arity2 = info.parameters.some((p) => p.isVariadic) ? undefined : info.parameters.length;
258
+ }
259
+ }
260
+ const arityTag2 = arity2 !== undefined ? `#${arity2}` : '';
261
+ return generateId(finalLabel, `${filePath}:${qualifiedName}${arityTag2}`);
262
+ }
263
+ }
264
+ current = current.parent;
265
+ }
266
+ return null;
267
+ };
268
+ /**
269
+ * Verify constructor bindings against SymbolTable and infer receiver types.
270
+ * Shared between sequential (processCalls) and worker (processCallsFromExtracted) paths.
271
+ */
272
+ const verifyConstructorBindings = (bindings, filePath, ctx, graph) => {
273
+ const verified = new Map();
274
+ for (const { scope, varName, calleeName, receiverClassName } of bindings) {
275
+ const tiered = ctx.resolve(calleeName, filePath);
276
+ const isClass = tiered?.candidates.some((def) => def.type === 'Class') ?? false;
277
+ if (isClass) {
278
+ verified.set(receiverKey(scope, varName), calleeName);
279
+ }
280
+ else {
281
+ let callableDefs = tiered?.candidates.filter((d) => d.type === 'Function' || d.type === 'Method');
282
+ // When receiver class is known (e.g. $this->method() in PHP), narrow
283
+ // candidates to methods owned by that class to avoid false disambiguation failures.
284
+ if (callableDefs && callableDefs.length > 1 && receiverClassName) {
285
+ if (graph) {
286
+ // Worker path: use graph.getNode (fast, already in-memory)
287
+ const narrowed = callableDefs.filter((d) => {
288
+ if (!d.ownerId)
289
+ return false;
290
+ const owner = graph.getNode(d.ownerId);
291
+ return owner?.properties.name === receiverClassName;
292
+ });
293
+ if (narrowed.length > 0)
294
+ callableDefs = narrowed;
295
+ }
296
+ else {
297
+ // Sequential path: use ctx.resolve (no graph available)
298
+ const classResolved = ctx.resolve(receiverClassName, filePath);
299
+ if (classResolved && classResolved.candidates.length > 0) {
300
+ const classNodeIds = new Set(classResolved.candidates.map((c) => c.nodeId));
301
+ const narrowed = callableDefs.filter((d) => d.ownerId && classNodeIds.has(d.ownerId));
302
+ if (narrowed.length > 0)
303
+ callableDefs = narrowed;
304
+ }
305
+ }
306
+ }
307
+ if (callableDefs && callableDefs.length === 1 && callableDefs[0].returnType) {
308
+ const typeName = extractReturnTypeName(callableDefs[0].returnType);
309
+ if (typeName) {
310
+ verified.set(receiverKey(scope, varName), typeName);
311
+ }
312
+ }
313
+ }
314
+ }
315
+ return verified;
316
+ };
317
+ /**
318
+ * Build an ImplementorMap from extracted heritage data.
319
+ * Only direct `implements` relationships are tracked (transitive not needed for
320
+ * the common Java/Kotlin/C# interface dispatch pattern).
321
+ * `extends` is ignored — dispatch keyed on abstract class bases is not modeled here.
322
+ */
323
+ /**
324
+ * Maps interface name → file paths of classes that implement it (direct only).
325
+ * When `ctx` is set, `kind: 'extends'` rows are classified like heritage-processor
326
+ * (C#/Java base_list: class vs interface parents share one capture name).
327
+ */
328
+ export const buildImplementorMap = (heritage, ctx) => {
329
+ const map = new Map();
330
+ for (const h of heritage) {
331
+ let record = false;
332
+ if (h.kind === 'implements') {
333
+ record = true;
334
+ }
335
+ else if (h.kind === 'extends' && ctx) {
336
+ const lang = getLanguageFromFilename(h.filePath);
337
+ if (lang) {
338
+ const { type } = resolveExtendsType(h.parentName, h.filePath, ctx, lang);
339
+ record = type === 'IMPLEMENTS';
340
+ }
341
+ }
342
+ if (record) {
343
+ let files = map.get(h.parentName);
344
+ if (!files) {
345
+ files = new Set();
346
+ map.set(h.parentName, files);
347
+ }
348
+ files.add(h.filePath);
349
+ }
350
+ }
351
+ return map;
352
+ };
353
+ /**
354
+ * Merge a chunk's implementor map into the global accumulator.
355
+ */
356
+ export const mergeImplementorMaps = (target, source) => {
357
+ for (const [name, files] of source) {
358
+ let existing = target.get(name);
359
+ if (!existing) {
360
+ existing = new Set();
361
+ target.set(name, existing);
362
+ }
363
+ for (const f of files)
364
+ existing.add(f);
365
+ }
366
+ };
367
+ /**
368
+ * After resolving a call to an interface method, find additional targets
369
+ * in classes implementing that interface. Returns implementation method
370
+ * results with lower confidence ('interface-dispatch').
371
+ */
372
+ function findInterfaceDispatchTargets(calledName, receiverTypeName, currentFile, ctx, implementorMap, primaryNodeId) {
373
+ const implFiles = implementorMap.get(receiverTypeName);
374
+ if (!implFiles || implFiles.size === 0)
375
+ return [];
376
+ const typeResolved = ctx.resolve(receiverTypeName, currentFile);
377
+ if (!typeResolved)
378
+ return [];
379
+ if (!typeResolved.candidates.some((c) => c.type === 'Interface'))
380
+ return [];
381
+ const results = [];
382
+ for (const implFile of implFiles) {
383
+ const methods = ctx.symbols.lookupExactAll(implFile, calledName);
384
+ for (const method of methods) {
385
+ if (method.nodeId !== primaryNodeId) {
386
+ results.push({
387
+ nodeId: method.nodeId,
388
+ confidence: 0.7,
389
+ reason: 'interface-dispatch',
390
+ });
391
+ }
392
+ }
393
+ }
394
+ return results;
395
+ }
396
+ export const processCalls = async (graph, files, astCache, ctx, onProgress, exportedTypeMap,
397
+ /** Phase 14: pre-resolved cross-file bindings to seed into buildTypeEnv. Keyed by filePath → Map<localName, typeName>. */
398
+ importedBindingsMap,
399
+ /** Phase 14 E3: cross-file return types for imported callables. Keyed by filePath → Map<calleeName, returnType>.
400
+ * Consulted ONLY when SymbolTable has no unambiguous match (local-first principle). */
401
+ importedReturnTypesMap,
402
+ /** Phase 14 E3: cross-file RAW return types for for-loop element extraction. Keyed by filePath → Map<calleeName, rawReturnType>. */
403
+ importedRawReturnTypesMap, implementorMap) => {
404
+ const parser = await loadParser();
405
+ const collectedHeritage = [];
406
+ const pendingWrites = [];
407
+ // Phase P cross-file: accumulate heritage across files for cross-file isSubclassOf.
408
+ // Used as a secondary check when per-file parentMap lacks the relationship — helps
409
+ // when the heritage-declaring file is processed before the call site file.
410
+ // For remaining cases (reverse file order), the SymbolTable class-type fallback applies.
411
+ const globalParentMap = new Map();
412
+ const globalParentSeen = new Map();
413
+ const logSkipped = isVerboseIngestionEnabled();
414
+ const skippedByLang = logSkipped ? new Map() : null;
415
+ for (let i = 0; i < files.length; i++) {
416
+ const file = files[i];
417
+ onProgress?.(i + 1, files.length);
418
+ if (i % 20 === 0)
419
+ await yieldToEventLoop();
420
+ const language = getLanguageFromFilename(file.path);
421
+ if (!language)
422
+ continue;
423
+ if (!isLanguageAvailable(language)) {
424
+ if (skippedByLang) {
425
+ skippedByLang.set(language, (skippedByLang.get(language) ?? 0) + 1);
426
+ }
427
+ continue;
428
+ }
429
+ const provider = getProvider(language);
430
+ const queryStr = provider.treeSitterQueries;
431
+ if (!queryStr)
432
+ continue;
433
+ await loadLanguage(language, file.path);
434
+ let tree = astCache.get(file.path);
435
+ if (!tree) {
436
+ try {
437
+ tree = parser.parse(file.content, undefined, {
438
+ bufferSize: getTreeSitterBufferSize(file.content.length),
439
+ });
440
+ }
441
+ catch (parseError) {
442
+ continue;
443
+ }
444
+ astCache.set(file.path, tree);
445
+ }
446
+ let query;
447
+ let matches;
448
+ try {
449
+ const language = parser.getLanguage();
450
+ query = new Parser.Query(language, queryStr);
451
+ matches = query.matches(tree.rootNode);
452
+ }
453
+ catch (queryError) {
454
+ console.warn(`Query error for ${file.path}:`, queryError);
455
+ continue;
456
+ }
457
+ // Pre-pass: extract heritage from query matches to build parentMap for buildTypeEnv.
458
+ // Heritage-processor runs in PARALLEL, so graph edges don't exist when buildTypeEnv runs.
459
+ const fileParentMap = new Map();
460
+ for (const match of matches) {
461
+ const captureMap = {};
462
+ match.captures.forEach((c) => (captureMap[c.name] = c.node));
463
+ if (captureMap['heritage.class'] && captureMap['heritage.extends']) {
464
+ const className = captureMap['heritage.class'].text;
465
+ const parentName = captureMap['heritage.extends'].text;
466
+ const extendsNode = captureMap['heritage.extends'];
467
+ const fieldDecl = extendsNode.parent;
468
+ if (fieldDecl?.type === 'field_declaration' && fieldDecl.childForFieldName('name'))
469
+ continue;
470
+ let parents = fileParentMap.get(className);
471
+ if (!parents) {
472
+ parents = [];
473
+ fileParentMap.set(className, parents);
474
+ }
475
+ if (!parents.includes(parentName))
476
+ parents.push(parentName);
477
+ }
478
+ }
479
+ const parentMap = fileParentMap;
480
+ // Merge per-file heritage into globalParentMap for cross-file isSubclassOf lookups.
481
+ // Uses a parallel Set (globalParentSeen) for O(1) deduplication instead of O(n) includes().
482
+ for (const [cls, parents] of fileParentMap) {
483
+ let global = globalParentMap.get(cls);
484
+ let seen = globalParentSeen.get(cls);
485
+ if (!global) {
486
+ global = [];
487
+ globalParentMap.set(cls, global);
488
+ }
489
+ if (!seen) {
490
+ seen = new Set();
491
+ globalParentSeen.set(cls, seen);
492
+ }
493
+ for (const p of parents) {
494
+ if (!seen.has(p)) {
495
+ seen.add(p);
496
+ global.push(p);
497
+ }
498
+ }
499
+ }
500
+ const importedBindings = importedBindingsMap?.get(file.path);
501
+ const importedReturnTypes = importedReturnTypesMap?.get(file.path);
502
+ const importedRawReturnTypes = importedRawReturnTypesMap?.get(file.path);
503
+ const typeEnv = buildTypeEnv(tree, language, {
504
+ symbolTable: ctx.symbols,
505
+ parentMap,
506
+ importedBindings,
507
+ importedReturnTypes,
508
+ importedRawReturnTypes,
509
+ enclosingFunctionFinder: provider?.enclosingFunctionFinder,
510
+ extractFunctionName: provider?.methodExtractor?.extractFunctionName,
511
+ });
512
+ if (typeEnv && exportedTypeMap) {
513
+ const fileExports = collectExportedBindings(typeEnv, file.path, ctx.symbols, graph);
514
+ if (fileExports)
515
+ exportedTypeMap.set(file.path, fileExports);
516
+ }
517
+ const callRouter = provider.callRouter;
518
+ const verifiedReceivers = typeEnv.constructorBindings.length > 0
519
+ ? verifyConstructorBindings(typeEnv.constructorBindings, file.path, ctx)
520
+ : new Map();
521
+ const receiverIndex = buildReceiverTypeIndex(verifiedReceivers);
522
+ ctx.enableCache(file.path);
523
+ const widenCache = new Map();
524
+ matches.forEach((match) => {
525
+ const captureMap = {};
526
+ match.captures.forEach((c) => (captureMap[c.name] = c.node));
527
+ // ── Write access: emit ACCESSES {reason: 'write'} for assignments to member fields ──
528
+ if (captureMap['assignment'] &&
529
+ captureMap['assignment.receiver'] &&
530
+ captureMap['assignment.property']) {
531
+ const receiverNode = captureMap['assignment.receiver'];
532
+ const propertyName = captureMap['assignment.property'].text;
533
+ // Resolve receiver type: simple identifier → TypeEnv lookup or class resolution
534
+ let receiverTypeName;
535
+ const receiverText = receiverNode.text;
536
+ if (receiverText && typeEnv) {
537
+ receiverTypeName = typeEnv.lookup(receiverText, captureMap['assignment']);
538
+ }
539
+ // Fall back to verified constructor bindings (mirrors CALLS resolution tier 2)
540
+ if (!receiverTypeName && receiverText && receiverIndex.size > 0) {
541
+ const enclosing = findEnclosingFunction(captureMap['assignment'], file.path, ctx, provider);
542
+ const funcName = enclosing ? extractFuncNameFromSourceId(enclosing) : '';
543
+ receiverTypeName = lookupReceiverType(receiverIndex, funcName, receiverText);
544
+ }
545
+ if (!receiverTypeName && receiverText) {
546
+ const resolved = ctx.resolve(receiverText, file.path);
547
+ if (resolved?.candidates.some((d) => d.type === 'Class' ||
548
+ d.type === 'Struct' ||
549
+ d.type === 'Interface' ||
550
+ d.type === 'Enum' ||
551
+ d.type === 'Record' ||
552
+ d.type === 'Impl')) {
553
+ receiverTypeName = receiverText;
554
+ }
555
+ }
556
+ if (receiverTypeName) {
557
+ const enclosing = findEnclosingFunction(captureMap['assignment'], file.path, ctx, provider);
558
+ const srcId = enclosing || generateId('File', file.path);
559
+ // Defer resolution: Ruby attr_accessor properties are registered during
560
+ // this same loop, so cross-file lookups fail if the declaring file hasn't
561
+ // been processed yet. Collect now, resolve after all files are done.
562
+ pendingWrites.push({ receiverTypeName, propertyName, filePath: file.path, srcId });
563
+ }
564
+ // Assignment-only capture (no @call sibling): skip the rest of this
565
+ // forEach iteration — this acts as a `continue` in the match loop.
566
+ if (!captureMap['call'])
567
+ return;
568
+ }
569
+ if (!captureMap['call'])
570
+ return;
571
+ const callNode = captureMap['call'];
572
+ const languageSeed = extractParsedCallSite(language, callNode);
573
+ if (languageSeed) {
574
+ if (provider.isBuiltInName(languageSeed.calledName))
575
+ return;
576
+ const sourceId = findEnclosingFunction(callNode, file.path, ctx, provider) ||
577
+ generateId('File', file.path);
578
+ const receiverName = languageSeed.callForm === 'member' ? languageSeed.receiverName : undefined;
579
+ let receiverTypeName = receiverName && typeEnv ? typeEnv.lookup(receiverName, callNode) : undefined;
580
+ if (receiverName !== undefined &&
581
+ receiverTypeName === undefined &&
582
+ languageSeed.callForm === 'member' &&
583
+ (language === 'java' || language === 'csharp' || language === 'kotlin')) {
584
+ const c0 = receiverName.charCodeAt(0);
585
+ if (c0 >= 65 && c0 <= 90)
586
+ receiverTypeName = receiverName;
587
+ }
588
+ const resolved = resolveCallTarget({
589
+ calledName: languageSeed.calledName,
590
+ callForm: languageSeed.callForm,
591
+ ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
592
+ ...(receiverName !== undefined ? { receiverName } : {}),
593
+ }, file.path, ctx, undefined, widenCache);
594
+ if (!resolved)
595
+ return;
596
+ graph.addRelationship({
597
+ id: generateId('CALLS', `${sourceId}:${languageSeed.calledName}->${resolved.nodeId}`),
598
+ sourceId,
599
+ targetId: resolved.nodeId,
600
+ type: 'CALLS',
601
+ confidence: resolved.confidence,
602
+ reason: resolved.reason,
603
+ });
604
+ if (implementorMap && languageSeed.callForm === 'member' && receiverTypeName) {
605
+ const implTargets = findInterfaceDispatchTargets(languageSeed.calledName, receiverTypeName, file.path, ctx, implementorMap, resolved.nodeId);
606
+ for (const impl of implTargets) {
607
+ graph.addRelationship({
608
+ id: generateId('CALLS', `${sourceId}:${languageSeed.calledName}->${impl.nodeId}`),
609
+ sourceId,
610
+ targetId: impl.nodeId,
611
+ type: 'CALLS',
612
+ confidence: impl.confidence,
613
+ reason: impl.reason,
614
+ });
615
+ }
616
+ }
617
+ return;
618
+ }
619
+ const nameNode = captureMap['call.name'];
620
+ if (!nameNode)
621
+ return;
622
+ const calledName = nameNode.text;
623
+ const routed = callRouter?.(calledName, captureMap['call']);
624
+ if (routed) {
625
+ switch (routed.kind) {
626
+ case 'skip':
627
+ case 'import':
628
+ return;
629
+ case 'heritage':
630
+ for (const item of routed.items) {
631
+ collectedHeritage.push({
632
+ filePath: file.path,
633
+ className: item.enclosingClass,
634
+ parentName: item.mixinName,
635
+ kind: item.heritageKind,
636
+ });
637
+ }
638
+ return;
639
+ case 'properties': {
640
+ const fileId = generateId('File', file.path);
641
+ const propEnclosingClassId = findEnclosingClassId(captureMap['call'], file.path);
642
+ for (const item of routed.items) {
643
+ const nodeId = generateId('Property', `${file.path}:${item.propName}`);
644
+ graph.addNode({
645
+ id: nodeId,
646
+ label: 'Property',
647
+ properties: {
648
+ name: item.propName,
649
+ filePath: file.path,
650
+ startLine: item.startLine,
651
+ endLine: item.endLine,
652
+ language,
653
+ isExported: true,
654
+ description: item.accessorType,
655
+ },
656
+ });
657
+ ctx.symbols.add(file.path, item.propName, nodeId, 'Property', {
658
+ ...(propEnclosingClassId ? { ownerId: propEnclosingClassId } : {}),
659
+ ...(item.declaredType ? { declaredType: item.declaredType } : {}),
660
+ });
661
+ const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
662
+ graph.addRelationship({
663
+ id: relId,
664
+ sourceId: fileId,
665
+ targetId: nodeId,
666
+ type: 'DEFINES',
667
+ confidence: 1.0,
668
+ reason: '',
669
+ });
670
+ if (propEnclosingClassId) {
671
+ graph.addRelationship({
672
+ id: generateId('HAS_PROPERTY', `${propEnclosingClassId}->${nodeId}`),
673
+ sourceId: propEnclosingClassId,
674
+ targetId: nodeId,
675
+ type: 'HAS_PROPERTY',
676
+ confidence: 1.0,
677
+ reason: '',
678
+ });
679
+ }
680
+ }
681
+ return;
682
+ }
683
+ case 'call':
684
+ break;
685
+ }
686
+ }
687
+ if (provider.isBuiltInName(calledName))
688
+ return;
689
+ const callForm = inferCallForm(callNode, nameNode);
690
+ const receiverName = callForm === 'member' ? extractReceiverName(nameNode) : undefined;
691
+ let receiverTypeName = receiverName && typeEnv ? typeEnv.lookup(receiverName, callNode) : undefined;
692
+ // Phase P: virtual dispatch override — when the declared type is a base class but
693
+ // the constructor created a known subclass, prefer the more specific type.
694
+ // Checks per-file parentMap first, then falls back to globalParentMap for
695
+ // cross-file heritage (e.g. Dog extends Animal declared in a different file).
696
+ // Reconstructs the exact scope key (funcName@startIndex\0varName) from the
697
+ // enclosing function AST node for a correct, O(1) map lookup.
698
+ if (receiverTypeName && receiverName && typeEnv && typeEnv.constructorTypeMap.size > 0) {
699
+ // Reconstruct scope key to match constructorTypeMap's scope\0varName format
700
+ let scope = '';
701
+ let p = callNode.parent;
702
+ while (p) {
703
+ if (FUNCTION_NODE_TYPES.has(p.type)) {
704
+ const funcName = provider.methodExtractor?.extractFunctionName?.(p)?.funcName ?? genericFuncName(p);
705
+ if (funcName) {
706
+ scope = `${funcName}@${p.startIndex}`;
707
+ break;
708
+ }
709
+ }
710
+ p = p.parent;
711
+ }
712
+ const ctorType = typeEnv.constructorTypeMap.get(`${scope}\0${receiverName}`);
713
+ if (ctorType && ctorType !== receiverTypeName) {
714
+ // Verify subclass relationship: per-file parentMap first, then cross-file
715
+ // globalParentMap, then fall back to SymbolTable class verification.
716
+ // The SymbolTable fallback handles cross-file cases where heritage is declared
717
+ // in a file not yet processed (e.g. Dog extends Animal in models/Dog.kt when
718
+ // processing services/App.kt). Since constructorTypeMap only records entries
719
+ // when a type annotation AND constructor are both present (val x: Base = Sub()),
720
+ // confirming both are class-like types is sufficient — the original code would
721
+ // not compile if Sub didn't extend Base.
722
+ if (isSubclassOf(ctorType, receiverTypeName, parentMap) ||
723
+ isSubclassOf(ctorType, receiverTypeName, globalParentMap) ||
724
+ (ctx.symbols
725
+ .lookupFuzzy(ctorType)
726
+ .some((d) => d.type === 'Class' || d.type === 'Struct') &&
727
+ ctx.symbols
728
+ .lookupFuzzy(receiverTypeName)
729
+ .some((d) => d.type === 'Class' || d.type === 'Struct' || d.type === 'Interface'))) {
730
+ receiverTypeName = ctorType;
731
+ }
732
+ }
733
+ }
734
+ // Fall back to verified constructor bindings for return type inference
735
+ if (!receiverTypeName && receiverName && receiverIndex.size > 0) {
736
+ const enclosingFunc = findEnclosingFunction(callNode, file.path, ctx, provider);
737
+ const funcName = enclosingFunc ? extractFuncNameFromSourceId(enclosingFunc) : '';
738
+ receiverTypeName = lookupReceiverType(receiverIndex, funcName, receiverName);
739
+ }
740
+ // Fall back to class-as-receiver for static method calls (e.g. UserService.find_user()).
741
+ // When the receiver name is not a variable in TypeEnv but resolves to a Class/Struct/Interface
742
+ // through the standard tiered resolution, use it directly as the receiver type.
743
+ if (!receiverTypeName && receiverName && callForm === 'member') {
744
+ const typeResolved = ctx.resolve(receiverName, file.path);
745
+ if (typeResolved &&
746
+ typeResolved.candidates.some((d) => d.type === 'Class' ||
747
+ d.type === 'Interface' ||
748
+ d.type === 'Struct' ||
749
+ d.type === 'Enum')) {
750
+ receiverTypeName = receiverName;
751
+ }
752
+ }
753
+ // Hoist sourceId so it's available for ACCESSES edge emission during chain walk.
754
+ const enclosingFuncId = findEnclosingFunction(callNode, file.path, ctx, provider);
755
+ const sourceId = enclosingFuncId || generateId('File', file.path);
756
+ // Fall back to mixed chain resolution when the receiver is a complex expression
757
+ // (field chain, call chain, or interleaved — e.g. user.address.city.save() or
758
+ // svc.getUser().address.save()). Handles all cases with a single unified walk.
759
+ if (callForm === 'member' && !receiverTypeName && !receiverName) {
760
+ const receiverNode = extractReceiverNode(nameNode);
761
+ if (receiverNode) {
762
+ const extracted = extractMixedChain(receiverNode);
763
+ if (extracted && extracted.chain.length > 0) {
764
+ let currentType = extracted.baseReceiverName && typeEnv
765
+ ? typeEnv.lookup(extracted.baseReceiverName, callNode)
766
+ : undefined;
767
+ if (!currentType && extracted.baseReceiverName && receiverIndex.size > 0) {
768
+ const funcName = enclosingFuncId ? extractFuncNameFromSourceId(enclosingFuncId) : '';
769
+ currentType = lookupReceiverType(receiverIndex, funcName, extracted.baseReceiverName);
770
+ }
771
+ if (!currentType && extracted.baseReceiverName) {
772
+ const cr = ctx.resolve(extracted.baseReceiverName, file.path);
773
+ if (cr?.candidates.some((d) => d.type === 'Class' ||
774
+ d.type === 'Interface' ||
775
+ d.type === 'Struct' ||
776
+ d.type === 'Enum')) {
777
+ currentType = extracted.baseReceiverName;
778
+ }
779
+ }
780
+ if (currentType) {
781
+ receiverTypeName = walkMixedChain(extracted.chain, currentType, file.path, ctx, makeAccessEmitter(graph, sourceId));
782
+ }
783
+ }
784
+ }
785
+ }
786
+ // Build overload hints for languages with inferLiteralType (Java/Kotlin/C#/C++).
787
+ // Only used when multiple candidates survive arity filtering — ~1-3% of calls.
788
+ const langConfig = provider.typeConfig;
789
+ const hints = langConfig?.inferLiteralType
790
+ ? { callNode, inferLiteralType: langConfig.inferLiteralType, typeEnv }
791
+ : undefined;
792
+ const resolved = resolveCallTarget({
793
+ calledName,
794
+ argCount: countCallArguments(callNode),
795
+ callForm,
796
+ receiverTypeName,
797
+ receiverName,
798
+ }, file.path, ctx, hints, widenCache);
799
+ if (!resolved)
800
+ return;
801
+ const relId = generateId('CALLS', `${sourceId}:${calledName}->${resolved.nodeId}`);
802
+ graph.addRelationship({
803
+ id: relId,
804
+ sourceId,
805
+ targetId: resolved.nodeId,
806
+ type: 'CALLS',
807
+ confidence: resolved.confidence,
808
+ reason: resolved.reason,
809
+ });
810
+ if (implementorMap && callForm === 'member' && receiverTypeName) {
811
+ const implTargets = findInterfaceDispatchTargets(calledName, receiverTypeName, file.path, ctx, implementorMap, resolved.nodeId);
812
+ for (const impl of implTargets) {
813
+ graph.addRelationship({
814
+ id: generateId('CALLS', `${sourceId}:${calledName}->${impl.nodeId}`),
815
+ sourceId,
816
+ targetId: impl.nodeId,
817
+ type: 'CALLS',
818
+ confidence: impl.confidence,
819
+ reason: impl.reason,
820
+ });
821
+ }
822
+ }
823
+ });
824
+ // Vue: emit CALLS edges for PascalCase components used in <template>.
825
+ // Template components are default-imported (not named), so we match the
826
+ // component name against imported .vue file basenames via the import map.
827
+ if (language === SupportedLanguages.Vue) {
828
+ const templateComponents = extractTemplateComponents(file.content);
829
+ if (templateComponents.length > 0) {
830
+ const fileId = generateId('File', file.path);
831
+ const importedFiles = ctx.importMap.get(file.path);
832
+ if (importedFiles) {
833
+ for (const componentName of templateComponents) {
834
+ for (const importedPath of importedFiles) {
835
+ if (!importedPath.endsWith('.vue'))
836
+ continue;
837
+ const basename = importedPath.slice(importedPath.lastIndexOf('/') + 1, importedPath.lastIndexOf('.'));
838
+ if (basename !== componentName)
839
+ continue;
840
+ const targetFileId = generateId('File', importedPath);
841
+ if (graph.getNode(targetFileId)) {
842
+ graph.addRelationship({
843
+ id: generateId('CALLS', `${fileId}:${componentName}->${targetFileId}`),
844
+ sourceId: fileId,
845
+ targetId: targetFileId,
846
+ type: 'CALLS',
847
+ confidence: 0.9,
848
+ reason: 'vue-template-component',
849
+ });
850
+ }
851
+ break;
852
+ }
853
+ }
854
+ }
855
+ }
856
+ }
857
+ ctx.clearCache();
858
+ }
859
+ // ── Resolve deferred write-access edges ──
860
+ // All properties (including Ruby attr_accessor) are now registered.
861
+ for (const pw of pendingWrites) {
862
+ const fieldOwner = resolveFieldOwnership(pw.receiverTypeName, pw.propertyName, pw.filePath, ctx);
863
+ if (fieldOwner) {
864
+ graph.addRelationship({
865
+ id: generateId('ACCESSES', `${pw.srcId}:${fieldOwner.nodeId}:write`),
866
+ sourceId: pw.srcId,
867
+ targetId: fieldOwner.nodeId,
868
+ type: 'ACCESSES',
869
+ confidence: 1.0,
870
+ reason: 'write',
871
+ });
872
+ }
873
+ }
874
+ if (skippedByLang && skippedByLang.size > 0) {
875
+ for (const [lang, count] of skippedByLang.entries()) {
876
+ console.warn(`[ingestion] Skipped ${count} ${lang} file(s) in call processing — ${lang} parser not available.`);
877
+ }
878
+ }
879
+ return collectedHeritage;
880
+ };
881
+ const CALLABLE_SYMBOL_TYPES = new Set(['Function', 'Method', 'Constructor', 'Macro', 'Delegate']);
882
+ const CONSTRUCTOR_TARGET_TYPES = new Set(['Constructor', 'Class', 'Struct', 'Record']);
883
+ const filterCallableCandidates = (candidates, argCount, callForm) => {
884
+ let kindFiltered;
885
+ if (callForm === 'constructor') {
886
+ const constructors = candidates.filter((c) => c.type === 'Constructor');
887
+ if (constructors.length > 0) {
888
+ kindFiltered = constructors;
889
+ }
890
+ else {
891
+ const types = candidates.filter((c) => CONSTRUCTOR_TARGET_TYPES.has(c.type));
892
+ kindFiltered =
893
+ types.length > 0 ? types : candidates.filter((c) => CALLABLE_SYMBOL_TYPES.has(c.type));
894
+ }
895
+ }
896
+ else {
897
+ kindFiltered = candidates.filter((c) => CALLABLE_SYMBOL_TYPES.has(c.type));
898
+ }
899
+ if (kindFiltered.length === 0)
900
+ return [];
901
+ if (argCount === undefined)
902
+ return kindFiltered;
903
+ const hasParameterMetadata = kindFiltered.some((candidate) => candidate.parameterCount !== undefined);
904
+ if (!hasParameterMetadata)
905
+ return kindFiltered;
906
+ return kindFiltered.filter((candidate) => candidate.parameterCount === undefined ||
907
+ (argCount >= (candidate.requiredParameterCount ?? candidate.parameterCount) &&
908
+ argCount <= candidate.parameterCount));
909
+ };
910
+ const toResolveResult = (definition, tier) => ({
911
+ nodeId: definition.nodeId,
912
+ confidence: TIER_CONFIDENCE[tier],
913
+ reason: tier === 'same-file' ? 'same-file' : tier === 'import-scoped' ? 'import-resolved' : 'global',
914
+ returnType: definition.returnType,
915
+ });
916
+ /**
917
+ * Kotlin often declares parameters with boxed names (`Int`, `Boolean`, …) while
918
+ * literal inference yields JVM primitives (`int`, `boolean`). This map aligns
919
+ * those for overload matching. Java parameter text is usually already primitive
920
+ * spellings, so lookups here are typically unchanged.
921
+ */
922
+ const KOTLIN_BOXED_TO_PRIMITIVE = {
923
+ Int: 'int',
924
+ Long: 'long',
925
+ Short: 'short',
926
+ Byte: 'byte',
927
+ Float: 'float',
928
+ Double: 'double',
929
+ Boolean: 'boolean',
930
+ Char: 'char',
931
+ };
932
+ const normalizeJvmTypeName = (name) => KOTLIN_BOXED_TO_PRIMITIVE[name] ?? name;
933
+ const matchCandidatesByArgTypes = (candidates, argTypes) => {
934
+ if (!candidates.some((c) => c.parameterTypes))
935
+ return null;
936
+ const matched = candidates.filter((c) => {
937
+ // Keep candidates without type info — conservative: partially-annotated codebases
938
+ // (e.g. C++ with some missing declarations) may have mixed typed/untyped overloads.
939
+ // If one typed and one untyped both survive, matched.length > 1 → returns null (no edge).
940
+ if (!c.parameterTypes)
941
+ return true;
942
+ return c.parameterTypes.every((pType, i) => {
943
+ if (i >= argTypes.length || !argTypes[i])
944
+ return true;
945
+ // Normalise Kotlin boxed type names (Int→int, Boolean→boolean, etc.) so
946
+ // that the stored declaration type matches the inferred literal type.
947
+ return normalizeJvmTypeName(pType) === argTypes[i];
948
+ });
949
+ });
950
+ if (matched.length === 1)
951
+ return matched[0];
952
+ // Multiple survivors may share the same nodeId (e.g. TypeScript overload signatures +
953
+ // implementation body all collide via generateId). Deduplicate by nodeId — if all
954
+ // matched candidates resolve to the same graph node, disambiguation succeeded.
955
+ if (matched.length > 1) {
956
+ const uniqueIds = new Set(matched.map((c) => c.nodeId));
957
+ if (uniqueIds.size === 1)
958
+ return matched[0];
959
+ }
960
+ return null;
961
+ };
962
+ /**
963
+ * Try to disambiguate overloaded candidates using argument literal types.
964
+ * Only invoked when filteredCandidates.length > 1 and at least one has parameterTypes.
965
+ * Returns the single matching candidate, or null if ambiguous/inconclusive.
966
+ */
967
+ const tryOverloadDisambiguation = (candidates, hints) => {
968
+ const argTypes = extractCallArgTypes(hints.callNode, hints.inferLiteralType, hints.typeEnv ? (varName, cn) => hints.typeEnv.lookup(varName, cn) : undefined);
969
+ if (!argTypes)
970
+ return null;
971
+ return matchCandidatesByArgTypes(candidates, argTypes);
972
+ };
973
+ const resolveCallTarget = (call, currentFile, ctx, overloadHints, widenCache, preComputedArgTypes) => {
974
+ const tiered = ctx.resolve(call.calledName, currentFile);
975
+ if (!tiered)
976
+ return null;
977
+ let filteredCandidates = filterCallableCandidates(tiered.candidates, call.argCount, call.callForm);
978
+ // Swift/Kotlin: constructor calls look like free function calls (no `new` keyword).
979
+ // If free-form filtering found no callable candidates but the symbol resolves to a
980
+ // Class/Struct, retry with constructor form so CONSTRUCTOR_TARGET_TYPES applies.
981
+ if (filteredCandidates.length === 0 && call.callForm === 'free') {
982
+ const hasTypeTarget = tiered.candidates.some((c) => c.type === 'Class' || c.type === 'Struct' || c.type === 'Enum');
983
+ if (hasTypeTarget) {
984
+ filteredCandidates = filterCallableCandidates(tiered.candidates, call.argCount, 'constructor');
985
+ }
986
+ }
987
+ // Module-qualified constructor pattern: e.g. Python `import models; models.User()`.
988
+ // The attribute access gives callForm='member', but the callee may be a Class — a valid
989
+ // constructor target. Re-try with constructor-form filtering so that `module.ClassName()`
990
+ // emits a CALLS edge to the class node.
991
+ if (filteredCandidates.length === 0 && call.callForm === 'member') {
992
+ filteredCandidates = filterCallableCandidates(tiered.candidates, call.argCount, 'constructor');
993
+ }
994
+ // Module-alias disambiguation: Python `import auth; auth.User()` — receiverName='auth'
995
+ // selects auth.py via moduleAliasMap. Runs for ALL member calls with a known module alias,
996
+ // not just ambiguous ones — same-file tier may shadow the correct cross-module target when
997
+ // the caller defines a function with the same name as the callee (Issue #417).
998
+ if (call.callForm === 'member' && call.receiverName) {
999
+ const aliasMap = ctx.moduleAliasMap?.get(currentFile);
1000
+ if (aliasMap) {
1001
+ const moduleFile = aliasMap.get(call.receiverName);
1002
+ if (moduleFile) {
1003
+ const aliasFiltered = filteredCandidates.filter((c) => c.filePath === moduleFile);
1004
+ if (aliasFiltered.length > 0) {
1005
+ filteredCandidates = aliasFiltered;
1006
+ }
1007
+ else {
1008
+ // Same-file tier returned a local match, but the alias points elsewhere.
1009
+ // Widen to global candidates and filter to the aliased module's file.
1010
+ // Use per-file widenCache to avoid repeated lookupFuzzy for the same
1011
+ // calledName+moduleFile from multiple call sites in the same file.
1012
+ const cacheKey = `${call.calledName}\0${moduleFile}`;
1013
+ let fuzzyDefs = widenCache?.get(cacheKey);
1014
+ if (!fuzzyDefs) {
1015
+ fuzzyDefs = ctx.symbols.lookupFuzzy(call.calledName);
1016
+ widenCache?.set(cacheKey, fuzzyDefs);
1017
+ }
1018
+ const widened = filterCallableCandidates(fuzzyDefs, call.argCount, call.callForm).filter((c) => c.filePath === moduleFile);
1019
+ if (widened.length > 0)
1020
+ filteredCandidates = widened;
1021
+ }
1022
+ }
1023
+ }
1024
+ }
1025
+ // D. Receiver-type filtering: for member calls with a known receiver type,
1026
+ // resolve the type through the same tiered import infrastructure, then
1027
+ // filter method candidates to the type's defining file. Fall back to
1028
+ // fuzzy ownerId matching only when file-based narrowing is inconclusive.
1029
+ //
1030
+ // Applied regardless of candidate count — the sole same-file candidate may
1031
+ // belong to the wrong class (e.g. super.save() should hit the parent's save,
1032
+ // not the child's own save method in the same file).
1033
+ if (call.callForm === 'member' && call.receiverTypeName) {
1034
+ // D1. Resolve the receiver type
1035
+ const typeResolved = ctx.resolve(call.receiverTypeName, currentFile);
1036
+ if (typeResolved && typeResolved.candidates.length > 0) {
1037
+ const typeNodeIds = new Set(typeResolved.candidates.map((d) => d.nodeId));
1038
+ const typeFiles = new Set(typeResolved.candidates.map((d) => d.filePath));
1039
+ // D2. Widen candidates: same-file tier may miss the parent's method when
1040
+ // it lives in another file. Query the symbol table directly for all
1041
+ // global methods with this name, then apply arity/kind filtering.
1042
+ // Cache the fuzzy lookup result per file to avoid O(n²) on large repos.
1043
+ let methodPool;
1044
+ if (filteredCandidates.length <= 1) {
1045
+ const fuzzyKey = `_fuzzy_${call.calledName}_${call.argCount ?? ''}_${call.callForm}`;
1046
+ const cached = widenCache?.get(fuzzyKey);
1047
+ if (cached) {
1048
+ methodPool = cached;
1049
+ }
1050
+ else {
1051
+ methodPool = filterCallableCandidates(ctx.symbols.lookupFuzzy(call.calledName), call.argCount, call.callForm);
1052
+ widenCache?.set(fuzzyKey, methodPool);
1053
+ }
1054
+ }
1055
+ else {
1056
+ methodPool = filteredCandidates;
1057
+ }
1058
+ // D3. File-based: prefer candidates whose filePath matches the resolved type's file
1059
+ const fileFiltered = methodPool.filter((c) => typeFiles.has(c.filePath));
1060
+ if (fileFiltered.length === 1) {
1061
+ return toResolveResult(fileFiltered[0], tiered.tier);
1062
+ }
1063
+ // D4. ownerId fallback: narrow by ownerId matching the type's nodeId
1064
+ const pool = fileFiltered.length > 0 ? fileFiltered : methodPool;
1065
+ const ownerFiltered = pool.filter((c) => c.ownerId && typeNodeIds.has(c.ownerId));
1066
+ if (ownerFiltered.length === 1) {
1067
+ return toResolveResult(ownerFiltered[0], tiered.tier);
1068
+ }
1069
+ // E. Try overload disambiguation on the narrowed pool
1070
+ if (fileFiltered.length > 1 || ownerFiltered.length > 1) {
1071
+ const overloadPool = ownerFiltered.length > 1 ? ownerFiltered : fileFiltered;
1072
+ const disambiguated = overloadHints
1073
+ ? tryOverloadDisambiguation(overloadPool, overloadHints)
1074
+ : preComputedArgTypes
1075
+ ? matchCandidatesByArgTypes(overloadPool, preComputedArgTypes)
1076
+ : null;
1077
+ if (disambiguated)
1078
+ return toResolveResult(disambiguated, tiered.tier);
1079
+ return null;
1080
+ }
1081
+ }
1082
+ }
1083
+ // E. Overload disambiguation: when multiple candidates survive arity + receiver filtering,
1084
+ // try matching argument types against parameter types (Phase P).
1085
+ // Sequential path uses AST-based hints; worker path uses pre-computed argTypes.
1086
+ if (filteredCandidates.length > 1) {
1087
+ const disambiguated = overloadHints
1088
+ ? tryOverloadDisambiguation(filteredCandidates, overloadHints)
1089
+ : preComputedArgTypes
1090
+ ? matchCandidatesByArgTypes(filteredCandidates, preComputedArgTypes)
1091
+ : null;
1092
+ if (disambiguated)
1093
+ return toResolveResult(disambiguated, tiered.tier);
1094
+ }
1095
+ if (filteredCandidates.length !== 1) {
1096
+ // Deduplicate: Swift extensions create multiple Class nodes with the same name.
1097
+ // When all candidates share the same type and differ only by file (extension vs
1098
+ // primary definition), they represent the same symbol. Prefer the primary
1099
+ // definition (shortest file path: Product.swift over ProductExtension.swift).
1100
+ if (filteredCandidates.length > 1) {
1101
+ const allSameType = filteredCandidates.every((c) => c.type === filteredCandidates[0].type);
1102
+ if (allSameType &&
1103
+ (filteredCandidates[0].type === 'Class' || filteredCandidates[0].type === 'Struct')) {
1104
+ const sorted = [...filteredCandidates].sort((a, b) => a.filePath.length - b.filePath.length);
1105
+ return toResolveResult(sorted[0], tiered.tier);
1106
+ }
1107
+ }
1108
+ return null;
1109
+ }
1110
+ return toResolveResult(filteredCandidates[0], tiered.tier);
1111
+ };
1112
+ // ── Scope key helpers ────────────────────────────────────────────────────
1113
+ // Scope keys use the format "funcName@startIndex" (produced by type-env.ts).
1114
+ // Source IDs use "Label:filepath:funcName" (produced by parse-worker.ts).
1115
+ // NUL (\0) is used as a composite-key separator because it cannot appear
1116
+ // in source-code identifiers, preventing ambiguous concatenation.
1117
+ //
1118
+ // receiverKey stores the FULL scope (funcName@startIndex) to prevent
1119
+ // collisions between overloaded methods with the same name in different
1120
+ // classes (e.g. User.save@100 and Repo.save@200 are distinct keys).
1121
+ // Lookup uses a secondary funcName-only index built in lookupReceiverType.
1122
+ /** Extract the function name from a scope key ("funcName@startIndex" → "funcName"). */
1123
+ const extractFuncNameFromScope = (scope) => scope.slice(0, scope.indexOf('@'));
1124
+ /** Extract the bare function name from a sourceId.
1125
+ * Handles both unqualified ("Function:filepath:funcName" → "funcName")
1126
+ * and qualified ("Function:filepath:ClassName.funcName" → "funcName").
1127
+ * Strips any trailing #<arity> suffix from Method/Constructor IDs. */
1128
+ const extractFuncNameFromSourceId = (sourceId) => {
1129
+ const lastColon = sourceId.lastIndexOf(':');
1130
+ const segment = lastColon >= 0 ? sourceId.slice(lastColon + 1) : '';
1131
+ const dotIdx = segment.lastIndexOf('.');
1132
+ const raw = dotIdx >= 0 ? segment.slice(dotIdx + 1) : segment;
1133
+ // Strip #<arity> suffix (e.g. "save#2" → "save")
1134
+ const hashIdx = raw.indexOf('#');
1135
+ return hashIdx >= 0 ? raw.slice(0, hashIdx) : raw;
1136
+ };
1137
+ /**
1138
+ * Build a composite key for receiver type storage.
1139
+ * Uses the full scope string (e.g. "save@100") to distinguish overloaded
1140
+ * methods with the same name in different classes.
1141
+ */
1142
+ const receiverKey = (scope, varName) => `${scope}\0${varName}`;
1143
+ /**
1144
+ * Build a two-level secondary index from the verified receiver map.
1145
+ * The verified map is keyed by `scope\0varName` where scope is either
1146
+ * "funcName@startIndex" (inside a function) or "" (file level).
1147
+ * Index structure: Map<funcName, Map<varName, ReceiverTypeEntry>>
1148
+ */
1149
+ const buildReceiverTypeIndex = (map) => {
1150
+ const index = new Map();
1151
+ for (const [key, typeName] of map) {
1152
+ const nul = key.indexOf('\0');
1153
+ if (nul < 0)
1154
+ continue;
1155
+ const scope = key.slice(0, nul);
1156
+ const varName = key.slice(nul + 1);
1157
+ if (!varName)
1158
+ continue;
1159
+ if (scope !== '' && !scope.includes('@'))
1160
+ continue;
1161
+ const funcName = scope === '' ? '' : scope.slice(0, scope.indexOf('@'));
1162
+ let varMap = index.get(funcName);
1163
+ if (!varMap) {
1164
+ varMap = new Map();
1165
+ index.set(funcName, varMap);
1166
+ }
1167
+ const existing = varMap.get(varName);
1168
+ if (existing === undefined) {
1169
+ varMap.set(varName, { kind: 'resolved', value: typeName });
1170
+ }
1171
+ else if (existing.kind === 'resolved' && existing.value !== typeName) {
1172
+ varMap.set(varName, { kind: 'ambiguous' });
1173
+ }
1174
+ }
1175
+ return index;
1176
+ };
1177
+ /**
1178
+ * O(1) receiver type lookup using the pre-built secondary index.
1179
+ * Returns the unique type name if unambiguous. Falls back to file-level scope.
1180
+ */
1181
+ const lookupReceiverType = (index, funcName, varName) => {
1182
+ const funcBucket = index.get(funcName);
1183
+ if (funcBucket) {
1184
+ const entry = funcBucket.get(varName);
1185
+ if (entry?.kind === 'resolved')
1186
+ return entry.value;
1187
+ if (entry?.kind === 'ambiguous') {
1188
+ // Ambiguous in this function scope — try file-level fallback
1189
+ const fileEntry = index.get('')?.get(varName);
1190
+ return fileEntry?.kind === 'resolved' ? fileEntry.value : undefined;
1191
+ }
1192
+ }
1193
+ // Fallback: file-level scope (funcName "")
1194
+ if (funcName !== '') {
1195
+ const fileEntry = index.get('')?.get(varName);
1196
+ if (fileEntry?.kind === 'resolved')
1197
+ return fileEntry.value;
1198
+ }
1199
+ return undefined;
1200
+ };
1201
+ /**
1202
+ * Resolve the type that results from accessing `receiverName.fieldName`.
1203
+ * Requires declaredType on the Property node (needed for chain walking continuation).
1204
+ */
1205
+ const resolveFieldAccessType = (receiverName, fieldName, filePath, ctx) => {
1206
+ const fieldDef = resolveFieldOwnership(receiverName, fieldName, filePath, ctx);
1207
+ if (!fieldDef?.declaredType)
1208
+ return undefined;
1209
+ // Use stripNullable (not extractReturnTypeName) — field types like List<User>
1210
+ // should be preserved as-is, not unwrapped to User. Only strip nullable wrappers.
1211
+ return {
1212
+ typeName: stripNullable(fieldDef.declaredType) ?? fieldDef.declaredType,
1213
+ fieldNodeId: fieldDef.nodeId,
1214
+ };
1215
+ };
1216
+ /**
1217
+ * Resolve a field's Property node given a receiver type name and field name.
1218
+ * Does NOT require declaredType — used by write-access tracking where only the
1219
+ * fieldNodeId is needed (no chain continuation).
1220
+ */
1221
+ const resolveFieldOwnership = (receiverName, fieldName, filePath, ctx) => {
1222
+ const typeResolved = ctx.resolve(receiverName, filePath);
1223
+ if (!typeResolved)
1224
+ return undefined;
1225
+ const classDef = typeResolved.candidates.find((d) => d.type === 'Class' ||
1226
+ d.type === 'Struct' ||
1227
+ d.type === 'Interface' ||
1228
+ d.type === 'Enum' ||
1229
+ d.type === 'Record' ||
1230
+ d.type === 'Impl');
1231
+ if (!classDef)
1232
+ return undefined;
1233
+ return ctx.symbols.lookupFieldByOwner(classDef.nodeId, fieldName) ?? undefined;
1234
+ };
1235
+ /**
1236
+ * Create a deduplicated ACCESSES edge emitter for a single source node.
1237
+ * Each (sourceId, fieldNodeId) pair is emitted at most once per source.
1238
+ */
1239
+ const makeAccessEmitter = (graph, sourceId) => {
1240
+ const emitted = new Set();
1241
+ return (fieldNodeId) => {
1242
+ const key = `${sourceId}\0${fieldNodeId}`;
1243
+ if (emitted.has(key))
1244
+ return;
1245
+ emitted.add(key);
1246
+ graph.addRelationship({
1247
+ id: generateId('ACCESSES', `${sourceId}:${fieldNodeId}:read`),
1248
+ sourceId,
1249
+ targetId: fieldNodeId,
1250
+ type: 'ACCESSES',
1251
+ confidence: 1.0,
1252
+ reason: 'read',
1253
+ });
1254
+ };
1255
+ };
1256
+ const walkMixedChain = (chain, startType, filePath, ctx, onFieldResolved) => {
1257
+ let currentType = startType;
1258
+ for (const step of chain) {
1259
+ if (!currentType)
1260
+ break;
1261
+ if (step.kind === 'field') {
1262
+ const resolved = resolveFieldAccessType(currentType, step.name, filePath, ctx);
1263
+ if (!resolved) {
1264
+ currentType = undefined;
1265
+ break;
1266
+ }
1267
+ onFieldResolved?.(resolved.fieldNodeId);
1268
+ currentType = resolved.typeName;
1269
+ }
1270
+ else {
1271
+ // Ruby/Python: property access is syntactically identical to method calls.
1272
+ // Try field resolution first — if the name is a known property with declaredType,
1273
+ // use that type directly. Otherwise fall back to method call resolution.
1274
+ const fieldResolved = resolveFieldAccessType(currentType, step.name, filePath, ctx);
1275
+ if (fieldResolved) {
1276
+ onFieldResolved?.(fieldResolved.fieldNodeId);
1277
+ currentType = fieldResolved.typeName;
1278
+ continue;
1279
+ }
1280
+ const resolved = resolveCallTarget({ calledName: step.name, callForm: 'member', receiverTypeName: currentType }, filePath, ctx);
1281
+ if (!resolved) {
1282
+ // Stdlib passthrough: unwrap(), clone(), etc. preserve the receiver type
1283
+ if (TYPE_PRESERVING_METHODS.has(step.name))
1284
+ continue;
1285
+ currentType = undefined;
1286
+ break;
1287
+ }
1288
+ if (!resolved.returnType) {
1289
+ currentType = undefined;
1290
+ break;
1291
+ }
1292
+ const retType = extractReturnTypeName(resolved.returnType);
1293
+ if (!retType) {
1294
+ currentType = undefined;
1295
+ break;
1296
+ }
1297
+ currentType = retType;
1298
+ }
1299
+ }
1300
+ return currentType;
1301
+ };
1302
+ /**
1303
+ * Fast path: resolve pre-extracted call sites from workers.
1304
+ * No AST parsing — workers already extracted calledName + sourceId.
1305
+ */
1306
+ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onProgress, constructorBindings, implementorMap) => {
1307
+ // Scope-aware receiver types: keyed by filePath → "funcName\0varName" → typeName.
1308
+ // The scope dimension prevents collisions when two functions in the same file
1309
+ // have same-named locals pointing to different constructor types.
1310
+ const fileReceiverTypes = new Map();
1311
+ if (constructorBindings) {
1312
+ for (const { filePath, bindings } of constructorBindings) {
1313
+ const verified = verifyConstructorBindings(bindings, filePath, ctx, graph);
1314
+ if (verified.size > 0) {
1315
+ fileReceiverTypes.set(filePath, buildReceiverTypeIndex(verified));
1316
+ }
1317
+ }
1318
+ }
1319
+ const byFile = new Map();
1320
+ for (const call of extractedCalls) {
1321
+ let list = byFile.get(call.filePath);
1322
+ if (!list) {
1323
+ list = [];
1324
+ byFile.set(call.filePath, list);
1325
+ }
1326
+ list.push(call);
1327
+ }
1328
+ const totalFiles = byFile.size;
1329
+ let filesProcessed = 0;
1330
+ for (const [filePath, calls] of byFile) {
1331
+ filesProcessed++;
1332
+ if (filesProcessed % 100 === 0) {
1333
+ onProgress?.(filesProcessed, totalFiles);
1334
+ await yieldToEventLoop();
1335
+ }
1336
+ ctx.enableCache(filePath);
1337
+ const widenCache = new Map();
1338
+ const receiverMap = fileReceiverTypes.get(filePath);
1339
+ for (const call of calls) {
1340
+ let effectiveCall = call;
1341
+ // Step 1: resolve receiver type from constructor bindings
1342
+ if (!call.receiverTypeName && call.receiverName && receiverMap) {
1343
+ const callFuncName = extractFuncNameFromSourceId(call.sourceId);
1344
+ const resolvedType = lookupReceiverType(receiverMap, callFuncName, call.receiverName);
1345
+ if (resolvedType) {
1346
+ effectiveCall = { ...call, receiverTypeName: resolvedType };
1347
+ }
1348
+ }
1349
+ // Step 1b: class-as-receiver for static method calls (e.g. UserService.find_user())
1350
+ if (!effectiveCall.receiverTypeName &&
1351
+ effectiveCall.receiverName &&
1352
+ effectiveCall.callForm === 'member') {
1353
+ const typeResolved = ctx.resolve(effectiveCall.receiverName, effectiveCall.filePath);
1354
+ if (typeResolved &&
1355
+ typeResolved.candidates.some((d) => d.type === 'Class' ||
1356
+ d.type === 'Interface' ||
1357
+ d.type === 'Struct' ||
1358
+ d.type === 'Enum')) {
1359
+ effectiveCall = { ...effectiveCall, receiverTypeName: effectiveCall.receiverName };
1360
+ }
1361
+ }
1362
+ // Step 1c: mixed chain resolution (field, call, or interleaved — e.g. svc.getUser().address.save()).
1363
+ // Runs whenever receiverMixedChain is present. Steps 1/1b may have resolved the base receiver
1364
+ // type already; that type is used as the chain's starting point.
1365
+ if (effectiveCall.receiverMixedChain?.length) {
1366
+ // Use the already-resolved base type (from Steps 1/1b) or look it up now.
1367
+ let currentType = effectiveCall.receiverTypeName;
1368
+ if (!currentType && effectiveCall.receiverName && receiverMap) {
1369
+ const callFuncName = extractFuncNameFromSourceId(effectiveCall.sourceId);
1370
+ currentType = lookupReceiverType(receiverMap, callFuncName, effectiveCall.receiverName);
1371
+ }
1372
+ if (!currentType && effectiveCall.receiverName) {
1373
+ const typeResolved = ctx.resolve(effectiveCall.receiverName, effectiveCall.filePath);
1374
+ if (typeResolved?.candidates.some((d) => d.type === 'Class' ||
1375
+ d.type === 'Interface' ||
1376
+ d.type === 'Struct' ||
1377
+ d.type === 'Enum')) {
1378
+ currentType = effectiveCall.receiverName;
1379
+ }
1380
+ }
1381
+ if (currentType) {
1382
+ const walkedType = walkMixedChain(effectiveCall.receiverMixedChain, currentType, effectiveCall.filePath, ctx, makeAccessEmitter(graph, effectiveCall.sourceId));
1383
+ if (walkedType) {
1384
+ effectiveCall = { ...effectiveCall, receiverTypeName: walkedType };
1385
+ }
1386
+ }
1387
+ }
1388
+ const resolved = resolveCallTarget(effectiveCall, effectiveCall.filePath, ctx, undefined, widenCache, effectiveCall.argTypes);
1389
+ if (!resolved) {
1390
+ // Vue template component fallback: match calledName against imported .vue basenames
1391
+ if (effectiveCall.filePath.endsWith('.vue') && effectiveCall.sourceId.startsWith('File:')) {
1392
+ const importedFiles = ctx.importMap.get(effectiveCall.filePath);
1393
+ if (importedFiles) {
1394
+ for (const importedPath of importedFiles) {
1395
+ if (!importedPath.endsWith('.vue'))
1396
+ continue;
1397
+ const basename = importedPath.slice(importedPath.lastIndexOf('/') + 1, importedPath.lastIndexOf('.'));
1398
+ if (basename !== effectiveCall.calledName)
1399
+ continue;
1400
+ const targetFileId = generateId('File', importedPath);
1401
+ if (graph.getNode(targetFileId)) {
1402
+ graph.addRelationship({
1403
+ id: generateId('CALLS', `${effectiveCall.sourceId}:${effectiveCall.calledName}->${targetFileId}`),
1404
+ sourceId: effectiveCall.sourceId,
1405
+ targetId: targetFileId,
1406
+ type: 'CALLS',
1407
+ confidence: 0.9,
1408
+ reason: 'vue-template-component',
1409
+ });
1410
+ }
1411
+ break;
1412
+ }
1413
+ }
1414
+ }
1415
+ continue;
1416
+ }
1417
+ const relId = generateId('CALLS', `${effectiveCall.sourceId}:${effectiveCall.calledName}->${resolved.nodeId}`);
1418
+ graph.addRelationship({
1419
+ id: relId,
1420
+ sourceId: effectiveCall.sourceId,
1421
+ targetId: resolved.nodeId,
1422
+ type: 'CALLS',
1423
+ confidence: resolved.confidence,
1424
+ reason: resolved.reason,
1425
+ });
1426
+ if (implementorMap && effectiveCall.callForm === 'member' && effectiveCall.receiverTypeName) {
1427
+ const implTargets = findInterfaceDispatchTargets(effectiveCall.calledName, effectiveCall.receiverTypeName, effectiveCall.filePath, ctx, implementorMap, resolved.nodeId);
1428
+ for (const impl of implTargets) {
1429
+ graph.addRelationship({
1430
+ id: generateId('CALLS', `${effectiveCall.sourceId}:${effectiveCall.calledName}->${impl.nodeId}`),
1431
+ sourceId: effectiveCall.sourceId,
1432
+ targetId: impl.nodeId,
1433
+ type: 'CALLS',
1434
+ confidence: impl.confidence,
1435
+ reason: impl.reason,
1436
+ });
1437
+ }
1438
+ }
1439
+ }
1440
+ ctx.clearCache();
1441
+ }
1442
+ onProgress?.(totalFiles, totalFiles);
1443
+ };
1444
+ /**
1445
+ * Resolve pre-extracted field write assignments to ACCESSES {reason: 'write'} edges.
1446
+ * Accepts optional constructorBindings for return-type-aware receiver inference,
1447
+ * mirroring processCallsFromExtracted's verified binding lookup.
1448
+ */
1449
+ export const processAssignmentsFromExtracted = (graph, assignments, ctx, constructorBindings) => {
1450
+ // Build per-file receiver type indexes from verified constructor bindings
1451
+ const fileReceiverTypes = new Map();
1452
+ if (constructorBindings) {
1453
+ for (const { filePath, bindings } of constructorBindings) {
1454
+ const verified = verifyConstructorBindings(bindings, filePath, ctx, graph);
1455
+ if (verified.size > 0) {
1456
+ fileReceiverTypes.set(filePath, buildReceiverTypeIndex(verified));
1457
+ }
1458
+ }
1459
+ }
1460
+ for (const asn of assignments) {
1461
+ // Resolve the receiver type
1462
+ let receiverTypeName = asn.receiverTypeName;
1463
+ // Tier 2: verified constructor bindings (return-type inference)
1464
+ if (!receiverTypeName && fileReceiverTypes.size > 0) {
1465
+ const receiverMap = fileReceiverTypes.get(asn.filePath);
1466
+ if (receiverMap) {
1467
+ const funcName = extractFuncNameFromSourceId(asn.sourceId);
1468
+ receiverTypeName = lookupReceiverType(receiverMap, funcName, asn.receiverText);
1469
+ }
1470
+ }
1471
+ // Tier 3: static class-as-receiver fallback
1472
+ if (!receiverTypeName) {
1473
+ const resolved = ctx.resolve(asn.receiverText, asn.filePath);
1474
+ if (resolved?.candidates.some((d) => d.type === 'Class' ||
1475
+ d.type === 'Struct' ||
1476
+ d.type === 'Interface' ||
1477
+ d.type === 'Enum' ||
1478
+ d.type === 'Record' ||
1479
+ d.type === 'Impl')) {
1480
+ receiverTypeName = asn.receiverText;
1481
+ }
1482
+ }
1483
+ if (!receiverTypeName)
1484
+ continue;
1485
+ const fieldOwner = resolveFieldOwnership(receiverTypeName, asn.propertyName, asn.filePath, ctx);
1486
+ if (!fieldOwner)
1487
+ continue;
1488
+ graph.addRelationship({
1489
+ id: generateId('ACCESSES', `${asn.sourceId}:${fieldOwner.nodeId}:write`),
1490
+ sourceId: asn.sourceId,
1491
+ targetId: fieldOwner.nodeId,
1492
+ type: 'ACCESSES',
1493
+ confidence: 1.0,
1494
+ reason: 'write',
1495
+ });
1496
+ }
1497
+ };
1498
+ /**
1499
+ * Resolve pre-extracted Laravel routes to CALLS edges from route files to controller methods.
1500
+ */
1501
+ export const processRoutesFromExtracted = async (graph, extractedRoutes, ctx, onProgress) => {
1502
+ for (let i = 0; i < extractedRoutes.length; i++) {
1503
+ const route = extractedRoutes[i];
1504
+ if (i % 50 === 0) {
1505
+ onProgress?.(i, extractedRoutes.length);
1506
+ await yieldToEventLoop();
1507
+ }
1508
+ if (!route.controllerName || !route.methodName)
1509
+ continue;
1510
+ const controllerResolved = ctx.resolve(route.controllerName, route.filePath);
1511
+ if (!controllerResolved || controllerResolved.candidates.length === 0)
1512
+ continue;
1513
+ if (controllerResolved.tier === 'global' && controllerResolved.candidates.length > 1)
1514
+ continue;
1515
+ const controllerDef = controllerResolved.candidates[0];
1516
+ const confidence = TIER_CONFIDENCE[controllerResolved.tier];
1517
+ const methodResolved = ctx.resolve(route.methodName, controllerDef.filePath);
1518
+ const methodId = methodResolved?.tier === 'same-file' ? methodResolved.candidates[0]?.nodeId : undefined;
1519
+ const sourceId = generateId('File', route.filePath);
1520
+ if (!methodId) {
1521
+ const guessedId = generateId('Method', `${controllerDef.filePath}:${route.methodName}`);
1522
+ const relId = generateId('CALLS', `${sourceId}:route->${guessedId}`);
1523
+ graph.addRelationship({
1524
+ id: relId,
1525
+ sourceId,
1526
+ targetId: guessedId,
1527
+ type: 'CALLS',
1528
+ confidence: confidence * 0.8,
1529
+ reason: 'laravel-route',
1530
+ });
1531
+ continue;
1532
+ }
1533
+ const relId = generateId('CALLS', `${sourceId}:route->${methodId}`);
1534
+ graph.addRelationship({
1535
+ id: relId,
1536
+ sourceId,
1537
+ targetId: methodId,
1538
+ type: 'CALLS',
1539
+ confidence,
1540
+ reason: 'laravel-route',
1541
+ });
1542
+ }
1543
+ onProgress?.(extractedRoutes.length, extractedRoutes.length);
1544
+ };
1545
+ /**
1546
+ * Extract property access keys from a consumer file's source code near fetch calls.
1547
+ *
1548
+ * Looks for three patterns after a fetch/response variable assignment:
1549
+ * 1. Destructuring: `const { data, pagination } = await res.json()`
1550
+ * 2. Property access: `response.data`, `result.items`
1551
+ * 3. Optional chaining: `data?.key1?.key2`
1552
+ *
1553
+ * Returns deduplicated top-level property names accessed on the response.
1554
+ *
1555
+ * NOTE: This scans the entire file content, not just code near a specific fetch call.
1556
+ * If a file has multiple fetch calls to different routes, all accessed keys are
1557
+ * attributed to each fetch. This is an acceptable tradeoff for regex-based extraction.
1558
+ */
1559
+ /** Common method names on response/data objects that are NOT property accesses */
1560
+ // Properties/methods to ignore when extracting consumer accessed keys from `data.X` patterns.
1561
+ // Avoids false positives from Fetch API, Array, Object, Promise, and DOM access on variables
1562
+ // that happen to share names with response variables (data, result, response, etc.).
1563
+ const RESPONSE_ACCESS_BLOCKLIST = new Set([
1564
+ // Fetch/Response API
1565
+ 'json',
1566
+ 'text',
1567
+ 'blob',
1568
+ 'arrayBuffer',
1569
+ 'formData',
1570
+ 'ok',
1571
+ 'status',
1572
+ 'headers',
1573
+ 'clone',
1574
+ // Promise
1575
+ 'then',
1576
+ 'catch',
1577
+ 'finally',
1578
+ // Array
1579
+ 'map',
1580
+ 'filter',
1581
+ 'forEach',
1582
+ 'reduce',
1583
+ 'find',
1584
+ 'some',
1585
+ 'every',
1586
+ 'push',
1587
+ 'pop',
1588
+ 'shift',
1589
+ 'unshift',
1590
+ 'splice',
1591
+ 'slice',
1592
+ 'concat',
1593
+ 'join',
1594
+ 'sort',
1595
+ 'reverse',
1596
+ 'includes',
1597
+ 'indexOf',
1598
+ // Object
1599
+ 'length',
1600
+ 'toString',
1601
+ 'valueOf',
1602
+ 'keys',
1603
+ 'values',
1604
+ 'entries',
1605
+ // DOM methods — file-download patterns often reuse `data`/`response` variable names
1606
+ 'appendChild',
1607
+ 'removeChild',
1608
+ 'insertBefore',
1609
+ 'replaceChild',
1610
+ 'replaceChildren',
1611
+ 'createElement',
1612
+ 'getElementById',
1613
+ 'querySelector',
1614
+ 'querySelectorAll',
1615
+ 'setAttribute',
1616
+ 'getAttribute',
1617
+ 'removeAttribute',
1618
+ 'hasAttribute',
1619
+ 'addEventListener',
1620
+ 'removeEventListener',
1621
+ 'dispatchEvent',
1622
+ 'classList',
1623
+ 'className',
1624
+ 'parentNode',
1625
+ 'parentElement',
1626
+ 'childNodes',
1627
+ 'children',
1628
+ 'nextSibling',
1629
+ 'previousSibling',
1630
+ 'firstChild',
1631
+ 'lastChild',
1632
+ 'click',
1633
+ 'focus',
1634
+ 'blur',
1635
+ 'submit',
1636
+ 'reset',
1637
+ 'innerHTML',
1638
+ 'outerHTML',
1639
+ 'textContent',
1640
+ 'innerText',
1641
+ ]);
1642
+ export const extractConsumerAccessedKeys = (content) => {
1643
+ const keys = new Set();
1644
+ // Pattern 1: Destructuring from .json() — const { key1, key2 } = await res.json()
1645
+ // Also matches: const { key1, key2 } = await (await fetch(...)).json()
1646
+ const destructurePattern = /(?:const|let|var)\s+\{([^}]+)\}\s*=\s*(?:await\s+)?(?:\w+\.json\s*\(\)|(?:await\s+)?(?:fetch|axios|got)\s*\([^)]*\)(?:\.then\s*\([^)]*\))?(?:\.json\s*\(\))?)/g;
1647
+ let match;
1648
+ while ((match = destructurePattern.exec(content)) !== null) {
1649
+ const destructuredBody = match[1];
1650
+ // Extract identifiers from destructuring, handling renamed bindings (key: alias)
1651
+ const keyPattern = /(\w+)\s*(?::\s*\w+)?/g;
1652
+ let keyMatch;
1653
+ while ((keyMatch = keyPattern.exec(destructuredBody)) !== null) {
1654
+ keys.add(keyMatch[1]);
1655
+ }
1656
+ }
1657
+ // Pattern 2: Destructuring from a data/result/response/json variable
1658
+ // e.g., const { items, total } = data; or const { error } = result;
1659
+ const dataVarDestructure = /(?:const|let|var)\s+\{([^}]+)\}\s*=\s*(?:data|result|response|json|body|res)\b/g;
1660
+ while ((match = dataVarDestructure.exec(content)) !== null) {
1661
+ const destructuredBody = match[1];
1662
+ const keyPattern = /(\w+)\s*(?::\s*\w+)?/g;
1663
+ let keyMatch;
1664
+ while ((keyMatch = keyPattern.exec(destructuredBody)) !== null) {
1665
+ keys.add(keyMatch[1]);
1666
+ }
1667
+ }
1668
+ // Pattern 3: Property access on common response variable names
1669
+ // Matches: data.key, response.key, result.key, json.key, body.key
1670
+ // Also matches optional chaining: data?.key
1671
+ const propAccessPattern = /\b(?:data|response|result|json|body|res)\s*(?:\?\.|\.)(\w+)/g;
1672
+ while ((match = propAccessPattern.exec(content)) !== null) {
1673
+ const key = match[1];
1674
+ // Skip common method calls that aren't property accesses
1675
+ if (!RESPONSE_ACCESS_BLOCKLIST.has(key)) {
1676
+ keys.add(key);
1677
+ }
1678
+ }
1679
+ return [...keys];
1680
+ };
1681
+ /**
1682
+ * Create FETCHES edges from extracted fetch() calls to matching Route nodes.
1683
+ * When consumerContents is provided, extracts property access patterns from
1684
+ * consumer files and encodes them in the edge reason field.
1685
+ */
1686
+ export const processNextjsFetchRoutes = (graph, fetchCalls, routeRegistry, // routeURL → handlerFilePath
1687
+ consumerContents) => {
1688
+ // Pre-count how many routes each consumer file matches (for confidence attribution)
1689
+ const routeCountByFile = new Map();
1690
+ for (const call of fetchCalls) {
1691
+ const normalized = normalizeFetchURL(call.fetchURL);
1692
+ if (!normalized)
1693
+ continue;
1694
+ for (const [routeURL] of routeRegistry) {
1695
+ if (routeMatches(normalized, routeURL)) {
1696
+ routeCountByFile.set(call.filePath, (routeCountByFile.get(call.filePath) ?? 0) + 1);
1697
+ break;
1698
+ }
1699
+ }
1700
+ }
1701
+ for (const call of fetchCalls) {
1702
+ const normalized = normalizeFetchURL(call.fetchURL);
1703
+ if (!normalized)
1704
+ continue;
1705
+ for (const [routeURL] of routeRegistry) {
1706
+ if (routeMatches(normalized, routeURL)) {
1707
+ const sourceId = generateId('File', call.filePath);
1708
+ const routeNodeId = generateId('Route', routeURL);
1709
+ // Extract consumer accessed keys if file content is available
1710
+ let reason = 'fetch-url-match';
1711
+ if (consumerContents) {
1712
+ const content = consumerContents.get(call.filePath);
1713
+ if (content) {
1714
+ const accessedKeys = extractConsumerAccessedKeys(content);
1715
+ if (accessedKeys.length > 0) {
1716
+ reason = `fetch-url-match|keys:${accessedKeys.join(',')}`;
1717
+ }
1718
+ }
1719
+ }
1720
+ // Encode multi-fetch count so downstream can set confidence
1721
+ const fetchCount = routeCountByFile.get(call.filePath) ?? 1;
1722
+ if (fetchCount > 1) {
1723
+ reason = `${reason}|fetches:${fetchCount}`;
1724
+ }
1725
+ graph.addRelationship({
1726
+ id: generateId('FETCHES', `${sourceId}->${routeNodeId}`),
1727
+ sourceId,
1728
+ targetId: routeNodeId,
1729
+ type: 'FETCHES',
1730
+ confidence: 0.9,
1731
+ reason,
1732
+ });
1733
+ break;
1734
+ }
1735
+ }
1736
+ }
1737
+ };
1738
+ /**
1739
+ * Extract fetch() calls from source files (sequential path).
1740
+ * Workers handle this via tree-sitter captures in parse-worker; this function
1741
+ * provides the same extraction for the sequential fallback path.
1742
+ */
1743
+ export const extractFetchCallsFromFiles = async (files, astCache) => {
1744
+ const parser = await loadParser();
1745
+ const result = [];
1746
+ for (const file of files) {
1747
+ const language = getLanguageFromFilename(file.path);
1748
+ if (!language)
1749
+ continue;
1750
+ if (!isLanguageAvailable(language))
1751
+ continue;
1752
+ const provider = getProvider(language);
1753
+ const queryStr = provider.treeSitterQueries;
1754
+ if (!queryStr)
1755
+ continue;
1756
+ await loadLanguage(language, file.path);
1757
+ let tree = astCache.get(file.path);
1758
+ if (!tree) {
1759
+ try {
1760
+ tree = parser.parse(file.content, undefined, {
1761
+ bufferSize: getTreeSitterBufferSize(file.content.length),
1762
+ });
1763
+ }
1764
+ catch {
1765
+ continue;
1766
+ }
1767
+ astCache.set(file.path, tree);
1768
+ }
1769
+ let matches;
1770
+ try {
1771
+ const lang = parser.getLanguage();
1772
+ const query = new Parser.Query(lang, queryStr);
1773
+ matches = query.matches(tree.rootNode);
1774
+ }
1775
+ catch {
1776
+ continue;
1777
+ }
1778
+ for (const match of matches) {
1779
+ const captureMap = {};
1780
+ match.captures.forEach((c) => (captureMap[c.name] = c.node));
1781
+ if (captureMap['route.fetch']) {
1782
+ const urlNode = captureMap['route.url'] ?? captureMap['route.template_url'];
1783
+ if (urlNode) {
1784
+ result.push({
1785
+ filePath: file.path,
1786
+ fetchURL: urlNode.text,
1787
+ lineNumber: captureMap['route.fetch'].startPosition.row,
1788
+ });
1789
+ }
1790
+ }
1791
+ else if (captureMap['http_client'] && captureMap['http_client.url']) {
1792
+ const method = captureMap['http_client.method']?.text;
1793
+ const url = captureMap['http_client.url'].text;
1794
+ const HTTP_CLIENT_ONLY = new Set(['head', 'options', 'request', 'ajax']);
1795
+ if (method && HTTP_CLIENT_ONLY.has(method) && url.startsWith('/')) {
1796
+ result.push({
1797
+ filePath: file.path,
1798
+ fetchURL: url,
1799
+ lineNumber: captureMap['http_client'].startPosition.row,
1800
+ });
1801
+ }
1802
+ }
1803
+ }
1804
+ }
1805
+ return result;
1806
+ };
1807
+ //# sourceMappingURL=call-processor.js.map