Skip to main content

dstack/
cmd_audit.rs

1use crate::config::Config;
2
3pub fn pre_commit() -> anyhow::Result<()> {
4    println!("=== dstack quality gate ===\n");
5    let questions = [
6        ("Negative tests?", "Did I test what happens when things go WRONG? (wrong password → 403, invalid input → error, missing field → handled)"),
7        ("Live verification?", "Did I verify against the LIVE system? (not just compilation — actual curl, DB query, or Chrome DevTools)"),
8        ("Companion doc updated?", "Did I update the .implementation.md with DETAILS? (bugs found, DB state, commit hashes — not one-liner 'DONE')"),
9        ("Tests prove the change?", "Would these tests FAIL without my code change? (decorative tests that pass regardless are worthless)"),
10        ("Truly done?", "Am I moving forward because it's TRULY done, or because I want to show progress?"),
11    ];
12    for (i, (short, detail)) in questions.iter().enumerate() {
13        println!("  {}. {} — {}", i + 1, short, detail);
14    }
15    println!("\nAnswer honestly before committing. These exist because skipping them cost us a client meeting.");
16    Ok(())
17}
18
19pub fn stale(cfg: &Config) -> anyhow::Result<()> {
20    eprintln!("Scanning for stale companion docs across tracked repos...\n");
21    let mut found = 0;
22    let now = std::time::SystemTime::now();
23
24    for repo in &cfg.repos.tracked {
25        let path = format!("{}/{}", cfg.repos.root, repo);
26        if !std::path::Path::new(&path).exists() {
27            continue;
28        }
29        // Find .implementation.md files
30        let output = std::process::Command::new("find")
31            .args([&path, "-name", "*.implementation.md", "-type", "f"])
32            .output()?;
33        for line in String::from_utf8_lossy(&output.stdout).lines() {
34            if line.is_empty() {
35                continue;
36            }
37            let file_path = std::path::Path::new(line);
38            if let Ok(meta) = file_path.metadata() {
39                if let Ok(modified) = meta.modified() {
40                    if let Ok(age) = now.duration_since(modified) {
41                        let days = age.as_secs() / 86400;
42                        if days > 14 {
43                            println!("STALE ({:>3}d): {}", days, line);
44                            found += 1;
45                        } else if days > 7 {
46                            println!("AGING ({:>3}d): {}", days, line);
47                        }
48                    }
49                }
50            }
51        }
52    }
53    if found == 0 {
54        println!("No stale companion docs found (>14 days).");
55    } else {
56        println!("\n{} stale file(s). Update or remove them.", found);
57    }
58    Ok(())
59}
60
61pub fn summary(cfg: &Config) -> anyhow::Result<()> {
62    println!("=== dstack audit summary ===\n");
63
64    // Count tracked repos
65    let total_repos = cfg.repos.tracked.len();
66    let existing: Vec<_> = cfg
67        .repos
68        .tracked
69        .iter()
70        .filter(|r| {
71            std::path::Path::new(&format!("{}/{}", cfg.repos.root, r)).exists()
72        })
73        .collect();
74
75    println!("Repos: {}/{} found", existing.len(), total_repos);
76    println!("Deploy targets: {}", cfg.deploy.len());
77    println!("Memory backend: {}", cfg.memory.backend);
78
79    // Check for dirty repos
80    let mut dirty = 0;
81    for repo in &existing {
82        let path = format!("{}/{}", cfg.repos.root, repo);
83        let output = std::process::Command::new("git")
84            .args(["-C", &path, "status", "--porcelain"])
85            .output()?;
86        let out = String::from_utf8_lossy(&output.stdout);
87        if !out.trim().is_empty() {
88            dirty += 1;
89        }
90    }
91    println!("Dirty repos: {}/{}", dirty, existing.len());
92
93    println!("\nQuality gate: dstack audit --pre-commit");
94    println!("Stale docs:   dstack audit --stale");
95    Ok(())
96}