(args: Record<string, unknown>, context: ToolContext)
| 22 | const MAX_LINE_LENGTH = 500 |
| 23 | |
| 24 | export async function searchFiles(args: Record<string, unknown>, context: ToolContext): Promise<ToolResult> { |
| 25 | const dirPath = resolveWorkspacePath(context.cwd, String(args.path ?? ".")) |
| 26 | const regexSource = String(args.regex ?? "") |
| 27 | const filePattern = args.file_pattern == null || args.file_pattern === "" ? null : String(args.file_pattern) |
| 28 | |
| 29 | let regex: RegExp |
| 30 | try { |
| 31 | regex = new RegExp(regexSource) |
| 32 | } catch (error) { |
| 33 | return { text: `Invalid regex: ${(error as Error).message}`, isError: true } |
| 34 | } |
| 35 | |
| 36 | if (!fs.existsSync(dirPath)) { |
| 37 | return { text: `Directory not found: ${dirPath}`, isError: true } |
| 38 | } |
| 39 | |
| 40 | // A bare pattern like "*.ts" should match at any depth, like ripgrep -g. |
| 41 | const isMatch = filePattern |
| 42 | ? picomatch(filePattern.includes("/") ? filePattern : `**/${filePattern}`, { dot: true }) |
| 43 | : () => true |
| 44 | |
| 45 | const results: string[] = [] |
| 46 | let matchCount = 0 |
| 47 | |
| 48 | const walk = (dir: string): void => { |
| 49 | if (matchCount >= MAX_RESULTS) return |
| 50 | let dirents: fs.Dirent[] |
| 51 | try { |
| 52 | dirents = fs.readdirSync(dir, { withFileTypes: true }) |
| 53 | } catch { |
| 54 | return |
| 55 | } |
| 56 | for (const dirent of dirents) { |
| 57 | if (matchCount >= MAX_RESULTS) return |
| 58 | const full = path.join(dir, dirent.name) |
| 59 | if (dirent.isDirectory()) { |
| 60 | if (!IGNORED_DIRS.has(dirent.name) && !dirent.name.startsWith(".")) walk(full) |
| 61 | continue |
| 62 | } |
| 63 | // picomatch only understands forward slashes, so normalize Windows paths. |
| 64 | const rel = path.relative(dirPath, full).split(path.sep).join("/") |
| 65 | if (!isMatch(rel)) continue |
| 66 | let stat: fs.Stats |
| 67 | try { |
| 68 | stat = fs.statSync(full) |
| 69 | } catch { |
| 70 | continue |
| 71 | } |
| 72 | if (stat.size > MAX_FILE_SIZE) continue |
| 73 | |
| 74 | let content: string |
| 75 | try { |
| 76 | content = fs.readFileSync(full, "utf8") |
| 77 | } catch { |
| 78 | continue |
| 79 | } |
| 80 | if (content.includes("\u0000")) continue // binary |
| 81 |
nothing calls this directly
no test coverage detected