From 0a8281f2dd94937f6e36dd09b8f0828519f63ff6 Mon Sep 17 00:00:00 2001 From: xuewenjie Date: Mon, 8 Dec 2025 10:58:50 +0800 Subject: [PATCH 1/3] fix: handle case-insensitive path comparison in glob tool on Windows --- packages/core/src/tools/glob.test.ts | 36 ++++++++++++++++++++++++++++ packages/core/src/tools/glob.ts | 11 +++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/core/src/tools/glob.test.ts b/packages/core/src/tools/glob.test.ts index 3729c251..ecfc27d9 100644 --- a/packages/core/src/tools/glob.test.ts +++ b/packages/core/src/tools/glob.test.ts @@ -198,6 +198,42 @@ describe('GlobTool', () => { ); }); + it('should find files even if workspace path casing differs from glob results (Windows)', async () => { + // Only relevant for Windows + if (process.platform !== 'win32') { + return; + } + + // 1. Create a path with mismatched casing for the workspace root + // e.g., if tempRootDir is "C:\Users\...", make it "c:\Users\..." + const drive = path.parse(tempRootDir).root; + if (!drive || !drive.match(/^[A-Z]:\\/)) { + // Skip if we can't determine/manipulate the drive letter easily + return; + } + + const lowerDrive = drive.toLowerCase(); + const mismatchedRootDir = + lowerDrive + tempRootDir.substring(drive.length); + + // 2. Create a new GlobTool instance with this mismatched root + const mismatchedConfig = { + ...mockConfig, + getTargetDir: () => mismatchedRootDir, + getWorkspaceContext: () => + createMockWorkspaceContext(mismatchedRootDir), + } as unknown as Config; + + const mismatchedGlobTool = new GlobTool(mismatchedConfig); + + // 3. Execute search + const params: GlobToolParams = { pattern: '*.txt' }; + const invocation = mismatchedGlobTool.build(params); + const result = await invocation.execute(abortSignal); + + expect(result.llmContent).toContain('Found 2 file(s)'); + }); + it('should return error if path is outside workspace', async () => { // Bypassing validation to test execute method directly vi.spyOn(globTool, 'validateToolParams').mockReturnValue(null); diff --git a/packages/core/src/tools/glob.ts b/packages/core/src/tools/glob.ts index 29b6cf86..38cc0de9 100644 --- a/packages/core/src/tools/glob.ts +++ b/packages/core/src/tools/glob.ts @@ -134,12 +134,19 @@ class GlobToolInvocation extends BaseToolInvocation< this.getFileFilteringOptions(), ); + const normalizePathForComparison = (p: string) => + process.platform === 'win32' ? p.toLowerCase() : p; + const filteredAbsolutePaths = new Set( - filteredPaths.map((p) => path.resolve(this.config.getTargetDir(), p)), + filteredPaths.map((p) => + normalizePathForComparison( + path.resolve(this.config.getTargetDir(), p), + ), + ), ); const filteredEntries = allEntries.filter((entry) => - filteredAbsolutePaths.has(entry.fullpath()), + filteredAbsolutePaths.has(normalizePathForComparison(entry.fullpath())), ); if (!filteredEntries || filteredEntries.length === 0) { From 5fa87e6fbb299231a64e3d5e8694170123150d2f Mon Sep 17 00:00:00 2001 From: xuewenjie Date: Mon, 8 Dec 2025 18:13:25 +0800 Subject: [PATCH 2/3] fix: handle case-insensitive path comparison on macOS in glob tool --- packages/core/src/tools/glob.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/tools/glob.ts b/packages/core/src/tools/glob.ts index 38cc0de9..a3b4a5d5 100644 --- a/packages/core/src/tools/glob.ts +++ b/packages/core/src/tools/glob.ts @@ -135,7 +135,9 @@ class GlobToolInvocation extends BaseToolInvocation< ); const normalizePathForComparison = (p: string) => - process.platform === 'win32' ? p.toLowerCase() : p; + process.platform === 'win32' || process.platform === 'darwin' + ? p.toLowerCase() + : p; const filteredAbsolutePaths = new Set( filteredPaths.map((p) => From dd7f9ed48954be33e2bf94459b8826507557f630 Mon Sep 17 00:00:00 2001 From: xuewenjie Date: Mon, 8 Dec 2025 18:15:40 +0800 Subject: [PATCH 3/3] test: update glob tool test to cover case-insensitive path comparison on macOS --- packages/core/src/tools/glob.test.ts | 36 ++++++++++++++++++---------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/core/src/tools/glob.test.ts b/packages/core/src/tools/glob.test.ts index ecfc27d9..b6a04c35 100644 --- a/packages/core/src/tools/glob.test.ts +++ b/packages/core/src/tools/glob.test.ts @@ -198,23 +198,33 @@ describe('GlobTool', () => { ); }); - it('should find files even if workspace path casing differs from glob results (Windows)', async () => { - // Only relevant for Windows - if (process.platform !== 'win32') { + it('should find files even if workspace path casing differs from glob results (Windows/macOS)', async () => { + // Only relevant for Windows and macOS + if (process.platform !== 'win32' && process.platform !== 'darwin') { return; } - // 1. Create a path with mismatched casing for the workspace root - // e.g., if tempRootDir is "C:\Users\...", make it "c:\Users\..." - const drive = path.parse(tempRootDir).root; - if (!drive || !drive.match(/^[A-Z]:\\/)) { - // Skip if we can't determine/manipulate the drive letter easily - return; - } + let mismatchedRootDir = tempRootDir; - const lowerDrive = drive.toLowerCase(); - const mismatchedRootDir = - lowerDrive + tempRootDir.substring(drive.length); + if (process.platform === 'win32') { + // 1. Create a path with mismatched casing for the workspace root + // e.g., if tempRootDir is "C:\Users\...", make it "c:\Users\..." + const drive = path.parse(tempRootDir).root; + if (!drive || !drive.match(/^[A-Z]:\\/)) { + // Skip if we can't determine/manipulate the drive letter easily + return; + } + + const lowerDrive = drive.toLowerCase(); + mismatchedRootDir = lowerDrive + tempRootDir.substring(drive.length); + } else { + // macOS: change the casing of the path + if (tempRootDir === tempRootDir.toLowerCase()) { + mismatchedRootDir = tempRootDir.toUpperCase(); + } else { + mismatchedRootDir = tempRootDir.toLowerCase(); + } + } // 2. Create a new GlobTool instance with this mismatched root const mismatchedConfig = {