use crate::rules::{Evidence, Finding, Rule, Stance, Trend};
use crate::shell::Parsed;
pub const RULE: Rule = Rule {
id: "git-add-broad",
default_stance: Stance::Observe,
evidence: Evidence {
per_1000: 5.4,
measured: "2026-08-20",
trend: Trend::Improving,
},
examine,
confirm: None,
};
fn examine(parsed: &Parsed) -> Option<Finding> {
for cmd in parsed.clauses() {
if cmd.program() != Some("git") || cmd.subcommand() != Some("add") {
continue;
}
let paths: Vec<&str> = cmd
.operands()
.iter()
.skip(1)
.map(|w| w.text.as_str())
.collect();
let dot = paths.iter().any(|p| *p == "." || *p == "./");
let broad = cmd.has_flag("--all")
|| cmd.has_flag("--update")
|| cmd.has_short('A')
|| cmd.has_short('u');
if !broad && !dot {
continue;
}
if paths.iter().any(|p| *p != "." && *p != "./") {
continue;
}
if cmd.has_short('p') || cmd.has_flag("--patch") {
continue;
}
return Some(Finding {
reason: "this stages every modified file in the tree, not the files this \
change is about — and `git add` is additive, so anything staged \
earlier in the session stays staged too."
.to_string(),
remedy: "Name the paths, or scope the flag to a directory \
(`git add -A packages/thing/`). Run `git status` before \
committing to see what is actually staged."
.to_string(),
span: cmd.at..cmd.end,
});
}
None
}