partial changes

This commit is contained in:
Will Li 2025-07-23 02:57:34 -07:00
parent 930536d2e6
commit 1789553e14
28 changed files with 370 additions and 295 deletions

View file

@ -131,6 +131,14 @@ export const globalSettingsSchema = z.object({
hasOpenedModeSelector: z.boolean().optional(),
lastModeExportPath: z.string().optional(),
lastModeImportPath: z.string().optional(),
rulesSettings: z
.object({
selectedRuleTypes: z.array(z.string()),
addToGitignore: z.boolean(),
includeCustomRules: z.boolean(),
customRulesText: z.string(),
})
.optional(),
})
export type GlobalSettings = z.infer<typeof globalSettingsSchema>

View file

@ -1976,6 +1976,27 @@ export const webviewMessageHandler = async (
// Silently fail - not critical
}
break
case "updateRulesSettings":
// Save rules settings to global state
await updateGlobalState("rulesSettings", {
selectedRuleTypes: message.selectedRuleTypes || ["general", "code"],
addToGitignore: message.addToGitignore !== undefined ? message.addToGitignore : true,
includeCustomRules: message.includeCustomRules || false,
customRulesText: message.customRulesText || "",
})
break
case "getRulesSettings":
// Send current rules settings to webview
const rulesSettings = getGlobalState("rulesSettings") || {
selectedRuleTypes: ["general", "code"],
addToGitignore: true,
}
await provider.postMessageToWebview({
type: "rulesSettings",
selectedRuleTypes: rulesSettings.selectedRuleTypes,
addToGitignore: rulesSettings.addToGitignore,
})
break
case "humanRelayResponse":
if (message.requestId && message.text) {
vscode.commands.executeCommand(getCommand("handleHumanRelayResponse"), {

View file

@ -109,10 +109,13 @@ export interface ExtensionMessage {
| "existingRuleFiles"
| "showDeleteMessageDialog"
| "showEditMessageDialog"
| "rulesSettings"
text?: string
payload?: any // Add a generic payload for now, can refine later
files?: string[] // For existingRuleFiles
sourceFileCount?: number // For existingRuleFiles to show warning for small repos
selectedRuleTypes?: string[] // For rulesSettings
addToGitignore?: boolean // For rulesSettings
action?:
| "chatButtonClicked"
| "mcpButtonClicked"
@ -195,6 +198,7 @@ export type ExtensionState = Pick<
| "allowedMaxRequests"
| "browserToolEnabled"
| "browserViewportSize"
| "rulesSettings"
| "screenshotQuality"
| "remoteBrowserEnabled"
| "remoteBrowserHost"

View file

@ -200,6 +200,8 @@ export interface WebviewMessage {
| "requestCodeIndexSecretStatus"
| "generateRules"
| "checkExistingRuleFiles"
| "updateRulesSettings"
| "getRulesSettings"
text?: string
editedMessageContent?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"

View file

@ -102,6 +102,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
const [fileSearchResults, setFileSearchResults] = useState<SearchResult[]>([])
const [searchLoading, setSearchLoading] = useState(false)
const [searchRequestId, setSearchRequestId] = useState<string>("")
const [waitingForRulesSettings, setWaitingForRulesSettings] = useState(false)
// Close dropdown when clicking outside.
useEffect(() => {
@ -158,12 +159,26 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
if (message.requestId === searchRequestId) {
setFileSearchResults(message.results || [])
}
} else if (message.type === "rulesSettings") {
// Only trigger generation if we're waiting for it (user used /make-rules command)
if (waitingForRulesSettings) {
setWaitingForRulesSettings(false)
// Received rules settings, now trigger generation
vscode.postMessage({
type: "generateRules",
selectedRuleTypes: message.selectedRuleTypes || ["general", "code"],
addToGitignore: message.addToGitignore !== undefined ? message.addToGitignore : true,
alwaysAllowWriteProtected: false,
includeCustomRules: message.includeCustomRules || false,
customRulesText: message.customRulesText || "",
})
}
}
}
window.addEventListener("message", messageHandler)
return () => window.removeEventListener("message", messageHandler)
}, [setInputValue, searchRequestId])
}, [setInputValue, searchRequestId, waitingForRulesSettings])
const [isDraggingOver, setIsDraggingOver] = useState(false)
const [textAreaBaseHeight, setTextAreaBaseHeight] = useState<number | undefined>(undefined)
@ -273,6 +288,18 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
return
}
if (type === ContextMenuOptionType.Rules) {
// Handle rules generation command
setInputValue("")
setShowContextMenu(false)
// Set flag to indicate we're waiting for settings
setWaitingForRulesSettings(true)
// First get the saved settings
vscode.postMessage({ type: "getRulesSettings" })
// The actual generation will be triggered when we receive the settings
return
}
if (
type === ContextMenuOptionType.File ||
type === ContextMenuOptionType.Folder ||

View file

@ -8,7 +8,7 @@ import { EXPERIMENT_IDS, experimentConfigsMap } from "@roo/experiments"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { cn } from "@src/lib/utils"
import { SetExperimentEnabled } from "./types"
import { SetExperimentEnabled, SetCachedStateField } from "./types"
import { SectionHeader } from "./SectionHeader"
import { Section } from "./Section"
import { ExperimentalFeature } from "./ExperimentalFeature"
@ -18,12 +18,21 @@ type ExperimentalSettingsProps = HTMLAttributes<HTMLDivElement> & {
experiments: Experiments
setExperimentEnabled: SetExperimentEnabled
hasUnsavedChanges?: boolean
rulesSettings?: {
selectedRuleTypes: string[]
addToGitignore: boolean
includeCustomRules: boolean
customRulesText: string
}
setCachedStateField: SetCachedStateField<"rulesSettings">
}
export const ExperimentalSettings = ({
experiments,
setExperimentEnabled,
hasUnsavedChanges,
rulesSettings,
setCachedStateField,
className,
...props
}: ExperimentalSettingsProps) => {
@ -70,7 +79,7 @@ export const ExperimentalSettings = ({
})}
</Section>
<RulesSettings className="mt-3" hasUnsavedChanges={hasUnsavedChanges} />
<RulesSettings className="mt-3" rulesSettings={rulesSettings} setCachedStateField={setCachedStateField} />
</div>
)
}

