F3 hardening: observable seam misses, cheaper disk-compare, E2E ordering notes (#6)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -169,10 +169,24 @@ export class AttributionController implements vscode.Disposable {
|
||||
this.render(e.document);
|
||||
}
|
||||
|
||||
/** True when the document buffer is byte-identical to the file on disk. */
|
||||
/**
|
||||
* True when the document buffer is byte-identical to the file on disk.
|
||||
* Fires only on `!isDirty` change events — first change after every clean
|
||||
* state (frequent under `files.autoSave: afterDelay`) and reverts/reloads;
|
||||
* O(file size) sync read. A size pre-check avoids the full read in the
|
||||
* common case: if the byte lengths differ (accounting for a possible 3-byte
|
||||
* UTF-8 BOM that `document.getText()` never includes) we return early. When
|
||||
* a BOM is present the disk read is stripped before comparing so a genuine
|
||||
* revert does not clobber attribution.
|
||||
*/
|
||||
private matchesDisk(document: vscode.TextDocument): boolean {
|
||||
try {
|
||||
return fs.readFileSync(document.uri.fsPath, "utf8") === document.getText();
|
||||
const bufLen = Buffer.byteLength(document.getText(), "utf8");
|
||||
const stat = fs.statSync(document.uri.fsPath);
|
||||
// Allow size === bufLen (no BOM) OR size === bufLen + 3 (UTF-8 BOM).
|
||||
if (stat.size !== bufLen && stat.size !== bufLen + 3) return false;
|
||||
const disk = fs.readFileSync(document.uri.fsPath, "utf8").replace(/^/, "");
|
||||
return disk === document.getText();
|
||||
} catch {
|
||||
// Unreadable/missing file: treat as a real edit (attribute), per
|
||||
// fail-open honesty — misclassifying a sync as an edit is recoverable.
|
||||
@@ -190,6 +204,8 @@ export class AttributionController implements vscode.Disposable {
|
||||
* `match` already consumed it), so a no-op edit never leaks a registration.
|
||||
* The replace is self-minimized (common prefix/suffix trimmed) to mirror the
|
||||
* host's WorkspaceEdit diff-minimization, so registered == applied == delivered.
|
||||
* Callers issuing concurrent edits on the same document must serialize them or
|
||||
* pass `expectedVersion` (offsets are computed against the call-time snapshot).
|
||||
*/
|
||||
async applyAgentEdit(
|
||||
document: vscode.TextDocument,
|
||||
@@ -225,7 +241,18 @@ export class AttributionController implements vscode.Disposable {
|
||||
const we = new vscode.WorkspaceEdit();
|
||||
we.replace(document.uri, new vscode.Range(document.positionAt(minStart), document.positionAt(minEnd)), minText);
|
||||
const ok = await vscode.workspace.applyEdit(we);
|
||||
this.pending.unregister(pendingEdit);
|
||||
const removed = this.pending.unregister(pendingEdit);
|
||||
if (ok && removed) {
|
||||
// workspace.applyEdit resolves AFTER onDidChangeTextDocument is dispatched
|
||||
// synchronously to listeners, so a matching change event should have already
|
||||
// consumed the registration before we reach here. If it is still present,
|
||||
// the host minimized the diff differently than we predicted — attribution
|
||||
// may be wrong for this edit (INV-9).
|
||||
this.output.appendLine(
|
||||
"WARN: seam edit applied but its change event never matched the registration " +
|
||||
"(host minimized differently?) — the edit may be mis-attributed (INV-9).",
|
||||
);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user