diff --git a/packages/core/src/tools/ripGrep.test.ts b/packages/core/src/tools/ripGrep.test.ts index 2f3174c4f9..e3c3ec6948 100644 --- a/packages/core/src/tools/ripGrep.test.ts +++ b/packages/core/src/tools/ripGrep.test.ts @@ -339,20 +339,18 @@ describe('RipGrepTool', () => { }; // Check for the core error message, as the full path might vary expect(grepTool.validateToolParams(params)).toContain( - 'Failed to access path stats for', + 'Path does not exist', ); expect(grepTool.validateToolParams(params)).toContain('nonexistent'); }); - it('should return error if path is a file, not a directory', async () => { + it('should allow path to be a file', async () => { const filePath = path.join(tempRootDir, 'fileA.txt'); const params: RipGrepToolParams = { pattern: 'hello', dir_path: filePath, }; - expect(grepTool.validateToolParams(params)).toContain( - `Path is not a directory: ${filePath}`, - ); + expect(grepTool.validateToolParams(params)).toBeNull(); }); }); @@ -396,7 +394,7 @@ describe('RipGrepTool', () => { const invocation = grepTool.build(params); const result = await invocation.execute(abortSignal); expect(result.llmContent).toContain( - 'Found 3 matches for pattern "world" in the workspace directory', + 'Found 3 matches for pattern "world" in path "."', ); expect(result.llmContent).toContain('File: fileA.txt'); expect(result.llmContent).toContain('L1: hello world'); @@ -457,7 +455,7 @@ describe('RipGrepTool', () => { const invocation = grepTool.build(params); const result = await invocation.execute(abortSignal); expect(result.llmContent).toContain( - 'Found 1 match for pattern "hello" in the workspace directory (filter: "*.js"):', + 'Found 1 match for pattern "hello" in path "." (filter: "*.js"):', ); expect(result.llmContent).toContain('File: fileB.js'); expect(result.llmContent).toContain( @@ -546,7 +544,7 @@ describe('RipGrepTool', () => { const invocation = grepTool.build(params); const result = await invocation.execute(abortSignal); expect(result.llmContent).toContain( - 'No matches found for pattern "nonexistentpattern" in the workspace directory.', + 'No matches found for pattern "nonexistentpattern" in path ".".', ); expect(result.returnDisplay).toBe('No matches found'); }); @@ -619,7 +617,7 @@ describe('RipGrepTool', () => { const invocation = grepTool.build(params); const result = await invocation.execute(abortSignal); expect(result.llmContent).toContain( - 'Found 1 match for pattern "foo.*bar" in the workspace directory:', + 'Found 1 match for pattern "foo.*bar" in path ".":', ); expect(result.llmContent).toContain('File: fileB.js'); expect(result.llmContent).toContain('L1: const foo = "bar";'); @@ -687,7 +685,7 @@ describe('RipGrepTool', () => { const invocation = grepTool.build(params); const result = await invocation.execute(abortSignal); expect(result.llmContent).toContain( - 'Found 2 matches for pattern "HELLO" in the workspace directory:', + 'Found 2 matches for pattern "HELLO" in path ".":', ); expect(result.llmContent).toContain('File: fileA.txt'); expect(result.llmContent).toContain('L1: hello world'); @@ -719,7 +717,7 @@ describe('RipGrepTool', () => { }); describe('multi-directory workspace', () => { - it('should search across all workspace directories when no path is specified', async () => { + it('should search only CWD when no path is specified (default behavior)', async () => { // Create additional directory with test files const secondDir = await fs.mkdtemp( path.join(os.tmpdir(), 'grep-tool-second-'), @@ -840,9 +838,9 @@ describe('RipGrepTool', () => { const invocation = multiDirGrepTool.build(params); const result = await invocation.execute(abortSignal); - // Should find matches in both directories + // Should find matches in CWD only (default behavior now) expect(result.llmContent).toContain( - 'Found 5 matches for pattern "world"', + 'Found 3 matches for pattern "world" in path "."', ); // Matches from first directory @@ -852,11 +850,11 @@ describe('RipGrepTool', () => { expect(result.llmContent).toContain('fileC.txt'); expect(result.llmContent).toContain('L1: another world in sub dir'); - // Matches from both directories - expect(result.llmContent).toContain('other.txt'); - expect(result.llmContent).toContain('L2: world in second'); - expect(result.llmContent).toContain('another.js'); - expect(result.llmContent).toContain('L1: function world()'); + // Should NOT find matches from second directory + expect(result.llmContent).not.toContain('other.txt'); + expect(result.llmContent).not.toContain('world in second'); + expect(result.llmContent).not.toContain('another.js'); + expect(result.llmContent).not.toContain('function world()'); // Clean up await fs.rm(secondDir, { recursive: true, force: true }); @@ -1574,11 +1572,187 @@ describe('RipGrepTool', () => { }); }); + describe('advanced search options', () => { + it('should handle case_sensitive parameter', async () => { + // Case-insensitive search (default) + mockSpawn.mockImplementationOnce( + createMockSpawn({ + outputData: + JSON.stringify({ + type: 'match', + data: { + path: { text: 'fileA.txt' }, + line_number: 1, + lines: { text: 'hello world\n' }, + }, + }) + '\n', + exitCode: 0, + }), + ); + let params: RipGrepToolParams = { pattern: 'HELLO' }; + let invocation = grepTool.build(params); + let result = await invocation.execute(abortSignal); + expect(mockSpawn).toHaveBeenLastCalledWith( + expect.anything(), + expect.arrayContaining(['--ignore-case']), + expect.anything(), + ); + expect(result.llmContent).toContain('Found 1 match for pattern "HELLO"'); + expect(result.llmContent).toContain('L1: hello world'); + + // Case-sensitive search + mockSpawn.mockImplementationOnce( + createMockSpawn({ + outputData: + JSON.stringify({ + type: 'match', + data: { + path: { text: 'fileA.txt' }, + line_number: 1, + lines: { text: 'HELLO world\n' }, + }, + }) + '\n', + exitCode: 0, + }), + ); + params = { pattern: 'HELLO', case_sensitive: true }; + invocation = grepTool.build(params); + result = await invocation.execute(abortSignal); + expect(mockSpawn).toHaveBeenLastCalledWith( + expect.anything(), + expect.not.arrayContaining(['--ignore-case']), + expect.anything(), + ); + expect(result.llmContent).toContain('Found 1 match for pattern "HELLO"'); + expect(result.llmContent).toContain('L1: HELLO world'); + }); + + it('should handle fixed_strings parameter', async () => { + mockSpawn.mockImplementationOnce( + createMockSpawn({ + outputData: + JSON.stringify({ + type: 'match', + data: { + path: { text: 'fileA.txt' }, + line_number: 1, + lines: { text: 'hello.world\n' }, + }, + }) + '\n', + exitCode: 0, + }), + ); + + const params: RipGrepToolParams = { + pattern: 'hello.world', + fixed_strings: true, + }; + const invocation = grepTool.build(params); + const result = await invocation.execute(abortSignal); + expect(mockSpawn).toHaveBeenLastCalledWith( + expect.anything(), + expect.arrayContaining(['--fixed-strings']), + expect.anything(), + ); + expect(result.llmContent).toContain( + 'Found 1 match for pattern "hello.world"', + ); + expect(result.llmContent).toContain('L1: hello.world'); + }); + + it('should handle no_ignore parameter', async () => { + mockSpawn.mockImplementationOnce( + createMockSpawn({ + outputData: + JSON.stringify({ + type: 'match', + data: { + path: { text: 'ignored.log' }, + line_number: 1, + lines: { text: 'secret log entry\n' }, + }, + }) + '\n', + exitCode: 0, + }), + ); + + const params: RipGrepToolParams = { pattern: 'secret', no_ignore: true }; + const invocation = grepTool.build(params); + const result = await invocation.execute(abortSignal); + + // Should have --no-ignore + expect(mockSpawn).toHaveBeenLastCalledWith( + expect.anything(), + expect.arrayContaining(['--no-ignore']), + expect.anything(), + ); + + // Should NOT have default excludes when no_ignore is true + expect(mockSpawn).toHaveBeenLastCalledWith( + expect.anything(), + expect.not.arrayContaining(['--glob', '!node_modules']), + expect.anything(), + ); + expect(result.llmContent).toContain('Found 1 match for pattern "secret"'); + expect(result.llmContent).toContain('File: ignored.log'); + expect(result.llmContent).toContain('L1: secret log entry'); + }); + + it('should handle context parameters', async () => { + mockSpawn.mockImplementationOnce( + createMockSpawn({ + outputData: + JSON.stringify({ + type: 'match', + data: { + path: { text: 'fileA.txt' }, + line_number: 2, + lines: { text: 'second line with world\n' }, + lines_before: [{ text: 'hello world\n' }], + lines_after: [ + { text: 'third line\n' }, + { text: 'fourth line\n' }, + ], + }, + }) + '\n', + exitCode: 0, + }), + ); + + const params: RipGrepToolParams = { + pattern: 'world', + context: 1, + after: 2, + before: 1, + }; + const invocation = grepTool.build(params); + const result = await invocation.execute(abortSignal); + + expect(mockSpawn).toHaveBeenLastCalledWith( + expect.anything(), + expect.arrayContaining([ + '--context', + '1', + '--after-context', + '2', + '--before-context', + '1', + ]), + expect.anything(), + ); + expect(result.llmContent).toContain('Found 1 match for pattern "world"'); + expect(result.llmContent).toContain('File: fileA.txt'); + expect(result.llmContent).toContain('L2: second line with world'); + // Note: Ripgrep JSON output for context lines doesn't include line numbers for context lines directly + // The current parsing only extracts the matched line, so we only assert on that. + }); + }); + describe('getDescription', () => { it('should generate correct description with pattern only', () => { const params: RipGrepToolParams = { pattern: 'testPattern' }; const invocation = grepTool.build(params); - expect(invocation.getDescription()).toBe("'testPattern'"); + expect(invocation.getDescription()).toBe("'testPattern' within ./"); }); it('should generate correct description with pattern and include', () => { @@ -1587,7 +1761,9 @@ describe('RipGrepTool', () => { include: '*.ts', }; const invocation = grepTool.build(params); - expect(invocation.getDescription()).toBe("'testPattern' in *.ts"); + expect(invocation.getDescription()).toBe( + "'testPattern' in *.ts within ./", + ); }); it('should generate correct description with pattern and path', async () => { @@ -1603,7 +1779,7 @@ describe('RipGrepTool', () => { expect(invocation.getDescription()).toContain(path.join('src', 'app')); }); - it('should indicate searching across all workspace directories when no path specified', () => { + it('should use ./ when no path is specified (defaults to CWD)', () => { // Create a mock config with multiple directories const multiDirConfig = { getTargetDir: () => tempRootDir, @@ -1615,9 +1791,7 @@ describe('RipGrepTool', () => { const multiDirGrepTool = new RipGrepTool(multiDirConfig); const params: RipGrepToolParams = { pattern: 'testPattern' }; const invocation = multiDirGrepTool.build(params); - expect(invocation.getDescription()).toBe( - "'testPattern' across all workspace directories", - ); + expect(invocation.getDescription()).toBe("'testPattern' within ./"); }); it('should generate correct description with pattern, include, and path', async () => { diff --git a/packages/core/src/tools/ripGrep.ts b/packages/core/src/tools/ripGrep.ts index b28a14b4ce..674fedb552 100644 --- a/packages/core/src/tools/ripGrep.ts +++ b/packages/core/src/tools/ripGrep.ts @@ -19,6 +19,10 @@ import { fileExists } from '../utils/fileUtils.js'; import { Storage } from '../config/storage.js'; import { GREP_TOOL_NAME } from './tool-names.js'; import { debugLogger } from '../utils/debugLogger.js'; +import { + FileExclusions, + COMMON_DIRECTORY_EXCLUDES, +} from '../utils/ignorePatterns.js'; const DEFAULT_TOTAL_MAX_MATCHES = 20000; @@ -75,6 +79,51 @@ export async function ensureRgPath(): Promise { throw new Error('Cannot use ripgrep.'); } +/** + * Checks if a path is within the root directory and resolves it. + * @param config The configuration object. + * @param relativePath Path relative to the root directory (or undefined for root). + * @returns The absolute path if valid and exists, or null if no path specified. + * @throws {Error} If path is outside root, doesn't exist, or isn't a directory/file. + */ +function resolveAndValidatePath( + config: Config, + relativePath?: string, +): string | null { + if (!relativePath) { + return null; + } + + const targetDir = config.getTargetDir(); + const targetPath = path.resolve(targetDir, relativePath); + + // Ensure the resolved path is within workspace boundaries + const workspaceContext = config.getWorkspaceContext(); + if (!workspaceContext.isPathWithinWorkspace(targetPath)) { + const directories = workspaceContext.getDirectories(); + throw new Error( + `Path validation failed: Attempted path "${relativePath}" resolves outside the allowed workspace directories: ${directories.join(', ')}`, + ); + } + + // Check existence and type after resolving + try { + const stats = fs.statSync(targetPath); + if (!stats.isDirectory() && !stats.isFile()) { + throw new Error( + `Path is not a valid directory or file: ${targetPath} (CWD: ${targetDir})`, + ); + } + } catch (error: unknown) { + if (isNodeError(error) && error.code === 'ENOENT') { + throw new Error(`Path does not exist: ${targetPath} (CWD: ${targetDir})`); + } + throw new Error(`Failed to access path stats for ${targetPath}: ${error}`); + } + + return targetPath; +} + /** * Parameters for the GrepTool */ @@ -93,6 +142,36 @@ export interface RipGrepToolParams { * File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}") */ include?: string; + + /** + * If true, searches case-sensitively. Defaults to false. + */ + case_sensitive?: boolean; + + /** + * If true, treats pattern as a literal string. Defaults to false. + */ + fixed_strings?: boolean; + + /** + * Show num lines of context around each match. + */ + context?: number; + + /** + * Show num lines after each match. + */ + after?: number; + + /** + * Show num lines before each match. + */ + before?: number; + + /** + * If true, does not respect .gitignore or default ignores (like build/dist). + */ + no_ignore?: boolean; } /** @@ -118,104 +197,38 @@ class GrepToolInvocation extends BaseToolInvocation< super(params, messageBus, _toolName, _toolDisplayName); } - /** - * Checks if a path is within the root directory and resolves it. - * @param relativePath Path relative to the root directory (or undefined for root). - * @returns The absolute path if valid and exists, or null if no path specified (to search all directories). - * @throws {Error} If path is outside root, doesn't exist, or isn't a directory. - */ - private resolveAndValidatePath(relativePath?: string): string | null { - // If no path specified, return null to indicate searching all workspace directories - if (!relativePath) { - return null; - } - - const targetPath = path.resolve(this.config.getTargetDir(), relativePath); - - // Security Check: Ensure the resolved path is within workspace boundaries - const workspaceContext = this.config.getWorkspaceContext(); - if (!workspaceContext.isPathWithinWorkspace(targetPath)) { - const directories = workspaceContext.getDirectories(); - throw new Error( - `Path validation failed: Attempted path "${relativePath}" resolves outside the allowed workspace directories: ${directories.join(', ')}`, - ); - } - - // Check existence and type after resolving - try { - const stats = fs.statSync(targetPath); - if (!stats.isDirectory()) { - throw new Error(`Path is not a directory: ${targetPath}`); - } - } catch (error: unknown) { - if (isNodeError(error) && error.code !== 'ENOENT') { - throw new Error(`Path does not exist: ${targetPath}`); - } - throw new Error( - `Failed to access path stats for ${targetPath}: ${error}`, - ); - } - - return targetPath; - } - async execute(signal: AbortSignal): Promise { try { - const workspaceContext = this.config.getWorkspaceContext(); - const searchDirAbs = this.resolveAndValidatePath(this.params.dir_path); - const searchDirDisplay = this.params.dir_path || '.'; + // Default to '.' if path is explicitly undefined/null. + // This forces CWD search instead of 'all workspaces' search by default. + const pathParam = this.params.dir_path || '.'; - // Determine which directories to search - let searchDirectories: readonly string[]; - if (searchDirAbs === null) { - // No path specified - search all workspace directories - searchDirectories = workspaceContext.getDirectories(); - } else { - // Specific path provided - search only that directory - searchDirectories = [searchDirAbs]; - } + const searchDirAbs = resolveAndValidatePath(this.config, pathParam); + const searchDirDisplay = pathParam; - let allMatches: GrepMatch[] = []; const totalMaxMatches = DEFAULT_TOTAL_MAX_MATCHES; - if (this.config.getDebugMode()) { debugLogger.log(`[GrepTool] Total result limit: ${totalMaxMatches}`); } - for (const searchDir of searchDirectories) { - const searchResult = await this.performRipgrepSearch({ - pattern: this.params.pattern, - path: searchDir, - include: this.params.include, - signal, - }); + let allMatches = await this.performRipgrepSearch({ + pattern: this.params.pattern, + path: searchDirAbs!, + include: this.params.include, + case_sensitive: this.params.case_sensitive, + fixed_strings: this.params.fixed_strings, + context: this.params.context, + after: this.params.after, + before: this.params.before, + no_ignore: this.params.no_ignore, + signal, + }); - if (searchDirectories.length > 1) { - const dirName = path.basename(searchDir); - searchResult.forEach((match) => { - match.filePath = path.join(dirName, match.filePath); - }); - } - - allMatches = allMatches.concat(searchResult); - - if (allMatches.length >= totalMaxMatches) { - allMatches = allMatches.slice(0, totalMaxMatches); - break; - } - } - - let searchLocationDescription: string; - if (searchDirAbs === null) { - const numDirs = workspaceContext.getDirectories().length; - searchLocationDescription = - numDirs > 1 - ? `across ${numDirs} workspace directories` - : `in the workspace directory`; - } else { - searchLocationDescription = `in path "${searchDirDisplay}"`; + if (allMatches.length >= totalMaxMatches) { + allMatches = allMatches.slice(0, totalMaxMatches); } + const searchLocationDescription = `in path "${searchDirDisplay}"`; if (allMatches.length === 0) { const noMatchMsg = `No matches found for pattern "${this.params.pattern}" ${searchLocationDescription}${this.params.include ? ` (filter: "${this.params.include}")` : ''}.`; return { llmContent: noMatchMsg, returnDisplay: `No matches found` }; @@ -314,29 +327,67 @@ class GrepToolInvocation extends BaseToolInvocation< pattern: string; path: string; include?: string; + case_sensitive?: boolean; + fixed_strings?: boolean; + context?: number; + after?: number; + before?: number; + no_ignore?: boolean; signal: AbortSignal; }): Promise { - const { pattern, path: absolutePath, include } = options; + const { + pattern, + path: absolutePath, + include, + case_sensitive, + fixed_strings, + context, + after, + before, + no_ignore, + } = options; - const rgArgs = ['--json', '--ignore-case', '--regexp', pattern]; + const rgArgs = ['--json']; + + if (!case_sensitive) { + rgArgs.push('--ignore-case'); + } + + if (fixed_strings) { + rgArgs.push('--fixed-strings'); + rgArgs.push(pattern); + } else { + rgArgs.push('--regexp', pattern); + } + + if (context) { + rgArgs.push('--context', context.toString()); + } + if (after) { + rgArgs.push('--after-context', after.toString()); + } + if (before) { + rgArgs.push('--before-context', before.toString()); + } + if (no_ignore) { + rgArgs.push('--no-ignore'); + } if (include) { rgArgs.push('--glob', include); } - const excludes = [ - '.git', - 'node_modules', - 'bower_components', - '*.log', - '*.tmp', - 'build', - 'dist', - 'coverage', - ]; - excludes.forEach((exclude) => { - rgArgs.push('--glob', `!${exclude}`); - }); + if (!no_ignore) { + const fileExclusions = new FileExclusions(this.config); + const excludes = fileExclusions.getGlobExcludes([ + ...COMMON_DIRECTORY_EXCLUDES, + '*.log', + '*.tmp', + ]); + excludes.forEach((exclude) => { + rgArgs.push('--glob', `!${exclude}`); + }); + } rgArgs.push('--threads', '4'); rgArgs.push(absolutePath); @@ -405,30 +456,16 @@ class GrepToolInvocation extends BaseToolInvocation< if (this.params.include) { description += ` in ${this.params.include}`; } - if (this.params.dir_path) { - const resolvedPath = path.resolve( - this.config.getTargetDir(), - this.params.dir_path, - ); - if ( - resolvedPath === this.config.getTargetDir() || - this.params.dir_path === '.' - ) { - description += ` within ./`; - } else { - const relativePath = makeRelative( - resolvedPath, - this.config.getTargetDir(), - ); - description += ` within ${shortenPath(relativePath)}`; - } + const pathParam = this.params.dir_path || '.'; + const resolvedPath = path.resolve(this.config.getTargetDir(), pathParam); + if (resolvedPath === this.config.getTargetDir() || pathParam === '.') { + description += ` within ./`; } else { - // When no path is specified, indicate searching all workspace directories - const workspaceContext = this.config.getWorkspaceContext(); - const directories = workspaceContext.getDirectories(); - if (directories.length > 1) { - description += ` across all workspace directories`; - } + const relativePath = makeRelative( + resolvedPath, + this.config.getTargetDir(), + ); + description += ` within ${shortenPath(relativePath)}`; } return description; } @@ -450,25 +487,55 @@ export class RipGrepTool extends BaseDeclarativeTool< super( RipGrepTool.Name, 'SearchText', - 'Searches for a regular expression pattern within the content of files in a specified directory (or current working directory). Can filter files by a glob pattern. Returns the lines containing matches, along with their file paths and line numbers. Total results limited to 20,000 matches like VSCode.', + 'FAST, optimized search powered by `ripgrep`. PREFERRED over standard `run_shell_command("grep ...")` due to better performance and automatic output limiting (max 20k matches).', Kind.Search, { properties: { pattern: { description: - "The regular expression (regex) pattern to search for within file contents (e.g., 'function\\s+myFunction', 'import\\s+\\{.*\\}\\s+from\\s+.*').", + "The pattern to search for. By default, treated as a Rust-flavored regular expression. Use '\\b' for precise symbol matching (e.g., '\\bMatchMe\\b').", type: 'string', }, dir_path: { description: - 'Optional: The absolute path to the directory to search within. If omitted, searches the current working directory.', + "Directory or file to search. Directories are searched recursively. Relative paths are resolved against current working directory. Defaults to current working directory ('.') if omitted.", type: 'string', }, include: { description: - "Optional: A glob pattern to filter which files are searched (e.g., '*.js', '*.{ts,tsx}', 'src/**'). If omitted, searches all files (respecting potential global ignores).", + "Glob pattern to filter files (e.g., '*.ts', 'src/**'). Recommended for large repositories to reduce noise. Defaults to all files if omitted.", type: 'string', }, + case_sensitive: { + description: + 'If true, search is case-sensitive. Defaults to false (ignore case) if omitted.', + type: 'boolean', + }, + fixed_strings: { + description: + 'If true, treats the `pattern` as a literal string instead of a regular expression. Defaults to false (basic regex) if omitted.', + type: 'boolean', + }, + context: { + description: + 'Show this many lines of context around each match (equivalent to grep -C). Defaults to 0 if omitted.', + type: 'integer', + }, + after: { + description: + 'Show this many lines after each match (equivalent to grep -A). Defaults to 0 if omitted.', + type: 'integer', + }, + before: { + description: + 'Show this many lines before each match (equivalent to grep -B). Defaults to 0 if omitted.', + type: 'integer', + }, + no_ignore: { + description: + 'If true, searches all files including those usually ignored (like in .gitignore, build/, dist/, etc). Defaults to false if omitted.', + type: 'boolean', + }, }, required: ['pattern'], type: 'object', @@ -479,47 +546,6 @@ export class RipGrepTool extends BaseDeclarativeTool< ); } - /** - * Checks if a path is within the root directory and resolves it. - * @param relativePath Path relative to the root directory (or undefined for root). - * @returns The absolute path if valid and exists, or null if no path specified (to search all directories). - * @throws {Error} If path is outside root, doesn't exist, or isn't a directory. - */ - private resolveAndValidatePath(relativePath?: string): string | null { - // If no path specified, return null to indicate searching all workspace directories - if (!relativePath) { - return null; - } - - const targetPath = path.resolve(this.config.getTargetDir(), relativePath); - - // Security Check: Ensure the resolved path is within workspace boundaries - const workspaceContext = this.config.getWorkspaceContext(); - if (!workspaceContext.isPathWithinWorkspace(targetPath)) { - const directories = workspaceContext.getDirectories(); - throw new Error( - `Path validation failed: Attempted path "${relativePath}" resolves outside the allowed workspace directories: ${directories.join(', ')}`, - ); - } - - // Check existence and type after resolving - try { - const stats = fs.statSync(targetPath); - if (!stats.isDirectory()) { - throw new Error(`Path is not a directory: ${targetPath}`); - } - } catch (error: unknown) { - if (isNodeError(error) && error.code !== 'ENOENT') { - throw new Error(`Path does not exist: ${targetPath}`); - } - throw new Error( - `Failed to access path stats for ${targetPath}: ${error}`, - ); - } - - return targetPath; - } - /** * Validates the parameters for the tool * @param params Parameters to validate @@ -537,7 +563,7 @@ export class RipGrepTool extends BaseDeclarativeTool< // Only validate path if one is provided if (params.dir_path) { try { - this.resolveAndValidatePath(params.dir_path); + resolveAndValidatePath(this.config, params.dir_path); } catch (error) { return getErrorMessage(error); }