claude_native/rules/
context_extra.rs1use crate::rules::*;
2use crate::scan::ProjectContext;
3
4pub struct NoSecretsInRepo;
7
8impl Rule for NoSecretsInRepo {
9 fn id(&self) -> &str { "2.5" }
10 fn name(&self) -> &str { "No secrets in the repository" }
11 fn dimension(&self) -> Dimension { Dimension::ContextEfficiency }
12 fn severity(&self) -> Severity { Severity::Critical }
13
14 fn check(&self, ctx: &ProjectContext) -> RuleResult {
15 if ctx.env_files.is_empty() { return self.pass(); }
16 let real: Vec<_> = ctx.env_files.iter()
17 .filter(|f| {
18 let name = f.file_name().and_then(|n| n.to_str()).unwrap_or("");
19 !name.contains("example") && !name.contains("sample") && !name.contains("template")
20 })
21 .collect();
22 if real.is_empty() { return self.pass(); }
23 let names: Vec<String> = real.iter().take(5)
24 .filter_map(|f| f.file_name().and_then(|n| n.to_str()).map(String::from))
25 .collect();
26 self.fail(
27 &format!("Potential secret files: {}", names.join(", ")),
28 Suggestion {
29 priority: SuggestionPriority::QuickWin,
30 title: "Remove/ignore secret files".into(),
31 description: "Add .env, .env.*, *.pem, *.key to .gitignore AND .claudeignore.".into(),
32 effort: Effort::Minutes,
33 },
34 )
35 }
36}
37
38pub struct ReadmeExistsAndConcise;
41
42impl Rule for ReadmeExistsAndConcise {
43 fn id(&self) -> &str { "2.6" }
44 fn name(&self) -> &str { "README exists and is concise" }
45 fn dimension(&self) -> Dimension { Dimension::ContextEfficiency }
46 fn severity(&self) -> Severity { Severity::Low }
47
48 fn check(&self, ctx: &ProjectContext) -> RuleResult {
49 if ctx.readme_content.is_none() {
50 return self.fail("No README.md found", Suggestion {
51 priority: SuggestionPriority::NiceToHave,
52 title: "Create README.md".into(),
53 description: "Create a concise README.md (<300 lines).".into(),
54 effort: Effort::Hour,
55 });
56 }
57 let lines = ctx.readme_line_count();
58 if lines <= 300 { self.pass() }
59 else {
60 self.warn(
61 &format!("README.md is {lines} lines (target: <300)"),
62 Suggestion {
63 priority: SuggestionPriority::NiceToHave,
64 title: "Trim README.md".into(),
65 description: "Move detailed docs to docs/.".into(),
66 effort: Effort::Hour,
67 },
68 )
69 }
70 }
71}
72
73pub struct SubdirClaudeMd;
76
77impl Rule for SubdirClaudeMd {
78 fn id(&self) -> &str { "2.7" }
79 fn name(&self) -> &str { "Subdirectory CLAUDE.md for large projects" }
80 fn dimension(&self) -> Dimension { Dimension::ContextEfficiency }
81 fn severity(&self) -> Severity { Severity::Low }
82
83 fn check(&self, ctx: &ProjectContext) -> RuleResult {
84 let count = ctx.source_file_count();
85 if count <= 20 { return self.pass(); }
86 if !ctx.subdirectory_claude_mds.is_empty() { self.pass() }
87 else {
88 self.warn(
89 &format!("{count} source files but no subdirectory CLAUDE.md files"),
90 Suggestion {
91 priority: SuggestionPriority::NiceToHave,
92 title: "Add CLAUDE.md to major subdirectories".into(),
93 description: "Subdirectory CLAUDE.md files load on-demand, saving tokens.".into(),
94 effort: Effort::Hour,
95 },
96 )
97 }
98 }
99}