| 1 | //! Workspace snapshots — pre/post-turn safety net. |
| 2 | //! |
| 3 | //! Each turn the engine takes a `pre-turn:<seq>` snapshot of the user's |
| 4 | //! workspace into a side git repo at |
| 5 | //! `~/.deepseek/snapshots/<project_hash>/<worktree_hash>/.git`, then a |
| 6 | //! matching `post-turn:<seq>` snapshot when the turn finishes. Users |
| 7 | //! can roll back via `/restore N` (slash command) or, when the model |
| 8 | //! recognises an "undo my last edit" intent, the `revert_turn` tool. |
| 9 | //! |
| 10 | //! ## Why a side repo? |
| 11 | //! |
| 12 | //! - The user's own `.git` is never touched. `--git-dir` and |
| 13 | //! `--work-tree` are *always* set together when we shell out to git; |
| 14 | //! that single invariant is what keeps snapshots and the user's repo |
| 15 | //! completely independent. |
| 16 | //! - Workspaces without git still get snapshots. |
| 17 | //! - `git`'s own deduplication (object packfiles) keeps the disk |
| 18 | //! footprint tractable — typical 100 MB workspace × 12 turns ≈ 1.2 GB |
| 19 | //! uncompressed but git's content-addressed storage usually brings |
| 20 | //! that down 10-30×. We mitigate further with: |
| 21 | //! - 7-day default retention (`session_manager` prunes at session |
| 22 | //! start via [`prune::prune_older_than`]). |
| 23 | //! - `gc.auto = 0` on the side repo (we don't want background gcs |
| 24 | //! firing mid-turn) plus an explicit `git gc --prune=now` after |
| 25 | //! prune. |
| 26 | //! - Startup cleanup for stale `tmp_pack_*` files left by interrupted |
| 27 | //! git pack operations. |
| 28 | //! |
| 29 | //! ## Failure model |
| 30 | //! |
| 31 | //! Pre/post-turn snapshot calls are **non-fatal**. If `git` is missing, |
| 32 | //! the disk is full, or the workspace is on a read-only filesystem, the |
| 33 | //! turn proceeds and the engine logs a warning. The snapshot is a |
| 34 | //! safety net, not a correctness gate. |
| 35 | //! |
| 36 | //! Workspaces over the configured size cap (`[snapshots] max_workspace_gb`, |
| 37 | //! default 2 GB of non-excluded content) skip snapshot init entirely. That |
| 38 | //! disable is intentionally loud: the operator is told once that undo is off |
| 39 | //! for the workspace, with the opt-in knobs (raise the cap, or set |
| 40 | //! `max_workspace_gb = 0` to disable the size gate). Scoped snapshot roots are |
| 41 | //! not yet a first-class config; the practical opt-in today is the cap override. |
| 42 | |
| 43 | pub mod paths; |
| 44 | pub mod prune; |
| 45 | pub mod repo; |
| 46 | |
| 47 | #[allow(unused_imports)] |
| 48 | pub use paths::{snapshot_dir_for, snapshot_git_dir}; |
| 49 | pub use prune::{DEFAULT_MAX_AGE, prune_older_than}; |
| 50 | |
| 51 | /// Maximum snapshots kept per workspace side-repo. Oldest are pruned |
| 52 | /// after each new snapshot to cap disk usage (#1112). |
| 53 | pub const DEFAULT_MAX_SNAPSHOTS: usize = 50; |
| 54 | #[allow(unused_imports)] |
| 55 | pub use repo::{ |
| 56 | DEFAULT_MAX_WORKSPACE_BYTES_FOR_SNAPSHOT, Snapshot, SnapshotId, SnapshotRepo, |
| 57 | estimate_workspace_size_bounded, |
| 58 | }; |
| 59 |