Skip to main content

starpod_hooks/
lib.rs

1//! # starpod-hooks — Lifecycle hook system for Starpod
2//!
3//! This crate provides the hook infrastructure for the Starpod AI assistant
4//! platform. It defines hook events, input/output types, callback mechanisms,
5//! and an execution engine with timeout, cancellation, circuit breaking,
6//! eligibility checks, and file-based discovery.
7//!
8//! ## Architecture
9//!
10//! Hooks are lifecycle callbacks that fire at specific points during agent
11//! execution. They can observe events (fire-and-forget) or control behavior
12//! (blocking with decisions).
13//!
14//! The system is built around these concepts:
15//!
16//! - **[`HookEvent`]** — enum of 18 lifecycle events (PreToolUse, PostToolUse, etc.)
17//! - **[`HookInput`]** — typed payload for each event, carrying context like session ID,
18//!   tool name, and tool input/output
19//! - **[`HookOutput`]** — return value from hooks, either async (fire-and-forget) or
20//!   sync (with decisions like approve/block)
21//! - **[`HookCallback`]** — async function signature for hook implementations
22//! - **[`HookCallbackMatcher`]** — groups callbacks with an optional regex filter,
23//!   identity (`name`), and eligibility requirements
24//! - **[`HookRegistry`]** — manages hooks by event type and runs them, with an
25//!   integrated circuit breaker and eligibility cache
26//!
27//! ## Circuit Breaker
28//!
29//! Named hooks are automatically monitored for failures. After
30//! [`CircuitBreakerConfig::max_consecutive_failures`] (default: 5) consecutive
31//! failures, the hook is "tripped" and skipped for a cooldown period (default:
32//! 60 seconds). After cooldown, one retry is allowed; a success resets the
33//! breaker, a failure re-opens it.
34//!
35//! ## Eligibility Requirements
36//!
37//! Hooks can declare [`HookRequirements`] specifying binaries that must be on
38//! PATH, environment variables that must be set, and allowed operating systems.
39//! Hooks whose requirements are not met are silently skipped. Results are cached
40//! per named hook to avoid repeated `which` syscalls.
41//!
42//! ## File-Based Discovery
43//!
44//! [`HookDiscovery`] scans directories for `<hook-name>/HOOK.md` files with
45//! TOML frontmatter. Each manifest declares the hook's event, matcher, timeout,
46//! requirements, and a shell command. The command receives [`HookInput`] as JSON
47//! on stdin and returns [`HookOutput`] as JSON on stdout.
48//!
49//! ## Quick Start
50//!
51//! ```rust
52//! use starpod_hooks::{HookRegistry, HookEvent, HookCallbackMatcher, hook_fn, HookOutput};
53//!
54//! let mut registry = HookRegistry::new();
55//!
56//! // Register a hook that fires after any Bash tool use
57//! registry.register(HookEvent::PostToolUse, vec![
58//!     HookCallbackMatcher::new(vec![
59//!         hook_fn(|input, _id, _cancel| async move {
60//!             println!("Tool used: {}", input.tool_name().unwrap_or("unknown"));
61//!             Ok(HookOutput::default())
62//!         }),
63//!     ])
64//!     .with_name("bash-logger")
65//!     .with_matcher("Bash")
66//!     .with_timeout(30),
67//! ]);
68//! ```
69//!
70//! ## Hook Events
71//!
72//! | Event | When it fires | Can block? |
73//! |-------|--------------|------------|
74//! | `PreToolUse` | Before tool execution | Yes — can modify input or deny |
75//! | `PostToolUse` | After successful tool execution | No |
76//! | `PostToolUseFailure` | After failed tool execution | No |
77//! | `UserPromptSubmit` | When user sends a message | Yes |
78//! | `SessionStart` | Session begins | No |
79//! | `SessionEnd` | Session ends | No |
80//! | `Stop` | Agent stopping | No |
81//! | `Notification` | System notification | No |
82//! | `SubagentStart` | Subagent launching | No |
83//! | `SubagentStop` | Subagent finished | No |
84//! | `PreCompact` | Before conversation compaction | No |
85//! | `PermissionRequest` | Permission decision needed | Yes |
86//! | `Setup` | Initial/maintenance setup | No |
87//! | `TeammateIdle` | Teammate idle | No |
88//! | `TaskCompleted` | Task finished | No |
89//! | `ConfigChange` | Configuration changed | No |
90//! | `WorktreeCreate` | Git worktree created | No |
91//! | `WorktreeRemove` | Git worktree removed | No |
92
93pub mod callback;
94pub mod circuit_breaker;
95pub mod discovery;
96pub mod eligibility;
97pub mod error;
98pub mod event;
99pub mod input;
100pub mod output;
101pub mod permissions;
102pub mod runner;
103
104// Re-export main public API
105pub use callback::{hook_fn, HookCallback, HookCallbackMatcher};
106pub use circuit_breaker::{BreakerStatus, CircuitBreaker, CircuitBreakerConfig};
107pub use discovery::{HookDiscovery, HookManifest};
108pub use eligibility::{EligibilityError, HookRequirements};
109pub use error::HookError;
110pub use event::HookEvent;
111pub use input::{
112    BaseHookInput, CompactTriggerType, ConfigChangeSource, HookInput, SessionStartSource,
113    SetupTrigger,
114};
115pub use output::{
116    AsyncHookOutput, HookDecision, HookOutput, HookSpecificOutput, PermissionRequestDecision,
117    SyncHookOutput,
118};
119pub use permissions::{PermissionDecision, PermissionLevel, PermissionUpdate};
120pub use runner::HookRegistry;