pi-hashline-edit-pro 2.5.1 → 2.5.2
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 -2
- package/package.json +1 -1
- package/src/file-kind.ts +4 -3
- package/src/hash-store.ts +29 -30
- package/src/served.ts +4 -12
- package/src/utils.ts +1 -3
- package/src/validation.ts +5 -0
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# pi-hashline-edit-pro
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/pi-hashline-edit-pro) [](https://www.npmjs.com/package/pi-hashline-edit-pro)
|
|
4
|
+
|
|
3
5
|
Hash-anchored `read` and `replace` tools for [pi-coding-agent](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent). Every line of a file gets a unique 3-character hash, and you edit by hash. No line numbers, no fuzzy matching, no edits landing on the wrong line.
|
|
4
6
|
|
|
5
7
|
Fork of [pi-hashline-edit](https://github.com/RimuruW/pi-hashline-edit) by RimuruW, extended with 3-character hashes and collision resolution.
|
|
@@ -68,7 +70,7 @@ Edge cases:
|
|
|
68
70
|
- UTF-16 and UTF-32 text (detected via BOM) is rejected, since editing it would corrupt the file.
|
|
69
71
|
- Empty files come back as a single empty-line hash (`HASH│`); use `replace` on that hash to insert content.
|
|
70
72
|
- BOMs are stripped for display. Non-UTF-8 bytes are shown as `U+FFFD`; editing such a file rewrites it as UTF-8, with a warning.
|
|
71
|
-
- Files over 238,328 lines are rejected with `[E_FILE_TOO_LARGE]`.
|
|
73
|
+
- Files over 238,328 lines or 100MB are rejected with `[E_FILE_TOO_LARGE]`.
|
|
72
74
|
|
|
73
75
|
## The replace tool
|
|
74
76
|
|
|
@@ -169,7 +171,7 @@ A no-op replace never changes the file, so anchors remain valid. On first run af
|
|
|
169
171
|
| `[E_UNDO_STALE]` | `undo_last_replace` refused: the file was modified or deleted after the last replace. |
|
|
170
172
|
| `[E_UNDO_UNAVAILABLE]` | Undo history could not be persisted to the hash store; the `replace` was refused and the file was left unchanged. |
|
|
171
173
|
| `[E_RANGE_STALE]` | A line in the replaced range no longer matches what was last shown (the file changed on disk, or the line was never shown). The edit was refused; the current range is returned with fresh anchors. |
|
|
172
|
-
| `[E_FILE_TOO_LARGE]` | The file exceeds the 238,328-line hashline limit. |
|
|
174
|
+
| `[E_FILE_TOO_LARGE]` | The file exceeds the 238,328-line hashline limit or the 100MB size limit. |
|
|
173
175
|
|
|
174
176
|
## Troubleshooting
|
|
175
177
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-hashline-edit-pro",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Hash-anchored read/replace/undo tools for pi-coding-agent. Every line gets a unique 3-char hash (A-Za-z0-9) that stays stable across edits; stale or ambiguous anchors are rejected, never fuzzy-matched. Undo persists across restarts.",
|
|
6
6
|
"main": "index.ts",
|
package/src/file-kind.ts
CHANGED
|
@@ -54,7 +54,8 @@ export type LFile =
|
|
|
54
54
|
| { kind: "directory" }
|
|
55
55
|
| { kind: "image"; mimeType: string }
|
|
56
56
|
| { kind: "text"; text: string; hadUtf8DecodeErrors?: true }
|
|
57
|
-
| { kind: "binary"; description: string }
|
|
57
|
+
| { kind: "binary"; description: string }
|
|
58
|
+
| { kind: "too_large"; description: string };
|
|
58
59
|
|
|
59
60
|
|
|
60
61
|
export interface LoadFileOptions {
|
|
@@ -78,8 +79,8 @@ export async function loadFileKindAndText(
|
|
|
78
79
|
}
|
|
79
80
|
if (pathStat.size > MAX_BYTES) {
|
|
80
81
|
return {
|
|
81
|
-
kind: "
|
|
82
|
-
description: `
|
|
82
|
+
kind: "too_large",
|
|
83
|
+
description: `exceeds the ${MAX_BYTES / (1024 * 1024)}MB size limit`,
|
|
83
84
|
};
|
|
84
85
|
}
|
|
85
86
|
|
package/src/hash-store.ts
CHANGED
|
@@ -49,6 +49,21 @@ export function isValidHashList(value: unknown): value is string[] {
|
|
|
49
49
|
return true;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export function parseHashList(raw: string, onInvalid: () => void): string[] | undefined {
|
|
53
|
+
let parsed: unknown;
|
|
54
|
+
try {
|
|
55
|
+
parsed = JSON.parse(raw);
|
|
56
|
+
} catch {
|
|
57
|
+
onInvalid();
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
if (!isValidHashList(parsed)) {
|
|
61
|
+
onInvalid();
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
|
|
52
67
|
function isValidSnapshot(value: unknown): value is LegacySnapshot {
|
|
53
68
|
if (typeof value !== "object" || value === null) return false;
|
|
54
69
|
const v = value as Record<string, unknown>;
|
|
@@ -418,21 +433,13 @@ export function getSnapshot(
|
|
|
418
433
|
}
|
|
419
434
|
const row = store.stmts.get(path, checksum, lineCount);
|
|
420
435
|
if (!row) return undefined;
|
|
421
|
-
|
|
422
|
-
try {
|
|
423
|
-
parsed = JSON.parse(row.hashes as string);
|
|
424
|
-
} catch {
|
|
436
|
+
const parsed = parseHashList(row.hashes as string, () => {
|
|
425
437
|
if (deleteCorrupt) store.stmts.deleteOne(path);
|
|
426
438
|
snapshotCache.delete(path);
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
return parsed;
|
|
432
|
-
}
|
|
433
|
-
if (deleteCorrupt) store.stmts.deleteOne(path);
|
|
434
|
-
snapshotCache.delete(path);
|
|
435
|
-
return undefined;
|
|
439
|
+
});
|
|
440
|
+
if (!parsed) return undefined;
|
|
441
|
+
cacheSnapshot(path, checksum, lineCount, parsed);
|
|
442
|
+
return parsed;
|
|
436
443
|
}
|
|
437
444
|
|
|
438
445
|
export function upsertSnapshot(
|
|
@@ -461,23 +468,15 @@ export function upsertUndo(store: HashStore, path: string, entry: UndoRecord): v
|
|
|
461
468
|
export function getUndoEntry(store: HashStore, path: string): UndoRecord | undefined {
|
|
462
469
|
const row = store.stmts.undoGet(path);
|
|
463
470
|
if (!row) return undefined;
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
ending: row.ending as string,
|
|
474
|
-
hashes: parsed as string[],
|
|
475
|
-
resultContent: row.result_content as string,
|
|
476
|
-
};
|
|
477
|
-
} catch {
|
|
478
|
-
store.stmts.undoDelete(path);
|
|
479
|
-
return undefined;
|
|
480
|
-
}
|
|
471
|
+
const parsed = parseHashList(row.hashes as string, () => store.stmts.undoDelete(path));
|
|
472
|
+
if (!parsed) return undefined;
|
|
473
|
+
return {
|
|
474
|
+
content: row.content as string,
|
|
475
|
+
bom: row.bom as string,
|
|
476
|
+
ending: row.ending as string,
|
|
477
|
+
hashes: parsed,
|
|
478
|
+
resultContent: row.result_content as string,
|
|
479
|
+
};
|
|
481
480
|
}
|
|
482
481
|
|
|
483
482
|
export function deleteUndo(store: HashStore, path: string): void {
|
package/src/served.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HashStore } from "./hash-store";
|
|
2
|
-
import {
|
|
2
|
+
import { parseHashList } from "./hash-store";
|
|
3
3
|
import { HASH_CLASS } from "./hashline/alphabet";
|
|
4
4
|
|
|
5
5
|
const SERVED_DIFF_ROW_RE = new RegExp(`^[+ ](${HASH_CLASS})│`);
|
|
@@ -16,17 +16,9 @@ export function servedHashesFromDiff(diff: string): string[] {
|
|
|
16
16
|
export function getServed(store: HashStore, path: string): Set<string> | undefined {
|
|
17
17
|
const row = store.stmts.servedGet(path);
|
|
18
18
|
if (!row) return undefined;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
store.stmts.servedDelete(path);
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
return new Set(parsed);
|
|
26
|
-
} catch {
|
|
27
|
-
store.stmts.servedDelete(path);
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
19
|
+
const parsed = parseHashList(row.hashes as string, () => store.stmts.servedDelete(path));
|
|
20
|
+
if (!parsed) return undefined;
|
|
21
|
+
return new Set(parsed);
|
|
30
22
|
}
|
|
31
23
|
|
|
32
24
|
export function recordServed(store: HashStore, path: string, hashes: string[]): void {
|
package/src/utils.ts
CHANGED
|
@@ -16,9 +16,7 @@ export function splitLines(text: string): string[] {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function visLines(text: string): string[] {
|
|
19
|
-
|
|
20
|
-
const lines = text.split("\n");
|
|
21
|
-
return text.endsWith("\n") ? lines.slice(0, -1) : lines;
|
|
19
|
+
return text.length === 0 ? [] : splitLines(text);
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
|
package/src/validation.ts
CHANGED
|
@@ -36,5 +36,10 @@ export function valKind(file: LFile, path: string): asserts file is { kind: "tex
|
|
|
36
36
|
if (file.kind === "image") {
|
|
37
37
|
throw new Error(`[E_NOT_TEXT] Path is an image file: ${path}. Hashline edit only supports text files.`);
|
|
38
38
|
}
|
|
39
|
+
if (file.kind === "too_large") {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`[E_FILE_TOO_LARGE] File is too large: ${path} (${file.description}). Hashline editing targets source-sized files; for very large files use write or a non-line-based approach.`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
39
44
|
}
|
|
40
45
|
|