mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
UI changes + refactor
This commit is contained in:
parent
83903797cc
commit
236ba2c1e7
4 changed files with 310 additions and 370 deletions
100
src/services/rules/__tests__/rulesGenerator.test.ts
Normal file
100
src/services/rules/__tests__/rulesGenerator.test.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
||||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import { createRulesGenerationTaskMessage } from "../rulesGenerator"
|
||||
|
||||
// Mock fs module
|
||||
vi.mock("fs/promises", () => ({
|
||||
mkdir: vi.fn(),
|
||||
}))
|
||||
|
||||
describe("rulesGenerator", () => {
|
||||
const mockWorkspacePath = "/test/workspace"
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe("createRulesGenerationTaskMessage", () => {
|
||||
it("should create directories when alwaysAllowWriteProtected is true", async () => {
|
||||
await createRulesGenerationTaskMessage(mockWorkspacePath, ["general"], false, true)
|
||||
|
||||
// Verify mkdir was called for each directory
|
||||
expect(fs.mkdir).toHaveBeenCalledWith(path.join(mockWorkspacePath, ".roo", "rules"), { recursive: true })
|
||||
expect(fs.mkdir).toHaveBeenCalledWith(path.join(mockWorkspacePath, ".roo", "rules-code"), {
|
||||
recursive: true,
|
||||
})
|
||||
expect(fs.mkdir).toHaveBeenCalledWith(path.join(mockWorkspacePath, ".roo", "rules-architect"), {
|
||||
recursive: true,
|
||||
})
|
||||
expect(fs.mkdir).toHaveBeenCalledWith(path.join(mockWorkspacePath, ".roo", "rules-debug"), {
|
||||
recursive: true,
|
||||
})
|
||||
expect(fs.mkdir).toHaveBeenCalledWith(path.join(mockWorkspacePath, ".roo", "rules-docs-extractor"), {
|
||||
recursive: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("should NOT create directories when alwaysAllowWriteProtected is false", async () => {
|
||||
await createRulesGenerationTaskMessage(mockWorkspacePath, ["general"], false, false)
|
||||
|
||||
// Verify mkdir was NOT called
|
||||
expect(fs.mkdir).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should include auto-approval note in message when alwaysAllowWriteProtected is true", async () => {
|
||||
const message = await createRulesGenerationTaskMessage(mockWorkspacePath, ["general"], false, true)
|
||||
|
||||
expect(message).toContain("The directory has already been created for you")
|
||||
expect(message).toContain("Auto-approval for protected file writes is enabled")
|
||||
})
|
||||
|
||||
it("should include manual approval note in message when alwaysAllowWriteProtected is false", async () => {
|
||||
const message = await createRulesGenerationTaskMessage(mockWorkspacePath, ["general"], false, false)
|
||||
|
||||
expect(message).toContain("Create the necessary directories if they don't exist")
|
||||
expect(message).toContain("You will need to approve the creation of protected directories and files")
|
||||
})
|
||||
|
||||
it("should handle multiple rule types", async () => {
|
||||
const message = await createRulesGenerationTaskMessage(
|
||||
mockWorkspacePath,
|
||||
["general", "code", "architect"],
|
||||
false,
|
||||
true,
|
||||
)
|
||||
|
||||
expect(message).toContain(".roo/rules/coding-standards.md")
|
||||
expect(message).toContain(".roo/rules-code/implementation-rules.md")
|
||||
expect(message).toContain(".roo/rules-architect/architecture-rules.md")
|
||||
})
|
||||
|
||||
it("should include gitignore instructions when addToGitignore is true", async () => {
|
||||
const message = await createRulesGenerationTaskMessage(mockWorkspacePath, ["general"], true, false)
|
||||
|
||||
expect(message).toContain("Add the generated files to .gitignore")
|
||||
})
|
||||
|
||||
it("should include analysis steps for each rule type", async () => {
|
||||
const message = await createRulesGenerationTaskMessage(mockWorkspacePath, ["code"], false, false)
|
||||
|
||||
// Check that code-specific analysis steps are included
|
||||
expect(message).toContain("Analyze package.json or equivalent files")
|
||||
expect(message).toContain("Check for linting and formatting tools")
|
||||
expect(message).toContain("Examine test files to understand testing patterns")
|
||||
})
|
||||
|
||||
it("should include different analysis steps for different rule types", async () => {
|
||||
const message = await createRulesGenerationTaskMessage(mockWorkspacePath, ["architect"], false, false)
|
||||
|
||||
// Check that architect-specific analysis steps are included
|
||||
expect(message).toContain("Analyze the overall directory structure")
|
||||
expect(message).toContain("Identify architectural patterns")
|
||||
expect(message).toContain("separation of concerns")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,205 +1,5 @@
|
|||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
|
||||
interface ProjectConfig {
|
||||
type: "typescript" | "javascript" | "python" | "java" | "go" | "rust" | "unknown"
|
||||
hasTypeScript: boolean
|
||||
hasESLint: boolean
|
||||
hasPrettier: boolean
|
||||
hasJest: boolean
|
||||
hasVitest: boolean
|
||||
hasPytest: boolean
|
||||
packageManager: "npm" | "yarn" | "pnpm" | "bun" | null
|
||||
dependencies: string[]
|
||||
devDependencies: string[]
|
||||
scripts: Record<string, string>
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the project configuration files to determine project type and tools
|
||||
*/
|
||||
async function analyzeProjectConfig(workspacePath: string): Promise<ProjectConfig> {
|
||||
const config: ProjectConfig = {
|
||||
type: "unknown",
|
||||
hasTypeScript: false,
|
||||
hasESLint: false,
|
||||
hasPrettier: false,
|
||||
hasJest: false,
|
||||
hasVitest: false,
|
||||
hasPytest: false,
|
||||
packageManager: null,
|
||||
dependencies: [],
|
||||
devDependencies: [],
|
||||
scripts: {},
|
||||
}
|
||||
|
||||
// Check for package.json
|
||||
const packageJsonPath = path.join(workspacePath, "package.json")
|
||||
if (await fileExistsAtPath(packageJsonPath)) {
|
||||
try {
|
||||
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"))
|
||||
|
||||
// Determine package manager
|
||||
if (await fileExistsAtPath(path.join(workspacePath, "yarn.lock"))) {
|
||||
config.packageManager = "yarn"
|
||||
} else if (await fileExistsAtPath(path.join(workspacePath, "pnpm-lock.yaml"))) {
|
||||
config.packageManager = "pnpm"
|
||||
} else if (await fileExistsAtPath(path.join(workspacePath, "bun.lockb"))) {
|
||||
config.packageManager = "bun"
|
||||
} else if (await fileExistsAtPath(path.join(workspacePath, "package-lock.json"))) {
|
||||
config.packageManager = "npm"
|
||||
}
|
||||
|
||||
// Extract dependencies
|
||||
config.dependencies = Object.keys(packageJson.dependencies || {})
|
||||
config.devDependencies = Object.keys(packageJson.devDependencies || {})
|
||||
config.scripts = packageJson.scripts || {}
|
||||
|
||||
// Check for TypeScript
|
||||
if (
|
||||
config.devDependencies.includes("typescript") ||
|
||||
config.dependencies.includes("typescript") ||
|
||||
(await fileExistsAtPath(path.join(workspacePath, "tsconfig.json")))
|
||||
) {
|
||||
config.hasTypeScript = true
|
||||
config.type = "typescript"
|
||||
} else {
|
||||
config.type = "javascript"
|
||||
}
|
||||
|
||||
// Check for testing frameworks
|
||||
if (config.devDependencies.includes("jest") || config.dependencies.includes("jest")) {
|
||||
config.hasJest = true
|
||||
}
|
||||
if (config.devDependencies.includes("vitest") || config.dependencies.includes("vitest")) {
|
||||
config.hasVitest = true
|
||||
}
|
||||
|
||||
// Check for linting/formatting
|
||||
if (config.devDependencies.includes("eslint") || config.dependencies.includes("eslint")) {
|
||||
config.hasESLint = true
|
||||
}
|
||||
if (config.devDependencies.includes("prettier") || config.dependencies.includes("prettier")) {
|
||||
config.hasPrettier = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing package.json:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Python project
|
||||
if (await fileExistsAtPath(path.join(workspacePath, "requirements.txt"))) {
|
||||
config.type = "python"
|
||||
} else if (await fileExistsAtPath(path.join(workspacePath, "pyproject.toml"))) {
|
||||
config.type = "python"
|
||||
// Check for pytest
|
||||
try {
|
||||
const pyprojectContent = await fs.readFile(path.join(workspacePath, "pyproject.toml"), "utf-8")
|
||||
if (pyprojectContent.includes("pytest")) {
|
||||
config.hasPytest = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error reading pyproject.toml:", error)
|
||||
}
|
||||
} else if (await fileExistsAtPath(path.join(workspacePath, "setup.py"))) {
|
||||
config.type = "python"
|
||||
}
|
||||
|
||||
// Check for other project types
|
||||
if (await fileExistsAtPath(path.join(workspacePath, "go.mod"))) {
|
||||
config.type = "go"
|
||||
} else if (await fileExistsAtPath(path.join(workspacePath, "Cargo.toml"))) {
|
||||
config.type = "rust"
|
||||
} else if (await fileExistsAtPath(path.join(workspacePath, "pom.xml"))) {
|
||||
config.type = "java"
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a summary of the codebase for LLM analysis
|
||||
*/
|
||||
async function generateCodebaseSummary(workspacePath: string, config: ProjectConfig): Promise<string> {
|
||||
const summary: string[] = []
|
||||
|
||||
summary.push("## Project Configuration Analysis")
|
||||
summary.push("")
|
||||
summary.push(`**Project Type:** ${config.type}`)
|
||||
summary.push(`**Package Manager:** ${config.packageManager || "None detected"}`)
|
||||
summary.push("")
|
||||
|
||||
// List key dependencies
|
||||
if (config.dependencies.length > 0) {
|
||||
summary.push("### Key Dependencies:")
|
||||
const keyDeps = config.dependencies.slice(0, 10)
|
||||
keyDeps.forEach((dep) => summary.push(`- ${dep}`))
|
||||
if (config.dependencies.length > 10) {
|
||||
summary.push(`- ... and ${config.dependencies.length - 10} more`)
|
||||
}
|
||||
summary.push("")
|
||||
}
|
||||
|
||||
// List dev dependencies
|
||||
if (config.devDependencies.length > 0) {
|
||||
summary.push("### Development Dependencies:")
|
||||
const keyDevDeps = config.devDependencies.slice(0, 10)
|
||||
keyDevDeps.forEach((dep) => summary.push(`- ${dep}`))
|
||||
if (config.devDependencies.length > 10) {
|
||||
summary.push(`- ... and ${config.devDependencies.length - 10} more`)
|
||||
}
|
||||
summary.push("")
|
||||
}
|
||||
|
||||
// List available scripts
|
||||
const scriptKeys = Object.keys(config.scripts)
|
||||
if (scriptKeys.length > 0) {
|
||||
summary.push("### Available Scripts:")
|
||||
scriptKeys.forEach((script) => summary.push(`- ${script}: ${config.scripts[script]}`))
|
||||
summary.push("")
|
||||
}
|
||||
|
||||
// List detected tools
|
||||
summary.push("### Detected Tools and Frameworks:")
|
||||
const tools: string[] = []
|
||||
if (config.hasTypeScript) tools.push("TypeScript")
|
||||
if (config.hasESLint) tools.push("ESLint")
|
||||
if (config.hasPrettier) tools.push("Prettier")
|
||||
if (config.hasJest) tools.push("Jest")
|
||||
if (config.hasVitest) tools.push("Vitest")
|
||||
if (config.hasPytest) tools.push("Pytest")
|
||||
|
||||
if (tools.length === 0) {
|
||||
tools.push("No specific tools detected")
|
||||
}
|
||||
|
||||
tools.forEach((tool) => summary.push(`- ${tool}`))
|
||||
summary.push("")
|
||||
|
||||
// Check for existing rules files
|
||||
const existingRulesFiles: string[] = []
|
||||
if (await fileExistsAtPath(path.join(workspacePath, "CLAUDE.md"))) {
|
||||
existingRulesFiles.push("CLAUDE.md")
|
||||
}
|
||||
if (await fileExistsAtPath(path.join(workspacePath, ".cursorrules"))) {
|
||||
existingRulesFiles.push(".cursorrules")
|
||||
}
|
||||
if (await fileExistsAtPath(path.join(workspacePath, ".cursor", "rules"))) {
|
||||
existingRulesFiles.push(".cursor/rules")
|
||||
}
|
||||
if (await fileExistsAtPath(path.join(workspacePath, ".github", "copilot-instructions.md"))) {
|
||||
existingRulesFiles.push(".github/copilot-instructions.md")
|
||||
}
|
||||
|
||||
if (existingRulesFiles.length > 0) {
|
||||
summary.push("## Existing Rules Files:")
|
||||
existingRulesFiles.forEach((file) => summary.push(`- ${file}`))
|
||||
summary.push("")
|
||||
}
|
||||
|
||||
return summary.join("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a comprehensive task message for rules generation that can be used with initClineWithTask
|
||||
|
|
@ -210,24 +10,22 @@ export async function createRulesGenerationTaskMessage(
|
|||
addToGitignore: boolean,
|
||||
alwaysAllowWriteProtected: boolean = false,
|
||||
): Promise<string> {
|
||||
// Analyze the project to get context
|
||||
const config = await analyzeProjectConfig(workspacePath)
|
||||
const codebaseSummary = await generateCodebaseSummary(workspacePath, config)
|
||||
// Only create directories if auto-approve is enabled
|
||||
if (alwaysAllowWriteProtected) {
|
||||
const directoriesToCreate = [
|
||||
path.join(workspacePath, ".roo", "rules"),
|
||||
path.join(workspacePath, ".roo", "rules-code"),
|
||||
path.join(workspacePath, ".roo", "rules-architect"),
|
||||
path.join(workspacePath, ".roo", "rules-debug"),
|
||||
path.join(workspacePath, ".roo", "rules-docs-extractor"),
|
||||
]
|
||||
|
||||
// Ensure all necessary directories exist at project root
|
||||
const directoriesToCreate = [
|
||||
path.join(workspacePath, ".roo", "rules"),
|
||||
path.join(workspacePath, ".roo", "rules-code"),
|
||||
path.join(workspacePath, ".roo", "rules-architect"),
|
||||
path.join(workspacePath, ".roo", "rules-debug"),
|
||||
path.join(workspacePath, ".roo", "rules-docs-extractor"),
|
||||
]
|
||||
|
||||
for (const dir of directoriesToCreate) {
|
||||
try {
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
} catch (error) {
|
||||
// Directory might already exist, which is fine
|
||||
for (const dir of directoriesToCreate) {
|
||||
try {
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
} catch (error) {
|
||||
// Directory might already exist, which is fine
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,6 +33,7 @@ export async function createRulesGenerationTaskMessage(
|
|||
interface RuleInstruction {
|
||||
path: string
|
||||
focus: string
|
||||
analysisSteps: string[]
|
||||
}
|
||||
|
||||
const ruleInstructions: RuleInstruction[] = selectedRuleTypes
|
||||
|
|
@ -244,26 +43,61 @@ export async function createRulesGenerationTaskMessage(
|
|||
return {
|
||||
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",
|
||||
],
|
||||
}
|
||||
case "code":
|
||||
return {
|
||||
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",
|
||||
],
|
||||
}
|
||||
case "architect":
|
||||
return {
|
||||
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",
|
||||
],
|
||||
}
|
||||
case "debug":
|
||||
return {
|
||||
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",
|
||||
],
|
||||
}
|
||||
case "docs-extractor":
|
||||
return {
|
||||
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",
|
||||
],
|
||||
}
|
||||
default:
|
||||
return null
|
||||
|
|
@ -276,40 +110,40 @@ export async function createRulesGenerationTaskMessage(
|
|||
|
||||
Your task is to:
|
||||
|
||||
1. **Analyze the project structure** - The codebase has been analyzed and here's what was found:
|
||||
1. **Analyze the project structure** by:
|
||||
${ruleInstructions.map((rule) => ` - For ${rule.path.split("/").pop()}: ${rule.analysisSteps.join("; ")}`).join("\n")}
|
||||
|
||||
${codebaseSummary}
|
||||
|
||||
2. **Create comprehensive rules** that include:
|
||||
- Build/lint/test commands (especially for running single tests)
|
||||
- Code style guidelines including imports, formatting, types, naming conventions
|
||||
- Error handling patterns
|
||||
- Project-specific conventions and best practices
|
||||
- File organization patterns
|
||||
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}
|
||||
- The directory has already been created for you
|
||||
- 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" : ""}`,
|
||||
- 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. **Open the generated file** in the editor for review
|
||||
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
|
||||
|
||||
The rules should be about 20-30 lines long and focus on the most important guidelines for this specific project. Make them actionable and specific to help AI agents work effectively in this codebase.
|
||||
5. **Keep rules concise** - aim for 20-30 lines per file, focusing on the most important guidelines
|
||||
|
||||
If there are existing rules files (like CLAUDE.md, .cursorrules, .cursor/rules, .github/copilot-instructions.md), incorporate and improve upon them.
|
||||
6. **Open the generated files** in the editor for review after creation
|
||||
|
||||
Use the \`safeWriteJson\` utility from \`src/utils/safeWriteJson.ts\` for any JSON file operations to ensure atomic writes.
|
||||
|
||||
${
|
||||
addToGitignore
|
||||
? `5. **Add the generated files to .gitignore**:
|
||||
? `7. **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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { HTMLAttributes, useState, useEffect } from "react"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { FileText, Loader2, AlertTriangle, Info } from "lucide-react"
|
||||
import { FileText, Loader2, AlertTriangle, Info, Sparkles } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
|
@ -156,7 +156,7 @@ export const RulesSettings = ({ className, ...props }: RulesSettingsProps) => {
|
|||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-2", className)} {...props}>
|
||||
<SectionHeader>
|
||||
<SectionHeader description={t("settings:rules.description")}>
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="w-4" />
|
||||
<div>{t("settings:rules.title")}</div>
|
||||
|
|
@ -164,132 +164,135 @@ export const RulesSettings = ({ className, ...props }: RulesSettingsProps) => {
|
|||
</SectionHeader>
|
||||
|
||||
<Section>
|
||||
<div className="space-y-4">
|
||||
<p className="text-vscode-descriptionForeground text-sm">{t("settings:rules.description")}</p>
|
||||
|
||||
{/* Recommendation box */}
|
||||
<div className="flex items-start gap-2 p-3 bg-vscode-inputValidation-infoBackground border border-vscode-inputValidation-infoBorder rounded-md">
|
||||
<Info className="w-4 h-4 text-vscode-inputValidation-infoForeground mt-0.5 flex-shrink-0" />
|
||||
<div className="text-sm text-vscode-inputValidation-infoForeground">
|
||||
{t("settings:rules.autoApproveRecommendation")}
|
||||
<div className="space-y-6">
|
||||
{/* Magic Rules Generation subsection */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2 font-bold">
|
||||
<Sparkles className="w-4 h-4" />
|
||||
<div>{t("settings:rules.magicGeneration.title")}</div>
|
||||
</div>
|
||||
<div className="text-vscode-descriptionForeground">
|
||||
{t("settings:rules.magicGeneration.description")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 pl-3 border-l-2 border-vscode-button-background">
|
||||
{/* Recommendation box */}
|
||||
<div className="flex items-start gap-2 p-3 bg-vscode-inputValidation-infoBackground border border-vscode-inputValidation-infoBorder rounded-md">
|
||||
<Info className="w-4 h-4 text-vscode-inputValidation-infoForeground mt-0.5 flex-shrink-0" />
|
||||
<div className="text-sm text-vscode-inputValidation-infoForeground">
|
||||
{t("settings:rules.autoApproveRecommendation")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-3">{t("settings:rules.selectTypes")}</h4>
|
||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-2">
|
||||
{ruleTypes.map((ruleType) => (
|
||||
<div
|
||||
key={ruleType.id}
|
||||
onClick={() => handleRuleTypeToggle(ruleType.id)}
|
||||
className={cn(
|
||||
"relative p-3 rounded-md border cursor-pointer transition-all",
|
||||
"hover:border-vscode-focusBorder",
|
||||
ruleType.checked
|
||||
? "bg-vscode-list-activeSelectionBackground border-vscode-focusBorder"
|
||||
: "bg-vscode-editor-background border-vscode-panel-border",
|
||||
)}>
|
||||
<div className="flex-1">
|
||||
<div className="text-sm font-medium flex items-center gap-1">
|
||||
{ruleType.label}
|
||||
{ruleType.exists && (
|
||||
<span
|
||||
className="text-vscode-testing-iconQueued"
|
||||
title={t("settings:rules.fileExists")}>
|
||||
•
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-1">
|
||||
{ruleType.description}
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-3">{t("settings:rules.selectTypes")}</h4>
|
||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-2">
|
||||
{ruleTypes.map((ruleType) => (
|
||||
<div
|
||||
key={ruleType.id}
|
||||
onClick={() => handleRuleTypeToggle(ruleType.id)}
|
||||
className={cn(
|
||||
"relative p-3 rounded-md border cursor-pointer transition-all",
|
||||
"hover:border-vscode-focusBorder",
|
||||
ruleType.checked
|
||||
? "bg-vscode-list-activeSelectionBackground border-vscode-focusBorder"
|
||||
: "bg-vscode-editor-background border-vscode-panel-border",
|
||||
)}>
|
||||
<div className="flex-1">
|
||||
<div className="text-sm font-medium flex items-center gap-1">
|
||||
{ruleType.label}
|
||||
{ruleType.exists && (
|
||||
<span
|
||||
className="text-vscode-testing-iconQueued"
|
||||
title={t("settings:rules.fileExists")}>
|
||||
•
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-1">
|
||||
{ruleType.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasExistingFiles && (
|
||||
<div className="flex items-start gap-2 p-3 bg-vscode-inputValidation-warningBackground border border-vscode-inputValidation-warningBorder rounded-md">
|
||||
<AlertTriangle className="w-4 h-4 text-vscode-inputValidation-warningForeground mt-0.5 flex-shrink-0" />
|
||||
<div className="text-sm text-vscode-inputValidation-warningForeground">
|
||||
<div>{t("settings:rules.overwriteWarning")}</div>
|
||||
<ul className="mt-1 ml-4 list-disc">
|
||||
{existingRules.map((rule) => (
|
||||
<li key={rule.id}>{rule.label}</li>
|
||||
))}
|
||||
</ul>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="border-t border-vscode-panel-border pt-4">
|
||||
<label className="flex items-center gap-2 cursor-pointer hover:opacity-80">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={addToGitignore}
|
||||
onChange={(e) => setAddToGitignore(e.target.checked)}
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium">{t("settings:rules.addToGitignore")}</div>
|
||||
<div className="text-xs text-vscode-descriptionForeground">
|
||||
{t("settings:rules.addToGitignoreDescription")}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-vscode-panel-border pt-4">
|
||||
<label className="flex items-center gap-2 cursor-pointer hover:opacity-80">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={alwaysAllowWriteProtected}
|
||||
onChange={(e) => {
|
||||
setAlwaysAllowWriteProtected(e.target.checked)
|
||||
vscode.postMessage({
|
||||
type: "alwaysAllowWriteProtected",
|
||||
bool: e.target.checked,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium">
|
||||
{t("settings:rules.autoApproveProtected")}
|
||||
</div>
|
||||
<div className="text-xs text-vscode-descriptionForeground">
|
||||
{t("settings:rules.autoApproveProtectedDescription")}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-48">
|
||||
<label className="text-sm font-medium mb-2 block">
|
||||
{t("settings:rules.apiConfigLabel")}
|
||||
</label>
|
||||
<Select value={selectedApiConfig} onValueChange={setSelectedApiConfig}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t("settings:rules.selectApiConfig")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(listApiConfigMeta || []).map((config) => (
|
||||
<SelectItem key={config.id} value={config.name}>
|
||||
{config.name}
|
||||
</SelectItem>
|
||||
{hasExistingFiles && (
|
||||
<div className="flex items-start gap-2 p-3 bg-vscode-inputValidation-warningBackground border border-vscode-inputValidation-warningBorder rounded-md">
|
||||
<AlertTriangle className="w-4 h-4 text-vscode-inputValidation-warningForeground mt-0.5 flex-shrink-0" />
|
||||
<div className="text-sm text-vscode-inputValidation-warningForeground">
|
||||
<div>{t("settings:rules.overwriteWarning")}</div>
|
||||
<ul className="mt-1 ml-4 list-disc">
|
||||
{existingRules.map((rule) => (
|
||||
<li key={rule.id}>{rule.label}</li>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="border-t border-vscode-panel-border pt-4">
|
||||
<label className="flex items-center gap-2 cursor-pointer hover:opacity-80">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={addToGitignore}
|
||||
onChange={(e) => setAddToGitignore(e.target.checked)}
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium">{t("settings:rules.addToGitignore")}</div>
|
||||
<div className="text-xs text-vscode-descriptionForeground">
|
||||
{t("settings:rules.addToGitignoreDescription")}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-vscode-panel-border pt-4">
|
||||
<label className="flex items-center gap-2 cursor-pointer hover:opacity-80">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={alwaysAllowWriteProtected}
|
||||
onChange={(e) => {
|
||||
setAlwaysAllowWriteProtected(e.target.checked)
|
||||
vscode.postMessage({
|
||||
type: "alwaysAllowWriteProtected",
|
||||
bool: e.target.checked,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium">
|
||||
{t("settings:rules.autoApproveProtected")}
|
||||
</div>
|
||||
<div className="text-xs text-vscode-descriptionForeground">
|
||||
{t("settings:rules.autoApproveProtectedDescription")}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<Select value={selectedApiConfig} onValueChange={setSelectedApiConfig}>
|
||||
<SelectTrigger className="w-fit min-w-[5rem] max-w-[8rem]">
|
||||
<SelectValue placeholder={t("settings:rules.selectApiConfig")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(listApiConfigMeta || []).map((config) => (
|
||||
<SelectItem key={config.id} value={config.name}>
|
||||
{config.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Button
|
||||
onClick={handleGenerateRules}
|
||||
disabled={isGenerating || !selectedApiConfig}
|
||||
variant="default"
|
||||
size="default"
|
||||
className="mt-6"
|
||||
className="w-full"
|
||||
title={t("settings:rules.generateButtonTooltip")}>
|
||||
{isGenerating ? (
|
||||
<>
|
||||
|
|
@ -304,29 +307,29 @@ export const RulesSettings = ({ className, ...props }: RulesSettingsProps) => {
|
|||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isGenerating && (
|
||||
<p className="text-vscode-descriptionForeground text-sm">
|
||||
{t("settings:rules.creatingTaskDescription")}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{generationStatus.type === "success" && (
|
||||
<div className="text-vscode-testing-iconPassed text-sm">
|
||||
<p>{t("settings:rules.taskCreated")}</p>
|
||||
<p className="text-vscode-descriptionForeground">{generationStatus.message}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{generationStatus.type === "error" && (
|
||||
<div className="text-vscode-testing-iconFailed text-sm">
|
||||
<p>{t("settings:rules.error")}</p>
|
||||
<p className="text-vscode-descriptionForeground">
|
||||
{t("settings:rules.errorDescription", { error: generationStatus.message })}
|
||||
{isGenerating && (
|
||||
<p className="text-vscode-descriptionForeground text-sm">
|
||||
{t("settings:rules.creatingTaskDescription")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{generationStatus.type === "success" && (
|
||||
<div className="text-vscode-testing-iconPassed text-sm">
|
||||
<p>{t("settings:rules.taskCreated")}</p>
|
||||
<p className="text-vscode-descriptionForeground">{generationStatus.message}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{generationStatus.type === "error" && (
|
||||
<div className="text-vscode-testing-iconFailed text-sm">
|
||||
<p>{t("settings:rules.error")}</p>
|
||||
<p className="text-vscode-descriptionForeground">
|
||||
{t("settings:rules.errorDescription", { error: generationStatus.message })}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
|
|
|||
|
|
@ -604,7 +604,11 @@
|
|||
},
|
||||
"rules": {
|
||||
"title": "Rules",
|
||||
"description": "Configure automatic rules generation for your codebase. Rules help AI agents understand your project's conventions and best practices.",
|
||||
"description": "Rules help AI agents understand your project's conventions and best practices!",
|
||||
"magicGeneration": {
|
||||
"title": "Magic Rules Generation",
|
||||
"description": "Automatically analyze your codebase and generate comprehensive rules tailored to your project's specific needs."
|
||||
},
|
||||
"generateButton": "Generate Rules",
|
||||
"generateButtonTooltip": "Create a new task to analyze the codebase and generate rules automatically",
|
||||
"generating": "Creating task...",
|
||||
|
|
@ -649,9 +653,8 @@
|
|||
"addToGitignore": "Add to .gitignore",
|
||||
"addToGitignoreDescription": "Automatically add the generated rule files to .gitignore to prevent them from being committed to version control",
|
||||
"autoApproveProtected": "Auto-approve protected file writes",
|
||||
"autoApproveProtectedDescription": "Allow Roo to write to .roo directory without requiring manual approval",
|
||||
"autoApproveProtectedDescription": "Allow Roo to make and write to .roo directory without requiring manual approval",
|
||||
"autoApproveRecommendation": "For the best experience generating rules, we recommend enabling auto-approve for both read and write operations in the Auto-Approve settings above.",
|
||||
"apiConfigLabel": "API Configuration",
|
||||
"selectApiConfig": "Select API configuration"
|
||||
},
|
||||
"promptCaching": {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue