F3 SLICE-3: pending-edit registry + sidecar section-merge update (INV-9 groundwork) (#6)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-10 09:51:19 -07:00
parent 363fd098fc
commit decdfbf31d
5 changed files with 142 additions and 2 deletions
+32
View File
@@ -0,0 +1,32 @@
import { describe, it, expect } from "vitest";
import { PendingEditRegistry } from "../src/pendingEdits";
import type { Provenance } from "../src/model";
const AGENT: Provenance = {
kind: "agent", id: "claude",
agent: { sdk: "@cline/sdk", model: "sonnet", sessionId: "run-1" },
};
describe("PendingEditRegistry (INV-9)", () => {
it("matches and consumes an exact registration", () => {
const reg = new PendingEditRegistry();
reg.register({ docPath: "d.md", start: 3, end: 7, newText: "new", provenance: AGENT, turnId: "t1" });
const hit = reg.match("d.md", { start: 3, end: 7, text: "new" });
expect(hit?.turnId).toBe("t1");
expect(reg.match("d.md", { start: 3, end: 7, text: "new" })).toBeNull();
});
it("does not match a different doc, range, or text (human typing stays human)", () => {
const reg = new PendingEditRegistry();
reg.register({ docPath: "d.md", start: 3, end: 7, newText: "new", provenance: AGENT });
expect(reg.match("other.md", { start: 3, end: 7, text: "new" })).toBeNull();
expect(reg.match("d.md", { start: 3, end: 8, text: "new" })).toBeNull();
expect(reg.match("d.md", { start: 3, end: 7, text: "neww" })).toBeNull();
});
it("unregister removes a failed application", () => {
const reg = new PendingEditRegistry();
const p = { docPath: "d.md", start: 0, end: 0, newText: "x", provenance: AGENT };
reg.register(p);
reg.unregister(p);
expect(reg.match("d.md", { start: 0, end: 0, text: "x" })).toBeNull();
});
});
+34
View File
@@ -54,4 +54,38 @@ describe("CoauthorStore", () => {
expect(second).toBe(first);
expect(first).toBe(serializeArtifact(a));
});
it("update(): two writers merge sections instead of clobbering (F3 §6.2)", () => {
const store = new CoauthorStore(root);
store.update("d.md", (a) => {
a.anchors["a_t"] = { fingerprint: { text: "t", before: "", after: "", lineHint: 0 } };
a.threads.push({ id: "t1", anchorId: "a_t", status: "open", messages: [] });
});
store.update("d.md", (a) => {
a.anchors["a_at"] = { fingerprint: { text: "x", before: "", after: "", lineHint: 0 } };
a.attributions.push({
id: "at1", anchorId: "a_at", author: { kind: "human", id: "ben" },
createdAt: "2026-06-10T00:00:00.000Z", updatedAt: "2026-06-10T00:00:00.000Z",
});
});
const loaded = store.load("d.md")!;
expect(loaded.threads).toHaveLength(1);
expect(loaded.attributions).toHaveLength(1);
expect(Object.keys(loaded.anchors).sort()).toEqual(["a_at", "a_t"]);
});
it("update() prunes anchors no longer referenced by threads or attributions", () => {
const store = new CoauthorStore(root);
store.update("d.md", (a) => {
a.anchors["a_old"] = { fingerprint: { text: "o", before: "", after: "", lineHint: 0 } };
a.attributions.push({
id: "at1", anchorId: "a_old", author: { kind: "human", id: "ben" },
createdAt: "2026-06-10T00:00:00.000Z", updatedAt: "2026-06-10T00:00:00.000Z",
});
});
store.update("d.md", (a) => {
a.attributions = [];
});
expect(Object.keys(store.load("d.md")!.anchors)).toEqual([]);
});
});