Documentation
¶
Overview ¶
Package hook is the thin adapter between the Claude Code hook contract and Director's coordination core (§5.4, §15.7). ALL knowledge of the hook wire shape — the stdin JSON fields, the stdout control protocol, the exit-code semantics — is isolated here so a contract change is a one-file edit (§15.7). That includes the other harnesses' dialects: Codex and the OpenCode plugin speak the CC shapes verbatim, and Copilot differs in exactly one place (a flat additionalContext instead of CC's hookSpecificOutput wrapper — see copilotContextOutput), which is why the context writers take a flavor. The handlers (sessionstart/posttooluse/stop/sessionend) work against the typed Input/control helpers in this file and never touch raw JSON or os.Stdin.
The cardinal rule (§13 t5, §5.4): a hook must NEVER block a session on internal failure. Dispatch wraps every handler so a panic or an error is recovered, logged loudly to health/, and turned into exit 0 with no blocking output. The single deliberate non-allow is the Stop emit-guard's decision:block, and only on a confident detection.
Index ¶
Constants ¶
const ( EventSessionStart = "sessionstart" EventPostToolUse = "posttooluse" EventStop = "stop" EventSessionEnd = "sessionend" )
Hook event names. These are the suffixes the hidden `director _hook <event>` verb takes and the names the shims pass. They mirror CC's hook_event_name values, lowercased for the CLI surface.
Variables ¶
This section is empty.
Functions ¶
func Dispatch ¶
Dispatch is THE entry point the hidden `director _hook <event>` verb calls. It routes by event name to the matching handler and wraps the whole thing fail-safe: a panic or an error in parsing or in any handler is recovered, logged loudly to health/, and yields exit 0 with NO blocking output. The only path that blocks is the Stop emit-guard, which writes its block from inside the handler before returning nil.
It always returns 0 in v1: §13 t5 requires that a broken hook never blocks a session, and the only "control" Director exerts (SessionStart injection, the Stop block) travels over stdout, not the exit code. Returning int (not void) keeps room for an intentional non-zero later without touching the CLI seam.
func DispatchStdin ¶
DispatchStdin is the convenience the CLI's `_hook` verb wires to: it runs Dispatch against the process's real stdin/stdout. Kept here so the os-level wiring stays inside the adapter (the CLI just calls this with the event name and resolved hub).
Types ¶
type Input ¶
type Input struct {
// Common to every hook event.
SessionID string `json:"session_id"`
TranscriptPath string `json:"transcript_path"`
CWD string `json:"cwd"`
HookEventName string `json:"hook_event_name"`
// SessionStart: startup | resume | clear | compact.
Source string `json:"source"`
// Stop / SubagentStop: true while a prior Stop hook is still active. We must
// never block when true, or we create an infinite stop→block→stop loop.
StopHookActive bool `json:"stop_hook_active"`
// PostToolUse: the name of the tool that just ran. v1 captures only tool_name —
// it's all the nudge gate needs. CC also sends tool_input/tool_response, but we
// leave them unmodeled (json.Unmarshal ignores unmapped fields) rather than carry
// unused fields.
ToolName string `json:"tool_name"`
// PreCompact: manual | auto. Unused in v1 beyond presence.
Trigger string `json:"trigger"`
// SessionEnd only: clear | logout | prompt_input_exit | other. How the session
// ended; carried for health-log attribution, never for a branch — every reason
// reaps the row alike.
Reason string `json:"reason"`
// Agent is a Director extension, NOT a CC wire field: an adapter that
// fabricates payloads (the OpenCode plugin) names itself here so flavor
// detection doesn't depend on heuristics. CC and the Codex shims never set
// it; absent means "detect from the payload" (see agentFlavor).
Agent string `json:"agent"`
}
Input is the typed projection of CC's hook stdin JSON. Only the fields Director's handlers use are modeled; unknown fields are ignored by the JSON decoder, so a CC addition can't break parsing. This struct is the ONLY place that names CC's wire fields — change it here if the contract moves (§15.7).