mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add granular checkboxes for re-read after edit settings
- Added reReadAfterEditGranular experiment with individual toggles for each edit operation type - Updated all file editing tools to check their specific granular setting - Created new ReReadAfterEditGranularSettings UI component with master toggle and individual checkboxes - Maintained backward compatibility with legacy reReadAfterEdit setting that overrides granular settings - Added comprehensive tests for the new granular settings functionality - Added translations for the new UI elements This allows users to selectively enable automatic review for specific edit types like: - Apply Diff (search and replace blocks) - Multi-file Apply Diff - Write to File (complete file rewrites) - Insert Content (add lines to files) - Search and Replace (find and replace text) Addresses Issue #8927 - implements the suggestion to have checkboxes for selecting review based on edit type
This commit is contained in:
parent
f86af7658b
commit
8e65a4da38
14 changed files with 347 additions and 45 deletions
|
|
@ -13,6 +13,7 @@ export const experimentIds = [
|
|||
"imageGeneration",
|
||||
"runSlashCommand",
|
||||
"reReadAfterEdit",
|
||||
"reReadAfterEditGranular",
|
||||
] as const
|
||||
|
||||
export const experimentIdsSchema = z.enum(experimentIds)
|
||||
|
|
@ -23,6 +24,17 @@ export type ExperimentId = z.infer<typeof experimentIdsSchema>
|
|||
* Experiments
|
||||
*/
|
||||
|
||||
// Schema for granular re-read after edit settings
|
||||
export const reReadAfterEditGranularSchema = z.object({
|
||||
applyDiff: z.boolean().optional(),
|
||||
multiApplyDiff: z.boolean().optional(),
|
||||
writeToFile: z.boolean().optional(),
|
||||
insertContent: z.boolean().optional(),
|
||||
searchAndReplace: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export type ReReadAfterEditGranular = z.infer<typeof reReadAfterEditGranularSchema>
|
||||
|
||||
export const experimentsSchema = z.object({
|
||||
powerSteering: z.boolean().optional(),
|
||||
multiFileApplyDiff: z.boolean().optional(),
|
||||
|
|
@ -30,6 +42,7 @@ export const experimentsSchema = z.object({
|
|||
imageGeneration: z.boolean().optional(),
|
||||
runSlashCommand: z.boolean().optional(),
|
||||
reReadAfterEdit: z.boolean().optional(),
|
||||
reReadAfterEditGranular: reReadAfterEditGranularSchema.optional(),
|
||||
})
|
||||
|
||||
export type Experiments = z.infer<typeof experimentsSchema>
|
||||
|
|
|
|||
|
|
@ -238,11 +238,8 @@ export async function applyDiffToolLegacy(
|
|||
? "\n<notice>Making multiple related changes in a single apply_diff is more efficient. If other changes are needed in this file, please include them as additional SEARCH/REPLACE blocks.</notice>"
|
||||
: ""
|
||||
|
||||
// Check if RE_READ_AFTER_EDIT experiment is enabled
|
||||
const isReReadAfterEditEnabled = experiments.isEnabled(
|
||||
state?.experiments ?? {},
|
||||
EXPERIMENT_IDS.RE_READ_AFTER_EDIT,
|
||||
)
|
||||
// Check if RE_READ_AFTER_EDIT experiment is enabled for applyDiff
|
||||
const isReReadAfterEditEnabled = experiments.isReReadAfterEditEnabled(state?.experiments ?? {}, "applyDiff")
|
||||
|
||||
const reReadSuggestion = isReReadAfterEditEnabled
|
||||
? `\n\n<review_suggestion>The file has been edited. Consider using the read_file tool to review the changes and ensure they are correct and complete.</review_suggestion>`
|
||||
|
|
|
|||
|
|
@ -184,11 +184,8 @@ export async function insertContentTool(
|
|||
// Get the formatted response message
|
||||
const message = await cline.diffViewProvider.pushToolWriteResult(cline, cline.cwd, !fileExists)
|
||||
|
||||
// Check if RE_READ_AFTER_EDIT experiment is enabled
|
||||
const isReReadAfterEditEnabled = experiments.isEnabled(
|
||||
state?.experiments ?? {},
|
||||
EXPERIMENT_IDS.RE_READ_AFTER_EDIT,
|
||||
)
|
||||
// Check if RE_READ_AFTER_EDIT experiment is enabled for insertContent
|
||||
const isReReadAfterEditEnabled = experiments.isReReadAfterEditEnabled(state?.experiments ?? {}, "insertContent")
|
||||
|
||||
const reReadSuggestion = isReReadAfterEditEnabled
|
||||
? `\n\n<review_suggestion>Content has been inserted into the file. Consider using the read_file tool to review the changes and ensure they are correct and complete.</review_suggestion>`
|
||||
|
|
|
|||
|
|
@ -676,12 +676,12 @@ ${errorDetails ? `\nTechnical details:\n${errorDetails}\n` : ""}
|
|||
? "\n<notice>Making multiple related changes in a single apply_diff is more efficient. If other changes are needed in this file, please include them as additional SEARCH/REPLACE blocks.</notice>"
|
||||
: ""
|
||||
|
||||
// Check if RE_READ_AFTER_EDIT experiment is enabled
|
||||
// Check if RE_READ_AFTER_EDIT experiment is enabled for multiApplyDiff
|
||||
const provider = cline.providerRef.deref()
|
||||
const state = await provider?.getState()
|
||||
const isReReadAfterEditEnabled = experiments.isEnabled(
|
||||
const isReReadAfterEditEnabled = experiments.isReReadAfterEditEnabled(
|
||||
state?.experiments ?? {},
|
||||
EXPERIMENT_IDS.RE_READ_AFTER_EDIT,
|
||||
"multiApplyDiff",
|
||||
)
|
||||
|
||||
// Count how many files were successfully edited
|
||||
|
|
|
|||
|
|
@ -258,10 +258,10 @@ export async function searchAndReplaceTool(
|
|||
false, // Always false for search_and_replace
|
||||
)
|
||||
|
||||
// Check if RE_READ_AFTER_EDIT experiment is enabled
|
||||
const isReReadAfterEditEnabled = experiments.isEnabled(
|
||||
// Check if RE_READ_AFTER_EDIT experiment is enabled for searchAndReplace
|
||||
const isReReadAfterEditEnabled = experiments.isReReadAfterEditEnabled(
|
||||
state?.experiments ?? {},
|
||||
EXPERIMENT_IDS.RE_READ_AFTER_EDIT,
|
||||
"searchAndReplace",
|
||||
)
|
||||
|
||||
const reReadSuggestion = isReReadAfterEditEnabled
|
||||
|
|
|
|||
|
|
@ -304,10 +304,10 @@ export async function writeToFileTool(
|
|||
// Get the formatted response message
|
||||
const message = await cline.diffViewProvider.pushToolWriteResult(cline, cline.cwd, !fileExists)
|
||||
|
||||
// Check if RE_READ_AFTER_EDIT experiment is enabled
|
||||
const isReReadAfterEditEnabled = experiments.isEnabled(
|
||||
// Check if RE_READ_AFTER_EDIT experiment is enabled for writeToFile
|
||||
const isReReadAfterEditEnabled = experiments.isReReadAfterEditEnabled(
|
||||
state?.experiments ?? {},
|
||||
EXPERIMENT_IDS.RE_READ_AFTER_EDIT,
|
||||
"writeToFile",
|
||||
)
|
||||
|
||||
const reReadSuggestion = isReReadAfterEditEnabled
|
||||
|
|
|
|||
96
src/shared/__tests__/experiments-granular.spec.ts
Normal file
96
src/shared/__tests__/experiments-granular.spec.ts
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
import { describe, it, expect } from "vitest"
|
||||
import type { Experiments, ReReadAfterEditGranular } from "@roo-code/types"
|
||||
import { experiments } from "../experiments"
|
||||
|
||||
describe("granular re-read after edit experiment", () => {
|
||||
describe("isReReadAfterEditEnabled", () => {
|
||||
it("should return true when legacy reReadAfterEdit is enabled", () => {
|
||||
const config: Experiments = {
|
||||
reReadAfterEdit: true,
|
||||
}
|
||||
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "applyDiff")).toBe(true)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "multiApplyDiff")).toBe(true)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "writeToFile")).toBe(true)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "insertContent")).toBe(true)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "searchAndReplace")).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false when legacy reReadAfterEdit is disabled and no granular settings", () => {
|
||||
const config: Experiments = {
|
||||
reReadAfterEdit: false,
|
||||
}
|
||||
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "applyDiff")).toBe(false)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "multiApplyDiff")).toBe(false)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "writeToFile")).toBe(false)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "insertContent")).toBe(false)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "searchAndReplace")).toBe(false)
|
||||
})
|
||||
|
||||
it("should return true for specific edit types when granular settings are enabled", () => {
|
||||
const config: Experiments = {
|
||||
reReadAfterEdit: false,
|
||||
reReadAfterEditGranular: {
|
||||
applyDiff: true,
|
||||
multiApplyDiff: false,
|
||||
writeToFile: true,
|
||||
insertContent: false,
|
||||
searchAndReplace: true,
|
||||
},
|
||||
}
|
||||
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "applyDiff")).toBe(true)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "multiApplyDiff")).toBe(false)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "writeToFile")).toBe(true)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "insertContent")).toBe(false)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "searchAndReplace")).toBe(true)
|
||||
})
|
||||
|
||||
it("should prioritize legacy setting over granular when both are present", () => {
|
||||
const config: Experiments = {
|
||||
reReadAfterEdit: true, // Legacy enabled
|
||||
reReadAfterEditGranular: {
|
||||
applyDiff: false, // Granular disabled
|
||||
multiApplyDiff: false,
|
||||
writeToFile: false,
|
||||
insertContent: false,
|
||||
searchAndReplace: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Legacy setting should override granular settings
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "applyDiff")).toBe(true)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "multiApplyDiff")).toBe(true)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "writeToFile")).toBe(true)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "insertContent")).toBe(true)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "searchAndReplace")).toBe(true)
|
||||
})
|
||||
|
||||
it("should handle partial granular settings", () => {
|
||||
const config: Experiments = {
|
||||
reReadAfterEdit: false,
|
||||
reReadAfterEditGranular: {
|
||||
applyDiff: true,
|
||||
// Other fields undefined
|
||||
} as ReReadAfterEditGranular,
|
||||
}
|
||||
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "applyDiff")).toBe(true)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "multiApplyDiff")).toBe(false)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "writeToFile")).toBe(false)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "insertContent")).toBe(false)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "searchAndReplace")).toBe(false)
|
||||
})
|
||||
|
||||
it("should return false when no experiments config is provided", () => {
|
||||
const config: Experiments = {}
|
||||
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "applyDiff")).toBe(false)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "multiApplyDiff")).toBe(false)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "writeToFile")).toBe(false)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "insertContent")).toBe(false)
|
||||
expect(experiments.isReReadAfterEditEnabled(config, "searchAndReplace")).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// npx vitest run src/shared/__tests__/experiments.spec.ts
|
||||
|
||||
import type { ExperimentId } from "@roo-code/types"
|
||||
import type { ExperimentId, Experiments as ExperimentsType } from "@roo-code/types"
|
||||
|
||||
import { EXPERIMENT_IDS, experimentConfigsMap, experiments as Experiments } from "../experiments"
|
||||
|
||||
|
|
@ -25,39 +25,51 @@ describe("experiments", () => {
|
|||
|
||||
describe("isEnabled", () => {
|
||||
it("returns false when POWER_STEERING experiment is not enabled", () => {
|
||||
const experiments: Record<ExperimentId, boolean> = {
|
||||
const experiments: ExperimentsType = {
|
||||
powerSteering: false,
|
||||
multiFileApplyDiff: false,
|
||||
preventFocusDisruption: false,
|
||||
imageGeneration: false,
|
||||
runSlashCommand: false,
|
||||
reReadAfterEdit: false,
|
||||
reReadAfterEditGranular: undefined,
|
||||
}
|
||||
expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true when experiment POWER_STEERING is enabled", () => {
|
||||
const experiments: Record<ExperimentId, boolean> = {
|
||||
const experiments: ExperimentsType = {
|
||||
powerSteering: true,
|
||||
multiFileApplyDiff: false,
|
||||
preventFocusDisruption: false,
|
||||
imageGeneration: false,
|
||||
runSlashCommand: false,
|
||||
reReadAfterEdit: false,
|
||||
reReadAfterEditGranular: undefined,
|
||||
}
|
||||
expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when experiment is not present", () => {
|
||||
const experiments: Record<ExperimentId, boolean> = {
|
||||
const experiments: ExperimentsType = {
|
||||
powerSteering: false,
|
||||
multiFileApplyDiff: false,
|
||||
preventFocusDisruption: false,
|
||||
imageGeneration: false,
|
||||
runSlashCommand: false,
|
||||
reReadAfterEdit: false,
|
||||
reReadAfterEditGranular: undefined,
|
||||
}
|
||||
expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("RE_READ_AFTER_EDIT_GRANULAR", () => {
|
||||
it("is configured correctly", () => {
|
||||
expect(EXPERIMENT_IDS.RE_READ_AFTER_EDIT_GRANULAR).toBe("reReadAfterEditGranular")
|
||||
expect(experimentConfigsMap.RE_READ_AFTER_EDIT_GRANULAR).toMatchObject({
|
||||
enabled: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
import type { AssertEqual, Equals, Keys, Values, ExperimentId, Experiments } from "@roo-code/types"
|
||||
import type {
|
||||
AssertEqual,
|
||||
Equals,
|
||||
Keys,
|
||||
Values,
|
||||
ExperimentId,
|
||||
Experiments,
|
||||
ReReadAfterEditGranular,
|
||||
} from "@roo-code/types"
|
||||
|
||||
export const EXPERIMENT_IDS = {
|
||||
MULTI_FILE_APPLY_DIFF: "multiFileApplyDiff",
|
||||
|
|
@ -7,6 +15,7 @@ export const EXPERIMENT_IDS = {
|
|||
IMAGE_GENERATION: "imageGeneration",
|
||||
RUN_SLASH_COMMAND: "runSlashCommand",
|
||||
RE_READ_AFTER_EDIT: "reReadAfterEdit",
|
||||
RE_READ_AFTER_EDIT_GRANULAR: "reReadAfterEditGranular",
|
||||
} as const satisfies Record<string, ExperimentId>
|
||||
|
||||
type _AssertExperimentIds = AssertEqual<Equals<ExperimentId, Values<typeof EXPERIMENT_IDS>>>
|
||||
|
|
@ -14,7 +23,7 @@ type _AssertExperimentIds = AssertEqual<Equals<ExperimentId, Values<typeof EXPER
|
|||
type ExperimentKey = Keys<typeof EXPERIMENT_IDS>
|
||||
|
||||
interface ExperimentConfig {
|
||||
enabled: boolean
|
||||
enabled: boolean | ReReadAfterEditGranular
|
||||
}
|
||||
|
||||
export const experimentConfigsMap: Record<ExperimentKey, ExperimentConfig> = {
|
||||
|
|
@ -24,6 +33,15 @@ export const experimentConfigsMap: Record<ExperimentKey, ExperimentConfig> = {
|
|||
IMAGE_GENERATION: { enabled: false },
|
||||
RUN_SLASH_COMMAND: { enabled: false },
|
||||
RE_READ_AFTER_EDIT: { enabled: false },
|
||||
RE_READ_AFTER_EDIT_GRANULAR: {
|
||||
enabled: {
|
||||
applyDiff: false,
|
||||
multiApplyDiff: false,
|
||||
writeToFile: false,
|
||||
insertContent: false,
|
||||
searchAndReplace: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const experimentDefault = Object.fromEntries(
|
||||
|
|
@ -31,9 +49,23 @@ export const experimentDefault = Object.fromEntries(
|
|||
EXPERIMENT_IDS[_ as keyof typeof EXPERIMENT_IDS] as ExperimentId,
|
||||
config.enabled,
|
||||
]),
|
||||
) as Record<ExperimentId, boolean>
|
||||
) as Experiments
|
||||
|
||||
export const experiments = {
|
||||
get: (id: ExperimentKey): ExperimentConfig | undefined => experimentConfigsMap[id],
|
||||
isEnabled: (experimentsConfig: Experiments, id: ExperimentId) => experimentsConfig[id] ?? experimentDefault[id],
|
||||
isReReadAfterEditEnabled: (experimentsConfig: Experiments, editType: keyof ReReadAfterEditGranular): boolean => {
|
||||
// If the legacy RE_READ_AFTER_EDIT is enabled, it applies to all edit types
|
||||
if (experimentsConfig.reReadAfterEdit) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if granular settings are enabled and if the specific edit type is enabled
|
||||
const granularSettings = experimentsConfig.reReadAfterEditGranular
|
||||
if (granularSettings && granularSettings[editType]) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
} as const
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { HTMLAttributes } from "react"
|
||||
import { FlaskConical } from "lucide-react"
|
||||
|
||||
import type { Experiments } from "@roo-code/types"
|
||||
import type { Experiments, ReReadAfterEditGranular } from "@roo-code/types"
|
||||
|
||||
import { EXPERIMENT_IDS, experimentConfigsMap } from "@roo/experiments"
|
||||
|
||||
|
|
@ -13,10 +13,12 @@ import { SectionHeader } from "./SectionHeader"
|
|||
import { Section } from "./Section"
|
||||
import { ExperimentalFeature } from "./ExperimentalFeature"
|
||||
import { ImageGenerationSettings } from "./ImageGenerationSettings"
|
||||
import { ReReadAfterEditGranularSettings } from "./ReReadAfterEditGranularSettings"
|
||||
|
||||
type ExperimentalSettingsProps = HTMLAttributes<HTMLDivElement> & {
|
||||
experiments: Experiments
|
||||
setExperimentEnabled: SetExperimentEnabled
|
||||
setReReadAfterEditGranular?: (settings: ReReadAfterEditGranular) => void
|
||||
apiConfiguration?: any
|
||||
setApiConfigurationField?: any
|
||||
openRouterImageApiKey?: string
|
||||
|
|
@ -28,6 +30,7 @@ type ExperimentalSettingsProps = HTMLAttributes<HTMLDivElement> & {
|
|||
export const ExperimentalSettings = ({
|
||||
experiments,
|
||||
setExperimentEnabled,
|
||||
setReReadAfterEditGranular,
|
||||
apiConfiguration,
|
||||
setApiConfigurationField,
|
||||
openRouterImageApiKey,
|
||||
|
|
@ -83,19 +86,38 @@ export const ExperimentalSettings = ({
|
|||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<ExperimentalFeature
|
||||
key={config[0]}
|
||||
experimentKey={config[0]}
|
||||
enabled={experiments[EXPERIMENT_IDS[config[0] as keyof typeof EXPERIMENT_IDS]] ?? false}
|
||||
onChange={(enabled) =>
|
||||
setExperimentEnabled(
|
||||
EXPERIMENT_IDS[config[0] as keyof typeof EXPERIMENT_IDS],
|
||||
enabled,
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
// Show granular settings for RE_READ_AFTER_EDIT
|
||||
if (config[0] === "RE_READ_AFTER_EDIT_GRANULAR" && setReReadAfterEditGranular) {
|
||||
return (
|
||||
<ReReadAfterEditGranularSettings
|
||||
key={config[0]}
|
||||
enabled={experiments[EXPERIMENT_IDS.RE_READ_AFTER_EDIT] ?? false}
|
||||
granularSettings={experiments[EXPERIMENT_IDS.RE_READ_AFTER_EDIT_GRANULAR]}
|
||||
onChange={(enabled) =>
|
||||
setExperimentEnabled(EXPERIMENT_IDS.RE_READ_AFTER_EDIT, enabled)
|
||||
}
|
||||
onGranularChange={setReReadAfterEditGranular}
|
||||
/>
|
||||
)
|
||||
}
|
||||
// Skip the legacy RE_READ_AFTER_EDIT if granular is available
|
||||
if (config[0] === "RE_READ_AFTER_EDIT" && setReReadAfterEditGranular) {
|
||||
return null
|
||||
}
|
||||
const experimentId = EXPERIMENT_IDS[config[0] as keyof typeof EXPERIMENT_IDS]
|
||||
const value = experiments[experimentId]
|
||||
// Only render if it's a boolean (not granular settings object)
|
||||
if (typeof value === "boolean" || value === undefined) {
|
||||
return (
|
||||
<ExperimentalFeature
|
||||
key={config[0]}
|
||||
experimentKey={config[0]}
|
||||
enabled={value ?? false}
|
||||
onChange={(enabled) => setExperimentEnabled(experimentId, enabled)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</Section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import type { ReReadAfterEditGranular } from "@roo-code/types"
|
||||
|
||||
interface ReReadAfterEditGranularSettingsProps {
|
||||
enabled: boolean
|
||||
granularSettings?: ReReadAfterEditGranular
|
||||
onChange: (enabled: boolean) => void
|
||||
onGranularChange: (settings: ReReadAfterEditGranular) => void
|
||||
}
|
||||
|
||||
export const ReReadAfterEditGranularSettings = ({
|
||||
enabled,
|
||||
granularSettings,
|
||||
onChange,
|
||||
onGranularChange,
|
||||
}: ReReadAfterEditGranularSettingsProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
const editTypes = [
|
||||
{ key: "applyDiff", label: "Apply Diff" },
|
||||
{ key: "multiApplyDiff", label: "Multi-file Apply Diff" },
|
||||
{ key: "writeToFile", label: "Write to File" },
|
||||
{ key: "insertContent", label: "Insert Content" },
|
||||
{ key: "searchAndReplace", label: "Search and Replace" },
|
||||
] as const
|
||||
|
||||
const handleGranularToggle = (editType: keyof ReReadAfterEditGranular, value: boolean) => {
|
||||
const newSettings: ReReadAfterEditGranular = {
|
||||
...granularSettings,
|
||||
[editType]: value,
|
||||
}
|
||||
onGranularChange(newSettings)
|
||||
}
|
||||
|
||||
const allChecked = editTypes.every((type) => granularSettings?.[type.key] === true)
|
||||
const someChecked = editTypes.some((type) => granularSettings?.[type.key] === true)
|
||||
const isIndeterminate = someChecked && !allChecked
|
||||
|
||||
const handleMasterToggle = (checked: boolean) => {
|
||||
const newSettings: ReReadAfterEditGranular = {}
|
||||
editTypes.forEach((type) => {
|
||||
newSettings[type.key] = checked
|
||||
})
|
||||
onGranularChange(newSettings)
|
||||
onChange(checked)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<VSCodeCheckbox
|
||||
checked={enabled || allChecked}
|
||||
indeterminate={isIndeterminate}
|
||||
onChange={(e: any) => handleMasterToggle(e.target.checked)}>
|
||||
<span className="font-medium">
|
||||
{t("settings:experimental.RE_READ_AFTER_EDIT_GRANULAR.name")}
|
||||
</span>
|
||||
</VSCodeCheckbox>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
{t("settings:experimental.RE_READ_AFTER_EDIT_GRANULAR.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Show granular options when enabled */}
|
||||
{(enabled || someChecked) && (
|
||||
<div className="ml-6 space-y-2 border-l-2 border-vscode-panel-border pl-4">
|
||||
<p className="text-vscode-descriptionForeground text-xs mb-2">
|
||||
{t("settings:experimental.RE_READ_AFTER_EDIT_GRANULAR.selectEditTypes")}
|
||||
</p>
|
||||
{editTypes.map((editType) => (
|
||||
<div key={editType.key} className="flex items-center gap-2">
|
||||
<VSCodeCheckbox
|
||||
checked={granularSettings?.[editType.key] === true}
|
||||
onChange={(e: any) => handleGranularToggle(editType.key, e.target.checked)}>
|
||||
<span className="text-sm">
|
||||
{t(`settings:experimental.RE_READ_AFTER_EDIT_GRANULAR.${editType.key}`)}
|
||||
</span>
|
||||
</VSCodeCheckbox>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ import {
|
|||
Glasses,
|
||||
} from "lucide-react"
|
||||
|
||||
import type { ProviderSettings, ExperimentId, TelemetrySetting } from "@roo-code/types"
|
||||
import type { ProviderSettings, ExperimentId, TelemetrySetting, ReReadAfterEditGranular } from "@roo-code/types"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { cn } from "@src/lib/utils"
|
||||
|
|
@ -272,6 +272,26 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
})
|
||||
}, [])
|
||||
|
||||
const setReReadAfterEditGranular = useCallback((settings: ReReadAfterEditGranular) => {
|
||||
setCachedState((prevState) => {
|
||||
// Only set change detected if value actually changed
|
||||
const previousStr = JSON.stringify(prevState.experiments?.reReadAfterEditGranular)
|
||||
const newStr = JSON.stringify(settings)
|
||||
|
||||
if (previousStr !== newStr) {
|
||||
setChangeDetected(true)
|
||||
}
|
||||
|
||||
return {
|
||||
...prevState,
|
||||
experiments: {
|
||||
...prevState.experiments,
|
||||
reReadAfterEditGranular: settings,
|
||||
},
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
const setTelemetrySetting = useCallback((setting: TelemetrySetting) => {
|
||||
setCachedState((prevState) => {
|
||||
if (prevState.telemetrySetting === setting) {
|
||||
|
|
@ -799,6 +819,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
{activeTab === "experimental" && (
|
||||
<ExperimentalSettings
|
||||
setExperimentEnabled={setExperimentEnabled}
|
||||
setReReadAfterEditGranular={setReReadAfterEditGranular}
|
||||
experiments={experiments}
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={setApiConfigurationField}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { render, screen, act } from "@/utils/test-utils"
|
||||
|
||||
import { ProviderSettings, ExperimentId, DEFAULT_CHECKPOINT_TIMEOUT_SECONDS } from "@roo-code/types"
|
||||
import { ProviderSettings, DEFAULT_CHECKPOINT_TIMEOUT_SECONDS } from "@roo-code/types"
|
||||
|
||||
import { ExtensionState } from "@roo/ExtensionMessage"
|
||||
|
||||
|
|
@ -192,7 +192,7 @@ describe("mergeExtensionState", () => {
|
|||
writeDelayMs: 1000,
|
||||
requestDelaySeconds: 5,
|
||||
mode: "default",
|
||||
experiments: {} as Record<ExperimentId, boolean>,
|
||||
experiments: {} as any,
|
||||
customModes: [],
|
||||
maxOpenTabsContext: 20,
|
||||
maxWorkspaceFiles: 100,
|
||||
|
|
@ -220,7 +220,7 @@ describe("mergeExtensionState", () => {
|
|||
const prevState: ExtensionState = {
|
||||
...baseState,
|
||||
apiConfiguration: { modelMaxTokens: 1234, modelMaxThinkingTokens: 123 },
|
||||
experiments: {} as Record<ExperimentId, boolean>,
|
||||
experiments: {} as any,
|
||||
checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS - 5,
|
||||
}
|
||||
|
||||
|
|
@ -238,7 +238,14 @@ describe("mergeExtensionState", () => {
|
|||
imageGeneration: false,
|
||||
runSlashCommand: false,
|
||||
reReadAfterEdit: false,
|
||||
} as Record<ExperimentId, boolean>,
|
||||
reReadAfterEditGranular: {
|
||||
applyDiff: false,
|
||||
multiApplyDiff: false,
|
||||
writeToFile: false,
|
||||
insertContent: false,
|
||||
searchAndReplace: false,
|
||||
},
|
||||
} as any,
|
||||
checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS + 5,
|
||||
}
|
||||
|
||||
|
|
@ -260,6 +267,13 @@ describe("mergeExtensionState", () => {
|
|||
imageGeneration: false,
|
||||
runSlashCommand: false,
|
||||
reReadAfterEdit: false,
|
||||
reReadAfterEditGranular: {
|
||||
applyDiff: false,
|
||||
multiApplyDiff: false,
|
||||
writeToFile: false,
|
||||
insertContent: false,
|
||||
searchAndReplace: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -779,6 +779,16 @@
|
|||
"RE_READ_AFTER_EDIT": {
|
||||
"name": "Suggest review after file edits",
|
||||
"description": "When enabled, Roo will suggest reviewing changes after editing files. This helps catch errors that might be introduced during editing by prompting to review the changes."
|
||||
},
|
||||
"RE_READ_AFTER_EDIT_GRANULAR": {
|
||||
"name": "Suggest review after file edits",
|
||||
"description": "Select which edit operations should prompt Roo to review changes. This helps catch errors introduced during editing.",
|
||||
"selectEditTypes": "Choose edit types to review:",
|
||||
"applyDiff": "Apply Diff (search and replace blocks)",
|
||||
"multiApplyDiff": "Multi-file Apply Diff",
|
||||
"writeToFile": "Write to File (complete file rewrites)",
|
||||
"insertContent": "Insert Content (add lines to files)",
|
||||
"searchAndReplace": "Search and Replace (find and replace text)"
|
||||
}
|
||||
},
|
||||
"promptCaching": {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue