gitnexus 1.6.4-rc.2 → 1.6.4-rc.21

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 (243) hide show
  1. package/README.md +35 -0
  2. package/dist/_shared/index.d.ts +1 -1
  3. package/dist/_shared/index.d.ts.map +1 -1
  4. package/dist/_shared/index.js +1 -1
  5. package/dist/_shared/index.js.map +1 -1
  6. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts +22 -14
  7. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts.map +1 -1
  8. package/dist/_shared/scope-resolution/finalize-algorithm.js +298 -37
  9. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  10. package/dist/_shared/scope-resolution/scope-tree.d.ts +23 -1
  11. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  12. package/dist/_shared/scope-resolution/scope-tree.js +36 -2
  13. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  14. package/dist/_shared/scope-resolution/types.d.ts +47 -3
  15. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  16. package/dist/_shared/scope-resolution/types.js +10 -2
  17. package/dist/_shared/scope-resolution/types.js.map +1 -1
  18. package/dist/cli/analyze.d.ts +6 -0
  19. package/dist/cli/analyze.js +35 -0
  20. package/dist/cli/doctor.d.ts +1 -0
  21. package/dist/cli/doctor.js +31 -0
  22. package/dist/cli/index.js +13 -0
  23. package/dist/cli/setup.js +2 -2
  24. package/dist/core/embeddings/config.d.ts +2 -0
  25. package/dist/core/embeddings/config.js +36 -0
  26. package/dist/core/embeddings/embedder.js +11 -6
  27. package/dist/core/embeddings/embedding-pipeline.d.ts +7 -1
  28. package/dist/core/embeddings/embedding-pipeline.js +93 -29
  29. package/dist/core/embeddings/exact-search.d.ts +15 -0
  30. package/dist/core/embeddings/exact-search.js +27 -0
  31. package/dist/core/embeddings/types.d.ts +4 -0
  32. package/dist/core/embeddings/types.js +2 -0
  33. package/dist/core/group/config-parser.js +2 -0
  34. package/dist/core/group/matching.d.ts +3 -3
  35. package/dist/core/group/matching.js +46 -6
  36. package/dist/core/group/storage.js +2 -0
  37. package/dist/core/group/sync.js +1 -1
  38. package/dist/core/group/types.d.ts +18 -0
  39. package/dist/core/ingestion/call-processor.d.ts +3 -3
  40. package/dist/core/ingestion/call-processor.js +58 -65
  41. package/dist/core/ingestion/constants.d.ts +4 -3
  42. package/dist/core/ingestion/constants.js +8 -3
  43. package/dist/core/ingestion/finalize-orchestrator.js +6 -3
  44. package/dist/core/ingestion/heritage-processor.js +2 -2
  45. package/dist/core/ingestion/import-processor.js +1 -1
  46. package/dist/core/ingestion/language-provider.d.ts +8 -0
  47. package/dist/core/ingestion/languages/csharp/captures.js +4 -1
  48. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +14 -13
  49. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +62 -50
  50. package/dist/core/ingestion/languages/python/captures.js +9 -1
  51. package/dist/core/ingestion/languages/python/index.d.ts +1 -1
  52. package/dist/core/ingestion/languages/python/index.js +1 -1
  53. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +3 -1
  54. package/dist/core/ingestion/languages/python/simple-hooks.js +8 -0
  55. package/dist/core/ingestion/languages/python.js +28 -1
  56. package/dist/core/ingestion/languages/swift.js +14 -0
  57. package/dist/core/ingestion/languages/typescript/arity-metadata.d.ts +59 -0
  58. package/dist/core/ingestion/languages/typescript/arity-metadata.js +103 -0
  59. package/dist/core/ingestion/languages/typescript/arity.d.ts +37 -0
  60. package/dist/core/ingestion/languages/typescript/arity.js +54 -0
  61. package/dist/core/ingestion/languages/typescript/cache-stats.d.ts +17 -0
  62. package/dist/core/ingestion/languages/typescript/cache-stats.js +28 -0
  63. package/dist/core/ingestion/languages/typescript/captures.d.ts +28 -0
  64. package/dist/core/ingestion/languages/typescript/captures.js +451 -0
  65. package/dist/core/ingestion/languages/typescript/import-decomposer.d.ts +49 -0
  66. package/dist/core/ingestion/languages/typescript/import-decomposer.js +371 -0
  67. package/dist/core/ingestion/languages/typescript/import-target.d.ts +50 -0
  68. package/dist/core/ingestion/languages/typescript/import-target.js +61 -0
  69. package/dist/core/ingestion/languages/typescript/index.d.ts +94 -0
  70. package/dist/core/ingestion/languages/typescript/index.js +94 -0
  71. package/dist/core/ingestion/languages/typescript/interpret.d.ts +35 -0
  72. package/dist/core/ingestion/languages/typescript/interpret.js +317 -0
  73. package/dist/core/ingestion/languages/typescript/merge-bindings.d.ts +62 -0
  74. package/dist/core/ingestion/languages/typescript/merge-bindings.js +158 -0
  75. package/dist/core/ingestion/languages/typescript/query.d.ts +77 -0
  76. package/dist/core/ingestion/languages/typescript/query.js +778 -0
  77. package/dist/core/ingestion/languages/typescript/receiver-binding.d.ts +59 -0
  78. package/dist/core/ingestion/languages/typescript/receiver-binding.js +171 -0
  79. package/dist/core/ingestion/languages/typescript/scope-resolver.d.ts +16 -0
  80. package/dist/core/ingestion/languages/typescript/scope-resolver.js +113 -0
  81. package/dist/core/ingestion/languages/typescript/simple-hooks.d.ts +71 -0
  82. package/dist/core/ingestion/languages/typescript/simple-hooks.js +131 -0
  83. package/dist/core/ingestion/languages/typescript.js +19 -0
  84. package/dist/core/ingestion/method-extractors/configs/swift.js +3 -4
  85. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +14 -1
  86. package/dist/core/ingestion/parsing-processor.js +20 -9
  87. package/dist/core/ingestion/pipeline-phases/processes.js +9 -4
  88. package/dist/core/ingestion/pipeline-phases/tools.d.ts +1 -0
  89. package/dist/core/ingestion/pipeline-phases/tools.js +10 -4
  90. package/dist/core/ingestion/registry-primary-flag.d.ts +3 -1
  91. package/dist/core/ingestion/registry-primary-flag.js +4 -1
  92. package/dist/core/ingestion/scope-extractor-bridge.d.ts +5 -2
  93. package/dist/core/ingestion/scope-extractor-bridge.js +7 -2
  94. package/dist/core/ingestion/scope-extractor.js +19 -18
  95. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +73 -11
  96. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +48 -10
  97. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +283 -14
  98. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +23 -2
  99. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +109 -37
  100. package/dist/core/ingestion/scope-resolution/passes/mro.js +3 -1
  101. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +13 -5
  102. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +11 -2
  103. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
  104. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +8 -0
  105. package/dist/core/ingestion/scope-resolution/pipeline/run.js +21 -5
  106. package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.d.ts +39 -0
  107. package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.js +65 -0
  108. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +54 -11
  109. package/dist/core/ingestion/scope-resolution/scope/walkers.js +105 -30
  110. package/dist/core/ingestion/type-extractors/swift.js +7 -4
  111. package/dist/core/ingestion/utils/ast-helpers.d.ts +2 -0
  112. package/dist/core/ingestion/utils/ast-helpers.js +12 -0
  113. package/dist/core/ingestion/utils/env.d.ts +10 -0
  114. package/dist/core/ingestion/utils/env.js +14 -0
  115. package/dist/core/ingestion/workers/parse-worker.d.ts +1 -0
  116. package/dist/core/ingestion/workers/parse-worker.js +15 -9
  117. package/dist/core/ingestion/workers/worker-pool.d.ts +11 -4
  118. package/dist/core/ingestion/workers/worker-pool.js +244 -48
  119. package/dist/core/lbug/extension-loader.d.ts +86 -0
  120. package/dist/core/lbug/extension-loader.js +184 -0
  121. package/dist/core/lbug/lbug-adapter.d.ts +18 -17
  122. package/dist/core/lbug/lbug-adapter.js +45 -73
  123. package/dist/core/lbug/pool-adapter.js +10 -28
  124. package/dist/core/platform/capabilities.d.ts +24 -0
  125. package/dist/core/platform/capabilities.js +54 -0
  126. package/dist/core/run-analyze.js +36 -9
  127. package/dist/core/search/bm25-index.d.ts +0 -17
  128. package/dist/core/search/bm25-index.js +10 -118
  129. package/dist/core/search/fts-indexes.d.ts +1 -0
  130. package/dist/core/search/fts-indexes.js +7 -0
  131. package/dist/core/search/fts-schema.d.ts +6 -0
  132. package/dist/core/search/fts-schema.js +7 -0
  133. package/dist/mcp/core/embedder.js +11 -4
  134. package/dist/mcp/local/local-backend.js +50 -15
  135. package/dist/server/api.d.ts +5 -0
  136. package/dist/server/api.js +113 -0
  137. package/hooks/claude/gitnexus-hook.cjs +11 -1
  138. package/package.json +6 -5
  139. package/scripts/build-tree-sitter-dart.cjs +42 -0
  140. package/scripts/build-tree-sitter-proto.cjs +1 -1
  141. package/scripts/build.js +22 -2
  142. package/scripts/install-duckdb-extension.mjs +37 -0
  143. package/vendor/tree-sitter-dart/README.md +18 -0
  144. package/vendor/tree-sitter-dart/binding.gyp +31 -0
  145. package/vendor/tree-sitter-dart/bindings/node/binding.cc +20 -0
  146. package/vendor/tree-sitter-dart/bindings/node/index.d.ts +28 -0
  147. package/vendor/tree-sitter-dart/bindings/node/index.js +7 -0
  148. package/vendor/tree-sitter-dart/grammar.js +2895 -0
  149. package/vendor/tree-sitter-dart/package.json +18 -0
  150. package/vendor/tree-sitter-dart/queries/highlights.scm +246 -0
  151. package/vendor/tree-sitter-dart/queries/tags.scm +92 -0
  152. package/vendor/tree-sitter-dart/queries/test.scm +1 -0
  153. package/vendor/tree-sitter-dart/src/grammar.json +12459 -0
  154. package/vendor/tree-sitter-dart/src/node-types.json +15055 -0
  155. package/vendor/tree-sitter-dart/src/parser.c +196127 -0
  156. package/vendor/tree-sitter-dart/src/scanner.c +130 -0
  157. package/vendor/tree-sitter-dart/src/tree_sitter/alloc.h +54 -0
  158. package/vendor/tree-sitter-dart/src/tree_sitter/array.h +290 -0
  159. package/vendor/tree-sitter-dart/src/tree_sitter/parser.h +265 -0
  160. package/vendor/tree-sitter-swift/LICENSE +21 -0
  161. package/vendor/tree-sitter-swift/README.md +139 -0
  162. package/vendor/tree-sitter-swift/bindings/node/index.d.ts +28 -0
  163. package/vendor/tree-sitter-swift/bindings/node/index.js +7 -0
  164. package/vendor/tree-sitter-swift/package.json +28 -0
  165. package/vendor/tree-sitter-swift/prebuilds/darwin-arm64/tree-sitter-swift.node +0 -0
  166. package/vendor/tree-sitter-swift/prebuilds/darwin-x64/tree-sitter-swift.node +0 -0
  167. package/vendor/tree-sitter-swift/prebuilds/linux-arm64/tree-sitter-swift.node +0 -0
  168. package/vendor/tree-sitter-swift/prebuilds/linux-x64/tree-sitter-swift.node +0 -0
  169. package/vendor/tree-sitter-swift/prebuilds/win32-arm64/tree-sitter-swift.node +0 -0
  170. package/vendor/tree-sitter-swift/prebuilds/win32-x64/tree-sitter-swift.node +0 -0
  171. package/vendor/tree-sitter-swift/src/node-types.json +30694 -0
  172. package/web/assets/agent-DaprsFSX.js +597 -0
  173. package/web/assets/architecture-YZFGNWBL-S5CXDPWN-DEdGaPg2.js +1 -0
  174. package/web/assets/architectureDiagram-EMZXCZ2Q-Domyk_gO.js +36 -0
  175. package/web/assets/blockDiagram-IGV67L2C-B_2kD7tM.js +132 -0
  176. package/web/assets/c4Diagram-DFAF54RM-BhJJW8Gg.js +10 -0
  177. package/web/assets/chunk-3GS5O3IE-jlWIjPsl.js +231 -0
  178. package/web/assets/chunk-3YCYZ6SJ-Blq_IzZs.js +1 -0
  179. package/web/assets/chunk-6NTNNK5N-DyPc58pp.js +1 -0
  180. package/web/assets/chunk-7RZVMHOQ-BdIU-RGO.js +321 -0
  181. package/web/assets/chunk-A34GCYZU-BI2i_LdU.js +1 -0
  182. package/web/assets/chunk-AEOMTBSW-D7qjBMHW.js +1 -0
  183. package/web/assets/chunk-CilyBKbf.js +1 -0
  184. package/web/assets/chunk-DJ7UZH7F-i11ywiBl.js +1 -0
  185. package/web/assets/chunk-DKKBVRCY-1SffGI1N.js +4 -0
  186. package/web/assets/chunk-DU5LTGQ6-DaPeiwD5.js +1 -0
  187. package/web/assets/chunk-FXACKDTF-uhhi2PC2.js +159 -0
  188. package/web/assets/chunk-H3VCZNTA-IchcISDt.js +1 -0
  189. package/web/assets/chunk-HN6EAY2L-D7ZFMNrB.js +1 -0
  190. package/web/assets/chunk-KSICW3F5-C2tZmXwv.js +15 -0
  191. package/web/assets/chunk-O5ABG6QK-Bt-Km84H.js +1 -0
  192. package/web/assets/chunk-PK6DOVAG-ChlWY0BQ.js +206 -0
  193. package/web/assets/chunk-RNJOYNJ4-B724K7cW.js +1 -0
  194. package/web/assets/chunk-RWUO3TPN-DYn1XriD.js +1 -0
  195. package/web/assets/chunk-TBF5ZNIQ-DKtDz6ae.js +1 -0
  196. package/web/assets/chunk-TU3PZOEN-DE5Qhc0N.js +1 -0
  197. package/web/assets/chunk-TYMNRAUI-g1h33cq-.js +1 -0
  198. package/web/assets/chunk-VELTKBKT-C9dVN39o.js +1 -0
  199. package/web/assets/chunk-W7ZLLLMY-Du-Hb9yb.js +1 -0
  200. package/web/assets/chunk-WSB5WSVC-B123clsZ.js +1 -0
  201. package/web/assets/chunk-XGPFEOL4-BR7Eue38.js +1 -0
  202. package/web/assets/classDiagram-PPOCWD7C-BglfKSs_.js +1 -0
  203. package/web/assets/classDiagram-v2-23LJLIIU-BSzTM28O.js +1 -0
  204. package/web/assets/context-builder-CqQNhRj1.js +15 -0
  205. package/web/assets/cose-bilkent-PNC4W37J-DCfErU-A.js +1 -0
  206. package/web/assets/dagre-E77IOHMT-tDRRhDoN.js +4 -0
  207. package/web/assets/diagram-H7BISOXX-CUVHlmAh.js +43 -0
  208. package/web/assets/diagram-JC5VWROH-BoyOxulB.js +24 -0
  209. package/web/assets/diagram-LXUTUG65-osr9hb7N.js +10 -0
  210. package/web/assets/diagram-WEHSV5V5-d8nUqS39.js +24 -0
  211. package/web/assets/erDiagram-GCSMX5X6-b-IwOhPS.js +85 -0
  212. package/web/assets/flowDiagram-OTCZ4VVT-Ott2Q0AP.js +162 -0
  213. package/web/assets/ganttDiagram-MUNLMDZQ-BYtgN_5s.js +292 -0
  214. package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-CFyBIGZq.js +1 -0
  215. package/web/assets/gitGraphDiagram-3HKGZ4G3-CsVD2gn4.js +106 -0
  216. package/web/assets/index-BleGLU8S.css +2 -0
  217. package/web/assets/index-C_xK08EW.js +885 -0
  218. package/web/assets/info-OMHHGYJF-BF2H5H6G-yjAxKEzh.js +1 -0
  219. package/web/assets/infoDiagram-MN7RKWGX-DXK0Unn5.js +2 -0
  220. package/web/assets/ishikawaDiagram-YMYX4NHK-CXsnC2FA.js +70 -0
  221. package/web/assets/journeyDiagram-SO5T7YLQ-BzZ07B-X.js +139 -0
  222. package/web/assets/kanban-definition-LJHFXRCJ-C6_EpAd9.js +89 -0
  223. package/web/assets/katex-GD7MH7QM-CJiOjBBJ.js +261 -0
  224. package/web/assets/mindmap-definition-2EUWGEK5-CCYGWZ1m.js +96 -0
  225. package/web/assets/packet-4T2RLAQJ-EV4IVRXR-B8k4E3IT.js +1 -0
  226. package/web/assets/pie-ZZUOXDRM-N23DN5KN-DdvfY118.js +1 -0
  227. package/web/assets/pieDiagram-3IATQBI2-RyvRlQb4.js +30 -0
  228. package/web/assets/quadrantDiagram-E256RVCF-Bfb6sxCx.js +7 -0
  229. package/web/assets/radar-PYXPWWZC-P6TP7ZYP-1EEDC_yU.js +1 -0
  230. package/web/assets/requirementDiagram-M5DCFWZL-DjvHDyvN.js +84 -0
  231. package/web/assets/sankeyDiagram-L3NBLAOT-CBCbbl8s.js +10 -0
  232. package/web/assets/sequenceDiagram-ZOUHS735-BscU8TUR.js +157 -0
  233. package/web/assets/stateDiagram-MLPALWAM-CJusEK2D.js +1 -0
  234. package/web/assets/stateDiagram-v2-B5LQ5ZB2-DImJ3PXD.js +1 -0
  235. package/web/assets/timeline-definition-5SPVSISX-DigPA1X8.js +120 -0
  236. package/web/assets/treeView-SZITEDCU-5DXDK3XO-CzPDt3aG.js +1 -0
  237. package/web/assets/treemap-W4RFUUIX-WYLRDWKO-B9Iqiorr.js +1 -0
  238. package/web/assets/vennDiagram-IE5QUKF5-C91UkZIf.js +34 -0
  239. package/web/assets/wardley-RL74JXVD-BCRCBASE-x42Qw7hp.js +1 -0
  240. package/web/assets/wardleyDiagram-XU3VSMPF-DloBhI0U.js +20 -0
  241. package/web/assets/xychartDiagram-ZHJ5623Y-BGWJvgwI.js +7 -0
  242. package/web/index.html +21 -0
  243. package/scripts/patch-tree-sitter-swift.cjs +0 -78
