1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum ParallelMode {
8 #[default]
9 Speculative,
10 Partition,
11}
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct TrackConfig {
15 pub name: String,
16 pub approach: String,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub model: Option<String>,
19}
20
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct Rubric {
23 #[serde(default = "default_correctness")]
24 pub correctness: f32,
25 #[serde(default = "default_design")]
26 pub design: f32,
27 #[serde(default = "default_maintainability")]
28 pub maintainability: f32,
29 #[serde(default = "default_security")]
30 pub security: f32,
31}
32
33fn default_correctness() -> f32 {
34 0.40
35}
36fn default_design() -> f32 {
37 0.30
38}
39fn default_maintainability() -> f32 {
40 0.20
41}
42fn default_security() -> f32 {
43 0.10
44}
45
46impl Default for Rubric {
47 fn default() -> Self {
48 Self {
49 correctness: default_correctness(),
50 design: default_design(),
51 maintainability: default_maintainability(),
52 security: default_security(),
53 }
54 }
55}
56
57impl Rubric {
58 pub fn version(&self) -> String {
59 format!(
60 "c{:.2}d{:.2}m{:.2}s{:.2}",
61 self.correctness, self.design, self.maintainability, self.security
62 )
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct JudgeConfig {
68 pub model: String,
69 #[serde(default)]
70 pub rubric: Rubric,
71}
72
73#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
74#[serde(rename_all = "snake_case")]
75pub enum PreFilterKind {
76 CargoCheck,
77 CargoClippyDeny,
78}
79
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
81pub struct ParallelConfig {
82 #[serde(default)]
83 pub mode: ParallelMode,
84 pub tracks: Vec<TrackConfig>,
85 pub judge: JudgeConfig,
86 #[serde(default, skip_serializing_if = "Vec::is_empty")]
87 pub pre_filter: Vec<PreFilterKind>,
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn rubric_version_is_stable() {
96 let r = Rubric::default();
97 assert_eq!(r.version(), "c0.40d0.30m0.20s0.10");
98 }
99
100 #[test]
101 fn parallel_config_roundtrips_yaml() {
102 let yaml = r#"
103mode: speculative
104judge:
105 model: claude-opus-4-8
106tracks:
107 - name: track-a
108 approach: "functional style"
109 - name: track-b
110 approach: "performance first"
111"#;
112 let cfg: ParallelConfig = serde_yaml::from_str(yaml).unwrap();
113 assert_eq!(cfg.tracks.len(), 2);
114 assert_eq!(cfg.mode, ParallelMode::Speculative);
115 let back = serde_yaml::to_string(&cfg).unwrap();
116 let cfg2: ParallelConfig = serde_yaml::from_str(&back).unwrap();
117 assert_eq!(cfg, cfg2);
118 }
119}