Skip to main content

ralph_workflow/config/
mod.rs

1//! Configuration Module
2//!
3//! Handles environment variables and configuration for Ralph.
4//!
5//! # Module Structure
6//!
7//! - [`types`]: Core configuration types (Config, `ReviewDepth`, Verbosity)
8//! - [`truncation`]: Truncation limits for verbosity levels
9//! - [`parser`]: Environment variable parsing (legacy)
10//! - [`unified`]: Unified configuration format types
11//! - [`loader`]: Unified configuration loader with env overrides
12//!
13//! # Configuration Sources
14//!
15//! Ralph configuration is loaded from (in order of priority):
16//! 1. `~/.config/ralph-workflow.toml` (primary, unified config)
17//! 2. Environment variables (RALPH_*) as overrides
18//! 3. CLI arguments (final override)
19//!
20//! # Usage
21//!
22//! ```ignore
23//! use crate::config::Config;
24//!
25//! let config = Config::from_env();
26//! println!("Developer iterations: {}", config.developer_iters);
27//! ```
28
29pub mod loader;
30pub mod parser;
31pub mod path_resolver;
32pub mod truncation;
33pub mod types;
34pub mod unified;
35
36// Re-export main types at module level for convenience
37pub use types::{Config, ReviewDepth, Verbosity};
38
39// Re-export unified config types for --init-global handling
40pub use unified::{
41    unified_config_path, CcsAliasConfig, CcsConfig, ConfigInitResult as UnifiedConfigInitResult,
42    UnifiedConfig,
43};
44
45// Re-export config environment types for dependency injection
46pub use path_resolver::{ConfigEnvironment, MemoryConfigEnvironment, RealConfigEnvironment};
47
48// Backward compatibility type aliases
49pub type ConfigPathResolver = dyn ConfigEnvironment;
50pub type RealConfigPathResolver = RealConfigEnvironment;
51pub type TestConfigPathResolver = MemoryConfigEnvironment;
52pub type MemoryConfigPathResolver = MemoryConfigEnvironment;
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use std::env;
58    use std::sync::Mutex;
59
60    static ENV_MUTEX: Mutex<()> = Mutex::new(());
61
62    #[test]
63    fn test_verbosity_from_u8() {
64        assert_eq!(Verbosity::from(0), Verbosity::Quiet);
65        assert_eq!(Verbosity::from(1), Verbosity::Normal);
66        assert_eq!(Verbosity::from(2), Verbosity::Verbose);
67        assert_eq!(Verbosity::from(3), Verbosity::Full);
68        assert_eq!(Verbosity::from(4), Verbosity::Debug);
69        assert_eq!(Verbosity::from(100), Verbosity::Debug);
70    }
71
72    #[test]
73    fn test_truncate_limits() {
74        // Quiet has reduced limits
75        assert_eq!(Verbosity::Quiet.truncate_limit("text"), 80);
76        assert_eq!(Verbosity::Quiet.truncate_limit("tool_input"), 40);
77
78        // Normal has conservative limits for manageable output
79        assert_eq!(Verbosity::Normal.truncate_limit("text"), 1000);
80        assert_eq!(Verbosity::Normal.truncate_limit("tool_input"), 300);
81        assert_eq!(Verbosity::Normal.truncate_limit("tool_result"), 500);
82
83        // Verbose (default) has conservative limits for reasonable output
84        assert_eq!(Verbosity::Verbose.truncate_limit("text"), 2000);
85        assert_eq!(Verbosity::Verbose.truncate_limit("tool_input"), 300);
86        assert_eq!(Verbosity::Verbose.truncate_limit("tool_result"), 500);
87
88        // Full and Debug have unlimited
89        assert_eq!(Verbosity::Full.truncate_limit("text"), 999_999);
90        assert_eq!(Verbosity::Debug.truncate_limit("text"), 999_999);
91    }
92
93    #[test]
94    fn test_verbosity_helpers() {
95        assert!(!Verbosity::Quiet.is_debug());
96        assert!(!Verbosity::Normal.is_debug());
97        assert!(!Verbosity::Verbose.is_debug());
98        assert!(!Verbosity::Full.is_debug());
99        assert!(Verbosity::Debug.is_debug());
100
101        assert!(!Verbosity::Quiet.is_verbose());
102        assert!(!Verbosity::Normal.is_verbose());
103        assert!(Verbosity::Verbose.is_verbose());
104        assert!(Verbosity::Full.is_verbose());
105        assert!(Verbosity::Debug.is_verbose());
106
107        // show_tool_input: true for Normal and above, false for Quiet
108        assert!(!Verbosity::Quiet.show_tool_input());
109        assert!(Verbosity::Normal.show_tool_input());
110        assert!(Verbosity::Verbose.show_tool_input());
111        assert!(Verbosity::Full.show_tool_input());
112        assert!(Verbosity::Debug.show_tool_input());
113    }
114
115    #[test]
116    fn test_review_depth_from_str() {
117        // Standard aliases
118        assert_eq!(
119            ReviewDepth::from_str("standard"),
120            Some(ReviewDepth::Standard)
121        );
122        assert_eq!(
123            ReviewDepth::from_str("default"),
124            Some(ReviewDepth::Standard)
125        );
126        assert_eq!(ReviewDepth::from_str("normal"), Some(ReviewDepth::Standard));
127
128        // Comprehensive aliases
129        assert_eq!(
130            ReviewDepth::from_str("comprehensive"),
131            Some(ReviewDepth::Comprehensive)
132        );
133        assert_eq!(
134            ReviewDepth::from_str("thorough"),
135            Some(ReviewDepth::Comprehensive)
136        );
137        assert_eq!(
138            ReviewDepth::from_str("full"),
139            Some(ReviewDepth::Comprehensive)
140        );
141
142        // Security aliases
143        assert_eq!(
144            ReviewDepth::from_str("security"),
145            Some(ReviewDepth::Security)
146        );
147        assert_eq!(ReviewDepth::from_str("secure"), Some(ReviewDepth::Security));
148        assert_eq!(
149            ReviewDepth::from_str("security-focused"),
150            Some(ReviewDepth::Security)
151        );
152
153        // Incremental aliases
154        assert_eq!(
155            ReviewDepth::from_str("incremental"),
156            Some(ReviewDepth::Incremental)
157        );
158        assert_eq!(
159            ReviewDepth::from_str("diff"),
160            Some(ReviewDepth::Incremental)
161        );
162        assert_eq!(
163            ReviewDepth::from_str("changed"),
164            Some(ReviewDepth::Incremental)
165        );
166
167        // Case insensitivity
168        assert_eq!(
169            ReviewDepth::from_str("SECURITY"),
170            Some(ReviewDepth::Security)
171        );
172        assert_eq!(
173            ReviewDepth::from_str("Comprehensive"),
174            Some(ReviewDepth::Comprehensive)
175        );
176
177        // Invalid values
178        assert_eq!(ReviewDepth::from_str("invalid"), None);
179        assert_eq!(ReviewDepth::from_str(""), None);
180    }
181
182    #[test]
183    fn test_review_depth_default() {
184        assert_eq!(ReviewDepth::default(), ReviewDepth::Standard);
185    }
186
187    #[test]
188    fn test_review_depth_description() {
189        assert!(ReviewDepth::Standard.description().contains("Balanced"));
190        assert!(ReviewDepth::Comprehensive
191            .description()
192            .contains("In-depth"));
193        assert!(ReviewDepth::Security.description().contains("OWASP"));
194        assert!(ReviewDepth::Incremental.description().contains("git diff"));
195    }
196
197    #[test]
198    fn test_with_commit_msg() {
199        let _guard = ENV_MUTEX.lock().unwrap();
200
201        // Clear any environment variables that might affect the test
202        env::remove_var("RALPH_DEVELOPER_AGENT");
203        env::remove_var("RALPH_DRIVER_AGENT");
204
205        let config = Config::default().with_commit_msg("custom message".to_string());
206        assert_eq!(config.commit_msg, "custom message");
207    }
208}