From 81aaa9c993d02d91a3dc952ae82cbb889e3e516f Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 16 Dec 2024 09:21:20 -0500 Subject: [PATCH 1/3] Add a hint that diff editing works best with latest Sonnet --- webview-ui/src/components/settings/SettingsView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index ffa45d0..f2765fd 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -155,7 +155,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { marginTop: "5px", color: "var(--vscode-descriptionForeground)", }}> - When enabled, Cline will be able to edit files more quickly and will automatically reject truncated full-file writes. + When enabled, Cline will be able to edit files more quickly and will automatically reject truncated full-file writes. Works best with the latest Claude 3.5 Sonnet model.

From c2b4b0545971721958ff5380bc81596724f8ebfb Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 16 Dec 2024 10:24:08 -0500 Subject: [PATCH 2/3] Diff debugging --- CHANGELOG.md | 4 ++ package-lock.json | 4 +- package.json | 2 +- src/core/Cline.ts | 3 +- src/core/__tests__/Cline.test.ts | 3 +- src/core/diff/DiffStrategy.ts | 4 +- src/core/diff/strategies/search-replace.ts | 56 +++++++++-------- src/core/diff/types.ts | 5 ++ src/core/webview/ClineProvider.ts | 61 ++++++++++++------- .../misc/__tests__/extract-text.test.ts | 32 ++++++++++ src/integrations/misc/extract-text.ts | 11 ++-- src/shared/ExtensionMessage.ts | 1 + src/shared/WebviewMessage.ts | 1 + .../src/components/settings/SettingsView.tsx | 22 ++++++- .../src/context/ExtensionStateContext.tsx | 11 +++- 15 files changed, 157 insertions(+), 63 deletions(-) create mode 100644 src/integrations/misc/__tests__/extract-text.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 85023ec..eeba972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Roo Cline Changelog +## [2.2.11] + +- Added settings checkbox for verbose diff debugging + ## [2.2.6 - 2.2.10] - More fixes to search/replace diffs diff --git a/package-lock.json b/package-lock.json index 3806ccb..4e5c9e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "roo-cline", - "version": "2.2.10", + "version": "2.2.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roo-cline", - "version": "2.2.10", + "version": "2.2.11", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.26.0", diff --git a/package.json b/package.json index 2b605d3..c0dd669 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.10", + "version": "2.2.11", "icon": "assets/icons/rocket.png", "galleryBanner": { "color": "#617A91", diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 5b190d7..e0f7c1b 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -97,6 +97,7 @@ export class Cline { apiConfiguration: ApiConfiguration, customInstructions?: string, diffEnabled?: boolean, + debugDiffEnabled?: boolean, task?: string, images?: string[], historyItem?: HistoryItem, @@ -109,7 +110,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) + this.diffStrategy = getDiffStrategy(this.api.getModel().id, debugDiffEnabled) } if (historyItem) { this.taskId = historyItem.id diff --git a/src/core/__tests__/Cline.test.ts b/src/core/__tests__/Cline.test.ts index f50ed4f..041298f 100644 --- a/src/core/__tests__/Cline.test.ts +++ b/src/core/__tests__/Cline.test.ts @@ -278,7 +278,8 @@ describe('Cline', () => { mockProvider, mockApiConfig, 'custom instructions', - false, + false, // diffEnabled + false, // debugDiffEnabled 'test task' ); diff --git a/src/core/diff/DiffStrategy.ts b/src/core/diff/DiffStrategy.ts index 355424e..c35ea83 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): DiffStrategy { +export function getDiffStrategy(model: string, debugEnabled?: boolean): 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) + return new SearchReplaceDiffStrategy(0.9, debugEnabled) } export type { DiffStrategy } diff --git a/src/core/diff/strategies/search-replace.ts b/src/core/diff/strategies/search-replace.ts index a950b08..65a74dd 100644 --- a/src/core/diff/strategies/search-replace.ts +++ b/src/core/diff/strategies/search-replace.ts @@ -1,4 +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 function levenshteinDistance(a: string, b: string): number { const matrix: number[][] = []; @@ -48,10 +51,12 @@ function getSimilarity(original: string, search: string): number { export class SearchReplaceDiffStrategy implements DiffStrategy { private fuzzyThreshold: number; + public debugEnabled: boolean; - constructor(fuzzyThreshold?: number) { + constructor(fuzzyThreshold?: number, debugEnabled?: boolean) { // Default to exact matching (1.0) unless fuzzy threshold specified this.fuzzyThreshold = fuzzyThreshold ?? 1.0; + this.debugEnabled = debugEnabled ?? false; } getToolDescription(cwd: string): string { @@ -119,15 +124,11 @@ Your search/replace content here // Extract the search and replace blocks const match = diffContent.match(/<<<<<<< SEARCH\n([\s\S]*?)\n=======\n([\s\S]*?)\n>>>>>>> REPLACE/); if (!match) { - // Log detailed format information - console.log('Invalid Diff Format Debug:', { - expectedFormat: "<<<<<<< SEARCH\\n[search content]\\n=======\\n[replace content]\\n>>>>>>> REPLACE", - tip: "Make sure to include both SEARCH and REPLACE sections with correct markers" - }); + 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" + error: `Invalid diff format - missing required SEARCH/REPLACE sections${debugInfo}` }; } @@ -167,15 +168,11 @@ Your search/replace content here const exactEndIndex = endLine - 1; if (exactStartIndex < 0 || exactEndIndex >= originalLines.length) { - // Log detailed debug information - console.log('Invalid Line Range Debug:', { - requestedRange: { start: startLine, end: endLine }, - fileBounds: { start: 1, end: originalLines.length } - }); - + const debugInfo = this.debugEnabled ? `\n\nDebug Info:\n- Requested Range: lines ${startLine}-${endLine}\n- File Bounds: lines 1-${originalLines.length}` : ''; + return { success: false, - error: `Line range ${startLine}-${endLine} is invalid (file has ${originalLines.length} lines)`, + error: `Line range ${startLine}-${endLine} is invalid (file has ${originalLines.length} lines)${debugInfo}`, }; } @@ -198,11 +195,12 @@ Your search/replace content here if (startLine !== undefined || endLine !== undefined) { // Convert to 0-based index and add buffer + const BUFFER_LINES = 5; if (startLine !== undefined) { - searchStartIndex = Math.max(0, startLine - 6); + searchStartIndex = Math.max(0, startLine - (BUFFER_LINES + 1)); } if (endLine !== undefined) { - searchEndIndex = Math.min(originalLines.length, endLine + 5); + searchEndIndex = Math.min(originalLines.length, endLine + BUFFER_LINES); } } @@ -224,17 +222,27 @@ Your search/replace content here // Require similarity to meet threshold if (matchIndex === -1 || bestMatchScore < this.fuzzyThreshold) { const searchChunk = searchLines.join('\n'); - // Log detailed debug information to console - console.log('Search/Replace Debug Info:', { - similarity: bestMatchScore, - threshold: this.fuzzyThreshold, - searchContent: searchChunk, - bestMatch: bestMatchContent || undefined - }); + 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) + ).join('\n'), + Math.max(1, startLine - BUFFER_LINES) + )}` + : `\n\nOriginal Content:\n${addLineNumbers(originalLines.join('\n'))}`; + const bestMatchSection = bestMatchContent + ? `\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- Line Range: lines ${startLine}-${endLine}\n\nSearch Content:\n${searchChunk}${bestMatchSection}${originalContentSection}` : ''; + + const lineRange = startLine !== undefined || endLine !== undefined ? + ` at ${startLine !== undefined ? `start: ${startLine}` : 'start'} to ${endLine !== undefined ? `end: ${endLine}` : 'end'}` : ''; return { success: false, - error: `No sufficiently similar match found${startLine !== undefined ? ` near lines ${startLine}-${endLine}` : ''} (${Math.round(bestMatchScore * 100)}% similar, needs ${Math.round(this.fuzzyThreshold * 100)}%)` + error: `No sufficiently similar match found${lineRange} (${Math.floor(bestMatchScore * 100)}% similar, needs ${Math.floor(this.fuzzyThreshold * 100)}%)${debugInfo}` }; } diff --git a/src/core/diff/types.ts b/src/core/diff/types.ts index 3957a1f..a662c47 100644 --- a/src/core/diff/types.ts +++ b/src/core/diff/types.ts @@ -13,6 +13,11 @@ 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 e998332..3f17b06 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -67,6 +67,7 @@ type GlobalStateKey = | "allowedCommands" | "soundEnabled" | "diffEnabled" + | "debugDiffEnabled" | "alwaysAllowMcp" export const GlobalFileNames = { @@ -207,28 +208,11 @@ export class ClineProvider implements vscode.WebviewViewProvider { async initClineWithTask(task?: string, images?: string[]) { await this.clearTask() - const { - apiConfiguration, - customInstructions, - diffEnabled, - } = await this.getState() - - this.cline = new Cline( - this, - apiConfiguration, - customInstructions, - diffEnabled, - task, - images - ) - } - - async initClineWithHistoryItem(historyItem: HistoryItem) { - await this.clearTask() - const { - apiConfiguration, - customInstructions, + const { + apiConfiguration, + customInstructions, diffEnabled, + debugDiffEnabled, } = await this.getState() this.cline = new Cline( @@ -236,6 +220,27 @@ export class ClineProvider implements vscode.WebviewViewProvider { apiConfiguration, customInstructions, diffEnabled, + debugDiffEnabled, + task, + images + ) + } + + async initClineWithHistoryItem(historyItem: HistoryItem) { + await this.clearTask() + const { + apiConfiguration, + customInstructions, + diffEnabled, + debugDiffEnabled, + } = await this.getState() + + this.cline = new Cline( + this, + apiConfiguration, + customInstructions, + diffEnabled, + debugDiffEnabled, undefined, undefined, historyItem, @@ -597,6 +602,11 @@ 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, @@ -923,6 +933,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { alwaysAllowMcp, soundEnabled, diffEnabled, + debugDiffEnabled, taskHistory, } = await this.getState() @@ -946,6 +957,7 @@ 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, } @@ -1040,6 +1052,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { allowedCommands, soundEnabled, diffEnabled, + debugDiffEnabled, ] = await Promise.all([ this.getGlobalState("apiProvider") as Promise, this.getGlobalState("apiModelId") as Promise, @@ -1077,6 +1090,7 @@ 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, ]) let apiProvider: ApiProvider @@ -1130,8 +1144,9 @@ export class ClineProvider implements vscode.WebviewViewProvider { alwaysAllowMcp: alwaysAllowMcp ?? false, taskHistory, allowedCommands, - soundEnabled, - diffEnabled, + soundEnabled: soundEnabled ?? false, + diffEnabled: diffEnabled ?? false, + debugDiffEnabled: debugDiffEnabled ?? false, } } diff --git a/src/integrations/misc/__tests__/extract-text.test.ts b/src/integrations/misc/__tests__/extract-text.test.ts new file mode 100644 index 0000000..89adbb1 --- /dev/null +++ b/src/integrations/misc/__tests__/extract-text.test.ts @@ -0,0 +1,32 @@ +import { addLineNumbers } from '../extract-text'; + +describe('addLineNumbers', () => { + it('should add line numbers starting from 1 by default', () => { + const input = 'line 1\nline 2\nline 3'; + const expected = '1 | line 1\n2 | line 2\n3 | line 3'; + expect(addLineNumbers(input)).toBe(expected); + }); + + it('should add line numbers starting from specified line number', () => { + const input = 'line 1\nline 2\nline 3'; + const expected = '10 | line 1\n11 | line 2\n12 | line 3'; + expect(addLineNumbers(input, 10)).toBe(expected); + }); + + it('should handle empty content', () => { + expect(addLineNumbers('')).toBe('1 | '); + expect(addLineNumbers('', 5)).toBe('5 | '); + }); + + it('should handle single line content', () => { + expect(addLineNumbers('single line')).toBe('1 | single line'); + expect(addLineNumbers('single line', 42)).toBe('42 | single line'); + }); + + it('should pad line numbers based on the highest line number', () => { + const input = 'line 1\nline 2'; + // When starting from 99, highest line will be 100, so needs 3 spaces padding + const expected = ' 99 | line 1\n100 | line 2'; + expect(addLineNumbers(input, 99)).toBe(expected); + }); +}); \ No newline at end of file diff --git a/src/integrations/misc/extract-text.ts b/src/integrations/misc/extract-text.ts index 3f9ff1c..576194c 100644 --- a/src/integrations/misc/extract-text.ts +++ b/src/integrations/misc/extract-text.ts @@ -53,15 +53,12 @@ async function extractTextFromIPYNB(filePath: string): Promise { return addLineNumbers(extractedText) } - -export function addLineNumbers(content: string): string { +export function addLineNumbers(content: string, startLine: number = 1): string { const lines = content.split('\n') - const maxLineNumberWidth = String(lines.length).length + const maxLineNumberWidth = String(startLine + lines.length - 1).length return lines .map((line, index) => { - const lineNumber = String(index + 1).padStart(maxLineNumberWidth, ' ') + const lineNumber = String(startLine + index).padStart(maxLineNumberWidth, ' ') return `${lineNumber} | ${line}` }).join('\n') -} - - +} \ No newline at end of file diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 608b5e5..b9ba21f 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -52,6 +52,7 @@ export interface ExtensionState { allowedCommands?: string[] soundEnabled?: boolean diffEnabled?: boolean + debugDiffEnabled?: boolean } export interface ClineMessage { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 31802b9..d4377ca 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -33,6 +33,7 @@ export interface WebviewMessage { | "playSound" | "soundEnabled" | "diffEnabled" + | "debugDiffEnabled" | "openMcpSettings" | "restartMcpServer" | "toggleToolAlwaysAllow" diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index ffa45d0..28e42d3 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -31,6 +31,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { setSoundEnabled, diffEnabled, setDiffEnabled, + debugDiffEnabled, + setDebugDiffEnabled, openRouterModels, setAllowedCommands, allowedCommands, @@ -46,7 +48,10 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { setApiErrorMessage(apiValidationResult) setModelIdErrorMessage(modelIdValidationResult) if (!apiValidationResult && !modelIdValidationResult) { - vscode.postMessage({ type: "apiConfiguration", apiConfiguration }) + vscode.postMessage({ + type: "apiConfiguration", + apiConfiguration + }) vscode.postMessage({ type: "customInstructions", text: customInstructions }) vscode.postMessage({ type: "alwaysAllowReadOnly", bool: alwaysAllowReadOnly }) vscode.postMessage({ type: "alwaysAllowWrite", bool: alwaysAllowWrite }) @@ -56,6 +61,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { vscode.postMessage({ type: "allowedCommands", commands: allowedCommands ?? [] }) vscode.postMessage({ type: "soundEnabled", bool: soundEnabled }) vscode.postMessage({ type: "diffEnabled", bool: diffEnabled }) + vscode.postMessage({ type: "debugDiffEnabled", bool: debugDiffEnabled }) onDone() } } @@ -324,6 +330,20 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { When enabled, Cline will play sound effects for notifications and events.

