From a1252f8994955dd8f101c59b96f41f717775b75d Mon Sep 17 00:00:00 2001 From: MuriloFP Date: Thu, 10 Jul 2025 16:54:32 -0300 Subject: [PATCH] fix(glob): include top-level files when recursively listing ignored directories - Fix issue where files at root level of directories in DIRS_TO_IGNORE were excluded - Add explicit include patterns (* and **/*) when targeting ignored directories - Modify exclusion pattern to use !*/dir/** instead of !**/dir/** for target directory - Add comprehensive test case for .roo/temp scenario Fixes #5176 --- .../glob/__tests__/list-files.spec.ts | 57 +++++++++++++++++++ src/services/glob/list-files.ts | 35 ++++++++++-- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/src/services/glob/__tests__/list-files.spec.ts b/src/services/glob/__tests__/list-files.spec.ts index 4e55f9558e..8b8c1c84a2 100644 --- a/src/services/glob/__tests__/list-files.spec.ts +++ b/src/services/glob/__tests__/list-files.spec.ts @@ -336,4 +336,61 @@ describe("hidden directory exclusion", () => { expect(hasTasksDir).toBe(true) expect(hasContextDir).toBe(true) }) + + it("should include top-level files when recursively listing a hidden directory that's also in DIRS_TO_IGNORE", async () => { + // This test specifically addresses the bug where files at the root level of .roo/temp + // were being excluded when using recursive listing + const mockSpawn = vi.mocked(childProcess.spawn) + const mockProcess = { + stdout: { + on: vi.fn((event, callback) => { + if (event === "data") { + // Simulate files that should be found in .roo/temp + setTimeout(() => { + callback(".roo/temp/teste1.md\n") + callback(".roo/temp/22/test2.md\n") + }, 10) + } + }), + }, + stderr: { + on: vi.fn(), + }, + on: vi.fn((event, callback) => { + if (event === "close") { + setTimeout(() => callback(0), 20) + } + }), + kill: vi.fn(), + } + + mockSpawn.mockReturnValue(mockProcess as any) + + // Mock directory listing for .roo/temp + const mockReaddir = vi.fn() + vi.mocked(fs.promises).readdir = mockReaddir + mockReaddir.mockResolvedValueOnce([{ name: "22", isDirectory: () => true, isSymbolicLink: () => false }]) + + // Call listFiles targeting .roo/temp (which is both hidden and in DIRS_TO_IGNORE) + const [files] = await listFiles("/test/.roo/temp", true, 100) + + // Verify ripgrep was called with correct arguments + const [rgPath, args] = mockSpawn.mock.calls[0] + expect(args).toContain("--no-ignore-vcs") + expect(args).toContain("--no-ignore") + + // Check for the inclusion patterns that should be added + expect(args).toContain("-g") + const gIndex = args.indexOf("-g") + expect(args[gIndex + 1]).toBe("*") + + // Verify that both top-level and nested files are included + const fileNames = files.map((f) => path.basename(f)) + expect(fileNames).toContain("teste1.md") + expect(fileNames).toContain("test2.md") + + // Ensure the top-level file is actually included + const topLevelFile = files.find((f) => f.endsWith("teste1.md")) + expect(topLevelFile).toBeTruthy() + }) }) diff --git a/src/services/glob/list-files.ts b/src/services/glob/list-files.ts index 2bb11cc4e7..d26ea6c6aa 100644 --- a/src/services/glob/list-files.ts +++ b/src/services/glob/list-files.ts @@ -208,23 +208,48 @@ function buildRecursiveArgs(dirPath: string): string[] { // (ripgrep does this automatically) // Check if we're explicitly targeting a hidden directory + // We need to check all parts of the path, not just the basename + const pathParts = dirPath.split(path.sep).filter((part) => part !== "") + const isTargetingHiddenDir = pathParts.some((part) => part.startsWith(".")) + + // Get the target directory name to check if it's in the ignore list const targetDirName = path.basename(dirPath) - const isTargetingHiddenDir = targetDirName.startsWith(".") + const isTargetInIgnoreList = DIRS_TO_IGNORE.includes(targetDirName) + + // If targeting a hidden directory or a directory in the ignore list, + // use special handling to ensure all files are shown + if (isTargetingHiddenDir || isTargetInIgnoreList) { + args.push("--no-ignore-vcs") + args.push("--no-ignore") + + // When targeting an ignored directory, we need to be careful with glob patterns + // Add a pattern to explicitly include files at the root level + args.push("-g", "*") + args.push("-g", "**/*") + } // Apply directory exclusions for recursive searches for (const dir of DIRS_TO_IGNORE) { // Special handling for hidden directories pattern if (dir === ".*") { - // Only exclude hidden directories if we're not explicitly targeting one - // This allows explicitly targeted hidden directories to be processed - // while excluding them from general recursive searches + // If we're explicitly targeting a hidden directory, don't exclude hidden files/dirs + // This allows the target hidden directory and all its contents to be listed if (!isTargetingHiddenDir) { + // Not targeting hidden dir: exclude all hidden directories args.push("-g", `!**/.*/**`) } + // If targeting hidden dir: don't add any exclusion for hidden directories continue } - args.push("-g", `!**/${dir}/**`) + // When targeting a directory that's in the ignore list, modify the exclusion pattern + // to only exclude nested directories with the same name, not the root level + if (dir === targetDirName && isTargetInIgnoreList) { + // Only exclude subdirectories, not files at the root level + args.push("-g", `!*/${dir}/**`) + } else { + args.push("-g", `!**/${dir}/**`) + } } return args }