Skip to main content

instruction_files/
lib.rs

1//! Discovery, auditing, and sync for AGENTS.md/CLAUDE.md instruction files.
2
3mod audit;
4mod discovery;
5mod types;
6
7pub use audit::{check_actionable, check_line_budget, check_staleness, check_tree_paths};
8pub use discovery::{find_instruction_files, find_root};
9pub use types::{AuditConfig, Issue};
10
11use anyhow::Result;
12use std::path::Path;
13
14/// Run the full audit with the given configuration.
15///
16/// Returns `Ok(())` on success, calls `std::process::exit(1)` on issues found.
17pub fn run(config: &AuditConfig, root_override: Option<&Path>) -> Result<()> {
18    println!("Auditing docs...\n");
19
20    let root = match root_override {
21        Some(p) => p.to_path_buf(),
22        None => find_root(config),
23    };
24    let files = find_instruction_files(&root, config);
25    let mut issues: Vec<Issue> = Vec::new();
26
27    for doc in &files {
28        let rel = doc
29            .strip_prefix(&root)
30            .unwrap_or(doc)
31            .to_string_lossy()
32            .to_string();
33        if let Ok(content) = std::fs::read_to_string(doc) {
34            issues.extend(check_tree_paths(&rel, &content, &root));
35            issues.extend(check_actionable(&rel, &content, config));
36        }
37    }
38
39    let (budget_issues, counts, total) = check_line_budget(&files, &root, config);
40    issues.extend(budget_issues);
41    issues.extend(check_staleness(&files, &root, config));
42
43    for issue in &issues {
44        let mut loc = format!("  {}", issue.file);
45        if issue.line > 0 {
46            if issue.end_line > issue.line {
47                loc.push_str(&format!(":{}-{}", issue.line, issue.end_line));
48            } else {
49                loc.push_str(&format!(":{}", issue.line));
50            }
51        }
52        let marker = if issue.warning { "\u{26a0}" } else { "\u{2717}" };
53        println!("{:<50} {} {}", loc, marker, issue.message);
54    }
55
56    let mark = if total <= LINE_BUDGET {
57        "\u{2713}"
58    } else {
59        "\u{2717}"
60    };
61    println!(
62        "\nCombined instruction files: {} lines (budget: {}) {}",
63        total, LINE_BUDGET, mark
64    );
65    for (name, n) in &counts {
66        println!("  {}: {}", name, n);
67    }
68
69    let n = issues.len();
70    if n > 0 {
71        println!("\nFound {} issue(s)", n);
72        std::process::exit(1);
73    } else {
74        println!("\nNo issues found \u{2713}");
75    }
76
77    Ok(())
78}
79
80const LINE_BUDGET: usize = 1000;