gitnexus 1.6.8-rc.35 → 1.6.8-rc.36

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.
@@ -74,6 +74,9 @@ function buildAdjacency(graph) {
74
74
  }
75
75
  return { parentMap, methodMap, parentEdgeType };
76
76
  }
77
+ /** Confidence for a single-ancestor override edge (a subclass overriding one parent
78
+ * method via class inheritance) — same tier as MRO-ordered resolution. */
79
+ const SINGLE_ANCESTOR_OVERRIDE_CONFIDENCE = 0.9;
77
80
  /** Resolve by MRO order — first ancestor in linearized order wins. */
78
81
  function resolveByMroOrder(methodName, defs, mroOrder, reasonPrefix) {
79
82
  for (const ancestorId of mroOrder) {
@@ -266,6 +269,112 @@ export function computeMRO(graph) {
266
269
  ambiguities,
267
270
  });
268
271
  }
272
+ // ── Single-ancestor override detection ──────────────────────────────────
273
+ // The collision loop above only emits when a method is defined in 2+ ancestors
274
+ // (defs.length < 2 is skipped). A subclass overriding a single parent method via
275
+ // class inheritance is the common case. Applicability is derived automatically from
276
+ // the language's existing MRO strategy — no separate per-language flag: every
277
+ // strategy resolves overrides by inheritance order EXCEPT `qualified-syntax` (Rust),
278
+ // where a same-named method is a trait implementation (METHOD_IMPLEMENTS), not an
279
+ // override. The EXTENDS-only walk below reinforces this structurally.
280
+ const emittedOverrideEdgeIds = new Set();
281
+ // Follow only EXTENDS (class-inheritance) parents and skip Interface/Trait ancestors:
282
+ // satisfying an interface/trait default method is a METHOD_IMPLEMENTS relationship
283
+ // (handled by emitMethodImplementsEdges), not an override. This also structurally
284
+ // excludes languages without class inheritance (e.g. Rust traits use IMPLEMENTS).
285
+ const extendsParentsOf = (nodeId) => (parentMap.get(nodeId) ?? []).filter((pid) => {
286
+ if (parentEdgeType.get(nodeId)?.get(pid) !== 'EXTENDS')
287
+ return false;
288
+ const pn = graph.getNode(pid);
289
+ return !!pn && pn.label !== 'Interface' && pn.label !== 'Trait';
290
+ });
291
+ for (const classId of parentMap.keys()) {
292
+ const classNode = graph.getNode(classId);
293
+ const language = classNode?.properties.language;
294
+ if (!language)
295
+ continue;
296
+ // Language-aware gate, reusing the MRO model we already maintain (not a new flag).
297
+ if (getProvider(language).mroStrategy === 'qualified-syntax')
298
+ continue;
299
+ const ownMethods = methodMap.get(classId) ?? [];
300
+ const extendsParents = extendsParentsOf(classId);
301
+ if (extendsParents.length === 0 || ownMethods.length === 0)
302
+ continue;
303
+ const orderedAncestorMethods = [];
304
+ {
305
+ const visited = new Set();
306
+ const queue = [...extendsParents];
307
+ while (queue.length > 0) {
308
+ const ancestorId = queue.shift();
309
+ if (visited.has(ancestorId))
310
+ continue;
311
+ visited.add(ancestorId);
312
+ const ancestorName = graph.getNode(ancestorId)?.properties.name;
313
+ const methodsByName = new Map();
314
+ for (const mid of methodMap.get(ancestorId) ?? []) {
315
+ const mn = graph.getNode(mid);
316
+ if (!mn || mn.label === 'Property' || typeof mn.properties.name !== 'string')
317
+ continue;
318
+ const entry = {
319
+ methodId: mid,
320
+ paramTypes: mn.properties.parameterTypes ?? [],
321
+ paramCount: mn.properties.parameterCount,
322
+ };
323
+ const bucket = methodsByName.get(mn.properties.name);
324
+ if (bucket)
325
+ bucket.push(entry);
326
+ else
327
+ methodsByName.set(mn.properties.name, [entry]);
328
+ }
329
+ orderedAncestorMethods.push({
330
+ className: typeof ancestorName === 'string' ? ancestorName : '<anonymous>',
331
+ methodsByName,
332
+ });
333
+ queue.push(...extendsParentsOf(ancestorId));
334
+ }
335
+ }
336
+ for (const methodId of ownMethods) {
337
+ const methodNode = graph.getNode(methodId);
338
+ if (!methodNode || methodNode.label === 'Property' || !methodNode.properties.name)
339
+ continue;
340
+ const methodName = methodNode.properties.name;
341
+ const ownParamTypes = methodNode.properties.parameterTypes ?? [];
342
+ const ownParamCount = methodNode.properties.parameterCount;
343
+ // Nearest ancestor (BFS order) defining this method by name AND signature, so a
344
+ // same-named overload (e.g. foo(int) vs foo()) is not mistaken for an override.
345
+ for (const { className, methodsByName } of orderedAncestorMethods) {
346
+ const sameName = methodsByName.get(methodName);
347
+ if (!sameName)
348
+ continue;
349
+ const matches = sameName.filter((m) => parameterTypesMatch(ownParamTypes, m.paramTypes, ownParamCount, m.paramCount).match);
350
+ if (matches.length === 0)
351
+ continue; // name present but no signature match — look deeper
352
+ // Emit only when the match is unambiguous (exactly one candidate); >1 means the
353
+ // override target cannot be pinned, so emit nothing. Either way stop — a closer
354
+ // ancestor shadows anything deeper.
355
+ if (matches.length === 1) {
356
+ const matchingMethodId = matches[0].methodId;
357
+ // Target the ancestor METHOD node (not the class), key the id on the method
358
+ // so distinct overrides stay distinct, and count only edges actually added
359
+ // (addRelationship is first-writer-wins, so a duplicate id is dropped).
360
+ const overrideId = generateId('METHOD_OVERRIDES', `${classId}->${matchingMethodId}`);
361
+ if (!emittedOverrideEdgeIds.has(overrideId)) {
362
+ emittedOverrideEdgeIds.add(overrideId);
363
+ graph.addRelationship({
364
+ id: overrideId,
365
+ sourceId: classId,
366
+ targetId: matchingMethodId,
367
+ type: 'METHOD_OVERRIDES',
368
+ confidence: SINGLE_ANCESTOR_OVERRIDE_CONFIDENCE,
369
+ reason: `single-ancestor override: ${className}::${methodName}()`,
370
+ });
371
+ overrideEdges++;
372
+ }
373
+ }
374
+ break; // nearest ancestor shadows deeper ones
375
+ }
376
+ }
377
+ }
269
378
  const methodImplementsEdges = emitMethodImplementsEdges(graph, parentMap, methodMap, parentEdgeType, ancestorsMap, edgeTypesMap);
270
379
  return { entries, overrideEdges, ambiguityCount, methodImplementsEdges };
271
380
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.8-rc.35",
3
+ "version": "1.6.8-rc.36",
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",