undo

package
v1.3.1 Latest Latest
Warning

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

Go to latest
Published: Jul 22, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package undo records what an import wrote and reverses it on request, backing `cct undo`. Import is cct's only operation that changes files, so a clean, verifiable reversal of the last import is the natural safety net.

A journal is written per import into cct's own config directory (never inside a coding-agent home). Each entry carries the destination path, the SHA-256 of the bytes cct wrote, and — for a replaced or merge-updated file — a backup of the original. Reversal is conservative: it only removes or restores a file whose current contents still match what the import wrote, so edits made after the import are never destroyed.

Index

Constants

View Source
const JournalVersion = 1

JournalVersion is the on-disk journal schema version.

Variables

This section is empty.

Functions

func Dir

func Dir(configDir string) string

Dir returns the directory holding import journals under a resolved config dir.

func Record

func Record(configDir string, j Journal) (string, error)

Record writes a journal for a completed import and prunes old journals to the retention limit. A journal with no entries is not written (nothing to undo).

func Remove

func Remove(j Journal) error

Remove deletes a journal file after it has been reversed.

func Validate added in v1.3.1

func Validate(j Journal) error

Validate checks a journal is structurally sound and self-consistent before any reversal touches the disk. Every failure mode here resolves to "change nothing": an unsupported version, a missing/relative path, a path that escapes the recorded agent home, a missing hash, or a created/overwritten record that disagrees with its backup fields all reject the whole journal.

Types

type Entry

type Entry struct {
	// Action mirrors the bundle import action ("import", "import-copy",
	// "replace", "update"); it is informational for display.
	Action string `json:"action"`
	// Dest is the absolute path of the file the import wrote.
	Dest string `json:"dest"`
	// WroteSHA is the SHA-256 of the bytes the import wrote to Dest. Reversal only
	// proceeds when Dest still hashes to this, so later edits are never clobbered.
	WroteSHA string `json:"wrote_sha256"`
	// Backup, when set, is the absolute path to a copy of the file that existed at
	// Dest before the import overwrote it (replace and merge-update). Reversal
	// restores it. Empty for a newly created file, which reversal deletes.
	Backup string `json:"backup,omitempty"`
	// BackupSHA is the SHA-256 of the backup's contents, recorded at import time.
	// Reversal refuses to restore a backup whose bytes no longer match, so a
	// swapped or tampered backup can never be written over a session.
	BackupSHA string `json:"backup_sha256,omitempty"`
	// Created is true when the import created a new file (no prior file at Dest),
	// so reversal deletes it rather than restoring a backup.
	Created bool `json:"created"`
	// ThreadID and Preview are carried for a readable undo listing.
	ThreadID string `json:"thread_id,omitempty"`
	Preview  string `json:"preview,omitempty"`
}

Entry is the reversal record for one written file.

type Journal

type Journal struct {
	Version int     `json:"version"`
	Time    string  `json:"time"` // RFC3339, when the import ran
	Tool    string  `json:"tool"`
	Home    string  `json:"home"`   // agent home root the import wrote into
	Bundle  string  `json:"bundle"` // source bundle path (for display)
	Entries []Entry `json:"entries"`
	// contains filtered or unexported fields
}

Journal is the full record of one import.

func Latest

func Latest(configDir string) (*Journal, error)

Latest returns the most recent journal for reversal, or nil when none exist.

It is deliberately strict: it loads the single newest journal file and validates it, and if that file is missing fields, points outside its recorded home, is truncated, or is otherwise malformed, it returns an error rather than silently falling back to an older journal. This upholds the core invariant — a corrupt, manipulated, or ambiguous journal must lead to "change nothing".

func List

func List(configDir string) ([]Journal, error)

List returns the recorded journals, newest first.

func (Journal) Path

func (j Journal) Path() string

Path returns the on-disk location of a journal loaded via List/Latest.

type Outcome

type Outcome struct {
	Entry  Entry
	Status string // removed | restored | already-gone | changed | not-regular |
	// backup-missing | backup-changed | error
	Message string
}

Outcome is what reversal did to one entry.

type Result

type Result struct {
	DryRun   bool
	Removed  int
	Restored int
	Skipped  int
	Outcomes []Outcome
}

Result summarizes a reversal.

func Reverse

func Reverse(j Journal, dryRun bool) Result

Reverse undoes an import journal. It deletes files the import created and restores backups for files it overwrote — but only when the file on disk still matches what the import wrote, so any change made afterward is left untouched and reported as skipped. On a real (non-dry-run) reversal, restored backups are removed and the journal file is deleted afterward by the caller via Remove.

Jump to

Keyboard shortcuts

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