gitnexus 1.6.6-rc.48 → 1.6.6-rc.49

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.
@@ -62,6 +62,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
62
62
  const { totalFiles, usedWorkerPool } = getPhaseOutput(results, 'parse');
63
63
  let communityResult;
64
64
  let processResult;
65
+ const resolutionOutcomes = getPhaseOutput(results, 'scopeResolution').resolutionOutcomes;
65
66
  if (!options?.skipGraphPhases) {
66
67
  communityResult = getPhaseOutput(results, 'communities').communityResult;
67
68
  processResult = getPhaseOutput(results, 'processes').processResult;
@@ -84,6 +85,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
84
85
  totalFileCount: totalFiles,
85
86
  communityResult,
86
87
  processResult,
88
+ resolutionOutcomes,
87
89
  usedWorkerPool,
88
90
  };
89
91
  };
@@ -23,6 +23,7 @@ import type { SemanticModel } from '../../model/semantic-model.js';
23
23
  import type { WorkspaceResolutionIndex } from '../workspace-index.js';
24
24
  import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
25
25
  import type { ScopeResolver } from '../contract/scope-resolver.js';
26
+ import type { ResolutionOutcomeRecorder } from '../resolution-outcome.js';
26
27
  import { type ConversionRankFn } from './overload-narrowing.js';
