hooks

package
v0.5.4 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const ManagedCommand = "semantica"

ManagedCommand is the CLI command provider hooks should invoke by default. It intentionally uses the bare executable name so hook configs survive moves between install locations like Homebrew, curl installs, and local rebuilds.

Variables

View Source
var CWDKey = cwdKeyType{}

CWDKey carries the working directory from capture state into ReadFromOffset for providers whose transcripts don't embed a project path.

View Source
var CaptureTimestampKey = captureTimestampKeyType{}

CaptureTimestampKey carries the capture state's unix-ms timestamp into ReadFromOffset for turn-scoped enrichment.

View Source
var ErrNoCaptureState = errors.New("no capture state")

ErrNoCaptureState is returned when no capture state file exists for a session.

View Source
var HookEventTypeKey = hookEventTypeKeyType{}

HookEventTypeKey carries the current lifecycle event type into provider transcript preparation.

View Source
var HookTimestampKey = hookTimestampKeyType{}

HookTimestampKey carries the current hook timestamp into provider transcript preparation.

View Source
var ModelKey = modelKeyType{}

ModelKey carries the hook event's model name into ReadFromOffset.

Functions

func CWDFromContext

func CWDFromContext(ctx context.Context) string

CWDFromContext extracts the working directory from the context, or "" if absent.

func CaptureAndRoute

func CaptureAndRoute(ctx context.Context, provider HookProvider, event *Event, bh *broker.Handle, blobStore *blobs.Store) error

CaptureAndRoute reads a transcript delta, routes the resulting events, and advances the saved offset only after every repo write succeeds.

func CaptureDirWritable added in v0.3.6

func CaptureDirWritable() error

CaptureDirWritable probes whether the global capture directory is writable.

func DeleteCaptureState

func DeleteCaptureState(sessionID string) error

DeleteCaptureState removes a capture state file by session ID.

func DeleteCaptureStateByKey

func DeleteCaptureStateByKey(key string) error

DeleteCaptureStateByKey removes a capture state file by its key.

func Dispatch

func Dispatch(ctx context.Context, provider HookProvider, event *Event, bh *broker.Handle, blobStore *blobs.Store) error

Dispatch routes a normalized hook event.

func ExtractBinary added in v0.3.3

func ExtractBinary(command string) string

ExtractBinary returns the binary path from a hook command string. Handles both guarded ("if command -v X ...; then X capture ...; fi") and unguarded ("X capture ...") formats.

func GuardedCommand added in v0.3.3

func GuardedCommand(bin, args string) string

GuardedCommand wraps a capture command with a shell guard that silently no-ops when the binary is not on PATH. Ensures the hook never blocks the agent and never produces errors for teammates who don't have Semantica.

func HookTimestampFromContext

func HookTimestampFromContext(ctx context.Context) int64

HookTimestampFromContext extracts the current hook timestamp in unix-ms, or 0 if absent.

func MarshalCompactJSON added in v0.3.8

func MarshalCompactJSON(v any) ([]byte, error)

MarshalCompactJSON is the compact counterpart for json.RawMessage fragments embedded inside settings documents.

func MarshalSettingsJSON added in v0.3.8

func MarshalSettingsJSON(v any) ([]byte, error)

MarshalSettingsJSON serializes hook settings as indented JSON without HTML escaping, keeping shell commands readable.

func ModelFromContext

func ModelFromContext(ctx context.Context) string

ModelFromContext extracts the model name from the context, or "" if absent.

func SaveCaptureState

func SaveCaptureState(state *CaptureState) error

SaveCaptureState writes a capture state through a unique temp file.

Multiple hook processes can update the same session concurrently, so each writer gets its own staging path. The live file is replaced only after the JSON has been fully written and closed. Temp files end in .json.tmp so LoadActiveCaptureStates ignores in-flight writes.

Types

type CaptureState

