Skip to main content

envoy/monitor/
doc.rs

1use std::sync::Arc;
2
3use parking_lot::Mutex;
4
5use crate::event::{EventSeverity, EventType};
6use crate::http::SharedState;
7
8/// Background doc freshness monitor. Checks git log for documentation activity.
9pub async fn run_doc_monitor(
10    state: SharedState,
11    project: String,
12    _repo_path: String,
13    interval_secs: u64,
14) {
15    let last_checked = Arc::new(Mutex::new(chrono::Utc::now().timestamp()));
16
17    loop {
18        tokio::time::sleep(std::time::Duration::from_secs(interval_secs)).await;
19
20        let output = tokio::process::Command::new("git")
21            .args(["log", "-1", "--format=%ct"])
22            .output()
23            .await;
24
25        let stdout = match output {
26            Ok(o) if o.status.success() => String::from_utf8_lossy(&o.stdout).trim().to_string(),
27            _ => continue,
28        };
29
30        let last_commit_ts: i64 = stdout.parse().unwrap_or(0);
31        let now = chrono::Utc::now().timestamp();
32        let age_seconds = now - last_commit_ts;
33
34        // Check + update under lock, release before async work
35        let should_emit = {
36            let mut last = last_checked.lock();
37            if age_seconds > 86400 && *last < now - 86400 {
38                *last = now;
39                true
40            } else {
41                false
42            }
43        };
44        // lock dropped here — safe to .await
45
46        if should_emit {
47            let state_fb = state.clone();
48            let project_fb = project.clone();
49            let project_fb2 = project.clone();
50            let _ = tokio::task::spawn_blocking(move || {
51                let engine = state_fb.engine.lock();
52                let severity = if age_seconds > 604800 {
53                    EventSeverity::Warning
54                } else {
55                    EventSeverity::Info
56                };
57                if let Ok(event) = state_fb.event_bus.ingest(
58                    engine.graph(),
59                    project_fb,
60                    EventType::DocSync,
61                    severity,
62                    "doc:wiki".into(),
63                    format!("Documentation last touched {}s ago", age_seconds),
64                    serde_json::json!({
65                        "last_updated_seconds": age_seconds,
66                    }),
67                ) {
68                    let event_json = serde_json::to_value(&event).unwrap_or_default();
69                    let subs = state_fb
70                        .subscription_store
71                        .subscribers(engine.graph(), &project_fb2)
72                        .unwrap_or_default();
73                    for agent_id in &subs {
74                        let delivered =
75                            state_fb
76                                .ws_registry
77                                .send_json(agent_id, "doc_event", &event_json);
78                        if delivered {
79                            let _ = state_fb.delivery_tracker.record_delivery(
80                                engine.graph(),
81                                agent_id,
82                                &event.id,
83                            );
84                        }
85                    }
86                }
87            })
88            .await;
89        }
90    }
91}