@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.
Files changed (60) hide show
  1. package/README.md +65 -53
  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 +54 -98
  24. package/src/credentials.ts +14 -14
  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 +7 -7
  30. package/src/index/bm25.ts +66 -6
  31. package/src/index/chunk-selector.ts +39 -11
  32. package/src/index/create.ts +5 -9
  33. package/src/index/files.ts +5 -1
  34. package/src/index/incremental.ts +48 -8
  35. package/src/index/sparse.ts +14 -2
  36. package/src/installer/agents.ts +85 -65
  37. package/src/installer/benchmark-mode.ts +40 -11
  38. package/src/installer/config.ts +105 -42
  39. package/src/installer/installer.ts +59 -34
  40. package/src/installer/legacy-hooks.ts +131 -0
  41. package/src/installer/prompt.ts +20 -22
  42. package/src/installer/search-policy.ts +10 -10
  43. package/src/mcp/index-cache.ts +16 -3
  44. package/src/mcp/server.ts +65 -46
  45. package/src/mcp/stdio.ts +17 -2
  46. package/src/miru-index.ts +90 -22
  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 +8 -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 +1 -15
  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
package/src/spinner.ts CHANGED
@@ -8,11 +8,14 @@ export class Spinner {
8
8
  private frame = 0;
9
9
  private belowLines = 0;
10
10
 
11
- constructor(private readonly message: string) {}
11
+ constructor(
12
+ private readonly message: string,
13
+ private readonly stderr: SpinnerOutput = process.stderr,
14
+ ) {}
12
15
 
13
16
  start(): void {
14
- if (!process.stderr.isTTY) {
15
- process.stderr.write(`${this.message}...\n`);
17
+ if (!this.stderr.isTTY) {
18
+ this.stderr.write(`${this.message}...\n`);
16
19
  return;
17
20
  }
18
21
  this.draw();
@@ -20,17 +23,17 @@ export class Spinner {
20
23
  }
21
24
 
22
25
  follow(line: string): void {
23
- if (!process.stderr.isTTY) {
24
- process.stderr.write(`${line}\n`);
26
+ if (!this.stderr.isTTY) {
27
+ this.stderr.write(`${line}\n`);
25
28
  return;
26
29
  }
27
- const cols = process.stderr.columns || 80;
30
+ const cols = this.stderr.columns || 80;
28
31
  const rows = line.split("\n").reduce((count, part) => {
29
32
  return count + Math.max(1, Math.ceil(displayWidth(part) / cols));
30
33
  }, 0);
31
- process.stderr.write(`\n${line}`);
34
+ this.stderr.write(`\n${line}`);
32
35
  this.belowLines += rows;
33
- process.stderr.write(`\x1b[${this.belowLines}A`);
36
+ this.stderr.write(`\x1b[${this.belowLines}A`);
34
37
  }
35
38
 
36
39
  stop(finalMessage?: string): void {
@@ -38,19 +41,19 @@ export class Spinner {
38
41
  clearInterval(this.timer);
39
42
  this.timer = null;
40
43
  }
41
- if (process.stderr.isTTY) {
42
- process.stderr.write("\r\x1b[K");
44
+ if (this.stderr.isTTY) {
45
+ this.stderr.write("\r\x1b[K");
43
46
  if (finalMessage) {
44
- process.stderr.write(finalMessage);
47
+ this.stderr.write(finalMessage);
45
48
  }
46
49
  if (this.belowLines > 0) {
47
- process.stderr.write(`\x1b[${this.belowLines}B`);
50
+ this.stderr.write(`\x1b[${this.belowLines}B`);
48
51
  }
49
52
  if (finalMessage || this.belowLines > 0) {
50
- process.stderr.write("\n");
53
+ this.stderr.write("\n");
51
54
  }
52
55
  } else if (finalMessage) {
53
- process.stderr.write(`${finalMessage}\n`);
56
+ this.stderr.write(`${finalMessage}\n`);
54
57
  }
55
58
  }
56
59
 
@@ -68,16 +71,23 @@ export class Spinner {
68
71
 
69
72
  private draw(): void {
70
73
  const glyph = FRAMES[this.frame++ % FRAMES.length];
71
- process.stderr.write(`\r${dim(glyph)} ${this.message}`);
74
+ this.stderr.write(`\r${dim(glyph)} ${this.message}`);
72
75
  }
73
76
  }
74
77
 
78
+ interface SpinnerOutput {
79
+ isTTY?: boolean;
80
+ columns?: number;
81
+ write(text: string): unknown;
82
+ }
83
+
75
84
  export async function withSpinner<T>(
76
85
  message: string,
77
86
  fn: () => Promise<T>,
78
87
  options?: { successMessage?: string; failMessage?: string },
88
+ output: SpinnerOutput = process.stderr,
79
89
  ): Promise<T> {
80
- const spinner = new Spinner(message);
90
+ const spinner = new Spinner(message, output);
81
91
  spinner.start();
82
92
  try {
83
93
  const result = await fn();
@@ -99,9 +99,6 @@ function whitespaceTokenize(text: string): string[] {
99
99
 
100
100
  function splitOnPunctuation(text: string): string[] {
101
101
  const chars = [...text];
102
- if (chars.length === 0) {
103
- return [];
104
- }
105
102
 
106
103
  const output: string[][] = [];
107
104
  let startNewWord = true;
@@ -138,9 +135,6 @@ function wordpieceTokenize(
138
135
  prefix: string,
139
136
  maxInputCharsPerWord: number,
140
137
  ): string[] {
141
- if (token.length === 0) {
142
- return [];
143
- }
144
138
  if (token.length > maxInputCharsPerWord) {
145
139
  return [unkToken];
146
140
  }
package/src/utils.ts CHANGED
@@ -100,22 +100,12 @@ export type ExpandResults = {
100
100
  chunks: Record<string, unknown>[];
101
101
  };
102
102
 
103
- export const DEFAULT_MCP_TOP_K = 3;
104
- export const MAX_MCP_TOP_K = 10;
103
+ export const SEARCH_RESULT_COUNT = 5;
105
104
 
106
105
  /** Extra chunks before/after the expand anchor — MCP `expand` tool defaults. */
107
106
  export const DEFAULT_EXPAND_BEFORE = 1;
108
107
  export const DEFAULT_EXPAND_AFTER = 1;
109
108
 
110
- /** Clamp MCP top_k to a sane range; omit for the default. */
111
- export function clampMcpTopK(topK?: number): number {
112
- const value = topK ?? DEFAULT_MCP_TOP_K;
113
- if (!Number.isFinite(value) || value < 1) {
114
- return DEFAULT_MCP_TOP_K;
115
- }
116
- return Math.min(Math.floor(value), MAX_MCP_TOP_K);
117
- }
118
-
119
109
  function chunkToResponseDict(chunk: Chunk, repoRoot?: string | null): Record<string, unknown> {
120
110
  const dict = chunkToDict(chunk);
121
111
  if (!repoRoot) {
@@ -175,10 +165,6 @@ export function expandChunksAtLine(
175
165
  const anchorIndex = fileChunks.findIndex(
176
166
  (chunk) => chunk.start_line === anchor.start_line && chunk.end_line === anchor.end_line,
177
167
  );
178
- if (anchorIndex < 0) {
179
- return { anchor, chunks: [anchor] };
180
- }
181
-
182
168
  const start = Math.max(0, anchorIndex - before);
183
169
  const end = Math.min(fileChunks.length, anchorIndex + after + 1);
184
170
  return { anchor, chunks: fileChunks.slice(start, end) };
package/src/version.ts CHANGED
@@ -26,8 +26,8 @@ export function miruVersion(): string {
26
26
  * - 0.x releases: `0.{minor}` (chunking may break on minor bumps pre-1.0).
27
27
  * - 1.x+: major version only.
28
28
  */
29
- export function indexCacheEpoch(): string {
30
- const parts = packageJson.version.split(".");
29
+ export function indexCacheEpoch(version: string = packageJson.version): string {
30
+ const parts = version.split(".");
31
31
  const major = parts[0] ?? "0";
32
32
  const minor = parts[1] ?? "0";
33
33
  if (major === "0") {
@@ -118,7 +118,7 @@ export async function fetchLatestPublishedVersion(fetchImpl: Fetcher = fetch): P
118
118
  }
119
119
 
120
120
  /** Print update hint to stderr when a newer package is on npm (at most once per day). */
121
- export async function maybeNotifyUpdate(): Promise<void> {
121
+ export async function maybeNotifyUpdate(fetchImpl: Fetcher = fetch): Promise<void> {
122
122
  if (process.env.MIRU_NO_UPDATE_CHECK === "1") {
123
123
  return;
124
124
  }
@@ -133,7 +133,7 @@ export async function maybeNotifyUpdate(): Promise<void> {
133
133
  }
134
134
 
135
135
  try {
136
- const latest = await fetchLatestPublishedVersion();
136
+ const latest = await fetchLatestPublishedVersion(fetchImpl);
137
137
  await writeUpdateCheckCache(latest);
138
138
  if (isVersionNewer(latest, current)) {
139
139
  writeUpdateNotice(latest, current);