@@ -24,6 +24,16 @@ import { findClassBindingInScope, findExportedDefByName, findReceiverTypeBinding
24
24
  * Practical code rarely exceeds 3-4 hops; the cap prevents
25
25
  * pathological recursion if the receiver text is malformed. */
26
26
  const COMPOUND_RECEIVER_MAX_DEPTH = 4;
27
+ const MAP_TUPLE_SENTINEL_RE = /^__MAP_TUPLE_(\d+)__:(.+)$/;
28
+ function parseMapTupleSentinel(text) {
29
+ const match = MAP_TUPLE_SENTINEL_RE.exec(text);
30
+ if (match === null)
31
+ return null;
32
+ const [, idxStr, rhs] = match;
33
+ if (idxStr === undefined || rhs === undefined)
34
+ return null;
35
+ return { tupleIdx: Number(idxStr), rhs };
36
+ }
27
37
  export function resolveCompoundReceiverClass(receiverText, inScope, scopes, index, options = {}, depth = 0) {
28
38
  const classScopeByDefId = index.classScopeByDefId;
29
39
  if (depth > COMPOUND_RECEIVER_MAX_DEPTH)
@@ -32,12 +42,57 @@ export function resolveCompoundReceiverClass(receiverText, inScope, scopes, inde
32
42
  if (text.length === 0)
33
43
  return undefined;
34
44
  const fieldFallback = options.fieldFallback ?? true;
35
- // Bare identifier — resolve via typeBinding then class lookup.
45
+ // Bare identifier — resolve via typeBinding first, then fall back to
46
+ // a direct class-name lookup. The class-name fallback handles
47
+ // "static receiver" shapes like `UserService.findUser()` where
48
+ // `UserService` isn't a variable but a class imported into scope.
36
49
  if (!text.includes('.') && !text.includes('(')) {
50
+ const mapTuple = parseMapTupleSentinel(text);
51
+ if (mapTuple !== null) {
52
+ const rhsTb = findReceiverTypeBinding(inScope, mapTuple.rhs, scopes);
53
+ if (rhsTb === undefined)
54
+ return undefined;
55
+ const arg = extractShallowMapTypeArgByIndex(rhsTb.rawName, mapTuple.tupleIdx);
56
+ if (arg === undefined)
57
+ return undefined;
58
+ return findClassBindingInScope(rhsTb.declaredAtScope, arg, scopes);
59
+ }
37
60
  const tb = findReceiverTypeBinding(inScope, text, scopes);
38
- if (tb === undefined)
39
- return undefined;
40
- return findClassBindingInScope(tb.declaredAtScope, tb.rawName, scopes);
61
+ if (tb !== undefined) {
62
+ // Map for-of: binding name is `user` but rawType is
63
+ // `__MAP_TUPLE_i__:entries` (see captures.ts) — same extraction as
64
+ // the literal-sentinel branch above.
65
+ const boundMapTuple = parseMapTupleSentinel(tb.rawName);
66
+ if (boundMapTuple !== null) {
67
+ const rhsTb = findReceiverTypeBinding(inScope, boundMapTuple.rhs, scopes);
68
+ if (rhsTb === undefined)
69
+ return undefined;
70
+ const arg = extractShallowMapTypeArgByIndex(rhsTb.rawName, boundMapTuple.tupleIdx);
71
+ if (arg === undefined)
72
+ return undefined;
73
+ return findClassBindingInScope(rhsTb.declaredAtScope, arg, scopes);
74
+ }
75
+ const viaTb = findClassBindingInScope(tb.declaredAtScope, tb.rawName, scopes);
76
+ if (viaTb !== undefined)
77
+ return viaTb;
78
+ // Member-alias / call-result shapes store the RHS path on rawName
79
+ // (`user.address`, `addr.getCity`) — resolve as a compound chain.
80
+ if (tb.rawName.includes('.') && !tb.rawName.includes('(')) {
81
+ const dotted = resolveCompoundReceiverClass(tb.rawName, inScope, scopes, index, options, depth + 1);
82
+ if (dotted !== undefined)
83
+ return dotted;
84
+ const dottedCall = resolveCompoundReceiverClass(`${tb.rawName}()`, inScope, scopes, index, options, depth + 1);
85
+ if (dottedCall !== undefined)
86
+ return dottedCall;
87
+ }
88
+ // Callable alias (`const user = getUser()` → type rawName `getUser`)
89
+ if (!tb.rawName.includes('.') && !tb.rawName.includes('(')) {
90
+ const callAlias = resolveCompoundReceiverClass(`${tb.rawName}()`, inScope, scopes, index, options, depth + 1);
91
+ if (callAlias !== undefined)
92
+ return callAlias;
93
+ }
94
+ }
95
+ return findClassBindingInScope(inScope, text, scopes);
41
96
  }
42
97
  // Trailing `()` — call expression. Strip it and resolve the function
43
98
  // expression's return type. We only handle the canonical `f()` /
@@ -120,12 +175,33 @@ export function resolveCompoundReceiverClass(receiverText, inScope, scopes, inde
120
175
  }
121
176
  }
122
177
  }
178
+ // `Map<K,V>.values()` / `this.repos.values()` — lib `Map` often has no
179
+ // parsed return-type binding; infer `V` from the receiver field's
180
+ // `Map<…>` annotation when the method is `values`.
181
+ if (retType === undefined && methodName === 'values') {
182
+ const mapVal = resolveMapValueTypeNameFromPrefix(objExpr, inScope, scopes, index, options);
183
+ if (mapVal !== undefined) {
184
+ retType = {
185
+ rawName: mapVal,
186
+ declaredAtScope: inScope,
187
+ source: 'return-annotation',
188
+ };
189
+ }
190
+ }
123
191
  if (retType === undefined)
124
192
  return undefined;
125
193
  return findClassBindingInScope(retType.declaredAtScope, retType.rawName, scopes);
126
194
  }
127
- // Pure dotted access `obj.field[.field]…` — walk fields.
128
- const parts = text.split('.');
195
+ // Mixed dotted + call chain: `obj.field.method().field.method()…`.
196
+ // Split at top-level `.` (those NOT inside balanced `(...)`) so a
197
+ // middle segment like `getUser()` stays intact. Each segment is
198
+ // either a bare identifier `field` OR `method(...)` — the former
199
+ // resolves via the current class's typeBindings (field → type),
200
+ // the latter resolves via the current class's typeBindings
201
+ // (method return-type). We accept both on each hop because class
202
+ // scopes store both method return types and field types under
203
+ // `typeBindings` keyed by the member name.
204
+ const parts = splitChainAtTopLevel(text);
129
205
  // Language-specific collection-accessor suffix (C#'s `data.Values`
130
206
  // on Dictionary<K,V>, etc.). When the provider hook recognizes
131
207
  // the final segment and unwraps the receiver's generic, return
@@ -133,6 +209,9 @@ export function resolveCompoundReceiverClass(receiverText, inScope, scopes, inde
133
209
  // because Dictionary-family types aren't local class defs.
134
210
  if (options.unwrapCollectionAccessor !== undefined && parts.length >= 2) {
135
211
  const last = parts[parts.length - 1];
212
+ const headInner = parts[0];
213
+ if (last === undefined || headInner === undefined)
214
+ return undefined;
136
215
  const prefix = parts.slice(0, -1).join('.');
137
216
  let prefixType;
138
217
  if (parts.length === 2) {
@@ -143,16 +222,18 @@ export function resolveCompoundReceiverClass(receiverText, inScope, scopes, inde
143
222
  // to find its typeRef. We need the TypeRef (not the class def)
144
223
  // because the hook inspects the raw generic args (e.g.
145
224
  // `Dictionary<string, User>`).
146
- const headInner = parts[0];
147
225
  let cur = findReceiverTypeBinding(inScope, headInner, scopes);
148
226
  for (let i = 1; i < parts.length - 1 && cur !== undefined; i++) {
227
+ const segment = parts[i];
228
+ if (segment === undefined)
229
+ break;
149
230
  const cls = findClassBindingInScope(cur.declaredAtScope, cur.rawName, scopes);
150
231
  if (cls === undefined) {
151
232
  cur = undefined;
152
233
  break;
153
234
  }
154
235
  const cs = classScopeByDefId.get(cls.nodeId);
155
- cur = cs?.typeBindings.get(parts[i]);
236
+ cur = cs?.typeBindings.get(segment);
156
237
  }
157
238
  prefixType = cur;
158
239
  }
@@ -164,20 +245,108 @@ export function resolveCompoundReceiverClass(receiverText, inScope, scopes, inde
164
245
  }
165
246
  }
166
247
  const head = parts[0];
167
- const headType = findReceiverTypeBinding(inScope, head, scopes);
248
+ if (head === undefined)
249
+ return undefined;
250
+ const headMemberName = stripCallParens(head);
251
+ const headType = findReceiverTypeBinding(inScope, headMemberName, scopes);
168
252
  let currentClass = headType
169
253
  ? findClassBindingInScope(headType.declaredAtScope, headType.rawName, scopes)
170
- : undefined;
254
+ : findClassBindingInScope(inScope, headMemberName, scopes);
255
+ // `const user = getUser(); user.address` — the typeBinding for `user`
256
+ // is an alias to the callee name (`getUser`), not a class. When
257
+ // `findClassBinding` on that rawName fails, treat it as a zero-arg
258
+ // call so return-type hoisting resolves to the class (`User`).
259
+ if (currentClass === undefined &&
260
+ headType !== undefined &&
261
+ !headType.rawName.includes('.') &&
262
+ !headType.rawName.includes('(')) {
263
+ currentClass = resolveCompoundReceiverClass(`${headType.rawName}()`, inScope, scopes, index, options, depth + 1);
264
+ }
171
265
  for (let i = 1; i < parts.length && currentClass !== undefined; i++) {
172
- const fieldName = parts[i];
266
+ const segment = parts[i];
267
+ if (segment === undefined)
268
+ break;
269
+ const memberName = stripCallParens(segment);
173
270
  const cs = classScopeByDefId.get(currentClass.nodeId);
174
- const fieldType = cs?.typeBindings.get(fieldName);
175
- if (fieldType === undefined)
271
+ let memberType = cs?.typeBindings.get(memberName);
272
+ if (memberType === undefined &&
273
+ options.hoistTypeBindingsToModule === true &&
274
+ cs !== undefined) {
275
+ let curId = cs.parent;
276
+ while (curId !== null) {
277
+ const curScope = scopes.scopeTree.getScope(curId);
278
+ if (curScope === undefined)
279
+ break;
280
+ const cand = curScope.typeBindings.get(memberName);
281
+ if (cand !== undefined) {
282
+ memberType = cand;
283
+ break;
284
+ }
285
+ curId = curScope.parent;
286
+ }
287
+ }
288
+ if (memberType === undefined) {
289
+ // Trailing segment may be a method name without `()` — e.g.
290
+ // `this.repos.values` from a for-of iterable capture. Try the
291
+ // call-shaped resolver before giving up.
292
+ if (!segment.includes('(')) {
293
+ const prefix = parts.slice(0, i).join('.');
294
+ const asCall = resolveCompoundReceiverClass(`${prefix}.${memberName}()`, inScope, scopes, index, options, depth + 1);
295
+ if (asCall !== undefined)
296
+ return asCall;
297
+ }
176
298
  return undefined;
177
- currentClass = findClassBindingInScope(fieldType.declaredAtScope, fieldType.rawName, scopes);
299
+ }
300
+ let nextClass = findClassBindingInScope(memberType.declaredAtScope, memberType.rawName, scopes);
301
+ if (nextClass === undefined) {
302
+ const fromMap = unwrapMapValueToClass(memberType, scopes);
303
+ if (fromMap !== undefined)
304
+ nextClass = fromMap;
305
+ }
306
+ currentClass = nextClass;
178
307
  }
179
308
  return currentClass;
180
309
  }
310
+ /**
311
+ * Split a chain expression like `a.b().c.d()` at top-level `.`
312
+ * separators — i.e. `.` characters NOT nested inside balanced
313
+ * `(...)`, `[...]`, or `<...>` delimiters. Returns the segments in
314
+ * order: `['a', 'b()', 'c', 'd()']`. Malformed input falls back to
315
+ * a plain `split('.')`.
316
+ */
317
+ function splitChainAtTopLevel(text) {
318
+ const out = [];
319
+ let depth = 0;
320
+ let last = 0;
321
+ for (let i = 0; i < text.length; i++) {
322
+ const ch = text[i];
323
+ if (ch === '(' || ch === '[' || ch === '<')
324
+ depth++;
325
+ else if (ch === ')' || ch === ']' || ch === '>')
326
+ depth = Math.max(0, depth - 1);
327
+ else if (ch === '.' && depth === 0) {
328
+ out.push(text.slice(last, i));
329
+ last = i + 1;
330
+ }
331
+ }
332
+ out.push(text.slice(last));
333
+ // Guard against pathological input (`a.` / `.a`) — drop empties.
334
+ return out.filter((s) => s.length > 0);
335
+ }
336
+ /**
337
+ * Strip a trailing `(...)` from a chain segment so typeBinding lookup
338
+ * uses the member name: `'getUser()'` → `'getUser'`. Leaves bare
339
+ * identifiers (`'address'`) unchanged. Arguments inside the parens
340
+ * are discarded — the compound resolver is return-type only.
341
+ */
342
+ function stripCallParens(segment) {
343
+ if (!segment.endsWith(')'))
344
+ return segment;
345
+ const open = segment.indexOf('(');
346
+ if (open === -1)
347
+ return segment;
348
+ return segment.slice(0, open);
349
+ }
181
350
  /** Find the index of the `(` that matches the trailing `)` of a
182
351
  * call-expression text. Returns -1 if unbalanced. */
183
352
  function matchingOpenParen(text) {
@@ -196,3 +365,103 @@ function matchingOpenParen(text) {
196
365
  }
197
366
  return -1;
198
367
  }
368
+ /** Type arguments of a shallow `Map<K,V>` / `ReadonlyMap<K,V>` (depth-aware). */
369
+ function extractShallowMapTypeArgByIndex(mapText, wantIndex) {
370
+ const t = mapText.trim();
371
+ const m = /^(?:ReadonlyMap|Map)\s*</.exec(t);
372
+ if (m === null || m.index !== 0)
373
+ return undefined;
374
+ const openIdx = m[0].length - 1;
375
+ if (t[openIdx] !== '<')
376
+ return undefined;
377
+ let depth = 1;
378
+ const args = [];
379
+ let segStart = openIdx + 1;
380
+ for (let i = openIdx + 1; i < t.length; i++) {
381
+ const ch = t[i];
382
+ if (ch === '<')
383
+ depth++;
384
+ else if (ch === '>') {
385
+ depth--;
386
+ if (depth === 0) {
387
+ const tail = t.slice(segStart, i).trim();
388
+ if (tail.length > 0)
389
+ args.push(tail);
390
+ break;
391
+ }
392
+ }
393
+ else if (ch === ',' && depth === 1) {
394
+ args.push(t.slice(segStart, i).trim());
395
+ segStart = i + 1;
396
+ }
397
+ }
398
+ const picked = args[wantIndex]?.trim();
399
+ return picked !== undefined && picked.length > 0 ? picked : undefined;
400
+ }
401
+ function unwrapMapValueToClass(memberType, scopes) {
402
+ const v = extractShallowMapTypeArgByIndex(memberType.rawName, 1);
403
+ if (v === undefined)
404
+ return undefined;
405
+ return findClassBindingInScope(memberType.declaredAtScope, v, scopes);
406
+ }
407
+ /**
408
+ * Walk `objExpr` as a field chain (`this.repos`) and return the `V`
409
+ * type name from a terminal `Map<K,V>` field binding — used when
410
+ * resolving `.values()` without a parsed stdlib return type.
411
+ */
412
+ function resolveMapValueTypeNameFromPrefix(objExpr, inScope, scopes, index, options) {
413
+ const classScopeByDefId = index.classScopeByDefId;
414
+ const parts = splitChainAtTopLevel(objExpr);
415
+ const head = parts[0];
416
+ if (head === undefined)
417
+ return undefined;
418
+ const headMemberName = stripCallParens(head);
419
+ const headType = findReceiverTypeBinding(inScope, headMemberName, scopes);
420
+ let currentClass = headType
421
+ ? findClassBindingInScope(headType.declaredAtScope, headType.rawName, scopes)
422
+ : findClassBindingInScope(inScope, headMemberName, scopes);
423
+ if (currentClass === undefined &&
424
+ headType !== undefined &&
425
+ !headType.rawName.includes('.') &&
426
+ !headType.rawName.includes('(')) {
427
+ currentClass = resolveCompoundReceiverClass(`${headType.rawName}()`, inScope, scopes, index, options, 1);
428
+ }
429
+ let lastMemberType;
430
+ for (let i = 1; i < parts.length && currentClass !== undefined; i++) {
431
+ const segment = parts[i];
432
+ if (segment === undefined)
433
+ break;
434
+ const memberName = stripCallParens(segment);
435
+ const cs = classScopeByDefId.get(currentClass.nodeId);
436
+ if (cs === undefined)
437
+ return undefined;
438
+ let memberType = cs.typeBindings.get(memberName);
439
+ if (memberType === undefined && options.hoistTypeBindingsToModule === true) {
440
+ let curId = cs.parent;
441
+ while (curId !== null) {
442
+ const curScope = scopes.scopeTree.getScope(curId);
443
+ if (curScope === undefined)
444
+ break;
445
+ const cand = curScope.typeBindings.get(memberName);
446
+ if (cand !== undefined) {
447
+ memberType = cand;
448
+ break;
449
+ }
450
+ curId = curScope.parent;
451
+ }
452
+ }
453
+ if (memberType === undefined)
454
+ return undefined;
455
+ lastMemberType = memberType;
456
+ let nextClass = findClassBindingInScope(memberType.declaredAtScope, memberType.rawName, scopes);
457
+ if (nextClass === undefined) {
458
+ const fromMap = unwrapMapValueToClass(memberType, scopes);
459
+ if (fromMap !== undefined)
460
+ nextClass = fromMap;
461
+ }
462
+ currentClass = nextClass;
463
+ }
464
+ if (lastMemberType === undefined)
465
+ return undefined;
466
+ return extractShallowMapTypeArgByIndex(lastMemberType.rawName, 1);
467
+ }
@@ -14,8 +14,29 @@
14
14
  * populated) but BEFORE `resolveReferenceSites` (so resolution
15
15
  * sees the propagated types).
