Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 27c47c0

Browse files
committed
feat: add user-configurable fuzzy match threshold setting
- Add fuzzyMatchThreshold to GlobalSettings schema (0-1 range, optional) - Update Task class to accept and pass threshold to MultiSearchReplaceDiffStrategy - Add UI slider control in ContextManagementSettings (80-100% range) - Add English translations for the new setting This allows users to adjust how strictly search content must match when editing files, addressing issues where models like gemini-2-flash-preview produce output with 89-96% similarity that fails with 100% exact match. Fixes #11087
1 parent b020f6b commit 27c47c0

6 files changed

Lines changed: 65 additions & 4 deletions

File tree

packages/types/src/global-settings.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,17 @@ export const globalSettingsSchema = z.object({
101101
alwaysAllowWriteOutsideWorkspace: z.boolean().optional(),
102102
alwaysAllowWriteProtected: z.boolean().optional(),
103103
writeDelayMs: z.number().min(0).optional(),
104+
/**
105+
* Fuzzy match threshold for diff operations.
106+
* Controls how strictly the search content must match the original file content.
107+
* Value between 0 and 1 where:
108+
* - 1.0 (100%) = exact match required (default)
109+
* - 0.9 (90%) = allows minor differences (recommended for some models)
110+
* - 0.8 (80%) = more lenient matching
111+
* Lower values allow more flexibility but may increase false positives.
112+
* @default 1.0
113+
*/
114+
fuzzyMatchThreshold: z.number().min(0).max(1).optional(),
104115
alwaysAllowBrowser: z.boolean().optional(),
105116
requestDelaySeconds: z.number().optional(),
106117
alwaysAllowMcp: z.boolean().optional(),

src/core/task/Task.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,13 @@ export interface TaskOptions extends CreateTaskOptions {
159159
workspacePath?: string
160160
/** Initial status for the task's history item (e.g., "active" for child tasks) */
161161
initialStatus?: "active" | "delegated" | "completed"
162+
/**
163+
* Fuzzy match threshold for diff operations (0-1).
164+
* 1.0 = exact match required (default)
165+
* 0.9 = 90% similarity (recommended for some models)
166+
* @default 1.0
167+
*/
168+
fuzzyMatchThreshold?: number
162169
}
163170

164171
export class Task extends EventEmitter<TaskEvents> implements TaskLike {
@@ -565,6 +572,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
565572
initialTodos,
566573
workspacePath,
567574
initialStatus,
575+
fuzzyMatchThreshold,
568576
}: TaskOptions) {
569577
super()
570578

@@ -683,8 +691,8 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
683691
// Listen for provider profile changes to update parser state
684692
this.setupProviderProfileChangeListener(provider)
685693

686-
// Set up diff strategy
687-
this.diffStrategy = new MultiSearchReplaceDiffStrategy()
694+
// Set up diff strategy with optional fuzzy match threshold
695+
this.diffStrategy = new MultiSearchReplaceDiffStrategy(fuzzyMatchThreshold)
688696

689697
this.toolRepetitionDetector = new ToolRepetitionDetector(this.consecutiveMistakeLimit)
690698

src/core/webview/ClineProvider.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -969,8 +969,15 @@ export class ClineProvider
969969
}
970970
}
971971

972-
const { apiConfiguration, enableCheckpoints, checkpointTimeout, experiments, cloudUserInfo, taskSyncEnabled } =
973-
await this.getState()
972+
const {
973+
apiConfiguration,
974+
enableCheckpoints,
975+
checkpointTimeout,
976+
experiments,
977+
cloudUserInfo,
978+
taskSyncEnabled,
979+
fuzzyMatchThreshold,
980+
} = await this.getState()
974981