View file

@ -1,18 +1,22 @@
import { HTMLAttributes, useState, useEffect } from "react"
import { VSCodeTextArea } from "@vscode/webview-ui-toolkit/react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { FileText, AlertTriangle, Info, Sparkles } from "lucide-react"
import { Button } from "@/components/ui/button"
import { FileText, AlertTriangle, Terminal } from "lucide-react"
import { vscode } from "@/utils/vscode"
import { cn } from "@/lib/utils"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StandardTooltip } from "@/components/ui"
import { VSCodeTextArea } from "@vscode/webview-ui-toolkit/react"
import { SectionHeader } from "./SectionHeader"
import { Section } from "./Section"
import { SetCachedStateField } from "./types"
type RulesSettingsProps = HTMLAttributes<HTMLDivElement> & {
hasUnsavedChanges?: boolean
rulesSettings?: {
selectedRuleTypes: string[]
addToGitignore: boolean
includeCustomRules: boolean
customRulesText: string
}
setCachedStateField: SetCachedStateField<"rulesSettings">
}
interface RuleType {
@ -23,73 +27,127 @@ interface RuleType {
exists?: boolean
}
export const RulesSettings = ({ className, hasUnsavedChanges, ...props }: RulesSettingsProps) => {
export const RulesSettings = ({ rulesSettings, setCachedStateField, className, ...props }: RulesSettingsProps) => {
const { t } = useAppTranslation()
const [addToGitignore, setAddToGitignore] = useState(true)
const [alwaysAllowWriteProtected, setAlwaysAllowWriteProtected] = useState(true)
const [selectedApiConfig, setSelectedApiConfig] = useState<string>("")
const [includeCustomRules, setIncludeCustomRules] = useState(false)
const [customRulesText, setCustomRulesText] = useState("")
const [sourceFileCount, setSourceFileCount] = useState<number | null>(null)
const { listApiConfigMeta, currentApiConfigName } = useExtensionState()
const [ruleTypes, setRuleTypes] = useState<RuleType[]>([
const allRuleTypes = [
{
id: "general",
label: t("settings:rules.types.general.label"),
description: t("settings:rules.types.general.description"),
checked: true,
exists: false,
},
{
id: "code",
label: t("settings:rules.types.code.label"),
description: t("settings:rules.types.code.description"),
checked: true,
exists: false,
},
{
id: "architect",
label: t("settings:rules.types.architect.label"),
description: t("settings:rules.types.architect.description"),
checked: true,
exists: false,
},
{
id: "debug",
label: t("settings:rules.types.debug.label"),
description: t("settings:rules.types.debug.description"),
checked: true,
exists: false,
},
{
id: "docs-extractor",
label: t("settings:rules.types.docsExtractor.label"),
description: t("settings:rules.types.docsExtractor.description"),
checked: true,
exists: false,
},
])
]
const [ruleTypes, setRuleTypes] = useState<RuleType[]>(
allRuleTypes.map((ruleType) => ({
...ruleType,
checked: rulesSettings?.selectedRuleTypes.includes(ruleType.id) ?? true,
exists: false,
})),
)
// Update rule types when rulesSettings prop changes
useEffect(() => {
if (rulesSettings) {
setRuleTypes((prev) =>
prev.map((ruleType) => ({
...ruleType,
checked: rulesSettings.selectedRuleTypes.includes(ruleType.id),
})),
)
}
}, [rulesSettings])
const handleRuleTypeToggle = (id: string) => {
setRuleTypes((prev) => prev.map((rule) => (rule.id === id ? { ...rule, checked: !rule.checked } : rule)))
// Update the cached state using the proper pattern
const updatedRules = ruleTypes.map((rule) => (rule.id === id ? { ...rule, checked: !rule.checked } : rule))
const selectedRuleTypes = updatedRules.filter((rule) => rule.checked).map((rule) => rule.id)
setCachedStateField("rulesSettings", {
selectedRuleTypes,
addToGitignore: rulesSettings?.addToGitignore ?? true,
includeCustomRules: rulesSettings?.includeCustomRules ?? false,
customRulesText: rulesSettings?.customRulesText ?? "",
})
}
// Check for existing files and get current settings when component mounts
const handleGitignoreToggle = (checked: boolean) => {
setCachedStateField("rulesSettings", {
selectedRuleTypes: rulesSettings?.selectedRuleTypes ?? [
"general",
"code",
"architect",
"debug",
"docs-extractor",
],
addToGitignore: checked,
includeCustomRules: rulesSettings?.includeCustomRules ?? false,
customRulesText: rulesSettings?.customRulesText ?? "",
})
}
const handleIncludeCustomRulesToggle = (checked: boolean) => {
setCachedStateField("rulesSettings", {
selectedRuleTypes: rulesSettings?.selectedRuleTypes ?? [
"general",
"code",
"architect",
"debug",
"docs-extractor",
],
addToGitignore: rulesSettings?.addToGitignore ?? true,
includeCustomRules: checked,
customRulesText: rulesSettings?.customRulesText ?? "",
})
}
const handleCustomRulesTextChange = (text: string) => {
setCachedStateField("rulesSettings", {
selectedRuleTypes: rulesSettings?.selectedRuleTypes ?? [
"general",
"code",
"architect",
"debug",
"docs-extractor",
],
addToGitignore: rulesSettings?.addToGitignore ?? true,
includeCustomRules: rulesSettings?.includeCustomRules ?? false,
customRulesText: text,
})
}
// Check for existing files when component mounts
useEffect(() => {
vscode.postMessage({
type: "checkExistingRuleFiles",
})
// Request current state to get alwaysAllowWriteProtected value
vscode.postMessage({ type: "webviewDidLaunch" })
// Set default API config
if (currentApiConfigName && !selectedApiConfig) {
setSelectedApiConfig(currentApiConfigName)
}
}, [currentApiConfigName, selectedApiConfig])
// Request current rules settings
vscode.postMessage({ type: "getRulesSettings" })
}, [])
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
@ -106,11 +164,9 @@ export const RulesSettings = ({ className, hasUnsavedChanges, ...props }: RulesS
if (message.sourceFileCount !== undefined) {
setSourceFileCount(message.sourceFileCount)
}
} else if (message.type === "state") {
// Update alwaysAllowWriteProtected from the extension state
if (message.state?.alwaysAllowWriteProtected !== undefined) {
setAlwaysAllowWriteProtected(message.state.alwaysAllowWriteProtected)
}
} else if (message.type === "rulesSettings") {
// Update settings from saved preferences - this is now handled by props
// The component will re-render when rulesSettings prop changes
}
}
@ -118,27 +174,8 @@ export const RulesSettings = ({ className, hasUnsavedChanges, ...props }: RulesS
return () => window.removeEventListener("message", handleMessage)
}, [])
const handleGenerateRules = () => {
const selectedRules = ruleTypes.filter((rule) => rule.checked)
if (selectedRules.length === 0) {
return
}
// Send message to extension to generate rules
vscode.postMessage({
type: "generateRules",
selectedRuleTypes: selectedRules.map((rule) => rule.id),
addToGitignore,
alwaysAllowWriteProtected,
apiConfigName: selectedApiConfig,
includeCustomRules,
customRulesText: includeCustomRules ? customRulesText : "",
})
}
const existingRules = ruleTypes.filter((rule) => rule.checked && rule.exists)
const hasExistingFiles = existingRules.length > 0
const hasSelectedRules = ruleTypes.some((rule) => rule.checked)
return (
<div className={cn("flex flex-col gap-2", className)} {...props}>
@ -150,198 +187,139 @@ export const RulesSettings = ({ className, hasUnsavedChanges, ...props }: RulesS
</SectionHeader>
<Section>
<div className="space-y-6">
{/* Magic Rules Generation subsection */}
<div className="flex flex-col gap-4">
<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 className="space-y-4">
{/* Command Line Instructions */}
<div className="flex items-start gap-2">
<Terminal className="w-4 h-4 text-vscode-foreground mt-0.5 flex-shrink-0" />
<div>
<div className="font-medium mb-1">
{t("settings:rules.commandTitle")}{" "}
<code className="bg-vscode-textBlockQuote-background px-2 py-1 rounded">
/make-rules
</code>
</div>
<div className="text-vscode-descriptionForeground">
{t("settings:rules.magicGeneration.description")}
</div>
</div>
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
{/* Recommendation box */}
<div className="flex items-start gap-2 p-2 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>
<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>
{/* Small repository warning */}
{sourceFileCount !== null && sourceFileCount > 0 && sourceFileCount < 20 && (
<div className="flex items-start gap-2 p-2 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">
{t("settings:rules.smallRepoWarning", { count: sourceFileCount })}
</div>
</div>
)}
{hasExistingFiles && (
<div className="flex items-start gap-2 p-2 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-2 space-y-2">
<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>
<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>
<label className="flex items-center gap-2 cursor-pointer hover:opacity-80">
<input
type="checkbox"
checked={includeCustomRules}
onChange={(e) => setIncludeCustomRules(e.target.checked)}
/>
<div>
<div className="text-sm font-medium">
{t("settings:rules.includeCustomRules")}
</div>
<div className="text-xs text-vscode-descriptionForeground">
{t("settings:rules.includeCustomRulesDescription")}
</div>
</div>
</label>
{includeCustomRules && (
<div className="mt-3 pl-6">
<VSCodeTextArea
resize="vertical"
value={customRulesText}
onChange={(e) => {
const value =
(e as unknown as CustomEvent)?.detail?.target?.value ||
((e as any).target as HTMLTextAreaElement).value
setCustomRulesText(value)
}}
placeholder={t("settings:rules.customRulesPlaceholder")}
rows={6}
className="w-full"
/>
<div className="text-xs text-vscode-descriptionForeground mt-1">
{t("settings:rules.customRulesHint")}
</div>
</div>
)}
</div>
<div className="flex flex-col gap-2 mt-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>
<StandardTooltip
content={
hasUnsavedChanges
? t("settings:rules.unsavedChangesError")
: !hasSelectedRules
? t("settings:rules.noRulesSelected")
: t("settings:rules.generateButtonTooltip")
}>
<span className="w-full">
<Button
onClick={handleGenerateRules}
disabled={!selectedApiConfig || hasUnsavedChanges || !hasSelectedRules}
variant="default"
size="default"
className="w-full">
<>
<FileText className="mr-2 h-4 w-4" />
{t("settings:rules.generateButton")}
</>
</Button>
</span>
</StandardTooltip>
{t("settings:rules.commandDescription")}
</div>
</div>
</div>
{/* Settings Content */}
<div className="pl-3 border-l-2 border-vscode-button-background space-y-4">
{/* Add to .gitignore option */}
<label className="flex items-center gap-2 cursor-pointer hover:opacity-80">
<input
type="checkbox"
checked={rulesSettings?.addToGitignore ?? true}
onChange={(e) => handleGitignoreToggle(e.target.checked)}
/>
<div>
<div className="font-medium">{t("settings:rules.addToGitignore")}</div>
<div className="text-vscode-descriptionForeground text-sm">
{t("settings:rules.addToGitignoreDescription")}
</div>
</div>
</label>
{/* Rule Type Selection */}
<div>
<h4 className="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="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-vscode-descriptionForeground text-sm mt-1">
{ruleType.description}
</div>
</div>
</div>
))}
</div>
</div>
{/* Custom Rules Section */}
<div>
<label className="flex items-center gap-2 cursor-pointer hover:opacity-80 mb-3">
<input
type="checkbox"
checked={rulesSettings?.includeCustomRules ?? false}
onChange={(e) => handleIncludeCustomRulesToggle(e.target.checked)}
/>
<div>
<div className="font-medium">{t("settings:rules.includeCustomRules")}</div>
<div className="text-vscode-descriptionForeground text-sm">
{t("settings:rules.includeCustomRulesDescription")}
</div>
</div>
</label>
{rulesSettings?.includeCustomRules && (
<div className="mt-3">
<label className="block font-medium mb-1">Custom Rules Template</label>
<VSCodeTextArea
resize="vertical"
value={rulesSettings?.customRulesText ?? ""}
onChange={(e) => {
const value =
(e as unknown as CustomEvent)?.detail?.target?.value ||
((e as any).target as HTMLTextAreaElement).value
handleCustomRulesTextChange(value)
}}
placeholder={t("settings:rules.customRulesPlaceholder")}
rows={6}
className="w-full"
/>
<div className="text-sm text-vscode-descriptionForeground mt-1">
{t("settings:rules.customRulesHint")}
</div>
</div>
)}
</div>
</div>
{/* Small repository warning */}
{sourceFileCount !== null && sourceFileCount > 0 && sourceFileCount < 20 && (
<div className="flex items-start gap-2 p-2 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-vscode-inputValidation-warningForeground">
{t("settings:rules.smallRepoWarning", { count: sourceFileCount })}
</div>
</div>
)}
{/* Existing files warning */}
{hasExistingFiles && (
<div className="flex items-start gap-2 p-2 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-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>
</Section>
</div>

View file

@ -177,6 +177,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList,
followupAutoApproveTimeoutMs,
rulesSettings,
} = cachedState
const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration])
@ -702,6 +703,8 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
setExperimentEnabled={setExperimentEnabled}
experiments={experiments}
hasUnsavedChanges={isChangeDetected}
rulesSettings={rulesSettings}
setCachedStateField={setCachedStateField}
/>
)}

