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 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 three ways: a 3-character hash length with perfect hashing, an occurrence-aware discriminator that makes identical content at different positions hash to different values, and collision resolution that ensures unique anchors for every line in a file.
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 three changes that compound:
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. With perfect hashing (collision resolution), every line in a file gets a unique anchor.
16
- 2. **Make the hash occurrence-aware.** The hash for line N is `xxHash32("C{occurrence}:{content}")` where `occurrence` is the running count of that content string earlier in the file. Two `import {...}` statements at different positions now hash to different values, so the model can target a specific occurrence without resorting to `offset` + a small `limit` window. All lines including symbol-only lines like `}` use the same occurrence-based discrimination.
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
- Hashes are occurrence-aware: a discriminator prefix is mixed into the xxHash input before the line content. All lines (including symbol-only lines like `}`) use `C{occurrence}` as the discriminator, where `occurrence` is the running count of that canonical content earlier in the file. This way:
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hashline-edit-pro",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
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": {
@@ -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
- const occurrence = (counts.get(canonical) ?? 0) + 1;
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(`${DISCRIMINATOR(occurrence)}:${canonical}:R${retry}`));
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(`${DISCRIMINATOR(1)}:${canonical}`));
99
+ return hashToString(xxh32(canonical));
104
100
  }
105
101
 
106
102
  export const HASH_FORMAT = {