gitnexus 1.6.5-rc.32 → 1.6.5-rc.33

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
@@ -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.33",
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",