truth-mirror 0.3.0

Truthfulness gate and adversarial reviewer harness for AI coding agents.
Documentation
use assert_cmd::Command;
use predicates::prelude::*;
use truth_mirror::ledger::{LedgerEntry, LedgerStore, ReviewerConfig, Verdict};

fn seed_rejection(state_dir: &std::path::Path, sha: &str) {
    let store = LedgerStore::new(state_dir);
    store
        .append_entry(&LedgerEntry::new_at(
            sha,
            Verdict::Reject,
            "CLAIM: rejected | verified: cargo test | evidence: tests:cargo-test",
            vec!["tests:cargo-test".to_owned()],
            ReviewerConfig::new("claude", "claude-opus-4-1", false),
            vec!["unsupported claim".to_owned()],
            100,
        ))
        .unwrap();
}

#[test]
fn ledger_list_shows_unresolved_rejection() {
    let temp = tempfile::tempdir().unwrap();
    seed_rejection(temp.path(), "abc123");

    Command::cargo_bin("truth-mirror")
        .unwrap()
        .args([
            "--state-dir",
            temp.path().to_str().unwrap(),
            "ledger",
            "list",
        ])
        .assert()
        .success()
        .stdout(predicate::str::contains("abc123"))
        .stdout(predicate::str::contains("REJECT"));
}

#[test]
fn ledger_show_prints_entry_details() {
    let temp = tempfile::tempdir().unwrap();
    seed_rejection(temp.path(), "abc123");

    Command::cargo_bin("truth-mirror")
        .unwrap()
        .args([
            "--state-dir",
            temp.path().to_str().unwrap(),
            "ledger",
            "show",
            "abc123",
        ])
        .assert()
        .success()
        .stdout(predicate::str::contains("unsupported claim"));
}

#[test]
fn ledger_resolve_clears_unresolved_stats() {
    let temp = tempfile::tempdir().unwrap();
    seed_rejection(temp.path(), "abc123");

    Command::cargo_bin("truth-mirror")
        .unwrap()
        .args([
            "--state-dir",
            temp.path().to_str().unwrap(),
            "ledger",
            "resolve",
            "abc123",
        ])
        .assert()
        .success()
        .stdout(predicate::str::contains("resolved abc123"));

    Command::cargo_bin("truth-mirror")
        .unwrap()
        .args([
            "--state-dir",
            temp.path().to_str().unwrap(),
            "ledger",
            "stats",
        ])
        .assert()
        .success()
        .stdout(predicate::str::contains("unresolved=0"))
        .stdout(predicate::str::contains("resolved=1"));
}

#[test]
fn ledger_waive_records_reason() {
    let temp = tempfile::tempdir().unwrap();
    seed_rejection(temp.path(), "abc123");

    Command::cargo_bin("truth-mirror")
        .unwrap()
        .args([
            "--state-dir",
            temp.path().to_str().unwrap(),
            "ledger",
            "waive",
            "abc123",
            "--reason",
            "Ramiro approved exception",
        ])
        .assert()
        .success()
        .stdout(predicate::str::contains("waived abc123"));

    let markdown = std::fs::read_to_string(temp.path().join("ledger.md")).unwrap();
    assert!(markdown.contains("Ramiro approved exception"));
}