From fecc19e4e9b60774da44b4c3ea22285faf79ba92 Mon Sep 17 00:00:00 2001 From: "robopipi[bot]" <336283572+robopipi[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 09:07:23 +0100 Subject: [PATCH] fix(coding-agent): keep grep context lines aligned with ripgrep when a file has a lone CR (#11465) When `context` is set, the grep tool reads the file again to print the lines around each match. It turned CRLF into LF and also turned every lone CR into LF before splitting the text into lines. ripgrep, which supplies the match line numbers, counts lines only at LF. A lone CR therefore added an extra line on pi's side, so every later line was off by one. For example, in a file containing `before\rprogress\nneedle\nafter`, a search for `needle` with `context: 1` showed `progress` as the line-2 match and left out `after`. The fix keeps the CRLF normalization and splits only on LF, so pi numbers lines the same way ripgrep does. Displayed lines already have `\r` removed, so the line before the match now shows as `beforeprogress`. **Testing** - Added a test in `packages/coding-agent/test/tools.test.ts` for a file with a lone CR searched with `context: 1`. It fails without the fix and passes with it. - `test/tools.test.ts`: all 87 tests pass. - `npm run check` passes. - Reproduction from the issue. Before the fix: ``` f.txt-1- before f.txt:2: progress f.txt-3- needle ``` After the fix: ``` f.txt-1- beforeprogress f.txt:2: needle f.txt-3- after ``` Fixes #11398 Requested by @badlogic Co-authored-by: triagent --- packages/coding-agent/src/core/tools/grep.ts | 2 +- packages/coding-agent/test/tools.test.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/coding-agent/src/core/tools/grep.ts b/packages/coding-agent/src/core/tools/grep.ts index a9562c69f..77d830cd1 100644 --- a/packages/coding-agent/src/core/tools/grep.ts +++ b/packages/coding-agent/src/core/tools/grep.ts @@ -150,7 +150,7 @@ export function createGrepToolDefinition( if (!lines) { try { const content = await ops.readFile(filePath); - lines = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n"); + lines = content.replace(/\r\n/g, "\n").split("\n"); } catch { lines = []; } diff --git a/packages/coding-agent/test/tools.test.ts b/packages/coding-agent/test/tools.test.ts index 91432623f..a096c2f91 100644 --- a/packages/coding-agent/test/tools.test.ts +++ b/packages/coding-agent/test/tools.test.ts @@ -908,6 +908,23 @@ describe("Coding Agent Tools", () => { expect(output).not.toContain("match two"); }); + // https://github.com/earendil-works/pi/issues/11398 + it("should keep context line numbers aligned with ripgrep when a file contains a lone CR", async () => { + const testFile = join(testDir, "lone-cr.txt"); + writeFileSync(testFile, "before\rprogress\nneedle\nafter"); + + const result = await grepTool.execute("test-call-lone-cr", { + pattern: "needle", + path: testFile, + context: 1, + }); + + const output = getTextOutput(result); + expect(output).toContain("lone-cr.txt-1- beforeprogress"); + expect(output).toContain("lone-cr.txt:2: needle"); + expect(output).toContain("lone-cr.txt-3- after"); + }); + // https://github.com/earendil-works/pi/issues/11203 it("should ignore RIPGREP_CONFIG_PATH", async () => { const configFile = join(testDir, "rg-config");