Skip to main content

mur_common/
parallel.rs

1//! Parallel tracks config — extends fleet.yaml `parallel:` section.
2
3use 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}