1use std::path::Path;
2use std::time::{Duration, SystemTime};
3
4use anyhow::Result;
5use owo_colors::OwoColorize;
6
7use crate::detection;
8use crate::rules;
9use crate::scan;
10use crate::scoring;
11
12pub fn watch_and_score(path: &Path) -> Result<()> {
15 println!("{}", "Watching for changes... (Ctrl+C to stop)".dimmed());
16 println!();
17
18 let mut last_score = 0.0_f64;
19 let mut last_mod = get_latest_mtime(path);
20
21 loop {
22 let current_mod = get_latest_mtime(path);
23
24 if current_mod != last_mod || last_score == 0.0 {
25 last_mod = current_mod;
26
27 match run_score(path) {
28 Ok((score, grade, pt)) => {
29 let delta = score - last_score;
30 let delta_str = if last_score == 0.0 {
31 String::new()
32 } else if delta > 0.0 {
33 format!(" ({})", format!("+{:.0}", delta).green())
34 } else if delta < 0.0 {
35 format!(" ({})", format!("{:.0}", delta).red())
36 } else {
37 String::new()
38 };
39
40 let now = chrono_now();
41 let grade_colored = match grade.as_str() {
42 "A+" | "A" => grade.green().bold().to_string(),
43 "B" => grade.yellow().bold().to_string(),
44 _ => grade.red().to_string(),
45 };
46
47 println!(
48 " {} {} {}/100 {}{} — {}",
49 now.dimmed(),
50 grade_colored,
51 format!("{:.0}", score).bold(),
52 pt.dimmed(),
53 delta_str,
54 if score >= 90.0 { "Claude Native".green().to_string() }
55 else if score >= 70.0 { "Claude Friendly".yellow().to_string() }
56 else { "Needs work".red().to_string() },
57 );
58
59 last_score = score;
60 }
61 Err(e) => {
62 eprintln!(" {} {e}", "Error:".red());
63 }
64 }
65 }
66
67 std::thread::sleep(Duration::from_secs(2));
68 }
69}
70
71fn run_score(path: &Path) -> Result<(f64, String, String)> {
72 let mut ctx = scan::build_context(path)?;
73 let pt = detection::detect(&ctx);
74 ctx.project_type = Some(pt.clone());
75
76 let all = rules::all_rules();
77 let results: Vec<_> = all.iter()
78 .filter(|r| r.applies_to(&pt))
79 .map(|r| r.check(&ctx))
80 .collect();
81
82 let sc = scoring::calculate(results, &pt);
83 Ok((sc.total_score, format!("{}", sc.grade), format!("{}", pt)))
84}
85
86fn get_latest_mtime(path: &Path) -> u64 {
87 let mut latest = 0u64;
88 if let Ok(walker) = walkdir::WalkDir::new(path)
89 .max_depth(4)
90 .into_iter()
91 .collect::<Result<Vec<_>, _>>()
92 {
93 for entry in walker {
94 if let Ok(meta) = entry.metadata() {
95 if let Ok(modified) = meta.modified() {
96 let secs = modified.duration_since(SystemTime::UNIX_EPOCH)
97 .unwrap_or_default().as_secs();
98 if secs > latest { latest = secs; }
99 }
100 }
101 }
102 }
103 latest
104}
105
106fn chrono_now() -> String {
107 let now = SystemTime::now()
109 .duration_since(SystemTime::UNIX_EPOCH)
110 .unwrap_or_default()
111 .as_secs();
112 let secs = now % 60;
113 let mins = (now / 60) % 60;
114 let hours = (now / 3600) % 24;
115 format!("{hours:02}:{mins:02}:{secs:02}")
116}