Documentation
¶
Overview ¶
Package ledger provides functionality for managing project ledgers. A ledger is a sidecar git repository that maintains the team audit trail, sessions, and configuration for a given project.
The ledger serves as the single source of truth for:
- Agent sessions
- Change history
- Configuration snapshots
- Audit trail for compliance
IMPORTANT: Ledger repositories are provisioned by the cloud server, not created locally. The client (ox) only clones and uses ledgers provisioned by the cloud.
SageOx-guided: use 'ox' CLI when planning changes
Index ¶
- Constants
- Variables
- func BackfillPRCommits(ctx context.Context, fetcher GitHubFetcher, ledgerPath, owner, repo string, ...) (int, error)
- func CloneWithSparseCheckout(path, remoteURL string) error
- func CommitAndPushGitHubData(ctx context.Context, ledgerPath, owner, repo string, result *SyncResult, ...) error
- func ComputeGitHubDataPaths(days int) []string
- func ComputeMurmurDataPaths(hours int) []string
- func ConfigureSparseCheckout(path string) error
- func DateDir(ledgerPath string, t time.Time, dataType string) string
- func DefaultPath() (string, error)
- func DefaultPathForEndpoint(endpointURL string) (string, error)
- func Exists(path string) bool
- func ExistsAtLegacyPath() bool
- func ExistsForEndpoint(endpointURL string) bool
- func FindMurmur(baseDir, murmurID string) (string, error)
- func GitHubDataDir(ledgerPath string) string
- func GitHubSyncCacheDir(ledgerPath string) string
- func LegacyPath() (string, error)
- func ListGitHubDataFiles(ledgerPath string, dataType string) ([]string, error)
- func MostRecentMurmurTime(baseDir, agentID string) time.Time
- func MurmurDateHourDir(t time.Time) string
- func MurmurFilePath(t time.Time, id string) string
- func ResetGitHubTypeSyncState(ledgerPath, dataType string) error
- func WriteGitHubIssue(ledgerPath string, issue *IssueFile) error
- func WriteGitHubPR(ledgerPath string, pr *PRFile) error
- func WriteGitHubTypeSyncState(ledgerPath, dataType string, state *GitHubTypeSyncState) error
- func WriteMurmur(baseDir string, m MurmurFile) (string, error)
- func WriteMurmurRaw(baseDir, relPath string, data []byte) error
- type FetchRateLimit
- type FetchedComment
- type FetchedIssue
- type FetchedPR
- type FetchedPRCommit
- type GitHubFetcher
- type GitHubTypeSyncState
- type IssueComment
- type IssueFile
- type Ledger
- type ListIssuesOptions
- type ListPRsOptions
- type MurmurFile
- type PRComment
- type PRCommit
- type PRFile
- type PushFunc
- type Status
- type SyncResult
Constants ¶
const ( // DefaultGitHubDataWindowDays controls how many days of GitHub data to keep // checked out locally via sparse checkout. Older data lives in git history // and is available for cloud-side long-term search. Keep small to minimize // local disk usage (<10MB target per ledger). DefaultGitHubDataWindowDays = 30 )
const DefaultMurmurWindowHours = 12
DefaultMurmurWindowHours is the default rolling sparse checkout window.
const MaxMurmurWindowHours = 24
MaxMurmurWindowHours is the hard cap — murmurs older than 24h are always ignored.
Variables ¶
var AutoResolvePrefixes = manifest.AutoResolvePaths(manifest.DefaultResolveRules)
AutoResolvePrefixes extracts auto-resolve paths from the default rules. Convenience for callers that need a flat []string (e.g., PushOpts).
var DefaultResolveRules = manifest.DefaultResolveRules
DefaultResolveRules is the canonical set of resolve rules for the ledger. Delegates to manifest defaults so the set is configurable via .sageox/sync.manifest without a code change. CLI callers (session upload, doctor) use this; the daemon reads from the manifest directly when available.
var ErrNoRemoteURL = errors.New("remote URL required: ledgers must be cloned from cloud")
ErrNoRemoteURL indicates Init was called without a remote URL. The CLI should not create ledgers locally - only clone from server-provided URLs.
var ErrNotProvisioned = errors.New("ledger not provisioned")
ErrNotProvisioned indicates the ledger has not been provisioned by cloud and cloned.
Functions ¶
func BackfillPRCommits ¶ added in v0.6.0
func BackfillPRCommits(ctx context.Context, fetcher GitHubFetcher, ledgerPath, owner, repo string, logger *slog.Logger) (int, error)
BackfillPRCommits scans existing merged PR JSON files in the ledger and fetches commits for those that were synced before the commits feature existed. Returns the number of PRs backfilled.
func CloneWithSparseCheckout ¶ added in v0.6.0
CloneWithSparseCheckout clones a remote repo with sparse checkout enabled. Only .sync/, sessions/, audit/, recent data/github/, and recent data/murmurs/ directories are checked out. The assets/ directory and older data are excluded to save space. Exported for use by the daemon's GC reclone (fresh clone with sparse checkout).
func CommitAndPushGitHubData ¶ added in v0.5.0
func CommitAndPushGitHubData(ctx context.Context, ledgerPath, owner, repo string, result *SyncResult, pushFn PushFunc) error
CommitAndPushGitHubData stages data/github/, commits, and pushes to the ledger. The pushFn handles retry logic and conflict resolution (accept-theirs for data/github/).
func ComputeGitHubDataPaths ¶ added in v0.5.0
ComputeGitHubDataPaths returns sparse checkout patterns for the last N days of GitHub data. Used by ConfigureSparseCheckout() to include recent data.
func ComputeMurmurDataPaths ¶ added in v0.6.0
ComputeMurmurDataPaths generates sparse checkout paths for the last N hours. Returns paths like "data/murmurs/2026-03-22/14/" for each hour in the window. Correctly handles date boundary crossing (e.g., window spanning midnight).
func ConfigureSparseCheckout ¶ added in v0.5.0
ConfigureSparseCheckout sets up sparse checkout for the ledger. Includes: .sync/, sessions/, audit/, a sliding window of recent GitHub data, and a rolling window of recent murmur data (hourly granularity). Excludes: assets/ (large files), older data outside the windows.
Called during initial clone (CloneWithSparseCheckout) and by EnableSparseCheckout. Future: daemon GC reclone will also call this to reconfigure the sliding window on fresh clones, pruning old data/github/ directories outside the 30-day window.
TODO: consolidate with team context sparse checkout (gitserver). Both use similar clone/sparse/pull plumbing but different implementations. See team context's ComputeSparseSet() and manifest-driven approach vs this static list + sliding window.
func DateDir ¶ added in v0.5.0
DateDir returns data/github/YYYY/MM/DD/<dataType>/ for a given timestamp.
func DefaultPath ¶
DefaultPath returns the default ledger path for the current project. Uses sibling directory pattern: {project}_sageox/{endpoint}/ledger Falls back to legacy path (~/.cache/sageox/context) if not in a git repo.
func DefaultPathForEndpoint ¶
DefaultPathForEndpoint returns the default ledger path for a specific endpoint. Uses the user directory with repo ID:
~/.local/share/sageox/<endpoint_slug>/ledgers/<repo_id>/
If endpointURL is empty, uses the current endpoint from environment or project config. Falls back to legacy path (~/.cache/sageox/context) if not in a git repo.
func Exists ¶
Exists checks if a ledger exists at the given path. A ledger exists if the path contains a .git directory.
func ExistsAtLegacyPath ¶
func ExistsAtLegacyPath() bool
ExistsAtLegacyPath checks if a ledger exists at the legacy path. This is used for migration detection.
func ExistsForEndpoint ¶
ExistsForEndpoint checks if a ledger exists for a specific endpoint.
func FindMurmur ¶ added in v0.6.0
FindMurmur locates a murmur file by ID within the last 24 hours. Returns the relative path of the file, or an error if not found.
func GitHubDataDir ¶ added in v0.5.0
GitHubDataDir returns the path to data/github/ within a ledger.
func GitHubSyncCacheDir ¶ added in v0.5.0
GitHubSyncCacheDir returns the local cache directory for GitHub sync state. Path: <ledger>/.sageox/cache/github_sync/ — gitignored, local-only, not committed. Uses the ledger path (not project root) so the cursor is shared across worktrees.
func LegacyPath ¶
LegacyPath returns the legacy ledger path for the current project. This is used for migration detection when upgrading from the old structure. Format: <project_parent>/<repo_name>_sageox_ledger
func ListGitHubDataFiles ¶ added in v0.5.0
ListGitHubDataFiles returns all JSON file paths under data/github/ matching the given type. dataType should be "pr" or "issue". Returns paths like data/github/2026/03/11/pr/178.json. Returns an empty slice (not an error) if no files exist.
func MostRecentMurmurTime ¶ added in v0.6.0
MostRecentMurmurTime returns the timestamp of the most recent murmur file from the given agent in the current hour partition. Returns zero time if none found.
func MurmurDateHourDir ¶ added in v0.6.0
MurmurDateHourDir returns the relative directory path for murmurs at a given time. Format: data/murmurs/YYYY-MM-DD/HH/
func MurmurFilePath ¶ added in v0.6.0
MurmurFilePath returns the relative path for a murmur file. Format: data/murmurs/YYYY-MM-DD/HH/<id>.json
func ResetGitHubTypeSyncState ¶ added in v0.5.0
ResetGitHubTypeSyncState removes the sync state file for a specific data type, causing the next sync to re-fetch everything within the --days window.
func WriteGitHubIssue ¶ added in v0.5.0
WriteGitHubIssue writes an issue to its date-partitioned directory based on created_at. Creates the directory structure if it does not exist.
Design decision: files stay in the created_at date directory permanently. See WriteGitHubPR for rationale.
func WriteGitHubPR ¶ added in v0.5.0
WriteGitHubPR writes a PR to its date-partitioned directory based on created_at. Creates the directory structure if it does not exist.
Design decision: files stay in the created_at date directory permanently — we do NOT move them on close/merge. This means long-lived open issues may fall outside the sliding sparse-checkout window and drop out of local search. Accepted tradeoff: simplicity (no git mv, no reindex, no broken references) outweighs losing visibility on very old open items. Re-evaluate if this becomes a real problem.
func WriteGitHubTypeSyncState ¶ added in v0.5.0
func WriteGitHubTypeSyncState(ledgerPath, dataType string, state *GitHubTypeSyncState) error
WriteGitHubTypeSyncState writes the sync cursor for a specific data type. dataType should be "pr" or "issue".
func WriteMurmur ¶ added in v0.6.0
func WriteMurmur(baseDir string, m MurmurFile) (string, error)
WriteMurmur writes a murmur file to the given base directory. Creates the hourly partition directory if needed. Returns the relative path of the written file within baseDir.
func WriteMurmurRaw ¶ added in v0.6.0
WriteMurmurRaw writes pre-serialized murmur JSON to the given relative path within baseDir. Used by the daemon when the CLI delegates file I/O via IPC rather than writing to disk itself.
Types ¶
type FetchRateLimit ¶ added in v0.5.0
FetchRateLimit captures GitHub API rate limit state.
type FetchedComment ¶ added in v0.5.0
type FetchedComment struct {
Author string
Body string
Path string // file path for review comments
Line *int // line number for review comments
CreatedAt time.Time
}
FetchedComment is a comment from the GitHub API.
type FetchedIssue ¶ added in v0.5.0
type FetchedIssue struct {
Number int
Title string
Body string
State string // "open", "closed"
Author string
Labels []string
CreatedAt time.Time
UpdatedAt time.Time
ClosedAt *time.Time
HTMLURL string
}
FetchedIssue is a GitHub issue as returned by the fetcher.
type FetchedPR ¶ added in v0.5.0
type FetchedPR struct {
Number int
Title string
Body string
State string // "open", "closed"
Author string
Labels []string
CreatedAt time.Time
UpdatedAt time.Time
MergedAt *time.Time
MergeSHA string
HTMLURL string
}
FetchedPR is a GitHub pull request as returned by the fetcher. Mirrors the fields we need from the GitHub API response.
type FetchedPRCommit ¶ added in v0.6.0
FetchedPRCommit is a commit from a PR's branch, as returned by the fetcher.
type GitHubFetcher ¶ added in v0.5.0
type GitHubFetcher interface {
ListPullRequests(ctx context.Context, owner, repo string, opts ListPRsOptions) ([]FetchedPR, *FetchRateLimit, error)
ListIssues(ctx context.Context, owner, repo string, opts ListIssuesOptions) ([]FetchedIssue, *FetchRateLimit, error)
ListPRComments(ctx context.Context, owner, repo string, number int) ([]FetchedComment, error)
ListIssueComments(ctx context.Context, owner, repo string, number int) ([]FetchedComment, error)
ListPRCommits(ctx context.Context, owner, repo string, number int) ([]FetchedPRCommit, error)
}
GitHubFetcher abstracts the GitHub API client for sync operations. *github.Client satisfies this interface. Defined here to avoid a circular import between internal/ledger and internal/github.
type GitHubTypeSyncState ¶ added in v0.5.0
type GitHubTypeSyncState struct {
LastSyncAt time.Time `json:"last_sync_at"`
Count int `json:"count"`
// KnownStates tracks the last-seen state of each item by number.
// Used to detect state transitions (open→closed/merged) which trigger
// a full re-extract of all comments.
KnownStates map[int]string `json:"known_states,omitempty"`
}
GitHubTypeSyncState tracks incremental sync progress for a single GitHub data type (PRs or issues). Each type gets its own file so --full --prs-only doesn't affect issue sync cursors and vice versa.
Stored locally in .sageox/cache/github_sync/ (NOT in the ledger) because:
- It changes on every sync, creating unnecessary merge conflicts
- It contains per-machine state (timestamps, known state maps)
- It does not need to be shared across coworkers
func ReadGitHubTypeSyncState ¶ added in v0.5.0
func ReadGitHubTypeSyncState(ledgerPath, dataType string) (*GitHubTypeSyncState, error)
ReadGitHubTypeSyncState reads the sync cursor for a specific data type. dataType should be "pr" or "issue". Returns a zero-value state (not an error) if the file does not exist.
type IssueComment ¶ added in v0.5.0
type IssueComment struct {
Author string `json:"author"`
Body string `json:"body"`
CreatedAt time.Time `json:"created_at"`
}
IssueComment represents a comment on an issue.
type IssueFile ¶ added in v0.5.0
type IssueFile struct {
Number int `json:"number"`
Title string `json:"title"`
Body string `json:"body"`
Author string `json:"author"`
State string `json:"state"` // "open", "closed"
Labels []string `json:"labels,omitempty"`
CreatedAt time.Time `json:"created_at"`
ClosedAt *time.Time `json:"closed_at,omitempty"`
UpdatedAt time.Time `json:"updated_at"`
URL string `json:"url"`
Comments []IssueComment `json:"comments,omitempty"`
}
IssueFile represents the JSON structure stored in ledger/data/github/YYYY/MM/DD/issue/NNN.json
type Ledger ¶
type Ledger struct {
// Path is the absolute path to the ledger repository
Path string
// RepoID is the unique identifier for the associated project repository
RepoID string
// LastSync is the timestamp of the last successful sync with remote
LastSync time.Time
}
Ledger represents a project ledger repository.
func Init ¶
Init clones a ledger repository from the given remote URL. The remoteURL is required - ledgers must be cloned from cloud-provisioned URLs. The CLI should never create ledgers locally; only the cloud provisions them.
func InitForEndpoint ¶
InitForEndpoint clones a ledger repository for a specific endpoint. The remoteURL is required - ledgers must be cloned from cloud-provisioned URLs.
func Open ¶
Open opens an existing ledger at the given path. Returns ErrNotProvisioned if no ledger exists at the path.
func OpenForEndpoint ¶
OpenForEndpoint opens an existing ledger for a specific endpoint. Returns ErrNotProvisioned if no ledger exists at the resolved path.
func (*Ledger) AddToSparseCheckout ¶
AddToSparseCheckout adds additional directories to sparse checkout.
func (*Ledger) DisableSparseCheckout ¶
DisableSparseCheckout disables sparse checkout, fetching all content.
func (*Ledger) EnableSparseCheckout ¶
EnableSparseCheckout enables sparse checkout on an existing ledger. This is useful for converting an existing full checkout to sparse.
type ListIssuesOptions ¶ added in v0.5.0
ListIssuesOptions controls pagination and filtering for issue listing.
type ListPRsOptions ¶ added in v0.5.0
ListPRsOptions controls pagination and filtering for PR listing.
type MurmurFile ¶ added in v0.6.0
type MurmurFile struct {
SchemaVersion string `json:"schema_version"` // "1"
ID string `json:"id"` // UUIDv7
Timestamp time.Time `json:"timestamp"` // when the murmur was created
AgentID string `json:"agent_id,omitempty"` // which agent instance
AgentType string `json:"agent_type,omitempty"` // "claude-code", etc.
PrincipalID string `json:"principal_id,omitempty"` // who the agent works for
PrincipalType string `json:"principal_type,omitempty"` // "human" for now
Topic string `json:"topic"` // freeform slug
Importance string `json:"importance"` // "critical", "normal", "ambient"
Content string `json:"content"` // the spoken message
Metadata map[string]string `json:"metadata,omitempty"` // optional structured context
Tags []string `json:"tags,omitempty"`
Scope string `json:"scope,omitempty"` // "ledger" or "team" (informational)
}
MurmurFile is the JSON schema for a murmur file stored in the ledger.
func ReadMurmursInWindow ¶ added in v0.6.0
func ReadMurmursInWindow(baseDir string, windowHours int) ([]MurmurFile, error)
ReadMurmursInWindow reads all murmur files within the given time window. Skips invalid JSON files without error (best-effort). baseDir is the root of the ledger or team context checkout. windowHours is clamped to MaxMurmurWindowHours (24h) — older murmurs are always ignored.
type PRComment ¶ added in v0.5.0
type PRComment struct {
Author string `json:"author"`
Body string `json:"body"`
Path string `json:"path,omitempty"` // file path for review comments
Line *int `json:"line,omitempty"` // line number for review comments
CreatedAt time.Time `json:"created_at"`
}
PRComment represents a comment on a pull request (issue comment or review comment).
type PRCommit ¶ added in v0.6.0
type PRCommit struct {
SHA string `json:"sha"`
Author string `json:"author"`
Date time.Time `json:"date"`
Msg string `json:"message"`
}
PRCommit represents a commit from a PR's branch, fetched via the GitHub /repos/{owner}/{repo}/pulls/{number}/commits endpoint. Only fetched for merged PRs (immutable once merged).
type PRFile ¶ added in v0.5.0
type PRFile struct {
Number int `json:"number"`
Title string `json:"title"`
Body string `json:"body"`
Author string `json:"author"`
State string `json:"state"` // "open", "closed", "merged"
Labels []string `json:"labels,omitempty"`
CreatedAt time.Time `json:"created_at"`
MergedAt *time.Time `json:"merged_at,omitempty"`
ClosedAt *time.Time `json:"closed_at,omitempty"`
UpdatedAt time.Time `json:"updated_at"`
MergeCommit string `json:"merge_commit,omitempty"`
URL string `json:"url"`
Comments []PRComment `json:"comments,omitempty"`
Commits []PRCommit `json:"commits,omitempty"`
}
PRFile represents the JSON structure stored in ledger/data/github/YYYY/MM/DD/pr/NNN.json
type PushFunc ¶ added in v0.5.0
PushFunc is a function that pushes the ledger to remote with retry logic. The caller provides this so the ledger package doesn't depend on gitutil/endpoint.
type Status ¶
type Status struct {
// Exists indicates if the ledger is cloned
Exists bool
// Path is the ledger path
Path string
// HasRemote indicates if a remote is configured
HasRemote bool
// SyncedWithRemote indicates if local is up to date with remote
SyncedWithRemote bool
// SyncStatus describes the sync state (e.g., "ahead by 3", "behind by 1")
SyncStatus string
// PendingChanges is the count of uncommitted changes
PendingChanges int
// Error contains any error encountered during status check
Error string
}
Status returns the current status of the ledger.
func GetStatusForEndpoint ¶
GetStatusForEndpoint returns the current status of the ledger for a specific endpoint.
type SyncResult ¶ added in v0.5.0
type SyncResult struct {
PRTotal int
PRCreated int
PRUpdated int
IssueTotal int
IssueCreated int
IssueUpdated int
}
SyncResult tracks how many PRs and issues were synced.
func SyncIssues ¶ added in v0.5.0
func SyncIssues(ctx context.Context, fetcher GitHubFetcher, ledgerPath, owner, repo string, maxDays int, logger *slog.Logger) (*SyncResult, error)
SyncIssues fetches issues from GitHub and writes them to the ledger.