@takara-ai/miru-code 1.3.1 → 1.4.0
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/package.json +1 -1
- package/src/benchmark/compare.ts +22 -5
- package/src/benchmark/locate-compare.ts +8 -6
- package/src/benchmark/summary.ts +10 -1
- package/src/benchmark/types.ts +11 -1
- package/src/help.ts +3 -2
- package/src/installer/search-policy.ts +4 -1
- package/src/mcp/format-text.ts +67 -0
- package/src/mcp/index-cache.ts +5 -0
- package/src/mcp/server.ts +28 -28
package/package.json
CHANGED
package/src/benchmark/compare.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { relativePathFromRoot } from "../index/incremental.ts";
|
|
4
|
+
import { formatExpandResultsText, formatResultsText } from "../mcp/format-text.ts";
|
|
4
5
|
import type { MiruIndex } from "../miru-index.ts";
|
|
5
|
-
import { applySnippetsToResults
|
|
6
|
+
import { applySnippetsToResults } from "../snippet.ts";
|
|
6
7
|
import { countTokens, tokenCountMethod, tokenizerJsonPath } from "../token-count.ts";
|
|
7
8
|
import type { SearchResult } from "../types.ts";
|
|
8
9
|
import {
|
|
@@ -10,6 +11,8 @@ import {
|
|
|
10
11
|
DEFAULT_EXPAND_BEFORE,
|
|
11
12
|
dedupeResultsByFile,
|
|
12
13
|
expandChunksAtLine,
|
|
14
|
+
formatExpandResults,
|
|
15
|
+
formatResults,
|
|
13
16
|
} from "../utils.ts";
|
|
14
17
|
import { type GrepFileHit, grepSearch } from "./grep.ts";
|
|
15
18
|
import { agentBenchmarkFromTokens, attachAgentBenchmark, tokenSavingsPct } from "./summary.ts";
|
|
@@ -83,6 +86,7 @@ async function readLineWindowTokens(
|
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
|
|
89
|
+
/** Tokens of the MCP `expand` text body (headers + chunk content), matching what agents receive. */
|
|
86
90
|
function miruExpandTokens(
|
|
87
91
|
index: MiruIndex,
|
|
88
92
|
repoPath: string,
|
|
@@ -94,7 +98,7 @@ function miruExpandTokens(
|
|
|
94
98
|
}
|
|
95
99
|
const [{ meta }] = applySnippetsToResults([top], query);
|
|
96
100
|
const line = meta.truncated ? meta.anchor_line : top.chunk.start_line;
|
|
97
|
-
const { chunks } = expandChunksAtLine(
|
|
101
|
+
const { anchor, chunks } = expandChunksAtLine(
|
|
98
102
|
index.chunks,
|
|
99
103
|
top.chunk.file_path,
|
|
100
104
|
line,
|
|
@@ -102,8 +106,18 @@ function miruExpandTokens(
|
|
|
102
106
|
DEFAULT_EXPAND_BEFORE,
|
|
103
107
|
DEFAULT_EXPAND_AFTER,
|
|
104
108
|
);
|
|
109
|
+
if (!anchor) {
|
|
110
|
+
return { tokens: 0, lineSpan: 0 };
|
|
111
|
+
}
|
|
112
|
+
const text = formatExpandResultsText(
|
|
113
|
+
formatExpandResults(top.chunk.file_path, line, anchor, chunks, {
|
|
114
|
+
repoRoot: repoPath,
|
|
115
|
+
before: DEFAULT_EXPAND_BEFORE,
|
|
116
|
+
after: DEFAULT_EXPAND_AFTER,
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
105
119
|
return {
|
|
106
|
-
tokens:
|
|
120
|
+
tokens: countTokens(text),
|
|
107
121
|
lineSpan: expandLineSpan(chunks),
|
|
108
122
|
};
|
|
109
123
|
}
|
|
@@ -164,8 +178,10 @@ export async function benchmarkSearchComparison(options: {
|
|
|
164
178
|
const started = performance.now();
|
|
165
179
|
const raw = await index.search({ query, topK, rerank: true });
|
|
166
180
|
const results = (dedupeByFile ? dedupeResultsByFile(raw) : raw).slice(0, topK);
|
|
167
|
-
|
|
168
|
-
const searchTokens =
|
|
181
|
+
// Count the MCP tool text body (path headers + snippets), not bare chunk content.
|
|
182
|
+
const searchTokens = countTokens(
|
|
183
|
+
formatResultsText(formatResults(query, results, { repoRoot: repoPath, snippet: true })),
|
|
184
|
+
);
|
|
169
185
|
const expand = miruExpandTokens(index, repoPath, results[0], query);
|
|
170
186
|
return {
|
|
171
187
|
results,
|
|
@@ -264,6 +280,7 @@ export function toAgentBenchmarkSummary(block: SearchBenchmarkBlock): AgentBench
|
|
|
264
280
|
block.accuracy.rank1_match,
|
|
265
281
|
);
|
|
266
282
|
summary.save_pct = block.efficiency.token_savings_pct;
|
|
283
|
+
summary.search_tok = block.miru.search_tokens;
|
|
267
284
|
if (block.accuracy.miru_only.length > 0) {
|
|
268
285
|
summary.miru_only = block.accuracy.miru_only.slice(0, MAX_AGENT_MIRU_ONLY);
|
|
269
286
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Compare Miru `locate`
|
|
2
|
+
* Compare Miru `locate` MCP response tokens vs the costlier native Grep path.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Miru side counts the agent-facing text body (`formatLiteralLocateText`), not
|
|
5
|
+
* the intermediate JSON payload. Baseline mirrors Cursor/Claude Grep-style
|
|
6
|
+
* output: fixed-string matches with ±GREP_CONTEXT lines (not slim `rg -n`).
|
|
7
|
+
* That is the expensive path agents take without `locate`, so savings measure
|
|
8
|
+
* real workflow cost — not an apples-to-apples contest against minimal ripgrep.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import {
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
type LiteralLocateOptions,
|
|
14
15
|
type LiteralLocateResult,
|
|
15
16
|
} from "../literal.ts";
|
|
17
|
+
import { formatLiteralLocateText } from "../mcp/format-text.ts";
|
|
16
18
|
import type { MiruIndex } from "../miru-index.ts";
|
|
17
19
|
import { countTokens } from "../token-count.ts";
|
|
18
20
|
import { GREP_CONTEXT } from "./grep.ts";
|
|
@@ -44,7 +46,7 @@ export async function benchmarkLocateComparison(options: {
|
|
|
44
46
|
const result = options.index.locateLiteral(options.literal, locateOpts);
|
|
45
47
|
const miruMs = performance.now() - miruStart;
|
|
46
48
|
const payload = formatLiteralLocate(result);
|
|
47
|
-
const miruTok = countTokens(
|
|
49
|
+
const miruTok = countTokens(formatLiteralLocateText(payload));
|
|
48
50
|
|
|
49
51
|
// Unbounded agent-style Grep dump (±context) — the costlier path without locate.
|
|
50
52
|
// Never use less context than the caller actually requested via context_lines,
|
package/src/benchmark/summary.ts
CHANGED
|
@@ -20,10 +20,19 @@ export function agentBenchmarkFromTokens(
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/** Attach compact `benchmark` to an MCP tool payload. */
|
|
23
|
+
/** Attach compact `benchmark` to an MCP tool payload (JSON path / tests). */
|
|
24
24
|
export function attachAgentBenchmark<T extends Record<string, unknown>>(
|
|
25
25
|
payload: T,
|
|
26
26
|
benchmark: AgentBenchmarkSummary,
|
|
27
27
|
): T & { benchmark: AgentBenchmarkSummary } {
|
|
28
28
|
return { ...payload, benchmark };
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Append a compact trailing JSON object so plaintext MCP bodies can still expose
|
|
33
|
+
* `benchmark` without wrapping the result in JSON scaffolding.
|
|
34
|
+
* `miru_tok` counts the body only — never this trailer.
|
|
35
|
+
*/
|
|
36
|
+
export function appendAgentBenchmark(body: string, benchmark: AgentBenchmarkSummary): string {
|
|
37
|
+
return `${body}\n\n${JSON.stringify({ benchmark })}`;
|
|
38
|
+
}
|
package/src/benchmark/types.ts
CHANGED
|
@@ -42,7 +42,12 @@ export interface SearchBenchmarkBlock {
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* Compact per-search stats appended to MCP `search` / `locate` responses.
|
|
47
|
+
* `miru_tok` is the agent-facing MCP text body for that tool (search also adds
|
|
48
|
+
* one expand body for workflow vs grep+read). Not JSON scaffolding, not the
|
|
49
|
+
* trailing `{"benchmark":...}` line.
|
|
50
|
+
*/
|
|
46
51
|
export interface AgentBenchmarkSummary {
|
|
47
52
|
save_pct: number;
|
|
48
53
|
miru_tok: number;
|
|
@@ -51,6 +56,11 @@ export interface AgentBenchmarkSummary {
|
|
|
51
56
|
rank1: boolean;
|
|
52
57
|
/** Present only when non-empty; capped at 3 paths. */
|
|
53
58
|
miru_only?: string[];
|
|
59
|
+
/**
|
|
60
|
+
* Search only: tokens of the search MCP text body alone (before expand).
|
|
61
|
+
* `miru_tok` = search_tok + expand body when present.
|
|
62
|
+
*/
|
|
63
|
+
search_tok?: number;
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
/** Compact cumulative rollup for MCP `read_benchmark` (agent-facing). */
|
package/src/help.ts
CHANGED
|
@@ -165,8 +165,9 @@ export function printCommandHelp(command: string): void {
|
|
|
165
165
|
writeStdout("Override path with MIRU_BENCHMARK_HISTORY_PATH. Append-only JSONL of savings.");
|
|
166
166
|
writeStdout("Repo paths in the report are plaintext — use `clear` on shared machines.");
|
|
167
167
|
writeStdout("");
|
|
168
|
-
writeStdout("Savings compare Miru
|
|
169
|
-
writeStdout("(rg search + read of the top matched file),
|
|
168
|
+
writeStdout("Savings compare Miru MCP response tokens (search text + expand text)");
|
|
169
|
+
writeStdout("to a Grep baseline (rg search + read of the top matched file),");
|
|
170
|
+
writeStdout("not bare chunk content and not the agent's full tool chain.");
|
|
170
171
|
writeStdout("Local paths only — git URL repos skip comparison with benchmark_skipped.");
|
|
171
172
|
writeStdout("");
|
|
172
173
|
return;
|
|
@@ -127,7 +127,10 @@ export const MCP_SERVER_INSTRUCTIONS =
|
|
|
127
127
|
|
|
128
128
|
export const MCP_BENCHMARK_SERVER_INSTRUCTIONS =
|
|
129
129
|
`${MCP_SERVER_INSTRUCTIONS} ` +
|
|
130
|
-
"BENCHMARK MODE: each `search` and `locate`
|
|
130
|
+
"BENCHMARK MODE: each `search` and `locate` ends with a compact JSON line " +
|
|
131
|
+
'`{"benchmark":{save_pct,miru_tok,grep_tok,saved_tok,rank1[,search_tok,miru_only]}}`. ' +
|
|
132
|
+
"Results stay plain text. For search, `miru_tok` is search MCP text + one expand (workflow vs grep+read); " +
|
|
133
|
+
"`search_tok` is this search body alone. " +
|
|
131
134
|
"Call `read_benchmark` for cumulative totals across both. Do not narrate benchmark stats unless the user asks. " +
|
|
132
135
|
"To leave benchmark mode, tell the user to run `miru benchmark off` and restart the agent.";
|
|
133
136
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ExpandResults } from "../utils.ts";
|
|
2
|
+
|
|
3
|
+
/** Plain-text rendering of `formatResults()` output — same info as the JSON shape, no scaffolding. */
|
|
4
|
+
export function formatResultsText(payload: {
|
|
5
|
+
query: string;
|
|
6
|
+
results: Record<string, unknown>[];
|
|
7
|
+
}): string {
|
|
8
|
+
if (payload.results.length === 0) {
|
|
9
|
+
return "No results found.";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const blocks = payload.results.map((result) => {
|
|
13
|
+
const chunk = result.chunk as Record<string, unknown>;
|
|
14
|
+
const startLine = chunk.truncated ? chunk.full_start_line : chunk.start_line;
|
|
15
|
+
const endLine = chunk.truncated ? chunk.full_end_line : chunk.end_line;
|
|
16
|
+
const truncatedNote = chunk.truncated ? " [truncated: call expand for full chunk]" : "";
|
|
17
|
+
const header = `${chunk.file_path}:${startLine}-${endLine}${truncatedNote}`;
|
|
18
|
+
return `${header}\n${chunk.content}`;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return blocks.join("\n\n");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Plain-text rendering of `formatLiteralLocate()` output. */
|
|
25
|
+
export function formatLiteralLocateText(payload: Record<string, unknown>): string {
|
|
26
|
+
const label =
|
|
27
|
+
(payload.literals as string[] | undefined)?.join(" | ") ?? (payload.literal as string);
|
|
28
|
+
const n = payload.n as number;
|
|
29
|
+
const files = payload.files as number;
|
|
30
|
+
const truncatedNote = payload.truncated ? " (truncated — pass a narrower include/exclude)" : "";
|
|
31
|
+
const header = `"${label}": ${n} match${n === 1 ? "" : "es"} across ${files} file${files === 1 ? "" : "s"}${truncatedNote}`;
|
|
32
|
+
|
|
33
|
+
const hits = payload.hits as
|
|
34
|
+
| Array<{ f: string; l: number; t?: string; ctx?: string[]; ctx_l?: number }>
|
|
35
|
+
| undefined;
|
|
36
|
+
if (!hits) {
|
|
37
|
+
return header;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const lines = [header, ""];
|
|
41
|
+
for (const hit of hits) {
|
|
42
|
+
lines.push(hit.t !== undefined ? `${hit.f}:${hit.l}: ${hit.t}` : `${hit.f}:${hit.l}`);
|
|
43
|
+
if (hit.ctx != null && hit.ctx_l !== undefined) {
|
|
44
|
+
const ctxStart = hit.ctx_l;
|
|
45
|
+
for (let i = 0; i < hit.ctx.length; i++) {
|
|
46
|
+
lines.push(` ${ctxStart + i}: ${hit.ctx[i]}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return lines.join("\n");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Plain-text rendering of `formatExpandResults()` output. */
|
|
54
|
+
export function formatExpandResultsText(payload: ExpandResults): string {
|
|
55
|
+
const anchorRange = payload.anchor
|
|
56
|
+
? `${payload.anchor.start_line}-${payload.anchor.end_line}`
|
|
57
|
+
: null;
|
|
58
|
+
const header = `${payload.file_path} — ${payload.chunk_count} chunk${payload.chunk_count === 1 ? "" : "s"} around line ${payload.line}`;
|
|
59
|
+
|
|
60
|
+
const blocks = payload.chunks.map((chunk) => {
|
|
61
|
+
const range = `${chunk.start_line}-${chunk.end_line}`;
|
|
62
|
+
const isAnchor = anchorRange === range;
|
|
63
|
+
return `${range}${isAnchor ? " (anchor)" : ""}\n${chunk.content}`;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return `${header}\n\n${blocks.join("\n\n")}`;
|
|
67
|
+
}
|
package/src/mcp/index-cache.ts
CHANGED
|
@@ -65,6 +65,11 @@ export function shouldIgnoreWatchPath(relativePath: string | null | undefined):
|
|
|
65
65
|
return false;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Session cache of MiruIndex instances. Keeps them fresh by reconciling
|
|
70
|
+
* mtimes on load (`checkAndQueueStaleFiles`) and applying incremental
|
|
71
|
+
* updates from fs.watch (`flushFileUpdates` / `applyFileChanges`).
|
|
72
|
+
*/
|
|
68
73
|
export class IndexCache {
|
|
69
74
|
private readonly content: ContentType[];
|
|
70
75
|
private readonly defaultRef: string | null;
|
package/src/mcp/server.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import packageJson from "../../package.json";
|
|
3
|
-
import {
|
|
3
|
+
import { benchmarkSearchComparison, toAgentBenchmarkSummary } from "../benchmark/compare.ts";
|
|
4
4
|
import {
|
|
5
5
|
appendBenchmarkQuery,
|
|
6
6
|
readBenchmarkRollup,
|
|
7
7
|
recordFromAgentSummary,
|
|
8
8
|
recordFromBenchmark,
|
|
9
9
|
} from "../benchmark/history.ts";
|
|
10
|
-
import {
|
|
10
|
+
import { benchmarkLocateComparison } from "../benchmark/locate-compare.ts";
|
|
11
|
+
import { appendAgentBenchmark } from "../benchmark/summary.ts";
|
|
11
12
|
import {
|
|
12
13
|
MCP_BENCHMARK_SERVER_INSTRUCTIONS,
|
|
13
14
|
MCP_EXPAND_TOOL_DESCRIPTION,
|
|
@@ -32,6 +33,11 @@ import {
|
|
|
32
33
|
MAX_MCP_TOP_K,
|
|
33
34
|
resolveChunk,
|
|
34
35
|
} from "../utils.ts";
|
|
36
|
+
import {
|
|
37
|
+
formatExpandResultsText,
|
|
38
|
+
formatLiteralLocateText,
|
|
39
|
+
formatResultsText,
|
|
40
|
+
} from "./format-text.ts";
|
|
35
41
|
import { getIndexForRepo, type IndexCache, toolText } from "./index-cache.ts";
|
|
36
42
|
import { MiruMcpServer } from "./runtime.ts";
|
|
37
43
|
|
|
@@ -43,14 +49,11 @@ const REPO_DESCRIPTION =
|
|
|
43
49
|
const BENCHMARK_LOCAL_ONLY_NOTE =
|
|
44
50
|
"Benchmark comparisons require a local repo path; git URL repos are skipped.";
|
|
45
51
|
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
): T & { benchmark_skipped: "local_repo_only"; note: string } {
|
|
49
|
-
return {
|
|
50
|
-
...payload,
|
|
52
|
+
function withBenchmarkSkippedNote(body: string): string {
|
|
53
|
+
return `${body}\n\n${JSON.stringify({
|
|
51
54
|
benchmark_skipped: "local_repo_only",
|
|
52
55
|
note: BENCHMARK_LOCAL_ONLY_NOTE,
|
|
53
|
-
}
|
|
56
|
+
})}`;
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
export function createMcpServer(
|
|
@@ -108,20 +111,18 @@ export function createMcpServer(
|
|
|
108
111
|
});
|
|
109
112
|
const results = comparison.results;
|
|
110
113
|
if (results.length === 0) {
|
|
111
|
-
return toolText(
|
|
114
|
+
return toolText("No results found.");
|
|
112
115
|
}
|
|
113
116
|
try {
|
|
114
117
|
await appendBenchmarkQuery(recordFromBenchmark(repoRoot, comparison.benchmark));
|
|
115
118
|
} catch {
|
|
116
119
|
// History is best-effort; never fail the search on persist errors.
|
|
117
120
|
}
|
|
121
|
+
const body = formatResultsText(
|
|
122
|
+
formatResults(query, results, { repoRoot, snippet: true }),
|
|
123
|
+
);
|
|
118
124
|
return toolText(
|
|
119
|
-
|
|
120
|
-
attachSearchBenchmark(
|
|
121
|
-
formatResults(query, results, { repoRoot, snippet: true }),
|
|
122
|
-
comparison.benchmark,
|
|
123
|
-
),
|
|
124
|
-
),
|
|
125
|
+
appendAgentBenchmark(body, toAgentBenchmarkSummary(comparison.benchmark)),
|
|
125
126
|
);
|
|
126
127
|
}
|
|
127
128
|
|
|
@@ -130,13 +131,14 @@ export function createMcpServer(
|
|
|
130
131
|
results = dedupeResultsByFile(results);
|
|
131
132
|
}
|
|
132
133
|
if (results.length === 0) {
|
|
133
|
-
return toolText(
|
|
134
|
+
return toolText("No results found.");
|
|
134
135
|
}
|
|
135
136
|
const payload = formatResults(query, results, { repoRoot, snippet: true });
|
|
137
|
+
const body = formatResultsText(payload);
|
|
136
138
|
if (benchmark && !repoRoot) {
|
|
137
|
-
return toolText(
|
|
139
|
+
return toolText(withBenchmarkSkippedNote(body));
|
|
138
140
|
}
|
|
139
|
-
return toolText(
|
|
141
|
+
return toolText(body);
|
|
140
142
|
} catch (err) {
|
|
141
143
|
return toolText(err instanceof Error ? err.message : String(err));
|
|
142
144
|
}
|
|
@@ -212,18 +214,18 @@ export function createMcpServer(
|
|
|
212
214
|
} catch {
|
|
213
215
|
// History is best-effort; never fail locate on persist errors.
|
|
214
216
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
);
|
|
217
|
+
const body = formatLiteralLocateText(comparison.payload);
|
|
218
|
+
return toolText(appendAgentBenchmark(body, comparison.benchmark));
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
const result = index.locateLiteral(literal, locateOpts);
|
|
222
223
|
const payload = formatLiteralLocate(result);
|
|
224
|
+
const body = formatLiteralLocateText(payload);
|
|
223
225
|
if (benchmark && !localRepoRoot(repo)) {
|
|
224
|
-
return toolText(
|
|
226
|
+
return toolText(withBenchmarkSkippedNote(body));
|
|
225
227
|
}
|
|
226
|
-
return toolText(
|
|
228
|
+
return toolText(body);
|
|
227
229
|
} catch (err) {
|
|
228
230
|
return toolText(err instanceof Error ? err.message : String(err));
|
|
229
231
|
}
|
|
@@ -277,7 +279,7 @@ export function createMcpServer(
|
|
|
277
279
|
);
|
|
278
280
|
}
|
|
279
281
|
return toolText(
|
|
280
|
-
|
|
282
|
+
formatExpandResultsText(
|
|
281
283
|
formatExpandResults(filePath, anchorLine, anchor, expanded, {
|
|
282
284
|
repoRoot,
|
|
283
285
|
before: beforeCount,
|
|
@@ -327,12 +329,10 @@ export function createMcpServer(
|
|
|
327
329
|
}
|
|
328
330
|
const results = await index.findRelated(chunk, clampMcpTopK(topK));
|
|
329
331
|
if (results.length === 0) {
|
|
330
|
-
return toolText(
|
|
331
|
-
JSON.stringify({ error: `No related chunks found for ${filePath}:${anchorLine}.` }),
|
|
332
|
-
);
|
|
332
|
+
return toolText(`No related chunks found for ${filePath}:${anchorLine}.`);
|
|
333
333
|
}
|
|
334
334
|
return toolText(
|
|
335
|
-
|
|
335
|
+
formatResultsText(
|
|
336
336
|
formatResults(`Chunks related to ${filePath}:${anchorLine}`, results, {
|
|
337
337
|
repoRoot,
|
|
338
338
|
snippet: true,
|