Files
locode/src/tools/grep.ts
T
kim 6fe98887d5 v0.6.0: complete all 12 local-model upgrades + raise maxIterations to 100
Upgrade candidates (all 12 done):
- #1 parallel read-only tool execution (runToolBatch, 4 loop sites)
- #2 configurable retry policy (maxRetries + exponential backoff via SDK)
- #3 script-aware token estimation (CJK/symbol/structure-aware heuristic)
- #4 head+tail output capping (truncate.ts), auto-applies to bash/git/etc
- #5 partial-history compaction (preserve recent tail, summarize older prefix)
- #6 dynamic max_tokens (resolveMaxTokens)
- #7 richer tool descriptions with "use when" guidance
- #8 MCP reconnect retry + /mcp reconnect command + session toolset refresh
- #9 context-window cache TTL (cachedAt timestamp, default 7 days)
- #10 edit_file similar-match suggestion on old_string miss (bounded Levenshtein)
- #11 git_status output head+tail (resolved via #4)
- #12 auto-accept now approves all mutating tools; auto-edit stays edit-only

Other:
- DEFAULT_MAX_ITERATIONS 50 -> 100 (local models issue one tool call per step)
- MaxIterationsError message guides resume + config override
- plus prior known-issues work (grep -e/--, session id sanitization, MCP content
  types, 12 hook events, plugin collisions, skill references, git ops expansion,
  configurable autoCompactThreshold, bashGuard, pathGuard, FilePanel, replay)
2026-08-20 16:52:03 +09:00

44 lines
1.9 KiB
TypeScript

import path from "node:path";
import { rgPath } from "@vscode/ripgrep";
import { execa } from "execa";
import { z } from "zod";
import type { ToolDef } from "./types.js";
const schema = z.object({
pattern: z.string().describe("Regular expression to search for."),
path: z.string().optional().describe("File or directory to search, relative to the working directory."),
glob: z.string().optional().describe("Restrict search to files matching this glob, e.g. '*.ts'."),
case_insensitive: z.boolean().optional(),
max_results: z.number().int().min(1).max(500).optional(),
});
export const grepTool: ToolDef<z.infer<typeof schema>> = {
name: "grep",
description: "Search file contents for a regular expression pattern using ripgrep. Use to find where a symbol/function/word is used across the codebase, or to locate files containing specific text. Faster than reading files one by one.",
schema,
mutating: false,
handler: async ({ pattern, path: searchPath, glob, case_insensitive, max_results }, ctx) => {
const args = ["--line-number", "--no-heading", "--color", "never"];
if (case_insensitive) args.push("--ignore-case");
if (glob) args.push("--glob", glob);
// Use -e for the pattern and -- before the path so a pattern beginning with "-"
// (or "--") is never parsed as a ripgrep flag.
args.push("-e", pattern, "--");
args.push(searchPath ? path.resolve(ctx.cwd, searchPath) : ctx.cwd);
try {
const { stdout } = await execa(rgPath, args);
const lines = stdout.split("\n").filter(Boolean);
const limit = max_results ?? 100;
return { matches: lines.slice(0, limit), truncated: lines.length > limit };
} catch (err) {
const exitCode = (err as { exitCode?: number }).exitCode;
if (exitCode === 1) {
// ripgrep exit code 1 means "no matches found", not an error
return { matches: [], truncated: false };
}
throw err;
}
},
};