1use 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
18pub 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#[derive(Clone, Debug)]
32pub struct ForgeConfig {
33 pub project_root: PathBuf,
35
36 pub forge_dir: PathBuf,
38
39 pub auto_watch: bool,
41
42 pub enable_lsp: bool,
44
45 pub enable_versioning: bool,
47
48 pub worker_threads: usize,
50}
51
52impl ForgeConfig {
53 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 pub fn without_auto_watch(mut self) -> Self {
70 self.auto_watch = false;
71 self
72 }
73
74 pub fn without_lsp(mut self) -> Self {
76 self.enable_lsp = false;
77 self
78 }
79
80 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 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 pub fn with_config(config: ForgeConfig) -> Result<Self> {
108 std::fs::create_dir_all(&config.forge_dir)
110 .context("Failed to create forge directory")?;
111
112 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 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 pub fn project_root(&self) -> &Path {
167 &self.config.project_root
168 }
169
170 pub fn forge_dir(&self) -> &Path {
172 &self.config.forge_dir
173 }
174
175 pub fn get_tool_status(&self, id: ToolId) -> Option<ToolStatus> {
198 self.lifecycle_manager.read().get_status(id)
199 }
200
201 pub fn subscribe_lifecycle_events(&self) -> broadcast::Receiver<super::lifecycle::LifecycleEvent> {
203 self.lifecycle_manager.read().subscribe()
204 }
205
206 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 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 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 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 pub fn get_generated_files(&self, tool: &str) -> Vec<PathBuf> {
258 self.code_tracker.read().get_files_by_tool(tool)
259 }
260
261 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 pub fn is_tool_registered(&self, name: &str) -> bool {
272 self.registry.read().is_registered(name)
273 }
274
275 pub fn get_tool_version(&self, name: &str) -> Option<Version> {
277 self.registry.read().version(name).cloned()
278 }
279
280 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 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}