gitnexus 1.6.5-rc.32 → 1.6.5-rc.34

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.
@@ -237,6 +237,12 @@ export function emitCppScopeCaptures(sourceText, filePath, cachedTree) {
237
237
  }
238
238
  out.push(grouped);
239
239
  }
240
+ // ── Emit inheritance references for scope-resolution MRO / EXTENDS ──
241
+ // Walk every class/struct base list and synthesize `@reference.inherits`
242
+ // captures consumed by the registry-primary graph bridge. The lookup name
243
+ // is normalized to the bare class name so `Base<T>` / `outer::v1::Base<T>`
244
+ // resolve through V1's simple-name `findClassBindingInScope('Base')`.
245
+ emitCppInheritanceCaptures(tree.rootNode, out);
240
246
  // ── Detect dependent-base relationships for two-phase template lookup ──
241
247
  // Walk the tree once, finding every `template_declaration` whose
242
248
  // child is a class/struct definition with a `base_class_clause` whose
@@ -248,6 +254,41 @@ export function emitCppScopeCaptures(sourceText, filePath, cachedTree) {
248
254
  detectCppDependentBases(tree.rootNode, filePath);
249
255
  return out;
250
256
  }
257
+ /**
258
+ * Walk every C++ class/struct base clause and emit `@reference.inherits`
259
+ * captures for each base so scope resolution can resolve them into EXTENDS
260
+ * edges. Lookup names are normalized to bare class names (`Base<T>` → `Base`,
261
+ * `outer::v1::Base<T>` → `Base`) to match the V1 simple-name
262
+ * `findClassBindingInScope` contract. This intentionally preserves the
263
+ * existing scope-chain tradeoff: qualified namespace context is discarded
264
+ * here instead of introducing a C++-only name-resolution lane in shared
265
+ * ingestion infrastructure.
266
+ */
267
+ function emitCppInheritanceCaptures(root, out) {
268
+ const stack = [root];
269
+ while (stack.length > 0) {
270
+ const node = stack.pop();
271
+ if (node.type === 'class_specifier' || node.type === 'struct_specifier') {
272
+ const baseClause = findChildOfType(node, ['base_class_clause']);
273
+ if (baseClause !== null) {
274
+ for (const base of iterBaseClasses(baseClause)) {
275
+ const baseName = extractBaseLookupName(base);
276
+ if (baseName.length === 0)
277
+ continue;
278
+ out.push({
279
+ '@reference.inherits': nodeToCapture('@reference.inherits', base),
280
+ '@reference.name': syntheticCapture('@reference.name', base, baseName),
281
+ });
282
+ }
283
+ }
284
+ }
285
+ for (let i = 0; i < node.childCount; i++) {
286
+ const child = node.child(i);
287
+ if (child !== null)
288
+ stack.push(child);
289
+ }
290
+ }
291
+ }
251
292
  /**
252
293
  * Walk the AST finding every template_declaration containing a class or
253
294
  * struct definition with a dependent base. Records (className, baseName)
@@ -277,7 +318,7 @@ function detectCppDependentBases(root, filePath) {
277
318
  if (baseClause !== null) {
278
319
  for (const base of iterBaseClasses(baseClause)) {
279
320
  if (isBaseDependent(base, params)) {
280
- const baseName = extractBaseSimpleName(base);
321
+ const baseName = extractBaseLookupName(base);
281
322
  if (baseName !== '') {
282
323
  markCppDependentBase(filePath, className, baseName);
283
324
  }
@@ -393,23 +434,40 @@ function isBaseDependent(baseNode, templateParams) {
393
434
  }
394
435
  return false;
395
436
  }
396
- /** Extract the simple name of a base class node. */
397
- function extractBaseSimpleName(baseNode) {
398
- if (baseNode.type === 'type_identifier')
437
+ /**
438
+ * Recursively extract the bare lookup name of a base class node.
439
+ * Examples: `Base` → `Base`, `Base<T>` → `Base`,
440
+ * `outer::v1::Base<T>` → `Base`. Namespace qualifiers are intentionally
441
+ * dropped to align with V1 scope-chain lookup everywhere else in the
442
+ * registry-primary pipeline.
443
+ */
444
+ function extractBaseLookupName(baseNode) {
445
+ if (baseNode.type === 'type_identifier' || baseNode.type === 'identifier')
399
446
  return baseNode.text;
400
447
  if (baseNode.type === 'template_type') {
401
448
  const nameNode = baseNode.childForFieldName('name');
402
449
  if (nameNode !== null)
403
- return nameNode.text;
404
- // Fallback: first type_identifier descendant.
405
- const id = findFirstDescendantOfType(baseNode, 'type_identifier');
450
+ return extractBaseLookupName(nameNode);
451
+ const id = findFirstDescendantOfType(baseNode, 'type_identifier') ??
452
+ findFirstDescendantOfType(baseNode, 'identifier');
406
453
  if (id !== null)
407
454
  return id.text;
408
455
  }
409
456
  if (baseNode.type === 'qualified_identifier') {
410
457
  const nameNode = baseNode.childForFieldName('name');
411
- if (nameNode !== null)
412
- return nameNode.text;
458
+ if (nameNode !== null) {
459
+ const nested = extractBaseLookupName(nameNode);
460
+ if (nested.length > 0)
461
+ return nested;
462
+ }
463
+ for (let i = baseNode.childCount - 1; i >= 0; i--) {
464
+ const child = baseNode.child(i);
465
+ if (child === null)
466
+ continue;
467
+ const nested = extractBaseLookupName(child);
468
+ if (nested.length > 0)
469
+ return nested;
470
+ }
413
471
  }
414
472
  return '';
415
473
  }
@@ -416,6 +416,25 @@ const CPP_SCOPE_QUERY = `
416
416
  scope: (_) @reference.receiver
417
417
  name: (identifier) @reference.name)) @reference.call.qualified
418
418
 
419
+ ;; Nested qualified receiver: outer::v1::Base<T>::f()
420
+ ;; tree-sitter-cpp nests this as qualified_identifier(name:
421
+ ;; qualified_identifier(scope: qualified_identifier(...), name: identifier)).
422
+ ;; Capturing the innermost receiver still gives isSuperReceiverInContext
423
+ ;; enough text to strip qualifiers/template args down to Base.
424
+ (call_expression
425
+ function: (qualified_identifier
426
+ name: (qualified_identifier
427
+ scope: (_) @reference.receiver
428
+ name: (identifier) @reference.name))) @reference.call.qualified
429
+
430
+ ;; Double-nested qualified receiver: outer::v1::Base<T>::f()
431
+ (call_expression
432
+ function: (qualified_identifier
433
+ name: (qualified_identifier
434
+ name: (qualified_identifier
435
+ scope: (_) @reference.receiver
436
+ name: (identifier) @reference.name)))) @reference.call.qualified
437
+
419
438
  ;; ─── References — member calls (obj.method() / ptr->method()) ───────
420
439
  (call_expression
421
440
  function: (field_expression
@@ -68,15 +68,19 @@ export const cppScopeResolver = {
68
68
  // fallback and wildcard expansion can suppress them as unqualified
69
69
  // cross-file callables.
70
70
  populateCppNonGloballyVisible(parsed);
71
- // Resolve recorded template-class → dependent-base simple names to
72
- // class nodeIds for two-phase template lookup (U3 of plan
73
- // 2026-05-13-001).
74
- populateCppDependentBases(parsed);
75
71
  // Build the class-def → enclosing-namespace-qualified-name map used
76
72
  // by ADL (U2 of plan 2026-05-13-001) to identify each argument type's
77
73
  // associated namespace for Koenig lookup.
78
74
  populateCppAssociatedNamespaces(parsed);
79
75
  },
76
+ // Resolve recorded template-class → dependent-base simple names to
77
+ // class nodeIds for two-phase template lookup (U3 of plan
78
+ // 2026-05-13-001). Runs AFTER all files have had `populateOwners`
79
+ // applied so that cross-file base classes (e.g. Base in base.h,
80
+ // Derived in derived.h) are reachable in the workspace index.
81
+ populateWorkspaceOwners: (parsedFiles) => {
82
+ populateCppDependentBases(parsedFiles);
83
+ },
80
84
  // Simple `isSuperReceiver` returns false for C++. Real super
81
85
  // classification is caller-context-dependent and lives in
82
86
  // `isSuperReceiverInContext` below — without scope context the
@@ -11,8 +11,20 @@
11
11
  * This module records — during `emitCppScopeCaptures` — which template
12
12
  * class declarations have which dependent base class names (per file).
13
13
  * `populateCppDependentBases` then resolves those names to class nodeIds
14
- * using the workspace registry, building the per-class set the
15
- * `isDependentBaseMember` predicate consumes.
14
+ * using a workspace-wide registry, building the per-class set the
15
+ * `isCppDependentBaseMember` predicate consumes.
16
+ *
17
+ * Cross-file resolution: `Base<T>` may be declared in a different header
18
+ * than `Derived<T>`. `populateCppDependentBases` therefore runs as a
19
+ * workspace-wide pass (`populateWorkspaceOwners` hook) after every file
20
+ * has had `populateOwners` applied, so all class defs are reachable.
21
+ *
22
+ * Namespace disambiguation: when multiple classes share a simple name
23
+ * (e.g., `Box` in two namespaces), the resolver prefers the candidate
24
+ * whose qualified-name prefix (namespace path) matches the deriving
25
+ * class's prefix. If no namespace match is found, a unique simple-name
26
+ * match is accepted; ambiguous matches (multiple candidates, no
27
+ * namespace winner) are skipped conservatively.
16
28
  *
17
29
  * NOTE: module-level state, single-process-single-repo use only.
18
30
  * `clearFileLocalNames()` clears this state alongside file-local linkage
@@ -33,15 +45,19 @@ export declare function markCppDependentBase(filePath: string, className: string
33
45
  /** Clear two-phase-lookup state. Called from `clearFileLocalNames`. */
34
46
  export declare function clearCppDependentBases(): void;
35
47
  /**
36
- * Resolve recorded dependent-base simple names to class nodeIds using
37
- * the parsed file's localDefs. Run as part of `populateOwners` so the
38
- * resolved set is available before any resolution pass consults it.
48
+ * Resolve recorded dependent-base simple names to class nodeIds using a
49
+ * workspace-wide index. Run as `populateWorkspaceOwners` after every
50
+ * file has had `populateOwners` applied, so class defs from ALL files
51
+ * are reachable.
39
52
  *
40
- * Matches by simple name within the same file (the template class and
41
- * its base are typically declared in the same TU; cross-file template
42
- * bases are an edge case deferred to V2).
53
+ * Disambiguation strategy (multiple classes sharing a simple name):
54
+ * 1. Prefer the candidate whose qualified-name namespace prefix matches
55
+ * the deriving class's namespace prefix (same-namespace bias).
56
+ * 2. Fall back to accepting a unique simple-name match.
57
+ * 3. Skip when multiple candidates exist and no namespace match is
58
+ * found (conservative: avoids false associations).
43
59
  */
44
- export declare function populateCppDependentBases(parsed: ParsedFile): void;
60
+ export declare function populateCppDependentBases(parsedFiles: readonly ParsedFile[]): void;
45
61
  /**
46
62
  * Two-phase lookup predicate: is the candidate def a member of a
47
63
  * dependent base of the caller's enclosing template class?
@@ -11,8 +11,20 @@
11
11
  * This module records — during `emitCppScopeCaptures` — which template
12
12
  * class declarations have which dependent base class names (per file).
13
13
  * `populateCppDependentBases` then resolves those names to class nodeIds
14
- * using the workspace registry, building the per-class set the
15
- * `isDependentBaseMember` predicate consumes.
14
+ * using a workspace-wide registry, building the per-class set the
15
+ * `isCppDependentBaseMember` predicate consumes.
16
+ *
17
+ * Cross-file resolution: `Base<T>` may be declared in a different header
18
+ * than `Derived<T>`. `populateCppDependentBases` therefore runs as a
19
+ * workspace-wide pass (`populateWorkspaceOwners` hook) after every file
20
+ * has had `populateOwners` applied, so all class defs are reachable.
21
+ *
22
+ * Namespace disambiguation: when multiple classes share a simple name
23
+ * (e.g., `Box` in two namespaces), the resolver prefers the candidate
24
+ * whose qualified-name prefix (namespace path) matches the deriving
25
+ * class's prefix. If no namespace match is found, a unique simple-name
26
+ * match is accepted; ambiguous matches (multiple candidates, no
27
+ * namespace winner) are skipped conservatively.
16
28
  *
17
29
  * NOTE: module-level state, single-process-single-repo use only.
18
30
  * `clearFileLocalNames()` clears this state alongside file-local linkage
@@ -61,40 +73,97 @@ export function clearCppDependentBases() {
61
73
  dependentBaseNodeIds.clear();
62
74
  }
63
75
  /**
64
- * Resolve recorded dependent-base simple names to class nodeIds using
65
- * the parsed file's localDefs. Run as part of `populateOwners` so the
66
- * resolved set is available before any resolution pass consults it.
76
+ * Resolve recorded dependent-base simple names to class nodeIds using a
77
+ * workspace-wide index. Run as `populateWorkspaceOwners` after every
78
+ * file has had `populateOwners` applied, so class defs from ALL files
79
+ * are reachable.
67
80
  *
68
- * Matches by simple name within the same file (the template class and
69
- * its base are typically declared in the same TU; cross-file template
70
- * bases are an edge case deferred to V2).
81
+ * Disambiguation strategy (multiple classes sharing a simple name):
82
+ * 1. Prefer the candidate whose qualified-name namespace prefix matches
83
+ * the deriving class's namespace prefix (same-namespace bias).
84
+ * 2. Fall back to accepting a unique simple-name match.
85
+ * 3. Skip when multiple candidates exist and no namespace match is
86
+ * found (conservative: avoids false associations).
71
87
  */
72
- export function populateCppDependentBases(parsed) {
73
- const perFile = dependentBasesByFile.get(parsed.filePath);
74
- if (perFile === undefined)
88
+ export function populateCppDependentBases(parsedFiles) {
89
+ if (dependentBasesByFile.size === 0)
75
90
  return;
76
- // Build simple-name → nodeId index for this file's class-like defs.
77
- const classByName = new Map();
78
- for (const def of parsed.localDefs) {
79
- if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface')
80
- continue;
81
- const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
82
- if (simple !== '')
83
- classByName.set(simple, def.nodeId);
91
+ // Build workspace-wide index: simpleName → {nodeId, nsPrefix}[]
92
+ // nsPrefix is the dot-joined namespace path (qualifiedName without the
93
+ // last segment). Classes at global scope have nsPrefix = ''.
94
+ const classesBySimpleName = new Map();
95
+ for (const parsed of parsedFiles) {
96
+ for (const def of parsed.localDefs) {
97
+ if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface')
98
+ continue;
99
+ const qn = def.qualifiedName ?? '';
100
+ const lastDot = qn.lastIndexOf('.');
101
+ const simple = lastDot >= 0 ? qn.slice(lastDot + 1) : qn;
102
+ if (simple === '')
103
+ continue;
104
+ const nsPrefix = lastDot >= 0 ? qn.slice(0, lastDot) : '';
105
+ let entries = classesBySimpleName.get(simple);
106
+ if (entries === undefined) {
107
+ entries = [];
108
+ classesBySimpleName.set(simple, entries);
109
+ }
110
+ entries.push({ nodeId: def.nodeId, nsPrefix });
111
+ }
84
112
  }
85
- for (const [className, baseNames] of perFile) {
86
- const classNodeId = classByName.get(className);
87
- if (classNodeId === undefined)
113
+ // Build a filePath → ParsedFile lookup for fast per-file access.
114
+ const parsedByFile = new Map();
115
+ for (const parsed of parsedFiles)
116
+ parsedByFile.set(parsed.filePath, parsed);
117
+ for (const [filePath, perFile] of dependentBasesByFile) {
118
+ const parsed = parsedByFile.get(filePath);
119
+ if (parsed === undefined)
88
120
  continue;
89
- let bases = dependentBaseNodeIds.get(classNodeId);
90
- if (bases === undefined) {
91
- bases = new Set();
92
- dependentBaseNodeIds.set(classNodeId, bases);
121
+ // Build a simple-name → {nodeId, nsPrefix} map for THIS file's
122
+ // class-like defs so we can identify each template class precisely
123
+ // (avoids cross-file name collisions for the deriving class itself).
124
+ const localClassByName = new Map();
125
+ for (const def of parsed.localDefs) {
126
+ if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface')
127
+ continue;
128
+ const qn = def.qualifiedName ?? '';
129
+ const lastDot = qn.lastIndexOf('.');
130
+ const simple = lastDot >= 0 ? qn.slice(lastDot + 1) : qn;
131
+ if (simple === '')
132
+ continue;
133
+ const nsPrefix = lastDot >= 0 ? qn.slice(0, lastDot) : '';
134
+ localClassByName.set(simple, { nodeId: def.nodeId, nsPrefix });
93
135
  }
94
- for (const baseName of baseNames) {
95
- const baseNodeId = classByName.get(baseName);
96
- if (baseNodeId !== undefined)
97
- bases.add(baseNodeId);
136
+ for (const [className, baseNames] of perFile) {
137
+ const classEntry = localClassByName.get(className);
138
+ if (classEntry === undefined)
139
+ continue;
140
+ let bases = dependentBaseNodeIds.get(classEntry.nodeId);
141
+ if (bases === undefined) {
142
+ bases = new Set();
143
+ dependentBaseNodeIds.set(classEntry.nodeId, bases);
144
+ }
145
+ for (const baseName of baseNames) {
146
+ const candidates = classesBySimpleName.get(baseName);
147
+ if (candidates === undefined || candidates.length === 0)
148
+ continue;
149
+ if (candidates.length === 1) {
150
+ // Unique simple-name match — accept regardless of namespace.
151
+ bases.add(candidates[0].nodeId);
152
+ continue;
153
+ }
154
+ // Multiple classes share the same simple name — prefer the one
155
+ // whose namespace matches the deriving class's namespace.
156
+ // V1: exact dot-prefix match only. Cross-namespace inheritance
157
+ // (e.g., `ns::outer::Derived` extending bare `Inner` defined in
158
+ // `ns::outer::inner`) and inline-namespace cases are deferred to
159
+ // V2; the conservative skip-on-ambiguity below avoids false
160
+ // associations in those edge cases.
161
+ const nsMatch = candidates.find((c) => c.nsPrefix === classEntry.nsPrefix);
162
+ if (nsMatch !== undefined) {
163
+ bases.add(nsMatch.nodeId);
164
+ }
165
+ // else: ambiguous (multiple candidates, no namespace match) → skip.
166
+ }
98
167
  }
99
168
  }
100
169
  }
@@ -87,6 +87,9 @@
87
87
  * attempting emission (even on dedup-collapse), because the
88
88
  * per-(caller, target) collapse semantics require multiple call
89
89
  * sites in the same caller body not produce multiple edges.
90
+ * `preEmitInheritanceEdges` also pre-marks every `inherits` site so
91
+ * the generic bridge cannot remap class heritage into method-owned
92
+ * EXTENDS edges via `resolveCallerGraphId`.
90
93
  *
91
94
  * - **I3 — `propagateImportedReturnTypes` mutation timing + ordering.**
92
95
  * The pass mutates `Scope.typeBindings` (a plain `new Map(...)` from
@@ -87,6 +87,9 @@
87
87
  * attempting emission (even on dedup-collapse), because the
88
88
  * per-(caller, target) collapse semantics require multiple call
89
89
  * sites in the same caller body not produce multiple edges.
90
+ * `preEmitInheritanceEdges` also pre-marks every `inherits` site so
91
+ * the generic bridge cannot remap class heritage into method-owned
92
+ * EXTENDS edges via `resolveCallerGraphId`.
90
93
  *
91
94
  * - **I3 — `propagateImportedReturnTypes` mutation timing + ordering.**
92
95
  * The pass mutates `Scope.typeBindings` (a plain `new Map(...)` from
@@ -28,14 +28,68 @@ import { extractParsedFile } from '../../scope-extractor-bridge.js';
28
28
  import { finalizeScopeModel } from '../../finalize-orchestrator.js';
29
29
  import { resolveReferenceSites } from '../../resolve-references.js';
30
30
  import { buildGraphNodeLookup } from '../graph-bridge/node-lookup.js';
31
+ import { resolveDefGraphId } from '../graph-bridge/ids.js';
31
32
  import { buildPopulatedMethodDispatch } from '../graph-bridge/method-dispatch.js';
33
+ import { tryEmitEdge } from '../graph-bridge/edges.js';
32
34
  import { propagateImportedReturnTypes } from '../passes/imported-return-types.js';
33
35
  import { emitReceiverBoundCalls } from '../passes/receiver-bound-calls.js';
34
36
  import { emitFreeCallFallback } from '../passes/free-call-fallback.js';
35
37
  import { emitReferencesViaLookup } from '../graph-bridge/references-to-edges.js';
36
38
  import { emitImportEdges } from '../graph-bridge/imports-to-edges.js';
39
+ import { findClassBindingInScope, findEnclosingClassDef } from '../scope/walkers.js';
37
40
  import { buildWorkspaceResolutionIndex } from '../workspace-index.js';
38
41
  import { logger } from '../../../logger.js';
42
+ /**
43
+ * Resolve inheritance reference sites early and pre-emit their EXTENDS edges
44
+ * before MRO construction. This lets template-base captures contribute to the
45
+ * graph in time for `buildMro`, while `handledSites` prevents the generic
46
+ * reference-edge bridge from re-emitting the same sites later.
47
+ *
48
+ * @returns Site keys to seed the downstream handled-site skip set.
49
+ */
50
+ function preEmitInheritanceEdges(graph, scopes, nodeLookup) {
51
+ const handledSites = new Set();
52
+ const seen = new Set();
53
+ const existing = new Set();
54
+ for (const rel of graph.iterRelationshipsByType('EXTENDS')) {
55
+ existing.add(`${rel.sourceId}->${rel.targetId}`);
56
+ }
57
+ for (const site of scopes.referenceSites) {
58
+ if (site.kind !== 'inherits')
59
+ continue;
60
+ const scope = scopes.scopeTree.getScope(site.inScope);
61
+ const siteKey = scope?.filePath !== undefined
62
+ ? `${scope.filePath}:${site.atRange.startLine}:${site.atRange.startCol}`
63
+ : undefined;
64
+ if (siteKey !== undefined) {
65
+ // Intentionally suppress every `inherits` site from the generic
66
+ // reference bridge, even when this pre-pass can't emit an EXTENDS
67
+ // edge. The shared bridge resolves the source via
68
+ // `resolveCallerGraphId`, which can degrade class-heritage sites into
69
+ // method-owned EXTENDS edges once methods exist on the class. This
70
+ // pre-pass is the authoritative inheritance emitter, so broad
71
+ // suppression keeps `buildMro` and the final graph class-owned.
72
+ handledSites.add(siteKey);
73
+ }
74
+ const targetDef = findClassBindingInScope(site.inScope, site.name, scopes);
75
+ if (targetDef === undefined)
76
+ continue;
77
+ const callerClass = findEnclosingClassDef(site.inScope, scopes);
78
+ if (callerClass === undefined)
79
+ continue;
80
+ const callerGraphId = resolveDefGraphId(callerClass.filePath, callerClass, nodeLookup);
81
+ const targetGraphId = resolveDefGraphId(targetDef.filePath, targetDef, nodeLookup);
82
+ if (callerGraphId === undefined || targetGraphId === undefined)
83
+ continue;
84
+ const edgeKey = `${callerGraphId}->${targetGraphId}`;
85
+ if (existing.has(edgeKey))
86
+ continue;
87
+ if (tryEmitEdge(graph, scopes, nodeLookup, site, targetDef, 'scope-resolution: inherits', seen, 0.85)) {
88
+ existing.add(edgeKey);
89
+ }
90
+ }
91
+ return handledSites;
92
+ }
39
93
  export function runScopeResolution(input, provider) {
40
94
  const { graph, files } = input;
41
95
  const onWarn = input.onWarn ?? (() => { });
@@ -108,8 +162,6 @@ export function runScopeResolution(input, provider) {
108
162
  // ── Phase 2: finalize → ScopeResolutionIndexes ─────────────────────────
109
163
  const allFilePaths = new Set(parsedFiles.map((f) => f.filePath));
110
164
  const nodeLookup = buildGraphNodeLookup(graph);
111
- const mroByClassDefId = provider.buildMro(graph, parsedFiles, nodeLookup);
112
- const extendsOnlyMroByClassDefId = provider.buildExtendsOnlyMro?.(graph, parsedFiles, nodeLookup);
113
165
  const resolutionConfig = input.resolutionConfig;
114
166
  const finalized = finalizeScopeModel(parsedFiles, {
115
167
  hooks: {
@@ -118,6 +170,9 @@ export function runScopeResolution(input, provider) {
118
170
  mergeBindings: (existing, incoming, scopeId) => provider.mergeBindings(existing, incoming, scopeId),
119
171
  },
120
172
  });
173
+ const preEmittedInheritanceSites = preEmitInheritanceEdges(graph, finalized, nodeLookup);
174
+ const mroByClassDefId = provider.buildMro(graph, parsedFiles, nodeLookup);
175
+ const extendsOnlyMroByClassDefId = provider.buildExtendsOnlyMro?.(graph, parsedFiles, nodeLookup);
121
176
  // Replace the empty MethodDispatchIndex that finalizeScopeModel
122
177
  // builds by design with the populated one derived from the
123
178
  // language's MRO. Spread produces a fresh `ScopeResolutionIndexes`
@@ -184,7 +239,7 @@ export function runScopeResolution(input, provider) {
184
239
  });
185
240
  const tResolve = PROF ? process.hrtime.bigint() : 0n;
186
241
  // ── Phase 4: emit graph edges (LOAD-BEARING ORDER — see I1) ────────────
187
- const handledSites = new Set();
242
+ const handledSites = new Set(preEmittedInheritanceSites);
188
243
  const receiverExtras = emitReceiverBoundCalls(graph, indexes, parsedFiles, nodeLookup, handledSites, provider, workspaceIndex, readonlyModel);
189
244
  const unresolvedReceiverExtras = provider.emitUnresolvedReceiverEdges !== undefined
190
245
  ? provider.emitUnresolvedReceiverEdges(graph, indexes, parsedFiles, nodeLookup, handledSites, readonlyModel)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.5-rc.32",
3
+ "version": "1.6.5-rc.34",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",