F3 SLICE-3: applyAgentEdit seam + live tracking + decorations/toggle (INV-7/INV-9) (#6)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+19
-3
@@ -1,17 +1,31 @@
|
||||
/**
|
||||
* CoauthorStore — load/save the per-document `.threads/` sidecar (spec §6.4).
|
||||
* Git-native, serverless, plain pretty JSON (INV-2). vscode-free (Node fs only),
|
||||
* so it is unit-testable; the FileSystemWatcher that triggers re-anchoring on
|
||||
* external change is wired in the vscode layer (ThreadController, SLICE-4).
|
||||
* so it is unit-testable; the SHARED FileSystemWatcher that triggers
|
||||
* re-anchoring on external change is wired in the vscode layer (extension.ts),
|
||||
* with self-write suppression centralized here because two controllers
|
||||
* (ThreadController, AttributionController) co-own the sidecar (F3 §6.2).
|
||||
*/
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { emptyArtifact, serializeArtifact, type Artifact } from "./model";
|
||||
|
||||
export class CoauthorStore {
|
||||
/** sidecar paths this store just wrote, so the shared watcher can ignore them. */
|
||||
private readonly selfWrites = new Set<string>();
|
||||
|
||||
/** @param rootDir absolute workspace-folder root that owns the `.threads/` tree. */
|
||||
constructor(private readonly rootDir: string) {}
|
||||
|
||||
/**
|
||||
* Delete-and-report: true iff `fsPath` was a sidecar we just wrote via
|
||||
* `update`. The shared watcher (extension.ts) calls this to suppress
|
||||
* self-write events before fanning out to the controllers.
|
||||
*/
|
||||
consumeSelfWrite(fsPath: string): boolean {
|
||||
return this.selfWrites.delete(fsPath);
|
||||
}
|
||||
|
||||
/** `.threads/<repo-relative-docPath>.json` (spec §6.3). */
|
||||
sidecarPath(docPath: string): string {
|
||||
return path.join(this.rootDir, ".threads", `${docPath}.json`);
|
||||
@@ -34,7 +48,8 @@ export class CoauthorStore {
|
||||
* mutates only its own section against a FRESH load, so ThreadController and
|
||||
* AttributionController never clobber each other. After the mutation, anchors
|
||||
* referenced by neither threads nor attributions are pruned (proposals are
|
||||
* still empty in F3 — revisit in F4).
|
||||
* still empty in F3 — revisit in F4). `mutate` MUST be synchronous: the
|
||||
* read-modify-write (and the self-write mark) completes within this call.
|
||||
*/
|
||||
update(docPath: string, mutate: (artifact: Artifact) => void): Artifact {
|
||||
const artifact = this.load(docPath) ?? emptyArtifact(docPath);
|
||||
@@ -46,6 +61,7 @@ export class CoauthorStore {
|
||||
for (const id of Object.keys(artifact.anchors)) {
|
||||
if (!referenced.has(id)) delete artifact.anchors[id];
|
||||
}
|
||||
this.selfWrites.add(this.sidecarPath(docPath));
|
||||
this.save(docPath, artifact);
|
||||
return artifact;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user