From 29aefbca4a6e29aae9f64ed949522ec6dd177798 Mon Sep 17 00:00:00 2001 From: can1357 Date: Sat, 18 Jul 2026 22:05:14 +0200 Subject: [PATCH] test: restored read-tool context-expansion contract tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The exact-bounds rewrite matched reverted PR #5812; the ±context expansion (1 leading + 3 trailing line) around explicit selectors is intended behavior so edit anchors at range boundaries stay fresh. --- packages/coding-agent/test/tools.test.ts | 67 ++++++++++++++++-------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/packages/coding-agent/test/tools.test.ts b/packages/coding-agent/test/tools.test.ts index 7e68fb21f..e6f48e0d7 100644 --- a/packages/coding-agent/test/tools.test.ts +++ b/packages/coding-agent/test/tools.test.ts @@ -474,7 +474,7 @@ describe("Coding Agent Tools", () => { expect(output).toMatch(/\[Showing lines 1-\d+ of 1000 \(\d+(\.\d+)?\s*KB limit\)\. Use :\d+ to continue\]/); }); - it("should handle offset parameter (exact bounds)", async () => { + it("should handle offset parameter (with leading context expansion)", async () => { const testFile = path.join(testDir, "offset-test.txt"); const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`); fs.writeFileSync(testFile, lines.join("\n")); @@ -482,16 +482,18 @@ describe("Coding Agent Tools", () => { const result = await readTool.execute("test-call-5", { path: `${testFile}:L51` }); const output = getTextOutput(result); - // Explicit selectors are honored exactly (#5802): the read starts at - // line 51 with no leading context lines. - expect(output).not.toContain("Line 50"); + // Read tool widens by 1 leading + 3 trailing unanchored context lines + // so anchors at the boundary stay fresh. Line 50 is the single leading + // context line; lines 47..49 are NOT included. + expect(output).not.toContain("Line 49"); + expect(output).toContain("Line 50"); expect(output).toContain("Line 51"); expect(output).toContain("Line 100"); // No truncation message since file fits within limits expect(output).not.toContain("Use :"); }); - it("should handle limit parameter (exact bounds)", async () => { + it("should handle limit parameter (with trailing context expansion)", async () => { const testFile = path.join(testDir, "limit-test.txt"); const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`); fs.writeFileSync(testFile, lines.join("\n")); @@ -499,32 +501,53 @@ describe("Coding Agent Tools", () => { const result = await readTool.execute("test-call-6", { path: `${testFile}:L1-L10` }); const output = getTextOutput(result); - // Explicit ranges return exactly the requested lines (#5802). + // Trailing context: lines 11..13 included so an edit anchored at + // the boundary stays fresh. expect(output).toContain("Line 1"); expect(output).toContain("Line 10"); - expect(output).not.toContain("Line 11"); - expect(output).toContain("[Showing lines 1-10 of 100. Use :11 to continue]"); + expect(output).toContain("Line 13"); + expect(output).not.toContain("Line 14"); + expect(output).toContain("[Showing lines 1-13 of 100. Use :14 to continue]"); }); - it("honors exact bounds when the range does not start at line 1", async () => { + it("does not expand on the leading side when offset is 1 or unspecified", async () => { + const testFile = path.join(testDir, "no-leading.txt"); + const lines = Array.from({ length: 50 }, (_, i) => `Line ${i + 1}`); + fs.writeFileSync(testFile, lines.join("\n")); + + // :L1-L5 has offset=1 → no leading context (already at the top). + // Trailing context still applies. + const result = await readTool.execute("test-no-leading", { + path: `${testFile}:L1-L5`, + }); + const output = getTextOutput(result); + + expect(output).toContain("Line 1"); + expect(output).toContain("Line 5"); + expect(output).toContain("Line 8"); + expect(output).not.toContain("Line 9"); + expect(output).toContain("[Showing lines 1-8 of 50. Use :9 to continue]"); + }); + + it("clamps leading context at file start without errors", async () => { const testFile = path.join(testDir, "leading-clamp.txt"); const lines = Array.from({ length: 50 }, (_, i) => `Line ${i + 1}`); fs.writeFileSync(testFile, lines.join("\n")); - // :L2-L5 returns exactly lines 2..5 — no leading or trailing - // context expansion (#5802). + // :L2-L5: offset=2 → expand by min(1, 1) = 1 leading line. const result = await readTool.execute("test-leading-clamp", { path: `${testFile}:L2-L5`, }); const output = getTextOutput(result); - expect(output).not.toContain("Line 1\n"); + expect(output).toContain("Line 1"); expect(output).toContain("Line 2"); expect(output).toContain("Line 5"); - expect(output).not.toContain("Line 6"); + expect(output).toContain("Line 8"); + expect(output).not.toContain("Line 9"); }); - it("should handle offset + limit together (exact bounds)", async () => { + it("should handle offset + limit together (1 leading + 3 trailing)", async () => { const testFile = path.join(testDir, "offset-limit-test.txt"); const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`); fs.writeFileSync(testFile, lines.join("\n")); @@ -534,12 +557,14 @@ describe("Coding Agent Tools", () => { }); const output = getTextOutput(result); - // Both endpoints are honored exactly (#5802). - expect(output).not.toContain("Line 40"); + // Both endpoints are user-constrained: 1 leading + 3 trailing. + expect(output).not.toContain("Line 39"); + expect(output).toContain("Line 40"); expect(output).toContain("Line 41"); expect(output).toContain("Line 60"); - expect(output).not.toContain("Line 61"); - expect(output).toContain("[Showing lines 41-60 of 100. Use :61 to continue]"); + expect(output).toContain("Line 63"); + expect(output).not.toContain("Line 64"); + expect(output).toContain("[Showing lines 40-63 of 100. Use :64 to continue]"); }); it("should show error when offset is beyond file length", async () => { @@ -757,10 +782,8 @@ describe("Coding Agent Tools", () => { expect(output).toContain("# Archive README"); expect(output).toContain("Line 2"); - // Explicit ranges are honored exactly (#5802): Line 3 stays behind - // the continuation hint. - expect(output).not.toContain("Line 3"); - expect(output).toContain("more lines in archive entry. Use :3 to continue"); + // Trailing context (±3) keeps Line 3 visible when present. + expect(output).toContain("Line 3"); }); }