From 06202be46fe39ebea23edc396e68aa24575387bb Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 16 Dec 2024 22:58:04 -0500 Subject: [PATCH 1/8] Remove debug checkbox --- src/core/Cline.ts | 3 +-- src/core/__tests__/Cline.test.ts | 5 +++-- src/core/diff/DiffStrategy.ts | 4 ++-- src/core/diff/strategies/search-replace.ts | 22 ++++--------------- src/core/diff/types.ts | 5 ----- src/core/webview/ClineProvider.ts | 21 +++--------------- src/shared/ExtensionMessage.ts | 1 - src/shared/WebviewMessage.ts | 1 - .../src/components/settings/SettingsView.tsx | 17 -------------- .../src/context/ExtensionStateContext.tsx | 6 ----- 10 files changed, 13 insertions(+), 72 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index e98be5e..fa89a69 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -97,7 +97,6 @@ export class Cline { apiConfiguration: ApiConfiguration, customInstructions?: string, diffEnabled?: boolean, - debugDiffEnabled?: boolean, task?: string, images?: string[], historyItem?: HistoryItem, @@ -110,7 +109,7 @@ export class Cline { this.diffViewProvider = new DiffViewProvider(cwd) this.customInstructions = customInstructions if (diffEnabled && this.api.getModel().id) { - this.diffStrategy = getDiffStrategy(this.api.getModel().id, debugDiffEnabled) + this.diffStrategy = getDiffStrategy(this.api.getModel().id) } if (historyItem) { this.taskId = historyItem.id diff --git a/src/core/__tests__/Cline.test.ts b/src/core/__tests__/Cline.test.ts index 041298f..8365c61 100644 --- a/src/core/__tests__/Cline.test.ts +++ b/src/core/__tests__/Cline.test.ts @@ -279,8 +279,9 @@ describe('Cline', () => { mockApiConfig, 'custom instructions', false, // diffEnabled - false, // debugDiffEnabled - 'test task' + 'test task', // task + undefined, // images + undefined // historyItem ); expect(cline.customInstructions).toBe('custom instructions'); diff --git a/src/core/diff/DiffStrategy.ts b/src/core/diff/DiffStrategy.ts index c35ea83..355424e 100644 --- a/src/core/diff/DiffStrategy.ts +++ b/src/core/diff/DiffStrategy.ts @@ -6,10 +6,10 @@ import { SearchReplaceDiffStrategy } from './strategies/search-replace' * @param model The name of the model being used (e.g., 'gpt-4', 'claude-3-opus') * @returns The appropriate diff strategy for the model */ -export function getDiffStrategy(model: string, debugEnabled?: boolean): DiffStrategy { +export function getDiffStrategy(model: string): DiffStrategy { // For now, return SearchReplaceDiffStrategy for all models (with a fuzzy threshold of 0.9) // This architecture allows for future optimizations based on model capabilities - return new SearchReplaceDiffStrategy(0.9, debugEnabled) + return new SearchReplaceDiffStrategy(0.9) } export type { DiffStrategy } diff --git a/src/core/diff/strategies/search-replace.ts b/src/core/diff/strategies/search-replace.ts index 9153c4e..87b6145 100644 --- a/src/core/diff/strategies/search-replace.ts +++ b/src/core/diff/strategies/search-replace.ts @@ -55,12 +55,10 @@ function getSimilarity(original: string, search: string): number { export class SearchReplaceDiffStrategy implements DiffStrategy { private fuzzyThreshold: number; - public debugEnabled: boolean; - constructor(fuzzyThreshold?: number, debugEnabled?: boolean) { + constructor(fuzzyThreshold?: number) { // Default to exact matching (1.0) unless fuzzy threshold specified this.fuzzyThreshold = fuzzyThreshold ?? 1.0; - this.debugEnabled = debugEnabled ?? false; } getToolDescription(cwd: string): string { @@ -177,11 +175,9 @@ Result: // Extract the search and replace blocks const match = diffContent.match(/<<<<<<< SEARCH\n([\s\S]*?)\n?=======\n([\s\S]*?)\n?>>>>>>> REPLACE/); if (!match) { - const debugInfo = this.debugEnabled ? `\n\nDebug Info:\n- Expected Format: <<<<<<< SEARCH\\n[search content]\\n=======\\n[replace content]\\n>>>>>>> REPLACE\n- Tip: Make sure to include both SEARCH and REPLACE sections with correct markers` : ''; - return { success: false, - error: `Invalid diff format - missing required SEARCH/REPLACE sections${debugInfo}` + error: `Invalid diff format - missing required SEARCH/REPLACE sections\n\nDebug Info:\n- Expected Format: <<<<<<< SEARCH\\n[search content]\\n=======\\n[replace content]\\n>>>>>>> REPLACE\n- Tip: Make sure to include both SEARCH and REPLACE sections with correct markers` }; } @@ -221,17 +217,9 @@ Result: const exactEndIndex = endLine - 1; if (exactStartIndex < 0 || exactEndIndex > originalLines.length || exactStartIndex > exactEndIndex) { - const debugInfo = this.debugEnabled ? `\n\nDebug Info:\n- Requested Range: lines ${startLine}-${endLine}\n- File Bounds: lines 1-${originalLines.length}` : ''; - - // Log detailed debug information - console.log('Invalid Line Range Debug:', { - requestedRange: { start: startLine, end: endLine }, - fileBounds: { start: 1, end: originalLines.length } - }); - return { success: false, - error: `Line range ${startLine}-${endLine} is invalid (file has ${originalLines.length} lines)${debugInfo}`, + error: `Line range ${startLine}-${endLine} is invalid (file has ${originalLines.length} lines)\n\nDebug Info:\n- Requested Range: lines ${startLine}-${endLine}\n- File Bounds: lines 1-${originalLines.length}`, }; } @@ -294,13 +282,11 @@ Result: ? `\n\nBest Match Found:\n${addLineNumbers(bestMatchContent, matchIndex + 1)}` : `\n\nBest Match Found:\n(no match)`; - const debugInfo = this.debugEnabled ? `\n\nDebug Info:\n- Similarity Score: ${Math.floor(bestMatchScore * 100)}%\n- Required Threshold: ${Math.floor(this.fuzzyThreshold * 100)}%\n- Search Range: ${startLine && endLine ? `lines ${startLine}-${endLine}` : 'start to end'}\n\nSearch Content:\n${searchChunk}${bestMatchSection}${originalContentSection}` : ''; - const lineRange = startLine || endLine ? ` at ${startLine ? `start: ${startLine}` : 'start'} to ${endLine ? `end: ${endLine}` : 'end'}` : ''; return { success: false, - error: `No sufficiently similar match found${lineRange} (${Math.floor(bestMatchScore * 100)}% similar, needs ${Math.floor(this.fuzzyThreshold * 100)}%)${debugInfo}` + error: `No sufficiently similar match found${lineRange} (${Math.floor(bestMatchScore * 100)}% similar, needs ${Math.floor(this.fuzzyThreshold * 100)}%)\n\nDebug Info:\n- Similarity Score: ${Math.floor(bestMatchScore * 100)}%\n- Required Threshold: ${Math.floor(this.fuzzyThreshold * 100)}%\n- Search Range: ${startLine && endLine ? `lines ${startLine}-${endLine}` : 'start to end'}\n\nSearch Content:\n${searchChunk}${bestMatchSection}${originalContentSection}` }; } diff --git a/src/core/diff/types.ts b/src/core/diff/types.ts index a662c47..3957a1f 100644 --- a/src/core/diff/types.ts +++ b/src/core/diff/types.ts @@ -13,11 +13,6 @@ export type DiffResult = }}; export interface DiffStrategy { - /** - * Whether to enable detailed debug logging - */ - debugEnabled?: boolean; - /** * Get the tool description for this diff strategy * @param cwd The current working directory diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 861046e..f4d9e5b 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -68,7 +68,6 @@ type GlobalStateKey = | "soundEnabled" | "soundVolume" | "diffEnabled" - | "debugDiffEnabled" | "alwaysAllowMcp" export const GlobalFileNames = { @@ -217,8 +216,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { const { apiConfiguration, customInstructions, - diffEnabled, - debugDiffEnabled, + diffEnabled } = await this.getState() this.cline = new Cline( @@ -226,7 +224,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { apiConfiguration, customInstructions, diffEnabled, - debugDiffEnabled, task, images ) @@ -237,8 +234,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { const { apiConfiguration, customInstructions, - diffEnabled, - debugDiffEnabled, + diffEnabled } = await this.getState() this.cline = new Cline( @@ -246,10 +242,9 @@ export class ClineProvider implements vscode.WebviewViewProvider { apiConfiguration, customInstructions, diffEnabled, - debugDiffEnabled, undefined, undefined, - historyItem, + historyItem ) } @@ -614,11 +609,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("diffEnabled", diffEnabled) await this.postStateToWebview() break - case "debugDiffEnabled": - const debugDiffEnabled = message.bool ?? false - await this.updateGlobalState("debugDiffEnabled", debugDiffEnabled) - await this.postStateToWebview() - break } }, null, @@ -945,7 +935,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { alwaysAllowMcp, soundEnabled, diffEnabled, - debugDiffEnabled, taskHistory, soundVolume, } = await this.getState() @@ -970,7 +959,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { .sort((a, b) => b.ts - a.ts), soundEnabled: soundEnabled ?? false, diffEnabled: diffEnabled ?? false, - debugDiffEnabled: debugDiffEnabled ?? false, shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId, allowedCommands, soundVolume: soundVolume ?? 0.5, @@ -1066,7 +1054,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { allowedCommands, soundEnabled, diffEnabled, - debugDiffEnabled, soundVolume, ] = await Promise.all([ this.getGlobalState("apiProvider") as Promise, @@ -1105,7 +1092,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getGlobalState("allowedCommands") as Promise, this.getGlobalState("soundEnabled") as Promise, this.getGlobalState("diffEnabled") as Promise, - this.getGlobalState("debugDiffEnabled") as Promise, this.getGlobalState("soundVolume") as Promise, ]) @@ -1162,7 +1148,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { allowedCommands, soundEnabled: soundEnabled ?? false, diffEnabled: diffEnabled ?? false, - debugDiffEnabled: debugDiffEnabled ?? false, soundVolume, } } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index e95bb80..07a3dde 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -53,7 +53,6 @@ export interface ExtensionState { soundEnabled?: boolean soundVolume?: number diffEnabled?: boolean - debugDiffEnabled?: boolean } export interface ClineMessage { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index aca93e1..2864a94 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -34,7 +34,6 @@ export interface WebviewMessage { | "soundEnabled" | "soundVolume" | "diffEnabled" - | "debugDiffEnabled" | "openMcpSettings" | "restartMcpServer" | "toggleToolAlwaysAllow" diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index e1c9701..817c3b4 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -33,8 +33,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { setSoundVolume, diffEnabled, setDiffEnabled, - debugDiffEnabled, - setDebugDiffEnabled, openRouterModels, setAllowedCommands, allowedCommands, @@ -64,7 +62,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { vscode.postMessage({ type: "soundEnabled", bool: soundEnabled }) vscode.postMessage({ type: "soundVolume", value: soundVolume }) vscode.postMessage({ type: "diffEnabled", bool: diffEnabled }) - vscode.postMessage({ type: "debugDiffEnabled", bool: debugDiffEnabled }) onDone() } } @@ -358,20 +355,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { )} - -
- setDebugDiffEnabled(e.target.checked)}> - Debug diff operations - -

- When enabled, Cline will show detailed debug information when applying diffs fails. -

-
{IS_DEV && ( diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index f4fdaa3..7b832ef 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -31,7 +31,6 @@ export interface ExtensionStateContextType extends ExtensionState { setSoundEnabled: (value: boolean) => void setSoundVolume: (value: number) => void setDiffEnabled: (value: boolean) => void - setDebugDiffEnabled: (value: boolean) => void } const ExtensionStateContext = createContext(undefined) @@ -46,7 +45,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode soundEnabled: false, soundVolume: 0.5, diffEnabled: false, - debugDiffEnabled: false, }) const [didHydrateState, setDidHydrateState] = useState(false) const [showWelcome, setShowWelcome] = useState(false) @@ -149,10 +147,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })), setSoundVolume: (value) => setState((prevState) => ({ ...prevState, soundVolume: value })), setDiffEnabled: (value) => setState((prevState) => ({ ...prevState, diffEnabled: value })), - setDebugDiffEnabled: (value) => setState((prevState) => ({ - ...prevState, - debugDiffEnabled: value - })), } return {children} From eb56cac16bedeeef8acd5f63c69c5b91261220fd Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 16 Dec 2024 23:09:48 -0500 Subject: [PATCH 2/8] Do fuzzy match search from the middle out and expand window to 20 lines --- .../__tests__/search-replace.test.ts | 76 ++++++++++++--- src/core/diff/strategies/search-replace.ts | 94 ++++++++++++------- 2 files changed, 126 insertions(+), 44 deletions(-) diff --git a/src/core/diff/strategies/__tests__/search-replace.test.ts b/src/core/diff/strategies/__tests__/search-replace.test.ts index f96aa17..c962b4a 100644 --- a/src/core/diff/strategies/__tests__/search-replace.test.ts +++ b/src/core/diff/strategies/__tests__/search-replace.test.ts @@ -5,7 +5,7 @@ describe('SearchReplaceDiffStrategy', () => { let strategy: SearchReplaceDiffStrategy beforeEach(() => { - strategy = new SearchReplaceDiffStrategy() // Default 1.0 threshold for exact matching + strategy = new SearchReplaceDiffStrategy(1.0, 5) // Default 1.0 threshold for exact matching, 5 line buffer for tests }) it('should replace matching content', () => { @@ -562,6 +562,63 @@ this.init(); }`); } }); + + it('should find matches from middle out', () => { + const originalContent = ` +function one() { + return "target"; +} + +function two() { + return "target"; +} + +function three() { + return "target"; +} + +function four() { + return "target"; +} + +function five() { + return "target"; +}`.trim() + + const diffContent = `test.ts +<<<<<<< SEARCH + return "target"; +======= + return "updated"; +>>>>>>> REPLACE` + + // Search around the middle (function three) + // Even though all functions contain the target text, + // it should match the one closest to line 9 first + const result = strategy.applyDiff(originalContent, diffContent, 9, 9) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe(`function one() { + return "target"; +} + +function two() { + return "target"; +} + +function three() { + return "updated"; +} + +function four() { + return "target"; +} + +function five() { + return "target"; +}`) + } + }) }) describe('line number stripping', () => { @@ -915,7 +972,7 @@ function test() { } }) - it('should insert at the start of the file if no start_line is provided for insertion', () => { + it('should error if no start_line is provided for insertion', () => { const originalContent = `function test() { return true; }` @@ -926,22 +983,15 @@ console.log("test"); >>>>>>> REPLACE` const result = strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`console.log("test"); -function test() { - return true; -}`) - } + expect(result.success).toBe(false) }) }) }) describe('fuzzy matching', () => { let strategy: SearchReplaceDiffStrategy - beforeEach(() => { - strategy = new SearchReplaceDiffStrategy(0.9) // 90% similarity threshold + strategy = new SearchReplaceDiffStrategy(0.9, 5) // 90% similarity threshold, 5 line buffer for tests }) it('should match content with small differences (>90% similar)', () => { @@ -959,6 +1009,8 @@ function getData() { } >>>>>>> REPLACE` + strategy = new SearchReplaceDiffStrategy(0.9, 5) // Use 5 line buffer for tests + const result = strategy.applyDiff(originalContent, diffContent) expect(result.success).toBe(true) if (result.success) { @@ -1008,7 +1060,7 @@ function sum(a, b) { let strategy: SearchReplaceDiffStrategy beforeEach(() => { - strategy = new SearchReplaceDiffStrategy() + strategy = new SearchReplaceDiffStrategy(0.9, 5) }) it('should find and replace within specified line range', () => { diff --git a/src/core/diff/strategies/search-replace.ts b/src/core/diff/strategies/search-replace.ts index 87b6145..d7bc30f 100644 --- a/src/core/diff/strategies/search-replace.ts +++ b/src/core/diff/strategies/search-replace.ts @@ -1,7 +1,7 @@ import { DiffStrategy, DiffResult } from "../types" import { addLineNumbers } from "../../../integrations/misc/extract-text" -const BUFFER_LINES = 5; // Number of extra context lines to show before and after matches +const BUFFER_LINES = 20; // Number of extra context lines to show before and after matches function levenshteinDistance(a: string, b: string): number { const matrix: number[][] = []; @@ -55,10 +55,12 @@ function getSimilarity(original: string, search: string): number { export class SearchReplaceDiffStrategy implements DiffStrategy { private fuzzyThreshold: number; + private bufferLines: number; - constructor(fuzzyThreshold?: number) { + constructor(fuzzyThreshold?: number, bufferLines?: number) { // Default to exact matching (1.0) unless fuzzy threshold specified this.fuzzyThreshold = fuzzyThreshold ?? 1.0; + this.bufferLines = bufferLines ?? BUFFER_LINES; } getToolDescription(cwd: string): string { @@ -205,12 +207,34 @@ Result: const searchLines = searchContent === '' ? [] : searchContent.split(/\r?\n/); const replaceLines = replaceContent === '' ? [] : replaceContent.split(/\r?\n/); const originalLines = originalContent.split(/\r?\n/); + + // Validate that empty search requires start line + if (searchLines.length === 0 && !startLine) { + return { + success: false, + error: `Empty search content requires start_line to be specified\n\nDebug Info:\n- Empty search content is only valid for insertions at a specific line\n- For insertions, specify the line number where content should be inserted` + }; + } + + // Validate that empty search requires same start and end line + if (searchLines.length === 0 && startLine && endLine && startLine !== endLine) { + return { + success: false, + error: `Empty search content requires start_line and end_line to be the same (got ${startLine}-${endLine})\n\nDebug Info:\n- Empty search content is only valid for insertions at a specific line\n- For insertions, use the same line number for both start_line and end_line` + }; + } - // First try exact line range if provided + // Initialize search variables let matchIndex = -1; let bestMatchScore = 0; let bestMatchContent = ""; - + const searchChunk = searchLines.join('\n'); + + // Determine search bounds + let searchStartIndex = 0; + let searchEndIndex = originalLines.length; + + // Validate and handle line range if provided if (startLine && endLine) { // Convert to 0-based index const exactStartIndex = startLine - 1; @@ -223,44 +247,50 @@ Result: }; } - // Check exact range first + // Try exact match first const originalChunk = originalLines.slice(exactStartIndex, exactEndIndex + 1).join('\n'); - const searchChunk = searchLines.join('\n'); - const similarity = getSimilarity(originalChunk, searchChunk); if (similarity >= this.fuzzyThreshold) { matchIndex = exactStartIndex; bestMatchScore = similarity; bestMatchContent = originalChunk; + } else { + // Set bounds for buffered search + searchStartIndex = Math.max(0, startLine - (this.bufferLines + 1)); + searchEndIndex = Math.min(originalLines.length, endLine + this.bufferLines); } } - // If no match found in exact range, try expanded range + // If no match found yet, try middle-out search within bounds if (matchIndex === -1) { - let searchStartIndex = 0; - let searchEndIndex = originalLines.length; + const midPoint = Math.floor((searchStartIndex + searchEndIndex) / 2); + let leftIndex = midPoint; + let rightIndex = midPoint + 1; - if (startLine || endLine) { - // Convert to 0-based index and add buffer - if (startLine) { - searchStartIndex = Math.max(0, startLine - (BUFFER_LINES + 1)); + // Search outward from the middle within bounds + while (leftIndex >= searchStartIndex || rightIndex <= searchEndIndex - searchLines.length) { + // Check left side if still in range + if (leftIndex >= searchStartIndex) { + const originalChunk = originalLines.slice(leftIndex, leftIndex + searchLines.length).join('\n'); + const similarity = getSimilarity(originalChunk, searchChunk); + if (similarity > bestMatchScore) { + bestMatchScore = similarity; + matchIndex = leftIndex; + bestMatchContent = originalChunk; + } + leftIndex--; } - if (endLine) { - searchEndIndex = Math.min(originalLines.length, endLine + BUFFER_LINES); - } - } - // Find the search content in the expanded range using fuzzy matching - for (let i = searchStartIndex; i <= searchEndIndex - searchLines.length; i++) { - // Join the lines and calculate overall similarity - const originalChunk = originalLines.slice(i, i + searchLines.length).join('\n'); - const searchChunk = searchLines.join('\n'); - - const similarity = getSimilarity(originalChunk, searchChunk); - if (similarity > bestMatchScore) { - bestMatchScore = similarity; - matchIndex = i; - bestMatchContent = originalChunk; + // Check right side if still in range + if (rightIndex <= searchEndIndex - searchLines.length) { + const originalChunk = originalLines.slice(rightIndex, rightIndex + searchLines.length).join('\n'); + const similarity = getSimilarity(originalChunk, searchChunk); + if (similarity > bestMatchScore) { + bestMatchScore = similarity; + matchIndex = rightIndex; + bestMatchContent = originalChunk; + } + rightIndex++; } } } @@ -271,10 +301,10 @@ Result: const originalContentSection = startLine !== undefined && endLine !== undefined ? `\n\nOriginal Content:\n${addLineNumbers( originalLines.slice( - Math.max(0, startLine - 1 - BUFFER_LINES), - Math.min(originalLines.length, endLine + BUFFER_LINES) + Math.max(0, startLine - 1 - this.bufferLines), + Math.min(originalLines.length, endLine + this.bufferLines) ).join('\n'), - Math.max(1, startLine - BUFFER_LINES) + Math.max(1, startLine - this.bufferLines) )}` : `\n\nOriginal Content:\n${addLineNumbers(originalLines.join('\n'))}`; From 85fd2fce4e79fb528fda3055f3826782755fdfb1 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 17 Dec 2024 00:20:35 -0500 Subject: [PATCH 3/8] Only output apply_diff error in chat if we fail to apply the diff to a file 2x in a row --- src/core/Cline.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index fa89a69..1426237 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -75,6 +75,7 @@ export class Cline { private askResponseImages?: string[] private lastMessageTs?: number private consecutiveMistakeCount: number = 0 + private consecutiveMistakeCountForApplyDiff: Map = new Map() private providerRef: WeakRef private abort: boolean = false didFinishAborting = false @@ -1248,15 +1249,18 @@ export class Cline { } if (!diffResult.success) { this.consecutiveMistakeCount++ + const currentCount = (this.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1 + this.consecutiveMistakeCountForApplyDiff.set(relPath, currentCount) const errorDetails = diffResult.details ? `\n\nDetails:\n${JSON.stringify(diffResult.details, null, 2)}` : '' - await this.say("error", `Unable to apply diff to file: ${absolutePath}\n${diffResult.error}${errorDetails}`) + if (currentCount >= 2) { + await this.say("error", `Unable to apply diff to file: ${absolutePath}\n${diffResult.error}${errorDetails}`) + } pushToolResult(`Error applying diff to file: ${absolutePath}\n${diffResult.error}${errorDetails}`) break } - const newContent = diffResult.content this.consecutiveMistakeCount = 0 - + this.consecutiveMistakeCountForApplyDiff.delete(relPath) // Show diff view before asking for approval this.diffViewProvider.editType = "modify" await this.diffViewProvider.open(relPath); From 7d6e04fbe80e007d33fad2ee6447cac6c3db3488 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 17 Dec 2024 00:25:26 -0500 Subject: [PATCH 4/8] Error formatting --- src/core/Cline.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 1426237..a4569d3 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -1230,8 +1230,9 @@ export class Cline { if (!fileExists) { this.consecutiveMistakeCount++ - await this.say("error", `File does not exist at path: ${absolutePath}`) - pushToolResult(`Error: File does not exist at path: ${absolutePath}`) + const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` + await this.say("error", formattedError) + pushToolResult(formattedError) break } @@ -1251,11 +1252,12 @@ export class Cline { this.consecutiveMistakeCount++ const currentCount = (this.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1 this.consecutiveMistakeCountForApplyDiff.set(relPath, currentCount) - const errorDetails = diffResult.details ? `\n\nDetails:\n${JSON.stringify(diffResult.details, null, 2)}` : '' + const errorDetails = diffResult.details ? JSON.stringify(diffResult.details, null, 2) : '' + const formattedError = `Unable to apply diff to file: ${absolutePath}\n\n\n${diffResult.error}${errorDetails ? `\n\nDetails:\n${errorDetails}` : ''}\n` if (currentCount >= 2) { - await this.say("error", `Unable to apply diff to file: ${absolutePath}\n${diffResult.error}${errorDetails}`) + await this.say("error", formattedError) } - pushToolResult(`Error applying diff to file: ${absolutePath}\n${diffResult.error}${errorDetails}`) + pushToolResult(formattedError) break } From 792780e78d3aede3ee8e6a0d7dca9865ce834c38 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 17 Dec 2024 01:15:15 -0500 Subject: [PATCH 5/8] Go back to older prompt --- src/core/diff/strategies/search-replace.ts | 67 +++------------------- 1 file changed, 9 insertions(+), 58 deletions(-) diff --git a/src/core/diff/strategies/search-replace.ts b/src/core/diff/strategies/search-replace.ts index d7bc30f..5409e1a 100644 --- a/src/core/diff/strategies/search-replace.ts +++ b/src/core/diff/strategies/search-replace.ts @@ -75,8 +75,8 @@ If you're not confident in the exact content to search for, use the read_file to Parameters: - path: (required) The path of the file to modify (relative to the current working directory ${cwd}) - diff: (required) The search/replace block defining the changes. -- start_line: (required) The line number where the search block starts (inclusive). -- end_line: (required) The line number where the search block ends (inclusive). +- start_line: (required) The line number where the search block starts. +- end_line: (required) The line number where the search block ends. Diff format: \`\`\` @@ -98,79 +98,30 @@ Original file: 5 | return total \`\`\` -1. Search/replace a specific chunk of code: +Search/Replace content: \`\`\` - -File path here - <<<<<<< SEARCH +def calculate_total(items): total = 0 for item in items: total += item return total ======= +def calculate_total(items): """Calculate total with 10% markup""" return sum(item * 1.1 for item in items) >>>>>>> REPLACE - -2 -5 - \`\`\` -Result: -\`\`\` -1 | def calculate_total(items): -2 | """Calculate total with 10% markup""" -3 | return sum(item * 1.1 for item in items) -\`\`\` - -2. Insert code at a specific line (start_line and end_line must be the same, and the content gets inserted before whatever is currently at that line): -\`\`\` +Usage: File path here -<<<<<<< SEARCH -======= - """TODO: Write a test for this""" ->>>>>>> REPLACE +Your search/replace content here -2 -2 - -\`\`\` - -Result: -\`\`\` -1 | def calculate_total(items): -2 | """TODO: Write a test for this""" -3 | """Calculate total with 10% markup""" -4 | return sum(item * 1.1 for item in items) -\`\`\` - -3. Delete code at a specific line range: -\`\`\` - -File path here - -<<<<<<< SEARCH - total = 0 - for item in items: - total += item - return total -======= ->>>>>>> REPLACE - -2 +1 5 - -\`\`\` - -Result: -\`\`\` -1 | def calculate_total(items): -\`\`\` -` +` } applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): DiffResult { From 7163b173e86d606d5e04eacc2a952b4161e9b674 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 17 Dec 2024 01:24:10 -0500 Subject: [PATCH 6/8] Fix test --- src/core/diff/strategies/__tests__/search-replace.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/diff/strategies/__tests__/search-replace.test.ts b/src/core/diff/strategies/__tests__/search-replace.test.ts index c962b4a..f84f3c9 100644 --- a/src/core/diff/strategies/__tests__/search-replace.test.ts +++ b/src/core/diff/strategies/__tests__/search-replace.test.ts @@ -1519,8 +1519,8 @@ function two() { it('should document start_line and end_line parameters', () => { const description = strategy.getToolDescription('/test') - expect(description).toContain('start_line: (required) The line number where the search block starts (inclusive).') - expect(description).toContain('end_line: (required) The line number where the search block ends (inclusive).') + expect(description).toContain('start_line: (required) The line number where the search block starts.') + expect(description).toContain('end_line: (required) The line number where the search block ends.') }) }) }) From c2f482b6684a32f5feefdeaca8dbc5a17bad4025 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 17 Dec 2024 01:44:01 -0500 Subject: [PATCH 7/8] Release 2.1.14 --- CHANGELOG.md | 4 ++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af4d60f..5e19e25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Roo Cline Changelog +## [2.2.14] + +- Make diff editing more robust to transient errors + ## [2.2.13] - Fixes to sound playing and applying diffs diff --git a/package-lock.json b/package-lock.json index 04f216d..17bf4b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "roo-cline", - "version": "2.2.13", + "version": "2.2.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roo-cline", - "version": "2.2.13", + "version": "2.2.14", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.26.0", diff --git a/package.json b/package.json index d12cca3..6c6e7e6 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Roo Cline", "description": "A fork of Cline, an autonomous coding agent, with some added experimental configuration and automation features.", "publisher": "RooVeterinaryInc", - "version": "2.2.13", + "version": "2.2.14", "icon": "assets/icons/rocket.png", "galleryBanner": { "color": "#617A91", From f5bd5fae0913b31095b117382239a02f4144c371 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 17 Dec 2024 07:50:53 -0500 Subject: [PATCH 8/8] Prompt tweak for gemini line numbers --- src/core/prompts/system.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index b497368..f9fe89e 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -61,7 +61,7 @@ Usage: Description: Request to write full content to a file at the specified path. If the file exists, it will be overwritten with the provided content. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. Parameters: - path: (required) The path of the file to write to (relative to the current working directory ${cwd.toPosix()}) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. +- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. Usage: File path here