feat: add re-read after edit experiment

Implements issue #8927 - Add option to suggest reviewing changes after file edits.
- Added RE_READ_AFTER_EDIT experiment configuration
- Updated all file editing tools to suggest review when experiment is enabled
- Added UI setting with English translation
- Added tests for the new experiment

When enabled, the AI will suggest using the read_file tool to review changes
after editing files, helping catch errors that might be introduced during editing.
This commit is contained in:
Roo Code 2025-10-30 10:26:19 +00:00
parent 59ab1f3f3c
commit 77b0278be3
9 changed files with 92 additions and 6 deletions

View file

@ -12,6 +12,7 @@ export const experimentIds = [
"preventFocusDisruption",
"imageGeneration",
"runSlashCommand",
"reReadAfterEdit",
] as const
export const experimentIdsSchema = z.enum(experimentIds)
@ -28,6 +29,7 @@ export const experimentsSchema = z.object({
preventFocusDisruption: z.boolean().optional(),
imageGeneration: z.boolean().optional(),
runSlashCommand: z.boolean().optional(),
reReadAfterEdit: z.boolean().optional(),
})
export type Experiments = z.infer<typeof experimentsSchema>

View file

@ -238,10 +238,20 @@ 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,
)
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>`
: ""
if (partFailHint) {
pushToolResult(partFailHint + message + singleBlockNotice)
pushToolResult(partFailHint + message + singleBlockNotice + reReadSuggestion)
} else {
pushToolResult(message + singleBlockNotice)
pushToolResult(message + singleBlockNotice + reReadSuggestion)
}
await cline.diffViewProvider.reset()

View file

@ -184,7 +184,17 @@ export async function insertContentTool(
// Get the formatted response message
const message = await cline.diffViewProvider.pushToolWriteResult(cline, cline.cwd, !fileExists)
pushToolResult(message)
// Check if RE_READ_AFTER_EDIT experiment is enabled
const isReReadAfterEditEnabled = experiments.isEnabled(
state?.experiments ?? {},
EXPERIMENT_IDS.RE_READ_AFTER_EDIT,
)
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>`
: ""
pushToolResult(message + reReadSuggestion)
await cline.diffViewProvider.reset()

View file

@ -676,8 +676,23 @@ ${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
const provider = cline.providerRef.deref()
const state = await provider?.getState()
const isReReadAfterEditEnabled = experiments.isEnabled(
state?.experiments ?? {},
EXPERIMENT_IDS.RE_READ_AFTER_EDIT,
)
// Count how many files were successfully edited
const editedFiles = operationResults.filter((op) => op.status === "approved").length
const reReadSuggestion =
isReReadAfterEditEnabled && editedFiles > 0
? `\n\n<review_suggestion>${editedFiles === 1 ? "The file has" : `${editedFiles} files have`} been edited. Consider using the read_file tool to review the changes and ensure they are correct and complete.</review_suggestion>`
: ""
// Push the final result combining all operation results
pushToolResult(results.join("\n\n") + singleBlockNotice)
pushToolResult(results.join("\n\n") + singleBlockNotice + reReadSuggestion)
cline.processQueuedMessages()
return
} catch (error) {

View file

@ -258,7 +258,17 @@ export async function searchAndReplaceTool(
false, // Always false for search_and_replace
)
pushToolResult(message)
// Check if RE_READ_AFTER_EDIT experiment is enabled
const isReReadAfterEditEnabled = experiments.isEnabled(
state?.experiments ?? {},
EXPERIMENT_IDS.RE_READ_AFTER_EDIT,
)
const reReadSuggestion = isReReadAfterEditEnabled
? `\n\n<review_suggestion>The file has been modified via search and replace. Consider using the read_file tool to review the changes and ensure they are correct and complete.</review_suggestion>`
: ""
pushToolResult(message + reReadSuggestion)
// Record successful tool usage and cleanup
cline.recordToolUsage("search_and_replace")

View file

@ -304,7 +304,17 @@ export async function writeToFileTool(
// Get the formatted response message
const message = await cline.diffViewProvider.pushToolWriteResult(cline, cline.cwd, !fileExists)
pushToolResult(message)
// Check if RE_READ_AFTER_EDIT experiment is enabled
const isReReadAfterEditEnabled = experiments.isEnabled(
state?.experiments ?? {},
EXPERIMENT_IDS.RE_READ_AFTER_EDIT,
)
const reReadSuggestion = isReReadAfterEditEnabled
? `\n\n<review_suggestion>The file has been ${fileExists ? "edited" : "created"}. Consider using the read_file tool to review the ${fileExists ? "changes" : "content"} and ensure ${fileExists ? "they are" : "it is"} correct and complete.</review_suggestion>`
: ""
pushToolResult(message + reReadSuggestion)
await cline.diffViewProvider.reset()

View file

@ -0,0 +1,23 @@
import { describe, it, expect } from "vitest"
import { EXPERIMENT_IDS, experiments, experimentDefault } from "../experiments"
describe("RE_READ_AFTER_EDIT experiment", () => {
it("should include RE_READ_AFTER_EDIT in EXPERIMENT_IDS", () => {
expect(EXPERIMENT_IDS.RE_READ_AFTER_EDIT).toBe("reReadAfterEdit")
})
it("should have RE_READ_AFTER_EDIT in default configuration", () => {
expect(experimentDefault.reReadAfterEdit).toBe(false)
})
it("should correctly check if RE_READ_AFTER_EDIT is enabled", () => {
const disabledConfig = { reReadAfterEdit: false }
expect(experiments.isEnabled(disabledConfig, EXPERIMENT_IDS.RE_READ_AFTER_EDIT)).toBe(false)
const enabledConfig = { reReadAfterEdit: true }
expect(experiments.isEnabled(enabledConfig, EXPERIMENT_IDS.RE_READ_AFTER_EDIT)).toBe(true)
const emptyConfig = {}
expect(experiments.isEnabled(emptyConfig, EXPERIMENT_IDS.RE_READ_AFTER_EDIT)).toBe(false)
})
})

View file

@ -6,6 +6,7 @@ export const EXPERIMENT_IDS = {
PREVENT_FOCUS_DISRUPTION: "preventFocusDisruption",
IMAGE_GENERATION: "imageGeneration",
RUN_SLASH_COMMAND: "runSlashCommand",
RE_READ_AFTER_EDIT: "reReadAfterEdit",
} as const satisfies Record<string, ExperimentId>
type _AssertExperimentIds = AssertEqual<Equals<ExperimentId, Values<typeof EXPERIMENT_IDS>>>
@ -22,6 +23,7 @@ export const experimentConfigsMap: Record<ExperimentKey, ExperimentConfig> = {
PREVENT_FOCUS_DISRUPTION: { enabled: false },
IMAGE_GENERATION: { enabled: false },
RUN_SLASH_COMMAND: { enabled: false },
RE_READ_AFTER_EDIT: { enabled: false },
}
export const experimentDefault = Object.fromEntries(

View file

@ -775,6 +775,10 @@
"RUN_SLASH_COMMAND": {
"name": "Enable model-initiated slash commands",
"description": "When enabled, Roo can run your slash commands to execute workflows."
},
"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."
}
},
"promptCaching": {