Browse Source

Merge pull request #364 from daniel-lxs/new_unified

New unified edit strategy
Matt Rubens 11 months ago
parent
commit
b35206bc9d

+ 99 - 8
package-lock.json

@@ -17,6 +17,7 @@
         "@modelcontextprotocol/sdk": "^1.0.1",
         "@modelcontextprotocol/sdk": "^1.0.1",
         "@types/clone-deep": "^4.0.4",
         "@types/clone-deep": "^4.0.4",
         "@types/pdf-parse": "^1.1.4",
         "@types/pdf-parse": "^1.1.4",
+        "@types/tmp": "^0.2.6",
         "@types/turndown": "^5.0.5",
         "@types/turndown": "^5.0.5",
         "@types/vscode": "^1.95.0",
         "@types/vscode": "^1.95.0",
         "@vscode/codicons": "^0.0.36",
         "@vscode/codicons": "^0.0.36",
@@ -27,7 +28,9 @@
         "default-shell": "^2.2.0",
         "default-shell": "^2.2.0",
         "delay": "^6.0.0",
         "delay": "^6.0.0",
         "diff": "^5.2.0",
         "diff": "^5.2.0",
+        "diff-match-patch": "^1.0.5",
         "fast-deep-equal": "^3.1.3",
         "fast-deep-equal": "^3.1.3",
+        "fastest-levenshtein": "^1.0.16",
         "globby": "^14.0.2",
         "globby": "^14.0.2",
         "isbinaryfile": "^5.0.2",
         "isbinaryfile": "^5.0.2",
         "mammoth": "^1.8.0",
         "mammoth": "^1.8.0",
@@ -39,8 +42,11 @@
         "puppeteer-chromium-resolver": "^23.0.0",
         "puppeteer-chromium-resolver": "^23.0.0",
         "puppeteer-core": "^23.4.0",
         "puppeteer-core": "^23.4.0",
         "serialize-error": "^11.0.3",
         "serialize-error": "^11.0.3",
+        "simple-git": "^3.27.0",
         "sound-play": "^1.1.0",
         "sound-play": "^1.1.0",
+        "string-similarity": "^4.0.4",
         "strip-ansi": "^7.1.0",
         "strip-ansi": "^7.1.0",
+        "tmp": "^0.2.3",
         "tree-sitter-wasms": "^0.1.11",
         "tree-sitter-wasms": "^0.1.11",
         "turndown": "^7.2.0",
         "turndown": "^7.2.0",
         "web-tree-sitter": "^0.22.6",
         "web-tree-sitter": "^0.22.6",
@@ -50,9 +56,11 @@
         "@changesets/cli": "^2.27.10",
         "@changesets/cli": "^2.27.10",
         "@changesets/types": "^6.0.0",
         "@changesets/types": "^6.0.0",
         "@types/diff": "^5.2.1",
         "@types/diff": "^5.2.1",
+        "@types/diff-match-patch": "^1.0.36",
         "@types/jest": "^29.5.14",
         "@types/jest": "^29.5.14",
         "@types/mocha": "^10.0.7",
         "@types/mocha": "^10.0.7",
         "@types/node": "20.x",
         "@types/node": "20.x",
+        "@types/string-similarity": "^4.0.2",
         "@typescript-eslint/eslint-plugin": "^7.14.1",
         "@typescript-eslint/eslint-plugin": "^7.14.1",
         "@typescript-eslint/parser": "^7.11.0",
         "@typescript-eslint/parser": "^7.11.0",
         "@vscode/test-cli": "^0.0.9",
         "@vscode/test-cli": "^0.0.9",
