gitnexus 1.6.8-rc.56 → 1.6.8-rc.57

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.
Files changed (88) hide show
  1. package/dist/_shared/graph/types.d.ts +17 -1
  2. package/dist/_shared/graph/types.d.ts.map +1 -1
  3. package/dist/cli/ai-context.js +3 -1
  4. package/dist/cli/eval-server.d.ts +8 -0
  5. package/dist/cli/eval-server.js +242 -2
  6. package/dist/cli/index.js +2 -0
  7. package/dist/cli/tool.d.ts +2 -0
  8. package/dist/cli/tool.js +11 -0
  9. package/dist/core/group/cross-impact.d.ts +2 -1
  10. package/dist/core/group/cross-impact.js +5 -1
  11. package/dist/core/incremental/subgraph-extract.js +7 -1
  12. package/dist/core/ingestion/cfg/emit.d.ts +68 -3
  13. package/dist/core/ingestion/cfg/emit.js +173 -13
  14. package/dist/core/ingestion/cfg/reaching-def-reason-codec.d.ts +106 -0
  15. package/dist/core/ingestion/cfg/reaching-def-reason-codec.js +133 -0
  16. package/dist/core/ingestion/cfg/reaching-defs.d.ts +14 -0
  17. package/dist/core/ingestion/cfg/reaching-defs.js +28 -0
  18. package/dist/core/ingestion/cfg/types.d.ts +43 -0
  19. package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.js +3 -1
  20. package/dist/core/ingestion/cfg/visitors/call-site-harvest.d.ts +44 -1
  21. package/dist/core/ingestion/cfg/visitors/call-site-harvest.js +47 -1
  22. package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +3 -1
  23. package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +116 -6
  24. package/dist/core/ingestion/cfg/visitors/dart-harvest.js +423 -52
  25. package/dist/core/ingestion/cfg/visitors/go-harvest.js +12 -20
  26. package/dist/core/ingestion/cfg/visitors/java-harvest.js +12 -2
  27. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +89 -6
  28. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +220 -6
  29. package/dist/core/ingestion/cfg/visitors/php-harvest.js +12 -2
  30. package/dist/core/ingestion/cfg/visitors/python-harvest.d.ts +40 -3
  31. package/dist/core/ingestion/cfg/visitors/python-harvest.js +153 -15
  32. package/dist/core/ingestion/cfg/visitors/ruby-harvest.d.ts +83 -4
  33. package/dist/core/ingestion/cfg/visitors/ruby-harvest.js +180 -12
  34. package/dist/core/ingestion/cfg/visitors/rust-harvest.d.ts +164 -5
  35. package/dist/core/ingestion/cfg/visitors/rust-harvest.js +332 -6
  36. package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +92 -6
  37. package/dist/core/ingestion/cfg/visitors/swift-harvest.js +189 -15
  38. package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +6 -1
  39. package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +37 -13
  40. package/dist/core/ingestion/pipeline-phases/call-summaries.d.ts +26 -0
  41. package/dist/core/ingestion/pipeline-phases/call-summaries.js +46 -0
  42. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  43. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  44. package/dist/core/ingestion/pipeline.d.ts +5 -0
  45. package/dist/core/ingestion/pipeline.js +2 -1
  46. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +75 -0
  47. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +67 -0
  48. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -2
  49. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +14 -2
  50. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +6 -1
  51. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +13 -1
  52. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +6 -0
  53. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +7 -0
  54. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +5 -0
  55. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +25 -16
  56. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +8 -0
  57. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +6 -0
  58. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +10 -0
  59. package/dist/core/ingestion/scope-resolution/pipeline/run.js +65 -8
  60. package/dist/core/ingestion/taint/call-summary-codec.d.ts +75 -0
  61. package/dist/core/ingestion/taint/call-summary-codec.js +128 -0
  62. package/dist/core/ingestion/taint/call-summary-emit.d.ts +43 -0
  63. package/dist/core/ingestion/taint/call-summary-emit.js +70 -0
  64. package/dist/core/ingestion/taint/call-summary-harvest.d.ts +77 -0
  65. package/dist/core/ingestion/taint/call-summary-harvest.js +179 -0
  66. package/dist/core/ingestion/taint/call-summary-model.d.ts +47 -0
  67. package/dist/core/ingestion/taint/call-summary-model.js +25 -0
  68. package/dist/core/ingestion/taint/emit.d.ts +2 -1
  69. package/dist/core/ingestion/taint/emit.js +7 -3
  70. package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +25 -1
  71. package/dist/core/ingestion/taint/summary-harvest-driver.js +64 -3
  72. package/dist/core/lbug/csv-generator.d.ts +7 -2
  73. package/dist/core/lbug/csv-generator.js +9 -2
  74. package/dist/core/lbug/lbug-adapter.d.ts +14 -0
  75. package/dist/core/lbug/lbug-adapter.js +59 -6
  76. package/dist/core/lbug/schema.d.ts +1 -1
  77. package/dist/core/lbug/schema.js +2 -0
  78. package/dist/core/run-analyze.js +22 -1
  79. package/dist/mcp/local/local-backend.d.ts +35 -0
  80. package/dist/mcp/local/local-backend.js +419 -33
  81. package/dist/mcp/local/pdg-impact.d.ts +384 -0
  82. package/dist/mcp/local/pdg-impact.js +1972 -0
  83. package/dist/mcp/tools.d.ts +1 -0
  84. package/dist/mcp/tools.js +22 -2
  85. package/dist/storage/parse-cache.js +20 -8
  86. package/dist/storage/repo-manager.d.ts +25 -2
  87. package/dist/storage/repo-manager.js +13 -2
  88. package/package.json +1 -1