16
16
  *
17
- * Generic; promoted from `languages/python/scope-resolver.ts` per the scope-resolution
18
- * generalization plan.
17
+ * **Ordering invariant (added 2026-04-24, RFC #909 Ring 3 / PR #1050):**
18
+ * The pass walks files in `indexes.sccs` reverse-topological order
19
+ * (leaves first per `tarjanSccs`). For each importer we chain-follow
20
+ * the source module's typeBindings BEFORE mirroring, so a multi-hop
21
+ * alias chain like
22
+ *
23
+ * models.ts: function getUser(): User
24
+ * service.ts: export const user = getUser() // user → getUser
25
+ * app.ts: import { user } from './service' // user → ?
26
+ *
27
+ * collapses to `app.user → User` in a single pass instead of stopping
28
+ * at the intermediate `getUser` ref. The motivating regression is the
29
+ * `ts-simple` integration fixture (`gitnexus/test/fixtures/scope-
30
+ * resolution/cross-file-binding/ts-simple/`), where `user.save()` and
31
+ * `user.getName()` only resolve when the chain collapse happens
32
+ * topologically.
33
+ *
34
+ * Cyclic SCCs reach a partial fixpoint via the same mirror step but
35
+ * are not guaranteed to fully resolve — see the `ts-circular`
36
+ * fixture, which only asserts pipeline-no-throw.
37
+ *
38
+ * Generic; promoted from `languages/python/scope-resolver.ts` per the
39
+ * scope-resolution generalization plan.
19
40
  */
20
41
  import type { ParsedFile } from '../../../../_shared/index.js';
21
42
  import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
@@ -14,10 +14,39 @@
14
14
  * populated) but BEFORE `resolveReferenceSites` (so resolution
15
15
  * sees the propagated types).
