evolving 0.1.2

git for decisions — an immutable, content-addressed ledger of human-authored decisions that resurfaces when a bound check goes red
Documentation
use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};

fn ev() -> Command {
    Command::new(env!("CARGO_BIN_EXE_ev"))
}
fn repo() -> std::path::PathBuf {
    static N: AtomicU64 = AtomicU64::new(0);
    let p = std::env::temp_dir().join(format!(
        "ev-brief-{}-{}",
        std::process::id(),
        N.fetch_add(1, Ordering::Relaxed)
    ));
    let _ = std::fs::remove_dir_all(&p);
    std::fs::create_dir_all(&p).unwrap();
    assert!(ev()
        .arg("init")
        .current_dir(&p)
        .output()
        .unwrap()
        .status
        .success());
    p
}
/// Record a decision, passing through any extra flags (e.g. --authority, --reject).
fn decide(repo: &std::path::Path, text: &str, extra: &[&str]) -> String {
    let mut args: Vec<&str> = vec!["decide", text, "--assume", "a reason", "--blame", "Wang Yu"];
    args.extend_from_slice(extra);
    let out = ev().args(&args).current_dir(repo).output().unwrap();
    assert!(
        out.status.success(),
        "decide: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    String::from_utf8_lossy(&out.stdout)
        .split_whitespace()
        .nth(1)
        .unwrap()
        .to_string()
}

#[test]
fn brief_should_show_a_user_ruled_decision_and_its_rejected_road_when_one_is_recorded() {
    // given: a user-ruled decision that records a road it rejected
    let r = repo();
    decide(
        &r,
        "keep the slice locked",
        &[
            "--authority",
            "user-ruled",
            "--reject",
            "v1.9: re-milestoned without sign-off",
        ],
    );

    // when: ev brief runs
    let out = ev().arg("brief").current_dir(&r).output().unwrap();

    // then: it exits 0 and names the decision, its authority, and the rejected road
    assert!(out.status.success());
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(stdout.contains("keep the slice locked"));
    assert!(stdout.contains("[user-ruled]"));
    assert!(stdout.contains("rejected v1.9"));
}

#[test]
fn brief_should_omit_a_decision_that_is_not_user_ruled_when_briefing() {
    // given: a plain decision with no declared authority
    let r = repo();
    decide(&r, "a disposable decision", &[]);

    // when: ev brief runs
    let out = ev().arg("brief").current_dir(&r).output().unwrap();

    // then: it exits 0 and shows none (the decision is not user-ruled)
    assert!(out.status.success());
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(!stdout.contains("a disposable decision"));
    assert!(stdout.contains("no user-ruled decisions"));
}

#[test]
fn brief_should_accept_a_limit_flag_when_one_is_passed() {
    // given: a user-ruled decision in the store
    let r = repo();
    decide(&r, "keep the slice locked", &["--authority", "user-ruled"]);

    // when: ev brief runs with --limit
    let out = ev()
        .args(["brief", "--limit", "1"])
        .current_dir(&r)
        .output()
        .unwrap();

    // then: the flag is accepted (exits 0) and the decision still shows
    assert!(out.status.success());
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(stdout.contains("keep the slice locked"));
}

#[test]
fn brief_should_order_user_ruled_decisions_most_recent_first_when_multiple_exist() {
    // given: two user-ruled decisions recorded in order — A first, then B (B is later)
    let r = repo();
    decide(&r, "decision A", &["--authority", "user-ruled"]);
    decide(&r, "decision B", &["--authority", "user-ruled"]);

    // when: ev brief runs
    let out = ev().arg("brief").current_dir(&r).output().unwrap();

    // then: B's text appears before A's (most-recent-first by held_since)
    assert!(out.status.success());
    let stdout = String::from_utf8_lossy(&out.stdout);
    let b = stdout.find("decision B").expect("B shown");
    let a = stdout.find("decision A").expect("A shown");
    assert!(b < a, "B should appear before A:\n{stdout}");
}

#[test]
fn brief_should_cap_and_note_the_remainder_when_over_the_limit() {
    // given: three user-ruled decisions
    let r = repo();
    decide(&r, "decision one", &["--authority", "user-ruled"]);
    decide(&r, "decision two", &["--authority", "user-ruled"]);
    decide(&r, "decision three", &["--authority", "user-ruled"]);

    // when: ev brief runs with --limit 2
    let out = ev()
        .args(["brief", "--limit", "2"])
        .current_dir(&r)
        .output()
        .unwrap();

    // then: exactly two decisions show plus a remainder footer pointing at `ev list`
    assert!(out.status.success());
    let stdout = String::from_utf8_lossy(&out.stdout);
    let shown = ["decision one", "decision two", "decision three"]
        .iter()
        .filter(|d| stdout.contains(**d))
        .count();
    assert_eq!(shown, 2, "exactly two decisions shown:\n{stdout}");
    assert!(stdout.contains("1 more"), "remainder count:\n{stdout}");
    assert!(stdout.contains("ev list"), "points at ev list:\n{stdout}");
}

#[test]
fn brief_should_show_all_when_limit_is_zero() {
    // given: three user-ruled decisions
    let r = repo();
    decide(&r, "decision one", &["--authority", "user-ruled"]);
    decide(&r, "decision two", &["--authority", "user-ruled"]);
    decide(&r, "decision three", &["--authority", "user-ruled"]);

    // when: ev brief runs with --limit 0
    let out = ev()
        .args(["brief", "--limit", "0"])
        .current_dir(&r)
        .output()
        .unwrap();

    // then: all three show and there is no remainder footer
    assert!(out.status.success());
    let stdout = String::from_utf8_lossy(&out.stdout);
    let shown = ["decision one", "decision two", "decision three"]
        .iter()
        .filter(|d| stdout.contains(**d))
        .count();
    assert_eq!(shown, 3, "all three shown:\n{stdout}");
    assert!(!stdout.contains("more user-ruled"), "no footer:\n{stdout}");
}

#[test]
fn brief_should_fail_when_there_is_no_store() {
    // given: a bare directory with no .evolving store
    let p = std::env::temp_dir().join(format!("ev-brief-nostore-{}", std::process::id()));
    let _ = std::fs::remove_dir_all(&p);
    std::fs::create_dir_all(&p).unwrap();

    // when: ev brief runs
    let out = ev().arg("brief").current_dir(&p).output().unwrap();

    // then: it errors and exits non-zero
    assert!(!out.status.success());
}

#[test]
fn brief_should_pin_a_rejected_road_ruling_above_the_cap_over_newer_plain_rulings() {
    // given: an OLD user-ruled decision that closed a road, then several NEWER plain user-ruled ones
    let r = repo();
    decide(
        &r,
        "the closed road",
        &["--authority", "user-ruled", "--reject", "x: closed"],
    );
    decide(&r, "newer plain one", &["--authority", "user-ruled"]);
    decide(&r, "newer plain two", &["--authority", "user-ruled"]);
    decide(&r, "newer plain three", &["--authority", "user-ruled"]);

    // when: ev brief runs with a cap of 2
    let out = ev()
        .args(["brief", "--limit", "2"])
        .current_dir(&r)
        .output()
        .unwrap();

    // then: the OLD rejected-road decision is pinned and shown, not buried by the newer plain ones
    assert!(out.status.success());
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(
        stdout.contains("the closed road"),
        "load-bearing ruling must be pinned above the cap:\n{stdout}"
    );
}

#[test]
fn brief_should_count_hidden_load_bearing_rulings_in_the_footer_when_they_exceed_the_cap() {
    // given: three user-ruled decisions, EACH closing a road
    let r = repo();
    decide(
        &r,
        "closed road one",
        &["--authority", "user-ruled", "--reject", "a: one"],
    );
    decide(
        &r,
        "closed road two",
        &["--authority", "user-ruled", "--reject", "b: two"],
    );
    decide(
        &r,
        "closed road three",
        &["--authority", "user-ruled", "--reject", "c: three"],
    );

    // when: ev brief runs with a cap of 1
    let out = ev()
        .args(["brief", "--limit", "1"])
        .current_dir(&r)
        .output()
        .unwrap();

    // then: the footer makes the elided load-bearing rulings visible by counting them
    assert!(out.status.success());
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(
        stdout.contains("2 with rejected roads"),
        "footer must count hidden load-bearing rulings:\n{stdout}"
    );
}

#[test]
fn brief_should_not_mention_rejected_roads_in_the_footer_when_none_are_hidden() {
    // given: one rejected-road ruling plus three plain user-ruled decisions
    let r = repo();
    decide(
        &r,
        "the closed road",
        &["--authority", "user-ruled", "--reject", "x: closed"],
    );
    decide(&r, "plain one", &["--authority", "user-ruled"]);
    decide(&r, "plain two", &["--authority", "user-ruled"]);
    decide(&r, "plain three", &["--authority", "user-ruled"]);

    // when: ev brief runs with a cap of 2 (the rejected-road ruling is pinned + shown)
    let out = ev()
        .args(["brief", "--limit", "2"])
        .current_dir(&r)
        .output()
        .unwrap();

    // then: the footer counts the two hidden plain rulings and does NOT mention rejected roads
    assert!(out.status.success());
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(
        stdout.contains("… 2 more user-ruled decision(s) — `ev list` for all"),
        "footer must read exactly without rejected-roads clause:\n{stdout}"
    );
    assert!(
        !stdout.contains("with rejected roads"),
        "no hidden load-bearing ruling, so no rejected-roads clause:\n{stdout}"
    );
}