(signal: AbortSignal)
| 108 | } |
| 109 | |
| 110 | async execute(signal: AbortSignal): Promise<ToolResult> { |
| 111 | try { |
| 112 | const workspaceContext = this.config.getWorkspaceContext(); |
| 113 | const searchDirAbs = this.resolveAndValidatePath(this.params.path); |
| 114 | const searchDirDisplay = this.params.path || '.'; |
| 115 | |
| 116 | // Determine which directories to search |
| 117 | let searchDirectories: readonly string[]; |
| 118 | if (searchDirAbs === null) { |
| 119 | // No path specified - search all workspace directories |
| 120 | searchDirectories = workspaceContext.getDirectories(); |
| 121 | } else { |
| 122 | // Specific path provided - search only that directory |
| 123 | searchDirectories = [searchDirAbs]; |
| 124 | } |
| 125 | |
| 126 | // Collect matches from all search directories |
| 127 | let allMatches: GrepMatch[] = []; |
| 128 | for (const searchDir of searchDirectories) { |
| 129 | const matches = await this.performGrepSearch({ |
| 130 | pattern: this.params.pattern, |
| 131 | path: searchDir, |
| 132 | include: this.params.include, |
| 133 | signal, |
| 134 | }); |
| 135 | |
| 136 | // Add directory prefix if searching multiple directories |
| 137 | if (searchDirectories.length > 1) { |
| 138 | const dirName = path.basename(searchDir); |
| 139 | matches.forEach((match) => { |
| 140 | match.filePath = path.join(dirName, match.filePath); |
| 141 | }); |
| 142 | } |
| 143 | |
| 144 | allMatches = allMatches.concat(matches); |
| 145 | } |
| 146 | |
| 147 | let searchLocationDescription: string; |
| 148 | if (searchDirAbs === null) { |
| 149 | const numDirs = workspaceContext.getDirectories().length; |
| 150 | searchLocationDescription = |
| 151 | numDirs > 1 |
| 152 | ? `across ${numDirs} workspace directories` |
| 153 | : `in the workspace directory`; |
| 154 | } else { |
| 155 | searchLocationDescription = `in path "${searchDirDisplay}"`; |
| 156 | } |
| 157 | |
| 158 | if (allMatches.length === 0) { |
| 159 | const noMatchMsg = `No matches found for pattern "${this.params.pattern}" ${searchLocationDescription}${this.params.include ? ` (filter: "${this.params.include}")` : ''}.`; |
| 160 | return { llmContent: noMatchMsg, returnDisplay: `No matches found` }; |
| 161 | } |
| 162 | |
| 163 | // Group matches by file |
| 164 | const matchesByFile = allMatches.reduce( |
| 165 | (acc, match) => { |
| 166 | const fileKey = match.filePath; |
| 167 | if (!acc[fileKey]) { |
nothing calls this directly
no test coverage detected