27
28
  export declare function emitFreeCallFallback(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, _referenceIndex: {
28
29
  readonly bySourceScope: ReadonlyMap<ScopeId, readonly Reference[]>;
@@ -51,6 +52,7 @@ export declare function emitFreeCallFallback(graph: KnowledgeGraph, scopes: Scop
51
52
  * fail at the call site. Three-valued; `'unknown'` keeps the
52
53
  * candidate (monotonicity). */
53
54
  readonly constraintCompatibility?: ScopeResolver['constraintCompatibility'];
55
+ readonly recordResolutionOutcome?: ResolutionOutcomeRecorder;
54
56
  }): number;
55
57
  /** Walk up from the call-site scope to the enclosing class scope,
56
58
  * pick a method member by name with overload narrowing on arity +
@@ -86,7 +86,18 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
86
86
  // Cross-file candidates are shadowing; keep first-match.
87
87
  const sameFile = narrowed.every((d) => d.filePath === narrowed[0].filePath);
88
88
  if (sameFile) {
89
- handledSites.add(`${parsed.filePath}:${site.atRange.startLine}:${site.atRange.startCol}`);
89
+ recordSuppressedOutcome(options.recordResolutionOutcome, {
90
+ phase: 'free-call-fallback',
91
+ filePath: parsed.filePath,
92
+ name: site.name,
93
+ range: site.atRange,
94
+ reason: suppressionReasonForOverload(narrowed, site.arity, {
95
+ conversionRankFn: options.conversionRankFn,
96
+ argumentTypes: site.argumentTypes,
97
+ }),
98
+ candidates: narrowed,
99
+ });
100
+ handledSites.add(siteKey(parsed.filePath, site));
90
101
  continue;
91
102
  }
92
103
  }
@@ -109,7 +120,19 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
109
120
  argumentTypes: site.argumentTypes,
110
121
  atRange: { startLine: site.atRange.startLine, startCol: site.atRange.startCol },
111
122
  }, parsed, scopes, parsedFiles);
112
- const siteKey = `${parsed.filePath}:${site.atRange.startLine}:${site.atRange.startCol}`;
123
+ const key = siteKey(parsed.filePath, site);
124
+ if (adlSuppressed && ordinary.length === 0) {
125
+ recordSuppressedOutcome(options.recordResolutionOutcome, {
126
+ phase: 'free-call-fallback',
127
+ filePath: parsed.filePath,
128
+ name: site.name,
129
+ range: site.atRange,
130
+ reason: 'adl-ordinary-lookup-blocked',
131
+ candidates: ordinary,
132
+ });
133
+ handledSites.add(key);
134
+ continue;
135
+ }
113
136
  if (adl === undefined || adl.length === 0) {
114
137
  // No ADL contribution. Default behavior: `ordinary[0]` —
115
138
  // scope-chain walk preserves local-shadows-import precedence.
@@ -135,7 +158,7 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
135
158
  fnDef = narrowed[0];
136
159
  }
137
160
  else if (narrowed.length === 0) {
138
- handledSites.add(siteKey);
161
+ handledSites.add(key);
139
162
  continue;
140
163
  }
141
164
  else {
@@ -145,7 +168,18 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
145
168
  // first-match (shadowing semantics).
146
169
  const sameFile = narrowed.every((d) => d.filePath === narrowed[0].filePath);
147
170
  if (sameFile) {
148
- handledSites.add(siteKey);
171
+ recordSuppressedOutcome(options.recordResolutionOutcome, {
172
+ phase: 'free-call-fallback',
173
+ filePath: parsed.filePath,
174
+ name: site.name,
175
+ range: site.atRange,
176
+ reason: suppressionReasonForOverload(narrowed, site.arity, {
177
+ conversionRankFn: options.conversionRankFn,
178
+ argumentTypes: site.argumentTypes,
179
+ }),
180
+ candidates: narrowed,
181
+ });
182
+ handledSites.add(key);
149
183
  continue;
150
184
  }
151
185
  fnDef = ordinary[0];
@@ -174,17 +208,28 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
174
208
  fnDef = narrowed[0];
175
209
  }
176
210
  else if (narrowed.length === 0) {
177
- handledSites.add(siteKey);
211
+ handledSites.add(key);
178
212
  continue;
179
213
  }
180
214
  else if (narrowed.length > 1) {
215
+ recordSuppressedOutcome(options.recordResolutionOutcome, {
216
+ phase: 'free-call-fallback',
217
+ filePath: parsed.filePath,
218
+ name: site.name,
219
+ range: site.atRange,
220
+ reason: suppressionReasonForOverload(narrowed, site.arity, {
221
+ conversionRankFn: options.conversionRankFn,
222
+ argumentTypes: site.argumentTypes,
223
+ }),
224
+ candidates: narrowed,
225
+ });
181
226
  if (isOverloadAmbiguousAfterNormalization(narrowed, site.arity)) {
182
- handledSites.add(siteKey);
227
+ handledSites.add(key);
183
228
  continue;
184
229
  }
185
230
  // Multiple survivors remain after conversion-rank scoring;
186
231
  // suppress instead of picking arbitrarily.
187
- handledSites.add(siteKey);
232
+ handledSites.add(key);
188
233
  continue;
189
234
  }
190
235
  }
@@ -215,7 +260,7 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
215
260
  // Always mark the site as handled — even when the dedup-collapse
216
261
  // means we don't add a new edge — so `emit-references` skips its
217
262
  // potentially-wrong fallback for the same site.
218
- handledSites.add(`${parsed.filePath}:${site.atRange.startLine}:${site.atRange.startCol}`);
263
+ handledSites.add(siteKey(parsed.filePath, site));
219
264
  const relId = `rel:CALLS:${callerGraphId}->${tgtGraphId}`;
220
265
  if (seen.has(relId))
221
266
  continue;
@@ -235,6 +280,31 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
235
280
  }
236
281
  return emitted;
237
282
  }
283
+ function siteKey(filePath, site) {
284
+ return `${filePath}:${site.atRange.startLine}:${site.atRange.startCol}`;
285
+ }
286
+ function suppressionReasonForOverload(candidates, arity, ctx) {
287
+ if (isOverloadAmbiguousAfterNormalization(candidates, arity)) {
288
+ return 'overload-ambiguous-normalization';
289
+ }
290
+ if (ctx.conversionRankFn !== undefined &&
291
+ ctx.argumentTypes !== undefined &&
292
+ ctx.argumentTypes.length > 0) {
293
+ return 'conversion-rank-tied';
294
+ }
295
+ return 'overload-ambiguous';
296
+ }
297
+ function recordSuppressedOutcome(record, input) {
298
+ record?.({
299
+ kind: 'suppressed',
300
+ phase: input.phase,
301
+ filePath: input.filePath,
302
+ name: input.name,
303
+ range: input.range,
304
+ reason: input.reason,
305
+ candidateIds: input.candidates.map((d) => d.nodeId),
306
+ });
307
+ }
238
308
  /**
239
309
  * Build a `simpleName -> callable defs` index from `scopes.defs` once per
240
310
  * pass. Mirrors the filter the old per-site scan applied: Function /
@@ -43,11 +43,14 @@ import type { SemanticModel } from '../../model/semantic-model.js';
43
43
  import type { ScopeResolver } from '../contract/scope-resolver.js';
44
44
  import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
45
45
  import type { WorkspaceResolutionIndex } from '../workspace-index.js';
46
+ import type { ResolutionOutcomeRecorder } from '../resolution-outcome.js';
46
47
  /** Subset of `ScopeResolver` consumed by this pass. Accepting the
47
48
  * subset rather than the full provider keeps tests and partial
48
49
  * refactors lighter — callers only need to populate what we read. */
49
50
  type ReceiverBoundProviderSubset = Pick<ScopeResolver, 'isSuperReceiver' | 'isSuperReceiverInContext' | 'fieldFallbackOnMethodLookup' | 'collapseMemberCallsByCallerTarget' | 'unwrapCollectionAccessor' | 'hoistTypeBindingsToModule' | 'resolveQualifiedReceiverMember' | 'resolveThisViaEnclosingClass' | 'conversionRankFn' | 'constraintCompatibility' | 'isStaticOnly'>;
50
- export declare function emitReceiverBoundCalls(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, handledSites: Set<string>, provider: ReceiverBoundProviderSubset, index: WorkspaceResolutionIndex, model: SemanticModel): number;
51
+ export declare function emitReceiverBoundCalls(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, handledSites: Set<string>, provider: ReceiverBoundProviderSubset, index: WorkspaceResolutionIndex, model: SemanticModel, options?: {
52
+ readonly recordResolutionOutcome?: ResolutionOutcomeRecorder;
53
+ }): number;
51
54
  /**
52
55
  * Sentinel returned by `pickOverload` when narrowing leaves >1 candidate
53
56
  * sharing identical normalized parameter-types. Callers should suppress
@@ -84,7 +84,7 @@ function resolveClassBindingForName(scopeId, rawClassName, scopes) {
84
84
  }
85
85
  return findClassBindingInScope(scopeId, baseName, scopes);
86
86
  }
87
- export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, handledSites, provider, index, model) {
87
+ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, handledSites, provider, index, model, options = {}) {
88
88
  let emitted = 0;
89
89
  // Per-pass dedup so the multiple cases don't double-emit if two of
90
90
  // them resolve the same site to the same target. NEVER pre-seed
@@ -372,6 +372,15 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
372
372
  if (memberDef === 'ambiguous') {
373
373
  // Same-name ambiguity across inline-namespace children (#1564):
374
374
  // suppress edge emission, mark site handled.
375
+ options.recordResolutionOutcome?.({
376
+ kind: 'suppressed',
377
+ phase: 'receiver-bound-calls',
378
+ filePath: parsed.filePath,
379
+ name: site.name,
380
+ range: site.atRange,
381
+ reason: 'inline-ns-ambiguous',
382
+ candidateIds: [],
383
+ });
375
384
  handledSites.add(siteKey);
376
385
  continue;
377
386
  }
@@ -518,6 +527,7 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
518
527
  const chain = [ownerDef.nodeId, ...scopes.methodDispatch.mroFor(ownerDef.nodeId)];
519
528
  let memberDef;
520
529
  let ambiguous = false;
530
+ let ambiguousOwnerId;
521
531
  // Track whether the chain walk filtered out any static-only
522
532
  // candidates. When it did and the chain ended with no
523
533
  // legitimate instance member, we mark the site as handled so
@@ -549,6 +559,7 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
549
559
  const picked = pickFirstNonStaticOnly(ownerId, memberName, site, model, provider);
550
560
  if (picked === OVERLOAD_AMBIGUOUS) {
551
561
  ambiguous = true;
562
+ ambiguousOwnerId = ownerId;
552
563
  break;
553
564
  }
554
565
  if (picked === STATIC_ONLY_FILTERED) {
@@ -570,6 +581,7 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
570
581
  // Suppress and mark handled so `emitReferencesViaLookup`
571
582
  // doesn't re-emit the pre-resolved reference. See
572
583
  // OVERLOAD_AMBIGUOUS docstring for the upstream cause.
584
+ recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ambiguousOwnerId ?? ownerDef.nodeId, memberName, model, provider);
573
585
  handledSites.add(siteKey);
574
586
  continue;
575
587
  }
@@ -634,6 +646,7 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
634
646
  const ownerGraphId = resolveDefGraphId(valueDef.filePath, valueDef, nodeLookup) ?? valueDef.nodeId;
635
647
  const picked = pickOverload(ownerGraphId, memberName, site, model, provider);
636
648
  if (picked === OVERLOAD_AMBIGUOUS) {
649
+ recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ownerGraphId, memberName, model, provider);
637
650
  handledSites.add(siteKey);
638
651
  continue;
639
652
  }
@@ -798,3 +811,32 @@ function pickFirstNonStaticOnly(ownerId, memberName, site, model, provider) {
798
811
  return OVERLOAD_AMBIGUOUS;
799
812
  return candidates[0] ?? overloads[0];
800
813
  }
814
+ function recordReceiverOverloadSuppression(record, filePath, site, ownerId, memberName, model, provider) {
815
+ if (record === undefined)
816
+ return;
817
+ const overloads = model.methods.lookupAllByOwner(ownerId, memberName);
818
+ const candidates = narrowOverloadCandidates(overloads, site.arity, site.argumentTypes, {
819
+ argumentTypeClasses: site.argumentTypeClasses,
820
+ conversionRankFn: provider.conversionRankFn,
821
+ constraintCompatibility: provider.constraintCompatibility,
822
+ });
823
+ const reason = isOverloadAmbiguousAfterNormalization(candidates, site.arity)
824
+ ? 'overload-ambiguous-normalization'
825
+ : hasConversionRankingSignal(site, provider)
826
+ ? 'conversion-rank-tied'
827
+ : 'overload-ambiguous';
828
+ record({
829
+ kind: 'suppressed',
830
+ phase: 'receiver-bound-calls',
831
+ filePath,
832
+ name: site.name,
833
+ range: site.atRange,
834
+ reason,
835
+ candidateIds: candidates.map((d) => d.nodeId),
836
+ });
837
+ }
838
+ function hasConversionRankingSignal(site, provider) {
839
+ return (provider.conversionRankFn !== undefined &&
840
+ site.argumentTypes !== undefined &&
841
+ site.argumentTypes.length > 0);
842
+ }
@@ -28,6 +28,7 @@
28
28
  */
29
29
  import type { PipelinePhase } from '../../pipeline-phases/types.js';
30
30
  import { SupportedLanguages } from '../../../../_shared/index.js';
31
+ import type { ResolutionOutcome } from '../resolution-outcome.js';
31
32
  export interface ScopeResolutionOutput {
32
33
  /** True when at least one language ran. */
33
34
  readonly ran: boolean;
@@ -37,6 +38,8 @@ export interface ScopeResolutionOutput {
37
38
  readonly importsEmitted: number;
38
39
  /** Reference (CALLS / ACCESSES / INHERITS / USES) edges emitted. */
39
40
  readonly referenceEdgesEmitted: number;
41
+ /** Additive stream of resolver diagnostics; does not affect graph edges. */
42
+ readonly resolutionOutcomes: readonly ResolutionOutcome[];
40
43
  /** Per-language breakdown for telemetry / shadow-parity. */
41
44
  readonly perLanguage: ReadonlyMap<SupportedLanguages, {
42
45
  readonly filesProcessed: number;
@@ -39,6 +39,7 @@ const NOOP_OUTPUT = Object.freeze({
39
39
  filesProcessed: 0,
40
40
  importsEmitted: 0,
41
41
  referenceEdgesEmitted: 0,
42
+ resolutionOutcomes: [],
42
43
  perLanguage: new Map(),
43
44
  });
44
45
  export const scopeResolutionPhase = {
@@ -84,6 +85,7 @@ export const scopeResolutionPhase = {
84
85
  let totalImports = 0;
85
86
  let totalRefs = 0;
86
87
  let anyRan = false;
88
+ const resolutionOutcomes = [];
87
89
  const perLanguage = new Map();
88
90
  for (const [lang, provider] of SCOPE_RESOLVERS) {
89
91
  if (!isRegistryPrimary(lang))
@@ -113,6 +115,9 @@ export const scopeResolutionPhase = {
113
115
  treeCache: scopeTreeCache,
114
116
  resolutionConfig,
115
117
  preExtractedParsedFiles: preExtractedByPath,
118
+ recordResolutionOutcome: (outcome) => {
119
+ resolutionOutcomes.push(outcome);
120
+ },
116
121
  onWarn: (msg) => {
117
122
  if (isSemanticModelValidatorEnabled()) {
118
123
  logger.warn(`[scope-resolution:${lang}] ${msg}`);
@@ -146,6 +151,7 @@ export const scopeResolutionPhase = {
146
151
  filesProcessed: totalFiles,
147
152
  importsEmitted: totalImports,
148
153
  referenceEdgesEmitted: totalRefs,
154
+ resolutionOutcomes,
149
155
  perLanguage,
150
156
  };
151
157
  },
@@ -27,6 +27,7 @@ import type { KnowledgeGraph } from '../../../graph/types.js';
27
27
  import type { MutableSemanticModel } from '../../model/semantic-model.js';
28
28
  import { type ResolveStats } from '../../resolve-references.js';
29
29
  import type { ScopeResolver } from '../contract/scope-resolver.js';
30
+ import type { ResolutionOutcome, ResolutionOutcomeRecorder } from '../resolution-outcome.js';
30
31
  interface RunScopeResolutionInput {
31
32
  readonly graph: KnowledgeGraph;
32
33
  /**
@@ -78,6 +79,11 @@ interface RunScopeResolutionInput {
78
79
  * Cache miss is safe — falls back to fresh extract.
79
80
  */
80
81
  readonly preExtractedParsedFiles?: ReadonlyMap<string, ParsedFile>;
82
+ /**
83
+ * Optional additive diagnostics sink. Resolver passes call this when they
84
+ * intentionally suppress an edge; the graph remains unchanged.
85
+ */
86
+ readonly recordResolutionOutcome?: ResolutionOutcomeRecorder;
81
87
  }
82
88
  interface RunScopeResolutionStats {
83
89
  readonly filesProcessed: number;
@@ -86,6 +92,7 @@ interface RunScopeResolutionStats {
86
92
  readonly resolve: ResolveStats;
87
93
  readonly referenceEdgesEmitted: number;
88
94
  readonly referenceSkipped: number;
95
+ readonly resolutionOutcomes: readonly ResolutionOutcome[];
89
96
  }
90
97
  export declare function runScopeResolution(input: RunScopeResolutionInput, provider: ScopeResolver): RunScopeResolutionStats;
91
98
  export {};
@@ -94,6 +94,11 @@ function preEmitInheritanceEdges(graph, scopes, nodeLookup) {
94
94
  export function runScopeResolution(input, provider) {
95
95
  const { graph, files } = input;
96
96
  const onWarn = input.onWarn ?? (() => { });
97
+ const resolutionOutcomes = [];
98
+ const recordResolutionOutcome = (outcome) => {
99
+ resolutionOutcomes.push(outcome);
100
+ input.recordResolutionOutcome?.(outcome);
101
+ };
97
102
  const PROF = process.env.PROF_SCOPE_RESOLUTION === '1';
98
103
  const tStart = PROF ? process.hrtime.bigint() : 0n;
99
104
  let fileContents;
@@ -157,6 +162,7 @@ export function runScopeResolution(input, provider) {
157
162
  resolve: { sitesProcessed: 0, referencesEmitted: 0, unresolved: 0 },
158
163
  referenceEdgesEmitted: 0,
159
164
  referenceSkipped: 0,
165
+ resolutionOutcomes,
160
166
  };
161
167
  }
162
168
  const tExtract = PROF ? process.hrtime.bigint() : 0n;
@@ -242,7 +248,9 @@ export function runScopeResolution(input, provider) {
242
248
  const tResolve = PROF ? process.hrtime.bigint() : 0n;
243
249
  // ── Phase 4: emit graph edges (LOAD-BEARING ORDER — see I1) ────────────
244
250
  const handledSites = new Set(preEmittedInheritanceSites);
245
- const receiverExtras = emitReceiverBoundCalls(graph, indexes, parsedFiles, nodeLookup, handledSites, provider, workspaceIndex, readonlyModel);
251
+ const receiverExtras = emitReceiverBoundCalls(graph, indexes, parsedFiles, nodeLookup, handledSites, provider, workspaceIndex, readonlyModel, {
252
+ recordResolutionOutcome,
253
+ });
246
254
  const unresolvedReceiverExtras = provider.emitUnresolvedReceiverEdges !== undefined
247
255
  ? provider.emitUnresolvedReceiverEdges(graph, indexes, parsedFiles, nodeLookup, handledSites, readonlyModel)
248
256
  : 0;
@@ -253,6 +261,7 @@ export function runScopeResolution(input, provider) {
253
261
  resolveAdlCandidates: provider.resolveAdlCandidates,
254
262
  conversionRankFn: provider.conversionRankFn,
255
263
  constraintCompatibility: provider.constraintCompatibility,
264
+ recordResolutionOutcome,
256
265
  });
257
266
  const { emitted, skipped } = emitReferencesViaLookup(graph, indexes, referenceIndex, nodeLookup, handledSites);
258
267
  const importsEmitted = emitImportEdges(graph, indexes.imports, indexes.scopeTree, provider.importEdgeReason);
@@ -274,5 +283,6 @@ export function runScopeResolution(input, provider) {
274
283
  resolve: resolveStats,
275
284
  referenceEdgesEmitted: emitted + receiverExtras + unresolvedReceiverExtras + freeCallExtras,
276
285
  referenceSkipped: skipped,
286
+ resolutionOutcomes,
277
287
  };
278
288
  }
@@ -0,0 +1,24 @@
1
+ import type { Range } from '../../../_shared/index.js';
2
+ export type ResolutionSuppressionReason = 'adl-ordinary-lookup-blocked' | 'conversion-rank-tied' | 'inline-ns-ambiguous' | 'overload-ambiguous' | 'overload-ambiguous-normalization';
3
+ export type ResolutionOutcome = {
4
+ readonly kind: 'resolved';
5
+ readonly targetId: string;
6
+ readonly phase: string;
7
+ readonly filePath: string;
8
+ readonly name: string;
9
+ readonly range: Range;
10
+ } | {
11
+ readonly kind: 'suppressed';
12
+ readonly reason: ResolutionSuppressionReason;
13
+ /**
14
+ * Scope-resolution definition IDs considered by the suppression decision.
15
+ * For `inline-ns-ambiguous` this is currently empty because the
16
+ * qualified namespace resolver returns only an `ambiguous` sentinel.
17
+ */
18
+ readonly candidateIds: readonly string[];
19
+ readonly phase: string;
20
+ readonly filePath: string;
21
+ readonly name: string;
22
+ readonly range: Range;
23
+ };
24
+ export type ResolutionOutcomeRecorder = (outcome: ResolutionOutcome) => void;
@@ -1,6 +1,7 @@
1
1
  import type { KnowledgeGraph } from '../core/graph/types.js';
2
2
  import { CommunityDetectionResult } from '../core/ingestion/community-processor.js';
3
3
  import { ProcessDetectionResult } from '../core/ingestion/process-processor.js';
4
+ import type { ResolutionOutcome } from '../core/ingestion/scope-resolution/resolution-outcome.js';
4
5
  export interface PipelineResult {
5
6
  graph: KnowledgeGraph;
6
7
  /** Absolute path to the repo root — used for lazy file reads during LadybugDB loading */
@@ -9,6 +10,12 @@ export interface PipelineResult {
9
10
  totalFileCount: number;
10
11
  communityResult?: CommunityDetectionResult;
11
12
  processResult?: ProcessDetectionResult;
13
+ /**
14
+ * Additive diagnostics for registry-primary resolution decisions that
15
+ * deliberately suppress edge emission. Empty means no diagnostic was
16
+ * produced; graph edge semantics are unchanged.
17
+ */
18
+ resolutionOutcomes: readonly ResolutionOutcome[];
12
19
  /**
13
20
  * True if the parse phase spawned a worker pool for this run. False means
14
21
  * the sequential fallback handled every chunk. Primarily a test affordance
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.6-rc.48",
3
+ "version": "1.6.6-rc.49",
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",