gitnexus 1.6.2-rc.9 → 1.6.2

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 (220) hide show
  1. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  2. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  3. package/dist/_shared/lbug/schema-constants.js +1 -0
  4. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  5. package/dist/_shared/mro-strategy.d.ts +38 -16
  6. package/dist/_shared/mro-strategy.d.ts.map +1 -1
  7. package/dist/cli/ai-context.js +0 -58
  8. package/dist/cli/analyze.js +3 -0
  9. package/dist/core/embeddings/ast-utils.d.ts +22 -0
  10. package/dist/core/embeddings/ast-utils.js +105 -0
  11. package/dist/core/embeddings/character-chunk.d.ts +12 -0
  12. package/dist/core/embeddings/character-chunk.js +43 -0
  13. package/dist/core/embeddings/chunker.d.ts +14 -0
  14. package/dist/core/embeddings/chunker.js +234 -0
  15. package/dist/core/embeddings/embedding-pipeline.d.ts +20 -24
  16. package/dist/core/embeddings/embedding-pipeline.js +176 -107
  17. package/dist/core/embeddings/line-index.d.ts +7 -0
  18. package/dist/core/embeddings/line-index.js +42 -0
  19. package/dist/core/embeddings/server-mapping.d.ts +15 -0
  20. package/dist/core/embeddings/server-mapping.js +33 -0
  21. package/dist/core/embeddings/structural-extractor.d.ts +15 -0
  22. package/dist/core/embeddings/structural-extractor.js +58 -0
  23. package/dist/core/embeddings/text-generator.d.ts +20 -13
  24. package/dist/core/embeddings/text-generator.js +151 -119
  25. package/dist/core/embeddings/types.d.ts +81 -3
  26. package/dist/core/embeddings/types.js +105 -3
  27. package/dist/core/group/extractors/http-patterns/node.js +130 -0
  28. package/dist/core/ingestion/call-extractors/configs/c-cpp.d.ts +3 -0
  29. package/dist/core/ingestion/call-extractors/configs/c-cpp.js +8 -0
  30. package/dist/core/ingestion/call-extractors/configs/csharp.d.ts +2 -0
  31. package/dist/core/ingestion/call-extractors/configs/csharp.js +6 -0
  32. package/dist/core/ingestion/call-extractors/configs/dart.d.ts +2 -0
  33. package/dist/core/ingestion/call-extractors/configs/dart.js +5 -0
  34. package/dist/core/ingestion/call-extractors/configs/go.d.ts +2 -0
  35. package/dist/core/ingestion/call-extractors/configs/go.js +5 -0
  36. package/dist/core/ingestion/call-extractors/configs/jvm.d.ts +3 -0
  37. package/dist/core/ingestion/call-extractors/configs/jvm.js +51 -0
  38. package/dist/core/ingestion/call-extractors/configs/php.d.ts +2 -0
  39. package/dist/core/ingestion/call-extractors/configs/php.js +5 -0
  40. package/dist/core/ingestion/call-extractors/configs/python.d.ts +2 -0
  41. package/dist/core/ingestion/call-extractors/configs/python.js +5 -0
  42. package/dist/core/ingestion/call-extractors/configs/ruby.d.ts +2 -0
  43. package/dist/core/ingestion/call-extractors/configs/ruby.js +5 -0
  44. package/dist/core/ingestion/call-extractors/configs/rust.d.ts +2 -0
  45. package/dist/core/ingestion/call-extractors/configs/rust.js +5 -0
  46. package/dist/core/ingestion/call-extractors/configs/swift.d.ts +2 -0
  47. package/dist/core/ingestion/call-extractors/configs/swift.js +5 -0
  48. package/dist/core/ingestion/call-extractors/configs/typescript-javascript.d.ts +3 -0
  49. package/dist/core/ingestion/call-extractors/configs/typescript-javascript.js +8 -0
  50. package/dist/core/ingestion/call-extractors/generic.d.ts +5 -0
  51. package/dist/core/ingestion/call-extractors/generic.js +59 -0
  52. package/dist/core/ingestion/call-processor.d.ts +2 -4
  53. package/dist/core/ingestion/call-processor.js +221 -89
  54. package/dist/core/ingestion/call-routing.d.ts +8 -12
  55. package/dist/core/ingestion/call-routing.js +13 -34
  56. package/dist/core/ingestion/call-types.d.ts +135 -0
  57. package/dist/core/ingestion/call-types.js +2 -0
  58. package/dist/core/ingestion/class-extractors/configs/c-cpp.d.ts +3 -0
  59. package/dist/core/ingestion/class-extractors/configs/c-cpp.js +11 -0
  60. package/dist/core/ingestion/class-extractors/configs/csharp.d.ts +2 -0
  61. package/dist/core/ingestion/class-extractors/configs/csharp.js +21 -0
  62. package/dist/core/ingestion/class-extractors/configs/dart.d.ts +2 -0
  63. package/dist/core/ingestion/class-extractors/configs/dart.js +7 -0
  64. package/dist/core/ingestion/class-extractors/configs/go.d.ts +2 -0
  65. package/dist/core/ingestion/class-extractors/configs/go.js +20 -0
  66. package/dist/core/ingestion/class-extractors/configs/jvm.d.ts +3 -0
  67. package/dist/core/ingestion/class-extractors/configs/jvm.js +35 -0
  68. package/dist/core/ingestion/class-extractors/configs/php.d.ts +2 -0
  69. package/dist/core/ingestion/class-extractors/configs/php.js +7 -0
  70. package/dist/core/ingestion/class-extractors/configs/python.d.ts +2 -0
  71. package/dist/core/ingestion/class-extractors/configs/python.js +7 -0
  72. package/dist/core/ingestion/class-extractors/configs/ruby.d.ts +2 -0
  73. package/dist/core/ingestion/class-extractors/configs/ruby.js +7 -0
  74. package/dist/core/ingestion/class-extractors/configs/rust.d.ts +2 -0
  75. package/dist/core/ingestion/class-extractors/configs/rust.js +7 -0
  76. package/dist/core/ingestion/class-extractors/configs/swift.d.ts +2 -0
  77. package/dist/core/ingestion/class-extractors/configs/swift.js +18 -0
  78. package/dist/core/ingestion/class-extractors/configs/typescript-javascript.d.ts +4 -0
  79. package/dist/core/ingestion/class-extractors/configs/typescript-javascript.js +28 -0
  80. package/dist/core/ingestion/field-types.d.ts +1 -1
  81. package/dist/core/ingestion/heritage-extractors/configs/go.d.ts +13 -0
  82. package/dist/core/ingestion/heritage-extractors/configs/go.js +20 -0
  83. package/dist/core/ingestion/heritage-extractors/configs/ruby.d.ts +18 -0
  84. package/dist/core/ingestion/heritage-extractors/configs/ruby.js +65 -0
  85. package/dist/core/ingestion/heritage-extractors/generic.d.ts +23 -0
  86. package/dist/core/ingestion/heritage-extractors/generic.js +47 -0
  87. package/dist/core/ingestion/heritage-processor.d.ts +9 -0
  88. package/dist/core/ingestion/heritage-processor.js +120 -85
  89. package/dist/core/ingestion/heritage-types.d.ts +73 -0
  90. package/dist/core/ingestion/heritage-types.js +2 -0
  91. package/dist/core/ingestion/import-resolvers/configs/c-cpp.d.ts +7 -0
  92. package/dist/core/ingestion/import-resolvers/configs/c-cpp.js +14 -0
  93. package/dist/core/ingestion/import-resolvers/configs/csharp.d.ts +8 -0
  94. package/dist/core/ingestion/import-resolvers/configs/csharp.js +27 -0
  95. package/dist/core/ingestion/import-resolvers/configs/dart.d.ts +17 -0
  96. package/dist/core/ingestion/import-resolvers/{dart.js → configs/dart.js} +26 -16
  97. package/dist/core/ingestion/import-resolvers/configs/go.d.ts +8 -0
  98. package/dist/core/ingestion/import-resolvers/configs/go.js +26 -0
  99. package/dist/core/ingestion/import-resolvers/configs/jvm.d.ts +13 -0
  100. package/dist/core/ingestion/import-resolvers/configs/jvm.js +68 -0
  101. package/dist/core/ingestion/import-resolvers/configs/php.d.ts +8 -0
  102. package/dist/core/ingestion/import-resolvers/configs/php.js +15 -0
  103. package/dist/core/ingestion/import-resolvers/configs/python.d.ts +12 -0
  104. package/dist/core/ingestion/import-resolvers/configs/python.js +41 -0
  105. package/dist/core/ingestion/import-resolvers/configs/ruby.d.ts +8 -0
  106. package/dist/core/ingestion/import-resolvers/configs/ruby.js +16 -0
  107. package/dist/core/ingestion/import-resolvers/configs/rust.d.ts +8 -0
  108. package/dist/core/ingestion/import-resolvers/configs/rust.js +54 -0
  109. package/dist/core/ingestion/import-resolvers/configs/swift.d.ts +8 -0
  110. package/dist/core/ingestion/import-resolvers/{swift.js → configs/swift.js} +10 -5
  111. package/dist/core/ingestion/import-resolvers/configs/typescript-javascript.d.ts +9 -0
  112. package/dist/core/ingestion/import-resolvers/configs/typescript-javascript.js +23 -0
  113. package/dist/core/ingestion/import-resolvers/csharp.d.ts +4 -5
  114. package/dist/core/ingestion/import-resolvers/csharp.js +4 -20
  115. package/dist/core/ingestion/import-resolvers/go.d.ts +4 -5
  116. package/dist/core/ingestion/import-resolvers/go.js +4 -19
  117. package/dist/core/ingestion/import-resolvers/jvm.d.ts +5 -10
  118. package/dist/core/ingestion/import-resolvers/jvm.js +5 -58
  119. package/dist/core/ingestion/import-resolvers/php.d.ts +4 -5
  120. package/dist/core/ingestion/import-resolvers/php.js +4 -7
  121. package/dist/core/ingestion/import-resolvers/python.d.ts +3 -6
  122. package/dist/core/ingestion/import-resolvers/python.js +3 -18
  123. package/dist/core/ingestion/import-resolvers/resolver-factory.d.ts +24 -0
  124. package/dist/core/ingestion/import-resolvers/resolver-factory.js +33 -0
  125. package/dist/core/ingestion/import-resolvers/ruby.d.ts +4 -5
  126. package/dist/core/ingestion/import-resolvers/ruby.js +4 -7
  127. package/dist/core/ingestion/import-resolvers/rust.d.ts +4 -5
  128. package/dist/core/ingestion/import-resolvers/rust.js +4 -47
  129. package/dist/core/ingestion/import-resolvers/standard.d.ts +3 -9
  130. package/dist/core/ingestion/import-resolvers/standard.js +7 -8
  131. package/dist/core/ingestion/import-resolvers/types.d.ts +24 -0
  132. package/dist/core/ingestion/language-provider.d.ts +80 -0
  133. package/dist/core/ingestion/languages/c-cpp.js +18 -12
  134. package/dist/core/ingestion/languages/csharp.js +13 -21
  135. package/dist/core/ingestion/languages/dart.js +13 -7
  136. package/dist/core/ingestion/languages/go.js +14 -20
  137. package/dist/core/ingestion/languages/java.js +13 -18
  138. package/dist/core/ingestion/languages/kotlin.js +13 -13
  139. package/dist/core/ingestion/languages/php.js +13 -7
  140. package/dist/core/ingestion/languages/python.js +13 -7
  141. package/dist/core/ingestion/languages/ruby.js +103 -22
  142. package/dist/core/ingestion/languages/rust.js +13 -7
  143. package/dist/core/ingestion/languages/swift.js +13 -18
  144. package/dist/core/ingestion/languages/typescript.js +18 -23
  145. package/dist/core/ingestion/languages/vue.js +13 -17
  146. package/dist/core/ingestion/model/heritage-map.d.ts +35 -0
  147. package/dist/core/ingestion/model/heritage-map.js +110 -9
  148. package/dist/core/ingestion/model/index.d.ts +2 -2
  149. package/dist/core/ingestion/model/index.js +1 -1
  150. package/dist/core/ingestion/model/resolve.d.ts +33 -28
  151. package/dist/core/ingestion/model/resolve.js +111 -27
  152. package/dist/core/ingestion/parsing-processor.d.ts +1 -2
  153. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +1 -0
  154. package/dist/core/ingestion/pipeline-phases/parse-impl.js +9 -3
  155. package/dist/core/ingestion/pipeline-phases/parse.d.ts +7 -0
  156. package/dist/core/ingestion/pipeline.d.ts +11 -0
  157. package/dist/core/ingestion/pipeline.js +9 -2
  158. package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -11
  159. package/dist/core/ingestion/tree-sitter-queries.js +81 -0
  160. package/dist/core/ingestion/type-env.d.ts +1 -1
  161. package/dist/core/ingestion/utils/ast-helpers.d.ts +1 -1
  162. package/dist/core/ingestion/utils/ast-helpers.js +22 -2
  163. package/dist/core/ingestion/utils/ruby-self-call.d.ts +52 -0
  164. package/dist/core/ingestion/utils/ruby-self-call.js +59 -0
  165. package/dist/core/ingestion/variable-extractors/configs/c-cpp.d.ts +3 -0
  166. package/dist/core/ingestion/variable-extractors/configs/c-cpp.js +81 -0
  167. package/dist/core/ingestion/variable-extractors/configs/csharp.d.ts +9 -0
  168. package/dist/core/ingestion/variable-extractors/configs/csharp.js +63 -0
  169. package/dist/core/ingestion/variable-extractors/configs/dart.d.ts +2 -0
  170. package/dist/core/ingestion/variable-extractors/configs/dart.js +94 -0
  171. package/dist/core/ingestion/variable-extractors/configs/go.d.ts +2 -0
  172. package/dist/core/ingestion/variable-extractors/configs/go.js +83 -0
  173. package/dist/core/ingestion/variable-extractors/configs/jvm.d.ts +18 -0
  174. package/dist/core/ingestion/variable-extractors/configs/jvm.js +115 -0
  175. package/dist/core/ingestion/variable-extractors/configs/php.d.ts +14 -0
  176. package/dist/core/ingestion/variable-extractors/configs/php.js +58 -0
  177. package/dist/core/ingestion/variable-extractors/configs/python.d.ts +2 -0
  178. package/dist/core/ingestion/variable-extractors/configs/python.js +101 -0
  179. package/dist/core/ingestion/variable-extractors/configs/ruby.d.ts +11 -0
  180. package/dist/core/ingestion/variable-extractors/configs/ruby.js +52 -0
  181. package/dist/core/ingestion/variable-extractors/configs/rust.d.ts +2 -0
  182. package/dist/core/ingestion/variable-extractors/configs/rust.js +76 -0
  183. package/dist/core/ingestion/variable-extractors/configs/swift.d.ts +2 -0
  184. package/dist/core/ingestion/variable-extractors/configs/swift.js +88 -0
  185. package/dist/core/ingestion/variable-extractors/configs/typescript-javascript.d.ts +3 -0
  186. package/dist/core/ingestion/variable-extractors/configs/typescript-javascript.js +83 -0
  187. package/dist/core/ingestion/variable-extractors/generic.d.ts +5 -0
  188. package/dist/core/ingestion/variable-extractors/generic.js +80 -0
  189. package/dist/core/ingestion/variable-types.d.ts +82 -0
  190. package/dist/core/ingestion/variable-types.js +2 -0
  191. package/dist/core/ingestion/workers/parse-worker.js +244 -217
  192. package/dist/core/ingestion/workers/worker-pool.js +3 -0
  193. package/dist/core/lbug/csv-generator.js +1 -0
  194. package/dist/core/lbug/lbug-adapter.d.ts +4 -5
  195. package/dist/core/lbug/lbug-adapter.js +38 -14
  196. package/dist/core/lbug/schema.d.ts +2 -1
  197. package/dist/core/lbug/schema.js +10 -1
  198. package/dist/core/run-analyze.js +6 -7
  199. package/dist/core/tree-sitter/parser-loader.d.ts +3 -0
  200. package/dist/core/tree-sitter/parser-loader.js +17 -8
  201. package/dist/mcp/local/local-backend.js +29 -19
  202. package/dist/server/api.js +2 -0
  203. package/dist/types/pipeline.d.ts +6 -0
  204. package/package.json +8 -7
  205. package/scripts/build-tree-sitter-proto.cjs +82 -0
  206. package/vendor/node_modules/node-addon-api/node_addon_api.Makefile +6 -0
  207. package/vendor/node_modules/node-addon-api/node_addon_api.target.mk +104 -0
  208. package/vendor/node_modules/node-addon-api/node_addon_api_except.target.mk +108 -0
  209. package/vendor/node_modules/node-addon-api/node_addon_api_except_all.target.mk +104 -0
  210. package/vendor/node_modules/node-addon-api/node_addon_api_maybe.target.mk +104 -0
  211. package/vendor/tree-sitter-proto/package.json +1 -7
  212. package/dist/core/ingestion/call-sites/extract-language-call-site.d.ts +0 -10
  213. package/dist/core/ingestion/call-sites/extract-language-call-site.js +0 -22
  214. package/dist/core/ingestion/call-sites/java.d.ts +0 -9
  215. package/dist/core/ingestion/call-sites/java.js +0 -30
  216. package/dist/core/ingestion/import-resolvers/dart.d.ts +0 -7
  217. package/dist/core/ingestion/import-resolvers/swift.d.ts +0 -7
  218. package/dist/core/ingestion/import-resolvers/vue.d.ts +0 -8
  219. package/dist/core/ingestion/import-resolvers/vue.js +0 -9
  220. package/scripts/preinstall-cleanup.cjs +0 -34
