diff --git a/packages/types/src/experiment.ts b/packages/types/src/experiment.ts index 37c6eecee756..7747850a0d1f 100644 --- a/packages/types/src/experiment.ts +++ b/packages/types/src/experiment.ts @@ -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 diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts index dcdd13462401..91ce6f95df45 100644 --- a/src/core/tools/applyDiffTool.ts +++ b/src/core/tools/applyDiffTool.ts @@ -238,10 +238,20 @@ export async function applyDiffToolLegacy( ? "\nMaking 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." : "" + // 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\nThe file has been edited. Consider using the read_file tool to review the changes and ensure they are correct and complete.` + : "" + if (partFailHint) { - pushToolResult(partFailHint + message + singleBlockNotice) + pushToolResult(partFailHint + message + singleBlockNotice + reReadSuggestion) } else { - pushToolResult(message + singleBlockNotice) + pushToolResult(message + singleBlockNotice + reReadSuggestion) } await cline.diffViewProvider.reset() diff --git a/src/core/tools/insertContentTool.ts b/src/core/tools/insertContentTool.ts index e7d3a06ab92d..c519c12e7993 100644 --- a/src/core/tools/insertContentTool.ts +++ b/src/core/tools/insertContentTool.ts @@ -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\nContent has been inserted into the file. Consider using the read_file tool to review the changes and ensure they are correct and complete.` + : "" + + pushToolResult(message + reReadSuggestion) await cline.diffViewProvider.reset() diff --git a/src/core/tools/multiApplyDiffTool.ts b/src/core/tools/multiApplyDiffTool.ts index a30778c5af0d..f8e96ab9b67e 100644 --- a/src/core/tools/multiApplyDiffTool.ts +++ b/src/core/tools/multiApplyDiffTool.ts @@ -676,8 +676,23 @@ ${errorDetails ? `\nTechnical details:\n${errorDetails}\n` : ""} ? "\nMaking 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." : "" + // 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${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.` + : "" + // 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) { diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts index b0ee3947e1eb..e351596bb87d 100644 --- a/src/core/tools/searchAndReplaceTool.ts +++ b/src/core/tools/searchAndReplaceTool.ts @@ -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\nThe 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.` + : "" + + pushToolResult(message + reReadSuggestion) // Record successful tool usage and cleanup cline.recordToolUsage("search_and_replace") diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts index 5abd96a20aff..d5c3706a8570 100644 --- a/src/core/tools/writeToFileTool.ts +++ b/src/core/tools/writeToFileTool.ts @@ -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\nThe 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.` + : "" + + pushToolResult(message + reReadSuggestion) await cline.diffViewProvider.reset() diff --git a/src/shared/__tests__/experiments-reReadAfterEdit.spec.ts b/src/shared/__tests__/experiments-reReadAfterEdit.spec.ts new file mode 100644 index 000000000000..646dc6d9d6fb --- /dev/null +++ b/src/shared/__tests__/experiments-reReadAfterEdit.spec.ts @@ -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) + }) +}) diff --git a/src/shared/__tests__/experiments.spec.ts b/src/shared/__tests__/experiments.spec.ts index 8a3c30044163..8e3a002180f7 100644 --- a/src/shared/__tests__/experiments.spec.ts +++ b/src/shared/__tests__/experiments.spec.ts @@ -31,6 +31,7 @@ describe("experiments", () => { preventFocusDisruption: false, imageGeneration: false, runSlashCommand: false, + reReadAfterEdit: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) }) @@ -42,6 +43,7 @@ describe("experiments", () => { preventFocusDisruption: false, imageGeneration: false, runSlashCommand: false, + reReadAfterEdit: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(true) }) @@ -53,6 +55,7 @@ describe("experiments", () => { preventFocusDisruption: false, imageGeneration: false, runSlashCommand: false, + reReadAfterEdit: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) }) diff --git a/src/shared/experiments.ts b/src/shared/experiments.ts index 90495c56b70b..d2703e19b6ff 100644 --- a/src/shared/experiments.ts +++ b/src/shared/experiments.ts @@ -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 type _AssertExperimentIds = AssertEqual>> @@ -22,6 +23,7 @@ export const experimentConfigsMap: Record = { 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( diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx index 92652733ddf1..e214c42eb5f2 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx @@ -237,6 +237,7 @@ describe("mergeExtensionState", () => { newTaskRequireTodos: false, imageGeneration: false, runSlashCommand: false, + reReadAfterEdit: false, } as Record, checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS + 5, } @@ -258,6 +259,7 @@ describe("mergeExtensionState", () => { newTaskRequireTodos: false, imageGeneration: false, runSlashCommand: false, + reReadAfterEdit: false, }) }) }) diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 40a08e113fef..6024f3196bb3 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -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": {