calllog

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 6, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package calllog records every invocation of a sofia-repo tool to a shared JSON-lines log so calls can be analysed and optimised later.

Log location follows the XDG Base Directory spec: by default `$XDG_STATE_HOME/sofia/calls.jsonl` (→ `~/.local/state/sofia/calls.jsonl`). The location can be overridden with the SOFIA_LOG_DIR env var, which is what `sf history` advertises so developers can point the log at the repo's own working tree if they prefer.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Finalize

func Finalize(fallbackTool string, err error)

Finalize guarantees exactly one log entry per invocation. Tools log themselves via Start/Finish; this fills the gaps those miss — chiefly Cobra arg-validation errors that abort before a tool's RunE ever runs. Idempotent and safe: a no-op when the tool already finished.

func Fingerprint

func Fingerprint(args []string) string

Fingerprint returns a stable, sort-independent identifier for a set of argument strings. Equivalent invocations (e.g. "A B" and "B A") share a fingerprint, so history can collapse them when surfacing repeated queries. The form is a 16-hex-char SHA-256 prefix — short enough to scan, long enough to avoid collisions across normal usage.

func Path

func Path() string

Path returns the file used for the shared JSONL log. Resolution order:

  1. $SOFIA_LOG_DIR/calls.jsonl — explicit override (CI, devs that want in-tree logs).
  2. $XDG_STATE_HOME/sofia/calls.jsonl — XDG-conformant default.
  3. ~/.local/state/sofia/calls.jsonl — XDG fallback when the env var is unset (the spec's defined default).
  4. ./calls.jsonl — final fallback if we can't even discover HOME.

All sofia tools (master `sf` binary and project-specific binaries) hit the same path so history aggregates the full picture.

func ProjectTag added in v0.2.0

func ProjectTag() string

func RegisterPluginGroups added in v0.2.0

func RegisterPluginGroups(names []string)

RegisterPluginGroups records plugin group names that must be excluded from the central call-log fallback (their bare invocation only prints help). Additive and idempotent. The plugin subcommands themselves (`<plugin>.<command>`) still log, since they do real work and self-log via Start/Finish.

func Run

func Run(root *cobra.Command, fallbackTool string) error

Run executes root and logs the invocation centrally, then returns the command error to the caller (which prints/exits). fallbackTool names the tool when the command path can't — standalone binaries whose root *is* the tool. A tool that actually ran will have set its own canonical name, which takes precedence.

func SessionID added in v0.2.0

func SessionID() string

func Source added in v0.2.0

func Source() string

Source, SessionID and ProjectTag expose the same classification calllog stamps onto its own entries, so a subprocess plugin can be handed identical SOFIA_SOURCE / SOFIA_SESSION_ID / SOFIA_TAG values (see internal/plugin's invocation env) without a parallel, drifting implementation.

func Track

func Track(tool string, args []string, fn func(t *Tracker) error) error

Track wraps fn with logging in one call. The caller can update summary fields via the returned tracker before fn returns.

func UnderTest added in v0.2.0

func UnderTest() bool

UnderTest exports underTest for packages (e.g. internal/dedup) that need to adjust their own behaviour under `go test` without duplicating the .test-suffix check.

Types

type Counter

type Counter struct {
	W      io.Writer
	Count  int64
	Tokens int64
}

Counter wraps w to track both the byte count and an approximate LLM token count of everything written through it. Token cost is estimated per chunk via tokens.Estimate; chunk-boundary inaccuracy in the rare case of a Write splitting a multi-byte rune is bounded by a single token and irrelevant for analytics.

func (*Counter) Write

func (c *Counter) Write(p []byte) (int, error)

type Entry

type Entry struct {
	Timestamp    string         `json:"ts"`
	Tool         string         `json:"tool"`
	Source       string         `json:"source,omitempty"` // agent | manual | test (who invoked it)
	SessionID    string         `json:"sid,omitempty"`    // Claude Code session id (joins with `sf cc`)
	Tag          string         `json:"tag,omitempty"`    // project the call belongs to
	Args         []string       `json:"args"`
	Fingerprint  string         `json:"fp,omitempty"` // sorted+joined args for grouping equivalent invocations
	DurationMs   int64          `json:"dur_ms"`
	ExitCode     int            `json:"exit"`
	Error        string         `json:"err,omitempty"`
	OutputBytes  int64          `json:"out_bytes,omitempty"`  // size of stdout payload, in bytes
	OutputTokens int64          `json:"out_tokens,omitempty"` // approximate LLM tokens (see internal/tokens)
	Summary      map[string]any `json:"summary,omitempty"`
}

func Read

func Read() ([]Entry, error)

Read loads every entry from the shared call log, in append order (oldest first). A missing log is not an error — it returns nil. Malformed lines are skipped so a single bad write can't break a reader. Used by tools that need the raw log (e.g. `sf gripe`'s list view, `sf doctor`'s pending-feedback count); `sf history` keeps its own filtering reader on top of the same file.

type Tracker

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

Tracker measures an invocation. Pair Start(...) with .Finish(err, summary).

func Start

func Start(tool string, args []string) *Tracker

func (*Tracker) Finish

func (t *Tracker) Finish(err error)

Finish writes the log entry. Idempotent — a second call is a no-op, so a tool's own Finish and the central Finalize can't double-log. Errors writing the log are swallowed; we don't want logging to break the user's tool invocation.

func (*Tracker) RecordOutput

func (t *Tracker) RecordOutput(c *Counter)

RecordOutput copies both byte and token accumulators from a Counter in one call — the typical Run() epilogue.

func (*Tracker) SetExitCode added in v0.2.0

func (t *Tracker) SetExitCode(code int)

SetExitCode records a specific process exit code. Finish otherwise collapses any error to exit 1; a subprocess (plugin) that exited with its own non-zero code calls SetExitCode first so the real code is logged. Set before Finish.

func (*Tracker) SetOutputBytes

func (t *Tracker) SetOutputBytes(n int64)

SetOutputBytes records the size of the user-facing payload (typically stdout). History can use this to spot heavy outputs that should be trimmed or capped.

func (*Tracker) SetOutputTokens

func (t *Tracker) SetOutputTokens(n int64)

SetOutputTokens records the approximate LLM token count for the user-facing payload (see package `internal/tokens`).

func (*Tracker) SetSummary

func (t *Tracker) SetSummary(s map[string]any)

SetSummary mutates the pending summary map; safe to call multiple times.

Jump to

Keyboard shortcuts

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