(
args: z.infer<typeof ArgsSchema>,
target: string,
maxResults: number,
outputMode: 'content' | 'files_only' | 'count',
)
| 95 | } |
| 96 | |
| 97 | private async runNative( |
| 98 | args: z.infer<typeof ArgsSchema>, |
| 99 | target: string, |
| 100 | maxResults: number, |
| 101 | outputMode: 'content' | 'files_only' | 'count', |
| 102 | ): Promise<ToolResult> { |
| 103 | const flags = args.case_insensitive ? 'gi' : 'g'; |
| 104 | let regex: RegExp; |
| 105 | try { |
| 106 | regex = new RegExp(args.pattern, flags); |
| 107 | } catch (e: any) { |
| 108 | return { content: `[REGEX_ERROR] Invalid pattern: ${e.message}`, isError: true }; |
| 109 | } |
| 110 | |
| 111 | const fileFilter = args.glob ? new RegExp(this.globToRegex(args.glob)) : null; |
| 112 | const IGNORED_DIRS = new Set([ |
| 113 | 'node_modules', '.git', 'dist', 'build', '__pycache__', 'target', '.next', '.cache', 'venv', '.venv', |
| 114 | ]); |
| 115 | |
| 116 | const fileMatches: Array<{ file: string; lines: Array<{ num: number; text: string }> }> = []; |
| 117 | let totalMatches = 0; |
| 118 | |
| 119 | async function walk(dir: string): Promise<void> { |
| 120 | if (totalMatches >= maxResults) return; |
| 121 | let entries: any[]; |
| 122 | try { |
| 123 | const stat = await fs.stat(dir); |
| 124 | if (stat.isFile()) { |
| 125 | entries = [{ name: path.basename(dir), isDirectory: () => false }]; |
| 126 | dir = path.dirname(dir); |
| 127 | } else { |
| 128 | entries = await fs.readdir(dir, { withFileTypes: true }); |
| 129 | } |
| 130 | } catch { return; } |
| 131 | |
| 132 | for (const entry of entries) { |
| 133 | if (totalMatches >= maxResults) return; |
| 134 | if (entry.name.startsWith('.') && entry.name !== '.env') continue; |
| 135 | if (entry.isDirectory()) { |
| 136 | if (IGNORED_DIRS.has(entry.name)) continue; |
| 137 | await walk(path.join(dir, entry.name)); |
| 138 | } else { |
| 139 | const full = path.join(dir, entry.name); |
| 140 | if (fileFilter && !fileFilter.test(entry.name)) continue; |
| 141 | let content: string; |
| 142 | try { |
| 143 | const stat = await fs.stat(full); |
| 144 | if (stat.size > 2 * 1024 * 1024) continue; // skip huge files |
| 145 | content = await fs.readFile(full, 'utf-8'); |
| 146 | } catch { continue; } |
| 147 | |
| 148 | const lines = content.split('\n'); |
| 149 | const matches: Array<{ num: number; text: string }> = []; |
| 150 | for (let i = 0; i < lines.length; i++) { |
| 151 | if (regex.test(lines[i]!)) { |
| 152 | matches.push({ num: i + 1, text: lines[i]! }); |
| 153 | totalMatches++; |
| 154 | if (totalMatches >= maxResults) break; |
no test coverage detected