session

package
v1.0.44 Latest Latest
Warning

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

Go to latest
Published: Mar 12, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package session handles session capture for specledger hooks (v2, inline capture).

Package session provides checkpoint session capture functionality for storing AI conversation segments linked to git commits and tasks.

Index

Constants

View Source
const (
	// MetadataTimeout is the timeout for metadata operations
	MetadataTimeout = 30 * time.Second
	// SessionsTable is the PostgREST table name
	SessionsTable = "sessions"
)
View Source
const (
	// MaxRetries is the maximum number of upload retries
	MaxRetries = 3
	// RetryDelay is the initial delay between retries
	RetryDelay = 5 * time.Second
	// QueueIndexFile is the name of the queue index file
	QueueIndexFile = ".queue.json"
)
View Source
const (
	// StorageBucket is the Supabase Storage bucket for sessions
	StorageBucket = "sessions"
	// StorageTimeout is the timeout for storage operations (30 seconds)
	StorageTimeout = 30 * time.Second
)
View Source
const (
	// CaptureErrorsLogFile is the name of the local error log file
	CaptureErrorsLogFile = "capture-errors.log"
)
View Source
const MaxSessionSize = 10 * 1024 * 1024

MaxSessionSize is the maximum uncompressed session size (10 MB)

Variables

This section is empty.

Functions

func BuildStoragePath

func BuildStoragePath(projectID, featureBranch, identifier string) string

BuildStoragePath constructs the storage path for a session

func Compress

func Compress(data []byte) ([]byte, error)

Compress compresses data using gzip

func CompressionRatio

func CompressionRatio(original, compressed int64) float64

CompressionRatio calculates the compression ratio (compressed/original)

func Decompress

func Decompress(data []byte) ([]byte, error)

Decompress decompresses gzip data

func GetBaseDir added in v1.0.30

func GetBaseDir() string

GetBaseDir returns the base path for local session storage (~/.specledger)

func GetCaptureErrorsLogPath added in v1.0.40

func GetCaptureErrorsLogPath() string

GetCaptureErrorsLogPath returns the path to the local capture errors log file

func GetCurrentBranch

func GetCurrentBranch(workdir string) (string, error)

GetCurrentBranch gets the current git branch name

func GetCurrentCommitHash

func GetCurrentCommitHash(workdir string) (string, error)

GetCurrentCommitHash gets the current HEAD commit hash

func GetLocalSession added in v1.0.30

func GetLocalSession(projectID, specKey, identifier string) ([]byte, error)

GetLocalSession retrieves a session from local storage (not necessarily queued)

func GetProjectID

func GetProjectID(workdir string) (string, error)

GetProjectID attempts to get the project ID from specledger.yaml

func GetProjectIDFromRemote added in v1.0.30

func GetProjectIDFromRemote(workdir string) (string, error)

GetProjectIDFromRemote attempts to get project ID by looking up git remote in Supabase

func GetProjectIDWithFallback added in v1.0.30

func GetProjectIDWithFallback(workdir string) (string, error)

GetProjectIDWithFallback tries specledger.yaml first, then falls back to git remote lookup. If found via fallback, persists the ID to specledger.yaml for future use.

func GetSessionDir added in v1.0.30

func GetSessionDir(projectID, specKey string) string

GetSessionDir returns the path for sessions: ~/.specledger/{project_id}/{spec_key}/

func GetSessionMetaPath added in v1.0.30

func GetSessionMetaPath(projectID, specKey, identifier string) string

GetSessionMetaPath returns the path for session metadata file

func GetSessionPath added in v1.0.30

func GetSessionPath(projectID, specKey, identifier string) string

GetSessionPath returns the full path for a session file Path: ~/.specledger/{project_id}/{spec_key}/{identifier}.json.gz

func GetSessionStatePath

func GetSessionStatePath() string

GetSessionStatePath returns the path to the session state file

func GetTranscriptSize

func GetTranscriptSize(transcriptPath string) (int64, error)

GetTranscriptSize returns the size of the transcript file

func IsGitCommit

func IsGitCommit(toolInput string) bool

IsGitCommit checks if the tool input contains a git commit command (but not amend) Handles chained commands like "git add . && git commit -m 'msg'"

func ListLocalSessions added in v1.0.30

func ListLocalSessions(projectID, specKey string) ([]string, error)

ListLocalSessions lists all local sessions for a project/branch

func LogCaptureError added in v1.0.40

func LogCaptureError(entry CaptureErrorEntry)

LogCaptureError logs a capture error to both local file and Sentry. Local write happens first (guaranteed). Sentry is best-effort. This function never panics and never blocks the caller for long.

