pi-hashline-edit-pro 1.0.7 → 1.0.8
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 +3 -3
- package/package.json +1 -1
- package/prompts/replace-guidelines.md +2 -1
- package/src/file-kind.ts +0 -20
- package/src/fs-write.ts +0 -16
- package/src/hash-store.ts +0 -4
- package/src/hashline/parse.ts +6 -0
- package/src/replace.ts +1 -2
- package/src/validation.ts +0 -5
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ Exactly one edit per call, with `hash_range_inclusive` and `content_lines` at th
|
|
|
90
90
|
| Field | Description |
|
|
91
91
|
| --- | --- |
|
|
92
92
|
| `hash_range_inclusive` | Pair of 3-char hashes from `read` output marking the first and last line of the range to replace (inclusive). |
|
|
93
|
-
| `content_lines` | Replacement content, one string per line. Use `[]` to delete the range. |
|
|
93
|
+
| `content_lines` | Replacement content, one string per line; entries must not contain line breaks. Use `[]` to delete the range. |
|
|
94
94
|
|
|
95
95
|
Behavior:
|
|
96
96
|
|
|
@@ -155,7 +155,7 @@ Settings live in `~/.config/pi-hashline-edit-pro/config.json`, created automatic
|
|
|
155
155
|
|
|
156
156
|
| Code | Meaning |
|
|
157
157
|
| --- | --- |
|
|
158
|
-
| `[E_BAD_SHAPE]` | Request envelope or edit item has unknown, missing, or wrongly-typed fields. |
|
|
158
|
+
| `[E_BAD_SHAPE]` | Request envelope or edit item has unknown, missing, or wrongly-typed fields, or a `content_lines` entry contains a line break. |
|
|
159
159
|
| `[E_BAD_REF]` | An anchor in `hash_range_inclusive` is not a bare 3-char hash. |
|
|
160
160
|
| `[E_STALE_ANCHOR]` | An anchor does not match any line in the current file; call `read` for fresh anchors. |
|
|
161
161
|
| `[E_AMBIGUOUS_ANCHOR]` | An anchor matches multiple lines; call `read` for fresh anchors. |
|
|
@@ -183,7 +183,7 @@ The alphabet is sized for an LLM consumer: the model tokenizes rather than squin
|
|
|
183
183
|
|
|
184
184
|
- **Stale anchors fail, per line.** A hash mismatch means that line's content changed since the last `read`. The error says so and, when only one anchor of a pair is stale, shows the current lines around the still-valid anchor so the range can be re-located without a full re-read. Mismatched anchors are never silently relocated to a "close enough" line — correctness over convenience.
|
|
185
185
|
- **Autocorrection only when the intent is unambiguous**, and always visible: hash-prefix and diff-row stripping produce a warning; the boundary-duplication fix is silent because the duplicate never reaches the file. Literal content is never silently altered when the intent is ambiguous (numbered deletion rows and unified-diff lines are written verbatim).
|
|
186
|
-
- **Byte-exact preservation.** UTF-8 BOMs, CRLF, LF, and CR-only line endings, file permissions, and trailing newlines survive edits and undo.
|
|
186
|
+
- **Byte-exact preservation.** UTF-8 BOMs, CRLF, LF, and CR-only line endings, file permissions, and trailing newlines survive edits and undo; files with mixed line endings are normalized to a single line ending on edit.
|
|
187
187
|
- **Atomic and ordered writes.** Files are written via temp-file-then-rename; symlink chains are resolved so the target is updated without replacing the symlink; hard-linked files are updated in place; concurrent edits to the same underlying file serialize through a per-target mutation queue.
|
|
188
188
|
- **One edit per call.** The request shape stays `{path, hash_range_inclusive, content_lines}` from schema through validation to application; there is no batching dialect.
|
|
189
189
|
|
package/package.json
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
- `replace`: hash_range_inclusive must use only anchors from the most recent read of the same file.
|
|
2
2
|
- `replace`: content_lines is a native JSON array of strings — never a serialized JSON string. When copying a line from read output, remove its HASH│ prefix and keep the leading whitespace exactly as shown.
|
|
3
|
-
- `replace`: minimize the replaced range — anchor only the lines that actually change; for insertions use a single-line range (e.g. the line after the insertion point) instead of a whole block, so fewer unchanged lines must be reproduced byte-exact.
|
|
3
|
+
- `replace`: minimize the replaced range — anchor only the lines that actually change; for insertions use a single-line range (e.g. the line after the insertion point) instead of a whole block, so fewer unchanged lines must be reproduced byte-exact.
|
|
4
|
+
- `replace`: content_lines entries are single lines — never embed a line break inside an entry; pass each line as its own array entry.
|
package/src/file-kind.ts
CHANGED
|
@@ -39,12 +39,6 @@ function isTextType(mimeType: string): boolean {
|
|
|
39
39
|
return mimeType.startsWith("text/") || TEXT_TYPES.has(mimeType);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export type FKind =
|
|
43
|
-
| { kind: "directory" }
|
|
44
|
-
| { kind: "image"; mimeType: string }
|
|
45
|
-
| { kind: "text" }
|
|
46
|
-
| { kind: "binary"; description: string };
|
|
47
|
-
|
|
48
42
|
export type LFile =
|
|
49
43
|
| { kind: "directory" }
|
|
50
44
|
| { kind: "image"; mimeType: string }
|
|
@@ -149,17 +143,3 @@ export async function loadFileKindAndText(
|
|
|
149
143
|
await fileHandle.close();
|
|
150
144
|
}
|
|
151
145
|
}
|
|
152
|
-
|
|
153
|
-
export async function classifyFileKind(filePath: string): Promise<FKind> {
|
|
154
|
-
const loaded = await loadFileKindAndText(filePath);
|
|
155
|
-
switch (loaded.kind) {
|
|
156
|
-
case "directory":
|
|
157
|
-
return loaded;
|
|
158
|
-
case "image":
|
|
159
|
-
return loaded;
|
|
160
|
-
case "binary":
|
|
161
|
-
return loaded;
|
|
162
|
-
case "text":
|
|
163
|
-
return { kind: "text" };
|
|
164
|
-
}
|
|
165
|
-
}
|
package/src/fs-write.ts
CHANGED
|
@@ -9,8 +9,6 @@ import {
|
|
|
9
9
|
rm,
|
|
10
10
|
stat,
|
|
11
11
|
writeFile,
|
|
12
|
-
copyFile,
|
|
13
|
-
chmod,
|
|
14
12
|
} from "fs/promises";
|
|
15
13
|
import { dirname, join, parse, resolve, sep } from "path";
|
|
16
14
|
import { errCode } from "./utils";
|
|
@@ -153,20 +151,6 @@ export async function writeAtomic(
|
|
|
153
151
|
await rename(tempPath, targetPath);
|
|
154
152
|
await syncDir(dir);
|
|
155
153
|
} catch (error: unknown) {
|
|
156
|
-
if (errCode(error) === "EXDEV") {
|
|
157
|
-
try {
|
|
158
|
-
await copyFile(tempPath, targetPath);
|
|
159
|
-
if (existingStats) {
|
|
160
|
-
await chmod(targetPath, existingStats.mode & 0o7777);
|
|
161
|
-
}
|
|
162
|
-
await rm(tempPath, { force: true });
|
|
163
|
-
await syncDir(dir);
|
|
164
|
-
return;
|
|
165
|
-
} catch {
|
|
166
|
-
try { await rm(tempPath, { force: true }); } catch {}
|
|
167
|
-
throw error;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
154
|
if (process.platform === "win32" && errCode(error) === "EPERM") {
|
|
171
155
|
try {
|
|
172
156
|
await writeFile(targetPath, content, "utf-8");
|
package/src/hash-store.ts
CHANGED
|
@@ -324,10 +324,6 @@ export function upsertSnapshot(
|
|
|
324
324
|
store.stmts.upsert(path, checksum, lineCount, JSON.stringify(hashes), Date.now());
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
export function deleteSnapshot(store: HashStore, path: string): void {
|
|
328
|
-
store.stmts.deleteOne(path);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
327
|
export function upsertUndo(store: HashStore, path: string, entry: UndoRecord): void {
|
|
332
328
|
store.stmts.undoUpsert(
|
|
333
329
|
path,
|
package/src/hashline/parse.ts
CHANGED
|
@@ -46,5 +46,11 @@ export function parseText(edit: string[] | string | null): string[] {
|
|
|
46
46
|
if (typeof edit === "string") {
|
|
47
47
|
throw new Error(CONTENT_LINES_NOT_STRING_MSG);
|
|
48
48
|
}
|
|
49
|
+
const lineBreakIndex = edit.findIndex((line) => /[\r\n]/.test(line));
|
|
50
|
+
if (lineBreakIndex >= 0) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`[E_BAD_SHAPE] "content_lines" entry at index ${lineBreakIndex} contains a \\r or \\n line break. Pass each line as its own array entry.`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
49
55
|
return edit;
|
|
50
56
|
}
|
package/src/replace.ts
CHANGED
|
@@ -47,7 +47,7 @@ import { loadHashStore, type HashStore } from "./hash-store";
|
|
|
47
47
|
|
|
48
48
|
const contentLinesSchema = Type.Array(Type.String(), {
|
|
49
49
|
description:
|
|
50
|
-
"Replacement content, one string per line. Use [] to delete the range."
|
|
50
|
+
"Replacement content, one string per line; entries must not contain line breaks. Use [] to delete the range."
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
const hashRangeInclSchema = Type.Array(
|
|
@@ -78,7 +78,6 @@ export type ReplaceDetails = {
|
|
|
78
78
|
firstChangedLine?: number;
|
|
79
79
|
snapshotId?: string;
|
|
80
80
|
classification?: "noop";
|
|
81
|
-
structureOutline?: string[];
|
|
82
81
|
metrics?: RMetrics;
|
|
83
82
|
};
|
|
84
83
|
|
package/src/validation.ts
CHANGED