Skip to content

Commit f547fe5

Browse files
committed
fix: Exclude cache files from rules compilation (RooCodeInc/Roo-Code#5283)
1 parent 7ebfd94 commit f547fe5

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
@@ -240,6 +240,106 @@ describe("loadRuleFiles", () => {
240240
expect(readFileMock).toHaveBeenCalledWith(expectedFile2Path, "utf-8")
241241
})
242242

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

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

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ async function readTextFilesFromDirectory(dirPath: string): Promise<Array<{ file
139139
// Check if it's a file (not a directory)
140140
const stats = await fs.stat(file)
141141
if (stats.isFile()) {
142+
// Filter out cache files and system files that shouldn't be in rules
143+
if (!shouldIncludeRuleFile(file)) {
144+
return null
145+
}
142146
const content = await safeReadFile(file)
143147
return { filename: file, content }
144148
}
@@ -149,7 +153,7 @@ async function readTextFilesFromDirectory(dirPath: string): Promise<Array<{ file
149153
}),
150154
)
151155

152-
// Filter out null values (directories or failed reads)
156+
// Filter out null values (directories, failed reads, or excluded files)
153157
return fileContents.filter((item): item is { filename: string; content: string } => item !== null)
154158
} catch (err) {
155159
return []
@@ -339,3 +343,44 @@ The following additional instructions are provided by the user, and should be fo
339343
${joinedSections}`
340344
: ""
341345
}
346+
347+
/**
348+
* Check if a file should be included in rule compilation.
349+
* Excludes cache files and system files that shouldn't be processed as rules.
350+
*/
351+
function shouldIncludeRuleFile(filename: string): boolean {
352+
const basename = path.basename(filename)
353+
354+
const cachePatterns = [
355+
"*.DS_Store",
356+
"*.bak",
357+
"*.cache",
358+
"*.crdownload",
359+
"*.db",
360+
"*.dmp",
361+
"*.dump",
362+
"*.eslintcache",
363+
"*.lock",
364+
"*.log",
365+
"*.old",
366+
"*.part",
367+
"*.partial",
368+
"*.pyc",
369+
"*.pyo",
370+
"*.stackdump",
371+
"*.swo",
372+
"*.swp",
373+
"*.temp",
374+
"*.tmp",
375+
"Thumbs.db",
376+
]
377+
378+
return !cachePatterns.some((pattern) => {
379+
if (pattern.startsWith("*.")) {
380+
const extension = pattern.slice(1)
381+
return basename.endsWith(extension)
382+
} else {
383+
return basename === pattern
384+
}
385+
})
386+
}

0 commit comments

Comments
 (0)