@d3ara1n/pi-hashline-edit 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d3ara1n/pi-hashline-edit",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "description": "Hashline-style file editing for pi — line-anchored edits verified by content hash, replacing oldText/newText matching",
6
6
  "keywords": [
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Override edit: hashline ops via structured `edits` (LINE#HASH anchors).
3
3
  *
4
- * Each op in `edits` references line anchors copied from read output (or from a
5
- * prior edit's "Updated anchors"). The core verifies each anchor live against
4
+ * Each op in `edits` references line anchors copied from read / grep / replace output
5
+ * (or from a prior edit's "Updated anchors"). The core verifies each anchor live against
6
6
  * the current file content — no snapshot, no global stale check: a cited line
7
7
  * that changed (or was misremembered) fails its own anchor; unchanged lines
8
8
  * elsewhere never block the edit. Legacy oldText/newText is not accepted — the
@@ -32,25 +32,41 @@ import { getState } from "./state.ts";
32
32
  /** Cap on the number of updated anchors returned inline (bounds token cost for large inserts). */
33
33
  const MAX_ANCHOR_LINES = 40;
34
34
 
35
- const anchorSchema = Type.Object({
36
- line: Type.Number({ description: "1-based line number" }),
37
- hash: Type.String({ description: "Line content hash copied from read output (the #HASH after the line number)" }),
38
- });
35
+ /** `{line, hash}` anchor pair as an op field, described per use. */
36
+ function anchorRef(description: string) {
37
+ return Type.Optional(
38
+ Type.Object(
39
+ {
40
+ line: Type.Number({ description: "1-based line number" }),
41
+ hash: Type.String({ description: "Line content hash copied from read output (the #HASH after the line number)" }),
42
+ },
43
+ { description },
44
+ ),
45
+ );
46
+ }
39
47
 
40
48
  const editOpSchema = Type.Object({
41
49
  op: Type.Union(
42
50
  [
43
- Type.Literal("replace"),
44
- Type.Literal("delete"),
45
- Type.Literal("insert_after"),
46
- Type.Literal("insert_before"),
47
- Type.Literal("append"),
48
- Type.Literal("prepend"),
51
+ Type.Literal("replace", { description: "Replace the cited line(s) with `body`." }),
52
+ Type.Literal("delete", { description: "Delete the cited line(s)." }),
53
+ Type.Literal("insert_after", {
54
+ description: "Insert `body` immediately after the anchor line — the anchor line is kept as-is; do NOT copy it into `body`.",
55
+ }),
56
+ Type.Literal("insert_before", {
57
+ description: "Insert `body` immediately before the anchor line — the anchor line is kept as-is; do NOT copy it into `body`.",
58
+ }),
59
+ Type.Literal("append", { description: "Append `body` at the end of the file." }),
60
+ Type.Literal("prepend", { description: "Prepend `body` at the start of the file." }),
49
61
  ],
50
62
  { description: "Operation kind" },
51
63
  ),
52
- anchor: Type.Optional(anchorSchema),
53
- end: Type.Optional(anchorSchema),
64
+ anchor: anchorRef(
65
+ "First line of the range (replace/delete) or the insertion point (insert_after/insert_before).",
66
+ ),
67
+ end: anchorRef(
68
+ "Last line of the range to replace/delete, inclusive: the op touches exactly [anchor..end]. Omit only for a single-line change (end == anchor). A multi-line change that forgets `end` succeeds silently with the rest of the intended range left in the file — a corrupted file, not an error.",
69
+ ),
54
70
  body: Type.Optional(Type.Array(Type.String(), { description: "New content lines (required for replace/insert/append/prepend; omit for delete)" })),
55
71
  });
56
72
 
@@ -180,12 +196,13 @@ export function makeEditOverride(cwd: string) {
180
196
  name: "edit" as const,
181
197
  label: "edit",
182
198
  description:
183
- "Edit a file via hashline ops (LINE#HASH anchors, content-verified). Each op in `edits` references line anchors from your latest read or edit result.",
199
+ "Edit a file via hashline ops (LINE#HASH anchors, content-verified). Each op in `edits` references line anchors from your latest read, grep, replace, or edit result.",
184
200
  promptSnippet: "Edit files via hashline ops (edits[] with LINE#HASH anchors from read)",
185
201
  promptGuidelines: [
186
202
  "Pass `edits`: an array of ops. Each op = {op, anchor?, end?, body?}.",
203
+ "Prefer one `edit` with multiple ops for several changes to the same file, rather than several separate `edit` calls.",
187
204
  "op ∈ replace | delete | insert_after | insert_before | append | prepend.",
188
- "anchor & end = {line, hash} copied from your latest read or edit result (the `#HASH` after each line number). replace/delete take anchor (+ optional end for a range); insert_after/insert_before take anchor; append/prepend take neither.",
205
+ "anchor & end = {line, hash} copied from your latest read, grep, replace, or edit result (the `#HASH` after each line number); grep's `-C` context lines are anchored and editable too. replace/delete take anchor (+ optional end for a range); insert_after/insert_before take anchor; append/prepend take neither.",
189
206
  "body = string[] of new content lines (required for replace/insert/append/prepend; omit for delete).",
190
207
  "A successful edit returns `Updated anchors` for the changed lines — use those (not stale line numbers) for the next edit to the same file; re-read only if you need lines outside that set.",
191
208
  ],
@@ -283,10 +283,16 @@ async function runReplace(
283
283
  }
284
284
  }
285
285
 
286
- const { diff, firstChangedLine } = generateDiffString(currentText, newText);
286
+ // generateDiffString / generateUnifiedPatch split on \n, so raw CRLF content would
287
+ // leave a trailing \r on every diff line — the TUI line-wrapper (wrapTextWithAnsi)
288
+ // then emits a spurious blank line per diff line. Normalize to LF for diff/patch
289
+ // only; the disk write above already preserved the original line endings.
290
+ const oldLf = currentText.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
291
+ const newLf = newText.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
292
+ const { diff, firstChangedLine } = generateDiffString(oldLf, newLf);
287
293
  const details: EditToolDetails = {
288
294
  diff,
289
- patch: generateUnifiedPatch(displayPath, currentText, newText),
295
+ patch: generateUnifiedPatch(displayPath, oldLf, newLf),
290
296
  firstChangedLine,
291
297
  };
292
298