use std::{fs, path::Path};
use assert_cmd::Command;
use predicates::prelude::*;
use truth_mirror::{
config::{MemorySkillCandidateKind, TruthMirrorConfig},
ledger::{LedgerEntry, LedgerStore, ReviewerConfig, Verdict},
memory_skill::{
CANDIDATE_SCHEMA_VERSION, CandidateStatus, EvidenceRef, MemorySkillCandidate,
MemorySkillStore,
},
reviewer::{QueuedReview, ReviewRun, ReviewRunStatus},
};
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");
}
fn reaped_pid() -> u32 {
let mut child = std::process::Command::new(std::env::current_exe().unwrap())
.arg("--help")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.expect("spawn self with --help");
let pid = child.id();
child.wait().expect("reap self-spawned process");
pid
}
fn git_stdout(repo: &Path, args: &[&str]) -> String {
let output = std::process::Command::new("git")
.args(args)
.current_dir(repo)
.output()
.unwrap();
assert!(output.status.success(), "git {args:?} failed");
String::from_utf8(output.stdout).unwrap().trim().to_owned()
}
#[test]
fn status_reports_hooks_queue_runs_ledger_and_entire_ref() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "user.email", "truth@example.invalid"]);
git(&repo, &["config", "user.name", "Truth Mirror Test"]);
fs::create_dir_all(repo.join(".entire/checkpoints")).unwrap();
fs::write(
repo.join(".entire/checkpoints/a1b2c3d4e5f6.json"),
r#"{"id":"a1b2c3d4e5f6"}"#,
)
.unwrap();
fs::write(repo.join("file.txt"), "hello\n").unwrap();
git(&repo, &["add", "file.txt"]);
git(
&repo,
&["add", "-f", ".entire/checkpoints/a1b2c3d4e5f6.json"],
);
git(
&repo,
&[
"commit",
"-m",
"feat: base",
"-m",
"CLAIM: base | verified: cargo test | evidence: tests:status\n\nEntire-Checkpoint: a1b2c3d4e5f6",
],
);
let sha = git_stdout(&repo, &["rev-parse", "HEAD"]);
git(
&repo,
&["update-ref", "refs/heads/entire/checkpoints/v1", "HEAD"],
);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
let state = repo.join(".truth");
let queued_review = QueuedReview {
run_id: String::new(),
commit_sha: sha.clone(),
enqueued_at_unix: 1,
petition_for: None,
batch_id: None,
};
fs::write(
state.join("review-queue.jsonl"),
format!("{}\n", serde_json::to_string(&queued_review).unwrap()),
)
.unwrap();
fs::create_dir_all(state.join("runs")).unwrap();
let run = ReviewRun {
id: "run-1".to_owned(),
commit_sha: sha.clone(),
target: "commit".to_owned(),
status: ReviewRunStatus::Queued,
phase: "queued".to_owned(),
ledger_entries: 0,
error: None,
worker_pid: None,
worker_start_token: None,
created_at_unix: 1,
updated_at_unix: 1,
started_at_unix: None,
completed_at_unix: None,
entire_checkpoint: None,
petition_for: None,
ledger_applied_phase: None,
pending_review: None,
batch: None,
claimed_at_unix: None,
next_attempt_at_unix: None,
worktree_path: None,
};
fs::write(
state.join("runs/run-1.json"),
serde_json::to_string(&run).unwrap(),
)
.unwrap();
LedgerStore::new(&state)
.append_entry(&LedgerEntry::new_at(
&sha,
Verdict::Reject,
"CLAIM: rejected | verified: cargo test | evidence: tests:status",
vec!["tests:status".to_owned()],
ReviewerConfig::new("claude", "model-b", false),
vec!["unsupported".to_owned()],
100,
))
.unwrap();
let subdir = repo.join("nested");
fs::create_dir(&subdir).unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&subdir)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: plain"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("queue: pending=1"))
.stdout(predicate::str::contains("review_runs: queued=1"))
.stdout(predicate::str::contains("review_run_liveness: ok"))
.stdout(predicate::str::contains(
"ledger: blocking_rejections=1 (needs_human=0)",
))
.stdout(predicate::str::contains(
"entire: ref=entire/checkpoints/v1",
));
}
#[test]
fn status_observes_scheduler_state_read_only_without_mutating_queue() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).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: base",
"-m",
"CLAIM: base | verified: cargo test | evidence: tests:status",
],
);
let sha = git_stdout(&repo, &["rev-parse", "HEAD"]);
let state = repo.join(".truth");
fs::create_dir_all(state.join("runs")).unwrap();
let queued_review = QueuedReview {
run_id: "run-queued".to_owned(),
commit_sha: sha.clone(),
enqueued_at_unix: 10,
petition_for: None,
batch_id: Some("batch-1".to_owned()),
};
let queue_path = state.join("review-queue.jsonl");
fs::write(
&queue_path,
format!("{}\n", serde_json::to_string(&queued_review).unwrap()),
)
.unwrap();
let before = fs::read(&queue_path).unwrap();
let queued = ReviewRun {
id: "run-queued".to_owned(),
commit_sha: sha.clone(),
target: "commit".to_owned(),
status: ReviewRunStatus::Queued,
phase: "queued".to_owned(),
ledger_entries: 0,
error: None,
worker_pid: None,
worker_start_token: None,
created_at_unix: 10,
updated_at_unix: 10,
started_at_unix: None,
completed_at_unix: None,
entire_checkpoint: None,
petition_for: None,
ledger_applied_phase: None,
pending_review: None,
batch: None,
claimed_at_unix: None,
next_attempt_at_unix: None,
worktree_path: None,
};
let running = ReviewRun {
id: "run-running".to_owned(),
commit_sha: sha.clone(),
target: "commit".to_owned(),
status: ReviewRunStatus::Running,
phase: "review-ledger".to_owned(),
ledger_entries: 0,
error: None,
worker_pid: Some(std::process::id()),
worker_start_token: None,
created_at_unix: 11,
updated_at_unix: 12,
started_at_unix: Some(12),
completed_at_unix: None,
entire_checkpoint: None,
petition_for: None,
ledger_applied_phase: None,
pending_review: None,
batch: None,
claimed_at_unix: Some(12),
next_attempt_at_unix: None,
worktree_path: None,
};
fs::write(
state.join("runs/run-queued.json"),
serde_json::to_string(&queued).unwrap(),
)
.unwrap();
fs::write(
state.join("runs/run-running.json"),
serde_json::to_string(&running).unwrap(),
)
.unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("queue: pending=1"))
.stdout(predicate::str::contains("review_runs: queued=1 running=1"))
.stdout(predicate::str::contains("review_run_liveness: ok"))
.stdout(predicate::str::contains(
"scheduler: owner=none claimed=1 inflight=1 active_workers=1 batches=1",
));
assert_eq!(
fs::read(&queue_path).unwrap(),
before,
"status must observe queue/run state without rewriting queue bytes"
);
}
#[test]
fn status_treats_missing_entire_checkpoint_as_optional() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("entire: none (optional)"));
}
#[test]
fn truth_alias_status_missing_git_repo_uses_alias_prefix() {
let temp = tempfile::tempdir().unwrap();
Command::cargo_bin("truth")
.unwrap()
.current_dir(temp.path())
.args(["status"])
.assert()
.failure()
.stderr(predicate::str::contains(
"this command requires a Git repository",
))
.stderr(predicate::str::contains("truth-mirror status").not());
}
#[test]
fn status_runs_when_config_is_malformed() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
fs::create_dir(repo.join(".truth")).unwrap();
fs::write(repo.join(".truth/config.toml"), "[policy\n").unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: plain"))
.stdout(predicate::str::contains("queue: pending=0"));
}
#[test]
fn status_degrades_malformed_queue_to_warning() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
let state = repo.join(".truth");
fs::create_dir(&state).unwrap();
fs::write(state.join("review-queue.jsonl"), "{not-json\n").unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: plain"))
.stdout(predicate::str::contains("queue: unavailable warning="))
.stdout(predicate::str::contains("review_runs: queued=0"))
.stdout(predicate::str::contains(
"ledger: blocking_rejections=0 (needs_human=0)",
));
}
#[test]
fn status_warns_when_review_run_record_is_malformed() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
let runs = repo.join(".truth/runs");
fs::create_dir_all(&runs).unwrap();
fs::write(runs.join("bad.json"), "{not-json\n").unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains(
"review_runs: queued=0 running=0 completed=0 failed=0 cancelled=0 warning=skipped_records:1",
));
}
#[test]
fn status_recognizes_relative_managed_hooks_path() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".truth/hooks"]);
let hooks = repo.join(".truth/hooks");
fs::create_dir_all(&hooks).unwrap();
fs::write(
hooks.join("commit-msg"),
"#!/bin/sh\n# truth-mirror managed hook\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&hooks.join("commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains(
"hook_mode: plain-legacy-truth-mirror",
))
.stdout(predicate::str::contains("hook commit-msg: installed"));
}
#[test]
fn status_recognizes_normalized_managed_hooks_path() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "./.truth/./hooks"]);
let hooks = repo.join(".truth/hooks");
fs::create_dir_all(&hooks).unwrap();
fs::write(
hooks.join("commit-msg"),
"#!/bin/sh\n# truth-mirror managed hook\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&hooks.join("commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains(
"hook_mode: plain-legacy-truth-mirror",
))
.stdout(predicate::str::contains("hook commit-msg: installed"));
}
#[cfg(unix)]
#[test]
fn status_reports_managed_hook_missing_when_not_executable() {
use std::os::unix::fs::PermissionsExt;
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
let hook = repo.join(".git/hooks/commit-msg");
let mut permissions = fs::metadata(&hook).unwrap().permissions();
permissions.set_mode(0o644);
fs::set_permissions(&hook, permissions).unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook commit-msg: missing"))
.stdout(predicate::str::contains("hook post-commit: installed"));
}
fn make_executable(path: &Path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut permissions = fs::metadata(path).unwrap().permissions();
permissions.set_mode(0o755);
fs::set_permissions(path, permissions).unwrap();
}
}
fn make_not_executable(path: &Path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut permissions = fs::metadata(path).unwrap().permissions();
permissions.set_mode(0o644);
fs::set_permissions(path, permissions).unwrap();
}
}
#[test]
fn status_recognizes_custom_forwarder_hooks() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks", "--inject-forwarder"])
.assert()
.success();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"))
.stdout(predicate::str::contains("hook pre-push: installed"));
}
#[test]
fn status_requires_custom_forwarder_helper() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks", "--inject-forwarder"])
.assert()
.success();
fs::remove_file(repo.join("githooks/_forward-local.sh")).unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: missing"))
.stdout(predicate::str::contains("hook post-commit: missing"));
}
#[test]
fn status_requires_custom_forwarder_local_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks", "--inject-forwarder"])
.assert()
.success();
fs::remove_file(repo.join(".git/hooks/commit-msg")).unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: missing"))
.stdout(predicate::str::contains("hook post-commit: installed"));
}
#[test]
fn status_rejects_custom_hook_that_only_mentions_git_common_dir() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir(repo.join("githooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\ngit rev-parse --git-common-dir >/dev/null\nexit 0\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\n# truth-mirror managed hook\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: missing"));
}
#[test]
fn status_recognizes_direct_custom_forwarder_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir(repo.join("githooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\ngit_dir=\"$(git rev-parse --git-common-dir)\"\nexec \"$git_dir/hooks/commit-msg\" \"$@\"\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\n# truth-mirror managed hook\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: missing"));
}
#[test]
fn status_recognizes_variable_based_custom_forwarder_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir(repo.join("githooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\nname=$(basename \"$0\")\ngit_dir=\"$(git rev-parse --git-common-dir)\"\nexec \"$git_dir/hooks/$name\" \"$@\"\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\n# truth-mirror managed hook\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: missing"));
}
#[test]
fn status_recognizes_direct_custom_truth_mirror_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir(repo.join("githooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: installed"));
}
#[test]
fn status_recognizes_direct_custom_truth_alias_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir(repo.join("githooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\nexec truth hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: installed"));
}
#[test]
fn status_recognizes_path_qualified_custom_truth_alias_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir(repo.join("githooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\nexec /usr/local/bin/truth hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: installed"));
}
#[test]
fn status_rejects_commented_direct_truth_mirror_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\n# exec truth-mirror hook-dispatch commit-msg \"$@\"\nexit 0\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: plain"))
.stdout(predicate::str::contains("hook commit-msg: missing"));
}
#[test]
fn status_rejects_wrong_hook_name_suffix() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\nexec truth-mirror hook-dispatch commit-msg-old \"$@\"\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: plain"))
.stdout(predicate::str::contains("hook commit-msg: missing"));
}
#[test]
fn status_rejects_truth_and_dispatch_in_different_shell_segments() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\nexec truth --version && hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: plain"))
.stdout(predicate::str::contains("hook commit-msg: missing"));
}
#[test]
fn status_rejects_custom_forwarder_that_drops_arguments() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir_all(repo.join("githooks")).unwrap();
fs::create_dir_all(repo.join(".git/hooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\ngit_dir=\"$(git rev-parse --git-common-dir)\"\nexec \"$git_dir/hooks/commit-msg\"\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\n# truth-mirror managed hook\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: missing"));
}
#[test]
fn status_recognizes_husky_entrypoint_and_content_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"));
}
#[cfg(unix)]
#[test]
fn status_requires_executable_husky_helper_when_entrypoint_execs_helper() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
make_not_executable(&repo.join(".husky/_/h"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: missing"))
.stdout(predicate::str::contains("hook post-commit: missing"));
}
#[test]
fn status_recognizes_direct_husky_entrypoint_without_helper() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
let internal = repo.join(".husky/_");
fs::create_dir_all(&internal).unwrap();
fs::write(
internal.join("commit-msg"),
"#!/bin/sh\nexec \"$(dirname \"$0\")/../commit-msg\" \"$@\"\n",
)
.unwrap();
make_executable(&internal.join("commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"));
}
#[test]
fn status_recognizes_husky_hooks_that_dispatch_through_a_resolved_binary() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky"]);
let husky = repo.join(".husky");
fs::create_dir_all(&husky).unwrap();
for hook in ["commit-msg", "post-commit", "pre-push"] {
fs::write(
husky.join(hook),
format!(
"#!/bin/sh\ntruth_mirror_bin=\"$(command -v truth-mirror || true)\"\n[ -n \"$truth_mirror_bin\" ] || exit 0\n\"$truth_mirror_bin\" hook-dispatch {hook} \"$@\"\n"
),
)
.unwrap();
make_executable(&husky.join(hook));
}
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"))
.stdout(predicate::str::contains("hook pre-push: installed"));
}
#[test]
fn status_rejects_commented_husky_entrypoint_delegate() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
let internal = repo.join(".husky/_");
fs::create_dir_all(&internal).unwrap();
fs::write(
internal.join("commit-msg"),
"#!/bin/sh\n# exec \"$(dirname \"$0\")/../commit-msg\" \"$@\"\nexit 0\n",
)
.unwrap();
make_executable(&internal.join("commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: missing"));
}
#[test]
fn status_requires_husky_entrypoint() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
fs::remove_file(repo.join(".husky/_/commit-msg")).unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: missing"))
.stdout(predicate::str::contains("hook post-commit: installed"));
}
#[test]
fn status_requires_husky_entrypoint_to_forward() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
fs::write(repo.join(".husky/_/commit-msg"), "#!/bin/sh\necho stale\n").unwrap();
make_executable(&repo.join(".husky/_/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: missing"))
.stdout(predicate::str::contains("hook post-commit: installed"));
}
#[cfg(unix)]
#[test]
fn status_recognizes_stock_husky_helper() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_stock_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
make_not_executable(&repo.join(".husky/_/h"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"))
.stdout(predicate::str::contains("hook pre-push: installed"));
}
#[cfg(unix)]
#[test]
fn status_recognizes_parameter_expansion_husky_helper() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_parameter_expansion_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
make_not_executable(&repo.join(".husky/_/h"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"))
.stdout(predicate::str::contains("hook pre-push: installed"));
}
#[cfg(unix)]
#[test]
fn status_recognizes_non_executable_stock_husky_content_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_stock_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
make_not_executable(&repo.join(".husky/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"));
}
fn write_husky_entrypoints(repo: &Path) {
let internal = repo.join(".husky/_");
fs::create_dir_all(&internal).unwrap();
fs::write(
internal.join("h"),
"#!/bin/sh\nhook_name=\"$(basename \"$0\")\"\ncontent_dir=\"$(dirname \"$(dirname \"$0\")\")\"\nexec \"$content_dir/$hook_name\" \"$@\"\n",
)
.unwrap();
make_executable(&internal.join("h"));
for hook in ["commit-msg", "post-commit", "pre-push"] {
fs::write(
internal.join(hook),
"#!/bin/sh\nexec \"$(dirname \"$0\")/h\" \"$@\"\n",
)
.unwrap();
make_executable(&internal.join(hook));
}
}
#[cfg(unix)]
#[test]
fn status_reports_installed_when_sourced_husky_h_is_not_executable() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
let internal = repo.join(".husky/_");
fs::create_dir_all(&internal).unwrap();
fs::write(
internal.join("h"),
"#!/bin/sh\nhook_name=\"$(basename \"$0\")\"\ncontent_dir=\"$(dirname \"$(dirname \"$0\")\")\"\nexec \"$content_dir/$hook_name\" \"$@\"\n",
)
.unwrap();
for hook in ["commit-msg", "post-commit", "pre-push"] {
fs::write(
internal.join(hook),
"#!/bin/sh\n. \"$(dirname \"$0\")/h\"\n",
)
.unwrap();
make_executable(&internal.join(hook));
}
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"))
.stdout(predicate::str::contains("hook pre-push: installed"));
}
fn write_stock_husky_entrypoints(repo: &Path) {
let internal = repo.join(".husky/_");
fs::create_dir_all(&internal).unwrap();
fs::write(
internal.join("h"),
r#"#!/bin/sh
n=$(basename "$0")
s=$(dirname "$(dirname "$0")")/$n
[ ! -f "$s" ] && exit 0
sh -e "$s" "$@"
"#,
)
.unwrap();
for hook in ["commit-msg", "post-commit", "pre-push"] {
fs::write(
internal.join(hook),
"#!/bin/sh\n. \"$(dirname \"$0\")/h\"\n",
)
.unwrap();
make_executable(&internal.join(hook));
}
}
fn write_parameter_expansion_husky_entrypoints(repo: &Path) {
let internal = repo.join(".husky/_");
fs::create_dir_all(&internal).unwrap();
fs::write(
internal.join("h"),
r#"#!/bin/sh
n=${0##*/}
s=${0%/*/*}/$n
[ ! -f "$s" ] && exit 0
sh -e "$s" "$@"
"#,
)
.unwrap();
for hook in ["commit-msg", "post-commit", "pre-push"] {
fs::write(
internal.join(hook),
"#!/bin/sh\n. \"$(dirname \"$0\")/h\"\n",
)
.unwrap();
make_executable(&internal.join(hook));
}
}
#[test]
fn status_reports_configured_reviewer_timeout() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("reviewer_timeout: 1200s"));
let state = repo.join(".truth");
fs::create_dir_all(&state).unwrap();
fs::write(state.join("config.toml"), "[reviewer]\ntimeout_secs = 90\n").unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("reviewer_timeout: 90s"));
fs::write(state.join("config.toml"), "[reviewer]\ntimeout_secs = 0\n").unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("reviewer_timeout: disabled"));
}
#[test]
fn status_reports_stale_running_rows_as_a_read_only_liveness_view() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
let state = repo.join(".truth");
let runs = state.join("runs");
fs::create_dir_all(&runs).unwrap();
let dead_pid = reaped_pid();
fs::write(
runs.join("orphan.json"),
format!(
"{{\"id\":\"orphan\",\"commit_sha\":\"abc123\",\"target\":\"commit\",\"status\":\"running\",\"phase\":\"reviewing\",\"ledger_entries\":0,\"error\":null,\"worker_pid\":{dead_pid},\"created_at_unix\":1,\"updated_at_unix\":1,\"started_at_unix\":1,\"completed_at_unix\":null}}"
),
)
.unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains(
"review_run_liveness: stale_running=1",
));
let row = fs::read_to_string(runs.join("orphan.json")).unwrap();
assert!(row.contains("\"status\":\"running\""));
}
#[test]
fn status_reports_pending_memory_skill_counts() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
let state = repo.join(".truth");
let config = TruthMirrorConfig::default();
let ledger_ref = EvidenceRef {
kind: "ledger".to_owned(),
path: Some("ledger.jsonl".to_owned()),
selector: Some("commit=status-sha".to_owned()),
..EvidenceRef::default()
};
MemorySkillStore::new(&state, &config.memory_skill)
.unwrap()
.write_candidate(&MemorySkillCandidate {
schema_version: CANDIDATE_SCHEMA_VERSION,
id: "msk_status".to_owned(),
fingerprint: "sha256:v1:status".to_owned(),
learning_key: "status learning".to_owned(),
status: CandidateStatus::Pending,
candidate_kind: MemorySkillCandidateKind::HowToSkill,
scope: "project".to_owned(),
source_commit: "status-sha".to_owned(),
source_claim: "CLAIM: status learning | verified: cargo test | evidence: tests:status"
.to_owned(),
truth_label: Verdict::Pass,
ledger_entry_ref: ledger_ref.clone(),
source_run_ref: EvidenceRef {
kind: "run".to_owned(),
path: Some("runs/run-status.json".to_owned()),
run_id: Some("run-status".to_owned()),
artifact: Some("run-status.json".to_owned()),
..EvidenceRef::default()
},
occurrence_count: 2,
occurrence_refs: vec![ledger_ref.clone()],
slug: "status-learning".to_owned(),
title: "Status Learning".to_owned(),
description: "A pending status test candidate.".to_owned(),
when_to_use: vec!["When checking status.".to_owned()],
procedure_steps: vec!["Run status.".to_owned()],
pitfalls: vec!["Do not skip status.".to_owned()],
verification_steps: vec!["Read status output.".to_owned()],
evidence: vec![ledger_ref],
created_at_unix: 1,
})
.unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains(
"memory_skill: pending_candidates=1 pending_advisories=0",
));
}