Documentation
¶
Index ¶
- Constants
- func FindRawSessionFile(rawDir, sessionID string) (string, error)
- func NonGitDir(dir string) (string, error)
- func NowTimestamp() string
- func ParseSessionIDsFromTrailer(message string) []string
- func RawSessionPath(rawDir, sessionID string) string
- func RawSubagentDir(rawDir, sessionID string) string
- func SanitizeID(id string) string
- func ValidSessionID(id string) bool
- type AILineAttribution
- type CommitRecord
- type SessionRecord
- type Store
- func (s *Store) AttestationStatePath() string
- func (s *Store) ClearTraceRunActive() error
- func (s *Store) CopyRawSessionFile(sessionID, srcPath string) error
- func (s *Store) CopySessionJSONL(sessionID, sourceDir string) error
- func (s *Store) DeleteFileSnapshot(sessionID, filePath string)
- func (s *Store) DeleteShellPreSignature(sessionID string)
- func (s *Store) Dir() string
- func (s *Store) GCOrphans(liveSHAs map[string]bool) error
- func (s *Store) GitDir() string
- func (s *Store) InitTraceDir() error
- func (s *Store) IsGit() bool
- func (s *Store) IsTraceInitialized() bool
- func (s *Store) IsTraceRunActive() bool
- func (s *Store) LoadAILineAttribution(sessionID string) *AILineAttribution
- func (s *Store) LoadAllAILineAttributions() ([]*AILineAttribution, error)
- func (s *Store) LoadAllCommitRecords() ([]*CommitRecord, error)
- func (s *Store) LoadAllSessionRecords() (map[string]*SessionRecord, error)
- func (s *Store) LoadFileSnapshot(sessionID, filePath string) ([]byte, error)
- func (s *Store) LoadSessionRecord(sessionID string) (*SessionRecord, error)
- func (s *Store) LoadShellPreSignature(sessionID string) (map[string]string, error)
- func (s *Store) LogFilePath() string
- func (s *Store) MarkTraceInitialized() error
- func (s *Store) MarkTraceRunActive() error
- func (s *Store) RawSessionDir() string
- func (s *Store) RecordLineRanges(sessionID, filePath string, ranges []aicodingsession.LineRange) error
- func (s *Store) RemoveTraceDir() error
- func (s *Store) RemoveTraceInitialized() error
- func (s *Store) SaveCommitRecord(rec *CommitRecord) error
- func (s *Store) SaveFileSnapshot(sessionID, filePath string, content []byte) error
- func (s *Store) SaveSessionRecord(rec *SessionRecord) error
- func (s *Store) SaveShellPreSignature(sessionID string, sig map[string]string) error
- func (s *Store) SessionRecordExists(sessionID string) bool
- func (s *Store) WipeTraceDir() error
Constants ¶
const TrailerKey = "Chainloop-Trace-Sessions"
TrailerKey is the git-style trailer the commit-msg hook writes when AI sessions are attributed to staged files. The key is matched case-sensitively per git trailer convention.
Variables ¶
This section is empty.
Functions ¶
func FindRawSessionFile ¶
FindRawSessionFile returns the path to the raw session file for sessionID within rawDir, or an error wrapping os.ErrNotExist when missing.
func NonGitDir ¶
NonGitDir returns the out-of-tree directory that parents trace state for a working directory with no git repository, under <user cache>/chainloop/trace/.
The path is derived from dir alone so that hook subprocesses recompute it instead of inheriting it: a hook is a grandchild of `chainloop trace run` (run -> agent -> hook), and an agent that rewrites the environment would otherwise send the hook to a different directory where it would silently record nothing. The user cache dir is $HOME-derived, which survives that far better than $TMPDIR, and it keeps live session state away from /tmp reapers.
func NowTimestamp ¶
func NowTimestamp() string
NowTimestamp returns the current UTC time in RFC3339 format.
func ParseSessionIDsFromTrailer ¶
ParseSessionIDsFromTrailer scans a commit message for `Chainloop-Trace-Sessions:` trailer lines and returns the sorted, deduped list of session IDs declared in the message. Lines must start with the trailer key (no leading whitespace) to match git's trailer parsing rules. Empty values and blank entries between commas are ignored.
func RawSessionPath ¶
RawSessionPath returns the path a session's raw transcript is copied to inside rawDir. Readers and writers must both name the file through this helper: the sanitized ID it applies is not recoverable from the raw one.
func RawSubagentDir ¶
RawSubagentDir returns the directory a session's copied subagent transcripts live in inside rawDir. Same naming contract as RawSessionPath.
func SanitizeID ¶
SanitizeID strips path separators and traversal components from an identifier before using it in filesystem paths. Exported so providers in other packages compute the same on-disk path that the writers in this package emit; otherwise an ID containing "/" or ".." would write to one filename and be read from another.
A rewritten ID also gets a digest suffix, since distinct IDs ("a/x" and "a_x") would otherwise collapse onto one filename and merge two sessions' state. Separating the rewritten set is all that is needed: agent-assigned IDs are UUID-shaped and pass through untouched.
Returning an already-safe ID verbatim makes this idempotent, which callers depend on — GCOrphans compares filename-derived keys against sanitized ones, and LoadAllAILineAttributions feeds a filename straight back in. Always appending the digest would double-suffix those keys and match nothing.
func ValidSessionID ¶
ValidSessionID reports whether an agent-assigned session ID can safely name a file. IDs reach us from agent hook payloads and from commit-message trailers, and both are used to read the agent's own transcript directory, so anything that is not a single path element could escape it. Real IDs are UUID- or slug-shaped, so this rejects nothing legitimate.
Writers additionally pass IDs through SanitizeID, which keeps a rejected ID from escaping our own state directory even if a caller skips this check.
Types ¶
type AILineAttribution ¶
type AILineAttribution struct {
SessionID string `json:"session_id"`
Files map[string][]aicodingsession.LineRange `json:"files"`
}
AILineAttribution tracks which line ranges in which files a session modified.
type CommitRecord ¶
type CommitRecord struct {
SHA string `json:"sha"`
Message string `json:"message"`
SessionIDs []string `json:"session_ids,omitempty"`
Timestamp string `json:"timestamp"`
// Tracked marks the record as already attested by a previous successful
// trace push. The pre-push hook uses it to skip pushing a redundant
// attestation when no new AI-assisted commits have been recorded since
// the last push (e.g. a bare `git push` with no new commits, or a
// `git tag && git push --tags` over already-pushed commits).
Tracked bool `json:"tracked,omitempty"`
}
CommitRecord holds metadata about a commit captured by the post-commit hook.
type SessionRecord ¶
type SessionRecord struct {
// SessionID is the agent-assigned identifier for this session.
SessionID string `json:"session_id"`
// Provider identifies the agent that produced this session (e.g. "claude-code", "cursor").
// Empty on records written before multi-provider support; consumers should treat that as claude-code.
Provider string `json:"provider,omitempty"`
// AgentVersion is the agent runtime version reported at session-start
// (e.g., Cursor's cursor_version). Empty for providers whose hook payload
// does not carry the version. Recorded once on the first hook only.
AgentVersion string `json:"agent_version,omitempty"`
// Model is the model identifier reported at session-start (e.g.,
// Cursor's "model" field). Empty for providers whose hook payload does
// not carry the model. Recorded once on the first hook only.
Model string `json:"model,omitempty"`
// Active reports whether the session is ongoing at the time of record write.
Active bool `json:"active"`
// StartedAt is the RFC3339 timestamp of when tracking began for this session.
StartedAt string `json:"started_at"`
}
SessionRecord holds metadata about an AI coding agent session.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store reads and writes the chainloop-trace state under a single parent directory: the .git directory inside a repository, or the out-of-tree directory `chainloop trace run` picks outside one. Which of the two it is decides whether git hooks can be installed and whether the directory itself is ours to remove, so the Store carries that rather than leaving callers to thread a second copy of the same path alongside it.
func Locate ¶
Locate returns a Store over the directory that parents chainloop-trace state, plus the working-tree root. Inside a git repository those are the .git directory and the repo root; outside one, they are the NonGitDir of the nearest ancestor of cwd with an active `chainloop trace run`, and that ancestor. The returned Store reports which of the two it is via IsGit.
Constructing the Store here is deliberate: callers never hold a bare state path they could confuse with the repo root returned alongside it.
The non-git branch probes the run-active sentinel rather than the mere existence of a state directory, so one left behind by a crashed run is not bound to by an unrelated later invocation. Only ErrNotARepository reaches that branch: a broken .git means a repository is there and unusable, and binding such a clone to an ancestor's out-of-tree state would silently record its commits nowhere.
func NewGitStore ¶
NewGitStore returns a Store over a repository's .git directory. Nothing is created or validated here.
func NewOutOfTreeStore ¶
NewOutOfTreeStore returns a Store over the directory that holds trace state outside a repository, as returned by NonGitDir.
func (*Store) AttestationStatePath ¶
AttestationStatePath returns the path to the local attestation state file under <dir>/chainloop-trace/. This file isolates trace attestation state from the default chainloop state location.
func (*Store) ClearTraceRunActive ¶
ClearTraceRunActive removes the trace-run-active sentinel.
func (*Store) CopyRawSessionFile ¶
CopyRawSessionFile copies a single session transcript file from srcPath to <dir>/chainloop-trace/raw/<sanitized-sessionID>.jsonl. Used by providers whose transcripts are single files (e.g. Cursor); Claude's multi-file layout (main + subagents) goes through CopySessionJSONL instead.
func (*Store) CopySessionJSONL ¶
CopySessionJSONL copies the Claude Code JSONL file to <dir>/chainloop-trace/raw/<id>.jsonl. It also copies subagent files if they exist.
func (*Store) DeleteFileSnapshot ¶
DeleteFileSnapshot removes a file snapshot after it's been processed.
func (*Store) DeleteShellPreSignature ¶
DeleteShellPreSignature removes the pre-command signature once processed.
func (*Store) Dir ¶
Dir returns the parent directory, one level above chainloop-trace/.
Only `trace run`'s cleanup needs it, to drop the empty out-of-tree directory after a non-git run. Inside a repository the same path is .git and must never be removed, so callers guarding on IsGit are the only legitimate users.
func (*Store) GCOrphans ¶
GCOrphans drops trace state whose underlying commit is no longer reachable from any local branch. liveSHAs is the union of SHAs reachable from every local branch tip; the caller computes it from the git client.
Two cleanups in sequence:
- Walk commits/, drop any record whose SHA isn't in liveSHAs (post-rebase orphan or commit on a deleted branch).
- Walk ai-lines/ and sessions/, drop any entry whose session ID is no longer referenced by a surviving CommitRecord.
An empty liveSHAs is treated as a pathological signal (no local branches readable, or the caller fed us garbage) and the GC short-circuits — wiping every record on a transient git error would be far worse than skipping a cycle of cleanup.
func (*Store) GitDir ¶
GitDir returns the repository's .git directory, and "" outside a repository. Callers that install or remove git hooks must take the path from here, so a store that is not a .git directory cannot be mistaken for one.
func (*Store) InitTraceDir ¶
InitTraceDir creates the <dir>/chainloop-trace/ directory structure.
func (*Store) IsTraceInitialized ¶
IsTraceInitialized checks whether trace has been initialized in this clone.
func (*Store) IsTraceRunActive ¶
IsTraceRunActive reports whether a `chainloop trace run` session owns this store's trace state.
func (*Store) LoadAILineAttribution ¶
func (s *Store) LoadAILineAttribution(sessionID string) *AILineAttribution
LoadAILineAttribution loads the AI line attribution for a session by consolidating all JSONL entries into a single AILineAttribution. Returns a zero-value struct if the file doesn't exist.
func (*Store) LoadAllAILineAttributions ¶
func (s *Store) LoadAllAILineAttributions() ([]*AILineAttribution, error)
LoadAllAILineAttributions loads AI line attribution for all sessions that have data.
func (*Store) LoadAllCommitRecords ¶
func (s *Store) LoadAllCommitRecords() ([]*CommitRecord, error)
LoadAllCommitRecords reads all commit records from <dir>/chainloop-trace/commits/.
func (*Store) LoadAllSessionRecords ¶
func (s *Store) LoadAllSessionRecords() (map[string]*SessionRecord, error)
LoadAllSessionRecords reads every SessionRecord under <dir>/chainloop-trace/sessions/ into a map keyed by SessionID. A missing directory yields an empty (non-nil) map and no error; per-file decode errors are skipped so a single malformed record doesn't abort the load.
func (*Store) LoadFileSnapshot ¶
LoadFileSnapshot loads a previously stored file snapshot.
func (*Store) LoadSessionRecord ¶
func (s *Store) LoadSessionRecord(sessionID string) (*SessionRecord, error)
LoadSessionRecord reads the SessionRecord for sessionID. Returns nil, nil when not found.
func (*Store) LoadShellPreSignature ¶
LoadShellPreSignature loads the pre-command working-tree signature for a session.
func (*Store) LogFilePath ¶
LogFilePath returns the path to the hook log file inside the trace directory.
func (*Store) MarkTraceInitialized ¶
MarkTraceInitialized creates the sentinel file indicating trace is initialized.
func (*Store) MarkTraceRunActive ¶
MarkTraceRunActive marks the trace state as belonging to a running `chainloop trace run` session. Removed by ClearTraceRunActive when the wrapped command exits.
func (*Store) RawSessionDir ¶
RawSessionDir returns the path to <dir>/chainloop-trace/raw/ for use as session dir in parsing.
func (*Store) RecordLineRanges ¶
func (s *Store) RecordLineRanges(sessionID, filePath string, ranges []aicodingsession.LineRange) error
RecordLineRanges appends line ranges for a file to a session's attribution JSONL. An empty or nil ranges slice still records the file as touched (e.g., deletion-only edits).
func (*Store) RemoveTraceDir ¶
RemoveTraceDir removes the entire <dir>/chainloop-trace/ directory.
func (*Store) RemoveTraceInitialized ¶
RemoveTraceInitialized removes the sentinel file.
func (*Store) SaveCommitRecord ¶
func (s *Store) SaveCommitRecord(rec *CommitRecord) error
SaveCommitRecord writes a commit record to <dir>/chainloop-trace/commits/<sha>.json.
func (*Store) SaveFileSnapshot ¶
SaveFileSnapshot stores a file's content before an AI edit.
func (*Store) SaveSessionRecord ¶
func (s *Store) SaveSessionRecord(rec *SessionRecord) error
SaveSessionRecord writes a session record to <dir>/chainloop-trace/sessions/<id>.json.
func (*Store) SaveShellPreSignature ¶
SaveShellPreSignature stores the working-tree signature captured before an agent-run shell command, so the post-command hook can diff against it. A single slot per session is used; concurrent shell calls in one turn overwrite it (see the parallel-shell limitation).
func (*Store) SessionRecordExists ¶
SessionRecordExists checks whether a session record file exists for the given session ID.
func (*Store) WipeTraceDir ¶
WipeTraceDir clears the single-use data left over by a successful push: raw transcripts and per-edit file snapshots. It intentionally preserves commits/, ai-lines/, and sessions/ — those carry session attribution that survives across pushes (a rebase replays commits with the same trailers, so the next push can still attribute them). Bounded growth of the surviving subdirectories is the job of GCOrphans, called after the wipe.