Просмотр исходного кода

fix: Exclude cache files from rules compilation (#5283)

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Murilo Pires 6 месяцев назад
Родитель
Сommit
933f28fe32

+ 100 - 0
src/core/prompts/sections/__tests__/custom-instructions.spec.ts

@@ -221,6 +221,106 @@ describe("loadRuleFiles", () => {
 		expect(readFileMock).toHaveBeenCalledWith(expectedFile2Path, "utf-8")
 		expect(readFileMock).toHaveBeenCalledWith(expectedFile2Path, "utf-8")
 	})
 	})
 
 
+	it("should filter out cache files from .roo/rules/ directory", async () => {
+		// Simulate .roo/rules directory exists
+		statMock.mockResolvedValueOnce({
+			isDirectory: vi.fn().mockReturnValue(true),
+		} as any)
+
+		// Simulate listing files including cache files
+		readdirMock.mockResolvedValueOnce([
+			{ name: "rule1.txt", isFile: () => true, isSymbolicLink: () => false, parentPath: "/fake/path/.roo/rules" },
+			{ name: ".DS_Store", isFile: () => true, isSymbolicLink: () => false, parentPath: "/fake/path/.roo/rules" },
+			{ name: "Thumbs.db", isFile: () => true, isSymbolicLink: () => false, parentPath: "/fake/path/.roo/rules" },
+			{ name: "rule2.md", isFile: () => true, isSymbolicLink: () => false, parentPath: "/fake/path/.roo/rules" },
+			{ name: "cache.log", isFile: () => true, isSymbolicLink: () => false, parentPath: "/fake/path/.roo/rules" },
+			{
+				name: "backup.bak",
+				isFile: () => true,
+				isSymbolicLink: () => false,
+				parentPath: "/fake/path/.roo/rules",
+			},
+			{ name: "temp.tmp", isFile: () => true, isSymbolicLink: () => false, parentPath: "/fake/path/.roo/rules" },
+			{
+				name: "script.pyc",
+				isFile: () => true,
+				isSymbolicLink: () => false,
+				parentPath: "/fake/path/.roo/rules",
+			},
+		] as any)
+
+		statMock.mockImplementation((path) => {
+			return Promise.resolve({
+				isFile: vi.fn().mockReturnValue(true),
+			}) as any
+		})
+
+		readFileMock.mockImplementation((filePath: PathLike) => {
+			const pathStr = filePath.toString()
+			const normalizedPath = pathStr.replace(/\\/g, "/")
+
+			// Only rule files should be read - cache files should be skipped
+			if (normalizedPath === "/fake/path/.roo/rules/rule1.txt") {
+				return Promise.resolve("rule 1 content")
+			}
+			if (normalizedPath === "/fake/path/.roo/rules/rule2.md") {
+				return Promise.resolve("rule 2 content")
+			}
+
+			// Cache files should not be read due to filtering
+			// If they somehow are read, return recognizable content
+			if (normalizedPath === "/fake/path/.roo/rules/.DS_Store") {
+				return Promise.resolve("DS_STORE_BINARY_CONTENT")
+			}
+			if (normalizedPath === "/fake/path/.roo/rules/Thumbs.db") {
+				return Promise.resolve("THUMBS_DB_CONTENT")
+			}
+			if (normalizedPath === "/fake/path/.roo/rules/backup.bak") {
+				return Promise.resolve("BACKUP_CONTENT")
+			}
+			if (normalizedPath === "/fake/path/.roo/rules/cache.log") {
+				return Promise.resolve("LOG_CONTENT")
+			}
+			if (normalizedPath === "/fake/path/.roo/rules/temp.tmp") {
+				return Promise.resolve("TEMP_CONTENT")
+			}
+			if (normalizedPath === "/fake/path/.roo/rules/script.pyc") {
+				return Promise.resolve("PYTHON_BYTECODE")
+			}
+
+			return Promise.reject({ code: "ENOENT" })
+		})
+
+		const result = await loadRuleFiles("/fake/path")
+
+		// Should contain rule files
+		expect(result).toContain("rule 1 content")
+		expect(result).toContain("rule 2 content")
+
+		// Should NOT contain cache file content - they should be filtered out
+		expect(result).not.toContain("DS_STORE_BINARY_CONTENT")
+		expect(result).not.toContain("THUMBS_DB_CONTENT")
+		expect(result).not.toContain("BACKUP_CONTENT")
+		expect(result).not.toContain("LOG_CONTENT")
+		expect(result).not.toContain("TEMP_CONTENT")
+		expect(result).not.toContain("PYTHON_BYTECODE")
+
+		// Verify cache files are not read at all
+		const expectedCacheFiles = [
+			"/fake/path/.roo/rules/.DS_Store",
+			"/fake/path/.roo/rules/Thumbs.db",
+			"/fake/path/.roo/rules/backup.bak",
+			"/fake/path/.roo/rules/cache.log",
+			"/fake/path/.roo/rules/temp.tmp",
+			"/fake/path/.roo/rules/script.pyc",
+		]
+
+		for (const cacheFile of expectedCacheFiles) {
+			const expectedPath = process.platform === "win32" ? cacheFile.replace(/\//g, "\\") : cacheFile
+			expect(readFileMock).not.toHaveBeenCalledWith(expectedPath, "utf-8")
+		}
+	})
+
 	it("should fall back to .roorules when .roo/rules/ is empty", async () => {
 	it("should fall back to .roorules when .roo/rules/ is empty", async () => {
 		// Simulate .roo/rules directory exists
 		// Simulate .roo/rules directory exists
 		statMock.mockResolvedValueOnce({
 		statMock.mockResolvedValueOnce({

+ 46 - 1
src/core/prompts/sections/custom-instructions.ts

@@ -123,6 +123,10 @@ async function readTextFilesFromDirectory(dirPath: string): Promise<Array<{ file
 					// Check if it's a file (not a directory)
 					// Check if it's a file (not a directory)
 					const stats = await fs.stat(file)
 					const stats = await fs.stat(file)
 					if (stats.isFile()) {
 					if (stats.isFile()) {
+						// Filter out cache files and system files that shouldn't be in rules
+						if (!shouldIncludeRuleFile(file)) {
+							return null
+						}
 						const content = await safeReadFile(file)
 						const content = await safeReadFile(file)
 						return { filename: file, content }
 						return { filename: file, content }
 					}
 					}
@@ -133,7 +137,7 @@ async function readTextFilesFromDirectory(dirPath: string): Promise<Array<{ file
 			}),
 			}),
 		)
 		)
 
 
-		// Filter out null values (directories or failed reads)
+		// Filter out null values (directories, failed reads, or excluded files)
 		return fileContents.filter((item): item is { filename: string; content: string } => item !== null)
 		return fileContents.filter((item): item is { filename: string; content: string } => item !== null)
 	} catch (err) {
 	} catch (err) {
 		return []
 		return []
@@ -297,3 +301,44 @@ The following additional instructions are provided by the user, and should be fo
 ${joinedSections}`
 ${joinedSections}`
 		: ""
 		: ""
 }
 }
+
+/**
+ * Check if a file should be included in rule compilation.
+ * Excludes cache files and system files that shouldn't be processed as rules.
+ */
+function shouldIncludeRuleFile(filename: string): boolean {
+	const basename = path.basename(filename)
+
+	const cachePatterns = [
+		"*.DS_Store",
+		"*.bak",
+		"*.cache",
+		"*.crdownload",
+		"*.db",
+		"*.dmp",
+		"*.dump",
+		"*.eslintcache",
+		"*.lock",
+		"*.log",
+		"*.old",
+		"*.part",
+		"*.partial",
+		"*.pyc",
+		"*.pyo",
+		"*.stackdump",
+		"*.swo",
+		"*.swp",
+		"*.temp",
+		"*.tmp",
+		"Thumbs.db",
+	]
+
+	return !cachePatterns.some((pattern) => {
+		if (pattern.startsWith("*.")) {
+			const extension = pattern.slice(1)
+			return basename.endsWith(extension)
+		} else {
+			return basename === pattern
+		}
+	})
+}