@@ -46,6 +46,9 @@ export type NodeProperties = {
46
46
  errorKeys?: string[];
47
47
  middleware?: string[];
48
48
  text?: string;
49
+ /** BasicBlock: space-joined leaf callee names invoked in the block — the
50
+ * statement-precise inter-procedural reach substrate for impact mode. */
51
+ callees?: string;
49
52
  [key: string]: unknown;
50
53
  };
51
54
  export type RelationshipType = 'CONTAINS' | 'CALLS' | 'INHERITS' | 'METHOD_OVERRIDES' | 'METHOD_IMPLEMENTS' | 'IMPORTS' | 'USES' | 'DEFINES' | 'DECORATES' | 'IMPLEMENTS' | 'EXTENDS' | 'HAS_METHOD' | 'HAS_PROPERTY' | 'ACCESSES' | 'MEMBER_OF' | 'STEP_IN_PROCESS' | 'HANDLES_ROUTE' | 'FETCHES' | 'HANDLES_TOOL' | 'ENTRY_POINT_OF' | 'WRAPS' | 'QUERIES'
@@ -93,7 +96,20 @@ export type RelationshipType = 'CONTAINS' | 'CALLS' | 'INHERITS' | 'METHOD_OVERR
93
96
  * removing it later is a breaking schema change — and it is deliberately
94
97
  * excluded from `VALID_RELATION_TYPES` so it never enters impact-style
95
98
  * symbol-space traversal (same posture as the taint substrate edges). */
96
- | 'POST_DOMINATE';
99
+ | 'POST_DOMINATE'
100
+ /** Per-callee dependence SUMMARY edge (PDG FU-C): a self-loop on a
101
+ * Function/Method/Constructor node carrying that callee's RETURN-VALUE
102
+ * ASCENT — which formal-parameter indices flow to the function's return
103
+ * value, encoded as a versioned bitset in the relation's existing `reason`
104
+ * column (the same single-channel pattern `CFG`/`REACHING_DEF`/`CDG` use,
105
+ * since the lone `CodeRelation` table has no dedicated label column). A
106
+ * later consumer phase lets an interprocedural slice ascend a callee's
107
+ * return effect into the caller continuation. Like the taint substrate
108
+ * edges it is an internal PDG-engine edge: deliberately EXCLUDED from
109
+ * `VALID_RELATION_TYPES` and the web schema so it never leaks into
110
+ * callgraph-style impact/relationship surfaces. Emitted only under `--pdg`;
111
+ * a default analyze emits zero. */
112
+ | 'CALL_SUMMARY';
97
113
  export interface GraphNode {
98
114
  id: string;
99
115
  label: NodeLabel;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/graph/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/graph/types.ts"],"names":[],"mappings":"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"}
@@ -120,7 +120,9 @@ This project is indexed by GitNexus as **${projectName}**${noStats ? '' : ` (${s
120
120
 
121
121
  ## Always Do
122
122
 
123
- - **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run \`impact({target: "symbolName", direction: "upstream"})\` and report the blast radius (direct callers, affected processes, risk level) to the user.
123
+ - **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run \`impact({target: "symbolName", direction: "upstream"})\` and report the blast radius (direct callers, affected processes, risk level) to the user.${hasPdg
124
+ ? ` For unified PDG impact, add \`mode: "pdg"\` with optional \`line: <N>\` — it returns statement-level \`affectedStatements\` over CDG + REACHING_DEF and inter-procedural symbols in \`interproceduralByDepth\`/\`byDepth\`; no-layer/degraded PDG results are UNKNOWN-risk notes (\`--pdg\` layer).`
125
+ : ''}
124
126
  - **MUST run \`detect_changes()\` before committing** to verify your changes only affect expected symbols and execution flows. For regression review, compare against the default branch: \`detect_changes({scope: "compare", base_ref: ${JSON.stringify(markdownSafeBranch(defaultBranch))}})\`.
125
127
  - **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
126
128
  - When exploring unfamiliar code, use \`query({search_query: "concept"})\` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
@@ -50,5 +50,13 @@ export declare function formatListReposResult(result: {
50
50
  repositories: RepoListing[];
51
51
  pagination?: ListReposPagination;
52
52
  }): string;
53
+ export declare function getNextStepHint(toolName: string, result?: any): string;
53
54
  export declare function evalServerCommand(options?: EvalServerOptions): Promise<void>;
55
+ /**
56
+ * Tools the eval-server exposes over HTTP — the read-only query surface the
57
+ * banner advertises. `LocalBackend.callTool` ALSO dispatches write-side / heavier
58
+ * tools (rename, shape_check, tool_map, …); the allowlist keeps a stray
59
+ * `POST /tool/<name>` from reaching those through this Docker/eval-harness server.
60
+ */
61
+ export declare const EVAL_SERVER_TOOLS: ReadonlySet<string>;
54
62
  export declare const MAX_BODY_SIZE: number;
@@ -29,6 +29,7 @@
29
29
  * POST /shutdown — Graceful shutdown.
30
30
  */
31
31
  import http from 'http';
32
+ import crypto from 'node:crypto';
32
33
  import { isIPv4, isIPv6 } from 'node:net';
33
34
  import { writeSync } from 'node:fs';
34
35
  import { LocalBackend, } from '../mcp/local/local-backend.js';
@@ -146,6 +147,14 @@ export function formatContextResult(result) {
146
147
  }
147
148
  return lines.join('\n').trim();
148
149
  }
150
+ function formatTruncationSuffix(result) {
151
+ const label = Array.isArray(result.truncatedByReasons)
152
+ ? result.truncatedByReasons.join(', ')
153
+ : typeof result.truncatedBy === 'string'
154
+ ? result.truncatedBy
155
+ : '';
156
+ return label ? ` (by ${label})` : '';
157
+ }
149
158
  export function formatImpactResult(result) {
150
159
  if (result.error) {
151
160
  const suggestion = result.suggestion ? `\nSuggestion: ${result.suggestion}` : '';
@@ -160,6 +169,25 @@ export function formatImpactResult(result) {
160
169
  // mirroring formatContextResult, so the real impact under whichever symbol the
161
170
  // caller meant is visible on the text surface, not just in the JSON.
162
171
  if (result.status === 'ambiguous') {
172
+ if (result.mode === 'pdg') {
173
+ const shown = result.candidates?.length ?? 0;
174
+ const totalCandidates = result.totalCandidates ?? shown;
175
+ const countPhrase = totalCandidates > shown
176
+ ? `${totalCandidates} symbols (showing ${shown})`
177
+ : `${totalCandidates} symbols`;
178
+ const lines = [
179
+ `${target?.name || '?'}: AMBIGUOUS — ${countPhrase} share this name. ` +
180
+ `PDG impact was not computed until the target is disambiguated. ` +
181
+ `Use --uid, file_path, or kind for one authoritative PDG result.`,
182
+ ];
183
+ if (result.message)
184
+ lines.push(String(result.message));
185
+ for (const c of result.candidates || []) {
186
+ const score = typeof c.score === 'number' ? ` score ${c.score}` : '';
187
+ lines.push(` ${c.kind} ${c.name} → ${c.filePath}:${c.line || '?'}${score} (uid: ${c.uid})`);
188
+ }
189
+ return lines.join('\n');
190
+ }
163
191
  // #2129 review F11 — report the FULL match count (`totalCandidates`), not the
164
192
  // truncated `candidates[]` length; note when the candidate list is capped.
165
193
  const shown = result.candidates?.length ?? 0;
@@ -180,6 +208,176 @@ export function formatImpactResult(result) {
180
208
  }
181
209
  return lines.join('\n');
182
210
  }
211
+ // ─── PDG mode (mode:'pdg') ────────────────────────────────────────────
212
+ // KTD8 presentation half. PDG results are intra-procedural Program
213
+ // Dependence Graph blast radii: the single collapsed `byDepth[1]` bucket
214
+ // has NO call-hop depth meaning (block-hops ≠ call-hops), so we must NOT
215
+ // reuse the callgraph "depth N / WILL BREAK (direct)" framing, the
216
+ // callgraph DI/dynamic-dispatch lower-bound copy, or the confident
217
+ // "isolated" zero. A degraded / no-body PDG result is INCONCLUSIVE, not
218
+ // safe-to-refactor — it gets the explicit caveat + remediation, never an
219
+ // empty blast radius. Detect on `mode:'pdg'` (every PDG return path —
220
+ // findings, degradation, no-body, no-dependence — carries it). Ambiguous
221
+ // PDG results carry `status:'ambiguous'` and are handled above; they never
222
+ // reach here.
223
+ if (result.mode === 'pdg') {
224
+ const name = target?.name || '?';
225
+ const appendPdgInterproceduralSymbols = (lines) => {
226
+ const byDepth = result.interproceduralByDepth || result.pdgInterprocedural?.byDepth || result.byDepth || {};
227
+ const byDepthCounts = result.interproceduralByDepthCounts ||
228
+ result.pdgInterprocedural?.byDepthCounts ||
229
+ result.byDepthCounts ||
230
+ {};
231
+ const depthKeys = Array.from(new Set([...Object.keys(byDepthCounts), ...Object.keys(byDepth)]))
232
+ .map((d) => Number(d))
233
+ .filter((d) => Number.isFinite(d))
234
+ .sort((a, b) => a - b);
235
+ const hasReach = depthKeys.some((depth) => {
236
+ const items = byDepth[depth] || byDepth[String(depth)] || [];
237
+ const count = byDepthCounts[depth] ?? byDepthCounts[String(depth)] ?? items.length;
238
+ return count > 0;
239
+ });
240
+ if (!hasReach)
241
+ return false;
242
+ const totalSymbols = result.pdgInterprocedural?.impactedCount ??
243
+ (typeof result.impactedCount === 'number' ? result.impactedCount : 0);
244
+ lines.push('');
245
+ lines.push(`Inter-procedural symbol reach (${totalSymbols}):`);
246
+ for (const depth of depthKeys) {
247
+ const items = byDepth[depth] || byDepth[String(depth)] || [];
248
+ const count = byDepthCounts[depth] ?? byDepthCounts[String(depth)] ?? items.length;
249
+ if (count <= 0)
250
+ continue;
251
+ lines.push(` d=${depth} (${count})`);
252
+ const shown = Math.min(items.length, 12);
253
+ for (const item of items.slice(0, shown)) {
254
+ const flags = [];
255
+ if (item.unresolved)
256
+ flags.push('unresolved');
257
+ if (item.ambiguous)
258
+ flags.push('ambiguous');
259
+ const flagStr = flags.length ? ` [${flags.join(', ')}]` : '';
260
+ lines.push(` ${item.type || ''} ${item.name} → ${item.filePath}${flagStr}`);
261
+ }
262
+ if (count > shown)
263
+ lines.push(` ... and ${count - shown} more`);
264
+ }
265
+ return true;
266
+ };
267
+ // (1) Degradation — the PDG layer (or a sub-layer) is absent/unreadable.
268
+ // `pdgLayer` is the non-'ready' state from `pdgLayerStatus`. Print the
269
+ // honest remediation, NOT a zero/empty blast radius.
270
+ if (result.pdgLayer) {
271
+ const subLayer = result.missingSubLayer
272
+ ? ` (missing sub-layer: ${result.missingSubLayer})`
273
+ : '';
274
+ return (`${name}: PDG impact unavailable — the index has no usable PDG layer ` +
275
+ `[${result.pdgLayer}]${subLayer}. This is NOT "no impact". ` +
276
+ `Re-index with \`gitnexus analyze --pdg\` to build the control/data ` +
277
+ `dependence layer, or use \`--mode callgraph\` for the call-graph blast radius.` +
278
+ (result.note ? `\n${result.note}` : ''));
279
+ }
280
+ // (2) No-body symbol (KTD6) — interface / type alias / abstract / ambient
281
+ // member / one-line declaration with no CFG. Show the caveat, never
282
+ // "isolated / no dependencies".
283
+ if (result.epistemic === 'no-pdg-body') {
284
+ const noBodyLines = [
285
+ `${name}: local PDG slice not applicable to this symbol — it has no PDG body ` +
286
+ `(no control/data dependence edges; e.g. an interface, type alias, ` +
287
+ `abstract/ambient member, or a one-line declaration). This is NOT a ` +
288
+ `confident "no impact".`,
289
+ ];
290
+ appendPdgInterproceduralSymbols(noBodyLines);
291
+ if (result.note)
292
+ noBodyLines.push(result.note);
293
+ return noBodyLines.join('\n');
294
+ }
295
+ // (2b) STATEMENT-ANCHORED SLICE (mode:'pdg' + line). When `criterionLine` is
296
+ // present the result is a statement slice: the seeded line plus the list of
297
+ // dependent statements (`affectedStatements: {line,filePath,text}[]`). Render
298
+ // those statements directly — this IS the useful output of statement mode —
299
+ // rather than the symbol-projection bucket below. Empty cases:
300
+ // - `pdg-no-block-at-line`: the line is blank / a comment / outside the
301
+ // body (no statement block) — print the steering note.
302
+ // - empty `affectedStatements` with `pdg-intra-procedural`: the line has no
303
+ // dependents in this direction — print the steering note.
304
+ // Each non-empty case also surfaces truncation honestly.
305
+ if (typeof result.criterionLine === 'number') {
306
+ const slice = Array.isArray(result.affectedStatements)
307
+ ? result.affectedStatements
308
+ : [];
309
+ const count = typeof result.affectedStatementCount === 'number'
310
+ ? result.affectedStatementCount
311
+ : slice.length;
312
+ // File anchor for the heading — the seeded statement's file (every slice
313
+ // statement shares the function's file). Fall back to the target's file.
314
+ const anchorFile = slice[0]?.filePath || target?.filePath || name;
315
+ if (count === 0 || slice.length === 0) {
316
+ // No statement block at the line, or no dependents in this direction.
317
+ // Print the honest note (pdg-no-block-at-line or the no-dependence note)
318
+ // verbatim — never an empty "isolated" headline.
319
+ const emptySliceLines = [
320
+ `No statements ${direction}-dependent on ${anchorFile}:${result.criterionLine}.`,
321
+ ];
322
+ if (result.truncated) {
323
+ const by = formatTruncationSuffix(result);
324
+ emptySliceLines.push(`⚠️ Truncated${by} — the dependence slice was bounded; deeper PDG-dependent statements may exist.`);
325
+ }
326
+ appendPdgInterproceduralSymbols(emptySliceLines);
327
+ if (result.note)
328
+ emptySliceLines.push(result.note);
329
+ return emptySliceLines.join('\n');
330
+ }
331
+ const slLines = [];
332
+ slLines.push(`Statements ${direction}-dependent on ${anchorFile}:${result.criterionLine} (${count}):`);
333
+ for (const s of slice) {
334
+ const text = typeof s.text === 'string' ? s.text : '';
335
+ slLines.push(` L${s.line}: ${text}`);
336
+ }
337
+ // Truncation honesty — the slice may be a lower bound (depth or per-step
338
+ // LIMIT bound). Surface it the same way the symbol render does.
339
+ if (result.truncated) {
340
+ const by = formatTruncationSuffix(result);
341
+ slLines.push(`⚠️ Truncated${by} — the dependence slice was bounded; deeper PDG-dependent statements may exist.`);
342
+ }
343
+ appendPdgInterproceduralSymbols(slLines);
344
+ if (result.note) {
345
+ slLines.push('');
346
+ slLines.push(`ℹ️ ${result.note}`);
347
+ }
348
+ return slLines.join('\n').trim();
349
+ }
350
+ const pdgLines = [];
351
+ if (!appendPdgInterproceduralSymbols(pdgLines)) {
352
+ pdgLines.push(`${name} (${direction}): no inter-procedural symbols reached. ` +
353
+ `The local PDG statement slice may still report affectedStatements when seeded with line:<N>.`);
354
+ }
355
+ // The assembled note carries the local-PDG framing plus the unified
356
+ // inter-procedural symbol-reach contract; surface it verbatim so the CLI
357
+ // reader sees the same honesty the JSON consumer does.
358
+ if (result.note) {
359
+ pdgLines.push('');
360
+ pdgLines.push(`ℹ️ ${result.note}`);
361
+ }
362
+ else {
363
+ pdgLines.push('');
364
+ pdgLines.push('ℹ️ Program Dependence Graph result — statement reach is reported in affectedStatements and inter-procedural symbol reach in interproceduralByDepth/byDepth.');
365
+ }
366
+ // Honest incompleteness signals (block-attribution + truncation).
367
+ if (result.ambiguousProjectionCount > 0) {
368
+ pdgLines.push(`⚠️ ${result.ambiguousProjectionCount} block(s) could not be attributed to a ` +
369
+ `unique owning symbol (same-line functions) — all colliding symbols are shown.`);
370
+ }
371
+ if (result.unresolvedBlockCount > 0) {
372
+ pdgLines.push(`⚠️ ${result.unresolvedBlockCount} dependence block(s) map to no owning ` +
373
+ `Function/Method/Constructor (top-level statement / closure) — surfaced under their file.`);
374
+ }
375
+ if (result.truncated) {
376
+ const by = formatTruncationSuffix(result);
377
+ pdgLines.push(`⚠️ Truncated${by} — the dependence traversal was bounded; deeper PDG impacts may exist.`);
378
+ }
379
+ return pdgLines.join('\n').trim();
380
+ }
183
381
  if (total === 0) {
184
382
  // #1858 — "isolated" is a confident claim. If an interface / indirection
185
383
  // boundary is on the path, the true count is a lower bound, not zero;
@@ -317,13 +515,20 @@ function formatToolResult(toolName, result) {
317
515
  // ─── Next-Step Hints ──────────────────────────────────────────────────
318
516
  // Guide the agent to the logical next tool call.
319
517
  // Critical for tool chaining: query → context → impact → fix.
320
- function getNextStepHint(toolName) {
518
+ export function getNextStepHint(toolName, result) {
321
519
  switch (toolName) {
322
520
  case 'query':
323
521
  return '\n---\nNext: Pick a symbol above and run gitnexus-context "<name>" to see all its callers, callees, and execution flows.';
324
522
  case 'context':
325
523
  return '\n---\nNext: To check what breaks if you change this, run gitnexus-impact "<name>" upstream';
326
524
  case 'impact':
525
+ if (result?.error ||
526
+ result?.status === 'ambiguous' ||
527
+ result?.mode === 'pdg' ||
528
+ result?.pdgLayer ||
529
+ typeof result?.criterionLine === 'number') {
530
+ return '';
531
+ }
327
532
  return '\n---\nNext: Review d=1 items first (WILL BREAK). Read the source with cat to understand the code, then make your fix.';
328
533
  case 'cypher':
329
534
  return '\n---\nNext: To explore a result symbol in depth, run gitnexus-context "<name>"';
@@ -376,6 +581,13 @@ export async function evalServerCommand(options) {
376
581
  process.exit(0);
377
582
  }, idleTimeoutSec * 1000);
378
583
  }
584
+ // Startup-generated shutdown token: a `POST /shutdown` must present it in the
585
+ // X-Shutdown-Token header. The local agent that launches the server reads it
586
+ // from the GITNEXUS_EVAL_SERVER_SHUTDOWN_TOKEN line on fd 1 (next to the READY
587
+ // signal); a client on another VM under `--host 0.0.0.0` cannot guess it, so it
588
+ // can no longer kill the server. (SIGINT/SIGTERM and the idle timeout still
589
+ // shut down locally without a token.)
590
+ const shutdownToken = crypto.randomBytes(24).toString('hex');
379
591
  const server = http.createServer(async (req, res) => {
380
592
  resetIdleTimer();
381
593
  try {
@@ -388,6 +600,12 @@ export async function evalServerCommand(options) {
388
600
  }
389
601
  // Shutdown
390
602
  if (req.method === 'POST' && req.url === '/shutdown') {
603
+ if (req.headers['x-shutdown-token'] !== shutdownToken) {
604
+ res.setHeader('Content-Type', 'application/json');
605
+ res.writeHead(403);
606
+ res.end(JSON.stringify({ error: 'forbidden: missing or invalid X-Shutdown-Token' }));
607
+ return;
608
+ }
391
609
  res.setHeader('Content-Type', 'application/json');
392
610
  res.writeHead(200);
393
611
  res.end(JSON.stringify({ status: 'shutting_down' }));
@@ -402,6 +620,12 @@ export async function evalServerCommand(options) {
402
620
  const toolMatch = req.url?.match(/^\/tool\/(\w+)$/);
403
621
  if (req.method === 'POST' && toolMatch) {
404
622
  const toolName = toolMatch[1];
623
+ if (!EVAL_SERVER_TOOLS.has(toolName)) {
624
+ res.setHeader('Content-Type', 'text/plain');
625
+ res.writeHead(400);
626
+ res.end(`Error: unsupported tool '${toolName}'. Supported: ${[...EVAL_SERVER_TOOLS].sort().join(', ')}`);
627
+ return;
628
+ }
405
629
  const body = await readBody(req);
406
630
  let args = {};
407
631
  if (body.trim()) {
@@ -418,7 +642,7 @@ export async function evalServerCommand(options) {
418
642
  // Call tool, format result as text, append next-step hint
419
643
  const result = await backend.callTool(toolName, args);
420
644
  const formatted = formatToolResult(toolName, result);
421
- const hint = getNextStepHint(toolName);
645
+ const hint = getNextStepHint(toolName, result);
422
646
  res.setHeader('Content-Type', 'text/plain');
423
647
  res.writeHead(200);
424
648
  res.end(formatted + hint);
@@ -518,6 +742,8 @@ export async function evalServerCommand(options) {
518
742
  try {
519
743
  // Use fd 1 directly — LadybugDB captures process.stdout (#324)
520
744
  writeSync(1, `GITNEXUS_EVAL_SERVER_READY:${displayHost}:${boundPort}\n`);
745
+ // The launching agent reads this to authorize POST /shutdown.
746
+ writeSync(1, `GITNEXUS_EVAL_SERVER_SHUTDOWN_TOKEN:${shutdownToken}\n`);
521
747
  }
522
748
  catch {
523
749
  // stdout may not be available (e.g., broken pipe)
@@ -533,6 +759,20 @@ export async function evalServerCommand(options) {
533
759
  process.on('SIGINT', shutdown);
534
760
  process.on('SIGTERM', shutdown);
535
761
  }
762
+ /**
763
+ * Tools the eval-server exposes over HTTP — the read-only query surface the
764
+ * banner advertises. `LocalBackend.callTool` ALSO dispatches write-side / heavier
765
+ * tools (rename, shape_check, tool_map, …); the allowlist keeps a stray
766
+ * `POST /tool/<name>` from reaching those through this Docker/eval-harness server.
767
+ */
768
+ export const EVAL_SERVER_TOOLS = new Set([
769
+ 'query',
770
+ 'context',
771
+ 'impact',
772
+ 'cypher',
773
+ 'detect_changes',
774
+ 'list_repos',
775
+ ]);
536
776
  export const MAX_BODY_SIZE = 1024 * 1024; // 1MB
537
777
  function readBody(req) {
538
778
  return new Promise((resolve, reject) => {
package/dist/cli/index.js CHANGED
@@ -224,6 +224,8 @@ program
224
224
  .command('impact [target]')
225
225
  .description('Blast radius analysis: what breaks if you change a symbol')
226
226
  .option('-d, --direction <dir>', 'upstream (dependants) or downstream (dependencies)', 'upstream')
227
+ .option('--mode <mode>', 'Engine: callgraph (default) or pdg (opt-in, intra-procedural; needs analyze --pdg)', 'callgraph')
228
+ .option('--line <number>', '1-based source line — PDG-only statement anchor (--mode pdg): slice the dependence from the statement at this line and show what depends on it')
227
229
  .option('-r, --repo <name>', 'Target repository')
228
230
  .option('--branch <name>', 'Scope to a specific branch index (multi-branch repos)')
229
231
  .option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
@@ -31,6 +31,8 @@ export declare function contextCommand(name: string, options?: {
31
31
  }): Promise<void>;
32
32
  export declare function impactCommand(target?: string, options?: {
33
33
  direction?: string;
34
+ mode?: string;
35
+ line?: string;
34
36
  repo?: string;
35
37
  branch?: string;
36
38
  uid?: string;
package/dist/cli/tool.js CHANGED
@@ -120,12 +120,23 @@ export async function impactCommand(target, options) {
120
120
  const rawOffset = parseInt(options?.offset ?? '', 10);
121
121
  const parsedLimit = Number.isFinite(rawLimit) ? rawLimit : undefined;
122
122
  const parsedOffset = Number.isFinite(rawOffset) ? rawOffset : undefined;
123
+ // `--line` is a PDG-only statement anchor (1-based source line). Parse it to
124
+ // an integer when provided and thread it ONLY when present, so the backend's
125
+ // line-without-pdg / non-positive-integer validation fires on the real value
126
+ // rather than on a silently-dropped flag. A non-numeric `--line` parses to
127
+ // NaN, which the backend rejects as a non-positive integer (loud, not silent).
128
+ const parsedLine = options?.line !== undefined ? parseInt(options.line, 10) : undefined;
123
129
  const result = await backend.callTool('impact', {
124
130
  target: target || undefined,
125
131
  target_uid: options?.uid,
126
132
  file_path: options?.file,
127
133
  kind: options?.kind,
128
134
  direction: options?.direction || 'upstream',
135
+ // Forward the engine selector; backend validates the enum (callgraph/pdg)
136
+ // and treats the default 'callgraph' identically to an omitted mode.
137
+ mode: options?.mode,
138
+ // PDG-only statement anchor — forwarded only when --line was given.
139
+ ...(parsedLine !== undefined ? { line: parsedLine } : {}),
129
140
  maxDepth: options?.depth ? parseInt(options.depth, 10) : undefined,
130
141
  includeTests: options?.includeTests ?? false,
131
142
  repo: options?.repo,
@@ -2,7 +2,7 @@
2
2
  * Cross-repo impact (Phase 1 local walk + Phase 2 bridge fan-out).
3
3
  * All bridge Cypher for this feature lives in this module.
4
4
  */
5
- import type { GroupImpactResult } from './types.js';
5
+ import type { CrossRepoImpact, GroupImpactResult } from './types.js';
6
6
  import type { GroupToolPort } from './service.js';
7
7
  /** Cross-boundary hops beyond this value are clamped (multi-hop reserved for future work). */
8
8
  export declare const MAX_SUPPORTED_CROSS_DEPTH = 1;
@@ -79,6 +79,7 @@ export declare function collectImpactSymbolUids(local: unknown, servicePrefix: s
79
79
  uids: string[];
80
80
  targetFilePath?: string;
81
81
  };
82
+ export declare function mergeRisk(localRisk: string, cross: CrossRepoImpact[]): string;
82
83
  export declare function runGroupImpact(deps: RunGroupImpactDeps, params: Record<string, unknown>): Promise<GroupImpactResult | {
83
84
  error: string;
84
85
  }>;
@@ -251,7 +251,11 @@ function extractProcessNames(impact) {
251
251
  return [];
252
252
  return o.affected_processes.map((p) => String(p.name ?? '')).filter(Boolean);
253
253
  }
254
- function mergeRisk(localRisk, cross) {
254
+ // Exported so the U4 PDG-result interchangeability contract (KTD8) can assert
255
+ // permanently that a PDG `risk:'UNKNOWN'` never coalesces to a confident `LOW`.
256
+ // No behavior change — `'UNKNOWN'` was already handled correctly at the
257
+ // `(localRisk === 'LOW' || localRisk === 'UNKNOWN')` branch below.
258
+ export function mergeRisk(localRisk, cross) {
255
259
  const highConf = cross.some((c) => c.contract.confidence >= 0.85);
256
260
  if (localRisk === 'CRITICAL')
257
261
  return 'CRITICAL';
@@ -57,7 +57,13 @@ const isGraphWide = (label) => label === 'Community' || label === 'Process';
57
57
  * A→C edge. These are always extracted (and the orchestrator delete-alls them
58
58
  * first, like Community/Process) so they rebuild from the fresh graph.
59
59
  */
60
- const isGraphWideRelType = (type) => type === 'TAINT_PATH';
60
+ // `CALL_SUMMARY` (PDG FU-C) is intra-procedural (a callee's RETURN-VALUE ASCENT
61
+ // depends only on its OWN body), but the orchestrator delete-alls it on an
62
+ // incremental `--pdg` writeback to keep the emit path single — so it must be
63
+ // re-included from the FULL fresh graph (which the emit phase recomputes every
64
+ // run) or an unchanged function's summary would be lost. Cheap: one self-loop
65
+ // edge per return-flowing function.
66
+ const isGraphWideRelType = (type) => type === 'TAINT_PATH' || type === 'CALL_SUMMARY';
61
67
  /**
62
68
  * Build a Map<nodeId, filePath> for every File-bound node in the graph.
63
69
  * Graph-wide nodes (Community/Process) have no filePath and are filtered.
@@ -19,7 +19,33 @@
19
19
  * kind cannot be its own edge type and is queried via `reason`.
20
20
  */
21
21
  import type { KnowledgeGraph } from '../../graph/types.js';
22
- import type { BindingEntry, FunctionCfg } from './types.js';
22
+ import { type ReachingDefsSolver } from './reaching-defs.js';
23
+ import type { BasicBlockData, BindingEntry, FunctionCfg } from './types.js';
24
+ /**
25
+ * Reserved token placed in `BasicBlock.callees` when a statement's call sites
26
+ * were truncated at {@link DEFAULT_PDG_MAX_SITES_PER_STATEMENT}: the recorded
27
+ * callee list is then INCOMPLETE, so over-cap callees are absent. `*` is not a
28
+ * valid identifier leaf, so it cannot collide with a real callee name. The
29
+ * impact bridge treats a slice containing this sentinel as "callees unknown" and
30
+ * keeps reach callgraph-equal (proven), rather than falsely labeling an
31
+ * absent-but-real callee `unproven-bridge`.
32
+ */
33
+ export declare const CALLEES_TRUNCATED_SENTINEL = "*";
34
+ /**
35
+ * Inner separator for the `BasicBlock.calleeIds` cell (resolved callee symbol
36
+ * ids). A TAB is used — NOT a space — because resolved ids embed `filePath` and
37
+ * C++ overload shape tags with multi-word primitive types (e.g. `unsigned char`,
38
+ * `long double`), so an id can legitimately contain a space; a space-joined cell
39
+ * then fragments on read and silently drops inter-procedural reach to that
40
+ * callee (#2227 tri-review). A tab cannot appear in a tree-sitter-derived id
41
+ * token (paths/identifiers/type tokens are tab-free) and round-trips intact
42
+ * through `escapeCSVField` (tab is in its preserved set) and the RFC-4180 COPY
43
+ * reader (every cell is quoted). Producer ({@link calleeIdsOfBlock}) and
44
+ * consumer (`splitCalleeIds`) import this single constant so they cannot drift.
45
+ * The sibling `callees` (leaf-name) cell stays space-joined — leaf names are
46
+ * bare identifiers and never contain a space.
47
+ */
48
+ export declare const CALLEE_ID_SEP = "\t";
23
49
  /**
24
50
  * Default per-function CFG edge cap. A pathological generated function could
25
51
  * otherwise emit an unbounded edge set; the cap bounds graph growth and is
@@ -152,7 +178,46 @@ export declare const hasEmitSafeFacts: (cfg: FunctionCfg) => boolean;
152
178
  * no silent truncation (KTD6/R6). Block nodes are always fully emitted (their
153
179
  * count is bounded by the function's statement count); only edges are capped.
154
180
  */
155
- export declare function emitFileCfgs(graph: KnowledgeGraph, cfgs: readonly FunctionCfg[], maxEdgesPerFunction?: number, onWarn?: (message: string) => void): CfgEmitResult;
181
+ /**
182
+ * Space-joined, sorted, de-duplicated leaf callee names invoked directly in a
183
+ * block (`call`/`new` sites; the leaf of a dotted path — `child_process.exec` ⇒
184
+ * `exec`). This is the persisted substrate for statement-precise inter-procedural
185
+ * impact: a callee reached from a function is "proven" to be impacted by a
186
+ * changed statement iff its name appears in the callees of a block in that
187
+ * statement's dependence slice. `sites` is harvested only for TS/JS under `--pdg`
188
+ * (and absent on synthetic ENTRY/EXIT), so the field is empty elsewhere and the
189
+ * bridge degrades to the prior (callgraph-equal) behavior. Space-joined because
190
+ * leaf names are identifiers (no spaces) and the field is itself one CSV cell.
191
+ */
192
+ export declare function calleesOfBlock(block: BasicBlockData): string;
193
+ /**
194
+ * Tab-joined ({@link CALLEE_ID_SEP}), sorted, de-duplicated RESOLVED callee symbol ids invoked
195
+ * directly in a block — the SOUND parallel to {@link calleesOfBlock}'s leaf
196
+ * names (#2227 follow-up plan U3, KTD1/KTD2/KTD7). Each block site's call-site
197
+ * anchor `at` (U1) is joined by EXACT position to the per-file resolved-id map
198
+ * `fileMap` (U2's `(line,col) → Set<calleeId>`), so a callee reached from a
199
+ * function is proven impacted by a changed statement iff its resolved id — not
200
+ * just its leaf NAME — appears in a slice block's `calleeIds`. This eliminates
201
+ * the same-leaf-name collision (false-proven) and import-alias (false-unproven)
202
+ * the name predicate suffers on overloading languages.
203
+ *
204
+ * The site partitioning is inherited verbatim from {@link calleesOfBlock}: the
205
+ * SAME `member-read`-skip and the SAME per-statement site cap (R7) — a capped
206
+ * statement adds {@link CALLEES_TRUNCATED_SENTINEL} so the bridge keeps the
207
+ * block callee-unknown for ids too (callgraph-equal rather than under-proving).
208
+ * Because `at` is the SAME anchor the CALLS resolution keyed `atRange` on
209
+ * (KTD7), the join lands on exactly the sites the name harvest partitioned,
210
+ * including the nested-function exclusion (so a single-line inline closure's
211
+ * inner call never leaks its id into the outer block).
212
+ *
213
+ * `fileMap` is the resolved-id map for THIS file (`calleeIdAccumulator.get(
214
+ * filePath)` in run.ts). Absent (pdg off, or a file with no captured CALLS) ⇒
215
+ * `''` — the bridge then degrades to the leaf-name fallback (R3). A site whose
216
+ * `at` is absent (pre-U1 channel) or whose position is not in the map
217
+ * contributes no id (graceful, never throws).
218
+ */
219
+ export declare function calleeIdsOfBlock(block: BasicBlockData, fileMap: ReadonlyMap<string, ReadonlySet<string>> | undefined): string;
220
+ export declare function emitFileCfgs(graph: KnowledgeGraph, cfgs: readonly FunctionCfg[], maxEdgesPerFunction?: number, onWarn?: (message: string) => void, calleeIdMap?: ReadonlyMap<string, ReadonlySet<string>>): CfgEmitResult;
156
221
  export interface ReachingDefEmitResult {
157
222
  /** Deduped (defBlock, useBlock, binding) edges persisted. */
158
223
  edges: number;
@@ -194,7 +259,7 @@ export declare const bindingKey: (b: BindingEntry) => string;
194
259
  * count — overflow is almost always one variable, which is exactly the datum
195
260
  * M3 tuning wants.
196
261
  */
197
- export declare function emitFileReachingDefs(graph: KnowledgeGraph, cfgs: readonly FunctionCfg[], maxEdgesPerFunction?: number, onWarn?: (message: string) => void): ReachingDefEmitResult;
262
+ export declare function emitFileReachingDefs(graph: KnowledgeGraph, cfgs: readonly FunctionCfg[], maxEdgesPerFunction?: number, onWarn?: (message: string) => void, solve?: ReachingDefsSolver): ReachingDefEmitResult;
198
263
  export interface CdgEmitResult {
199
264
  /** Deduped (controller, dependent, label) CDG edges persisted. */
200
265
  edges: number;