1use serde::{Deserialize, Serialize};
2
3use crate::{KIMETSU_SCHEMA_VERSION, KimetsuResult};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct ProjectConfig {
7 pub kimetsu: KimetsuSection,
8 pub model: ModelSection,
9 pub broker: BrokerSection,
10 pub shell: ShellSection,
11 pub ingestion: IngestionSection,
12 pub run: RunSection,
13}
14
15impl ProjectConfig {
16 pub fn default_for_project(project_id: impl Into<String>) -> Self {
17 Self {
18 kimetsu: KimetsuSection {
19 project_id: project_id.into(),
20 schema_version: KIMETSU_SCHEMA_VERSION,
21 },
22 model: ModelSection::default(),
23 broker: BrokerSection::default(),
24 shell: ShellSection::default(),
25 ingestion: IngestionSection::default(),
26 run: RunSection::default(),
27 }
28 }
29
30 pub fn from_toml(value: &str) -> KimetsuResult<Self> {
31 Ok(toml::from_str(value)?)
32 }
33
34 pub fn to_toml(&self) -> KimetsuResult<String> {
35 Ok(toml::to_string_pretty(self)?)
36 }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct KimetsuSection {
41 pub project_id: String,
42 pub schema_version: i64,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct ModelSection {
47 pub provider: String,
48 pub model: String,
49 pub api_key_env: String,
50 pub max_output_tokens: u32,
51 pub temperature: f32,
52 pub request_timeout_secs: u64,
53}
54
55impl Default for ModelSection {
56 fn default() -> Self {
57 Self {
58 provider: "anthropic".to_string(),
59 model: "claude-opus-4-7".to_string(),
60 api_key_env: "ANTHROPIC_API_KEY".to_string(),
61 max_output_tokens: 8192,
62 temperature: 0.2,
63 request_timeout_secs: 120,
64 }
65 }
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct BrokerSection {
70 pub default_budget_tokens: u32,
71 pub weights: BrokerWeights,
72}
73
74impl Default for BrokerSection {
75 fn default() -> Self {
76 Self {
77 default_budget_tokens: 6000,
78 weights: BrokerWeights::default(),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct BrokerWeights {
85 pub relevance: f32,
86 pub confidence: f32,
87 pub freshness: f32,
88 pub scope: f32,
89 pub localization: Option<StageWeights>,
90 pub patch_plan: Option<StageWeights>,
91 pub verification: Option<StageWeights>,
92 pub review: Option<StageWeights>,
93 #[serde(default = "default_decay_half_life_days")]
104 pub decay_half_life_days: f32,
105}
106
107fn default_decay_half_life_days() -> f32 {
108 30.0
109}
110
111impl Default for BrokerWeights {
112 fn default() -> Self {
113 Self {
114 relevance: 0.50,
115 confidence: 0.20,
116 freshness: 0.20,
117 scope: 0.10,
118 localization: Some(StageWeights {
119 relevance: 0.70,
120 confidence: 0.10,
121 freshness: 0.10,
122 scope: 0.10,
123 }),
124 patch_plan: Some(StageWeights {
125 relevance: 0.40,
126 confidence: 0.30,
127 freshness: 0.10,
128 scope: 0.20,
129 }),
130 verification: Some(StageWeights {
131 relevance: 0.40,
132 confidence: 0.10,
133 freshness: 0.40,
134 scope: 0.10,
135 }),
136 review: Some(StageWeights {
137 relevance: 0.50,
138 confidence: 0.20,
139 freshness: 0.20,
140 scope: 0.10,
141 }),
142 decay_half_life_days: default_decay_half_life_days(),
143 }
144 }
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct StageWeights {
149 pub relevance: f32,
150 pub confidence: f32,
151 pub freshness: f32,
152 pub scope: f32,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct ShellSection {
157 pub default_timeout_secs: u64,
158 pub max_timeout_secs: u64,
159 pub env_allowlist_extra: Vec<String>,
160 pub redact_secrets: bool,
161}
162
163impl Default for ShellSection {
164 fn default() -> Self {
165 Self {
166 default_timeout_secs: 60,
167 max_timeout_secs: 600,
168 env_allowlist_extra: vec!["RUSTFLAGS".to_string(), "CARGO_HOME".to_string()],
169 redact_secrets: true,
170 }
171 }
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct IngestionSection {
176 pub max_file_bytes: u64,
177 pub extra_skip_dirs: Vec<String>,
178 pub max_total_files: u64,
179}
180
181impl Default for IngestionSection {
182 fn default() -> Self {
183 Self {
184 max_file_bytes: 524_288,
185 extra_skip_dirs: Vec::new(),
186 max_total_files: 50_000,
187 }
188 }
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct RunSection {
193 pub max_total_tool_calls: u32,
194 pub max_total_model_turns: u32,
195 pub max_total_cost_usd: f32,
196}
197
198impl Default for RunSection {
199 fn default() -> Self {
200 Self {
207 max_total_tool_calls: 60,
208 max_total_model_turns: 30,
209 max_total_cost_usd: 250.0,
210 }
211 }
212}