gitnexus 1.6.3-rc.8 → 1.6.3

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 (285) hide show
  1. package/README.md +21 -5
  2. package/dist/_shared/graph/types.d.ts +16 -0
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +20 -2
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/index.js +11 -0
  7. package/dist/_shared/index.js.map +1 -1
  8. package/dist/_shared/scope-resolution/def-index.js +2 -2
  9. package/dist/_shared/scope-resolution/def-index.js.map +1 -1
  10. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +8 -0
  11. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
  12. package/dist/_shared/scope-resolution/method-dispatch-index.js +2 -2
  13. package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
  14. package/dist/_shared/scope-resolution/module-scope-index.d.ts +8 -0
  15. package/dist/_shared/scope-resolution/module-scope-index.d.ts.map +1 -1
  16. package/dist/_shared/scope-resolution/module-scope-index.js +10 -2
  17. package/dist/_shared/scope-resolution/module-scope-index.js.map +1 -1
  18. package/dist/_shared/scope-resolution/parsed-file.d.ts +76 -0
  19. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -0
  20. package/dist/_shared/scope-resolution/parsed-file.js +54 -0
  21. package/dist/_shared/scope-resolution/parsed-file.js.map +1 -0
  22. package/dist/_shared/scope-resolution/position-index.d.ts +12 -0
  23. package/dist/_shared/scope-resolution/position-index.d.ts.map +1 -1
  24. package/dist/_shared/scope-resolution/position-index.js +2 -2
  25. package/dist/_shared/scope-resolution/position-index.js.map +1 -1
  26. package/dist/_shared/scope-resolution/qualified-name-index.js +2 -2
  27. package/dist/_shared/scope-resolution/qualified-name-index.js.map +1 -1
  28. package/dist/_shared/scope-resolution/reference-site.d.ts +75 -0
  29. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -0
  30. package/dist/_shared/scope-resolution/reference-site.js +24 -0
  31. package/dist/_shared/scope-resolution/reference-site.js.map +1 -0
  32. package/dist/_shared/scope-resolution/registries/class-registry.d.ts +27 -0
  33. package/dist/_shared/scope-resolution/registries/class-registry.d.ts.map +1 -0
  34. package/dist/_shared/scope-resolution/registries/class-registry.js +30 -0
  35. package/dist/_shared/scope-resolution/registries/class-registry.js.map +1 -0
  36. package/dist/_shared/scope-resolution/registries/context.d.ts +69 -0
  37. package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -0
  38. package/dist/_shared/scope-resolution/registries/context.js +44 -0
  39. package/dist/_shared/scope-resolution/registries/context.js.map +1 -0
  40. package/dist/_shared/scope-resolution/registries/evidence.d.ts +56 -0
  41. package/dist/_shared/scope-resolution/registries/evidence.d.ts.map +1 -0
  42. package/dist/_shared/scope-resolution/registries/evidence.js +150 -0
  43. package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -0
  44. package/dist/_shared/scope-resolution/registries/field-registry.d.ts +26 -0
  45. package/dist/_shared/scope-resolution/registries/field-registry.d.ts.map +1 -0
  46. package/dist/_shared/scope-resolution/registries/field-registry.js +31 -0
  47. package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -0
  48. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +81 -0
  49. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -0
  50. package/dist/_shared/scope-resolution/registries/lookup-core.js +332 -0
  51. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -0
  52. package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts +33 -0
  53. package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts.map +1 -0
  54. package/dist/_shared/scope-resolution/registries/lookup-qualified.js +56 -0
  55. package/dist/_shared/scope-resolution/registries/lookup-qualified.js.map +1 -0
  56. package/dist/_shared/scope-resolution/registries/method-registry.d.ts +36 -0
  57. package/dist/_shared/scope-resolution/registries/method-registry.d.ts.map +1 -0
  58. package/dist/_shared/scope-resolution/registries/method-registry.js +32 -0
  59. package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -0
  60. package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts +43 -0
  61. package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts.map +1 -0
  62. package/dist/_shared/scope-resolution/registries/tie-breaks.js +60 -0
  63. package/dist/_shared/scope-resolution/registries/tie-breaks.js.map +1 -0
  64. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
  65. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
  66. package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
  67. package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
  68. package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
  69. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  70. package/dist/_shared/scope-resolution/scope-tree.js +3 -2
  71. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  72. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
  73. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
  74. package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
  75. package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
  76. package/dist/_shared/scope-resolution/types.d.ts +11 -0
  77. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  78. package/dist/cli/ai-context.js +35 -4
  79. package/dist/cli/analyze.d.ts +27 -0
  80. package/dist/cli/analyze.js +31 -1
  81. package/dist/cli/clean.js +19 -1
  82. package/dist/cli/group.js +73 -0
  83. package/dist/cli/index-repo.js +8 -1
  84. package/dist/cli/index.js +26 -1
  85. package/dist/cli/list.js +11 -1
  86. package/dist/cli/remove.d.ts +30 -0
  87. package/dist/cli/remove.js +99 -0
  88. package/dist/cli/setup.js +185 -57
  89. package/dist/cli/tool.d.ts +5 -0
  90. package/dist/cli/tool.js +42 -0
  91. package/dist/config/ignore-service.d.ts +9 -0
  92. package/dist/config/ignore-service.js +80 -13
  93. package/dist/core/embedding-mode.d.ts +30 -0
  94. package/dist/core/embedding-mode.js +30 -0
  95. package/dist/core/embeddings/ast-utils.js +22 -22
  96. package/dist/core/embeddings/chunker.js +30 -25
  97. package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
  98. package/dist/core/embeddings/embedding-pipeline.js +15 -6
  99. package/dist/core/embeddings/text-generator.d.ts +1 -1
  100. package/dist/core/embeddings/text-generator.js +33 -24
  101. package/dist/core/embeddings/types.d.ts +43 -1
  102. package/dist/core/embeddings/types.js +101 -29
  103. package/dist/core/git-staleness.d.ts +18 -0
  104. package/dist/core/git-staleness.js +108 -0
  105. package/dist/core/graph/graph.js +115 -20
  106. package/dist/core/graph/types.d.ts +12 -1
  107. package/dist/core/group/config-parser.d.ts +4 -0
  108. package/dist/core/group/config-parser.js +18 -1
  109. package/dist/core/group/cross-impact.d.ts +41 -0
  110. package/dist/core/group/cross-impact.js +441 -0
  111. package/dist/core/group/extractors/http-patterns/php.js +126 -18
  112. package/dist/core/group/group-path-utils.d.ts +17 -0
  113. package/dist/core/group/group-path-utils.js +40 -0
  114. package/dist/core/group/resolve-at-member.d.ts +10 -0
  115. package/dist/core/group/resolve-at-member.js +31 -0
  116. package/dist/core/group/service.d.ts +9 -0
  117. package/dist/core/group/service.js +259 -25
  118. package/dist/core/group/types.d.ts +30 -0
  119. package/dist/core/ingestion/ast-cache.d.ts +16 -1
  120. package/dist/core/ingestion/ast-cache.js +14 -2
  121. package/dist/core/ingestion/call-processor.js +9 -0
  122. package/dist/core/ingestion/emit-references.d.ts +88 -0
  123. package/dist/core/ingestion/emit-references.js +229 -0
  124. package/dist/core/ingestion/filesystem-walker.js +6 -4
  125. package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
  126. package/dist/core/ingestion/finalize-orchestrator.js +139 -0
  127. package/dist/core/ingestion/framework-detection.js +6 -2
  128. package/dist/core/ingestion/import-processor.js +4 -0
  129. package/dist/core/ingestion/import-resolvers/python.js +9 -6
  130. package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
  131. package/dist/core/ingestion/import-target-adapter.js +95 -0
  132. package/dist/core/ingestion/language-provider.d.ts +36 -33
  133. package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
  134. package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
  135. package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
  136. package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
  137. package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
  138. package/dist/core/ingestion/languages/csharp/arity.js +37 -0
  139. package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
  140. package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
  141. package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
  142. package/dist/core/ingestion/languages/csharp/captures.js +249 -0
  143. package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
  144. package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
  145. package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
  146. package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
  147. package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
  148. package/dist/core/ingestion/languages/csharp/index.js +82 -0
  149. package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
  150. package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
  151. package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
  152. package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
  153. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
  154. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
  155. package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
  156. package/dist/core/ingestion/languages/csharp/query.js +515 -0
  157. package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
  158. package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
  159. package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
  160. package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
  161. package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
  162. package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
  163. package/dist/core/ingestion/languages/csharp.js +14 -0
  164. package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
  165. package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
  166. package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
  167. package/dist/core/ingestion/languages/python/arity.js +38 -0
  168. package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
  169. package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
  170. package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
  171. package/dist/core/ingestion/languages/python/captures.js +106 -0
  172. package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
  173. package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
  174. package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
  175. package/dist/core/ingestion/languages/python/import-target.js +99 -0
  176. package/dist/core/ingestion/languages/python/index.d.ts +80 -0
  177. package/dist/core/ingestion/languages/python/index.js +80 -0
  178. package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
  179. package/dist/core/ingestion/languages/python/interpret.js +191 -0
  180. package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
  181. package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
  182. package/dist/core/ingestion/languages/python/query.d.ts +9 -0
  183. package/dist/core/ingestion/languages/python/query.js +267 -0
  184. package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
  185. package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
  186. package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
  187. package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
  188. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
  189. package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
  190. package/dist/core/ingestion/languages/python.js +14 -0
  191. package/dist/core/ingestion/model/method-registry.d.ts +9 -0
  192. package/dist/core/ingestion/model/method-registry.js +4 -0
  193. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
  194. package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
  195. package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
  196. package/dist/core/ingestion/model/semantic-model.js +55 -0
  197. package/dist/core/ingestion/mro-processor.js +38 -22
  198. package/dist/core/ingestion/parsing-processor.d.ts +18 -1
  199. package/dist/core/ingestion/parsing-processor.js +45 -11
  200. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  201. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  202. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +10 -0
  203. package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
  204. package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
  205. package/dist/core/ingestion/pipeline.js +2 -1
  206. package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
  207. package/dist/core/ingestion/registry-primary-flag.js +111 -0
  208. package/dist/core/ingestion/resolve-references.d.ts +63 -0
  209. package/dist/core/ingestion/resolve-references.js +175 -0
  210. package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
  211. package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
  212. package/dist/core/ingestion/scope-extractor.d.ts +86 -0
  213. package/dist/core/ingestion/scope-extractor.js +758 -0
  214. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
  215. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
  216. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
  217. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
  218. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
  219. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
  220. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
  221. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
  222. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
  223. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
  224. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
  225. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
  226. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
  227. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
  228. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
  229. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
  230. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
  231. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
  232. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
  233. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
  234. package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
  235. package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
  236. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
  237. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
  238. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
  239. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
  240. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
  241. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
  242. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
  243. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
  244. package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
  245. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
  246. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
  247. package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
  248. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
  249. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
  250. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
  251. package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
  252. package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
  253. package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
  254. package/dist/core/ingestion/shadow-harness.d.ts +113 -0
  255. package/dist/core/ingestion/shadow-harness.js +148 -0
  256. package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
  257. package/dist/core/ingestion/utils/ast-helpers.js +70 -0
  258. package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
  259. package/dist/core/ingestion/utils/max-file-size.js +52 -0
  260. package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
  261. package/dist/core/ingestion/workers/parse-worker.js +57 -21
  262. package/dist/core/lbug/lbug-adapter.d.ts +22 -2
  263. package/dist/core/lbug/lbug-adapter.js +58 -14
  264. package/dist/core/lbug/pool-adapter.d.ts +17 -0
  265. package/dist/core/lbug/pool-adapter.js +24 -14
  266. package/dist/core/run-analyze.d.ts +32 -0
  267. package/dist/core/run-analyze.js +74 -19
  268. package/dist/core/search/bm25-index.d.ts +18 -0
  269. package/dist/core/search/bm25-index.js +125 -12
  270. package/dist/core/tree-sitter/parser-loader.js +6 -1
  271. package/dist/mcp/local/local-backend.d.ts +67 -3
  272. package/dist/mcp/local/local-backend.js +296 -34
  273. package/dist/mcp/resources.d.ts +31 -0
  274. package/dist/mcp/resources.js +100 -17
  275. package/dist/mcp/tools.d.ts +4 -1
  276. package/dist/mcp/tools.js +75 -54
  277. package/dist/server/api.js +6 -2
  278. package/dist/storage/git.d.ts +49 -0
  279. package/dist/storage/git.js +111 -0
  280. package/dist/storage/repo-manager.d.ts +246 -1
  281. package/dist/storage/repo-manager.js +391 -9
  282. package/package.json +7 -6
  283. package/scripts/bench-scope-resolution.ts +134 -0
  284. package/scripts/ci-list-migrated-languages.ts +24 -0
  285. package/skills/gitnexus-cli.md +1 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Resolve a compound-receiver expression's TYPE — `user.address.save()`,
3
+ * `svc.get_user().save()`, `c.greet().save()` — to the class def of
4
+ * the value the receiver expression produces.
5
+ *
6
+ * Three shapes (parsed C-family-style):
7
+ * - bare identifier `name` — look up via typeBinding chain
8
+ * - dotted `obj.field[.field]…` — walk fields via class-scope typeBindings
9
+ * - call `expr.method()` — recurse into expr, find method's return-type
10
+ * typeBinding on its class, resolve to a class
11
+ *
12
+ * **Field-fallback heuristic** (Phase-9C "unified fixpoint"): when the
13
+ * receiver class has no `methodName`, walk its fields and try the
14
+ * lookup on each field's type. Useful for dynamically-typed languages
15
+ * (Python). Strictly-typed languages should pass
16
+ * `fieldFallbackOnMethodLookup: false` via `ScopeResolver`.
17
+ *
18
+ * Generic for any C-family language (`.` member access, `()` call
19
+ * syntax). Languages with non-C-family syntax (Ruby blocks, COBOL)
20
+ * either don't trigger the call branch or skip this pass entirely.
21
+ */
22
+ import type { ScopeId, SymbolDefinition } from '../../../../_shared/index.js';
23
+ import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
24
+ import type { WorkspaceResolutionIndex } from '../workspace-index.js';
25
+ interface ResolveCompoundReceiverOptions {
26
+ /** When true (default), if method lookup fails on the receiver's
27
+ * class, walk its fields and try the lookup on each field's class.
28
+ * Phase-9C "unified fixpoint" — Python-shaped heuristic. */
29
+ readonly fieldFallback?: boolean;
30
+ /** Language-specific accessor unwrap — `data.Values` on a
31
+ * Dictionary<K,V>-typed receiver yields V (C#), etc. Returns the
32
+ * element type's simple name, or `undefined` to let the regular
33
+ * field-walk handle the access. */
34
+ readonly unwrapCollectionAccessor?: (receiverType: string, accessor: string) => string | undefined;
35
+ /** Walk up from the class scope to ancestor (Module) scopes when
36
+ * looking up a method's return-type typeBinding. Only enable for
37
+ * languages that hoist return-type bindings to Module scope (C#);
38
+ * otherwise we risk picking up unrelated module-level bindings. */
39
+ readonly hoistTypeBindingsToModule?: boolean;
40
+ }
41
+ export declare function resolveCompoundReceiverClass(receiverText: string, inScope: ScopeId, scopes: ScopeResolutionIndexes, index: WorkspaceResolutionIndex, options?: ResolveCompoundReceiverOptions, depth?: number): SymbolDefinition | undefined;
42
+ export {};
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Resolve a compound-receiver expression's TYPE — `user.address.save()`,
3
+ * `svc.get_user().save()`, `c.greet().save()` — to the class def of
4
+ * the value the receiver expression produces.
5
+ *
6
+ * Three shapes (parsed C-family-style):
7
+ * - bare identifier `name` — look up via typeBinding chain
8
+ * - dotted `obj.field[.field]…` — walk fields via class-scope typeBindings
9
+ * - call `expr.method()` — recurse into expr, find method's return-type
10
+ * typeBinding on its class, resolve to a class
11
+ *
12
+ * **Field-fallback heuristic** (Phase-9C "unified fixpoint"): when the
13
+ * receiver class has no `methodName`, walk its fields and try the
14
+ * lookup on each field's type. Useful for dynamically-typed languages
15
+ * (Python). Strictly-typed languages should pass
16
+ * `fieldFallbackOnMethodLookup: false` via `ScopeResolver`.
17
+ *
18
+ * Generic for any C-family language (`.` member access, `()` call
19
+ * syntax). Languages with non-C-family syntax (Ruby blocks, COBOL)
20
+ * either don't trigger the call branch or skip this pass entirely.
21
+ */
22
+ import { findClassBindingInScope, findExportedDefByName, findReceiverTypeBinding, } from '../scope/walkers.js';
23
+ /** Max depth for compound-receiver chain resolution (`a().b().c().d()`).
24
+ * Practical code rarely exceeds 3-4 hops; the cap prevents
25
+ * pathological recursion if the receiver text is malformed. */
26
+ const COMPOUND_RECEIVER_MAX_DEPTH = 4;
27
+ export function resolveCompoundReceiverClass(receiverText, inScope, scopes, index, options = {}, depth = 0) {
28
+ const classScopeByDefId = index.classScopeByDefId;
29
+ if (depth > COMPOUND_RECEIVER_MAX_DEPTH)
30
+ return undefined;
31
+ const text = receiverText.trim();
32
+ if (text.length === 0)
33
+ return undefined;
34
+ const fieldFallback = options.fieldFallback ?? true;
35
+ // Bare identifier — resolve via typeBinding then class lookup.
36
+ if (!text.includes('.') && !text.includes('(')) {
37
+ const tb = findReceiverTypeBinding(inScope, text, scopes);
38
+ if (tb === undefined)
39
+ return undefined;
40
+ return findClassBindingInScope(tb.declaredAtScope, tb.rawName, scopes);
41
+ }
42
+ // Trailing `()` — call expression. Strip it and resolve the function
43
+ // expression's return type. We only handle the canonical `f()` /
44
+ // `obj.method()` shape; nested-arg expressions like `f(g())` are
45
+ // out of scope for V1 (depth-capped recursion catches infinite loops).
46
+ if (text.endsWith(')')) {
47
+ const openIdx = matchingOpenParen(text);
48
+ if (openIdx === -1)
49
+ return undefined;
50
+ const fnExpr = text.slice(0, openIdx).trim();
51
+ if (fnExpr.length === 0)
52
+ return undefined;
53
+ const lastDot = fnExpr.lastIndexOf('.');
54
+ if (lastDot === -1) {
55
+ // Free call `name()`. Look up function in scope, then its
56
+ // return-type typeBinding (which lives in the function's
57
+ // enclosing scope per the language's return-type hoist rule).
58
+ const fnDef = findExportedDefByName(fnExpr, inScope, scopes, index);
59
+ if (fnDef === undefined)
60
+ return undefined;
61
+ const retType = findReceiverTypeBinding(inScope, fnExpr, scopes);
62
+ if (retType === undefined)
63
+ return undefined;
64
+ return findClassBindingInScope(retType.declaredAtScope, retType.rawName, scopes);
65
+ }
66
+ // `obj.method()` — resolve obj's class, look up method's return
67
+ // type on that class scope (or the MRO).
68
+ const objExpr = fnExpr.slice(0, lastDot);
69
+ const methodName = fnExpr.slice(lastDot + 1);
70
+ const objClass = resolveCompoundReceiverClass(objExpr, inScope, scopes, index, options, depth + 1);
71
+ if (objClass === undefined)
72
+ return undefined;
73
+ let retType;
74
+ const ownerChain = [objClass.nodeId, ...scopes.methodDispatch.mroFor(objClass.nodeId)];
75
+ for (const ownerId of ownerChain) {
76
+ const cs = classScopeByDefId.get(ownerId);
77
+ const candidate = cs?.typeBindings.get(methodName);
78
+ if (candidate !== undefined) {
79
+ retType = candidate;
80
+ break;
81
+ }
82
+ // Fallback: walk up from the class scope looking for a return-
83
+ // type binding on an ancestor (Module) scope. Gated on
84
+ // `hoistTypeBindingsToModule` because only languages that hoist
85
+ // method return-type bindings to Module scope need this path;
86
+ // enabling it unconditionally would let other languages pick up
87
+ // unrelated module-level bindings. See contract doc for the
88
+ // invariant and `propagateImportedReturnTypes` for how the
89
+ // hoisted bindings originate.
90
+ if (cs !== undefined && options.hoistTypeBindingsToModule === true) {
91
+ let curId = cs.parent;
92
+ while (curId !== null) {
93
+ const curScope = scopes.scopeTree.getScope(curId);
94
+ if (curScope === undefined)
95
+ break;
96
+ const cand = curScope.typeBindings.get(methodName);
97
+ if (cand !== undefined) {
98
+ retType = cand;
99
+ break;
100
+ }
101
+ curId = curScope.parent;
102
+ }
103
+ if (retType !== undefined)
104
+ break;
105
+ }
106
+ }
107
+ if (retType === undefined && fieldFallback) {
108
+ const objCs = classScopeByDefId.get(objClass.nodeId);
109
+ if (objCs !== undefined) {
110
+ for (const [, fieldType] of objCs.typeBindings) {
111
+ const fieldClass = findClassBindingInScope(fieldType.declaredAtScope, fieldType.rawName, scopes);
112
+ if (fieldClass === undefined)
113
+ continue;
114
+ const fcs = classScopeByDefId.get(fieldClass.nodeId);
115
+ const candidate = fcs?.typeBindings.get(methodName);
116
+ if (candidate !== undefined) {
117
+ retType = candidate;
118
+ break;
119
+ }
120
+ }
121
+ }
122
+ }
123
+ if (retType === undefined)
124
+ return undefined;
125
+ return findClassBindingInScope(retType.declaredAtScope, retType.rawName, scopes);
126
+ }
127
+ // Pure dotted access `obj.field[.field]…` — walk fields.
128
+ const parts = text.split('.');
129
+ // Language-specific collection-accessor suffix (C#'s `data.Values`
130
+ // on Dictionary<K,V>, etc.). When the provider hook recognizes
131
+ // the final segment and unwraps the receiver's generic, return
132
+ // the element class directly. Resolved before the field-walk
133
+ // because Dictionary-family types aren't local class defs.
134
+ if (options.unwrapCollectionAccessor !== undefined && parts.length >= 2) {
135
+ const last = parts[parts.length - 1];
136
+ const prefix = parts.slice(0, -1).join('.');
137
+ let prefixType;
138
+ if (parts.length === 2) {
139
+ prefixType = findReceiverTypeBinding(inScope, prefix, scopes);
140
+ }
141
+ else {
142
+ // Recursive resolution: walk the prefix as a dotted class chain
143
+ // to find its typeRef. We need the TypeRef (not the class def)
144
+ // because the hook inspects the raw generic args (e.g.
145
+ // `Dictionary<string, User>`).
146
+ const headInner = parts[0];
147
+ let cur = findReceiverTypeBinding(inScope, headInner, scopes);
148
+ for (let i = 1; i < parts.length - 1 && cur !== undefined; i++) {
149
+ const cls = findClassBindingInScope(cur.declaredAtScope, cur.rawName, scopes);
150
+ if (cls === undefined) {
151
+ cur = undefined;
152
+ break;
153
+ }
154
+ const cs = classScopeByDefId.get(cls.nodeId);
155
+ cur = cs?.typeBindings.get(parts[i]);
156
+ }
157
+ prefixType = cur;
158
+ }
159
+ if (prefixType !== undefined) {
160
+ const elemName = options.unwrapCollectionAccessor(prefixType.rawName, last);
161
+ if (elemName !== undefined) {
162
+ return findClassBindingInScope(prefixType.declaredAtScope, elemName, scopes);
163
+ }
164
+ }
165
+ }
166
+ const head = parts[0];
167
+ const headType = findReceiverTypeBinding(inScope, head, scopes);
168
+ let currentClass = headType
169
+ ? findClassBindingInScope(headType.declaredAtScope, headType.rawName, scopes)
170
+ : undefined;
171
+ for (let i = 1; i < parts.length && currentClass !== undefined; i++) {
172
+ const fieldName = parts[i];
173
+ const cs = classScopeByDefId.get(currentClass.nodeId);
174
+ const fieldType = cs?.typeBindings.get(fieldName);
175
+ if (fieldType === undefined)
176
+ return undefined;
177
+ currentClass = findClassBindingInScope(fieldType.declaredAtScope, fieldType.rawName, scopes);
178
+ }
179
+ return currentClass;
180
+ }
181
+ /** Find the index of the `(` that matches the trailing `)` of a
182
+ * call-expression text. Returns -1 if unbalanced. */
183
+ function matchingOpenParen(text) {
184
+ if (!text.endsWith(')'))
185
+ return -1;
186
+ let depth = 0;
187
+ for (let i = text.length - 1; i >= 0; i--) {
188
+ const ch = text[i];
189
+ if (ch === ')')
190
+ depth++;
191
+ else if (ch === '(') {
192
+ depth--;
193
+ if (depth === 0)
194
+ return i;
195
+ }
196
+ }
197
+ return -1;
198
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Emit CALLS edges for free-call reference sites whose target is
3
+ * imported (or otherwise visible only via post-finalize scope.bindings).
4
+ *
5
+ * The shared `MethodRegistry.lookup` only consults `scope.bindings`
6
+ * (pre-finalize / local-only) for free calls. Cross-file imports land
7
+ * in `indexes.bindings` (post-finalize). Without this fallback, every
8
+ * `from x import f; f()` resolves to "unresolved".
9
+ *
10
+ * **Free-call dedup contract (Contract Invariant I2):** free calls
11
+ * collapse to one CALLS edge per (caller, target) pair regardless of
12
+ * how many call sites the caller contains. Mirrors the legacy DAG's
13
+ * dedup semantics (what the `default-params` / `variadic` / `overload`
14
+ * fixtures expect). Member calls keep position-based dedup elsewhere.
15
+ *
16
+ * Generic; promoted from `languages/python/scope-resolver.ts` per the scope-resolution
17
+ * generalization plan.
18
+ */
19
+ import type { ParsedFile, Reference, ScopeId } from '../../../../_shared/index.js';
20
+ import type { KnowledgeGraph } from '../../../graph/types.js';
21
+ import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
22
+ import type { SemanticModel } from '../../model/semantic-model.js';
23
+ import type { WorkspaceResolutionIndex } from '../workspace-index.js';
24
+ import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
25
+ export declare function emitFreeCallFallback(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, _referenceIndex: {
26
+ readonly bySourceScope: ReadonlyMap<ScopeId, readonly Reference[]>;
27
+ }, handledSites: Set<string>, model: SemanticModel, workspaceIndex: WorkspaceResolutionIndex): number;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Emit CALLS edges for free-call reference sites whose target is
3
+ * imported (or otherwise visible only via post-finalize scope.bindings).
4
+ *
5
+ * The shared `MethodRegistry.lookup` only consults `scope.bindings`
6
+ * (pre-finalize / local-only) for free calls. Cross-file imports land
7
+ * in `indexes.bindings` (post-finalize). Without this fallback, every
8
+ * `from x import f; f()` resolves to "unresolved".
9
+ *
10
+ * **Free-call dedup contract (Contract Invariant I2):** free calls
11
+ * collapse to one CALLS edge per (caller, target) pair regardless of
12
+ * how many call sites the caller contains. Mirrors the legacy DAG's
13
+ * dedup semantics (what the `default-params` / `variadic` / `overload`
14
+ * fixtures expect). Member calls keep position-based dedup elsewhere.
15
+ *
16
+ * Generic; promoted from `languages/python/scope-resolver.ts` per the scope-resolution
17
+ * generalization plan.
18
+ */
19
+ import { resolveCallerGraphId, resolveDefGraphId } from '../graph-bridge/ids.js';
20
+ import { findCallableBindingInScope, findClassBindingInScope } from '../scope/walkers.js';
21
+ import { narrowOverloadCandidates } from './overload-narrowing.js';
22
+ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _referenceIndex, handledSites, model, workspaceIndex) {
23
+ let emitted = 0;
24
+ const seen = new Set();
25
+ for (const parsed of parsedFiles) {
26
+ for (const site of parsed.referenceSites) {
27
+ if (site.kind !== 'call')
28
+ continue;
29
+ if (site.explicitReceiver !== undefined)
30
+ continue;
31
+ // Constructor form (`new User(...)`): resolve the class, then
32
+ // emit CALLS to its explicit Constructor def (when present) or
33
+ // to the Class node itself (implicit constructor). Legacy emits
34
+ // the same two targets; see test expectations.
35
+ let fnDef;
36
+ if (site.callForm === 'constructor') {
37
+ const classDef = findClassBindingInScope(site.inScope, site.name, scopes);
38
+ if (classDef !== undefined) {
39
+ fnDef = pickConstructorOrClass(classDef, workspaceIndex);
40
+ }
41
+ }
42
+ // Implicit-this overload narrowing: an unqualified call inside
43
+ // a method body might be calling a sibling overload on the
44
+ // enclosing class. When the workspace has multiple methods of
45
+ // the same name in a single class, choose the best match by
46
+ // arity + argument types.
47
+ if (fnDef === undefined) {
48
+ fnDef = pickImplicitThisOverload(site, scopes, workspaceIndex, model);
49
+ }
50
+ if (fnDef === undefined) {
51
+ fnDef = findCallableBindingInScope(site.inScope, site.name, scopes);
52
+ }
53
+ if (fnDef === undefined)
54
+ continue;
55
+ const callerGraphId = resolveCallerGraphId(site.inScope, scopes, nodeLookup);
56
+ if (callerGraphId === undefined)
57
+ continue;
58
+ const tgtGraphId = resolveDefGraphId(fnDef.filePath, fnDef, nodeLookup);
59
+ if (tgtGraphId === undefined)
60
+ continue;
61
+ // Always mark the site as handled — even when the dedup-collapse
62
+ // means we don't add a new edge — so `emit-references` skips its
63
+ // potentially-wrong fallback for the same site.
64
+ handledSites.add(`${parsed.filePath}:${site.atRange.startLine}:${site.atRange.startCol}`);
65
+ const relId = `rel:CALLS:${callerGraphId}->${tgtGraphId}`;
66
+ if (seen.has(relId))
67
+ continue;
68
+ seen.add(relId);
69
+ graph.addRelationship({
70
+ id: relId,
71
+ sourceId: callerGraphId,
72
+ targetId: tgtGraphId,
73
+ type: 'CALLS',
74
+ confidence: 0.85,
75
+ // Match legacy DAG's reason convention so consumers that
76
+ // assert `reason === 'import-resolved'` keep working.
77
+ reason: fnDef.filePath !== parsed.filePath ? 'import-resolved' : 'local-call',
78
+ });
79
+ emitted++;
80
+ }
81
+ }
82
+ return emitted;
83
+ }
84
+ /** For a constructor call `new X(...)`, return the X class's explicit
85
+ * Constructor def (by walking the class scope's ownedDefs) or the
86
+ * Class def itself when no explicit Constructor exists. Matches
87
+ * legacy behavior — tests assert targetLabel === 'Class' for implicit
88
+ * ctors and targetLabel === 'Constructor' for explicit ones. */
89
+ function pickConstructorOrClass(classDef, workspaceIndex) {
90
+ const classScope = workspaceIndex.classScopeByDefId.get(classDef.nodeId);
91
+ if (classScope === undefined)
92
+ return classDef;
93
+ for (const def of classScope.ownedDefs) {
94
+ if (def.type === 'Constructor')
95
+ return def;
96
+ }
97
+ return classDef;
98
+ }
99
+ /** Walk up from the call-site scope to the enclosing class scope,
100
+ * pick a method member by name with overload narrowing on arity +
101
+ * argument types. Returns undefined if there's no enclosing class
102
+ * or no matching method. Used for implicit-this calls inside a
103
+ * class body where multiple overloads share the call name. */
104
+ function pickImplicitThisOverload(site, scopes, workspaceIndex, model) {
105
+ // Find the enclosing Class scope by walking parents.
106
+ let curId = site.inScope;
107
+ let classScopeId;
108
+ while (curId !== null) {
109
+ const sc = scopes.scopeTree.getScope(curId);
110
+ if (sc === undefined)
111
+ break;
112
+ if (sc.kind === 'Class') {
113
+ classScopeId = sc.id;
114
+ break;
115
+ }
116
+ curId = sc.parent;
117
+ }
118
+ if (classScopeId === undefined)
119
+ return undefined;
120
+ // O(1) reverse-lookup via inverse map on WorkspaceResolutionIndex.
121
+ const classDefId = workspaceIndex.classScopeIdToDefId.get(classScopeId);
122
+ if (classDefId === undefined)
123
+ return undefined;
124
+ const overloads = model.methods.lookupAllByOwner(classDefId, site.name);
125
+ if (overloads.length === 0)
126
+ return undefined;
127
+ if (overloads.length === 1)
128
+ return overloads[0];
129
+ const candidates = narrowOverloadCandidates(overloads, site.arity, site.argumentTypes);
130
+ return candidates[0];
131
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Cross-file return-type typeBinding propagation + post-finalize
3
+ * chain re-follow.
4
+ *
5
+ * **Why this lives in scope-resolution:** the algorithm is language-agnostic.
6
+ * Every language with cross-file callable imports needs the same
7
+ * mirror-binding step, otherwise `u = f(); u.save()` only resolves
8
+ * when `f` is in the same file as the call.
9
+ *
10
+ * **Mutation contract (Contract Invariant I3 + I6):**
11
+ * - Mutates `Scope.typeBindings` (a plain `new Map(...)` from
12
+ * `draftToScope`, NOT frozen — intentional, do not freeze).
13
+ * - MUST run AFTER `finalizeScopeModel` (so `indexes.bindings` is
14
+ * populated) but BEFORE `resolveReferenceSites` (so resolution
15
+ * sees the propagated types).
16
+ *
17
+ * Generic; promoted from `languages/python/scope-resolver.ts` per the scope-resolution
18
+ * generalization plan.
19
+ */
20
+ import type { ParsedFile } from '../../../../_shared/index.js';
21
+ import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
22
+ import type { WorkspaceResolutionIndex } from '../workspace-index.js';
23
+ /**
24
+ * Copy return-type typeBindings across module boundaries via import
25
+ * bindings. For each module-scope import like `from x import f`, look
26
+ * up `f` in the source file's module-scope typeBindings (which carries
27
+ * `f → ReturnType` from the language's return-type annotation
28
+ * capture) and mirror that binding into the importer's module scope.
29
+ *
30
+ * After propagation, re-runs the chain-follow on every scope's
31
+ * typeBindings — the in-extractor pass-4 ran before propagation and
32
+ * missed any chain whose terminal lived in a foreign file.
33
+ *
34
+ * Scope-chain concern (verified 2026-04-21): `pythonImportOwningScope`
35
+ * documents that function-local `from x import y` binds `y` to the
36
+ * inner function scope, which would make a module-only write miss
37
+ * non-module importers. In practice `finalize-algorithm` hoists those
38
+ * bindings into `indexes.bindings[moduleScope]` regardless of where
39
+ * the `import` statement appears — the integration fixture
40
+ * `python-function-local-import-chain` exercises a chained
41
+ * receiver-bound call `u = get_user(); u.save()` inside a function
42
+ * body and emits the expected `do_work → User.save` edge. The
43
+ * module-scope write is sufficient today. If finalize routing ever
44
+ * changes to honor the hook's per-scope contract, this pass must
45
+ * iterate `indexes.bindings` over every scope and mirror into the
46
+ * binding-owning scope's `typeBindings`, not just the module's.
47
+ */
48
+ export declare function propagateImportedReturnTypes(parsedFiles: readonly ParsedFile[], indexes: ScopeResolutionIndexes, index: WorkspaceResolutionIndex): void;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Cross-file return-type typeBinding propagation + post-finalize
3
+ * chain re-follow.
4
+ *
5
+ * **Why this lives in scope-resolution:** the algorithm is language-agnostic.
6
+ * Every language with cross-file callable imports needs the same
7
+ * mirror-binding step, otherwise `u = f(); u.save()` only resolves
8
+ * when `f` is in the same file as the call.
9
+ *
10
+ * **Mutation contract (Contract Invariant I3 + I6):**
11
+ * - Mutates `Scope.typeBindings` (a plain `new Map(...)` from
12
+ * `draftToScope`, NOT frozen — intentional, do not freeze).
13
+ * - MUST run AFTER `finalizeScopeModel` (so `indexes.bindings` is
14
+ * populated) but BEFORE `resolveReferenceSites` (so resolution
15
+ * sees the propagated types).
16
+ *
17
+ * Generic; promoted from `languages/python/scope-resolver.ts` per the scope-resolution
18
+ * generalization plan.
19
+ */
20
+ /** Max chain depth for the post-finalize re-follow. */
21
+ const RECHAIN_MAX_DEPTH = 8;
22
+ /** Walk `ref.rawName` through the scope chain's typeBindings looking
23
+ * for a terminal class-like rawName. Mirrors the in-extractor
24
+ * `followChainedRef` but operates on post-finalize Scope objects so
25
+ * it can see imported return-types propagated by
26
+ * `propagateImportedReturnTypes`. */
27
+ function followChainPostFinalize(start, fromScopeId, scopes) {
28
+ let current = start;
29
+ const visited = new Set();
30
+ for (let depth = 0; depth < RECHAIN_MAX_DEPTH; depth++) {
31
+ if (current.rawName.includes('.'))
32
+ return current;
33
+ let scopeId = fromScopeId;
34
+ let next;
35
+ while (scopeId !== null) {
36
+ const scope = scopes.scopeTree.getScope(scopeId);
37
+ if (scope === undefined)
38
+ break;
39
+ next = scope.typeBindings.get(current.rawName);
40
+ if (next !== undefined && next !== current)
41
+ break;
42
+ next = undefined;
43
+ scopeId = scope.parent;
44
+ }
45
+ if (next === undefined)
46
+ return current;
47
+ if (visited.has(next.rawName))
48
+ return current;
49
+ visited.add(next.rawName);
50
+ current = next;
51
+ }
52
+ return current;
53
+ }
54
+ /**
55
+ * Copy return-type typeBindings across module boundaries via import
56
+ * bindings. For each module-scope import like `from x import f`, look
57
+ * up `f` in the source file's module-scope typeBindings (which carries
58
+ * `f → ReturnType` from the language's return-type annotation
59
+ * capture) and mirror that binding into the importer's module scope.
60
+ *
61
+ * After propagation, re-runs the chain-follow on every scope's
62
+ * typeBindings — the in-extractor pass-4 ran before propagation and
63
+ * missed any chain whose terminal lived in a foreign file.
64
+ *
65
+ * Scope-chain concern (verified 2026-04-21): `pythonImportOwningScope`
66
+ * documents that function-local `from x import y` binds `y` to the
67
+ * inner function scope, which would make a module-only write miss
68
+ * non-module importers. In practice `finalize-algorithm` hoists those
69
+ * bindings into `indexes.bindings[moduleScope]` regardless of where
70
+ * the `import` statement appears — the integration fixture
71
+ * `python-function-local-import-chain` exercises a chained
72
+ * receiver-bound call `u = get_user(); u.save()` inside a function
73
+ * body and emits the expected `do_work → User.save` edge. The
74
+ * module-scope write is sufficient today. If finalize routing ever
75
+ * changes to honor the hook's per-scope contract, this pass must
76
+ * iterate `indexes.bindings` over every scope and mirror into the
77
+ * binding-owning scope's `typeBindings`, not just the module's.
78
+ */
79
+ export function propagateImportedReturnTypes(parsedFiles, indexes, index) {
80
+ const moduleScopeByFile = index.moduleScopeByFile;
81
+ for (const parsed of parsedFiles) {
82
+ const importerModule = moduleScopeByFile.get(parsed.filePath);
83
+ if (importerModule === undefined)
84
+ continue;
85
+ const finalizedBindings = indexes.bindings.get(importerModule.id);
86
+ if (finalizedBindings === undefined)
87
+ continue;
88
+ for (const [localName, refs] of finalizedBindings) {
89
+ // Skip if importer already has a typeBinding for this name (e.g.
90
+ // an explicit local annotation should win over import-derived).
91
+ if (importerModule.typeBindings.has(localName))
92
+ continue;
93
+ for (const ref of refs) {
94
+ if (ref.origin !== 'import' && ref.origin !== 'reexport')
95
+ continue;
96
+ const sourceModule = moduleScopeByFile.get(ref.def.filePath);
97
+ if (sourceModule === undefined)
98
+ continue;
99
+ // The source file's typeBinding is keyed by the def's simple
100
+ // name (e.g. `get_user`), not the importer's local alias. Use
101
+ // the def's qualifiedName tail.
102
+ const qn = ref.def.qualifiedName;
103
+ if (qn === undefined)
104
+ continue;
105
+ const dot = qn.lastIndexOf('.');
106
+ const sourceName = dot === -1 ? qn : qn.slice(dot + 1);
107
+ const sourceTypeRef = sourceModule.typeBindings.get(sourceName);
108
+ if (sourceTypeRef === undefined)
109
+ continue;
110
+ // Mirror the binding under the importer's local alias —
111
+ // mutating typeBindings is safe because draftToScope produced
112
+ // a non-frozen Map.
113
+ importerModule.typeBindings.set(localName, sourceTypeRef);
114
+ break;
115
+ }
116
+ }
117
+ }
118
+ // Re-follow chains across every scope so chains terminating in a
119
+ // freshly-propagated import binding resolve to their terminal type.
120
+ for (const parsed of parsedFiles) {
121
+ for (const scope of parsed.scopes) {
122
+ for (const [name, ref] of scope.typeBindings) {
123
+ const resolved = followChainPostFinalize(ref, scope.id, indexes);
124
+ if (resolved !== ref) {
125
+ scope.typeBindings.set(name, resolved);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Generic MRO (method-resolution-order) builder.
3
+ *
4
+ * Walks the graph's `EXTENDS` edges to recover an inheritance map,
5
+ * then asks the per-language `LinearizeStrategy` to order each class's
6
+ * ancestors. Returns `Map<classDefId, ancestorDefId[]>` ready to plug
7
+ * into `MethodDispatchIndex` via `buildPopulatedMethodDispatch`.
8
+ *
9
+ * **Why a strategy hook:** linearization differs across languages.
10
+ * - Python (depth-first first-seen, single inheritance): trivially
11
+ * correct; multi-inheritance falls back to BFS dedup. Real C3
12
+ * would handle diamond hierarchies — defer until we hit one.
13
+ * - Java (single-inheritance only): walk one parent.
14
+ * - C++ (multiple inheritance): C3-like or BFS depending on how
15
+ * strict the consumer needs to be.
16
+ * - Languages without inheritance (COBOL): return empty list.
17
+ *
18
+ * The strategy receives the FULL ancestry context (`directParents` +
19
+ * `parentsByDefId`) so C3 implementations have what they need.
20
+ */
21
+ import type { ParsedFile } from '../../../../_shared/index.js';
22
+ import type { KnowledgeGraph } from '../../../graph/types.js';
23
+ import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
24
+ import type { LinearizeStrategy } from '../contract/scope-resolver.js';
25
+ /**
26
+ * Build an MRO map keyed by scope-resolution Class `DefId`.
27
+ *
28
+ * Steps:
29
+ * 1. Collect EXTENDS edges from the graph → `parentsByGraphId`.
30
+ * 2. Collect Class defs from `parsedFiles` and translate to graph
31
+ * ids via `nodeLookup` → `defIdByGraphId` (the bridge between
32
+ * scope-resolution DefId and the legacy graph node id).
33
+ * 3. For each Class def, ask `linearize` for its ancestor order.
34
+ */
35
+ export declare function buildMro(graph: KnowledgeGraph, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, linearize: LinearizeStrategy): Map<string, string[]>;
36
+ /**
37
+ * Default linearization: depth-first BFS-with-visited, first-seen
38
+ * wins. Correct for single-inheritance languages and for Python's
39
+ * simplified MRO. Multi-inheritance diamond hierarchies need a real
40
+ * C3 implementation; per-language overrides land here.
41
+ */
42
+ export declare const defaultLinearize: LinearizeStrategy;