diff --git a/packages/coding-agent/src/edit/diff.ts b/packages/coding-agent/src/edit/diff.ts index 2defef075..143b73795 100644 --- a/packages/coding-agent/src/edit/diff.ts +++ b/packages/coding-agent/src/edit/diff.ts @@ -58,7 +58,7 @@ function formatNumberedDiffLine(prefix: "+" | "-" | " ", lineNum: number, conten * Generate a unified diff string with line numbers and context. * Returns both the diff string and the first changed line number (in the new file). */ -export function generateDiffString(oldContent: string, newContent: string, contextLines = 4): DiffResult { +export function generateDiffString(oldContent: string, newContent: string, contextLines = 2): DiffResult { const parts = Diff.diffLines(oldContent, newContent); const output: string[] = []; @@ -119,8 +119,9 @@ export function generateDiffString(oldContent: string, newContent: string, conte linesToShow = raw.slice(0, contextLimit); } + // Leading-skip placeholder is omitted: the first emitted line's + // number already conveys that earlier lines were trimmed. if (leadingSkip > 0) { - output.push(formatNumberedDiffLine(" ", oldLineNum, "...")); oldLineNum += leadingSkip; newLineNum += leadingSkip; } @@ -143,8 +144,9 @@ export function generateDiffString(oldContent: string, newContent: string, conte } } + // Trailing-skip placeholder is omitted for the same reason: the + // final emitted line's number tells the reader the file continues. if (trailingSkip > 0) { - output.push(formatNumberedDiffLine(" ", oldLineNum, "...")); oldLineNum += trailingSkip; newLineNum += trailingSkip; } diff --git a/packages/hashline/src/prompt.md b/packages/hashline/src/prompt.md index f3d5b85f8..ebafe92dc 100644 --- a/packages/hashline/src/prompt.md +++ b/packages/hashline/src/prompt.md @@ -21,13 +21,18 @@ A-B! delete A..B (or A! == A..A) - **Use `+` for a blank payload line; use `++text` to write a line starting with `+text`.** - **Inserts add ONLY the rows you list.** The file's existing newlines around the anchor stay. NEVER tack a trailing `+` blank "for spacing" — it writes a literal blank line into the file, doubling whatever is already there. - **A bare `LINE↑`/`LINE↓` with no payload still inserts ONE blank line.** Not a no-op. Omit the op if you want nothing there. -- **Go small.** Add → `↑`/`↓`; replace → `:`; delete → `!`. +- **Pick the op for your intent.** Does the anchor's existing content SURVIVE? + - Survives + new lines next to it → `↑` / `↓`. Go small: prefer `↑`/`↓` over `:` whenever you can. + - Changes in place → `:` + - Goes away → `!` + When unsure: you wanted `↓`. `:` is destructive — it deletes the anchor line. - **Line numbers are frozen references to what you have seen.** Later ops in the same hunk still use original line numbers; they do NOT shift as earlier ops apply. - **NEVER replay past your range.** Stop before B+1; extend B if needed. - **Read lines look like replace ops.** `84:content` = "make line 84 content" — and inline content is rejected. Don't echo read-style rows. +- **`LINE:` from a read is NOT `LINE:` as an op.** Read shows what's there; the op DELETES it. Want to keep what you just read? Use `↑`/`↓`, not `:`. - **NEVER fabricate file hashes.** Missing? Re-`read`. @@ -60,6 +65,11 @@ A-B! delete A..B (or A! == A..A) # WRONG — INSERT used to change a line (old line survives) 1↓ +const X = "b"; +# WRONG — REPLACE used to add a line (original is silently deleted) +# intent: keep `const X = "a";`, add `const Y = X;` on the next line +1: ++const Y = X; +# `1:` replaces line 1 — `const X = "a";` is gone, breaking `f()` which returns X. Use `1↓` to insert after. # WRONG — echoing read-style lines as context before the real op 1:const X = "a"; 1-2: