use super::super::types::{Item, Kind};
use super::days_between;
use crate::check::rules::support::quote;
use crate::corpus::Corpus;
use crate::workspace::vcs;
pub fn run(c: &Corpus, docs: &[usize], out: &mut Vec<Item>) {
let limit = c.cfg.raw.audit.stale_days;
for &i in docs {
let d = &c.docs[i];
if d.status().as_deref() != Some("active") {
continue;
}
let governs = d.rel().remove("governs").unwrap_or_default();
if governs.is_empty() {
continue;
}
if let Some(pin) = d.str_field("verified_at") {
let docs = vec![format!("**/{}/**", c.cfg.spec_dir()), format!("{}/**", c.cfg.spec_dir())];
let repin = format!("spec set {} verified_at=HEAD --write", crate::shell::word(&d.id));
let (detail, next) = match vcs::commits_since(&c.cfg.root, &pin, &governs, &docs) {
None => (format!("verified_at {pin} is not a commit in this repository"), repin),
Some(list) if list.is_empty() => continue,
Some(list) => (
format!(
"governed code changed in {} commit(s) since verified_at {pin} (latest {})",
list.len(),
list[0]
),
format!(
"git diff {pin}..HEAD -- {} # then update the body and: {repin}",
governs.iter().map(|g| quote(g)).collect::<Vec<_>>().join(" ")
),
),
};
out.push(Item {
kind: Kind::Stale,
subject: d.id.to_string(),
path: d.path.clone(),
line: 1,
detail,
next: Some(next),
});
continue;
}
let Some(updated) = d.str_field("updated") else { continue };
let Some(code) = vcs::last_change(&c.cfg.root, &governs) else { continue };
let Some(gap) = days_between(&updated, &code) else { continue };
if gap > limit {
out.push(Item {
kind: Kind::Stale,
subject: d.id.to_string(),
path: d.path.clone(),
line: 1,
detail: format!("governed code changed {code}, {gap} days after the document ({updated})"),
next: Some(format!(
"spec show {} && spec log {}",
crate::shell::word(&d.id),
crate::shell::word(&d.id)
)),
});
}
}
}