975982
const task = new Task({
976983
provider: this,
@@ -980,6 +987,7 @@ export class ClineProvider
980987
consecutiveMistakeLimit: apiConfiguration.consecutiveMistakeLimit,
981988
historyItem,
982989
experiments,
990+
fuzzyMatchThreshold,
983991
rootTask: historyItem.rootTask,
984992
parentTask: historyItem.parentTask,
985993
taskNumber: historyItem.number,
@@ -2877,6 +2885,7 @@ export class ClineProvider
28772885
experiments,
28782886
cloudUserInfo,
28792887
remoteControlEnabled,
2888+
fuzzyMatchThreshold,
28802889
} = await this.getState()
28812890

28822891
// Single-open-task invariant: always enforce for user-initiated top-level tasks
@@ -2901,6 +2910,7 @@ export class ClineProvider
29012910
task: text,
29022911
images,
29032912
experiments,
2913+
fuzzyMatchThreshold,
29042914
rootTask: this.clineStack.length > 0 ? this.clineStack[0] : undefined,
29052915
parentTask,
29062916
taskNumber: this.clineStack.length + 1,

webview-ui/src/components/settings/ContextManagementSettings.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type ContextManagementSettingsProps = HTMLAttributes<HTMLDivElement> & {
3939
includeDiagnosticMessages?: boolean
4040
maxDiagnosticMessages?: number
4141
writeDelayMs: number
42+
fuzzyMatchThreshold?: number
4243
includeCurrentTime?: boolean
4344
includeCurrentCost?: boolean
4445
maxGitStatusFiles?: number
@@ -57,6 +58,7 @@ type ContextManagementSettingsProps = HTMLAttributes<HTMLDivElement> & {
5758
| "includeDiagnosticMessages"
5859
| "maxDiagnosticMessages"
5960
| "writeDelayMs"
61+
| "fuzzyMatchThreshold"
6062
| "includeCurrentTime"
6163
| "includeCurrentCost"
6264
| "maxGitStatusFiles"
@@ -78,6 +80,7 @@ export const ContextManagementSettings = ({
7880
includeDiagnosticMessages,
7981
maxDiagnosticMessages,
8082
writeDelayMs,
83+
fuzzyMatchThreshold,
8184
includeCurrentTime,
8285
includeCurrentCost,
8386
maxGitStatusFiles,
@@ -406,6 +409,29 @@ export const ContextManagementSettings = ({
406409
</div>
407410
</SearchableSetting>
408411

412+
<SearchableSetting
413+
settingId="context-fuzzy-match-threshold"
414+
section="contextManagement"
415+
label={t("settings:contextManagement.fuzzyMatchThreshold.label")}>
416+
<span className="block font-medium mb-1">
417+
{t("settings:contextManagement.fuzzyMatchThreshold.label")}
418+
</span>
419+
<div className="flex items-center gap-2">
420+
<Slider
421+
min={80}
422+
max={100}
423+
step={1}
424+
value={[Math.round((fuzzyMatchThreshold ?? 1.0) * 100)]}
425+
onValueChange={([value]) => setCachedStateField("fuzzyMatchThreshold", value / 100)}
426+
data-testid="fuzzy-match-threshold-slider"
427+
/>
428+
<span className="w-12">{Math.round((fuzzyMatchThreshold ?? 1.0) * 100)}%</span>
429+
</div>
430+
<div className="text-vscode-descriptionForeground text-sm mt-1">
431+
{t("settings:contextManagement.fuzzyMatchThreshold.description")}
432+
</div>
433+
</SearchableSetting>
434+
409435
<SearchableSetting
410436
settingId="context-include-current-time"
411437
section="contextManagement"

webview-ui/src/components/settings/SettingsView.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
192192
terminalZshP10k,
193193
terminalZdotdir,
194194
writeDelayMs,
195+
fuzzyMatchThreshold,
195196
showRooIgnoredFiles,
196197
enableSubfolderRules,
197198
remoteBrowserEnabled,
@@ -857,6 +858,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
857858
includeDiagnosticMessages={includeDiagnosticMessages}
858859
maxDiagnosticMessages={maxDiagnosticMessages}
859860
writeDelayMs={writeDelayMs}
861+
fuzzyMatchThreshold={fuzzyMatchThreshold}
860862
includeCurrentTime={includeCurrentTime}
861863
includeCurrentCost={includeCurrentCost}
862864
maxGitStatusFiles={maxGitStatusFiles}

webview-ui/src/i18n/locales/en/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,10 @@
738738
"description": "Time to wait after file writes before proceeding, allowing diagnostic tools to process changes and detect issues."
739739
}
740740
},
741+
"fuzzyMatchThreshold": {
742+
"label": "Fuzzy match threshold for file edits",
743+
"description": "Controls how strictly the search content must match when editing files. Lower values allow more tolerance for minor formatting differences that some models produce. 100% requires exact match (default), 90% is recommended for models that produce slight variations."
744+
},
741745
"condensingThreshold": {
742746
"label": "Condensing Trigger Threshold",
743747
"selectProfile": "Configure threshold for profile",

0 commit comments

Comments
 (0)