func SaveLocalSession added in v1.0.30

func SaveLocalSession(projectID, specKey, identifier string, data []byte) error

SaveLocalSession saves a session to local storage

func SaveSessionState

func SaveSessionState(state *SessionState) error

SaveSessionState saves the session state to disk

func UpdateSessionOffset

func UpdateSessionOffset(sessionID string, offset int64, commitHash string, transcriptPath string) error

UpdateSessionOffset updates the offset tracking for a session

Types

type CaptureErrorEntry added in v1.0.40

type CaptureErrorEntry struct {
	Timestamp     time.Time `json:"timestamp"`
	UserID        string    `json:"user_id"`
	ProjectID     string    `json:"project_id"`
	SessionID     string    `json:"session_id,omitempty"`
	ErrorMessage  string    `json:"error_message"`
	FeatureBranch string    `json:"feature_branch,omitempty"`
	CommitHash    string    `json:"commit_hash,omitempty"`
	RetryCount    int       `json:"retry_count"`
}

CaptureErrorEntry represents a single error log entry

type CaptureResult

type CaptureResult struct {
	Captured     bool   // whether a session was captured
	SessionID    string // the session ID if captured
	StoragePath  string // path in storage
	MessageCount int    // number of messages in the session
	SizeBytes    int64  // compressed size
	RawSizeBytes int64  // uncompressed size
	Queued       bool   // whether it was queued for later upload
	Error        error  // any error that occurred
}

CaptureResult represents the outcome of a capture operation

func Capture

func Capture(input *HookInput) *CaptureResult

Capture orchestrates the session capture flow. Implements graceful degradation: captures metadata even without transcript. Implements graceful degradation: captures metadata even without transcript

func CaptureFromStdin

func CaptureFromStdin() *CaptureResult

CaptureFromStdin reads hook input from stdin and captures the session

func CaptureTestMode added in v1.0.18

func CaptureTestMode() *CaptureResult

CaptureTestMode runs a test capture simulation for manual testing

type CreateSessionInput

type CreateSessionInput struct {
	ProjectID     string        `json:"project_id"`
	FeatureBranch string        `json:"feature_branch"`
	CommitHash    *string       `json:"commit_hash,omitempty"`
	TaskID        *string       `json:"task_id,omitempty"`
	AuthorID      string        `json:"author_id"`
	StoragePath   string        `json:"storage_path"`
	Status        SessionStatus `json:"status"`
	SizeBytes     int64         `json:"size_bytes"`
	RawSizeBytes  int64         `json:"raw_size_bytes"`
	MessageCount  int           `json:"message_count"`
}

CreateSessionInput represents the input for creating a session

type HookInput

type HookInput struct {
	SessionID      string       `json:"session_id"`
	TranscriptPath string       `json:"transcript_path"`
	Cwd            string       `json:"cwd"`
	PermissionMode string       `json:"permission_mode,omitempty"`
	HookEventName  string       `json:"hook_event_name"`
	ToolName       string       `json:"tool_name"`
	ToolInput      ToolInput    `json:"tool_input"`    // the command that was run
	ToolResponse   ToolResponse `json:"tool_response"` // response from the tool
	ToolUseID      string       `json:"tool_use_id"`   // unique ID for this tool use
}

HookInput represents the JSON input from Claude Code hooks

func ParseHookInput

func ParseHookInput(data []byte) (*HookInput, error)

ParseHookInput parses the hook input from stdin

func (*HookInput) ToolSuccess

func (h *HookInput) ToolSuccess() bool

ToolSuccess returns true if the tool executed successfully (no interruption)

type Message

type Message struct {
	Role      string    `json:"role"`      // "user" or "assistant"
	Content   string    `json:"content"`   // message content
	Timestamp time.Time `json:"timestamp"` // when the message was sent
}

Message represents a single message in the conversation

func ComputeDelta

func ComputeDelta(transcriptPath string, lastOffset int64) ([]Message, int64, error)

ComputeDelta reads new lines from a transcript file since the last offset

type MetadataClient

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

MetadataClient handles Supabase PostgREST operations for session metadata

func NewMetadataClient

func NewMetadataClient() *MetadataClient

NewMetadataClient creates a new metadata client

func (*MetadataClient) Create

func (m *MetadataClient) Create(accessToken string, input *CreateSessionInput) (*SessionMetadata, error)

Create creates a new session metadata record

func (*MetadataClient) GetByCommitHash

func (m *MetadataClient) GetByCommitHash(accessToken string, projectID string, commitHash string) (*SessionMetadata, error)

GetByCommitHash retrieves a session by project and commit hash

func (*MetadataClient) GetByID

