diff --git a/packages/core/src/tools/glob.test.ts b/packages/core/src/tools/glob.test.ts index 934b7ce7..7af09352 100644 --- a/packages/core/src/tools/glob.test.ts +++ b/packages/core/src/tools/glob.test.ts @@ -150,6 +150,34 @@ describe('GlobTool', () => { expect(result.returnDisplay).toBe('No files found'); }); + it('should find files with special characters in the name', async () => { + await fs.writeFile(path.join(tempRootDir, 'file[1].txt'), 'content'); + const params: GlobToolParams = { pattern: 'file[1].txt' }; + const invocation = globTool.build(params); + const result = await invocation.execute(abortSignal); + expect(result.llmContent).toContain('Found 1 file(s)'); + expect(result.llmContent).toContain( + path.join(tempRootDir, 'file[1].txt'), + ); + }); + + it('should find files with special characters like [] and () in the path', async () => { + const filePath = path.join( + tempRootDir, + 'src/app/[test]/(dashboard)/testing/components/code.tsx', + ); + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, 'content'); + + const params: GlobToolParams = { + pattern: 'src/app/[test]/(dashboard)/testing/components/code.tsx', + }; + const invocation = globTool.build(params); + const result = await invocation.execute(abortSignal); + expect(result.llmContent).toContain('Found 1 file(s)'); + expect(result.llmContent).toContain(filePath); + }); + it('should correctly sort files by modification time (newest first)', async () => { const params: GlobToolParams = { pattern: '*.sortme' }; const invocation = globTool.build(params); diff --git a/packages/core/src/tools/glob.ts b/packages/core/src/tools/glob.ts index 65454232..ae82de76 100644 --- a/packages/core/src/tools/glob.ts +++ b/packages/core/src/tools/glob.ts @@ -6,7 +6,7 @@ import fs from 'fs'; import path from 'path'; -import { glob } from 'glob'; +import { glob, escape } from 'glob'; import { SchemaValidator } from '../utils/schemaValidator.js'; import { BaseDeclarativeTool, @@ -137,7 +137,13 @@ class GlobToolInvocation extends BaseToolInvocation< let allEntries: GlobPath[] = []; for (const searchDir of searchDirectories) { - const entries = (await glob(this.params.pattern, { + let pattern = this.params.pattern; + const fullPath = path.join(searchDir, pattern); + if (fs.existsSync(fullPath)) { + pattern = escape(pattern); + } + + const entries = (await glob(pattern, { cwd: searchDir, withFileTypes: true, nodir: true, diff --git a/packages/core/src/tools/read-many-files.test.ts b/packages/core/src/tools/read-many-files.test.ts index a57b3851..b432998d 100644 --- a/packages/core/src/tools/read-many-files.test.ts +++ b/packages/core/src/tools/read-many-files.test.ts @@ -527,6 +527,43 @@ describe('ReadManyFilesTool', () => { expect(truncatedFileContent).toContain('L200'); expect(truncatedFileContent).not.toContain('L2400'); }); + + it('should read files with special characters like [] and () in the path', async () => { + const filePath = 'src/app/[test]/(dashboard)/testing/components/code.tsx'; + createFile(filePath, 'Content of receive-detail'); + const params = { paths: [filePath] }; + const invocation = tool.build(params); + const result = await invocation.execute(new AbortController().signal); + const expectedPath = path.join(tempRootDir, filePath); + expect(result.llmContent).toEqual([ + `--- ${expectedPath} --- + +Content of receive-detail + +`, + ]); + expect(result.returnDisplay).toContain( + 'Successfully read and concatenated content from **1 file(s)**', + ); + }); + + it('should read files with special characters in the name', async () => { + createFile('file[1].txt', 'Content of file[1]'); + const params = { paths: ['file[1].txt'] }; + const invocation = tool.build(params); + const result = await invocation.execute(new AbortController().signal); + const expectedPath = path.join(tempRootDir, 'file[1].txt'); + expect(result.llmContent).toEqual([ + `--- ${expectedPath} --- + +Content of file[1] + +`, + ]); + expect(result.returnDisplay).toContain( + 'Successfully read and concatenated content from **1 file(s)**', + ); + }); }); describe('Batch Processing', () => { diff --git a/packages/core/src/tools/read-many-files.ts b/packages/core/src/tools/read-many-files.ts index 46aab23d..6a6eca9b 100644 --- a/packages/core/src/tools/read-many-files.ts +++ b/packages/core/src/tools/read-many-files.ts @@ -13,8 +13,9 @@ import { } from './tools.js'; import { SchemaValidator } from '../utils/schemaValidator.js'; import { getErrorMessage } from '../utils/errors.js'; +import * as fs from 'fs'; import * as path from 'path'; -import { glob } from 'glob'; +import { glob, escape } from 'glob'; import { getCurrentGeminiMdFilename } from './memoryTool.js'; import { detectFileType, @@ -245,18 +246,27 @@ ${finalExclusionPatternsForDescription const workspaceDirs = this.config.getWorkspaceContext().getDirectories(); for (const dir of workspaceDirs) { - const entriesInDir = await glob( - searchPatterns.map((p) => p.replace(/\\/g, '/')), - { - cwd: dir, - ignore: effectiveExcludes, - nodir: true, - dot: true, - absolute: true, - nocase: true, - signal, - }, - ); + const processedPatterns = []; + for (const p of searchPatterns) { + const normalizedP = p.replace(/\\/g, '/'); + const fullPath = path.join(dir, normalizedP); + if (fs.existsSync(fullPath)) { + processedPatterns.push(escape(normalizedP)); + } else { + // The path does not exist or is not a file, so we treat it as a glob pattern. + processedPatterns.push(normalizedP); + } + } + + const entriesInDir = await glob(processedPatterns, { + cwd: dir, + ignore: effectiveExcludes, + nodir: true, + dot: true, + absolute: true, + nocase: true, + signal, + }); for (const entry of entriesInDir) { allEntries.add(entry); }