Help us shape the future of Go! Take the 2026 Go Developer Survey

checkpoints

package
v2.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package checkpoints keeps backups of the files celeste's write tools change, on disk per session, so a change can be undone later — also from another process (2.0 F4).

Index

Constants

View Source
const (
	KeepSessions = 20
	KeepAge      = 30 * 24 * time.Hour
)

Retention (2.0 F4): a session's checkpoints survive while the session is one of the KeepSessions most recently changed, or was changed less than KeepAge ago — whichever keeps more.

Variables

View Source
var ErrDisabled = errors.New("file checkpoints are disabled: no home or cache directory")

ErrDisabled is returned by a store with no directory to keep checkpoints in (see Root): checkpoints are off.

View Source
var ErrNotRead = errors.New("not read in this session")

ErrNotRead: an existing file was never read in this session.

Functions

func DisplayPath

func DisplayPath(workspace, path string) string

DisplayPath shows path relative to workspace when it is inside it, and as it is otherwise.

func FormatChanges

func FormatChanges(changes []FileChange, workspace string) string

FormatChanges renders ComputeDiff's result for /diff: one line per file, relative to workspace when inside it, in the order given (ComputeDiff sorts by path).

func LatestSessionFor

func LatestSessionFor(root, path string) (string, error)

LatestSessionFor returns the session (its directory name under root) whose newest checkpoint of path is the most recent across all sessions (celeste revert without --session). Paths match as pathutil.Same does.

func Prune

func Prune(root, keep string, now time.Time) error

Prune deletes the session directories under root that retention does not keep. keep (the session starting now) always survives. A missing root is nothing to prune.

func Root

func Root() string

Root is where every session's checkpoints live: ~/.celeste/checkpoints, or <user cache dir>/celeste/checkpoints when there is no home directory. With neither it is "" and checkpoints are off (one warning): they are never written under the working directory.

func SameEntry

func SameEntry(a, b Entry) bool

SameEntry reports whether a and b are the same checkpoint.

func SessionDir

func SessionDir(root, sessionID string) string

SessionDir is sessionID's directory under root (ruling 5), or "" (no checkpoints) when root is "".

Types

type Checkpoint

type Checkpoint struct {
	// contains filtered or unexported fields
}

Checkpoint is an entry its writer can still roll back.

func (*Checkpoint) Commit

func (c *Checkpoint) Commit() error

Commit records the file as the write left it (Entry.After), once the tool's write succeeded, and closes the checkpoint. Every Checkpoint ends in Commit or Rollback. An entry already undone or evicted is left alone.

func (*Checkpoint) Entry

func (c *Checkpoint) Entry() Entry

Entry is the recorded entry.

func (*Checkpoint) Rollback

func (c *Checkpoint) Rollback() error

Rollback restores the file to its checkpointed state (deleting a file that did not exist) and removes the entry and its backup: for a write that failed after Checkpoint. After the entry was already undone, reverted or evicted it does nothing.

type Entry

type Entry struct {
	// MessageID is the ID of the tool call that made the change ("" when
	// unknown), so a rewind can map entries to messages.
	MessageID string `json:"message_id"`
	// Path is the absolute path of the changed file.
	Path string `json:"path"`
	// Version counts the session's changes to Path: 1, 2, …
	Version int `json:"version"`
	// Backup is the backup's file name in the session directory; "" when
	// the file did not exist, so undoing the change deletes it.
	Backup string `json:"backup"`
	// Time is when the checkpoint was taken (UTC).
	Time time.Time `json:"time"`
	// After is the file as the change left it, recorded when the tool's
	// write succeeded (Commit); nil until then, or when it could not be
	// read. Undoing the change compares the file with it (Changed).
	After *FileState `json:"after,omitempty"`
	// Root is the real directory undoing the change must stay inside (the
	// workspace, see CheckpointIn), and Rel the file's path inside it at
	// the time of the change. Undo reaches the file only through Root, so
	// a directory replaced by a symlink since cannot redirect it. Empty in
	// an index written before they existed.
	Root string `json:"root,omitempty"`
	Rel  string `json:"rel,omitempty"`
}

