pi-hashline-edit-pro 0.7.0 → 0.8.0
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 +5 -11
- package/package.json +1 -1
- package/src/hashline/hash.ts +3 -7
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A [pi-coding-agent](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent) extension that replaces the built-in `read` and `edit` tools with a hash-anchored line-replacing workflow. Strict semantics, no silent relocation, no autocorrection, no fuzzy fallback. 3-character content hashes over a 64-character URL-safe base64 alphabet give 18 bits of entropy per anchor, with perfect hashing (collision resolution) ensuring every line gets a unique anchor.
|
|
4
4
|
|
|
5
|
-
Fork of [pi-hashline-edit](https://github.com/RimuruW/pi-hashline-edit) by RimuruW. The strict-semantics policy is unchanged. This fork extends the upstream design in
|
|
5
|
+
Fork of [pi-hashline-edit](https://github.com/RimuruW/pi-hashline-edit) by RimuruW. The strict-semantics policy is unchanged. This fork extends the upstream design in two ways: a 3-character hash length with a 64-character alphabet for more entropy, and perfect hashing (collision resolution) that ensures every line in a file gets a unique anchor.
|
|
6
6
|
|
|
7
7
|
Every line returned by `read` carries a short content hash. Edits reference those hashes instead of raw text, so the tool can detect stale context and reject outdated changes before they reach the file.
|
|
8
8
|
|
|
@@ -10,11 +10,10 @@ Every line returned by `read` carries a short content hash. Edits reference thos
|
|
|
10
10
|
|
|
11
11
|
The original uses 2-character hashes of a 16-character alphabet, with the hash being a pure function of line content. That's 8 bits / 256 buckets, and two byte-identical lines (e.g. repeated `import` statements, repeated `}`) always share a hash because the hash is `xxHash32(content)`.
|
|
12
12
|
|
|
13
|
-
This fork makes
|
|
13
|
+
This fork makes two changes that compound:
|
|
14
14
|
|
|
15
|
-
1. **Bump hash length to 3 characters** of the 64-char URL-safe base64 alphabet. That gives 18 bits / 262,144 buckets
|
|
16
|
-
2. **
|
|
17
|
-
3. **Perfect hashing (collision resolution).** When computing hashes for a file, if a line's base hash collides with an already-assigned hash, the hash is incremented (using a retry counter in the discriminator: `C{occurrence}:R{retry}`) until a unique hash is found. This ensures every line in a file gets a unique anchor, even with the shorter 3-character hash space.
|
|
15
|
+
1. **Bump hash length to 3 characters** of the 64-char URL-safe base64 alphabet. That gives 18 bits / 262,144 buckets, up from 8 bits / 256 in the upstream.
|
|
16
|
+
2. **Perfect hashing (collision resolution).** When computing hashes for a file, if a line's base hash collides with an already-assigned hash, the hash is incremented (using a retry counter: `R{retry}`) until a unique hash is found. This ensures every line in a file gets a unique anchor, even with the shorter 3-character hash space. Two byte-identical lines (e.g. repeated `import` statements, repeated `}`) get different hashes automatically.
|
|
18
17
|
|
|
19
18
|
## Installation
|
|
20
19
|
|
|
@@ -117,12 +116,7 @@ Hashes are computed with [xxhash-wasm](https://github.com/jungomi/xxhash-wasm) (
|
|
|
117
116
|
|
|
118
117
|
The alphabet is sized for an LLM consumer. The model tokenizes, it doesn't squint at pixel glyphs, so the human-readability heuristics used by smaller hand-curated alphabets (no G/L/I/O because they look like digits, no vowels so the hash doesn't accidentally spell a word, no hex digits so it can't be confused with `0xFF`) don't apply. The full 64 chars give maximum entropy per character, with case and digits included.
|
|
119
118
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
- `}` on line 5 and `}` on line 17 hash differently (1st vs 2nd occurrence of `}`).
|
|
123
|
-
- `import { foo } from 'bar';` on line 3 and the same string on line 47 hash differently (1st vs 2nd occurrence).
|
|
124
|
-
|
|
125
|
-
**Perfect hashing (collision resolution):** When computing hashes for a file, if a line's base hash collides with an already-assigned hash, the hash is incremented (using a retry counter in the discriminator: `C{occurrence}:R{retry}`) until a unique hash is found. This ensures every line in a file gets a unique anchor, even with the shorter 3-character hash space.
|
|
119
|
+
**Perfect hashing (collision resolution):** When computing hashes for a file, if a line's base hash collides with an already-assigned hash, the hash is incremented (using a retry counter: `R{retry}`) until a unique hash is found. This ensures every line in a file gets a unique anchor, even with the shorter 3-character hash space. Two byte-identical lines (e.g. repeated `}` or repeated `import` statements) get different hashes automatically.
|
|
126
120
|
|
|
127
121
|
The runtime always precomputes the full per-line hash array for a file via `computeLineHashes(content)`, then looks up by line number during validation and during `read` / `replace` response formatting. There is no per-line recomputation that could disagree with what the model saw in its last read.
|
|
128
122
|
|
package/package.json
CHANGED
package/src/hashline/hash.ts
CHANGED
|
@@ -71,7 +71,6 @@ function xxh32(input: string, seed = 0): number {
|
|
|
71
71
|
return getHasher().h32(input, seed) >>> 0;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const DISCRIMINATOR = (occurrence: number): string => `C${occurrence}`;
|
|
75
74
|
|
|
76
75
|
function canonicalizeLine(line: string): string {
|
|
77
76
|
return line.replace(/\r/g, "").trimEnd();
|
|
@@ -80,17 +79,14 @@ function canonicalizeLine(line: string): string {
|
|
|
80
79
|
export function computeLineHashes(content: string): string[] {
|
|
81
80
|
const lines = content.split("\n");
|
|
82
81
|
const hashes = new Array<string>(lines.length);
|
|
83
|
-
const counts = new Map<string, number>();
|
|
84
82
|
const assigned = new Set<string>();
|
|
85
83
|
for (let i = 0; i < lines.length; i++) {
|
|
86
84
|
const canonical = canonicalizeLine(lines[i]!);
|
|
87
|
-
|
|
88
|
-
counts.set(canonical, occurrence);
|
|
89
|
-
let hash = hashToString(xxh32(`${DISCRIMINATOR(occurrence)}:${canonical}`));
|
|
85
|
+
let hash = hashToString(xxh32(canonical));
|
|
90
86
|
let retry = 0;
|
|
91
87
|
while (assigned.has(hash)) {
|
|
92
88
|
retry++;
|
|
93
|
-
hash = hashToString(xxh32(`${
|
|
89
|
+
hash = hashToString(xxh32(`${canonical}:R${retry}`));
|
|
94
90
|
}
|
|
95
91
|
assigned.add(hash);
|
|
96
92
|
hashes[i] = hash;
|
|
@@ -100,7 +96,7 @@ export function computeLineHashes(content: string): string[] {
|
|
|
100
96
|
|
|
101
97
|
export function computeLineHash(idx: number, line: string): string {
|
|
102
98
|
const canonical = canonicalizeLine(line);
|
|
103
|
-
return hashToString(xxh32(
|
|
99
|
+
return hashToString(xxh32(canonical));
|
|
104
100
|
}
|
|
105
101
|
|
|
106
102
|
export const HASH_FORMAT = {
|