koala-drift 1.0.4

Wiki ↔ code drift detector.
Documentation
//! `koala-core drift --explain` rendering contract.

use koala_core::invariant::Context;
use koala_drift::{format_report, Registry as DriftRegistry};
use std::fs;
use tempfile::TempDir;

fn write(dir: &std::path::Path, rel: &str, body: &str) {
    let p = dir.join(rel);
    fs::create_dir_all(p.parent().unwrap()).unwrap();
    fs::write(p, body).unwrap();
}

fn run_with_violation() -> (DriftRegistry, Vec<koala_drift::Finding>) {
    let tmp = TempDir::new().unwrap();
    let body = "---\n\
id: x\n\
status: in-progress\n\
---\n\
# X\n\n\
## Acceptance criteria(机械可验证)\n\n\
- [ ] does the thing\n\
  → crates/x/tests/missing.rs#none\n";
    write(tmp.path(), "wiki/features/x.md", body);
    let ctx = Context::new(tmp.path().to_path_buf());
    let reg = DriftRegistry::builtin();
    let findings = reg.run_all(&ctx);
    // Tempdir lives only inside this fn — clone so caller can use findings.
    (reg, findings.to_vec())
}

#[test]
fn explain_offers_fix_hints() {
    let (reg, findings) = run_with_violation();
    let out = format_report(&reg, &findings, true);
    assert!(
        out.contains("intent:"),
        "explain mode missing intent: {out}"
    );
    assert!(
        out.contains("▶ hint:"),
        "should always show fix hint: {out}"
    );
    assert!(
        out.contains("[feature.acceptance-test-ref]"),
        "should label finding by check id: {out}"
    );
}

#[test]
fn non_explain_truncates_claim_and_omits_intent() {
    let (reg, findings) = run_with_violation();
    let out = format_report(&reg, &findings, false);
    assert!(
        !out.contains("intent:"),
        "non-explain mode should not show intent: {out}"
    );
    assert!(out.contains("▶ hint:"), "fix hint always shown: {out}");
}

#[test]
fn empty_findings_reports_clean() {
    let tmp = TempDir::new().unwrap();
    let ctx = Context::new(tmp.path().to_path_buf());
    let reg = DriftRegistry::builtin();
    let findings = reg.run_all(&ctx);
    let out = format_report(&reg, &findings, true);
    assert!(
        out.starts_with("drift: no findings"),
        "empty repo should report clean: {out}"
    );
    let expected = format!("{} check(s)", reg.len());
    assert!(
        out.contains(&expected),
        "should mention check count ({expected}): {out}"
    );
}