// Package hooksjson implements the matcher-group hooks file that several agents // (claude-code, goose, qwen, agy, droid) share byte-for-byte in shape. Each such // file is a JSON object with a "hooks" sub-map keyed by native event name, whose // values are matcher groups ({matcher?, hooks:[{type,command,timeout}]}). The // adapters differed only in the file path, the AO command prefix, the per-hook // timeout, and which events they install, so they describe those with a Manager // and share the install/uninstall/detect logic here. // // The read/write path preserves every top-level key and every user-defined hook // AO does not own, and writes atomically, so installing AO's hooks never clobbers // unrelated settings. package hooksjson import ( "context" "encoding/json" "errors" "fmt" "os" "path/filepath" "strings" "github.com/aoagents/agent-orchestrator/backend/internal/adapters/agent/hookutil" ) // HookEntry is one command hook inside a matcher group. type HookEntry struct { Type string `json:"type"` Command string `json:"command"` Timeout int `json:"timeout,omitempty"` } // MatcherGroup is a set of hooks sharing one matcher. Matcher is a pointer so it // round-trips exactly: events that require a matcher (e.g. claude SessionStart's // "startup") carry one; events that omit it serialize without the key. type MatcherGroup struct { Matcher *string `json:"matcher,omitempty"` Hooks []HookEntry `json:"hooks"` } // HookSpec describes one hook AO installs: the native event it attaches to, its // optional matcher, and the command to run. Adapters define these in code rather // than reading an embedded template. type HookSpec struct { Event string Matcher *string Command string } // Manager installs, removes, and detects AO's hooks in one agent's matcher-group // hooks file. Construct one per adapter with its file path, command prefix, // per-hook timeout, and managed hook set. type Manager struct { // Label prefixes error messages, e.g. "claude-code" or "goose", so the // wrapped error reads "