-
Notifications
You must be signed in to change notification settings - Fork 2.5k
feat: Automatic generation for rules file #5873
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
e09afaa
beb5f7a
586e237
8390379
236ba2c
3890db6
775c321
743278c
5706d85
4f26c7f
4154599
a7e4e5b
930536d
1789553
2cf2de0
bb59cf9
85c1b65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| import { describe, it, expect } from "vitest" | ||
| import { | ||
| generateRulesInstructions, | ||
| ruleTypeDefinitions, | ||
| RulesGenerationOptions, | ||
| RuleInstruction, | ||
| } from "../generate-rules" | ||
|
|
||
| describe("generateRulesInstructions", () => { | ||
| it("should generate instructions with all options enabled", () => { | ||
| const ruleInstructions: RuleInstruction[] = [ruleTypeDefinitions.general, ruleTypeDefinitions.code] | ||
|
|
||
| const options: RulesGenerationOptions = { | ||
| selectedRuleTypes: ["general", "code"], | ||
| addToGitignore: true, | ||
| alwaysAllowWriteProtected: true, | ||
| includeCustomRules: true, | ||
| customRulesText: "Always use TypeScript", | ||
| } | ||
|
|
||
| const result = generateRulesInstructions(ruleInstructions, options) | ||
|
|
||
| expect(result).toContain("Analyze this codebase and generate comprehensive rules") | ||
| expect(result).toContain("coding-standards.md") | ||
| expect(result).toContain("implementation-rules.md") | ||
| expect(result).toContain("The directory has already been created for you") | ||
| expect(result).toContain("Add the generated files to .gitignore") | ||
| expect(result).toContain("Always use TypeScript") | ||
| }) | ||
|
|
||
| it("should generate instructions with minimal options", () => { | ||
| const ruleInstructions: RuleInstruction[] = [ruleTypeDefinitions.general] | ||
|
|
||
| const options: RulesGenerationOptions = { | ||
| selectedRuleTypes: ["general"], | ||
| addToGitignore: false, | ||
| alwaysAllowWriteProtected: false, | ||
| includeCustomRules: false, | ||
| customRulesText: "", | ||
| } | ||
|
|
||
| const result = generateRulesInstructions(ruleInstructions, options) | ||
|
|
||
| expect(result).toContain("Analyze this codebase and generate comprehensive rules") | ||
| expect(result).toContain("coding-standards.md") | ||
| expect(result).toContain("Create the necessary directories if they don't exist") | ||
| expect(result).not.toContain("Add the generated files to .gitignore") | ||
| expect(result).not.toContain("Additional rules from User") | ||
| }) | ||
|
|
||
| it("should handle all rule types", () => { | ||
| const allRuleTypes = Object.keys(ruleTypeDefinitions) | ||
| const ruleInstructions: RuleInstruction[] = allRuleTypes.map( | ||
| (type) => ruleTypeDefinitions[type as keyof typeof ruleTypeDefinitions], | ||
| ) | ||
|
|
||
| const options: RulesGenerationOptions = { | ||
| selectedRuleTypes: allRuleTypes, | ||
| addToGitignore: false, | ||
| alwaysAllowWriteProtected: false, | ||
| includeCustomRules: false, | ||
| customRulesText: "", | ||
| } | ||
|
|
||
| const result = generateRulesInstructions(ruleInstructions, options) | ||
|
|
||
| expect(result).toContain("coding-standards.md") | ||
| expect(result).toContain("implementation-rules.md") | ||
| expect(result).toContain("architecture-rules.md") | ||
| expect(result).toContain("debugging-rules.md") | ||
| expect(result).toContain("documentation-rules.md") | ||
| }) | ||
| }) | ||
|
|
||
| describe("ruleTypeDefinitions", () => { | ||
| it("should have all expected rule types", () => { | ||
| expect(ruleTypeDefinitions).toHaveProperty("general") | ||
| expect(ruleTypeDefinitions).toHaveProperty("code") | ||
| expect(ruleTypeDefinitions).toHaveProperty("architect") | ||
| expect(ruleTypeDefinitions).toHaveProperty("debug") | ||
| expect(ruleTypeDefinitions).toHaveProperty("docs-extractor") | ||
| }) | ||
|
|
||
| it("should have proper structure for each rule type", () => { | ||
| Object.values(ruleTypeDefinitions).forEach((rule) => { | ||
| expect(rule).toHaveProperty("path") | ||
| expect(rule).toHaveProperty("focus") | ||
| expect(rule).toHaveProperty("analysisSteps") | ||
| expect(Array.isArray(rule.analysisSteps)).toBe(true) | ||
| expect(rule.analysisSteps.length).toBeGreaterThan(0) | ||
| }) | ||
| }) | ||
|
|
||
| it("should have correct paths for each rule type", () => { | ||
| expect(ruleTypeDefinitions.general.path).toBe(".roo/rules/coding-standards.md") | ||
| expect(ruleTypeDefinitions.code.path).toBe(".roo/rules-code/implementation-rules.md") | ||
| expect(ruleTypeDefinitions.architect.path).toBe(".roo/rules-architect/architecture-rules.md") | ||
| expect(ruleTypeDefinitions.debug.path).toBe(".roo/rules-debug/debugging-rules.md") | ||
| expect(ruleTypeDefinitions["docs-extractor"].path).toBe(".roo/rules-docs-extractor/documentation-rules.md") | ||
| }) | ||
|
|
||
| it("should include proper instructions for existing rule files", () => { | ||
| const ruleInstructions: RuleInstruction[] = [ruleTypeDefinitions.general] | ||
| const options: RulesGenerationOptions = { | ||
| selectedRuleTypes: ["general"], | ||
| addToGitignore: false, | ||
| alwaysAllowWriteProtected: false, | ||
| includeCustomRules: false, | ||
| customRulesText: "", | ||
| } | ||
|
|
||
| const result = generateRulesInstructions(ruleInstructions, options) | ||
|
|
||
| expect(result).toContain("Look for existing rule files") | ||
| expect(result).toContain("CLAUDE.md, .cursorrules, .cursor/rules, or .github/copilot-instructions.md") | ||
| expect(result).toContain("If found, incorporate and improve upon their content") | ||
| }) | ||
|
|
||
| it("should include proper formatting instructions", () => { | ||
| const ruleInstructions: RuleInstruction[] = [ruleTypeDefinitions.general] | ||
| const options: RulesGenerationOptions = { | ||
| selectedRuleTypes: ["general"], | ||
| addToGitignore: false, | ||
| alwaysAllowWriteProtected: false, | ||
| includeCustomRules: false, | ||
| customRulesText: "", | ||
| } | ||
|
|
||
| const result = generateRulesInstructions(ruleInstructions, options) | ||
|
|
||
| expect(result).toContain("Make the rules actionable and specific") | ||
| expect(result).toContain("Build/lint/test commands") | ||
| expect(result).toContain("Code style guidelines") | ||
| expect(result).toContain("Error handling patterns") | ||
| expect(result).toContain("Keep rules concise") | ||
| expect(result).toContain("aim for 20 lines per file") | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| export interface RuleInstruction { | ||
| path: string | ||
| focus: string | ||
| analysisSteps: string[] | ||
| } | ||
|
|
||
| export interface RulesGenerationOptions { | ||
| selectedRuleTypes: string[] | ||
| addToGitignore: boolean | ||
| alwaysAllowWriteProtected: boolean | ||
| includeCustomRules: boolean | ||
| customRulesText: string | ||
| } | ||
|
|
||
| export function generateRulesInstructions( | ||
| ruleInstructions: RuleInstruction[], | ||
| options: RulesGenerationOptions, | ||
| ): string { | ||
| const { addToGitignore, alwaysAllowWriteProtected, includeCustomRules, customRulesText } = options | ||
|
|
||
| return `Analyze this codebase and generate comprehensive rules for AI agents working in this repository. | ||
| Your task is to: | ||
| 1. **Analyze the project structure** by: | ||
| ${ruleInstructions.map((rule) => ` - For ${rule.path.split("/").pop()}: ${rule.analysisSteps.join("; ")}`).join("\n")} | ||
| 2. **Look for existing rule files** that might provide guidance: | ||
| - Check for CLAUDE.md, .cursorrules, .cursor/rules, or .github/copilot-instructions.md | ||
| - If found, incorporate and improve upon their content | ||
| 3. **Generate and save the following rule files**: | ||
| ${ruleInstructions | ||
| .map( | ||
| (rule, index) => ` | ||
| ${index + 1}. **${rule.path}** | ||
| - Focus: ${rule.focus}${alwaysAllowWriteProtected ? "\n - The directory has already been created for you" : "\n - Create the necessary directories if they don't exist"} | ||
| - Always overwrite the existing file if it exists | ||
| - Use the \`write_to_file\` tool to save the content${alwaysAllowWriteProtected ? "\n - Note: Auto-approval for protected file writes is enabled, so you can write to .roo directories without manual approval" : "\n - Note: You will need to approve the creation of protected directories and files"}`, | ||
| ) | ||
| .join("\n")} | ||
| 4. **Make the rules actionable and specific** by including: | ||
| - Build/lint/test commands (especially for running single tests) | ||
| - Code style guidelines including imports, formatting, types, naming conventions | ||
| - Error handling patterns specific to this project | ||
| - Project-specific conventions and best practices | ||
| - File organization patterns | ||
| 5. **Keep rules concise** - aim for 20 lines per file, focusing on the most important guidelines | ||
| ${ | ||
| addToGitignore | ||
| ? `6. **Add the generated files to .gitignore**: | ||
| - After generating all rule files, add entries to .gitignore to prevent them from being committed | ||
| - Add each generated file path to .gitignore (e.g., .roo/rules/coding-standards.md) | ||
| - If .gitignore doesn't exist, create it | ||
| - If the entries already exist in .gitignore, don't duplicate them` | ||
| : "" | ||
| } | ||
| ${ | ||
| includeCustomRules && customRulesText | ||
| ? `\n**Additional rules from User to add to the rules file:**\n${customRulesText}` | ||
| : "" | ||
| }` | ||
| } | ||
|
|
||
| export const ruleTypeDefinitions = { | ||
| general: { | ||
| path: ".roo/rules/coding-standards.md", | ||
| focus: "General coding standards that apply to all modes, including naming conventions, file organization, and general best practices", | ||
| analysisSteps: [ | ||
| "Examine the project structure and file organization patterns", | ||
| "Identify naming conventions for files, functions, variables, and classes", | ||
| "Look for general coding patterns and conventions used throughout the codebase", | ||
| "Check for any existing documentation or README files that describe project standards", | ||
| ], | ||
| }, | ||
|
Comment on lines
+70
to
+79
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At least in my testing this didn't seem to generate very useful rules. I think it might be worth tweaking the prompt, especially since this is the content that will end up in the system prompt for every one of the modes (including the custom modes). What do you think the most important information for the model to know is? What does Claude Code do? |
||
| code: { | ||
| path: ".roo/rules-code/implementation-rules.md", | ||
| focus: "Specific rules for code implementation, focusing on syntax patterns, code structure, error handling, testing approaches, and detailed implementation guidelines", | ||
| analysisSteps: [ | ||
| "Analyze package.json or equivalent files to identify dependencies and build tools", | ||
| "Check for linting and formatting tools (ESLint, Prettier, etc.) and their configurations", | ||
| "Examine test files to understand testing patterns and frameworks used", | ||
| "Look for error handling patterns and logging strategies", | ||
| "Identify code style preferences and import/export patterns", | ||
| "Check for TypeScript usage and type definition patterns if applicable", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The typescript mention might be a little too specific? |
||
| ], | ||
| }, | ||
| architect: { | ||
| path: ".roo/rules-architect/architecture-rules.md", | ||
| focus: "High-level system design rules, focusing on file layout, module organization, architectural patterns, and system-wide design principles", | ||
| analysisSteps: [ | ||
| "Analyze the overall directory structure and module organization", | ||
| "Identify architectural patterns (MVC, microservices, monorepo, etc.)", | ||
| "Look for separation of concerns and layering patterns", | ||
| "Check for API design patterns and service boundaries", | ||
| "Examine how different parts of the system communicate", | ||
| ], | ||
| }, | ||
| debug: { | ||
| path: ".roo/rules-debug/debugging-rules.md", | ||
| focus: "Debugging workflow rules, including error investigation approaches, logging strategies, troubleshooting patterns, and debugging best practices", | ||
| analysisSteps: [ | ||
| "Identify logging frameworks and patterns used in the codebase", | ||
| "Look for error handling and exception patterns", | ||
| "Check for debugging tools or scripts in the project", | ||
| "Analyze test structure for debugging approaches", | ||
| "Look for monitoring or observability patterns", | ||
| ], | ||
| }, | ||
| "docs-extractor": { | ||
| path: ".roo/rules-docs-extractor/documentation-rules.md", | ||
| focus: "Documentation extraction and formatting rules, including documentation style guides, API documentation patterns, and content organization", | ||
| analysisSteps: [ | ||
| "Check for existing documentation files and their formats", | ||
| "Analyze code comments and documentation patterns", | ||
| "Look for API documentation tools or generators", | ||
| "Identify documentation structure and organization patterns", | ||
| "Check for examples or tutorials in the codebase", | ||
| ], | ||
| }, | ||
|
Comment on lines
+114
to
+124
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should have this option - it's only applicable in the Roo Code repo |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1861,6 +1861,91 @@ export const webviewMessageHandler = async ( | |
| }) | ||
| } | ||
| break | ||
| case "generateRules": | ||
liwilliam2021 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Generate rules for the current workspace by spawning a new task | ||
| try { | ||
| // Import the rules generation service | ||
| const { handleGenerateRules } = await import("../../services/rules/rulesGenerator") | ||
|
|
||
| // Call the refactored function with all necessary parameters | ||
| await handleGenerateRules( | ||
| provider, | ||
| { | ||
| selectedRuleTypes: message.selectedRuleTypes, | ||
| addToGitignore: message.addToGitignore, | ||
| alwaysAllowWriteProtected: message.alwaysAllowWriteProtected, | ||
| apiConfigName: message.apiConfigName, | ||
| includeCustomRules: message.includeCustomRules, | ||
| customRulesText: message.customRulesText, | ||
| }, | ||
| getGlobalState, | ||
| updateGlobalState, | ||
| ) | ||
| } catch (error) { | ||
| // Show error message to user | ||
| const errorMessage = error instanceof Error ? error.message : String(error) | ||
| vscode.window.showErrorMessage(`Failed to generate rules: ${errorMessage}`) | ||
| } | ||
| break | ||
| case "checkExistingRuleFiles": | ||
| // Check which rule files already exist and count source files | ||
| try { | ||
| const workspacePath = getWorkspacePath() | ||
| if (!workspacePath) { | ||
| break | ||
| } | ||
|
|
||
| const { fileExistsAtPath } = await import("../../utils/fs") | ||
| const path = await import("path") | ||
| const fs = await import("fs/promises") | ||
|
|
||
| const ruleTypeToPath: Record<string, string> = { | ||
| general: path.join(workspacePath, ".roo", "rules", "coding-standards.md"), | ||
| code: path.join(workspacePath, ".roo", "rules-code", "implementation-rules.md"), | ||
| architect: path.join(workspacePath, ".roo", "rules-architect", "architecture-rules.md"), | ||
| debug: path.join(workspacePath, ".roo", "rules-debug", "debugging-rules.md"), | ||
| "docs-extractor": path.join( | ||
| workspacePath, | ||
| ".roo", | ||
| "rules-docs-extractor", | ||
| "documentation-rules.md", | ||
| ), | ||
| } | ||
|
|
||
| const existingFiles: string[] = [] | ||
| for (const [type, filePath] of Object.entries(ruleTypeToPath)) { | ||
| if (await fileExistsAtPath(filePath)) { | ||
| existingFiles.push(type) | ||
| } | ||
| } | ||
|
|
||
| // Count all files in the workspace | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we can use one of the existing ways of listing files in the workspace so the logic is consistent |
||
| let sourceFileCount = 0 | ||
| try { | ||
| // Use VS Code API to count all files | ||
| const vscode = await import("vscode") | ||
|
|
||
| // Find all files (excluding common non-project files) | ||
| const pattern = "**/*" | ||
| const excludePattern = | ||
| "**/node_modules/**,**/.git/**,**/dist/**,**/build/**,**/.next/**,**/.nuxt/**,**/coverage/**,**/.cache/**" | ||
|
|
||
| const files = await vscode.workspace.findFiles(pattern, excludePattern) | ||
| sourceFileCount = files.length | ||
| } catch (error) { | ||
| // If counting fails, set to -1 to indicate unknown | ||
| sourceFileCount = -1 | ||
| } | ||
|
|
||
| await provider.postMessageToWebview({ | ||
| type: "existingRuleFiles", | ||
| files: existingFiles, | ||
| sourceFileCount, | ||
| }) | ||
| } catch (error) { | ||
| // Silently fail - not critical | ||
| } | ||
| break | ||
| case "humanRelayResponse": | ||
| if (message.requestId && message.text) { | ||
| vscode.commands.executeCommand(getCommand("handleHumanRelayResponse"), { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the prompt need to know whether alwaysAllowWriteProtected is enabled? I wouldn't think so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I just read a little more. Zooming out I don't think this feature should be interacting with the alwaysAllowWriteProtected setting the way it is (or encouraging people to set it to true as part of this feature).