history

package
v0.16.13 Latest Latest
Warning

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

Go to latest
Published: Jun 22, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Quantity-based tiered compaction for the persistent revision store.

The revisions / changes directories grow unbounded over a project's lifetime — every Commit() writes a new revision dir + diff payloads. On a heavy project this is hundreds of MB per month and untenable over a year. The ChangeTracker is a short-horizon stop-gap (undo a bad sed -i, recover a hasty rm), not a long-term audit log — git is for that — so the compaction policy is correspondingly simple:

  • Hot (most recent HotCount revisions): kept verbatim
  • Warm (next WarmCount): conversation.json dropped
  • Dropped (everything older): deleted (or archived if ArchiveFrozen is enabled)

Position is by revision-directory mtime (newest first). The view tools (view_history) bump mtime when they access a revision so an old revision the user comes back to floats to the top automatically — the next compaction pass sees it as hot and keeps it (or warm, depending on its new position).

Index

Constants

View Source
const (
	RedColor             = "\x1b[31m"
	GreenColor           = "\x1b[32m"
	YellowColor          = "\x1b[33m"
	BoldStyle            = "\x1b[1m"
	ResetColor           = "\x1b[0m"
	NumberOfContextLines = 3 // Number of context lines to show around changes
)

Color constants for better readability

View Source
const RedactedContentMarker = "[REDACTED - external file]"

RedactedContentMarker is the canonical marker used when file content is redacted because the file is outside the workspace root (to avoid leaking sensitive data). It is defined here, in the lower-level history package, so both pkg/history and pkg/agent reference a single source of truth instead of maintaining duplicate copies that can silently drift. pkg/agent references this via history.RedactedContentMarker.

Variables

This section is empty.

Functions

func ClearAll

func ClearAll(workspace string) (changesCleared int, revisionsCleared int, err error)

ClearAll removes all change entries and all revision directories. If workspace is non-empty, it operates on that workspace's .sprout directory. If workspace is empty, it uses the globally configured paths. Returns the number of changes cleared, revisions cleared, and any error.

func ClearOlderThan

func ClearOlderThan(workspace string, since time.Time) (changesCleared int, revisionsCleared int, err error)

ClearOlderThan removes all change entries and revision directories where the change timestamp is strictly before 'since'. If workspace is non-empty, it operates on that workspace's .sprout directory. If workspace is empty, it uses the globally configured paths. Returns the number of changes cleared, revisions cleared, and any error.

func GetChangedFilesSince

func GetChangedFilesSince(since time.Time) ([]string, error)

GetChangedFilesSince returns a unique list of filenames changed after the given time.

func GetChangesDir

func GetChangesDir() string

GetChangesDir returns the current changes directory path

func GetDiff

func GetDiff(filename, originalCode, newCode string) string

func GetFilesForRevision

func GetFilesForRevision(revisionID string) ([]string, error)

HasActiveChangesForRevision returns whether a revision ID exists and has any active changes GetFilesForRevision returns the file paths of all active changes in a revision. Returns an empty slice if the revision is not found or has no active changes.

func GetRevisionsDir

func GetRevisionsDir() string

GetRevisionsDir returns the current revisions directory path

func HasActiveChangesForRevision

func HasActiveChangesForRevision(revisionID string) (bool, error)

func InitializeHistoryPaths

func InitializeHistoryPaths(config *configuration.Config)

InitializeHistoryPaths configures the history storage paths based on configuration This should be called at application startup to ensure correct path resolution

func IsChangeOlderThan

func IsChangeOlderThan(metadataPath string, since time.Time) bool

IsChangeOlderThan reads a change's metadata.json and returns true if the change's timestamp is strictly before 'since'. Returns false if the file cannot be read or parsed.

func PrintDiff

func PrintDiff(filename, originalCode, newCode string)

func PrintRevisionHistory

func PrintRevisionHistory() error

func PrintRevisionHistoryBuffer

func PrintRevisionHistoryBuffer() (string, error)

PrintRevisionHistoryBuffer displays the revision history to a buffer for seamless console experience

func PrintRevisionHistoryWithReader

func PrintRevisionHistoryWithReader(inputReader *bufio.Reader) error

PrintRevisionHistoryWithReader allows custom input reader for interactive navigation

func RecordBaseRevision

func RecordBaseRevision(requestHash, instructions, response string, conversation []APIMessage) (string, error)

RecordBaseRevision saves the initial request and response, returning a revision ID. conversation is the full conversation history (all user/assistant/tool messages)

func RecordChange

func RecordChange(baseRevisionID string, filename, originalCode, newCode, description, note string) error

RecordChange saves a specific file change against a base revision.

func RecordChangeWithDetails

func RecordChangeWithDetails(baseRevisionID string, filename, originalCode, newCode, description, note string, originalPrompt string, llmMessage string, editingModel string) error

RecordChangeWithDetails saves a specific file change against a base revision with additional details.

func RevertChangeByRevisionID

func RevertChangeByRevisionID(revisionID string) error

RevertChangeByRevisionID reverts all changes associated with a given revision ID.

func TouchRevision

func TouchRevision(revisionID string) error

TouchRevision bumps the revision directory's mtime to now. Called when view_history accesses a revision so the next compaction pass considers it "recently used" and keeps it (or re-promotes it from warm back toward hot) regardless of its position in raw creation order. No-op if the revision dir doesn't exist (already dropped).

Types

type APIMessage

