truth-mirror 0.7.3

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

const DEFAULT_BINARY_NAME: &str = "truth-mirror";

pub fn async_review_drain_hint(global_args: &str) -> String {
    async_review_drain_hint_for_binary(&active_binary_name(), global_args)
}

pub fn diagnostic_prefix() -> String {
    diagnostic_prefix_for_binary(&active_binary_name())
}

pub fn command_for_cli(global_args: &str, subcommand: &str) -> String {
    command_for_binary(&active_binary_name(), global_args, subcommand)
}

fn async_review_drain_hint_for_binary(binary_name: &str, global_args: &str) -> String {
    format!(
        "run `{}` to drain now or `{}` to keep draining",
        command_for_binary(binary_name, global_args, "watch --once"),
        command_for_binary(binary_name, global_args, "watch")
    )
}

pub fn async_review_drain_hint_for_cli(config_path: Option<&Path>, state_dir: &Path) -> String {
    async_review_drain_hint_for_cli_with_binary(&active_binary_name(), config_path, state_dir)
}

fn async_review_drain_hint_for_cli_with_binary(
    binary_name: &str,
    config_path: Option<&Path>,
    state_dir: &Path,
) -> String {
    let mut args = Vec::new();
    if let Some(config_path) = config_path {
        args.push(format!("--config {}", quote_path(config_path)));
    }
    if state_dir != Path::new(crate::config::DEFAULT_STATE_DIR) {
        args.push(format!("--state-dir {}", quote_path(state_dir)));
    }
    let global_args = if args.is_empty() {
        String::new()
    } else {
        format!("{} ", args.join(" "))
    };
    async_review_drain_hint_for_binary(binary_name, &global_args)
}

fn diagnostic_prefix_for_binary(binary_name: &str) -> String {
    format!("{binary_name}:")
}

fn command_for_binary(binary_name: &str, global_args: &str, subcommand: &str) -> String {
    let binary = quote_binary_name(binary_name);
    let global_args = global_args.trim();
    if global_args.is_empty() {
        format!("{binary} {subcommand}")
    } else {
        format!("{binary} {global_args} {subcommand}")
    }
}

fn active_binary_name() -> String {
    binary_name_from_argv0(std::env::args_os().next().as_deref())
}

fn binary_name_from_argv0(path: Option<&OsStr>) -> String {
    path.and_then(|path| {
        Path::new(path)
            .file_stem()
            .and_then(|name| name.to_str())
            .map(str::to_owned)
    })
    .unwrap_or_else(|| DEFAULT_BINARY_NAME.to_owned())
}

fn quote_path(path: &Path) -> String {
    let value = path.to_string_lossy();
    quote_shell_arg(&value)
}

fn quote_binary_name(binary_name: &str) -> String {
    if !binary_name.is_empty()
        && binary_name.chars().all(|character| {
            character.is_ascii_alphanumeric() || matches!(character, '-' | '_' | '.' | '/')
        })
    {
        binary_name.to_owned()
    } else {
        quote_shell_arg(binary_name)
    }
}

fn quote_shell_arg(value: &str) -> String {
    format!("'{}'", value.replace('\'', "'\\''"))
}

#[cfg(test)]
mod tests {
    use std::{ffi::OsStr, path::Path};

    use super::{
        DEFAULT_BINARY_NAME, async_review_drain_hint_for_binary,
        async_review_drain_hint_for_cli_with_binary, binary_name_from_argv0, command_for_binary,
        diagnostic_prefix_for_binary,
    };

    #[test]
    fn default_state_dir_uses_bare_watch_commands() {
        assert_eq!(
            async_review_drain_hint_for_cli_with_binary("truth-mirror", None, Path::new(".truth")),
            async_review_drain_hint_for_binary("truth-mirror", "")
        );
    }

    #[test]
    fn custom_state_dir_is_quoted_in_watch_commands() {
        assert_eq!(
            async_review_drain_hint_for_cli_with_binary(
                "truth-mirror",
                None,
                Path::new("/tmp/truth state's queue")
            ),
            "run `truth-mirror --state-dir '/tmp/truth state'\\''s queue' watch --once` to drain now or `truth-mirror --state-dir '/tmp/truth state'\\''s queue' watch` to keep draining"
        );
    }

    #[test]
    fn custom_config_is_quoted_in_watch_commands() {
        assert_eq!(
            async_review_drain_hint_for_cli_with_binary(
                "truth-mirror",
                Some(Path::new("/tmp/truth config.toml")),
                Path::new(".truth")
            ),
            "run `truth-mirror --config '/tmp/truth config.toml' watch --once` to drain now or `truth-mirror --config '/tmp/truth config.toml' watch` to keep draining"
        );
    }

    #[test]
    fn binary_name_is_used_in_watch_commands() {
        assert_eq!(
            async_review_drain_hint_for_binary("truth", ""),
            "run `truth watch --once` to drain now or `truth watch` to keep draining"
        );
    }

    #[test]
    fn diagnostic_prefix_uses_injected_binary_name() {
        assert_eq!(diagnostic_prefix_for_binary("truth"), "truth:");
    }

    #[test]
    fn command_builder_inserts_global_arg_spacing() {
        assert_eq!(
            command_for_binary("truth", "--state-dir .truth", "install-hooks"),
            "truth --state-dir .truth install-hooks"
        );
        assert_eq!(
            command_for_binary("truth", "--state-dir .truth ", "install-hooks"),
            "truth --state-dir .truth install-hooks"
        );
    }

    #[test]
    fn binary_name_with_shell_specials_is_quoted_in_watch_commands() {
        assert_eq!(
            async_review_drain_hint_for_binary("truth mirror", ""),
            "run `'truth mirror' watch --once` to drain now or `'truth mirror' watch` to keep draining"
        );
    }

    #[test]
    fn binary_name_from_argv0_uses_file_stem_or_default() {
        assert_eq!(
            binary_name_from_argv0(Some(OsStr::new("/usr/local/bin/truth"))),
            "truth"
        );
        assert_eq!(
            binary_name_from_argv0(Some(OsStr::new("/tmp/truth mirror"))),
            "truth mirror"
        );
        assert_eq!(
            binary_name_from_argv0(Some(OsStr::new(""))),
            DEFAULT_BINARY_NAME
        );
        assert_eq!(binary_name_from_argv0(None), DEFAULT_BINARY_NAME);
    }
}