ricecoder_storage/markdown_config/integration/
modes.rs1use crate::markdown_config::error::MarkdownConfigResult;
4use crate::markdown_config::loader::{ConfigFile, ConfigFileType, ConfigurationLoader};
5use crate::markdown_config::types::ModeConfig;
6use std::path::PathBuf;
7use std::sync::Arc;
8use tracing::{debug, info, warn};
9
10pub type RegistrationResult = (usize, usize, Vec<(String, String)>);
12
13pub trait ModeRegistrar: Send + Sync {
18 fn register_mode(&mut self, mode: ModeConfig) -> Result<(), String>;
20}
21
22pub struct ModeConfigIntegration {
27 loader: Arc<ConfigurationLoader>,
28}
29
30impl ModeConfigIntegration {
31 pub fn new(loader: Arc<ConfigurationLoader>) -> Self {
33 Self { loader }
34 }
35
36 pub fn discover_mode_configs(&self, paths: &[PathBuf]) -> MarkdownConfigResult<Vec<ConfigFile>> {
44 let all_files = self.loader.discover(paths)?;
45
46 let mode_files: Vec<ConfigFile> = all_files
48 .into_iter()
49 .filter(|f| f.config_type == ConfigFileType::Mode)
50 .collect();
51
52 debug!("Discovered {} mode configuration files", mode_files.len());
53 Ok(mode_files)
54 }
55
56 pub async fn load_mode_configs(
64 &self,
65 paths: &[PathBuf],
66 ) -> MarkdownConfigResult<(Vec<ModeConfig>, Vec<(PathBuf, String)>)> {
67 let files = self.discover_mode_configs(paths)?;
68
69 let mut modes = Vec::new();
70 let mut errors = Vec::new();
71
72 for file in files {
73 match self.loader.load(&file).await {
74 Ok(config) => {
75 match config {
76 crate::markdown_config::loader::LoadedConfig::Mode(mode) => {
77 debug!("Loaded mode configuration: {}", mode.name);
78 modes.push(mode);
79 }
80 _ => {
81 warn!("Expected mode configuration but got different type from {}", file.path.display());
82 errors.push((
83 file.path,
84 "Expected mode configuration but got different type".to_string(),
85 ));
86 }
87 }
88 }
89 Err(e) => {
90 let error_msg = e.to_string();
91 warn!("Failed to load mode configuration from {}: {}", file.path.display(), error_msg);
92 errors.push((file.path, error_msg));
93 }
94 }
95 }
96
97 info!("Loaded {} mode configurations", modes.len());
98 Ok((modes, errors))
99 }
100
101 pub fn register_modes(
113 &self,
114 modes: Vec<ModeConfig>,
115 registrar: &mut dyn ModeRegistrar,
116 ) -> MarkdownConfigResult<RegistrationResult> {
117 let mut success_count = 0;
118 let mut error_count = 0;
119 let mut errors = Vec::new();
120
121 for mode in modes {
122 if let Err(e) = mode.validate() {
124 error_count += 1;
125 let error_msg = format!("Invalid mode configuration: {}", e);
126 warn!("Failed to register mode '{}': {}", mode.name, error_msg);
127 errors.push((mode.name.clone(), error_msg));
128 continue;
129 }
130
131 debug!("Registering mode: {}", mode.name);
132
133 match registrar.register_mode(mode.clone()) {
135 Ok(_) => {
136 success_count += 1;
137 info!("Registered mode: {}", mode.name);
138 }
139 Err(e) => {
140 error_count += 1;
141 warn!("Failed to register mode '{}': {}", mode.name, e);
142 errors.push((mode.name.clone(), e));
143 }
144 }
145 }
146
147 debug!(
148 "Mode registration complete: {} successful, {} failed",
149 success_count, error_count
150 );
151
152 Ok((success_count, error_count, errors))
153 }
154
155 pub async fn load_and_register_modes(
164 &self,
165 paths: &[PathBuf],
166 registrar: &mut dyn ModeRegistrar,
167 ) -> MarkdownConfigResult<(usize, usize, Vec<(String, String)>)> {
168 let (modes, load_errors) = self.load_mode_configs(paths).await?;
169
170 let (success, errors, mut reg_errors) = self.register_modes(modes, registrar)?;
171
172 for (path, msg) in load_errors {
174 reg_errors.push((path.display().to_string(), msg));
175 }
176
177 Ok((success, errors, reg_errors))
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::markdown_config::registry::ConfigRegistry;
185 use std::fs;
186 use tempfile::TempDir;
187
188 fn create_test_mode_file(dir: &PathBuf, name: &str, content: &str) -> PathBuf {
189 let path = dir.join(format!("{}.mode.md", name));
190 fs::write(&path, content).unwrap();
191 path
192 }
193
194 #[test]
195 fn test_discover_mode_configs() {
196 let temp_dir = TempDir::new().unwrap();
197 let dir_path = temp_dir.path().to_path_buf();
198
199 create_test_mode_file(&dir_path, "mode1", "---\nname: mode1\n---\nTest");
201 create_test_mode_file(&dir_path, "mode2", "---\nname: mode2\n---\nTest");
202
203 fs::write(dir_path.join("agent1.agent.md"), "---\nname: agent1\n---\nTest").unwrap();
205
206 let registry = Arc::new(ConfigRegistry::new());
207 let loader = Arc::new(ConfigurationLoader::new(registry));
208 let integration = ModeConfigIntegration::new(loader);
209
210 let discovered = integration.discover_mode_configs(&[dir_path]).unwrap();
211
212 assert_eq!(discovered.len(), 2);
213 assert!(discovered.iter().all(|f| f.config_type == ConfigFileType::Mode));
214 }
215
216 #[tokio::test]
217 async fn test_load_mode_configs() {
218 let temp_dir = TempDir::new().unwrap();
219 let dir_path = temp_dir.path().to_path_buf();
220
221 let mode_content = r#"---
222name: focus-mode
223description: Focus mode
224keybinding: C-f
225enabled: true
226---
227Focus on the task"#;
228
229 create_test_mode_file(&dir_path, "focus-mode", mode_content);
230
231 let registry = Arc::new(ConfigRegistry::new());
232 let loader = Arc::new(ConfigurationLoader::new(registry));
233 let integration = ModeConfigIntegration::new(loader);
234
235 let (modes, errors) = integration.load_mode_configs(&[dir_path]).await.unwrap();
236
237 assert_eq!(modes.len(), 1);
238 assert_eq!(errors.len(), 0);
239 assert_eq!(modes[0].name, "focus-mode");
240 assert_eq!(modes[0].keybinding, Some("C-f".to_string()));
241 }
242
243 #[tokio::test]
244 async fn test_load_mode_configs_with_errors() {
245 let temp_dir = TempDir::new().unwrap();
246 let dir_path = temp_dir.path().to_path_buf();
247
248 let valid_content = r#"---
250name: valid-mode
251---
252Valid mode"#;
253 create_test_mode_file(&dir_path, "valid-mode", valid_content);
254
255 fs::write(dir_path.join("invalid.mode.md"), "# No frontmatter\nJust markdown").unwrap();
257
258 let registry = Arc::new(ConfigRegistry::new());
259 let loader = Arc::new(ConfigurationLoader::new(registry));
260 let integration = ModeConfigIntegration::new(loader);
261
262 let (modes, errors) = integration.load_mode_configs(&[dir_path]).await.unwrap();
263
264 assert_eq!(modes.len(), 1);
265 assert_eq!(errors.len(), 1);
266 assert_eq!(modes[0].name, "valid-mode");
267 }
268
269 #[test]
270 fn test_register_with_mode_manager() {
271 let registry = Arc::new(ConfigRegistry::new());
272 let loader = Arc::new(ConfigurationLoader::new(registry));
273 let integration = ModeConfigIntegration::new(loader);
274
275 let modes = vec![
276 ModeConfig {
277 name: "mode1".to_string(),
278 description: Some("Test mode 1".to_string()),
279 prompt: "You are mode 1".to_string(),
280 keybinding: Some("C-m".to_string()),
281 enabled: true,
282 },
283 ModeConfig {
284 name: "mode2".to_string(),
285 description: Some("Test mode 2".to_string()),
286 prompt: "You are mode 2".to_string(),
287 keybinding: None,
288 enabled: false,
289 },
290 ];
291
292 struct MockRegistrar;
293 impl ModeRegistrar for MockRegistrar {
294 fn register_mode(&mut self, _mode: ModeConfig) -> Result<(), String> {
295 Ok(())
296 }
297 }
298
299 let mut registrar = MockRegistrar;
300 let (success, errors, error_list) = integration
301 .register_modes(modes, &mut registrar)
302 .unwrap();
303
304 assert_eq!(success, 2);
305 assert_eq!(errors, 0);
306 assert_eq!(error_list.len(), 0);
307 }
308
309 #[test]
310 fn test_register_invalid_mode() {
311 let registry = Arc::new(ConfigRegistry::new());
312 let loader = Arc::new(ConfigurationLoader::new(registry));
313 let integration = ModeConfigIntegration::new(loader);
314
315 let modes = vec![
316 ModeConfig {
317 name: String::new(), description: None,
319 prompt: "Test".to_string(),
320 keybinding: None,
321 enabled: true,
322 },
323 ];
324
325 struct MockRegistrar;
326 impl ModeRegistrar for MockRegistrar {
327 fn register_mode(&mut self, _mode: ModeConfig) -> Result<(), String> {
328 Ok(())
329 }
330 }
331
332 let mut registrar = MockRegistrar;
333 let (success, errors, error_list) = integration
334 .register_modes(modes, &mut registrar)
335 .unwrap();
336
337 assert_eq!(success, 0);
338 assert_eq!(errors, 1);
339 assert_eq!(error_list.len(), 1);
340 }
341}