@takara-ai/miru-code 1.8.2 → 1.10.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 +65 -53
- 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 +54 -98
- package/src/credentials.ts +14 -14
- 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 +7 -7
- package/src/index/bm25.ts +66 -6
- package/src/index/chunk-selector.ts +39 -11
- 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 +10 -10
- package/src/mcp/index-cache.ts +16 -3
- package/src/mcp/server.ts +65 -46
- package/src/mcp/stdio.ts +17 -2
- package/src/miru-index.ts +90 -22
- 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 +8 -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 +1 -15
- 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/src/cli.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { type AgentId, writeAgentFile } from "./agents.ts";
|
|
3
3
|
import { clearBenchmarkHistory, resolveBenchmarkHistoryPath } from "./benchmark/history.ts";
|
|
4
4
|
import { clearCache } from "./cache.ts";
|
|
5
|
+
import { shouldRunMcp } from "./cli-mode.ts";
|
|
5
6
|
import {
|
|
6
7
|
fail,
|
|
7
8
|
formatRelatedHeader,
|
|
@@ -26,7 +27,6 @@ import {
|
|
|
26
27
|
printSageMakerHelp,
|
|
27
28
|
} from "./help.ts";
|
|
28
29
|
import { getBenchmarkModeStatus, setBenchmarkMode } from "./installer/benchmark-mode.ts";
|
|
29
|
-
import { runSearchGuardFromStdin } from "./installer/hooks/search-guard.ts";
|
|
30
30
|
import { runInstaller } from "./installer/installer.ts";
|
|
31
31
|
import { promptConfirm } from "./installer/prompt.ts";
|
|
32
32
|
import {
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
resolveChunk,
|
|
57
57
|
resolveContent,
|
|
58
58
|
resolveSearchPath,
|
|
59
|
+
SEARCH_RESULT_COUNT,
|
|
59
60
|
} from "./utils.ts";
|
|
60
61
|
import { maybeNotifyUpdate, miruVersion } from "./version.ts";
|
|
61
62
|
|
|
@@ -71,27 +72,6 @@ try {
|
|
|
71
72
|
// Leave recovery to ensureCredentials / setup.
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
const CLI_COMMANDS = new Set([
|
|
75
|
-
"search",
|
|
76
|
-
"locate",
|
|
77
|
-
"expand",
|
|
78
|
-
"find-related",
|
|
79
|
-
"init",
|
|
80
|
-
"install",
|
|
81
|
-
"uninstall",
|
|
82
|
-
"setup",
|
|
83
|
-
"clear",
|
|
84
|
-
"benchmark",
|
|
85
|
-
"env",
|
|
86
|
-
"environment",
|
|
87
|
-
"hook-guard",
|
|
88
|
-
"help",
|
|
89
|
-
"-h",
|
|
90
|
-
"--help",
|
|
91
|
-
"-v",
|
|
92
|
-
"--version",
|
|
93
|
-
]);
|
|
94
|
-
|
|
95
75
|
const AGENTS = new Set<AgentId>(AGENT_IDS);
|
|
96
76
|
|
|
97
77
|
function parseFlagArgv(argv: string[], flag: string): { present: boolean; rest: string[] } {
|
|
@@ -132,25 +112,6 @@ function parseContentArgv(argv: string[]): { content: ContentType[]; rest: strin
|
|
|
132
112
|
return { content: resolveContent(content), rest };
|
|
133
113
|
}
|
|
134
114
|
|
|
135
|
-
function parseTopK(argv: string[]): { topK: number; rest: string[] } {
|
|
136
|
-
const rest: string[] = [];
|
|
137
|
-
let topK = 5;
|
|
138
|
-
for (let i = 0; i < argv.length; i++) {
|
|
139
|
-
const arg = argv[i];
|
|
140
|
-
if (arg === "-k" || arg === "--top-k") {
|
|
141
|
-
const raw = argv[++i];
|
|
142
|
-
if (raw) {
|
|
143
|
-
topK = Number(raw);
|
|
144
|
-
}
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
if (arg !== undefined) {
|
|
148
|
-
rest.push(arg);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return { topK: Number.isFinite(topK) && topK >= 1 ? Math.floor(topK) : 5, rest };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
115
|
function emitSearchOutput(
|
|
155
116
|
query: string,
|
|
156
117
|
results: SearchResult[],
|
|
@@ -177,7 +138,6 @@ function emitSearchOutput(
|
|
|
177
138
|
async function runSearch(
|
|
178
139
|
path: string,
|
|
179
140
|
query: string,
|
|
180
|
-
topK: number,
|
|
181
141
|
content: ContentType[],
|
|
182
142
|
jsonFlag: boolean,
|
|
183
143
|
): Promise<void> {
|
|
@@ -186,7 +146,7 @@ async function runSearch(
|
|
|
186
146
|
const index = await withSpinner("Indexing and searching", async () => {
|
|
187
147
|
const built = await MiruIndex.fromSource(path, content);
|
|
188
148
|
await built.saveToCache(path);
|
|
189
|
-
const results = await built.search({ query, topK });
|
|
149
|
+
const results = await built.search({ query, topK: SEARCH_RESULT_COUNT });
|
|
190
150
|
return { index: built, results };
|
|
191
151
|
});
|
|
192
152
|
|
|
@@ -245,7 +205,6 @@ async function runFindRelated(
|
|
|
245
205
|
path: string,
|
|
246
206
|
filePath: string,
|
|
247
207
|
line: number,
|
|
248
|
-
topK: number,
|
|
249
208
|
content: ContentType[],
|
|
250
209
|
jsonFlag: boolean,
|
|
251
210
|
): Promise<void> {
|
|
@@ -257,7 +216,7 @@ async function runFindRelated(
|
|
|
257
216
|
if (!chunk) {
|
|
258
217
|
throw new RelatedChunkNotFoundError(filePath, line);
|
|
259
218
|
}
|
|
260
|
-
const hits = await built.findRelated(chunk,
|
|
219
|
+
const hits = await built.findRelated(chunk, SEARCH_RESULT_COUNT);
|
|
261
220
|
await built.saveToCache(path);
|
|
262
221
|
return {
|
|
263
222
|
results: hits,
|
|
@@ -412,21 +371,11 @@ async function runBenchmarkCommand(rest: string[]): Promise<void> {
|
|
|
412
371
|
async function runCli(argv: string[]): Promise<void> {
|
|
413
372
|
const [command, ...rest] = argv;
|
|
414
373
|
|
|
415
|
-
if (command === undefined) {
|
|
374
|
+
if (command === undefined || command === "-h" || command === "--help") {
|
|
416
375
|
printMainHelp();
|
|
417
376
|
return;
|
|
418
377
|
}
|
|
419
378
|
|
|
420
|
-
if (command === "-h" || command === "--help") {
|
|
421
|
-
printMainHelp();
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (command === "-v" || command === "--version") {
|
|
426
|
-
console.log(miruVersion());
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
379
|
if (command === "help") {
|
|
431
380
|
const topic = rest[0];
|
|
432
381
|
if (!topic || topic === "-h" || topic === "--help") {
|
|
@@ -456,10 +405,6 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
456
405
|
return;
|
|
457
406
|
}
|
|
458
407
|
|
|
459
|
-
if (command === "hook-guard") {
|
|
460
|
-
process.exit(await runSearchGuardFromStdin());
|
|
461
|
-
}
|
|
462
|
-
|
|
463
408
|
if (command === "install" || command === "uninstall") {
|
|
464
409
|
await runInstaller(command);
|
|
465
410
|
return;
|
|
@@ -552,21 +497,28 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
552
497
|
|
|
553
498
|
const { present: jsonFlag, rest: jsonRest } = parseFlagArgv(rest, "--json");
|
|
554
499
|
const { content, rest: contentRest } = parseContentArgv(jsonRest);
|
|
555
|
-
|
|
500
|
+
|
|
501
|
+
if (
|
|
502
|
+
(command === "search" || command === "find-related") &&
|
|
503
|
+
contentRest.some((arg) => arg === "-k" || arg === "--top-k")
|
|
504
|
+
) {
|
|
505
|
+
fail("The -k/--top-k option has been removed; Miru returns a fixed number of results.");
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
556
508
|
|
|
557
509
|
if (command === "search") {
|
|
558
|
-
const query =
|
|
510
|
+
const query = contentRest[0];
|
|
559
511
|
if (!query) {
|
|
560
512
|
printCommandHelp("search");
|
|
561
513
|
process.exit(1);
|
|
562
514
|
}
|
|
563
|
-
const path = resolveSearchPath(
|
|
564
|
-
await runSearch(path, query,
|
|
515
|
+
const path = resolveSearchPath(contentRest[1] ?? process.cwd());
|
|
516
|
+
await runSearch(path, query, content, jsonFlag);
|
|
565
517
|
return;
|
|
566
518
|
}
|
|
567
519
|
|
|
568
520
|
if (command === "locate") {
|
|
569
|
-
const literal =
|
|
521
|
+
const literal = contentRest[0];
|
|
570
522
|
if (!literal) {
|
|
571
523
|
printCommandHelp("locate");
|
|
572
524
|
process.exit(1);
|
|
@@ -585,10 +537,10 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
585
537
|
}
|
|
586
538
|
return Math.floor(n);
|
|
587
539
|
};
|
|
588
|
-
for (let i = 1; i <
|
|
589
|
-
const arg =
|
|
590
|
-
if (arg === "--mode" &&
|
|
591
|
-
const value =
|
|
540
|
+
for (let i = 1; i < contentRest.length; i++) {
|
|
541
|
+
const arg = contentRest[i];
|
|
542
|
+
if (arg === "--mode" && contentRest[i + 1]) {
|
|
543
|
+
const value = contentRest[++i];
|
|
592
544
|
if (value !== "count" && value !== "locations" && value !== "lines") {
|
|
593
545
|
fail(`Unknown locate mode "${value}". Use count, locations, or lines.`);
|
|
594
546
|
process.exit(1);
|
|
@@ -596,8 +548,8 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
596
548
|
options.mode = value as LiteralMode;
|
|
597
549
|
continue;
|
|
598
550
|
}
|
|
599
|
-
if (arg === "--limit" &&
|
|
600
|
-
options.limit = positiveInt("--limit",
|
|
551
|
+
if (arg === "--limit" && contentRest[i + 1]) {
|
|
552
|
+
options.limit = positiveInt("--limit", contentRest[++i], 1);
|
|
601
553
|
continue;
|
|
602
554
|
}
|
|
603
555
|
if (arg === "--ignore-case") {
|
|
@@ -608,16 +560,16 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
608
560
|
options.match_variants = true;
|
|
609
561
|
continue;
|
|
610
562
|
}
|
|
611
|
-
if (arg === "--include" &&
|
|
612
|
-
include.push(
|
|
563
|
+
if (arg === "--include" && contentRest[i + 1]) {
|
|
564
|
+
include.push(contentRest[++i] as string);
|
|
613
565
|
continue;
|
|
614
566
|
}
|
|
615
|
-
if (arg === "--exclude" &&
|
|
616
|
-
exclude.push(
|
|
567
|
+
if (arg === "--exclude" && contentRest[i + 1]) {
|
|
568
|
+
exclude.push(contentRest[++i] as string);
|
|
617
569
|
continue;
|
|
618
570
|
}
|
|
619
|
-
if (arg === "--context" &&
|
|
620
|
-
options.context_lines = positiveInt("--context",
|
|
571
|
+
if (arg === "--context" && contentRest[i + 1]) {
|
|
572
|
+
options.context_lines = positiveInt("--context", contentRest[++i], 0);
|
|
621
573
|
continue;
|
|
622
574
|
}
|
|
623
575
|
if (arg !== undefined) {
|
|
@@ -636,22 +588,22 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
636
588
|
}
|
|
637
589
|
|
|
638
590
|
if (command === "expand") {
|
|
639
|
-
const filePath =
|
|
640
|
-
const lineRaw =
|
|
591
|
+
const filePath = contentRest[0];
|
|
592
|
+
const lineRaw = contentRest[1];
|
|
641
593
|
if (!filePath || !lineRaw) {
|
|
642
594
|
printCommandHelp("expand");
|
|
643
595
|
process.exit(1);
|
|
644
596
|
}
|
|
645
597
|
const line = Number(lineRaw);
|
|
646
|
-
const path = resolveSearchPath(
|
|
598
|
+
const path = resolveSearchPath(contentRest[2] ?? process.cwd());
|
|
647
599
|
let before = DEFAULT_EXPAND_BEFORE;
|
|
648
600
|
let after = DEFAULT_EXPAND_AFTER;
|
|
649
|
-
for (let i = 3; i <
|
|
650
|
-
const arg =
|
|
651
|
-
if (arg === "--before" &&
|
|
652
|
-
before = Number(
|
|
653
|
-
} else if (arg === "--after" &&
|
|
654
|
-
after = Number(
|
|
601
|
+
for (let i = 3; i < contentRest.length; i++) {
|
|
602
|
+
const arg = contentRest[i];
|
|
603
|
+
if (arg === "--before" && contentRest[i + 1]) {
|
|
604
|
+
before = Number(contentRest[++i]);
|
|
605
|
+
} else if (arg === "--after" && contentRest[i + 1]) {
|
|
606
|
+
after = Number(contentRest[++i]);
|
|
655
607
|
}
|
|
656
608
|
}
|
|
657
609
|
await runExpand(path, filePath, line, before, after, content, jsonFlag);
|
|
@@ -659,15 +611,15 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
659
611
|
}
|
|
660
612
|
|
|
661
613
|
if (command === "find-related") {
|
|
662
|
-
const filePath =
|
|
663
|
-
const lineRaw =
|
|
614
|
+
const filePath = contentRest[0];
|
|
615
|
+
const lineRaw = contentRest[1];
|
|
664
616
|
if (!filePath || !lineRaw) {
|
|
665
617
|
printCommandHelp("find-related");
|
|
666
618
|
process.exit(1);
|
|
667
619
|
}
|
|
668
620
|
const line = Number(lineRaw);
|
|
669
|
-
const path = resolveSearchPath(
|
|
670
|
-
await runFindRelated(path, filePath, line,
|
|
621
|
+
const path = resolveSearchPath(contentRest[2] ?? process.cwd());
|
|
622
|
+
await runFindRelated(path, filePath, line, content, jsonFlag);
|
|
671
623
|
return;
|
|
672
624
|
}
|
|
673
625
|
|
|
@@ -687,6 +639,10 @@ async function runMcp(argv: string[]): Promise<void> {
|
|
|
687
639
|
benchmark = true;
|
|
688
640
|
continue;
|
|
689
641
|
}
|
|
642
|
+
if (arg === "--benchmark=true" || arg === "--benchmark=false") {
|
|
643
|
+
benchmark = arg === "--benchmark=true";
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
690
646
|
if (arg === "--ref" && argv[i + 1]) {
|
|
691
647
|
ref = argv[++i] ?? null;
|
|
692
648
|
continue;
|
|
@@ -734,24 +690,24 @@ async function main(): Promise<void> {
|
|
|
734
690
|
const argv = process.argv.slice(2);
|
|
735
691
|
const first = argv[0];
|
|
736
692
|
|
|
693
|
+
// Old hook configs may survive until the next install/uninstall; let their
|
|
694
|
+
// search commands proceed normally while the installer removes the hooks.
|
|
695
|
+
if (first === "hook-guard") return;
|
|
696
|
+
|
|
737
697
|
if (first === "-v" || first === "--version") {
|
|
738
698
|
console.log(miruVersion());
|
|
739
699
|
return;
|
|
740
700
|
}
|
|
741
701
|
|
|
742
|
-
if (first === "hook-guard") {
|
|
743
|
-
process.exit(await runSearchGuardFromStdin());
|
|
744
|
-
}
|
|
745
|
-
|
|
746
702
|
const updateNotice = maybeNotifyUpdate();
|
|
747
703
|
|
|
748
|
-
if (
|
|
749
|
-
await
|
|
704
|
+
if (shouldRunMcp(argv, Boolean(process.stdin.isTTY), Boolean(process.stdout.isTTY))) {
|
|
705
|
+
await updateNotice;
|
|
706
|
+
await runMcpWithCredentials(first === "mcp" ? argv.slice(1) : argv);
|
|
750
707
|
return;
|
|
751
708
|
}
|
|
752
709
|
|
|
753
|
-
await updateNotice;
|
|
754
|
-
await runMcpWithCredentials(argv);
|
|
710
|
+
await Promise.all([runCli(argv), updateNotice]);
|
|
755
711
|
}
|
|
756
712
|
|
|
757
713
|
main().catch((err) => {
|
package/src/credentials.ts
CHANGED
|
@@ -20,17 +20,22 @@ export function setStoredCredentialsEnvToken(token: string): void {
|
|
|
20
20
|
process.env.TAKARA_API_KEY = token;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/** True for an unresolved `${...}` placeholder — a plugin host that didn't substitute it. */
|
|
24
|
+
function isUnsubstitutedPlaceholder(value: string): boolean {
|
|
25
|
+
return value.startsWith("${") && value.endsWith("}");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function resolveCredentialsDir(platform: NodeJS.Platform = process.platform): string {
|
|
24
29
|
const override = process.env.MIRU_CREDENTIALS_DIR;
|
|
25
|
-
if (override) {
|
|
30
|
+
if (override && !isUnsubstitutedPlaceholder(override)) {
|
|
26
31
|
return override;
|
|
27
32
|
}
|
|
28
33
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
29
|
-
if (
|
|
34
|
+
if (platform === "win32") {
|
|
30
35
|
const base = process.env.APPDATA ?? join(home, "AppData", "Roaming");
|
|
31
36
|
return join(base, "miru");
|
|
32
37
|
}
|
|
33
|
-
if (
|
|
38
|
+
if (platform === "darwin") {
|
|
34
39
|
return join(home, "Library", "Application Support", "miru");
|
|
35
40
|
}
|
|
36
41
|
const xdg = process.env.XDG_CONFIG_HOME ?? join(home, ".config");
|
|
@@ -41,12 +46,12 @@ export function resolveCredentialsDir(): string {
|
|
|
41
46
|
* Global Miru state directory (credentials, benchmark history, etc.).
|
|
42
47
|
* Same location as `resolveCredentialsDir()`; prefer this name for non-secret files.
|
|
43
48
|
*/
|
|
44
|
-
export function resolveMiruStateDir(): string {
|
|
45
|
-
return resolveCredentialsDir();
|
|
49
|
+
export function resolveMiruStateDir(platform: NodeJS.Platform = process.platform): string {
|
|
50
|
+
return resolveCredentialsDir(platform);
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
export function resolveCredentialsPath(): string {
|
|
49
|
-
return join(resolveCredentialsDir(), CREDENTIALS_FILENAME);
|
|
53
|
+
export function resolveCredentialsPath(platform: NodeJS.Platform = process.platform): string {
|
|
54
|
+
return join(resolveCredentialsDir(platform), CREDENTIALS_FILENAME);
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
export async function readStoredCredentials(): Promise<StoredCredentials | null> {
|
|
@@ -262,12 +267,7 @@ export async function clearStoredCredentials(): Promise<{ cleared: boolean; path
|
|
|
262
267
|
}
|
|
263
268
|
|
|
264
269
|
// Read for env cleanup, but never let parse errors block deletion.
|
|
265
|
-
|
|
266
|
-
try {
|
|
267
|
-
stored = await readStoredCredentials();
|
|
268
|
-
} catch {
|
|
269
|
-
// Ignore parse/read errors — removal still proceeds.
|
|
270
|
-
}
|
|
270
|
+
const stored = await readStoredCredentials();
|
|
271
271
|
await rm(path, { force: true });
|
|
272
272
|
|
|
273
273
|
if (stored) {
|
package/src/embeddings/openai.ts
CHANGED
|
@@ -353,7 +353,9 @@ export class EmbeddingApiError extends Error {
|
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
|
|
356
|
+
type EmbeddingFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
357
|
+
|
|
358
|
+
function createClient(fetchImpl: EmbeddingFetch = fetch): EmbeddingClient {
|
|
357
359
|
const baseUrl = resolveEmbeddingBaseUrl();
|
|
358
360
|
const endpoint = `${baseUrl}/embeddings`;
|
|
359
361
|
|
|
@@ -369,7 +371,7 @@ function createClient(): EmbeddingClient {
|
|
|
369
371
|
if (dimensions != null) {
|
|
370
372
|
body.dimensions = dimensions;
|
|
371
373
|
}
|
|
372
|
-
const response = await
|
|
374
|
+
const response = await fetchImpl(endpoint, {
|
|
373
375
|
method: "POST",
|
|
374
376
|
headers: {
|
|
375
377
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -407,9 +409,10 @@ export class OpenAIEmbeddingBackend implements EmbeddingBackend {
|
|
|
407
409
|
maxEmbedChars?: number;
|
|
408
410
|
dimensions?: number;
|
|
409
411
|
client?: EmbeddingClient;
|
|
412
|
+
fetchImpl?: EmbeddingFetch;
|
|
410
413
|
}) {
|
|
411
414
|
this.model = options?.model ?? resolveEmbeddingModel();
|
|
412
|
-
this.client = options?.client ?? createClient();
|
|
415
|
+
this.client = options?.client ?? createClient(options?.fetchImpl);
|
|
413
416
|
this.batchSize = options?.batchSize ?? resolveEmbeddingBatchSize();
|
|
414
417
|
this.maxEmbedChars = options?.maxEmbedChars ?? resolveMaxEmbedChars();
|
|
415
418
|
this.requestedDimensions = options?.dimensions ?? resolveEmbeddingDimensions(this.model);
|
|
@@ -26,6 +26,11 @@ function loadAwsCredentialProvider(): Promise<AwsCredentialProviderModule> {
|
|
|
26
26
|
return awsCredentialProviderPromise;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/** Warm the lazily loaded AWS SDK modules before the first SageMaker request. */
|
|
30
|
+
export async function preloadSageMakerSdk(): Promise<void> {
|
|
31
|
+
await Promise.all([loadSageMakerRuntime(), loadAwsCredentialProvider()]);
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
/** arn:aws:sagemaker:<region>:<account-id>:endpoint/<endpoint-name> (also covers aws-cn/aws-us-gov). */
|
|
30
35
|
const ENDPOINT_ARN_PATTERN = /^arn:aws[a-z0-9-]*:sagemaker:([a-z0-9-]+):(\d{12}):endpoint\/(.+)$/;
|
|
31
36
|
|
|
@@ -344,7 +349,13 @@ function buildRequestBody(
|
|
|
344
349
|
return body;
|
|
345
350
|
}
|
|
346
351
|
|
|
347
|
-
export function createSageMakerClient(
|
|
352
|
+
export function createSageMakerClient(
|
|
353
|
+
config: SageMakerEmbeddingConfig,
|
|
354
|
+
loaders: {
|
|
355
|
+
loadRuntime?: () => Promise<SageMakerRuntimeModule>;
|
|
356
|
+
loadCredentials?: () => Promise<AwsCredentialProviderModule>;
|
|
357
|
+
} = {},
|
|
358
|
+
): EmbeddingClient {
|
|
348
359
|
let client: InstanceType<SageMakerRuntimeModule["SageMakerRuntimeClient"]> | null = null;
|
|
349
360
|
|
|
350
361
|
return {
|
|
@@ -354,7 +365,10 @@ export function createSageMakerClient(config: SageMakerEmbeddingConfig): Embeddi
|
|
|
354
365
|
dimensions?: number,
|
|
355
366
|
): Promise<EmbeddingResponse> {
|
|
356
367
|
const [{ InvokeEndpointCommand, SageMakerRuntimeClient }, { defaultProvider }] =
|
|
357
|
-
await Promise.all([
|
|
368
|
+
await Promise.all([
|
|
369
|
+
(loaders.loadRuntime ?? loadSageMakerRuntime)(),
|
|
370
|
+
(loaders.loadCredentials ?? loadAwsCredentialProvider)(),
|
|
371
|
+
]);
|
|
358
372
|
if (!client) {
|
|
359
373
|
const profile = config.profile?.trim() || process.env.AWS_PROFILE?.trim();
|
|
360
374
|
const sdkCredentials = defaultProvider({ profile });
|
|
@@ -414,9 +428,10 @@ export interface ValidateSageMakerResult {
|
|
|
414
428
|
/** Real InvokeEndpoint round trip, used by `miru setup --sagemaker` to confirm auth + shape. */
|
|
415
429
|
export async function validateSageMakerConnection(
|
|
416
430
|
config: SageMakerEmbeddingConfig,
|
|
431
|
+
createClient: typeof createSageMakerClient = createSageMakerClient,
|
|
417
432
|
): Promise<ValidateSageMakerResult> {
|
|
418
433
|
try {
|
|
419
|
-
const client =
|
|
434
|
+
const client = createClient(config);
|
|
420
435
|
const response = await client.createEmbeddings("miru setup validation", "sagemaker-setup");
|
|
421
436
|
const embedding = response.data[0]?.embedding;
|
|
422
437
|
if (!embedding) {
|
|
@@ -12,12 +12,15 @@ export interface ValidateApiKeyResult {
|
|
|
12
12
|
message: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export async function validateEmbeddingApiKey(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
export async function validateEmbeddingApiKey(
|
|
16
|
+
options: {
|
|
17
|
+
apiKey: string;
|
|
18
|
+
baseUrl?: string;
|
|
19
|
+
model?: string;
|
|
20
|
+
dimensions?: number;
|
|
21
|
+
},
|
|
22
|
+
fetchImpl: (input: string | URL | Request, init?: RequestInit) => Promise<Response> = fetch,
|
|
23
|
+
): Promise<ValidateApiKeyResult> {
|
|
21
24
|
const baseUrl = (options.baseUrl ?? resolveEmbeddingBaseUrl()).replace(/\/$/, "");
|
|
22
25
|
const model = options.model ?? resolveTakaraEmbeddingModel();
|
|
23
26
|
const dimensions = options.dimensions ?? resolveEmbeddingDimensions(model);
|
|
@@ -33,7 +36,7 @@ export async function validateEmbeddingApiKey(options: {
|
|
|
33
36
|
|
|
34
37
|
let response: Response;
|
|
35
38
|
try {
|
|
36
|
-
response = await
|
|
39
|
+
response = await fetchImpl(endpoint, {
|
|
37
40
|
method: "POST",
|
|
38
41
|
headers: {
|
|
39
42
|
Authorization: `Bearer ${options.apiKey}`,
|
package/src/git.ts
CHANGED
|
@@ -5,7 +5,11 @@ import { envOptionalInt } from "./env.ts";
|
|
|
5
5
|
|
|
6
6
|
const DEFAULT_CLONE_TIMEOUT_SEC = 60;
|
|
7
7
|
|
|
8
|
-
export async function cloneGitRepository(
|
|
8
|
+
export async function cloneGitRepository(
|
|
9
|
+
url: string,
|
|
10
|
+
ref?: string | null,
|
|
11
|
+
spawn: typeof Bun.spawn = Bun.spawn,
|
|
12
|
+
): Promise<string> {
|
|
9
13
|
const timeoutSec = envOptionalInt(["MIRU_CLONE_TIMEOUT"], 1) ?? DEFAULT_CLONE_TIMEOUT_SEC;
|
|
10
14
|
const dir = await mkdtemp(join(tmpdir(), "miru-git-"));
|
|
11
15
|
const args = ["clone", "--depth", "1"];
|
|
@@ -16,7 +20,7 @@ export async function cloneGitRepository(url: string, ref?: string | null): Prom
|
|
|
16
20
|
|
|
17
21
|
let proc: ReturnType<typeof Bun.spawn>;
|
|
18
22
|
try {
|
|
19
|
-
proc =
|
|
23
|
+
proc = spawn(["git", ...args], {
|
|
20
24
|
stdout: "pipe",
|
|
21
25
|
stderr: "pipe",
|
|
22
26
|
stdin: "ignore",
|
package/src/help.ts
CHANGED
|
@@ -28,7 +28,7 @@ export function printMainHelp(): void {
|
|
|
28
28
|
header();
|
|
29
29
|
|
|
30
30
|
section("Usage");
|
|
31
|
-
writeStdout(" miru
|
|
31
|
+
writeStdout(" miru Show help in a terminal");
|
|
32
32
|
writeStdout(" miru <command> [options]");
|
|
33
33
|
divider();
|
|
34
34
|
|
|
@@ -37,6 +37,7 @@ export function printMainHelp(): void {
|
|
|
37
37
|
commandRow("locate", "Exact substring location in the index");
|
|
38
38
|
commandRow("expand", "Adjacent chunks in the same file as a hit");
|
|
39
39
|
commandRow("find-related", "Find chunks related to a file:line");
|
|
40
|
+
commandRow("mcp", "Start MCP server (stdio)");
|
|
40
41
|
commandRow("setup", "Authenticate with Takara");
|
|
41
42
|
commandRow("install", "Configure miru across coding agents");
|
|
42
43
|
commandRow("uninstall", "Remove miru agent configuration");
|
|
@@ -108,11 +109,10 @@ export function printCommandHelp(command: string): void {
|
|
|
108
109
|
section("Usage");
|
|
109
110
|
writeStdout(" miru search <query> [path] [options]");
|
|
110
111
|
section("Options");
|
|
111
|
-
writeStdout(" -k, --top-k N Number of results (default: 5)");
|
|
112
112
|
writeStdout(" --content TYPE code | docs | config | all (default: code config)");
|
|
113
113
|
writeStdout(" --json JSON output (default when piped)");
|
|
114
114
|
section("Example");
|
|
115
|
-
writeStdout(' miru search "where is auth" ./src
|
|
115
|
+
writeStdout(' miru search "where is auth" ./src --content code docs');
|
|
116
116
|
writeStdout("");
|
|
117
117
|
return;
|
|
118
118
|
case "locate":
|
|
@@ -149,7 +149,7 @@ export function printCommandHelp(command: string): void {
|
|
|
149
149
|
section("Usage");
|
|
150
150
|
writeStdout(" miru find-related <file> <line> [path] [options]");
|
|
151
151
|
section("Example");
|
|
152
|
-
writeStdout(" miru find-related src/auth.ts 42 .
|
|
152
|
+
writeStdout(" miru find-related src/auth.ts 42 .");
|
|
153
153
|
writeStdout("");
|
|
154
154
|
return;
|
|
155
155
|
case "setup":
|
|
@@ -178,7 +178,7 @@ export function printCommandHelp(command: string): void {
|
|
|
178
178
|
case "install":
|
|
179
179
|
commandHeader("install", "Interactive global agent setup.");
|
|
180
180
|
writeStdout("Configures MCP server, instructions, sub-agent files, and optional");
|
|
181
|
-
writeStdout("experimental integrations (
|
|
181
|
+
writeStdout("experimental integrations (Caveman, STE writing) under");
|
|
182
182
|
writeStdout("your user config (~/.claude, ~/.cursor, etc.).");
|
|
183
183
|
writeStdout("");
|
|
184
184
|
writeStdout(
|
|
@@ -244,9 +244,9 @@ export function printCommandHelp(command: string): void {
|
|
|
244
244
|
writeStdout("");
|
|
245
245
|
return;
|
|
246
246
|
case "mcp":
|
|
247
|
-
commandHeader("mcp", "Stdio MCP server
|
|
247
|
+
commandHeader("mcp", "Stdio MCP server.");
|
|
248
248
|
section("Usage");
|
|
249
|
-
writeStdout(" miru [--ref BRANCH] [--content TYPE ...] [--benchmark]");
|
|
249
|
+
writeStdout(" miru mcp [--ref BRANCH] [--content TYPE ...] [--benchmark]");
|
|
250
250
|
writeStdout(" Default content: code config");
|
|
251
251
|
writeStdout("");
|
|
252
252
|
writeStdout("Indexes on the first search/expand/find_related tool call (repo argument).");
|
package/src/index/bm25.ts
CHANGED
|
@@ -42,12 +42,21 @@ function runScoreWorker(job: Bm25ScoreJob): Promise<Bm25ScoreResult> {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export class BM25Index {
|
|
45
|
-
private docFreq
|
|
46
|
-
private docLengths: number[]
|
|
47
|
-
private postings
|
|
48
|
-
private avgDocLength
|
|
49
|
-
private numDocs
|
|
50
|
-
private totalLen
|
|
45
|
+
private docFreq: Map<string, number>;
|
|
46
|
+
private docLengths: number[];
|
|
47
|
+
private postings: Map<string, PostingsList>;
|
|
48
|
+
private avgDocLength: number;
|
|
49
|
+
private numDocs: number;
|
|
50
|
+
private totalLen: number;
|
|
51
|
+
|
|
52
|
+
constructor() {
|
|
53
|
+
this.docFreq = new Map();
|
|
54
|
+
this.docLengths = [];
|
|
55
|
+
this.postings = new Map();
|
|
56
|
+
this.avgDocLength = 0;
|
|
57
|
+
this.numDocs = 0;
|
|
58
|
+
this.totalLen = 0;
|
|
59
|
+
}
|
|
51
60
|
|
|
52
61
|
/** Append one tokenized document. Safe to call incrementally while indexing. */
|
|
53
62
|
addDocument(doc: string[]): number {
|
|
@@ -73,6 +82,57 @@ export class BM25Index {
|
|
|
73
82
|
return docIndex;
|
|
74
83
|
}
|
|
75
84
|
|
|
85
|
+
get size(): number {
|
|
86
|
+
return this.numDocs;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A new index with the given documents removed and later doc indices shifted down,
|
|
91
|
+
* equal to re-indexing the surviving docs in order. `this` is left untouched, so
|
|
92
|
+
* searches already holding it never see a half-updated index.
|
|
93
|
+
*/
|
|
94
|
+
withoutDocuments(removed: ReadonlySet<number>): BM25Index {
|
|
95
|
+
const next = new BM25Index();
|
|
96
|
+
const remap = new Int32Array(this.numDocs);
|
|
97
|
+
let kept = 0;
|
|
98
|
+
let firstRemoved = this.numDocs;
|
|
99
|
+
for (let doc = 0; doc < this.numDocs; doc++) {
|
|
100
|
+
if (removed.has(doc)) {
|
|
101
|
+
remap[doc] = -1;
|
|
102
|
+
if (firstRemoved === this.numDocs) {
|
|
103
|
+
firstRemoved = doc;
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
remap[doc] = kept++;
|
|
107
|
+
const length = this.docLengths[doc] ?? 0;
|
|
108
|
+
next.docLengths.push(length);
|
|
109
|
+
next.totalLen += length;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
next.numDocs = kept;
|
|
113
|
+
next.avgDocLength = kept === 0 ? 0 : next.totalLen / kept;
|
|
114
|
+
|
|
115
|
+
for (const [term, list] of this.postings) {
|
|
116
|
+
// Lists ascend by doc index, so one ending before the first removal is untouched.
|
|
117
|
+
// Copy the array (addDocument appends to it) but share the immutable tuples.
|
|
118
|
+
const untouched = (list.at(-1)?.[0] ?? 0) < firstRemoved;
|
|
119
|
+
const nextList: PostingsList = untouched ? list.slice() : [];
|
|
120
|
+
if (!untouched) {
|
|
121
|
+
for (const entry of list) {
|
|
122
|
+
const mapped = remap[entry[0]] ?? -1;
|
|
123
|
+
if (mapped !== -1) {
|
|
124
|
+
nextList.push(mapped === entry[0] ? entry : [mapped, entry[1]]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (nextList.length > 0) {
|
|
129
|
+
next.postings.set(term, nextList);
|
|
130
|
+
next.docFreq.set(term, nextList.length);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return next;
|
|
134
|
+
}
|
|
135
|
+
|
|
76
136
|
index(tokenizedDocs: string[][]): void {
|
|
77
137
|
this.numDocs = 0;
|
|
78
138
|
this.docFreq.clear();
|