View file

@ -229,6 +229,12 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
},
codebaseIndexModels: { ollama: {}, openai: {} },
alwaysAllowUpdateTodoList: true,
rulesSettings: {
selectedRuleTypes: ["general", "code", "architect", "debug", "docs-extractor"],
addToGitignore: true,
includeCustomRules: false,
customRulesText: "",
},
})
const [didHydrateState, setDidHydrateState] = useState(false)

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "Afegir automàticament els arxius de regles generats a .gitignore per evitar que es facin commit al control de versions",
"autoApproveProtected": "Aprovar automàticament l'escriptura d'arxius protegits",
"autoApproveProtectedDescription": "Permetre que Roo creï i escrigui al directori .roo sense requerir aprovació manual",
"autoApproveRecommendation": "Per a la millor experiència de generació de regles, recomanem habilitar l'aprovació automàtica tant per a operacions de lectura com d'escriptura a la configuració d'Aprovació Automàtica de dalt.",
"selectApiConfig": "Seleccionar configuració d'API",
"includeCustomRules": "Incloure les meves regles personalitzades",
"includeCustomRulesDescription": "Afegir instruccions personalitzades al prompt de generació de regles",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "Füge die generierten Regeldateien automatisch zu .gitignore hinzu, um zu verhindern, dass sie in die Versionskontrolle übernommen werden",
"autoApproveProtected": "Geschützte Dateischreibvorgänge automatisch genehmigen",
"autoApproveProtectedDescription": "Erlaube Roo, das .roo-Verzeichnis zu erstellen und zu beschreiben, ohne manuelle Genehmigung zu benötigen",
"autoApproveRecommendation": "Für die beste Erfahrung beim Generieren von Regeln empfehlen wir, die automatische Genehmigung sowohl für Lese- als auch für Schreibvorgänge in den Auto-Approve-Einstellungen oben zu aktivieren.",
"selectApiConfig": "API-Konfiguration auswählen",
"includeCustomRules": "Meine eigenen Regeln einbeziehen",
"includeCustomRulesDescription": "Füge benutzerdefinierte Anweisungen zur Regelgenerierungsaufforderung hinzu",

