@@ -3,6 +3,9 @@ import path from "path"
33
44import { LANGUAGES , isLanguage } from "../../../shared/language"
55
6+ /**
7+ * Safely read a file and return its trimmed content
8+ */
69async function safeReadFile ( filePath : string ) : Promise < string > {
710 try {
811 const content = await fs . readFile ( filePath , "utf-8" )
@@ -16,7 +19,81 @@ async function safeReadFile(filePath: string): Promise<string> {
1619 }
1720}
1821
22+ /**
23+ * Check if a directory exists
24+ */
25+ async function directoryExists ( dirPath : string ) : Promise < boolean > {
26+ try {
27+ const stats = await fs . stat ( dirPath )
28+ return stats . isDirectory ( )
29+ } catch ( err ) {
30+ return false
31+ }
32+ }
33+
34+ /**
35+ * Read all text files from a directory in alphabetical order
36+ */
37+ async function readTextFilesFromDirectory ( dirPath : string ) : Promise < Array < { filename : string ; content : string } > > {
38+ try {
39+ const files = await fs
40+ . readdir ( dirPath , { withFileTypes : true , recursive : true } )
41+ . then ( ( files ) => files . filter ( ( file ) => file . isFile ( ) ) )
42+ . then ( ( files ) => files . map ( ( file ) => path . resolve ( dirPath , file . name ) ) )
43+
44+ const fileContents = await Promise . all (
45+ files . map ( async ( file ) => {
46+ const filePath = path . join ( dirPath , file )
47+ try {
48+ // Check if it's a file (not a directory)
49+ const stats = await fs . stat ( filePath )
50+ if ( stats . isFile ( ) ) {
51+ const content = await safeReadFile ( filePath )
52+ return { filename : file , content }
53+ }
54+ return null
55+ } catch ( err ) {
56+ return null
57+ }
58+ } ) ,
59+ )
60+
61+ // Filter out null values (directories or failed reads)
62+ return fileContents . filter ( ( item ) : item is { filename : string ; content : string } => item !== null )
63+ } catch ( err ) {
64+ return [ ]
65+ }
66+ }
67+
68+ /**
69+ * Format content from multiple files with filenames as headers
70+ */
71+ function formatDirectoryContent ( dirPath : string , files : Array < { filename : string ; content : string } > ) : string {
72+ if ( files . length === 0 ) return ""
73+
74+ const formattedContent = files
75+ . map ( ( file ) => {
76+ return `# Filename: ${ file . filename } \n${ file . content } `
77+ } )
78+ . join ( "\n\n" )
79+
80+ return `\n# Rules from ${ path . relative ( process . cwd ( ) , dirPath ) } :\n${ formattedContent } \n`
81+ }
82+
83+ /**
84+ * Load rule files from the specified directory
85+ */
1986export async function loadRuleFiles ( cwd : string ) : Promise < string > {
87+ // Check for .roo/rules/ directory
88+ const rooRulesDir = path . join ( cwd , ".roo" , "rules" )
89+ if ( await directoryExists ( rooRulesDir ) ) {
90+ const files = await readTextFilesFromDirectory ( rooRulesDir )
91+ if ( files . length > 0 ) {
92+ return formatDirectoryContent ( rooRulesDir , files )
93+ }
94+ }
95+
96+ // Fall back to existing behavior
2097 const ruleFiles = [ ".roorules" , ".clinerules" ]
2198
2299 for ( const file of ruleFiles ) {
@@ -41,16 +118,30 @@ export async function addCustomInstructions(
41118 // Load mode-specific rules if mode is provided
42119 let modeRuleContent = ""
43120 let usedRuleFile = ""
121+
44122 if ( mode ) {
45- const rooModeRuleFile = `.roorules-${ mode } `
46- modeRuleContent = await safeReadFile ( path . join ( cwd , rooModeRuleFile ) )
47- if ( modeRuleContent ) {
48- usedRuleFile = rooModeRuleFile
49- } else {
50- const clineModeRuleFile = `.clinerules-${ mode } `
51- modeRuleContent = await safeReadFile ( path . join ( cwd , clineModeRuleFile ) )
123+ // Check for .roo/rules-${mode}/ directory
124+ const modeRulesDir = path . join ( cwd , ".roo" , `rules-${ mode } ` )
125+ if ( await directoryExists ( modeRulesDir ) ) {
126+ const files = await readTextFilesFromDirectory ( modeRulesDir )
127+ if ( files . length > 0 ) {
128+ modeRuleContent = formatDirectoryContent ( modeRulesDir , files )
129+ usedRuleFile = modeRulesDir
130+ }
131+ }
132+
133+ // If no directory exists, fall back to existing behavior
134+ if ( ! modeRuleContent ) {
135+ const rooModeRuleFile = `.roorules-${ mode } `
136+ modeRuleContent = await safeReadFile ( path . join ( cwd , rooModeRuleFile ) )
52137 if ( modeRuleContent ) {
53- usedRuleFile = clineModeRuleFile
138+ usedRuleFile = rooModeRuleFile
139+ } else {
140+ const clineModeRuleFile = `.clinerules-${ mode } `
141+ modeRuleContent = await safeReadFile ( path . join ( cwd , clineModeRuleFile ) )
142+ if ( modeRuleContent ) {
143+ usedRuleFile = clineModeRuleFile
144+ }
54145 }
55146 }
56147 }
@@ -78,7 +169,11 @@ export async function addCustomInstructions(
78169
79170 // Add mode-specific rules first if they exist
80171 if ( modeRuleContent && modeRuleContent . trim ( ) ) {
81- rules . push ( `# Rules from ${ usedRuleFile } :\n${ modeRuleContent } ` )
172+ if ( usedRuleFile . includes ( path . join ( ".roo" , `rules-${ mode } ` ) ) ) {
173+ rules . push ( modeRuleContent . trim ( ) )
174+ } else {
175+ rules . push ( `# Rules from ${ usedRuleFile } :\n${ modeRuleContent } ` )
176+ }
82177 }
83178
84179 if ( options . rooIgnoreInstructions ) {
0 commit comments