openlore 2.0.7 → 2.0.9
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/README.md +160 -25
- package/dist/api/init.d.ts.map +1 -1
- package/dist/api/init.js +9 -2
- package/dist/api/init.js.map +1 -1
- package/dist/cli/commands/analyze.d.ts.map +1 -1
- package/dist/cli/commands/analyze.js +5 -3
- package/dist/cli/commands/analyze.js.map +1 -1
- package/dist/cli/commands/decisions.d.ts +0 -1
- package/dist/cli/commands/decisions.d.ts.map +1 -1
- package/dist/cli/commands/decisions.js +19 -31
- package/dist/cli/commands/decisions.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +85 -23
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +16 -3
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts +766 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +444 -244
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/orient.d.ts.map +1 -1
- package/dist/cli/commands/orient.js +14 -2
- package/dist/cli/commands/orient.js.map +1 -1
- package/dist/cli/commands/prove.d.ts +29 -0
- package/dist/cli/commands/prove.d.ts.map +1 -0
- package/dist/cli/commands/prove.js +160 -0
- package/dist/cli/commands/prove.js.map +1 -0
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +5 -3
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/install/adapters/claude-code.d.ts +8 -2
- package/dist/cli/install/adapters/claude-code.d.ts.map +1 -1
- package/dist/cli/install/adapters/claude-code.js +99 -35
- package/dist/cli/install/adapters/claude-code.js.map +1 -1
- package/dist/cli/install/detect.d.ts.map +1 -1
- package/dist/cli/install/detect.js +14 -0
- package/dist/cli/install/detect.js.map +1 -1
- package/dist/cli/install/templates/agent-instructions.md +4 -2
- package/dist/constants.d.ts +16 -3
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +24 -3
- package/dist/constants.js.map +1 -1
- package/dist/core/agent-eval/measure.d.ts +57 -0
- package/dist/core/agent-eval/measure.d.ts.map +1 -0
- package/dist/core/agent-eval/measure.js +85 -0
- package/dist/core/agent-eval/measure.js.map +1 -0
- package/dist/core/agent-eval/scorecard.d.ts +37 -0
- package/dist/core/agent-eval/scorecard.d.ts.map +1 -0
- package/dist/core/agent-eval/scorecard.js +70 -0
- package/dist/core/agent-eval/scorecard.js.map +1 -0
- package/dist/core/agent-eval/tasks.d.ts +37 -0
- package/dist/core/agent-eval/tasks.d.ts.map +1 -0
- package/dist/core/agent-eval/tasks.js +76 -0
- package/dist/core/agent-eval/tasks.js.map +1 -0
- package/dist/core/analyzer/architecture-writer.d.ts +5 -3
- package/dist/core/analyzer/architecture-writer.d.ts.map +1 -1
- package/dist/core/analyzer/architecture-writer.js +6 -4
- package/dist/core/analyzer/architecture-writer.js.map +1 -1
- package/dist/core/analyzer/artifact-generator.d.ts.map +1 -1
- package/dist/core/analyzer/artifact-generator.js +59 -4
- package/dist/core/analyzer/artifact-generator.js.map +1 -1
- package/dist/core/analyzer/call-graph.d.ts +19 -1
- package/dist/core/analyzer/call-graph.d.ts.map +1 -1
- package/dist/core/analyzer/call-graph.js +130 -28
- package/dist/core/analyzer/call-graph.js.map +1 -1
- package/dist/core/analyzer/spec-vector-index.d.ts.map +1 -1
- package/dist/core/analyzer/spec-vector-index.js +6 -2
- package/dist/core/analyzer/spec-vector-index.js.map +1 -1
- package/dist/core/architecture/check.d.ts +63 -0
- package/dist/core/architecture/check.d.ts.map +1 -0
- package/dist/core/architecture/check.js +192 -0
- package/dist/core/architecture/check.js.map +1 -0
- package/dist/core/architecture/rules.d.ts +73 -0
- package/dist/core/architecture/rules.d.ts.map +1 -0
- package/dist/core/architecture/rules.js +201 -0
- package/dist/core/architecture/rules.js.map +1 -0
- package/dist/core/decisions/project.d.ts +59 -0
- package/dist/core/decisions/project.d.ts.map +1 -0
- package/dist/core/decisions/project.js +68 -0
- package/dist/core/decisions/project.js.map +1 -0
- package/dist/core/decisions/verifier.d.ts +10 -0
- package/dist/core/decisions/verifier.d.ts.map +1 -1
- package/dist/core/decisions/verifier.js +48 -5
- package/dist/core/decisions/verifier.js.map +1 -1
- package/dist/core/provenance/change-coupling.d.ts +68 -0
- package/dist/core/provenance/change-coupling.d.ts.map +1 -0
- package/dist/core/provenance/change-coupling.js +134 -0
- package/dist/core/provenance/change-coupling.js.map +1 -0
- package/dist/core/provenance/git-provenance.d.ts +67 -0
- package/dist/core/provenance/git-provenance.d.ts.map +1 -0
- package/dist/core/provenance/git-provenance.js +177 -0
- package/dist/core/provenance/git-provenance.js.map +1 -0
- package/dist/core/provenance/project.d.ts +37 -0
- package/dist/core/provenance/project.d.ts.map +1 -0
- package/dist/core/provenance/project.js +46 -0
- package/dist/core/provenance/project.js.map +1 -0
- package/dist/core/services/config-manager.d.ts +17 -0
- package/dist/core/services/config-manager.d.ts.map +1 -1
- package/dist/core/services/config-manager.js +35 -1
- package/dist/core/services/config-manager.js.map +1 -1
- package/dist/core/services/edge-store.d.ts +41 -0
- package/dist/core/services/edge-store.d.ts.map +1 -1
- package/dist/core/services/edge-store.js +251 -3
- package/dist/core/services/edge-store.js.map +1 -1
- package/dist/core/services/llm-service.d.ts +9 -0
- package/dist/core/services/llm-service.d.ts.map +1 -1
- package/dist/core/services/llm-service.js +15 -4
- package/dist/core/services/llm-service.js.map +1 -1
- package/dist/core/services/mcp-handlers/architecture.d.ts +19 -0
- package/dist/core/services/mcp-handlers/architecture.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/architecture.js +104 -0
- package/dist/core/services/mcp-handlers/architecture.js.map +1 -0
- package/dist/core/services/mcp-handlers/change-coupling.d.ts +16 -0
- package/dist/core/services/mcp-handlers/change-coupling.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/change-coupling.js +57 -0
- package/dist/core/services/mcp-handlers/change-coupling.js.map +1 -0
- package/dist/core/services/mcp-handlers/graph.d.ts +27 -0
- package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/graph.js +98 -16
- package/dist/core/services/mcp-handlers/graph.js.map +1 -1
- package/dist/core/services/mcp-handlers/orient.d.ts +1 -1
- package/dist/core/services/mcp-handlers/orient.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/orient.js +196 -39
- package/dist/core/services/mcp-handlers/orient.js.map +1 -1
- package/dist/core/services/mcp-handlers/progressive.d.ts +46 -0
- package/dist/core/services/mcp-handlers/progressive.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/progressive.js +0 -0
- package/dist/core/services/mcp-handlers/progressive.js.map +1 -0
- package/dist/core/services/mcp-handlers/reachability.d.ts +30 -0
- package/dist/core/services/mcp-handlers/reachability.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/reachability.js +222 -0
- package/dist/core/services/mcp-handlers/reachability.js.map +1 -0
- package/dist/core/services/mcp-handlers/semantic.d.ts +1 -1
- package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/semantic.js +39 -26
- package/dist/core/services/mcp-handlers/semantic.js.map +1 -1
- package/dist/core/services/mcp-handlers/structural-diff.d.ts +31 -0
- package/dist/core/services/mcp-handlers/structural-diff.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/structural-diff.js +268 -0
- package/dist/core/services/mcp-handlers/structural-diff.js.map +1 -0
- package/dist/core/services/mcp-handlers/test-impact.d.ts +34 -0
- package/dist/core/services/mcp-handlers/test-impact.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/test-impact.js +221 -0
- package/dist/core/services/mcp-handlers/test-impact.js.map +1 -0
- package/dist/core/services/mcp-handlers/tool-guard.d.ts +45 -0
- package/dist/core/services/mcp-handlers/tool-guard.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/tool-guard.js +81 -0
- package/dist/core/services/mcp-handlers/tool-guard.js.map +1 -0
- package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/utils.js +15 -1
- package/dist/core/services/mcp-handlers/utils.js.map +1 -1
- package/dist/core/services/mcp-watcher.d.ts +7 -0
- package/dist/core/services/mcp-watcher.d.ts.map +1 -1
- package/dist/core/services/mcp-watcher.js +46 -0
- package/dist/core/services/mcp-watcher.js.map +1 -1
- package/dist/core/services/project-detector.d.ts.map +1 -1
- package/dist/core/services/project-detector.js +45 -2
- package/dist/core/services/project-detector.js.map +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural Change Analysis — graph diff (spec-21).
|
|
3
|
+
*
|
|
4
|
+
* Given a change (working tree vs a ref, or two refs), compute what changed
|
|
5
|
+
* *structurally*: which functions/edges were added or removed, which signatures
|
|
6
|
+
* changed, and which existing callers are now STALE because a callee's signature
|
|
7
|
+
* moved under them. A review/refactor agent gets a precise structural changelog
|
|
8
|
+
* instead of re-deriving consequences from a raw text diff.
|
|
9
|
+
*
|
|
10
|
+
* The difference between "these 40 lines changed" (git diff) and "this removed
|
|
11
|
+
* function C, altered the signature of D, and 5 of D's callers are now stale"
|
|
12
|
+
* (graph diff) — a computed consequence (Layer 3), not retrieval.
|
|
13
|
+
*
|
|
14
|
+
* Bounded: only the changed files are re-parsed (old content via `git show`, new
|
|
15
|
+
* via the working tree / ref), so two in-memory snapshots are cheap. The canonical
|
|
16
|
+
* graph is never mutated. Stale callers come from the cached graph (all callers).
|
|
17
|
+
*
|
|
18
|
+
* Honest limits: rename/move detection is heuristic — a renamed function looks like
|
|
19
|
+
* delete + add — so both interpretations are reported, never silently guessed.
|
|
20
|
+
*/
|
|
21
|
+
import { execFile } from 'node:child_process';
|
|
22
|
+
import { readFile } from 'node:fs/promises';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
import { promisify } from 'node:util';
|
|
25
|
+
import { validateDirectory, readCachedContext } from './utils.js';
|
|
26
|
+
import { isGitRepository, resolveBaseRef, validateGitRef, getChangedFiles } from '../../drift/git-diff.js';
|
|
27
|
+
import { CallGraphBuilder, serializeCallGraph } from '../../analyzer/call-graph.js';
|
|
28
|
+
import { detectLanguage } from '../../analyzer/signature-extractor.js';
|
|
29
|
+
const execFileAsync = promisify(execFile);
|
|
30
|
+
/** Content of a file at a git ref, or '' if it didn't exist there. */
|
|
31
|
+
async function fileAtRef(rootPath, ref, path) {
|
|
32
|
+
try {
|
|
33
|
+
const { stdout } = await execFileAsync('git', ['show', `${ref}:${path}`], {
|
|
34
|
+
cwd: rootPath, maxBuffer: 32 * 1024 * 1024,
|
|
35
|
+
});
|
|
36
|
+
return stdout;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Signature shape with the identifier removed — for rename matching. */
|
|
43
|
+
function signatureShape(signature) {
|
|
44
|
+
if (!signature)
|
|
45
|
+
return '';
|
|
46
|
+
const paren = signature.indexOf('(');
|
|
47
|
+
return (paren >= 0 ? signature.slice(paren) : signature).replace(/\s+/g, ' ').trim();
|
|
48
|
+
}
|
|
49
|
+
function nodeRef(n) {
|
|
50
|
+
return { name: n.name, file: n.filePath, className: n.className ?? null, signature: n.signature ?? n.name };
|
|
51
|
+
}
|
|
52
|
+
export async function handleStructuralDiff(input) {
|
|
53
|
+
const absDir = await validateDirectory(input.directory);
|
|
54
|
+
if (!(await isGitRepository(absDir))) {
|
|
55
|
+
return { error: 'Not a git repository. Structural diff requires git.' };
|
|
56
|
+
}
|
|
57
|
+
const baseRaw = input.baseRef ?? 'HEAD';
|
|
58
|
+
try {
|
|
59
|
+
validateGitRef(baseRaw);
|
|
60
|
+
if (input.headRef)
|
|
61
|
+
validateGitRef(input.headRef);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
return { error: `Invalid git ref: ${err instanceof Error ? err.message : String(err)}` };
|
|
65
|
+
}
|
|
66
|
+
const resolvedBase = await resolveBaseRef(absDir, baseRaw);
|
|
67
|
+
// ── Changed files ───────────────────────────────────────────────────────────
|
|
68
|
+
let changed;
|
|
69
|
+
try {
|
|
70
|
+
if (input.headRef) {
|
|
71
|
+
const { stdout } = await execFileAsync('git', ['diff', '--name-status', '--diff-filter=ACDMR', `${resolvedBase}..${input.headRef}`], { cwd: absDir, maxBuffer: 16 * 1024 * 1024 });
|
|
72
|
+
changed = stdout.split('\n').filter(Boolean).map(line => {
|
|
73
|
+
const parts = line.split('\t');
|
|
74
|
+
const s = parts[0].charAt(0);
|
|
75
|
+
const status = s === 'A' ? 'added' : s === 'D' ? 'deleted' : s === 'R' ? 'renamed' : 'modified';
|
|
76
|
+
return status === 'renamed' && parts.length >= 3
|
|
77
|
+
? { path: parts[2], status, oldPath: parts[1] }
|
|
78
|
+
: { path: parts[1], status };
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
const diff = await getChangedFiles({ rootPath: absDir, baseRef: resolvedBase, includeUnstaged: true });
|
|
83
|
+
changed = diff.files.map(f => ({ path: f.path, status: f.status, oldPath: f.oldPath }));
|
|
84
|
+
// git diff excludes untracked files; a brand-new file's functions are all
|
|
85
|
+
// structural additions, so fold them in for the working-tree comparison.
|
|
86
|
+
try {
|
|
87
|
+
const { stdout } = await execFileAsync('git', ['ls-files', '--others', '--exclude-standard'], {
|
|
88
|
+
cwd: absDir, maxBuffer: 16 * 1024 * 1024,
|
|
89
|
+
});
|
|
90
|
+
const seen = new Set(changed.map(c => c.path));
|
|
91
|
+
for (const path of stdout.split('\n').map(s => s.trim()).filter(Boolean)) {
|
|
92
|
+
if (!seen.has(path))
|
|
93
|
+
changed.push({ path, status: 'added' });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch { /* untracked enumeration is best-effort */ }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
return { error: `git diff failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
101
|
+
}
|
|
102
|
+
// Code files only; logical path = the new path (so a file move doesn't explode
|
|
103
|
+
// into all-removed + all-added at the function level).
|
|
104
|
+
const codeChanged = changed.filter(c => {
|
|
105
|
+
const lang = detectLanguage(c.path);
|
|
106
|
+
return lang && lang !== 'Unknown' && lang !== 'unknown';
|
|
107
|
+
});
|
|
108
|
+
if (codeChanged.length === 0) {
|
|
109
|
+
return {
|
|
110
|
+
base: resolvedBase, head: input.headRef ?? 'working tree',
|
|
111
|
+
message: 'No changed code files between the two states (only non-code or no changes).',
|
|
112
|
+
summary: emptySummary(),
|
|
113
|
+
soundness: diffSoundness(true),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// ── Build old + new snapshots from just the changed files ───────────────────
|
|
117
|
+
const oldFiles = [];
|
|
118
|
+
const newFiles = [];
|
|
119
|
+
for (const c of codeChanged) {
|
|
120
|
+
const lang = detectLanguage(c.path);
|
|
121
|
+
const oldSrcPath = c.oldPath ?? c.path;
|
|
122
|
+
const oldContent = c.status === 'added' ? '' : await fileAtRef(absDir, resolvedBase, oldSrcPath);
|
|
123
|
+
let newContent = '';
|
|
124
|
+
if (c.status !== 'deleted') {
|
|
125
|
+
newContent = input.headRef
|
|
126
|
+
? await fileAtRef(absDir, input.headRef, c.path)
|
|
127
|
+
: await readFile(join(absDir, c.path), 'utf-8').catch(() => '');
|
|
128
|
+
}
|
|
129
|
+
if (oldContent)
|
|
130
|
+
oldFiles.push({ path: c.path, content: oldContent, language: lang });
|
|
131
|
+
if (newContent)
|
|
132
|
+
newFiles.push({ path: c.path, content: newContent, language: lang });
|
|
133
|
+
}
|
|
134
|
+
const oldGraph = await safeBuild(oldFiles);
|
|
135
|
+
const newGraph = await safeBuild(newFiles);
|
|
136
|
+
const oldNodes = new Map(oldGraph.nodes.filter(isCode).map(n => [n.id, n]));
|
|
137
|
+
const newNodes = new Map(newGraph.nodes.filter(isCode).map(n => [n.id, n]));
|
|
138
|
+
// ── Node-level delta ────────────────────────────────────────────────────────
|
|
139
|
+
const added = [];
|
|
140
|
+
const removed = [];
|
|
141
|
+
const signatureChanged = [];
|
|
142
|
+
for (const [id, n] of newNodes)
|
|
143
|
+
if (!oldNodes.has(id))
|
|
144
|
+
added.push(n);
|
|
145
|
+
for (const [id, n] of oldNodes) {
|
|
146
|
+
const nu = newNodes.get(id);
|
|
147
|
+
if (!nu) {
|
|
148
|
+
removed.push(n);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if ((n.signature ?? '') !== (nu.signature ?? '')) {
|
|
152
|
+
signatureChanged.push({ node: nu, before: n.signature ?? n.name, after: nu.signature ?? n.name });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// ── Rename/move candidates — removed↔added with matching signature shape ─────
|
|
156
|
+
const renameCandidates = [];
|
|
157
|
+
for (const r of removed) {
|
|
158
|
+
for (const a of added) {
|
|
159
|
+
const sameShape = signatureShape(r.signature) && signatureShape(r.signature) === signatureShape(a.signature);
|
|
160
|
+
const sameFile = r.filePath === a.filePath;
|
|
161
|
+
if (!sameShape)
|
|
162
|
+
continue;
|
|
163
|
+
const confidence = sameFile ? 'high' : 'medium';
|
|
164
|
+
renameCandidates.push({
|
|
165
|
+
from: nodeRef(r), to: nodeRef(a), confidence,
|
|
166
|
+
note: `"${r.name}" may have been renamed/moved to "${a.name}" (matching signature shape${sameFile ? ', same file' : ''}). Reported as both remove+add and this rename candidate — verify.`,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// ── Edge delta (calls among / out of the changed files) ─────────────────────
|
|
171
|
+
const edgeKey = (e) => `${e.callerId}\0${e.calleeName}`;
|
|
172
|
+
const oldEdges = new Map(oldGraph.edges.filter(isCallEdge).map(e => [edgeKey(e), e]));
|
|
173
|
+
const newEdges = new Map(newGraph.edges.filter(isCallEdge).map(e => [edgeKey(e), e]));
|
|
174
|
+
const addedEdges = [...newEdges].filter(([k]) => !oldEdges.has(k)).map(([, e]) => edgePair(e, newGraph));
|
|
175
|
+
const removedEdges = [...oldEdges].filter(([k]) => !newEdges.has(k)).map(([, e]) => edgePair(e, oldGraph));
|
|
176
|
+
// ── Stale callers — callers (in the canonical graph) of a node whose signature
|
|
177
|
+
// changed or that was removed, that are NOT themselves in the changed set ──
|
|
178
|
+
const ctx = await readCachedContext(absDir);
|
|
179
|
+
const changedPaths = new Set(codeChanged.map(c => c.path));
|
|
180
|
+
const staleCallerNote = ctx?.edgeStore
|
|
181
|
+
? undefined
|
|
182
|
+
: 'No cached graph — stale-caller analysis skipped. Run analyze_codebase to enable it.';
|
|
183
|
+
const collectStaleCallers = (nodeId) => {
|
|
184
|
+
if (!ctx?.edgeStore)
|
|
185
|
+
return [];
|
|
186
|
+
const out = new Map();
|
|
187
|
+
for (const e of ctx.edgeStore.getCallers(nodeId)) {
|
|
188
|
+
if (e.kind && e.kind !== 'calls')
|
|
189
|
+
continue;
|
|
190
|
+
const caller = ctx.edgeStore.getNode(e.callerId);
|
|
191
|
+
if (!caller || caller.isExternal)
|
|
192
|
+
continue;
|
|
193
|
+
if (changedPaths.has(caller.filePath))
|
|
194
|
+
continue; // updated in this change
|
|
195
|
+
out.set(caller.id, { name: caller.name, file: caller.filePath });
|
|
196
|
+
}
|
|
197
|
+
return [...out.values()].sort((a, b) => a.file.localeCompare(b.file) || a.name.localeCompare(b.name));
|
|
198
|
+
};
|
|
199
|
+
const limit = Math.max(1, Math.min(input.maxResults ?? 200, 1000));
|
|
200
|
+
const sigChangedOut = signatureChanged
|
|
201
|
+
.map(s => ({ ...nodeRef(s.node), before: s.before, after: s.after, staleCallers: collectStaleCallers(s.node.id) }))
|
|
202
|
+
.sort((a, b) => b.staleCallers.length - a.staleCallers.length || a.file.localeCompare(b.file));
|
|
203
|
+
const removedOut = removed
|
|
204
|
+
.map(n => ({ ...nodeRef(n), staleCallers: collectStaleCallers(n.id) }))
|
|
205
|
+
.sort((a, b) => b.staleCallers.length - a.staleCallers.length || a.file.localeCompare(b.file));
|
|
206
|
+
const totalStale = new Set([...sigChangedOut, ...removedOut].flatMap(x => x.staleCallers.map(c => `${c.file}::${c.name}`))).size;
|
|
207
|
+
return {
|
|
208
|
+
base: resolvedBase,
|
|
209
|
+
head: input.headRef ?? 'working tree',
|
|
210
|
+
changedFiles: codeChanged.map(c => ({ path: c.path, status: c.status, ...(c.oldPath ? { oldPath: c.oldPath } : {}) })),
|
|
211
|
+
summary: {
|
|
212
|
+
addedFunctions: added.length,
|
|
213
|
+
removedFunctions: removed.length,
|
|
214
|
+
signatureChanges: signatureChanged.length,
|
|
215
|
+
addedEdges: addedEdges.length,
|
|
216
|
+
removedEdges: removedEdges.length,
|
|
217
|
+
staleCallers: totalStale,
|
|
218
|
+
renameCandidates: renameCandidates.length,
|
|
219
|
+
},
|
|
220
|
+
added: added.map(nodeRef).sort(byFileName).slice(0, limit),
|
|
221
|
+
removed: removedOut.slice(0, limit),
|
|
222
|
+
signatureChanged: sigChangedOut.slice(0, limit),
|
|
223
|
+
renameCandidates: renameCandidates.slice(0, limit),
|
|
224
|
+
edges: { added: addedEdges.slice(0, limit), removed: removedEdges.slice(0, limit) },
|
|
225
|
+
...(staleCallerNote ? { note: staleCallerNote } : {}),
|
|
226
|
+
soundness: diffSoundness(false),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// ── helpers ────────────────────────────────────────────────────────────────────
|
|
230
|
+
function isCode(n) { return !n.isExternal; }
|
|
231
|
+
function isCallEdge(e) {
|
|
232
|
+
return (!e.kind || e.kind === 'calls') && !!e.calleeId;
|
|
233
|
+
}
|
|
234
|
+
function byFileName(a, b) {
|
|
235
|
+
return a.file.localeCompare(b.file) || a.name.localeCompare(b.name);
|
|
236
|
+
}
|
|
237
|
+
function edgePair(e, g) {
|
|
238
|
+
const caller = g.nodes.find(n => n.id === e.callerId);
|
|
239
|
+
return { caller: caller?.name ?? e.callerId, callee: e.calleeName, file: caller?.filePath ?? '' };
|
|
240
|
+
}
|
|
241
|
+
async function safeBuild(files) {
|
|
242
|
+
if (files.length === 0)
|
|
243
|
+
return emptyGraph();
|
|
244
|
+
try {
|
|
245
|
+
return serializeCallGraph(await new CallGraphBuilder().build(files));
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return emptyGraph();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function emptyGraph() {
|
|
252
|
+
return { nodes: [], edges: [], classes: [], inheritanceEdges: [], hubFunctions: [], entryPoints: [], layerViolations: [],
|
|
253
|
+
stats: { totalNodes: 0, totalEdges: 0, avgFanIn: 0, avgFanOut: 0 } };
|
|
254
|
+
}
|
|
255
|
+
function emptySummary() {
|
|
256
|
+
return { addedFunctions: 0, removedFunctions: 0, signatureChanges: 0, addedEdges: 0, removedEdges: 0, staleCallers: 0, renameCandidates: 0 };
|
|
257
|
+
}
|
|
258
|
+
function diffSoundness(empty) {
|
|
259
|
+
const caveats = [
|
|
260
|
+
'Rename/move detection is heuristic — a renamed function appears as remove+add. Candidates are reported separately; never assume a rename without verifying.',
|
|
261
|
+
'Signature-change detection is limited to what the analyzer extracts per language; cross-language signature notions differ.',
|
|
262
|
+
'Edge deltas cover calls among/out of the changed files; calls into unchanged files resolve against the canonical graph for stale callers only.',
|
|
263
|
+
];
|
|
264
|
+
if (empty)
|
|
265
|
+
caveats.push('No code files changed — the structural delta is empty.');
|
|
266
|
+
return { posture: 'structural-complement-to-git-diff', caveats };
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=structural-diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structural-diff.js","sourceRoot":"","sources":["../../../../src/core/services/mcp-handlers/structural-diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic Test Impact Selection (spec-19) — static, call-graph-based
|
|
3
|
+
* regression test selection (RTS) served to the agent at edit time.
|
|
4
|
+
*
|
|
5
|
+
* "I changed parseConfig() — which tests should I run?" is answered by walking the
|
|
6
|
+
* call graph *backward* from the change to every test that transitively reaches it.
|
|
7
|
+
* grep can't (the reach is through indirect calls); the model is slow and guesses;
|
|
8
|
+
* a deterministic graph does it instantly over edges we already store (`calls`,
|
|
9
|
+
* `tested_by`, inheritance).
|
|
10
|
+
*
|
|
11
|
+
* Soundness is stated honestly: this is an OVER-APPROXIMATE PRIORITIZER, not a
|
|
12
|
+
* sound replacement for the full suite. Direct/static dispatch is safely
|
|
13
|
+
* over-approximated; dynamic dispatch, reflection, and DI can under-select.
|
|
14
|
+
*/
|
|
15
|
+
import type { SerializedCallGraph, FunctionNode } from '../../analyzer/call-graph.js';
|
|
16
|
+
export interface SelectTestsInput {
|
|
17
|
+
directory: string;
|
|
18
|
+
/** Explicit changed symbols (function/method names). */
|
|
19
|
+
changedSymbols?: string[];
|
|
20
|
+
/** Git ref to diff the working tree against (e.g. "HEAD", "main"). */
|
|
21
|
+
diffRef?: string;
|
|
22
|
+
/** Max backward-reachability depth (default 12, capped). */
|
|
23
|
+
maxDepth?: number;
|
|
24
|
+
}
|
|
25
|
+
/** Resolve changed symbols → seed production nodes (exact name preferred). */
|
|
26
|
+
export declare function seedsFromSymbols(cg: SerializedCallGraph, symbols: string[]): FunctionNode[];
|
|
27
|
+
/** Resolve changed files (from a diff) → seed production nodes. */
|
|
28
|
+
export declare function seedsFromFiles(cg: SerializedCallGraph, files: string[]): FunctionNode[];
|
|
29
|
+
/**
|
|
30
|
+
* Select the tests that transitively reach a set of changed symbols/files.
|
|
31
|
+
* Read-only, deterministic, offline. Returns `unknown` (additive-by-cast).
|
|
32
|
+
*/
|
|
33
|
+
export declare function handleSelectTests(input: SelectTestsInput): Promise<unknown>;
|
|
34
|
+
//# sourceMappingURL=test-impact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-impact.d.ts","sourceRoot":"","sources":["../../../../src/core/services/mcp-handlers/test-impact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAGtF,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAYD,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAW3F;AASD,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,EAAE,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAOvF;AAOD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CA+JjF"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic Test Impact Selection (spec-19) — static, call-graph-based
|
|
3
|
+
* regression test selection (RTS) served to the agent at edit time.
|
|
4
|
+
*
|
|
5
|
+
* "I changed parseConfig() — which tests should I run?" is answered by walking the
|
|
6
|
+
* call graph *backward* from the change to every test that transitively reaches it.
|
|
7
|
+
* grep can't (the reach is through indirect calls); the model is slow and guesses;
|
|
8
|
+
* a deterministic graph does it instantly over edges we already store (`calls`,
|
|
9
|
+
* `tested_by`, inheritance).
|
|
10
|
+
*
|
|
11
|
+
* Soundness is stated honestly: this is an OVER-APPROXIMATE PRIORITIZER, not a
|
|
12
|
+
* sound replacement for the full suite. Direct/static dispatch is safely
|
|
13
|
+
* over-approximated; dynamic dispatch, reflection, and DI can under-select.
|
|
14
|
+
*/
|
|
15
|
+
import { validateDirectory, readCachedContext } from './utils.js';
|
|
16
|
+
import { buildAdjacency } from './graph.js';
|
|
17
|
+
import { SUBGRAPH_MAX_DEPTH_LIMIT } from '../../../constants.js';
|
|
18
|
+
const CONF_RANK = { high: 3, medium: 2, low: 1 };
|
|
19
|
+
/** Resolve changed symbols → seed production nodes (exact name preferred). */
|
|
20
|
+
export function seedsFromSymbols(cg, symbols) {
|
|
21
|
+
const out = new Map();
|
|
22
|
+
for (const sym of symbols) {
|
|
23
|
+
const lower = sym.toLowerCase();
|
|
24
|
+
const exact = cg.nodes.filter(n => !n.isExternal && !n.isTest && n.name.toLowerCase() === lower);
|
|
25
|
+
const pick = exact.length > 0
|
|
26
|
+
? exact
|
|
27
|
+
: cg.nodes.filter(n => !n.isExternal && !n.isTest && n.name.toLowerCase().includes(lower));
|
|
28
|
+
for (const n of pick)
|
|
29
|
+
out.set(n.id, n);
|
|
30
|
+
}
|
|
31
|
+
return [...out.values()];
|
|
32
|
+
}
|
|
33
|
+
/** Tolerant file match: exact or suffix either way. */
|
|
34
|
+
function fileMatches(nodeFile, changed) {
|
|
35
|
+
if (nodeFile === changed)
|
|
36
|
+
return true;
|
|
37
|
+
const a = nodeFile.replace(/^\/+/, ''), b = changed.replace(/^\/+/, '');
|
|
38
|
+
return a === b || a.endsWith('/' + b) || b.endsWith('/' + a);
|
|
39
|
+
}
|
|
40
|
+
/** Resolve changed files (from a diff) → seed production nodes. */
|
|
41
|
+
export function seedsFromFiles(cg, files) {
|
|
42
|
+
const out = new Map();
|
|
43
|
+
for (const n of cg.nodes) {
|
|
44
|
+
if (n.isExternal || n.isTest)
|
|
45
|
+
continue;
|
|
46
|
+
if (files.some(f => fileMatches(n.filePath, f)))
|
|
47
|
+
out.set(n.id, n);
|
|
48
|
+
}
|
|
49
|
+
return [...out.values()];
|
|
50
|
+
}
|
|
51
|
+
/** Test identity for dedup across the two discovery paths. */
|
|
52
|
+
function testKey(file, name) {
|
|
53
|
+
return `${file}\0${name}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Select the tests that transitively reach a set of changed symbols/files.
|
|
57
|
+
* Read-only, deterministic, offline. Returns `unknown` (additive-by-cast).
|
|
58
|
+
*/
|
|
59
|
+
export async function handleSelectTests(input) {
|
|
60
|
+
const absDir = await validateDirectory(input.directory);
|
|
61
|
+
const ctx = await readCachedContext(absDir);
|
|
62
|
+
if (!ctx)
|
|
63
|
+
return { error: 'No analysis found. Run analyze_codebase first.' };
|
|
64
|
+
if (!ctx.callGraph)
|
|
65
|
+
return { error: 'Call graph not available. Re-run analyze_codebase.' };
|
|
66
|
+
const cg = ctx.callGraph;
|
|
67
|
+
const maxDepth = Math.max(1, Math.min(input.maxDepth ?? 12, SUBGRAPH_MAX_DEPTH_LIMIT));
|
|
68
|
+
// ── Resolve the changed set ────────────────────────────────────────────────
|
|
69
|
+
let seeds = [];
|
|
70
|
+
let changedFiles = [];
|
|
71
|
+
if (input.changedSymbols && input.changedSymbols.length > 0) {
|
|
72
|
+
seeds = seedsFromSymbols(cg, input.changedSymbols);
|
|
73
|
+
}
|
|
74
|
+
else if (input.diffRef !== undefined) {
|
|
75
|
+
try {
|
|
76
|
+
const { getChangedFiles } = await import('../../drift/git-diff.js');
|
|
77
|
+
const diff = await getChangedFiles({ rootPath: absDir, baseRef: input.diffRef || 'HEAD', includeUnstaged: true });
|
|
78
|
+
changedFiles = diff.files.map(f => f.path);
|
|
79
|
+
seeds = seedsFromFiles(cg, changedFiles);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
return { error: `git diff failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
return { error: 'Provide changedSymbols (a list of names) or diffRef (a git ref to diff against).' };
|
|
87
|
+
}
|
|
88
|
+
if (seeds.length === 0) {
|
|
89
|
+
return {
|
|
90
|
+
changed: changedFiles,
|
|
91
|
+
selectedTests: [],
|
|
92
|
+
message: input.diffRef !== undefined
|
|
93
|
+
? 'No changed production functions matched the call graph. The diff may touch only non-code files, or analyze_codebase is stale.'
|
|
94
|
+
: 'No matching production functions found for the given symbols.',
|
|
95
|
+
soundness: { posture: 'over-approximate', caveats: ['No seeds resolved — nothing to select.'] },
|
|
96
|
+
coverage: { languages: [], testDetection: 'none' },
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// ── Backward reachability with path tracking (calls + inheritance) ──────────
|
|
100
|
+
const { nodeMap, backward } = buildAdjacency(cg);
|
|
101
|
+
const seedIds = new Set(seeds.map(s => s.id));
|
|
102
|
+
const depthOf = new Map();
|
|
103
|
+
const parent = new Map(); // node → next node toward a seed
|
|
104
|
+
const queue = [];
|
|
105
|
+
for (const s of seeds) {
|
|
106
|
+
depthOf.set(s.id, 0);
|
|
107
|
+
queue.push({ id: s.id, depth: 0 });
|
|
108
|
+
}
|
|
109
|
+
while (queue.length) {
|
|
110
|
+
const { id, depth } = queue.shift();
|
|
111
|
+
if (depth >= maxDepth)
|
|
112
|
+
continue;
|
|
113
|
+
for (const caller of [...(backward.get(id) ?? [])].sort()) {
|
|
114
|
+
if (!depthOf.has(caller)) {
|
|
115
|
+
depthOf.set(caller, depth + 1);
|
|
116
|
+
parent.set(caller, id);
|
|
117
|
+
queue.push({ id: caller, depth: depth + 1 });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Path from a reached node down to its seed: [node, …, changedFn].
|
|
122
|
+
const pathToSeed = (id) => {
|
|
123
|
+
const names = [];
|
|
124
|
+
let cur = id;
|
|
125
|
+
const guard = new Set();
|
|
126
|
+
while (cur !== undefined && !guard.has(cur)) {
|
|
127
|
+
guard.add(cur);
|
|
128
|
+
names.push(nodeMap.get(cur)?.name ?? cur);
|
|
129
|
+
if (depthOf.get(cur) === 0)
|
|
130
|
+
break;
|
|
131
|
+
cur = parent.get(cur);
|
|
132
|
+
}
|
|
133
|
+
return names;
|
|
134
|
+
};
|
|
135
|
+
const byTest = new Map();
|
|
136
|
+
const add = (file, name, viaPath, confidence) => {
|
|
137
|
+
const key = testKey(file, name);
|
|
138
|
+
const existing = byTest.get(key);
|
|
139
|
+
if (!existing || CONF_RANK[confidence] > CONF_RANK[existing.confidence] ||
|
|
140
|
+
(CONF_RANK[confidence] === CONF_RANK[existing.confidence] && viaPath.length < existing.viaPath.length)) {
|
|
141
|
+
byTest.set(key, { test: name, file, viaPath, confidence });
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
// Source 1 — test nodes reached by the backward call-walk.
|
|
145
|
+
for (const [id, depth] of depthOf) {
|
|
146
|
+
if (depth === 0)
|
|
147
|
+
continue;
|
|
148
|
+
const n = nodeMap.get(id);
|
|
149
|
+
if (!n?.isTest || n.isExternal)
|
|
150
|
+
continue;
|
|
151
|
+
const confidence = depth === 1 ? 'high' : depth <= 3 ? 'medium' : 'low';
|
|
152
|
+
add(n.filePath, n.name, pathToSeed(id), confidence);
|
|
153
|
+
}
|
|
154
|
+
// Source 2 — `tested_by` edges on any reached production node (catches import-
|
|
155
|
+
// based associations whose test node isn't a real call-graph caller).
|
|
156
|
+
for (const e of cg.edges) {
|
|
157
|
+
if (e.kind !== 'tested_by')
|
|
158
|
+
continue;
|
|
159
|
+
if (!depthOf.has(e.callerId))
|
|
160
|
+
continue; // production node not in the impacted set
|
|
161
|
+
const testFile = e.calleeId.includes('::') ? e.calleeId.split('::')[0] : e.calleeId;
|
|
162
|
+
const onSeed = seedIds.has(e.callerId);
|
|
163
|
+
const confidence = onSeed ? 'high' : 'medium';
|
|
164
|
+
add(testFile, e.calleeName, [e.calleeName, ...pathToSeed(e.callerId)], confidence);
|
|
165
|
+
}
|
|
166
|
+
// Fallback — seeds with no reaching test at all: associate tests of sibling
|
|
167
|
+
// functions in the same file (newly-added / untested functions), low confidence.
|
|
168
|
+
let usedFileFallback = false;
|
|
169
|
+
const reachedSeedFiles = new Set([...byTest.values()].flatMap(t => t.viaPath));
|
|
170
|
+
for (const s of seeds) {
|
|
171
|
+
const seedHasTest = [...byTest.values()].some(t => t.viaPath.includes(s.name));
|
|
172
|
+
if (seedHasTest)
|
|
173
|
+
continue;
|
|
174
|
+
for (const e of cg.edges) {
|
|
175
|
+
if (e.kind !== 'tested_by')
|
|
176
|
+
continue;
|
|
177
|
+
const prod = nodeMap.get(e.callerId);
|
|
178
|
+
if (!prod || prod.filePath !== s.filePath)
|
|
179
|
+
continue;
|
|
180
|
+
const testFile = e.calleeId.includes('::') ? e.calleeId.split('::')[0] : e.calleeId;
|
|
181
|
+
add(testFile, e.calleeName, [e.calleeName, `(same file as ${s.name})`], 'low');
|
|
182
|
+
usedFileFallback = true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
void reachedSeedFiles;
|
|
186
|
+
const selectedTests = [...byTest.values()].sort((a, b) => CONF_RANK[b.confidence] - CONF_RANK[a.confidence] ||
|
|
187
|
+
a.file.localeCompare(b.file) || a.test.localeCompare(b.test));
|
|
188
|
+
// ── Coverage & soundness (honest, never falsely confident) ──────────────────
|
|
189
|
+
const seedLangs = [...new Set(seeds.map(s => s.language))].sort();
|
|
190
|
+
const graphHasTests = cg.nodes.some(n => n.isTest) || cg.edges.some(e => e.kind === 'tested_by');
|
|
191
|
+
const langsWithTests = new Set(cg.nodes.filter(n => n.isTest).map(n => n.language));
|
|
192
|
+
const testDetection = !graphHasTests ? 'none'
|
|
193
|
+
: seedLangs.every(l => langsWithTests.has(l)) ? 'full'
|
|
194
|
+
: 'partial';
|
|
195
|
+
const caveats = [
|
|
196
|
+
'Static call-graph selection is an over-approximate prioritizer, not a sound replacement for the full suite.',
|
|
197
|
+
'Dynamic dispatch, reflection, and dependency injection can under-select (a relevant test may be missed).',
|
|
198
|
+
];
|
|
199
|
+
if (testDetection === 'none') {
|
|
200
|
+
caveats.push('No tests were detected in this graph — the selection is empty, not "no tests needed". Verify test-file detection for your languages.');
|
|
201
|
+
}
|
|
202
|
+
else if (testDetection === 'partial') {
|
|
203
|
+
caveats.push(`Test detection is incomplete for some changed languages (${seedLangs.join(', ')}); tests in undetected languages are missing.`);
|
|
204
|
+
}
|
|
205
|
+
if (usedFileFallback) {
|
|
206
|
+
caveats.push('Some seeds had no reaching test; sibling-file tests were included at low confidence (likely newly-added or untested functions).');
|
|
207
|
+
}
|
|
208
|
+
if (selectedTests.length === 0 && testDetection !== 'none') {
|
|
209
|
+
caveats.push('No test transitively reaches the change. It may be genuinely untested, or reached only via dynamic dispatch this static analysis cannot see.');
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
changed: input.changedSymbols && input.changedSymbols.length > 0
|
|
213
|
+
? seeds.map(s => s.name)
|
|
214
|
+
: changedFiles,
|
|
215
|
+
seeds: seeds.map(s => ({ name: s.name, file: s.filePath })),
|
|
216
|
+
selectedTests,
|
|
217
|
+
soundness: { posture: 'over-approximate', caveats },
|
|
218
|
+
coverage: { languages: seedLangs, testDetection },
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=test-impact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-impact.js","sourceRoot":"","sources":["../../../../src/core/services/mcp-handlers/test-impact.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool response hardening (spec-10).
|
|
3
|
+
*
|
|
4
|
+
* A single set of guards every tool runs through in the CallTool handler, so the
|
|
5
|
+
* whole surface has one uniform input-validation, timeout, output-cap, and
|
|
6
|
+
* error-normalization path:
|
|
7
|
+
*
|
|
8
|
+
* - Input validation BEFORE the handler runs, against the tool's own declared
|
|
9
|
+
* `inputSchema` (reusing the hand-written JSON-Schema-subset validator from
|
|
10
|
+
* spec-05 — no Ajv). Invalid args map to JSON-RPC -32602 (spec-12).
|
|
11
|
+
* - Per-tool timeout via Promise.race, with slow tools overridden.
|
|
12
|
+
* - Output size cap: oversized results are truncated DETERMINISTICALLY with a
|
|
13
|
+
* `truncated: true` note telling the agent how to narrow the query — never a
|
|
14
|
+
* silent drop.
|
|
15
|
+
* - Error normalization to a stable code taxonomy, distinguishing "repo not
|
|
16
|
+
* analyzed yet" (actionable) from real failures.
|
|
17
|
+
*/
|
|
18
|
+
/** Stable MCP tool error-code taxonomy. */
|
|
19
|
+
export type McpToolErrorCode = 'INVALID_ARGS' | 'NOT_ANALYZED' | 'TIMEOUT' | 'OUTPUT_TRUNCATED' | 'INTERNAL';
|
|
20
|
+
/**
|
|
21
|
+
* Validate args against a tool's inputSchema. Returns a human-readable message on
|
|
22
|
+
* failure, or null when valid (or when no schema is declared).
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateToolArgs(args: unknown, inputSchema: unknown): string | null;
|
|
25
|
+
/** Thrown when a tool exceeds its timeout — classified as TIMEOUT downstream. */
|
|
26
|
+
export declare class ToolTimeoutError extends Error {
|
|
27
|
+
readonly toolName: string;
|
|
28
|
+
readonly ms: number;
|
|
29
|
+
constructor(toolName: string, ms: number);
|
|
30
|
+
}
|
|
31
|
+
/** The timeout budget for a tool (per-tool override or the default). */
|
|
32
|
+
export declare function toolTimeoutMs(toolName: string): number;
|
|
33
|
+
/** Race a tool's work against its timeout. Rejects with ToolTimeoutError on expiry. */
|
|
34
|
+
export declare function withToolTimeout<T>(work: Promise<T>, toolName: string, msOverride?: number): Promise<T>;
|
|
35
|
+
/**
|
|
36
|
+
* Deterministically cap a result string to a byte budget. When over budget, cut on
|
|
37
|
+
* a UTF-8-safe boundary and append a note explaining how to narrow the query.
|
|
38
|
+
*/
|
|
39
|
+
export declare function capOutput(text: string, maxBytes: number): {
|
|
40
|
+
text: string;
|
|
41
|
+
truncated: boolean;
|
|
42
|
+
};
|
|
43
|
+
/** Map an error to the stable taxonomy code (actionable vs real failure). */
|
|
44
|
+
export declare function classifyToolError(err: unknown): McpToolErrorCode;
|
|
45
|
+
//# sourceMappingURL=tool-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-guard.d.ts","sourceRoot":"","sources":["../../../../src/core/services/mcp-handlers/tool-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,2CAA2C;AAC3C,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,cAAc,GAAG,SAAS,GAAG,kBAAkB,GAAG,UAAU,CAAC;AAE7G;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAKnF;AAED,iFAAiF;AACjF,qBAAa,gBAAiB,SAAQ,KAAK;aACb,QAAQ,EAAE,MAAM;aAAkB,EAAE,EAAE,MAAM;gBAA5C,QAAQ,EAAE,MAAM,EAAkB,EAAE,EAAE,MAAM;CAIzE;AAED,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAOtG;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAS9F;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAOhE"}
|