diff --git a/packages/runtime/src/tools/built-in/apply-patch.ts b/packages/runtime/src/tools/built-in/apply-patch.ts index df22d5cd..70d47e94 100644 --- a/packages/runtime/src/tools/built-in/apply-patch.ts +++ b/packages/runtime/src/tools/built-in/apply-patch.ts @@ -258,6 +258,9 @@ function _applyHunks( lineIndex = position + hunk.oldLines.length; } + // Insertion-only hunks target the end of the file regardless of their order + // in the patch, so apply every replacement from the bottom up by position. + replacements.sort((left, right) => left[0] - right[0]); for (const [position, oldLength, newLines] of replacements.reverse()) { lines.splice(position, oldLength, ...newLines); } diff --git a/packages/runtime/tests/tools/built-in/apply-patch.test.ts b/packages/runtime/tests/tools/built-in/apply-patch.test.ts index 42bbf641..ac39a975 100644 --- a/packages/runtime/tests/tools/built-in/apply-patch.test.ts +++ b/packages/runtime/tests/tools/built-in/apply-patch.test.ts @@ -163,6 +163,25 @@ describe("apply_patch built-in", () => { expect(await fs.readFile(filePath, "utf8")).toBe("first\nsecond\n"); }); + test("keeps insertion-only hunks at the end when a later hunk grows the file", async () => { + const workspace = await createWorkspace(); + const filePath = path.join(workspace, "example.txt"); + const original = "alpha\nbeta\ngamma\ndelta\n"; + const expected = "alpha\nbeta one\nbeta two\ngamma\ndelta\nomega\n"; + const insertion = "@@\n+omega"; + const edit = "@@\n alpha\n-beta\n+beta one\n+beta two\n gamma"; + const patch = (...hunks: string[]) => + `*** Begin Patch\n*** Update File: example.txt\n${hunks.join("\n")}\n*** End Patch`; + + await fs.writeFile(filePath, original, "utf8"); + await applyPatch(patch(edit, insertion), workspace); + expect(await fs.readFile(filePath, "utf8")).toBe(expected); + + await fs.writeFile(filePath, original, "utf8"); + await applyPatch(patch(insertion, edit), workspace); + expect(await fs.readFile(filePath, "utf8")).toBe(expected); + }); + test("can remove the complete contents of a file", async () => { const workspace = await createWorkspace(); const filePath = path.join(workspace, "example.txt");