@@ -1,4 +1,25 @@
1
- import { CLASS_TYPES, CALL_TARGET_TYPES } from './model/symbol-table.js';
1
+ import { CLASS_TYPES, CALL_TARGET_TYPES, lookupMethodByOwnerWithMRO } from './model/index.js';
2
+ /**
3
+ * DAG stage 4 fallback: used when `selectDispatch` is absent or returns null.
4
+ * Preserves pre-DAG dispatch semantics:
5
+ * - 'constructor' → constructor branch
6
+ * - 'free' → free branch (admits Swift/Kotlin class-target fast path)
7
+ * - 'member' or undefined → owner-scoped branch
8
+ *
9
+ * `undefined` callForm MUST route through owner-scoped (not free) so bare
10
+ * identifiers without a classified shape do NOT trigger `resolveFreeCall`'s
11
+ * class-target fast path. Without a `receiverTypeName`, the owner-scoped
12
+ * branch falls through to `resolveModuleAliasedCall` + `singleCandidate`,
13
+ * matching legacy behavior where non-callable symbols (Class, Interface)
14
+ * null-route instead of producing spurious Constructor edges.
15
+ */
16
+ const defaultDispatchDecision = (callForm) => {
17
+ if (callForm === 'constructor')
18
+ return { primary: 'constructor' };
19
+ if (callForm === 'free')
20
+ return { primary: 'free' };
21
+ return { primary: 'owner-scoped' };
22
+ };
2
23
  import Parser from 'tree-sitter';
3
24
  import { TIER_CONFIDENCE } from './model/resolution-context.js';
4
25
  import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
@@ -15,8 +36,6 @@ import { getTreeSitterBufferSize } from './constants.js';
15
36
  import { normalizeFetchURL, routeMatches } from './route-extractors/nextjs.js';
16
37
  import { extractTemplateComponents } from './vue-sfc-extractor.js';
17
38
  import { extractReturnTypeName, stripNullable } from './type-extractors/shared.js';
18
- import { extractParsedCallSite } from './call-sites/extract-language-call-site.js';
19
- import { lookupMethodByOwnerWithMRO } from './model/resolve.js';
20
39
  /**
21
40
  * Type labels treated as class-like **method-dispatch receivers** by the call
22
41
  * resolver — the set walked by the MRO / heritage path for member and static
@@ -611,23 +630,27 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
611
630
  // Extract heritage from query matches to build parentMap for buildTypeEnv.
612
631
  // Heritage-processor runs in PARALLEL, so graph edges don't exist when buildTypeEnv runs.
613
632
  const fileParentMap = new Map();
614
- for (const match of matches) {
615
- const captureMap = {};
616
- match.captures.forEach((c) => (captureMap[c.name] = c.node));
617
- if (captureMap['heritage.class'] && captureMap['heritage.extends']) {
618
- const className = captureMap['heritage.class'].text;
619
- const parentName = captureMap['heritage.extends'].text;
620
- const extendsNode = captureMap['heritage.extends'];
621
- const fieldDecl = extendsNode.parent;
622
- if (fieldDecl?.type === 'field_declaration' && fieldDecl.childForFieldName('name'))
623
- continue;
624
- let parents = fileParentMap.get(className);
625
- if (!parents) {
626
- parents = [];
627
- fileParentMap.set(className, parents);
633
+ if (provider.heritageExtractor) {
634
+ for (const match of matches) {
635
+ const captureMap = {};
636
+ match.captures.forEach((c) => (captureMap[c.name] = c.node));
637
+ if (captureMap['heritage.class']) {
638
+ const heritageItems = provider.heritageExtractor.extract(captureMap, {
639
+ filePath: file.path,
640
+ language,
641
+ });
642
+ for (const item of heritageItems) {
643
+ if (item.kind === 'extends') {
644
+ let parents = fileParentMap.get(item.className);
645
+ if (!parents) {
646
+ parents = [];
647
+ fileParentMap.set(item.className, parents);
648
+ }
649
+ if (!parents.includes(item.parentName))
650
+ parents.push(item.parentName);
651
+ }
652
+ }
628
653
  }
629
- if (!parents.includes(parentName))
630
- parents.push(parentName);
631
654
  }
632
655
  }
633
656
  const parentMap = fileParentMap;
@@ -733,73 +756,84 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
733
756
  if (!captureMap['call'])
734
757
  return;
735
758
  const callNode = captureMap['call'];
736
- const languageSeed = extractParsedCallSite(language, callNode);
737
- if (languageSeed) {
738
- if (provider.isBuiltInName(languageSeed.calledName))
739
- return;
740
- const sourceId = findEnclosingFunction(callNode, file.path, ctx, provider) ||
741
- generateId('File', file.path);
742
- const receiverName = languageSeed.callForm === 'member' ? languageSeed.receiverName : undefined;
743
- let receiverTypeName = receiverName && typeEnv ? typeEnv.lookup(receiverName, callNode) : undefined;
744
- if (receiverName !== undefined &&
745
- receiverTypeName === undefined &&
746
- languageSeed.callForm === 'member' &&
747
- (language === 'java' || language === 'csharp' || language === 'kotlin')) {
748
- const c0 = receiverName.charCodeAt(0);
749
- if (c0 >= 65 && c0 <= 90)
750
- receiverTypeName = receiverName;
751
- }
752
- const resolved = resolveCallTarget({
753
- calledName: languageSeed.calledName,
754
- callForm: languageSeed.callForm,
755
- ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
756
- ...(receiverName !== undefined ? { receiverName } : {}),
757
- }, file.path, ctx, undefined, widenCache, undefined, heritageMap);
758
- if (!resolved)
759
- return;
760
- graph.addRelationship({
761
- id: generateId('CALLS', `${sourceId}:${languageSeed.calledName}->${resolved.nodeId}`),
762
- sourceId,
763
- targetId: resolved.nodeId,
764
- type: 'CALLS',
765
- confidence: resolved.confidence,
766
- reason: resolved.reason,
767
- });
768
- if (heritageMap && languageSeed.callForm === 'member' && receiverTypeName) {
769
- const implTargets = findInterfaceDispatchTargets(languageSeed.calledName, receiverTypeName, file.path, ctx, heritageMap, resolved.nodeId);
770
- for (const impl of implTargets) {
771
- graph.addRelationship({
772
- id: generateId('CALLS', `${sourceId}:${languageSeed.calledName}->${impl.nodeId}`),
773
- sourceId,
774
- targetId: impl.nodeId,
775
- type: 'CALLS',
776
- confidence: impl.confidence,
777
- reason: impl.reason,
778
- });
759
+ const callExtractor = provider.callExtractor;
760
+ // ── Language-specific call site (e.g. Java :: method references) ──
761
+ if (callExtractor) {
762
+ const langCallSite = callExtractor.extract(callNode, undefined);
763
+ if (langCallSite) {
764
+ if (provider.isBuiltInName(langCallSite.calledName))
765
+ return;
766
+ const sourceId = findEnclosingFunction(callNode, file.path, ctx, provider) ||
767
+ generateId('File', file.path);
768
+ const receiverName = langCallSite.callForm === 'member' ? langCallSite.receiverName : undefined;
769
+ let receiverTypeName = receiverName && typeEnv ? typeEnv.lookup(receiverName, callNode) : undefined;
770
+ if (langCallSite.typeAsReceiverHeuristic &&
771
+ receiverName !== undefined &&
772
+ receiverTypeName === undefined &&
773
+ langCallSite.callForm === 'member') {
774
+ const c0 = receiverName.charCodeAt(0);
775
+ if (c0 >= 65 && c0 <= 90)
776
+ receiverTypeName = receiverName;
777
+ }
778
+ const resolved = resolveCallTarget({
779
+ calledName: langCallSite.calledName,
780
+ callForm: langCallSite.callForm,
781
+ ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
782
+ ...(receiverName !== undefined ? { receiverName } : {}),
783
+ }, file.path, ctx, undefined, widenCache, undefined, heritageMap);
784
+ if (!resolved)
785
+ return;
786
+ graph.addRelationship({
787
+ id: generateId('CALLS', `${sourceId}:${langCallSite.calledName}->${resolved.nodeId}`),
788
+ sourceId,
789
+ targetId: resolved.nodeId,
790
+ type: 'CALLS',
791
+ confidence: resolved.confidence,
792
+ reason: resolved.reason,
793
+ });
794
+ if (heritageMap && langCallSite.callForm === 'member' && receiverTypeName) {
795
+ const implTargets = findInterfaceDispatchTargets(langCallSite.calledName, receiverTypeName, file.path, ctx, heritageMap, resolved.nodeId);
796
+ for (const impl of implTargets) {
797
+ graph.addRelationship({
798
+ id: generateId('CALLS', `${sourceId}:${langCallSite.calledName}->${impl.nodeId}`),
799
+ sourceId,
800
+ targetId: impl.nodeId,
801
+ type: 'CALLS',
802
+ confidence: impl.confidence,
803
+ reason: impl.reason,
804
+ });
805
+ }
779
806
  }
807
+ return;
780
808
  }
781
- return;
782
809
  }
783
810
  const nameNode = captureMap['call.name'];
784
811
  if (!nameNode)
785
812
  return;
786
813
  const calledName = nameNode.text;
814
+ // Check heritage extractor for call-based heritage (e.g., Ruby include/extend/prepend)
815
+ if (provider.heritageExtractor?.extractFromCall) {
816
+ const heritageItems = provider.heritageExtractor.extractFromCall(calledName, captureMap['call'], { filePath: file.path, language });
817
+ if (heritageItems !== null) {
818
+ for (const item of heritageItems) {
819
+ collectedHeritage.push({
820
+ filePath: file.path,
821
+ className: item.className,
822
+ parentName: item.parentName,
823
+ kind: item.kind,
824
+ });
825
+ }
826
+ return;
827
+ }
828
+ }
829
+ // Dispatch: route language-specific calls (properties, imports)
830
+ // Heritage routing is handled by heritageExtractor.extractFromCall above.
787
831
  const routed = callRouter?.(calledName, captureMap['call']);
788
832
  if (routed) {
789
833
  switch (routed.kind) {
790
834
  case 'skip':
791
835
  case 'import':
792
836
  return;
793
- case 'heritage':
794
- for (const item of routed.items) {
795
- collectedHeritage.push({
796
- filePath: file.path,
797
- className: item.enclosingClass,
798
- parentName: item.mixinName,
799
- kind: item.heritageKind,
800
- });
801
- }
802
- return;
803
837
  case 'properties': {
804
838
  const fileId = generateId('File', file.path);
805
839
  const propEnclosingClassId = findEnclosingClassId(captureMap['call'], file.path);
@@ -850,9 +884,16 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
850
884
  }
851
885
  if (provider.isBuiltInName(calledName))
852
886
  return;
853
- const callForm = inferCallForm(callNode, nameNode);
854
- const receiverName = callForm === 'member' ? extractReceiverName(nameNode) : undefined;
887
+ // --- DAG stage 2-3: classify-form + infer-receiver (shared defaults) ---
888
+ // These stages run the shared inference chain. Language providers can
889
+ // customize infer-receiver (stage 3) via the inferImplicitReceiver hook
890
+ // which runs AFTER this default chain (typed-binding → constructor-map →
891
+ // module-alias → class-as-receiver → mixed-chain), and selectDispatch
892
+ // (stage 4) which picks the resolver branch.
893
+ let callForm = inferCallForm(callNode, nameNode);
894
+ let receiverName = callForm === 'member' ? extractReceiverName(nameNode) : undefined;
855
895
  let receiverTypeName = receiverName && typeEnv ? typeEnv.lookup(receiverName, callNode) : undefined;
896
+ let receiverSource = receiverTypeName ? 'typed-binding' : 'none';
856
897
  // Phase P: virtual dispatch override — when the declared type is a base class but
857
898
  // the constructor created a known subclass, prefer the more specific type.
858
899
  // Checks per-file parentMap first, then falls back to globalParentMap for
@@ -888,6 +929,7 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
888
929
  (ctx.model.types.lookupClassByName(ctorType).length > 0 &&
889
930
  ctx.model.types.lookupClassByName(receiverTypeName).length > 0)) {
890
931
  receiverTypeName = ctorType;
932
+ receiverSource = 'constructor-map';
891
933
  }
892
934
  }
893
935
  }
@@ -896,18 +938,25 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
896
938
  const enclosingFunc = findEnclosingFunction(callNode, file.path, ctx, provider);
897
939
  const funcName = enclosingFunc ? extractFuncNameFromSourceId(enclosingFunc) : '';
898
940
  receiverTypeName = lookupReceiverType(receiverIndex, funcName, receiverName);
941
+ if (receiverTypeName)
942
+ receiverSource = 'constructor-map';
899
943
  }
900
- // Fall back to class-as-receiver for static method calls (e.g. UserService.find_user()).
901
- // When the receiver name is not a variable in TypeEnv but resolves to a Class/Struct/Interface
902
- // through the standard tiered resolution, use it directly as the receiver type.
944
+ // Fall back to class-as-receiver for static method calls (e.g. UserService.find_user(),
945
+ // Greetable.format()). When the receiver name is not a variable in TypeEnv but
946
+ // resolves to a class-like symbol (Class / Interface / Struct / Enum / Trait) via
947
+ // tiered resolution, use it directly as the receiver type. `Trait` is included so
948
+ // Ruby module class-method calls flow through the class-as-receiver path and reach
949
+ // the `selectDispatch` hook's singleton branch.
903
950
  if (!receiverTypeName && receiverName && callForm === 'member') {
904
951
  const typeResolved = ctx.resolve(receiverName, file.path);
905
952
  if (typeResolved &&
906
953
  typeResolved.candidates.some((d) => d.type === 'Class' ||
907
954
  d.type === 'Interface' ||
908
955
  d.type === 'Struct' ||
909
- d.type === 'Enum')) {
956
+ d.type === 'Enum' ||
957
+ d.type === 'Trait')) {
910
958
  receiverTypeName = receiverName;
959
+ receiverSource = 'class-as-receiver';
911
960
  }
912
961
  }
913
962
  // Hoist sourceId so it's available for ACCESSES edge emission during chain walk.
@@ -939,10 +988,48 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
939
988
  }
940
989
  if (currentType) {
941
990
  receiverTypeName = walkMixedChain(extracted.chain, currentType, file.path, ctx, makeAccessEmitter(graph, sourceId), heritageMap);
991
+ if (receiverTypeName)
992
+ receiverSource = 'mixed-chain';
942
993
  }
943
994
  }
944
995
  }
945
996
  }
997
+ // --- DAG stage 3: infer-receiver (provider hook) ---
998
+ // Synthesize implicit receivers for languages that omit them (e.g., Ruby bare-call).
999
+ // This hook runs AFTER the shared inference chain so explicit receivers /
1000
+ // typed bindings always take precedence. Output (if non-null) overlays onto
1001
+ // the ReceiverEnriched for the next stage.
1002
+ let dispatchHint;
1003
+ if (provider.inferImplicitReceiver) {
1004
+ const override = provider.inferImplicitReceiver({
1005
+ calledName,
1006
+ callForm,
1007
+ receiverName,
1008
+ receiverTypeName,
1009
+ callNode,
1010
+ filePath: file.path,
1011
+ });
1012
+ if (override) {
1013
+ callForm = override.callForm;
1014
+ receiverName = override.receiverName;
1015
+ receiverTypeName = override.receiverTypeName;
1016
+ receiverSource = override.receiverSource;
1017
+ dispatchHint = override.hint;
1018
+ }
1019
+ }
1020
+ // --- DAG stage 4: select-dispatch (provider hook + default fallback) ---
1021
+ // Decide which resolver path to try first (primary) and fallback strategy.
1022
+ // Language providers can customize dispatch via selectDispatch hook; all
1023
+ // others use the shared defaultDispatchDecision. Always non-null after this
1024
+ // block so downstream resolvers are table-driven.
1025
+ const dispatchDecision = provider.selectDispatch?.({
1026
+ calledName,
1027
+ callForm,
1028
+ receiverName,
1029
+ receiverTypeName,
1030
+ receiverSource,
1031
+ hint: dispatchHint,
1032
+ }) ?? defaultDispatchDecision(callForm);
946
1033
  // Build overload hints for languages with inferLiteralType (Java/Kotlin/C#/C++).
947
1034
  // Only used when multiple candidates survive arity filtering — ~1-3% of calls.
948
1035
  const langConfig = provider.typeConfig;
@@ -955,7 +1042,7 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
955
1042
  callForm,
956
1043
  receiverTypeName,
957
1044
  receiverName,
958
- }, file.path, ctx, hints, widenCache, undefined, heritageMap);
1045
+ }, file.path, ctx, hints, widenCache, undefined, heritageMap, dispatchDecision);
959
1046
  if (!resolved)
960
1047
  return;
961
1048
  const relId = generateId('CALLS', `${sourceId}:${calledName}->${resolved.nodeId}`);
@@ -1357,26 +1444,45 @@ const singleCandidate = (tiered, argCount, callForm) => {
1357
1444
  };
1358
1445
  /** @internal Exported for unit tests. Do not use outside tests. */
