nodex_core/rules/
freshness.rs1use chrono::Local;
2
3use crate::config::Config;
4use crate::model::Graph;
5
6use super::{Rule, Severity, Violation};
7
8pub struct StaleReviewRule;
10
11impl Rule for StaleReviewRule {
12 fn id(&self) -> &str {
13 "stale_review"
14 }
15
16 fn severity(&self) -> Severity {
17 Severity::Warning
18 }
19
20 fn check(&self, graph: &Graph, config: &Config) -> Vec<Violation> {
21 let today = Local::now().date_naive();
22 let Some(cutoff) =
27 today.checked_sub_days(chrono::Days::new(u64::from(config.detection.stale_days)))
28 else {
29 return Vec::new();
30 };
31
32 graph
33 .nodes()
34 .values()
35 .filter_map(|node| {
36 if config.is_terminal(node.status.as_str()) {
37 return None;
38 }
39 let reviewed = node.reviewed?;
40 if reviewed >= cutoff {
41 return None;
42 }
43 let days = (today - reviewed).num_days();
44 Some(Violation {
45 rule_id: self.id().to_string(),
46 severity: self.severity(),
47 node_id: Some(node.id.clone()),
48 path: Some(node.path.to_string_lossy().to_string()),
49 message: format!(
50 "not reviewed for {days} days (threshold: {} days)",
51 config.detection.stale_days
52 ),
53 })
54 })
55 .collect()
56 }
57}