@takara-ai/miru-code 0.1.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/LICENSE +21 -0
- package/README.md +176 -0
- package/bin/miru.cjs +41 -0
- package/package.json +72 -0
- package/src/agents/claude.md +41 -0
- package/src/agents/copilot.md +41 -0
- package/src/agents/cursor.md +40 -0
- package/src/agents/gemini.md +43 -0
- package/src/agents/kiro.md +43 -0
- package/src/agents/opencode.md +44 -0
- package/src/agents.ts +30 -0
- package/src/cache.ts +141 -0
- package/src/chunking/chunking.ts +36 -0
- package/src/chunking/lines.ts +79 -0
- package/src/chunking/structural.ts +244 -0
- package/src/cli.ts +245 -0
- package/src/concurrency.ts +58 -0
- package/src/embeddings/openai.ts +442 -0
- package/src/env-files.ts +44 -0
- package/src/env.ts +50 -0
- package/src/git.ts +52 -0
- package/src/index/bm25-types.ts +18 -0
- package/src/index/bm25-worker.ts +48 -0
- package/src/index/bm25.ts +238 -0
- package/src/index/create.ts +195 -0
- package/src/index/dense.ts +105 -0
- package/src/index/file-walker.ts +113 -0
- package/src/index/files.ts +126 -0
- package/src/index/persistence.ts +100 -0
- package/src/index/quantize.ts +168 -0
- package/src/index/semantic-index.ts +11 -0
- package/src/index/sparse.ts +25 -0
- package/src/index/topk.ts +52 -0
- package/src/index/vector-storage.ts +27 -0
- package/src/index.ts +3 -0
- package/src/mcp/index-cache.ts +95 -0
- package/src/mcp/serve.ts +24 -0
- package/src/mcp/server.ts +100 -0
- package/src/miru-index.ts +269 -0
- package/src/ranking/boosting.ts +329 -0
- package/src/ranking/penalties.ts +110 -0
- package/src/ranking/weighting.ts +11 -0
- package/src/search.ts +180 -0
- package/src/tokens.ts +27 -0
- package/src/types.ts +47 -0
- package/src/utils.ts +64 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { type AgentId, writeAgentFile } from "./agents.ts";
|
|
4
|
+
import { clearCache } from "./cache.ts";
|
|
5
|
+
import { loadEnvFiles } from "./env-files.ts";
|
|
6
|
+
import { serveMcp } from "./mcp/serve.ts";
|
|
7
|
+
import { MiruIndex } from "./miru-index.ts";
|
|
8
|
+
import type { ContentType } from "./types.ts";
|
|
9
|
+
import { formatResults, isGitUrl, resolveChunk, resolveContent } from "./utils.ts";
|
|
10
|
+
|
|
11
|
+
loadEnvFiles();
|
|
12
|
+
|
|
13
|
+
const CLI_COMMANDS = new Set(["search", "find-related", "init", "clear", "-h", "--help"]);
|
|
14
|
+
|
|
15
|
+
const AGENTS = new Set<AgentId>(["claude", "copilot", "cursor", "gemini", "kiro", "opencode"]);
|
|
16
|
+
|
|
17
|
+
function parseContentArgv(argv: string[]): { content: ContentType[]; rest: string[] } {
|
|
18
|
+
const rest: string[] = [];
|
|
19
|
+
const content: string[] = [];
|
|
20
|
+
for (let i = 0; i < argv.length; i++) {
|
|
21
|
+
const arg = argv[i];
|
|
22
|
+
if (arg === "--content") {
|
|
23
|
+
i++;
|
|
24
|
+
while (i < argv.length) {
|
|
25
|
+
const value = argv[i];
|
|
26
|
+
if (value === undefined || value.startsWith("-")) {
|
|
27
|
+
i--;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
content.push(value);
|
|
31
|
+
i++;
|
|
32
|
+
}
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (arg !== undefined) {
|
|
36
|
+
rest.push(arg);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { content: resolveContent(content.length > 0 ? content : ["code"]), rest };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseTopK(argv: string[]): { topK: number; rest: string[] } {
|
|
43
|
+
const rest: string[] = [];
|
|
44
|
+
let topK = 5;
|
|
45
|
+
for (let i = 0; i < argv.length; i++) {
|
|
46
|
+
const arg = argv[i];
|
|
47
|
+
if (arg === "-k" || arg === "--top-k") {
|
|
48
|
+
const raw = argv[++i];
|
|
49
|
+
if (raw) {
|
|
50
|
+
topK = Number(raw);
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (arg !== undefined) {
|
|
55
|
+
rest.push(arg);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { topK: Number.isFinite(topK) && topK >= 1 ? Math.floor(topK) : 5, rest };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function runSearch(
|
|
62
|
+
path: string,
|
|
63
|
+
query: string,
|
|
64
|
+
topK: number,
|
|
65
|
+
content: ContentType[],
|
|
66
|
+
): Promise<void> {
|
|
67
|
+
const index = await MiruIndex.fromSource(path, content);
|
|
68
|
+
await index.saveToDefaultCache(path);
|
|
69
|
+
const results = await index.search({ query, topK });
|
|
70
|
+
const out =
|
|
71
|
+
results.length > 0 ? formatResults(query, results) : { error: "No results found." as const };
|
|
72
|
+
console.log(JSON.stringify(out));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function runFindRelated(
|
|
76
|
+
path: string,
|
|
77
|
+
filePath: string,
|
|
78
|
+
line: number,
|
|
79
|
+
topK: number,
|
|
80
|
+
content: ContentType[],
|
|
81
|
+
): Promise<void> {
|
|
82
|
+
const index = await MiruIndex.fromSource(path, content);
|
|
83
|
+
const chunk = resolveChunk(index.chunks, filePath, line);
|
|
84
|
+
if (!chunk) {
|
|
85
|
+
console.error(`No chunk found at ${filePath}:${line}.`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
const results = await index.findRelated(chunk, topK);
|
|
89
|
+
const label = `Chunks related to ${filePath}:${line}`;
|
|
90
|
+
const out =
|
|
91
|
+
results.length > 0
|
|
92
|
+
? formatResults(label, results)
|
|
93
|
+
: { error: `No related chunks found for ${filePath}:${line}.` };
|
|
94
|
+
console.log(JSON.stringify(out));
|
|
95
|
+
await index.saveToDefaultCache(path);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function runInit(agent: AgentId, force: boolean): Promise<void> {
|
|
99
|
+
const dest = await writeAgentFile(agent, { force });
|
|
100
|
+
console.log(`Created ${dest}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function runClear(path: string): Promise<void> {
|
|
104
|
+
await clearCache(path);
|
|
105
|
+
console.log(`Cleared cached index for: ${path}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function printHelp(): void {
|
|
109
|
+
console.log(`Miru (見る) — hybrid code search (Bun + TypeScript + Takara)
|
|
110
|
+
|
|
111
|
+
CLI:
|
|
112
|
+
miru search <query> [path] [-k N] [--content code|docs|config|all]
|
|
113
|
+
miru find-related <file_path> <line> [path] [-k N] [--content ...]
|
|
114
|
+
miru init [--agent cursor] [--force]
|
|
115
|
+
miru clear [path]
|
|
116
|
+
|
|
117
|
+
MCP server (default when no CLI subcommand):
|
|
118
|
+
miru
|
|
119
|
+
miru [path] [--ref BRANCH] [--content code ...]
|
|
120
|
+
|
|
121
|
+
With no path, indexes the process working directory (Cursor sets this to your workspace).
|
|
122
|
+
|
|
123
|
+
Examples:
|
|
124
|
+
miru search "auth middleware" ./src
|
|
125
|
+
miru find-related src/auth.ts 42 .
|
|
126
|
+
|
|
127
|
+
Environment:
|
|
128
|
+
TAKARA_API_KEY / OPENAI_API_KEY / MIRU_OPENAI_API_KEY Takara bearer token
|
|
129
|
+
MIRU_OPENAI_BASE_URL Default: infer.dev.takara.ai
|
|
130
|
+
`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function runCli(argv: string[]): Promise<void> {
|
|
134
|
+
const [command, ...rest] = argv;
|
|
135
|
+
|
|
136
|
+
if (command === "-h" || command === "--help" || command === undefined) {
|
|
137
|
+
printHelp();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (command === "init") {
|
|
142
|
+
let agent: AgentId = "claude";
|
|
143
|
+
let force = false;
|
|
144
|
+
for (let i = 0; i < rest.length; i++) {
|
|
145
|
+
const arg = rest[i];
|
|
146
|
+
if (arg === "--force") {
|
|
147
|
+
force = true;
|
|
148
|
+
} else if ((arg === "--agent" || arg === "-a") && rest[i + 1]) {
|
|
149
|
+
const value = rest[++i] as AgentId;
|
|
150
|
+
if (AGENTS.has(value)) {
|
|
151
|
+
agent = value;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
await runInit(agent, force);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (command === "clear") {
|
|
160
|
+
const path = resolve(rest[0] ?? process.cwd());
|
|
161
|
+
await runClear(path);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const { content, rest: contentRest } = parseContentArgv(rest);
|
|
166
|
+
const { topK, rest: sizedRest } = parseTopK(contentRest);
|
|
167
|
+
|
|
168
|
+
if (command === "search") {
|
|
169
|
+
const query = sizedRest[0];
|
|
170
|
+
if (!query) {
|
|
171
|
+
console.error("Usage: miru search <query> [path] [-k N] [--content ...]");
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
const path = resolve(sizedRest[1] ?? process.cwd());
|
|
175
|
+
await runSearch(path, query, topK, content);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (command === "find-related") {
|
|
180
|
+
const filePath = sizedRest[0];
|
|
181
|
+
const lineRaw = sizedRest[1];
|
|
182
|
+
if (!filePath || !lineRaw) {
|
|
183
|
+
console.error("Usage: miru find-related <file_path> <line> [path] [-k N] [--content ...]");
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
const line = Number(lineRaw);
|
|
187
|
+
const path = resolve(sizedRest[2] ?? process.cwd());
|
|
188
|
+
await runFindRelated(path, filePath, line, topK, content);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
printHelp();
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function runMcp(argv: string[]): Promise<void> {
|
|
197
|
+
let path: string | null = null;
|
|
198
|
+
let ref: string | null = null;
|
|
199
|
+
const contentTokens: string[] = [];
|
|
200
|
+
|
|
201
|
+
for (let i = 0; i < argv.length; i++) {
|
|
202
|
+
const arg = argv[i];
|
|
203
|
+
if (arg === "--ref" && argv[i + 1]) {
|
|
204
|
+
ref = argv[++i] ?? null;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (arg === "--content") {
|
|
208
|
+
i++;
|
|
209
|
+
while (i < argv.length) {
|
|
210
|
+
const value = argv[i];
|
|
211
|
+
if (value === undefined || value.startsWith("-")) {
|
|
212
|
+
i--;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
contentTokens.push(value);
|
|
216
|
+
i++;
|
|
217
|
+
}
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (arg && !arg.startsWith("-") && path === null) {
|
|
221
|
+
path = isGitUrl(arg) ? arg : resolve(arg);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await serveMcp({
|
|
226
|
+
path,
|
|
227
|
+
ref,
|
|
228
|
+
content: resolveContent(contentTokens.length > 0 ? contentTokens : ["code"]),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function main(): Promise<void> {
|
|
233
|
+
const argv = process.argv.slice(2);
|
|
234
|
+
const first = argv[0];
|
|
235
|
+
if (first && CLI_COMMANDS.has(first)) {
|
|
236
|
+
await runCli(argv);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
await runMcp(argv);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
main().catch((err) => {
|
|
243
|
+
console.error(err instanceof Error ? err.message : err);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { availableParallelism, cpus } from "node:os";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_RESERVE_CORES = 2;
|
|
4
|
+
|
|
5
|
+
export function resolveCpuCount(): number {
|
|
6
|
+
if (typeof availableParallelism === "function") {
|
|
7
|
+
return availableParallelism();
|
|
8
|
+
}
|
|
9
|
+
return cpus().length;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Worker pool size: logical CPUs minus 2, minimum 1. Override with MIRU_CONCURRENCY. */
|
|
13
|
+
export function resolveWorkerConcurrency(): number {
|
|
14
|
+
const raw =
|
|
15
|
+
process.env.MIRU_CONCURRENCY ??
|
|
16
|
+
process.env.SEMBLE_CONCURRENCY ??
|
|
17
|
+
process.env.MIRU_WORKERS ??
|
|
18
|
+
process.env.SEMBLE_WORKERS;
|
|
19
|
+
if (raw) {
|
|
20
|
+
const n = Number(raw);
|
|
21
|
+
if (Number.isFinite(n) && n >= 1) {
|
|
22
|
+
return Math.floor(n);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return Math.max(1, resolveCpuCount() - DEFAULT_RESERVE_CORES);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Run async work over items with at most `concurrency` tasks in flight. Results keep input order. */
|
|
29
|
+
export async function mapPool<T, R>(
|
|
30
|
+
items: readonly T[],
|
|
31
|
+
concurrency: number,
|
|
32
|
+
worker: (item: T, index: number) => Promise<R>,
|
|
33
|
+
): Promise<R[]> {
|
|
34
|
+
if (items.length === 0) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const limit = Math.max(1, Math.min(concurrency, items.length));
|
|
39
|
+
const results = new Array<R>(items.length);
|
|
40
|
+
let nextIndex = 0;
|
|
41
|
+
|
|
42
|
+
async function drain(): Promise<void> {
|
|
43
|
+
while (true) {
|
|
44
|
+
const i = nextIndex++;
|
|
45
|
+
if (i >= items.length) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const item = items[i];
|
|
49
|
+
if (item === undefined) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
results[i] = await worker(item, i);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await Promise.all(Array.from({ length: limit }, () => drain()));
|
|
57
|
+
return results;
|
|
58
|
+
}
|