11import * as fs from "fs/promises"
22import * as path from "path"
33import * as vscode from "vscode"
4+ import type { ProviderSettings } from "@roo-code/types"
45import { fileExistsAtPath } from "../../utils/fs"
56import { getProjectRooDirectoryForCwd } from "../roo-config/index"
7+ import { singleCompletionHandler } from "../../utils/single-completion-handler"
68
79interface ProjectConfig {
810 type : "typescript" | "javascript" | "python" | "java" | "go" | "rust" | "unknown"
@@ -110,9 +112,166 @@ async function analyzeProjectConfig(workspacePath: string): Promise<ProjectConfi
110112}
111113
112114/**
113- * Generates rules content based on project analysis
115+ * Generates a summary of the codebase for LLM analysis
114116 */
115- function generateRulesContent ( config : ProjectConfig , workspacePath : string ) : string {
117+ async function generateCodebaseSummary ( workspacePath : string , config : ProjectConfig ) : Promise < string > {
118+ const summary : string [ ] = [ ]
119+
120+ // Project structure overview
121+ summary . push ( "# Codebase Analysis" )
122+ summary . push ( "" )
123+ summary . push ( `**Project Type**: ${ config . type } ` )
124+ summary . push ( `**Package Manager**: ${ config . packageManager || "none" } ` )
125+ summary . push ( "" )
126+
127+ // Configuration files
128+ summary . push ( "## Configuration Files Found:" )
129+ const configFiles : string [ ] = [ ]
130+
131+ if ( await fileExistsAtPath ( path . join ( workspacePath , "package.json" ) ) ) {
132+ configFiles . push ( "package.json" )
133+ }
134+ if ( await fileExistsAtPath ( path . join ( workspacePath , "tsconfig.json" ) ) ) {
135+ configFiles . push ( "tsconfig.json" )
136+ }
137+ if ( await fileExistsAtPath ( path . join ( workspacePath , ".eslintrc.js" ) ) ) {
138+ configFiles . push ( ".eslintrc.js" )
139+ }
140+ if ( await fileExistsAtPath ( path . join ( workspacePath , ".eslintrc.json" ) ) ) {
141+ configFiles . push ( ".eslintrc.json" )
142+ }
143+ if ( await fileExistsAtPath ( path . join ( workspacePath , ".prettierrc" ) ) ) {
144+ configFiles . push ( ".prettierrc" )
145+ }
146+ if ( await fileExistsAtPath ( path . join ( workspacePath , "jest.config.js" ) ) ) {
147+ configFiles . push ( "jest.config.js" )
148+ }
149+ if ( await fileExistsAtPath ( path . join ( workspacePath , "vitest.config.ts" ) ) ) {
150+ configFiles . push ( "vitest.config.ts" )
151+ }
152+ if ( await fileExistsAtPath ( path . join ( workspacePath , "pyproject.toml" ) ) ) {
153+ configFiles . push ( "pyproject.toml" )
154+ }
155+ if ( await fileExistsAtPath ( path . join ( workspacePath , "Cargo.toml" ) ) ) {
156+ configFiles . push ( "Cargo.toml" )
157+ }
158+ if ( await fileExistsAtPath ( path . join ( workspacePath , "go.mod" ) ) ) {
159+ configFiles . push ( "go.mod" )
160+ }
161+
162+ configFiles . forEach ( ( file ) => summary . push ( `- ${ file } ` ) )
163+ summary . push ( "" )
164+
165+ // Dependencies
166+ if ( config . dependencies . length > 0 ) {
167+ summary . push ( "## Key Dependencies:" )
168+ config . dependencies . slice ( 0 , 10 ) . forEach ( ( dep ) => summary . push ( `- ${ dep } ` ) )
169+ summary . push ( "" )
170+ }
171+
172+ if ( config . devDependencies . length > 0 ) {
173+ summary . push ( "## Dev Dependencies:" )
174+ config . devDependencies . slice ( 0 , 10 ) . forEach ( ( dep ) => summary . push ( `- ${ dep } ` ) )
175+ summary . push ( "" )
176+ }
177+
178+ // Scripts
179+ if ( Object . keys ( config . scripts ) . length > 0 ) {
180+ summary . push ( "## Available Scripts:" )
181+ Object . entries ( config . scripts ) . forEach ( ( [ name , command ] ) => {
182+ summary . push ( `- **${ name } **: \`${ command } \`` )
183+ } )
184+ summary . push ( "" )
185+ }
186+
187+ // Tools detected
188+ summary . push ( "## Tools Detected:" )
189+ const tools : string [ ] = [ ]
190+ if ( config . hasTypeScript ) tools . push ( "TypeScript" )
191+ if ( config . hasESLint ) tools . push ( "ESLint" )
192+ if ( config . hasPrettier ) tools . push ( "Prettier" )
193+ if ( config . hasJest ) tools . push ( "Jest" )
194+ if ( config . hasVitest ) tools . push ( "Vitest" )
195+ if ( config . hasPytest ) tools . push ( "Pytest" )
196+
197+ tools . forEach ( ( tool ) => summary . push ( `- ${ tool } ` ) )
198+ summary . push ( "" )
199+
200+ // Check for existing rules files
201+ const existingRulesFiles : string [ ] = [ ]
202+ if ( await fileExistsAtPath ( path . join ( workspacePath , "CLAUDE.md" ) ) ) {
203+ existingRulesFiles . push ( "CLAUDE.md" )
204+ }
205+ if ( await fileExistsAtPath ( path . join ( workspacePath , ".cursorrules" ) ) ) {
206+ existingRulesFiles . push ( ".cursorrules" )
207+ }
208+ if ( await fileExistsAtPath ( path . join ( workspacePath , ".cursor" , "rules" ) ) ) {
209+ existingRulesFiles . push ( ".cursor/rules" )
210+ }
211+ if ( await fileExistsAtPath ( path . join ( workspacePath , ".github" , "copilot-instructions.md" ) ) ) {
212+ existingRulesFiles . push ( ".github/copilot-instructions.md" )
213+ }
214+
215+ if ( existingRulesFiles . length > 0 ) {
216+ summary . push ( "## Existing Rules Files:" )
217+ existingRulesFiles . forEach ( ( file ) => summary . push ( `- ${ file } ` ) )
218+ summary . push ( "" )
219+ }
220+
221+ return summary . join ( "\n" )
222+ }
223+
224+ /**
225+ * Generates rules content using LLM analysis with fallback to deterministic approach
226+ */
227+ async function generateRulesWithLLM (
228+ workspacePath : string ,
229+ config : ProjectConfig ,
230+ apiConfiguration ?: ProviderSettings ,
231+ ) : Promise < string > {
232+ if ( ! apiConfiguration ) {
233+ // Fallback to deterministic generation
234+ return generateDeterministicRules ( config , workspacePath )
235+ }
236+
237+ try {
238+ const codebaseSummary = await generateCodebaseSummary ( workspacePath , config )
239+
240+ const prompt = `Please analyze this codebase and create a comprehensive rules file containing:
241+
242+ 1. Build/lint/test commands - especially for running a single test
243+ 2. Code style guidelines including imports, formatting, types, naming conventions, error handling, etc.
244+ 3. Project-specific conventions and best practices
245+
246+ The file you create will be given to agentic coding agents that operate in this repository. Make it about 20 lines long and focus on the most important rules for this specific project.
247+
248+ If there are existing rules files mentioned below, make sure to incorporate and improve upon them.
249+
250+ Here's the codebase analysis:
251+
252+ ${ codebaseSummary }
253+
254+ Please respond with only the rules content in markdown format, starting with "# Project Rules".`
255+
256+ const llmResponse = await singleCompletionHandler ( apiConfiguration , prompt )
257+
258+ // Validate that we got a reasonable response
259+ if ( llmResponse && llmResponse . trim ( ) . length > 100 && llmResponse . includes ( "# Project Rules" ) ) {
260+ return llmResponse . trim ( )
261+ } else {
262+ console . warn ( "LLM response was invalid, falling back to deterministic generation" )
263+ return generateDeterministicRules ( config , workspacePath )
264+ }
265+ } catch ( error ) {
266+ console . error ( "Error generating rules with LLM, falling back to deterministic generation:" , error )
267+ return generateDeterministicRules ( config , workspacePath )
268+ }
269+ }
270+
271+ /**
272+ * Generates rules content deterministically (fallback approach)
273+ */
274+ function generateDeterministicRules ( config : ProjectConfig , workspacePath : string ) : string {
116275 const sections : string [ ] = [ ]
117276
118277 // Header
@@ -196,37 +355,6 @@ function generateRulesContent(config: ProjectConfig, workspacePath: string): str
196355
197356 sections . push ( "" )
198357
199- // Project Structure
200- sections . push ( "## Project Structure" )
201- sections . push ( "" )
202- sections . push ( "- Follow the existing project structure and naming conventions" )
203- sections . push ( "- Place new files in appropriate directories" )
204- sections . push ( "- Use consistent file naming (kebab-case, camelCase, or PascalCase as per project convention)" )
205-
206- sections . push ( "" )
207-
208- // Language-specific rules
209- if ( config . type === "typescript" || config . type === "javascript" ) {
210- sections . push ( "## JavaScript/TypeScript Guidelines" )
211- sections . push ( "" )
212- sections . push ( "- Use ES6+ syntax (const/let, arrow functions, destructuring, etc.)" )
213- sections . push ( "- Prefer functional programming patterns where appropriate" )
214- sections . push ( "- Handle errors properly with try/catch blocks" )
215- sections . push ( "- Use async/await for asynchronous operations" )
216- sections . push ( "- Follow existing import/export patterns" )
217- sections . push ( "" )
218- }
219-
220- if ( config . type === "python" ) {
221- sections . push ( "## Python Guidelines" )
222- sections . push ( "" )
223- sections . push ( "- Follow PEP 8 style guide" )
224- sections . push ( "- Use type hints where appropriate" )
225- sections . push ( "- Write docstrings for functions and classes" )
226- sections . push ( "- Use virtual environments for dependency management" )
227- sections . push ( "" )
228- }
229-
230358 // General Best Practices
231359 sections . push ( "## General Best Practices" )
232360 sections . push ( "" )
@@ -238,35 +366,21 @@ function generateRulesContent(config: ProjectConfig, workspacePath: string): str
238366 sections . push ( "- Write meaningful commit messages" )
239367 sections . push ( "" )
240368
241- // Dependencies
242- if ( config . dependencies . length > 0 || config . devDependencies . length > 0 ) {
243- sections . push ( "## Key Dependencies" )
244- sections . push ( "" )
245-
246- // List some key dependencies
247- const keyDeps = [ ...config . dependencies , ...config . devDependencies ]
248- . filter ( ( dep ) => ! dep . startsWith ( "@types/" ) )
249- . slice ( 0 , 10 )
250-
251- keyDeps . forEach ( ( dep ) => {
252- sections . push ( `- ${ dep } ` )
253- } )
254-
255- sections . push ( "" )
256- }
257-
258369 return sections . join ( "\n" )
259370}
260371
261372/**
262373 * Generates rules for the workspace and saves them to a file
263374 */
264- export async function generateRulesForWorkspace ( workspacePath : string ) : Promise < string > {
375+ export async function generateRulesForWorkspace (
376+ workspacePath : string ,
377+ apiConfiguration ?: ProviderSettings ,
378+ ) : Promise < string > {
265379 // Analyze the project
266380 const config = await analyzeProjectConfig ( workspacePath )
267381
268- // Generate rules content
269- const rulesContent = generateRulesContent ( config , workspacePath )
382+ // Generate rules content using LLM with fallback
383+ const rulesContent = await generateRulesWithLLM ( workspacePath , config , apiConfiguration )
270384
271385 // Ensure .roo/rules directory exists
272386 const rooDir = getProjectRooDirectoryForCwd ( workspacePath )
0 commit comments