truth-mirror 0.1.0

Truthfulness gate and adversarial reviewer harness for AI coding agents.
Documentation
use assert_cmd::Command;
use predicates::prelude::*;

#[test]
fn install_hooks_dry_run_prints_plan_without_writing_hooks() {
    let temp = tempfile::tempdir().unwrap();
    std::process::Command::new("git")
        .arg("init")
        .current_dir(temp.path())
        .status()
        .unwrap();

    Command::cargo_bin("truth-mirror")
        .unwrap()
        .current_dir(temp.path())
        .args(["install-hooks", "--dry-run", "--claude", "--codex", "--pi"])
        .assert()
        .success()
        .stdout(predicate::str::contains("truth-mirror hook plan"))
        .stdout(predicate::str::contains("commit-msg"))
        .stdout(predicate::str::contains("pre-push"))
        .stdout(predicate::str::contains(
            "surface: claude -> .claude/settings.json",
        ))
        .stdout(predicate::str::contains(
            "surface: codex -> .codex/hooks.json",
        ))
        .stdout(predicate::str::contains("surface: pi -> .pi/hooks.json"));

    assert!(!temp.path().join(".truth-mirror/hooks/commit-msg").exists());
    assert!(!temp.path().join(".claude/settings.json").exists());
    assert!(!temp.path().join(".codex/hooks.json").exists());
    assert!(!temp.path().join(".pi/hooks.json").exists());
}

fn git_init(dir: &std::path::Path) {
    std::process::Command::new("git")
        .arg("init")
        .current_dir(dir)
        .status()
        .unwrap();
}

#[test]
fn install_hooks_writes_selected_agent_surfaces() {
    let temp = tempfile::tempdir().unwrap();
    git_init(temp.path());

    Command::cargo_bin("truth-mirror")
        .unwrap()
        .current_dir(temp.path())
        .args(["install-hooks", "--claude", "--codex", "--pi"])
        .assert()
        .success();

    let claude = std::fs::read_to_string(temp.path().join(".claude/settings.json")).unwrap();
    assert!(claude.contains("UserPromptSubmit"));
    assert!(claude.contains("truth-mirror reinject --agent claude"));

    let codex = std::fs::read_to_string(temp.path().join(".codex/hooks.json")).unwrap();
    assert!(codex.contains("truth-mirror reinject --agent codex"));

    let pi = std::fs::read_to_string(temp.path().join(".pi/hooks.json")).unwrap();
    assert!(pi.contains("truth-mirror reinject --agent pi"));
}

#[test]
fn install_hooks_only_touches_selected_agents() {
    let temp = tempfile::tempdir().unwrap();
    git_init(temp.path());

    Command::cargo_bin("truth-mirror")
        .unwrap()
        .current_dir(temp.path())
        .args(["install-hooks", "--claude"])
        .assert()
        .success();

    assert!(temp.path().join(".claude/settings.json").exists());
    assert!(!temp.path().join(".codex/hooks.json").exists());
    assert!(!temp.path().join(".pi/hooks.json").exists());
}

#[test]
fn install_is_non_clobbering_and_uninstall_reverses_only_our_entries() {
    let temp = tempfile::tempdir().unwrap();
    git_init(temp.path());
    std::fs::create_dir_all(temp.path().join(".claude")).unwrap();
    std::fs::write(
        temp.path().join(".claude/settings.json"),
        r#"{"model":"sonnet","hooks":{"PreToolUse":[{"matcher":"Bash"}]}}"#,
    )
    .unwrap();

    Command::cargo_bin("truth-mirror")
        .unwrap()
        .current_dir(temp.path())
        .args(["install-hooks", "--claude"])
        .assert()
        .success();

    let after_install = std::fs::read_to_string(temp.path().join(".claude/settings.json")).unwrap();
    assert!(after_install.contains("sonnet"));
    assert!(after_install.contains("PreToolUse"));
    assert!(after_install.contains("truth-mirror reinject --agent claude"));

    // Bare uninstall clears all agent surfaces but must preserve foreign config.
    Command::cargo_bin("truth-mirror")
        .unwrap()
        .current_dir(temp.path())
        .args(["install-hooks", "--uninstall"])
        .assert()
        .success();

    let after_uninstall =
        std::fs::read_to_string(temp.path().join(".claude/settings.json")).unwrap();
    assert!(after_uninstall.contains("sonnet"));
    assert!(after_uninstall.contains("PreToolUse"));
    assert!(!after_uninstall.contains("truth-mirror reinject"));
}