1mod audit;
4mod discovery;
5pub use agent_runbooks as runbooks;
6#[cfg(feature = "ontology")]
7pub mod ontology;
8#[cfg(feature = "spec-audit")]
9pub mod spec_audit;
10mod types;
11
12pub use audit::{
13 check_actionable, check_context_invariant, check_line_budget, check_staleness,
14 check_tree_paths,
15};
16pub use discovery::{find_instruction_files, find_root};
17#[cfg(feature = "ontology")]
18pub use ontology::check_ontology_terms;
19pub use runbooks::init_runbooks;
20pub use types::{AuditConfig, Issue, is_agent_file};
21
22use agent_kit::audit_common::LINE_BUDGET;
23use anyhow::Result;
24use std::path::Path;
25
26pub fn run(
34 config: &AuditConfig,
35 root_override: Option<&Path>,
36 #[cfg(feature = "ontology")] ontology_dir: Option<&Path>,
37) -> Result<()> {
38 println!("Auditing docs...\n");
39
40 let root = match root_override {
41 Some(p) => p.to_path_buf(),
42 None => find_root(config),
43 };
44 let files = find_instruction_files(&root, config);
45 let mut issues: Vec<Issue> = Vec::new();
46
47 for doc in &files {
48 let rel = doc
49 .strip_prefix(&root)
50 .unwrap_or(doc)
51 .to_string_lossy()
52 .to_string();
53 if let Ok(content) = std::fs::read_to_string(doc) {
54 issues.extend(check_tree_paths(&rel, &content, &root));
55 issues.extend(check_actionable(&rel, &content, config));
56 issues.extend(check_context_invariant(&rel, &content, config));
57 #[cfg(feature = "ontology")]
58 if let Some(onto_dir) = ontology_dir {
59 issues.extend(check_ontology_terms(&rel, &content, onto_dir));
60 }
61 }
62 }
63
64 let (budget_issues, counts, total) = check_line_budget(&files, &root, config);
65 issues.extend(budget_issues);
66 issues.extend(check_staleness(&files, &root, config));
67
68 for issue in &issues {
69 let mut loc = format!(" {}", issue.file);
70 if issue.line > 0 {
71 if issue.end_line > issue.line {
72 loc.push_str(&format!(":{}-{}", issue.line, issue.end_line));
73 } else {
74 loc.push_str(&format!(":{}", issue.line));
75 }
76 }
77 let marker = if issue.warning { "\u{26a0}" } else { "\u{2717}" };
78 println!("{:<50} {} {}", loc, marker, issue.message);
79 }
80
81 let mark = if total <= LINE_BUDGET {
82 "\u{2713}"
83 } else {
84 "\u{2717}"
85 };
86 println!(
87 "\nCombined instruction files: {} lines (budget: {}) {}",
88 total, LINE_BUDGET, mark
89 );
90 for (name, n) in &counts {
91 println!(" {}: {}", name, n);
92 }
93
94 let n = issues.len();
95 if n > 0 {
96 println!("\nFound {} issue(s)", n);
97 std::process::exit(1);
98 } else {
99 println!("\nNo issues found \u{2713}");
100 }
101
102 Ok(())
103}