Entry is one checkpoint: Path as it was before a tool changed it.

func RevertFile

func RevertFile(root, path, sessionID string, check func(Entry) error) (string, Entry, error)

RevertFile restores path from its newest checkpoint in sessionID — or, when sessionID is "", in the session that changed it last — and removes that entry (celeste revert, 2.0 F4). check, when not nil, is asked about that entry first, as in RevertIf.

type FileChange

type FileChange struct {
	Path       string
	Insertions int
	Deletions  int
	IsNew      bool
	// Deleted: the file no longer exists.
	Deleted bool
	// Binary: either side holds a NUL byte; no line counts.
	Binary bool
	// Approx: the line comparison was too large; the counts are the
	// difference in line count only.
	Approx bool
	// TooBig: a side is over maxDiffBytes and was not read; OldSize and
	// NewSize are the byte sizes.
	TooBig           bool
	OldSize, NewSize int64
	// Err: this file could not be compared (the others still are).
	Err string
}

FileChange represents the diff stats for a single file.

type FileState

type FileState struct {
	Size   int64  `json:"size"`
	SHA256 string `json:"sha256"`
}

FileState identifies a file's contents: its size and SHA-256.

func Changed

func Changed(e Entry) (now FileState, changed bool, err error)

Changed reports whether e's file is no longer as e's change left it: something else (an editor, a formatter, a bash command, another write whose checkpoint failed) changed it since, so undoing e would overwrite that — or, for a file the change created, delete it. A file that is gone has nothing to lose (false). An entry whose After was never recorded counts as changed: celeste cannot tell. now is the file's state (zero when it is gone or unreadable).

func StateOf

func StateOf(path string) (FileState, error)

StateOf reads path's FileState (a regular file only; a FIFO or device is refused, never waited on).

type FileTracker

type FileTracker struct {
	// contains filtered or unexported fields
}

FileTracker tracks file modification times to detect stale reads. When a file is read, its mtime is recorded. Before writing, the caller can check whether the file was modified externally since the last read.

func NewFileTracker

func NewFileTracker() *FileTracker

NewFileTracker creates a new FileTracker.

func (*FileTracker) CheckRead

func (ft *FileTracker) CheckRead(path string) error

CheckRead is the must-read-before-edit rule (2.0 W4 ruling 6): nil for a file that does not exist, or that was read (or written) in this session and is unchanged since; ErrNotRead for an existing file never read; the CheckStale error for one changed since its read; the stat error for a file that cannot be checked (reading it first would not help).

func (*FileTracker) CheckStale

func (ft *FileTracker) CheckStale(path string) error

CheckStale compares the file's current mtime against the stored mtime. Returns an error if the file was modified externally since the last read. Returns nil if the file has never been tracked (first write is allowed).

func (*FileTracker) ClearStale

func (ft *FileTracker) ClearStale(path string)

ClearStale removes tracking for the given path (e.g. after a successful re-read).

func (*FileTracker) RecordRead

func (ft *FileTracker) RecordRead(path string) error

RecordRead stats the file at path and stores its current mtime.

func (*FileTracker) Reset

func (ft *FileTracker) Reset()

Reset forgets every recorded read, so the next write to any file is allowed as a first write. MCP chat calls it when a shared environment starts a call with no other call in flight: staleness is judged per call.

type SnapshotManager

type SnapshotManager struct {
	// contains filtered or unexported fields
}

SnapshotManager is one session's checkpoints: its backups and index.json in one directory. It is safe for concurrent use. Every change is written to the index before it takes effect in memory.

func NewSnapshotManager

func NewSnapshotManager(sessionID string) *SnapshotManager

NewSnapshotManager opens the checkpoints of sessionID under Root(), with whatever an earlier process recorded for it (a resumed session). The first call in a process prunes old sessions first (never this one). Nothing is created on disk until the first checkpoint.

