@sean.holung/minicode 0.3.2 → 0.3.3
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 +48 -43
- package/dist/scripts/run-benchmarks.js +147 -0
- package/dist/src/agent/config.js +149 -40
- package/dist/src/agent/editable-config.js +314 -0
- package/dist/src/analysis/structural-analysis.js +379 -0
- package/dist/src/benchmark/evaluator.js +79 -0
- package/dist/src/benchmark/index.js +4 -0
- package/dist/src/benchmark/reporter.js +177 -0
- package/dist/src/benchmark/runner.js +100 -0
- package/dist/src/benchmark/task-loader.js +78 -0
- package/dist/src/benchmark/types.js +5 -0
- package/dist/src/cli/args.js +10 -0
- package/dist/src/cli/config-slash-command.js +135 -0
- package/dist/src/cli/plugin-install.js +69 -0
- package/dist/src/index.js +76 -6
- package/dist/src/indexer/cache.js +6 -4
- package/dist/src/indexer/code-map.js +41 -13
- package/dist/src/indexer/plugins/typescript.js +70 -23
- package/dist/src/indexer/project-index.js +175 -36
- package/dist/src/indexer/symbol-names.js +92 -0
- package/dist/src/model-utils.js +18 -0
- package/dist/src/serve/agent-bridge.js +203 -24
- package/dist/src/serve/mcp-server.js +405 -0
- package/dist/src/serve/server.js +165 -10
- package/dist/src/serve/websocket.js +8 -0
- package/dist/src/shared/graph-styles.js +119 -0
- package/dist/src/tools/find-path.js +75 -0
- package/dist/src/tools/find-references.js +7 -2
- package/dist/src/tools/get-dependencies.js +3 -2
- package/dist/src/tools/read-symbol.js +12 -5
- package/dist/src/tools/registry.js +3 -1
- package/dist/src/tools/search-code-map.js +4 -2
- package/dist/src/ui/app.js +1 -1
- package/dist/src/ui/cli-ink.js +79 -4
- package/dist/src/ui/components/header-bar.js +6 -2
- package/dist/src/ui/state/ui-store.js +5 -0
- package/dist/src/web/app.js +1124 -176
- package/dist/src/web/index.html +113 -3
- package/dist/src/web/style.css +973 -55
- package/dist/tests/agent.test.js +31 -0
- package/dist/tests/analysis-helpers.test.js +89 -0
- package/dist/tests/analysis-ui.test.js +29 -0
- package/dist/tests/benchmark-harness.test.js +527 -0
- package/dist/tests/config-api.test.js +143 -0
- package/dist/tests/config-integration.test.js +751 -0
- package/dist/tests/config-slash-command.test.js +106 -0
- package/dist/tests/config.test.js +42 -1
- package/dist/tests/context-indicator.test.js +220 -0
- package/dist/tests/editable-config.test.js +109 -0
- package/dist/tests/find-path.test.js +183 -0
- package/dist/tests/focus-tracker.test.js +62 -0
- package/dist/tests/graph-onboarding.test.js +55 -0
- package/dist/tests/graph-styles.test.js +65 -0
- package/dist/tests/indexer.test.js +137 -0
- package/dist/tests/mcp-and-plugin.test.js +186 -0
- package/dist/tests/model-client-openai.test.js +29 -0
- package/dist/tests/model-selection.test.js +136 -0
- package/dist/tests/model-utils.test.js +22 -0
- package/dist/tests/reasoning-effort.test.js +264 -0
- package/dist/tests/run-benchmarks.test.js +161 -0
- package/dist/tests/search-code-map.test.js +18 -0
- package/dist/tests/serve.integration.test.js +218 -2
- package/dist/tests/session-ui.test.js +21 -0
- package/dist/tests/session.test.js +50 -0
- package/dist/tests/settings-ui.test.js +30 -0
- package/dist/tests/structural-analysis.test.js +218 -0
- package/node_modules/@minicode/agent-sdk/README.md +80 -51
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.d.ts +16 -5
- 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 +51 -33
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/agent/types.d.ts +14 -0
- package/node_modules/@minicode/agent-sdk/dist/src/agent/types.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts +3 -2
- package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/index.js +2 -0
- package/node_modules/@minicode/agent-sdk/dist/src/index.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/focus-tracker.d.ts +35 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/focus-tracker.d.ts.map +1 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/focus-tracker.js +64 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/focus-tracker.js.map +1 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/types.d.ts +7 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/types.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.d.ts +5 -1
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.js +83 -11
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/safety/guardrails.d.ts +1 -0
- package/node_modules/@minicode/agent-sdk/dist/src/safety/guardrails.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/safety/guardrails.js +8 -1
- package/node_modules/@minicode/agent-sdk/dist/src/safety/guardrails.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/session/session.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/session/session.js +4 -1
- package/node_modules/@minicode/agent-sdk/dist/src/session/session.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.js +3 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.js +8 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -5
- package/plugin/.claude-plugin/plugin.json +12 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/CLAUDE.md +26 -0
- package/plugin/skills/analyze/SKILL.md +12 -0
- package/plugin/skills/focus/SKILL.md +20 -0
- package/plugin/skills/graph/SKILL.md +13 -0
- package/plugin/skills/symbols/SKILL.md +13 -0
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { CodingAgent, createModelClient } from "@minicode/agent-sdk";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { watch } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { analyzeProjectStructure } from "../analysis/structural-analysis.js";
|
|
2
6
|
import { loadAgentConfig } from "../agent/config.js";
|
|
3
7
|
import { computeFileHashes, getWorkspaceCacheDir, loadIndex, saveIndex, } from "../indexer/cache.js";
|
|
4
8
|
import { buildProjectIndex } from "../indexer/project-index.js";
|
|
9
|
+
import { sortModelsAlphabetically } from "../model-utils.js";
|
|
5
10
|
import { createToolRegistry } from "../tools/registry.js";
|
|
6
11
|
import { listSessions, loadSession, loadSessionByLabel, saveSession, } from "../session/session-store.js";
|
|
12
|
+
import { getSymbolDisplayName } from "../indexer/symbol-names.js";
|
|
7
13
|
export class AgentBridge {
|
|
8
14
|
agent;
|
|
9
15
|
config;
|
|
16
|
+
modelClient;
|
|
10
17
|
projectIndex;
|
|
11
18
|
buildAgent;
|
|
12
19
|
busy = false;
|
|
@@ -16,6 +23,8 @@ export class AgentBridge {
|
|
|
16
23
|
listeners = new Set();
|
|
17
24
|
pinnedSymbols = new Set();
|
|
18
25
|
annotations = new Map();
|
|
26
|
+
fileWatcher = null;
|
|
27
|
+
reindexTimers = new Map();
|
|
19
28
|
constructor(broadcast, verbose) {
|
|
20
29
|
this.broadcast = broadcast;
|
|
21
30
|
this.verbose = verbose;
|
|
@@ -34,7 +43,14 @@ export class AgentBridge {
|
|
|
34
43
|
}
|
|
35
44
|
async init() {
|
|
36
45
|
const config = await loadAgentConfig();
|
|
37
|
-
|
|
46
|
+
let modelClient;
|
|
47
|
+
try {
|
|
48
|
+
modelClient = createModelClient(config);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Model client may fail to initialize if API keys are missing.
|
|
52
|
+
// Serve mode continues so the web UI can show setup instructions.
|
|
53
|
+
}
|
|
38
54
|
let projectIndex;
|
|
39
55
|
try {
|
|
40
56
|
const cacheDir = getWorkspaceCacheDir(config.workspaceRoot);
|
|
@@ -60,23 +76,94 @@ export class AgentBridge {
|
|
|
60
76
|
};
|
|
61
77
|
this.config = config;
|
|
62
78
|
this.projectIndex = projectIndex;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
? {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
if (modelClient) {
|
|
80
|
+
this.modelClient = modelClient;
|
|
81
|
+
this.buildAgent = (session, onUiUpdate) => {
|
|
82
|
+
return new CodingAgent({
|
|
83
|
+
config,
|
|
84
|
+
modelClient,
|
|
85
|
+
toolRegistry,
|
|
86
|
+
verbose: this.verbose,
|
|
87
|
+
...(session ? { session } : {}),
|
|
88
|
+
...(projectIndex !== undefined
|
|
89
|
+
? { getCodeMap: (focusSymbols) => projectIndex.getCodeMap(undefined, focusSymbols) }
|
|
90
|
+
: {}),
|
|
91
|
+
onUiUpdate: onUiUpdate ?? ((event) => {
|
|
92
|
+
this.emit(event);
|
|
93
|
+
}),
|
|
94
|
+
getSystemPromptSuffix: () => this.buildAnnotationSuffix(),
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
this.agent = this.buildAgent();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// ── File watcher for automatic reindexing ──
|
|
101
|
+
static WATCH_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
102
|
+
static SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", "coverage"]);
|
|
103
|
+
static REINDEX_DEBOUNCE_MS = 300;
|
|
104
|
+
/**
|
|
105
|
+
* Start watching the workspace for file changes and reindex automatically.
|
|
106
|
+
* Call after init(). Safe to call if no project index exists (no-ops).
|
|
107
|
+
*/
|
|
108
|
+
startFileWatcher() {
|
|
109
|
+
if (!this.projectIndex || this.fileWatcher)
|
|
110
|
+
return;
|
|
111
|
+
const workspaceRoot = this.config.workspaceRoot;
|
|
112
|
+
try {
|
|
113
|
+
this.fileWatcher = watch(workspaceRoot, { recursive: true }, (_event, filename) => {
|
|
114
|
+
if (!filename || !this.projectIndex)
|
|
115
|
+
return;
|
|
116
|
+
const normalized = filename.replace(/\\/g, "/");
|
|
117
|
+
// Skip ignored directories
|
|
118
|
+
const parts = normalized.split("/");
|
|
119
|
+
if (parts.some((p) => AgentBridge.SKIP_DIRS.has(p) || p.startsWith(".")))
|
|
120
|
+
return;
|
|
121
|
+
// Skip non-indexable extensions
|
|
122
|
+
const ext = path.extname(normalized).toLowerCase();
|
|
123
|
+
if (!AgentBridge.WATCH_EXTENSIONS.has(ext))
|
|
124
|
+
return;
|
|
125
|
+
// Debounce: if same file changes rapidly, only reindex once
|
|
126
|
+
const existing = this.reindexTimers.get(normalized);
|
|
127
|
+
if (existing)
|
|
128
|
+
clearTimeout(existing);
|
|
129
|
+
this.reindexTimers.set(normalized, setTimeout(() => {
|
|
130
|
+
this.reindexTimers.delete(normalized);
|
|
131
|
+
void this.reindexChangedFile(normalized);
|
|
132
|
+
}, AgentBridge.REINDEX_DEBOUNCE_MS));
|
|
77
133
|
});
|
|
78
|
-
}
|
|
79
|
-
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
console.warn("[warn] File watcher could not start (recursive fs.watch may not be supported on this platform).\n" +
|
|
137
|
+
" File changes will not trigger automatic reindexing. Restart minicode serve to pick up changes.");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
stopFileWatcher() {
|
|
141
|
+
if (this.fileWatcher) {
|
|
142
|
+
this.fileWatcher.close();
|
|
143
|
+
this.fileWatcher = null;
|
|
144
|
+
}
|
|
145
|
+
for (const timer of this.reindexTimers.values()) {
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
}
|
|
148
|
+
this.reindexTimers.clear();
|
|
149
|
+
}
|
|
150
|
+
async reindexChangedFile(relPath) {
|
|
151
|
+
if (!this.projectIndex)
|
|
152
|
+
return;
|
|
153
|
+
const absPath = path.resolve(this.config.workspaceRoot, relPath);
|
|
154
|
+
try {
|
|
155
|
+
const content = await readFile(absPath, "utf-8");
|
|
156
|
+
this.projectIndex.reindexFile(relPath, content);
|
|
157
|
+
if (this.verbose) {
|
|
158
|
+
console.error(`[watch] Reindexed: ${relPath}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// File may have been deleted — ignore
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
isReady() {
|
|
166
|
+
return this.agent !== undefined;
|
|
80
167
|
}
|
|
81
168
|
isBusy() {
|
|
82
169
|
return this.busy;
|
|
@@ -84,10 +171,20 @@ export class AgentBridge {
|
|
|
84
171
|
getConfig() {
|
|
85
172
|
return this.config;
|
|
86
173
|
}
|
|
87
|
-
|
|
174
|
+
requireAgent() {
|
|
175
|
+
if (!this.agent) {
|
|
176
|
+
throw new Error("Agent is not configured. Set MODEL and provider settings in ~/.minicode/.env");
|
|
177
|
+
}
|
|
88
178
|
return this.agent;
|
|
89
179
|
}
|
|
180
|
+
getAgent() {
|
|
181
|
+
return this.requireAgent();
|
|
182
|
+
}
|
|
183
|
+
getCurrentSessionId() {
|
|
184
|
+
return this.requireAgent().getSession().id;
|
|
185
|
+
}
|
|
90
186
|
async runTurn(message) {
|
|
187
|
+
const agent = this.requireAgent();
|
|
91
188
|
if (this.busy) {
|
|
92
189
|
throw new Error("busy");
|
|
93
190
|
}
|
|
@@ -95,7 +192,7 @@ export class AgentBridge {
|
|
|
95
192
|
this.abortController = new AbortController();
|
|
96
193
|
try {
|
|
97
194
|
this.emit({ type: "turn_start" });
|
|
98
|
-
const result = await
|
|
195
|
+
const result = await agent.runTurn(message, {
|
|
99
196
|
signal: this.abortController.signal,
|
|
100
197
|
});
|
|
101
198
|
this.emit({
|
|
@@ -126,12 +223,16 @@ export class AgentBridge {
|
|
|
126
223
|
}
|
|
127
224
|
// Session operations
|
|
128
225
|
async saveSess(label) {
|
|
226
|
+
const agent = this.requireAgent();
|
|
129
227
|
const annotationsObj = this.annotations.size > 0
|
|
130
228
|
? Object.fromEntries(this.annotations)
|
|
131
229
|
: undefined;
|
|
132
|
-
return saveSession(
|
|
230
|
+
return saveSession(agent.getSession(), label, annotationsObj);
|
|
133
231
|
}
|
|
134
232
|
async loadSess(label) {
|
|
233
|
+
if (!this.buildAgent) {
|
|
234
|
+
throw new Error("Agent is not configured.");
|
|
235
|
+
}
|
|
135
236
|
const result = (await loadSessionByLabel(label)) ?? (await loadSession(label));
|
|
136
237
|
if (!result)
|
|
137
238
|
return null;
|
|
@@ -158,7 +259,7 @@ export class AgentBridge {
|
|
|
158
259
|
const symbols = [];
|
|
159
260
|
for (const sym of this.projectIndex.symbols.values()) {
|
|
160
261
|
symbols.push({
|
|
161
|
-
name: sym
|
|
262
|
+
name: getSymbolDisplayName(sym),
|
|
162
263
|
qualifiedName: sym.qualifiedName,
|
|
163
264
|
kind: sym.kind,
|
|
164
265
|
filePath: sym.filePath,
|
|
@@ -182,7 +283,7 @@ export class AgentBridge {
|
|
|
182
283
|
if (cone.length === 0)
|
|
183
284
|
return undefined;
|
|
184
285
|
return cone.map((sym) => ({
|
|
185
|
-
name: sym
|
|
286
|
+
name: getSymbolDisplayName(sym),
|
|
186
287
|
qualifiedName: sym.qualifiedName,
|
|
187
288
|
kind: sym.kind,
|
|
188
289
|
filePath: sym.filePath,
|
|
@@ -198,7 +299,14 @@ export class AgentBridge {
|
|
|
198
299
|
// Find all edges pointing TO this symbol
|
|
199
300
|
const refs = this.projectIndex.dependencyEdges
|
|
200
301
|
.filter((e) => e.to === sym.qualifiedName || e.to === sym.name)
|
|
201
|
-
.map((e) =>
|
|
302
|
+
.map((e) => {
|
|
303
|
+
const fromSymbol = this.projectIndex.getSymbol(e.from);
|
|
304
|
+
return {
|
|
305
|
+
from: e.from,
|
|
306
|
+
kind: e.kind,
|
|
307
|
+
...(fromSymbol ? { name: getSymbolDisplayName(fromSymbol) } : {}),
|
|
308
|
+
};
|
|
309
|
+
});
|
|
202
310
|
return refs;
|
|
203
311
|
}
|
|
204
312
|
getCodeMap(tokenBudget) {
|
|
@@ -214,7 +322,7 @@ export class AgentBridge {
|
|
|
214
322
|
for (const sym of this.projectIndex.symbols.values()) {
|
|
215
323
|
nodes.push({
|
|
216
324
|
id: sym.qualifiedName,
|
|
217
|
-
name: sym
|
|
325
|
+
name: getSymbolDisplayName(sym),
|
|
218
326
|
kind: sym.kind,
|
|
219
327
|
filePath: sym.filePath,
|
|
220
328
|
exported: sym.exported,
|
|
@@ -227,6 +335,11 @@ export class AgentBridge {
|
|
|
227
335
|
}));
|
|
228
336
|
return { nodes, edges };
|
|
229
337
|
}
|
|
338
|
+
getStructuralAnalysis() {
|
|
339
|
+
if (!this.projectIndex)
|
|
340
|
+
return undefined;
|
|
341
|
+
return analyzeProjectStructure(this.projectIndex);
|
|
342
|
+
}
|
|
230
343
|
getPinnedSymbols() {
|
|
231
344
|
return [...this.pinnedSymbols];
|
|
232
345
|
}
|
|
@@ -337,10 +450,22 @@ export class AgentBridge {
|
|
|
337
450
|
}
|
|
338
451
|
return result;
|
|
339
452
|
}
|
|
453
|
+
// ── Model selection ──
|
|
454
|
+
async listModels() {
|
|
455
|
+
if (this.modelClient?.listModels) {
|
|
456
|
+
return sortModelsAlphabetically(await this.modelClient.listModels());
|
|
457
|
+
}
|
|
458
|
+
return [];
|
|
459
|
+
}
|
|
460
|
+
switchModel(modelId) {
|
|
461
|
+
this.config.model = modelId;
|
|
462
|
+
}
|
|
340
463
|
// ── Explain ──
|
|
341
464
|
async explainSymbol(name, onEvent, signal) {
|
|
342
465
|
if (!this.projectIndex)
|
|
343
466
|
throw new Error("No project index");
|
|
467
|
+
if (!this.buildAgent)
|
|
468
|
+
throw new Error("Agent is not configured.");
|
|
344
469
|
const sym = this.projectIndex.getSymbol(name);
|
|
345
470
|
if (!sym)
|
|
346
471
|
throw new Error(`Symbol "${name}" not found`);
|
|
@@ -353,4 +478,58 @@ Be concise but thorough.`;
|
|
|
353
478
|
const result = await explainAgent.runTurn(prompt, opts);
|
|
354
479
|
return result.text;
|
|
355
480
|
}
|
|
481
|
+
async explainStructuralFinding(findingId, onEvent, signal) {
|
|
482
|
+
const report = this.getStructuralAnalysis();
|
|
483
|
+
if (!report)
|
|
484
|
+
throw new Error("No project index");
|
|
485
|
+
if (!this.buildAgent)
|
|
486
|
+
throw new Error("Agent is not configured.");
|
|
487
|
+
const finding = report.findings.find((item) => item.id === findingId);
|
|
488
|
+
if (!finding)
|
|
489
|
+
throw new Error(`Structural finding "${findingId}" not found`);
|
|
490
|
+
const explainAgent = this.buildAgent(undefined, onEvent);
|
|
491
|
+
const affectedSymbols = finding.symbols.length > 0
|
|
492
|
+
? finding.symbols.slice(0, 8).join(", ")
|
|
493
|
+
: "(none)";
|
|
494
|
+
const affectedFiles = finding.files.length > 0
|
|
495
|
+
? finding.files.slice(0, 6).join(", ")
|
|
496
|
+
: "(none)";
|
|
497
|
+
const rationale = finding.rationale.map((item) => `- ${item}`).join("\n");
|
|
498
|
+
const metrics = Object.entries(finding.metrics)
|
|
499
|
+
.map(([key, value]) => `- ${key}: ${String(value)}`)
|
|
500
|
+
.join("\n");
|
|
501
|
+
const prompt = `Interpret this deterministic structural analysis finding for the current codebase.
|
|
502
|
+
|
|
503
|
+
Finding type: ${finding.type}
|
|
504
|
+
Severity: ${finding.severity}
|
|
505
|
+
Title: ${finding.title}
|
|
506
|
+
Summary: ${finding.summary}
|
|
507
|
+
|
|
508
|
+
Affected symbols:
|
|
509
|
+
${affectedSymbols}
|
|
510
|
+
|
|
511
|
+
Affected files:
|
|
512
|
+
${affectedFiles}
|
|
513
|
+
|
|
514
|
+
Deterministic rationale:
|
|
515
|
+
${rationale || "- No extra rationale provided."}
|
|
516
|
+
|
|
517
|
+
Metrics:
|
|
518
|
+
${metrics || "- No metrics provided."}
|
|
519
|
+
|
|
520
|
+
Important constraints:
|
|
521
|
+
- This finding came from deterministic graph analysis, not from the model.
|
|
522
|
+
- Your job is to interpret the finding, not to replace it.
|
|
523
|
+
- Keep the explanation advisory and note where the signal may be noisy or incomplete.
|
|
524
|
+
- If useful, inspect a few affected symbols with read_symbol, get_dependencies, and find_references before explaining.
|
|
525
|
+
|
|
526
|
+
Respond with short sections for:
|
|
527
|
+
1. What this likely means
|
|
528
|
+
2. Why it may matter
|
|
529
|
+
3. Caveats / false-positive risk
|
|
530
|
+
4. Potential next steps`;
|
|
531
|
+
const opts = signal ? { signal } : undefined;
|
|
532
|
+
const result = await explainAgent.runTurn(prompt, opts);
|
|
533
|
+
return result.text;
|
|
534
|
+
}
|
|
356
535
|
}
|