From 30c730f2b2d2326f692c7e244401ba3b8f2cae63 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 2 Feb 2026 17:48:38 +0000 Subject: [PATCH] fix: make start_line and end_line parsing more lenient to accept brackets Some models send `:start_line:[245]` instead of `:start_line:245`. This updates the regex to accept optional brackets around line numbers, improving compatibility with models like MiniMax-2.1 and Gemini. Supported formats: - :start_line:253 - :start_line: 253 - :start_line:[253] - :start_line: [253] Fixes #11087 --- .changeset/lenient-start-line-parsing.md | 16 +++ .../__tests__/multi-search-replace.spec.ts | 110 ++++++++++++++++++ .../diff/strategies/multi-search-replace.ts | 12 +- 3 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 .changeset/lenient-start-line-parsing.md diff --git a/.changeset/lenient-start-line-parsing.md b/.changeset/lenient-start-line-parsing.md new file mode 100644 index 00000000000..afe49a66a3b --- /dev/null +++ b/.changeset/lenient-start-line-parsing.md @@ -0,0 +1,16 @@ +--- +"roo-cline": patch +--- + +fix: make start_line and end_line parsing more lenient to accept brackets + +Some models send `:start_line:[245]` or `:start_line: [245]` instead of `:start_line:245`. This change updates the regex to accept optional brackets around the line number, improving compatibility with models like MiniMax-2.1 and Gemini. + +Supported formats: + +- `:start_line:253` +- `:start_line: 253` +- `:start_line:[253]` +- `:start_line: [253]` + +Fixes #11087 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 f06f3f406fb..74e449fe6ea 100644 --- a/src/core/diff/strategies/__tests__/multi-search-replace.spec.ts +++ b/src/core/diff/strategies/__tests__/multi-search-replace.spec.ts @@ -92,6 +92,30 @@ describe("MultiSearchReplaceDiffStrategy", () => { expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) }) + it("validates line numbers with brackets (model variation)", () => { + const diff = + "<<<<<<< SEARCH\n" + + ":start_line:[10]\n" + + "-------\n" + + "content1\n" + + "=======\n" + + "new1\n" + + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + + it("validates line numbers with space and brackets (model variation)", () => { + const diff = + "<<<<<<< SEARCH\n" + + ":start_line: [10]\n" + + "-------\n" + + "content1\n" + + "=======\n" + + "new1\n" + + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + it("detects separator before search", () => { const diff = "=======\n" + "content\n" + ">>>>>>> REPLACE" const result = strategy["validateMarkerSequencing"](diff) @@ -826,6 +850,92 @@ function five() { } }) }) + + describe("bracket variations in line numbers", () => { + let strategy: MultiSearchReplaceDiffStrategy + + beforeEach(() => { + strategy = new MultiSearchReplaceDiffStrategy(1.0, 5) + }) + + it("should accept start_line with brackets around the number", async () => { + const originalContent = 'function hello() {\n console.log("hello")\n}\n' + const diffContent = + "test.ts\n" + + "<<<<<<< SEARCH\n" + + ":start_line:[1]\n" + + "-------\n" + + "function hello() {\n" + + "=======\n" + + "function helloWorld() {\n" + + ">>>>>>> REPLACE" + + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe('function helloWorld() {\n console.log("hello")\n}\n') + } + }) + + it("should accept start_line with space and brackets around the number", async () => { + const originalContent = 'function hello() {\n console.log("hello")\n}\n' + const diffContent = + "test.ts\n" + + "<<<<<<< SEARCH\n" + + ":start_line: [1]\n" + + "-------\n" + + "function hello() {\n" + + "=======\n" + + "function helloWorld() {\n" + + ">>>>>>> REPLACE" + + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe('function helloWorld() {\n console.log("hello")\n}\n') + } + }) + + it("should accept end_line with brackets around the number", async () => { + const originalContent = 'function hello() {\n console.log("hello")\n}\n' + const diffContent = + "test.ts\n" + + "<<<<<<< SEARCH\n" + + ":start_line:[1]\n" + + ":end_line:[1]\n" + + "-------\n" + + "function hello() {\n" + + "=======\n" + + "function helloWorld() {\n" + + ">>>>>>> REPLACE" + + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe('function helloWorld() {\n console.log("hello")\n}\n') + } + }) + + it("should accept end_line with space and brackets around the number", async () => { + const originalContent = 'function hello() {\n console.log("hello")\n}\n' + const diffContent = + "test.ts\n" + + "<<<<<<< SEARCH\n" + + ":start_line: [1]\n" + + ":end_line: [1]\n" + + "-------\n" + + "function hello() {\n" + + "=======\n" + + "function helloWorld() {\n" + + ">>>>>>> REPLACE" + + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe('function helloWorld() {\n console.log("hello")\n}\n') + } + }) + }) }) describe("fuzzy matching", () => { diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index f43bbee0dc9..3cbcd3f9757 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -265,11 +265,13 @@ export class MultiSearchReplaceDiffStrategy implements DiffStrategy { 2. (??\s*\n((?:\:start_line:\s*(\d+)\s*\n))?((?:\:end_line:\s*(\d+)\s*\n))?((?>>>>>> REPLACE)(?=\n|$)/g, + /(?:^|\n)(??\s*\n((?:\:start_line:\s*\[?(\d+)\]?\s*\n))?((?:\:end_line:\s*\[?(\d+)\]?\s*\n))?((?>>>>>> REPLACE)(?=\n|$)/g, ), ]