feat(hashline-edit): add autocorrect, BOM/CRLF normalization, and file creation support

Implements key features from oh-my-pi to improve agent editing success rates:

- Autocorrect v1: single-line merge expansion, wrapped line restoration,
  paired indent restoration (autocorrect-replacement-lines.ts)
- BOM/CRLF normalization: canonicalize on read, restore on write
  (file-text-canonicalization.ts)
- Pre-validate all hashes before mutation (edit-ordering.ts)
- File creation via append/prepend operations (new types + executor logic)
- Modular refactoring: split edit-operations.ts into focused modules
  (primitives, ordering, deduplication, diff, executor)
- Enhanced tool description with operation choice guide and recovery hints

All 50 tests pass. TypeScript clean. Build successful.
This commit is contained in:
YeonGyu-Kim
2026-02-22 14:13:59 +09:00
parent ea3c0dc50d
commit 3ba84d2107
14 changed files with 774 additions and 341 deletions
+46
View File
@@ -216,4 +216,50 @@ describe("createHashlineEditTool", () => {
expect(fs.existsSync(filePath)).toBe(false)
expect(result).toContain("Successfully deleted")
})
it("creates missing file with append and prepend", async () => {
//#given
const filePath = path.join(tempDir, "created.txt")
//#when
const result = await tool.execute(
{
filePath,
edits: [
{ type: "append", text: ["line2"] },
{ type: "prepend", text: ["line1"] },
],
},
createMockContext(),
)
//#then
expect(fs.existsSync(filePath)).toBe(true)
expect(fs.readFileSync(filePath, "utf-8")).toBe("line1\nline2")
expect(result).toContain("Successfully applied 2 edit(s)")
})
it("preserves BOM and CRLF through hashline_edit", async () => {
//#given
const filePath = path.join(tempDir, "crlf-bom.txt")
const bomCrLf = "\uFEFFline1\r\nline2\r\n"
fs.writeFileSync(filePath, bomCrLf)
const line2Hash = computeLineHash(2, "line2")
//#when
await tool.execute(
{
filePath,
edits: [{ type: "set_line", line: `2#${line2Hash}`, text: "line2-updated" }],
},
createMockContext(),
)
//#then
const bytes = fs.readFileSync(filePath)
expect(bytes[0]).toBe(0xef)
expect(bytes[1]).toBe(0xbb)
expect(bytes[2]).toBe(0xbf)
expect(bytes.toString("utf-8")).toBe("\uFEFFline1\r\nline2-updated\r\n")
})
})