gitnexus 1.6.11-rc.3 → 1.6.11-rc.4
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.
|
@@ -3000,7 +3000,22 @@ export class LocalBackend {
|
|
|
3000
3000
|
else if (isQualified) {
|
|
3001
3001
|
// Parenthesised because the kind filter below is appended with AND, which
|
|
3002
3002
|
// binds tighter than OR.
|
|
3003
|
-
|
|
3003
|
+
// #3074: a repo-relative file path (e.g. "supabase/functions/_shared/crypto.ts")
|
|
3004
|
+
// is the most natural way to name a File and is exactly what `target.filePath`
|
|
3005
|
+
// reports, but the old clause only matched `n.id` (= "File:<path>") or basename
|
|
3006
|
+
// `n.name`, so the same path the graph stores never resolved. Also match the
|
|
3007
|
+
// repo-relative `n.filePath` exactly and via an anchored suffix (segment-boundary
|
|
3008
|
+
// "ENDS WITH $suffix" where suffix is "/"+path) so "a.ts" does not spuriously
|
|
3009
|
+
// match "mylib/a.ts" — same anchoring used in detect_changes (#2915).
|
|
3010
|
+
const suffix = pathSuffixOf(name);
|
|
3011
|
+
// File-path terms must be scoped to File nodes — n.filePath is shared by
|
|
3012
|
+
// every symbol in the file, so an unlabeled predicate would turn
|
|
3013
|
+
// "src/actions.ts" into every symbol in that file (bot review #3084 P1).
|
|
3014
|
+
// LadybugDB does not allow label tests in WHERE (n:File), so scope via
|
|
3015
|
+
// id prefix — File nodes are `File:<path>`.
|
|
3016
|
+
whereClause = `WHERE (n.id = $symName OR n.name = $symName OR (n.id STARTS WITH $filePrefix AND (n.filePath = $symName OR n.filePath ENDS WITH $suffix)))`;
|
|
3017
|
+
queryParams.suffix = suffix;
|
|
3018
|
+
queryParams.filePrefix = 'File:';
|
|
3004
3019
|
}
|
|
3005
3020
|
else {
|
|
3006
3021
|
whereClause = `WHERE n.name = $symName`;
|
|
@@ -3079,7 +3094,7 @@ export class LocalBackend {
|
|
|
3079
3094
|
if (rows.length === 0)
|
|
3080
3095
|
return { kind: 'not_found' };
|
|
3081
3096
|
// Normalise row shape across object / tuple returns from LadybugDB.
|
|
3082
|
-
|
|
3097
|
+
let normalized = rows.map((r) => ({
|
|
3083
3098
|
id: (r.id ?? r[0]),
|
|
3084
3099
|
name: (r.name ?? r[1]),
|
|
3085
3100
|
type: (r.type ?? r[2] ?? ''),
|
|
@@ -3088,6 +3103,14 @@ export class LocalBackend {
|
|
|
3088
3103
|
endLine: (r.endLine ?? r[5]),
|
|
3089
3104
|
...(include_content ? { content: (r.content ?? r[6]) } : {}),
|
|
3090
3105
|
}));
|
|
3106
|
+
// An exact File path wins over anchored suffix candidates. Without this,
|
|
3107
|
+
// `lib/a.ts` and `src/lib/a.ts` both score as File candidates and turn an
|
|
3108
|
+
// otherwise unambiguous exact target into `ambiguous` (#3084 review P2).
|
|
3109
|
+
if (isQualified) {
|
|
3110
|
+
const exactFiles = normalized.filter((candidate) => candidate.id.startsWith('File:') && candidate.filePath === name);
|
|
3111
|
+
if (exactFiles.length > 0)
|
|
3112
|
+
normalized = exactFiles;
|
|
3113
|
+
}
|
|
3091
3114
|
// The COUNT can never legitimately be below the page it accompanies, so a
|
|
3092
3115
|
// value under `normalized.length` means the count leg failed or returned an
|
|
3093
3116
|
// unreadable shape. Keep the window size as the floor — reporting zero would
|
|
@@ -4942,6 +4965,7 @@ export class LocalBackend {
|
|
|
4942
4965
|
direction: params.direction,
|
|
4943
4966
|
suggestion,
|
|
4944
4967
|
recoverySuggestion,
|
|
4968
|
+
undetermined: true,
|
|
4945
4969
|
});
|
|
4946
4970
|
return pdgErr;
|
|
4947
4971
|
}
|
|
@@ -4949,7 +4973,7 @@ export class LocalBackend {
|
|
|
4949
4973
|
error: message,
|
|
4950
4974
|
target: { name: params.target },
|
|
4951
4975
|
direction: params.direction,
|
|
4952
|
-
impactedCount:
|
|
4976
|
+
impactedCount: null,
|
|
4953
4977
|
risk: 'UNKNOWN',
|
|
4954
4978
|
suggestion,
|
|
4955
4979
|
...(recoverySuggestion ? { recoverySuggestion } : {}),
|
|
@@ -5036,6 +5060,7 @@ export class LocalBackend {
|
|
|
5036
5060
|
`(single-repo PDG impact). Remove them or use mode:'callgraph' for cross-repo fan-out.`,
|
|
5037
5061
|
target: crossDepthTarget,
|
|
5038
5062
|
direction,
|
|
5063
|
+
undetermined: true,
|
|
5039
5064
|
});
|
|
5040
5065
|
return pdgErr;
|
|
5041
5066
|
}
|
|
@@ -5091,12 +5116,17 @@ export class LocalBackend {
|
|
|
5091
5116
|
error: `Target '${missing}' not found`,
|
|
5092
5117
|
target: notFoundTarget,
|
|
5093
5118
|
direction,
|
|
5119
|
+
undetermined: true,
|
|
5094
5120
|
})
|
|
5095
5121
|
: {
|
|
5096
5122
|
error: `Target '${missing}' not found`,
|
|
5097
5123
|
target: { name: target },
|
|
5098
5124
|
direction,
|
|
5099
|
-
|
|
5125
|
+
// #3074 follow-up: do not ship a normal-shaped 0/UNKNOWN blast radius
|
|
5126
|
+
// alongside the error — it reads as a real "nothing depends on this"
|
|
5127
|
+
// answer. Null marks UNDETERMINED (same as the ambiguous path) so a
|
|
5128
|
+
// consumer testing `impactedCount === 0` cannot misread a miss as safe.
|
|
5129
|
+
impactedCount: null,
|
|
5100
5130
|
risk: 'UNKNOWN',
|
|
5101
5131
|
};
|
|
5102
5132
|
}
|
|
@@ -31,8 +31,10 @@ export declare function splitCalleeIds(raw: unknown): string[];
|
|
|
31
31
|
* Bump on any breaking change to the PDG result fields.
|
|
32
32
|
* v2: `startLine` in the result is now 1-based display (#2380), matching the
|
|
33
33
|
* context/query/impact tools (was 0-based).
|
|
34
|
+
* v3: error envelopes use `impactedCount: null` when a target cannot be resolved
|
|
35
|
+
* (#3074), so consumers cannot read a miss as a measured zero.
|
|
34
36
|
*/
|
|
35
|
-
export declare const PDG_RESULT_VERSION:
|
|
37
|
+
export declare const PDG_RESULT_VERSION: 3;
|
|
36
38
|
/** A reachable dependence block resolved to its source statement. */
|
|
37
39
|
export interface PdgStatement {
|
|
38
40
|
/** 1-based source line where the statement's block starts. */
|
|
@@ -229,7 +231,7 @@ export interface PdgInterproceduralImpact {
|
|
|
229
231
|
export interface PdgImpactBaseResult extends PdgImpactParityFields {
|
|
230
232
|
mode: 'pdg';
|
|
231
233
|
/** Contract version of the mode:'pdg' impact result shape; bump on any breaking change to the PDG result fields. */
|
|
232
|
-
pdgResultVersion:
|
|
234
|
+
pdgResultVersion: 3;
|
|
233
235
|
target: PdgImpactTarget;
|
|
234
236
|
direction: 'upstream' | 'downstream';
|
|
235
237
|
impactedCount: number;
|
|
@@ -296,11 +298,11 @@ export interface PdgImpactDegradedResult extends PdgImpactBaseResult {
|
|
|
296
298
|
export interface PdgImpactErrorResult {
|
|
297
299
|
mode?: 'pdg';
|
|
298
300
|
/** Contract version of the mode:'pdg' impact result shape; bump on any breaking change to the PDG result fields. */
|
|
299
|
-
pdgResultVersion:
|
|
301
|
+
pdgResultVersion: 3;
|
|
300
302
|
error: string;
|
|
301
303
|
target: PdgImpactTarget;
|
|
302
304
|
direction: 'upstream' | 'downstream';
|
|
303
|
-
impactedCount:
|
|
305
|
+
impactedCount: number | null;
|
|
304
306
|
risk: 'UNKNOWN';
|
|
305
307
|
suggestion?: string;
|
|
306
308
|
recoverySuggestion?: string;
|
|
@@ -313,6 +315,8 @@ export declare function makePdgImpactErrorResult(input: {
|
|
|
313
315
|
mode?: 'pdg';
|
|
314
316
|
suggestion?: string;
|
|
315
317
|
recoverySuggestion?: string;
|
|
318
|
+
/** True when analysis did not obtain a measured impact count. */
|
|
319
|
+
undetermined?: boolean;
|
|
316
320
|
}): PdgImpactErrorResult;
|
|
317
321
|
export declare function isPdgDegradedLayerStatus(layer: PdgLayerStatus): layer is PdgDegradedLayerStatus;
|
|
318
322
|
export declare function makePdgLayerDegradedResult(input: {
|
|
@@ -109,8 +109,10 @@ export function splitCalleeIds(raw) {
|
|
|
109
109
|
* Bump on any breaking change to the PDG result fields.
|
|
110
110
|
* v2: `startLine` in the result is now 1-based display (#2380), matching the
|
|
111
111
|
* context/query/impact tools (was 0-based).
|
|
112
|
+
* v3: error envelopes use `impactedCount: null` when a target cannot be resolved
|
|
113
|
+
* (#3074), so consumers cannot read a miss as a measured zero.
|
|
112
114
|
*/
|
|
113
|
-
export const PDG_RESULT_VERSION =
|
|
115
|
+
export const PDG_RESULT_VERSION = 3;
|
|
114
116
|
/**
|
|
115
117
|
* FU-B-2 intra-block def→use line walk. Given a block's self REACHING_DEF
|
|
116
118
|
* def→use line PAIRS (every `defLine → useLine` step decoded from the edge
|
|
@@ -469,7 +471,10 @@ export function makePdgImpactErrorResult(input) {
|
|
|
469
471
|
error: input.error,
|
|
470
472
|
target: input.target,
|
|
471
473
|
direction: input.direction,
|
|
472
|
-
|
|
474
|
+
// #3074 follow-up + P2 review: an unmeasured PDG result must not ship a
|
|
475
|
+
// confident-looking 0 blast radius. null marks UNDETERMINED, so a consumer
|
|
476
|
+
// testing === 0 cannot misread a miss or failed query as safe.
|
|
477
|
+
impactedCount: input.undetermined ? null : 0,
|
|
473
478
|
risk: 'UNKNOWN',
|
|
474
479
|
...(input.suggestion ? { suggestion: input.suggestion } : {}),
|
|
475
480
|
...(input.recoverySuggestion ? { recoverySuggestion: input.recoverySuggestion } : {}),
|
package/dist/mcp/tools.js
CHANGED
|
@@ -425,7 +425,7 @@ MODE (opt-in): "callgraph" (default) walks symbol→symbol edges (CALLS/IMPORTS/
|
|
|
425
425
|
|
|
426
426
|
STATEMENT-ANCHORED PDG SLICE: with mode:'pdg', pass "line" (1-based source line within the target symbol) to seed the dependence slice on the statement at that line and return what depends on it in affectedStatements (line + text). Inter-procedural symbols are still reported through interproceduralByDepth/pdgInterprocedural and the compatibility byDepth bucket. Without "line", pdg returns whole-symbol inter-procedural reach plus local whole-symbol PDG diagnostics.
|
|
427
427
|
|
|
428
|
-
PDG OUTPUT CONTRACT: every mode:'pdg' result (success, empty, degraded, or error) carries pdgResultVersion:
|
|
428
|
+
PDG OUTPUT CONTRACT: every mode:'pdg' result (success, empty, degraded, or error) carries pdgResultVersion:3 — a stable discriminator for external consumers that bumps on any breaking change to the PDG result shape (distinct from the DB schema version). Successful PDG results include mode:'pdg', a full target envelope (id/name/type/filePath), affectedStatements, affectedStatementCount, interproceduralByDepth/pdgInterprocedural for cross-function reach, compatibility byDepth/byDepthCounts, risk:'UNKNOWN', and a note describing the unified contract. Degraded PDG results (no-layer, sub-layer-missing, unknown) keep mode:'pdg', pdgResultVersion:3, target metadata when the target resolves, risk:'UNKNOWN', note/remediation, and empty byDepth parity fields — never a false-safe zero. If depth and limit both bound the slice, truncatedByReasons reports both causes while truncatedBy remains scalar. Return-value-ascent coverage is published structurally at pdgEvidence.ascent — present iff the inter-procedural descent ran, including on an empty slice — with referencesScanned (DISTINCT callees scanned for a CALL_SUMMARY: a distinct-id tally, not a call-site count — two call sites to the same callee count once), returnFlowFound (whether the ascent fired anywhere in the slice), undecodableSummaryCount, examinedComplete (whether that scan covered every callee the index recorded a resolved id for on the visited blocks), incompleteReasons ('traversal-truncated' | 'callee-list-capped' | 'callee-ids-unrecorded'), and callSummaryLayerPresent. Read callSummaryLayerPresent FIRST: false ⇒ a pre-CALL_SUMMARY index, so {referencesScanned:N>0, returnFlowFound:false} is self-consistent and says nothing about the callees — the scan ran, but no layer existed in which a return-flow could be recorded (remedy: re-run gitnexus analyze --pdg). Branch on those fields; the note narrates the same facts in prose for humans and is not a stable contract.
|
|
429
429
|
|
|
430
430
|
WHEN TO USE: Before making code changes — especially refactoring, renaming, or modifying shared code. Shows what would break.
|
|
431
431
|
AFTER THIS: Review d=1 items (WILL BREAK). Use context() on high-risk symbols.
|
package/package.json
CHANGED