Skip to main content

ralph_workflow/cli/reducer/
state.rs

1//! CLI argument state.
2//!
3//! This module defines the state structure that accumulates CLI argument values
4//! as events are processed through the reducer.
5
6use serde::{Deserialize, Serialize};
7
8/// Preset type for iteration count defaults.
9///
10/// Each preset defines default values for developer iterations and reviewer reviews.
11/// These can be overridden by explicit -D/-R flags.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
13pub enum PresetType {
14    /// Quick mode: 1 dev iteration, 1 review pass (-Q)
15    Quick,
16    /// Rapid mode: 2 dev iterations, 1 review pass (-U)
17    Rapid,
18    /// Long mode: 15 dev iterations, 10 review passes (-L)
19    Long,
20    /// Standard mode: 5 dev iterations, 2 review passes (-S)
21    Standard,
22    /// Thorough mode: 10 dev iterations, 5 review passes (-T)
23    Thorough,
24}
25
26impl PresetType {
27    /// Get the (developer_iters, reviewer_reviews) counts for this preset.
28    #[must_use]
29    pub fn iteration_counts(self) -> (u32, u32) {
30        match self {
31            PresetType::Quick => (1, 1),
32            PresetType::Rapid => (2, 1),
33            PresetType::Long => (15, 10),
34            PresetType::Standard => (5, 2),
35            PresetType::Thorough => (10, 5),
36        }
37    }
38
39    /// Get the developer iterations for this preset.
40    #[must_use]
41    pub fn developer_iters(self) -> u32 {
42        self.iteration_counts().0
43    }
44
45    /// Get the reviewer reviews for this preset.
46    #[must_use]
47    pub fn reviewer_reviews(self) -> u32 {
48        self.iteration_counts().1
49    }
50}
51
52/// CLI argument state (intermediate representation before Config).
53///
54/// This struct accumulates parsed CLI argument values as events are processed.
55/// Values of `None` indicate the argument was not specified and should fall back
56/// to config file or default values.
57#[derive(Clone, Debug, Default, Serialize, Deserialize)]
58pub struct CliState {
59    // ===== Verbosity =====
60    /// Explicit verbosity level (0-4)
61    pub verbosity: Option<u8>,
62    /// Quiet mode flag (--quiet)
63    pub quiet_mode: bool,
64    /// Full mode flag (--full)
65    pub full_mode: bool,
66    /// Debug mode flag (--debug)
67    pub debug_mode: bool,
68
69    // ===== Preset =====
70    /// Which preset was applied (last wins if multiple specified)
71    pub preset_applied: Option<PresetType>,
72
73    // ===== Iteration Counts =====
74    /// Explicit developer iterations (-D/--developer-iters)
75    pub developer_iters: Option<u32>,
76    /// Explicit reviewer reviews (-R/--reviewer-reviews)
77    pub reviewer_reviews: Option<u32>,
78
79    // ===== Agent Selection =====
80    /// Developer agent name
81    pub developer_agent: Option<String>,
82    /// Reviewer agent name
83    pub reviewer_agent: Option<String>,
84    /// Developer model override
85    pub developer_model: Option<String>,
86    /// Reviewer model override
87    pub reviewer_model: Option<String>,
88    /// Developer provider override
89    pub developer_provider: Option<String>,
90    /// Reviewer provider override
91    pub reviewer_provider: Option<String>,
92    /// Reviewer JSON parser override
93    pub reviewer_json_parser: Option<String>,
94
95    // ===== Configuration =====
96    /// Isolation mode setting (None = use config default, Some(false) = disabled)
97    pub isolation_mode: Option<bool>,
98    /// Review depth level
99    pub review_depth: Option<String>,
100    /// Git user name for commits
101    pub git_user_name: Option<String>,
102    /// Git user email for commits
103    pub git_user_email: Option<String>,
104    /// Show streaming metrics flag
105    pub streaming_metrics: bool,
106
107    // ===== Agent Preset =====
108    /// Named agent preset (default, opencode)
109    pub agent_preset: Option<String>,
110
111    // ===== Processing Status =====
112    /// Whether CLI processing is complete
113    pub complete: bool,
114}
115
116impl CliState {
117    /// Create a new initial state.
118    #[must_use]
119    pub fn initial() -> Self {
120        Self::default()
121    }
122
123    /// Resolve final developer iterations count.
124    ///
125    /// Priority order:
126    /// 1. Explicit -D/--developer-iters flag
127    /// 2. Preset default (if a preset was applied)
128    /// 3. Config default (passed as argument)
129    #[must_use]
130    pub fn resolved_developer_iters(&self, config_default: u32) -> u32 {
131        self.developer_iters.unwrap_or_else(|| {
132            self.preset_applied
133                .map(PresetType::developer_iters)
134                .unwrap_or(config_default)
135        })
136    }
137
138    /// Resolve final reviewer reviews count.
139    ///
140    /// Priority order:
141    /// 1. Explicit -R/--reviewer-reviews flag
142    /// 2. Preset default (if a preset was applied)
143    /// 3. Config default (passed as argument)
144    #[must_use]
145    pub fn resolved_reviewer_reviews(&self, config_default: u32) -> u32 {
146        self.reviewer_reviews.unwrap_or_else(|| {
147            self.preset_applied
148                .map(PresetType::reviewer_reviews)
149                .unwrap_or(config_default)
150        })
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_preset_iteration_counts() {
160        assert_eq!(PresetType::Quick.iteration_counts(), (1, 1));
161        assert_eq!(PresetType::Rapid.iteration_counts(), (2, 1));
162        assert_eq!(PresetType::Long.iteration_counts(), (15, 10));
163        assert_eq!(PresetType::Standard.iteration_counts(), (5, 2));
164        assert_eq!(PresetType::Thorough.iteration_counts(), (10, 5));
165    }
166
167    #[test]
168    fn test_preset_individual_accessors() {
169        assert_eq!(PresetType::Quick.developer_iters(), 1);
170        assert_eq!(PresetType::Quick.reviewer_reviews(), 1);
171        assert_eq!(PresetType::Long.developer_iters(), 15);
172        assert_eq!(PresetType::Long.reviewer_reviews(), 10);
173    }
174
175    #[test]
176    fn test_initial_state() {
177        let state = CliState::initial();
178        assert!(!state.complete);
179        assert!(state.preset_applied.is_none());
180        assert!(state.developer_iters.is_none());
181        assert!(state.reviewer_reviews.is_none());
182        assert!(!state.quiet_mode);
183        assert!(!state.streaming_metrics);
184    }
185
186    #[test]
187    fn test_resolved_iters_explicit_override() {
188        let mut state = CliState::initial();
189        state.preset_applied = Some(PresetType::Quick); // Would give 1
190        state.developer_iters = Some(10); // Explicit override
191        state.reviewer_reviews = Some(5); // Explicit override
192
193        // Explicit values take precedence over preset
194        assert_eq!(state.resolved_developer_iters(99), 10);
195        assert_eq!(state.resolved_reviewer_reviews(99), 5);
196    }
197
198    #[test]
199    fn test_resolved_iters_preset_fallback() {
200        let mut state = CliState::initial();
201        state.preset_applied = Some(PresetType::Long);
202
203        // Preset values used when no explicit override
204        assert_eq!(state.resolved_developer_iters(99), 15);
205        assert_eq!(state.resolved_reviewer_reviews(99), 10);
206    }
207
208    #[test]
209    fn test_resolved_iters_config_fallback() {
210        let state = CliState::initial();
211
212        // Config defaults used when no preset or explicit override
213        assert_eq!(state.resolved_developer_iters(5), 5);
214        assert_eq!(state.resolved_reviewer_reviews(2), 2);
215    }
216
217    #[test]
218    fn test_state_serialization() {
219        let mut state = CliState::initial();
220        state.preset_applied = Some(PresetType::Thorough);
221        state.developer_agent = Some("claude".to_string());
222
223        let json = serde_json::to_string(&state).unwrap();
224        let deserialized: CliState = serde_json::from_str(&json).unwrap();
225
226        assert_eq!(state.preset_applied, deserialized.preset_applied);
227        assert_eq!(state.developer_agent, deserialized.developer_agent);
228    }
229}