@takara-ai/miru-code 0.4.11 → 0.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takara-ai/miru-code",
3
- "version": "0.4.11",
3
+ "version": "0.5.0",
4
4
  "description": "Miru (見る) — hybrid code search for agents. Bun, TypeScript, Takara embeddings.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -50,6 +50,9 @@
50
50
  "lint": "biome check .",
51
51
  "lint:fix": "biome check --write .",
52
52
  "prepublishOnly": "bun run typecheck && bun test",
53
+ "benchmark:ab": "bun run scripts/search-ab-local.ts",
54
+ "benchmark:ab-multi": "bun run scripts/search-ab-multi.ts",
55
+ "benchmark:recall": "bun run scripts/benchmark-recall-tokens.ts",
53
56
  "benchmark:quality": "bun run scripts/benchmark-quality.ts",
54
57
  "benchmark:quantization": "bun run scripts/benchmark-quantization.ts"
55
58
  },
@@ -3,30 +3,3 @@ name: miru-code
3
3
  description: Code search via Miru MCP. Use search, expand, and find_related. DO NOT use Grep, Glob, or Read for exploration when Miru MCP is available.
4
4
  tools: Bash, Read
5
5
  ---
6
-
7
- When Miru MCP is available, use MCP `search`, `expand`, and `find_related` — not Grep, Glob, or Read-for-exploration.
8
-
9
- | Task | Tool |
10
- |------|------|
11
- | How/where/what handles X? | Miru `search` (once) |
12
- | Same file, more context | Miru `expand` |
13
- | Similar implementations | Miru `find_related` |
14
- | Exact literal confirmation | Grep |
15
- | Edit a located file | Read |
16
-
17
- ### MCP workflow
18
-
19
- 1. Call `search` with `repo` set to the project root; follow `guidance` in the response.
20
- 2. If context is cut off, call `expand` with the hit's `file_path` and `line`.
21
- 3. Use `find_related` for similar code in other files — not for same-file context.
22
- 4. Read files only to edit or when `expand` still lacks context.
23
-
24
- ### CLI fallback
25
-
26
- ```bash
27
- miru search "authentication flow" .
28
- miru expand src/auth.ts 42 .
29
- miru find-related src/auth.ts 42 .
30
- ```
31
-
32
- If `miru` is not on `$PATH`, use `bunx @takara-ai/miru-code`.
@@ -2,15 +2,3 @@
2
2
  name: miru-code
3
3
  description: Code search via Miru MCP. Use search, expand, and find_related instead of grep_search or codebase_search when Miru is connected.
4
4
  ---
5
-
6
- When Miru MCP is available, use `search`, `expand`, and `find_related` instead of `grep_search`, `codebase_search`, or broad file reads.
7
-
8
- | Task | Tool |
9
- |------|------|
10
- | How/where/what handles X? | Miru `search` (once; follow `guidance`) |
11
- | Same file, more context | Miru `expand` |
12
- | Similar implementations | Miru `find_related` |
13
- | Exact literal confirmation | grep_search |
14
- | Edit a located file | read_file |
15
-
16
- Pass project root as `repo` on every MCP call.
@@ -2,29 +2,3 @@
2
2
  name: miru-code
3
3
  description: Code search via Miru MCP. Use search, expand, and find_related. DO NOT use Grep, Glob, or SemanticSearch when Miru MCP is available.
4
4
  ---
5
-
6
- Use Miru MCP tools when connected:
7
-
8
- - `search` — one call per question; always pass project root as `repo`; read `guidance` in the response
9
- - `expand` — adjacent chunks in the same file when a hit looks truncated (`file_path` + `line`)
10
- - `find_related` — similar code in other files after a search hit
11
-
12
- Do not re-search with paraphrases when `guidance.sufficient` is true. Do not read whole files for exploration.
13
-
14
- | Task | Tool |
15
- |------|------|
16
- | How/where/what handles X? | Miru `search` (once) |
17
- | Same file, more context | Miru `expand` |
18
- | Similar implementations | Miru `find_related` |
19
- | Exact literal confirmation | Grep |
20
- | Edit a located file | Read |
21
-
22
- CLI fallback when MCP is unavailable:
23
-
24
- ```bash
25
- miru search "authentication flow" .
26
- miru expand src/auth.ts 42 .
27
- miru find-related src/auth.ts 42 .
28
- ```
29
-
30
- If `miru` is not on `$PATH`, use `bunx @takara-ai/miru-code`.
@@ -2,17 +2,3 @@
2
2
  name: miru-code
3
3
  description: Code search via Miru MCP. Use search, expand, and find_related instead of grep_search or read_file for exploration.
4
4
  ---
5
-
6
- When Miru MCP is available, use `search`, `expand`, and `find_related` instead of `grep_search`, `glob`, or `read_file` for codebase exploration.
7
-
8
- | Task | Tool |
9
- |------|------|
10
- | How/where/what handles X? | Miru `search` (once; follow `guidance`) |
11
- | Same file, more context | Miru `expand` |
12
- | Similar implementations | Miru `find_related` |
13
- | Exact literal confirmation | grep_search |
14
- | Edit a located file | read_file |
15
-
16
- Pass project root as `repo` on every MCP call. Do not re-search when `guidance.sufficient` is true.
17
-
18
- CLI fallback: `miru search "query" .` or `bunx @takara-ai/miru-code search "query" .`
@@ -2,9 +2,3 @@
2
2
  name: miru-code
3
3
  description: Code search via Miru MCP. Use search, expand, and find_related for codebase exploration instead of built-in grep or file search.
4
4
  ---
5
-
6
- When Miru MCP is available, use `search`, `expand`, and `find_related` before any built-in search or broad file reads.
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.
9
-
10
- CLI fallback: `miru search "query" .`
@@ -3,9 +3,3 @@ name: miru-code
3
3
  description: Code search via Miru MCP. Use search, expand, and find_related instead of grep or glob for exploration.
4
4
  mode: subagent
5
5
  ---
6
-
7
- When Miru MCP is available, use `search`, `expand`, and `find_related` instead of grep, glob, or read-for-exploration.
8
-
9
- Pass project root as `repo` on every MCP call. Follow `guidance` in search responses.
10
-
11
- CLI fallback: `miru search "query" .`
package/src/agents.ts CHANGED
@@ -1,17 +1,56 @@
1
1
  import { mkdir, readFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
+ import {
4
+ buildSubagentBody,
5
+ type NativeToolNames,
6
+ } from "./installer/search-policy.ts";
3
7
 
4
8
  export type AgentId = "claude" | "copilot" | "cursor" | "gemini" | "kiro" | "opencode";
5
9
 
6
10
  const AGENTS_DIR = join(import.meta.dir, "agents");
7
11
 
12
+ const AGENT_NATIVE_TOOLS: Record<AgentId, NativeToolNames> = {
13
+ claude: {
14
+ explorationDenied: "Grep, Glob, SemanticSearch, or Read",
15
+ grep: "Grep",
16
+ read: "Read",
17
+ },
18
+ cursor: {
19
+ explorationDenied: "Grep, Glob, SemanticSearch, or Read",
20
+ grep: "Grep",
21
+ read: "Read",
22
+ },
23
+ copilot: {
24
+ explorationDenied: "grep_search, codebase_search, glob, or read_file",
25
+ grep: "grep_search",
26
+ read: "read_file",
27
+ },
28
+ gemini: {
29
+ explorationDenied: "grep_search, glob, or read_file",
30
+ grep: "grep_search",
31
+ read: "read_file",
32
+ },
33
+ kiro: {
34
+ explorationDenied: "built-in grep, file search, or broad file reads",
35
+ grep: "Grep",
36
+ read: "Read",
37
+ },
38
+ opencode: {
39
+ explorationDenied: "grep, glob, or read-for-exploration",
40
+ grep: "grep",
41
+ read: "read",
42
+ },
43
+ };
44
+
8
45
  export function agentDestination(agent: AgentId): string {
9
46
  const baseDir = agent === "copilot" ? ".github" : `.${agent}`;
10
47
  return join(baseDir, "agents", "miru-code.md");
11
48
  }
12
49
 
13
50
  export async function loadAgentTemplate(agent: AgentId): Promise<string> {
14
- return readFile(join(AGENTS_DIR, `${agent}.md`), "utf-8");
51
+ const frontmatter = (await readFile(join(AGENTS_DIR, `${agent}.md`), "utf-8")).trim();
52
+ const body = buildSubagentBody(AGENT_NATIVE_TOOLS[agent]);
53
+ return `${frontmatter}\n\n${body}\n`;
15
54
  }
16
55
 
17
56
  export async function writeAgentFile(
@@ -9,8 +9,10 @@ import { BM25Index } from "./bm25.ts";
9
9
  import { walkFiles } from "./file-walker.ts";
10
10
  import { detectLanguage, getExtensions, getFileStatus, readFileText } from "./files.ts";
11
11
  import type { SemanticIndex } from "./semantic-index.ts";
12
+ import { loadRootEntryChunks } from "./entry-chunks.ts";
12
13
  import { enrichForBm25 } from "./sparse.ts";
13
14
  import { buildSemanticIndex } from "./vector-storage.ts";
15
+ import { searchImprovementsEnabled } from "../ranking/features.ts";
14
16
 
15
17
  const DEFAULT_PIPELINE_EMBED_BATCH = 64;
16
18
  const DEFAULT_PIPELINE_EMBED_INFLIGHT = 8;
@@ -145,6 +147,17 @@ export async function createIndexFromPath(
145
147
  await Promise.race(runningFiles);
146
148
  }
147
149
 
150
+ if (searchImprovementsEnabled()) {
151
+ const entryChunks = await loadRootEntryChunks(
152
+ resolved,
153
+ displayRoot ? root : undefined,
154
+ );
155
+ if (entryChunks.length > 0) {
156
+ readyBySeq.set(fileSeq++, entryChunks);
157
+ flushReadySequentialChunks();
158
+ }
159
+ }
160
+
148
161
  flushReadySequentialChunks();
149
162
  await maybeScheduleEmbed(true);
150
163
  await Promise.all(embedPromises);
@@ -0,0 +1,71 @@
1
+ import { join, relative } from "node:path";
2
+ import type { Chunk } from "../types.ts";
3
+
4
+ function indexedPath(absPath: string, repoRoot: string, displayRoot?: string): string {
5
+ if (displayRoot) {
6
+ return relative(displayRoot, absPath).replace(/\\/g, "/");
7
+ }
8
+ return absPath.replace(/\\/g, "/");
9
+ }
10
+
11
+ async function packageJsonEntryChunks(
12
+ absPath: string,
13
+ repoRoot: string,
14
+ displayRoot?: string,
15
+ ): Promise<Chunk[]> {
16
+ let pkg: Record<string, unknown>;
17
+ try {
18
+ pkg = JSON.parse(await Bun.file(absPath).text()) as Record<string, unknown>;
19
+ } catch {
20
+ return [];
21
+ }
22
+
23
+ const entryLines = ["[package entry]"];
24
+
25
+ const bin = pkg.bin;
26
+ if (typeof bin === "string") {
27
+ entryLines.push(`bin: ${bin}`);
28
+ } else if (bin && typeof bin === "object") {
29
+ for (const [name, target] of Object.entries(bin)) {
30
+ entryLines.push(`bin ${name}: ${String(target)}`);
31
+ }
32
+ }
33
+
34
+ if (typeof pkg.main === "string") {
35
+ entryLines.push(`main: ${pkg.main}`);
36
+ }
37
+
38
+ const types = pkg.types ?? pkg.typings;
39
+ if (typeof types === "string") {
40
+ entryLines.push(`types: ${types}`);
41
+ }
42
+
43
+ if (entryLines.length <= 1) {
44
+ return [];
45
+ }
46
+
47
+ const filePath = indexedPath(absPath, repoRoot, displayRoot);
48
+ const content = `${entryLines.join("\n")}\n`;
49
+
50
+ return [
51
+ {
52
+ content,
53
+ file_path: filePath,
54
+ start_line: 1,
55
+ end_line: entryLines.length,
56
+ language: "json",
57
+ },
58
+ ];
59
+ }
60
+
61
+ /** Synthetic chunks for root config entry points (bin/main) when indexing code repos. */
62
+ export async function loadRootEntryChunks(
63
+ repoRoot: string,
64
+ displayRoot?: string,
65
+ ): Promise<Chunk[]> {
66
+ const pkgPath = join(repoRoot, "package.json");
67
+ if (!(await Bun.file(pkgPath).exists())) {
68
+ return [];
69
+ }
70
+ return packageJsonEntryChunks(pkgPath, repoRoot, displayRoot);
71
+ }
@@ -1,3 +1,5 @@
1
+ import { SEARCH_GUARD_EXPAND_HINT } from "../search-policy.ts";
2
+
1
3
  const REDIRECT_PREFIX =
2
4
  "Use Miru MCP instead of built-in search tools (pass project root as `repo`). " +
3
5
  "Grep/Glob/Shell are only for exact literal confirmation or non-code tasks.";
@@ -119,10 +121,7 @@ export function searchGuardBlockReason(
119
121
  toolInput: Record<string, unknown>,
120
122
  ): string {
121
123
  const query = suggestedMiruQuery(toolName, toolInput);
122
- return (
123
- `${REDIRECT_PREFIX} Try Miru \`search\` with query "${query}". ` +
124
- "If the hit looks truncated, call `expand` on its file_path + line — do not re-search or read the whole file."
125
- );
124
+ return `${REDIRECT_PREFIX} Try Miru \`search\` with query "${query}". ${SEARCH_GUARD_EXPAND_HINT}`;
126
125
  }
127
126
 
128
127
  /** Map host-specific stdin JSON into a common evaluation shape. */
@@ -15,11 +15,31 @@ const ESC = "\x1b";
15
15
  const HIDE_CURSOR = `${ESC}[?25l`;
16
16
  const SHOW_CURSOR = `${ESC}[?25h`;
17
17
  const CLEAR_LINE = `${ESC}[2K`;
18
+ const CURSOR_START = `${ESC}[G`;
18
19
 
19
20
  function writeRaw(text: string): void {
20
21
  output.write(text);
21
22
  }
22
23
 
24
+ function createFrameWriter(): (lines: string[]) => void {
25
+ let previousLineCount = 0;
26
+
27
+ return (lines: string[]) => {
28
+ if (previousLineCount > 0) {
29
+ writeRaw(`${ESC}[${previousLineCount}A${CURSOR_START}`);
30
+ }
31
+ writeRaw(`${CLEAR_LINE}${lines.join(`\n${CLEAR_LINE}`)}\n`);
32
+ const extra = previousLineCount - lines.length;
33
+ if (extra > 0) {
34
+ for (let i = 0; i < extra; i++) {
35
+ writeRaw(`${CLEAR_LINE}\n`);
36
+ }
37
+ writeRaw(`${ESC}[${extra}A${CURSOR_START}`);
38
+ }
39
+ previousLineCount = lines.length;
40
+ };
41
+ }
42
+
23
43
  function readKey(): Promise<Buffer> {
24
44
  return new Promise((resolve, reject) => {
25
45
  const onData = (chunk: Buffer) => {
@@ -90,12 +110,12 @@ async function withRawMode<T>(fn: () => Promise<T>): Promise<T> {
90
110
  }
91
111
  }
92
112
 
93
- function renderMultiSelect<T>(
113
+ function buildMultiSelectLines<T>(
94
114
  title: string,
95
115
  items: Array<{ label: string; value: T; checked: boolean }>,
96
116
  cursor: number,
97
117
  footer: string,
98
- ): void {
118
+ ): string[] {
99
119
  const lines: string[] = [];
100
120
  lines.push("");
101
121
  lines.push(bold(title));
@@ -113,7 +133,7 @@ function renderMultiSelect<T>(
113
133
  }
114
134
  lines.push("");
115
135
  lines.push(dim(footer));
116
- writeRaw(`${CLEAR_LINE}${lines.join(`\n${CLEAR_LINE}`)}\n`);
136
+ return lines;
117
137
  }
118
138
 
119
139
  export async function promptMultiSelect<T>(
@@ -130,10 +150,11 @@ export async function promptMultiSelect<T>(
130
150
 
131
151
  const footer = "↑↓ move space toggle a all enter confirm ctrl-c cancel";
132
152
  let cursor = 0;
153
+ const drawFrame = createFrameWriter();
133
154
 
134
155
  return withRawMode(async () => {
135
156
  while (true) {
136
- renderMultiSelect(title, items, cursor, footer);
157
+ drawFrame(buildMultiSelectLines(title, items, cursor, footer));
137
158
  const key = parseKey(await readKey());
138
159
 
139
160
  if (key === "ctrl-c") {
@@ -163,11 +184,13 @@ export async function promptMultiSelect<T>(
163
184
  });
164
185
  }
165
186
 
166
- function renderConfirm(question: string, yesSelected: boolean): void {
187
+ function buildConfirmLines(question: string, yesSelected: boolean): string[] {
167
188
  const yes = yesSelected ? green(bold(" Yes ")) : dim(" Yes ");
168
189
  const no = yesSelected ? dim(" No ") : green(bold(" No "));
169
- writeRaw(`${CLEAR_LINE}${question} ${yes} / ${no}\n`);
170
- writeRaw(`${CLEAR_LINE}${dim("←→ or y/n to choose enter to confirm ctrl-c to cancel")}\n`);
190
+ return [
191
+ `${question} ${yes} / ${no}`,
192
+ dim("←→ or y/n to choose enter to confirm ctrl-c to cancel"),
193
+ ];
171
194
  }
172
195
 
173
196
  export async function promptConfirm(question: string, defaultYes = true): Promise<boolean> {
@@ -186,10 +209,11 @@ export async function promptConfirm(question: string, defaultYes = true): Promis
186
209
  }
187
210
 
188
211
  let yesSelected = defaultYes;
212
+ const drawFrame = createFrameWriter();
189
213
 
190
214
  return withRawMode(async () => {
191
215
  while (true) {
192
- renderConfirm(question, yesSelected);
216
+ drawFrame(buildConfirmLines(question, yesSelected));
193
217
  const key = parseKey(await readKey());
194
218
 
195
219
  if (key === "ctrl-c") {
@@ -1,32 +1,86 @@
1
1
  /** Shared code-search policy text for instructions, Cursor rules, and sub-agents. */
2
2
 
3
- export const SEARCH_POLICY_TABLE = `| Task | Use | Not |
3
+ export const SNIPPET_GUIDANCE =
4
+ "Search returns compact snippets (~±15 lines around the best match). " +
5
+ "When a hit has `truncated: true`, call `expand` with `file_path` and `anchor_line` " +
6
+ "(or `start_line`) — do not re-search or Read the whole file.";
7
+
8
+ export interface NativeToolNames {
9
+ explorationDenied: string;
10
+ grep: string;
11
+ read: string;
12
+ }
13
+
14
+ export function buildSearchPolicyTable(native: NativeToolNames): string {
15
+ return `| Task | Use | Not |
4
16
  |------|-----|-----|
5
- | How/where/what handles X? | Miru MCP \`search\` (once) | Grep, Glob, SemanticSearch, repeat searches |
6
- | Same file, more context | Miru MCP \`expand\` | Re-search, Read whole file |
7
- | Similar code elsewhere | Miru MCP \`find_related\` | Grep chains |
8
- | Search docs or config | Miru \`search\` with \`content\` | Grep README paths |
9
- | Exact literal exists? | Grep | Miru |
10
- | Edit a known file:line | Read (after Miru found it) | Read-before-search |`;
17
+ | How/where/what handles X? | Miru MCP \`search\` (once) | ${native.explorationDenied}, repeat searches |
18
+ | Same file, more context | Miru MCP \`expand\` on \`truncated: true\` | Re-search, ${native.read} whole file |
19
+ | Similar code elsewhere | Miru MCP \`find_related\` | ${native.grep} chains |
20
+ | Search docs or config | Miru \`search\` | ${native.grep} README paths |
21
+ | Exact literal exists? | ${native.grep} | Miru |
22
+ | Edit a known file:line | ${native.read} (after Miru found it) | ${native.read}-before-search |`;
23
+ }
11
24
 
12
- export const SEARCH_POLICY_BODY = `DO NOT use Grep, Glob, SemanticSearch, or Read to explore how code works when Miru MCP is available.
25
+ export function buildSearchPolicyBody(native: NativeToolNames): string {
26
+ return `DO NOT use ${native.explorationDenied} to explore how code works when Miru MCP is available.
27
+
28
+ ${SNIPPET_GUIDANCE}
13
29
 
14
30
  Use Miru MCP tools:
15
- - \`search\` — one call per question; pass project root as \`repo\`; read \`guidance.sufficient\` and \`suggested_next\` in the response
16
- - \`expand\` — adjacent chunks in the same file when a hit looks truncated (\`file_path\` + \`line\` from the hit)
17
- - \`find_related\` — similar code in other files after a \`search\` hit
31
+ - \`search\` — one call per question; pass project root as \`repo\`
32
+ - \`expand\` — more context in the same file when \`truncated: true\` (\`file_path\` + \`anchor_line\` or \`start_line\`)
33
+ - \`find_related\` — similar code in other files (hits may also be snippets; use \`expand\` if truncated)
18
34
 
19
35
  Stop rules:
20
- - If \`guidance.sufficient\` is true, answer — do not re-search with paraphrases
21
- - If context is cut off in one file, call \`expand\` — not another \`search\` or a full-file Read
22
- - Read is for editing a path Miru already gave you, not for exploration
36
+ - Answer from the first \`search\` — do not re-search with paraphrases
37
+ - On \`truncated: true\`, call \`expand\` — not another \`search\` or a full-file ${native.read}
38
+ - ${native.read} is for editing a path Miru already gave you, not for exploration
23
39
 
24
40
  Native tools are allowed ONLY when:
25
41
  - confirming an exact literal string (env var name, error code, quoted text)
26
42
  - reading a file you already located via Miru, to edit it
27
43
  - searching outside the indexed repo
28
44
 
29
- ${SEARCH_POLICY_TABLE}`;
45
+ ${buildSearchPolicyTable(native)}`;
46
+ }
47
+
48
+ export function buildMcpWorkflow(native: NativeToolNames): string {
49
+ return `### MCP workflow
50
+
51
+ 1. Call \`search\` with \`repo\` set to the project root (local path or https:// git URL).
52
+ 2. If a hit has \`truncated: true\`, call \`expand\` with \`file_path\` and \`anchor_line\` (or \`start_line\`).
53
+ 3. Use \`find_related\` to trace similar code in other files — not for more context in the same file.
54
+ 4. ${native.read} via \`absolute_path\` only when editing or when \`expand\` still lacks context.`;
55
+ }
56
+
57
+ const CLI_FALLBACK = `### CLI fallback (no MCP in this session)
58
+
59
+ \`\`\`bash
60
+ miru search "authentication flow" .
61
+ miru expand src/auth.ts 42 .
62
+ miru find-related src/auth.ts 42 .
63
+ \`\`\`
64
+
65
+ If \`miru\` is not on \`$PATH\`, use \`bunx @takara-ai/miru-code\`.`;
66
+
67
+ export function buildSubagentBody(native: NativeToolNames): string {
68
+ return `When Miru MCP is available, use MCP \`search\`, \`expand\`, and \`find_related\` — not ${native.explorationDenied} for exploration.
69
+
70
+ ${buildSearchPolicyBody(native)}
71
+
72
+ ${buildMcpWorkflow(native)}
73
+
74
+ ${CLI_FALLBACK}`;
75
+ }
76
+
77
+ export const DEFAULT_NATIVE_TOOLS: NativeToolNames = {
78
+ explorationDenied: "Grep, Glob, SemanticSearch, or Read",
79
+ grep: "Grep",
80
+ read: "Read",
81
+ };
82
+
83
+ export const SEARCH_POLICY_BODY = buildSearchPolicyBody(DEFAULT_NATIVE_TOOLS);
30
84
 
31
85
  export const INSTRUCTIONS_MARKDOWN = `## Miru Code Search
32
86
 
@@ -54,25 +108,31 @@ alwaysApply: true
54
108
 
55
109
  ${SEARCH_POLICY_BODY}
56
110
 
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.`;
111
+ When Miru MCP is connected, call \`search\` once per question. On \`truncated: true\`, call \`expand\` with \`file_path\` and \`anchor_line\`. Never use Cursor SemanticSearch or Grep for codebase questions unless confirming an exact literal.`;
58
112
 
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.
113
+ export const SUBAGENT_BODY = buildSubagentBody(DEFAULT_NATIVE_TOOLS);
60
114
 
61
- ${SEARCH_POLICY_BODY}
115
+ export const MCP_SERVER_INSTRUCTIONS =
116
+ "DO NOT use Grep, Glob, SemanticSearch, or Read to explore code when this server is available. " +
117
+ `${SNIPPET_GUIDANCE} ` +
118
+ "Workflow: (1) `search` once per question — do NOT re-search with paraphrases. " +
119
+ "(2) On `truncated: true`, call `expand` with `file_path` and `anchor_line` (or `start_line`) — do NOT read the whole file. " +
120
+ "(3) `find_related` traces similar code in other files, not more context in the same file. " +
121
+ "Always pass the project root as `repo`. Local repos return `absolute_path` on each hit — use Read only to edit. " +
122
+ "Native search is only for exact literal confirmation or files outside the indexed repo.";
62
123
 
63
- ### MCP workflow
124
+ export const MCP_SEARCH_TOOL_DESCRIPTION =
125
+ "DEFAULT code exploration tool. Returns compact snippets (~±15 lines). One call per question is usually enough. " +
126
+ "When a hit has `truncated: true`, call `expand` with `file_path` and `anchor_line` (or `start_line`) — not re-search or Read.";
64
127
 
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\`.
67
- 3. Use \`find_related\` to trace similar code in other files — not for more context in the same file.
68
- 4. Read via \`absolute_path\` only when editing or when \`expand\` still lacks context.
128
+ export const MCP_EXPAND_TOOL_DESCRIPTION =
129
+ "More context in the SAME file as a search hit. Pass `file_path` + `anchor_line` or `start_line` from a truncated hit; " +
130
+ "returns adjacent indexed chunks. Use when `truncated: true` — NOT for similar code in other files (use find_related). " +
131
+ "Prefer this over re-searching or reading the whole file.";
69
132
 
70
- ### CLI fallback (no MCP in this session)
133
+ export const MCP_FIND_RELATED_TOOL_DESCRIPTION =
134
+ "Find code similar to a file:line in OTHER parts of the codebase. Results may be snippets; use `expand` when `truncated: true`. " +
135
+ "For more context in the same file, use `expand` instead.";
71
136
 
72
- \`\`\`bash
73
- miru search "authentication flow" .
74
- miru expand src/auth.ts 42 .
75
- miru find-related src/auth.ts 42 .
76
- \`\`\`
77
-
78
- If \`miru\` is not on \`$PATH\`, use \`bunx @takara-ai/miru-code\`.`;
137
+ export const SEARCH_GUARD_EXPAND_HINT =
138
+ "If a hit has `truncated: true`, call `expand` with `file_path` and `anchor_line` — do not re-search or read the whole file.";