+ +
+ 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 f9690b6..4d0e97f 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -30,6 +30,7 @@ export interface ExtensionStateContextType extends ExtensionState { setAllowedCommands: (value: string[]) => void setSoundEnabled: (value: boolean) => void setDiffEnabled: (value: boolean) => void + setDebugDiffEnabled: (value: boolean) => void } const ExtensionStateContext = createContext(undefined) @@ -43,6 +44,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode allowedCommands: [], soundEnabled: false, diffEnabled: false, + debugDiffEnabled: false, }) const [didHydrateState, setDidHydrateState] = useState(false) const [showWelcome, setShowWelcome] = useState(false) @@ -129,7 +131,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode openRouterModels, mcpServers, filePaths, - setApiConfiguration: (value) => setState((prevState) => ({ ...prevState, apiConfiguration: value })), + setApiConfiguration: (value) => setState((prevState) => ({ + ...prevState, + apiConfiguration: value + })), setCustomInstructions: (value) => setState((prevState) => ({ ...prevState, customInstructions: value })), setAlwaysAllowReadOnly: (value) => setState((prevState) => ({ ...prevState, alwaysAllowReadOnly: value })), setAlwaysAllowWrite: (value) => setState((prevState) => ({ ...prevState, alwaysAllowWrite: value })), @@ -140,6 +145,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setAllowedCommands: (value) => setState((prevState) => ({ ...prevState, allowedCommands: value })), setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })), setDiffEnabled: (value) => setState((prevState) => ({ ...prevState, diffEnabled: value })), + setDebugDiffEnabled: (value) => setState((prevState) => ({ + ...prevState, + debugDiffEnabled: value + })), } return {children} From 61b21043452e299fa33204361206ba674cb558fd Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 16 Dec 2024 12:22:23 -0500 Subject: [PATCH 3/3] Fix bug where start/end line not passed to diff --- src/core/Cline.ts | 7 ++++++- src/core/assistant-message/index.ts | 2 ++ src/core/diff/strategies/search-replace.ts | 17 ++++++++--------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index e0f7c1b..e98be5e 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -1238,7 +1238,12 @@ export class Cline { const originalContent = await fs.readFile(absolutePath, "utf-8") // Apply the diff to the original content - const diffResult = this.diffStrategy?.applyDiff(originalContent, diffContent) ?? { + const diffResult = this.diffStrategy?.applyDiff( + originalContent, + diffContent, + parseInt(block.params.start_line ?? ''), + parseInt(block.params.end_line ?? '') + ) ?? { success: false, error: "No diff strategy available" } diff --git a/src/core/assistant-message/index.ts b/src/core/assistant-message/index.ts index d10967c..241f8c7 100644 --- a/src/core/assistant-message/index.ts +++ b/src/core/assistant-message/index.ts @@ -44,6 +44,8 @@ export const toolParamNames = [ "question", "result", "diff", + "start_line", + "end_line", ] as const export type ToolParamName = (typeof toolParamNames)[number] diff --git a/src/core/diff/strategies/search-replace.ts b/src/core/diff/strategies/search-replace.ts index 65a74dd..2fbfe5b 100644 --- a/src/core/diff/strategies/search-replace.ts +++ b/src/core/diff/strategies/search-replace.ts @@ -162,12 +162,12 @@ Your search/replace content here let bestMatchScore = 0; let bestMatchContent = ""; - if (startLine !== undefined && endLine !== undefined) { + if (startLine && endLine) { // Convert to 0-based index const exactStartIndex = startLine - 1; const exactEndIndex = endLine - 1; - if (exactStartIndex < 0 || exactEndIndex >= originalLines.length) { + 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}` : ''; return { @@ -193,13 +193,12 @@ Your search/replace content here let searchStartIndex = 0; let searchEndIndex = originalLines.length; - if (startLine !== undefined || endLine !== undefined) { + if (startLine || endLine) { // Convert to 0-based index and add buffer - const BUFFER_LINES = 5; - if (startLine !== undefined) { + if (startLine) { searchStartIndex = Math.max(0, startLine - (BUFFER_LINES + 1)); } - if (endLine !== undefined) { + if (endLine) { searchEndIndex = Math.min(originalLines.length, endLine + BUFFER_LINES); } } @@ -236,10 +235,10 @@ Your search/replace content here ? `\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- Line Range: lines ${startLine}-${endLine}\n\nSearch Content:\n${searchChunk}${bestMatchSection}${originalContentSection}` : ''; + 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 !== undefined || endLine !== undefined ? - ` at ${startLine !== undefined ? `start: ${startLine}` : 'start'} to ${endLine !== undefined ? `end: ${endLine}` : 'end'}` : ''; + 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}`