@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.
Files changed (60) hide show
  1. package/README.md +43 -48
  2. package/docs/coverage-badge.json +6 -0
  3. package/grammars/manifest.json +10 -2
  4. package/grammars/tree-sitter-astro.wasm +0 -0
  5. package/grammars/tree-sitter-php.wasm +0 -0
  6. package/grammars/tree-sitter-php_only.wasm +0 -0
  7. package/grammars/tree-sitter-sql.wasm +0 -0
  8. package/grammars/tree-sitter-svelte.wasm +0 -0
  9. package/grammars/tree-sitter-vue.wasm +0 -0
  10. package/package.json +10 -2
  11. package/src/agents.ts +4 -4
  12. package/src/auth/device.ts +26 -21
  13. package/src/auth/providers.ts +26 -11
  14. package/src/benchmark/compare.ts +3 -10
  15. package/src/benchmark/grep.ts +64 -37
  16. package/src/benchmark/locate-compare.ts +8 -1
  17. package/src/benchmark/rg-literal.ts +9 -4
  18. package/src/cache.ts +3 -3
  19. package/src/chunking/ast.ts +21 -3
  20. package/src/chunking/grammars.ts +8 -3
  21. package/src/chunking/structural.ts +13 -24
  22. package/src/cli-mode.ts +18 -0
  23. package/src/cli.ts +10 -45
  24. package/src/credentials.ts +8 -13
  25. package/src/embeddings/openai.ts +6 -3
  26. package/src/embeddings/sagemaker.ts +18 -3
  27. package/src/embeddings/validate.ts +10 -7
  28. package/src/git.ts +6 -2
  29. package/src/help.ts +5 -4
  30. package/src/index/bm25.ts +66 -6
  31. package/src/index/create.ts +5 -9
  32. package/src/index/files.ts +5 -1
  33. package/src/index/incremental.ts +48 -8
  34. package/src/index/sparse.ts +14 -2
  35. package/src/installer/agents.ts +85 -65
  36. package/src/installer/benchmark-mode.ts +40 -11
  37. package/src/installer/config.ts +105 -42
  38. package/src/installer/installer.ts +59 -34
  39. package/src/installer/legacy-hooks.ts +131 -0
  40. package/src/installer/prompt.ts +20 -22
  41. package/src/installer/search-policy.ts +8 -8
  42. package/src/mcp/auth-tool.ts +93 -33
  43. package/src/mcp/index-cache.ts +16 -3
  44. package/src/mcp/server.ts +42 -22
  45. package/src/mcp/stdio.ts +17 -2
  46. package/src/miru-index.ts +54 -18
  47. package/src/prompt.ts +51 -14
  48. package/src/ranking/boosting.ts +5 -6
  49. package/src/ranking/location.ts +6 -10
  50. package/src/ranking/penalties.ts +14 -22
  51. package/src/search.ts +4 -12
  52. package/src/setup.ts +25 -13
  53. package/src/snippet.ts +8 -26
  54. package/src/spinner.ts +26 -16
  55. package/src/tokenizer/bert-wordpiece.ts +0 -6
  56. package/src/utils.ts +0 -4
  57. package/src/version.ts +4 -4
  58. package/src/installer/hooks/install.ts +0 -549
  59. package/src/installer/hooks/opencode-plugin.ts +0 -54
  60. package/src/installer/hooks/search-guard.ts +0 -272
