@takara-ai/miru-code 0.4.11 → 0.4.12
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/agents/claude.md +1 -1
- package/src/agents/copilot.md +1 -1
- package/src/agents/cursor.md +2 -2
- package/src/agents/gemini.md +2 -2
- package/src/agents/kiro.md +1 -1
- package/src/agents/opencode.md +1 -1
- package/src/installer/search-policy.ts +4 -4
- package/src/mcp/server.ts +16 -7
- package/src/utils.ts +11 -102
package/package.json
CHANGED
package/src/agents/claude.md
CHANGED
|
@@ -16,7 +16,7 @@ When Miru MCP is available, use MCP `search`, `expand`, and `find_related` — n
|
|
|
16
16
|
|
|
17
17
|
### MCP workflow
|
|
18
18
|
|
|
19
|
-
1. Call `search` with `repo` set to the project root
|
|
19
|
+
1. Call `search` with `repo` set to the project root.
|
|
20
20
|
2. If context is cut off, call `expand` with the hit's `file_path` and `line`.
|
|
21
21
|
3. Use `find_related` for similar code in other files — not for same-file context.
|
|
22
22
|
4. Read files only to edit or when `expand` still lacks context.
|
package/src/agents/copilot.md
CHANGED
|
@@ -7,7 +7,7 @@ When Miru MCP is available, use `search`, `expand`, and `find_related` instead o
|
|
|
7
7
|
|
|
8
8
|
| Task | Tool |
|
|
9
9
|
|------|------|
|
|
10
|
-
| How/where/what handles X? | Miru `search` (once
|
|
10
|
+
| How/where/what handles X? | Miru `search` (once) |
|
|
11
11
|
| Same file, more context | Miru `expand` |
|
|
12
12
|
| Similar implementations | Miru `find_related` |
|
|
13
13
|
| Exact literal confirmation | grep_search |
|
package/src/agents/cursor.md
CHANGED
|
@@ -5,11 +5,11 @@ description: Code search via Miru MCP. Use search, expand, and find_related. DO
|
|
|
5
5
|
|
|
6
6
|
Use Miru MCP tools when connected:
|
|
7
7
|
|
|
8
|
-
- `search` — one call per question; always pass project root as `repo
|
|
8
|
+
- `search` — one call per question; always pass project root as `repo`
|
|
9
9
|
- `expand` — adjacent chunks in the same file when a hit looks truncated (`file_path` + `line`)
|
|
10
10
|
- `find_related` — similar code in other files after a search hit
|
|
11
11
|
|
|
12
|
-
Do not re-search with paraphrases
|
|
12
|
+
Do not re-search with paraphrases after a good first hit. Do not read whole files for exploration.
|
|
13
13
|
|
|
14
14
|
| Task | Tool |
|
|
15
15
|
|------|------|
|
package/src/agents/gemini.md
CHANGED
|
@@ -7,12 +7,12 @@ When Miru MCP is available, use `search`, `expand`, and `find_related` instead o
|
|
|
7
7
|
|
|
8
8
|
| Task | Tool |
|
|
9
9
|
|------|------|
|
|
10
|
-
| How/where/what handles X? | Miru `search` (once
|
|
10
|
+
| How/where/what handles X? | Miru `search` (once) |
|
|
11
11
|
| Same file, more context | Miru `expand` |
|
|
12
12
|
| Similar implementations | Miru `find_related` |
|
|
13
13
|
| Exact literal confirmation | grep_search |
|
|
14
14
|
| Edit a located file | read_file |
|
|
15
15
|
|
|
16
|
-
Pass project root as `repo` on every MCP call. Do not re-search
|
|
16
|
+
Pass project root as `repo` on every MCP call. Do not re-search with paraphrases after a good first hit.
|
|
17
17
|
|
|
18
18
|
CLI fallback: `miru search "query" .` or `bunx @takara-ai/miru-code search "query" .`
|
package/src/agents/kiro.md
CHANGED
|
@@ -5,6 +5,6 @@ description: Code search via Miru MCP. Use search, expand, and find_related for
|
|
|
5
5
|
|
|
6
6
|
When Miru MCP is available, use `search`, `expand`, and `find_related` before any built-in search or broad file reads.
|
|
7
7
|
|
|
8
|
-
Pass project root as `repo`.
|
|
8
|
+
Pass project root as `repo`. Use `expand` for truncated hits in the same file. Use Grep only for exact literal confirmation.
|
|
9
9
|
|
|
10
10
|
CLI fallback: `miru search "query" .`
|
package/src/agents/opencode.md
CHANGED
|
@@ -6,6 +6,6 @@ mode: subagent
|
|
|
6
6
|
|
|
7
7
|
When Miru MCP is available, use `search`, `expand`, and `find_related` instead of grep, glob, or read-for-exploration.
|
|
8
8
|
|
|
9
|
-
Pass project root as `repo` on every MCP call.
|
|
9
|
+
Pass project root as `repo` on every MCP call.
|
|
10
10
|
|
|
11
11
|
CLI fallback: `miru search "query" .`
|
|
@@ -12,12 +12,12 @@ export const SEARCH_POLICY_TABLE = `| Task | Use | Not |
|
|
|
12
12
|
export const SEARCH_POLICY_BODY = `DO NOT use Grep, Glob, SemanticSearch, or Read to explore how code works when Miru MCP is available.
|
|
13
13
|
|
|
14
14
|
Use Miru MCP tools:
|
|
15
|
-
- \`search\` — one call per question; pass project root as \`repo
|
|
15
|
+
- \`search\` — one call per question; pass project root as \`repo\`
|
|
16
16
|
- \`expand\` — adjacent chunks in the same file when a hit looks truncated (\`file_path\` + \`line\` from the hit)
|
|
17
17
|
- \`find_related\` — similar code in other files after a \`search\` hit
|
|
18
18
|
|
|
19
19
|
Stop rules:
|
|
20
|
-
-
|
|
20
|
+
- Answer from the first \`search\` — do not re-search with paraphrases
|
|
21
21
|
- If context is cut off in one file, call \`expand\` — not another \`search\` or a full-file Read
|
|
22
22
|
- Read is for editing a path Miru already gave you, not for exploration
|
|
23
23
|
|
|
@@ -54,7 +54,7 @@ alwaysApply: true
|
|
|
54
54
|
|
|
55
55
|
${SEARCH_POLICY_BODY}
|
|
56
56
|
|
|
57
|
-
When Miru MCP is connected, call \`search\` once per question
|
|
57
|
+
When Miru MCP is connected, call \`search\` once per question. Use \`expand\` for same-file context. Never use Cursor SemanticSearch or Grep for codebase questions unless confirming an exact literal.`;
|
|
58
58
|
|
|
59
59
|
export const SUBAGENT_BODY = `When Miru MCP is available, use MCP \`search\`, \`expand\`, and \`find_related\` — not Grep, Glob, SemanticSearch, or Read-for-exploration.
|
|
60
60
|
|
|
@@ -63,7 +63,7 @@ ${SEARCH_POLICY_BODY}
|
|
|
63
63
|
### MCP workflow
|
|
64
64
|
|
|
65
65
|
1. Call \`search\` with \`repo\` set to the project root (local path or https:// git URL).
|
|
66
|
-
2. If
|
|
66
|
+
2. If a hit looks truncated, call \`expand\` with the hit's \`file_path\` and \`line\`.
|
|
67
67
|
3. Use \`find_related\` to trace similar code in other files — not for more context in the same file.
|
|
68
68
|
4. Read via \`absolute_path\` only when editing or when \`expand\` still lacks context.
|
|
69
69
|
|
package/src/mcp/server.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import type { ContentType } from "../types.ts";
|
|
4
4
|
import {
|
|
5
|
+
clampMcpTopK,
|
|
5
6
|
DEFAULT_MCP_TOP_K,
|
|
6
7
|
dedupeResultsByFile,
|
|
7
8
|
expandChunksAtLine,
|
|
8
9
|
formatExpandResults,
|
|
9
10
|
formatResults,
|
|
10
11
|
localRepoRoot,
|
|
12
|
+
MAX_MCP_TOP_K,
|
|
11
13
|
resolveChunk,
|
|
12
14
|
} from "../utils.ts";
|
|
13
15
|
import { getIndexForRepo, type IndexCache, toolText } from "./index-cache.ts";
|
|
@@ -19,8 +21,8 @@ const REPO_DESCRIPTION =
|
|
|
19
21
|
|
|
20
22
|
const SERVER_INSTRUCTIONS =
|
|
21
23
|
"DO NOT use Grep, Glob, SemanticSearch, or Read to explore code when this server is available. " +
|
|
22
|
-
"Workflow: (1) `search` once per question —
|
|
23
|
-
"(2) If context looks cut off, call `expand` on the hit's file_path + line — do NOT
|
|
24
|
+
"Workflow: (1) `search` once per question — do NOT re-search with paraphrases. " +
|
|
25
|
+
"(2) If context looks cut off, call `expand` on the hit's file_path + line — do NOT read the whole file. " +
|
|
24
26
|
"(3) `find_related` traces similar code in other files, not more context in the same file. " +
|
|
25
27
|
"Always pass the project root as `repo`. Local repos return `absolute_path` on each hit — use Read only to edit. " +
|
|
26
28
|
"Native search is only for exact literal confirmation or files outside the indexed repo.";
|
|
@@ -40,7 +42,7 @@ export function createMcpServer(cache: IndexCache): McpServer {
|
|
|
40
42
|
"search",
|
|
41
43
|
{
|
|
42
44
|
description:
|
|
43
|
-
"DEFAULT code exploration tool. One call per question is usually enough
|
|
45
|
+
"DEFAULT code exploration tool. One call per question is usually enough. " +
|
|
44
46
|
"If a hit looks truncated, call `expand` (same file) instead of re-searching or Read. " +
|
|
45
47
|
"Indexes `repo` on first call; later calls reuse the session cache.",
|
|
46
48
|
inputSchema: {
|
|
@@ -50,8 +52,9 @@ export function createMcpServer(cache: IndexCache): McpServer {
|
|
|
50
52
|
.number()
|
|
51
53
|
.int()
|
|
52
54
|
.min(1)
|
|
55
|
+
.max(MAX_MCP_TOP_K)
|
|
53
56
|
.optional()
|
|
54
|
-
.describe(`Number of results (default ${DEFAULT_MCP_TOP_K}).`),
|
|
57
|
+
.describe(`Number of results (default ${DEFAULT_MCP_TOP_K}, max ${MAX_MCP_TOP_K}).`),
|
|
55
58
|
dedupe_by_file: z
|
|
56
59
|
.boolean()
|
|
57
60
|
.optional()
|
|
@@ -61,7 +64,7 @@ export function createMcpServer(cache: IndexCache): McpServer {
|
|
|
61
64
|
async ({ query, repo, top_k: topK, dedupe_by_file: dedupeByFile }) => {
|
|
62
65
|
try {
|
|
63
66
|
const index = await getIndexForRepo(repo, cache);
|
|
64
|
-
let results = await index.search({ query, topK: topK
|
|
67
|
+
let results = await index.search({ query, topK: clampMcpTopK(topK) });
|
|
65
68
|
if (dedupeByFile !== false) {
|
|
66
69
|
results = dedupeResultsByFile(results);
|
|
67
70
|
}
|
|
@@ -151,7 +154,13 @@ export function createMcpServer(cache: IndexCache): McpServer {
|
|
|
151
154
|
),
|
|
152
155
|
line: z.number().int().describe("Line number (1-indexed)."),
|
|
153
156
|
repo: z.string().describe(REPO_DESCRIPTION),
|
|
154
|
-
top_k: z
|
|
157
|
+
top_k: z
|
|
158
|
+
.number()
|
|
159
|
+
.int()
|
|
160
|
+
.min(1)
|
|
161
|
+
.max(MAX_MCP_TOP_K)
|
|
162
|
+
.optional()
|
|
163
|
+
.describe(`Number of similar chunks to return (default ${DEFAULT_MCP_TOP_K}, max ${MAX_MCP_TOP_K}).`),
|
|
155
164
|
},
|
|
156
165
|
},
|
|
157
166
|
async ({ file_path: filePath, line, repo, top_k: topK }) => {
|
|
@@ -164,7 +173,7 @@ export function createMcpServer(cache: IndexCache): McpServer {
|
|
|
164
173
|
`No chunk found at ${filePath}:${line}. Make sure the file is indexed and the line number is within a known chunk.`,
|
|
165
174
|
);
|
|
166
175
|
}
|
|
167
|
-
const results = await index.findRelated(chunk, topK
|
|
176
|
+
const results = await index.findRelated(chunk, clampMcpTopK(topK));
|
|
168
177
|
if (results.length === 0) {
|
|
169
178
|
return toolText(
|
|
170
179
|
JSON.stringify({ error: `No related chunks found for ${filePath}:${line}.` }),
|
package/src/utils.ts
CHANGED
|
@@ -66,29 +66,25 @@ export function resolveChunk(
|
|
|
66
66
|
return fallback;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
export type SearchGuidance = {
|
|
70
|
-
sufficient: boolean;
|
|
71
|
-
reason: string;
|
|
72
|
-
suggested_next: "answer" | "expand" | "find_related" | "read_to_edit";
|
|
73
|
-
expand?: { file_path: string; line: number };
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export type ExpandGuidance = {
|
|
77
|
-
sufficient: boolean;
|
|
78
|
-
reason: string;
|
|
79
|
-
suggested_next: "answer" | "search";
|
|
80
|
-
};
|
|
81
|
-
|
|
82
69
|
export type ExpandResults = {
|
|
83
70
|
file_path: string;
|
|
84
71
|
line: number;
|
|
85
72
|
chunk_count: number;
|
|
86
|
-
guidance: ExpandGuidance;
|
|
87
73
|
anchor: Record<string, unknown> | null;
|
|
88
74
|
chunks: Record<string, unknown>[];
|
|
89
75
|
};
|
|
90
76
|
|
|
91
77
|
export const DEFAULT_MCP_TOP_K = 3;
|
|
78
|
+
export const MAX_MCP_TOP_K = 10;
|
|
79
|
+
|
|
80
|
+
/** Clamp MCP top_k to a sane range; omit for the default. */
|
|
81
|
+
export function clampMcpTopK(topK?: number): number {
|
|
82
|
+
const value = topK ?? DEFAULT_MCP_TOP_K;
|
|
83
|
+
if (!Number.isFinite(value) || value < 1) {
|
|
84
|
+
return DEFAULT_MCP_TOP_K;
|
|
85
|
+
}
|
|
86
|
+
return Math.min(Math.floor(value), MAX_MCP_TOP_K);
|
|
87
|
+
}
|
|
92
88
|
|
|
93
89
|
function chunkToResponseDict(chunk: Chunk, repoRoot?: string | null): Record<string, unknown> {
|
|
94
90
|
const dict = chunkToDict(chunk);
|
|
@@ -158,90 +154,14 @@ export function expandChunksAtLine(
|
|
|
158
154
|
return { anchor, chunks: fileChunks.slice(start, end) };
|
|
159
155
|
}
|
|
160
156
|
|
|
161
|
-
export function assessSearchGuidance(query: string, results: SearchResult[]): SearchGuidance {
|
|
162
|
-
if (results.length === 0) {
|
|
163
|
-
return {
|
|
164
|
-
sufficient: false,
|
|
165
|
-
reason: "No results returned.",
|
|
166
|
-
suggested_next: "find_related",
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const top = results[0];
|
|
171
|
-
if (!top) {
|
|
172
|
-
return {
|
|
173
|
-
sufficient: false,
|
|
174
|
-
reason: "No results returned.",
|
|
175
|
-
suggested_next: "find_related",
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const topChunk = top.chunk;
|
|
180
|
-
const expandHint = { file_path: topChunk.file_path, line: topChunk.start_line };
|
|
181
|
-
const second = results[1];
|
|
182
|
-
const scoreRatio = second && second.score > 0 ? top.score / second.score : Number.POSITIVE_INFINITY;
|
|
183
|
-
const strongLead = results.length === 1 || scoreRatio >= 1.25;
|
|
184
|
-
|
|
185
|
-
const sameFileHits = results.filter((result) => result.chunk.file_path === topChunk.file_path).length;
|
|
186
|
-
const uniqueFiles = new Set(results.map((result) => result.chunk.file_path)).size;
|
|
187
|
-
const lineSpan = topChunk.end_line - topChunk.start_line + 1;
|
|
188
|
-
const narrowQuery = /\b(why|how|what|when|where|does|bug|error|fail)\b/i.test(query);
|
|
189
|
-
|
|
190
|
-
if (sameFileHits > 1 && lineSpan < 40) {
|
|
191
|
-
return {
|
|
192
|
-
sufficient: false,
|
|
193
|
-
reason: `${sameFileHits} hits in ${topChunk.file_path}; slices may be incomplete.`,
|
|
194
|
-
suggested_next: "expand",
|
|
195
|
-
expand: expandHint,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (lineSpan < 12 && sameFileHits >= 1) {
|
|
200
|
-
return {
|
|
201
|
-
sufficient: false,
|
|
202
|
-
reason: "Top hit is a short slice; more context may be in adjacent chunks.",
|
|
203
|
-
suggested_next: "expand",
|
|
204
|
-
expand: expandHint,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (uniqueFiles > 2 && !strongLead) {
|
|
209
|
-
return {
|
|
210
|
-
sufficient: false,
|
|
211
|
-
reason: "Results span several files; pick the best hit or use find_related to trace neighbors.",
|
|
212
|
-
suggested_next: "find_related",
|
|
213
|
-
expand: expandHint,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (strongLead && (narrowQuery || lineSpan >= 15)) {
|
|
218
|
-
return {
|
|
219
|
-
sufficient: true,
|
|
220
|
-
reason:
|
|
221
|
-
results.length === 1
|
|
222
|
-
? "Single focused hit — answer from this chunk unless you are editing."
|
|
223
|
-
: `Top hit leads (${scoreRatio === Number.POSITIVE_INFINITY ? "only result" : `${scoreRatio.toFixed(1)}x`}).`,
|
|
224
|
-
suggested_next: "answer",
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
sufficient: true,
|
|
230
|
-
reason: "Top hit is likely enough; call expand if the chunk looks cut off.",
|
|
231
|
-
suggested_next: "answer",
|
|
232
|
-
expand: expandHint,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
157
|
export function formatResults(
|
|
237
158
|
query: string,
|
|
238
159
|
results: SearchResult[],
|
|
239
160
|
options?: { repoRoot?: string | null },
|
|
240
|
-
): { query: string;
|
|
161
|
+
): { query: string; results: Record<string, unknown>[] } {
|
|
241
162
|
const repoRoot = options?.repoRoot ?? null;
|
|
242
163
|
return {
|
|
243
164
|
query,
|
|
244
|
-
guidance: assessSearchGuidance(query, results),
|
|
245
165
|
results: results.map((result) => ({
|
|
246
166
|
chunk: chunkToResponseDict(result.chunk, repoRoot),
|
|
247
167
|
score: result.score,
|
|
@@ -258,22 +178,11 @@ export function formatExpandResults(
|
|
|
258
178
|
): ExpandResults {
|
|
259
179
|
const repoRoot = options?.repoRoot ?? null;
|
|
260
180
|
const indexedPath = toIndexedFilePath(filePath, repoRoot) || filePath;
|
|
261
|
-
const suggestedNext: ExpandGuidance["suggested_next"] = expanded.length > 0 ? "answer" : "search";
|
|
262
181
|
|
|
263
182
|
return {
|
|
264
183
|
file_path: indexedPath,
|
|
265
184
|
line,
|
|
266
185
|
chunk_count: expanded.length,
|
|
267
|
-
guidance: {
|
|
268
|
-
sufficient: expanded.length > 1,
|
|
269
|
-
reason:
|
|
270
|
-
expanded.length === 0
|
|
271
|
-
? "No chunks at this location."
|
|
272
|
-
: expanded.length === 1
|
|
273
|
-
? "Single chunk at location; call expand with higher before/after if still truncated."
|
|
274
|
-
: `Returned ${expanded.length} adjacent chunks in ${indexedPath}.`,
|
|
275
|
-
suggested_next: suggestedNext,
|
|
276
|
-
},
|
|
277
186
|
anchor: anchor ? chunkToResponseDict(anchor, repoRoot) : null,
|
|
278
187
|
chunks: expanded.map((chunk) => chunkToResponseDict(chunk, repoRoot)),
|
|
279
188
|
};
|