type APIMessage struct {
	Role             string        `json:"role"`
	Content          string        `json:"content"`
	ReasoningContent string        `json:"reasoning_content,omitempty"`
	ToolCallID       string        `json:"tool_call_id,omitempty"`
	ToolCalls        []APIToolCall `json:"tool_calls,omitempty"`
}

APIMessage represents a message in the conversation (imported from agent_api to avoid circular dependency)

type APIToolCall

type APIToolCall struct {
	ID       string `json:"id"`
	Type     string `json:"type"`
	Function struct {
		Name      string `json:"name"`
		Arguments string `json:"arguments"`
	} `json:"function"`
}

APIToolCall represents a tool call in a message

type ChangeLog

type ChangeLog struct {
	RequestHash      string
	Instructions     string
	Response         string
	FileRevisionHash string
	Filename         string
	OriginalCode     string
	NewCode          string
	Description      string
	Note             sql.NullString
	Status           string
	Timestamp        time.Time
	OriginalPrompt   string // Added: Original user prompt
	LLMMessage       string // Added: Full message sent to LLM
	AgentModel       string // Added: Editing model used
	HasConversation  bool   // Added: Whether conversation.json exists for this revision
	// Tier reflects the revision's compaction state: "hot" (full data
	// including conversation.json) or "warm" (conversation.json
	// dropped). Empty string is treated as hot for backward compat.
	Tier string
}

ChangeLog represents a logged change, including context from the base revision.

func GetAllChanges

func GetAllChanges() ([]ChangeLog, error)

GetAllChanges returns all recorded changes (most recent first).

func GetAllChangesMetadata added in v0.16.12

func GetAllChangesMetadata() ([]ChangeLog, error)

GetAllChangesMetadata returns change metadata WITHOUT reading or base64-decoding the .original/.updated content files. This is the lightweight alternative to GetAllChanges for callers that only need the manifest fields (filename, revision, timestamp, status, tier) — primarily list_changes when include_diff/show_content aren't set.

The OriginalCode and NewCode fields of the returned ChangeLog entries are left EMPTY. Callers that infer op/recoverability from content presence should instead use HasOriginal/HasNew, which report whether the content files exist on disk (a cheap os.Stat, not a read+decode). This avoids the O(total-history) base64 decode that fetchAllChanges performs on every list_changes invocation.

func GetChangesSince

func GetChangesSince(since time.Time) ([]ChangeLog, error)

GetChangesSince returns changes whose timestamp is strictly after the provided time.

type ChangeMetadata

type ChangeMetadata struct {
	Version          int       `json:"version"`
	Filename         string    `json:"filename"`
	FileRevisionHash string    `json:"file_revision_hash"`
	RequestHash      string    `json:"request_hash"` // This is the revision ID
	Timestamp        time.Time `json:"timestamp"`
	Status           string    `json:"status"`
	Note             string    `json:"note"`
	Description      string    `json:"description"`
	OriginalPrompt   string    `json:"original_prompt,omitempty"` // Added: Original user prompt
	LLMMessage       string    `json:"llm_message,omitempty"`     // Added: Full message sent to LLM
	AgentModel       string    `json:"agent_model,omitempty"`     // Added: Editing model used
}

ChangeMetadata stores metadata about a specific file change.

type CompactionStats

type CompactionStats struct {
	TotalRevisions         int
	HotKept                int
	WarmDemoted            int // revisions moved hot→warm or already warm
	Dropped                int // revisions moved out of warm → deleted/archived
	ChangesPayloadsDeleted int
	BytesReclaimed         int64
	HardCapTrimmed         int
	OrphanChangesDropped   int
	OverCapChangesDropped  int
	AgedChangesDropped     int
}

CompactionStats reports what a single CompactRevisions pass did. Useful for logs / metrics; not consumed by anything load-bearing.

func CompactRevisions

func CompactRevisions(policy RetentionPolicy) (CompactionStats, error)

CompactRevisions runs one compaction pass over the configured revisions directory according to the given policy. Safe to call concurrently from multiple agents (mutex-serialized). Idempotent: repeated calls are no-ops once revisions are in their target tier.

Returns stats for logging; errors are non-fatal at the call site (caller should log and continue — a failed compaction just means disk usage stays where it was, nothing breaks).

type RetentionPolicy

type RetentionPolicy struct {
	HotCount      int
	WarmCount     int
	MaxDirBytes   int64
	ArchiveFrozen bool

	// MaxChangesPerRevision caps the per-revision change-record count
	// in the changes/ directory. A single runaway session can produce
	// tens of thousands of records (e.g. when the agent `cd`s into
	// $HOME and a shell walk misclassifies pre-existing files as
	// creates). Without this cap, count-based bloat persists even
	// when total bytes are under MaxDirBytes. Zero disables.
	MaxChangesPerRevision int

	// MaxChangesAge drops change records older than this regardless of
	// their parent revision's tier. Belt-and-suspenders against
	// changes/ growing unbounded inside the hot window. Zero disables.
	MaxChangesAge time.Duration
}

RetentionPolicy is the subset of RevisionRetentionConfig the compactor needs. Kept separate to avoid a cycle with pkg/configuration.

type RevisionGroup

type RevisionGroup struct {
	RevisionID   string
	Instructions string
	Response     string
	Changes      []ChangeLog
	Timestamp    time.Time
	AgentModel   string       // Editing model used for this revision
	Conversation []APIMessage // Full conversation history for multi-turn conversations
}

RevisionGroup represents a group of changes that belong to the same revision

func GetRevisionGroups

func GetRevisionGroups() ([]RevisionGroup, error)

GetRevisionGroups returns all revision groups sorted by timestamp (most recent first)

Jump to

Keyboard shortcuts

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