@@ -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 ( {
0 commit comments