@@ -1,272 +0,0 @@
1
- import { SEARCH_GUARD_EXPAND_HINT } from "../search-policy.ts";
2
-
3
- const REDIRECT_PREFIX =
4
- "Use Miru MCP instead of built-in search tools (pass project root as `repo`). " +
5
- "Prefer Miru `locate` for exact literals and `search` for meaning-based questions.";
6
-
7
- const MIRU_TOOL_RE = /miru/i;
8
-
9
- const GREP_TOOL_RE = /^(grep|grep_search|Grep)$/i;
10
- const GLOB_TOOL_RE = /^(glob|glob_file_search|Glob)$/i;
11
- const SEMANTIC_TOOL_RE = /^(SemanticSearch|codebase_search)$/i;
12
- const SHELL_TOOL_RE = /^(Shell|Bash|execute_bash|shell)$/i;
13
-
14
- /** Returns true when a grep pattern looks like an exact literal lookup. */
15
- export function isLiteralGrepPattern(pattern: string): boolean {
16
- const trimmed = pattern.trim();
17
- if (!trimmed) {
18
- return false;
19
- }
20
- if (/^[`'"][^`'"]+[`'"]$/.test(trimmed)) {
21
- return true;
22
- }
23
- if (/^[A-Z][A-Z0-9_]{2,}$/.test(trimmed)) {
24
- return true;
25
- }
26
- if (/^[a-f0-9-]{36}$/i.test(trimmed)) {
27
- return true;
28
- }
29
- if (/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(trimmed) && trimmed.length <= 48) {
30
- return true;
31
- }
32
- return false;
33
- }
34
-
35
- /** Shell commands that look like codebase exploration rather than builds/tests. */
36
- export function isExplorationShell(command: string): boolean {
37
- const cmd = command.trim();
38
- if (!cmd) {
39
- return false;
40
- }
41
- if (
42
- /\b(git|npm|bun|pnpm|yarn|cargo|go|make|cmake|docker|kubectl|pytest|jest|vitest)\b/.test(cmd)
43
- ) {
44
- return false;
45
- }
46
- return /\b(rg|ripgrep|grep|find|ag|ack|fd|locate)\b/.test(cmd);
47
- }
48
-
49
- export type HookPayload = {
50
- tool_name?: string;
51
- tool_input?: Record<string, unknown>;
52
- hook_event_name?: string;
53
- agent_action_name?: string;
54
- tool_info?: Record<string, unknown>;
55
- };
56
-
57
- export type GuardDecision = {
58
- block: boolean;
59
- reason: string;
60
- };
61
-
62
- export type HookResponseFormat = "claude" | "gemini" | "cursor" | "stderr";
63
-
64
- function grepPatternFromInput(toolInput: Record<string, unknown>): string {
65
- return String(
66
- toolInput.pattern ??
67
- toolInput.query ??
68
- toolInput.regex ??
69
- toolInput.needle ??
70
- toolInput.search_term ??
71
- "",
72
- );
73
- }
74
-
75
- function globPatternFromInput(toolInput: Record<string, unknown>): string {
76
- return String(
77
- toolInput.glob_pattern ?? toolInput.pattern ?? toolInput.glob ?? toolInput.path ?? "",
78
- );
79
- }
80
-
81
- /** MCP tool descriptor paths are not codebase exploration. */
82
- export function isMcpDescriptorGlob(pattern: string): boolean {
83
- const normalized = pattern.replace(/\\/g, "/").toLowerCase();
84
- return (
85
- normalized.includes("/mcps/") ||
86
- normalized.includes("mcps/**/tools") ||
87
- /\/mcps\/[^/]+\/tools/.test(normalized)
88
- );
89
- }
90
-
91
- function suggestedMiruQuery(toolName: string, toolInput: Record<string, unknown>): string {
92
- if (GREP_TOOL_RE.test(toolName) || toolName.toLowerCase() === "grep_search") {
93
- const pattern = grepPatternFromInput(toolInput).trim();
94
- if (pattern) {
95
- return pattern;
96
- }
97
- }
98
- if (GLOB_TOOL_RE.test(toolName)) {
99
- const pattern = globPatternFromInput(toolInput).trim();
100
- if (pattern) {
101
- return `files matching ${pattern}`;
102
- }
103
- }
104
- if (SEMANTIC_TOOL_RE.test(toolName)) {
105
- const query = String(toolInput.query ?? toolInput.search_term ?? "").trim();
106
- if (query) {
107
- return query;
108
- }
109
- }
110
- if (SHELL_TOOL_RE.test(toolName)) {
111
- const command = String(toolInput.command ?? toolInput.cmd ?? "").trim();
112
- if (command) {
113
- return command;
114
- }
115
- }
116
- return "your question about how the code works";
117
- }
118
-
119
- export function searchGuardBlockReason(
120
- toolName: string,
121
- toolInput: Record<string, unknown>,
122
- ): string {
123
- const query = suggestedMiruQuery(toolName, toolInput);
124
- if (GREP_TOOL_RE.test(toolName) && isLiteralGrepPattern(grepPatternFromInput(toolInput))) {
125
- return `${REDIRECT_PREFIX} Try Miru \`locate\` with literal "${query.replace(/^['"`]|['"`]$/g, "")}". ${SEARCH_GUARD_EXPAND_HINT}`;
126
- }
127
- return `${REDIRECT_PREFIX} Try Miru \`search\` with query "${query}". ${SEARCH_GUARD_EXPAND_HINT}`;
128
- }
129
-
130
- /** Map host-specific stdin JSON into a common evaluation shape. */
131
- export function normalizeHookPayload(raw: Record<string, unknown>): HookPayload {
132
- const agentAction = raw.agent_action_name;
133
- if (typeof agentAction === "string") {
134
- const toolInfo =
135
- raw.tool_info && typeof raw.tool_info === "object" && !Array.isArray(raw.tool_info)
136
- ? (raw.tool_info as Record<string, unknown>)
137
- : {};
138
- if (agentAction === "pre_run_command") {
139
- return {
140
- hook_event_name: "windsurf_pre_run_command",
141
- tool_name: "Shell",
142
- tool_input: { command: toolInfo.command_line ?? "" },
143
- };
144
- }
145
- }
146
-
147
- return {
148
- tool_name: typeof raw.tool_name === "string" ? raw.tool_name : undefined,
149
- tool_input:
150
- raw.tool_input && typeof raw.tool_input === "object" && !Array.isArray(raw.tool_input)
151
- ? (raw.tool_input as Record<string, unknown>)
152
- : {},
153
- hook_event_name: typeof raw.hook_event_name === "string" ? raw.hook_event_name : undefined,
154
- agent_action_name:
155
- typeof raw.agent_action_name === "string" ? raw.agent_action_name : undefined,
156
- tool_info:
157
- raw.tool_info && typeof raw.tool_info === "object" && !Array.isArray(raw.tool_info)
158
- ? (raw.tool_info as Record<string, unknown>)
159
- : undefined,
160
- };
161
- }
162
-
163
- export function evaluateSearchGuard(payload: HookPayload): GuardDecision {
164
- const toolName = String(payload.tool_name ?? "");
165
- const toolInput = payload.tool_input ?? {};
166
-
167
- if (!toolName || MIRU_TOOL_RE.test(toolName)) {
168
- return { block: false, reason: "" };
169
- }
170
-
171
- if (GREP_TOOL_RE.test(toolName)) {
172
- return { block: true, reason: searchGuardBlockReason(toolName, toolInput) };
173
- }
174
-
175
- if (GLOB_TOOL_RE.test(toolName)) {
176
- const pattern = globPatternFromInput(toolInput);
177
- if (isMcpDescriptorGlob(pattern)) {
178
- return { block: false, reason: "" };
179
- }
180
- return { block: true, reason: searchGuardBlockReason(toolName, toolInput) };
181
- }
182
-
183
- if (SEMANTIC_TOOL_RE.test(toolName)) {
184
- return { block: true, reason: searchGuardBlockReason(toolName, toolInput) };
185
- }
186
-
187
- if (SHELL_TOOL_RE.test(toolName)) {
188
- const command = String(toolInput.command ?? toolInput.cmd ?? "");
189
- if (isExplorationShell(command)) {
190
- return { block: true, reason: searchGuardBlockReason(toolName, toolInput) };
191
- }
192
- return { block: false, reason: "" };
193
- }
194
-
195
- return { block: false, reason: "" };
196
- }
197
-
198
- export function hookResponseFormat(payload: HookPayload): HookResponseFormat {
199
- const event = payload.hook_event_name ?? "";
200
- if (event === "PreToolUse") {
201
- return "claude";
202
- }
203
- if (event === "BeforeTool") {
204
- return "gemini";
205
- }
206
- if (event === "preToolUse" || event === "windsurf_pre_run_command") {
207
- return "stderr";
208
- }
209
- return "cursor";
210
- }
211
-
212
- export function claudeHookResponse(reason: string): string {
213
- return JSON.stringify({
214
- hookSpecificOutput: {
215
- hookEventName: "PreToolUse",
216
- permissionDecision: "deny",
217
- permissionDecisionReason: reason,
218
- },
219
- });
220
- }
221
-
222
- export function geminiHookResponse(reason: string): string {
223
- return JSON.stringify({
224
- decision: "deny",
225
- reason,
226
- });
227
- }
228
-
229
- export function cursorHookResponse(reason: string): string {
230
- return JSON.stringify({
231
- permission: "deny",
232
- agent_message: reason,
233
- user_message: "Miru: use MCP search/expand instead of built-in grep/glob.",
234
- });
235
- }
236
-
237
- export async function runSearchGuardFromStdin(): Promise<number> {
238
- const text = await Bun.stdin.text();
239
- if (!text.trim()) {
240
- return 0;
241
- }
242
-
243
- let raw: Record<string, unknown>;
244
- try {
245
- raw = JSON.parse(text) as Record<string, unknown>;
246
- } catch {
247
- return 0;
248
- }
249
-
250
- const payload = normalizeHookPayload(raw);
251
- const decision = evaluateSearchGuard(payload);
252
- if (!decision.block) {
253
- return 0;
254
- }
255
-
256
- const format = hookResponseFormat(payload);
257
- if (format === "claude") {
258
- process.stdout.write(claudeHookResponse(decision.reason));
259
- return 0;
260
- }
261
- if (format === "gemini") {
262
- process.stdout.write(geminiHookResponse(decision.reason));
263
- return 0;
264
- }
265
- if (format === "stderr") {
266
- process.stderr.write(decision.reason);
267
- return 2;
268
- }
269
-
270
- process.stdout.write(cursorHookResponse(decision.reason));
271
- return 2;
272
- }