16
16
  *
17
- * Generic; promoted from `languages/python/scope-resolver.ts` per the scope-resolution
18
- * generalization plan.
17
+ * **Ordering invariant (added 2026-04-24, RFC #909 Ring 3 / PR #1050):**
18
+ * The pass walks files in `indexes.sccs` reverse-topological order
19
+ * (leaves first per `tarjanSccs`). For each importer we chain-follow
20
+ * the source module's typeBindings BEFORE mirroring, so a multi-hop
21
+ * alias chain like
22
+ *
23
+ * models.ts: function getUser(): User
24
+ * service.ts: export const user = getUser() // user → getUser
25
+ * app.ts: import { user } from './service' // user → ?
26
+ *
27
+ * collapses to `app.user → User` in a single pass instead of stopping
28
+ * at the intermediate `getUser` ref. The motivating regression is the
29
+ * `ts-simple` integration fixture (`gitnexus/test/fixtures/scope-
30
+ * resolution/cross-file-binding/ts-simple/`), where `user.save()` and
31
+ * `user.getName()` only resolve when the chain collapse happens
32
+ * topologically.
33
+ *
34
+ * Cyclic SCCs reach a partial fixpoint via the same mirror step but
35
+ * are not guaranteed to fully resolve — see the `ts-circular`
36
+ * fixture, which only asserts pipeline-no-throw.
37
+ *
38
+ * Generic; promoted from `languages/python/scope-resolver.ts` per the
39
+ * scope-resolution generalization plan.
40
+ */
41
+ import { lookupBindingsAt, namesAtScope } from '../scope/walkers.js';
42
+ /**
43
+ * Max chain depth for the post-finalize re-follow. Effective end-to-end
44
+ * depth is roughly 2× this number, because chain-following runs once
45
+ * inside each importer's source module before mirroring AND once on
46
+ * the importer's own typeBindings after mirroring; deeply nested
47
+ * intra-module aliases can compose with cross-file aliases of the same
48
+ * depth. 8 covers all production fixtures with headroom.
19
49
  */
20
- /** Max chain depth for the post-finalize re-follow. */
21
50
  const RECHAIN_MAX_DEPTH = 8;
22
51
  /** Walk `ref.rawName` through the scope chain's typeBindings looking
23
52
  * for a terminal class-like rawName. Mirrors the in-extractor
@@ -78,47 +107,90 @@ function followChainPostFinalize(start, fromScopeId, scopes) {
78
107
  */
79
108
  export function propagateImportedReturnTypes(parsedFiles, indexes, index) {
80
109
  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))
