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 truncation;
32pub mod types;
33pub mod unified;
34
35// Re-export main types at module level for convenience
36pub use types::{Config, ReviewDepth, Verbosity};
37
38// Re-export unified config types for --init-global handling
39pub use unified::{
40    unified_config_path, CcsAliasConfig, CcsConfig, ConfigInitResult as UnifiedConfigInitResult,
41    UnifiedConfig,
42};
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use std::env;
48    use std::sync::Mutex;
49
50    static ENV_MUTEX: Mutex<()> = Mutex::new(());
51
52    #[test]
53    fn test_verbosity_from_u8() {
54        assert_eq!(Verbosity::from(0), Verbosity::Quiet);
55        assert_eq!(Verbosity::from(1), Verbosity::Normal);
56        assert_eq!(Verbosity::from(2), Verbosity::Verbose);
57        assert_eq!(Verbosity::from(3), Verbosity::Full);
58        assert_eq!(Verbosity::from(4), Verbosity::Debug);
59        assert_eq!(Verbosity::from(100), Verbosity::Debug);
60    }
61
62    #[test]
63    fn test_truncate_limits() {
64        // Quiet has reduced limits
65        assert_eq!(Verbosity::Quiet.truncate_limit("text"), 80);
66        assert_eq!(Verbosity::Quiet.truncate_limit("tool_input"), 40);
67
68        // Normal has increased limits for better usability
69        // NOTE: tool_input is unlimited in Normal mode to show full tool input
70        assert_eq!(Verbosity::Normal.truncate_limit("text"), 400);
71        assert_eq!(Verbosity::Normal.truncate_limit("tool_input"), 999_999);
72
73        // Verbose (default) has generous limits for understanding agent behavior
74        // NOTE: tool_input is unlimited in Verbose mode for maximum visibility
75        assert_eq!(Verbosity::Verbose.truncate_limit("text"), 800);
76        assert_eq!(Verbosity::Verbose.truncate_limit("tool_input"), 999_999);
77
78        // Full and Debug have unlimited
79        assert_eq!(Verbosity::Full.truncate_limit("text"), 999_999);
80        assert_eq!(Verbosity::Debug.truncate_limit("text"), 999_999);
81    }
82
83    #[test]
84    fn test_verbosity_helpers() {
85        assert!(!Verbosity::Quiet.is_debug());
86        assert!(!Verbosity::Normal.is_debug());
87        assert!(!Verbosity::Verbose.is_debug());
88        assert!(!Verbosity::Full.is_debug());
89        assert!(Verbosity::Debug.is_debug());
90
91        assert!(!Verbosity::Quiet.is_verbose());
92        assert!(!Verbosity::Normal.is_verbose());
93        assert!(Verbosity::Verbose.is_verbose());
94        assert!(Verbosity::Full.is_verbose());
95        assert!(Verbosity::Debug.is_verbose());
96
97        // show_tool_input: true for Normal and above, false for Quiet
98        assert!(!Verbosity::Quiet.show_tool_input());
99        assert!(Verbosity::Normal.show_tool_input());
100        assert!(Verbosity::Verbose.show_tool_input());
101        assert!(Verbosity::Full.show_tool_input());
102        assert!(Verbosity::Debug.show_tool_input());
103    }
104
105    #[test]
106    fn test_review_depth_from_str() {
107        // Standard aliases
108        assert_eq!(
109            ReviewDepth::from_str("standard"),
110            Some(ReviewDepth::Standard)
111        );
112        assert_eq!(
113            ReviewDepth::from_str("default"),
114            Some(ReviewDepth::Standard)
115        );
116        assert_eq!(ReviewDepth::from_str("normal"), Some(ReviewDepth::Standard));
117
118        // Comprehensive aliases
119        assert_eq!(
120            ReviewDepth::from_str("comprehensive"),
121            Some(ReviewDepth::Comprehensive)
122        );
123        assert_eq!(
124            ReviewDepth::from_str("thorough"),
125            Some(ReviewDepth::Comprehensive)
126        );
127        assert_eq!(
128            ReviewDepth::from_str("full"),
129            Some(ReviewDepth::Comprehensive)
130        );
131
132        // Security aliases
133        assert_eq!(
134            ReviewDepth::from_str("security"),
135            Some(ReviewDepth::Security)
136        );
137        assert_eq!(ReviewDepth::from_str("secure"), Some(ReviewDepth::Security));
138        assert_eq!(
139            ReviewDepth::from_str("security-focused"),
140            Some(ReviewDepth::Security)
141        );
142
143        // Incremental aliases
144        assert_eq!(
145            ReviewDepth::from_str("incremental"),
146            Some(ReviewDepth::Incremental)
147        );
148        assert_eq!(
149            ReviewDepth::from_str("diff"),
150            Some(ReviewDepth::Incremental)
151        );
152        assert_eq!(
153            ReviewDepth::from_str("changed"),
154            Some(ReviewDepth::Incremental)
155        );
156
157        // Case insensitivity
158        assert_eq!(
159            ReviewDepth::from_str("SECURITY"),
160            Some(ReviewDepth::Security)
161        );
162        assert_eq!(
163            ReviewDepth::from_str("Comprehensive"),
164            Some(ReviewDepth::Comprehensive)
165        );
166
167        // Invalid values
168        assert_eq!(ReviewDepth::from_str("invalid"), None);
169        assert_eq!(ReviewDepth::from_str(""), None);
170    }
171
172    #[test]
173    fn test_review_depth_default() {
174        assert_eq!(ReviewDepth::default(), ReviewDepth::Standard);
175    }
176
177    #[test]
178    fn test_review_depth_description() {
179        assert!(ReviewDepth::Standard.description().contains("Balanced"));
180        assert!(ReviewDepth::Comprehensive
181            .description()
182            .contains("In-depth"));
183        assert!(ReviewDepth::Security.description().contains("OWASP"));
184        assert!(ReviewDepth::Incremental.description().contains("git diff"));
185    }
186
187    #[test]
188    fn test_with_commit_msg() {
189        let _guard = ENV_MUTEX.lock().unwrap();
190
191        // Clear any environment variables that might affect the test
192        env::remove_var("RALPH_DEVELOPER_AGENT");
193        env::remove_var("RALPH_DRIVER_AGENT");
194
195        let config = Config::default().with_commit_msg("custom message".to_string());
196        assert_eq!(config.commit_msg, "custom message");
197    }
198}