From 32c1a818a6eb6a014b39d6616ab87641d2d92cbc Mon Sep 17 00:00:00 2001 From: yzy <1585004297@qq.com> Date: Sat, 22 Aug 2026 23:02:36 +0800 Subject: [PATCH 1/2] fix(coding-agent): count edit occurrences in the space the match was found in --- .../fix-edit-diff-occurrence-counting.md | 1 + .../coding-agent/src/core/tools/edit-diff.ts | 13 ++++++--- .../test/edit-diff-occurrences.test.ts | 27 +++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 packages/coding-agent/.changes/fix-edit-diff-occurrence-counting.md create mode 100644 packages/coding-agent/test/edit-diff-occurrences.test.ts diff --git a/packages/coding-agent/.changes/fix-edit-diff-occurrence-counting.md b/packages/coding-agent/.changes/fix-edit-diff-occurrence-counting.md new file mode 100644 index 0000000000..a0bdfb1bcc --- /dev/null +++ b/packages/coding-agent/.changes/fix-edit-diff-occurrence-counting.md @@ -0,0 +1 @@ +- Fixed the edit tool rejecting a unique exact match when fuzzy normalization (e.g. smart quotes elsewhere in the file) would collapse it into multiple occurrences. diff --git a/packages/coding-agent/src/core/tools/edit-diff.ts b/packages/coding-agent/src/core/tools/edit-diff.ts index a1fe118647..aa067d0c3a 100644 --- a/packages/coding-agent/src/core/tools/edit-diff.ts +++ b/packages/coding-agent/src/core/tools/edit-diff.ts @@ -126,9 +126,8 @@ export function stripBom(content: string): { bom: string; text: string } { } function countOccurrences(content: string, oldText: string): number { - const fuzzyContent = normalizeForFuzzyMatch(content); - const fuzzyOldText = normalizeForFuzzyMatch(oldText); - return fuzzyContent.split(fuzzyOldText).length - 1; + if (oldText.length === 0) return 0; + return content.split(oldText).length - 1; } function getNotFoundError(path: string, editIndex: number, totalEdits: number): Error { @@ -206,7 +205,13 @@ export function applyEditsToNormalizedContent( throw getNotFoundError(path, i, normalizedEdits.length); } - const occurrences = countOccurrences(baseContent, edit.oldText); + // Count occurrences in the same space the match was found in, so an exact + // match is not rejected because fuzzy normalization collapses extra hits + // (e.g. smart quotes elsewhere in the file). + const occurrences = countOccurrences( + matchResult.usedFuzzyMatch ? normalizeForFuzzyMatch(baseContent) : baseContent, + matchResult.usedFuzzyMatch ? normalizeForFuzzyMatch(edit.oldText) : edit.oldText, + ); if (occurrences > 1) { throw getDuplicateError(path, i, normalizedEdits.length, occurrences); } diff --git a/packages/coding-agent/test/edit-diff-occurrences.test.ts b/packages/coding-agent/test/edit-diff-occurrences.test.ts new file mode 100644 index 0000000000..0e62ce8a25 --- /dev/null +++ b/packages/coding-agent/test/edit-diff-occurrences.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, test } from "vitest"; +import { applyEditsToNormalizedContent } from "../src/core/tools/edit-diff.js"; + +describe("applyEditsToNormalizedContent occurrence counting", () => { + test("exact match is unique even if fuzzy normalization would collapse other occurrences", () => { + // "it's" appears exactly once, but normalizing the smart quote in "it’s" + // would make it look like a second occurrence. + const content = "it’s here\nit's there\n"; + const result = applyEditsToNormalizedContent(content, [{ oldText: "it's", newText: "ITS" }], "file.txt"); + expect(result.newContent).toBe("it’s here\nITS there\n"); + }); + + test("fuzzy match still counts occurrences in normalized space", () => { + // oldText needs fuzzy matching (smart quotes in content) and normalizes + // to two occurrences. + const content = "it’s here\nit’s there\n"; + expect(() => applyEditsToNormalizedContent(content, [{ oldText: "it's", newText: "x" }], "file.txt")).toThrow( + /must be unique/, + ); + }); + + test("genuinely duplicated exact text is rejected", () => { + expect(() => applyEditsToNormalizedContent("a\nb\na\n", [{ oldText: "a", newText: "x" }], "file.txt")).toThrow( + /must be unique/, + ); + }); +}); From 60e4a1e2676713335551917f4b2186d24ae0053a Mon Sep 17 00:00:00 2001 From: yzy <1585004297@qq.com> Date: Sat, 22 Aug 2026 23:04:44 +0800 Subject: [PATCH 2/2] fix(tui): sort directory completions first for quoted values --- packages/tui/.changes/fix-autocomplete-directory-sorting.md | 1 + packages/tui/src/autocomplete.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 packages/tui/.changes/fix-autocomplete-directory-sorting.md diff --git a/packages/tui/.changes/fix-autocomplete-directory-sorting.md b/packages/tui/.changes/fix-autocomplete-directory-sorting.md new file mode 100644 index 0000000000..8646f9ddc5 --- /dev/null +++ b/packages/tui/.changes/fix-autocomplete-directory-sorting.md @@ -0,0 +1 @@ +- Fixed directory-first ordering of path completions when the completion value is quoted (e.g. paths containing spaces or `@"..."` prefixes). diff --git a/packages/tui/src/autocomplete.ts b/packages/tui/src/autocomplete.ts index 4b50464df7..ddb8871370 100644 --- a/packages/tui/src/autocomplete.ts +++ b/packages/tui/src/autocomplete.ts @@ -658,8 +658,8 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider { } suggestions.sort((a, b) => { - const aIsDir = a.value.endsWith("/"); - const bIsDir = b.value.endsWith("/"); + const aIsDir = a.label.endsWith("/"); + const bIsDir = b.label.endsWith("/"); if (aIsDir && !bIsDir) return -1; if (!aIsDir && bIsDir) return 1; return a.label.localeCompare(b.label);