mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
fix(mcp): tolerate adapter-materialized line:0 in impact callgraph mode (#2279)
Some MCP client/agent adapters serialize an omitted optional numeric
field as `0` rather than dropping it, so callgraph `impact` calls arrive
carrying a spurious `line: 0`. `line` is a PDG-only statement anchor and
is meaningless on the callgraph path, so the backend rejected the call
("'line' is only supported with mode:'pdg'") and strict clients rejected
it client-side against the advertised `minimum: 1`.
Treat a literal `line: 0` as omitted in `_impactImpl` when mode !== 'pdg'
and let the normal symbol→symbol BFS run. The coercion is deliberately
narrow: only the literal 0, only on the callgraph path. A genuine
positive `line` on callgraph still errors (real mode mistake), negative/
fractional values still error, and pdg mode is untouched — `line: 0`
there is still rejected (there is no 1-based source line 0 to anchor on).
Regression tests pin the full matrix: callgraph + line:0 runs the BFS and
is byte-identical to omitting line; pdg + line:0 still errors; positive
line on callgraph still errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
698f5efc82
commit
cc761ce7cd
2 changed files with 60 additions and 3 deletions
|
|
@ -4490,9 +4490,21 @@ export class LocalBackend {
|
|||
}
|
||||
const mode = modeResult.mode;
|
||||
|
||||
// #2279: some MCP client/agent adapters serialize an *omitted* optional
|
||||
// numeric field as `0` rather than dropping it, so callgraph calls arrive
|
||||
// carrying a spurious `line: 0`. `line` is meaningless on the callgraph path
|
||||
// (the symbol→symbol BFS has no statement notion), so treat a literal `0`
|
||||
// there as omitted and let the normal traversal run. The coercion is
|
||||
// deliberately narrow — only the literal `0`, only when mode !== 'pdg':
|
||||
// a genuine positive `line` on callgraph still errors (real mode mistake),
|
||||
// negative/fractional values still error, and pdg mode is untouched (the
|
||||
// normalization is an identity there, so `line: 0` is still rejected below —
|
||||
// there is no 1-based source line `0` to anchor on).
|
||||
const effectiveLine = mode !== 'pdg' && params.line === 0 ? undefined : params.line;
|
||||
|
||||
// `line` is a PDG-only statement anchor. Reject it on the callgraph path
|
||||
// rather than silently ignore (the symbol→symbol BFS has no statement notion).
|
||||
if (params.line !== undefined && mode !== 'pdg') {
|
||||
if (effectiveLine !== undefined && mode !== 'pdg') {
|
||||
return {
|
||||
error: `Parameter 'line' is only supported with mode:'pdg' (it anchors the dependence slice on a statement). Remove it or set mode:'pdg'.`,
|
||||
target: { name: params.target },
|
||||
|
|
@ -4503,8 +4515,8 @@ export class LocalBackend {
|
|||
}
|
||||
// A provided `line` must be a positive integer.
|
||||
if (
|
||||
params.line !== undefined &&
|
||||
(!Number.isInteger(params.line) || (params.line as number) < 1)
|
||||
effectiveLine !== undefined &&
|
||||
(!Number.isInteger(effectiveLine) || (effectiveLine as number) < 1)
|
||||
) {
|
||||
// Line param fails validation before target resolution → partial-but-typed
|
||||
// target on the pdg path (typed PdgImpactTarget, not an inline literal).
|
||||
|
|
|
|||
|
|
@ -1642,6 +1642,51 @@ describe('LocalBackend impact mode (KTD1/KTD5/KTD12)', () => {
|
|||
},
|
||||
);
|
||||
|
||||
// #2279: some MCP client/agent adapters serialize an *omitted* optional
|
||||
// numeric field as `0`. On the callgraph path `line` is meaningless, so a
|
||||
// literal `line: 0` must be tolerated as omitted (NOT the PDG-only error) and
|
||||
// route to the normal BFS — distinct from a genuine positive `line` (above),
|
||||
// which stays a hard error.
|
||||
it.each([['callgraph'], [undefined]])(
|
||||
'mode:%j + adapter-materialized line:0 is treated as omitted and runs the BFS (#2279)',
|
||||
async (mode) => {
|
||||
resolveSingleTarget();
|
||||
const bfsSpy = vi.spyOn(backend as any, '_runImpactBFS');
|
||||
const result = await backend.callTool('impact', {
|
||||
target: 'main',
|
||||
direction: 'upstream',
|
||||
mode: mode as any,
|
||||
line: 0,
|
||||
});
|
||||
// No PDG-only error, no positive-integer error — line:0 is swallowed.
|
||||
expect(result.error ?? '').not.toMatch(/'line' is only supported with mode:'pdg'/);
|
||||
expect(result.error ?? '').not.toMatch(/'line' must be a positive integer/);
|
||||
expect(result.target).toBeDefined();
|
||||
expect(bfsSpy).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
);
|
||||
|
||||
it("mode:'callgraph'/undefined + line:0 is byte-identical to omitting line (#2279)", async () => {
|
||||
resolveSingleTarget();
|
||||
const omitted = await backend.callTool('impact', { target: 'main', direction: 'upstream' });
|
||||
const callgraphZero = await backend.callTool('impact', {
|
||||
target: 'main',
|
||||
direction: 'upstream',
|
||||
mode: 'callgraph',
|
||||
line: 0,
|
||||
});
|
||||
const undefZero = await backend.callTool('impact', {
|
||||
target: 'main',
|
||||
direction: 'upstream',
|
||||
mode: undefined,
|
||||
line: 0,
|
||||
});
|
||||
// The normalization must leave the callgraph result indistinguishable from a
|
||||
// call that never carried `line` — the spurious 0 must not leak into output.
|
||||
expect(callgraphZero).toEqual(omitted);
|
||||
expect(undefZero).toEqual(omitted);
|
||||
});
|
||||
|
||||
it.each([[0], [-1], [1.5]])(
|
||||
"mode:'pdg' + non-positive-integer line %j → structured {error}, never routed to traversal",
|
||||
async (badLine) => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue