| Phase 0a Zoo-Code-Org#1380 |
fix/mcp-settings-stub-race-1371 |
open, CI green, awaiting review (tracking #1385) |
| Phase 0b Zoo-Code-Org#1381 |
fix/write-delay-default |
open, CI re-running (fixed a missed DiffViewProvider spec assertion), awaiting review (tracking #1386) |
| Phase 0c Zoo-Code-Org#1382 |
fix/abandoned-subtask-save-race-1021 |
open, CI green, awaiting review (tracking #1387) |
| S1 Zoo-Code-Org#1383 |
feat/version-token-s1 |
open — #1383 (3 commits; final review APPROVE; CI green on ubuntu gate; tracking #1388) |
| L2 Zoo-Code-Org#1384 |
feat/l2-chat-diff-default |
open — #1384 (74 lines, 1 commit; ALL checks green incl. ubuntu+windows unit + codecov/patch; CodeRabbit 3/3 findings confirmed fixed & replied in c0ef476; tracking #1389) |
| S3 Zoo-Code-Org#1391 |
feat/atomic-publish-s3 |
open — #1395 (amended to a37dd24 after the 5th CodeRabbit pass: the "no temp left on failure" spec now drives a real post-commit backup cleanup failure (unlink EPERM) and asserts the target stays committed with no temp behind — the non-fatal cleanup path is now covered; 4th-pass tempPath fchmod fix retained; local gates green: 49 unit, safeWriteText.ts 100% stmts/branch/lines, eslint 0, tsc 0; ALL checks green incl. ubuntu unit + e2e-mock + codecov/patch; CodeRabbit review completed; 14/14 findings replied; tracking #1391) |
| S2 Zoo-Code-Org#1390 |
feat/observation-registry-s2 |
open — #1394 (stacked on Zoo-Code-Org#1383, targets main; CodeRabbit findings addressed in 2965ad1, ubuntu CI green; tracking #1390) |
| L1 Zoo-Code-Org#1396 |
feat/async-save-diagnostics-l1 |
open — #1403 (991ab69, rebased onto S3 head a37dd24: preDiagnostics race fixed; diagnostics tail filtered to the saved file via arePathsEqual (case-insensitive on Windows); stale .catch comment corrected; as any -> bracket notation (ledger 310->306); 4th CodeRabbit pass fixed — 100 ms in-memory settle delay moved from the blocking save path into the diagnostics tail (saves with diagnostics off / writeDelayMs 0 no longer pay it); 77/77 unit, eslint 0, tsc 0; all 12 CodeRabbit findings replied — 3 cured by the S3 rebased base; e2e-mock passed (subtasks resumeTask flake did not reproduce); stacked on Zoo-Code-Org#1395 (S3); tracking #1396) |
| B1 |
feat/per-write-checkpoints-b1 |
open — #1404 (amended to b674c42 (round 3: task-start baseline now awaited before the request loop + deferred-promise test) — perWrite garbled i18n fixed in 7 locales, task-start baseline forced, CheckpointSettings spec updated; ALL checks green incl. ubuntu+windows unit + codecov/patch, independent on upstream main 78c712a: per-write checkpoints in write_to_file/edit_file/apply_patch — checkpoint now gated on full patch success (handlers report success; no checkpoint after rejected approval / failed write), task-start baseline guard (incl. unset default-on test), perWriteCheckpoints setting default-on round-trip incl. explicit-false webview-state test + 17-locale translations; ubuntu CI + e2e-mock + codecov green; all 5 CodeRabbit findings replied (1 false positive — single shared default constant verified); local gates: eslint 0, tsc 0 src+webview, affected suites green; tracking #1397) |
| S4a |
feat/guarded-write-s4a |
open — #1405 (amended to 7a25fc0 — enqueue settled-chain eviction + replaceIfVersion ENOENT normalization + regression tests; ubuntu + codecov/patch green after the amend; on S3 head a37dd24 + S1×3 + S2 stack: CAS core + per-path FIFO chain + registry hook; CodeRabbit pass fixed — resolveAbsolutePath always path.resolve (registry-key match), pre/post-read bigint stat token capture on native + legacy read paths (mutation mid-read leaves target unobserved), safeWriteJson locks the resolved publish target (symlink-alias coordination); local gates: 169 units, 100% line coverage on changed lines, eslint 0, tsc 0; 3/3 findings replied; tracking #1399) |
| S4b |
feat/guarded-write-wiring-s4b |
open — #1408 (rebased to 68be264 on amended S4a 7a25fc0 — inherits the ENOENT fix; CI re-running; guard wired once at the DiffViewProvider.saveDirectly choke point (6 tools / 7 call sites), per-tool writeKind plumbing, fail-closed on collected taskRef; local gates: 188 units, 100% on changed lines, eslint 0, tsc 0; tracking #1400) |
| B2 |
feat/change-journal-b2 |
open — #1406 (amended to a4e7311 (round 3: mistake counter now resets only on a fully successful patch + 2 regression tests) on B1 head baacf59 — handler result objects, no-op/move gating, partial-flush gate patchSucceeded |
| B3a |
feat/change-cards-b3a |
open — #1411 (amended to 78bd752 (round 3: apply-patch + edit-file checkpoints now awaited, no fire-and-forget interleaving + 2 deferred-promise tests) on B2 head 410591e — round-2 CR fixes: checkpointSave.spec negative assertions now filter recorded say calls by the change_card type (the three-argument toHaveBeenCalledWith can never match the seven-argument call, so the old assertion guarded nothing), mergeExtensionState spec fixture uses non-default perWriteCheckpoints/changeCardDetail and asserts a partial push that omits the keys preserves them; CI re-running; change_card payload + checkpointSave emission, tool approval-diff plumbing (WriteToFile / EditFile / ApplyPatch), changeCardDetail setting full round trip, i18n 18 locales; split from the planned B3a scope by the 1000-line cap — 934 changed lines; local gates: 429 units, eslint 0 src+webview+types, tsc 0 both dirs, 100% on changed lines; tracking #1401) |
| B3c |
feat/rollback-service-b3c |
open — #1410 (amended to 2b4a8ce on B3a head 4e5dc4b — restoreFile POSIX toPosix normalization + win32 regression test; round-2 CR fix: restoreFile now constrains the target to the workspace before the checkout/delete branch (CWE-22 containment via path.resolve + trailing-separator prefix check) with a traversal regression test; CI re-running; rollbackFile / rollbackStep + ShadowCheckpointService.restoreFile + fileExistsInCommit; 434 lines; local gates: eslint 0, tsc 0, 100% on changed lines; tracking #1409) |
| B3b |
feat/change-cards-ui-b3b |
open — #1412 (amended to a0bb49b on B3c head 5f88c41 — round-2 CR fixes: no-task rollback now posts a correlated checkpointRollbackResult failure (the requesting card clears its pending state), ChangeCard validates the parsed payload with changeCardSchema (a truncated/pre-series record no longer throws during render), the settings spec dirts the form via the per-write control before Save, and the handler lazy-imports the rollback module so specs that mock vscode minimally no longer execute the checkpoint/editor import graph (fixes the 16-file import-chain failure on ubuntu); CI re-running; stacked on B3c: ChangeCard chat component (summary lazy-diff / full inline / compact), per-file + per-step rollback buttons with the typed webview→extension channel (checkpointRollbackFile/Step → B3c rollbackFile/rollbackStep → checkpointRollbackResult by cardTs), the changeCardDetail settings control (pre-staged from the B3a split), chat i18n all 18 locales (en-only verified to fail check-translations parity); local gates: webview 162 files / 1781 tests, src rollback spec 6, eslint 0 all dirs, tsc 0 both dirs, 100% on changed lines; raw diff 1300 (1226 at first push; +74 round-2 fixes) — budget exception documented in the PR body; tracking #1402) |
| S5 |
— |
blocked on upstream Zoo-Code-Org#1319 (open) |
| S6 |
— |
blocked on upstream Zoo-Code-Org#1066 (CHANGES_REQUESTED) |
[ENHANCEMENT] File-write safety series (upstream epic Zoo-Code-Org#1375): version-guarded atomic writes + per-step visibility/rollback — PR series plan
1. Goal
Make agent file writes fast, loud-failing, self-healing, atomic, and auditable — in that order.
2. Series overview
3. PR specs
S1 — A1 Version token
src/utils/versionToken.ts:computeVersionToken(filePath): Promise<string>+ pureversionTokenOfStat(stats)for testability.dev:ino:size:mtimeNs:ctimeNsfrom onefs.stat; ns fields as decimal strings from BigInt (no float precision loss).S2 — A2 Observation registry
src/core/task/observationRegistry.ts: per-taskMap<absolutePath, { version, observedAt }>; owner = task, so parent and subtask observations are independent.Taskowns an instance;ReadFileToolrecords an observation after a successful read (one extra stat, same call the write guard will reuse).S3 — A4 Atomic publish
src/services/file-safety/safeWriteText.ts: temp file in a private per-write staging dir → write → fsync → close → atomic rename; on Windows: ReplaceFile with DACL copy, rename fallback. Generalizes the staging/backup/rollback logic currently insidesafeWriteJson(which is refactored to call this).saveDirectly(all five write tools + write_to_file) switches from rawfs.writeFiletosafeWriteText.safeWriteJsonexisting suite still 100% (no regression); patch coverage 100%.saveDirectlyno longer calls rawfs.writeFile;safeWriteJsonbehavior unchanged.S4 — A3 Guarded write/edit (the behavior change)
src/core/tools/guardedWrite.ts: compare-and-swap on the write path:createIfAbsent: new file succeeds; existing file fails (forces a read first — the model re-reads and retries with the observed version);createIfAbsent;replaceIfVersion(version): mismatch fails with "stale version — re-read the file, then retry";S5 — A5 Zoo-Code-Org#920 cross-instance regression test
updateTaskHistoryon the locked-merge write; asserts the second instance's merge preserves the first's fields (no clobbered history item).fix/920-concurrent-task-history-cross-instance(5 commits, prior work).S6 — A6 Zoo-Code-Org#1221 truncated tool-call parser
NativeToolCallParser: a finalization failure must not reuse the streaming-phasenativeArgs(partial JSON). Fail the tool call with an explicit "arguments were truncated" error instead of writing truncated content to disk.L1 — Async post-save diagnostics
saveDirectlytail: instead of awaiting LSP diagnostics, emit them as an asynchronous follow-up event once settled.L2 — Chat-diff default approval path
PREVENT_FOCUS_DISRUPTIONdefaultfalse→true(src/shared/experiments.ts:22); toggle kept as escape hatch, storage key unchanged (§6).B1 — Per-write checkpoint
<globalStorage>/tasks/<taskId>/checkpoints.git-in-shadow can show that step's snapshot; task start is cheap (no worktree).perWriteCheckpoints(boolean, defaulttrue) — master switch for the B cluster; full Persisted Setting Checklist round trip ships in this PR (§6).B2 — Per-task change journal
changes.jsonlunder the task dir; one entry per file write: path, operation, checkpoint id, diff stats (additions/deletions from the already-computed approval diff).B3 — Per-step change cards + rollback
changeCardDetail("full" | "summary", default"summary") — round trip ships in B3a (§6).4. Order
5. Risks
6. Configuration / user settings
Principle: the safety core (A1–A6) is not configurable — an off-switch would reintroduce the corruption this epic fixes. Configurable surface = the B cluster (visibility/rollback convenience) + existing behavior toggles (speed, approval surface). Every new setting follows the AGENTS.md Persisted Setting Checklist — full round trip:
global-settings.ts(definition + shared default constant) →ExtensionState/message types →SettingsViewbinding to localcachedState(never live state) →updateSettingspayload →webviewMessageHandlerpersistence via ContextProxy →ClineProvider.getState()with default →getStateToPostToWebview()(destructure + return) → every consumer same default semantics → import/export schema → focused tests (UI binding/save, persisted + unset via posted state).New settings
perWriteCheckpointstruechangeCardDetail"summary"summary= one line ("N files changed +X −Y") + per-file list + rollback, diff rendered lazily on expand;full= inline unified diff rendered by default. Auto-approval steps always get the compact card regardless.Existing settings (reused — no new key)
writeDelayMsDEFAULT_WRITE_DELAY_MS); setting, UI, round trip unchanged.PREVENT_FOCUS_DISRUPTIONfalse→true(experimental map,src/shared/experiments.ts:22). Keep the toggle as the escape hatch for users who prefer diff-editor approval. Decision inside the L2 PR: if the experimental settings section is hard to discover, surface it as a first-class approval-surface setting — move/rename in the UI only; the storage key stays stable so existing user values carry over.Deliberately NOT settings (decision record)
Round-trip cost and diff budget
Each new setting touches ~8–10 files (types, extension host, provider, webview settings, schema, tests) ≈ 100–300 changed lines, and counts toward the diff budget (800-line target / 1000 hard cap):
perWriteCheckpointsround trip ships inside the B1 PR (not a separate PR) — it is part of the 2d B1 estimate.changeCardDetailround trip ships inside B3a (extension-host side); if B3a + round trip exceeds budget, B3a keeps setting + payload and B3b is UI-only.experiments.ts+ its spec update — trivial.updateSettings; posted state asserts both persisted and default; import/export round-trips.check-translationsCI covers it.7. E2e plan (apps/vscode-e2e, aimock)
Principle (AGENTS.md test pyramid): e2e only for real extension-host boundaries and full-workflow smoke; detailed assertions stay at unit/spec. Existing base suites to extend:
suite/tools/write-to-file.test.ts(real task → write_to_file → disk assertions, aimock-replayed) andsuite/subtasks.test.ts(+fixtures/subtasks.ts). New fixtures follow the aimock workflow:TEST_FILE-filtered record runs,toolCallIdmatching for turn 2+, match strings without timestamps/paths, verify withpnpm --filter @roo-code/vscode-e2e test:ci:mock.write_to_fileon an existing file the model never read → expect the tool-call failure step ("file not read yet") →read_file(matched by toolCallId) →write_to_fileretry →attempt_completion; assert final on-disk content = new content and the chat step shows the failure + success. E2 concurrent subtask writers (ships with S4b or as a follow-up small PR): extend the subtasks suite — two subtasks write the same file; assert final content is one complete version (no torn/interleaved file); keep ordering assertions loose to avoid CI flake8. Per-PR execution rules (AGENTS.md)
pnpm --dir src exec vitest run <path>; suite green before push.pnpm --dir src exec eslint --max-warnings=0 <files>; suppression counts never increase..changesetfiles, no CHANGELOG edits (per AGENTS.md).9. Acceptance criteria (series complete)
pnpm --filter @roo-code/vscode-e2e test:ci:mock), including E1 (stale-write self-heal), E2 (concurrent subtask writers), E3 (truncated tool call writes nothing), and E4 (step rollback end-to-end).perWriteCheckpointsandchangeCardDetailcomplete the full Persisted Setting Checklist (UI save via cachedState, posted-state persisted + default assertions, import/export round trip, all locales);PREVENT_FOCUS_DISRUPTIONdefaults totruewith the existing toggle preserved.Execution status (synced 2026-08-28 (round 5): B2 access-denied + failed-move partial-flush fixes + all CR sweep fixes — final SHAs b674c42 / a4e7311 / 78bd752 / 2b4a8ce / a0bb49b; S3+L1 + S4/B sweep fixes green
Blockers to watch: Zoo-Code-Org#1066 (S6), Zoo-Code-Org#1319 (S5), Zoo-Code-Org#1046 landing (raises A3 urgency), Zoo-Code-Org#1379 merge (Task.ts rebase).