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