UI changes + refactor

This commit is contained in:
Will Li 2025-07-17 18:26:24 -07:00
parent 83903797cc
commit 236ba2c1e7
4 changed files with 310 additions and 370 deletions

View 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")
})
})
})

View file

@ -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

View file

@ -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>

View file

@ -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": {