Sfoglia il codice sorgente

Merge pull request #364 from daniel-lxs/new_unified

New unified edit strategy
Matt Rubens 11 mesi fa
parent
commit
b35206bc9d

+ 99 - 8
package-lock.json

@@ -17,6 +17,7 @@
         "@modelcontextprotocol/sdk": "^1.0.1",
         "@types/clone-deep": "^4.0.4",
         "@types/pdf-parse": "^1.1.4",
+        "@types/tmp": "^0.2.6",
         "@types/turndown": "^5.0.5",
         "@types/vscode": "^1.95.0",
         "@vscode/codicons": "^0.0.36",
@@ -27,7 +28,9 @@
         "default-shell": "^2.2.0",
         "delay": "^6.0.0",
         "diff": "^5.2.0",
+        "diff-match-patch": "^1.0.5",
         "fast-deep-equal": "^3.1.3",
+        "fastest-levenshtein": "^1.0.16",
         "globby": "^14.0.2",
         "isbinaryfile": "^5.0.2",
         "mammoth": "^1.8.0",
@@ -39,8 +42,11 @@
         "puppeteer-chromium-resolver": "^23.0.0",
         "puppeteer-core": "^23.4.0",
         "serialize-error": "^11.0.3",
+        "simple-git": "^3.27.0",
         "sound-play": "^1.1.0",
+        "string-similarity": "^4.0.4",
         "strip-ansi": "^7.1.0",
+        "tmp": "^0.2.3",
         "tree-sitter-wasms": "^0.1.11",
         "turndown": "^7.2.0",
         "web-tree-sitter": "^0.22.6",
@@ -50,9 +56,11 @@
         "@changesets/cli": "^2.27.10",
         "@changesets/types": "^6.0.0",
         "@types/diff": "^5.2.1",
+        "@types/diff-match-patch": "^1.0.36",
         "@types/jest": "^29.5.14",
         "@types/mocha": "^10.0.7",
         "@types/node": "20.x",
+        "@types/string-similarity": "^4.0.2",
         "@typescript-eslint/eslint-plugin": "^7.14.1",
         "@typescript-eslint/parser": "^7.11.0",
         "@vscode/test-cli": "^0.0.9",
@@ -4108,6 +4116,21 @@
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
+    "node_modules/@kwsites/file-exists": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
+      "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.1.1"
+      }
+    },
+    "node_modules/@kwsites/promise-deferred": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
+      "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
+      "license": "MIT"
+    },
     "node_modules/@manypkg/find-root": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz",
@@ -6069,6 +6092,13 @@
       "integrity": "sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ==",
       "dev": true
     },
+    "node_modules/@types/diff-match-patch": {
+      "version": "1.0.36",
+      "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz",
+      "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/graceful-fs": {
       "version": "4.1.9",
       "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
@@ -6146,6 +6176,19 @@
       "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
       "dev": true
     },
+    "node_modules/@types/string-similarity": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/@types/string-similarity/-/string-similarity-4.0.2.tgz",
+      "integrity": "sha512-LkJQ/jsXtCVMK+sKYAmX/8zEq+/46f1PTQw7YtmQwb74jemS1SlNLmARM2Zml9DgdDTWKAtc5L13WorpHPDjDA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/tmp": {
+      "version": "0.2.6",
+      "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz",
+      "integrity": "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==",
+      "license": "MIT"
+    },
     "node_modules/@types/turndown": {
       "version": "5.0.5",
       "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz",
@@ -7913,6 +7956,12 @@
         "node": ">=0.3.1"
       }
     },
+    "node_modules/diff-match-patch": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
+      "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
+      "license": "Apache-2.0"
+    },
     "node_modules/diff-sequences": {
       "version": "29.6.3",
       "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
@@ -8716,6 +8765,19 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/external-editor/node_modules/tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "os-tmpdir": "~1.0.2"
+      },
+      "engines": {
+        "node": ">=0.6.0"
+      }
+    },
     "node_modules/extract-zip": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
@@ -8807,6 +8869,15 @@
         "fxparser": "src/cli/cli.js"
       }
     },
+    "node_modules/fastest-levenshtein": {
+      "version": "1.0.16",
+      "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
+      "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 4.9.1"
+      }
+    },
     "node_modules/fastq": {
       "version": "1.17.1",
       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
@@ -12720,6 +12791,7 @@
       "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
       "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
       "dev": true,
+      "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
       }
@@ -13926,6 +13998,21 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/simple-git": {
+      "version": "3.27.0",
+      "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz",
+      "integrity": "sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==",
+      "license": "MIT",
+      "dependencies": {
+        "@kwsites/file-exists": "^1.1.1",
+        "@kwsites/promise-deferred": "^1.1.1",
+        "debug": "^4.3.5"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/steveukx/git-js?sponsor=1"
+      }
+    },
     "node_modules/sisteransi": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -14202,6 +14289,13 @@
         "node": ">=8"
       }
     },
+    "node_modules/string-similarity": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz",
+      "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==",
+      "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
+      "license": "ISC"
+    },
     "node_modules/string-width": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@@ -14544,15 +14638,12 @@
       "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
     },
     "node_modules/tmp": {
-      "version": "0.0.33",
-      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
-      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
-      "dev": true,
-      "dependencies": {
-        "os-tmpdir": "~1.0.2"
-      },
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
+      "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
+      "license": "MIT",
       "engines": {
-        "node": ">=0.6.0"
+        "node": ">=14.14"
       }
     },
     "node_modules/tmpl": {

+ 8 - 0
package.json

@@ -202,9 +202,11 @@
     "@changesets/cli": "^2.27.10",
     "@changesets/types": "^6.0.0",
     "@types/diff": "^5.2.1",
+    "@types/diff-match-patch": "^1.0.36",
     "@types/jest": "^29.5.14",
     "@types/mocha": "^10.0.7",
     "@types/node": "20.x",
+    "@types/string-similarity": "^4.0.2",
     "@typescript-eslint/eslint-plugin": "^7.14.1",
     "@typescript-eslint/parser": "^7.11.0",
     "@vscode/test-cli": "^0.0.9",
@@ -230,6 +232,7 @@
     "@modelcontextprotocol/sdk": "^1.0.1",
     "@types/clone-deep": "^4.0.4",
     "@types/pdf-parse": "^1.1.4",
+    "@types/tmp": "^0.2.6",
     "@types/turndown": "^5.0.5",
     "@types/vscode": "^1.95.0",
     "@vscode/codicons": "^0.0.36",
@@ -240,7 +243,9 @@
     "default-shell": "^2.2.0",
     "delay": "^6.0.0",
     "diff": "^5.2.0",
+    "diff-match-patch": "^1.0.5",
     "fast-deep-equal": "^3.1.3",
+    "fastest-levenshtein": "^1.0.16",
     "globby": "^14.0.2",
     "isbinaryfile": "^5.0.2",
     "mammoth": "^1.8.0",
@@ -252,8 +257,11 @@
     "puppeteer-chromium-resolver": "^23.0.0",
     "puppeteer-core": "^23.4.0",
     "serialize-error": "^11.0.3",
+    "simple-git": "^3.27.0",
     "sound-play": "^1.1.0",
+    "string-similarity": "^4.0.4",
     "strip-ansi": "^7.1.0",
+    "tmp": "^0.2.3",
     "tree-sitter-wasms": "^0.1.11",
     "turndown": "^7.2.0",
     "web-tree-sitter": "^0.22.6",

+ 32 - 12
src/core/Cline.ts

@@ -52,6 +52,7 @@ import { detectCodeOmission } from "../integrations/editor/detect-omission"
 import { BrowserSession } from "../services/browser/BrowserSession"
 import { OpenRouterHandler } from "../api/providers/openrouter"
 import { McpHub } from "../services/mcp/McpHub"
+import crypto from "crypto"
 
 const cwd =
 	vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
@@ -71,6 +72,7 @@ export class Cline {
 	customInstructions?: string
 	diffStrategy?: DiffStrategy
 	diffEnabled: boolean = false
+	fuzzyMatchThreshold: number = 1.0
 
 	apiConversationHistory: (Anthropic.MessageParam & { ts?: number })[] = []
 	clineMessages: ClineMessage[] = []
@@ -105,28 +107,46 @@ export class Cline {
 		fuzzyMatchThreshold?: number,
 		task?: string | undefined,
 		images?: string[] | undefined,
-		historyItem?: HistoryItem | undefined
+		historyItem?: HistoryItem | undefined,
+		experimentalDiffStrategy: boolean = false,
 	) {
-		this.providerRef = new WeakRef(provider)
+		if (!task && !images && !historyItem) {
+			throw new Error('Either historyItem or task/images must be provided');
+		}
+
+		this.taskId = crypto.randomUUID()
 		this.api = buildApiHandler(apiConfiguration)
 		this.terminalManager = new TerminalManager()
 		this.urlContentFetcher = new UrlContentFetcher(provider.context)
 		this.browserSession = new BrowserSession(provider.context)
-		this.diffViewProvider = new DiffViewProvider(cwd)
 		this.customInstructions = customInstructions
 		this.diffEnabled = enableDiff ?? false
-		if (this.diffEnabled && this.api.getModel().id) {
-			this.diffStrategy = getDiffStrategy(this.api.getModel().id, fuzzyMatchThreshold ?? 1.0)
-		}
+		this.fuzzyMatchThreshold = fuzzyMatchThreshold ?? 1.0
+		this.providerRef = new WeakRef(provider)
+		this.diffViewProvider = new DiffViewProvider(cwd)
+
 		if (historyItem) {
 			this.taskId = historyItem.id
-			this.resumeTaskFromHistory()
-		} else if (task || images) {
-			this.taskId = Date.now().toString()
+		}
+
+		// Initialize diffStrategy based on current state
+		this.updateDiffStrategy(experimentalDiffStrategy)
+
+		if (task || images) {
 			this.startTask(task, images)
-		} else {
-			throw new Error("Either historyItem or task/images must be provided")
+		} else if (historyItem) {
+			this.resumeTaskFromHistory()
+		}
+	}
+
+	// Add method to update diffStrategy
+	async updateDiffStrategy(experimentalDiffStrategy?: boolean) {
+		// If not provided, get from current state
+		if (experimentalDiffStrategy === undefined) {
+			const { experimentalDiffStrategy: stateExperimentalDiffStrategy } = await this.providerRef.deref()?.getState() ?? {}
+			experimentalDiffStrategy = stateExperimentalDiffStrategy ?? false
 		}
+		this.diffStrategy = getDiffStrategy(this.api.getModel().id, this.fuzzyMatchThreshold, experimentalDiffStrategy)
 	}
 
 	// Storing task to disk for history
@@ -1326,7 +1346,7 @@ export class Cline {
 								const originalContent = await fs.readFile(absolutePath, "utf-8")
 
 								// Apply the diff to the original content
-								const diffResult = this.diffStrategy?.applyDiff(
+								const diffResult = await this.diffStrategy?.applyDiff(
 									originalContent, 
 									diffContent, 
 									parseInt(block.params.start_line ?? ''), 

+ 2 - 2
src/core/__tests__/Cline.test.ts

@@ -322,7 +322,7 @@ describe('Cline', () => {
 
             expect(cline.diffEnabled).toBe(true);
             expect(cline.diffStrategy).toBeDefined();
-            expect(getDiffStrategySpy).toHaveBeenCalledWith('claude-3-5-sonnet-20241022', 0.9);
+            expect(getDiffStrategySpy).toHaveBeenCalledWith('claude-3-5-sonnet-20241022', 0.9, false);
             
             getDiffStrategySpy.mockRestore();
         });
@@ -341,7 +341,7 @@ describe('Cline', () => {
 
             expect(cline.diffEnabled).toBe(true);
             expect(cline.diffStrategy).toBeDefined();
-            expect(getDiffStrategySpy).toHaveBeenCalledWith('claude-3-5-sonnet-20241022', 1.0);
+            expect(getDiffStrategySpy).toHaveBeenCalledWith('claude-3-5-sonnet-20241022', 1.0, false);
             
             getDiffStrategySpy.mockRestore();
         });

+ 6 - 4
src/core/diff/DiffStrategy.ts

@@ -1,15 +1,17 @@
 import type { DiffStrategy } from './types'
 import { UnifiedDiffStrategy } from './strategies/unified'
 import { SearchReplaceDiffStrategy } from './strategies/search-replace'
+import { NewUnifiedDiffStrategy } from './strategies/new-unified'
 /**
  * Get the appropriate diff strategy for the given model
  * @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, fuzzyMatchThreshold?: number): DiffStrategy {
-    // For now, return SearchReplaceDiffStrategy for all models
-    // This architecture allows for future optimizations based on model capabilities
-    return new SearchReplaceDiffStrategy(fuzzyMatchThreshold ?? 1.0)
+export function getDiffStrategy(model: string, fuzzyMatchThreshold?: number, experimentalDiffStrategy: boolean = false): DiffStrategy {
+    if (experimentalDiffStrategy) {
+        return new NewUnifiedDiffStrategy(fuzzyMatchThreshold)
+    }
+    return new SearchReplaceDiffStrategy(fuzzyMatchThreshold)
 }
 
 export type { DiffStrategy }

+ 739 - 0
src/core/diff/strategies/__tests__/new-unified.test.ts

@@ -0,0 +1,739 @@
+import { NewUnifiedDiffStrategy } from '../new-unified';
+
+describe('main', () => {
+
+  let strategy: NewUnifiedDiffStrategy
+
+  beforeEach(() => {
+      strategy = new NewUnifiedDiffStrategy(0.97)
+  })
+
+  describe('constructor', () => {
+    it('should use default confidence threshold when not provided', () => {
+      const defaultStrategy = new NewUnifiedDiffStrategy()
+      expect(defaultStrategy['confidenceThreshold']).toBe(1)
+    })
+
+    it('should use provided confidence threshold', () => {
+      const customStrategy = new NewUnifiedDiffStrategy(0.85)
+      expect(customStrategy['confidenceThreshold']).toBe(0.85)
+    })
+
+    it('should enforce minimum confidence threshold', () => {
+      const lowStrategy = new NewUnifiedDiffStrategy(0.7) // Below minimum of 0.8
+      expect(lowStrategy['confidenceThreshold']).toBe(0.8)
+    })
+  })
+
+  describe('getToolDescription', () => {
+      it('should return tool description with correct cwd', () => {
+          const cwd = '/test/path'
+          const description = strategy.getToolDescription(cwd)
+          
+          expect(description).toContain('apply_diff')
+          expect(description).toContain(cwd)
+          expect(description).toContain('Parameters:')
+          expect(description).toContain('Format Requirements:')
+      })
+  })
+
+  it('should apply simple diff correctly', async () => {
+    const original = `line1
+line2
+line3`;
+
+    const diff = `--- a/file.txt
++++ b/file.txt
+@@ ... @@
+ line1
++new line
+ line2
+-line3
++modified line3`;
+
+    const result = await strategy.applyDiff(original, diff);
+    expect(result.success).toBe(true);
+    if(result.success) {
+      expect(result.content).toBe(`line1
+new line
+line2
+modified line3`);
+    }
+  });
+
+  it('should handle multiple hunks', async () => {
+    const original = `line1
+line2
+line3
+line4
+line5`;
+
+    const diff = `--- a/file.txt
++++ b/file.txt
+@@ ... @@
+ line1
++new line
+ line2
+-line3
++modified line3
+@@ ... @@
+ line4
+-line5
++modified line5
++new line at end`;
+
+    const result = await strategy.applyDiff(original, diff);
+    expect(result.success).toBe(true);
+    if (result.success) {
+      expect(result.content).toBe(`line1
+new line
+line2
+modified line3
+line4
+modified line5
+new line at end`);
+    }
+  });
+
+  it('should handle complex large', async () => {
+    const original = `line1
+line2
+line3
+line4
+line5
+line6
+line7
+line8
+line9
+line10`;
+
+    const diff = `--- a/file.txt
++++ b/file.txt
+@@ ... @@
+ line1
++header line
++another header
+ line2
+-line3
+-line4
++modified line3
++modified line4
++extra line
+@@ ... @@
+ line6
++middle section
+ line7
+-line8
++changed line8
++bonus line
+@@ ... @@
+ line9
+-line10
++final line
++very last line`;
+
+    const result = await strategy.applyDiff(original, diff);
+    expect(result.success).toBe(true);
+    if (result.success) {
+      expect(result.content).toBe(`line1
+header line
+another header
+line2
+modified line3
+modified line4
+extra line
+line5
+line6
+middle section
+line7
+changed line8
+bonus line
+line9
+final line
+very last line`);
+    }
+  });
+
+  it('should handle indentation changes', async () => {
+    const original = `first line
+  indented line
+    double indented line
+  back to single indent
+no indent
+  indented again
+    double indent again
+      triple indent
+  back to single
+last line`;
+
+    const diff = `--- original
++++ modified
+@@ ... @@
+ first line
+   indented line
++	tab indented line
++  new indented line
+     double indented line
+   back to single indent
+ no indent
+   indented again
+     double indent again
+-      triple indent
++      hi there mate
+   back to single
+ last line`;
+
+    const expected = `first line
+  indented line
+	tab indented line
+  new indented line
+    double indented line
+  back to single indent
+no indent
+  indented again
+    double indent again
+      hi there mate
+  back to single
+last line`;
+
+    const result = await strategy.applyDiff(original, diff);
+    expect(result.success).toBe(true);
+    if (result.success) {
+      expect(result.content).toBe(expected);
+    }
+  });
+
+  it('should handle high level edits', async () => {
+
+    const original = `def factorial(n):
+    if n == 0:
+        return 1
+    else:
+        return n * factorial(n-1)`
+    const diff = `@@ ... @@
+-def factorial(n):
+-    if n == 0:
+-        return 1
+-    else:
+-        return n * factorial(n-1)
++def factorial(number):
++    if number == 0:
++        return 1
++    else:
++        return number * factorial(number-1)`
+
+const expected = `def factorial(number):
+    if number == 0:
+        return 1
+    else:
+        return number * factorial(number-1)`
+
+    const result = await strategy.applyDiff(original, diff);
+    expect(result.success).toBe(true);
+    if (result.success) {
+      expect(result.content).toBe(expected);
+    }
+  });
+
+  it('it should handle very complex edits', async () => {
+    const original = `//Initialize the array that will hold the primes
+var primeArray = [];
+/*Write a function that checks for primeness and
+ pushes those values to t*he array*/
+function PrimeCheck(candidate){
+  isPrime = true;
+  for(var i = 2; i < candidate && isPrime; i++){
+    if(candidate%i === 0){
+      isPrime = false;
+    } else {
+      isPrime = true;
+    }
+  }
+  if(isPrime){
+    primeArray.push(candidate);
+  }
+  return primeArray;
+}
+/*Write the code that runs the above until the
+ l ength of the array equa*ls the number of primes
+ desired*/
+
+var numPrimes = prompt("How many primes?");
+
+//Display the finished array of primes
+
+//for loop starting at 2 as that is the lowest prime number keep going until the array is as long as we requested
+for (var i = 2; primeArray.length < numPrimes; i++) {
+  PrimeCheck(i); //
+}
+console.log(primeArray);
+`
+
+    const diff = `--- test_diff.js
++++ test_diff.js
+@@ ... @@
+-//Initialize the array that will hold the primes
+ var primeArray = [];
+-/*Write a function that checks for primeness and
+- pushes those values to t*he array*/
+ function PrimeCheck(candidate){
+   isPrime = true;
+   for(var i = 2; i < candidate && isPrime; i++){
+@@ ... @@
+   return primeArray;
+ }
+-/*Write the code that runs the above until the
+-  l ength of the array equa*ls the number of primes
+-  desired*/
+ 
+ var numPrimes = prompt("How many primes?");
+ 
+-//Display the finished array of primes
+-
+-//for loop starting at 2 as that is the lowest prime number keep going until the array is as long as we requested
+ for (var i = 2; primeArray.length < numPrimes; i++) {
+-  PrimeCheck(i); //
++  PrimeCheck(i);
+ }
+ console.log(primeArray);`
+
+    const expected = `var primeArray = [];
+function PrimeCheck(candidate){
+  isPrime = true;
+  for(var i = 2; i < candidate && isPrime; i++){
+    if(candidate%i === 0){
+      isPrime = false;
+    } else {
+      isPrime = true;
+    }
+  }
+  if(isPrime){
+    primeArray.push(candidate);
+  }
+  return primeArray;
+}
+
+var numPrimes = prompt("How many primes?");
+
+for (var i = 2; primeArray.length < numPrimes; i++) {
+  PrimeCheck(i);
+}
+console.log(primeArray);
+`
+ 
+
+    const result = await strategy.applyDiff(original, diff);
+    expect(result.success).toBe(true);
+    if (result.success) {
+      expect(result.content).toBe(expected);
+    }
+  });
+
+  describe('error handling and edge cases', () => {
+    it('should reject completely invalid diff format', async () => {
+      const original = 'line1\nline2\nline3';
+      const invalidDiff = 'this is not a diff at all';
+      
+      const result = await strategy.applyDiff(original, invalidDiff);
+      expect(result.success).toBe(false);
+    });
+
+    it('should reject diff with invalid hunk format', async () => {
+      const original = 'line1\nline2\nline3';
+      const invalidHunkDiff = `--- a/file.txt
++++ b/file.txt
+invalid hunk header
+ line1
+-line2
++new line`;
+      
+      const result = await strategy.applyDiff(original, invalidHunkDiff);
+      expect(result.success).toBe(false);
+    });
+
+    it('should fail when diff tries to modify non-existent content', async () => {
+      const original = 'line1\nline2\nline3';
+      const nonMatchingDiff = `--- a/file.txt
++++ b/file.txt
+@@ ... @@
+ line1
+-nonexistent line
++new line
+ line3`;
+      
+      const result = await strategy.applyDiff(original, nonMatchingDiff);
+      expect(result.success).toBe(false);
+    });
+
+    it('should handle overlapping hunks', async () => {
+      const original = `line1
+line2
+line3
+line4
+line5`;
+      const overlappingDiff = `--- a/file.txt
++++ b/file.txt
+@@ ... @@
+ line1
+ line2
+-line3
++modified3
+ line4
+@@ ... @@
+ line2
+-line3
+-line4
++modified3and4
+ line5`;
+      
+      const result = await strategy.applyDiff(original, overlappingDiff);
+      expect(result.success).toBe(false);
+    });
+
+    it('should handle empty lines modifications', async () => {
+      const original = `line1
+
+line3
+
+line5`;
+      const emptyLinesDiff = `--- a/file.txt
++++ b/file.txt
+@@ ... @@
+ line1
+
+-line3
++line3modified
+
+ line5`;
+      
+      const result = await strategy.applyDiff(original, emptyLinesDiff);
+      expect(result.success).toBe(true);
+      if (result.success) {
+        expect(result.content).toBe(`line1
+
+line3modified
+
+line5`);
+      }
+    });
+
+    it('should handle mixed line endings in diff', async () => {
+      const original = 'line1\r\nline2\nline3\r\n';
+      const mixedEndingsDiff = `--- a/file.txt
++++ b/file.txt
+@@ ... @@
+ line1\r
+-line2
++modified2\r
+ line3`;
+      
+      const result = await strategy.applyDiff(original, mixedEndingsDiff);
+      expect(result.success).toBe(true);
+      if (result.success) {
+        expect(result.content).toBe('line1\r\nmodified2\r\nline3\r\n');
+      }
+    });
+
+    it('should handle partial line modifications', async () => {
+      const original = 'const value = oldValue + 123;';
+      const partialDiff = `--- a/file.txt
++++ b/file.txt
+@@ ... @@
+-const value = oldValue + 123;
++const value = newValue + 123;`;
+      
+      const result = await strategy.applyDiff(original, partialDiff);
+      expect(result.success).toBe(true);
+      if (result.success) {
+        expect(result.content).toBe('const value = newValue + 123;');
+      }
+    });
+
+    it('should handle slightly malformed but recoverable diff', async () => {
+      const original = 'line1\nline2\nline3';
+      // Missing space after --- and +++
+      const slightlyBadDiff = `---a/file.txt
++++b/file.txt
+@@ ... @@
+ line1
+-line2
++new line
+ line3`;
+      
+      const result = await strategy.applyDiff(original, slightlyBadDiff);
+      expect(result.success).toBe(true);
+      if (result.success) {
+        expect(result.content).toBe('line1\nnew line\nline3');
+      }
+    });
+  });
+
+  describe('similar code sections', () => {
+    it('should correctly modify the right section when similar code exists', async () => {
+      const original = `function add(a, b) {
+  return a + b;
+}
+
+function subtract(a, b) {
+  return a - b;
+}
+
+function multiply(a, b) {
+  return a + b;  // Bug here
+}`;
+
+      const diff = `--- a/math.js
++++ b/math.js
+@@ ... @@
+ function multiply(a, b) {
+-  return a + b;  // Bug here
++  return a * b;
+ }`;
+
+      const result = await strategy.applyDiff(original, diff);
+      expect(result.success).toBe(true);
+      if (result.success) {
+        expect(result.content).toBe(`function add(a, b) {
+  return a + b;
+}
+
+function subtract(a, b) {
+  return a - b;
+}
+
+function multiply(a, b) {
+  return a * b;
+}`);
+      }
+    });
+
+    it('should handle multiple similar sections with correct context', async () => {
+      const original = `if (condition) {
+  doSomething();
+  doSomething();
+  doSomething();
+}
+
+if (otherCondition) {
+  doSomething();
+  doSomething();
+  doSomething();
+}`;
+
+      const diff = `--- a/file.js
++++ b/file.js
+@@ ... @@
+ if (otherCondition) {
+   doSomething();
+-  doSomething();
++  doSomethingElse();
+   doSomething();
+ }`;
+
+      const result = await strategy.applyDiff(original, diff);
+      expect(result.success).toBe(true);
+      if (result.success) {
+        expect(result.content).toBe(`if (condition) {
+  doSomething();
+  doSomething();
+  doSomething();
+}
+
+if (otherCondition) {
+  doSomething();
+  doSomethingElse();
+  doSomething();
+}`);
+      }
+    });
+  });
+
+  describe('hunk splitting', () => {
+    it('should handle large diffs with multiple non-contiguous changes', async () => {
+      const original = `import { readFile } from 'fs';
+import { join } from 'path';
+import { Logger } from './logger';
+
+const logger = new Logger();
+
+async function processFile(filePath: string) {
+  try {
+    const data = await readFile(filePath, 'utf8');
+    logger.info('File read successfully');
+    return data;
+  } catch (error) {
+    logger.error('Failed to read file:', error);
+    throw error;
+  }
+}
+
+function validateInput(input: string): boolean {
+  if (!input) {
+    logger.warn('Empty input received');
+    return false;
+  }
+  return input.length > 0;
+}
+
+async function writeOutput(data: string) {
+  logger.info('Processing output');
+  // TODO: Implement output writing
+  return Promise.resolve();
+}
+
+function parseConfig(configPath: string) {
+  logger.debug('Reading config from:', configPath);
+  // Basic config parsing
+  return {
+    enabled: true,
+    maxRetries: 3
+  };
+}
+
+export {
+  processFile,
+  validateInput,
+  writeOutput,
+  parseConfig
+};`;
+
+      const diff = `--- a/file.ts
++++ b/file.ts
+@@ ... @@
+-import { readFile } from 'fs';
++import { readFile, writeFile } from 'fs';
+ import { join } from 'path';
+-import { Logger } from './logger';
++import { Logger } from './utils/logger';
++import { Config } from './types';
+ 
+-const logger = new Logger();
++const logger = new Logger('FileProcessor');
+ 
+ async function processFile(filePath: string) {
+   try {
+     const data = await readFile(filePath, 'utf8');
+-    logger.info('File read successfully');
++    logger.info(\`File \${filePath} read successfully\`);
+     return data;
+   } catch (error) {
+-    logger.error('Failed to read file:', error);
++    logger.error(\`Failed to read file \${filePath}:\`, error);
+     throw error;
+   }
+ }
+ 
+ function validateInput(input: string): boolean {
+   if (!input) {
+-    logger.warn('Empty input received');
++    logger.warn('Validation failed: Empty input received');
+     return false;
+   }
+-  return input.length > 0;
++  return input.trim().length > 0;
+ }
+ 
+-async function writeOutput(data: string) {
+-  logger.info('Processing output');
+-  // TODO: Implement output writing
+-  return Promise.resolve();
++async function writeOutput(data: string, outputPath: string) {
++  try {
++    await writeFile(outputPath, data, 'utf8');
++    logger.info(\`Output written to \${outputPath}\`);
++  } catch (error) {
++    logger.error(\`Failed to write output to \${outputPath}:\`, error);
++    throw error;
++  }
+ }
+ 
+-function parseConfig(configPath: string) {
+-  logger.debug('Reading config from:', configPath);
+-  // Basic config parsing
+-  return {
+-    enabled: true,
+-    maxRetries: 3
+-  };
++async function parseConfig(configPath: string): Promise<Config> {
++  try {
++    const configData = await readFile(configPath, 'utf8');
++    logger.debug(\`Reading config from \${configPath}\`);
++    return JSON.parse(configData);
++  } catch (error) {
++    logger.error(\`Failed to parse config from \${configPath}:\`, error);
++    throw error;
++  }
+ }
+ 
+ export {
+   processFile,
+   validateInput,
+   writeOutput,
+-  parseConfig
++  parseConfig,
++  type Config
+ };`;
+
+      const expected = `import { readFile, writeFile } from 'fs';
+import { join } from 'path';
+import { Logger } from './utils/logger';
+import { Config } from './types';
+
+const logger = new Logger('FileProcessor');
+
+async function processFile(filePath: string) {
+  try {
+    const data = await readFile(filePath, 'utf8');
+    logger.info(\`File \${filePath} read successfully\`);
+    return data;
+  } catch (error) {
+    logger.error(\`Failed to read file \${filePath}:\`, error);
+    throw error;
+  }
+}
+
+function validateInput(input: string): boolean {
+  if (!input) {
+    logger.warn('Validation failed: Empty input received');
+    return false;
+  }
+  return input.trim().length > 0;
+}
+
+async function writeOutput(data: string, outputPath: string) {
+  try {
+    await writeFile(outputPath, data, 'utf8');
+    logger.info(\`Output written to \${outputPath}\`);
+  } catch (error) {
+    logger.error(\`Failed to write output to \${outputPath}:\`, error);
+    throw error;
+  }
+}
+
+async function parseConfig(configPath: string): Promise<Config> {
+  try {
+    const configData = await readFile(configPath, 'utf8');
+    logger.debug(\`Reading config from \${configPath}\`);
+    return JSON.parse(configData);
+  } catch (error) {
+    logger.error(\`Failed to parse config from \${configPath}:\`, error);
+    throw error;
+  }
+}
+
+export {
+  processFile,
+  validateInput,
+  writeOutput,
+  parseConfig,
+  type Config
+};`;
+
+      const result = await strategy.applyDiff(original, diff);
+      expect(result.success).toBe(true);
+      if (result.success) {
+        expect(result.content).toBe(expected);
+      }
+    });
+  });
+});

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

@@ -8,7 +8,7 @@ describe('SearchReplaceDiffStrategy', () => {
             strategy = new SearchReplaceDiffStrategy(1.0, 5) // Default 1.0 threshold for exact matching, 5 line buffer for tests
         })
 
-        it('should replace matching content', () => {
+        it('should replace matching content', async () => {
             const originalContent = 'function hello() {\n    console.log("hello")\n}\n'
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -21,14 +21,14 @@ function hello() {
 }
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe('function hello() {\n    console.log("hello world")\n}\n')
             }
         })
 
-        it('should match content with different surrounding whitespace', () => {
+        it('should match content with different surrounding whitespace', async () => {
             const originalContent = '\nfunction example() {\n    return 42;\n}\n\n'
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -41,14 +41,14 @@ function example() {
 }
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe('\nfunction example() {\n    return 43;\n}\n\n')
             }
         })
 
-        it('should match content with different indentation in search block', () => {
+        it('should match content with different indentation in search block', async () => {
             const originalContent = '    function test() {\n        return true;\n    }\n'
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -61,14 +61,14 @@ function test() {
 }
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe('    function test() {\n        return false;\n    }\n')
             }
         })
 
-        it('should handle tab-based indentation', () => {
+        it('should handle tab-based indentation', async () => {
             const originalContent = "function test() {\n\treturn true;\n}\n"
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -81,14 +81,14 @@ function test() {
 }
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe("function test() {\n\treturn false;\n}\n")
             }
         })
 
-        it('should preserve mixed tabs and spaces', () => {
+        it('should preserve mixed tabs and spaces', async () => {
             const originalContent = "\tclass Example {\n\t    constructor() {\n\t\tthis.value = 0;\n\t    }\n\t}"
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -105,14 +105,14 @@ function test() {
 \t}
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe("\tclass Example {\n\t    constructor() {\n\t\tthis.value = 1;\n\t    }\n\t}")
             }
         })
 
-        it('should handle additional indentation with tabs', () => {
+        it('should handle additional indentation with tabs', async () => {
             const originalContent = "\tfunction test() {\n\t\treturn true;\n\t}"
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -126,14 +126,14 @@ function test() {
 }
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe("\tfunction test() {\n\t\t// Add comment\n\t\treturn false;\n\t}")
             }
         })
 
-        it('should preserve exact indentation characters when adding lines', () => {
+        it('should preserve exact indentation characters when adding lines', async () => {
             const originalContent = "\tfunction test() {\n\t\treturn true;\n\t}"
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -148,14 +148,14 @@ function test() {
 \t}
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe("\tfunction test() {\n\t\t// First comment\n\t\t// Second comment\n\t\treturn true;\n\t}")
             }
         })
 
-        it('should handle Windows-style CRLF line endings', () => {
+        it('should handle Windows-style CRLF line endings', async () => {
             const originalContent = "function test() {\r\n    return true;\r\n}\r\n"
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -168,14 +168,14 @@ function test() {
 }
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe("function test() {\r\n    return false;\r\n}\r\n")
             }
         })
 
-        it('should return false if search content does not match', () => {
+        it('should return false if search content does not match', async () => {
             const originalContent = 'function hello() {\n    console.log("hello")\n}\n'
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -188,19 +188,19 @@ function hello() {
 }
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(false)
         })
 
-        it('should return false if diff format is invalid', () => {
+        it('should return false if diff format is invalid', async () => {
             const originalContent = 'function hello() {\n    console.log("hello")\n}\n'
             const diffContent = `test.ts\nInvalid diff format`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(false)
         })
 
-        it('should handle multiple lines with proper indentation', () => {
+        it('should handle multiple lines with proper indentation', async () => {
             const originalContent = 'class Example {\n    constructor() {\n        this.value = 0\n    }\n\n    getValue() {\n        return this.value\n    }\n}\n'
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -215,14 +215,14 @@ function hello() {
     }
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe('class Example {\n    constructor() {\n        this.value = 0\n    }\n\n    getValue() {\n        // Add logging\n        console.log("Getting value")\n        return this.value\n    }\n}\n')
             }
         })
 
-        it('should preserve whitespace exactly in the output', () => {
+        it('should preserve whitespace exactly in the output', async () => {
             const originalContent = "    indented\n        more indented\n    back\n"
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -235,14 +235,14 @@ function hello() {
     end
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe("    modified\n        still indented\n    end\n")
             }
         })
 
-        it('should preserve indentation when adding new lines after existing content', () => {
+        it('should preserve indentation when adding new lines after existing content', async () => {
             const originalContent = '				onScroll={() => updateHighlights()}'
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -255,14 +255,14 @@ function hello() {
 				}}
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe('				onScroll={() => updateHighlights()}\n				onDragOver={(e) => {\n					e.preventDefault()\n					e.stopPropagation()\n				}}')
             }
         })
 
-        it('should handle varying indentation levels correctly', () => {
+        it('should handle varying indentation levels correctly', async () => {
             const originalContent = `
 class Example {
     constructor() {
@@ -296,7 +296,7 @@ class Example {
     }
 >>>>>>> REPLACE`.trim();
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`
@@ -313,7 +313,7 @@ class Example {
             }
         })
 
-        it('should handle mixed indentation styles in the same file', () => {
+        it('should handle mixed indentation styles in the same file', async () => {
             const originalContent = `class Example {
     constructor() {
         this.value = 0;
@@ -340,7 +340,7 @@ class Example {
     }
 >>>>>>> REPLACE`;
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`class Example {
@@ -355,7 +355,7 @@ class Example {
             }
         })
         
-        it('should handle Python-style significant whitespace', () => {
+        it('should handle Python-style significant whitespace', async () => {
             const originalContent = `def example():
     if condition:
         do_something()
@@ -376,7 +376,7 @@ class Example {
             process(item)
 >>>>>>> REPLACE`;
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`def example():
@@ -389,7 +389,7 @@ class Example {
             }
         });
         
-        it('should preserve empty lines with indentation', () => {
+        it('should preserve empty lines with indentation', async () => {
             const originalContent = `function test() {
     const x = 1;
     
@@ -409,7 +409,7 @@ class Example {
     if (x) {
 >>>>>>> REPLACE`;
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`function test() {
@@ -423,7 +423,7 @@ class Example {
             }  
         });
         
-        it('should handle indentation when replacing entire blocks', () => {
+        it('should handle indentation when replacing entire blocks', async () => {
             const originalContent = `class Test {
     method() {
         if (true) {
@@ -450,7 +450,7 @@ class Example {
     }
 >>>>>>> REPLACE`;
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`class Test {
@@ -467,7 +467,7 @@ class Example {
             }
         });
 
-        it('should handle negative indentation relative to search content', () => {
+        it('should handle negative indentation relative to search content', async () => {
             const originalContent = `class Example {
     constructor() {
         if (true) {
@@ -484,8 +484,8 @@ class Example {
         this.init();
         this.setup();
 >>>>>>> REPLACE`;
-        
-            const result = strategy.applyDiff(originalContent, diffContent);
+          
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             if (result.success) {
             expect(result.content).toBe(`class Example {
@@ -499,7 +499,7 @@ class Example {
             }
         });
         
-        it('should handle extreme negative indentation (no indent)', () => {
+        it('should handle extreme negative indentation (no indent)', async () => {
     const originalContent = `class Example {
     constructor() {
         if (true) {
@@ -514,7 +514,7 @@ class Example {
 this.init();
 >>>>>>> REPLACE`;
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`class Example {
@@ -527,7 +527,7 @@ this.init();
             }
         });
         
-        it('should handle mixed indentation changes in replace block', () => {
+        it('should handle mixed indentation changes in replace block', async () => {
     const originalContent = `class Example {
     constructor() {
         if (true) {
@@ -548,7 +548,7 @@ this.init();
     this.validate();
 >>>>>>> REPLACE`;
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`class Example {
@@ -563,7 +563,7 @@ this.init();
             }
         });
 
-        it('should find matches from middle out', () => {
+        it('should find matches from middle out', async () => {
             const originalContent = `
 function one() {
     return "target";
@@ -595,7 +595,7 @@ function five() {
             // 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)
+            const result = await strategy.applyDiff(originalContent, diffContent, 9, 9)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`function one() {
@@ -629,7 +629,7 @@ function five() {
                 strategy = new SearchReplaceDiffStrategy()
             })
         
-            it('should strip line numbers from both search and replace sections', () => {
+            it('should strip line numbers from both search and replace sections', async () => {
                 const originalContent = 'function test() {\n    return true;\n}\n'
                 const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -642,14 +642,14 @@ function five() {
 3 | }
 >>>>>>> REPLACE`
         
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe('function test() {\n    return false;\n}\n')
                 }
             })
 
-            it('should strip line numbers with leading spaces', () => {
+            it('should strip line numbers with leading spaces', async () => {
                 const originalContent = 'function test() {\n    return true;\n}\n'
                 const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -662,14 +662,14 @@ function five() {
  3 | }
 >>>>>>> REPLACE`
         
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe('function test() {\n    return false;\n}\n')
                 }
             })
         
-            it('should not strip when not all lines have numbers in either section', () => {
+            it('should not strip when not all lines have numbers in either section', async () => {
                 const originalContent = 'function test() {\n    return true;\n}\n'
                 const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -682,11 +682,11 @@ function five() {
 3 | }
 >>>>>>> REPLACE`
         
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(false)
             })
         
-            it('should preserve content that naturally starts with pipe', () => {
+            it('should preserve content that naturally starts with pipe', async () => {
                 const originalContent = '|header|another|\n|---|---|\n|data|more|\n'
                 const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -699,14 +699,14 @@ function five() {
 3 | |data|updated|
 >>>>>>> REPLACE`
         
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe('|header|another|\n|---|---|\n|data|updated|\n')
                 }
             })
         
-            it('should preserve indentation when stripping line numbers', () => {
+            it('should preserve indentation when stripping line numbers', async () => {
                 const originalContent = '    function test() {\n        return true;\n    }\n'
                 const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -719,14 +719,14 @@ function five() {
 3 |     }
 >>>>>>> REPLACE`
         
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe('    function test() {\n        return false;\n    }\n')
                 }
             })
         
-            it('should handle different line numbers between sections', () => {
+            it('should handle different line numbers between sections', async () => {
                 const originalContent = 'function test() {\n    return true;\n}\n'
                 const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -739,14 +739,14 @@ function five() {
 22 | }
 >>>>>>> REPLACE`
         
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe('function test() {\n    return false;\n}\n')
                 }
             })
 
-            it('should not strip content that starts with pipe but no line number', () => {
+            it('should not strip content that starts with pipe but no line number', async () => {
                 const originalContent = '| Pipe\n|---|\n| Data\n'
                 const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -759,14 +759,14 @@ function five() {
 | Updated
 >>>>>>> REPLACE`
             
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe('| Pipe\n|---|\n| Updated\n')
                 }
             })
             
-            it('should handle mix of line-numbered and pipe-only content', () => {
+            it('should handle mix of line-numbered and pipe-only content', async () => {
                 const originalContent = '| Pipe\n|---|\n| Data\n'
                 const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -779,7 +779,7 @@ function five() {
 3 | | NewData
 >>>>>>> REPLACE`
             
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe('1 | | Pipe\n2 | |---|\n3 | | NewData\n')
@@ -796,7 +796,7 @@ function five() {
         })
     
         describe('deletion', () => {
-            it('should delete code when replace block is empty', () => {
+            it('should delete code when replace block is empty', async () => {
                 const originalContent = `function test() {
     console.log("hello");
     // Comment to remove
@@ -808,7 +808,7 @@ function five() {
 =======
 >>>>>>> REPLACE`
     
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe(`function test() {
@@ -818,7 +818,7 @@ function five() {
                 }
             })
     
-            it('should delete multiple lines when replace block is empty', () => {
+            it('should delete multiple lines when replace block is empty', async () => {
                 const originalContent = `class Example {
     constructor() {
         // Initialize
@@ -838,7 +838,7 @@ function five() {
 =======
 >>>>>>> REPLACE`
     
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe(`class Example {
@@ -848,7 +848,7 @@ function five() {
                 }
             })
     
-            it('should preserve indentation when deleting nested code', () => {
+            it('should preserve indentation when deleting nested code', async () => {
                 const originalContent = `function outer() {
     if (true) {
         // Remove this
@@ -865,7 +865,7 @@ function five() {
 =======
 >>>>>>> REPLACE`
     
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe(`function outer() {
@@ -878,7 +878,7 @@ function five() {
         })
     
         describe('insertion', () => {
-            it('should insert code at specified line when search block is empty', () => {
+            it('should insert code at specified line when search block is empty', async () => {
             const originalContent = `function test() {
     const x = 1;
     return x;
@@ -889,7 +889,7 @@ function five() {
     console.log("Adding log");
 >>>>>>> REPLACE`
     
-                const result = strategy.applyDiff(originalContent, diffContent, 2, 2)
+                const result = await strategy.applyDiff(originalContent, diffContent, 2, 2)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe(`function test() {
@@ -900,7 +900,7 @@ function five() {
                 }
             })
     
-            it('should preserve indentation when inserting at nested location', () => {
+            it('should preserve indentation when inserting at nested location', async () => {
                 const originalContent = `function test() {
     if (true) {
         const x = 1;
@@ -913,7 +913,7 @@ function five() {
         console.log("After");
 >>>>>>> REPLACE`
     
-                const result = strategy.applyDiff(originalContent, diffContent, 3, 3)
+                const result = await strategy.applyDiff(originalContent, diffContent, 3, 3)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe(`function test() {
@@ -926,7 +926,7 @@ function five() {
                 }
             })
     
-            it('should handle insertion at start of file', () => {
+            it('should handle insertion at start of file', async () => {
                 const originalContent = `function test() {
     return true;
 }`
@@ -938,7 +938,7 @@ function five() {
 
 >>>>>>> REPLACE`
     
-                const result = strategy.applyDiff(originalContent, diffContent, 1, 1)
+                const result = await strategy.applyDiff(originalContent, diffContent, 1, 1)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe(`// Copyright 2024
@@ -950,7 +950,7 @@ function test() {
                 }
             })
     
-            it('should handle insertion at end of file', () => {
+            it('should handle insertion at end of file', async () => {
                 const originalContent = `function test() {
     return true;
 }`
@@ -961,7 +961,7 @@ function test() {
 // End of file
 >>>>>>> REPLACE`
     
-                const result = strategy.applyDiff(originalContent, diffContent, 4, 4)
+                const result = await strategy.applyDiff(originalContent, diffContent, 4, 4)
                 expect(result.success).toBe(true)
                 if (result.success) {
                     expect(result.content).toBe(`function test() {
@@ -972,7 +972,7 @@ function test() {
                 }
             })
     
-            it('should error if no start_line is provided for insertion', () => {
+            it('should error if no start_line is provided for insertion', async () => {
                 const originalContent = `function test() {
     return true;
 }`
@@ -982,7 +982,7 @@ function test() {
 console.log("test");
 >>>>>>> REPLACE`
     
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(false)
             })
         })
@@ -994,7 +994,7 @@ console.log("test");
             strategy = new SearchReplaceDiffStrategy(0.9, 5) // 90% similarity threshold, 5 line buffer for tests
         })
 
-        it('should match content with small differences (>90% similar)', () => {
+        it('should match content with small differences (>90% similar)', async () => {
             const originalContent = 'function getData() {\n    const results = fetchData();\n    return results.filter(Boolean);\n}\n'
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -1011,14 +1011,14 @@ function getData() {
 
             strategy = new SearchReplaceDiffStrategy(0.9, 5) // Use 5 line buffer for tests
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe('function getData() {\n    const data = fetchData();\n    return data.filter(Boolean);\n}\n')
             }
         })
 
-        it('should not match when content is too different (<90% similar)', () => {
+        it('should not match when content is too different (<90% similar)', async () => {
             const originalContent = 'function processUsers(data) {\n    return data.map(user => user.name);\n}\n'
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -1031,11 +1031,11 @@ function processData(data) {
 }
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(false)
         })
 
-        it('should match content with extra whitespace', () => {
+        it('should match content with extra whitespace', async () => {
             const originalContent = 'function sum(a, b) {\n    return a + b;\n}'
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -1048,14 +1048,14 @@ function sum(a, b) {
 }
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe('function sum(a, b) {\n    return a + b + 1;\n}')
             }
         })
 
-        it('should not exact match empty lines', () => {
+        it('should not exact match empty lines', async () => {
             const originalContent = 'function sum(a, b) {\n\n    return a + b;\n}'
             const diffContent = `test.ts
 <<<<<<< SEARCH
@@ -1065,7 +1065,7 @@ import { a } from "a";
 function sum(a, b) {
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe('import { a } from "a";\nfunction sum(a, b) {\n\n    return a + b;\n}')
@@ -1080,7 +1080,7 @@ function sum(a, b) {
             strategy = new SearchReplaceDiffStrategy(0.9, 5)
         })
 
-        it('should find and replace within specified line range', () => {
+        it('should find and replace within specified line range', async () => {
             const originalContent = `
 function one() {
     return 1;
@@ -1105,7 +1105,7 @@ function two() {
 }
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent, 5, 7)
+            const result = await strategy.applyDiff(originalContent, diffContent, 5, 7)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`function one() {
@@ -1122,7 +1122,7 @@ function three() {
             }
         })
 
-        it('should find and replace within buffer zone (5 lines before/after)', () => {
+        it('should find and replace within buffer zone (5 lines before/after)', async () => {
             const originalContent = `
 function one() {
     return 1;
@@ -1149,7 +1149,7 @@ function three() {
 
             // Even though we specify lines 5-7, it should still find the match at lines 9-11
             // because it's within the 5-line buffer zone
-            const result = strategy.applyDiff(originalContent, diffContent, 5, 7)
+            const result = await strategy.applyDiff(originalContent, diffContent, 5, 7)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`function one() {
@@ -1166,7 +1166,7 @@ function three() {
             }
         })
 
-        it('should not find matches outside search range and buffer zone', () => {
+        it('should not find matches outside search range and buffer zone', async () => {
             const originalContent = `
 function one() {
     return 1;
@@ -1201,11 +1201,11 @@ function five() {
 
             // Searching around function two() (lines 5-7)
             // function five() is more than 5 lines away, so it shouldn't match
-            const result = strategy.applyDiff(originalContent, diffContent, 5, 7)
+            const result = await strategy.applyDiff(originalContent, diffContent, 5, 7)
             expect(result.success).toBe(false)
         })
 
-        it('should handle search range at start of file', () => {
+        it('should handle search range at start of file', async () => {
             const originalContent = `
 function one() {
     return 1;
@@ -1226,7 +1226,7 @@ function one() {
 }
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent, 1, 3)
+            const result = await strategy.applyDiff(originalContent, diffContent, 1, 3)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`function one() {
@@ -1239,7 +1239,7 @@ function two() {
             }
         })
 
-        it('should handle search range at end of file', () => {
+        it('should handle search range at end of file', async () => {
             const originalContent = `
 function one() {
     return 1;
@@ -1260,7 +1260,7 @@ function two() {
 }
 >>>>>>> REPLACE`
 
-            const result = strategy.applyDiff(originalContent, diffContent, 5, 7)
+            const result = await strategy.applyDiff(originalContent, diffContent, 5, 7)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`function one() {
@@ -1273,7 +1273,7 @@ function two() {
             }
         })
 
-        it('should match specific instance of duplicate code using line numbers', () => {
+        it('should match specific instance of duplicate code using line numbers', async () => {
             const originalContent = `
 function processData(data) {
     return data.map(x => x * 2);
@@ -1306,7 +1306,7 @@ function processData(data) {
 >>>>>>> REPLACE`
 
             // Target the second instance of processData
-            const result = strategy.applyDiff(originalContent, diffContent, 10, 12)
+            const result = await strategy.applyDiff(originalContent, diffContent, 10, 12)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`function processData(data) {
@@ -1330,7 +1330,7 @@ function moreStuff() {
             }
         })
 
-        it('should search from start line to end of file when only start_line is provided', () => {
+        it('should search from start line to end of file when only start_line is provided', async () => {
             const originalContent = `
 function one() {
     return 1;
@@ -1356,7 +1356,7 @@ function three() {
 >>>>>>> REPLACE`
 
             // Only provide start_line, should search from there to end of file
-            const result = strategy.applyDiff(originalContent, diffContent, 8)
+            const result = await strategy.applyDiff(originalContent, diffContent, 8)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`function one() {
@@ -1373,7 +1373,7 @@ function three() {
             }
         })
 
-        it('should search from start of file to end line when only end_line is provided', () => {
+        it('should search from start of file to end line when only end_line is provided', async () => {
             const originalContent = `
 function one() {
     return 1;
@@ -1399,7 +1399,7 @@ function one() {
 >>>>>>> REPLACE`
 
             // Only provide end_line, should search from start of file to there
-            const result = strategy.applyDiff(originalContent, diffContent, undefined, 4)
+            const result = await strategy.applyDiff(originalContent, diffContent, undefined, 4)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`function one() {
@@ -1416,7 +1416,7 @@ function three() {
             }
         })
 
-        it('should prioritize exact line match over expanded search', () => {
+        it('should prioritize exact line match over expanded search', async () => {
             const originalContent = `
 function one() {
     return 1;
@@ -1446,7 +1446,7 @@ function process() {
 
             // Should match the second instance exactly at lines 10-12
             // even though the first instance at 6-8 is within the expanded search range
-            const result = strategy.applyDiff(originalContent, diffContent, 10, 12)
+            const result = await strategy.applyDiff(originalContent, diffContent, 10, 12)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`
@@ -1468,7 +1468,7 @@ function two() {
             }
         })
 
-        it('should fall back to expanded search only if exact match fails', () => {
+        it('should fall back to expanded search only if exact match fails', async () => {
             const originalContent = `
 function one() {
     return 1;
@@ -1494,7 +1494,7 @@ function process() {
 
             // Specify wrong line numbers (3-5), but content exists at 6-8
             // Should still find and replace it since it's within the expanded range
-            const result = strategy.applyDiff(originalContent, diffContent, 3, 5)
+            const result = await strategy.applyDiff(originalContent, diffContent, 3, 5)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(`function one() {
@@ -1519,14 +1519,14 @@ function two() {
             strategy = new SearchReplaceDiffStrategy()
         })
 
-        it('should include the current working directory', () => {
+        it('should include the current working directory', async () => {
             const cwd = '/test/dir'
-            const description = strategy.getToolDescription(cwd)
+            const description = await strategy.getToolDescription(cwd)
             expect(description).toContain(`relative to the current working directory ${cwd}`)
         })
 
-        it('should include required format elements', () => {
-            const description = strategy.getToolDescription('/test')
+        it('should include required format elements', async () => {
+            const description = await strategy.getToolDescription('/test')
             expect(description).toContain('<<<<<<< SEARCH')
             expect(description).toContain('=======')
             expect(description).toContain('>>>>>>> REPLACE')
@@ -1534,8 +1534,8 @@ function two() {
             expect(description).toContain('</apply_diff>')
         })
 
-        it('should document start_line and end_line parameters', () => {
-            const description = strategy.getToolDescription('/test')
+        it('should document start_line and end_line parameters', async () => {
+            const description = await strategy.getToolDescription('/test')
             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.')
         })

+ 11 - 11
src/core/diff/strategies/__tests__/unified.test.ts

@@ -20,7 +20,7 @@ describe('UnifiedDiffStrategy', () => {
     })
 
     describe('applyDiff', () => {
-        it('should successfully apply a function modification diff', () => {
+        it('should successfully apply a function modification diff', async () => {
             const originalContent = `import { Logger } from '../logger';
 
 function calculateTotal(items: number[]): number {
@@ -58,14 +58,14 @@ function calculateTotal(items: number[]): number {
 
 export { calculateTotal };`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(expected)
             }
         })
 
-        it('should successfully apply a diff adding a new method', () => {
+        it('should successfully apply a diff adding a new method', async () => {
             const originalContent = `class Calculator {
   add(a: number, b: number): number {
     return a + b;
@@ -95,14 +95,14 @@ export { calculateTotal };`
   }
 }`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(expected)
             }
         })
 
-        it('should successfully apply a diff modifying imports', () => {
+        it('should successfully apply a diff modifying imports', async () => {
             const originalContent = `import { useState } from 'react';
 import { Button } from './components';
 
@@ -132,15 +132,15 @@ function App() {
   useEffect(() => { document.title = \`Count: \${count}\` }, [count]);
   return <Button onClick={() => setCount(count + 1)}>{count}</Button>;
 }`
-
-            const result = strategy.applyDiff(originalContent, diffContent)
+          
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(expected)
             }
         })
 
-        it('should successfully apply a diff with multiple hunks', () => {
+        it('should successfully apply a diff with multiple hunks', async () => {
             const originalContent = `import { readFile, writeFile } from 'fs';
 
 function processFile(path: string) {
@@ -198,14 +198,14 @@ async function processFile(path: string) {
 
 export { processFile };`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(expected)
             }
         })
 
-        it('should handle empty original content', () => {
+        it('should handle empty original content', async () => {
             const originalContent = ''
             const diffContent = `--- empty.ts
 +++ empty.ts
@@ -218,7 +218,7 @@ export { processFile };`
   return \`Hello, \${name}!\`;
 }\n`
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             if (result.success) {
                 expect(result.content).toBe(expected)

+ 295 - 0
src/core/diff/strategies/new-unified/__tests__/edit-strategies.test.ts

@@ -0,0 +1,295 @@
+import { applyContextMatching, applyDMP, applyGitFallback } from "../edit-strategies"
+import { Hunk } from "../types"
+
+const testCases = [
+	{
+		name: "should return original content if no match is found",
+		hunk: {
+			changes: [
+				{ type: "context", content: "line1" },
+				{ type: "add", content: "line2" },
+			],
+		} as Hunk,
+		content: ["line1", "line3"],
+		matchPosition: -1,
+		expected: {
+			confidence: 0,
+			result: ["line1", "line3"],
+		},
+		expectedResult: "line1\nline3",
+		strategies: ["context", "dmp"],
+	},
+	{
+		name: "should apply a simple add change",
+		hunk: {
+			changes: [
+				{ type: "context", content: "line1" },
+				{ type: "add", content: "line2" },
+			],
+		} as Hunk,
+		content: ["line1", "line3"],
+		matchPosition: 0,
+		expected: {
+			confidence: 1,
+			result: ["line1", "line2", "line3"],
+		},
+		expectedResult: "line1\nline2\nline3",
+		strategies: ["context", "dmp"],
+	},
+	{
+		name: "should apply a simple remove change",
+		hunk: {
+			changes: [
+				{ type: "context", content: "line1" },
+				{ type: "remove", content: "line2" },
+			],
+		} as Hunk,
+		content: ["line1", "line2", "line3"],
+		matchPosition: 0,
+		expected: {
+			confidence: 1,
+			result: ["line1", "line3"],
+		},
+		expectedResult: "line1\nline3",
+		strategies: ["context", "dmp"],
+	},
+	{
+		name: "should apply a simple context change",
+		hunk: {
+			changes: [{ type: "context", content: "line1" }],
+		} as Hunk,
+		content: ["line1", "line2", "line3"],
+		matchPosition: 0,
+		expected: {
+			confidence: 1,
+			result: ["line1", "line2", "line3"],
+		},
+		expectedResult: "line1\nline2\nline3",
+		strategies: ["context", "dmp"],
+	},
+	{
+		name: "should apply a multi-line add change",
+		hunk: {
+			changes: [
+				{ type: "context", content: "line1" },
+				{ type: "add", content: "line2\nline3" },
+			],
+		} as Hunk,
+		content: ["line1", "line4"],
+		matchPosition: 0,
+		expected: {
+			confidence: 1,
+			result: ["line1", "line2\nline3", "line4"],
+		},
+		expectedResult: "line1\nline2\nline3\nline4",
+		strategies: ["context", "dmp"],
+	},
+	{
+		name: "should apply a multi-line remove change",
+		hunk: {
+			changes: [
+				{ type: "context", content: "line1" },
+				{ type: "remove", content: "line2\nline3" },
+			],
+		} as Hunk,
+		content: ["line1", "line2", "line3", "line4"],
+		matchPosition: 0,
+		expected: {
+			confidence: 1,
+			result: ["line1", "line4"],
+		},
+		expectedResult: "line1\nline4",
+		strategies: ["context", "dmp"],
+	},
+	{
+		name: "should apply a multi-line context change",
+		hunk: {
+			changes: [
+				{ type: "context", content: "line1" },
+				{ type: "context", content: "line2\nline3" },
+			],
+		} as Hunk,
+		content: ["line1", "line2", "line3", "line4"],
+		matchPosition: 0,
+		expected: {
+			confidence: 1,
+			result: ["line1", "line2\nline3", "line4"],
+		},
+		expectedResult: "line1\nline2\nline3\nline4",
+		strategies: ["context", "dmp"],
+	},
+	{
+		name: "should apply a change with indentation",
+		hunk: {
+			changes: [
+				{ type: "context", content: "  line1" },
+				{ type: "add", content: "    line2" },
+			],
+		} as Hunk,
+		content: ["  line1", "  line3"],
+		matchPosition: 0,
+		expected: {
+			confidence: 1,
+			result: ["  line1", "    line2", "  line3"],
+		},
+		expectedResult: "  line1\n    line2\n  line3",
+		strategies: ["context", "dmp"],
+	},
+	{
+		name: "should apply a change with mixed indentation",
+		hunk: {
+			changes: [
+				{ type: "context", content: "\tline1" },
+				{ type: "add", content: "  line2" },
+			],
+		} as Hunk,
+		content: ["\tline1", "  line3"],
+		matchPosition: 0,
+		expected: {
+			confidence: 1,
+			result: ["\tline1", "  line2", "  line3"],
+		},
+		expectedResult: "\tline1\n  line2\n  line3",
+		strategies: ["context", "dmp"],
+	},
+	{
+		name: "should apply a change with mixed indentation and multi-line",
+		hunk: {
+			changes: [
+				{ type: "context", content: "  line1" },
+				{ type: "add", content: "\tline2\n    line3" },
+			],
+		} as Hunk,
+		content: ["  line1", "  line4"],
+		matchPosition: 0,
+		expected: {
+			confidence: 1,
+			result: ["  line1", "\tline2\n    line3", "  line4"],
+		},
+		expectedResult: "  line1\n\tline2\n    line3\n  line4",
+		strategies: ["context", "dmp"],
+	},
+	{
+		name: "should apply a complex change with mixed indentation and multi-line",
+		hunk: {
+			changes: [
+				{ type: "context", content: "  line1" },
+				{ type: "remove", content: "    line2" },
+				{ type: "add", content: "\tline3\n      line4" },
+				{ type: "context", content: "  line5" },
+			],
+		} as Hunk,
+		content: ["  line1", "    line2", "  line5", "  line6"],
+		matchPosition: 0,
+		expected: {
+			confidence: 1,
+			result: ["  line1", "\tline3\n      line4", "  line5", "  line6"],
+		},
+		expectedResult: "  line1\n\tline3\n      line4\n  line5\n  line6",
+		strategies: ["context", "dmp"],
+	},
+	{
+		name: "should apply a complex change with mixed indentation and multi-line and context",
+		hunk: {
+			changes: [
+				{ type: "context", content: "  line1" },
+				{ type: "remove", content: "    line2" },
+				{ type: "add", content: "\tline3\n      line4" },
+				{ type: "context", content: "  line5" },
+				{ type: "context", content: "  line6" },
+			],
+		} as Hunk,
+		content: ["  line1", "    line2", "  line5", "  line6", "  line7"],
+		matchPosition: 0,
+		expected: {
+			confidence: 1,
+			result: ["  line1", "\tline3\n      line4", "  line5", "  line6", "  line7"],
+		},
+		expectedResult: "  line1\n\tline3\n      line4\n  line5\n  line6\n  line7",
+		strategies: ["context", "dmp"],
+	},
+	{
+		name: "should apply a complex change with mixed indentation and multi-line and context and a different match position",
+		hunk: {
+			changes: [
+				{ type: "context", content: "  line1" },
+				{ type: "remove", content: "    line2" },
+				{ type: "add", content: "\tline3\n      line4" },
+				{ type: "context", content: "  line5" },
+				{ type: "context", content: "  line6" },
+			],
+		} as Hunk,
+		content: ["  line0", "  line1", "    line2", "  line5", "  line6", "  line7"],
+		matchPosition: 1,
+		expected: {
+			confidence: 1,
+			result: ["  line0", "  line1", "\tline3\n      line4", "  line5", "  line6", "  line7"],
+		},
+		expectedResult: "  line0\n  line1\n\tline3\n      line4\n  line5\n  line6\n  line7",
+		strategies: ["context", "dmp"],
+	},
+]
+
+describe("applyContextMatching", () => {
+	testCases.forEach(({ name, hunk, content, matchPosition, expected, strategies, expectedResult }) => {
+		if (!strategies?.includes("context")) {
+			return
+		}
+		it(name, () => {
+			const result = applyContextMatching(hunk, content, matchPosition)
+			expect(result.result.join("\n")).toEqual(expectedResult)
+			expect(result.confidence).toBeGreaterThanOrEqual(expected.confidence)
+			expect(result.strategy).toBe("context")
+		})
+	})
+})
+
+describe("applyDMP", () => {
+	testCases.forEach(({ name, hunk, content, matchPosition, expected, strategies, expectedResult }) => {
+		if (!strategies?.includes("dmp")) {
+			return
+		}
+		it(name, () => {
+			const result = applyDMP(hunk, content, matchPosition)
+			expect(result.result.join("\n")).toEqual(expectedResult)
+			expect(result.confidence).toBeGreaterThanOrEqual(expected.confidence)
+			expect(result.strategy).toBe("dmp")
+		})
+	})
+})
+
+describe("applyGitFallback", () => {
+	it("should successfully apply changes using git operations", async () => {
+		const hunk = {
+			changes: [
+				{ type: "context", content: "line1", indent: "" },
+				{ type: "remove", content: "line2", indent: "" },
+				{ type: "add", content: "new line2", indent: "" },
+				{ type: "context", content: "line3", indent: "" }
+			]
+		} as Hunk
+
+		const content = ["line1", "line2", "line3"]
+		const result = await applyGitFallback(hunk, content)
+
+		expect(result.result.join("\n")).toEqual("line1\nnew line2\nline3")
+		expect(result.confidence).toBe(1)
+		expect(result.strategy).toBe("git-fallback")
+	})
+
+	it("should return original content with 0 confidence when changes cannot be applied", async () => {
+		const hunk = {
+			changes: [
+				{ type: "context", content: "nonexistent", indent: "" },
+				{ type: "add", content: "new line", indent: "" }
+			]
+		} as Hunk
+
+		const content = ["line1", "line2", "line3"]
+		const result = await applyGitFallback(hunk, content)
+
+		expect(result.result).toEqual(content)
+		expect(result.confidence).toBe(0)
+		expect(result.strategy).toBe("git-fallback")
+	})
+})

+ 262 - 0
src/core/diff/strategies/new-unified/__tests__/search-strategies.test.ts

@@ -0,0 +1,262 @@
+import { findAnchorMatch, findExactMatch, findSimilarityMatch, findLevenshteinMatch } from "../search-strategies"
+
+type SearchStrategy = (
+	searchStr: string,
+	content: string[],
+	startIndex?: number
+) => {
+	index: number
+	confidence: number
+	strategy: string
+}
+
+const testCases = [
+    {
+        name: "should return no match if the search string is not found",
+        searchStr: "not found",
+        content: ["line1", "line2", "line3"],
+        expected: { index: -1, confidence: 0 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return a match if the search string is found",
+        searchStr: "line2",
+        content: ["line1", "line2", "line3"],
+        expected: { index: 1, confidence: 1 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return a match with correct index when startIndex is provided",
+        searchStr: "line3",
+        content: ["line1", "line2", "line3", "line4", "line3"],
+        startIndex: 3,
+        expected: { index: 4, confidence: 1 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return a match even if there are more lines in content",
+        searchStr: "line2",
+        content: ["line1", "line2", "line3", "line4", "line5"],
+        expected: { index: 1, confidence: 1 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return a match even if the search string is at the beginning of the content",
+        searchStr: "line1",
+        content: ["line1", "line2", "line3"],
+        expected: { index: 0, confidence: 1 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return a match even if the search string is at the end of the content",
+        searchStr: "line3",
+        content: ["line1", "line2", "line3"],
+        expected: { index: 2, confidence: 1 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return a match for a multi-line search string",
+        searchStr: "line2\nline3",
+        content: ["line1", "line2", "line3", "line4"],
+        expected: { index: 1, confidence: 1 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return no match if a multi-line search string is not found",
+        searchStr: "line2\nline4",
+        content: ["line1", "line2", "line3", "line4"],
+        expected: { index: -1, confidence: 0 },
+        strategies: ["exact", "similarity"],
+    },
+    {
+        name: "should return a match with indentation",
+        searchStr: "  line2",
+        content: ["line1", "  line2", "line3"],
+        expected: { index: 1, confidence: 1 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return a match with more complex indentation",
+        searchStr: "    line3",
+        content: ["  line1", "    line2", "    line3", "  line4"],
+        expected: { index: 2, confidence: 1 },
+         strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return a match with mixed indentation",
+        searchStr: "\tline2",
+        content: ["  line1", "\tline2", "    line3"],
+        expected: { index: 1, confidence: 1 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return a match with mixed indentation and multi-line",
+        searchStr: "  line2\n\tline3",
+        content: ["line1", "  line2", "\tline3", "    line4"],
+        expected: { index: 1, confidence: 1 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return no match if mixed indentation and multi-line is not found",
+        searchStr: "  line2\n    line4",
+        content: ["line1", "  line2", "\tline3", "    line4"],
+        expected: { index: -1, confidence: 0 },
+        strategies: ["exact", "similarity"],
+    },
+    {
+        name: "should return a match with leading and trailing spaces",
+        searchStr: "  line2  ",
+        content: ["line1", "  line2  ", "line3"],
+        expected: { index: 1, confidence: 1 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return a match with leading and trailing tabs",
+        searchStr: "\tline2\t",
+        content: ["line1", "\tline2\t", "line3"],
+        expected: { index: 1, confidence: 1 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return a match with mixed leading and trailing spaces and tabs",
+        searchStr: " \tline2\t ",
+        content: ["line1", " \tline2\t ", "line3"],
+        expected: { index: 1, confidence: 1 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return a match with mixed leading and trailing spaces and tabs and multi-line",
+        searchStr: " \tline2\t \n  line3  ",
+        content: ["line1", " \tline2\t ", "  line3  ", "line4"],
+        expected: { index: 1, confidence: 1 },
+        strategies: ["exact", "similarity", "levenshtein"],
+    },
+    {
+        name: "should return no match if mixed leading and trailing spaces and tabs and multi-line is not found",
+        searchStr: " \tline2\t \n  line4  ",
+        content: ["line1", " \tline2\t ", "  line3  ", "line4"],
+        expected: { index: -1, confidence: 0 },
+        strategies: ["exact", "similarity"],
+    },
+]
+
+describe("findExactMatch", () => {
+    testCases.forEach(({ name, searchStr, content, startIndex, expected, strategies }) => {
+		if (!strategies?.includes("exact")) {
+			return
+		}
+        it(name, () => {
+			const result = findExactMatch(searchStr, content, startIndex)
+			expect(result.index).toBe(expected.index)
+			expect(result.confidence).toBeGreaterThanOrEqual(expected.confidence)
+			expect(result.strategy).toMatch(/exact(-overlapping)?/)
+		})
+	})
+})
+
+describe("findAnchorMatch", () => {
+    const anchorTestCases = [
+        {
+            name: "should return no match if no anchors are found",
+            searchStr: "   \n   \n   ",
+            content: ["line1", "line2", "line3"],
+            expected: { index: -1, confidence: 0 },
+        },
+        {
+            name: "should return no match if anchor positions cannot be validated",
+            searchStr: "unique line\ncontext line 1\ncontext line 2",
+			content: [
+				"different line 1",
+				"different line 2",
+				"different line 3",
+				"another unique line",
+				"context line 1",
+				"context line 2",
+			],
+            expected: { index: -1, confidence: 0 },
+        },
+        {
+            name: "should return a match if anchor positions can be validated",
+            searchStr: "unique line\ncontext line 1\ncontext line 2",
+            content: ["line1", "line2", "unique line", "context line 1", "context line 2", "line 6"],
+            expected: { index: 2, confidence: 1 },
+        },
+        {
+            name: "should return a match with correct index when startIndex is provided",
+            searchStr: "unique line\ncontext line 1\ncontext line 2",
+            content: ["line1", "line2", "line3", "unique line", "context line 1", "context line 2", "line 7"],
+            startIndex: 3,
+            expected: { index: 3, confidence: 1 },
+        },
+        {
+            name: "should return a match even if there are more lines in content",
+            searchStr: "unique line\ncontext line 1\ncontext line 2",
+			content: [
+				"line1",
+				"line2",
+				"unique line",
+				"context line 1",
+				"context line 2",
+				"line 6",
+				"extra line 1",
+				"extra line 2",
+			],
+            expected: { index: 2, confidence: 1 },
+        },
+        {
+            name: "should return a match even if the anchor is at the beginning of the content",
+            searchStr: "unique line\ncontext line 1\ncontext line 2",
+            content: ["unique line", "context line 1", "context line 2", "line 6"],
+            expected: { index: 0, confidence: 1 },
+        },
+        {
+            name: "should return a match even if the anchor is at the end of the content",
+            searchStr: "unique line\ncontext line 1\ncontext line 2",
+            content: ["line1", "line2", "unique line", "context line 1", "context line 2"],
+            expected: { index: 2, confidence: 1 },
+        },
+        {
+            name: "should return no match if no valid anchor is found",
+            searchStr: "non-unique line\ncontext line 1\ncontext line 2",
+            content: ["line1", "line2", "non-unique line", "context line 1", "context line 2", "non-unique line"],
+            expected: { index: -1, confidence: 0 },
+        },
+	]
+
+    anchorTestCases.forEach(({ name, searchStr, content, startIndex, expected }) => {
+        it(name, () => {
+			const result = findAnchorMatch(searchStr, content, startIndex)
+			expect(result.index).toBe(expected.index)
+			expect(result.confidence).toBeGreaterThanOrEqual(expected.confidence)
+			expect(result.strategy).toBe("anchor")
+		})
+	})
+})
+
+describe("findSimilarityMatch", () => {
+    testCases.forEach(({ name, searchStr, content, startIndex, expected, strategies }) => {
+		if (!strategies?.includes("similarity")) {
+			return
+		}
+        it(name, () => {
+			const result = findSimilarityMatch(searchStr, content, startIndex)
+			expect(result.index).toBe(expected.index)
+			expect(result.confidence).toBeGreaterThanOrEqual(expected.confidence)
+			expect(result.strategy).toBe("similarity")
+		})
+	})
+})
+
+describe("findLevenshteinMatch", () => {
+    testCases.forEach(({ name, searchStr, content, startIndex, expected, strategies }) => {
+		if (!strategies?.includes("levenshtein")) {
+			return
+		}
+        it(name, () => {
+			const result = findLevenshteinMatch(searchStr, content, startIndex)
+			expect(result.index).toBe(expected.index)
+			expect(result.confidence).toBeGreaterThanOrEqual(expected.confidence)
+			expect(result.strategy).toBe("levenshtein")
+		})
+	})
+})

+ 305 - 0
src/core/diff/strategies/new-unified/edit-strategies.ts

@@ -0,0 +1,305 @@
+import { diff_match_patch } from "diff-match-patch"
+import { EditResult, Hunk } from "./types"
+import { getDMPSimilarity, validateEditResult } from "./search-strategies"
+import * as path from "path"
+import simpleGit, { SimpleGit } from "simple-git"
+import * as tmp from "tmp"
+import * as fs from "fs"
+
+// Helper function to infer indentation - simplified version
+function inferIndentation(line: string, contextLines: string[], previousIndent: string = ""): string {
+	// If the line has explicit indentation in the change, use it exactly
+	const lineMatch = line.match(/^(\s+)/)
+	if (lineMatch) {
+		return lineMatch[1]
+	}
+
+	// If we have context lines, use the indentation from the first context line
+	const contextLine = contextLines[0]
+	if (contextLine) {
+		const contextMatch = contextLine.match(/^(\s+)/)
+    if (contextMatch) {
+			return contextMatch[1]
+		}
+	}
+
+	// Fallback to previous indent
+	return previousIndent
+}
+
+// Context matching edit strategy
+export function applyContextMatching(
+	hunk: Hunk,
+	content: string[],
+	matchPosition: number,
+): EditResult {
+  if (matchPosition === -1) {
+		return { confidence: 0, result: content, strategy: "context" }
+	}
+
+	const newResult = [...content.slice(0, matchPosition)]
+	let sourceIndex = matchPosition
+
+  for (const change of hunk.changes) {
+		if (change.type === "context") {
+			// Use the original line from content if available
+			if (sourceIndex < content.length) {
+				newResult.push(content[sourceIndex])
+			} else {
+				const line = change.indent ? change.indent + change.content : change.content
+				newResult.push(line)
+			}
+			sourceIndex++
+		} else if (change.type === "add") {
+			// Use exactly the indentation from the change
+			const baseIndent = change.indent || ""
+
+			// Handle multi-line additions
+			const lines = change.content.split("\n").map((line) => {
+				// If the line already has indentation, preserve it relative to the base indent
+				const lineIndentMatch = line.match(/^(\s*)(.*)/)
+				if (lineIndentMatch) {
+					const [, lineIndent, content] = lineIndentMatch
+					// Only add base indent if the line doesn't already have it
+					return lineIndent ? line : baseIndent + content
+				}
+				return baseIndent + line
+			})
+
+			newResult.push(...lines)
+		} else if (change.type === "remove") {
+			// Handle multi-line removes by incrementing sourceIndex for each line
+			const removedLines = change.content.split("\n").length
+			sourceIndex += removedLines
+		}
+	}
+
+	// Append remaining content
+	newResult.push(...content.slice(sourceIndex))
+
+	// Calculate confidence based on the actual changes
+	const afterText = newResult.slice(matchPosition, newResult.length - (content.length - sourceIndex)).join("\n")
+
+	const confidence = validateEditResult(hunk, afterText)
+
+	return { 
+		confidence,
+		result: newResult,
+		strategy: "context"
+	}
+}
+
+// DMP edit strategy
+export function applyDMP(
+	hunk: Hunk,
+	content: string[],
+	matchPosition: number,
+): EditResult {
+  if (matchPosition === -1) {
+		return { confidence: 0, result: content, strategy: "dmp" }
+	}
+
+	const dmp = new diff_match_patch()
+
+	// Calculate total lines in before block accounting for multi-line content
+	const beforeLineCount = hunk.changes
+		.filter((change) => change.type === "context" || change.type === "remove")
+		.reduce((count, change) => count + change.content.split("\n").length, 0)
+  
+  // Build BEFORE block (context + removals)
+  const beforeLines = hunk.changes
+		.filter((change) => change.type === "context" || change.type === "remove")
+		.map((change) => {
+			if (change.originalLine) {
+				return change.originalLine
+			}
+			return change.indent ? change.indent + change.content : change.content
+		})
+  
+  // Build AFTER block (context + additions)
+  const afterLines = hunk.changes
+		.filter((change) => change.type === "context" || change.type === "add")
+		.map((change) => {
+			if (change.originalLine) {
+				return change.originalLine
+			}
+			return change.indent ? change.indent + change.content : change.content
+		})
+
+	// Convert to text with proper line endings
+	const beforeText = beforeLines.join("\n")
+	const afterText = afterLines.join("\n")
+
+	// Create and apply patch
+	const patch = dmp.patch_make(beforeText, afterText)
+	const targetText = content.slice(matchPosition, matchPosition + beforeLineCount).join("\n")
+	const [patchedText] = dmp.patch_apply(patch, targetText)
+
+	// Split result and preserve line endings
+	const patchedLines = patchedText.split("\n")
+
+	// Construct final result
+  const newResult = [
+    ...content.slice(0, matchPosition),
+    ...patchedLines,
+		...content.slice(matchPosition + beforeLineCount),
+	]
+  
+	const confidence = validateEditResult(hunk, patchedText)
+  
+  return {
+		confidence,
+    result: newResult,
+		strategy: "dmp",
+	}
+}
+
+// Git fallback strategy that works with full content
+export async function applyGitFallback(hunk: Hunk, content: string[]): Promise<EditResult> {
+	let tmpDir: tmp.DirResult | undefined
+
+	try {
+		tmpDir = tmp.dirSync({ unsafeCleanup: true })
+		const git: SimpleGit = simpleGit(tmpDir.name)
+
+		await git.init()
+		await git.addConfig("user.name", "Temp")
+		await git.addConfig("user.email", "[email protected]")
+
+		const filePath = path.join(tmpDir.name, "file.txt")
+
+		const searchLines = hunk.changes
+			.filter((change) => change.type === "context" || change.type === "remove")
+			.map((change) => change.originalLine || change.indent + change.content)
+		
+		const replaceLines = hunk.changes
+			.filter((change) => change.type === "context" || change.type === "add")
+			.map((change) => change.originalLine || change.indent + change.content)
+
+		const searchText = searchLines.join("\n")
+		const replaceText = replaceLines.join("\n")
+		const originalText = content.join("\n")
+
+		try {
+			fs.writeFileSync(filePath, originalText)
+			await git.add("file.txt")
+			const originalCommit = await git.commit("original")
+			console.log("Strategy 1 - Original commit:", originalCommit.commit)
+
+			fs.writeFileSync(filePath, searchText)
+			await git.add("file.txt")
+			const searchCommit1 = await git.commit("search")
+			console.log("Strategy 1 - Search commit:", searchCommit1.commit)
+
+			fs.writeFileSync(filePath, replaceText)
+			await git.add("file.txt")
+			const replaceCommit = await git.commit("replace")
+			console.log("Strategy 1 - Replace commit:", replaceCommit.commit)
+
+			console.log("Strategy 1 - Attempting checkout of:", originalCommit.commit)
+			await git.raw(["checkout", originalCommit.commit])
+			try {
+				console.log("Strategy 1 - Attempting cherry-pick of:", replaceCommit.commit)
+				await git.raw(["cherry-pick", "--minimal", replaceCommit.commit])
+
+				const newText = fs.readFileSync(filePath, "utf-8")
+				const newLines = newText.split("\n")
+				return {
+					confidence: 1,
+					result: newLines,
+					strategy: "git-fallback",
+				}
+			} catch (cherryPickError) {
+				console.error("Strategy 1 failed with merge conflict")
+			}
+		} catch (error) {
+			console.error("Strategy 1 failed:", error)
+		}
+
+		try {
+			await git.init()
+			await git.addConfig("user.name", "Temp")
+			await git.addConfig("user.email", "[email protected]")
+
+			fs.writeFileSync(filePath, searchText)
+			await git.add("file.txt")
+			const searchCommit = await git.commit("search")
+			const searchHash = searchCommit.commit.replace(/^HEAD /, "")
+			console.log("Strategy 2 - Search commit:", searchHash)
+
+			fs.writeFileSync(filePath, replaceText)
+			await git.add("file.txt")
+			const replaceCommit = await git.commit("replace")
+			const replaceHash = replaceCommit.commit.replace(/^HEAD /, "")
+			console.log("Strategy 2 - Replace commit:", replaceHash)
+
+			console.log("Strategy 2 - Attempting checkout of:", searchHash)
+			await git.raw(["checkout", searchHash])
+			fs.writeFileSync(filePath, originalText)
+			await git.add("file.txt")
+			const originalCommit2 = await git.commit("original")
+			console.log("Strategy 2 - Original commit:", originalCommit2.commit)
+
+			try {
+				console.log("Strategy 2 - Attempting cherry-pick of:", replaceHash)
+				await git.raw(["cherry-pick", "--minimal", replaceHash])
+
+				const newText = fs.readFileSync(filePath, "utf-8")
+				const newLines = newText.split("\n")
+				return {
+					confidence: 1,
+					result: newLines,
+					strategy: "git-fallback",
+				}
+			} catch (cherryPickError) {
+				console.error("Strategy 2 failed with merge conflict")
+			}
+		} catch (error) {
+			console.error("Strategy 2 failed:", error)
+		}
+
+		console.error("Git fallback failed")
+		return { confidence: 0, result: content, strategy: "git-fallback" }
+	} catch (error) {
+		console.error("Git fallback strategy failed:", error)
+		return { confidence: 0, result: content, strategy: "git-fallback" }
+	} finally {
+		if (tmpDir) {
+			tmpDir.removeCallback()
+		}
+	}
+}
+
+// Main edit function that tries strategies sequentially
+export async function applyEdit(
+	hunk: Hunk, 
+	content: string[], 
+	matchPosition: number, 
+	confidence: number,
+	confidenceThreshold: number = 0.97
+): Promise<EditResult> {
+	// Don't attempt regular edits if confidence is too low
+	if (confidence < confidenceThreshold) {
+		console.log(
+			`Search confidence (${confidence}) below minimum threshold (${confidenceThreshold}), trying git fallback...`
+		)
+		return applyGitFallback(hunk, content)
+	}
+
+	// Try each strategy in sequence until one succeeds
+	const strategies = [
+		{ name: "dmp", apply: () => applyDMP(hunk, content, matchPosition) },
+		{ name: "context", apply: () => applyContextMatching(hunk, content, matchPosition) },
+		{ name: "git-fallback", apply: () => applyGitFallback(hunk, content) },
+	]
+
+	// Try strategies sequentially until one succeeds
+	for (const strategy of strategies) {
+		const result = await strategy.apply()
+		if (result.confidence >= confidenceThreshold) {
+			return result
+		}
+	}
+
+	return { confidence: 0, result: content, strategy: "none" }
+}

+ 359 - 0
src/core/diff/strategies/new-unified/index.ts

@@ -0,0 +1,359 @@
+import { Diff, Hunk, Change } from "./types"
+import { findBestMatch, prepareSearchString } from "./search-strategies"
+import { applyEdit } from "./edit-strategies"
+import { DiffResult, DiffStrategy } from "../../types"
+
+export class NewUnifiedDiffStrategy implements DiffStrategy {
+	private readonly confidenceThreshold: number
+
+	constructor(confidenceThreshold: number = 1) {
+		this.confidenceThreshold = Math.max(confidenceThreshold, 0.8)
+	}
+
+	private parseUnifiedDiff(diff: string): Diff {
+		const MAX_CONTEXT_LINES = 6 // Number of context lines to keep before/after changes
+		const lines = diff.split("\n")
+		const hunks: Hunk[] = []
+		let currentHunk: Hunk | null = null
+
+		let i = 0
+		while (i < lines.length && !lines[i].startsWith("@@")) {
+			i++
+		}
+
+		for (; i < lines.length; i++) {
+			const line = lines[i]
+
+			if (line.startsWith("@@")) {
+				if (
+					currentHunk &&
+					currentHunk.changes.length > 0 &&
+					currentHunk.changes.some((change) => change.type === "add" || change.type === "remove")
+				) {
+					const changes = currentHunk.changes
+					let startIdx = 0
+					let endIdx = changes.length - 1
+
+					for (let j = 0; j < changes.length; j++) {
+						if (changes[j].type !== "context") {
+							startIdx = Math.max(0, j - MAX_CONTEXT_LINES)
+							break
+						}
+					}
+
+					for (let j = changes.length - 1; j >= 0; j--) {
+						if (changes[j].type !== "context") {
+							endIdx = Math.min(changes.length - 1, j + MAX_CONTEXT_LINES)
+							break
+						}
+					}
+
+					currentHunk.changes = changes.slice(startIdx, endIdx + 1)
+					hunks.push(currentHunk)
+				}
+				currentHunk = { changes: [] }
+				continue
+			}
+
+			if (!currentHunk) {
+				continue
+			}
+
+			const content = line.slice(1)
+			const indentMatch = content.match(/^(\s*)/)
+			const indent = indentMatch ? indentMatch[0] : ""
+			const trimmedContent = content.slice(indent.length)
+
+			if (line.startsWith(" ")) {
+				currentHunk.changes.push({
+					type: "context",
+					content: trimmedContent,
+					indent,
+					originalLine: content,
+				})
+			} else if (line.startsWith("+")) {
+				currentHunk.changes.push({
+					type: "add",
+					content: trimmedContent,
+					indent,
+					originalLine: content,
+				})
+			} else if (line.startsWith("-")) {
+				currentHunk.changes.push({
+					type: "remove",
+					content: trimmedContent,
+					indent,
+					originalLine: content,
+				})
+			} else {
+				const finalContent = trimmedContent ? " " + trimmedContent : " "
+				currentHunk.changes.push({
+					type: "context",
+					content: finalContent,
+					indent,
+					originalLine: content,
+				})
+			}
+		}
+
+		if (
+			currentHunk &&
+			currentHunk.changes.length > 0 &&
+			currentHunk.changes.some((change) => change.type === "add" || change.type === "remove")
+		) {
+			hunks.push(currentHunk)
+		}
+
+		return { hunks }
+	}
+
+	getToolDescription(cwd: string): string {
+		return `# apply_diff Tool Rules:
+
+Generate a unified diff similar to what "diff -U0" would produce. 
+
+The first two lines must include the file paths, starting with "---" for the original file and "+++" for the updated file. Do not include timestamps with the file paths.
+
+Each hunk of changes must start with a line containing only "@@ ... @@". Do not include line numbers or ranges in the "@@ ... @@" lines. These are not necessary for the user's patch tool.
+
+Your output must be a correct, clean patch that applies successfully against the current file contents. Mark all lines that need to be removed or changed with "-". Mark all new or modified lines with "+". Ensure you include all necessary changes; missing or unmarked lines will result in a broken patch.
+
+Indentation matters! Make sure to preserve the exact indentation of both removed and added lines.
+
+Start a new hunk for each section of the file that requires changes. However, include only the hunks that contain actual changes. If a hunk consists entirely of unchanged lines, skip it.
+
+Group related changes together in the same hunk whenever possible. Output hunks in whatever logical order makes the most sense.
+
+When editing a function, method, loop, or similar code block, replace the *entire* block in one hunk. Use "-" lines to delete the existing block and "+" lines to add the updated block. This ensures accuracy in your diffs.
+
+If you need to move code within a file, create two hunks: one to delete the code from its original location and another to insert it at the new location.
+
+To create a new file, show a diff from "--- /dev/null" to "+++ path/to/new/file.ext".
+
+Format Requirements:
+
+\`\`\`diff
+--- mathweb/flask/app.py
++++ mathweb/flask/app.py
+@@ ... @@
+-class MathWeb:
++import sympy
+
++
++class MathWeb:
+@@ ... @@
+-def is_prime(x):
+-    if x < 2:
+-        return False
+-    for i in range(2, int(math.sqrt(x)) + 1):
+-        if x % i == 0:
+-            return False
+-    return True
+@@ ... @@
[email protected]('/prime/<int:n>')
+-def nth_prime(n):
+-    count = 0
+-    num = 1
+-    while count < n:
+-        num += 1
+-        if is_prime(num):
+-            count += 1
+-    return str(num)
[email protected]('/prime/<int:n>')
++def nth_prime(n):
++    count = 0
++    num = 1
++    while count < n:
++        num += 1
++        if sympy.isprime(num):
++            count += 1
++    return str(num)
+\`\`\`
+
+Be precise, consistent, and follow these rules carefully to generate correct diffs!
+
+Parameters:
+- path: (required) The path of the file to apply the diff to (relative to the current working directory ${cwd})
+- diff: (required) The diff content in unified format to apply to the file.
+
+Usage:
+<apply_diff>
+<path>File path here</path>
+<diff>
+Your diff here
+</diff>
+</apply_diff>`
+	}
+
+	// Helper function to split a hunk into smaller hunks based on contiguous changes
+	private splitHunk(hunk: Hunk): Hunk[] {
+		const result: Hunk[] = []
+		let currentHunk: Hunk | null = null
+		let contextBefore: Change[] = []
+		let contextAfter: Change[] = []
+		const MAX_CONTEXT_LINES = 3 // Keep 3 lines of context before/after changes
+
+		for (let i = 0; i < hunk.changes.length; i++) {
+			const change = hunk.changes[i]
+
+			if (change.type === "context") {
+				if (!currentHunk) {
+					contextBefore.push(change)
+					if (contextBefore.length > MAX_CONTEXT_LINES) {
+						contextBefore.shift()
+					}
+				} else {
+					contextAfter.push(change)
+					if (contextAfter.length > MAX_CONTEXT_LINES) {
+						// We've collected enough context after changes, create a new hunk
+						currentHunk.changes.push(...contextAfter)
+						result.push(currentHunk)
+						currentHunk = null
+						// Keep the last few context lines for the next hunk
+						contextBefore = contextAfter
+						contextAfter = []
+					}
+				}
+			} else {
+				if (!currentHunk) {
+					currentHunk = { changes: [...contextBefore] }
+					contextAfter = []
+				} else if (contextAfter.length > 0) {
+					// Add accumulated context to current hunk
+					currentHunk.changes.push(...contextAfter)
+					contextAfter = []
+				}
+				currentHunk.changes.push(change)
+			}
+		}
+
+		// Add any remaining changes
+		if (currentHunk) {
+			if (contextAfter.length > 0) {
+				currentHunk.changes.push(...contextAfter)
+			}
+			result.push(currentHunk)
+		}
+
+		return result
+	}
+
+	async applyDiff(
+		originalContent: string,
+		diffContent: string,
+		startLine?: number,
+		endLine?: number
+	): Promise<DiffResult> {
+		const parsedDiff = this.parseUnifiedDiff(diffContent)
+		const originalLines = originalContent.split("\n")
+		let result = [...originalLines]
+
+		if (!parsedDiff.hunks.length) {
+			return {
+				success: false,
+				error: "No hunks found in diff. Please ensure your diff includes actual changes and follows the unified diff format.",
+			}
+		}
+
+		for (const hunk of parsedDiff.hunks) {
+			const contextStr = prepareSearchString(hunk.changes)
+			const {
+				index: matchPosition,
+				confidence,
+				strategy,
+			} = findBestMatch(contextStr, result, 0, this.confidenceThreshold)
+
+			if (confidence < this.confidenceThreshold) {
+				console.log("Full hunk application failed, trying sub-hunks strategy")
+				// Try splitting the hunk into smaller hunks
+				const subHunks = this.splitHunk(hunk)
+				let subHunkSuccess = true
+				let subHunkResult = [...result]
+
+				for (const subHunk of subHunks) {
+					const subContextStr = prepareSearchString(subHunk.changes)
+					const subSearchResult = findBestMatch(subContextStr, subHunkResult, 0, this.confidenceThreshold)
+
+					if (subSearchResult.confidence >= this.confidenceThreshold) {
+						const subEditResult = await applyEdit(
+							subHunk,
+							subHunkResult,
+							subSearchResult.index,
+							subSearchResult.confidence,
+							this.confidenceThreshold
+						)
+						if (subEditResult.confidence >= this.confidenceThreshold) {
+							subHunkResult = subEditResult.result
+							continue
+						}
+					}
+					subHunkSuccess = false
+					break
+				}
+
+				if (subHunkSuccess) {
+					result = subHunkResult
+					continue
+				}
+
+				// If sub-hunks also failed, return the original error
+				const contextLines = hunk.changes.filter((c) => c.type === "context").length
+				const totalLines = hunk.changes.length
+				const contextRatio = contextLines / totalLines
+
+				let errorMsg = `Failed to find a matching location in the file (${Math.floor(
+					confidence * 100
+				)}% confidence, needs ${Math.floor(this.confidenceThreshold * 100)}%)\n\n`
+				errorMsg += "Debug Info:\n"
+				errorMsg += `- Search Strategy Used: ${strategy}\n`
+				errorMsg += `- Context Lines: ${contextLines} out of ${totalLines} total lines (${Math.floor(
+					contextRatio * 100
+				)}%)\n`
+				errorMsg += `- Attempted to split into ${subHunks.length} sub-hunks but still failed\n`
+
+				if (contextRatio < 0.2) {
+					errorMsg += "\nPossible Issues:\n"
+					errorMsg += "- Not enough context lines to uniquely identify the location\n"
+					errorMsg += "- Add a few more lines of unchanged code around your changes\n"
+				} else if (contextRatio > 0.5) {
+					errorMsg += "\nPossible Issues:\n"
+					errorMsg += "- Too many context lines may reduce search accuracy\n"
+					errorMsg += "- Try to keep only 2-3 lines of context before and after changes\n"
+				} else {
+					errorMsg += "\nPossible Issues:\n"
+					errorMsg += "- The diff may be targeting a different version of the file\n"
+					errorMsg +=
+						"- There may be too many changes in a single hunk, try splitting the changes into multiple hunks\n"
+				}
+
+				if (startLine && endLine) {
+					errorMsg += `\nSearch Range: lines ${startLine}-${endLine}\n`
+				}
+
+				return { success: false, error: errorMsg }
+			}
+
+			const editResult = await applyEdit(hunk, result, matchPosition, confidence, this.confidenceThreshold)
+			if (editResult.confidence >= this.confidenceThreshold) {
+				result = editResult.result
+			} else {
+				// Edit failure - likely due to content mismatch
+				let errorMsg = `Failed to apply the edit using ${editResult.strategy} strategy (${Math.floor(
+					editResult.confidence * 100
+				)}% confidence)\n\n`
+				errorMsg += "Debug Info:\n"
+				errorMsg += "- The location was found but the content didn't match exactly\n"
+				errorMsg += "- This usually means the file has been modified since the diff was created\n"
+				errorMsg += "- Or the diff may be targeting a different version of the file\n"
+				errorMsg += "\nPossible Solutions:\n"
+				errorMsg += "1. Refresh your view of the file and create a new diff\n"
+				errorMsg += "2. Double-check that the removed lines (-) match the current file content\n"
+				errorMsg += "3. Ensure your diff targets the correct version of the file"
+
+				return { success: false, error: errorMsg }
+			}
+		}
+
+		return { success: true, content: result.join("\n") }
+	}
+}

+ 408 - 0
src/core/diff/strategies/new-unified/search-strategies.ts

@@ -0,0 +1,408 @@
+import { compareTwoStrings } from "string-similarity"
+import { closest } from "fastest-levenshtein"
+import { diff_match_patch } from "diff-match-patch"
+import { Change, Hunk } from "./types"
+
+export type SearchResult = {
+	index: number
+	confidence: number
+	strategy: string
+}
+
+const LARGE_FILE_THRESHOLD = 1000 // lines
+const UNIQUE_CONTENT_BOOST = 0.05
+const DEFAULT_OVERLAP_SIZE = 3 // lines of overlap between windows
+const MAX_WINDOW_SIZE = 500 // maximum lines in a window
+
+// Helper function to calculate adaptive confidence threshold based on file size
+function getAdaptiveThreshold(contentLength: number, baseThreshold: number): number {
+	if (contentLength <= LARGE_FILE_THRESHOLD) {
+		return baseThreshold
+	}
+	return Math.max(baseThreshold - 0.07, 0.8) // Reduce threshold for large files but keep minimum at 80%
+}
+
+// Helper function to evaluate content uniqueness
+function evaluateContentUniqueness(searchStr: string, content: string[]): number {
+	const searchLines = searchStr.split("\n")
+	const uniqueLines = new Set(searchLines)
+	const contentStr = content.join("\n")
+
+	// Calculate how many search lines are relatively unique in the content
+	let uniqueCount = 0
+	for (const line of uniqueLines) {
+		const regex = new RegExp(line.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g")
+		const matches = contentStr.match(regex)
+		if (matches && matches.length <= 2) {
+			// Line appears at most twice
+			uniqueCount++
+		}
+	}
+
+	return uniqueCount / uniqueLines.size
+}
+
+// Helper function to prepare search string from context
+export function prepareSearchString(changes: Change[]): string {
+	const lines = changes.filter((c) => c.type === "context" || c.type === "remove").map((c) => c.originalLine)
+	return lines.join("\n")
+}
+
+// Helper function to evaluate similarity between two texts
+export function evaluateSimilarity(original: string, modified: string): number {
+	return compareTwoStrings(original, modified)
+}
+
+// Helper function to validate using diff-match-patch
+export function getDMPSimilarity(original: string, modified: string): number {
+	const dmp = new diff_match_patch()
+	const diffs = dmp.diff_main(original, modified)
+	dmp.diff_cleanupSemantic(diffs)
+	const patches = dmp.patch_make(original, diffs)
+	const [expectedText] = dmp.patch_apply(patches, original)
+
+	const similarity = evaluateSimilarity(expectedText, modified)
+	return similarity
+}
+
+// Helper function to validate edit results using hunk information
+export function validateEditResult(hunk: Hunk, result: string): number {
+	// Build the expected text from the hunk
+	const expectedText = hunk.changes
+		.filter(change => change.type === "context" || change.type === "add")
+		.map(change => change.indent ? change.indent + change.content : change.content)
+		.join("\n");
+
+	// Calculate similarity between the result and expected text
+	const similarity = getDMPSimilarity(expectedText, result);
+
+	// If the result is unchanged from original, return low confidence
+	const originalText = hunk.changes
+		.filter(change => change.type === "context" || change.type === "remove")
+		.map(change => change.indent ? change.indent + change.content : change.content)
+		.join("\n");
+
+	const originalSimilarity = getDMPSimilarity(originalText, result);
+	if (originalSimilarity > 0.97 && similarity !== 1) {
+		return 0.8 * similarity;  // Some confidence since we found the right location
+	}
+  
+	// For partial matches, scale the confidence but keep it high if we're close
+	return similarity;
+}
+
+// Helper function to validate context lines against original content
+function validateContextLines(searchStr: string, content: string, confidenceThreshold: number): number {
+	// Extract just the context lines from the search string
+	const contextLines = searchStr.split("\n").filter((line) => !line.startsWith("-")) // Exclude removed lines
+
+	// Compare context lines with content
+	const similarity = evaluateSimilarity(contextLines.join("\n"), content)
+
+	// Get adaptive threshold based on content size
+	const threshold = getAdaptiveThreshold(content.split("\n").length, confidenceThreshold)
+
+	// Calculate uniqueness boost
+	const uniquenessScore = evaluateContentUniqueness(searchStr, content.split("\n"))
+	const uniquenessBoost = uniquenessScore * UNIQUE_CONTENT_BOOST
+
+	// Adjust confidence based on threshold and uniqueness
+	return similarity < threshold ? similarity * 0.3 + uniquenessBoost : similarity + uniquenessBoost
+}
+
+// Helper function to create overlapping windows
+function createOverlappingWindows(
+	content: string[],
+	searchSize: number,
+	overlapSize: number = DEFAULT_OVERLAP_SIZE
+): { window: string[]; startIndex: number }[] {
+	const windows: { window: string[]; startIndex: number }[] = []
+
+	// Ensure minimum window size is at least searchSize
+	const effectiveWindowSize = Math.max(searchSize, Math.min(searchSize * 2, MAX_WINDOW_SIZE))
+
+	// Ensure overlap size doesn't exceed window size
+	const effectiveOverlapSize = Math.min(overlapSize, effectiveWindowSize - 1)
+
+	// Calculate step size, ensure it's at least 1
+	const stepSize = Math.max(1, effectiveWindowSize - effectiveOverlapSize)
+
+	for (let i = 0; i < content.length; i += stepSize) {
+		const windowContent = content.slice(i, i + effectiveWindowSize)
+		if (windowContent.length >= searchSize) {
+			windows.push({ window: windowContent, startIndex: i })
+		}
+	}
+
+	return windows
+}
+
+// Helper function to combine overlapping matches
+function combineOverlappingMatches(
+	matches: (SearchResult & { windowIndex: number })[],
+	overlapSize: number = DEFAULT_OVERLAP_SIZE
+): SearchResult[] {
+	if (matches.length === 0) {
+		return []
+	}
+
+	// Sort matches by confidence
+	matches.sort((a, b) => b.confidence - a.confidence)
+
+	const combinedMatches: SearchResult[] = []
+	const usedIndices = new Set<number>()
+
+	for (const match of matches) {
+		if (usedIndices.has(match.windowIndex)) {
+			continue
+		}
+
+		// Find overlapping matches
+		const overlapping = matches.filter(
+			(m) =>
+				Math.abs(m.windowIndex - match.windowIndex) === 1 &&
+				Math.abs(m.index - match.index) <= overlapSize &&
+				!usedIndices.has(m.windowIndex)
+		)
+
+		if (overlapping.length > 0) {
+			// Boost confidence if we find same match in overlapping windows
+			const avgConfidence =
+				(match.confidence + overlapping.reduce((sum, m) => sum + m.confidence, 0)) / (overlapping.length + 1)
+			const boost = Math.min(0.05 * overlapping.length, 0.1) // Max 10% boost
+
+			combinedMatches.push({
+				index: match.index,
+				confidence: Math.min(1, avgConfidence + boost),
+				strategy: `${match.strategy}-overlapping`,
+			})
+
+			usedIndices.add(match.windowIndex)
+			overlapping.forEach((m) => usedIndices.add(m.windowIndex))
+		} else {
+			combinedMatches.push({
+				index: match.index,
+				confidence: match.confidence,
+				strategy: match.strategy,
+			})
+			usedIndices.add(match.windowIndex)
+		}
+	}
+
+	return combinedMatches
+}
+
+export function findExactMatch(
+	searchStr: string,
+	content: string[],
+	startIndex: number = 0,
+	confidenceThreshold: number = 0.97
+): SearchResult {
+	const searchLines = searchStr.split("\n")
+	const windows = createOverlappingWindows(content.slice(startIndex), searchLines.length)
+	const matches: (SearchResult & { windowIndex: number })[] = []
+
+	windows.forEach((windowData, windowIndex) => {
+		const windowStr = windowData.window.join("\n")
+		const exactMatch = windowStr.indexOf(searchStr)
+
+		if (exactMatch !== -1) {
+			const matchedContent = windowData.window
+				.slice(
+					windowStr.slice(0, exactMatch).split("\n").length - 1,
+					windowStr.slice(0, exactMatch).split("\n").length - 1 + searchLines.length
+				)
+				.join("\n")
+
+			const similarity = getDMPSimilarity(searchStr, matchedContent)
+			const contextSimilarity = validateContextLines(searchStr, matchedContent, confidenceThreshold)
+			const confidence = Math.min(similarity, contextSimilarity)
+
+			matches.push({
+				index: startIndex + windowData.startIndex + windowStr.slice(0, exactMatch).split("\n").length - 1,
+				confidence,
+				strategy: "exact",
+				windowIndex,
+			})
+		}
+	})
+
+	const combinedMatches = combineOverlappingMatches(matches)
+	return combinedMatches.length > 0 ? combinedMatches[0] : { index: -1, confidence: 0, strategy: "exact" }
+}
+
+// String similarity strategy
+export function findSimilarityMatch(
+	searchStr: string,
+	content: string[],
+	startIndex: number = 0,
+	confidenceThreshold: number = 0.97
+): SearchResult {
+	const searchLines = searchStr.split("\n")
+	let bestScore = 0
+	let bestIndex = -1
+
+	for (let i = startIndex; i < content.length - searchLines.length + 1; i++) {
+		const windowStr = content.slice(i, i + searchLines.length).join("\n")
+		const score = compareTwoStrings(searchStr, windowStr)
+		if (score > bestScore && score >= confidenceThreshold) {
+			const similarity = getDMPSimilarity(searchStr, windowStr)
+			const contextSimilarity = validateContextLines(searchStr, windowStr, confidenceThreshold)
+			const adjustedScore = Math.min(similarity, contextSimilarity) * score
+
+			if (adjustedScore > bestScore) {
+				bestScore = adjustedScore
+				bestIndex = i
+			}
+		}
+	}
+
+	return {
+		index: bestIndex,
+		confidence: bestIndex !== -1 ? bestScore : 0,
+		strategy: "similarity",
+	}
+}
+
+// Levenshtein strategy
+export function findLevenshteinMatch(
+	searchStr: string,
+	content: string[],
+	startIndex: number = 0,
+	confidenceThreshold: number = 0.97
+): SearchResult {
+	const searchLines = searchStr.split("\n")
+	const candidates = []
+
+	for (let i = startIndex; i < content.length - searchLines.length + 1; i++) {
+		candidates.push(content.slice(i, i + searchLines.length).join("\n"))
+	}
+
+	if (candidates.length > 0) {
+		const closestMatch = closest(searchStr, candidates)
+		const index = startIndex + candidates.indexOf(closestMatch)
+		const similarity = getDMPSimilarity(searchStr, closestMatch)
+		const contextSimilarity = validateContextLines(searchStr, closestMatch, confidenceThreshold)
+		const confidence = Math.min(similarity, contextSimilarity)
+		return {
+			index: confidence === 0 ? -1 : index,
+			confidence: index !== -1 ? confidence : 0,
+			strategy: "levenshtein",
+		}
+	}
+
+	return { index: -1, confidence: 0, strategy: "levenshtein" }
+}
+
+// Helper function to identify anchor lines
+function identifyAnchors(searchStr: string): { first: string | null; last: string | null } {
+	const searchLines = searchStr.split("\n")
+	let first: string | null = null
+	let last: string | null = null
+
+	// Find the first non-empty line
+	for (const line of searchLines) {
+		if (line.trim()) {
+			first = line
+			break
+		}
+	}
+
+	// Find the last non-empty line
+	for (let i = searchLines.length - 1; i >= 0; i--) {
+		if (searchLines[i].trim()) {
+			last = searchLines[i]
+			break
+		}
+	}
+
+	return { first, last }
+}
+
+// Anchor-based search strategy
+export function findAnchorMatch(
+	searchStr: string,
+	content: string[],
+	startIndex: number = 0,
+	confidenceThreshold: number = 0.97
+): SearchResult {
+	const searchLines = searchStr.split("\n")
+	const { first, last } = identifyAnchors(searchStr)
+
+	if (!first || !last) {
+		return { index: -1, confidence: 0, strategy: "anchor" }
+	}
+
+	let firstIndex = -1
+	let lastIndex = -1
+
+	// Check if the first anchor is unique
+	let firstOccurrences = 0
+	for (const contentLine of content) {
+		if (contentLine === first) {
+			firstOccurrences++
+		}
+	}
+
+	if (firstOccurrences !== 1) {
+		return { index: -1, confidence: 0, strategy: "anchor" }
+	}
+
+	// Find the first anchor
+	for (let i = startIndex; i < content.length; i++) {
+		if (content[i] === first) {
+			firstIndex = i
+			break
+		}
+	}
+
+	// Find the last anchor
+	for (let i = content.length - 1; i >= startIndex; i--) {
+		if (content[i] === last) {
+			lastIndex = i
+			break
+		}
+	}
+
+	if (firstIndex === -1 || lastIndex === -1 || lastIndex <= firstIndex) {
+		return { index: -1, confidence: 0, strategy: "anchor" }
+	}
+
+	// Validate the context
+	const expectedContext = searchLines.slice(searchLines.indexOf(first) + 1, searchLines.indexOf(last)).join("\n")
+	const actualContext = content.slice(firstIndex + 1, lastIndex).join("\n")
+	const contextSimilarity = evaluateSimilarity(expectedContext, actualContext)
+
+	if (contextSimilarity < getAdaptiveThreshold(content.length, confidenceThreshold)) {
+		return { index: -1, confidence: 0, strategy: "anchor" }
+	}
+
+	const confidence = 1
+
+	return {
+		index: firstIndex,
+		confidence: confidence,
+		strategy: "anchor",
+	}
+}
+
+// Main search function that tries all strategies
+export function findBestMatch(
+	searchStr: string,
+	content: string[],
+	startIndex: number = 0,
+	confidenceThreshold: number = 0.97
+): SearchResult {
+	const strategies = [findExactMatch, findAnchorMatch, findSimilarityMatch, findLevenshteinMatch]
+
+	let bestResult: SearchResult = { index: -1, confidence: 0, strategy: "none" }
+
+	for (const strategy of strategies) {
+		const result = strategy(searchStr, content, startIndex, confidenceThreshold)
+		if (result.confidence > bestResult.confidence) {
+			bestResult = result
+		}
+	}
+
+	return bestResult
+}

+ 20 - 0
src/core/diff/strategies/new-unified/types.ts

@@ -0,0 +1,20 @@
+export type Change = {
+  type: 'context' | 'add' | 'remove';
+  content: string;
+  indent: string;
+  originalLine?: string;
+};
+
+export type Hunk = {
+  changes: Change[];
+};
+
+export type Diff = {
+  hunks: Hunk[];
+}; 
+
+export type EditResult = {
+  confidence: number;
+  result: string[];
+  strategy: string;
+};

+ 1 - 1
src/core/diff/strategies/search-replace.ts

@@ -127,7 +127,7 @@ Your search/replace content here
 </apply_diff>`
     }
 
-    applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): DiffResult {
+    async applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): Promise<DiffResult> {
         // Extract the search and replace blocks
         const match = diffContent.match(/<<<<<<< SEARCH\n([\s\S]*?)\n?=======\n([\s\S]*?)\n?>>>>>>> REPLACE/);
         if (!match) {

+ 1 - 1
src/core/diff/strategies/unified.ts

@@ -108,7 +108,7 @@ Your diff here
 </apply_diff>`
     }
 
-    applyDiff(originalContent: string, diffContent: string): DiffResult {
+    async applyDiff(originalContent: string, diffContent: string): Promise<DiffResult> {
         try {
             const result = applyPatch(originalContent, diffContent)
             if (result === false) {

+ 2 - 2
src/core/diff/types.ts

@@ -28,5 +28,5 @@ export interface DiffStrategy {
      * @param endLine Optional line number where the search block ends. If not provided, searches the entire file.
      * @returns A DiffResult object containing either the successful result or error details
      */
-    applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): DiffResult
-}
+    applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): Promise<DiffResult>
+}

+ 24 - 5
src/core/webview/ClineProvider.ts

@@ -99,6 +99,7 @@ type GlobalStateKey =
 	| "modeApiConfigs"
 	| "customPrompts"
 	| "enhancementApiConfigId"
+  	| "experimentalDiffStrategy"
 	| "autoApprovalEnabled"
 
 export const GlobalFileNames = {
@@ -254,6 +255,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			fuzzyMatchThreshold,
 			mode,
 			customInstructions: globalInstructions,
+      experimentalDiffStrategy
 		} = await this.getState()
 
 		const modeInstructions = customPrompts?.[mode]?.customInstructions
@@ -268,7 +270,9 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			diffEnabled,
 			fuzzyMatchThreshold,
 			task,
-			images
+			images,
+			undefined,
+			experimentalDiffStrategy
 		)
 	}
 
@@ -281,6 +285,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			fuzzyMatchThreshold,
 			mode,
 			customInstructions: globalInstructions,
+      experimentalDiffStrategy
 		} = await this.getState()
 
 		const modeInstructions = customPrompts?.[mode]?.customInstructions
@@ -296,7 +301,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			fuzzyMatchThreshold,
 			undefined,
 			undefined,
-			historyItem
+			historyItem,
+			experimentalDiffStrategy
 		)
 	}
 
@@ -1070,6 +1076,13 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 							vscode.window.showErrorMessage("Failed to get list api configuration")
 						}
 						break
+          case "experimentalDiffStrategy":
+						await this.updateGlobalState("experimentalDiffStrategy", message.bool ?? false)
+						// Update diffStrategy in current Cline instance if it exists
+						if (this.cline) {
+							await this.cline.updateDiffStrategy(message.bool ?? false)
+						}
+						await this.postStateToWebview()
 				}
 			},
 			null,
@@ -1541,7 +1554,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 		uiMessagesFilePath: string
 		apiConversationHistory: Anthropic.MessageParam[]
 	}> {
-		const history = ((await this.getGlobalState("taskHistory")) as HistoryItem[] | undefined) || []
+		const history = (await this.getGlobalState("taskHistory") as HistoryItem[] | undefined) || []
 		const historyItem = history.find((item) => item.id === id)
 		if (historyItem) {
 			const taskDirPath = path.join(this.context.globalStorageUri.fsPath, "tasks", id)
@@ -1606,7 +1619,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 
 	async deleteTaskFromState(id: string) {
 		// Remove the task from history
-		const taskHistory = ((await this.getGlobalState("taskHistory")) as HistoryItem[]) || []
+		const taskHistory = (await this.getGlobalState("taskHistory") as HistoryItem[]) || []
 		const updatedTaskHistory = taskHistory.filter((task) => task.id !== id)
 		await this.updateGlobalState("taskHistory", updatedTaskHistory)
 
@@ -1647,6 +1660,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			mode,
 			customPrompts,
 			enhancementApiConfigId,
+      		experimentalDiffStrategy,
 			autoApprovalEnabled,
 		} = await this.getState()
 
@@ -1687,6 +1701,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			mode: mode ?? codeMode,
 			customPrompts: customPrompts ?? {},
 			enhancementApiConfigId,
+      		experimentalDiffStrategy: experimentalDiffStrategy ?? false,
 			autoApprovalEnabled: autoApprovalEnabled ?? false,
 		}
 	}
@@ -1803,6 +1818,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			modeApiConfigs,
 			customPrompts,
 			enhancementApiConfigId,
+      		experimentalDiffStrategy,
 			autoApprovalEnabled,
 		] = await Promise.all([
 			this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
@@ -1864,6 +1880,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			this.getGlobalState("modeApiConfigs") as Promise<Record<Mode, string> | undefined>,
 			this.getGlobalState("customPrompts") as Promise<CustomPrompts | undefined>,
 			this.getGlobalState("enhancementApiConfigId") as Promise<string | undefined>,
+      		this.getGlobalState("experimentalDiffStrategy") as Promise<boolean | undefined>,
 			this.getGlobalState("autoApprovalEnabled") as Promise<boolean | undefined>,
 		])
 
@@ -1969,13 +1986,15 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			modeApiConfigs: modeApiConfigs ?? {} as Record<Mode, string>,
 			customPrompts: customPrompts ?? {},
 			enhancementApiConfigId,
+      		experimentalDiffStrategy: experimentalDiffStrategy ?? false,
 			autoApprovalEnabled: autoApprovalEnabled ?? false,
 		}
 	}
 
 	async updateTaskHistory(item: HistoryItem): Promise<HistoryItem[]> {
-		const history = ((await this.getGlobalState("taskHistory")) as HistoryItem[]) || []
+		const history = (await this.getGlobalState("taskHistory") as HistoryItem[] | undefined) || []
 		const existingItemIndex = history.findIndex((h) => h.id === item.id)
+
 		if (existingItemIndex !== -1) {
 			history[existingItemIndex] = item
 		} else {

+ 2 - 0
src/core/webview/__tests__/ClineProvider.test.ts

@@ -610,6 +610,8 @@ describe('ClineProvider', () => {
             true,
             1.0,
             'Test task',
+            undefined,
+            undefined,
             undefined
         );
     });

+ 1 - 0
src/shared/ExtensionMessage.ts

@@ -94,6 +94,7 @@ export interface ExtensionState {
 	mode: Mode
 	modeApiConfigs?: Record<Mode, string>
 	enhancementApiConfigId?: string
+  	experimentalDiffStrategy?: boolean
 	autoApprovalEnabled?: boolean
 }
 

+ 1 - 0
src/shared/WebviewMessage.ts

@@ -72,6 +72,7 @@ export interface WebviewMessage {
 		| "getSystemPrompt"
 		| "systemPrompt"
 		| "enhancementApiConfigId"
+    	| "experimentalDiffStrategy"
 		| "autoApprovalEnabled"
 	text?: string
 	disabled?: boolean

+ 24 - 2
webview-ui/src/components/settings/SettingsView.tsx

@@ -61,6 +61,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 		listApiConfigMeta,
 		mode,
 		setMode,
+    experimentalDiffStrategy,
+		setExperimentalDiffStrategy,
 	} = useExtensionState()
 	const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
 	const [modelIdErrorMessage, setModelIdErrorMessage] = useState<string | undefined>(undefined)
@@ -103,6 +105,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 				apiConfiguration
 			})
 			vscode.postMessage({ type: "mode", text: mode })
+      vscode.postMessage({ type: "experimentalDiffStrategy", bool: experimentalDiffStrategy })
 			onDone()
 		}
 	}
@@ -328,7 +331,13 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 				</div>
 
 				<div style={{ marginBottom: 5 }}>
-					<VSCodeCheckbox checked={diffEnabled} onChange={(e: any) => setDiffEnabled(e.target.checked)}>
+					<VSCodeCheckbox checked={diffEnabled} onChange={(e: any) => {
+						setDiffEnabled(e.target.checked)
+						if (!e.target.checked) {
+							// Reset experimental strategy when diffs are disabled
+							setExperimentalDiffStrategy(false)
+						}
+					}}>
 						<span style={{ fontWeight: "500" }}>Enable editing through diffs</span>
 					</VSCodeCheckbox>
 					<p
@@ -342,6 +351,19 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 
 					{diffEnabled && (
 						<div style={{ marginTop: 10 }}>
+							<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
+								<span style={{ color: "var(--vscode-errorForeground)" }}>⚠️</span>
+								<VSCodeCheckbox
+									checked={experimentalDiffStrategy}
+									onChange={(e: any) => setExperimentalDiffStrategy(e.target.checked)}>
+									<span style={{ fontWeight: "500" }}>Use experimental unified diff strategy</span>
+								</VSCodeCheckbox>
+							</div>
+							<p style={{ fontSize: "12px", marginBottom: 15, color: "var(--vscode-descriptionForeground)" }}>
+								Enable the experimental unified diff strategy. This strategy might reduce the number of retries caused by model errors but may cause unexpected behavior or incorrect edits.
+								Only enable if you understand the risks and are willing to carefully review all changes.
+							</p>
+
 							<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
 								<span style={{ fontWeight: "500", minWidth: '100px' }}>Match precision</span>
 								<input
@@ -363,7 +385,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 									{Math.round((fuzzyMatchThreshold || 1) * 100)}%
 								</span>
 							</div>
-							<p style={{ fontSize: "12px", marginBottom: 10, color: "var(--vscode-descriptionForeground)" }}>
+							<p style={{ fontSize: "12px", marginTop: "5px", color: "var(--vscode-descriptionForeground)" }}>
 								This slider controls how precisely code sections must match when applying diffs. Lower values allow more flexible matching but increase the risk of incorrect replacements. Use values below 100% with extreme caution.
 							</p>
 						</div>

+ 5 - 0
webview-ui/src/context/ExtensionStateContext.tsx

@@ -63,6 +63,8 @@ export interface ExtensionStateContextType extends ExtensionState {
 	setCustomPrompts: (value: CustomPrompts) => void
 	enhancementApiConfigId?: string
 	setEnhancementApiConfigId: (value: string) => void
+	experimentalDiffStrategy: boolean
+	setExperimentalDiffStrategy: (value: boolean) => void
 	autoApprovalEnabled?: boolean
 	setAutoApprovalEnabled: (value: boolean) => void
 }
@@ -93,6 +95,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		mode: codeMode,
 		customPrompts: defaultPrompts,
 		enhancementApiConfigId: '',
+		experimentalDiffStrategy: false,
 		autoApprovalEnabled: false,
 	})
 	const [didHydrateState, setDidHydrateState] = useState(false)
@@ -211,6 +214,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		fuzzyMatchThreshold: state.fuzzyMatchThreshold,
 		writeDelayMs: state.writeDelayMs,
 		screenshotQuality: state.screenshotQuality,
+		experimentalDiffStrategy: state.experimentalDiffStrategy ?? false,
 		setApiConfiguration: (value) => setState((prevState) => ({
 			...prevState,
 			apiConfiguration: value
@@ -241,6 +245,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		setMode: (value: Mode) => setState((prevState) => ({ ...prevState, mode: value })),
 		setCustomPrompts: (value) => setState((prevState) => ({ ...prevState, customPrompts: value })),
 		setEnhancementApiConfigId: (value) => setState((prevState) => ({ ...prevState, enhancementApiConfigId: value })),
+		setExperimentalDiffStrategy: (value) => setState((prevState) => ({ ...prevState, experimentalDiffStrategy: value })),
 		setAutoApprovalEnabled: (value) => setState((prevState) => ({ ...prevState, autoApprovalEnabled: value })),
 	}