Skip to content

Commit 933f28f

Browse files
fix: Exclude cache files from rules compilation (RooCodeInc#5283)
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
1 parent adb3ee9 commit 933f28f

File tree

2 files changed

+146
-1
lines changed

2 files changed

+146
-1
lines changed

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

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,106 @@ describe("loadRuleFiles", () => {
221221
expect(readFileMock).toHaveBeenCalledWith(expectedFile2Path, "utf-8")
222222
})
223223

224+
it("should filter out cache files from .roo/rules/ directory", async () => {
225+
// Simulate .roo/rules directory exists
226+
statMock.mockResolvedValueOnce({
227+
isDirectory: vi.fn().mockReturnValue(true),
228+
} as any)
229+
230+
// Simulate listing files including cache files
231+
readdirMock.mockResolvedValueOnce([
232+
{ name: "rule1.txt", isFile: () => true, isSymbolicLink: () => false, parentPath: "/fake/path/.roo/rules" },
233+
{ name: ".DS_Store", isFile: () => true, isSymbolicLink: () => false, parentPath: "/fake/path/.roo/rules" },
234+
{ name: "Thumbs.db", isFile: () => true, isSymbolicLink: () => false, parentPath: "/fake/path/.roo/rules" },
235+
{ name: "rule2.md", isFile: () => true, isSymbolicLink: () => false, parentPath: "/fake/path/.roo/rules" },
236+
{ name: "cache.log", isFile: () => true, isSymbolicLink: () => false, parentPath: "/fake/path/.roo/rules" },
237+
{
238+
name: "backup.bak",
239+
isFile: () => true,
240+
isSymbolicLink: () => false,
241+
parentPath: "/fake/path/.roo/rules",
242+
},
243+
{ name: "temp.tmp", isFile: () => true, isSymbolicLink: () => false, parentPath: "/fake/path/.roo/rules" },
244+
{
245+
name: "script.pyc",
246+
isFile: () => true,
247+
isSymbolicLink: () => false,
248+
parentPath: "/fake/path/.roo/rules",
249+
},
250+
] as any)
251+
252+
statMock.mockImplementation((path) => {
253+
return Promise.resolve({
254+
isFile: vi.fn().mockReturnValue(true),
255+
}) as any
256+
})
257+
258+
readFileMock.mockImplementation((filePath: PathLike) => {
259+
const pathStr = filePath.toString()
260+
const normalizedPath = pathStr.replace(/\\/g, "/")
261+
262+
// Only rule files should be read - cache files should be skipped
263+
if (normalizedPath === "/fake/path/.roo/rules/rule1.txt") {
264+
return Promise.resolve("rule 1 content")
265+
}
266+
if (normalizedPath === "/fake/path/.roo/rules/rule2.md") {
267+
return Promise.resolve("rule 2 content")
268+
}
269+
270+
// Cache files should not be read due to filtering
271+
// If they somehow are read, return recognizable content
272+
if (normalizedPath === "/fake/path/.roo/rules/.DS_Store") {
273+
return Promise.resolve("DS_STORE_BINARY_CONTENT")
274+
}
275+
if (normalizedPath === "/fake/path/.roo/rules/Thumbs.db") {
276+
return Promise.resolve("THUMBS_DB_CONTENT")
277+
}
278+
if (normalizedPath === "/fake/path/.roo/rules/backup.bak") {
279+
return Promise.resolve("BACKUP_CONTENT")
280+
}
281+
if (normalizedPath === "/fake/path/.roo/rules/cache.log") {
282+
return Promise.resolve("LOG_CONTENT")
283+
}
284+
if (normalizedPath === "/fake/path/.roo/rules/temp.tmp") {
285+
return Promise.resolve("TEMP_CONTENT")
286+
}
287+
if (normalizedPath === "/fake/path/.roo/rules/script.pyc") {
288+
return Promise.resolve("PYTHON_BYTECODE")
289+
}
290+
291+
return Promise.reject({ code: "ENOENT" })
292+
})
293+
294+
const result = await loadRuleFiles("/fake/path")
295+
296+
// Should contain rule files
297+
expect(result).toContain("rule 1 content")
298+
expect(result).toContain("rule 2 content")
299+
300+
// Should NOT contain cache file content - they should be filtered out
301+
expect(result).not.toContain("DS_STORE_BINARY_CONTENT")
302+
expect(result).not.toContain("THUMBS_DB_CONTENT")
303+
expect(result).not.toContain("BACKUP_CONTENT")
304+
expect(result).not.toContain("LOG_CONTENT")
305+
expect(result).not.toContain("TEMP_CONTENT")
306+
expect(result).not.toContain("PYTHON_BYTECODE")
307+
308+
// Verify cache files are not read at all
309+
const expectedCacheFiles = [
310+
"/fake/path/.roo/rules/.DS_Store",
311+
"/fake/path/.roo/rules/Thumbs.db",
312+
"/fake/path/.roo/rules/backup.bak",
313+
"/fake/path/.roo/rules/cache.log",
314+
"/fake/path/.roo/rules/temp.tmp",
315+
"/fake/path/.roo/rules/script.pyc",
316+
]
317+
318+
for (const cacheFile of expectedCacheFiles) {
319+
const expectedPath = process.platform === "win32" ? cacheFile.replace(/\//g, "\\") : cacheFile
320+
expect(readFileMock).not.toHaveBeenCalledWith(expectedPath, "utf-8")
321+
}
322+
})
323+
224324
it("should fall back to .roorules when .roo/rules/ is empty", async () => {
225325
// Simulate .roo/rules directory exists
226326
statMock.mockResolvedValueOnce({

src/core/prompts/sections/custom-instructions.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ async function readTextFilesFromDirectory(dirPath: string): Promise<Array<{ file
123123
// Check if it's a file (not a directory)
124124
const stats = await fs.stat(file)
125125
if (stats.isFile()) {
126+
// Filter out cache files and system files that shouldn't be in rules
127+
if (!shouldIncludeRuleFile(file)) {
128+
return null
129+
}
126130
const content = await safeReadFile(file)
127131
return { filename: file, content }
128132
}
@@ -133,7 +137,7 @@ async function readTextFilesFromDirectory(dirPath: string): Promise<Array<{ file
133137
}),
134138
)
135139

136-
// Filter out null values (directories or failed reads)
140+
// Filter out null values (directories, failed reads, or excluded files)
137141
return fileContents.filter((item): item is { filename: string; content: string } => item !== null)
138142
} catch (err) {
139143
return []
@@ -297,3 +301,44 @@ The following additional instructions are provided by the user, and should be fo
297301
${joinedSections}`
298302
: ""
299303
}
304+
305+
/**
306+
* Check if a file should be included in rule compilation.
307+
* Excludes cache files and system files that shouldn't be processed as rules.
308+
*/
309+
function shouldIncludeRuleFile(filename: string): boolean {
310+
const basename = path.basename(filename)
311+
312+
const cachePatterns = [
313+
"*.DS_Store",
314+
"*.bak",
315+
"*.cache",
316+
"*.crdownload",
317+
"*.db",
318+
"*.dmp",
319+
"*.dump",
320+
"*.eslintcache",
321+
"*.lock",
322+
"*.log",
323+
"*.old",
324+
"*.part",
325+
"*.partial",
326+
"*.pyc",
327+
"*.pyo",
328+
"*.stackdump",
329+
"*.swo",
330+
"*.swp",
331+
"*.temp",
332+
"*.tmp",
333+
"Thumbs.db",
334+
]
335+
336+
return !cachePatterns.some((pattern) => {
337+
if (pattern.startsWith("*.")) {
338+
const extension = pattern.slice(1)
339+
return basename.endsWith(extension)
340+
} else {
341+
return basename === pattern
342+
}
343+
})
344+
}

0 commit comments

Comments
 (0)