1359
1446
  export const _resolveCallTargetForTesting = (call, currentFile, ctx, opts) => resolveCallTarget(call, currentFile, ctx, opts?.overloadHints, opts?.widenCache, opts?.preComputedArgTypes, opts?.heritageMap);
1360
- const resolveCallTarget = (call, currentFile, ctx, overloadHints, widenCache, preComputedArgTypes, heritageMap) => {
1447
+ const resolveCallTarget = (call, currentFile, ctx, overloadHints, widenCache, preComputedArgTypes, heritageMap, dispatchDecision) => {
1361
1448
  const tiered = ctx.resolve(call.calledName, currentFile);
1362
1449
  if (!tiered)
1363
1450
  return null;
1364
- if (call.callForm === 'free') {
1451
+ // DAG dispatch: use decision.primary to pick the resolver branch.
1452
+ // Callers that own the DAG (processCalls + crossFile deferred paths)
1453
+ // pass a decision; other callers use the shared default ladder.
1454
+ // Language-specific primary / fallback / ancestryView overrides come from
1455
+ // the provider's `selectDispatch` hook.
1456
+ const decision = dispatchDecision ?? defaultDispatchDecision(call.callForm);
1457
+ const primary = decision.primary;
1458
+ if (primary === 'free') {
1365
1459
  return resolveFreeCall(call.calledName, currentFile, ctx, call.argCount, tiered, overloadHints, preComputedArgTypes);
1366
1460
  }
1367
- if (call.callForm === 'constructor') {
1461
+ if (primary === 'constructor') {
1368
1462
  return (resolveStaticCall(call.calledName, currentFile, ctx, call.argCount, tiered, overloadHints, preComputedArgTypes) ?? singleCandidate(tiered, call.argCount, 'constructor'));
1369
1463
  }
1464
+ // primary === 'owner-scoped'
1370
1465
  if (call.receiverTypeName) {
1371
1466
  // Skip the owner-scoped MRO path when the tiered pool has genuine
1372
1467
  // overload ambiguity that needs D1-D4+E handling, not D0.
1373
1468
  const skipMember = (!!overloadHints || !!preComputedArgTypes) &&
1374
1469
  countCallableCandidates(tiered.candidates, call.argCount, call.callForm) > 1;
1375
1470
  // Try owner-scoped (resolveMemberCall) then file-scoped (resolveMemberCallByFile).
1471
+ // DAG: dispatchDecision.ancestryView selects instance vs singleton ancestry
1472
+ // for kind-aware MRO strategies. Ruby `Account.log` flows via 'singleton'.
1473
+ //
1474
+ // Singleton-ancestry miss MUST NOT degrade to the file-scoped fallback:
1475
+ // resolveMemberCallByFile matches by ownerId and would happily pick an
1476
+ // instance method defined on the same class, leaking instance dispatch
1477
+ // onto what was declared a class-method call. For singleton dispatch,
1478
+ // a miss either null-routes or falls through to `decision.fallback`.
1479
+ const singletonDispatch = decision.ancestryView === 'singleton';
1376
1480
  const memberResult = (!skipMember
1377
- ? resolveMemberCall(call.receiverTypeName, call.calledName, currentFile, ctx, heritageMap, call.argCount)
1481
+ ? resolveMemberCall(call.receiverTypeName, call.calledName, currentFile, ctx, heritageMap, call.argCount, decision.ancestryView)
1378
1482
  : null) ??
1379
- resolveMemberCallByFile(call.calledName, call.receiverTypeName, currentFile, ctx, call.argCount, call.callForm, overloadHints, preComputedArgTypes);
1483
+ (singletonDispatch
1484
+ ? null
1485
+ : resolveMemberCallByFile(call.calledName, call.receiverTypeName, currentFile, ctx, call.argCount, call.callForm, overloadHints, preComputedArgTypes));
1380
1486
  if (memberResult)
1381
1487
  return memberResult;
1382
1488
  // Module-alias narrowing runs as a FALLBACK, after owner/file-scoped
@@ -1409,7 +1515,19 @@ const resolveCallTarget = (call, currentFile, ctx, overloadHints, widenCache, pr
1409
1515
  // hierarchy. When the type is NOT in the index (PHP `mixed`, dynamic
1410
1516
  // types, unresolvable aliases), the scoped resolvers had nothing to
1411
1517
  // work with and singleCandidate is the correct last resort.
1518
+ //
1519
+ // DAG fallback override: when `select-dispatch` returned
1520
+ // `fallback: 'free-arity-narrowed'` (today: Ruby implicit-self bare
1521
+ // calls whose enclosing class doesn't define the method), fall through
1522
+ // to free-call resolution instead of null-routing. This preserves
1523
+ // existing free-call arity-narrowing heuristics for bare calls that
1524
+ // happen to target methods on unrelated classes.
1412
1525
  if (typeResolves && typeResolves.candidates.length > 0) {
1526
+ if (decision.fallback === 'free-arity-narrowed') {
1527
+ const free = resolveFreeCall(call.calledName, currentFile, ctx, call.argCount, tiered, overloadHints, preComputedArgTypes);
1528
+ if (free)
1529
+ return free;
1530
+ }
1413
1531
  return null; // null-route: type resolved, no candidate matched
1414
1532
  }
1415
1533
  return singleCandidate(tiered, call.argCount, call.callForm);
@@ -1565,7 +1683,14 @@ const resolveFieldOwnership = (receiverName, fieldName, filePath, ctx) => {
1565
1683
  * Threaded out here so callers don't need a second `ctx.resolve(ownerType, ...)` call —
1566
1684
  * this decouples callers from `ctx.resolve`'s per-file caching contract.
1567
1685
  */
1568
- const resolveMethodByOwner = (receiverTypeName, methodName, filePath, ctx, heritageMap, argCount) => {
1686
+ const resolveMethodByOwner = (receiverTypeName, methodName, filePath, ctx, heritageMap, argCount,
1687
+ /**
1688
+ * DAG-sourced ancestry selector. `'singleton'` routes through
1689
+ * `heritageMap.getSingletonAncestry(owner)` for class-method dispatch
1690
+ * (Ruby `Account.log` via `extend LoggerMixin`). Default / undefined
1691
+ * uses the walker's instance-dispatch behavior.
1692
+ */
1693
+ ancestryView) => {
1569
1694
  const typeResolved = ctx.resolve(receiverTypeName, filePath);
1570
1695
  if (!typeResolved)
1571
1696
  return undefined;
@@ -1593,8 +1718,15 @@ const resolveMethodByOwner = (receiverTypeName, methodName, filePath, ctx, herit
1593
1718
  for (const candidate of typeResolved.candidates) {
1594
1719
  if (!CLASS_LIKE_TYPES.has(candidate.type))
1595
1720
  continue;
1721
+ // Singleton dispatch: when the DAG decision requested the singleton
1722
+ // ancestry view, pass `heritageMap.getSingletonAncestry` as the walker's
1723
+ // ancestry override. Kind-aware strategies (e.g. MroStrategy 'ruby-mixin')
1724
+ // honor the override by scanning it linearly in place of their default walk.
1725
+ const singletonOverride = ancestryView === 'singleton' && canWalkMRO && heritageMap
1726
+ ? heritageMap.getSingletonAncestry(candidate.nodeId).map((e) => e.parentId)
1727
+ : undefined;
1596
1728
  const def = canWalkMRO
1597
- ? lookupMethodByOwnerWithMRO(candidate.nodeId, methodName, heritageMap, ctx.model, mroStrategy, argCount)
1729
+ ? lookupMethodByOwnerWithMRO(candidate.nodeId, methodName, heritageMap, ctx.model, mroStrategy, argCount, singletonOverride)
1598
1730
  : ctx.model.methods.lookupMethodByOwner(candidate.nodeId, methodName, argCount);
1599
1731
  if (!def)
1600
1732
  continue;
@@ -1641,8 +1773,8 @@ const resolveMethodByOwner = (receiverTypeName, methodName, filePath, ctx, herit
1641
1773
  * @param ctx - Resolution context
1642
1774
  * @param heritageMap - Optional heritage map for MRO-aware ancestor walking
1643
1775
  */
1644
- export const resolveMemberCall = (ownerType, methodName, currentFile, ctx, heritageMap, argCount) => {
1645
- const resolved = resolveMethodByOwner(ownerType, methodName, currentFile, ctx, heritageMap, argCount);
1776
+ export const resolveMemberCall = (ownerType, methodName, currentFile, ctx, heritageMap, argCount, ancestryView) => {
1777
+ const resolved = resolveMethodByOwner(ownerType, methodName, currentFile, ctx, heritageMap, argCount, ancestryView);
1646
1778
  if (!resolved)
1647
1779
  return null;
1648
1780
  return toResolveResult(resolved.def, resolved.tier);
@@ -1,10 +1,14 @@
1
1
  /**
2
2
  * Shared Ruby call routing logic.
3
3
  *
4
- * Ruby expresses imports, heritage (mixins), and property definitions as
5
- * method calls rather than syntax-level constructs. This module provides a
6
- * routing function used by the CLI call-processor, CLI parse-worker, and
7
- * the web call-processor so that the classification logic lives in one place.
4
+ * Ruby expresses imports and property definitions as method calls rather
5
+ * than syntax-level constructs. This module provides a routing function
6
+ * used by the CLI call-processor, CLI parse-worker, and the web
7
+ * call-processor so that the classification logic lives in one place.
8
+ *
9
+ * Heritage (mixins: include/extend/prepend) was previously routed here
10
+ * but is now handled by heritageExtractor.extractFromCall before the
11
+ * call router runs. The router still returns 'skip' for these calls.
8
12
  *
9
13
  * NOTE: This file is intentionally duplicated in gitnexus-web/ because the
10
14
  * two packages have separate build targets (Node native vs WASM/browser).
@@ -24,9 +28,6 @@ export type RubyCallRouting = {
24
28
  kind: 'import';
25
29
  importPath: string;
26
30
  isRelative: boolean;
27
- } | {
28
- kind: 'heritage';
29
- items: RubyHeritageItem[];
30
31
  } | {
31
32
  kind: 'properties';
32
33
  items: RubyPropertyItem[];
@@ -35,11 +36,6 @@ export type RubyCallRouting = {
35
36
  } | {
36
37
  kind: 'skip';
37
38
  };
38
- export interface RubyHeritageItem {
39
- enclosingClass: string;
40
- mixinName: string;
41
- heritageKind: 'include' | 'extend' | 'prepend';
42
- }
43
39
  export type RubyAccessorType = 'attr_accessor' | 'attr_reader' | 'attr_writer';
44
40
  export interface RubyPropertyItem {
45
41
  propName: string;
@@ -1,10 +1,14 @@
1
1
  /**
2
2
  * Shared Ruby call routing logic.
3
3
  *
4
- * Ruby expresses imports, heritage (mixins), and property definitions as
5
- * method calls rather than syntax-level constructs. This module provides a
6
- * routing function used by the CLI call-processor, CLI parse-worker, and
7
- * the web call-processor so that the classification logic lives in one place.
4
+ * Ruby expresses imports and property definitions as method calls rather
5
+ * than syntax-level constructs. This module provides a routing function
6
+ * used by the CLI call-processor, CLI parse-worker, and the web
7
+ * call-processor so that the classification logic lives in one place.
8
+ *
9
+ * Heritage (mixins: include/extend/prepend) was previously routed here
10
+ * but is now handled by heritageExtractor.extractFromCall before the
11
+ * call router runs. The router still returns 'skip' for these calls.
8
12
  *
9
13
  * NOTE: This file is intentionally duplicated in gitnexus-web/ because the
10
14
  * two packages have separate build targets (Node native vs WASM/browser).
@@ -13,8 +17,6 @@
13
17
  // ── Pre-allocated singletons for common return values ────────────────────────
14
18
  const CALL_RESULT = { kind: 'call' };
15
19
  const SKIP_RESULT = { kind: 'skip' };
16
- /** Max depth for parent-walking loops to prevent pathological AST traversals */
17
- const MAX_PARENT_DEPTH = 50;
18
20
  // ── Routing function ────────────────────────────────────────────────────────
19
21
  /**
20
22
  * Classify a Ruby call node and extract its semantic payload.
@@ -42,35 +44,12 @@ export function routeRubyCall(calledName, callNode) {
42
44
  }
43
45
  return { kind: 'import', importPath, isRelative };
44
46
  }
45
- // ── include / extend / prepend heritage (mixin) ──────────────────────
47
+ // ── include / extend / prepend heritage (now handled by heritageExtractor)
48
+ // Call-based heritage is intercepted by heritageExtractor.extractFromCall
49
+ // before the call router runs. Return SKIP_RESULT so these calls don't
50
+ // fall through to normal call processing.
46
51
  if (calledName === 'include' || calledName === 'extend' || calledName === 'prepend') {
47
- let enclosingClass = null;
48
- let current = callNode.parent;
49
- let depth = 0;
50
- while (current && ++depth <= MAX_PARENT_DEPTH) {
51
- if (current.type === 'class' || current.type === 'module') {
52
- const nameNode = current.childForFieldName?.('name');
53
- if (nameNode) {
54
- enclosingClass = nameNode.text;
55
- break;
56
- }
57
- }
58
- current = current.parent;
59
- }
60
- if (!enclosingClass)
61
- return SKIP_RESULT;
62
- const items = [];
63
- const argList = callNode.childForFieldName?.('arguments');
64
- for (const arg of argList?.children ?? []) {
65
- if (arg.type === 'constant' || arg.type === 'scope_resolution') {
66
- items.push({
67
- enclosingClass,
68
- mixinName: arg.text,
69
- heritageKind: calledName,
70
- });
71
- }
72
- }
73
- return items.length > 0 ? { kind: 'heritage', items } : SKIP_RESULT;
52
+ return SKIP_RESULT;
74
53
  }
75
54
  // ── attr_accessor / attr_reader / attr_writer → property definitions ───
76
55
  if (calledName === 'attr_accessor' ||