@takara-ai/miru-code 0.1.4 → 0.2.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 +6 -4
- package/package.json +1 -2
- package/src/cli.ts +3 -10
- package/src/mcp/index-cache.ts +27 -11
- package/src/mcp/serve.ts +3 -13
- package/src/mcp/server.ts +10 -10
package/README.md
CHANGED
|
@@ -93,14 +93,14 @@ Run `miru init --agent <id> --force` to overwrite an existing sub-agent file.
|
|
|
93
93
|
|
|
94
94
|
### MCP server
|
|
95
95
|
|
|
96
|
-
Run with **no arguments** to start the stdio MCP server.
|
|
96
|
+
Run with **no arguments** to start the stdio MCP server. Indexing begins on the first `search` or `find_related` call, using the `repo` path you pass. Optional `--ref` sets the default git ref for remote repos.
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
99
|
bunx @takara-ai/miru-code
|
|
100
100
|
# or after bun add -g @takara-ai/miru-code: miru
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
**Tools:** `search`, `find_related` —
|
|
103
|
+
**Tools:** `search`, `find_related` — `repo` is required (project root or https:// git URL).
|
|
104
104
|
|
|
105
105
|
For MCP configs, use `bunx` as the command (`bun` with `x` in `args` works too — they're equivalent).
|
|
106
106
|
|
|
@@ -352,12 +352,14 @@ bun run typecheck
|
|
|
352
352
|
|
|
353
353
|
Releases use **Bun** for install, test, and `bun pm pack`. The registry upload uses **`npm publish`** on that tarball because [Bun does not yet support npm OIDC trusted publishing](https://github.com/oven-sh/bun/issues/22423) — no long-lived npm tokens in GitHub secrets.
|
|
354
354
|
|
|
355
|
-
**Local
|
|
355
|
+
**Local publish** (logged in to npm; `publishConfig.access` is already `public`):
|
|
356
356
|
|
|
357
357
|
```bash
|
|
358
|
-
bun publish
|
|
358
|
+
bun publish
|
|
359
359
|
```
|
|
360
360
|
|
|
361
|
+
Runs `prepublishOnly` (typecheck + tests), then a single registry upload. Do not add a `publish` script that calls `npm publish` — Bun runs lifecycle scripts after its own upload and would publish twice.
|
|
362
|
+
|
|
361
363
|
**CI** (after Trusted Publisher is configured on npm):
|
|
362
364
|
|
|
363
365
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takara-ai/miru-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Miru (見る) — hybrid code search for agents. Bun, TypeScript, Takara embeddings.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
"lint": "biome check .",
|
|
51
51
|
"lint:fix": "biome check --write .",
|
|
52
52
|
"prepublishOnly": "bun run typecheck && bun test",
|
|
53
|
-
"publish": "npm publish --access public",
|
|
54
53
|
"benchmark:quality": "bun run scripts/benchmark-quality.ts",
|
|
55
54
|
"benchmark:quantization": "bun run scripts/benchmark-quantization.ts"
|
|
56
55
|
},
|
package/src/cli.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { serveMcp } from "./mcp/serve.ts";
|
|
|
8
8
|
import { MiruIndex } from "./miru-index.ts";
|
|
9
9
|
import { ensureCredentials, runClearCredentials, runSetup } from "./setup.ts";
|
|
10
10
|
import type { ContentType } from "./types.ts";
|
|
11
|
-
import { formatResults,
|
|
11
|
+
import { formatResults, resolveChunk, resolveContent } from "./utils.ts";
|
|
12
12
|
|
|
13
13
|
loadEnvFiles();
|
|
14
14
|
await loadStoredCredentials();
|
|
@@ -121,10 +121,9 @@ CLI:
|
|
|
121
121
|
miru clear [path]
|
|
122
122
|
|
|
123
123
|
MCP server (default when no CLI subcommand):
|
|
124
|
-
miru
|
|
125
|
-
miru [path] [--ref BRANCH] [--content code ...]
|
|
124
|
+
miru [--ref BRANCH] [--content code ...]
|
|
126
125
|
|
|
127
|
-
|
|
126
|
+
Indexes a repo on the first search/find_related call via the tool repo argument.
|
|
128
127
|
|
|
129
128
|
Examples:
|
|
130
129
|
miru search "auth middleware" ./src
|
|
@@ -226,7 +225,6 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
226
225
|
}
|
|
227
226
|
|
|
228
227
|
async function runMcp(argv: string[]): Promise<void> {
|
|
229
|
-
let path: string | null = null;
|
|
230
228
|
let ref: string | null = null;
|
|
231
229
|
const contentTokens: string[] = [];
|
|
232
230
|
|
|
@@ -247,15 +245,10 @@ async function runMcp(argv: string[]): Promise<void> {
|
|
|
247
245
|
contentTokens.push(value);
|
|
248
246
|
i++;
|
|
249
247
|
}
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
if (arg && !arg.startsWith("-") && path === null) {
|
|
253
|
-
path = isGitUrl(arg) ? arg : resolve(arg);
|
|
254
248
|
}
|
|
255
249
|
}
|
|
256
250
|
|
|
257
251
|
await serveMcp({
|
|
258
|
-
path,
|
|
259
252
|
ref,
|
|
260
253
|
content: resolveContent(contentTokens.length > 0 ? contentTokens : ["code"]),
|
|
261
254
|
});
|
package/src/mcp/index-cache.ts
CHANGED
|
@@ -57,11 +57,14 @@ export function shouldIgnoreWatchPath(relativePath: string | null | undefined):
|
|
|
57
57
|
|
|
58
58
|
export class IndexCache {
|
|
59
59
|
private readonly content: ContentType[];
|
|
60
|
+
private readonly defaultRef: string | null;
|
|
60
61
|
private readonly entries = new Map<string, CacheEntry>();
|
|
61
62
|
private watcher: ReturnType<typeof watch> | null = null;
|
|
63
|
+
private watchedSource: string | null = null;
|
|
62
64
|
|
|
63
|
-
constructor(content: ContentType[] = ["code"]) {
|
|
65
|
+
constructor(content: ContentType[] = ["code"], defaultRef: string | null = null) {
|
|
64
66
|
this.content = content;
|
|
67
|
+
this.defaultRef = defaultRef;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
private ensureEntry(cacheKey: string): CacheEntry {
|
|
@@ -107,6 +110,7 @@ export class IndexCache {
|
|
|
107
110
|
void task
|
|
108
111
|
.then((index) => {
|
|
109
112
|
entry.index = index;
|
|
113
|
+
this.maybeStartWatcher(source);
|
|
110
114
|
void this.flushFileUpdates(cacheKey, source);
|
|
111
115
|
return index;
|
|
112
116
|
})
|
|
@@ -120,11 +124,12 @@ export class IndexCache {
|
|
|
120
124
|
}
|
|
121
125
|
|
|
122
126
|
async get(source: string, ref?: string | null): Promise<MiruIndex> {
|
|
123
|
-
const
|
|
127
|
+
const resolvedRef = ref ?? this.defaultRef;
|
|
128
|
+
const cacheKey = computeSourceCacheKey(source, resolvedRef);
|
|
124
129
|
const entry = this.ensureEntry(cacheKey);
|
|
125
130
|
|
|
126
131
|
if (!entry.task) {
|
|
127
|
-
this.startBuild(source,
|
|
132
|
+
this.startBuild(source, resolvedRef, cacheKey);
|
|
128
133
|
}
|
|
129
134
|
|
|
130
135
|
const index = await entry.task;
|
|
@@ -237,6 +242,18 @@ export class IndexCache {
|
|
|
237
242
|
entry.updateChain = entry.updateChain.then(run, run);
|
|
238
243
|
}
|
|
239
244
|
|
|
245
|
+
private maybeStartWatcher(source: string): void {
|
|
246
|
+
if (!mcpWatchEnabled() || isGitUrl(source)) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const resolved = resolve(source);
|
|
250
|
+
if (this.watchedSource === resolved) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
this.watchedSource = resolved;
|
|
254
|
+
this.startWatcher(resolved);
|
|
255
|
+
}
|
|
256
|
+
|
|
240
257
|
startWatcher(path: string): void {
|
|
241
258
|
const resolved = resolve(path);
|
|
242
259
|
if (this.watcher) {
|
|
@@ -251,34 +268,33 @@ export class IndexCache {
|
|
|
251
268
|
close(): void {
|
|
252
269
|
this.watcher?.close();
|
|
253
270
|
this.watcher = null;
|
|
271
|
+
this.watchedSource = null;
|
|
254
272
|
this.entries.clear();
|
|
255
273
|
}
|
|
256
274
|
}
|
|
257
275
|
|
|
258
276
|
export async function getIndexForRepo(
|
|
259
277
|
repo: string | null | undefined,
|
|
260
|
-
defaultSource: string | null,
|
|
261
278
|
cache: IndexCache,
|
|
262
279
|
ref?: string | null,
|
|
263
280
|
): Promise<MiruIndex> {
|
|
264
|
-
if (
|
|
281
|
+
if (!repo) {
|
|
265
282
|
throw new Error(
|
|
266
|
-
|
|
283
|
+
"Pass an https:// or http:// git URL or local directory path as `repo` (project root for local workspaces).",
|
|
267
284
|
);
|
|
268
285
|
}
|
|
269
286
|
|
|
270
|
-
|
|
271
|
-
if (!source) {
|
|
287
|
+
if (isGitUrl(repo) && !repo.startsWith("https://") && !repo.startsWith("http://")) {
|
|
272
288
|
throw new Error(
|
|
273
|
-
|
|
289
|
+
`Only https://, http://, or local directory paths are accepted as \`repo\`. Got: ${repo}`,
|
|
274
290
|
);
|
|
275
291
|
}
|
|
276
292
|
|
|
277
293
|
try {
|
|
278
|
-
return await cache.get(
|
|
294
|
+
return await cache.get(repo, ref);
|
|
279
295
|
} catch (err) {
|
|
280
296
|
const message = err instanceof Error ? err.message : String(err);
|
|
281
|
-
throw new Error(`Failed to index ${
|
|
297
|
+
throw new Error(`Failed to index ${repo}: ${message}`);
|
|
282
298
|
}
|
|
283
299
|
}
|
|
284
300
|
|
package/src/mcp/serve.ts
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
2
1
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
2
|
import type { ContentType } from "../types.ts";
|
|
4
|
-
import {
|
|
5
|
-
import { IndexCache, mcpWatchEnabled } from "./index-cache.ts";
|
|
3
|
+
import { IndexCache } from "./index-cache.ts";
|
|
6
4
|
import { createMcpServer } from "./server.ts";
|
|
7
5
|
|
|
8
6
|
export async function serveMcp(options: {
|
|
9
|
-
path?: string | null;
|
|
10
7
|
ref?: string | null;
|
|
11
8
|
content?: ContentType[];
|
|
12
9
|
}): Promise<void> {
|
|
13
|
-
const cache = new IndexCache(options.content ?? ["code"]);
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
void cache.get(defaultSource, options.ref).catch(() => undefined);
|
|
17
|
-
if (!isGitUrl(defaultSource) && mcpWatchEnabled()) {
|
|
18
|
-
cache.startWatcher(defaultSource);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const server = createMcpServer(cache, defaultSource);
|
|
10
|
+
const cache = new IndexCache(options.content ?? ["code"], options.ref ?? null);
|
|
11
|
+
const server = createMcpServer(cache);
|
|
22
12
|
const transport = new StdioServerTransport();
|
|
23
13
|
await server.connect(transport);
|
|
24
14
|
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -6,17 +6,17 @@ import { getIndexForRepo, type IndexCache, toolText } from "./index-cache.ts";
|
|
|
6
6
|
|
|
7
7
|
const REPO_DESCRIPTION =
|
|
8
8
|
"https:// or http:// git URL (e.g. https://github.com/org/repo) or local directory path to index and search. " +
|
|
9
|
-
"
|
|
10
|
-
"The index is
|
|
9
|
+
"Pass the project root for local workspaces. " +
|
|
10
|
+
"The index is built on the first tool call and cached for the session.";
|
|
11
11
|
|
|
12
12
|
const SERVER_INSTRUCTIONS =
|
|
13
13
|
"Instant code search for any local or remote git repository. " +
|
|
14
14
|
"Call `search` to find relevant code; call `find_related` on a result to discover similar code elsewhere. " +
|
|
15
|
-
"
|
|
16
|
-
"
|
|
15
|
+
"Always pass the project root as `repo` on the first call (local path or https:// git URL). " +
|
|
16
|
+
"Never guess or infer URLs. " +
|
|
17
17
|
"Prefer these tools over Grep, Glob, or Read for any question about how code works.";
|
|
18
18
|
|
|
19
|
-
export function createMcpServer(cache: IndexCache
|
|
19
|
+
export function createMcpServer(cache: IndexCache): McpServer {
|
|
20
20
|
const server = new McpServer(
|
|
21
21
|
{
|
|
22
22
|
name: "miru",
|
|
@@ -32,16 +32,16 @@ export function createMcpServer(cache: IndexCache, defaultSource: string | null
|
|
|
32
32
|
{
|
|
33
33
|
description:
|
|
34
34
|
"Search a codebase with a natural-language or code query. " +
|
|
35
|
-
"
|
|
35
|
+
"Indexes `repo` on the first call; later calls reuse the session cache.",
|
|
36
36
|
inputSchema: {
|
|
37
37
|
query: z.string().describe("Natural language or code query."),
|
|
38
|
-
repo: z.string().
|
|
38
|
+
repo: z.string().describe(REPO_DESCRIPTION),
|
|
39
39
|
top_k: z.number().int().min(1).optional().describe("Number of results to return."),
|
|
40
40
|
},
|
|
41
41
|
},
|
|
42
42
|
async ({ query, repo, top_k: topK }) => {
|
|
43
43
|
try {
|
|
44
|
-
const index = await getIndexForRepo(repo
|
|
44
|
+
const index = await getIndexForRepo(repo, cache);
|
|
45
45
|
const results = await index.search({ query, topK: topK ?? 5 });
|
|
46
46
|
if (results.length === 0) {
|
|
47
47
|
return toolText(JSON.stringify({ error: "No results found." }));
|
|
@@ -66,13 +66,13 @@ export function createMcpServer(cache: IndexCache, defaultSource: string | null
|
|
|
66
66
|
"Path to the file as stored in the index (use file_path from a search result).",
|
|
67
67
|
),
|
|
68
68
|
line: z.number().int().describe("Line number (1-indexed)."),
|
|
69
|
-
repo: z.string().
|
|
69
|
+
repo: z.string().describe(REPO_DESCRIPTION),
|
|
70
70
|
top_k: z.number().int().min(1).optional().describe("Number of similar chunks to return."),
|
|
71
71
|
},
|
|
72
72
|
},
|
|
73
73
|
async ({ file_path: filePath, line, repo, top_k: topK }) => {
|
|
74
74
|
try {
|
|
75
|
-
const index = await getIndexForRepo(repo
|
|
75
|
+
const index = await getIndexForRepo(repo, cache);
|
|
76
76
|
const chunk = resolveChunk(index.chunks, filePath, line);
|
|
77
77
|
if (!chunk) {
|
|
78
78
|
return toolText(
|