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.
- package/dist/_shared/graph/types.d.ts +17 -1
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/cli/ai-context.js +3 -1
- package/dist/cli/eval-server.d.ts +8 -0
- package/dist/cli/eval-server.js +242 -2
- package/dist/cli/index.js +2 -0
- package/dist/cli/tool.d.ts +2 -0
- package/dist/cli/tool.js +11 -0
- package/dist/core/group/cross-impact.d.ts +2 -1
- package/dist/core/group/cross-impact.js +5 -1
- package/dist/core/incremental/subgraph-extract.js +7 -1
- package/dist/core/ingestion/cfg/emit.d.ts +68 -3
- package/dist/core/ingestion/cfg/emit.js +173 -13
- package/dist/core/ingestion/cfg/reaching-def-reason-codec.d.ts +106 -0
- package/dist/core/ingestion/cfg/reaching-def-reason-codec.js +133 -0
- package/dist/core/ingestion/cfg/reaching-defs.d.ts +14 -0
- package/dist/core/ingestion/cfg/reaching-defs.js +28 -0
- package/dist/core/ingestion/cfg/types.d.ts +43 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.js +3 -1
- package/dist/core/ingestion/cfg/visitors/call-site-harvest.d.ts +44 -1
- package/dist/core/ingestion/cfg/visitors/call-site-harvest.js +47 -1
- package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +3 -1
- package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +116 -6
- package/dist/core/ingestion/cfg/visitors/dart-harvest.js +423 -52
- package/dist/core/ingestion/cfg/visitors/go-harvest.js +12 -20
- package/dist/core/ingestion/cfg/visitors/java-harvest.js +12 -2
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +89 -6
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +220 -6
- package/dist/core/ingestion/cfg/visitors/php-harvest.js +12 -2
- package/dist/core/ingestion/cfg/visitors/python-harvest.d.ts +40 -3
- package/dist/core/ingestion/cfg/visitors/python-harvest.js +153 -15
- package/dist/core/ingestion/cfg/visitors/ruby-harvest.d.ts +83 -4
- package/dist/core/ingestion/cfg/visitors/ruby-harvest.js +180 -12
- package/dist/core/ingestion/cfg/visitors/rust-harvest.d.ts +164 -5
- package/dist/core/ingestion/cfg/visitors/rust-harvest.js +332 -6
- package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +92 -6
- package/dist/core/ingestion/cfg/visitors/swift-harvest.js +189 -15
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +6 -1
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +37 -13
- package/dist/core/ingestion/pipeline-phases/call-summaries.d.ts +26 -0
- package/dist/core/ingestion/pipeline-phases/call-summaries.js +46 -0
- package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/index.js +1 -0
- package/dist/core/ingestion/pipeline.d.ts +5 -0
- package/dist/core/ingestion/pipeline.js +2 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +75 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +67 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +14 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +6 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +13 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +6 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +7 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +5 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +25 -16
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +8 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +6 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +10 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +65 -8
- package/dist/core/ingestion/taint/call-summary-codec.d.ts +75 -0
- package/dist/core/ingestion/taint/call-summary-codec.js +128 -0
- package/dist/core/ingestion/taint/call-summary-emit.d.ts +43 -0
- package/dist/core/ingestion/taint/call-summary-emit.js +70 -0
- package/dist/core/ingestion/taint/call-summary-harvest.d.ts +77 -0
- package/dist/core/ingestion/taint/call-summary-harvest.js +179 -0
- package/dist/core/ingestion/taint/call-summary-model.d.ts +47 -0
- package/dist/core/ingestion/taint/call-summary-model.js +25 -0
- package/dist/core/ingestion/taint/emit.d.ts +2 -1
- package/dist/core/ingestion/taint/emit.js +7 -3
- package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +25 -1
- package/dist/core/ingestion/taint/summary-harvest-driver.js +64 -3
- package/dist/core/lbug/csv-generator.d.ts +7 -2
- package/dist/core/lbug/csv-generator.js +9 -2
- package/dist/core/lbug/lbug-adapter.d.ts +14 -0
- package/dist/core/lbug/lbug-adapter.js +59 -6
- package/dist/core/lbug/schema.d.ts +1 -1
- package/dist/core/lbug/schema.js +2 -0
- package/dist/core/run-analyze.js +22 -1
- package/dist/mcp/local/local-backend.d.ts +35 -0
- package/dist/mcp/local/local-backend.js +419 -33
- package/dist/mcp/local/pdg-impact.d.ts +384 -0
- package/dist/mcp/local/pdg-impact.js +1972 -0
- package/dist/mcp/tools.d.ts +1 -0
- package/dist/mcp/tools.js +22 -2
- package/dist/storage/parse-cache.js +20 -8
- package/dist/storage/repo-manager.d.ts +25 -2
- package/dist/storage/repo-manager.js +13 -2
- 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":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,OAAO,GACP,UAAU,GACV,QAAQ,GACR,UAAU,GACV,WAAW,GACX,MAAM,GACN,WAAW,GACX,QAAQ,GACR,MAAM,GACN,aAAa,GACb,WAAW,GACX,SAAS,GAET,QAAQ,GACR,OAAO,GACP,SAAS,GACT,OAAO,GACP,WAAW,GACX,OAAO,GACP,MAAM,GACN,WAAW,GACX,OAAO,GACP,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,UAAU,GACV,YAAY,GACZ,aAAa,GACb,UAAU,GACV,SAAS,GACT,OAAO,GACP,MAAM,GAGN,YAAY,CAAC;AAEjB,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IAEjC,WAAW,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,CAAC;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB,IAAI,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/graph/types.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/ai-context.js
CHANGED
|
@@ -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;
|
package/dist/cli/eval-server.js
CHANGED
|
@@ -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)')
|
package/dist/cli/tool.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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;
|