1use serde::{Deserialize, Serialize};
4
5use crate::provider::ProviderType;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
9#[serde(rename_all = "snake_case")]
10pub enum RotationStrategy {
11 #[default]
12 None,
13 Random,
14 #[serde(alias = "round-robin")]
15 RoundRobin,
16}
17
18pub(crate) fn default_rotation_strategy_enum() -> RotationStrategy {
19 RotationStrategy::None
20}
21
22macro_rules! default_fn {
24 ($name:ident, $type:ty, $value:expr) => {
25 pub(crate) fn $name() -> $type {
26 $value
27 }
28 };
29}
30
31default_fn!(default_complexity_criteria, usize, 10);
36default_fn!(default_complexity_files, usize, 5);
37default_fn!(default_complexity_words, usize, 150);
38default_fn!(default_simple_criteria, usize, 1);
39default_fn!(default_simple_files, usize, 1);
40default_fn!(default_simple_words, usize, 3);
41default_fn!(default_max_retries, usize, 3);
42default_fn!(default_retry_delay_ms, u64, 60_000); default_fn!(default_backoff_multiplier, f64, 2.0);
44default_fn!(default_poll_interval_ms, u64, 5000); default_fn!(default_site_output_dir, String, "./public/".to_string());
46default_fn!(default_site_base_url, String, "/".to_string());
47default_fn!(default_site_title, String, "Project Specs".to_string());
48default_fn!(
49 default_include_statuses,
50 Vec<String>,
51 vec![
52 "completed".to_string(),
53 "in_progress".to_string(),
54 "pending".to_string(),
55 ]
56);
57default_fn!(default_true, bool, true);
58default_fn!(default_agent_weight, usize, 1);
59default_fn!(default_agent_name, String, "main".to_string());
60default_fn!(default_agent_command, String, "claude".to_string());
61default_fn!(default_max_concurrent, usize, 2);
62default_fn!(default_stagger_delay_ms, u64, 1000); default_fn!(default_stagger_jitter_ms, u64, 200); default_fn!(default_prompt, String, "bootstrap".to_string());
65default_fn!(default_branch_prefix, String, "chant/".to_string());
66default_fn!(default_main_branch, String, "main".to_string());
67
68#[derive(Debug, Clone, Deserialize, Serialize)]
74pub struct LintThresholds {
75 #[serde(default = "default_complexity_criteria")]
77 pub complexity_criteria: usize,
78 #[serde(default = "default_complexity_files")]
80 pub complexity_files: usize,
81 #[serde(default = "default_complexity_words")]
83 pub complexity_words: usize,
84 #[serde(default = "default_simple_criteria")]
86 pub simple_criteria: usize,
87 #[serde(default = "default_simple_files")]
89 pub simple_files: usize,
90 #[serde(default = "default_simple_words")]
92 pub simple_words: usize,
93}
94
95impl Default for LintThresholds {
96 fn default() -> Self {
97 Self {
98 complexity_criteria: default_complexity_criteria(),
99 complexity_files: default_complexity_files(),
100 complexity_words: default_complexity_words(),
101 simple_criteria: default_simple_criteria(),
102 simple_files: default_simple_files(),
103 simple_words: default_simple_words(),
104 }
105 }
106}
107
108fn default_lint_disable() -> Vec<String> {
110 vec!["complexity_words".to_string()]
111}
112
113#[derive(Debug, Clone, Deserialize, Serialize)]
115pub struct LintConfig {
116 #[serde(default)]
118 pub thresholds: LintThresholds,
119 #[serde(default = "default_lint_disable")]
121 pub disable: Vec<String>,
122}
123
124impl Default for LintConfig {
125 fn default() -> Self {
126 Self {
127 thresholds: LintThresholds::default(),
128 disable: default_lint_disable(),
129 }
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
135#[serde(rename_all = "lowercase")]
136pub enum OnPermanentFailure {
137 #[default]
139 Skip,
140 Stop,
142}
143
144#[derive(Debug, Clone, Deserialize, Serialize)]
146pub struct FailureConfig {
147 #[serde(default = "default_max_retries")]
149 pub max_retries: usize,
150 #[serde(default = "default_retry_delay_ms")]
152 pub retry_delay_ms: u64,
153 #[serde(default = "default_backoff_multiplier")]
155 pub backoff_multiplier: f64,
156 #[serde(default)]
158 pub retryable_patterns: Vec<String>,
159 #[serde(default)]
161 pub on_permanent_failure: OnPermanentFailure,
162}
163
164impl Default for FailureConfig {
165 fn default() -> Self {
166 Self {
167 max_retries: default_max_retries(),
168 retry_delay_ms: default_retry_delay_ms(),
169 backoff_multiplier: default_backoff_multiplier(),
170 retryable_patterns: vec![],
171 on_permanent_failure: OnPermanentFailure::default(),
172 }
173 }
174}
175
176#[derive(Debug, Clone, Deserialize, Serialize)]
178pub struct WatchConfig {
179 #[serde(default = "default_poll_interval_ms")]
181 pub poll_interval_ms: u64,
182 #[serde(default)]
184 pub failure: FailureConfig,
185 #[serde(default = "default_idle_timeout_minutes")]
187 pub idle_timeout_minutes: u64,
188}
189
190fn default_idle_timeout_minutes() -> u64 {
191 5
192}
193
194impl Default for WatchConfig {
195 fn default() -> Self {
196 Self {
197 poll_interval_ms: default_poll_interval_ms(),
198 failure: FailureConfig::default(),
199 idle_timeout_minutes: default_idle_timeout_minutes(),
200 }
201 }
202}
203
204#[derive(Debug, Clone, Deserialize, Serialize)]
206pub struct SiteIncludeConfig {
207 #[serde(default = "default_include_statuses")]
209 pub statuses: Vec<String>,
210 #[serde(default)]
212 pub labels: Vec<String>,
213}
214
215impl Default for SiteIncludeConfig {
216 fn default() -> Self {
217 Self {
218 statuses: default_include_statuses(),
219 labels: vec![],
220 }
221 }
222}
223
224#[derive(Debug, Clone, Deserialize, Serialize, Default)]
226pub struct SiteExcludeConfig {
227 #[serde(default)]
229 pub labels: Vec<String>,
230 #[serde(default)]
232 pub fields: Vec<String>,
233}
234
235#[derive(Debug, Clone, Deserialize, Serialize)]
237pub struct SiteFeaturesConfig {
238 #[serde(default = "default_true")]
240 pub changelog: bool,
241 #[serde(default = "default_true")]
243 pub dependency_graph: bool,
244 #[serde(default = "default_true")]
246 pub timeline: bool,
247 #[serde(default = "default_true")]
249 pub status_indexes: bool,
250 #[serde(default = "default_true")]
252 pub label_indexes: bool,
253}
254
255impl Default for SiteFeaturesConfig {
256 fn default() -> Self {
257 Self {
258 changelog: true,
259 dependency_graph: true,
260 timeline: true,
261 status_indexes: true,
262 label_indexes: true,
263 }
264 }
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
269#[serde(rename_all = "lowercase")]
270pub enum GraphDetailLevel {
271 Minimal,
273 Titles,
275 #[default]
277 Full,
278}
279
280#[derive(Debug, Clone, Deserialize, Serialize)]
282pub struct SiteGraphConfig {
283 #[serde(default)]
285 pub detail: GraphDetailLevel,
286}
287
288impl Default for SiteGraphConfig {
289 fn default() -> Self {
290 Self {
291 detail: GraphDetailLevel::Full,
292 }
293 }
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
298#[serde(rename_all = "lowercase")]
299pub enum TimelineGroupBy {
300 #[default]
302 Day,
303 Week,
305 Month,
307}
308
309#[derive(Debug, Clone, Deserialize, Serialize)]
311pub struct SiteTimelineConfig {
312 #[serde(default)]
314 pub group_by: TimelineGroupBy,
315 #[serde(default)]
317 pub include_pending: bool,
318}
319
320impl Default for SiteTimelineConfig {
321 fn default() -> Self {
322 Self {
323 group_by: TimelineGroupBy::Day,
324 include_pending: false,
325 }
326 }
327}
328
329#[derive(Debug, Clone, Deserialize, Serialize, Default)]
331pub struct SiteConfig {
332 #[serde(default = "default_site_output_dir")]
334 pub output_dir: String,
335 #[serde(default = "default_site_base_url")]
337 pub base_url: String,
338 #[serde(default = "default_site_title")]
340 pub title: String,
341 #[serde(default)]
343 pub include: SiteIncludeConfig,
344 #[serde(default)]
346 pub exclude: SiteExcludeConfig,
347 #[serde(default)]
349 pub features: SiteFeaturesConfig,
350 #[serde(default)]
352 pub graph: SiteGraphConfig,
353 #[serde(default)]
355 pub timeline: SiteTimelineConfig,
356}
357
358#[derive(Debug, Deserialize, Clone)]
360pub struct AgentConfig {
361 #[serde(default = "default_agent_name")]
363 pub name: String,
364 #[serde(default = "default_agent_command")]
366 pub command: String,
367 #[serde(default = "default_max_concurrent")]
369 pub max_concurrent: usize,
370 #[serde(default = "default_agent_weight")]
372 pub weight: usize,
373}
374
375impl Default for AgentConfig {
376 fn default() -> Self {
377 Self {
378 name: default_agent_name(),
379 command: default_agent_command(),
380 max_concurrent: default_max_concurrent(),
381 weight: default_agent_weight(),
382 }
383 }
384}
385
386#[derive(Debug, Deserialize, Clone)]
388pub struct ParallelConfig {
389 #[serde(default)]
391 pub agents: Vec<AgentConfig>,
392 #[serde(default = "default_stagger_delay_ms")]
394 pub stagger_delay_ms: u64,
395 #[serde(default = "default_stagger_jitter_ms")]
397 pub stagger_jitter_ms: u64,
398}
399
400impl ParallelConfig {
401 pub fn total_capacity(&self) -> usize {
403 self.agents.iter().map(|a| a.max_concurrent).sum()
404 }
405}
406
407impl Default for ParallelConfig {
408 fn default() -> Self {
409 Self {
410 agents: vec![AgentConfig::default()],
411 stagger_delay_ms: default_stagger_delay_ms(),
412 stagger_jitter_ms: default_stagger_jitter_ms(),
413 }
414 }
415}
416
417#[derive(Debug, Clone, Deserialize)]
418pub struct DefaultsConfig {
419 #[serde(default = "default_prompt")]
420 pub prompt: String,
421 #[serde(default = "default_branch_prefix")]
422 pub branch_prefix: String,
423 #[serde(default)]
425 pub model: Option<String>,
426 #[serde(default)]
428 pub split_model: Option<String>,
429 #[serde(default = "default_main_branch")]
431 pub main_branch: String,
432 #[serde(default)]
434 pub provider: ProviderType,
435 #[serde(default = "default_rotation_strategy_enum")]
437 pub rotation_strategy: RotationStrategy,
438 #[serde(default)]
440 pub prompt_extensions: Vec<String>,
441}
442
443impl Default for DefaultsConfig {
444 fn default() -> Self {
445 Self {
446 prompt: default_prompt(),
447 branch_prefix: default_branch_prefix(),
448 model: None,
449 split_model: None,
450 main_branch: default_main_branch(),
451 provider: ProviderType::Claude,
452 rotation_strategy: default_rotation_strategy_enum(),
453 prompt_extensions: vec![],
454 }
455 }
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
463 fn rotation_strategy_accepts_kebab_case() {
464 let json = r#""round-robin""#;
465 let strategy: RotationStrategy = serde_json::from_str(json).unwrap();
466 assert_eq!(strategy, RotationStrategy::RoundRobin);
467 }
468
469 #[test]
470 fn rotation_strategy_accepts_snake_case() {
471 let json = r#""round_robin""#;
472 let strategy: RotationStrategy = serde_json::from_str(json).unwrap();
473 assert_eq!(strategy, RotationStrategy::RoundRobin);
474 }
475}