110
+ // Walk SCCs in reverse-topological order (`indexes.sccs` is leaves-
111
+ // first per `tarjanSccs`). For each file we mirror import bindings
112
+ // AFTER chain-following the source module's typeBindings, so a
113
+ // multi-hop alias chain like
114
+ // models.ts: function getUser(): User
115
+ // service.ts: export const user = getUser() // user → getUser
116
+ // app.ts: import { user } from './service' // user → ?
117
+ // collapses to `app.user → User` instead of stopping at the
118
+ // intermediate `getUser` ref. Without topological ordering, app.ts
119
+ // could be processed before service.ts had its own typeBindings
120
+ // chain-followed, leaving the importer with an unresolvable interim
121
+ // ref. Cyclic SCCs reach a partial fixpoint via the same mirror
122
+ // step but are not guaranteed to fully resolve — see the
123
+ // ts-circular cross-file-binding fixture which only asserts that
124
+ // the pipeline does not throw.
125
+ for (const scc of indexes.sccs) {
126
+ for (const filePath of scc.files) {
127
+ const importerModule = moduleScopeByFile.get(filePath);
128
+ if (importerModule === undefined)
92
129
  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)
130
+ // Iterate finalized + augmented binding names at this scope so
131
+ // post-finalize hooks (e.g. `using static` augmentations from
132
+ // `populateCsharpNamespaceSiblings`) are visible to the
133
+ // import-derived typeBinding mirror. Both helpers fast-path when
134
+ // no augmentations exist for the scope, so the common case is
135
+ // allocation-free. See I8.
136
+ for (const localName of namesAtScope(importerModule.id, indexes)) {
137
+ // Skip if importer already has a typeBinding for this name —
138
+ // an explicit local annotation must win over import-derived.
139
+ if (importerModule.typeBindings.has(localName))
98
140
  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;
141
+ const refs = lookupBindingsAt(importerModule.id, localName, indexes);
142
+ for (const ref of refs) {
143
+ if (ref.origin !== 'import' && ref.origin !== 'reexport')
144
+ continue;
145
+ const sourceModule = moduleScopeByFile.get(ref.def.filePath);
146
+ if (sourceModule === undefined)
147
+ continue;
148
+ // The source file's typeBinding is keyed by the def's simple
149
+ // name (e.g. `get_user`), not the importer's local alias.
150
+ const qn = ref.def.qualifiedName;
151
+ if (qn === undefined)
152
+ continue;
153
+ const dot = qn.lastIndexOf('.');
154
+ const sourceName = dot === -1 ? qn : qn.slice(dot + 1);
155
+ const sourceTypeRef = sourceModule.typeBindings.get(sourceName);
156
+ if (sourceTypeRef === undefined)
157
+ continue;
158
+ // Chain-follow inside the source module so we mirror the
159
+ // terminal type, not an intermediate intra-source reference.
160
+ const terminal = followChainPostFinalize(sourceTypeRef, sourceModule.id, indexes);
161
+ // Mutating typeBindings is safe because draftToScope
162
+ // produced a non-frozen Map (Contract Invariant I3/I8).
163
+ importerModule.typeBindings.set(localName, terminal);
164
+ // First-write-wins for the local alias: if the same
165
+ // `localName` was registered multiple times via
166
+ // `mergeBindings` (rare; happens with conflicting
167
+ // re-exports), only the first ref with a usable
168
+ // typeBinding source is mirrored. Conflict resolution
169
+ // among multiple sources is the merger's job, not ours.
170
+ break;
171
+ }
172
+ }
173
+ // Chain-follow this importer's own module typeBindings now —
174
+ // any local `const x = importedFn()` resolves while we have
175
+ // freshly-mirrored bindings, and downstream importers in a
176
+ // later (closer-to-root) SCC will see x's terminal type rather
177
+ // than an intra-module call ref.
178
+ for (const [name, ref] of importerModule.typeBindings) {
179
+ const resolved = followChainPostFinalize(ref, importerModule.id, indexes);
180
+ if (resolved !== ref) {
181
+ importerModule.typeBindings.set(name, resolved);
182
+ }
115
183
  }
116
184
  }
