Skip to main content

code_system_graph_hooks/
types.rs

1use std::io;
2use std::path::PathBuf;
3use std::str::FromStr;
4
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9/// Agent host targeted by an installation.
10#[derive(
11    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
12)]
13#[serde(rename_all = "snake_case")]
14#[non_exhaustive]
15pub enum HostKind {
16    /// Anthropic Claude Code.
17    ClaudeCode,
18    /// `OpenAI` Codex CLI.
19    Codex,
20    /// Google Gemini CLI.
21    Gemini,
22    /// Google Antigravity IDE.
23    Antigravity,
24    /// Cursor editor and CLI.
25    Cursor,
26}
27
28impl HostKind {
29    /// Returns the stable command-line and state-file identifier.
30    #[must_use]
31    pub const fn as_str(self) -> &'static str {
32        match self {
33            Self::ClaudeCode => "claude-code",
34            Self::Codex => "codex",
35            Self::Gemini => "gemini",
36            Self::Antigravity => "antigravity",
37            Self::Cursor => "cursor",
38        }
39    }
40}
41
42impl FromStr for HostKind {
43    type Err = HookError;
44
45    fn from_str(value: &str) -> Result<Self, Self::Err> {
46        match value {
47            "claude-code" => Ok(Self::ClaudeCode),
48            "codex" => Ok(Self::Codex),
49            "gemini" => Ok(Self::Gemini),
50            "antigravity" => Ok(Self::Antigravity),
51            "cursor" => Ok(Self::Cursor),
52            other => Err(HookError::UnknownHost(other.to_owned())),
53        }
54    }
55}
56
57/// Failure policy for installed integration.
58#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
59#[serde(rename_all = "snake_case")]
60#[non_exhaustive]
61pub enum HookMode {
62    /// Prompt routing and hook failures never block normal host operation.
63    #[default]
64    Advisory,
65    /// Prompt routing remains advisory, while the Git pre-commit gate fails closed.
66    Strict,
67}
68
69/// Complete request for installing, inspecting, or removing one host integration.
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
71pub struct InstallRequest {
72    /// Repository worktree root in which host files are managed.
73    pub root: PathBuf,
74    /// Host whose documented project configuration is targeted.
75    pub host: HostKind,
76    /// Advisory or strict pre-commit behavior.
77    #[serde(default)]
78    pub mode: HookMode,
79    /// `Code System Graph` executable used by the strict gate and to locate its sibling hook runtime.
80    pub code_system_graph_binary: PathBuf,
81    /// `Code System Graph` `SQLite` database passed to strict staged-change analysis.
82    pub database: PathBuf,
83    /// Registered `Code System Graph` workspace name.
84    pub workspace: String,
85    /// Registered repository alias for the selected root.
86    pub repository: String,
87    /// Whether installed guidance may recommend CodeGraph-backed local exploration.
88    #[serde(default)]
89    pub codegraph_enabled: bool,
90}
91
92/// Result of an installation attempt.
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
94pub struct InstallReport {
95    /// Whether any managed file changed.
96    pub changed: bool,
97    /// Host configuration or guidance file managed by this installation.
98    pub host_file: PathBuf,
99    /// Managed state file written with restrictive permissions.
100    pub state_file: PathBuf,
101    /// Repository-local Git ignore file when the root belongs to a Git worktree.
102    pub gitignore_path: Option<PathBuf>,
103    /// Whether installation added the generated-state rule.
104    pub gitignore_updated: bool,
105    /// Files whose previous contents were backed up.
106    pub backups: Vec<PathBuf>,
107    /// Non-fatal duplicate or compatibility observations.
108    pub warnings: Vec<String>,
109    /// Host limitation when a stable prompt-hook protocol cannot carry guidance.
110    pub limitation: Option<String>,
111}
112
113/// Current state of one host integration.
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
115pub struct HookStatus {
116    /// Whether all files required by the requested mode are present and marker-owned.
117    pub installed: bool,
118    /// Whether the host routing component is installed.
119    pub routing_installed: bool,
120    /// Whether the strict pre-commit component is installed.
121    pub strict_gate_installed: bool,
122    /// Host configuration or guidance path.
123    pub host_file: PathBuf,
124    /// State path used for installation metadata and runtime deduplication.
125    pub state_file: PathBuf,
126    /// Non-fatal duplicate product-hook observations.
127    pub warnings: Vec<String>,
128    /// Host limitation when a guidance file is used instead of a prompt hook.
129    pub limitation: Option<String>,
130}
131
132/// Result of removing one host integration.
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
134pub struct UninstallReport {
135    /// Whether any marker-owned content was removed.
136    pub changed: bool,
137    /// Backups created before merging marker-owned content out of existing files.
138    pub backups: Vec<PathBuf>,
139    /// Files deleted because they contained only marker-owned content.
140    pub removed_files: Vec<PathBuf>,
141    /// Non-fatal observations made while uninstalling.
142    pub warnings: Vec<String>,
143}
144
145/// Routing category inferred only from submitted prompt text.
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
147#[serde(rename_all = "snake_case")]
148#[non_exhaustive]
149pub enum RoutingIntent {
150    /// No repository-intelligence routing signal was found.
151    None,
152    /// Work is local to one repository.
153    LocalRepository,
154    /// Work spans repositories, contracts, architecture, impact, diffs, or pull requests.
155    Federated,
156}
157
158/// Input to the host-independent prompt router.
159#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
160pub struct RoutingRequest {
161    /// Host whose documented output shape should be generated.
162    pub host: HostKind,
163    /// Repository root used only as a deduplication scope.
164    pub root: PathBuf,
165    /// Raw host event. Only the top-level `prompt` and session identifier are read.
166    pub event: serde_json::Value,
167    /// Whether guidance may recommend CodeGraph-backed local exploration.
168    #[serde(default)]
169    pub codegraph_enabled: bool,
170    /// Session/repository deduplication lifetime in seconds.
171    #[serde(default = "default_ttl_seconds")]
172    pub ttl_seconds: u64,
173}
174
175/// Host-independent routing result.
176#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
177pub struct RoutingResponse {
178    /// Classified prompt intent.
179    pub intent: RoutingIntent,
180    /// Static guidance, omitted when no routing signal exists or TTL deduplication suppresses it.
181    pub guidance: Option<String>,
182    /// Whether a prior session/repository decision suppressed duplicate guidance.
183    pub deduplicated: bool,
184}
185
186/// Error returned by host installation and routing APIs.
187#[derive(Debug, Error)]
188#[non_exhaustive]
189pub enum HookError {
190    /// Filesystem operation failed.
191    #[error("filesystem operation failed for `{path}`: {source}")]
192    Io {
193        /// Affected path.
194        path: PathBuf,
195        /// Underlying I/O error.
196        #[source]
197        source: io::Error,
198    },
199    /// Host JSON configuration was malformed or had an incompatible shape.
200    #[error("invalid host configuration `{path}`: {message}")]
201    InvalidConfiguration {
202        /// Affected host file.
203        path: PathBuf,
204        /// Validation detail.
205        message: String,
206    },
207    /// Serialization failed.
208    #[error("JSON serialization failed: {0}")]
209    Json(#[from] serde_json::Error),
210    /// The repository root has no final component from which to derive an alias.
211    #[error("repository root `{0}` has no final path component")]
212    MissingRepositoryAlias(PathBuf),
213    /// System time is earlier than the Unix epoch.
214    #[error("system clock is earlier than the Unix epoch")]
215    InvalidSystemTime,
216    /// A command-line host identifier is unknown.
217    #[error("unknown host `{0}`")]
218    UnknownHost(String),
219    /// A routing event omitted a textual top-level prompt.
220    #[error("hook event does not contain a top-level string `prompt`")]
221    MissingPrompt,
222}
223
224pub(crate) const fn default_ttl_seconds() -> u64 {
225    300
226}