@takara-ai/miru-code 1.3.0 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takara-ai/miru-code",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Miru (見る) — hybrid code search for agents. Bun, TypeScript, Takara embeddings.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,8 +1,9 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { relativePathFromRoot } from "../index/incremental.ts";
4
+ import { formatExpandResultsText, formatResultsText } from "../mcp/format-text.ts";
4
5
  import type { MiruIndex } from "../miru-index.ts";
5
- import { applySnippetsToResults, estimateResultTokens } from "../snippet.ts";
6
+ import { applySnippetsToResults } from "../snippet.ts";
6
7
  import { countTokens, tokenCountMethod, tokenizerJsonPath } from "../token-count.ts";
7
8
  import type { SearchResult } from "../types.ts";
8
9
  import {
@@ -10,6 +11,8 @@ import {
10
11
  DEFAULT_EXPAND_BEFORE,
11
12
  dedupeResultsByFile,
12
13
  expandChunksAtLine,
14
+ formatExpandResults,
15
+ formatResults,
13
16
  } from "../utils.ts";
14
17
  import { type GrepFileHit, grepSearch } from "./grep.ts";
15
18
  import { agentBenchmarkFromTokens, attachAgentBenchmark, tokenSavingsPct } from "./summary.ts";
@@ -83,6 +86,7 @@ async function readLineWindowTokens(
83
86
  }
84
87
  }
85
88
 
