truth-mirror 0.2.0

Truthfulness gate and adversarial reviewer harness for AI coding agents.
Documentation
use std::{fs, path::Path};

use assert_cmd::Command;
use predicates::prelude::*;

fn git(repo: &Path, args: &[&str]) {
    let status = std::process::Command::new("git")
        .args(args)
        .current_dir(repo)
        .status()
        .unwrap();
    assert!(status.success(), "git {args:?} failed");
}

#[cfg(unix)]
fn make_executable(path: &Path) {
    use std::os::unix::fs::PermissionsExt;

    let mut permissions = fs::metadata(path).unwrap().permissions();
    permissions.set_mode(0o755);
    fs::set_permissions(path, permissions).unwrap();
}

#[cfg(not(unix))]
fn make_executable(_path: &Path) {}

#[test]
fn review_command_writes_ledger_with_mocked_reviewer_cli() {
    let temp = tempfile::tempdir().unwrap();
    let repo = temp.path().join("repo");
    let bin = temp.path().join("bin");
    fs::create_dir(&repo).unwrap();
    fs::create_dir(&bin).unwrap();

    git(&repo, &["init"]);
    git(&repo, &["config", "user.email", "truth@example.invalid"]);
    git(&repo, &["config", "user.name", "Truth Mirror Test"]);
    fs::write(repo.join("file.txt"), "hello\n").unwrap();
    git(&repo, &["add", "file.txt"]);
    git(
        &repo,
        &[
            "commit",
            "-m",
            "feat: add file",
            "-m",
            "CLAIM: add file | verified: cargo test | evidence: tests:review-e2e",
        ],
    );

    let fake_codex = bin.join("codex");
    fs::write(
        &fake_codex,
        "#!/usr/bin/env python3\nprint('VERDICT: PASS')\nprint('FINDINGS:')\n",
    )
    .unwrap();
    make_executable(&fake_codex);

    let path = format!(
        "{}:{}",
        bin.display(),
        std::env::var("PATH").unwrap_or_default()
    );

    Command::cargo_bin("truth-mirror")
        .unwrap()
        .current_dir(&repo)
        .env("PATH", path)
        .args([
            "--state-dir",
            ".truth-mirror",
            "review",
            "HEAD",
            "--watched-agent",
            "claude",
            "--watched-model",
            "model-a",
            "--reviewer-harness",
            "codex",
            "--reviewer-model",
            "model-b",
        ])
        .assert()
        .success();

    let ledger = fs::read_to_string(repo.join(".truth-mirror/ledger.jsonl")).unwrap();
    assert!(ledger.contains("\"verdict\":\"PASS\""));

    Command::cargo_bin("truth-mirror")
        .unwrap()
        .current_dir(&repo)
        .args(["--state-dir", ".truth-mirror", "ledger", "stats"])
        .assert()
        .success()
        .stdout(predicate::str::contains("pass=1"));
}

#[test]
fn review_strict_goal_loops_until_lie_threshold() {
    let temp = tempfile::tempdir().unwrap();
    let repo = temp.path().join("repo");
    let bin = temp.path().join("bin");
    fs::create_dir(&repo).unwrap();
    fs::create_dir(&bin).unwrap();

    git(&repo, &["init"]);
    git(&repo, &["config", "user.email", "truth@example.invalid"]);
    git(&repo, &["config", "user.name", "Truth Mirror Test"]);
    fs::write(repo.join("file.txt"), "hello\n").unwrap();
    git(&repo, &["add", "file.txt"]);
    git(
        &repo,
        &[
            "commit",
            "-m",
            "feat: add file",
            "-m",
            "CLAIM: add file | verified: cargo test | evidence: tests:strict-goal-e2e",
        ],
    );

    // Reviewer always rejects; the loop should stop the first time it exposes a lie.
    let fake_codex = bin.join("codex");
    fs::write(
        &fake_codex,
        "#!/usr/bin/env python3\nprint('VERDICT: REJECT')\nprint('FINDINGS:')\nprint('- fabricated evidence')\n",
    )
    .unwrap();
    make_executable(&fake_codex);

    let path = format!(
        "{}:{}",
        bin.display(),
        std::env::var("PATH").unwrap_or_default()
    );

    Command::cargo_bin("truth-mirror")
        .unwrap()
        .current_dir(&repo)
        .env("PATH", path)
        .args([
            "--state-dir",
            ".truth-mirror",
            "review",
            "HEAD",
            "--watched-agent",
            "claude",
            "--watched-model",
            "model-a",
            "--reviewer-harness",
            "codex",
            "--reviewer-model",
            "model-b",
            "--strict-goal",
            "--stop-after-lies",
            "1",
            "--max-passes",
            "5",
        ])
        .assert()
        .success()
        .stdout(predicate::str::contains("1 pass"))
        .stdout(predicate::str::contains("1 lie"))
        .stdout(predicate::str::contains("stopped: lies exposed"));

    let ledger = fs::read_to_string(repo.join(".truth-mirror/ledger.jsonl")).unwrap();
    assert!(ledger.contains("\"verdict\":\"REJECT\""));
}