@sean.holung/minicode 0.3.4 → 0.3.6
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 +25 -47
- package/dist/scripts/run-benchmarks.js +73 -28
- package/dist/src/agent/config.js +51 -66
- package/dist/src/agent/editable-config.js +50 -58
- package/dist/src/agent/home-env.js +74 -0
- package/dist/src/benchmark/runner.js +142 -59
- package/dist/src/cli/config-slash-command.js +15 -13
- package/dist/src/indexer/project-index.js +49 -13
- package/dist/src/serve/agent-bridge.js +99 -31
- package/dist/src/serve/mcp-server.js +70 -21
- package/dist/src/serve/server.js +198 -8
- package/dist/src/session/session-preview.js +14 -0
- package/dist/src/shared/graph-search.js +80 -0
- package/dist/src/shared/graph-selection.js +40 -0
- package/dist/src/shared/graph-symbols.js +82 -0
- package/dist/src/shared/symbol-resolution.js +33 -0
- package/dist/src/tools/find-path.js +15 -6
- package/dist/src/tools/find-references.js +7 -2
- package/dist/src/tools/get-dependencies.js +8 -3
- package/dist/src/tools/read-symbol.js +9 -3
- package/dist/src/tools/registry.js +4 -1
- package/dist/src/tools/search-code-map.js +18 -3
- package/dist/src/web/app.js +646 -87
- package/dist/src/web/index.html +68 -6
- package/dist/src/web/style.css +208 -1
- package/dist/tests/benchmark-harness.test.js +100 -0
- package/dist/tests/config-api.test.js +5 -5
- package/dist/tests/config-integration.test.js +130 -56
- package/dist/tests/config-slash-command.test.js +12 -11
- package/dist/tests/config.test.js +12 -4
- package/dist/tests/editable-config.test.js +15 -12
- package/dist/tests/file-tools.test.js +34 -1
- package/dist/tests/find-path.test.js +43 -2
- package/dist/tests/find-references.test.js +49 -0
- package/dist/tests/get-dependencies.test.js +23 -0
- package/dist/tests/graph-onboarding.test.js +10 -1
- package/dist/tests/graph-search.test.js +66 -0
- package/dist/tests/graph-selection.test.js +58 -0
- package/dist/tests/graph-symbols.test.js +45 -0
- package/dist/tests/home-env.test.js +56 -0
- package/dist/tests/indexer.test.js +6 -0
- package/dist/tests/read-symbol.test.js +35 -0
- package/dist/tests/request-tracker.test.js +15 -0
- package/dist/tests/run-benchmarks.test.js +117 -33
- package/dist/tests/search-code-map.test.js +2 -0
- package/dist/tests/serve.integration.test.js +338 -9
- package/dist/tests/session-preview.test.js +56 -0
- package/dist/tests/session-ui.test.js +4 -0
- package/dist/tests/settings-ui.test.js +18 -0
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js +2 -1
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/index.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/types.d.ts +3 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/types.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/registry.d.ts +3 -0
- package/node_modules/@minicode/agent-sdk/dist/src/tools/registry.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/registry.js +4 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/registry.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/run-command.d.ts +11 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/run-command.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/run-command.js +4 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/run-command.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/search.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/search.js +16 -8
- package/node_modules/@minicode/agent-sdk/dist/src/tools/search.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.js +19 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -48,22 +48,26 @@ function buildAdjacencyTo(edges) {
|
|
|
48
48
|
}
|
|
49
49
|
return map;
|
|
50
50
|
}
|
|
51
|
-
function
|
|
51
|
+
function resolveSymbols(name, symbols) {
|
|
52
52
|
const direct = symbols.get(name);
|
|
53
53
|
if (direct)
|
|
54
|
-
return direct;
|
|
54
|
+
return [direct];
|
|
55
55
|
const matches = [...symbols.values()].filter((sym) => getSymbolLookupNames(sym).includes(name));
|
|
56
56
|
if (matches.length === 0) {
|
|
57
|
-
return
|
|
57
|
+
return [];
|
|
58
58
|
}
|
|
59
59
|
matches.sort((a, b) => Number(b.exported) - Number(a.exported) ||
|
|
60
60
|
a.filePath.localeCompare(b.filePath) ||
|
|
61
61
|
a.startLine - b.startLine ||
|
|
62
62
|
a.qualifiedName.localeCompare(b.qualifiedName));
|
|
63
|
-
return matches
|
|
63
|
+
return matches;
|
|
64
|
+
}
|
|
65
|
+
function resolveSymbol(name, symbols) {
|
|
66
|
+
return resolveSymbols(name, symbols)[0];
|
|
64
67
|
}
|
|
65
68
|
export function createProjectIndex(symbols, files, dependencyEdges, plugins, projectFiles, workspaceRoot) {
|
|
66
69
|
let adjacencyFrom = buildAdjacencyFrom(dependencyEdges);
|
|
70
|
+
const root = path.resolve(workspaceRoot);
|
|
67
71
|
function rebuildSymbolsMap() {
|
|
68
72
|
const normalizedSymbols = normalizeIndexedSymbols(files);
|
|
69
73
|
symbols.clear();
|
|
@@ -71,6 +75,42 @@ export function createProjectIndex(symbols, files, dependencyEdges, plugins, pro
|
|
|
71
75
|
symbols.set(qualifiedName, symbol);
|
|
72
76
|
}
|
|
73
77
|
}
|
|
78
|
+
function rebuildDependencyEdges() {
|
|
79
|
+
for (const p of plugins) {
|
|
80
|
+
if (p.resolveDependencies) {
|
|
81
|
+
const allSymbols = [...symbols.values()];
|
|
82
|
+
const edges = p.resolveDependencies(allSymbols, projectFiles);
|
|
83
|
+
dependencyEdges.splice(0, dependencyEdges.length, ...edges);
|
|
84
|
+
adjacencyFrom = buildAdjacencyFrom(dependencyEdges);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function refreshFromWorkspace() {
|
|
90
|
+
const validExtensions = new Set(plugins.flatMap((p) => p.extensions));
|
|
91
|
+
const sourceFiles = [];
|
|
92
|
+
await collectSourceFiles(root, root, sourceFiles, validExtensions);
|
|
93
|
+
files.clear();
|
|
94
|
+
projectFiles.clear();
|
|
95
|
+
for (const relPath of sourceFiles) {
|
|
96
|
+
const plugin = getPluginForFile(relPath, plugins);
|
|
97
|
+
if (!plugin)
|
|
98
|
+
continue;
|
|
99
|
+
const absPath = path.join(root, relPath);
|
|
100
|
+
let content;
|
|
101
|
+
try {
|
|
102
|
+
content = await readFile(absPath, "utf8");
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
projectFiles.set(relPath, content);
|
|
108
|
+
const extracted = plugin.indexFile(relPath, content);
|
|
109
|
+
files.set(relPath, extracted);
|
|
110
|
+
}
|
|
111
|
+
rebuildSymbolsMap();
|
|
112
|
+
rebuildDependencyEdges();
|
|
113
|
+
}
|
|
74
114
|
return {
|
|
75
115
|
symbols,
|
|
76
116
|
files,
|
|
@@ -81,6 +121,9 @@ export function createProjectIndex(symbols, files, dependencyEdges, plugins, pro
|
|
|
81
121
|
getSymbol(name) {
|
|
82
122
|
return resolveSymbol(name, symbols);
|
|
83
123
|
},
|
|
124
|
+
getSymbolMatches(name) {
|
|
125
|
+
return resolveSymbols(name, symbols);
|
|
126
|
+
},
|
|
84
127
|
getSymbolsInFile(filePath) {
|
|
85
128
|
return files.get(filePath) ?? [];
|
|
86
129
|
},
|
|
@@ -226,16 +269,9 @@ export function createProjectIndex(symbols, files, dependencyEdges, plugins, pro
|
|
|
226
269
|
const extracted = plugin.indexFile(relPath, content);
|
|
227
270
|
files.set(relPath, extracted);
|
|
228
271
|
rebuildSymbolsMap();
|
|
229
|
-
|
|
230
|
-
if (p.resolveDependencies) {
|
|
231
|
-
const allSymbols = [...symbols.values()];
|
|
232
|
-
const edges = p.resolveDependencies(allSymbols, projectFiles);
|
|
233
|
-
dependencyEdges.splice(0, dependencyEdges.length, ...edges);
|
|
234
|
-
adjacencyFrom = buildAdjacencyFrom(dependencyEdges);
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
272
|
+
rebuildDependencyEdges();
|
|
238
273
|
},
|
|
274
|
+
refreshFromWorkspace,
|
|
239
275
|
};
|
|
240
276
|
}
|
|
241
277
|
/**
|
|
@@ -13,9 +13,11 @@ import { getSymbolDisplayName } from "../indexer/symbol-names.js";
|
|
|
13
13
|
export class AgentBridge {
|
|
14
14
|
agent;
|
|
15
15
|
config;
|
|
16
|
+
baseConfig;
|
|
16
17
|
modelClient;
|
|
17
18
|
projectIndex;
|
|
18
|
-
|
|
19
|
+
toolRegistry;
|
|
20
|
+
sessionOpenRouterConnected = false;
|
|
19
21
|
busy = false;
|
|
20
22
|
abortController = null;
|
|
21
23
|
broadcast;
|
|
@@ -43,9 +45,8 @@ export class AgentBridge {
|
|
|
43
45
|
}
|
|
44
46
|
async init() {
|
|
45
47
|
const config = await loadAgentConfig();
|
|
46
|
-
let modelClient;
|
|
47
48
|
try {
|
|
48
|
-
modelClient = createModelClient(config);
|
|
49
|
+
this.modelClient = createModelClient(config);
|
|
49
50
|
}
|
|
50
51
|
catch {
|
|
51
52
|
// Model client may fail to initialize if API keys are missing.
|
|
@@ -75,27 +76,46 @@ export class AgentBridge {
|
|
|
75
76
|
return this.appendAnnotationsToResult(name, input, result);
|
|
76
77
|
};
|
|
77
78
|
this.config = config;
|
|
79
|
+
this.baseConfig = AgentBridge.cloneConfig(config);
|
|
78
80
|
this.projectIndex = projectIndex;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
};
|
|
97
|
-
this.agent = this.buildAgent();
|
|
81
|
+
this.toolRegistry = toolRegistry;
|
|
82
|
+
if (this.modelClient) {
|
|
83
|
+
this.agent = this.createAgent();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
static cloneConfig(config) {
|
|
87
|
+
return {
|
|
88
|
+
...config,
|
|
89
|
+
commandDenylist: [...config.commandDenylist],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
static applyConfig(target, source) {
|
|
93
|
+
const targetRecord = target;
|
|
94
|
+
for (const key of Object.keys(targetRecord)) {
|
|
95
|
+
if (!(key in source)) {
|
|
96
|
+
delete targetRecord[key];
|
|
97
|
+
}
|
|
98
98
|
}
|
|
99
|
+
Object.assign(targetRecord, AgentBridge.cloneConfig(source));
|
|
100
|
+
}
|
|
101
|
+
createAgent(session, onUiUpdate) {
|
|
102
|
+
if (!this.modelClient || !this.toolRegistry) {
|
|
103
|
+
throw new Error("Agent runtime is not initialized.");
|
|
104
|
+
}
|
|
105
|
+
return new CodingAgent({
|
|
106
|
+
config: this.config,
|
|
107
|
+
modelClient: this.modelClient,
|
|
108
|
+
toolRegistry: this.toolRegistry,
|
|
109
|
+
verbose: this.verbose,
|
|
110
|
+
...(session ? { session } : {}),
|
|
111
|
+
...(this.projectIndex !== undefined
|
|
112
|
+
? { getCodeMap: (focusSymbols) => this.projectIndex.getCodeMap(undefined, focusSymbols) }
|
|
113
|
+
: {}),
|
|
114
|
+
onUiUpdate: onUiUpdate ?? ((event) => {
|
|
115
|
+
this.emit(event);
|
|
116
|
+
}),
|
|
117
|
+
getSystemPromptSuffix: () => this.buildAnnotationSuffix(),
|
|
118
|
+
});
|
|
99
119
|
}
|
|
100
120
|
// ── File watcher for automatic reindexing ──
|
|
101
121
|
static WATCH_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
@@ -171,6 +191,45 @@ export class AgentBridge {
|
|
|
171
191
|
getConfig() {
|
|
172
192
|
return this.config;
|
|
173
193
|
}
|
|
194
|
+
isOpenRouterSessionConnected() {
|
|
195
|
+
return this.sessionOpenRouterConnected;
|
|
196
|
+
}
|
|
197
|
+
connectOpenRouter(apiKey) {
|
|
198
|
+
const trimmedKey = apiKey.trim();
|
|
199
|
+
if (!trimmedKey) {
|
|
200
|
+
throw new Error("OpenRouter OAuth exchange did not return an API key.");
|
|
201
|
+
}
|
|
202
|
+
if (this.busy) {
|
|
203
|
+
throw new Error("busy");
|
|
204
|
+
}
|
|
205
|
+
const currentSession = this.agent?.getSession();
|
|
206
|
+
this.config.modelProvider = "openai-compatible";
|
|
207
|
+
this.config.openAiBaseUrl = "https://openrouter.ai/api/v1";
|
|
208
|
+
this.config.openAiApiKey = trimmedKey;
|
|
209
|
+
this.sessionOpenRouterConnected = true;
|
|
210
|
+
this.modelClient = createModelClient(this.config);
|
|
211
|
+
this.agent = this.createAgent(currentSession);
|
|
212
|
+
}
|
|
213
|
+
disconnectOpenRouter() {
|
|
214
|
+
if (this.busy) {
|
|
215
|
+
throw new Error("busy");
|
|
216
|
+
}
|
|
217
|
+
if (!this.sessionOpenRouterConnected) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
const currentSession = this.agent?.getSession();
|
|
221
|
+
AgentBridge.applyConfig(this.config, this.baseConfig);
|
|
222
|
+
this.sessionOpenRouterConnected = false;
|
|
223
|
+
try {
|
|
224
|
+
this.modelClient = createModelClient(this.config);
|
|
225
|
+
this.agent = this.createAgent(currentSession);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
this.modelClient = undefined;
|
|
229
|
+
this.agent = undefined;
|
|
230
|
+
}
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
174
233
|
requireAgent() {
|
|
175
234
|
if (!this.agent) {
|
|
176
235
|
throw new Error("Agent is not configured. Set MODEL and provider settings in ~/.minicode/.env");
|
|
@@ -230,13 +289,13 @@ export class AgentBridge {
|
|
|
230
289
|
return saveSession(agent.getSession(), label, annotationsObj);
|
|
231
290
|
}
|
|
232
291
|
async loadSess(label) {
|
|
233
|
-
if (!this.
|
|
292
|
+
if (!this.modelClient) {
|
|
234
293
|
throw new Error("Agent is not configured.");
|
|
235
294
|
}
|
|
236
295
|
const result = (await loadSessionByLabel(label)) ?? (await loadSession(label));
|
|
237
296
|
if (!result)
|
|
238
297
|
return null;
|
|
239
|
-
this.agent = this.
|
|
298
|
+
this.agent = this.createAgent(result.session);
|
|
240
299
|
// Restore annotations from saved session
|
|
241
300
|
this.annotations.clear();
|
|
242
301
|
if (result.annotations) {
|
|
@@ -276,10 +335,18 @@ export class AgentBridge {
|
|
|
276
335
|
return undefined;
|
|
277
336
|
return this.projectIndex.getSymbol(name);
|
|
278
337
|
}
|
|
338
|
+
getSymbolMatches(name) {
|
|
339
|
+
if (!this.projectIndex)
|
|
340
|
+
return [];
|
|
341
|
+
return this.projectIndex.getSymbolMatches(name);
|
|
342
|
+
}
|
|
279
343
|
getDependencies(symbolName, depth) {
|
|
280
344
|
if (!this.projectIndex)
|
|
281
345
|
return undefined;
|
|
282
|
-
const
|
|
346
|
+
const matches = this.projectIndex.getSymbolMatches(symbolName);
|
|
347
|
+
if (matches.length !== 1)
|
|
348
|
+
return undefined;
|
|
349
|
+
const cone = this.projectIndex.getDependencyCone(matches[0].qualifiedName, depth);
|
|
283
350
|
if (cone.length === 0)
|
|
284
351
|
return undefined;
|
|
285
352
|
return cone.map((sym) => ({
|
|
@@ -293,9 +360,10 @@ export class AgentBridge {
|
|
|
293
360
|
getReferences(symbolName) {
|
|
294
361
|
if (!this.projectIndex)
|
|
295
362
|
return undefined;
|
|
296
|
-
const
|
|
297
|
-
if (
|
|
363
|
+
const matches = this.projectIndex.getSymbolMatches(symbolName);
|
|
364
|
+
if (matches.length !== 1)
|
|
298
365
|
return undefined;
|
|
366
|
+
const sym = matches[0];
|
|
299
367
|
// Find all edges pointing TO this symbol
|
|
300
368
|
const refs = this.projectIndex.dependencyEdges
|
|
301
369
|
.filter((e) => e.to === sym.qualifiedName || e.to === sym.name)
|
|
@@ -464,12 +532,12 @@ export class AgentBridge {
|
|
|
464
532
|
async explainSymbol(name, onEvent, signal) {
|
|
465
533
|
if (!this.projectIndex)
|
|
466
534
|
throw new Error("No project index");
|
|
467
|
-
if (!this.
|
|
535
|
+
if (!this.modelClient)
|
|
468
536
|
throw new Error("Agent is not configured.");
|
|
469
537
|
const sym = this.projectIndex.getSymbol(name);
|
|
470
538
|
if (!sym)
|
|
471
539
|
throw new Error(`Symbol "${name}" not found`);
|
|
472
|
-
const explainAgent = this.
|
|
540
|
+
const explainAgent = this.createAgent(undefined, onEvent);
|
|
473
541
|
const prompt = `Explain "${sym.name}" (${sym.kind} in ${sym.filePath}).
|
|
474
542
|
Use read_symbol, get_dependencies, find_references to gather context.
|
|
475
543
|
Explain what it does, how it works, what depends on it, and key design decisions.
|
|
@@ -482,12 +550,12 @@ Be concise but thorough.`;
|
|
|
482
550
|
const report = this.getStructuralAnalysis();
|
|
483
551
|
if (!report)
|
|
484
552
|
throw new Error("No project index");
|
|
485
|
-
if (!this.
|
|
553
|
+
if (!this.modelClient)
|
|
486
554
|
throw new Error("Agent is not configured.");
|
|
487
555
|
const finding = report.findings.find((item) => item.id === findingId);
|
|
488
556
|
if (!finding)
|
|
489
557
|
throw new Error(`Structural finding "${findingId}" not found`);
|
|
490
|
-
const explainAgent = this.
|
|
558
|
+
const explainAgent = this.createAgent(undefined, onEvent);
|
|
491
559
|
const affectedSymbols = finding.symbols.length > 0
|
|
492
560
|
? finding.symbols.slice(0, 8).join(", ")
|
|
493
561
|
: "(none)";
|
|
@@ -10,6 +10,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
10
10
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { getSymbolDisplayName } from "../indexer/symbol-names.js";
|
|
13
|
+
import { formatAmbiguousSymbolMatches, resolveSymbolInput, } from "../shared/symbol-resolution.js";
|
|
13
14
|
/** Active transports keyed by session ID. */
|
|
14
15
|
const transports = new Map();
|
|
15
16
|
/**
|
|
@@ -44,12 +45,19 @@ function createMcpServer(bridge, emit) {
|
|
|
44
45
|
// ── Tools ──
|
|
45
46
|
server.tool("read_symbol", "Read a specific function, class, interface, or type by name from the AST index. Returns source code, signature, dependencies, references, and annotations in one call — much more targeted than reading an entire file. PREFERRED over read_file for .ts/.tsx/.js/.jsx when you know the symbol name.", { name: z.string().describe("The symbol name or qualified name (e.g. 'Session' or 'Session.trim')") }, async ({ name }) => {
|
|
46
47
|
return wrapToolCall("read_symbol", { name }, async () => {
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
48
|
+
const resolution = resolveSymbolInput(bridge, name);
|
|
49
|
+
if (resolution.status === "missing") {
|
|
49
50
|
return { content: [{ type: "text", text: `Symbol "${name}" not found in the project index.` }], isError: true };
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
if (resolution.status === "ambiguous") {
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: formatAmbiguousSymbolMatches("read_symbol", name, resolution.matches) }],
|
|
55
|
+
isError: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const sym = resolution.symbol;
|
|
59
|
+
const deps = bridge.getDependencies(sym.qualifiedName, 1);
|
|
60
|
+
const refs = bridge.getReferences(sym.qualifiedName);
|
|
53
61
|
const lines = [
|
|
54
62
|
`## ${sym.kind}: ${getSymbolDisplayName(sym)}`,
|
|
55
63
|
`File: ${sym.filePath}:${sym.startLine}`,
|
|
@@ -85,14 +93,28 @@ function createMcpServer(bridge, emit) {
|
|
|
85
93
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
86
94
|
});
|
|
87
95
|
});
|
|
88
|
-
server.tool("find_references", "Find all symbols that call, import, or reference a given symbol. Essential for understanding impact before making changes.", { name: z.string().describe("The symbol name to find references for") }, async ({ name }) => {
|
|
96
|
+
server.tool("find_references", "Find all symbols that call, import, or reference a given symbol. Essential for understanding impact before making changes.", { name: z.string().describe("The symbol name or qualified name to find references for") }, async ({ name }) => {
|
|
89
97
|
return wrapToolCall("find_references", { name }, async () => {
|
|
90
|
-
const
|
|
98
|
+
const resolution = resolveSymbolInput(bridge, name);
|
|
99
|
+
if (resolution.status === "missing") {
|
|
100
|
+
return { content: [{ type: "text", text: `Symbol "${name}" not found.` }], isError: true };
|
|
101
|
+
}
|
|
102
|
+
if (resolution.status === "ambiguous") {
|
|
103
|
+
return {
|
|
104
|
+
content: [{ type: "text", text: formatAmbiguousSymbolMatches("find_references", name, resolution.matches) }],
|
|
105
|
+
isError: true,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const symbol = resolution.symbol;
|
|
109
|
+
const refs = bridge.getReferences(symbol.qualifiedName);
|
|
91
110
|
if (!refs || refs.length === 0) {
|
|
92
111
|
return { content: [{ type: "text", text: `No references found for "${name}".` }] };
|
|
93
112
|
}
|
|
94
|
-
const lines = [
|
|
95
|
-
|
|
113
|
+
const lines = [
|
|
114
|
+
`References to ${getSymbolDisplayName(symbol)}:`,
|
|
115
|
+
...refs.map((r) => ` - ${r.name ?? r.from} (${r.kind})`),
|
|
116
|
+
];
|
|
117
|
+
const annotations = bridge.getAnnotationsForSymbol(symbol.qualifiedName);
|
|
96
118
|
if (annotations.length > 0) {
|
|
97
119
|
lines.push("", `[User annotation: ${annotations.join("; ")}]`);
|
|
98
120
|
}
|
|
@@ -100,19 +122,30 @@ function createMcpServer(bridge, emit) {
|
|
|
100
122
|
});
|
|
101
123
|
});
|
|
102
124
|
server.tool("get_dependencies", "Get the dependency cone of a symbol — everything it calls, imports, extends, or references. Essential for understanding implementation and data flow.", {
|
|
103
|
-
name: z.string().describe("The symbol name to get dependencies for"),
|
|
125
|
+
name: z.string().describe("The symbol name or qualified name to get dependencies for"),
|
|
104
126
|
depth: z.number().optional().default(2).describe("How many levels deep to traverse (default: 2)"),
|
|
105
127
|
}, async ({ name, depth }) => {
|
|
106
128
|
return wrapToolCall("get_dependencies", { name, depth }, async () => {
|
|
107
|
-
const
|
|
129
|
+
const resolution = resolveSymbolInput(bridge, name);
|
|
130
|
+
if (resolution.status === "missing") {
|
|
131
|
+
return { content: [{ type: "text", text: `Symbol "${name}" not found.` }], isError: true };
|
|
132
|
+
}
|
|
133
|
+
if (resolution.status === "ambiguous") {
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: formatAmbiguousSymbolMatches("get_dependencies", name, resolution.matches) }],
|
|
136
|
+
isError: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const symbol = resolution.symbol;
|
|
140
|
+
const deps = bridge.getDependencies(symbol.qualifiedName, depth);
|
|
108
141
|
if (!deps || deps.length === 0) {
|
|
109
142
|
return { content: [{ type: "text", text: `No dependencies found for "${name}".` }] };
|
|
110
143
|
}
|
|
111
144
|
const lines = [
|
|
112
|
-
`Dependencies of
|
|
145
|
+
`Dependencies of ${getSymbolDisplayName(symbol)} (depth=${depth}):`,
|
|
113
146
|
...deps.map((d) => ` - ${d.qualifiedName} (${d.kind}) — ${d.filePath}`),
|
|
114
147
|
];
|
|
115
|
-
const annotations = bridge.getAnnotationsForSymbol(
|
|
148
|
+
const annotations = bridge.getAnnotationsForSymbol(symbol.qualifiedName);
|
|
116
149
|
if (annotations.length > 0) {
|
|
117
150
|
lines.push("", `[User annotation: ${annotations.join("; ")}]`);
|
|
118
151
|
}
|
|
@@ -140,29 +173,41 @@ function createMcpServer(bridge, emit) {
|
|
|
140
173
|
}
|
|
141
174
|
const lines = [
|
|
142
175
|
`Found ${matches.length} symbol(s) matching "${query}":`,
|
|
143
|
-
...matches.map((s) => ` - ${s.name} (${s.kind}) — ${s.filePath}:${s.startLine}${s.signature ? `\n ${s.signature}` : ""}`),
|
|
176
|
+
...matches.map((s) => ` - ${s.name} (${s.kind}) — ${s.filePath}:${s.startLine} — qualified: ${s.qualifiedName}${s.signature ? `\n ${s.signature}` : ""}`),
|
|
144
177
|
];
|
|
145
178
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
146
179
|
});
|
|
147
180
|
});
|
|
148
181
|
server.tool("find_path", "Find the shortest dependency path between two symbols, or trace a symbol back to an entry point. Useful for understanding how code connects.", {
|
|
149
|
-
from: z.string().describe("Source symbol name"),
|
|
150
|
-
to: z.string().optional().describe("Target symbol name. If omitted, traces back to the nearest entry point."),
|
|
182
|
+
from: z.string().describe("Source symbol name or qualified name"),
|
|
183
|
+
to: z.string().optional().describe("Target symbol name or qualified name. If omitted, traces back to the nearest entry point."),
|
|
151
184
|
}, async ({ from, to }) => {
|
|
152
185
|
return wrapToolCall("find_path", { from, to }, async () => {
|
|
153
186
|
if (!bridge.hasIndex()) {
|
|
154
187
|
return { content: [{ type: "text", text: "No project index available." }], isError: true };
|
|
155
188
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (!fromSym) {
|
|
189
|
+
const fromResolution = resolveSymbolInput(bridge, from);
|
|
190
|
+
if (fromResolution.status === "missing") {
|
|
159
191
|
return { content: [{ type: "text", text: `Symbol "${from}" not found.` }], isError: true };
|
|
160
192
|
}
|
|
193
|
+
if (fromResolution.status === "ambiguous") {
|
|
194
|
+
return {
|
|
195
|
+
content: [{ type: "text", text: formatAmbiguousSymbolMatches("find_path", from, fromResolution.matches) }],
|
|
196
|
+
isError: true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const fromSym = fromResolution.symbol;
|
|
161
200
|
if (to) {
|
|
162
|
-
const
|
|
163
|
-
if (
|
|
201
|
+
const toResolution = resolveSymbolInput(bridge, to);
|
|
202
|
+
if (toResolution.status === "missing") {
|
|
164
203
|
return { content: [{ type: "text", text: `Symbol "${to}" not found.` }], isError: true };
|
|
165
204
|
}
|
|
205
|
+
if (toResolution.status === "ambiguous") {
|
|
206
|
+
return {
|
|
207
|
+
content: [{ type: "text", text: formatAmbiguousSymbolMatches("find_path", to, toResolution.matches) }],
|
|
208
|
+
isError: true,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
166
211
|
}
|
|
167
212
|
// Delegate to the find_path tool via the tool registry
|
|
168
213
|
// We need the actual ProjectIndex for this, so access it through the bridge's getGraph
|
|
@@ -189,7 +234,11 @@ function createMcpServer(bridge, emit) {
|
|
|
189
234
|
}
|
|
190
235
|
const startId = fromSym.qualifiedName;
|
|
191
236
|
if (to) {
|
|
192
|
-
const
|
|
237
|
+
const toResolution = resolveSymbolInput(bridge, to);
|
|
238
|
+
if (toResolution.status !== "resolved") {
|
|
239
|
+
return { content: [{ type: "text", text: `Symbol "${to}" could not be resolved.` }], isError: true };
|
|
240
|
+
}
|
|
241
|
+
const toSym = toResolution.symbol;
|
|
193
242
|
const endId = toSym.qualifiedName;
|
|
194
243
|
// BFS shortest path
|
|
195
244
|
const visited = new Set([startId]);
|