From 53e22e3f344f807a565a1fae08cf2d39b86a75c6 Mon Sep 17 00:00:00 2001 From: go165 <196723798+go165@users.noreply.github.com> Date: Mon, 15 Jun 2026 02:55:17 +0800 Subject: [PATCH] fix(filesystem): show context for failed edits --- src/filesystem/__tests__/lib.test.ts | 14 ++++++++++++++ src/filesystem/lib.ts | 28 +++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/filesystem/__tests__/lib.test.ts b/src/filesystem/__tests__/lib.test.ts index e0ae61224f..adf1254ad6 100644 --- a/src/filesystem/__tests__/lib.test.ts +++ b/src/filesystem/__tests__/lib.test.ts @@ -519,6 +519,20 @@ describe('Lib Functions', () => { .rejects.toThrow('Could not find exact match for edit'); }); + it('includes line context when edit text is not found', async () => { + const edits = [ + { oldText: 'line2 with typo', newText: 'replacement' } + ]; + + await expect(applyFileEdits('/test/file.txt', edits, false)) + .rejects.toThrow( + 'Could not find exact match for edit:\nline2 with typo\n\n' + + 'First 3 lines of file (3 total lines):\n' + + '1: line1\n2: line2\n3: line3\n\n' + + 'Hint: re-read the file and copy the exact current text, including whitespace.' + ); + }); + it('handles complex multi-line edits with indentation', async () => { mockFs.readFile.mockResolvedValue('function test() {\n console.log("hello");\n return true;\n}'); diff --git a/src/filesystem/lib.ts b/src/filesystem/lib.ts index ce4af9f38a..e308ffd267 100644 --- a/src/filesystem/lib.ts +++ b/src/filesystem/lib.ts @@ -191,6 +191,32 @@ interface FileEdit { newText: string; } +function buildEditNotFoundError(edit: FileEdit, content: string): Error { + const contentLines = content.endsWith('\n') + ? content.slice(0, -1).split('\n') + : content.split('\n'); + const oldLines = normalizeLineEndings(edit.oldText).split('\n'); + const anchorLine = oldLines.find(line => line.trim().length > 0)?.trim(); + const anchorIndex = anchorLine + ? contentLines.findIndex(line => line.trim().includes(anchorLine)) + : -1; + const contextStart = Math.max(0, (anchorIndex >= 0 ? anchorIndex : 0) - 2); + const contextEnd = Math.min(contentLines.length, contextStart + 5); + const contextLines = contentLines + .slice(contextStart, contextEnd) + .map((line, index) => `${contextStart + index + 1}: ${line}`) + .join('\n'); + const contextHeading = anchorIndex >= 0 + ? `Closest context around line ${anchorIndex + 1}` + : `First ${contextEnd - contextStart} lines of file`; + + return new Error( + `Could not find exact match for edit:\n${edit.oldText}\n\n` + + `${contextHeading} (${contentLines.length} total lines):\n${contextLines}\n\n` + + 'Hint: re-read the file and copy the exact current text, including whitespace.' + ); +} + export async function applyFileEdits( filePath: string, edits: FileEdit[], @@ -248,7 +274,7 @@ export async function applyFileEdits( } if (!matchFound) { - throw new Error(`Could not find exact match for edit:\n${edit.oldText}`); + throw buildEditNotFoundError(edit, modifiedContent); } }