pi-hashline-edit-pro 4.2.7 → 4.2.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/package.json +1 -1
- package/src/anchor-registry.ts +15 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-hashline-edit-pro",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Hash-anchored read/replace/insert/grep tools for pi-coding-agent. Every line gets a unique 4-char tokenizer-friendly anchor 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/anchor-registry.ts
CHANGED
|
@@ -152,13 +152,14 @@ interface SessionState {
|
|
|
152
152
|
served: Map<string, Map<string, string>>;
|
|
153
153
|
everMinted: AnchorMintedSet;
|
|
154
154
|
probe: number;
|
|
155
|
+
allocatedChecksum: Map<string, string>;
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
const SIDECAR_SUFFIX = ".registry.jsonl";
|
|
158
159
|
const SIDECAR_COMPACT_LINES = 5000;
|
|
159
160
|
const SIDECAR_COMPACT_BYTES = 1024 * 1024;
|
|
160
161
|
const SIDECAR_COMPACT_CHUNK = 5000;
|
|
161
|
-
const SIDECAR_HEADER_BYTES = 64 * 1024;
|
|
162
|
+
export const SIDECAR_HEADER_BYTES = 64 * 1024;
|
|
162
163
|
const SIDECAR_HEADER_CHUNK = 4096;
|
|
163
164
|
let currentKey: string | undefined;
|
|
164
165
|
const sidecarByKey = new Map<string, string>();
|
|
@@ -174,7 +175,7 @@ function newSessionState(seed?: string): SessionState {
|
|
|
174
175
|
probe = (probe * 256 + byte) % ANCHOR_COUNT;
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
|
-
return { owned: new Map(), served: new Map(), everMinted: new Set(), probe };
|
|
178
|
+
return { owned: new Map(), served: new Map(), everMinted: new Set(), probe, allocatedChecksum: new Map() };
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
function seedServedFromOwned(state: SessionState): void {
|
|
@@ -476,6 +477,7 @@ export function clearRegistry(): void {
|
|
|
476
477
|
if (!state) return;
|
|
477
478
|
state.owned.clear();
|
|
478
479
|
state.served.clear();
|
|
480
|
+
state.allocatedChecksum.clear();
|
|
479
481
|
appendEvent({ kind: "clear" });
|
|
480
482
|
}
|
|
481
483
|
|
|
@@ -532,6 +534,7 @@ export function shadowStateFrom(state: SessionState): SessionState {
|
|
|
532
534
|
served: new Map(),
|
|
533
535
|
everMinted: new ShadowMintedSet(state.everMinted),
|
|
534
536
|
probe: state.probe,
|
|
537
|
+
allocatedChecksum: state.allocatedChecksum,
|
|
535
538
|
};
|
|
536
539
|
}
|
|
537
540
|
|
|
@@ -743,6 +746,11 @@ export function alignOwnership(
|
|
|
743
746
|
return { anchors, freed: freed.map((f) => f.anchor), minted: minted.map((m) => m.anchor) };
|
|
744
747
|
}
|
|
745
748
|
|
|
749
|
+
function snapshotMatchesAllocation(state: SessionState | undefined, path: string, checksum: string): boolean {
|
|
750
|
+
const allocated = state?.allocatedChecksum.get(path);
|
|
751
|
+
return allocated === undefined || allocated === checksum;
|
|
752
|
+
}
|
|
753
|
+
|
|
746
754
|
export async function allocateFileAnchors(
|
|
747
755
|
store: HashStore,
|
|
748
756
|
path: string,
|
|
@@ -754,12 +762,14 @@ export async function allocateFileAnchors(
|
|
|
754
762
|
},
|
|
755
763
|
): Promise<string[]> {
|
|
756
764
|
ensureRegistry();
|
|
765
|
+
const registry = current();
|
|
757
766
|
const shadow = options?.shadow === true;
|
|
758
767
|
const lines = splitLines(content);
|
|
759
768
|
const checksums = lines.map((line) => contentChecksum(hashSource(line)));
|
|
760
769
|
if (options?.previous?.spans) {
|
|
761
770
|
const prevChecksums = splitLines(options.previous.content).map((line) => contentChecksum(hashSource(line)));
|
|
762
771
|
const aligned = alignOwnershipWithSpans(path, options.previous.hashes, prevChecksums, checksums, options.previous.spans, { shadow });
|
|
772
|
+
if (!shadow && registry) registry.allocatedChecksum.set(path, contentChecksum(content));
|
|
763
773
|
if (!shadow && options.persist !== false) {
|
|
764
774
|
persistSnapshot(store, path, content, aligned.anchors, checksums);
|
|
765
775
|
}
|
|
@@ -772,7 +782,7 @@ export async function allocateFileAnchors(
|
|
|
772
782
|
prevChecksums = splitLines(options.previous.content).map((line) => contentChecksum(hashSource(line)));
|
|
773
783
|
} else {
|
|
774
784
|
const previousState = getAllocatedState(store, path, !shadow);
|
|
775
|
-
if (previousState) {
|
|
785
|
+
if (previousState && snapshotMatchesAllocation(registry, path, previousState.contentChecksum)) {
|
|
776
786
|
prevAnchors = previousState.anchors;
|
|
777
787
|
prevChecksums = previousState.checksums;
|
|
778
788
|
if (!prevChecksums && previousState.contentChecksum === contentChecksum(content)) {
|
|
@@ -800,6 +810,7 @@ export async function allocateFileAnchors(
|
|
|
800
810
|
}
|
|
801
811
|
return { anchors, freed: [], minted: anchors };
|
|
802
812
|
})();
|
|
813
|
+
if (!shadow && registry) registry.allocatedChecksum.set(path, contentChecksum(content));
|
|
803
814
|
if (!shadow && options?.persist !== false) {
|
|
804
815
|
persistSnapshot(store, path, content, aligned.anchors, checksums);
|
|
805
816
|
}
|
|
@@ -848,7 +859,7 @@ export async function readSidecarHeader(sidecar: string): Promise<string> {
|
|
|
848
859
|
const newline = text.indexOf("\n");
|
|
849
860
|
if (newline >= 0) return text.slice(0, newline);
|
|
850
861
|
}
|
|
851
|
-
return buffer.subarray(0, readBytes).toString("utf-8");
|
|
862
|
+
return readBytes === buffer.length ? "" : buffer.subarray(0, readBytes).toString("utf-8");
|
|
852
863
|
} finally {
|
|
853
864
|
await handle.close();
|
|
854
865
|
}
|