@takara-ai/miru-code 1.8.2 → 1.10.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/README.md +65 -53
- package/docs/coverage-badge.json +6 -0
- package/grammars/manifest.json +10 -2
- package/grammars/tree-sitter-astro.wasm +0 -0
- package/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-php_only.wasm +0 -0
- package/grammars/tree-sitter-sql.wasm +0 -0
- package/grammars/tree-sitter-svelte.wasm +0 -0
- package/grammars/tree-sitter-vue.wasm +0 -0
- package/package.json +10 -2
- package/src/agents.ts +4 -4
- package/src/auth/device.ts +26 -21
- package/src/auth/providers.ts +26 -11
- package/src/benchmark/compare.ts +3 -10
- package/src/benchmark/grep.ts +64 -37
- package/src/benchmark/locate-compare.ts +8 -1
- package/src/benchmark/rg-literal.ts +9 -4
- package/src/cache.ts +3 -3
- package/src/chunking/ast.ts +21 -3
- package/src/chunking/grammars.ts +8 -3
- package/src/chunking/structural.ts +13 -24
- package/src/cli-mode.ts +18 -0
- package/src/cli.ts +54 -98
- package/src/credentials.ts +14 -14
- package/src/embeddings/openai.ts +6 -3
- package/src/embeddings/sagemaker.ts +18 -3
- package/src/embeddings/validate.ts +10 -7
- package/src/git.ts +6 -2
- package/src/help.ts +7 -7
- package/src/index/bm25.ts +66 -6
- package/src/index/chunk-selector.ts +39 -11
- package/src/index/create.ts +5 -9
- package/src/index/files.ts +5 -1
- package/src/index/incremental.ts +48 -8
- package/src/index/sparse.ts +14 -2
- package/src/installer/agents.ts +85 -65
- package/src/installer/benchmark-mode.ts +40 -11
- package/src/installer/config.ts +105 -42
- package/src/installer/installer.ts +59 -34
- package/src/installer/legacy-hooks.ts +131 -0
- package/src/installer/prompt.ts +20 -22
- package/src/installer/search-policy.ts +10 -10
- package/src/mcp/index-cache.ts +16 -3
- package/src/mcp/server.ts +65 -46
- package/src/mcp/stdio.ts +17 -2
- package/src/miru-index.ts +90 -22
- package/src/prompt.ts +51 -14
- package/src/ranking/boosting.ts +5 -6
- package/src/ranking/location.ts +6 -10
- package/src/ranking/penalties.ts +14 -22
- package/src/search.ts +8 -12
- package/src/setup.ts +25 -13
- package/src/snippet.ts +8 -26
- package/src/spinner.ts +26 -16
- package/src/tokenizer/bert-wordpiece.ts +0 -6
- package/src/utils.ts +1 -15
- package/src/version.ts +4 -4
- package/src/installer/hooks/install.ts +0 -549
- package/src/installer/hooks/opencode-plugin.ts +0 -54
- package/src/installer/hooks/search-guard.ts +0 -272
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Miru (見る)
|
|
2
2
|
|
|
3
|
-
[](https://github.com/takara-ai/miru-code/actions/workflows/ci.yml) [](./LICENSE) [](https://bun.sh)
|
|
3
|
+
[](https://github.com/takara-ai/miru-code/actions/workflows/ci.yml) [](https://github.com/takara-ai/miru-code/actions/workflows/ci.yml) [](./LICENSE) [](https://bun.sh)
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
**Hybrid code search for AI coding agents.** Find code by meaning, not grep.
|
|
@@ -38,6 +38,22 @@ Restart Claude Code (or reload plugins) when prompted. Update: `/plugin marketpl
|
|
|
38
38
|
|
|
39
39
|
**Any other IDE, or if marketplace install isn't available:** use the CLI install below.
|
|
40
40
|
|
|
41
|
+
### What you get from a plugin install vs the CLI
|
|
42
|
+
|
|
43
|
+
Plugin installs don't carry full CLI parity — what each one gives you depends on the IDE's own plugin capabilities, not just Miru's packaging:
|
|
44
|
+
|
|
45
|
+
| | Claude Code | Codex | Cursor |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| MCP tools (`search`, `locate`, `expand`, `find_related`) | ✅ | ✅ | ❌ *(plugin ships skills + rules only — no MCP entry yet)* |
|
|
48
|
+
| `miru` / Caveman / STE skills | ✅ | ✅ | ✅ |
|
|
49
|
+
| Dedicated sub-agent (`miru:miru-code`) | ✅ | — | — |
|
|
50
|
+
| Benchmark mode toggle (`/plugin configure`) | ✅ | — | — |
|
|
51
|
+
| Credentials | own plugin-scoped dir | own plugin-scoped dir | n/a |
|
|
52
|
+
|
|
53
|
+
`—` means the IDE's plugin schema has no equivalent mechanism to port these to (not a packaging gap we can close): Codex's and Cursor's plugin manifests have no `agents` or `userConfig` fields, so the dedicated sub-agent and the benchmark toggle are Claude-Code-only.
|
|
54
|
+
|
|
55
|
+
**Credentials are plugin-scoped, not shared with your CLI install.** Claude Code and Codex plugins each store auth in their own IDE-managed data directory (survives plugin updates, removed on uninstall). This means `miru setup` done via the CLI does **not** carry over to a plugin install, or vice versa — each authenticates independently on first use (interactive device-code login bootstraps automatically). If you use both the CLI and a plugin, you'll sign in twice.
|
|
56
|
+
|
|
41
57
|
## Install (CLI)
|
|
42
58
|
|
|
43
59
|
```bash
|
|
@@ -75,7 +91,7 @@ Interactive TUI — **↑↓** move, **space** toggle, **a** all, **enter** conf
|
|
|
75
91
|
| Instructions | Search policy in `CLAUDE.md` / `AGENTS.md` / `GEMINI.md` |
|
|
76
92
|
| Sub-agent | Dedicated `miru-code` agent file |
|
|
77
93
|
| Cursor rules | Always-on `.cursor/rules/miru-code.mdc` (Cursor only) |
|
|
78
|
-
|
|
|
94
|
+
| Old Miru hooks | Removes stale search hooks from older releases |
|
|
79
95
|
| Caveman *(experimental)* | On-demand chat compression skill (`/caveman`) |
|
|
80
96
|
| STE writing *(experimental)* | On-demand clear technical English for docs (`/ste`) |
|
|
81
97
|
|
|
@@ -89,35 +105,33 @@ miru uninstall # remove miru config
|
|
|
89
105
|
**Supported:** Cursor · Claude Code · Gemini CLI · Kiro · OpenCode · GitHub Copilot · Codex · VS Code · Visual Studio (Windows) · Windsurf / Devin Desktop
|
|
90
106
|
|
|
91
107
|
|
|
92
|
-
| IDE | MCP | Instructions / rules |
|
|
93
|
-
| -------------- | ------------------------------------- | ------------------------------- |
|
|
94
|
-
| Cursor | `~/.cursor/mcp.json` | `~/.cursor/rules/miru-code.mdc` | `~/.
|
|
95
|
-
| Claude Code | `~/.claude.json` | `~/.claude/CLAUDE.md` | `~/.claude/
|
|
96
|
-
| Gemini CLI | `~/.gemini/settings.json` | `~/.gemini/GEMINI.md` | `~/.
|
|
97
|
-
| Kiro | `~/.kiro/settings/mcp.json` | `~/.kiro/steering/miru.md` | `~/.kiro/
|
|
98
|
-
| OpenCode | `$XDG_CONFIG_HOME/opencode/opencode.json(c)` *(else `~/.config/opencode/…`)* | `…/AGENTS.md` |
|
|
99
|
-
| GitHub Copilot | `~/.copilot/mcp-config.json` | — | `~/.
|
|
100
|
-
| Codex | `~/.codex/config.toml` | `~/.codex/AGENTS.md` | `~/.
|
|
101
|
-
| VS Code | `…/Code/User/mcp.json` | — | `~/.
|
|
102
|
-
| Visual Studio | `%USERPROFILE%\.mcp.json` | — | `~/.
|
|
103
|
-
| Windsurf | — | — | `~/.
|
|
108
|
+
| IDE | MCP | Instructions / rules | Caveman *(experimental)* | STE *(experimental)* |
|
|
109
|
+
| -------------- | ------------------------------------- | ------------------------------- | ----------------------------------- | --------------------------------------- |
|
|
110
|
+
| Cursor | `~/.cursor/mcp.json` | `~/.cursor/rules/miru-code.mdc` | `~/.agents/skills/caveman/SKILL.md` | `~/.agents/skills/ste/SKILL.md` |
|
|
111
|
+
| Claude Code | `~/.claude.json` | `~/.claude/CLAUDE.md` | `~/.claude/skills/caveman/SKILL.md` | `~/.claude/skills/ste/SKILL.md` |
|
|
112
|
+
| Gemini CLI | `~/.gemini/settings.json` | `~/.gemini/GEMINI.md` | `~/.agents/skills/caveman/SKILL.md` | `~/.agents/skills/ste/SKILL.md` |
|
|
113
|
+
| Kiro | `~/.kiro/settings/mcp.json` | `~/.kiro/steering/miru.md` | `~/.kiro/skills/caveman/SKILL.md` | `~/.kiro/skills/ste/SKILL.md` |
|
|
114
|
+
| OpenCode | `$XDG_CONFIG_HOME/opencode/opencode.json(c)` *(else `~/.config/opencode/…`)* | `…/AGENTS.md` | `~/.agents/skills/caveman/SKILL.md` | `~/.agents/skills/ste/SKILL.md` |
|
|
115
|
+
| GitHub Copilot | `~/.copilot/mcp-config.json` | — | `~/.agents/skills/caveman/SKILL.md` | `~/.agents/skills/ste/SKILL.md` |
|
|
116
|
+
| Codex | `~/.codex/config.toml` | `~/.codex/AGENTS.md` | `~/.agents/skills/caveman/SKILL.md` | `~/.agents/skills/ste/SKILL.md` |
|
|
117
|
+
| VS Code | `…/Code/User/mcp.json` | — | `~/.agents/skills/caveman/SKILL.md` | `~/.agents/skills/ste/SKILL.md` |
|
|
118
|
+
| Visual Studio | `%USERPROFILE%\.mcp.json` | — | `~/.agents/skills/caveman/SKILL.md` | `~/.agents/skills/ste/SKILL.md` |
|
|
119
|
+
| Windsurf | — | — | `~/.agents/skills/caveman/SKILL.md` | `~/.agents/skills/ste/SKILL.md` |
|
|
104
120
|
|
|
105
121
|
|
|
106
122
|
### Plugin packaging
|
|
107
123
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
- Codex: `.codex-plugin/plugin.json` and `.agents/plugins/marketplace.json`
|
|
111
|
-
- Claude Code: `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json`
|
|
124
|
+
- Codex: `.codex-plugin/plugin.json`, `.agents/plugins/marketplace.json`, shares the root `mcp.json`
|
|
125
|
+
- Claude Code: `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`, its own `.claude-plugin/mcp.json` (needed for the `userConfig` benchmark toggle — see [What you get from a plugin install vs the CLI](#what-you-get-from-a-plugin-install-vs-the-cli))
|
|
112
126
|
- Cursor: `plugin.json` and `.cursor/rules/miru-code-search.mdc`
|
|
113
127
|
|
|
114
128
|
Current limitation:
|
|
115
129
|
|
|
116
|
-
- these plugin manifests
|
|
130
|
+
- these plugin manifests launch the published Miru runtime through `bunx @takara-ai/miru-code@latest mcp`
|
|
117
131
|
- that means local source edits do not affect plugin behavior until a package version is published
|
|
118
132
|
- and a fully self-contained “no Bun required” plugin install is still future work
|
|
119
133
|
|
|
120
|
-
###
|
|
134
|
+
### On-demand skills
|
|
121
135
|
|
|
122
136
|
Sub-agent files are also written where supported (see `miru install` plan). Windsurf hooks only *(experimental)* — no MCP entry yet. Caveman is an on-demand Agent Skill (default off): invoke with `/caveman` or “talk like caveman”; stop with “normal mode”. Invocation UI varies by IDE (`/caveman`, `$caveman`, `@caveman`, etc.). Most IDEs share `~/.agents/skills/caveman/SKILL.md` (including Copilot / VS Code / Visual Studio); Claude Code and Kiro keep vendor-native skill dirs. Ownership is tracked on the shared path so uninstalling one IDE keeps the skill while another still owns it; selecting all owners removes it once. STE is an on-demand Agent Skill (default off): invoke with `/ste` or “de-slop this”; keep articles and complete sentences. Most IDEs share `~/.agents/skills/ste/` the same way (ownership via `miru-owners.json`); Claude Code and Kiro keep vendor-native STE dirs.
|
|
123
137
|
|
|
@@ -143,17 +157,6 @@ Off by default at install time. Enable Caveman in the installer for any supporte
|
|
|
143
157
|
|
|
144
158
|
</details>
|
|
145
159
|
|
|
146
|
-
<details>
|
|
147
|
-
<summary>Search hooks <em>(experimental)</em></summary>
|
|
148
|
-
|
|
149
|
-
Search hooks are **experimental** — behavior and IDE support may change between releases.
|
|
150
|
-
|
|
151
|
-
Hooks run `miru hook-guard` before built-in search tools execute. They **block** conceptual Grep/Glob/SemanticSearch and shell `rg`/`grep`/`find`, and tell the agent to use Miru MCP `search` (or `locate` for exact literals) instead. Exact literal lookups (e.g. `REDIS_HOST`, a symbol name) still pass through.
|
|
152
|
-
|
|
153
|
-
Hooks are **off by default** at install time. Enable them in the installer if you want built-in Grep/Glob redirected to Miru MCP.
|
|
154
|
-
|
|
155
|
-
</details>
|
|
156
|
-
|
|
157
160
|
**Team sub-agent in a repo** (optional):
|
|
158
161
|
|
|
159
162
|
```bash
|
|
@@ -174,14 +177,14 @@ miru find-related src/auth.ts 42 ./src
|
|
|
174
177
|
Terminal output is human-readable; use `--json` for scripts. One-off without installing:
|
|
175
178
|
|
|
176
179
|
```bash
|
|
177
|
-
bunx @takara-ai/miru-code search "auth middleware" ./src
|
|
180
|
+
bunx @takara-ai/miru-code@latest search "auth middleware" ./src
|
|
178
181
|
```
|
|
179
182
|
|
|
180
183
|
---
|
|
181
184
|
|
|
182
185
|
## MCP tools
|
|
183
186
|
|
|
184
|
-
When wired via `miru install`, the MCP server exposes `search`, `locate`, `expand`, and `find_related`. `read_benchmark` appears only in [benchmark mode](#benchmark-mode).
|
|
187
|
+
When wired via `miru install`, the MCP server exposes `search`, `locate`, `expand`, and `find_related`. `read_benchmark` appears only in [benchmark mode](#benchmark-mode). `repo` is optional: it defaults to the server's startup directory. Set it for another repo, or if your MCP client starts elsewhere. The index is built on the first call and cached for the session.
|
|
185
188
|
|
|
186
189
|
|
|
187
190
|
| Tool | When to use |
|
|
@@ -195,11 +198,13 @@ When wired via `miru install`, the MCP server exposes `search`, `locate`, `expan
|
|
|
195
198
|
|
|
196
199
|
### Workflow
|
|
197
200
|
|
|
198
|
-
1. **`search`** with `query`
|
|
199
|
-
2. If a hit has **`truncated: true`**, call **`expand`** with `file_path
|
|
200
|
-
3. To trace similar patterns elsewhere, call **`find_related`** with the same `file_path
|
|
201
|
+
1. **`search`** with `query` — returns compact snippets (~±15 lines) and relevance scores.
|
|
202
|
+
2. If a hit has **`truncated: true`**, call **`expand`** with `file_path` and `anchor_line` — not another search or a full-file read.
|
|
203
|
+
3. To trace similar patterns elsewhere, call **`find_related`** with the same `file_path` and `anchor_line`.
|
|
201
204
|
4. Use your editor's **Read** on `absolute_path` only when editing or when `expand` still lacks context.
|
|
202
205
|
|
|
206
|
+
Keep `repo` on follow-up calls when set.
|
|
207
|
+
|
|
203
208
|
Prefer these tools over Grep, Glob, or SemanticSearch when Miru MCP is connected — hooks and instructions enforce that when enabled.
|
|
204
209
|
|
|
205
210
|
Local repo hits include `absolute_path` for one-click navigation. Parameter reference is below under [MCP parameters](#mcp-parameters).
|
|
@@ -220,10 +225,11 @@ MCP watches local files and updates the index incrementally. Package upgrades in
|
|
|
220
225
|
|
|
221
226
|
Miru chunks source in tiers: **AST** (tree-sitter, default) → **structural** heuristics → **line** splits.
|
|
222
227
|
|
|
223
|
-
**AST chunking** —
|
|
228
|
+
**AST chunking** — 26 languages (syntax-aware boundaries via vendored `web-tree-sitter` grammars):
|
|
224
229
|
|
|
225
230
|
| Language | Typical extensions |
|
|
226
231
|
|----------|-------------------|
|
|
232
|
+
| astro | `.astro` |
|
|
227
233
|
| bash | `.sh`, `.bash`, `.zsh` |
|
|
228
234
|
| c | `.c` |
|
|
229
235
|
| cpp | `.cpp`, `.h`, `.hpp`, etc. |
|
|
@@ -231,10 +237,10 @@ Miru chunks source in tiers: **AST** (tree-sitter, default) → **structural** h
|
|
|
231
237
|
| css | `.css` |
|
|
232
238
|
| dart | `.dart` |
|
|
233
239
|
| elixir | `.ex`, `.exs` |
|
|
234
|
-
| embeddedtemplate |
|
|
240
|
+
| embeddedtemplate | `.erb`, `.ejs` |
|
|
235
241
|
| go | `.go` |
|
|
236
242
|
| haskell | `.hs` |
|
|
237
|
-
| html | `.html` |
|
|
243
|
+
| html | `.html`, `.htm` |
|
|
238
244
|
| java | `.java` |
|
|
239
245
|
| javascript | `.js`, `.jsx`, `.mjs`, `.cjs` |
|
|
240
246
|
| json | `.json` |
|
|
@@ -245,17 +251,20 @@ Miru chunks source in tiers: **AST** (tree-sitter, default) → **structural** h
|
|
|
245
251
|
| rust | `.rs` |
|
|
246
252
|
| scala | `.scala` |
|
|
247
253
|
| solidity | `.sol` |
|
|
254
|
+
| sql | `.sql` |
|
|
255
|
+
| svelte | `.svelte` |
|
|
248
256
|
| typescript | `.ts`, `.tsx`, `.mts`, `.cts` |
|
|
257
|
+
| vue | `.vue` |
|
|
249
258
|
|
|
250
259
|
**Structural fallback** (brace/indent heuristics when AST is unavailable): python, go, typescript, javascript, cpp, c.
|
|
251
260
|
|
|
252
|
-
**Line fallback:** everything else that gets indexed (kotlin, swift,
|
|
261
|
+
**Line fallback:** everything else that gets indexed (kotlin, swift, etc.) — still searchable, coarser chunks.
|
|
253
262
|
|
|
254
263
|
Set `MIRU_AST_CHUNKING=0` to disable AST and use structural → lines only.
|
|
255
264
|
|
|
256
265
|
## CLI reference
|
|
257
266
|
|
|
258
|
-
Run `miru -h` for the command list, or `miru <command> -h` for details.
|
|
267
|
+
Run `miru` in a terminal or `miru -h` for the command list, or `miru <command> -h` for details.
|
|
259
268
|
|
|
260
269
|
| Command | Purpose |
|
|
261
270
|
| ----------------------------------------- | --------------------------------------------------------------------------------- |
|
|
@@ -269,8 +278,7 @@ Run `miru -h` for the command list, or `miru <command> -h` for details.
|
|
|
269
278
|
| `miru benchmark on/off/status/clear` | Toggle MCP benchmark mode / clear report |
|
|
270
279
|
| `miru init --agent <id>` | Project-local sub-agent |
|
|
271
280
|
| `miru clear [path]` | Drop index cache (use after big CLI-only refactors) |
|
|
272
|
-
| `miru
|
|
273
|
-
| `miru` | Start MCP server (`--benchmark` for comparisons) |
|
|
281
|
+
| `miru mcp` | Start MCP server (`--benchmark` for comparisons) |
|
|
274
282
|
|
|
275
283
|
|
|
276
284
|
CLI uses hyphens (`find-related`); MCP tool names use underscores (`find_related`).
|
|
@@ -285,7 +293,7 @@ bun add @takara-ai/miru-code
|
|
|
285
293
|
import { MiruIndex } from "@takara-ai/miru-code";
|
|
286
294
|
|
|
287
295
|
const index = await MiruIndex.fromPath("./src");
|
|
288
|
-
const results = await index.search({ query: "BM25 tokenize"
|
|
296
|
+
const results = await index.search({ query: "BM25 tokenize" });
|
|
289
297
|
```
|
|
290
298
|
|
|
291
299
|
## Environment
|
|
@@ -352,8 +360,9 @@ Override with `MIRU_BENCHMARK_HISTORY_PATH`. Append-only JSONL of compact token
|
|
|
352
360
|
| Param | Required | Notes |
|
|
353
361
|
| ---------------- | -------- | --------------------------------------- |
|
|
354
362
|
| `query` | yes | Natural language or code query |
|
|
355
|
-
| `repo` |
|
|
356
|
-
| `
|
|
363
|
+
| `repo` | no | Startup directory by default; set for another repo |
|
|
364
|
+
| `include` | no | Gitignore-style glob patterns; only matching files are searched (same as `locate.include`) |
|
|
365
|
+
| `exclude` | no | Gitignore-style glob patterns; matching files are skipped (same as `locate.exclude`) |
|
|
357
366
|
| `dedupe_by_file` | no | Keep best hit per file (default `true`) |
|
|
358
367
|
|
|
359
368
|
|
|
@@ -363,7 +372,9 @@ Override with `MIRU_BENCHMARK_HISTORY_PATH`. Append-only JSONL of compact token
|
|
|
363
372
|
| Param | Required | Notes |
|
|
364
373
|
| ------------- | -------- | ----------------------------------------------------------------------------------- |
|
|
365
374
|
| `literal` | yes | Exact substring to find |
|
|
366
|
-
| `repo` |
|
|
375
|
+
| `repo` | no | Startup directory by default; set for another repo |
|
|
376
|
+
| `include` | no | Gitignore-style glob patterns; only matching files are searched |
|
|
377
|
+
| `exclude` | no | Gitignore-style glob patterns; matching files are skipped |
|
|
367
378
|
| `mode` | no | `count` · `locations` · `lines` (default). Prefer `count`/`locations` when possible |
|
|
368
379
|
| `limit` | no | Cap returned hits. Omit to return all matches |
|
|
369
380
|
| `ignore_case` | no | Case-insensitive match (default `false`) |
|
|
@@ -376,7 +387,7 @@ Override with `MIRU_BENCHMARK_HISTORY_PATH`. Append-only JSONL of compact token
|
|
|
376
387
|
| ------------------ | -------- | --------------------------------------------------------------------- |
|
|
377
388
|
| `file_path` | yes | From hit `file_path` or `absolute_path` (local repos) |
|
|
378
389
|
| `anchor_line` | yes | From the search hit (`anchor_line` when truncated, else `start_line`) |
|
|
379
|
-
| `repo` |
|
|
390
|
+
| `repo` | no | Same repo as the search, if set |
|
|
380
391
|
| `before` / `after` | no | Extra chunks before/after anchor (default 1 each) |
|
|
381
392
|
|
|
382
393
|
|
|
@@ -387,8 +398,7 @@ Override with `MIRU_BENCHMARK_HISTORY_PATH`. Append-only JSONL of compact token
|
|
|
387
398
|
| ------------- | -------- | -------------------------------------------- |
|
|
388
399
|
| `file_path` | yes | From a search hit |
|
|
389
400
|
| `anchor_line` | yes | From the search hit |
|
|
390
|
-
| `repo` |
|
|
391
|
-
| `top_k` | no | Related chunks to return (default 3, max 10) |
|
|
401
|
+
| `repo` | no | Same repo as the search, if set |
|
|
392
402
|
|
|
393
403
|
|
|
394
404
|
`**read_benchmark**` *(benchmark mode only)*
|
|
@@ -405,16 +415,18 @@ Override with `MIRU_BENCHMARK_HISTORY_PATH`. Append-only JSONL of compact token
|
|
|
405
415
|
{
|
|
406
416
|
"miru": {
|
|
407
417
|
"command": "miru",
|
|
408
|
-
"args": []
|
|
418
|
+
"args": ["mcp"]
|
|
409
419
|
}
|
|
410
420
|
}
|
|
411
421
|
```
|
|
412
422
|
|
|
413
|
-
For benchmark mode, set `"args": ["--benchmark"]` (or append
|
|
423
|
+
For benchmark mode, set `"args": ["mcp", "--benchmark"]` (or append the flag). Prefer `miru benchmark on` after a normal install — it updates every agent config.
|
|
414
424
|
|
|
415
425
|
Run `miru setup` once so the server can load credentials from `credentials.json`. If the MCP server starts in an interactive terminal without stored credentials, it will start device login automatically.
|
|
416
426
|
|
|
417
|
-
Use `bunx` + `@takara-ai/miru-code` if `miru` is not global. Wrapper key varies by IDE (`mcpServers`, `servers`, or `mcp`).
|
|
427
|
+
Use `bunx` + `@takara-ai/miru-code@latest` if `miru` is not global. The installer uses this command so each new MCP server launch can pick up a published version without a global update. A running server keeps its current version until restarted. Wrapper key varies by IDE (`mcpServers`, `servers`, or `mcp`).
|
|
428
|
+
|
|
429
|
+
Older headless MCP configs that launch `miru` without a subcommand, with or without MCP flags, continue to work. Run `miru install` again to update managed entries to the explicit `mcp` subcommand, then restart your coding agent. For manual MCP configs, add `mcp` after the executable or package name.
|
|
418
430
|
|
|
419
431
|
## Developing
|
|
420
432
|
|
package/grammars/manifest.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"version": 1,
|
|
3
3
|
"runtime": "web-tree-sitter@^0.26.9",
|
|
4
4
|
"languages": {
|
|
5
|
+
"astro": "tree-sitter-astro.wasm",
|
|
5
6
|
"bash": "tree-sitter-bash.wasm",
|
|
6
7
|
"c": "tree-sitter-c.wasm",
|
|
7
8
|
"cpp": "tree-sitter-cpp.wasm",
|
|
@@ -23,9 +24,13 @@
|
|
|
23
24
|
"rust": "tree-sitter-rust.wasm",
|
|
24
25
|
"scala": "tree-sitter-scala.wasm",
|
|
25
26
|
"solidity": "tree-sitter-solidity.wasm",
|
|
26
|
-
"
|
|
27
|
+
"sql": "tree-sitter-sql.wasm",
|
|
28
|
+
"svelte": "tree-sitter-svelte.wasm",
|
|
29
|
+
"typescript": "tree-sitter-typescript.wasm",
|
|
30
|
+
"vue": "tree-sitter-vue.wasm"
|
|
27
31
|
},
|
|
28
32
|
"files": [
|
|
33
|
+
"tree-sitter-astro.wasm",
|
|
29
34
|
"tree-sitter-bash.wasm",
|
|
30
35
|
"tree-sitter-c.wasm",
|
|
31
36
|
"tree-sitter-c_sharp.wasm",
|
|
@@ -50,7 +55,10 @@
|
|
|
50
55
|
"tree-sitter-rust.wasm",
|
|
51
56
|
"tree-sitter-scala.wasm",
|
|
52
57
|
"tree-sitter-solidity.wasm",
|
|
58
|
+
"tree-sitter-sql.wasm",
|
|
59
|
+
"tree-sitter-svelte.wasm",
|
|
53
60
|
"tree-sitter-tsx.wasm",
|
|
54
|
-
"tree-sitter-typescript.wasm"
|
|
61
|
+
"tree-sitter-typescript.wasm",
|
|
62
|
+
"tree-sitter-vue.wasm"
|
|
55
63
|
]
|
|
56
64
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takara-ai/miru-code",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
|
+
"mcpName": "ai.takara/miru",
|
|
4
5
|
"description": "Miru (見る) — hybrid code search for agents. Bun, TypeScript, Takara embeddings.",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"license": "MIT",
|
|
@@ -50,13 +51,15 @@
|
|
|
50
51
|
"miru": "bun run src/cli.ts",
|
|
51
52
|
"mcp": "bun run src/cli.ts",
|
|
52
53
|
"test": "bun test",
|
|
54
|
+
"coverage": "bun test --coverage --coverage-reporter=lcov",
|
|
55
|
+
"coverage:badge": "bun run coverage && bun run scripts/update-coverage-badge.ts",
|
|
53
56
|
"typecheck": "tsc --noEmit",
|
|
54
57
|
"lint": "biome check .",
|
|
55
58
|
"lint:fix": "biome check --write .",
|
|
56
59
|
"prepare": "bun run scripts/install-git-hooks.ts",
|
|
57
60
|
"build:kiro-plugin": "bun run scripts/build-kiro-plugin.ts",
|
|
58
61
|
"vendor-grammars": "bun run scripts/vendor-grammars.ts",
|
|
59
|
-
"prepublishOnly": "bun run
|
|
62
|
+
"prepublishOnly": "bun run typecheck && bun test",
|
|
60
63
|
"benchmark:ab": "bun run scripts/search-ab-local.ts",
|
|
61
64
|
"benchmark:ab-multi": "bun run scripts/search-ab-multi.ts",
|
|
62
65
|
"benchmark:codesearchnet-compression": "bun run scripts/benchmark-codesearchnet-compression.ts",
|
|
@@ -83,13 +86,17 @@
|
|
|
83
86
|
},
|
|
84
87
|
"devDependencies": {
|
|
85
88
|
"@biomejs/biome": "^2.5.1",
|
|
89
|
+
"@derekstride/tree-sitter-sql": "^0.3.11",
|
|
86
90
|
"@huggingface/tokenizers": "^0.1.3",
|
|
91
|
+
"@tree-sitter-grammars/tree-sitter-svelte": "^1.0.2",
|
|
87
92
|
"@types/bun": "^1.3.14",
|
|
88
93
|
"ajv": "^8.17.1",
|
|
89
94
|
"ajv-formats": "^3.0.1",
|
|
95
|
+
"tree-sitter-astro": "github:virchau13/tree-sitter-astro#213f6e6",
|
|
90
96
|
"tree-sitter-bash": "^0.25.1",
|
|
91
97
|
"tree-sitter-c": "^0.24.1",
|
|
92
98
|
"tree-sitter-c-sharp": "^0.23.5",
|
|
99
|
+
"tree-sitter-cli": "^0.27.0",
|
|
93
100
|
"tree-sitter-cpp": "^0.23.4",
|
|
94
101
|
"tree-sitter-css": "^0.25.0",
|
|
95
102
|
"tree-sitter-dart": "^1.0.0",
|
|
@@ -109,6 +116,7 @@
|
|
|
109
116
|
"tree-sitter-scala": "^0.24.0",
|
|
110
117
|
"tree-sitter-solidity": "^1.2.13",
|
|
111
118
|
"tree-sitter-typescript": "^0.23.2",
|
|
119
|
+
"tree-sitter-vue": "github:tree-sitter-grammars/tree-sitter-vue#ce8011a",
|
|
112
120
|
"typescript": "^6.0.3"
|
|
113
121
|
}
|
|
114
122
|
}
|
package/src/agents.ts
CHANGED
|
@@ -38,9 +38,9 @@ const AGENT_NATIVE_TOOLS: Record<AgentId, NativeToolNames> = {
|
|
|
38
38
|
},
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
export function agentDestination(agent: AgentId): string {
|
|
41
|
+
export function agentDestination(agent: AgentId, cwd = "."): string {
|
|
42
42
|
const baseDir = agent === "copilot" ? ".github" : `.${agent}`;
|
|
43
|
-
return join(baseDir, "agents", "miru-code.md");
|
|
43
|
+
return join(cwd, baseDir, "agents", "miru-code.md");
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export async function loadAgentTemplate(agent: AgentId): Promise<string> {
|
|
@@ -51,9 +51,9 @@ export async function loadAgentTemplate(agent: AgentId): Promise<string> {
|
|
|
51
51
|
|
|
52
52
|
export async function writeAgentFile(
|
|
53
53
|
agent: AgentId,
|
|
54
|
-
options: { force?: boolean } = {},
|
|
54
|
+
options: { force?: boolean; cwd?: string } = {},
|
|
55
55
|
): Promise<string> {
|
|
56
|
-
const dest = agentDestination(agent);
|
|
56
|
+
const dest = agentDestination(agent, options.cwd);
|
|
57
57
|
const existing = await Bun.file(dest).exists();
|
|
58
58
|
if (existing && !options.force) {
|
|
59
59
|
throw new Error(`${dest} already exists. Run with --force to overwrite.`);
|
package/src/auth/device.ts
CHANGED
|
@@ -205,30 +205,31 @@ export async function checkDeviceAuthorizationOnce(
|
|
|
205
205
|
|
|
206
206
|
export async function pollDeviceAuthorization(
|
|
207
207
|
start: DeviceAuthorizationStart,
|
|
208
|
-
options?: {
|
|
208
|
+
options?: {
|
|
209
|
+
config?: DeviceAuthConfig;
|
|
210
|
+
fetchImpl?: typeof fetch;
|
|
211
|
+
sleepImpl?: (ms: number) => Promise<void>;
|
|
212
|
+
},
|
|
209
213
|
): Promise<DeviceAuthorizationTokens> {
|
|
210
214
|
const deadline = Date.now() + start.expiresIn * 1000;
|
|
211
215
|
let intervalMs = start.interval * 1000;
|
|
212
216
|
|
|
213
217
|
while (Date.now() < deadline) {
|
|
214
|
-
await sleep(intervalMs);
|
|
218
|
+
await (options?.sleepImpl ?? sleep)(intervalMs);
|
|
215
219
|
|
|
216
220
|
const check = await checkDeviceAuthorizationOnce(start, options);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
if (check.status === "expired") {
|
|
231
|
-
throw new Error("Device login expired before it was completed.");
|
|
221
|
+
switch (check.status) {
|
|
222
|
+
case "success":
|
|
223
|
+
return check.tokens;
|
|
224
|
+
case "pending":
|
|
225
|
+
continue;
|
|
226
|
+
case "slow_down":
|
|
227
|
+
intervalMs += 5_000;
|
|
228
|
+
continue;
|
|
229
|
+
case "denied":
|
|
230
|
+
throw new Error("Device login was denied.");
|
|
231
|
+
case "expired":
|
|
232
|
+
throw new Error("Device login expired before it was completed.");
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
235
|
|
|
@@ -284,15 +285,19 @@ export function deviceCredentialsNeedRefresh(credentials: StoredDeviceCodeCreden
|
|
|
284
285
|
return expiresAt <= Date.now() + EXPIRY_SKEW_MS;
|
|
285
286
|
}
|
|
286
287
|
|
|
287
|
-
export function openBrowserForDeviceLogin(
|
|
288
|
+
export function openBrowserForDeviceLogin(
|
|
289
|
+
url: string,
|
|
290
|
+
platform: NodeJS.Platform = process.platform,
|
|
291
|
+
spawn: typeof Bun.spawn = Bun.spawn,
|
|
292
|
+
): boolean {
|
|
288
293
|
const command =
|
|
289
|
-
|
|
294
|
+
platform === "darwin"
|
|
290
295
|
? ["open", url]
|
|
291
|
-
:
|
|
296
|
+
: platform === "win32"
|
|
292
297
|
? ["cmd", "/c", "start", "", url]
|
|
293
298
|
: ["xdg-open", url];
|
|
294
299
|
try {
|
|
295
|
-
const proc =
|
|
300
|
+
const proc = spawn(command, {
|
|
296
301
|
stdin: "ignore",
|
|
297
302
|
stdout: "ignore",
|
|
298
303
|
stderr: "ignore",
|
package/src/auth/providers.ts
CHANGED
|
@@ -20,10 +20,19 @@ export interface AuthenticateOptions {
|
|
|
20
20
|
interactive?: boolean;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
export interface AuthProviderDependencies {
|
|
24
|
+
promptHidden?: typeof promptHidden;
|
|
25
|
+
validateApiKey?: typeof validateEmbeddingApiKey;
|
|
26
|
+
promptConfirm?: typeof promptConfirm;
|
|
27
|
+
startDeviceAuthorization?: typeof startDeviceAuthorization;
|
|
28
|
+
pollDeviceAuthorization?: typeof pollDeviceAuthorization;
|
|
29
|
+
openBrowser?: (url: string) => boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function promptApiKey(promptHiddenImpl: typeof promptHidden = promptHidden): Promise<string> {
|
|
24
33
|
let key = "";
|
|
25
34
|
while (!key) {
|
|
26
|
-
key = await
|
|
35
|
+
key = await promptHiddenImpl("Takara API key (input hidden): ", process.stderr);
|
|
27
36
|
if (!key) {
|
|
28
37
|
warn("API key cannot be empty.");
|
|
29
38
|
}
|
|
@@ -43,12 +52,13 @@ async function resolveAuthMode(options: AuthenticateOptions): Promise<AuthMode>
|
|
|
43
52
|
|
|
44
53
|
async function authenticateWithApiKey(
|
|
45
54
|
options: AuthenticateOptions,
|
|
55
|
+
dependencies: AuthProviderDependencies,
|
|
46
56
|
): Promise<AuthenticatedCredentials> {
|
|
47
|
-
const apiKey = options.apiKey ?? (await promptApiKey());
|
|
57
|
+
const apiKey = options.apiKey ?? (await promptApiKey(dependencies.promptHidden));
|
|
48
58
|
if (!options.skipValidation) {
|
|
49
59
|
const spinner = new Spinner("Validating API key");
|
|
50
60
|
spinner.start();
|
|
51
|
-
const result = await validateEmbeddingApiKey({ apiKey });
|
|
61
|
+
const result = await (dependencies.validateApiKey ?? validateEmbeddingApiKey)({ apiKey });
|
|
52
62
|
if (!result.valid) {
|
|
53
63
|
spinner.stop();
|
|
54
64
|
throw new Error(result.message);
|
|
@@ -60,19 +70,20 @@ async function authenticateWithApiKey(
|
|
|
60
70
|
|
|
61
71
|
async function authenticateWithDeviceCode(
|
|
62
72
|
options: AuthenticateOptions,
|
|
73
|
+
dependencies: AuthProviderDependencies,
|
|
63
74
|
): Promise<AuthenticatedCredentials> {
|
|
64
75
|
writeStderr("");
|
|
65
76
|
const spinner = new Spinner("Authenticating");
|
|
66
77
|
spinner.start();
|
|
67
78
|
try {
|
|
68
|
-
const start = await startDeviceAuthorization();
|
|
79
|
+
const start = await (dependencies.startDeviceAuthorization ?? startDeviceAuthorization)();
|
|
69
80
|
const verificationUrl = start.verificationUriComplete ?? start.verificationUri;
|
|
70
81
|
|
|
71
82
|
const shouldOpenBrowser =
|
|
72
83
|
options.interactive &&
|
|
73
84
|
(process.env.MIRU_OPEN_BROWSER === undefined || process.env.MIRU_OPEN_BROWSER === "1");
|
|
74
85
|
if (shouldOpenBrowser) {
|
|
75
|
-
openBrowserForDeviceLogin(verificationUrl);
|
|
86
|
+
(dependencies.openBrowser ?? openBrowserForDeviceLogin)(verificationUrl);
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
let visit = dim(` Visit ${verificationUrl}`);
|
|
@@ -81,7 +92,7 @@ async function authenticateWithDeviceCode(
|
|
|
81
92
|
}
|
|
82
93
|
spinner.follow(visit);
|
|
83
94
|
|
|
84
|
-
const tokens = await pollDeviceAuthorization(start);
|
|
95
|
+
const tokens = await (dependencies.pollDeviceAuthorization ?? pollDeviceAuthorization)(start);
|
|
85
96
|
spinner.succeed("");
|
|
86
97
|
return {
|
|
87
98
|
kind: "device_code",
|
|
@@ -95,9 +106,12 @@ async function authenticateWithDeviceCode(
|
|
|
95
106
|
spinner.stop();
|
|
96
107
|
if (options.allowManualFallback && options.interactive) {
|
|
97
108
|
warn(err instanceof Error ? err.message : String(err));
|
|
98
|
-
const fallback = await promptConfirm
|
|
109
|
+
const fallback = await (dependencies.promptConfirm ?? promptConfirm)(
|
|
110
|
+
"Enter an API key instead?",
|
|
111
|
+
true,
|
|
112
|
+
);
|
|
99
113
|
if (fallback) {
|
|
100
|
-
return authenticateWithApiKey(options);
|
|
114
|
+
return authenticateWithApiKey(options, dependencies);
|
|
101
115
|
}
|
|
102
116
|
}
|
|
103
117
|
throw err;
|
|
@@ -106,10 +120,11 @@ async function authenticateWithDeviceCode(
|
|
|
106
120
|
|
|
107
121
|
export async function authenticateWithProvider(
|
|
108
122
|
options: AuthenticateOptions,
|
|
123
|
+
dependencies: AuthProviderDependencies = {},
|
|
109
124
|
): Promise<AuthenticatedCredentials> {
|
|
110
125
|
const mode = await resolveAuthMode(options);
|
|
111
126
|
if (mode === "device_code") {
|
|
112
|
-
return authenticateWithDeviceCode(options);
|
|
127
|
+
return authenticateWithDeviceCode(options, dependencies);
|
|
113
128
|
}
|
|
114
|
-
return authenticateWithApiKey(options);
|
|
129
|
+
return authenticateWithApiKey(options, dependencies);
|
|
115
130
|
}
|
package/src/benchmark/compare.ts
CHANGED
|
@@ -49,9 +49,6 @@ function firstGrepMatchLine(hit: GrepFileHit | undefined): number | null {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function expandLineSpan(chunks: { start_line: number; end_line: number }[]): number {
|
|
52
|
-
if (chunks.length === 0) {
|
|
53
|
-
return 0;
|
|
54
|
-
}
|
|
55
52
|
const start = Math.min(...chunks.map((c) => c.start_line));
|
|
56
53
|
const end = Math.max(...chunks.map((c) => c.end_line));
|
|
57
54
|
return end - start + 1;
|
|
@@ -74,9 +71,6 @@ async function readLineWindowTokens(
|
|
|
74
71
|
try {
|
|
75
72
|
const text = await readFile(absPath, "utf-8");
|
|
76
73
|
const lines = text.split("\n");
|
|
77
|
-
if (lines.length === 0) {
|
|
78
|
-
return 0;
|
|
79
|
-
}
|
|
80
74
|
const half = Math.max(1, Math.floor(lineSpan / 2));
|
|
81
75
|
const start = Math.max(1, centerLine - half);
|
|
82
76
|
const end = Math.min(lines.length, centerLine + half);
|
|
@@ -127,9 +121,6 @@ function topKOverlapPct(miruFiles: string[], grepFiles: string[]): number {
|
|
|
127
121
|
return 100;
|
|
128
122
|
}
|
|
129
123
|
const union = new Set([...miruFiles, ...grepFiles]);
|
|
130
|
-
if (union.size === 0) {
|
|
131
|
-
return 0;
|
|
132
|
-
}
|
|
133
124
|
const overlap = miruFiles.filter((file) => grepFiles.includes(file)).length;
|
|
134
125
|
return Math.round((overlap / union.size) * 100);
|
|
135
126
|
}
|
|
@@ -169,9 +160,11 @@ export async function benchmarkSearchComparison(options: {
|
|
|
169
160
|
/** Keep only the best hit per file (default true). Matches MCP `dedupe_by_file`. */
|
|
170
161
|
dedupeByFile?: boolean;
|
|
171
162
|
relevant?: string[];
|
|
163
|
+
dependencies?: { grepSearch?: typeof grepSearch };
|
|
172
164
|
}): Promise<{ benchmark: SearchBenchmarkBlock; results: SearchResult[] }> {
|
|
173
165
|
const { query, repoPath, index, topK, relevant } = options;
|
|
174
166
|
const dedupeByFile = options.dedupeByFile !== false;
|
|
167
|
+
const searchGrep = options.dependencies?.grepSearch ?? grepSearch;
|
|
175
168
|
const parallelStart = performance.now();
|
|
176
169
|
|
|
177
170
|
const miruPromise = (async () => {
|
|
@@ -194,7 +187,7 @@ export async function benchmarkSearchComparison(options: {
|
|
|
194
187
|
|
|
195
188
|
const grepSearchPromise = (async () => {
|
|
196
189
|
const started = performance.now();
|
|
197
|
-
const grep = await
|
|
190
|
+
const grep = await searchGrep(repoPath, query, topK);
|
|
198
191
|
return { grep, latencyMs: performance.now() - started };
|
|
199
192
|
})();
|
|
200
193
|
|