1use std::path::Path;
4
5#[derive(Debug, Clone)]
9pub struct AuditConfig {
10 pub root_markers: Vec<&'static str>,
13
14 pub include_claude_md: bool,
17
18 pub source_extensions: Vec<&'static str>,
21
22 pub source_dirs: Vec<&'static str>,
25
26 pub skip_dirs: Vec<&'static str>,
28}
29
30impl AuditConfig {
31 pub fn agent_doc() -> Self {
34 Self {
35 root_markers: vec![
36 "Cargo.toml",
37 "package.json",
38 "pyproject.toml",
39 "setup.py",
40 "go.mod",
41 "Gemfile",
42 "pom.xml",
43 "build.gradle",
44 "CMakeLists.txt",
45 "Makefile",
46 "flake.nix",
47 "deno.json",
48 "composer.json",
49 ],
50 include_claude_md: true,
51 source_extensions: vec![
52 "rs", "ts", "tsx", "js", "jsx", "py", "go", "rb", "java", "kt", "c", "cpp", "h",
53 "hpp", "cs", "swift", "zig", "hs", "ml", "ex", "exs", "clj", "scala", "lua",
54 "php", "sh", "bash", "zsh",
55 ],
56 source_dirs: vec!["src", "lib", "app", "pkg", "cmd", "internal"],
57 skip_dirs: vec![
58 "node_modules",
59 "target",
60 "build",
61 "dist",
62 ".git",
63 "__pycache__",
64 ".venv",
65 "vendor",
66 ".next",
67 "out",
68 ],
69 }
70 }
71
72 pub fn corky() -> Self {
75 Self {
76 root_markers: vec!["Cargo.toml"],
77 include_claude_md: false,
78 source_extensions: vec!["rs"],
79 source_dirs: vec!["src"],
80 skip_dirs: vec!["target", ".git"],
81 }
82 }
83}
84
85pub struct Issue {
87 pub file: String,
88 pub line: usize,
89 pub end_line: usize,
90 pub message: String,
91 pub warning: bool,
92}
93
94pub fn is_agent_file(rel: &str, config: &AuditConfig) -> bool {
96 let name = Path::new(rel)
97 .file_name()
98 .and_then(|n| n.to_str())
99 .unwrap_or("");
100 if name == "AGENTS.md" || name == "SKILL.md" {
101 return true;
102 }
103 if config.include_claude_md && name == "CLAUDE.md" {
104 return true;
105 }
106 false
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn is_agent_file_with_claude() {
115 let config = AuditConfig::agent_doc();
116 assert!(is_agent_file("AGENTS.md", &config));
117 assert!(is_agent_file("SKILL.md", &config));
118 assert!(is_agent_file("CLAUDE.md", &config));
119 assert!(is_agent_file("src/AGENTS.md", &config));
120 assert!(is_agent_file(".claude/skills/email/SKILL.md", &config));
121 assert!(is_agent_file("nested/path/CLAUDE.md", &config));
122 }
123
124 #[test]
125 fn is_agent_file_without_claude() {
126 let config = AuditConfig::corky();
127 assert!(is_agent_file("AGENTS.md", &config));
128 assert!(is_agent_file("SKILL.md", &config));
129 assert!(!is_agent_file("CLAUDE.md", &config));
130 }
131
132 #[test]
133 fn is_agent_file_rejects() {
134 let config = AuditConfig::agent_doc();
135 assert!(!is_agent_file("README.md", &config));
136 assert!(!is_agent_file("agents.md", &config));
137 assert!(!is_agent_file("CHANGELOG.md", &config));
138 assert!(!is_agent_file("src/main.rs", &config));
139 }
140}