@takara-ai/miru-code 1.8.1 → 1.8.2
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 +1 -1
- package/src/installer/search-policy.ts +35 -39
- package/src/mcp/index-cache.ts +27 -72
- package/src/mcp/server.ts +15 -4
- package/src/types.ts +2 -2
- package/src/version.ts +7 -1
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** Shared code-search policy text for instructions, Cursor rules, and sub-agents. */
|
|
2
2
|
|
|
3
3
|
export const SNIPPET_GUIDANCE =
|
|
4
|
-
"Search returns compact snippets (~±15 lines
|
|
5
|
-
"
|
|
4
|
+
"Search returns compact snippets (~±15 lines). On `truncated: true`, call `expand` with " +
|
|
5
|
+
"`file_path` and `anchor_line` — only if the snippet doesn't already answer the question.";
|
|
6
6
|
|
|
7
7
|
export interface NativeToolNames {
|
|
8
8
|
explorationDenied: string;
|
|
@@ -13,30 +13,36 @@ export interface NativeToolNames {
|
|
|
13
13
|
export function buildSearchPolicyTable(native: NativeToolNames): string {
|
|
14
14
|
return `| Task | Use | Not |
|
|
15
15
|
|------|-----|-----|
|
|
16
|
-
|
|
|
17
|
-
| How/where/what handles X? | Miru MCP \`search\` (once) | ${native.
|
|
16
|
+
| Exact literal (identifier, quoted string, env var, error code) | Miru MCP \`locate\` | Miru \`search\`, ${native.grep} |
|
|
17
|
+
| How/where/what handles X? (conceptual) | Miru MCP \`search\` (once) | ${native.grep}, ${native.explorationDenied} |
|
|
18
18
|
| Same file, more context | Miru MCP \`expand\` on \`truncated: true\` | Re-search, ${native.read} whole file |
|
|
19
19
|
| Similar code elsewhere | Miru MCP \`find_related\` | ${native.grep} chains |
|
|
20
20
|
| Search docs or config | Miru \`search\` | ${native.grep} README paths |
|
|
21
|
-
| Exact literal string in a file? | Miru MCP \`locate\` | ${native.grep}, Miru \`search\` |
|
|
22
21
|
| Edit a known file:line | ${native.read} (after Miru found it) | ${native.read}-before-search |`;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
export function buildSearchPolicyBody(native: NativeToolNames): string {
|
|
26
25
|
return `DO NOT use ${native.explorationDenied} to explore how code works when Miru MCP is available.
|
|
27
26
|
|
|
27
|
+
**Check first:** exact token (identifier, quoted string, env var, error code) → \`locate\`. Otherwise → \`search\`.
|
|
28
|
+
|
|
28
29
|
${SNIPPET_GUIDANCE}
|
|
29
30
|
|
|
30
31
|
Use Miru MCP tools:
|
|
31
|
-
- \`search\` — one call per question; pass project root as \`repo\`
|
|
32
32
|
- \`locate\` — exact substring (env var, symbol, error code); prefer \`mode=count\` or \`locations\`
|
|
33
|
+
- \`search\` — one call per conceptual question; pass project root as \`repo\`
|
|
33
34
|
- \`expand\` — more context in the same file when \`truncated: true\` (\`file_path\` + \`anchor_line\`)
|
|
34
35
|
- \`find_related\` — similar code in other files (hits may also be snippets; use \`expand\` if truncated)
|
|
35
36
|
|
|
37
|
+
Call shapes:
|
|
38
|
+
- \`locate(literal="<exact substring>", repo="<project root>")\` — prefer \`mode="locations"\`/\`"count"\`
|
|
39
|
+
- \`search(query="<natural language>", repo="<project root>")\`
|
|
40
|
+
|
|
36
41
|
Stop rules:
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
42
|
+
- Literal in the request → \`locate\`, not \`search\`
|
|
43
|
+
- One \`search\` per question — no paraphrase retries
|
|
44
|
+
- \`expand\` only if the snippet doesn't already answer
|
|
45
|
+
- ${native.read} only to edit a path Miru already found, not to explore
|
|
40
46
|
|
|
41
47
|
Native tools are allowed ONLY when:
|
|
42
48
|
- reading a file you already located via Miru, to edit it
|
|
@@ -50,7 +56,7 @@ export function buildMcpWorkflow(native: NativeToolNames): string {
|
|
|
50
56
|
|
|
51
57
|
1. Call \`search\` with \`repo\` set to the project root (local path or https:// git URL).
|
|
52
58
|
2. For exact literals, call \`locate\` (prefer \`mode=count\` or \`locations\`).
|
|
53
|
-
3.
|
|
59
|
+
3. On \`truncated: true\`, call \`expand\` — only if the snippet doesn't already answer.
|
|
54
60
|
4. Use \`find_related\` to trace similar code in other files — not for more context in the same file.
|
|
55
61
|
5. ${native.read} via \`absolute_path\` only when editing or when \`expand\` still lacks context.`;
|
|
56
62
|
}
|
|
@@ -64,6 +70,7 @@ miru expand src/auth.ts 42 .
|
|
|
64
70
|
miru find-related src/auth.ts 42 .
|
|
65
71
|
\`\`\`
|
|
66
72
|
|
|
73
|
+
After large refactors or CLI-only use, run \`miru clear <path>\` to rebuild the disk cache.
|
|
67
74
|
If \`miru\` is not on \`$PATH\`, use \`bunx @takara-ai/miru-code\`.`;
|
|
68
75
|
|
|
69
76
|
export function buildSubagentBody(native: NativeToolNames): string {
|
|
@@ -90,16 +97,7 @@ ${SEARCH_POLICY_BODY}
|
|
|
90
97
|
|
|
91
98
|
Run \`miru setup\` once — the MCP server loads credentials from \`credentials.json\`, and interactive first use can auto-start device login when nothing is stored.
|
|
92
99
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
\`\`\`bash
|
|
96
|
-
miru search "authentication flow" .
|
|
97
|
-
miru expand src/auth.ts 42 .
|
|
98
|
-
miru find-related src/auth.ts 42 .
|
|
99
|
-
\`\`\`
|
|
100
|
-
|
|
101
|
-
After large refactors or CLI-only use, run \`miru clear <path>\` to rebuild the disk cache.
|
|
102
|
-
If \`miru\` is not on \`$PATH\`, use \`bunx @takara-ai/miru-code\`.`;
|
|
100
|
+
${CLI_FALLBACK}`;
|
|
103
101
|
|
|
104
102
|
export const CURSOR_RULES_MDC = `---
|
|
105
103
|
description: Miru MCP is the default for all code exploration
|
|
@@ -110,20 +108,19 @@ alwaysApply: true
|
|
|
110
108
|
|
|
111
109
|
${SEARCH_POLICY_BODY}
|
|
112
110
|
|
|
113
|
-
When Miru MCP is connected
|
|
111
|
+
When Miru MCP is connected: literal in the request → \`locate\`, not Grep or \`search\`. Otherwise \`search\` once. \`expand\` only if the snippet doesn't already answer. Never use Cursor SemanticSearch for codebase questions.`;
|
|
114
112
|
|
|
115
113
|
export const SUBAGENT_BODY = buildSubagentBody(DEFAULT_NATIVE_TOOLS);
|
|
116
114
|
|
|
117
115
|
export const MCP_SERVER_INSTRUCTIONS =
|
|
118
116
|
"Miru `search` is your default for all code search queries in indexed repos — the best, fastest, and cheapest way to find code; better than any other tool. " +
|
|
119
117
|
"DO NOT use Grep, grep_search, codebase_search, Glob, SemanticSearch, or Read to explore code when this server is available. " +
|
|
118
|
+
"Exact token (identifier, quoted string, env var, error code) → `locate`; everything else → `search` once, no paraphrase retries. " +
|
|
120
119
|
`${SNIPPET_GUIDANCE} ` +
|
|
121
|
-
"
|
|
122
|
-
"
|
|
123
|
-
"(3) `find_related` traces similar code in other files, not more context in the same file. " +
|
|
124
|
-
"(4) `locate` finds exact substrings (env vars, symbols, error codes) — prefer over Grep; use mode=count or locations when possible. " +
|
|
125
|
-
"Always pass the project root as `repo`. Local repos return `absolute_path` on each hit — use Read only to edit. " +
|
|
120
|
+
"`find_related` traces similar code elsewhere, not more context in the same file. " +
|
|
121
|
+
"Always pass the project root as `repo`; local repos return `absolute_path` — use Read only to edit. " +
|
|
126
122
|
"Native Grep/Glob only outside the indexed repo or for non-code tasks. " +
|
|
123
|
+
"Default indexed scope is code, config, and docs; an empty result can mean out-of-scope, not nonexistent. " +
|
|
127
124
|
"On credential errors, call this server's `auth` tool — not the host `mcp_auth` tool.";
|
|
128
125
|
|
|
129
126
|
export const MCP_BENCHMARK_SERVER_INSTRUCTIONS =
|
|
@@ -137,25 +134,25 @@ export const MCP_BENCHMARK_SERVER_INSTRUCTIONS =
|
|
|
137
134
|
|
|
138
135
|
export const MCP_SEARCH_TOOL_DESCRIPTION =
|
|
139
136
|
"Your default search for all code search queries in this indexed repo — the best, fastest, and cheapest way to find code; better than any other tool. " +
|
|
140
|
-
"Returns compact snippets (~±15 lines). One call per question
|
|
141
|
-
"
|
|
142
|
-
"
|
|
137
|
+
"Returns compact snippets (~±15 lines). One call per question. " +
|
|
138
|
+
"Exact literal (env var, symbol, error code, quoted text)? Use `locate` instead. " +
|
|
139
|
+
"On `truncated: true`, call `expand` — only if the snippet doesn't already answer the question.";
|
|
143
140
|
|
|
144
141
|
export const MCP_LOCATE_TOOL_DESCRIPTION =
|
|
145
|
-
"Exact substring locator over the Miru index. Use for known literals (env vars, symbols, error codes, quoted text) — not meaning-based questions (`search`)
|
|
142
|
+
"Exact substring locator over the Miru index. Use for known literals (env vars, symbols, error codes, quoted text) — not meaning-based questions (`search`), " +
|
|
143
|
+
'even ones phrased as "where is X" or "what handles X" if X is a literal. ' +
|
|
146
144
|
"Returns ALL matches by default as compact {n,files,hits} — do NOT fall back to Grep/rg when n is large. " +
|
|
147
145
|
"Prefer mode=locations (or count for totals only); use lines when you need matching line text. " +
|
|
148
146
|
"Optional `limit` only if you intentionally want a sample. " +
|
|
149
|
-
"Pass an array to `literal`
|
|
150
|
-
'
|
|
151
|
-
"
|
|
152
|
-
"
|
|
147
|
+
"Pass an array to `literal` for several spellings in one call. " +
|
|
148
|
+
'`match_variants: true` also matches other casings (e.g. "rateLimit" also finds "rate_limit"). ' +
|
|
149
|
+
"`include`/`exclude` (gitignore-style globs) scope to part of a monorepo; " +
|
|
150
|
+
"`context_lines` in `lines` mode gets inline context instead of a follow-up `expand`. " +
|
|
153
151
|
"In benchmark mode, includes compact token savings vs agent Grep.";
|
|
154
152
|
|
|
155
153
|
export const MCP_EXPAND_TOOL_DESCRIPTION =
|
|
156
|
-
"More context in the SAME file as a search hit. Pass `file_path` + `anchor_line` from the hit
|
|
157
|
-
"
|
|
158
|
-
"Prefer this over re-searching or reading the whole file.";
|
|
154
|
+
"More context in the SAME file as a search hit. Pass `file_path` + `anchor_line` from the hit. " +
|
|
155
|
+
"Use only if `truncated: true` and the snippet doesn't already answer the question — not for similar code elsewhere (use find_related).";
|
|
159
156
|
|
|
160
157
|
export const MCP_FIND_RELATED_TOOL_DESCRIPTION =
|
|
161
158
|
"Find code similar to a file:line in OTHER parts of the codebase. Results may be snippets; use `expand` when `truncated: true`. " +
|
|
@@ -165,5 +162,4 @@ export const MCP_READ_BENCHMARK_TOOL_DESCRIPTION =
|
|
|
165
162
|
"Cumulative Miru vs Grep token savings from saved `search` and `locate` calls. Returns compact totals {n,saved,save_pct,miru,grep}. " +
|
|
166
163
|
"Do not call unless the user asks about savings.";
|
|
167
164
|
|
|
168
|
-
export const SEARCH_GUARD_EXPAND_HINT =
|
|
169
|
-
"If a hit has `truncated: true`, call `expand` with `file_path` and `anchor_line` — do not re-search or read the whole file.";
|
|
165
|
+
export const SEARCH_GUARD_EXPAND_HINT = SNIPPET_GUIDANCE;
|
package/src/mcp/index-cache.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { watch } from "node:fs";
|
|
2
2
|
import { relative, resolve } from "node:path";
|
|
3
|
+
import { isCredentialsError } from "../auth/errors.ts";
|
|
3
4
|
import { walkFiles } from "../index/file-walker.ts";
|
|
4
5
|
import { getExtensions } from "../index/files.ts";
|
|
5
6
|
import { normalizeRelativePath, relativePathFromRoot } from "../index/incremental.ts";
|
|
@@ -13,7 +14,6 @@ import {
|
|
|
13
14
|
} from "../utils.ts";
|
|
14
15
|
|
|
15
16
|
const CACHE_MAX_SIZE = 10;
|
|
16
|
-
const BUN_WATCH_RECONCILE_INTERVAL_MS = 500;
|
|
17
17
|
|
|
18
18
|
/** Directory names we skip for MCP fs.watch update triggers (aligned with file-walker). */
|
|
19
19
|
const WATCH_IGNORED_DIR_NAMES = new Set([
|
|
@@ -45,6 +45,8 @@ type CacheEntry = {
|
|
|
45
45
|
pendingPaths: Set<string>;
|
|
46
46
|
flushQueued: boolean;
|
|
47
47
|
updateChain: Promise<void>;
|
|
48
|
+
/** Dead-credentials failure from a background re-embed; `get()` throws it until a retry clears it. */
|
|
49
|
+
lastError: Error | null;
|
|
48
50
|
};
|
|
49
51
|
|
|
50
52
|
export function mcpWatchEnabled(): boolean {
|
|
@@ -98,6 +100,7 @@ export class IndexCache {
|
|
|
98
100
|
pendingPaths: new Set(),
|
|
99
101
|
flushQueued: false,
|
|
100
102
|
updateChain: Promise.resolve(),
|
|
103
|
+
lastError: null,
|
|
101
104
|
};
|
|
102
105
|
this.entries.set(cacheKey, entry);
|
|
103
106
|
}
|
|
@@ -171,6 +174,9 @@ export class IndexCache {
|
|
|
171
174
|
throw new Error(`Failed to load index for ${source}`);
|
|
172
175
|
}
|
|
173
176
|
await entry.updateChain;
|
|
177
|
+
if (entry.lastError) {
|
|
178
|
+
throw entry.lastError;
|
|
179
|
+
}
|
|
174
180
|
return index;
|
|
175
181
|
}
|
|
176
182
|
|
|
@@ -183,39 +189,11 @@ export class IndexCache {
|
|
|
183
189
|
}
|
|
184
190
|
}
|
|
185
191
|
|
|
186
|
-
private queueIndexedPaths(source: string, index: MiruIndex): void {
|
|
187
|
-
const cacheKey = computeSourceCacheKey(source);
|
|
188
|
-
const entry = this.ensureEntry(cacheKey, source);
|
|
189
|
-
for (const chunk of index.chunks) {
|
|
190
|
-
entry.pendingPaths.add(normalizeRelativePath(chunk.file_path));
|
|
191
|
-
}
|
|
192
|
-
this.scheduleFlush(cacheKey, source, entry);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** macOS recursive fs.watch often omits filename; refresh all indexed paths incrementally. */
|
|
196
|
-
private noteAmbiguousDirectoryChange(source: string): void {
|
|
197
|
-
const cacheKey = computeSourceCacheKey(source);
|
|
198
|
-
const entry = this.entries.get(cacheKey);
|
|
199
|
-
if (!entry) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (entry.index) {
|
|
204
|
-
this.queueIndexedPaths(source, entry.index);
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (entry.task) {
|
|
209
|
-
void entry.task.then((index) => {
|
|
210
|
-
entry.index = index;
|
|
211
|
-
this.queueIndexedPaths(source, index);
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
192
|
private noteFileChange(source: string, filename: string | null | undefined): void {
|
|
217
193
|
if (!filename) {
|
|
218
|
-
|
|
194
|
+
// macOS recursive fs.watch could omit the filename on old Bun releases (pre-1.3.14
|
|
195
|
+
// fs.watch rewrite); confirmed fixed on current Bun. No reconcile fallback for
|
|
196
|
+
// this case anymore -- an event with no filename is simply dropped.
|
|
219
197
|
return;
|
|
220
198
|
}
|
|
221
199
|
if (shouldIgnoreWatchPath(filename)) {
|
|
@@ -253,34 +231,38 @@ export class IndexCache {
|
|
|
253
231
|
return;
|
|
254
232
|
}
|
|
255
233
|
|
|
234
|
+
const requeue = (err?: unknown): void => {
|
|
235
|
+
if (err !== undefined && isCredentialsError(err)) {
|
|
236
|
+
entry.lastError = err instanceof Error ? err : new Error(String(err));
|
|
237
|
+
}
|
|
238
|
+
for (const p of paths) {
|
|
239
|
+
entry.pendingPaths.add(p);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
256
243
|
// Prefer an index already in hand (or published on the entry). Never await
|
|
257
244
|
// entry.task while that task is itself waiting on this flush — that deadlocks.
|
|
258
245
|
let index = knownIndex ?? entry.index;
|
|
259
246
|
if (!index && entry.task) {
|
|
260
247
|
try {
|
|
261
248
|
index = await entry.task;
|
|
262
|
-
} catch {
|
|
263
|
-
|
|
264
|
-
entry.pendingPaths.add(p);
|
|
265
|
-
}
|
|
249
|
+
} catch (err) {
|
|
250
|
+
requeue(err);
|
|
266
251
|
return;
|
|
267
252
|
}
|
|
268
253
|
}
|
|
269
254
|
if (!index) {
|
|
270
|
-
|
|
271
|
-
entry.pendingPaths.add(p);
|
|
272
|
-
}
|
|
255
|
+
requeue();
|
|
273
256
|
return;
|
|
274
257
|
}
|
|
275
258
|
|
|
276
259
|
try {
|
|
277
260
|
await index.applyFileChanges(paths);
|
|
278
|
-
} catch {
|
|
279
|
-
|
|
280
|
-
entry.pendingPaths.add(p);
|
|
281
|
-
}
|
|
261
|
+
} catch (err) {
|
|
262
|
+
requeue(err);
|
|
282
263
|
return;
|
|
283
264
|
}
|
|
265
|
+
entry.lastError = null;
|
|
284
266
|
if (!isGitUrl(source)) {
|
|
285
267
|
try {
|
|
286
268
|
await index.saveToCache(resolve(source), { force: true });
|
|
@@ -364,40 +346,13 @@ export class IndexCache {
|
|
|
364
346
|
this.noteFileChange(path, filename);
|
|
365
347
|
});
|
|
366
348
|
} catch {
|
|
367
|
-
// Recursive fs.watch is unavailable on some platforms
|
|
368
|
-
//
|
|
349
|
+
// Recursive fs.watch is unavailable on some platforms (no fallback here
|
|
350
|
+
// anymore -- such a local index will only refresh on next cold load).
|
|
369
351
|
}
|
|
370
352
|
|
|
371
|
-
// Bun 1.3 on macOS can create a recursive watcher without delivering file
|
|
372
|
-
// events. Reconcile periodically in Bun so a silent watcher cannot leave an
|
|
373
|
-
// MCP index stale. Native watchers remain the fast path everywhere else.
|
|
374
|
-
let reconcileInFlight = false;
|
|
375
|
-
const reconcile = (): void => {
|
|
376
|
-
if (reconcileInFlight) {
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
const cacheKey = computeSourceCacheKey(path);
|
|
380
|
-
const entry = this.entries.get(cacheKey);
|
|
381
|
-
if (!entry?.index) {
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
reconcileInFlight = true;
|
|
385
|
-
void this.checkAndQueueStaleFiles(path, entry.index, cacheKey).finally(() => {
|
|
386
|
-
reconcileInFlight = false;
|
|
387
|
-
});
|
|
388
|
-
};
|
|
389
|
-
const interval =
|
|
390
|
-
process.versions.bun || !nativeWatcher
|
|
391
|
-
? setInterval(reconcile, BUN_WATCH_RECONCILE_INTERVAL_MS)
|
|
392
|
-
: null;
|
|
393
|
-
interval?.unref();
|
|
394
|
-
|
|
395
353
|
this.watchers.set(resolved, {
|
|
396
354
|
close: () => {
|
|
397
355
|
nativeWatcher?.close();
|
|
398
|
-
if (interval) {
|
|
399
|
-
clearInterval(interval);
|
|
400
|
-
}
|
|
401
356
|
},
|
|
402
357
|
});
|
|
403
358
|
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -226,10 +226,17 @@ export function createMcpServer(
|
|
|
226
226
|
inputSchema: {
|
|
227
227
|
literal: z
|
|
228
228
|
.union([z.string().min(1), z.array(z.string().min(1)).min(1)])
|
|
229
|
+
.optional()
|
|
229
230
|
.describe(
|
|
230
231
|
"Exact substring to find (env var, symbol, error code, quoted text). " +
|
|
231
232
|
"Pass an array to OR-match several substrings (e.g. spelling/casing variants) in one call.",
|
|
232
233
|
),
|
|
234
|
+
query: z
|
|
235
|
+
.union([z.string().min(1), z.array(z.string().min(1)).min(1)])
|
|
236
|
+
.optional()
|
|
237
|
+
.describe(
|
|
238
|
+
"Alias for `literal` — accepted so a call shaped like `search(query=...)` still resolves as an exact-substring lookup.",
|
|
239
|
+
),
|
|
233
240
|
repo: z.string().describe(REPO_DESCRIPTION),
|
|
234
241
|
mode: z
|
|
235
242
|
.enum(["count", "locations", "lines"])
|
|
@@ -270,13 +277,17 @@ export function createMcpServer(
|
|
|
270
277
|
.describe("Lines of context before/after each match, like `grep -C` (mode=lines only)."),
|
|
271
278
|
},
|
|
272
279
|
},
|
|
273
|
-
async ({ literal, repo, ...locateOpts }) => {
|
|
280
|
+
async ({ literal, query, repo, ...locateOpts }) => {
|
|
281
|
+
const lit = literal ?? query;
|
|
282
|
+
if (lit == null) {
|
|
283
|
+
return toolErrorText(new Error("locate requires `literal` (exact substring to find)."));
|
|
284
|
+
}
|
|
274
285
|
try {
|
|
275
286
|
const index = await getIndexForRepo(repo, cache);
|
|
276
287
|
|
|
277
288
|
let skip: BenchmarkSkipReason | undefined;
|
|
278
289
|
const repoRoot = localRepoRoot(repo);
|
|
279
|
-
if (benchmark && repoRoot && typeof
|
|
290
|
+
if (benchmark && repoRoot && typeof lit === "string") {
|
|
280
291
|
if (locateOpts.limit != null) {
|
|
281
292
|
skip = "limited_locate";
|
|
282
293
|
} else if (!selectComparableLiteralSearchTool()) {
|
|
@@ -285,7 +296,7 @@ export function createMcpServer(
|
|
|
285
296
|
if (!skip) {
|
|
286
297
|
const comparison = await withGrepTimeoutFallback(() =>
|
|
287
298
|
benchmarkLocateComparison({
|
|
288
|
-
literal,
|
|
299
|
+
literal: lit,
|
|
289
300
|
repoPath: repoRoot,
|
|
290
301
|
index,
|
|
291
302
|
locate: locateOpts,
|
|
@@ -302,7 +313,7 @@ export function createMcpServer(
|
|
|
302
313
|
skip = "local_repo_only";
|
|
303
314
|
}
|
|
304
315
|
|
|
305
|
-
const result = index.locateLiteral(
|
|
316
|
+
const result = index.locateLiteral(lit, locateOpts);
|
|
306
317
|
const payload = formatLiteralLocate(result);
|
|
307
318
|
const body = formatLiteralLocateText(payload);
|
|
308
319
|
return toolText(skip ? withBenchmarkSkippedNote(body, skip) : body);
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type ContentType = "code" | "docs" | "config";
|
|
2
2
|
|
|
3
|
-
/** Default index corpus: source code
|
|
4
|
-
export const DEFAULT_CONTENT_TYPES: readonly ContentType[] = ["code", "config"];
|
|
3
|
+
/** Default index corpus: source code, config (json/yaml/toml/…), and docs (md/rst/txt). */
|
|
4
|
+
export const DEFAULT_CONTENT_TYPES: readonly ContentType[] = ["code", "config", "docs"];
|
|
5
5
|
|
|
6
6
|
/** Mutable copy of {@link DEFAULT_CONTENT_TYPES} for API defaults. */
|
|
7
7
|
export function defaultContentTypes(): ContentType[] {
|
package/src/version.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import { join } from "node:path";
|
|
2
3
|
import packageJson from "../package.json";
|
|
3
4
|
import { resolveCacheFolder } from "./cache.ts";
|
|
@@ -79,7 +80,12 @@ function updateCheckPath(): string {
|
|
|
79
80
|
|
|
80
81
|
async function readUpdateCheckCache(): Promise<UpdateCheckCache | null> {
|
|
81
82
|
try {
|
|
82
|
-
|
|
83
|
+
// node:fs/promises, not Bun.file().text(): a rejecting Bun.file() read
|
|
84
|
+
// (the common case here — no cache on a fresh install) doesn't keep
|
|
85
|
+
// Bun's Windows event loop alive, so the process can exit silently
|
|
86
|
+
// before this ever settles. Confirmed regression in Bun 1.4.0-1.4.2:
|
|
87
|
+
// github.com/oven-sh/bun/issues/39787 (fix open, not yet released).
|
|
88
|
+
const raw = await readFile(updateCheckPath(), "utf8");
|
|
83
89
|
const parsed = JSON.parse(raw) as UpdateCheckCache;
|
|
84
90
|
if (typeof parsed.checkedAt !== "number" || typeof parsed.latest !== "string") {
|
|
85
91
|
return null;
|