@@ -4108,6 +4116,21 @@
         "@jridgewell/sourcemap-codec": "^1.4.14"
         "@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": {
     "node_modules/@manypkg/find-root": {
       "version": "1.1.0",
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz",
       "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz",
@@ -6069,6 +6092,13 @@
       "integrity": "sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ==",
       "integrity": "sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ==",
       "dev": true
       "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": {
     "node_modules/@types/graceful-fs": {
       "version": "4.1.9",
       "version": "4.1.9",
       "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
       "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
@@ -6146,6 +6176,19 @@
       "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
       "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
       "dev": true
       "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": {
     "node_modules/@types/turndown": {
       "version": "5.0.5",
       "version": "5.0.5",
       "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz",
       "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz",
@@ -7913,6 +7956,12 @@
         "node": ">=0.3.1"
         "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": {
     "node_modules/diff-sequences": {
       "version": "29.6.3",
       "version": "29.6.3",
       "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
       "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
@@ -8716,6 +8765,19 @@
         "node": ">=0.10.0"
         "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": {
     "node_modules/extract-zip": {
       "version": "2.0.1",
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
       "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
@@ -8807,6 +8869,15 @@
         "fxparser": "src/cli/cli.js"
         "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": {
     "node_modules/fastq": {
       "version": "1.17.1",
       "version": "1.17.1",
       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
       "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",
       "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
       "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
       "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
       "dev": true,
       "dev": true,
+      "license": "MIT",
       "engines": {
       "engines": {
         "node": ">=0.10.0"
         "node": ">=0.10.0"
       }
       }
@@ -13926,6 +13998,21 @@
         "url": "https://github.com/sponsors/isaacs"
         "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": {
     "node_modules/sisteransi": {
       "version": "1.0.5",
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
       "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -14202,6 +14289,13 @@
         "node": ">=8"
         "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": {
     "node_modules/string-width": {
       "version": "5.1.2",
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@@ -14544,15 +14638,12 @@
       "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
       "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
     },
     },
     "node_modules/tmp": {
     "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": {
       "engines": {
-        "node": ">=0.6.0"
+        "node": ">=14.14"
       }
       }
     },
     },
     "node_modules/tmpl": {
     "node_modules/tmpl": {

+ 8 - 0
package.json

@@ -202,9 +202,11 @@
     "@changesets/cli": "^2.27.10",
     "@changesets/cli": "^2.27.10",
     "@changesets/types": "^6.0.0",
     "@changesets/types": "^6.0.0",
     "@types/diff": "^5.2.1",
     "@types/diff": "^5.2.1",
+    "@types/diff-match-patch": "^1.0.36",
     "@types/jest": "^29.5.14",
     "@types/jest": "^29.5.14",
     "@types/mocha": "^10.0.7",
     "@types/mocha": "^10.0.7",
     "@types/node": "20.x",
     "@types/node": "20.x",
+    "@types/string-similarity": "^4.0.2",
     "@typescript-eslint/eslint-plugin": "^7.14.1",
     "@typescript-eslint/eslint-plugin": "^7.14.1",
     "@typescript-eslint/parser": "^7.11.0",
     "@typescript-eslint/parser": "^7.11.0",
     "@vscode/test-cli": "^0.0.9",
     "@vscode/test-cli": "^0.0.9",
@@ -230,6 +232,7 @@
     "@modelcontextprotocol/sdk": "^1.0.1",
     "@modelcontextprotocol/sdk": "^1.0.1",
     "@types/clone-deep": "^4.0.4",
     "@types/clone-deep": "^4.0.4",
     "@types/pdf-parse": "^1.1.4",
     "@types/pdf-parse": "^1.1.4",
+    "@types/tmp": "^0.2.6",
     "@types/turndown": "^5.0.5",
     "@types/turndown": "^5.0.5",
     "@types/vscode": "^1.95.0",
     "@types/vscode": "^1.95.0",
     "@vscode/codicons": "^0.0.36",
     "@vscode/codicons": "^0.0.36",
@@ -240,7 +243,9 @@
     "default-shell": "^2.2.0",
     "default-shell": "^2.2.0",
     "delay": "^6.0.0",
     "delay": "^6.0.0",
     "diff": "^5.2.0",
     "diff": "^5.2.0",
+    "diff-match-patch": "^1.0.5",
     "fast-deep-equal": "^3.1.3",
     "fast-deep-equal": "^3.1.3",
+    "fastest-levenshtein": "^1.0.16",
     "globby": "^14.0.2",
     "globby": "^14.0.2",
     "isbinaryfile": "^5.0.2",
     "isbinaryfile": "^5.0.2",
     "mammoth": "^1.8.0",
     "mammoth": "^1.8.0",
@@ -252,8 +257,11 @@
     "puppeteer-chromium-resolver": "^23.0.0",
     "puppeteer-chromium-resolver": "^23.0.0",
     "puppeteer-core": "^23.4.0",
     "puppeteer-core": "^23.4.0",
     "serialize-error": "^11.0.3",
     "serialize-error": "^11.0.3",
+    "simple-git": "^3.27.0",
     "sound-play": "^1.1.0",
     "sound-play": "^1.1.0",
+    "string-similarity": "^4.0.4",
     "strip-ansi": "^7.1.0",
     "strip-ansi": "^7.1.0",
+    "tmp": "^0.2.3",
     "tree-sitter-wasms": "^0.1.11",
     "tree-sitter-wasms": "^0.1.11",
     "turndown": "^7.2.0",
     "turndown": "^7.2.0",
     "web-tree-sitter": "^0.22.6",
     "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 { BrowserSession } from "../services/browser/BrowserSession"
 import { OpenRouterHandler } from "../api/providers/openrouter"
 import { OpenRouterHandler } from "../api/providers/openrouter"
 import { McpHub } from "../services/mcp/McpHub"
 import { McpHub } from "../services/mcp/McpHub"
+import crypto from "crypto"
 
 
 const cwd =
 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
 	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
 	customInstructions?: string
 	diffStrategy?: DiffStrategy
 	diffStrategy?: DiffStrategy
 	diffEnabled: boolean = false
 	diffEnabled: boolean = false
+	fuzzyMatchThreshold: number = 1.0
 
 
 	apiConversationHistory: (Anthropic.MessageParam & { ts?: number })[] = []
 	apiConversationHistory: (Anthropic.MessageParam & { ts?: number })[] = []
 	clineMessages: ClineMessage[] = []
 	clineMessages: ClineMessage[] = []
@@ -105,28 +107,46 @@ export class Cline {
 		fuzzyMatchThreshold?: number,
 		fuzzyMatchThreshold?: number,
 		task?: string | undefined,
 		task?: string | undefined,
 		images?: 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.api = buildApiHandler(apiConfiguration)
 		this.terminalManager = new TerminalManager()
 		this.terminalManager = new TerminalManager()
 		this.urlContentFetcher = new UrlContentFetcher(provider.context)
 		this.urlContentFetcher = new UrlContentFetcher(provider.context)
 		this.browserSession = new BrowserSession(provider.context)
 		this.browserSession = new BrowserSession(provider.context)
-		this.diffViewProvider = new DiffViewProvider(cwd)
 		this.customInstructions = customInstructions
 		this.customInstructions = customInstructions
 		this.diffEnabled = enableDiff ?? false
 		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) {
 		if (historyItem) {
 			this.taskId = historyItem.id
 			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)
 			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
 	// Storing task to disk for history
@@ -1326,7 +1346,7 @@ export class Cline {
 								const originalContent = await fs.readFile(absolutePath, "utf-8")
 								const originalContent = await fs.readFile(absolutePath, "utf-8")
 
 
 								// Apply the diff to the original content
 								// Apply the diff to the original content
-								const diffResult = this.diffStrategy?.applyDiff(
+								const diffResult = await this.diffStrategy?.applyDiff(
 									originalContent, 
 									originalContent, 
 									diffContent, 
 									diffContent, 
 									parseInt(block.params.start_line ?? ''), 
 									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.diffEnabled).toBe(true);
             expect(cline.diffStrategy).toBeDefined();
             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();
             getDiffStrategySpy.mockRestore();
         });
         });
@@ -341,7 +341,7 @@ describe('Cline', () => {
 
 
             expect(cline.diffEnabled).toBe(true);
             expect(cline.diffEnabled).toBe(true);
             expect(cline.diffStrategy).toBeDefined();
             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();
             getDiffStrategySpy.mockRestore();
         });
         });

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

@@ -1,15 +1,17 @@
 import type { DiffStrategy } from './types'
 import type { DiffStrategy } from './types'
 import { UnifiedDiffStrategy } from './strategies/unified'
 import { UnifiedDiffStrategy } from './strategies/unified'
 import { SearchReplaceDiffStrategy } from './strategies/search-replace'
 import { SearchReplaceDiffStrategy } from './strategies/search-replace'
+import { NewUnifiedDiffStrategy } from './strategies/new-unified'
 /**
 /**
  * Get the appropriate diff strategy for the given model
  * 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')
  * @param model The name of the model being used (e.g., 'gpt-4', 'claude-3-opus')
  * @returns The appropriate diff strategy for the model
  * @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 }
 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
             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 originalContent = 'function hello() {\n    console.log("hello")\n}\n'
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -21,14 +21,14 @@ function hello() {
 }
 }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe('function hello() {\n    console.log("hello world")\n}\n')
                 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 originalContent = '\nfunction example() {\n    return 42;\n}\n\n'
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -41,14 +41,14 @@ function example() {
 }
 }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe('\nfunction example() {\n    return 43;\n}\n\n')
                 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 originalContent = '    function test() {\n        return true;\n    }\n'
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -61,14 +61,14 @@ function test() {
 }
 }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe('    function test() {\n        return false;\n    }\n')
                 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 originalContent = "function test() {\n\treturn true;\n}\n"
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -81,14 +81,14 @@ function test() {
 }
 }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe("function test() {\n\treturn false;\n}\n")
                 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 originalContent = "\tclass Example {\n\t    constructor() {\n\t\tthis.value = 0;\n\t    }\n\t}"
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -105,14 +105,14 @@ function test() {
 \t}
 \t}
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe("\tclass Example {\n\t    constructor() {\n\t\tthis.value = 1;\n\t    }\n\t}")
                 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 originalContent = "\tfunction test() {\n\t\treturn true;\n\t}"
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -126,14 +126,14 @@ function test() {
 }
 }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe("\tfunction test() {\n\t\t// Add comment\n\t\treturn false;\n\t}")
                 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 originalContent = "\tfunction test() {\n\t\treturn true;\n\t}"
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -148,14 +148,14 @@ function test() {
 \t}
 \t}
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             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}")
                 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 originalContent = "function test() {\r\n    return true;\r\n}\r\n"
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -168,14 +168,14 @@ function test() {
 }
 }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe("function test() {\r\n    return false;\r\n}\r\n")
                 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 originalContent = 'function hello() {\n    console.log("hello")\n}\n'
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -188,19 +188,19 @@ function hello() {
 }
 }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(false)
             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 originalContent = 'function hello() {\n    console.log("hello")\n}\n'
             const diffContent = `test.ts\nInvalid diff format`
             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)
             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 originalContent = 'class Example {\n    constructor() {\n        this.value = 0\n    }\n\n    getValue() {\n        return this.value\n    }\n}\n'
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -215,14 +215,14 @@ function hello() {
     }
     }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             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')
                 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 originalContent = "    indented\n        more indented\n    back\n"
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -235,14 +235,14 @@ function hello() {
     end
     end
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe("    modified\n        still indented\n    end\n")
                 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 originalContent = '				onScroll={() => updateHighlights()}'
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -255,14 +255,14 @@ function hello() {
 				}}
 				}}
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe('				onScroll={() => updateHighlights()}\n				onDragOver={(e) => {\n					e.preventDefault()\n					e.stopPropagation()\n				}}')
                 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 = `
             const originalContent = `
 class Example {
 class Example {
     constructor() {
     constructor() {
@@ -296,7 +296,7 @@ class Example {
     }
     }
 >>>>>>> REPLACE`.trim();
 >>>>>>> REPLACE`.trim();
         
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`
                 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 {
             const originalContent = `class Example {
     constructor() {
     constructor() {
         this.value = 0;
         this.value = 0;
@@ -340,7 +340,7 @@ class Example {
     }
     }
 >>>>>>> REPLACE`;
 >>>>>>> REPLACE`;
         
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`class Example {
                 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():
             const originalContent = `def example():
     if condition:
     if condition:
         do_something()
         do_something()
@@ -376,7 +376,7 @@ class Example {
             process(item)
             process(item)
 >>>>>>> REPLACE`;
 >>>>>>> REPLACE`;
         
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`def example():
                 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 originalContent = `function test() {
     const x = 1;
     const x = 1;
     
     
@@ -409,7 +409,7 @@ class Example {
     if (x) {
     if (x) {
 >>>>>>> REPLACE`;
 >>>>>>> REPLACE`;
         
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`function test() {
                 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 {
             const originalContent = `class Test {
     method() {
     method() {
         if (true) {
         if (true) {
@@ -450,7 +450,7 @@ class Example {
     }
     }
 >>>>>>> REPLACE`;
 >>>>>>> REPLACE`;
         
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`class Test {
                 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 {
             const originalContent = `class Example {
     constructor() {
     constructor() {
         if (true) {
         if (true) {
@@ -484,8 +484,8 @@ class Example {
         this.init();
         this.init();
         this.setup();
         this.setup();
 >>>>>>> REPLACE`;
 >>>>>>> REPLACE`;
-        
-            const result = strategy.applyDiff(originalContent, diffContent);
+          
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
             expect(result.content).toBe(`class Example {
             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 {
     const originalContent = `class Example {
     constructor() {
     constructor() {
         if (true) {
         if (true) {
@@ -514,7 +514,7 @@ class Example {
 this.init();
 this.init();
 >>>>>>> REPLACE`;
 >>>>>>> REPLACE`;
         
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`class Example {
                 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 {
     const originalContent = `class Example {
     constructor() {
     constructor() {
         if (true) {
         if (true) {
@@ -548,7 +548,7 @@ this.init();
     this.validate();
     this.validate();
 >>>>>>> REPLACE`;
 >>>>>>> REPLACE`;
         
         
-            const result = strategy.applyDiff(originalContent, diffContent);
+            const result = await strategy.applyDiff(originalContent, diffContent);
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`class Example {
                 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 = `
             const originalContent = `
 function one() {
 function one() {
     return "target";
     return "target";
@@ -595,7 +595,7 @@ function five() {
             // Search around the middle (function three)
             // Search around the middle (function three)
             // Even though all functions contain the target text,
             // Even though all functions contain the target text,
             // it should match the one closest to line 9 first
             // 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)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`function one() {
                 expect(result.content).toBe(`function one() {
@@ -629,7 +629,7 @@ function five() {
                 strategy = new SearchReplaceDiffStrategy()
                 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 originalContent = 'function test() {\n    return true;\n}\n'
                 const diffContent = `test.ts
                 const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -642,14 +642,14 @@ function five() {
 3 | }
 3 | }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
         
         
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe('function test() {\n    return false;\n}\n')
                     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 originalContent = 'function test() {\n    return true;\n}\n'
                 const diffContent = `test.ts
                 const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -662,14 +662,14 @@ function five() {
  3 | }
  3 | }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
         
         
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe('function test() {\n    return false;\n}\n')
                     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 originalContent = 'function test() {\n    return true;\n}\n'
                 const diffContent = `test.ts
                 const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -682,11 +682,11 @@ function five() {
 3 | }
 3 | }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
         
         
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(false)
                 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 originalContent = '|header|another|\n|---|---|\n|data|more|\n'
                 const diffContent = `test.ts
                 const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -699,14 +699,14 @@ function five() {
 3 | |data|updated|
 3 | |data|updated|
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
         
         
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe('|header|another|\n|---|---|\n|data|updated|\n')
                     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 originalContent = '    function test() {\n        return true;\n    }\n'
                 const diffContent = `test.ts
                 const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -719,14 +719,14 @@ function five() {
 3 |     }
 3 |     }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
         
         
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe('    function test() {\n        return false;\n    }\n')
                     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 originalContent = 'function test() {\n    return true;\n}\n'
                 const diffContent = `test.ts
                 const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -739,14 +739,14 @@ function five() {
 22 | }
 22 | }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
         
         
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe('function test() {\n    return false;\n}\n')
                     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 originalContent = '| Pipe\n|---|\n| Data\n'
                 const diffContent = `test.ts
                 const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -759,14 +759,14 @@ function five() {
 | Updated
 | Updated
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
             
             
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe('| Pipe\n|---|\n| Updated\n')
                     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 originalContent = '| Pipe\n|---|\n| Data\n'
                 const diffContent = `test.ts
                 const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -779,7 +779,7 @@ function five() {
 3 | | NewData
 3 | | NewData
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
             
             
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe('1 | | Pipe\n2 | |---|\n3 | | NewData\n')
                     expect(result.content).toBe('1 | | Pipe\n2 | |---|\n3 | | NewData\n')
@@ -796,7 +796,7 @@ function five() {
         })
         })
     
     
         describe('deletion', () => {
         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() {
                 const originalContent = `function test() {
     console.log("hello");
     console.log("hello");
     // Comment to remove
     // Comment to remove
@@ -808,7 +808,7 @@ function five() {
 =======
 =======
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
     
     
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe(`function test() {
                     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 {
                 const originalContent = `class Example {
     constructor() {
     constructor() {
         // Initialize
         // Initialize
@@ -838,7 +838,7 @@ function five() {
 =======
 =======
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
     
     
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe(`class Example {
                     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() {
                 const originalContent = `function outer() {
     if (true) {
     if (true) {
         // Remove this
         // Remove this
@@ -865,7 +865,7 @@ function five() {
 =======
 =======
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
     
     
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe(`function outer() {
                     expect(result.content).toBe(`function outer() {
@@ -878,7 +878,7 @@ function five() {
         })
         })
     
     
         describe('insertion', () => {
         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 originalContent = `function test() {
     const x = 1;
     const x = 1;
     return x;
     return x;
@@ -889,7 +889,7 @@ function five() {
     console.log("Adding log");
     console.log("Adding log");
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
     
     
-                const result = strategy.applyDiff(originalContent, diffContent, 2, 2)
+                const result = await strategy.applyDiff(originalContent, diffContent, 2, 2)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe(`function test() {
                     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() {
                 const originalContent = `function test() {
     if (true) {
     if (true) {
         const x = 1;
         const x = 1;
@@ -913,7 +913,7 @@ function five() {
         console.log("After");
         console.log("After");
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
     
     
-                const result = strategy.applyDiff(originalContent, diffContent, 3, 3)
+                const result = await strategy.applyDiff(originalContent, diffContent, 3, 3)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe(`function test() {
                     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() {
                 const originalContent = `function test() {
     return true;
     return true;
 }`
 }`
@@ -938,7 +938,7 @@ function five() {
 
 
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
     
     
-                const result = strategy.applyDiff(originalContent, diffContent, 1, 1)
+                const result = await strategy.applyDiff(originalContent, diffContent, 1, 1)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe(`// Copyright 2024
                     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() {
                 const originalContent = `function test() {
     return true;
     return true;
 }`
 }`
@@ -961,7 +961,7 @@ function test() {
 // End of file
 // End of file
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
     
     
-                const result = strategy.applyDiff(originalContent, diffContent, 4, 4)
+                const result = await strategy.applyDiff(originalContent, diffContent, 4, 4)
                 expect(result.success).toBe(true)
                 expect(result.success).toBe(true)
                 if (result.success) {
                 if (result.success) {
                     expect(result.content).toBe(`function test() {
                     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() {
                 const originalContent = `function test() {
     return true;
     return true;
 }`
 }`
@@ -982,7 +982,7 @@ function test() {
 console.log("test");
 console.log("test");
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
     
     
-                const result = strategy.applyDiff(originalContent, diffContent)
+                const result = await strategy.applyDiff(originalContent, diffContent)
                 expect(result.success).toBe(false)
                 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
             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 originalContent = 'function getData() {\n    const results = fetchData();\n    return results.filter(Boolean);\n}\n'
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -1011,14 +1011,14 @@ function getData() {
 
 
             strategy = new SearchReplaceDiffStrategy(0.9, 5) // Use 5 line buffer for tests
             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)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe('function getData() {\n    const data = fetchData();\n    return data.filter(Boolean);\n}\n')
                 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 originalContent = 'function processUsers(data) {\n    return data.map(user => user.name);\n}\n'
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -1031,11 +1031,11 @@ function processData(data) {
 }
 }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(false)
             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 originalContent = 'function sum(a, b) {\n    return a + b;\n}'
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -1048,14 +1048,14 @@ function sum(a, b) {
 }
 }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe('function sum(a, b) {\n    return a + b + 1;\n}')
                 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 originalContent = 'function sum(a, b) {\n\n    return a + b;\n}'
             const diffContent = `test.ts
             const diffContent = `test.ts
 <<<<<<< SEARCH
 <<<<<<< SEARCH
@@ -1065,7 +1065,7 @@ import { a } from "a";
 function sum(a, b) {
 function sum(a, b) {
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe('import { a } from "a";\nfunction sum(a, b) {\n\n    return a + b;\n}')
                 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)
             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 = `
             const originalContent = `
 function one() {
 function one() {
     return 1;
     return 1;
@@ -1105,7 +1105,7 @@ function two() {
 }
 }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent, 5, 7)
+            const result = await strategy.applyDiff(originalContent, diffContent, 5, 7)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`function one() {
                 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 = `
             const originalContent = `
 function one() {
 function one() {
     return 1;
     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
             // 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
             // 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)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`function one() {
                 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 = `
             const originalContent = `
 function one() {
 function one() {
     return 1;
     return 1;
@@ -1201,11 +1201,11 @@ function five() {
 
 
             // Searching around function two() (lines 5-7)
             // Searching around function two() (lines 5-7)
             // function five() is more than 5 lines away, so it shouldn't match
             // 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)
             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 = `
             const originalContent = `
 function one() {
 function one() {
     return 1;
     return 1;
@@ -1226,7 +1226,7 @@ function one() {
 }
 }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent, 1, 3)
+            const result = await strategy.applyDiff(originalContent, diffContent, 1, 3)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`function one() {
                 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 = `
             const originalContent = `
 function one() {
 function one() {
     return 1;
     return 1;
@@ -1260,7 +1260,7 @@ function two() {
 }
 }
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent, 5, 7)
+            const result = await strategy.applyDiff(originalContent, diffContent, 5, 7)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`function one() {
                 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 = `
             const originalContent = `
 function processData(data) {
 function processData(data) {
     return data.map(x => x * 2);
     return data.map(x => x * 2);
@@ -1306,7 +1306,7 @@ function processData(data) {
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
             // Target the second instance of processData
             // 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)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`function processData(data) {
                 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 = `
             const originalContent = `
 function one() {
 function one() {
     return 1;
     return 1;
@@ -1356,7 +1356,7 @@ function three() {
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
             // Only provide start_line, should search from there to end of file
             // 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)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`function one() {
                 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 = `
             const originalContent = `
 function one() {
 function one() {
     return 1;
     return 1;
@@ -1399,7 +1399,7 @@ function one() {
 >>>>>>> REPLACE`
 >>>>>>> REPLACE`
 
 
             // Only provide end_line, should search from start of file to there
             // 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)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`function one() {
                 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 = `
             const originalContent = `
 function one() {
 function one() {
     return 1;
     return 1;
@@ -1446,7 +1446,7 @@ function process() {
 
 
             // Should match the second instance exactly at lines 10-12
             // Should match the second instance exactly at lines 10-12
             // even though the first instance at 6-8 is within the expanded search range
             // 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)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`
                 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 = `
             const originalContent = `
 function one() {
 function one() {
     return 1;
     return 1;
@@ -1494,7 +1494,7 @@ function process() {
 
 
             // Specify wrong line numbers (3-5), but content exists at 6-8
             // 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
             // 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)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(`function one() {
                 expect(result.content).toBe(`function one() {
@@ -1519,14 +1519,14 @@ function two() {
             strategy = new SearchReplaceDiffStrategy()
             strategy = new SearchReplaceDiffStrategy()
         })
         })
 
 
-        it('should include the current working directory', () => {
+        it('should include the current working directory', async () => {
             const cwd = '/test/dir'
             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}`)
             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('<<<<<<< SEARCH')
             expect(description).toContain('=======')
             expect(description).toContain('=======')
             expect(description).toContain('>>>>>>> REPLACE')
             expect(description).toContain('>>>>>>> REPLACE')
@@ -1534,8 +1534,8 @@ function two() {
             expect(description).toContain('</apply_diff>')
             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('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.')
             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', () => {
     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';
             const originalContent = `import { Logger } from '../logger';
 
 
 function calculateTotal(items: number[]): number {
 function calculateTotal(items: number[]): number {
@@ -58,14 +58,14 @@ function calculateTotal(items: number[]): number {
 
 
 export { calculateTotal };`
 export { calculateTotal };`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(expected)
                 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 {
             const originalContent = `class Calculator {
   add(a: number, b: number): number {
   add(a: number, b: number): number {
     return a + b;
     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)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(expected)
                 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';
             const originalContent = `import { useState } from 'react';
 import { Button } from './components';
 import { Button } from './components';
 
 
@@ -132,15 +132,15 @@ function App() {
   useEffect(() => { document.title = \`Count: \${count}\` }, [count]);
   useEffect(() => { document.title = \`Count: \${count}\` }, [count]);
   return <Button onClick={() => setCount(count + 1)}>{count}</Button>;
   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)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(expected)
                 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';
             const originalContent = `import { readFile, writeFile } from 'fs';
 
 
 function processFile(path: string) {
 function processFile(path: string) {
@@ -198,14 +198,14 @@ async function processFile(path: string) {
 
 
 export { processFile };`
 export { processFile };`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(expected)
                 expect(result.content).toBe(expected)
             }
             }
         })
         })
 
 
-        it('should handle empty original content', () => {
+        it('should handle empty original content', async () => {
             const originalContent = ''
             const originalContent = ''
             const diffContent = `--- empty.ts
             const diffContent = `--- empty.ts
 +++ empty.ts
 +++ empty.ts
@@ -218,7 +218,7 @@ export { processFile };`
   return \`Hello, \${name}!\`;
   return \`Hello, \${name}!\`;
 }\n`
 }\n`
 
 
-            const result = strategy.applyDiff(originalContent, diffContent)
+            const result = await strategy.applyDiff(originalContent, diffContent)
             expect(result.success).toBe(true)
             expect(result.success).toBe(true)
             if (result.success) {
             if (result.success) {
                 expect(result.content).toBe(expected)
                 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>`
 </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
         // Extract the search and replace blocks
         const match = diffContent.match(/<<<<<<< SEARCH\n([\s\S]*?)\n?=======\n([\s\S]*?)\n?>>>>>>> REPLACE/);
         const match = diffContent.match(/<<<<<<< SEARCH\n([\s\S]*?)\n?=======\n([\s\S]*?)\n?>>>>>>> REPLACE/);
         if (!match) {
         if (!match) {

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

@@ -108,7 +108,7 @@ Your diff here
 </apply_diff>`
 </apply_diff>`
     }
     }
 
 
-    applyDiff(originalContent: string, diffContent: string): DiffResult {
+    async applyDiff(originalContent: string, diffContent: string): Promise<DiffResult> {
         try {
         try {
             const result = applyPatch(originalContent, diffContent)
             const result = applyPatch(originalContent, diffContent)
             if (result === false) {
             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.
      * @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
      * @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"
 	| "modeApiConfigs"
 	| "customPrompts"
 	| "customPrompts"
 	| "enhancementApiConfigId"
 	| "enhancementApiConfigId"
+  	| "experimentalDiffStrategy"
 	| "autoApprovalEnabled"
 	| "autoApprovalEnabled"
 
 
 export const GlobalFileNames = {
 export const GlobalFileNames = {
@@ -254,6 +255,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			fuzzyMatchThreshold,
 			fuzzyMatchThreshold,
 			mode,
 			mode,
 			customInstructions: globalInstructions,
 			customInstructions: globalInstructions,
+      experimentalDiffStrategy
 		} = await this.getState()
 		} = await this.getState()
 
 
 		const modeInstructions = customPrompts?.[mode]?.customInstructions
 		const modeInstructions = customPrompts?.[mode]?.customInstructions
@@ -268,7 +270,9 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			diffEnabled,
 			diffEnabled,
 			fuzzyMatchThreshold,
 			fuzzyMatchThreshold,
 			task,
 			task,
-			images
+			images,
+			undefined,
+			experimentalDiffStrategy
 		)
 		)
 	}
 	}
 
 
@@ -281,6 +285,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			fuzzyMatchThreshold,
 			fuzzyMatchThreshold,
 			mode,
 			mode,
 			customInstructions: globalInstructions,
 			customInstructions: globalInstructions,
+      experimentalDiffStrategy
 		} = await this.getState()
 		} = await this.getState()
 
 
 		const modeInstructions = customPrompts?.[mode]?.customInstructions
 		const modeInstructions = customPrompts?.[mode]?.customInstructions
@@ -296,7 +301,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			fuzzyMatchThreshold,
 			fuzzyMatchThreshold,
 			undefined,
 			undefined,
 			undefined,
 			undefined,
-			historyItem
+			historyItem,
+			experimentalDiffStrategy
 		)
 		)
 	}
 	}
 
 
@@ -1070,6 +1076,13 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 							vscode.window.showErrorMessage("Failed to get list api configuration")
 							vscode.window.showErrorMessage("Failed to get list api configuration")
 						}
 						}
 						break
 						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,
 			null,
@@ -1541,7 +1554,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 		uiMessagesFilePath: string
 		uiMessagesFilePath: string
 		apiConversationHistory: Anthropic.MessageParam[]
 		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)
 		const historyItem = history.find((item) => item.id === id)
 		if (historyItem) {
 		if (historyItem) {
 			const taskDirPath = path.join(this.context.globalStorageUri.fsPath, "tasks", id)
 			const taskDirPath = path.join(this.context.globalStorageUri.fsPath, "tasks", id)
@@ -1606,7 +1619,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 
 
 	async deleteTaskFromState(id: string) {
 	async deleteTaskFromState(id: string) {
 		// Remove the task from history
 		// 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)
 		const updatedTaskHistory = taskHistory.filter((task) => task.id !== id)
 		await this.updateGlobalState("taskHistory", updatedTaskHistory)
 		await this.updateGlobalState("taskHistory", updatedTaskHistory)
 
 
@@ -1647,6 +1660,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			mode,
 			mode,
 			customPrompts,
 			customPrompts,
 			enhancementApiConfigId,
 			enhancementApiConfigId,
+      		experimentalDiffStrategy,
 			autoApprovalEnabled,
 			autoApprovalEnabled,
 		} = await this.getState()
 		} = await this.getState()
 
 
@@ -1687,6 +1701,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			mode: mode ?? codeMode,
 			mode: mode ?? codeMode,
 			customPrompts: customPrompts ?? {},
 			customPrompts: customPrompts ?? {},
 			enhancementApiConfigId,
 			enhancementApiConfigId,
+      		experimentalDiffStrategy: experimentalDiffStrategy ?? false,
 			autoApprovalEnabled: autoApprovalEnabled ?? false,
 			autoApprovalEnabled: autoApprovalEnabled ?? false,
 		}
 		}
 	}
 	}
@@ -1803,6 +1818,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			modeApiConfigs,
 			modeApiConfigs,
 			customPrompts,
 			customPrompts,
 			enhancementApiConfigId,
 			enhancementApiConfigId,
+      		experimentalDiffStrategy,
 			autoApprovalEnabled,
 			autoApprovalEnabled,
 		] = await Promise.all([
 		] = await Promise.all([
 			this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
 			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("modeApiConfigs") as Promise<Record<Mode, string> | undefined>,
 			this.getGlobalState("customPrompts") as Promise<CustomPrompts | undefined>,
 			this.getGlobalState("customPrompts") as Promise<CustomPrompts | undefined>,
 			this.getGlobalState("enhancementApiConfigId") as Promise<string | undefined>,
 			this.getGlobalState("enhancementApiConfigId") as Promise<string | undefined>,
+      		this.getGlobalState("experimentalDiffStrategy") as Promise<boolean | undefined>,
 			this.getGlobalState("autoApprovalEnabled") 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>,
 			modeApiConfigs: modeApiConfigs ?? {} as Record<Mode, string>,
 			customPrompts: customPrompts ?? {},
 			customPrompts: customPrompts ?? {},
 			enhancementApiConfigId,
 			enhancementApiConfigId,
+      		experimentalDiffStrategy: experimentalDiffStrategy ?? false,
 			autoApprovalEnabled: autoApprovalEnabled ?? false,
 			autoApprovalEnabled: autoApprovalEnabled ?? false,
 		}
 		}
 	}
 	}
 
 
 	async updateTaskHistory(item: HistoryItem): Promise<HistoryItem[]> {
 	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)
 		const existingItemIndex = history.findIndex((h) => h.id === item.id)
+
 		if (existingItemIndex !== -1) {
 		if (existingItemIndex !== -1) {
 			history[existingItemIndex] = item
 			history[existingItemIndex] = item
 		} else {
 		} else {

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

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

+ 1 - 0
src/shared/ExtensionMessage.ts

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

+ 1 - 0
src/shared/WebviewMessage.ts

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

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

@@ -61,6 +61,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 		listApiConfigMeta,
 		listApiConfigMeta,
 		mode,
 		mode,
 		setMode,
 		setMode,
+    experimentalDiffStrategy,
+		setExperimentalDiffStrategy,
 	} = useExtensionState()
 	} = useExtensionState()
 	const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
 	const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
 	const [modelIdErrorMessage, setModelIdErrorMessage] = useState<string | undefined>(undefined)
 	const [modelIdErrorMessage, setModelIdErrorMessage] = useState<string | undefined>(undefined)
@@ -103,6 +105,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 				apiConfiguration
 				apiConfiguration
 			})
 			})
 			vscode.postMessage({ type: "mode", text: mode })
 			vscode.postMessage({ type: "mode", text: mode })
+      vscode.postMessage({ type: "experimentalDiffStrategy", bool: experimentalDiffStrategy })
 			onDone()
 			onDone()
 		}
 		}
 	}
 	}
@@ -328,7 +331,13 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 				</div>
 				</div>
 
 
 				<div style={{ marginBottom: 5 }}>
 				<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>
 						<span style={{ fontWeight: "500" }}>Enable editing through diffs</span>
 					</VSCodeCheckbox>
 					</VSCodeCheckbox>
 					<p
 					<p
@@ -342,6 +351,19 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 
 
 					{diffEnabled && (
 					{diffEnabled && (
 						<div style={{ marginTop: 10 }}>
 						<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' }}>
 							<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
 								<span style={{ fontWeight: "500", minWidth: '100px' }}>Match precision</span>
 								<span style={{ fontWeight: "500", minWidth: '100px' }}>Match precision</span>
 								<input
 								<input
@@ -363,7 +385,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 									{Math.round((fuzzyMatchThreshold || 1) * 100)}%
 									{Math.round((fuzzyMatchThreshold || 1) * 100)}%
 								</span>
 								</span>
 							</div>
 							</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.
 								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>
 							</p>
 						</div>
 						</div>

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

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