@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takara-ai/miru-code",
3
- "version": "0.4.11",
3
+ "version": "0.4.12",
4
4
  "description": "Miru (見る) — hybrid code search for agents. Bun, TypeScript, Takara embeddings.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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; follow `guidance` in the response.
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.
@@ -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; follow `guidance`) |
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 |
@@ -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`; read `guidance` in the response
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 when `guidance.sufficient` is true. Do not read whole files for exploration.
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
  |------|------|
@@ -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; follow `guidance`) |
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 when `guidance.sufficient` is true.
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" .`
@@ -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`. Follow `guidance` in search responses. Use `expand` for truncated hits in the same file. Use Grep only for exact literal confirmation.
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" .`
@@ -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. Follow `guidance` in search responses.
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\`; read \`guidance.sufficient\` and \`suggested_next\` in the response
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
- - If \`guidance.sufficient\` is true, answer — do not re-search with paraphrases
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 and follow \`guidance\` in the response. Use \`expand\` for same-file context. Never use Cursor SemanticSearch or Grep for codebase questions unless confirming an exact literal.`;
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 \`guidance.suggested_next\` is \`expand\`, call \`expand\` with the hit's \`file_path\` and \`line\`.
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 — check `guidance.sufficient` and `suggested_next` in the response. " +
23
- "(2) If context looks cut off, call `expand` on the hit's file_path + line — do NOT re-search with paraphrases or read the whole file. " +
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 — read `guidance` in the response. " +
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 ?? DEFAULT_MCP_TOP_K });
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.number().int().min(1).optional().describe("Number of similar chunks to return."),
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 ?? DEFAULT_MCP_TOP_K);
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; guidance: SearchGuidance; results: Record<string, unknown>[] } {
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
  };