View file

@ -653,13 +653,14 @@
"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 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.",
"selectApiConfig": "Select API configuration",
"includeCustomRules": "Include my own rules",
"includeCustomRulesDescription": "Add custom instructions to the rule generation prompt",
"customRulesPlaceholder": "Enter your custom rules or instructions here...",
"customRulesHint": "These will be included in the prompt when generating rules for your project",
"smallRepoWarning": "This repository contains only {{count}} files. With limited code examples, the generated rules may be overly specific to your current implementation. Consider regenerating rules as your project grows."
"smallRepoWarning": "This repository contains only {{count}} files. With limited code examples, the generated rules may be overly specific to your current implementation. Consider regenerating rules as your project grows.",
"commandTitle": "Initialize rules using the chat command:",
"commandDescription": "Type this command in the chat to generate AI rules based on your selected preferences below."
},
"promptCaching": {
"label": "Disable prompt caching",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "Añadir automáticamente los archivos de reglas generados a .gitignore para evitar que se confirmen en el control de versiones",
"autoApproveProtected": "Aprobar automáticamente escrituras de archivos protegidos",
"autoApproveProtectedDescription": "Permitir a Roo crear y escribir en el directorio .roo sin requerir aprobación manual",
"autoApproveRecommendation": "Para la mejor experiencia generando reglas, recomendamos habilitar la aprobación automática tanto para operaciones de lectura como de escritura en la configuración de Aprobación Automática arriba.",
"selectApiConfig": "Seleccionar configuración de API",
"includeCustomRules": "Incluir mis propias reglas",
"includeCustomRulesDescription": "Añadir instrucciones personalizadas al prompt de generación de reglas",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "Ajouter automatiquement les fichiers de règles générés à .gitignore pour éviter qu'ils soient validés dans le contrôle de version",
"autoApproveProtected": "Approuver automatiquement les écritures de fichiers protégés",
"autoApproveProtectedDescription": "Permettre à Roo de créer et d'écrire dans le répertoire .roo sans nécessiter d'approbation manuelle",
"autoApproveRecommendation": "Pour la meilleure expérience de génération de règles, nous recommandons d'activer l'approbation automatique pour les opérations de lecture et d'écriture dans les paramètres d'approbation automatique ci-dessus.",
"selectApiConfig": "Sélectionner la configuration API",
"includeCustomRules": "Inclure mes propres règles",
"includeCustomRulesDescription": "Ajouter des instructions personnalisées à l'invite de génération de règles",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "जेनेरेटेड रूल फाइलों को ऑटोमेटिकली .gitignore में ऐड करो ताकि वे वर्जन कंट्रोल में कमिट न हों",
"autoApproveProtected": "प्रोटेक्टेड फाइल राइटिंग को ऑटो अप्रूव करो",
"autoApproveProtectedDescription": "Roo को मैन्युअल अप्रूवल की जरूरत के बिना .roo डायरेक्टरी में क्रिएट और राइट करने की अनुमति दो",
"autoApproveRecommendation": "बेस्ट रूल जेनेरेशन एक्सपीरियंस के लिए, हम ऊपर ऑटो अप्रूवल सेटिंग्स में रीड और राइट दोनों ऑपरेशन्स के लिए ऑटो अप्रूवल इनेबल करने की सिफारिश करते हैं।",
"selectApiConfig": "API कॉन्फ़िगरेशन सेलेक्ट करो",
"includeCustomRules": "मेरे कस्टम रूल्स इंक्लूड करो",
"includeCustomRulesDescription": "रूल जेनेरेशन प्रॉम्प्ट में कस्टम इंस्ट्रक्शन्स ऐड करो",

View file

@ -623,7 +623,6 @@
"addToGitignoreDescription": "Secara otomatis menambahkan file aturan yang dihasilkan ke .gitignore untuk mencegah mereka di-commit ke version control",
"autoApproveProtected": "Otomatis setujui penulisan file yang dilindungi",
"autoApproveProtectedDescription": "Izinkan Roo untuk membuat dan menulis ke direktori .roo tanpa memerlukan persetujuan manual",
"autoApproveRecommendation": "Untuk pengalaman generasi aturan terbaik, kami merekomendasikan mengaktifkan persetujuan otomatis untuk operasi baca dan tulis di pengaturan Auto Approval di atas.",
"selectApiConfig": "Pilih Konfigurasi API",
"includeCustomRules": "Sertakan aturan kustom saya",
"includeCustomRulesDescription": "Tambahkan instruksi kustom ke prompt generasi aturan",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "Aggiungi automaticamente i file di regole generati a .gitignore per evitare che vengano committati nel controllo versione",
"autoApproveProtected": "Approva automaticamente scritture file protetti",
"autoApproveProtectedDescription": "Consenti a Roo di creare e scrivere nella directory .roo senza richiedere approvazione manuale",
"autoApproveRecommendation": "Per la migliore esperienza nella generazione di regole, raccomandiamo di abilitare l'approvazione automatica sia per le operazioni di lettura che di scrittura nelle impostazioni di Approvazione Automatica sopra.",
"selectApiConfig": "Seleziona configurazione API",
"includeCustomRules": "Includi le mie regole",
"includeCustomRulesDescription": "Aggiungi istruzioni personalizzate al prompt di generazione regole",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "生成されたルールファイルを自動的に.gitignoreに追加して、バージョン管理にコミットされないようにします",
"autoApproveProtected": "保護されたファイル書き込みを自動承認",
"autoApproveProtectedDescription": "Rooが手動承認を必要とせずに.rooディレクトリを作成・書き込みできるようにします",
"autoApproveRecommendation": "ルール生成の最良の体験のために、上記の自動承認設定で読み取りと書き込み操作の両方の自動承認を有効にすることをお勧めします。",
"selectApiConfig": "API設定を選択",
"includeCustomRules": "独自のルールを含める",
"includeCustomRulesDescription": "ルール生成プロンプトにカスタム指示を追加",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "생성된 규칙 파일을 자동으로 .gitignore에 추가하여 버전 관리에 커밋되지 않도록 합니다",
"autoApproveProtected": "보호된 파일 쓰기 자동 승인",
"autoApproveProtectedDescription": "Roo가 수동 승인 없이 .roo 디렉토리를 생성하고 쓸 수 있도록 허용",
"autoApproveRecommendation": "최상의 규칙 생성 경험을 위해 위의 자동 승인 설정에서 읽기 및 쓰기 작업 모두에 대한 자동 승인을 활성화하는 것을 권장합니다.",
"selectApiConfig": "API 구성 선택",
"includeCustomRules": "내 규칙 포함",
"includeCustomRulesDescription": "규칙 생성 프롬프트에 사용자 정의 지침 추가",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "Voeg gegenereerde regelbestanden automatisch toe aan .gitignore om te voorkomen dat ze worden gecommit naar versiebeheer",
"autoApproveProtected": "Automatisch goedkeuren van schrijven naar beschermde bestanden",
"autoApproveProtectedDescription": "Sta Roo toe om te maken en schrijven naar de .roo directory zonder handmatige goedkeuring te vereisen",
"autoApproveRecommendation": "Voor de beste regelgeneratie-ervaring raden we aan om automatische goedkeuring in te schakelen voor zowel lees- als schrijfoperaties in de Auto Approval instellingen hierboven.",
"selectApiConfig": "Selecteer API-configuratie",
"includeCustomRules": "Mijn aangepaste regels opnemen",
"includeCustomRulesDescription": "Voeg aangepaste instructies toe aan de regelgeneratie prompt",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "Automatycznie dodaj wygenerowane pliki zasad do .gitignore, aby zapobiec ich commitowaniu do kontroli wersji",
"autoApproveProtected": "Automatycznie zatwierdź pisanie chronionych plików",
"autoApproveProtectedDescription": "Pozwól Roo na tworzenie i pisanie do katalogu .roo bez wymagania ręcznego zatwierdzenia",
"autoApproveRecommendation": "Dla najlepszego doświadczenia generowania zasad zalecamy włączenie automatycznego zatwierdzania zarówno dla operacji odczytu, jak i zapisu w ustawieniach Auto Approval powyżej.",
"selectApiConfig": "Wybierz konfigurację API",
"includeCustomRules": "Uwzględnij moje niestandardowe zasady",
"includeCustomRulesDescription": "Dodaj niestandardowe instrukcje do promptu generowania zasad",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "Adicionar automaticamente os arquivos de regras gerados ao .gitignore para evitar que sejam commitados no controle de versão",
"autoApproveProtected": "Aprovar automaticamente gravações de arquivos protegidos",
"autoApproveProtectedDescription": "Permitir que o Roo crie e grave no diretório .roo sem exigir aprovação manual",
"autoApproveRecommendation": "Para a melhor experiência gerando regras, recomendamos habilitar a aprovação automática tanto para operações de leitura quanto de escrita nas configurações de Aprovação Automática acima.",
"selectApiConfig": "Selecionar configuração da API",
"includeCustomRules": "Incluir minhas próprias regras",
"includeCustomRulesDescription": "Adicionar instruções personalizadas ao prompt de geração de regras",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "Автоматически добавить сгенерированные файлы правил в .gitignore, чтобы предотвратить их коммит в систему контроля версий",
"autoApproveProtected": "Автоматически одобрять запись защищённых файлов",
"autoApproveProtectedDescription": "Разрешить Roo создавать и записывать в директорию .roo без требования ручного одобрения",
"autoApproveRecommendation": "Для лучшего опыта генерации правил мы рекомендуем включить автоматическое одобрение как для операций чтения, так и записи в настройках Автоматического Одобрения выше.",
"selectApiConfig": "Выбрать конфигурацию API",
"includeCustomRules": "Включить мои собственные правила",
"includeCustomRulesDescription": "Добавить пользовательские инструкции в промпт генерации правил",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "Üretilen kural dosyalarını otomatik olarak .gitignore'a ekleyerek sürüm kontrolüne commit edilmesini önle",
"autoApproveProtected": "Korumalı dosya yazımını otomatik onayla",
"autoApproveProtectedDescription": "Roo'nun manuel onay gerektirmeden .roo dizininde oluşturma ve yazma yapmasına izin ver",
"autoApproveRecommendation": "En iyi kural üretimi deneyimi için yukarıdaki Otomatik Onay ayarlarında hem okuma hem de yazma işlemleri için otomatik onayı etkinleştirmenizi öneririz.",
"selectApiConfig": "API Yapılandırması Seç",
"includeCustomRules": "Kendi kurallarımı dahil et",
"includeCustomRulesDescription": "Kural üretimi promptuna özel talimatlar ekle",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "Tự động thêm các tệp quy tắc được tạo vào .gitignore để ngăn chúng được commit vào kiểm soát phiên bản",
"autoApproveProtected": "Tự động phê duyệt ghi tệp được bảo vệ",
"autoApproveProtectedDescription": "Cho phép Roo tạo và ghi vào thư mục .roo mà không cần phê duyệt thủ công",
"autoApproveRecommendation": "Để có trải nghiệm tạo quy tắc tốt nhất, chúng tôi khuyến nghị bật tự động phê duyệt cho cả hoạt động đọc và ghi trong cài đặt Tự động phê duyệt ở trên.",
"selectApiConfig": "Chọn cấu hình API",
"includeCustomRules": "Bao gồm quy tắc tùy chỉnh của tôi",
"includeCustomRulesDescription": "Thêm hướng dẫn tùy chỉnh vào prompt tạo quy tắc",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "自动将生成的规则文件添加到 .gitignore 以防止提交到版本控制",
"autoApproveProtected": "自动批准受保护文件写入",
"autoApproveProtectedDescription": "允许 Roo 创建和写入 .roo 目录而无需手动批准",
"autoApproveRecommendation": "为获得最佳规则生成体验,我们建议在上方的自动批准设置中启用读取和写入操作的自动批准。",
"selectApiConfig": "选择 API 配置",
"includeCustomRules": "包含我的自定义规则",
"includeCustomRulesDescription": "向规则生成提示添加自定义指令",

View file

@ -619,7 +619,6 @@
"addToGitignoreDescription": "自動將產生的規則檔案新增到 .gitignore 以防止提交到版本控制",
"autoApproveProtected": "自動核准受保護檔案寫入",
"autoApproveProtectedDescription": "允許 Roo 建立和寫入 .roo 目錄而無需手動核准",
"autoApproveRecommendation": "為獲得最佳規則產生體驗,我們建議在上方的自動核准設定中啟用讀取和寫入操作的自動核准。",
"selectApiConfig": "選擇 API 設定",
"includeCustomRules": "包含我的自訂規則",
"includeCustomRulesDescription": "向規則產生提示新增自訂指令",

View file

@ -105,6 +105,7 @@ export enum ContextMenuOptionType {
Git = "git",
NoResults = "noResults",
Mode = "mode", // Add mode type
Rules = "rules", // Add rules type
}
export interface ContextMenuQueryItem {
@ -123,38 +124,70 @@ export function getContextMenuOptions(
dynamicSearchResults: SearchResult[] = [],
modes?: ModeConfig[],
): ContextMenuQueryItem[] {
// Handle slash commands for modes
// Handle slash commands
if (query.startsWith("/") && inputValue.startsWith("/")) {
const modeQuery = query.slice(1)
if (!modes?.length) return [{ type: ContextMenuOptionType.NoResults }]
const commandQuery = query.slice(1).toLowerCase()
// Create searchable strings array for fzf
const searchableItems = modes.map((mode) => ({
original: mode,
searchStr: mode.name,
}))
// Check if it's the make-rules command
if (commandQuery === "" || "make-rules".startsWith(commandQuery)) {
const rulesCommand: ContextMenuQueryItem = {
type: ContextMenuOptionType.Rules,
value: "make-rules",
label: "Make Rules",
description: "Generate AI rules for your project",
icon: "$(book)",
}
// Initialize fzf instance for fuzzy search
const fzf = new Fzf(searchableItems, {
selector: (item) => item.searchStr,
})
// If query is empty or matches "make-rules", show rules command
if (commandQuery === "" || "make-rules".startsWith(commandQuery)) {
// Also include mode options if query is empty
if (commandQuery === "" && modes?.length) {
const modeOptions = modes.map((mode) => ({
type: ContextMenuOptionType.Mode,
value: mode.slug,
label: mode.name,
description: getModeDescription(mode),
}))
return [rulesCommand, ...modeOptions]
}
return [rulesCommand]
}
}
// Get fuzzy matching items
const matchingModes = modeQuery
? fzf.find(modeQuery).map((result) => ({
type: ContextMenuOptionType.Mode,
value: result.item.original.slug,
label: result.item.original.name,
description: getModeDescription(result.item.original),
}))
: modes.map((mode) => ({
type: ContextMenuOptionType.Mode,
value: mode.slug,
label: mode.name,
description: getModeDescription(mode),
}))
// Handle mode selection
if (modes?.length) {
const modeQuery = commandQuery
return matchingModes.length > 0 ? matchingModes : [{ type: ContextMenuOptionType.NoResults }]
// Create searchable strings array for fzf
const searchableItems = modes.map((mode) => ({
original: mode,
searchStr: mode.name.toLowerCase(),
}))
// Initialize fzf instance for fuzzy search
const fzf = new Fzf(searchableItems, {
selector: (item) => item.searchStr,
})
// Get fuzzy matching items
const matchingModes = modeQuery
? fzf.find(modeQuery).map((result) => ({
type: ContextMenuOptionType.Mode,
value: result.item.original.slug,
label: result.item.original.name,
description: getModeDescription(result.item.original),
}))
: modes.map((mode) => ({
type: ContextMenuOptionType.Mode,
value: mode.slug,
label: mode.name,
description: getModeDescription(mode),
}))
return matchingModes.length > 0 ? matchingModes : [{ type: ContextMenuOptionType.NoResults }]
}
return [{ type: ContextMenuOptionType.NoResults }]
}
const workingChanges: ContextMenuQueryItem = {