@takara-ai/miru-code 1.9.0 → 1.10.1
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 +43 -48
- 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 +10 -45
- package/src/credentials.ts +8 -13
- 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 +5 -4
- package/src/index/bm25.ts +66 -6
- 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 +8 -8
- package/src/mcp/auth-tool.ts +93 -33
- package/src/mcp/index-cache.ts +16 -3
- package/src/mcp/server.ts +42 -22
- package/src/mcp/stdio.ts +17 -2
- package/src/miru-index.ts +54 -18
- 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 +4 -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 +0 -4
- 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.
|
|
@@ -48,10 +48,9 @@ Plugin installs don't carry full CLI parity — what each one gives you depends
|
|
|
48
48
|
| `miru` / Caveman / STE skills | ✅ | ✅ | ✅ |
|
|
49
49
|
| Dedicated sub-agent (`miru:miru-code`) | ✅ | — | — |
|
|
50
50
|
| Benchmark mode toggle (`/plugin configure`) | ✅ | — | — |
|
|
51
|
-
| Search hooks | — | — | — |
|
|
52
51
|
| Credentials | own plugin-scoped dir | own plugin-scoped dir | n/a |
|
|
53
52
|
|
|
54
|
-
`—` 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.
|
|
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.
|
|
55
54
|
|
|
56
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.
|
|
57
56
|
|
|
@@ -92,7 +91,7 @@ Interactive TUI — **↑↓** move, **space** toggle, **a** all, **enter** conf
|
|
|
92
91
|
| Instructions | Search policy in `CLAUDE.md` / `AGENTS.md` / `GEMINI.md` |
|
|
93
92
|
| Sub-agent | Dedicated `miru-code` agent file |
|
|
94
93
|
| Cursor rules | Always-on `.cursor/rules/miru-code.mdc` (Cursor only) |
|
|
95
|
-
|
|
|
94
|
+
| Old Miru hooks | Removes stale search hooks from older releases |
|
|
96
95
|
| Caveman *(experimental)* | On-demand chat compression skill (`/caveman`) |
|
|
97
96
|
| STE writing *(experimental)* | On-demand clear technical English for docs (`/ste`) |
|
|
98
97
|
|
|
@@ -106,18 +105,18 @@ miru uninstall # remove miru config
|
|
|
106
105
|
**Supported:** Cursor · Claude Code · Gemini CLI · Kiro · OpenCode · GitHub Copilot · Codex · VS Code · Visual Studio (Windows) · Windsurf / Devin Desktop
|
|
107
106
|
|
|
108
107
|
|
|
109
|
-
| IDE | MCP | Instructions / rules |
|
|
110
|
-
| -------------- | ------------------------------------- | ------------------------------- |
|
|
111
|
-
| Cursor | `~/.cursor/mcp.json` | `~/.cursor/rules/miru-code.mdc` | `~/.
|
|
112
|
-
| Claude Code | `~/.claude.json` | `~/.claude/CLAUDE.md` | `~/.claude/
|
|
113
|
-
| Gemini CLI | `~/.gemini/settings.json` | `~/.gemini/GEMINI.md` | `~/.
|
|
114
|
-
| Kiro | `~/.kiro/settings/mcp.json` | `~/.kiro/steering/miru.md` | `~/.kiro/
|
|
115
|
-
| OpenCode | `$XDG_CONFIG_HOME/opencode/opencode.json(c)` *(else `~/.config/opencode/…`)* | `…/AGENTS.md` |
|
|
116
|
-
| GitHub Copilot | `~/.copilot/mcp-config.json` | — | `~/.
|
|
117
|
-
| Codex | `~/.codex/config.toml` | `~/.codex/AGENTS.md` | `~/.
|
|
118
|
-
| VS Code | `…/Code/User/mcp.json` | — | `~/.
|
|
119
|
-
| Visual Studio | `%USERPROFILE%\.mcp.json` | — | `~/.
|
|
120
|
-
| 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` |
|
|
121
120
|
|
|
122
121
|
|
|
123
122
|
### Plugin packaging
|
|
@@ -128,11 +127,11 @@ miru uninstall # remove miru config
|
|
|
128
127
|
|
|
129
128
|
Current limitation:
|
|
130
129
|
|
|
131
|
-
- these plugin manifests
|
|
130
|
+
- these plugin manifests launch the published Miru runtime through `bunx @takara-ai/miru-code@latest mcp`
|
|
132
131
|
- that means local source edits do not affect plugin behavior until a package version is published
|
|
133
132
|
- and a fully self-contained “no Bun required” plugin install is still future work
|
|
134
133
|
|
|
135
|
-
###
|
|
134
|
+
### On-demand skills
|
|
136
135
|
|
|
137
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.
|
|
138
137
|
|
|
@@ -158,17 +157,6 @@ Off by default at install time. Enable Caveman in the installer for any supporte
|
|
|
158
157
|
|
|
159
158
|
</details>
|
|
160
159
|
|
|
161
|
-
<details>
|
|
162
|
-
<summary>Search hooks <em>(experimental)</em></summary>
|
|
163
|
-
|
|
164
|
-
Search hooks are **experimental** — behavior and IDE support may change between releases.
|
|
165
|
-
|
|
166
|
-
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.
|
|
167
|
-
|
|
168
|
-
Hooks are **off by default** at install time. Enable them in the installer if you want built-in Grep/Glob redirected to Miru MCP.
|
|
169
|
-
|
|
170
|
-
</details>
|
|
171
|
-
|
|
172
160
|
**Team sub-agent in a repo** (optional):
|
|
173
161
|
|
|
174
162
|
```bash
|
|
@@ -189,14 +177,14 @@ miru find-related src/auth.ts 42 ./src
|
|
|
189
177
|
Terminal output is human-readable; use `--json` for scripts. One-off without installing:
|
|
190
178
|
|
|
191
179
|
```bash
|
|
192
|
-
bunx @takara-ai/miru-code search "auth middleware" ./src
|
|
180
|
+
bunx @takara-ai/miru-code@latest search "auth middleware" ./src
|
|
193
181
|
```
|
|
194
182
|
|
|
195
183
|
---
|
|
196
184
|
|
|
197
185
|
## MCP tools
|
|
198
186
|
|
|
199
|
-
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.
|
|
200
188
|
|
|
201
189
|
|
|
202
190
|
| Tool | When to use |
|
|
@@ -210,11 +198,13 @@ When wired via `miru install`, the MCP server exposes `search`, `locate`, `expan
|
|
|
210
198
|
|
|
211
199
|
### Workflow
|
|
212
200
|
|
|
213
|
-
1. **`search`** with `query`
|
|
214
|
-
2. If a hit has **`truncated: true`**, call **`expand`** with `file_path
|
|
215
|
-
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`.
|
|
216
204
|
4. Use your editor's **Read** on `absolute_path` only when editing or when `expand` still lacks context.
|
|
217
205
|
|
|
206
|
+
Keep `repo` on follow-up calls when set.
|
|
207
|
+
|
|
218
208
|
Prefer these tools over Grep, Glob, or SemanticSearch when Miru MCP is connected — hooks and instructions enforce that when enabled.
|
|
219
209
|
|
|
220
210
|
Local repo hits include `absolute_path` for one-click navigation. Parameter reference is below under [MCP parameters](#mcp-parameters).
|
|
@@ -235,10 +225,11 @@ MCP watches local files and updates the index incrementally. Package upgrades in
|
|
|
235
225
|
|
|
236
226
|
Miru chunks source in tiers: **AST** (tree-sitter, default) → **structural** heuristics → **line** splits.
|
|
237
227
|
|
|
238
|
-
**AST chunking** —
|
|
228
|
+
**AST chunking** — 26 languages (syntax-aware boundaries via vendored `web-tree-sitter` grammars):
|
|
239
229
|
|
|
240
230
|
| Language | Typical extensions |
|
|
241
231
|
|----------|-------------------|
|
|
232
|
+
| astro | `.astro` |
|
|
242
233
|
| bash | `.sh`, `.bash`, `.zsh` |
|
|
243
234
|
| c | `.c` |
|
|
244
235
|
| cpp | `.cpp`, `.h`, `.hpp`, etc. |
|
|
@@ -246,10 +237,10 @@ Miru chunks source in tiers: **AST** (tree-sitter, default) → **structural** h
|
|
|
246
237
|
| css | `.css` |
|
|
247
238
|
| dart | `.dart` |
|
|
248
239
|
| elixir | `.ex`, `.exs` |
|
|
249
|
-
| embeddedtemplate |
|
|
240
|
+
| embeddedtemplate | `.erb`, `.ejs` |
|
|
250
241
|
| go | `.go` |
|
|
251
242
|
| haskell | `.hs` |
|
|
252
|
-
| html | `.html` |
|
|
243
|
+
| html | `.html`, `.htm` |
|
|
253
244
|
| java | `.java` |
|
|
254
245
|
| javascript | `.js`, `.jsx`, `.mjs`, `.cjs` |
|
|
255
246
|
| json | `.json` |
|
|
@@ -260,17 +251,20 @@ Miru chunks source in tiers: **AST** (tree-sitter, default) → **structural** h
|
|
|
260
251
|
| rust | `.rs` |
|
|
261
252
|
| scala | `.scala` |
|
|
262
253
|
| solidity | `.sol` |
|
|
254
|
+
| sql | `.sql` |
|
|
255
|
+
| svelte | `.svelte` |
|
|
263
256
|
| typescript | `.ts`, `.tsx`, `.mts`, `.cts` |
|
|
257
|
+
| vue | `.vue` |
|
|
264
258
|
|
|
265
259
|
**Structural fallback** (brace/indent heuristics when AST is unavailable): python, go, typescript, javascript, cpp, c.
|
|
266
260
|
|
|
267
|
-
**Line fallback:** everything else that gets indexed (kotlin, swift,
|
|
261
|
+
**Line fallback:** everything else that gets indexed (kotlin, swift, etc.) — still searchable, coarser chunks.
|
|
268
262
|
|
|
269
263
|
Set `MIRU_AST_CHUNKING=0` to disable AST and use structural → lines only.
|
|
270
264
|
|
|
271
265
|
## CLI reference
|
|
272
266
|
|
|
273
|
-
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.
|
|
274
268
|
|
|
275
269
|
| Command | Purpose |
|
|
276
270
|
| ----------------------------------------- | --------------------------------------------------------------------------------- |
|
|
@@ -284,8 +278,7 @@ Run `miru -h` for the command list, or `miru <command> -h` for details.
|
|
|
284
278
|
| `miru benchmark on/off/status/clear` | Toggle MCP benchmark mode / clear report |
|
|
285
279
|
| `miru init --agent <id>` | Project-local sub-agent |
|
|
286
280
|
| `miru clear [path]` | Drop index cache (use after big CLI-only refactors) |
|
|
287
|
-
| `miru
|
|
288
|
-
| `miru` | Start MCP server (`--benchmark` for comparisons) |
|
|
281
|
+
| `miru mcp` | Start MCP server (`--benchmark` for comparisons) |
|
|
289
282
|
|
|
290
283
|
|
|
291
284
|
CLI uses hyphens (`find-related`); MCP tool names use underscores (`find_related`).
|
|
@@ -367,7 +360,7 @@ Override with `MIRU_BENCHMARK_HISTORY_PATH`. Append-only JSONL of compact token
|
|
|
367
360
|
| Param | Required | Notes |
|
|
368
361
|
| ---------------- | -------- | --------------------------------------- |
|
|
369
362
|
| `query` | yes | Natural language or code query |
|
|
370
|
-
| `repo` |
|
|
363
|
+
| `repo` | no | Startup directory by default; set for another repo |
|
|
371
364
|
| `include` | no | Gitignore-style glob patterns; only matching files are searched (same as `locate.include`) |
|
|
372
365
|
| `exclude` | no | Gitignore-style glob patterns; matching files are skipped (same as `locate.exclude`) |
|
|
373
366
|
| `dedupe_by_file` | no | Keep best hit per file (default `true`) |
|
|
@@ -379,7 +372,7 @@ Override with `MIRU_BENCHMARK_HISTORY_PATH`. Append-only JSONL of compact token
|
|
|
379
372
|
| Param | Required | Notes |
|
|
380
373
|
| ------------- | -------- | ----------------------------------------------------------------------------------- |
|
|
381
374
|
| `literal` | yes | Exact substring to find |
|
|
382
|
-
| `repo` |
|
|
375
|
+
| `repo` | no | Startup directory by default; set for another repo |
|
|
383
376
|
| `include` | no | Gitignore-style glob patterns; only matching files are searched |
|
|
384
377
|
| `exclude` | no | Gitignore-style glob patterns; matching files are skipped |
|
|
385
378
|
| `mode` | no | `count` · `locations` · `lines` (default). Prefer `count`/`locations` when possible |
|
|
@@ -394,7 +387,7 @@ Override with `MIRU_BENCHMARK_HISTORY_PATH`. Append-only JSONL of compact token
|
|
|
394
387
|
| ------------------ | -------- | --------------------------------------------------------------------- |
|
|
395
388
|
| `file_path` | yes | From hit `file_path` or `absolute_path` (local repos) |
|
|
396
389
|
| `anchor_line` | yes | From the search hit (`anchor_line` when truncated, else `start_line`) |
|
|
397
|
-
| `repo` |
|
|
390
|
+
| `repo` | no | Same repo as the search, if set |
|
|
398
391
|
| `before` / `after` | no | Extra chunks before/after anchor (default 1 each) |
|
|
399
392
|
|
|
400
393
|
|
|
@@ -405,7 +398,7 @@ Override with `MIRU_BENCHMARK_HISTORY_PATH`. Append-only JSONL of compact token
|
|
|
405
398
|
| ------------- | -------- | -------------------------------------------- |
|
|
406
399
|
| `file_path` | yes | From a search hit |
|
|
407
400
|
| `anchor_line` | yes | From the search hit |
|
|
408
|
-
| `repo` |
|
|
401
|
+
| `repo` | no | Same repo as the search, if set |
|
|
409
402
|
|
|
410
403
|
|
|
411
404
|
`**read_benchmark**` *(benchmark mode only)*
|
|
@@ -422,16 +415,18 @@ Override with `MIRU_BENCHMARK_HISTORY_PATH`. Append-only JSONL of compact token
|
|
|
422
415
|
{
|
|
423
416
|
"miru": {
|
|
424
417
|
"command": "miru",
|
|
425
|
-
"args": []
|
|
418
|
+
"args": ["mcp"]
|
|
426
419
|
}
|
|
427
420
|
}
|
|
428
421
|
```
|
|
429
422
|
|
|
430
|
-
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.
|
|
431
424
|
|
|
432
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.
|
|
433
426
|
|
|
434
|
-
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.
|
|
435
430
|
|
|
436
431
|
## Developing
|
|
437
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.1",
|
|
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
|
|