dx_forge/core/
forge.rs

1//! Main Forge struct - unified API for DX tools
2
3use anyhow::{Context, Result};
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6use parking_lot::RwLock;
7use tokio::sync::broadcast;
8
9use crate::orchestrator::{Orchestrator, OrchestratorConfig};
10use crate::watcher::{DualWatcher, FileChange};
11use crate::version::{ToolRegistry, Version};
12use crate::injection::InjectionManager;
13use super::lifecycle::{LifecycleManager, ToolId, ToolStatus};
14use super::tracking::GeneratedCodeTracker;
15use super::editor_integration::EditorIntegration;
16
17
18/// Main Forge instance - provides unified API for DX tools
19pub struct Forge {
20    config: ForgeConfig,
21    _orchestrator: Arc<RwLock<Orchestrator>>,
22    watcher: Option<Arc<RwLock<DualWatcher>>>,
23    registry: Arc<RwLock<ToolRegistry>>,
24    _injection_manager: Arc<RwLock<InjectionManager>>,
25    lifecycle_manager: Arc<RwLock<LifecycleManager>>,
26    code_tracker: Arc<RwLock<GeneratedCodeTracker>>,
27    _editor_integration: Arc<RwLock<EditorIntegration>>,
28}
29
30/// Configuration for Forge instance
31#[derive(Clone, Debug)]
32pub struct ForgeConfig {
33    /// Root directory of the project
34    pub project_root: PathBuf,
35    
36    /// Forge data directory (.dx/forge)
37    pub forge_dir: PathBuf,
38    
39    /// Automatically start file watching
40    pub auto_watch: bool,
41    
42    /// Enable LSP integration
43    pub enable_lsp: bool,
44    
45    /// Enable version control features
46    pub enable_versioning: bool,
47    
48    /// Number of worker threads for orchestration
49    pub worker_threads: usize,
50}
51
52impl ForgeConfig {
53    /// Create default configuration for a project
54    pub fn new(project_root: impl AsRef<Path>) -> Self {
55        let project_root = project_root.as_ref().to_path_buf();
56        let forge_dir = project_root.join(".dx").join("forge");
57        
58        Self {
59            project_root,
60            forge_dir,
61            auto_watch: true,
62            enable_lsp: true,
63            enable_versioning: true,
64            worker_threads: num_cpus::get(),
65        }
66    }
67    
68    /// Disable automatic file watching
69    pub fn without_auto_watch(mut self) -> Self {
70        self.auto_watch = false;
71        self
72    }
73    
74    /// Disable LSP integration
75    pub fn without_lsp(mut self) -> Self {
76        self.enable_lsp = false;
77        self
78    }
79    
80    /// Set custom forge directory
81    pub fn with_forge_dir(mut self, dir: impl AsRef<Path>) -> Self {
82        self.forge_dir = dir.as_ref().to_path_buf();
83        self
84    }
85}
86
87impl Forge {
88    /// Create a new Forge instance for a project
89    ///
90    /// # Example
91    ///
92    /// ```rust,no_run
93    /// use dx_forge::Forge;
94    ///
95    /// #[tokio::main]
96    /// async fn main() -> anyhow::Result<()> {
97    ///     let forge = Forge::new(".")?;
98    ///     Ok(())
99    /// }
100    /// ```
101    pub fn new(project_root: impl AsRef<Path>) -> Result<Self> {
102        let config = ForgeConfig::new(project_root);
103        Self::with_config(config)
104    }
105    
106    /// Create Forge instance with custom configuration
107    pub fn with_config(config: ForgeConfig) -> Result<Self> {
108        // Ensure forge directory exists
109        std::fs::create_dir_all(&config.forge_dir)
110            .context("Failed to create forge directory")?;
111        
112        // Initialize components
113        let orchestrator_config = OrchestratorConfig {
114            parallel: false,
115            fail_fast: true,
116            max_concurrent: config.worker_threads,
117            traffic_branch_enabled: true,
118        };
119        
120        let orchestrator = Arc::new(RwLock::new(
121            Orchestrator::with_config(config.project_root.clone(), orchestrator_config)
122                .context("Failed to initialize orchestrator")?,
123        ));
124        
125        let registry = Arc::new(RwLock::new(
126            ToolRegistry::new(&config.forge_dir)
127                .context("Failed to initialize tool registry")?,
128        ));
129        
130        let injection_manager = Arc::new(RwLock::new(
131            InjectionManager::new(&config.forge_dir)
132                .context("Failed to initialize injection manager")?,
133        ));
134        
135        let lifecycle_manager = Arc::new(RwLock::new(LifecycleManager::new()));
136        
137        let code_tracker = Arc::new(RwLock::new(
138            GeneratedCodeTracker::new(&config.forge_dir)
139                .context("Failed to initialize code tracker")?,
140        ));
141        
142        let editor_integration = Arc::new(RwLock::new(EditorIntegration::new()));
143        
144        // Initialize watcher if auto_watch is enabled
145        let watcher = if config.auto_watch {
146            let dual_watcher = DualWatcher::new()
147                .context("Failed to initialize file watcher")?;
148            Some(Arc::new(RwLock::new(dual_watcher)))
149        } else {
150            None
151        };
152        
153        Ok(Self {
154            config,
155            _orchestrator: orchestrator,
156            watcher,
157            registry,
158            _injection_manager: injection_manager,
159            lifecycle_manager,
160            code_tracker,
161            _editor_integration: editor_integration,
162        })
163    }
164    
165    /// Get the project root directory
166    pub fn project_root(&self) -> &Path {
167        &self.config.project_root
168    }
169    
170    /// Get the forge data directory
171    pub fn forge_dir(&self) -> &Path {
172        &self.config.forge_dir
173    }
174    
175    // ========================================================================
176    // Tool Lifecycle Management
177    // ========================================================================
178    
179    /// Register a new DX tool
180    ///
181    /// # Example
182    ///
183    /// ```rust,no_run
184    /// use dx_forge::{Forge, DxTool};
185    ///
186    /// struct MyTool;
187    /// impl DxTool for MyTool {
188    ///     fn name(&self) -> &str { "my-tool" }
189    ///     fn version(&self) -> &str { "1.0.0" }
190    ///     fn priority(&self) -> i32 { 50 }
191    ///     async fn execute(&self, ctx: &dx_forge::ExecutionContext) -> anyhow::Result<dx_forge::ToolOutput> {
192    ///         Ok(dx_forge::ToolOutput::success())
193    ///     }
194    /// }
195    
196    /// Get the current status of a tool
197    pub fn get_tool_status(&self, id: ToolId) -> Option<ToolStatus> {
198        self.lifecycle_manager.read().get_status(id)
199    }
200    
201    /// Subscribe to lifecycle events
202    pub fn subscribe_lifecycle_events(&self) -> broadcast::Receiver<super::lifecycle::LifecycleEvent> {
203        self.lifecycle_manager.read().subscribe()
204    }
205    
206    // ========================================================================
207    // File Watching & Change Detection
208    // ========================================================================
209    
210    /// Start watching a directory for changes
211    pub async fn watch_directory(&mut self, path: impl AsRef<Path>) -> Result<()> {
212        if let Some(watcher) = &self.watcher {
213            let path_ref = path.as_ref();
214            watcher.write().start(path_ref).await?;
215            tracing::info!("Started watching directory: {:?}", path_ref);
216            Ok(())
217        } else {
218            anyhow::bail!("File watching is disabled in configuration")
219        }
220    }
221    
222    /// Subscribe to file change events
223    pub fn subscribe_changes(&self) -> Result<broadcast::Receiver<FileChange>> {
224        if let Some(watcher) = &self.watcher {
225            Ok(watcher.read().receiver())
226        } else {
227            anyhow::bail!("File watching is disabled in configuration")
228        }
229    }
230    
231    /// Stop file watching
232    pub async fn stop_watching(&mut self) -> Result<()> {
233        if let Some(watcher) = &self.watcher {
234            watcher.write().stop().await?;
235            tracing::info!("Stopped file watching");
236            Ok(())
237        } else {
238            Ok(())
239        }
240    }
241    
242    // ========================================================================
243    // Generated Code Tracking
244    // ========================================================================
245    
246    /// Track a file as being generated by a tool
247    pub fn track_generated_file(
248        &mut self,
249        file: PathBuf,
250        tool: &str,
251        metadata: std::collections::HashMap<String, String>,
252    ) -> Result<()> {
253        self.code_tracker.write().track_file(file, tool, metadata)
254    }
255    
256    /// Get all files generated by a specific tool
257    pub fn get_generated_files(&self, tool: &str) -> Vec<PathBuf> {
258        self.code_tracker.read().get_files_by_tool(tool)
259    }
260    
261    /// Remove all files generated by a tool
262    pub async fn cleanup_generated(&mut self, tool: &str) -> Result<Vec<PathBuf>> {
263        self.code_tracker.write().cleanup_tool_files(tool).await
264    }
265    
266    // ========================================================================
267    // Tool Registry & Versioning
268    // ========================================================================
269    
270    /// Check if a tool is registered
271    pub fn is_tool_registered(&self, name: &str) -> bool {
272        self.registry.read().is_registered(name)
273    }
274    
275    /// Get version of a registered tool
276    pub fn get_tool_version(&self, name: &str) -> Option<Version> {
277        self.registry.read().version(name).cloned()
278    }
279    
280    /// List all registered tools
281    pub fn list_tools(&self) -> Vec<String> {
282        self.registry
283            .read()
284            .list()
285            .iter()
286            .map(|info| info.name.clone())
287            .collect()
288    }
289}
290
291impl Drop for Forge {
292    fn drop(&mut self) {
293        // Cleanup: stop all running tools
294        if let Some(mut lifecycle) = self.lifecycle_manager.try_write() {
295            if let Err(e) = lifecycle.stop_all() {
296                tracing::error!("Failed to stop all tools during cleanup: {}", e);
297            }
298        }
299        
300        tracing::debug!("Forge instance dropped");
301    }
302}