Skip to main content

claudectl_core/
config.rs

1//! Configuration data types shared between the binary and (future) TUI crate.
2//!
3//! The binary still owns *parsing* (TOML, CLI flags, layering) — but the
4//! resulting `BrainConfig` / `IdleConfig` structs live here so downstream
5//! crates (notably the TUI extracted under #275) can hold them without
6//! depending back on the binary's `crate::config` module.
7
8/// Configuration for the optional local LLM brain.
9/// When `None`, brain is completely disabled with zero overhead.
10#[derive(Debug, Clone)]
11pub struct BrainConfig {
12    pub enabled: bool,
13    pub endpoint: String,
14    pub model: String,
15    pub auto_mode: bool,
16    pub timeout_ms: u64,
17    pub max_context_tokens: u32,
18    pub few_shot_count: usize,
19    pub max_sessions: usize,
20    pub orchestrate: bool,
21    pub orchestrate_interval_secs: u64,
22    /// Optional stronger model to escalate to when the primary (`model`)
23    /// returns a low-confidence decision. `None` ⇒ routing off; every decision
24    /// uses `model` (today's behavior). See `brain::client::infer_routed` (#370).
25    pub escalation_model: Option<String>,
26    /// Confidence below which a primary-model decision is re-run on
27    /// `escalation_model`. Only consulted when an escalation model is set.
28    pub escalation_threshold: f64,
29    /// Command prefixes that identify test-runner invocations. When one of
30    /// these fails (non-zero exit), the reaper fans the failure out to recent
31    /// brain-approved edits as a `TestFailed` outcome (#238). Empty disables
32    /// test-failure attribution.
33    pub test_runners: Vec<String>,
34}
35
36/// Default test-runner command prefixes. Matched as command-line prefix on
37/// the normalized command (whitespace-collapsed, lowercased). Users override
38/// via `test_runners` in the `[brain]` config section.
39pub fn default_test_runners() -> Vec<String> {
40    [
41        "cargo test",
42        "cargo nextest",
43        "npm test",
44        "npm run test",
45        "pnpm test",
46        "yarn test",
47        "bun test",
48        "pytest",
49        "go test",
50        "jest",
51        "vitest",
52        "mix test",
53        "rspec",
54    ]
55    .iter()
56    .map(|s| s.to_string())
57    .collect()
58}
59
60impl Default for BrainConfig {
61    fn default() -> Self {
62        Self {
63            enabled: true,
64            endpoint: "http://localhost:11434/api/generate".into(),
65            model: "gemma4:e4b".into(),
66            auto_mode: false,
67            timeout_ms: 5000,
68            max_context_tokens: 4000,
69            few_shot_count: 5,
70            max_sessions: 10,
71            orchestrate: false,
72            orchestrate_interval_secs: 30,
73            escalation_model: None,
74            escalation_threshold: 0.7,
75            test_runners: default_test_runners(),
76        }
77    }
78}
79
80/// Configuration for idle mode and unattended work queue.
81#[allow(dead_code)]
82#[derive(Debug, Clone)]
83pub struct IdleConfig {
84    pub enabled: bool,
85    pub after_idle_mins: u64,
86    pub max_concurrent: usize,
87    pub max_cost_usd: f64,
88    pub tasks: Vec<IdleTask>,
89}
90
91#[allow(dead_code)]
92#[derive(Debug, Clone)]
93pub struct IdleTask {
94    pub prompt: String,
95    pub cwd: Option<String>,
96    pub priority: u32,
97}
98
99impl Default for IdleConfig {
100    fn default() -> Self {
101        Self {
102            enabled: false,
103            after_idle_mins: 15,
104            max_concurrent: 2,
105            max_cost_usd: 5.0,
106            tasks: Vec::new(),
107        }
108    }
109}