truth-mirror 0.7.0

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

use anyhow::Result as AnyResult;

use crate::cli as app_cli;

use super::{
    render::render_skill,
    scan::{scan_memory_skill_advisory, scan_memory_skill_candidate},
    store::MemorySkillStore,
    types::MemorySkillCandidate,
};

pub fn run(
    args: app_cli::MemorySkillArgs,
    state_dir: &Path,
    config: &crate::config::TruthMirrorConfig,
) -> AnyResult<ExitCode> {
    let store = MemorySkillStore::new(state_dir, &config.memory_skill)?;
    match args.command {
        app_cli::MemorySkillCommand::List => {
            let candidates = store.read_candidates()?;
            let advisories = store
                .read_advisories()?
                .into_iter()
                .filter(|advisory| !advisory.dismissed)
                .collect::<Vec<_>>();
            if candidates.is_empty() && advisories.is_empty() {
                println!("No memory-skill candidates.");
            } else {
                for candidate in &candidates {
                    scan_memory_skill_candidate(candidate, &config.memory_skill)?;
                }
                for advisory in &advisories {
                    scan_memory_skill_advisory(advisory, &config.memory_skill)?;
                }
                for candidate in candidates {
                    println!(
                        "{} {} {} {} {}",
                        candidate.id,
                        candidate.status,
                        candidate.candidate_kind.as_str(),
                        candidate.slug,
                        candidate.source_commit
                    );
                }
                for advisory in advisories {
                    println!(
                        "advisory {} suggested {} {} {}",
                        advisory.id,
                        advisory.candidate_kind.as_str(),
                        advisory.slug,
                        advisory.source_commit
                    );
                }
            }
        }
        app_cli::MemorySkillCommand::Show { candidate_id } => {
            let candidate = store.show(&candidate_id)?;
            scan_memory_skill_candidate(&candidate, &config.memory_skill)?;
            print_candidate(&candidate);
        }
        app_cli::MemorySkillCommand::Render { candidate_id } => {
            let candidate = store.show(&candidate_id)?;
            scan_memory_skill_candidate(&candidate, &config.memory_skill)?;
            let skill = render_skill(&candidate);
            print!("{skill}");
        }
        app_cli::MemorySkillCommand::Approve {
            candidate_id,
            apply,
        } => match store.approve(&candidate_id, apply)? {
            Some(path) => println!("approved and applied {candidate_id} -> {}", path.display()),
            None => println!("approved {candidate_id}"),
        },
        app_cli::MemorySkillCommand::Apply { candidate_id } => {
            let path = store.apply(&candidate_id)?;
            println!("applied {candidate_id} -> {}", path.display());
        }
        app_cli::MemorySkillCommand::Reject {
            candidate_id,
            reason,
        } => {
            store.reject(&candidate_id, &reason)?;
            println!("rejected {candidate_id}");
        }
        app_cli::MemorySkillCommand::Supersede {
            candidate_id,
            replacement,
            reason,
        } => {
            store.supersede(&candidate_id, &replacement, &reason)?;
            println!("superseded {candidate_id} with {replacement}");
        }
        app_cli::MemorySkillCommand::DismissAdvisory { advisory_id } => {
            store.dismiss_advisory(&advisory_id)?;
            println!("dismissed advisory {advisory_id}");
        }
    }
    Ok(ExitCode::SUCCESS)
}

fn print_candidate(candidate: &MemorySkillCandidate) {
    println!("id: {}", candidate.id);
    println!("status: {}", candidate.status);
    println!("kind: {}", candidate.candidate_kind.as_str());
    println!("slug: {}", candidate.slug);
    println!("title: {}", candidate.title);
    println!("description: {}", candidate.description);
    println!("source_commit: {}", candidate.source_commit);
    println!("source_claim: {}", candidate.source_claim);
    println!("truth_label: {}", candidate.truth_label);
    println!("occurrence_count: {}", candidate.occurrence_count);
    println!("fingerprint: {}", candidate.fingerprint);
    println!("ledger_entry_ref: {}", candidate.ledger_entry_ref.display());
    println!("source_run_ref: {}", candidate.source_run_ref.display());
    print_items("when_to_use", &candidate.when_to_use);
    print_items("procedure_steps", &candidate.procedure_steps);
    print_items("pitfalls", &candidate.pitfalls);
    print_items("verification_steps", &candidate.verification_steps);
    println!("occurrence_refs:");
    for reference in &candidate.occurrence_refs {
        println!("  - {}", reference.display());
    }
    println!("evidence:");
    for reference in &candidate.evidence {
        println!("  - {}", reference.display());
    }
}

fn print_items(label: &str, items: &[String]) {
    println!("{label}:");
    for item in items {
        println!("  - {item}");
    }
}