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 +4 -9
- package/package.json +1 -1
- package/prompts/replace.md +1 -5
- package/src/hashline/apply.ts +1 -2
- package/src/hashline/resolve.ts +2 -4
- package/src/replace-normalize.ts +1 -0
- package/src/replace.ts +1 -1
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
|
-
{ "
|
|
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 `{
|
|
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 `{
|
|
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
package/prompts/replace.md
CHANGED
|
@@ -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 `
|
|
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.
|
package/src/hashline/apply.ts
CHANGED
|
@@ -256,8 +256,7 @@ export function applyEdits(
|
|
|
256
256
|
);
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
|
|
260
|
-
warnings.push(...barePrefixWarnings);
|
|
259
|
+
assertNoBarePrefix(edits, lineIndex.fileLines, fileHashes);
|
|
261
260
|
warnUnicodeEsc(edits, warnings);
|
|
262
261
|
|
|
263
262
|
const orderedSpans = resSpans(
|
package/src/hashline/resolve.ts
CHANGED
|
@@ -187,8 +187,7 @@ export function assertNoBarePrefix(
|
|
|
187
187
|
edits: HEdit[],
|
|
188
188
|
fileLines: string[],
|
|
189
189
|
fileHashes: string[],
|
|
190
|
-
):
|
|
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("; ");
|
package/src/replace-normalize.ts
CHANGED
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", "
|
|
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: [...]}.`
|