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