use std::io;
use std::path::PathBuf;
use std::str::FromStr;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum HostKind {
ClaudeCode,
Codex,
Gemini,
Antigravity,
Cursor,
}
impl HostKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::ClaudeCode => "claude-code",
Self::Codex => "codex",
Self::Gemini => "gemini",
Self::Antigravity => "antigravity",
Self::Cursor => "cursor",
}
}
}
impl FromStr for HostKind {
type Err = HookError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"claude-code" => Ok(Self::ClaudeCode),
"codex" => Ok(Self::Codex),
"gemini" => Ok(Self::Gemini),
"antigravity" => Ok(Self::Antigravity),
"cursor" => Ok(Self::Cursor),
other => Err(HookError::UnknownHost(other.to_owned())),
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum HookMode {
#[default]
Advisory,
Strict,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct InstallRequest {
pub root: PathBuf,
pub host: HostKind,
#[serde(default)]
pub mode: HookMode,
pub code_system_graph_binary: PathBuf,
pub database: PathBuf,
pub workspace: String,
pub repository: String,
#[serde(default)]
pub codegraph_enabled: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct InstallReport {
pub changed: bool,
pub host_file: PathBuf,
pub state_file: PathBuf,
pub gitignore_path: Option<PathBuf>,
pub gitignore_updated: bool,
pub backups: Vec<PathBuf>,
pub warnings: Vec<String>,
pub limitation: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct HookStatus {
pub installed: bool,
pub routing_installed: bool,
pub strict_gate_installed: bool,
pub host_file: PathBuf,
pub state_file: PathBuf,
pub warnings: Vec<String>,
pub limitation: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct UninstallReport {
pub changed: bool,
pub backups: Vec<PathBuf>,
pub removed_files: Vec<PathBuf>,
pub warnings: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum RoutingIntent {
None,
LocalRepository,
Federated,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct RoutingRequest {
pub host: HostKind,
pub root: PathBuf,
pub event: serde_json::Value,
#[serde(default)]
pub codegraph_enabled: bool,
#[serde(default = "default_ttl_seconds")]
pub ttl_seconds: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct RoutingResponse {
pub intent: RoutingIntent,
pub guidance: Option<String>,
pub deduplicated: bool,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum HookError {
#[error("filesystem operation failed for `{path}`: {source}")]
Io {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("invalid host configuration `{path}`: {message}")]
InvalidConfiguration {
path: PathBuf,
message: String,
},
#[error("JSON serialization failed: {0}")]
Json(#[from] serde_json::Error),
#[error("repository root `{0}` has no final path component")]
MissingRepositoryAlias(PathBuf),
#[error("system clock is earlier than the Unix epoch")]
InvalidSystemTime,
#[error("unknown host `{0}`")]
UnknownHost(String),
#[error("hook event does not contain a top-level string `prompt`")]
MissingPrompt,
}
pub(crate) const fn default_ttl_seconds() -> u64 {
300
}