Skip to main content

ralph_workflow/cli/reducer/
apply.rs

1//! Apply CLI state to Config.
2//!
3//! This module handles the final step of the CLI processing pipeline:
4//! taking a CliState and applying its values to the actual Config struct.
5
6use super::state::CliState;
7use crate::config::{Config, ReviewDepth, Verbosity};
8
9/// Apply CLI state to configuration.
10///
11/// This function takes the accumulated CLI state and applies all values
12/// to the Config struct, respecting priority rules:
13///
14/// - Verbosity: debug > full > quiet > explicit > base
15/// - Iterations: explicit -D/-R > preset > config default
16/// - Agent settings: CLI > config > defaults
17///
18/// # Arguments
19///
20/// * `cli_state` - The CLI state after processing all events
21/// * `config` - The configuration to modify (will be updated in-place)
22pub fn apply_cli_state_to_config(cli_state: &CliState, config: &mut Config) {
23    // ===== Verbosity =====
24    // Priority: debug > full > quiet > explicit > base
25    if cli_state.debug_mode {
26        config.verbosity = Verbosity::Debug;
27    } else if cli_state.full_mode {
28        config.verbosity = Verbosity::Full;
29    } else if cli_state.quiet_mode {
30        config.verbosity = Verbosity::Quiet;
31    } else if let Some(level) = cli_state.verbosity {
32        config.verbosity = Verbosity::from(level);
33    }
34
35    // ===== Iteration Counts =====
36    // Resolve using CliState's built-in logic (explicit > preset > default)
37    let current_developer_iters = config.developer_iters;
38    let current_reviewer_reviews = config.reviewer_reviews;
39
40    config.developer_iters = cli_state.resolved_developer_iters(current_developer_iters);
41    config.reviewer_reviews = cli_state.resolved_reviewer_reviews(current_reviewer_reviews);
42
43    // ===== Agent Selection =====
44    if let Some(ref agent) = cli_state.developer_agent {
45        config.developer_agent = Some(agent.clone());
46    }
47    if let Some(ref agent) = cli_state.reviewer_agent {
48        config.reviewer_agent = Some(agent.clone());
49    }
50
51    // ===== Model and Provider Overrides =====
52    if let Some(ref model) = cli_state.developer_model {
53        config.developer_model = Some(model.clone());
54    }
55    if let Some(ref model) = cli_state.reviewer_model {
56        config.reviewer_model = Some(model.clone());
57    }
58    if let Some(ref provider) = cli_state.developer_provider {
59        config.developer_provider = Some(provider.clone());
60    }
61    if let Some(ref provider) = cli_state.reviewer_provider {
62        config.reviewer_provider = Some(provider.clone());
63    }
64    if let Some(ref parser) = cli_state.reviewer_json_parser {
65        config.reviewer_json_parser = Some(parser.clone());
66    }
67
68    // ===== Configuration Flags =====
69    // Isolation mode: explicit CLI flag > config default
70    if let Some(isolation_mode) = cli_state.isolation_mode {
71        config.isolation_mode = isolation_mode;
72    }
73
74    // Review depth
75    if let Some(ref depth) = cli_state.review_depth {
76        if let Some(parsed) = ReviewDepth::from_str(depth) {
77            config.review_depth = parsed;
78        }
79        // Invalid depth values are silently ignored (will use config default)
80    }
81
82    // Git identity (highest priority in resolution chain)
83    if let Some(ref name) = cli_state.git_user_name {
84        config.git_user_name = Some(name.clone());
85    }
86    if let Some(ref email) = cli_state.git_user_email {
87        config.git_user_email = Some(email.clone());
88    }
89
90    // Streaming metrics
91    if cli_state.streaming_metrics {
92        config.show_streaming_metrics = true;
93    }
94
95    // ===== Agent Presets =====
96    // Handle named presets (default, opencode)
97    if let Some(ref preset) = cli_state.agent_preset {
98        match preset.as_str() {
99            "default" => {
100                // No override - use agent_chain defaults from config
101            }
102            "opencode" => {
103                config.developer_agent = Some("opencode".to_string());
104                config.reviewer_agent = Some("opencode".to_string());
105            }
106            _ => {
107                // Unknown preset - ignore
108            }
109        }
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use crate::config::types::{BehavioralFlags, FeatureFlags};
117
118    fn create_test_config() -> Config {
119        Config {
120            developer_agent: None,
121            reviewer_agent: None,
122            developer_cmd: None,
123            reviewer_cmd: None,
124            commit_cmd: None,
125            developer_model: None,
126            reviewer_model: None,
127            developer_provider: None,
128            reviewer_provider: None,
129            reviewer_json_parser: None,
130            features: FeatureFlags {
131                checkpoint_enabled: true,
132                force_universal_prompt: false,
133            },
134            developer_iters: 5,
135            reviewer_reviews: 2,
136            fast_check_cmd: None,
137            full_check_cmd: None,
138            behavior: BehavioralFlags {
139                interactive: true,
140                auto_detect_stack: true,
141                strict_validation: false,
142            },
143            prompt_path: std::path::PathBuf::from(".agent/last_prompt.txt"),
144            user_templates_dir: None,
145            developer_context: 1,
146            reviewer_context: 0,
147            verbosity: Verbosity::Verbose,
148            review_depth: ReviewDepth::Standard,
149            isolation_mode: true,
150            git_user_name: None,
151            git_user_email: None,
152            show_streaming_metrics: false,
153            review_format_retries: 5,
154            max_dev_continuations: Some(2),
155            max_xsd_retries: Some(10),
156            max_same_agent_retries: Some(2),
157        }
158    }
159
160    #[test]
161    fn test_apply_verbosity_debug() {
162        let cli_state = CliState {
163            debug_mode: true,
164            ..Default::default()
165        };
166
167        let mut config = create_test_config();
168        apply_cli_state_to_config(&cli_state, &mut config);
169
170        assert_eq!(config.verbosity, Verbosity::Debug);
171    }
172
173    #[test]
174    fn test_apply_verbosity_full() {
175        let cli_state = CliState {
176            full_mode: true,
177            ..Default::default()
178        };
179
180        let mut config = create_test_config();
181        apply_cli_state_to_config(&cli_state, &mut config);
182
183        assert_eq!(config.verbosity, Verbosity::Full);
184    }
185
186    #[test]
187    fn test_apply_verbosity_quiet() {
188        let cli_state = CliState {
189            quiet_mode: true,
190            ..Default::default()
191        };
192
193        let mut config = create_test_config();
194        apply_cli_state_to_config(&cli_state, &mut config);
195
196        assert_eq!(config.verbosity, Verbosity::Quiet);
197    }
198
199    #[test]
200    fn test_apply_verbosity_explicit() {
201        let cli_state = CliState {
202            verbosity: Some(3),
203            ..Default::default()
204        };
205
206        let mut config = create_test_config();
207        apply_cli_state_to_config(&cli_state, &mut config);
208
209        assert_eq!(config.verbosity, Verbosity::Full); // level 3 = Full
210    }
211
212    #[test]
213    fn test_apply_iters_from_preset() {
214        use super::super::state::PresetType;
215
216        let cli_state = CliState {
217            preset_applied: Some(PresetType::Long),
218            ..Default::default()
219        };
220
221        let mut config = create_test_config();
222        config.developer_iters = 5;
223        config.reviewer_reviews = 2;
224
225        apply_cli_state_to_config(&cli_state, &mut config);
226
227        assert_eq!(config.developer_iters, 15);
228        assert_eq!(config.reviewer_reviews, 10);
229    }
230
231    #[test]
232    fn test_apply_iters_explicit_override_preset() {
233        use super::super::state::PresetType;
234
235        let cli_state = CliState {
236            preset_applied: Some(PresetType::Quick), // Would give 1, 1
237            developer_iters: Some(7),                // Explicit override
238            reviewer_reviews: Some(3),               // Explicit override
239            ..Default::default()
240        };
241
242        let mut config = create_test_config();
243        apply_cli_state_to_config(&cli_state, &mut config);
244
245        // Explicit values should override preset
246        assert_eq!(config.developer_iters, 7);
247        assert_eq!(config.reviewer_reviews, 3);
248    }
249
250    #[test]
251    fn test_apply_developer_agent() {
252        let cli_state = CliState {
253            developer_agent: Some("claude".to_string()),
254            ..Default::default()
255        };
256
257        let mut config = create_test_config();
258        apply_cli_state_to_config(&cli_state, &mut config);
259
260        assert_eq!(config.developer_agent, Some("claude".to_string()));
261    }
262
263    #[test]
264    fn test_apply_reviewer_agent() {
265        let cli_state = CliState {
266            reviewer_agent: Some("gpt".to_string()),
267            ..Default::default()
268        };
269
270        let mut config = create_test_config();
271        apply_cli_state_to_config(&cli_state, &mut config);
272
273        assert_eq!(config.reviewer_agent, Some("gpt".to_string()));
274    }
275
276    #[test]
277    fn test_apply_isolation_mode_disabled() {
278        let cli_state = CliState {
279            isolation_mode: Some(false),
280            ..Default::default()
281        };
282
283        let mut config = create_test_config();
284        config.isolation_mode = true;
285
286        apply_cli_state_to_config(&cli_state, &mut config);
287
288        assert!(!config.isolation_mode);
289    }
290
291    #[test]
292    fn test_apply_review_depth() {
293        let cli_state = CliState {
294            review_depth: Some("comprehensive".to_string()),
295            ..Default::default()
296        };
297
298        let mut config = create_test_config();
299        apply_cli_state_to_config(&cli_state, &mut config);
300
301        assert_eq!(config.review_depth, ReviewDepth::Comprehensive);
302    }
303
304    #[test]
305    fn test_apply_git_identity() {
306        let cli_state = CliState {
307            git_user_name: Some("John Doe".to_string()),
308            git_user_email: Some("john@example.com".to_string()),
309            ..Default::default()
310        };
311
312        let mut config = create_test_config();
313        apply_cli_state_to_config(&cli_state, &mut config);
314
315        assert_eq!(config.git_user_name, Some("John Doe".to_string()));
316        assert_eq!(config.git_user_email, Some("john@example.com".to_string()));
317    }
318
319    #[test]
320    fn test_apply_streaming_metrics() {
321        let cli_state = CliState {
322            streaming_metrics: true,
323            ..Default::default()
324        };
325
326        let mut config = create_test_config();
327        apply_cli_state_to_config(&cli_state, &mut config);
328
329        assert!(config.show_streaming_metrics);
330    }
331
332    #[test]
333    fn test_apply_agent_preset_opencode() {
334        let cli_state = CliState {
335            agent_preset: Some("opencode".to_string()),
336            ..Default::default()
337        };
338
339        let mut config = create_test_config();
340        apply_cli_state_to_config(&cli_state, &mut config);
341
342        assert_eq!(config.developer_agent, Some("opencode".to_string()));
343        assert_eq!(config.reviewer_agent, Some("opencode".to_string()));
344    }
345
346    #[test]
347    fn test_apply_agent_preset_default() {
348        let cli_state = CliState {
349            agent_preset: Some("default".to_string()),
350            ..Default::default()
351        };
352
353        let mut config = create_test_config();
354        config.developer_agent = Some("existing-dev".to_string());
355        config.reviewer_agent = Some("existing-rev".to_string());
356
357        apply_cli_state_to_config(&cli_state, &mut config);
358
359        // Default preset should not change existing agents
360        assert_eq!(config.developer_agent, Some("existing-dev".to_string()));
361        assert_eq!(config.reviewer_agent, Some("existing-rev".to_string()));
362    }
363
364    #[test]
365    fn test_apply_preserves_unrelated_config_fields() {
366        let cli_state = CliState {
367            developer_agent: Some("new-agent".to_string()),
368            ..Default::default()
369        };
370
371        let mut config = create_test_config();
372        config.isolation_mode = true;
373        config.review_depth = ReviewDepth::Comprehensive;
374
375        apply_cli_state_to_config(&cli_state, &mut config);
376
377        // Should only change developer_agent
378        assert_eq!(config.developer_agent, Some("new-agent".to_string()));
379        assert!(config.isolation_mode);
380        assert_eq!(config.review_depth, ReviewDepth::Comprehensive);
381    }
382}