From 700bcf34554733cd823dd20392fdddae01955cbd Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Sun, 15 Mar 2026 19:08:24 +0100 Subject: [PATCH] fix(coding-agent): normalize fuzzy edit matching and run touched tests closes #2044 --- AGENTS.md | 1 + .../coding-agent/src/core/tools/edit-diff.ts | 1 + packages/coding-agent/test/tools.test.ts | 30 +++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index bb6cf309..e405c514 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,6 +25,7 @@ read README.md, then ask which module(s) to work on. Based on the answer, read t - NEVER run: `npm run dev`, `npm run build`, `npm test` - Only run specific tests if user instructs: `npx tsx ../../node_modules/vitest/dist/cli.js --run test/specific.test.ts` - Run tests from the package root, not the repo root. +- If you create or modify a test file, you MUST run that test file and iterate until it passes. - When writing tests, run them, identify issues in either the test or implementation, and iterate until fixed. - NEVER commit unless user asks diff --git a/packages/coding-agent/src/core/tools/edit-diff.ts b/packages/coding-agent/src/core/tools/edit-diff.ts index 17f017bf..4077e747 100644 --- a/packages/coding-agent/src/core/tools/edit-diff.ts +++ b/packages/coding-agent/src/core/tools/edit-diff.ts @@ -34,6 +34,7 @@ export function restoreLineEndings(text: string, ending: "\r\n" | "\n"): string export function normalizeForFuzzyMatch(text: string): string { return ( text + .normalize("NFKC") // Strip trailing whitespace per line .split("\n") .map((line) => line.trimEnd()) diff --git a/packages/coding-agent/test/tools.test.ts b/packages/coding-agent/test/tools.test.ts index 5d097e2c..4098a837 100644 --- a/packages/coding-agent/test/tools.test.ts +++ b/packages/coding-agent/test/tools.test.ts @@ -442,6 +442,36 @@ describe("edit tool fuzzy matching", () => { expect(content).toBe("replaced\nline three\n"); }); + it("should match fullwidth punctuation in Chinese text", async () => { + const testFile = join(testDir, "chinese-punctuation.txt"); + writeFileSync(testFile, "你好,世界\n你好(世界)\n"); + + const result = await editTool.execute("test-fuzzy-chinese", { + path: testFile, + oldText: "你好,世界\n你好(世界)\n", + newText: "你好,pi\n你好(pi)\n", + }); + + expect(getTextOutput(result)).toContain("Successfully replaced"); + const content = readFileSync(testFile, "utf-8"); + expect(content).toBe("你好,pi\n你好(pi)\n"); + }); + + it("should match compatibility-equivalent Unicode forms", async () => { + const testFile = join(testDir, "unicode-compatibility.txt"); + writeFileSync(testFile, "ABC123\ncafe\u0301\n"); + + const result = await editTool.execute("test-fuzzy-unicode", { + path: testFile, + oldText: "ABC123\ncafé\n", + newText: "XYZ789\ncoffee\n", + }); + + expect(getTextOutput(result)).toContain("Successfully replaced"); + const content = readFileSync(testFile, "utf-8"); + expect(content).toBe("XYZ789\ncoffee\n"); + }); + it("should match smart single quotes to ASCII quotes", async () => { const testFile = join(testDir, "smart-quotes.txt"); // File has smart/curly single quotes (U+2018, U+2019)