type CaptureState struct {
	SessionID        string `json:"session_id"`
	StateKey         string `json:"state_key,omitempty"` // Override key; defaults to SessionID.
	Provider         string `json:"provider"`
	TranscriptRef    string `json:"transcript_ref"`
	TranscriptOffset int    `json:"transcript_offset"`
	Timestamp        int64  `json:"timestamp"`

	TurnID            string `json:"turn_id,omitempty"`
	PromptSubmittedAt int64  `json:"prompt_submitted_at,omitempty"`
	CWD               string `json:"cwd,omitempty"` // working directory from hook payload
}

CaptureState tracks the current offset for an active transcript.

func LoadActiveCaptureStates

func LoadActiveCaptureStates() ([]*CaptureState, error)

LoadActiveCaptureStates scans all capture state files.

func LoadCaptureState

func LoadCaptureState(sessionID string) (*CaptureState, error)

LoadCaptureState reads a capture state file for the given session. Returns ErrNoCaptureState if the file does not exist.

func LoadCaptureStateByKey

func LoadCaptureStateByKey(key string) (*CaptureState, error)

LoadCaptureStateByKey reads a capture state file by its key. Returns ErrNoCaptureState if the file does not exist.

func (*CaptureState) Key

func (s *CaptureState) Key() string

Key returns the state file key.

type CwdGatedProvider added in v0.5.0

type CwdGatedProvider interface {
	// ShouldCapture inspects the raw hook stdin payload and returns true
	// when the session originates from a repo that should be captured.
	// The payload is the unmodified bytes that ParseHookEvent would
	// otherwise consume; implementations must not retain it. Returning
	// false suppresses every downstream side effect for this invocation.
	//
	// activeRepos is the broker's current set of registered, active
	// repos. Implementations match the payload's working directory
	// against this set using whatever canonicalization rules fit the
	// provider (typically resolving the cwd to a git repo root).
	//
	// Errors are treated as "do not capture" by the capture entrypoint;
	// because that entrypoint suppresses every downstream side effect
	// when the gate denies, errors do NOT reach the hook-error log
	// either (logging a parse failure here would itself leak that a
	// hook fired outside any registered repo, defeating the privacy
	// boundary). Implementations should still return errors for genuine
	// malformed input so callers writing structured tests can
	// distinguish "deny" from "broken". Routine misses (cwd outside
	// any registered repo) return false with a nil error.
	ShouldCapture(ctx context.Context, payload []byte, activeRepos []broker.RegisteredRepo) (bool, error)
}

CwdGatedProvider is an optional interface for providers whose hook configuration may fire on sessions outside the user's registered repos. When implemented, the capture entrypoint inspects the raw stdin payload before parsing or dispatching and exits cleanly when the session's cwd does not resolve to an enabled repo - no transcript reading, no broker writes, no error logging.