117
185
  }
118
- // Re-follow chains across every scope so chains terminating in a
119
- // freshly-propagated import binding resolve to their terminal type.
186
+ // Final pass: chain-follow non-module scopes (function-local
187
+ // typeBindings). Module scopes were already followed inside the
188
+ // SCC loop above.
120
189
  for (const parsed of parsedFiles) {
190
+ const moduleScopeId = moduleScopeByFile.get(parsed.filePath)?.id;
121
191
  for (const scope of parsed.scopes) {
192
+ if (scope.id === moduleScopeId)
193
+ continue;
122
194
  for (const [name, ref] of scope.typeBindings) {
123
195
  const resolved = followChainPostFinalize(ref, scope.id, indexes);
124
196
  if (resolved !== ref) {
@@ -86,8 +86,10 @@ export const defaultLinearize = (_classDefId, directParents, parentsByDefId) =>
86
86
  const ancestors = [];
87
87
  const visited = new Set();
88
88
  const queue = [...directParents];
89
- while (queue.length > 0) {
89
+ for (;;) {
90
90
  const cur = queue.shift();
91
+ if (cur === undefined)
92
+ break;
91
93
  if (visited.has(cur))
92
94
  continue;
93
95
  visited.add(cur);
@@ -234,10 +234,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
234
234
  }
235
235
  }
236
236
  // ── Case 3b: chain-typebinding (`city → user.get_city`) ──────
237
- if (typeRef !== undefined &&
238
- typeRef.rawName.includes('.') &&
239
- !typeRef.rawName.includes('(') &&
240
- !namespaceTargets.has(typeRef.rawName.split('.')[0])) {
237
+ const chainHead = typeRef !== undefined && typeRef.rawName.includes('.') && !typeRef.rawName.includes('(')
238
+ ? (typeRef.rawName.split('.', 1)[0] ?? '')
239
+ : undefined;
240
+ if (typeRef !== undefined && chainHead !== undefined && !namespaceTargets.has(chainHead)) {
241
241
  // Try the plain dotted-field walk first — covers property /
242
242
  // collection-accessor shapes (`.Values`, Kotlin `.size`) and
243
243
  // field chains. Fall back to call-form (`x()`) which treats
@@ -269,7 +269,15 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
269
269
  }
270
270
  // ── Case 4: simple typeBinding (`u: U`) ──────────────────────
271
271
  if (typeRef !== undefined && !typeRef.rawName.includes('.')) {
272
- const ownerDef = findClassBindingInScope(site.inScope, typeRef.rawName, scopes);
272
+ let ownerDef = findClassBindingInScope(site.inScope, typeRef.rawName, scopes);
273
+ // `findClassBindingInScope(..., typeRef.rawName)` only works when
274
+ // rawName is itself a class symbol. Map for-of tuple bindings
275
+ // (`__MAP_TUPLE_i__:mapId`), callable aliases (`getUser` → User),
276
+ // and other compound-friendly shapes need the compound resolver
277
+ // keyed by the receiver identifier.
278
+ if (ownerDef === undefined) {
279
+ ownerDef = resolveCompoundReceiverClass(receiverName, site.inScope, scopes, index, compoundOpts);
280
+ }
273
281
  if (ownerDef !== undefined) {
274
282
  const chain = [ownerDef.nodeId, ...scopes.methodDispatch.mroFor(ownerDef.nodeId)];
275
283
  let memberDef;
@@ -32,7 +32,7 @@ import { getLanguageFromFilename } from '../../../../_shared/index.js';
32
32
  import { readFileContents } from '../../filesystem-walker.js';
33
33
  import { runScopeResolution } from './run.js';
34
34
  import { SCOPE_RESOLVERS } from './registry.js';
35
- import { isDev } from '../../utils/env.js';
35
+ import { isDev, isSemanticModelValidatorEnabled } from '../../utils/env.js';
36
36
  const NOOP_OUTPUT = Object.freeze({
37
37
  ran: false,
38
38
  filesProcessed: 0,
@@ -87,14 +87,23 @@ export const scopeResolutionPhase = {
87
87
  if (content !== undefined)
88
88
  files.push({ path: fp, content });
89
89
  }
90
+ // Load per-language import-resolution config (tsconfig paths,
91
+ // composer.json autoload, go.mod, ...). One I/O round trip per
92
+ // workspace pass — cached implicitly by the result handed to
93
+ // every `resolveImportTarget` call below.
94
+ const resolutionConfig = provider.loadResolutionConfig !== undefined
95
+ ? await provider.loadResolutionConfig(ctx.repoPath)
96
+ : undefined;
90
97
  const stats = runScopeResolution({
91
98
  graph: ctx.graph,
92
99
  model,
93
100
  files,
94
101
  treeCache: scopeTreeCache,
102
+ resolutionConfig,
95
103
  onWarn: (msg) => {
96
- if (isDev)
104
+ if (isSemanticModelValidatorEnabled()) {
97
105
  console.warn(`[scope-resolution:${lang}] ${msg}`);
106
+ }
98
107
  },
99
108
  }, provider);
100
109
  anyRan = true;