func (*SnapshotManager) Checkpoint

func (sm *SnapshotManager) Checkpoint(path, messageID string) (*Checkpoint, error)

Checkpoint backs up path as it is now and records the entry in the index. Call it after the tool's input validated, immediately before the write, and Rollback the result if the write then fails (2.0 F4). A path that does not exist yet is recorded without a backup. A path that is not a regular file is refused and nothing is recorded. The backup is a full copy, read into memory, with no size limit: a session holds at most defaultMaxEntries of them, which bounds the count but not the bytes (a session that rewrites a large file keeps up to that many copies of it).

Undoing the change stays inside path's own directory; the write tools use CheckpointIn to keep it inside the workspace.

func (*SnapshotManager) CheckpointIn

func (sm *SnapshotManager) CheckpointIn(workspace, path, messageID string) (*Checkpoint, error)

CheckpointIn is Checkpoint for a path inside workspace: undoing the change (undo, rewind, celeste revert, Rollback) reaches the file only through workspace, so it cannot write or delete anything outside it, even after a directory on the way was replaced by a symlink. path's directory must exist and resolve inside workspace.

func (*SnapshotManager) Cleanup

func (sm *SnapshotManager) Cleanup() error

Cleanup removes the session's directory and forgets its entries.

func (*SnapshotManager) ComputeDiff

func (sm *SnapshotManager) ComputeDiff() ([]FileChange, error)

ComputeDiff compares each changed file's oldest backup (its state before the session changed it) with the file now. Sorted by path. A file that cannot be compared carries its error in FileChange.Err. The session lock is held only while the index and the backups are read, so an undo in another process cannot remove a backup between the two; the current files are read and diffed after it is released, so a large /diff never keeps other processes waiting.

func (*SnapshotManager) Dir

func (sm *SnapshotManager) Dir() string

Dir is the session's checkpoint directory.

func (*SnapshotManager) Entries

func (sm *SnapshotManager) Entries() []Entry

Entries returns a copy of the session's entries, oldest first.

func (*SnapshotManager) Evicted

func (sm *SnapshotManager) Evicted(ids []string) bool

Evicted reports whether the cap evicted an entry recorded for any of ids (see evictedFile): a rewind of them cannot restore every change.

func (*SnapshotManager) Files

func (sm *SnapshotManager) Files() []string

Files returns the sorted, de-duplicated paths of this session's entries: the files it changed (#200's files-modified list).

func (*SnapshotManager) Revert

func (sm *SnapshotManager) Revert(path string) (Entry, error)

Revert restores path from its newest entry and removes that entry.

func (*SnapshotManager) RevertIf

func (sm *SnapshotManager) RevertIf(path string, check func(Entry) error) (Entry, error)

RevertIf is Revert that first asks check (when not nil) about the entry it would undo, under the store's locks, and undoes nothing when check returns an error, which it returns.

func (*SnapshotManager) RevertLast

func (sm *SnapshotManager) RevertLast() (Entry, error)

RevertLast restores the file of the newest entry and removes it (/undo).

func (*SnapshotManager) RevertLastIf

func (sm *SnapshotManager) RevertLastIf(check func(Entry) error) (Entry, error)

RevertLastIf is RevertLast with check, as RevertIf.

func (*SnapshotManager) RewindTo

func (sm *SnapshotManager) RewindTo(messageID string) ([]Entry, error)

RewindTo undoes, newest first, every entry from the first one recorded for messageID to the newest (W4's /rewind) and returns them in that order. On an error it stops and returns what it undid so far.

func (*SnapshotManager) RewindToAnyIf

func (sm *SnapshotManager) RewindToAnyIf(ids []string, check func([]Entry) error) ([]Entry, error)

RewindToAnyIf is RewindTo from the first entry recorded for any of ids (2.0 W4's /rewind, ruling 5). check (when not nil) first sees the entries it would undo, oldest first, under the store's locks; an error from it undoes nothing and is returned. When no entry has one of ids it undoes nothing and returns no error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL