diff --git a/docs/tools/edit.md b/docs/tools/edit.md index 87b807780..e10700c0b 100644 --- a/docs/tools/edit.md +++ b/docs/tools/edit.md @@ -43,7 +43,7 @@ Patch language inside `input`: - Every body row is `+TEXT`; `+` alone adds a blank line. - `DEL` never has body rows. - There is no repeat row kind. To keep a line, leave it out of every range; split edits into multiple hunks when needed. - - `-` rows are invalid. Literal text beginning with `-` or `+` must be written as `+-text` / `++text`. + - `-` rows are invalid. Literal Markdown bullets or text beginning with `-` / `+` must be written as `+- item` / `++ item`. Anchors come from `read`/`grep` output. `read` emits a `[PATH#TAG]` header from the session snapshot store and lines as `LINE:TEXT`; copy the header into the edit section and copy only the line number into hunk headers. @@ -165,7 +165,7 @@ DEL 20 - Stray payload line: - `line N: payload line has no preceding hunk header. Use \`SWAP N.=M:\`, \`DEL N.=M\`, or \`INS.PRE|POST|HEAD|TAIL:\` above the body. Got "...".` - Minus row: - - ``line N: `-` rows are not valid; the range already names the lines being changed. For a literal `-` line, write `+-…`.`` + - ``line N: `-` rows are not valid; the range already names the lines being changed. For Markdown bullets or other literal `-` lines, prefix the literal row with `+`: `+- item`.`` - Empty body-bearing hunk: - `line N: \`INS\` needs at least one \`+TEXT\` body row.` - `line N: \`SWAP.BLK N:\` needs at least one \`+TEXT\` body row. To delete a block, use \`DEL.BLK N\`.` diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 7ad9ee50e..6691d1e2e 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -21,6 +21,7 @@ - Fixed RPC mode abort_bash being blocked by running bash commands by dispatching bash in the background. - Fixed task.maxConcurrency and task.maxRecursionDepth limits being bypassed by sub-spawn paths, ensuring limits are dynamically resized and respected. - Fixed the edit tool inflating session files by pruning extremely large file snapshots from tool-result details. +- Fixed edit-tool Markdown list guidance so hashline parser errors and the model-facing prompt teach `+- item` escaping instead of steering agents toward full-file `write` fallbacks. ([#4179](https://github.com/can1357/oh-my-pi/issues/4179)) - Fixed workstation OS detection rendering "Kernel: unknown" on macOS 15+. - Fixed /copy code and /copy cmd commands being treated as normal prompts instead of copying the requested blocks. - Fixed interactive bash status line not updating after directory changes (cd). diff --git a/packages/hashline/CHANGELOG.md b/packages/hashline/CHANGELOG.md index 8a9763300..1c2b32c2f 100644 --- a/packages/hashline/CHANGELOG.md +++ b/packages/hashline/CHANGELOG.md @@ -6,6 +6,7 @@ - Fixed an issue where snapshot tag collisions could cause line-anchored edits to be incorrectly applied to unrelated content. - Fixed tracking of edit anchors when earlier in-session insertions or deletions shift unchanged target lines. +- Fixed hashline edit guidance for Markdown list rows by teaching `+- item` escaping in the model prompt and minus-row parser error. ([#4179](https://github.com/can1357/oh-my-pi/issues/4179)) ## [16.2.8] - 2026-06-30 diff --git a/packages/hashline/src/messages.ts b/packages/hashline/src/messages.ts index d1759c74f..e86b0d748 100644 --- a/packages/hashline/src/messages.ts +++ b/packages/hashline/src/messages.ts @@ -54,7 +54,7 @@ export const BARE_BODY_AUTO_PIPED_WARNING = /** Unified-diff-style `-` row in a hunk body. */ export const MINUS_ROW_REJECTED = - "`-` rows are not valid; the range already names the lines being changed. For a literal `-` line, write `+-…`."; + "`-` rows are not valid; the range already names the lines being changed. For Markdown bullets or other literal `-` lines, prefix the literal row with `+`: `+- item`."; /** Replace hunk with no body. */ export const EMPTY_REPLACE = `\`SWAP N${HL_RANGE_SEP}M:\` needs at least one \`+TEXT\` body row. To delete lines, use \`DEL N${HL_RANGE_SEP}M\`.`; diff --git a/packages/hashline/src/prompt.md b/packages/hashline/src/prompt.md index 355e1f828..655401689 100644 --- a/packages/hashline/src/prompt.md +++ b/packages/hashline/src/prompt.md @@ -19,7 +19,7 @@ Single line: `SWAP N.=N:` / `DEL N`. The range is the ORIGINAL lines you touch; -Body rows appear only under a `:` header. Every body row is `+TEXT` — add a literal line `TEXT`, verbatim (leading whitespace kept); `+` alone adds a blank line. No other row kind. NEVER write `-old` or a bare/context line. To keep a line, leave it out of every range. To insert a literal line starting with `-` or `+`, prefix it: `+-x`, `++x`. +Body rows appear only under a `:` header. Every body row is `+TEXT` — add a literal line `TEXT`, verbatim (leading whitespace kept); `+` alone adds a blank line. No other row kind. NEVER write `-old` or a bare/context line. To keep a line, leave it out of every range. Literal lines starting with `-`/`+` still need the body prefix: Markdown `- item` → `+- item`, `+ item` → `++ item`. @@ -103,6 +103,14 @@ INS.TAIL: +greet("everyone") ``` +Insert Markdown bullets — the leading `+` is the body-row marker; the file receives `- task`: +``` +[PLAN.md#A1B2] +INS.POST 2: ++- task ++ - nested task +``` + Replace the whole `greet` function block — `SWAP.BLK 1:` resolves lines 1–3 (the `def` header through `print(msg)`); line 4 is a separate statement and stays: ``` [greet.py#A1B2] @@ -160,5 +168,5 @@ INS.POST 3: If you remember nothing else: 1. RE-GROUND AFTER EVERY EDIT. Every apply mints a fresh `#TAG` and renumbers — take the next edit's numbers from the edit response or a fresh `read`. Stale tag or surprise? STOP, re-`read`. 2. RANGES ARE TIGHT. Cover only lines that change; a stale wide range shreds everything it spans. Whole construct → `SWAP.BLK N`. -3. THE BODY IS THE FINAL CONTENT. Only `+TEXT` rows; never `-old`/context lines. The range does the deleting. +3. THE BODY IS THE FINAL CONTENT. Every body row starts with `+`; Markdown bullets use `+- item`, not `- item`. diff --git a/packages/hashline/test/leniency.test.ts b/packages/hashline/test/leniency.test.ts index 2136a850a..70af46634 100644 --- a/packages/hashline/test/leniency.test.ts +++ b/packages/hashline/test/leniency.test.ts @@ -166,12 +166,16 @@ describe("hashline body contracts", () => { expect(applyEdits(FILE, result.edits).text).toBe('a\n1: "one",\n2: "two",\nd\ne'); }); - it("rejects `-` body rows with a teaching error", () => { - expect(() => parsePatch("SWAP 2.=2:\n-old\n+new")).toThrow(/`-` rows are not valid/); + it("rejects `-` body rows with Markdown bullet escape guidance", () => { + expect(() => parsePatch("SWAP 2.=2:\n-old\n+new")).toThrow( + /Markdown bullets or other literal `-` lines.*`\+- item`/, + ); }); - it("allows literal text that begins with `-` or `+` when prefixed with `+`", () => { - expect(applyPatch(FILE, "SWAP 2.=2:\n+-literal\n++plus")).toBe("a\n-literal\n+plus\nc\nd\ne"); + it("allows literal Markdown bullets and plus-prefixed text when prefixed with `+`", () => { + expect(applyPatch(FILE, "SWAP 2.=2:\n+- item\n+ - nested\n++plus")).toBe( + "a\n- item\n - nested\n+plus\nc\nd\ne", + ); }); it("treats empty replace as delete and still rejects empty insert", () => {