From d7afbc588df074d877459d6b666209c8ffe75a01 Mon Sep 17 00:00:00 2001 From: "Zoo (VP)" Date: Sun, 20 Sep 2026 11:04:48 +0900 Subject: [PATCH 1/6] fix(prompts): warn that apply_diff SEARCH requires whole lines, not substrings --- src/core/diff/strategies/multi-search-replace.ts | 2 +- src/core/prompts/tools/native-tools/apply_diff.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index a8ba0215d3..c8af32c70a 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -577,7 +577,7 @@ export class MultiSearchReplaceDiffStrategy implements DiffStrategy { diffResults.push({ success: false, - error: `No sufficiently similar match found${lineRange} (${Math.floor(bestMatchScore * 100)}% similar, needs ${Math.floor(this.fuzzyThreshold * 100)}%)\n\nDebug Info:\n- Similarity Score: ${Math.floor(bestMatchScore * 100)}%\n- Required Threshold: ${Math.floor(this.fuzzyThreshold * 100)}%\n- Search Range: ${startLine ? `starting at line ${startLine}` : "start to end"}\n- Levenshtein Distance: ${levenDist >= 0 ? `${levenDist} characters` : "N/A"}\n- Search Length: ${searchChunk.length} characters\n- Best Match Length: ${bestMatchContent ? bestMatchContent.length : 0} characters\n- Tried both standard and aggressive line number stripping\n- Tip: Use the read_file tool to get the latest content of the file before attempting to use the apply_diff tool again, as the file content may have changed\n\nSearch Content:\n${searchChunk}${bestMatchSection}${originalContentSection}`, + error: `No sufficiently similar match found${lineRange} (${Math.floor(bestMatchScore * 100)}% similar, needs ${Math.floor(this.fuzzyThreshold * 100)}%)\n\nDebug Info:\n- Similarity Score: ${Math.floor(bestMatchScore * 100)}%\n- Required Threshold: ${Math.floor(this.fuzzyThreshold * 100)}%\n- Search Range: ${startLine ? `starting at line ${startLine}` : "start to end"}\n- Levenshtein Distance: ${levenDist >= 0 ? `${levenDist} characters` : "N/A"}\n- Search Length: ${searchChunk.length} characters\n- Best Match Length: ${bestMatchContent ? bestMatchContent.length : 0} characters\n- Tried both standard and aggressive line number stripping\n- Tip: Use the read_file tool to get the latest content of the file before attempting to use the apply_diff tool again, as the file content may have changed\n- Tip: The SEARCH block must contain complete, whole lines — partial-line (substring) matching is not supported. If the target line is very long, copy the entire line into the SEARCH block\n\nSearch Content:\n${searchChunk}${bestMatchSection}${originalContentSection}`, }) continue } diff --git a/src/core/prompts/tools/native-tools/apply_diff.ts b/src/core/prompts/tools/native-tools/apply_diff.ts index cdbd99df33..720dddef3a 100644 --- a/src/core/prompts/tools/native-tools/apply_diff.ts +++ b/src/core/prompts/tools/native-tools/apply_diff.ts @@ -14,6 +14,7 @@ const DIFF_PARAMETER_DESCRIPTION = `A string containing one or more search/repla CRITICAL: - The ':start_line:[line_number]' header is strongly recommended for accurate matching. When provided, it must follow the exact syntax ':start_line:[integer]' (for example: ':start_line:220'). Do not write headers with shorthand forms like ':220' or variations like ':start_line=220'. - Copy the exact lines from the source file for a 100% string match including all whitespace, indentation, and newlines. +- The SEARCH block must contain complete, whole lines — partial-line (substring) matching is not supported. When the target line is very long (e.g. a Markdown paragraph), copy the entire line into the SEARCH block. - Ensure the separator '-------' is on its own line immediately following ':start_line:[line_number]' with a newline.` export const apply_diff = { From b43ef9b8a4508b7e34b1a88a719a86bb60070c5b Mon Sep 17 00:00:00 2001 From: myk1yt Date: Tue, 6 Oct 2026 01:43:04 +0900 Subject: [PATCH 2/6] test(prompts,diff): assert whole-lines guidance in apply_diff description and no-match error --- .../__tests__/multi-search-replace.spec.ts | 23 +++++++++++ .../native-tools/__tests__/apply_diff.spec.ts | 40 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/core/prompts/tools/native-tools/__tests__/apply_diff.spec.ts diff --git a/src/core/diff/strategies/__tests__/multi-search-replace.spec.ts b/src/core/diff/strategies/__tests__/multi-search-replace.spec.ts index 297e64fa83..07ffc419a4 100644 --- a/src/core/diff/strategies/__tests__/multi-search-replace.spec.ts +++ b/src/core/diff/strategies/__tests__/multi-search-replace.spec.ts @@ -905,6 +905,29 @@ replacement content here expect(error).toContain("Best Match Length: 0 characters") }) + it("should include a whole-lines tip when no sufficiently similar match is found", async () => { + const originalContent = "line one\nline two" + const diffContent = `test.ts +<<<<<<< SEARCH +:start_line:999 +------- +non-existent content that cannot be found in the file +======= +replacement content here +>>>>>>> REPLACE` + + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(false) + const error = + !result.success && result.failParts?.[0] + ? "error" in result.failParts[0] + ? result.failParts[0].error + : "" + : "" + expect(error).toContain("No sufficiently similar match found") + expect(error).toContain("Tip: The SEARCH block must contain complete, whole lines") + }) + it("should include scoped original content when search fails with start_line that has a low-score match", async () => { const originalContent = "function existing() {\n return 42;\n}\n" const diffContent = `test.ts diff --git a/src/core/prompts/tools/native-tools/__tests__/apply_diff.spec.ts b/src/core/prompts/tools/native-tools/__tests__/apply_diff.spec.ts new file mode 100644 index 0000000000..5df5488242 --- /dev/null +++ b/src/core/prompts/tools/native-tools/__tests__/apply_diff.spec.ts @@ -0,0 +1,40 @@ +import type OpenAI from "openai" +import { apply_diff } from "../apply_diff" + +// Helper type to access function tools +type FunctionTool = OpenAI.Chat.ChatCompletionTool & { type: "function" } + +// Helper to get function definition from tool +const getFunctionDef = (tool: OpenAI.Chat.ChatCompletionTool) => (tool as FunctionTool).function + +// OpenAI types the parameter schema as FunctionParameters (Record). +// Narrow it with runtime checks instead of assertions so the spec tracks schema changes. +function asRecord(value: unknown): Record { + if (typeof value !== "object" || value === null) { + throw new Error("expected an object value in the apply_diff parameter schema") + } + return value as Record +} + +const getDiffParameterDescription = (): string => { + const parameters = asRecord(getFunctionDef(apply_diff).parameters) + const properties = asRecord(parameters["properties"]) + const diff = asRecord(properties["diff"]) + const description = diff["description"] + if (typeof description !== "string") { + throw new Error("diff parameter must declare a string description") + } + return description +} + +describe("apply_diff diff parameter description", () => { + const diffDescription = getDiffParameterDescription() + + it("should require complete, whole lines in the SEARCH block", () => { + expect(diffDescription).toContain("The SEARCH block must contain complete, whole lines") + }) + + it("should warn that partial-line (substring) matching is not supported", () => { + expect(diffDescription).toContain("partial-line (substring) matching is not supported") + }) +}) From 77161c04392f54f4d7000f6396faa9e5eb81f2c0 Mon Sep 17 00:00:00 2001 From: myk1yt Date: Tue, 6 Oct 2026 01:47:15 +0900 Subject: [PATCH 3/6] chore: restore unrelated spec formatting altered by merge hook --- .../__tests__/base-openai-compatible-provider.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts b/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts index c057173aff..b251168461 100644 --- a/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts +++ b/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts @@ -365,7 +365,9 @@ describe("BaseOpenAiCompatibleProvider", () => { await customHandler.completePrompt("hello") - expect(mockCreate).toHaveBeenCalledWith(expect.objectContaining({ model: "some/custom-model-not-in-list" })) + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ model: "some/custom-model-not-in-list" }), + ) }) }) From 0deb3d69ac981c6f444bc25dfeea0a17954bde75 Mon Sep 17 00:00:00 2001 From: myk1yt Date: Tue, 6 Oct 2026 04:14:00 +0900 Subject: [PATCH 4/6] chore: retrigger review-state reconciliation From 64682c03e3bd619a090dcbbdd499a0c8e3e30d1a Mon Sep 17 00:00:00 2001 From: myk1yt Date: Tue, 6 Oct 2026 04:54:50 +0900 Subject: [PATCH 5/6] chore: re-run checks cancelled by infra From 3bf102bbaef728e3795fa43c6fd367254c05388b Mon Sep 17 00:00:00 2001 From: myk1yt Date: Tue, 6 Oct 2026 05:29:46 +0900 Subject: [PATCH 6/6] chore: retrigger review-state reconciliation