Skip to content

Commit 3fcfc8d

Browse files
authored
Merge pull request #145 from RooVetGit/more_diff_iterations
More diff iterations
2 parents fb8a33e + f5bd5fa commit 3fcfc8d

File tree

15 files changed

+171
-187
lines changed

15 files changed

+171
-187
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Roo Cline Changelog
22

3+
## [2.2.14]
4+
5+
- Make diff editing more robust to transient errors
6+
37
## [2.2.13]
48

59
- Fixes to sound playing and applying diffs

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Roo Cline",
44
"description": "A fork of Cline, an autonomous coding agent, with some added experimental configuration and automation features.",
55
"publisher": "RooVeterinaryInc",
6-
"version": "2.2.13",
6+
"version": "2.2.14",
77
"icon": "assets/icons/rocket.png",
88
"galleryBanner": {
99
"color": "#617A91",

src/core/Cline.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export class Cline {
7575
private askResponseImages?: string[]
7676
private lastMessageTs?: number
7777
private consecutiveMistakeCount: number = 0
78+
private consecutiveMistakeCountForApplyDiff: Map<string, number> = new Map()
7879
private providerRef: WeakRef<ClineProvider>
7980
private abort: boolean = false
8081
didFinishAborting = false
@@ -97,7 +98,6 @@ export class Cline {
9798
apiConfiguration: ApiConfiguration,
9899
customInstructions?: string,
99100
diffEnabled?: boolean,
100-
debugDiffEnabled?: boolean,
101101
task?: string,
102102
images?: string[],
103103
historyItem?: HistoryItem,
@@ -110,7 +110,7 @@ export class Cline {
110110
this.diffViewProvider = new DiffViewProvider(cwd)
111111
this.customInstructions = customInstructions
112112
if (diffEnabled && this.api.getModel().id) {
113-
this.diffStrategy = getDiffStrategy(this.api.getModel().id, debugDiffEnabled)
113+
this.diffStrategy = getDiffStrategy(this.api.getModel().id)
114114
}
115115
if (historyItem) {
116116
this.taskId = historyItem.id
@@ -1230,8 +1230,9 @@ export class Cline {
12301230

12311231
if (!fileExists) {
12321232
this.consecutiveMistakeCount++
1233-
await this.say("error", `File does not exist at path: ${absolutePath}`)
1234-
pushToolResult(`Error: File does not exist at path: ${absolutePath}`)
1233+
const formattedError = `File does not exist at path: ${absolutePath}\n\n<error_details>\nThe specified file could not be found. Please verify the file path and try again.\n</error_details>`
1234+
await this.say("error", formattedError)
1235+
pushToolResult(formattedError)
12351236
break
12361237
}
12371238

@@ -1249,15 +1250,19 @@ export class Cline {
12491250
}
12501251
if (!diffResult.success) {
12511252
this.consecutiveMistakeCount++
1252-
const errorDetails = diffResult.details ? `\n\nDetails:\n${JSON.stringify(diffResult.details, null, 2)}` : ''
1253-
await this.say("error", `Unable to apply diff to file: ${absolutePath}\n${diffResult.error}${errorDetails}`)
1254-
pushToolResult(`Error applying diff to file: ${absolutePath}\n${diffResult.error}${errorDetails}`)
1253+
const currentCount = (this.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1
1254+
this.consecutiveMistakeCountForApplyDiff.set(relPath, currentCount)
1255+
const errorDetails = diffResult.details ? JSON.stringify(diffResult.details, null, 2) : ''
1256+
const formattedError = `Unable to apply diff to file: ${absolutePath}\n\n<error_details>\n${diffResult.error}${errorDetails ? `\n\nDetails:\n${errorDetails}` : ''}\n</error_details>`
1257+
if (currentCount >= 2) {
1258+
await this.say("error", formattedError)
1259+
}
1260+
pushToolResult(formattedError)
12551261
break
12561262
}
1257-
const newContent = diffResult.content
12581263

12591264
this.consecutiveMistakeCount = 0
1260-
1265+
this.consecutiveMistakeCountForApplyDiff.delete(relPath)
12611266
// Show diff view before asking for approval
12621267
this.diffViewProvider.editType = "modify"
12631268
await this.diffViewProvider.open(relPath);

src/core/__tests__/Cline.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,9 @@ describe('Cline', () => {
279279
mockApiConfig,
280280
'custom instructions',
281281
false, // diffEnabled
282-
false, // debugDiffEnabled
283-
'test task'
282+
'test task', // task
283+
undefined, // images
284+
undefined // historyItem
284285
);
285286

286287
expect(cline.customInstructions).toBe('custom instructions');

src/core/diff/DiffStrategy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import { SearchReplaceDiffStrategy } from './strategies/search-replace'
66
* @param model The name of the model being used (e.g., 'gpt-4', 'claude-3-opus')
77
* @returns The appropriate diff strategy for the model
88
*/
9-
export function getDiffStrategy(model: string, debugEnabled?: boolean): DiffStrategy {
9+
export function getDiffStrategy(model: string): DiffStrategy {
1010
// For now, return SearchReplaceDiffStrategy for all models (with a fuzzy threshold of 0.9)
1111
// This architecture allows for future optimizations based on model capabilities
12-
return new SearchReplaceDiffStrategy(0.9, debugEnabled)
12+
return new SearchReplaceDiffStrategy(0.9)
1313
}
1414

1515
export type { DiffStrategy }

src/core/diff/strategies/__tests__/search-replace.test.ts

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ describe('SearchReplaceDiffStrategy', () => {
55
let strategy: SearchReplaceDiffStrategy
66

77
beforeEach(() => {
8-
strategy = new SearchReplaceDiffStrategy() // Default 1.0 threshold for exact matching
8+
strategy = new SearchReplaceDiffStrategy(1.0, 5) // Default 1.0 threshold for exact matching, 5 line buffer for tests
99
})
1010

1111
it('should replace matching content', () => {
@@ -562,6 +562,63 @@ this.init();
562562
}`);
563563
}
564564
});
565+
566+
it('should find matches from middle out', () => {
567+
const originalContent = `
568+
function one() {
569+
return "target";
570+
}
571+
572+
function two() {
573+
return "target";
574+
}
575+
576+
function three() {
577+
return "target";
578+
}
579+
580+
function four() {
581+
return "target";
582+
}
583+
584+
function five() {
585+
return "target";
586+
}`.trim()
587+
588+
const diffContent = `test.ts
589+
<<<<<<< SEARCH
590+
return "target";
591+
=======
592+
return "updated";
593+
>>>>>>> REPLACE`
594+
595+
// Search around the middle (function three)
596+
// Even though all functions contain the target text,
597+
// it should match the one closest to line 9 first
598+
const result = strategy.applyDiff(originalContent, diffContent, 9, 9)
599+
expect(result.success).toBe(true)
600+
if (result.success) {
601+
expect(result.content).toBe(`function one() {
602+
return "target";
603+
}
604+
605+
function two() {
606+
return "target";
607+
}
608+
609+
function three() {
610+
return "updated";
611+
}
612+
613+
function four() {
614+
return "target";
615+
}
616+
617+
function five() {
618+
return "target";
619+
}`)
620+
}
621+
})
565622
})
566623

567624
describe('line number stripping', () => {
@@ -915,7 +972,7 @@ function test() {
915972
}
916973
})
917974

918-
it('should insert at the start of the file if no start_line is provided for insertion', () => {
975+
it('should error if no start_line is provided for insertion', () => {
919976
const originalContent = `function test() {
920977
return true;
921978
}`
@@ -926,22 +983,15 @@ console.log("test");
926983
>>>>>>> REPLACE`
927984

928985
const result = strategy.applyDiff(originalContent, diffContent)
929-
expect(result.success).toBe(true)
930-
if (result.success) {
931-
expect(result.content).toBe(`console.log("test");
932-
function test() {
933-
return true;
934-
}`)
935-
}
986+
expect(result.success).toBe(false)
936987
})
937988
})
938989
})
939990

940991
describe('fuzzy matching', () => {
941992
let strategy: SearchReplaceDiffStrategy
942-
943993
beforeEach(() => {
944-
strategy = new SearchReplaceDiffStrategy(0.9) // 90% similarity threshold
994+
strategy = new SearchReplaceDiffStrategy(0.9, 5) // 90% similarity threshold, 5 line buffer for tests
945995
})
946996

947997
it('should match content with small differences (>90% similar)', () => {
@@ -959,6 +1009,8 @@ function getData() {
9591009
}
9601010
>>>>>>> REPLACE`
9611011

1012+
strategy = new SearchReplaceDiffStrategy(0.9, 5) // Use 5 line buffer for tests
1013+
9621014
const result = strategy.applyDiff(originalContent, diffContent)
9631015
expect(result.success).toBe(true)
9641016
if (result.success) {
@@ -1008,7 +1060,7 @@ function sum(a, b) {
10081060
let strategy: SearchReplaceDiffStrategy
10091061

10101062
beforeEach(() => {
1011-
strategy = new SearchReplaceDiffStrategy()
1063+
strategy = new SearchReplaceDiffStrategy(0.9, 5)
10121064
})
10131065

10141066
it('should find and replace within specified line range', () => {
@@ -1467,8 +1519,8 @@ function two() {
14671519

14681520
it('should document start_line and end_line parameters', () => {
14691521
const description = strategy.getToolDescription('/test')
1470-
expect(description).toContain('start_line: (required) The line number where the search block starts (inclusive).')
1471-
expect(description).toContain('end_line: (required) The line number where the search block ends (inclusive).')
1522+
expect(description).toContain('start_line: (required) The line number where the search block starts.')
1523+
expect(description).toContain('end_line: (required) The line number where the search block ends.')
14721524
})
14731525
})
14741526
})

0 commit comments

Comments
 (0)