refactor(f8): controllers depend on SidecarRouter + isAuthorable (key via keyOf)
Spec §6.4: constructor store CoauthorStore→SidecarRouter, rootDir→string|undefined; isInRoot/isTracked gate → isAuthorable(scheme); per-doc key via store.keyOf(docIdentity). Seam + artifact logic unchanged. currentAuthor omits git email when no root (fail-open). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,14 +10,14 @@
|
||||
*/
|
||||
import * as fs from "node:fs";
|
||||
import * as vscode from "vscode";
|
||||
import { CoauthorStore } from "./store";
|
||||
import { SidecarRouter, docIdentity } from "./sidecarRouter";
|
||||
import { newId, type AttributionRecord, type Provenance } from "./model";
|
||||
import { buildFingerprint, resolve, type OffsetRange } from "./anchorer";
|
||||
import { gitUserEmail } from "./identity";
|
||||
import { applyChange, coalesce, type LiveSpan } from "./attributionTracker";
|
||||
import { minimizeReplace, PendingEditRegistry } from "./pendingEdits";
|
||||
import type { VersionGuard } from "./versionGuard";
|
||||
import { isUnderRoot } from "./workspacePath";
|
||||
import { isAuthorable } from "./workspacePath";
|
||||
|
||||
/** Test-facing snapshot of live attribution state for a document. */
|
||||
export interface RenderedSpan {
|
||||
@@ -74,8 +74,8 @@ export class AttributionController implements vscode.Disposable {
|
||||
readonly onDidApplyAgentEdit: vscode.Event<{ document: vscode.TextDocument }> = this.applyEmitter.event;
|
||||
|
||||
constructor(
|
||||
private readonly store: CoauthorStore,
|
||||
private readonly rootDir: string,
|
||||
private readonly store: SidecarRouter,
|
||||
private readonly rootDir: string | undefined,
|
||||
private readonly guard: VersionGuard,
|
||||
) {
|
||||
this.disposables.push(this.agentType, this.humanType, this.statusItem, this.output, this.applyEmitter);
|
||||
@@ -88,14 +88,15 @@ export class AttributionController implements vscode.Disposable {
|
||||
}
|
||||
|
||||
private isTracked(document: vscode.TextDocument): boolean {
|
||||
return document.uri.scheme === "file" && isUnderRoot(document.uri.fsPath, this.rootDir);
|
||||
return isAuthorable(document.uri.scheme);
|
||||
}
|
||||
private docPathOf(uri: vscode.Uri): string {
|
||||
return vscode.workspace.asRelativePath(uri, false);
|
||||
/** The single document key (F8): repo-relative path in-workspace, URI string otherwise. */
|
||||
private keyOf(document: vscode.TextDocument): string {
|
||||
return this.store.keyOf(docIdentity(document));
|
||||
}
|
||||
private currentAuthor(): Provenance {
|
||||
const id = vscode.workspace.getConfiguration("git").get<string>("user.name") || process.env.USER || "human";
|
||||
const email = gitUserEmail(this.rootDir);
|
||||
const email = this.rootDir !== undefined ? gitUserEmail(this.rootDir) : undefined;
|
||||
return { kind: "human", id, ...(email !== undefined ? { email } : {}) };
|
||||
}
|
||||
private state(docPath: string): DocAttribution {
|
||||
@@ -112,7 +113,7 @@ export class AttributionController implements vscode.Disposable {
|
||||
/** Load the sidecar and re-resolve every attribution (live span | orphan). */
|
||||
loadAll(document: vscode.TextDocument): void {
|
||||
if (!this.isTracked(document)) return;
|
||||
const docPath = this.docPathOf(document.uri);
|
||||
const docPath = this.keyOf(document);
|
||||
const s = this.state(docPath);
|
||||
const artifact = this.store.load(docPath);
|
||||
s.spans = [];
|
||||
@@ -143,7 +144,7 @@ export class AttributionController implements vscode.Disposable {
|
||||
handleExternalSidecarChange(uri: vscode.Uri): void {
|
||||
for (const s of this.docs.values()) {
|
||||
if (this.store.sidecarPath(s.docPath) === uri.fsPath) {
|
||||
const doc = vscode.workspace.textDocuments.find((d) => this.docPathOf(d.uri) === s.docPath);
|
||||
const doc = vscode.workspace.textDocuments.find((d) => this.keyOf(d) === s.docPath);
|
||||
if (doc) this.loadAll(doc);
|
||||
}
|
||||
}
|
||||
@@ -153,7 +154,7 @@ export class AttributionController implements vscode.Disposable {
|
||||
|
||||
private onDidChange(e: vscode.TextDocumentChangeEvent): void {
|
||||
if (!this.isTracked(e.document) || e.contentChanges.length === 0) return;
|
||||
const docPath = this.docPathOf(e.document.uri);
|
||||
const docPath = this.keyOf(e.document);
|
||||
if (!e.document.isDirty && this.matchesDisk(e.document)) {
|
||||
// Disk sync (revert / external reload): buffer now equals the file on
|
||||
// disk — re-resolve, never attribute (PUC-4). A real edit can also
|
||||
@@ -248,7 +249,7 @@ export class AttributionController implements vscode.Disposable {
|
||||
): Promise<boolean> {
|
||||
if (!this.isTracked(document)) return false;
|
||||
if (opts?.expectedVersion !== undefined && document.version !== opts.expectedVersion) return false;
|
||||
const docPath = this.docPathOf(document.uri);
|
||||
const docPath = this.keyOf(document);
|
||||
const startOffset = document.offsetAt(range.start);
|
||||
const endOffset = document.offsetAt(range.end);
|
||||
const oldText = document.getText(range);
|
||||
@@ -298,8 +299,8 @@ export class AttributionController implements vscode.Disposable {
|
||||
|
||||
private onDidSave(document: vscode.TextDocument): void {
|
||||
if (!this.isTracked(document)) return;
|
||||
if (this.guard.isReadOnly(this.docPathOf(document.uri))) return;
|
||||
const docPath = this.docPathOf(document.uri);
|
||||
if (this.guard.isReadOnly(this.keyOf(document))) return;
|
||||
const docPath = this.keyOf(document);
|
||||
const s = this.docs.get(docPath);
|
||||
// Allow save when hadAttributions is true even if spans/orphans are now empty:
|
||||
// that means the user deliberately deleted all attributed text, and we must
|
||||
@@ -351,7 +352,7 @@ export class AttributionController implements vscode.Disposable {
|
||||
|
||||
private render(document: vscode.TextDocument): void {
|
||||
if (!this.isTracked(document)) return;
|
||||
const s = this.docs.get(this.docPathOf(document.uri));
|
||||
const s = this.docs.get(this.keyOf(document));
|
||||
const spans = this.visible && s ? s.spans : [];
|
||||
const toRange = (sp: LiveSpan) =>
|
||||
new vscode.Range(document.positionAt(sp.start), document.positionAt(sp.end));
|
||||
|
||||
Reference in New Issue
Block a user