89
+ /** Tokens of the MCP `expand` text body (headers + chunk content), matching what agents receive. */
86
90
  function miruExpandTokens(
87
91
  index: MiruIndex,
88
92
  repoPath: string,
@@ -94,7 +98,7 @@ function miruExpandTokens(
94
98
  }
95
99
  const [{ meta }] = applySnippetsToResults([top], query);
96
100
  const line = meta.truncated ? meta.anchor_line : top.chunk.start_line;
97
- const { chunks } = expandChunksAtLine(
101
+ const { anchor, chunks } = expandChunksAtLine(
98
102
  index.chunks,
99
103
  top.chunk.file_path,
100
104
  line,
@@ -102,8 +106,18 @@ function miruExpandTokens(
102
106
  DEFAULT_EXPAND_BEFORE,
103
107
  DEFAULT_EXPAND_AFTER,
104
108
  );
109
+ if (!anchor) {
110
+ return { tokens: 0, lineSpan: 0 };
111
+ }
112
+ const text = formatExpandResultsText(
113
+ formatExpandResults(top.chunk.file_path, line, anchor, chunks, {
114
+ repoRoot: repoPath,
115
+ before: DEFAULT_EXPAND_BEFORE,
116
+ after: DEFAULT_EXPAND_AFTER,
117
+ }),
118
+ );
105
119
  return {
106
- tokens: chunks.reduce((sum, chunk) => sum + countTokens(chunk.content), 0),
120
+ tokens: countTokens(text),
107
121
  lineSpan: expandLineSpan(chunks),
108
122
  };
109
123
  }
@@ -164,8 +178,10 @@ export async function benchmarkSearchComparison(options: {
164
178
  const started = performance.now();
165
179
  const raw = await index.search({ query, topK, rerank: true });
166
180
  const results = (dedupeByFile ? dedupeResultsByFile(raw) : raw).slice(0, topK);
167
- const snippetResults = applySnippetsToResults(results, query).map((entry) => entry.result);
168
- const searchTokens = estimateResultTokens(snippetResults);
181
+ // Count the MCP tool text body (path headers + snippets), not bare chunk content.
182
+ const searchTokens = countTokens(
183
+ formatResultsText(formatResults(query, results, { repoRoot: repoPath, snippet: true })),
184
+ );
169
185
  const expand = miruExpandTokens(index, repoPath, results[0], query);
170
186
  return {
171
187
  results,
@@ -264,6 +280,7 @@ export function toAgentBenchmarkSummary(block: SearchBenchmarkBlock): AgentBench
264
280
  block.accuracy.rank1_match,
265
281
  );
266
282
  summary.save_pct = block.efficiency.token_savings_pct;
283
+ summary.search_tok = block.miru.search_tokens;
267
284
  if (block.accuracy.miru_only.length > 0) {
268
285
  summary.miru_only = block.accuracy.miru_only.slice(0, MAX_AGENT_MIRU_ONLY);
269
286
  }
@@ -1,10 +1,11 @@
1
1
  /**
2
- * Compare Miru `locate` agent payload tokens vs the costlier native Grep path.
2
+ * Compare Miru `locate` MCP response tokens vs the costlier native Grep path.
3
3
  *
4
- * Baseline mirrors Cursor/Claude Grep-style output: fixed-string matches with
5
- * ±GREP_CONTEXT lines (not slim `rg -n`). That is the expensive path agents
6
- * take without `locate`, so savings measure real workflow cost — not an
7
- * apples-to-apples payload contest against optimally-minimal ripgrep.
4
+ * Miru side counts the agent-facing text body (`formatLiteralLocateText`), not
5
+ * the intermediate JSON payload. Baseline mirrors Cursor/Claude Grep-style
6
+ * output: fixed-string matches with ±GREP_CONTEXT lines (not slim `rg -n`).
7
+ * That is the expensive path agents take without `locate`, so savings measure
8
+ * real workflow cost — not an apples-to-apples contest against minimal ripgrep.
8
9
  */
9
10
 
10
11
  import {
@@ -13,6 +14,7 @@ import {
13
14
  type LiteralLocateOptions,
14
15
  type LiteralLocateResult,
15
16
  } from "../literal.ts";
17
+ import { formatLiteralLocateText } from "../mcp/format-text.ts";
16
18
  import type { MiruIndex } from "../miru-index.ts";
17
19
  import { countTokens } from "../token-count.ts";
18
20
  import { GREP_CONTEXT } from "./grep.ts";
@@ -44,11 +46,16 @@ export async function benchmarkLocateComparison(options: {
44
46
  const result = options.index.locateLiteral(options.literal, locateOpts);
45
47
  const miruMs = performance.now() - miruStart;
46
48
  const payload = formatLiteralLocate(result);
47
- const miruTok = countTokens(JSON.stringify(payload));
49
+ const miruTok = countTokens(formatLiteralLocateText(payload));
48
50
 
49
51
  // Unbounded agent-style Grep dump (±context) — the costlier path without locate.
50
- const grep = await rgLiteralOutput(options.repoPath, options.literal, {
51
- context: GREP_CONTEXT,
52
+ // Never use less context than the caller actually requested via context_lines,
53
+ // or a locate call that returns inline context looks artificially expensive
54
+ // against a baseline that didn't have to fetch that context at all. Likewise,
55
+ // grep every variant `match_variants`/an array `literal` actually matched —
56
+ // one grep pattern isn't equivalent recall, so it isn't a fair baseline.
57
+ const grep = await rgLiteralOutput(options.repoPath, result.literals ?? [options.literal], {
58
+ context: Math.max(GREP_CONTEXT, locateOpts.context_lines ?? 0),
52
59
  maxCount: 0,
53
60
  ignoreCase: locateOpts.ignore_case,
54
61
  });
@@ -61,13 +61,14 @@ function parsePathLinePrefix(line: string): { path: string; line: number } | nul
61
61
 
62
62
  export async function rgLiteralOutput(
63
63
  repoRoot: string,
64
- literal: string,
64
+ literal: string | readonly string[],
65
65
  options: {
66
66
  context?: number;
67
67
  maxCount?: number;
68
68
  ignoreCase?: boolean;
69
69
  } = {},
70
70
  ): Promise<RgLiteralOutput> {
71
+ const literals = Array.isArray(literal) ? literal : [literal as string];
71
72
  const context = options.context ?? 0;
72
73
  const maxCount = options.maxCount ?? 20;
73
74
  const tool = selectBenchmarkSearchTool();
@@ -76,7 +77,7 @@ export async function rgLiteralOutput(
76
77
  }
77
78
  const start = performance.now();
78
79
  const proc = Bun.spawn(
79
- buildLiteralArgs(tool, repoRoot, literal, context, maxCount, !!options.ignoreCase),
80
+ buildLiteralArgs(tool, repoRoot, literals, context, maxCount, !!options.ignoreCase),
80
81
  {
81
82
  stdout: "pipe",
82
83
  stderr: "pipe",
@@ -90,10 +91,14 @@ export async function rgLiteralOutput(
90
91
  return { text, tokens: countTokens(text), latency_ms, ...stats };
91
92
  }
92
93
 
94
+ /**
95
+ * OR-matches every literal, mirroring what an agent without `locate` would have to run
96
+ * to get the same recall as `literals`/`match_variants` (one pattern isn't equivalent).
97
+ */
93
98
  function buildLiteralArgs(
94
99
  tool: "rg" | "grep" | "findstr",
95
100
  repoRoot: string,
96
- literal: string,
101
+ literals: readonly string[],
97
102
  context: number,
98
103
  maxCount: number,
99
104
  ignoreCase: boolean,
@@ -109,7 +114,10 @@ function buildLiteralArgs(
109
114
  if (maxCount > 0) {
110
115
  args.push("-m", String(maxCount));
111
116
  }
112
- args.push(literal, repoRoot);
117
+ for (const l of literals) {
118
+ args.push("-e", l);
119
+ }
120
+ args.push(repoRoot);
113
121
  return args;
114
122
  }
115
123
  if (tool === "grep") {
@@ -132,10 +140,17 @@ function buildLiteralArgs(
132
140
  if (maxCount > 0) {
133
141
  args.push("-m", String(maxCount));
134
142
  }
135
- args.push(literal, repoRoot);
143
+ for (const l of literals) {
144
+ args.push("-e", l);
145
+ }
146
+ args.push(repoRoot);
136
147
  return args;
137
148
  }
138
- const args = ["findstr", "/S", "/N", "/P", `/C:${literal}`, "*"];
149
+ const args = ["findstr", "/S", "/N", "/P"];
150
+ for (const l of literals) {
151
+ args.push(`/C:${l}`);
152
+ }
153
+ args.push("*");
139
154
  if (ignoreCase) {
140
155
  args.splice(4, 0, "/I");
141
156
  }
@@ -20,10 +20,19 @@ export function agentBenchmarkFromTokens(
20
20
  };
21
21
  }
22
22
 
23
- /** Attach compact `benchmark` to an MCP tool payload. */
23
+ /** Attach compact `benchmark` to an MCP tool payload (JSON path / tests). */
24
24
  export function attachAgentBenchmark<T extends Record<string, unknown>>(
25
25
  payload: T,
26
26
  benchmark: AgentBenchmarkSummary,
27
27
  ): T & { benchmark: AgentBenchmarkSummary } {
28
28
  return { ...payload, benchmark };
29
29
  }
30
+
31
+ /**
32
+ * Append a compact trailing JSON object so plaintext MCP bodies can still expose
33
+ * `benchmark` without wrapping the result in JSON scaffolding.
34
+ * `miru_tok` counts the body only — never this trailer.
35
+ */
36
+ export function appendAgentBenchmark(body: string, benchmark: AgentBenchmarkSummary): string {
37
+ return `${body}\n\n${JSON.stringify({ benchmark })}`;
38
+ }
@@ -42,7 +42,12 @@ export interface SearchBenchmarkBlock {
42
42
  };
43
43
  }
44
44
 
45
- /** Compact per-search stats attached to MCP `search` responses (agent-facing). */
45
+ /**
46
+ * Compact per-search stats appended to MCP `search` / `locate` responses.
47
+ * `miru_tok` is the agent-facing MCP text body for that tool (search also adds
48
+ * one expand body for workflow vs grep+read). Not JSON scaffolding, not the
49
+ * trailing `{"benchmark":...}` line.
50
+ */
46
51
  export interface AgentBenchmarkSummary {
47
52
  save_pct: number;
48
53
  miru_tok: number;
@@ -51,6 +56,11 @@ export interface AgentBenchmarkSummary {
51
56
  rank1: boolean;
52
57
  /** Present only when non-empty; capped at 3 paths. */
53
58
  miru_only?: string[];
59
+ /**
60
+ * Search only: tokens of the search MCP text body alone (before expand).
61
+ * `miru_tok` = search_tok + expand body when present.
62
+ */
63
+ search_tok?: number;
54
64
  }
55
65
 
56
66
  /** Compact cumulative rollup for MCP `read_benchmark` (agent-facing). */
package/src/help.ts CHANGED
@@ -165,8 +165,9 @@ export function printCommandHelp(command: string): void {
165
165
  writeStdout("Override path with MIRU_BENCHMARK_HISTORY_PATH. Append-only JSONL of savings.");
166
166
  writeStdout("Repo paths in the report are plaintext — use `clear` on shared machines.");
167
167
  writeStdout("");
168
- writeStdout("Savings compare Miru workflow tokens to a Grep baseline");
169
- writeStdout("(rg search + read of the top matched file), not the agent's full tool chain.");
168
+ writeStdout("Savings compare Miru MCP response tokens (search text + expand text)");
169
+ writeStdout("to a Grep baseline (rg search + read of the top matched file),");
170
+ writeStdout("not bare chunk content and not the agent's full tool chain.");
170
171
  writeStdout("Local paths only — git URL repos skip comparison with benchmark_skipped.");
171
172
  writeStdout("");
172
173
  return;
@@ -127,7 +127,10 @@ export const MCP_SERVER_INSTRUCTIONS =
127
127
 
128
128
  export const MCP_BENCHMARK_SERVER_INSTRUCTIONS =
129
129
  `${MCP_SERVER_INSTRUCTIONS} ` +
130
- "BENCHMARK MODE: each `search` and `locate` includes compact `benchmark` {save_pct,miru_tok,grep_tok,saved_tok,rank1}. " +
130
+ "BENCHMARK MODE: each `search` and `locate` ends with a compact JSON line " +
131
+ '`{"benchmark":{save_pct,miru_tok,grep_tok,saved_tok,rank1[,search_tok,miru_only]}}`. ' +
132
+ "Results stay plain text. For search, `miru_tok` is search MCP text + one expand (workflow vs grep+read); " +
133
+ "`search_tok` is this search body alone. " +
131
134
  "Call `read_benchmark` for cumulative totals across both. Do not narrate benchmark stats unless the user asks. " +
132
135
  "To leave benchmark mode, tell the user to run `miru benchmark off` and restart the agent.";
133
136
 
@@ -0,0 +1,67 @@
1
+ import type { ExpandResults } from "../utils.ts";
2
+
3
+ /** Plain-text rendering of `formatResults()` output — same info as the JSON shape, no scaffolding. */
4
+ export function formatResultsText(payload: {
5
+ query: string;
6
+ results: Record<string, unknown>[];
7
+ }): string {
8
+ if (payload.results.length === 0) {
9
+ return "No results found.";
10
+ }
11
+
12
+ const blocks = payload.results.map((result) => {
13
+ const chunk = result.chunk as Record<string, unknown>;
14
+ const startLine = chunk.truncated ? chunk.full_start_line : chunk.start_line;
15
+ const endLine = chunk.truncated ? chunk.full_end_line : chunk.end_line;
16
+ const truncatedNote = chunk.truncated ? " [truncated: call expand for full chunk]" : "";
17
+ const header = `${chunk.file_path}:${startLine}-${endLine}${truncatedNote}`;
18
+ return `${header}\n${chunk.content}`;
19
+ });
20
+
21
+ return blocks.join("\n\n");
22
+ }
23
+
24
+ /** Plain-text rendering of `formatLiteralLocate()` output. */
25
+ export function formatLiteralLocateText(payload: Record<string, unknown>): string {
26
+ const label =
27
+ (payload.literals as string[] | undefined)?.join(" | ") ?? (payload.literal as string);
28
+ const n = payload.n as number;
29
+ const files = payload.files as number;
30
+ const truncatedNote = payload.truncated ? " (truncated — pass a narrower include/exclude)" : "";
31
+ const header = `"${label}": ${n} match${n === 1 ? "" : "es"} across ${files} file${files === 1 ? "" : "s"}${truncatedNote}`;
32
+
33
+ const hits = payload.hits as
34
+ | Array<{ f: string; l: number; t?: string; ctx?: string[]; ctx_l?: number }>
35
+ | undefined;
36
+ if (!hits) {
37
+ return header;
38
+ }
39
+
40
+ const lines = [header, ""];
41
+ for (const hit of hits) {
42
+ lines.push(hit.t !== undefined ? `${hit.f}:${hit.l}: ${hit.t}` : `${hit.f}:${hit.l}`);
43
+ if (hit.ctx != null && hit.ctx_l !== undefined) {
44
+ const ctxStart = hit.ctx_l;
45
+ for (let i = 0; i < hit.ctx.length; i++) {
46
+ lines.push(` ${ctxStart + i}: ${hit.ctx[i]}`);
47
+ }
48
+ }
49
+ }
50
+ return lines.join("\n");
51
+ }
52
+
53
+ /** Plain-text rendering of `formatExpandResults()` output. */
54
+ export function formatExpandResultsText(payload: ExpandResults): string {
55
+ const anchorRange = payload.anchor
56
+ ? `${payload.anchor.start_line}-${payload.anchor.end_line}`
57
+ : null;
58
+ const header = `${payload.file_path} — ${payload.chunk_count} chunk${payload.chunk_count === 1 ? "" : "s"} around line ${payload.line}`;
59
+
60
+ const blocks = payload.chunks.map((chunk) => {
61
+ const range = `${chunk.start_line}-${chunk.end_line}`;
62
+ const isAnchor = anchorRange === range;
63
+ return `${range}${isAnchor ? " (anchor)" : ""}\n${chunk.content}`;
64
+ });
65
+
66
+ return `${header}\n\n${blocks.join("\n\n")}`;
67
+ }
@@ -65,6 +65,11 @@ export function shouldIgnoreWatchPath(relativePath: string | null | undefined):
65
65
  return false;
66
66
  }
67
67
 
68
+ /**
69
+ * Session cache of MiruIndex instances. Keeps them fresh by reconciling
70
+ * mtimes on load (`checkAndQueueStaleFiles`) and applying incremental
71
+ * updates from fs.watch (`flushFileUpdates` / `applyFileChanges`).
72
+ */
68
73
  export class IndexCache {
69
74
  private readonly content: ContentType[];
70
75
  private readonly defaultRef: string | null;
package/src/mcp/server.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  import * as z from "zod";
2
2
  import packageJson from "../../package.json";
3
- import { attachSearchBenchmark, benchmarkSearchComparison } from "../benchmark/compare.ts";
3
+ import { benchmarkSearchComparison, toAgentBenchmarkSummary } from "../benchmark/compare.ts";
4
4
  import {
5
5
  appendBenchmarkQuery,
6
6
  readBenchmarkRollup,
7
7
  recordFromAgentSummary,
8
8
  recordFromBenchmark,
9
9
  } from "../benchmark/history.ts";
10
- import { attachLocateBenchmark, benchmarkLocateComparison } from "../benchmark/locate-compare.ts";
10
+ import { benchmarkLocateComparison } from "../benchmark/locate-compare.ts";
11
+ import { appendAgentBenchmark } from "../benchmark/summary.ts";
11
12
  import {
12
13
  MCP_BENCHMARK_SERVER_INSTRUCTIONS,
13
14
  MCP_EXPAND_TOOL_DESCRIPTION,
@@ -32,6 +33,11 @@ import {
32
33
  MAX_MCP_TOP_K,
33
34
  resolveChunk,
34
35
  } from "../utils.ts";
36
+ import {
37
+ formatExpandResultsText,
38
+ formatLiteralLocateText,
39
+ formatResultsText,
40
+ } from "./format-text.ts";
35
41
  import { getIndexForRepo, type IndexCache, toolText } from "./index-cache.ts";
36
42
  import { MiruMcpServer } from "./runtime.ts";
37
43
 
@@ -43,14 +49,11 @@ const REPO_DESCRIPTION =
43
49
  const BENCHMARK_LOCAL_ONLY_NOTE =
44
50
  "Benchmark comparisons require a local repo path; git URL repos are skipped.";
45
51
 
46
- function withBenchmarkSkipped<T extends Record<string, unknown>>(
47
- payload: T,
48
- ): T & { benchmark_skipped: "local_repo_only"; note: string } {
49
- return {
50
- ...payload,
52
+ function withBenchmarkSkippedNote(body: string): string {
53
+ return `${body}\n\n${JSON.stringify({
51
54
  benchmark_skipped: "local_repo_only",
52
55
  note: BENCHMARK_LOCAL_ONLY_NOTE,
53
- };
56
+ })}`;
54
57
  }
55
58
 
56
59
  export function createMcpServer(
@@ -108,20 +111,18 @@ export function createMcpServer(
108
111
  });
109
112
  const results = comparison.results;
110
113
  if (results.length === 0) {
111
- return toolText(JSON.stringify({ error: "No results found." }));
114
+ return toolText("No results found.");
112
115
  }
113
116
  try {
114
117
  await appendBenchmarkQuery(recordFromBenchmark(repoRoot, comparison.benchmark));
115
118
  } catch {
116
119
  // History is best-effort; never fail the search on persist errors.
117
120
  }
121
+ const body = formatResultsText(
122
+ formatResults(query, results, { repoRoot, snippet: true }),
123
+ );
118
124
  return toolText(
119
- JSON.stringify(
120
- attachSearchBenchmark(
121
- formatResults(query, results, { repoRoot, snippet: true }),
122
- comparison.benchmark,
123
- ),
124
- ),
125
+ appendAgentBenchmark(body, toAgentBenchmarkSummary(comparison.benchmark)),
125
126
  );
126
127
  }
127
128
 
@@ -130,13 +131,14 @@ export function createMcpServer(
130
131
  results = dedupeResultsByFile(results);
131
132
  }
132
133
  if (results.length === 0) {
133
- return toolText(JSON.stringify({ error: "No results found." }));
134
+ return toolText("No results found.");
134
135
  }
135
136
  const payload = formatResults(query, results, { repoRoot, snippet: true });
137
+ const body = formatResultsText(payload);
136
138
  if (benchmark && !repoRoot) {
137
- return toolText(JSON.stringify(withBenchmarkSkipped(payload)));
139
+ return toolText(withBenchmarkSkippedNote(body));
138
140
  }
139
- return toolText(JSON.stringify(payload));
141
+ return toolText(body);
140
142
  } catch (err) {
141
143
  return toolText(err instanceof Error ? err.message : String(err));
142
144
  }
@@ -212,18 +214,18 @@ export function createMcpServer(
212
214
  } catch {
213
215
  // History is best-effort; never fail locate on persist errors.
214
216
  }
215
- return toolText(
216
- JSON.stringify(attachLocateBenchmark(comparison.payload, comparison.benchmark)),
217
- );
217
+ const body = formatLiteralLocateText(comparison.payload);
218
+ return toolText(appendAgentBenchmark(body, comparison.benchmark));
218
219
  }
219
220
  }
220
221
 
221
222
  const result = index.locateLiteral(literal, locateOpts);
222
223
  const payload = formatLiteralLocate(result);
224
+ const body = formatLiteralLocateText(payload);
223
225
  if (benchmark && !localRepoRoot(repo)) {
224
- return toolText(JSON.stringify(withBenchmarkSkipped(payload)));
226
+ return toolText(withBenchmarkSkippedNote(body));
225
227
  }
226
- return toolText(JSON.stringify(payload));
228
+ return toolText(body);
227
229
  } catch (err) {
228
230
  return toolText(err instanceof Error ? err.message : String(err));
229
231
  }
@@ -277,7 +279,7 @@ export function createMcpServer(
277
279
  );
278
280
  }
279
281
  return toolText(
280
- JSON.stringify(
282
+ formatExpandResultsText(
281
283
  formatExpandResults(filePath, anchorLine, anchor, expanded, {
282
284
  repoRoot,
283
285
  before: beforeCount,
@@ -327,12 +329,10 @@ export function createMcpServer(
327
329
  }
328
330
  const results = await index.findRelated(chunk, clampMcpTopK(topK));
329
331
  if (results.length === 0) {
330
- return toolText(
331
- JSON.stringify({ error: `No related chunks found for ${filePath}:${anchorLine}.` }),
332
- );
332
+ return toolText(`No related chunks found for ${filePath}:${anchorLine}.`);
333
333
  }
334
334
  return toolText(
335
- JSON.stringify(
335
+ formatResultsText(
336
336
  formatResults(`Chunks related to ${filePath}:${anchorLine}`, results, {
337
337
  repoRoot,
338
338
  snippet: true,