Skip to main content

hematite/agent/
specular.rs

1use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
2use std::path::PathBuf;
3use std::process::Command;
4use tokio::sync::mpsc;
5
6#[derive(Debug, Clone)]
7pub enum SpecularEvent {
8    FileChanged(PathBuf),
9    SyntaxError { path: PathBuf, details: String },
10}
11
12/// Spawns the OS file watcher asynchronously and transmits internal alerts into via the mpsc channel
13pub fn spawn_watcher(
14    tx: mpsc::Sender<SpecularEvent>,
15) -> Result<RecommendedWatcher, Box<dyn std::error::Error>> {
16    let (std_tx, std_rx) = std::sync::mpsc::channel();
17
18    let mut watcher = notify::RecommendedWatcher::new(std_tx, Config::default())?;
19
20    // Attach physically to the source directory to intercept code additions
21    if std::path::Path::new("./src").exists() {
22        watcher.watch(std::path::Path::new("./src"), RecursiveMode::Recursive)?;
23    }
24
25    // Spawn a transparent background OS thread to translate synchronous notify events
26    // perfectly over into the async tokio run cycle bounds.
27    std::thread::spawn(move || {
28        // Initial heartbeat to populate the UI Radar
29        let _ = tx.blocking_send(SpecularEvent::FileChanged(PathBuf::from("./src")));
30
31        for res in std_rx {
32            match res {
33                Ok(Event { kind, paths, .. }) => {
34                    if kind.is_modify() {
35                        for path in paths {
36                            if let Some(ext) = path.extension() {
37                                if ext == "rs" {
38                                    // 1) File modification identified locally
39                                    let _ =
40                                        tx.blocking_send(SpecularEvent::FileChanged(path.clone()));
41
42                                    // 2) Trigger pseudo-cargo check
43                                    let output = Command::new("cargo").arg("check").output();
44
45                                    if let Ok(cmd_out) = output {
46                                        if !cmd_out.status.success() {
47                                            // 3) Proactive anomaly! Harvest compiler crash and send to AgentLoop
48                                            let error_str =
49                                                String::from_utf8_lossy(&cmd_out.stderr)
50                                                    .to_string();
51                                            let _ = tx.blocking_send(SpecularEvent::SyntaxError {
52                                                path: path.clone(),
53                                                details: error_str,
54                                            });
55                                        }
56                                    }
57                                }
58                            }
59                        }
60                    }
61                }
62                Err(e) => println!("Specular internal tracking error: {:?}", e),
63            }
64        }
65    });
66
67    Ok(watcher)
68}