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}