pi-hashline-edit-pro 0.11.1 → 0.11.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/README.md CHANGED
@@ -66,19 +66,14 @@ Replaces using the `HASH│content` anchors from `read` output to target lines p
66
66
  {
67
67
  "path": "src/main.ts",
68
68
  "edits": [
69
- { "old_range": ["ve7", "ve7"], "new_lines": [" console.log('hashline');"] }
69
+ { "hash_range_incl": ["ve7", "ve7"], "new_lines": [" console.log('hashline');"] }
70
70
  ]
71
71
  }
72
72
  ```
73
-
74
- | Field | Purpose |
75
- |---|---|
76
- | `old_range` | Inclusive line range `[start_hash, end_hash]` (required). |
77
- | `new_lines` | Replacement content (one string per line). Use `[]` to delete. |
73
+ | `hash_range_incl` | Inclusive line range `[start_hash, end_hash]` (required). |
78
74
 
79
75
  - **Request structure validation.** The request envelope (`path`, `edits`) and individual edit items are validated before any file I/O. Unknown fields, missing required fields, invalid types, and malformed anchors are rejected with `[E_BAD_SHAPE]` or `[E_BAD_REF]`.
80
- - **Legacy dialect rejected.** The native top-level `oldText`/`newText` (and `old_text`/`new_text`) dialect is rejected with `[E_LEGACY_SHAPE]`. The error message tells the model to call `read` first and send `{old_range: ["<START>", "<END>"], new_lines: [...]}`.
81
-
76
+ - **Legacy dialect rejected.** The native top-level `oldText`/`newText` (and `old_text`/`new_text`) dialect is rejected with `[E_LEGACY_SHAPE]`. The error message tells the model to call `read` first and send `{hash_range_incl: ["<START>", "<END>"], new_lines: [...]}`.
82
77
  All edits in a single call validate against the same pre-edit snapshot and apply bottom-up, so the hashes from a single `read` call remain valid across all edits in the batch.
83
78
 
84
79
  ### Chained edits
@@ -105,7 +100,7 @@ The post-edit diff (with `+`/`-` markers) is exposed to the host UI via `details
105
100
  - **Stale anchors fail.** A hash mismatch means the file has changed since the last `read`. The error tells the model to call `read()` to get fresh anchors, then copy the 3-character HASH from each line into the next replace call.
106
101
  - **No fallback relocation.** Mismatched anchors are never silently relocated to a "close enough" line. This trades convenience for correctness.
107
102
  - **Strict patch content.** If `new_lines` contains `+HASH│` display prefixes (or `-N ` diff rows), the edit is rejected with `[E_INVALID_PATCH]`. Bare `HASH│` content (the first 4 chars of a `new_lines` entry looking like 3 base64 chars + `│`) is also rejected with `[E_BARE_HASH_PREFIX]`. When the suspect's prefix happens to match a real file-line anchor, the error message flags that as strong evidence the model copied an anchor from the read output.
108
- - **Legacy dialect rejected.** The native top-level `oldText`/`newText` (and `old_text`/`new_text`) dialect is rejected with `[E_LEGACY_SHAPE]`. The error message tells the model to call `read` first and send `{old_range: ["<START>", "<END>"], new_lines: [...]}`.
103
+ - **Legacy dialect rejected.** The native top-level `oldText`/`newText` (and `old_text`/`new_text`) dialect is rejected with `[E_LEGACY_SHAPE]`. The error message tells the model to call `read` first and send `{hash_range_incl: ["<START>", "<END>"], new_lines: [...]}`.
109
104
  - **Atomic writes.** Files are written via temp-file-then-rename to avoid corruption from interrupted writes. Symlink chains are resolved so the target file is updated without replacing the symlink. Hard-linked files are updated in place to preserve the shared inode. File permissions are preserved across atomic renames.
110
105
  - **Per-file mutation queue.** Edits queue by the canonical write target, so concurrent edits through different symlink paths still serialize onto the same underlying file.
111
106
  - **Boundary duplication warnings.** When the last line of a replacement matches the next surviving line (or the first line matches the preceding one), a warning is emitted. This catches a common LLM pattern where closing delimiters like `}`, `});`, or `} else {` are accidentally duplicated. The warning includes the surviving line's post-edit hash so the model can reference it in a follow-up edit. No autocorrection is applied — the duplicate stays in the file and the model decides whether to remove it. Raw line comparison (not trimmed) avoids false positives when indentation differs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hashline-edit-pro",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "description": "Strict hashline read/replace tool for pi-coding-agent with hash-anchored edits (3-char, 18-bit, perfect hashing)",
5
5
  "main": "index.ts",
6
6
  "repository": {
@@ -7,10 +7,6 @@ How to use:
7
7
  1. Call `read` to get HASH anchors:
8
8
  ```
9
9
  read({ path: "src/main.ts" })
10
- // Returns:
11
- // MQX│const x = 1;
12
- // ZPM│const y = 2;
13
- // VRW│const z = 3;
14
10
  ```
15
11
 
16
12
  2. Copy the 3-character HASH (before `│`) into `hash_range_incl`:
@@ -92,7 +88,7 @@ Error recovery:
92
88
  - `[E_BAD_REF]` — malformed HASH. Re-read and try again.
93
89
  - `[E_BAD_OP]` — invalid operation (e.g. start line > end line).
94
90
  - `[E_BAD_SHAPE]` — malformed request or edit item (missing fields, wrong types, unknown fields).
95
- - `[E_LEGACY_SHAPE]` — old `oldText`/`newText` or `old_range` format detected. Use `{hash_range_incl, new_lines}` instead.
91
+ - `[E_LEGACY_SHAPE]` — old `oldText`/`newText` or `old_text`/`new_text` format detected. Use `{hash_range_incl, new_lines}` instead.
96
92
  - `[E_EDIT_CONFLICT]` — two edits overlap on the same line range. Make edits non-overlapping.
97
93
  - `[E_AMBIGUOUS_ANCHOR]` — hash collision. Call `read` to get fresh anchors.
98
94
  - `[E_BARE_HASH_PREFIX]` — a `new_lines` entry starts with `HASH│`. Remove the hash prefix; keep only the literal line content that appears after `│` in `read` output. `hash_range_incl` uses hashes, `new_lines` does not.
@@ -256,8 +256,7 @@ export function applyEdits(
256
256
  );
257
257
  }
258
258
 
259
- const barePrefixWarnings = assertNoBarePrefix(edits, lineIndex.fileLines, fileHashes);
260
- warnings.push(...barePrefixWarnings);
259
+ assertNoBarePrefix(edits, lineIndex.fileLines, fileHashes);
261
260
  warnUnicodeEsc(edits, warnings);
262
261
 
263
262
  const orderedSpans = resSpans(
@@ -187,8 +187,7 @@ export function assertNoBarePrefix(
187
187
  edits: HEdit[],
188
188
  fileLines: string[],
189
189
  fileHashes: string[],
190
- ): string[] {
191
- assertAligned(fileLines, fileHashes, "assertNoBarePrefix");
190
+ ): void {
192
191
  const suspects: { line: string; hash: string; editIndex: number; lineIndex: number }[] = [];
193
192
  for (let editIndex = 0; editIndex < edits.length; editIndex++) {
194
193
  const edit = edits[editIndex]!;
@@ -198,8 +197,7 @@ export function assertNoBarePrefix(
198
197
  if (match) suspects.push({ line, hash: match[1]!, editIndex, lineIndex });
199
198
  }
200
199
  }
201
- if (suspects.length === 0) return [];
202
-
200
+ if (suspects.length === 0) return;
203
201
  const locations = suspects
204
202
  .map((s) => `edit ${s.editIndex}, new_lines[${s.lineIndex}]`)
205
203
  .join("; ");
@@ -31,5 +31,6 @@ export function normReq(input: unknown): unknown {
31
31
  record.edits = coerceEditsArray(record.edits);
32
32
  }
33
33
 
34
+
34
35
  return record;
35
36
  }
package/src/replace.ts CHANGED
@@ -99,7 +99,7 @@ export function assertReq(
99
99
  throw new Error("[E_BAD_SHAPE] Edit request must be an object.");
100
100
  }
101
101
 
102
- for (const legacyKey of ["oldText", "newText", "old_text", "new_text", "start", "end", "lines", "old_range"]) {
102
+ for (const legacyKey of ["oldText", "newText", "old_text", "new_text", "old_range", "start", "end", "lines"]) {
103
103
  if (has(request, legacyKey)) {
104
104
  throw new Error(
105
105
  `[E_LEGACY_SHAPE] "${legacyKey}" is not supported. Use {hash_range_incl: ["<START>", "<END>"], new_lines: [...]}.`