func (m *MetadataClient) GetByID(accessToken string, sessionID string) (*SessionMetadata, error)

GetByID retrieves a session by its ID

func (*MetadataClient) GetByTaskID

func (m *MetadataClient) GetByTaskID(accessToken string, projectID string, taskID string) (*SessionMetadata, error)

GetByTaskID retrieves a session by project and task ID

func (*MetadataClient) ListByFeature

func (m *MetadataClient) ListByFeature(accessToken string, projectID string, featureBranch string) ([]SessionMetadata, error)

ListByFeature retrieves all sessions for a feature branch

func (*MetadataClient) Query

func (m *MetadataClient) Query(accessToken string, opts *QueryOptions) ([]SessionMetadata, error)

Query queries sessions based on options

type QueryOptions

type QueryOptions struct {
	ProjectID     string
	FeatureBranch string
	CommitHash    string
	TaskID        string
	AuthorID      string
	StartDate     *time.Time
	EndDate       *time.Time
	Limit         int
	Offset        int
	OrderBy       string
	OrderDesc     bool
}

QueryOptions represents options for querying sessions

type Queue

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

Queue manages the local session upload queue

func NewQueue

func NewQueue() *Queue

NewQueue creates a new queue manager

func (*Queue) Count

func (q *Queue) Count() (int, error)

Count returns the number of sessions in the queue

func (*Queue) Dequeue

func (q *Queue) Dequeue(projectID, specKey, identifier string) error

Dequeue removes a session from the queue

func (*Queue) Enqueue

func (q *Queue) Enqueue(entry *QueueEntry, compressedData []byte) error

Enqueue adds a session to the upload queue Stores at: ~/.specledger/{project_id}/{spec_key}/{identifier}.json.gz

func (*Queue) GetQueuedSession

func (q *Queue) GetQueuedSession(projectID, specKey, identifier string) ([]byte, *QueueEntry, error)

GetQueuedSession retrieves a queued session's data and metadata

func (*Queue) List

func (q *Queue) List() ([]QueueRef, error)

List returns all pending queue references

func (*Queue) ListEntries added in v1.0.30

func (q *Queue) ListEntries() ([]*QueueEntry, error)

ListEntries returns all queue entries with their metadata

func (*Queue) ProcessQueue

func (q *Queue) ProcessQueue(accessToken string) (uploaded int, failed int, skipped int, errors []error)

ProcessQueue attempts to upload all queued sessions

func (*Queue) ShouldRetry

func (q *Queue) ShouldRetry(entry *QueueEntry) bool

ShouldRetry checks if a session should be retried

func (*Queue) UpdateRetryCount

func (q *Queue) UpdateRetryCount(projectID, specKey, identifier string) error

UpdateRetryCount updates the retry count for a queued session

type QueueEntry

type QueueEntry struct {
	SessionID     string        `json:"session_id"`
	ProjectID     string        `json:"project_id"`
	FeatureBranch string        `json:"feature_branch"`
	CommitHash    *string       `json:"commit_hash,omitempty"`
	TaskID        *string       `json:"task_id,omitempty"`
	AuthorID      string        `json:"author_id"`
	Status        SessionStatus `json:"status"`
	CreatedAt     time.Time     `json:"created_at"`
	RetryCount    int           `json:"retry_count"`
	LastRetry     *time.Time    `json:"last_retry,omitempty"`
}

QueueEntry represents a session queued for upload

type QueueIndex added in v1.0.30

type QueueIndex struct {
	Pending []QueueRef `json:"pending"`
}

QueueIndex tracks pending uploads across all projects/branches

type QueueRef added in v1.0.30

type QueueRef struct {
	ProjectID  string    `json:"project_id"`
	SpecKey    string    `json:"spec_key"`
	Identifier string    `json:"identifier"`
	QueuedAt   time.Time `json:"queued_at"`
}

QueueRef references a queued session by its location

type SessionContent

type SessionContent struct {
	Version       string    `json:"version"`        // schema version (e.g., "1.0")
	SessionID     string    `json:"session_id"`     // unique identifier
	FeatureBranch string    `json:"feature_branch"` // e.g., "010-checkpoint-session-capture"
	CommitHash    string    `json:"commit_hash"`    // git commit hash (nullable for task sessions)
	TaskID        string    `json:"task_id"`        // task ID (nullable for commit sessions)
	Author        string    `json:"author"`         // user email
	CapturedAt    time.Time `json:"captured_at"`    // when captured
	Messages      []Message `json:"messages"`       // conversation messages
}

SessionContent represents the full session data stored in Supabase Storage

type SessionMetadata

type SessionMetadata struct {
	ID            string        `json:"id"`
	ProjectID     string        `json:"project_id"`
	FeatureBranch string        `json:"feature_branch"`
	CommitHash    *string       `json:"commit_hash,omitempty"`
	TaskID        *string       `json:"task_id,omitempty"`
	AuthorID      string        `json:"author_id"`
	StoragePath   string        `json:"storage_path"`
	Status        SessionStatus `json:"status"`
	SizeBytes     int64         `json:"size_bytes"`     // compressed size
	RawSizeBytes  int64         `json:"raw_size_bytes"` // uncompressed size
	MessageCount  int           `json:"message_count"`
	CreatedAt     time.Time     `json:"created_at"`
}

SessionMetadata represents the queryable metadata stored in the database

type SessionOffsetInfo

type SessionOffsetInfo struct {
	LastOffset     int64  `json:"last_offset"`     // byte offset in transcript file
	LastCommit     string `json:"last_commit"`     // last commit hash captured
	TranscriptPath string `json:"transcript_path"` // path to the transcript file
}

SessionOffsetInfo tracks the last captured position in the transcript

func GetSessionOffset

func GetSessionOffset(sessionID string) (*SessionOffsetInfo, error)

GetSessionOffset retrieves the last offset for a session

type SessionState

type SessionState struct {
	Sessions map[string]*SessionOffsetInfo `json:"sessions"`
}

SessionState represents the local tracking state for delta computation

func LoadSessionState

func LoadSessionState() (*SessionState, error)

LoadSessionState loads the session state from disk

type SessionStatus

type SessionStatus string

SessionStatus represents the completion status of a session

const (
	StatusComplete  SessionStatus = "complete"
	StatusRejected  SessionStatus = "rejected"
	StatusAbandoned SessionStatus = "abandoned"
)

type SignedURLResponse

type SignedURLResponse struct {
	SignedURL string `json:"signedURL"`
}

SignedURLResponse represents the response from signed URL generation

type StorageClient

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

StorageClient handles Supabase Storage operations

func NewStorageClient

func NewStorageClient() *StorageClient

NewStorageClient creates a new storage client

func (*StorageClient) Download

func (s *StorageClient) Download(accessToken string, storagePath string) ([]byte, error)

Download downloads session content from Supabase Storage

func (*StorageClient) GetSignedURL

func (s *StorageClient) GetSignedURL(accessToken string, storagePath string, expiresIn int) (*SignedURLResponse, error)

GetSignedURL generates a time-limited signed URL for session content

func (*StorageClient) Upload

func (s *StorageClient) Upload(accessToken string, storagePath string, data []byte) (*UploadResponse, error)

Upload uploads compressed session content to Supabase Storage

type ToolInput added in v1.0.17

type ToolInput struct {
	Raw json.RawMessage
}

ToolInput represents the tool_input field from Claude Code hooks. For Bash tools, this is {"command": "..."}. We use json.RawMessage to handle both object and string formats.

func (*ToolInput) Command added in v1.0.17

func (t *ToolInput) Command() string

Command extracts the command string from the tool input. Handles both object format {"command": "..."} and plain string format.

func (*ToolInput) UnmarshalJSON added in v1.0.17

func (t *ToolInput) UnmarshalJSON(data []byte) error

type ToolResponse added in v1.0.30

type ToolResponse struct {
	Stdout      string `json:"stdout"`
	Stderr      string `json:"stderr"`
	Interrupted bool   `json:"interrupted"`
	ExitCode    *int   `json:"exitCode,omitempty"` // may be present for some tools
}

ToolResponse represents the tool_response field from Claude Code hooks

type TranscriptLine

type TranscriptLine struct {
	Type      string         `json:"type"`              // "user", "assistant", "tool_use", etc.
	Message   *TranscriptMsg `json:"message,omitempty"` // nested message object
	Content   string         `json:"content,omitempty"` // direct content (fallback)
	Timestamp time.Time      `json:"timestamp"`         // when recorded
	UUID      string         `json:"uuid,omitempty"`    // message UUID
	Role      string         `json:"role,omitempty"`    // alternative to type
}

TranscriptLine represents a single line from the Claude Code transcript JSONL

type TranscriptMsg

type TranscriptMsg struct {
	Role    string      `json:"role"`              // "user" or "assistant"
	Content interface{} `json:"content,omitempty"` // string or array of content blocks
}

TranscriptMsg represents the nested message in Claude Code transcripts

type UploadResponse

type UploadResponse struct {
	Key string `json:"Key"`
	ID  string `json:"Id"`
}

UploadResponse represents the response from an upload operation

Jump to

Keyboard shortcuts

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