Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
13 changes: 9 additions & 4 deletions packages/coding-agent/src/core/tools/edit-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down
27 changes: 27 additions & 0 deletions packages/coding-agent/test/edit-diff-occurrences.test.ts
Original file line number Diff line number Diff line change
@@ -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/,
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fixed directory-first ordering of path completions when the completion value is quoted (e.g. paths containing spaces or `@"..."` prefixes).
4 changes: 2 additions & 2 deletions packages/tui/src/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down