use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use xchecker_selectors::Selectors;
use xchecker_utils::types::ConfigSource;
pub const DEFAULT_HOOK_TIMEOUT_SECS: u64 = 60;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum OnFail {
#[default]
Warn,
Fail,
}
impl std::fmt::Display for OnFail {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Warn => write!(f, "warn"),
Self::Fail => write!(f, "fail"),
}
}
}
#[cfg_attr(not(test), allow(dead_code))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HookType {
PrePhase,
PostPhase,
}
impl std::fmt::Display for HookType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::PrePhase => write!(f, "pre_phase"),
Self::PostPhase => write!(f, "post_phase"),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct HookConfig {
pub command: String,
#[serde(default)]
pub on_fail: OnFail,
#[serde(default = "default_timeout")]
pub timeout: u64,
}
fn default_timeout() -> u64 {
DEFAULT_HOOK_TIMEOUT_SECS
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct HooksConfig {
#[serde(default)]
pub pre_phase: HashMap<String, HookConfig>,
#[serde(default)]
pub post_phase: HashMap<String, HookConfig>,
}
impl HooksConfig {
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub fn get_pre_phase_hook(&self, phase: crate::types::PhaseId) -> Option<&HookConfig> {
self.pre_phase.get(phase.as_str())
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub fn get_post_phase_hook(&self, phase: crate::types::PhaseId) -> Option<&HookConfig> {
self.post_phase.get(phase.as_str())
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub fn has_hooks(&self) -> bool {
!self.pre_phase.is_empty() || !self.post_phase.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct Config {
pub defaults: Defaults,
pub selectors: Selectors,
pub runner: RunnerConfig,
pub llm: LlmConfig,
pub phases: PhasesConfig,
#[allow(dead_code)]
pub hooks: HooksConfig,
pub security: SecurityConfig,
pub source_attribution: HashMap<String, ConfigSource>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Defaults {
pub model: Option<String>,
pub max_turns: Option<u32>,
pub packet_max_bytes: Option<usize>,
pub packet_max_lines: Option<usize>,
pub output_format: Option<String>,
pub verbose: Option<bool>,
pub phase_timeout: Option<u64>,
pub stdout_cap_bytes: Option<usize>,
pub stderr_cap_bytes: Option<usize>,
pub lock_ttl_seconds: Option<u64>,
pub debug_packet: Option<bool>,
pub allow_links: Option<bool>,
pub strict_validation: Option<bool>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LlmConfig {
pub provider: Option<String>,
pub fallback_provider: Option<String>,
pub claude: Option<ClaudeConfig>,
pub gemini: Option<GeminiConfig>,
pub openrouter: Option<OpenRouterConfig>,
pub anthropic: Option<AnthropicConfig>,
pub execution_strategy: Option<String>,
pub prompt_template: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ClaudeConfig {
pub binary: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GeminiConfig {
pub binary: Option<String>,
pub default_model: Option<String>,
pub profiles: Option<HashMap<String, GeminiProfileConfig>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GeminiProfileConfig {
pub model: Option<String>,
pub max_tokens: Option<u32>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct OpenRouterConfig {
pub api_key_env: Option<String>,
pub base_url: Option<String>,
pub model: Option<String>,
pub max_tokens: Option<u32>,
pub temperature: Option<f32>,
pub budget: Option<u32>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AnthropicConfig {
pub api_key_env: Option<String>,
pub base_url: Option<String>,
pub model: Option<String>,
pub max_tokens: Option<u32>,
pub temperature: Option<f32>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct PhaseConfig {
pub model: Option<String>,
pub max_turns: Option<u32>,
pub phase_timeout: Option<u64>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct PhasesConfig {
pub requirements: Option<PhaseConfig>,
pub design: Option<PhaseConfig>,
pub tasks: Option<PhaseConfig>,
pub review: Option<PhaseConfig>,
pub fixup: Option<PhaseConfig>,
#[serde(rename = "final")]
pub final_: Option<PhaseConfig>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RunnerConfig {
pub mode: Option<String>,
pub distro: Option<String>,
pub claude_path: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct SecurityConfig {
#[serde(default)]
pub extra_secret_patterns: Vec<String>,
#[serde(default)]
pub ignore_secret_patterns: Vec<String>,
}
impl Default for Defaults {
fn default() -> Self {
Self {
model: None,
max_turns: Some(6),
packet_max_bytes: Some(65536),
packet_max_lines: Some(1200),
output_format: Some("stream-json".to_string()),
verbose: Some(false),
phase_timeout: Some(600), stdout_cap_bytes: Some(2097152), stderr_cap_bytes: Some(262144), lock_ttl_seconds: Some(900), debug_packet: Some(false),
allow_links: Some(false),
strict_validation: None, }
}
}
impl Default for RunnerConfig {
fn default() -> Self {
Self {
mode: Some("auto".to_string()),
distro: None,
claude_path: None,
}
}
}