Providers that prefer the default behavior (any registered repo gates capture, regardless of the session's cwd) simply do not implement this interface.

type DirectHookEmitter

type DirectHookEmitter interface {
	// BuildHookEvents constructs RawEvents from a hook Event's payload fields.
	// Called by the dispatcher for ToolStepCompleted, SubagentPromptSubmitted,
	// SubagentCompleted, and optionally PromptSubmitted. The provider stores
	// blobs via bs and returns fully populated RawEvents ready for routing and
	// writing.
	BuildHookEvents(ctx context.Context, event *Event, bs api.BlobPutter) ([]broker.RawEvent, error)
}

DirectHookEmitter is an optional interface for providers that can emit RawEvents directly from hook payloads without waiting for transcript replay. Used by providers that expose structured hook payloads for prompt and tool events such as Write, Edit, Bash, and Agent.

type DiscoveryContext added in v0.3.8

type DiscoveryContext struct {
	// Cwd is the parent session's working directory.
	Cwd string

	// PromptTime is the parent's PromptSubmitted timestamp in unix-ms.
	PromptTime int64

	// StopTime is the upper bound of the parent's active window in unix-ms.
	StopTime int64

	// ParentSessionID is the parent's hook session id.
	ParentSessionID string

	// ParentAgentName is the parent's agent name when the provider exposes it.
	ParentAgentName string
}

DiscoveryContext carries lifecycle-known facts about the parent turn for providers that store child transcripts in a directory shared with unrelated sessions and need extra signals to disambiguate. Fields may be empty; implementations must tolerate that rather than fail outright.

type Event

type Event struct {
	Type          EventType
	SessionID     string
	TranscriptRef string // path to transcript file
	Prompt        string // user prompt (PromptSubmitted only)
	Model         string // LLM model name
	Timestamp     int64  // unix ms, from hook payload or time.Now()
	ToolUseID     string // for subagent events and tool steps
	SubagentID    string
	Metadata      map[string]string

	// Step capture fields (ToolStepCompleted, SubagentPromptSubmitted).
	TurnID       string          // resolved from capture state or set by dispatcher
	CWD          string          // working directory from hook payload
	ToolName     string          // Write, Edit, Bash, Agent, etc.
	ToolInput    json.RawMessage // raw tool_input from hook payload
	ToolResponse json.RawMessage // raw tool_response from hook payload
}

Event is the provider-agnostic representation of an agent lifecycle event. Produced by HookProvider.ParseHookEvent from provider-specific stdin JSON.

type EventType

type EventType int

EventType represents a normalized agent lifecycle event.

const (
	PromptSubmitted EventType = iota
	AgentCompleted
	SessionOpened
	SessionClosed
	ContextCompacted
	SubagentSpawned
	SubagentCompleted
	ToolStepCompleted       // state-changing PostToolUse (Write, Edit, Bash)
	SubagentPromptSubmitted // PreToolUse[Agent] prompt event
	IncrementalCapture      // mid-turn trigger to scan transcript from saved offset
)

func HookEventTypeFromContext

func HookEventTypeFromContext(ctx context.Context) (EventType, bool)

HookEventTypeFromContext extracts the current lifecycle event type from the context, or false if absent.

func (EventType) HookPhase

func (t EventType) HookPhase() string

HookPhase returns a short stable string for the event's lifecycle phase. Used by providers to disambiguate event IDs when the same tool_use_id appears in both a pre and post hook (e.g., PreToolUse[Agent] and PostToolUse[Agent] share a tool_use_id but are different events).

type HookProvider

type HookProvider interface {
	// Name returns the provider identifier (e.g., "claude-code").
	Name() string

	// DisplayName returns a human-friendly label (e.g., "Claude Code").
	DisplayName() string

	// IsAvailable reports whether the provider can be discovered on this
	// machine, either via an executable or provider-specific local state.
	IsAvailable() bool

	// InstallHooks writes hook configuration to the provider's config file
	// (e.g., <repoRoot>/.claude/settings.local.json). Returns the number
	// of hooks installed.
	InstallHooks(ctx context.Context, repoRoot string, binaryPath string) (int, error)

	// UninstallHooks removes Semantica hooks from the provider's repo-local
	// config file.
	UninstallHooks(ctx context.Context, repoRoot string) error

	// AreHooksInstalled checks if Semantica hooks are configured in the
	// given repo.
	AreHooksInstalled(ctx context.Context, repoRoot string) bool

	// HookBinary returns the executable name or path configured in the
	// provider's hook settings for the given repo.
	// Returns only the binary token, not the full command line with arguments.
	// Used by health checks to verify the binary is actually reachable via
	// exec.LookPath.
	HookBinary(ctx context.Context, repoRoot string) (string, error)

	// ParseHookEvent parses stdin JSON into a normalized Event.
	ParseHookEvent(ctx context.Context, hookName string, stdin io.Reader) (*Event, error)

	// TranscriptOffset returns the current position in the transcript
	// (line count for JSONL, message count for JSON).
	// Called during PromptSubmitted to capture the offset before the agent acts.
	TranscriptOffset(ctx context.Context, transcriptRef string) (int, error)

	// ReadFromOffset reads the transcript starting at the given offset and
	// returns parsed RawEvents. This is the enrichment step - extracting
	// file paths, token usage, tool calls from the provider's format.
	ReadFromOffset(ctx context.Context, transcriptRef string, offset int, bs api.BlobPutter) ([]broker.RawEvent, int, error)
}

HookProvider is implemented by each agent provider for hook-based capture.

type Registry added in v0.5.1

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

Registry is the explicit-injection container for hook providers. Production wiring lives in internal/providers/composition.go, which builds a Registry over the full canonical set via NewHookRegistry. Tests construct their own Registry inline with NewRegistry over just the providers they need. There is no package-level default registry; callers always pass an explicit instance.

Get and List are safe for concurrent reads after construction; the constructor copies its arguments into an internal map so callers can mutate the input slice without affecting the registry.

func NewRegistry added in v0.5.1

func NewRegistry(providers ...HookProvider) *Registry

NewRegistry constructs a Registry over the given hook providers. Order of List() output is canonical (see providerOrder), not the argument order, so anchors that want deterministic iteration get the same order every consumer sees.

func (*Registry) Get added in v0.5.1

func (r *Registry) Get(name string) HookProvider

Get returns the registered provider for the given name, or nil when nothing is registered under that name. Callers must handle the nil case (a hook payload may report a provider this binary wasn't built with, or a future provider that's unknown today).

func (*Registry) List added in v0.5.1

func (r *Registry) List() []HookProvider

List returns the registered providers in canonical order.

func (*Registry) ListAvailable added in v0.5.1

func (r *Registry) ListAvailable() []HookProvider

ListAvailable returns the subset of registered providers whose IsAvailable() reports true on the current host, in canonical order. Used by health checks and `semantica agents` to filter the full set to what's actually installed.

type SubagentDiscoverer

type SubagentDiscoverer interface {
	// DiscoverSubagentTranscripts returns paths to all subagent transcript
	// files associated with the given parent transcript. Implementations
	// consume only the DiscoveryContext fields they need.
	DiscoverSubagentTranscripts(ctx context.Context, parentTranscriptRef string, dctx DiscoveryContext) ([]string, error)

	// SubagentStateKey returns a stable key for the subagent's capture state
	// file, derived from the subagent transcript path. Must be unique per
	// subagent and safe for use as a filename component.
	SubagentStateKey(subagentTranscriptRef string) string
}

SubagentDiscoverer is an optional interface for providers that support subagent (child) transcripts stored separately from the parent transcript. When implemented, the SubagentCompleted handler scans for child transcripts and reads each one with its own capture state.

type TranscriptPreparer

type TranscriptPreparer interface {
	// PrepareTranscript ensures the transcript file is complete and readable.
	// Called before ReadFromOffset. Must block until the file is ready or
	// a timeout is reached. Return nil on timeout so
	// capture proceeds with whatever data is available.
	PrepareTranscript(ctx context.Context, transcriptRef string) error
}

TranscriptPreparer is an optional interface for providers whose transcripts may not be fully flushed to disk when the hook fires. If implemented, PrepareTranscript is called before every ReadFromOffset.

Directories

Path Synopsis
Package builder provides shared scaffolding for the per-provider direct-emit hook handlers under internal/hooks/<provider>.
Package builder provides shared scaffolding for the per-provider direct-emit hook handlers under internal/hooks/<provider>.
Package codex provides hook-based capture for OpenAI Codex sessions.
Package codex provides hook-based capture for OpenAI Codex sessions.
Package testutil provides the shared golden-file harness for the direct-emit hook providers under internal/hooks/<provider>.
Package testutil provides the shared golden-file harness for the direct-emit hook providers under internal/hooks/<provider>.

Jump to

Keyboard shortcuts

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