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        }
155    }
156
157    #[test]
158    fn test_apply_verbosity_debug() {
159        let cli_state = CliState {
160            debug_mode: true,
161            ..Default::default()
162        };
163
164        let mut config = create_test_config();
165        apply_cli_state_to_config(&cli_state, &mut config);
166
167        assert_eq!(config.verbosity, Verbosity::Debug);
168    }
169
170    #[test]
171    fn test_apply_verbosity_full() {
172        let cli_state = CliState {
173            full_mode: true,
174            ..Default::default()
175        };
176
177        let mut config = create_test_config();
178        apply_cli_state_to_config(&cli_state, &mut config);
179
180        assert_eq!(config.verbosity, Verbosity::Full);
181    }
182
183    #[test]
184    fn test_apply_verbosity_quiet() {
185        let cli_state = CliState {
186            quiet_mode: true,
187            ..Default::default()
188        };
189
190        let mut config = create_test_config();
191        apply_cli_state_to_config(&cli_state, &mut config);
192
193        assert_eq!(config.verbosity, Verbosity::Quiet);
194    }
195
196    #[test]
197    fn test_apply_verbosity_explicit() {
198        let cli_state = CliState {
199            verbosity: Some(3),
200            ..Default::default()
201        };
202
203        let mut config = create_test_config();
204        apply_cli_state_to_config(&cli_state, &mut config);
205
206        assert_eq!(config.verbosity, Verbosity::Full); // level 3 = Full
207    }
208
209    #[test]
210    fn test_apply_iters_from_preset() {
211        use super::super::state::PresetType;
212
213        let cli_state = CliState {
214            preset_applied: Some(PresetType::Long),
215            ..Default::default()
216        };
217
218        let mut config = create_test_config();
219        config.developer_iters = 5;
220        config.reviewer_reviews = 2;
221
222        apply_cli_state_to_config(&cli_state, &mut config);
223
224        assert_eq!(config.developer_iters, 15);
225        assert_eq!(config.reviewer_reviews, 10);
226    }
227
228    #[test]
229    fn test_apply_iters_explicit_override_preset() {
230        use super::super::state::PresetType;
231
232        let cli_state = CliState {
233            preset_applied: Some(PresetType::Quick), // Would give 1, 1
234            developer_iters: Some(7),                // Explicit override
235            reviewer_reviews: Some(3),               // Explicit override
236            ..Default::default()
237        };
238
239        let mut config = create_test_config();
240        apply_cli_state_to_config(&cli_state, &mut config);
241
242        // Explicit values should override preset
243        assert_eq!(config.developer_iters, 7);
244        assert_eq!(config.reviewer_reviews, 3);
245    }
246
247    #[test]
248    fn test_apply_developer_agent() {
249        let cli_state = CliState {
250            developer_agent: Some("claude".to_string()),
251            ..Default::default()
252        };
253
254        let mut config = create_test_config();
255        apply_cli_state_to_config(&cli_state, &mut config);
256
257        assert_eq!(config.developer_agent, Some("claude".to_string()));
258    }
259
260    #[test]
261    fn test_apply_reviewer_agent() {
262        let cli_state = CliState {
263            reviewer_agent: Some("gpt".to_string()),
264            ..Default::default()
265        };
266
267        let mut config = create_test_config();
268        apply_cli_state_to_config(&cli_state, &mut config);
269
270        assert_eq!(config.reviewer_agent, Some("gpt".to_string()));
271    }
272
273    #[test]
274    fn test_apply_isolation_mode_disabled() {
275        let cli_state = CliState {
276            isolation_mode: Some(false),
277            ..Default::default()
278        };
279
280        let mut config = create_test_config();
281        config.isolation_mode = true;
282
283        apply_cli_state_to_config(&cli_state, &mut config);
284
285        assert!(!config.isolation_mode);
286    }
287
288    #[test]
289    fn test_apply_review_depth() {
290        let cli_state = CliState {
291            review_depth: Some("comprehensive".to_string()),
292            ..Default::default()
293        };
294
295        let mut config = create_test_config();
296        apply_cli_state_to_config(&cli_state, &mut config);
297
298        assert_eq!(config.review_depth, ReviewDepth::Comprehensive);
299    }
300
301    #[test]
302    fn test_apply_git_identity() {
303        let cli_state = CliState {
304            git_user_name: Some("John Doe".to_string()),
305            git_user_email: Some("john@example.com".to_string()),
306            ..Default::default()
307        };
308
309        let mut config = create_test_config();
310        apply_cli_state_to_config(&cli_state, &mut config);
311
312        assert_eq!(config.git_user_name, Some("John Doe".to_string()));
313        assert_eq!(config.git_user_email, Some("john@example.com".to_string()));
314    }
315
316    #[test]
317    fn test_apply_streaming_metrics() {
318        let cli_state = CliState {
319            streaming_metrics: true,
320            ..Default::default()
321        };
322
323        let mut config = create_test_config();
324        apply_cli_state_to_config(&cli_state, &mut config);
325
326        assert!(config.show_streaming_metrics);
327    }
328
329    #[test]
330    fn test_apply_agent_preset_opencode() {
331        let cli_state = CliState {
332            agent_preset: Some("opencode".to_string()),
333            ..Default::default()
334        };
335
336        let mut config = create_test_config();
337        apply_cli_state_to_config(&cli_state, &mut config);
338
339        assert_eq!(config.developer_agent, Some("opencode".to_string()));
340        assert_eq!(config.reviewer_agent, Some("opencode".to_string()));
341    }
342
343    #[test]
344    fn test_apply_agent_preset_default() {
345        let cli_state = CliState {
346            agent_preset: Some("default".to_string()),
347            ..Default::default()
348        };
349
350        let mut config = create_test_config();
351        config.developer_agent = Some("existing-dev".to_string());
352        config.reviewer_agent = Some("existing-rev".to_string());
353
354        apply_cli_state_to_config(&cli_state, &mut config);
355
356        // Default preset should not change existing agents
357        assert_eq!(config.developer_agent, Some("existing-dev".to_string()));
358        assert_eq!(config.reviewer_agent, Some("existing-rev".to_string()));
359    }
360
361    #[test]
362    fn test_apply_preserves_unrelated_config_fields() {
363        let cli_state = CliState {
364            developer_agent: Some("new-agent".to_string()),
365            ..Default::default()
366        };
367
368        let mut config = create_test_config();
369        config.isolation_mode = true;
370        config.review_depth = ReviewDepth::Comprehensive;
371
372        apply_cli_state_to_config(&cli_state, &mut config);
373
374        // Should only change developer_agent
375        assert_eq!(config.developer_agent, Some("new-agent".to_string()));
376        assert!(config.isolation_mode);
377        assert_eq!(config.review_depth, ReviewDepth::Comprehensive);
378    }
379}