checkmate-cli 0.4.1

Checkmate - API Testing Framework CLI
//! Prime command - Output project context for AI agents
//!
//! This command is designed to be run by SessionStart hooks to inject
//! Checkmate context into AI agent sessions.

use crate::config::Config;
use crate::history::{self, format_run_short};
use crate::project::CheckmateProject;
use crate::runner::TestSpec;

/// Run the prime command
pub fn run() -> Result<(), Box<dyn std::error::Error>> {
    let project = match CheckmateProject::discover() {
        Some(p) => p,
        None => {
            // Silently exit if no project - don't pollute agent context
            return Ok(());
        }
    };

    let config = Config::load(Some(&project));

    println!("# Checkmate API Testing Context\n");

    // Project info
    println!("**Project**: {}", project.root.display());
    if let Some(ref base_url) = config.env.base_url {
        println!("**Base URL**: {}", base_url);
    }
    println!();

    // Available test specs
    print_test_specs(&project)?;

    // Recent run history
    print_recent_history(&project)?;

    // Quick reference
    print_quick_reference();

    Ok(())
}

fn print_test_specs(project: &CheckmateProject) -> Result<(), Box<dyn std::error::Error>> {
    if !project.tests_dir.exists() {
        return Ok(());
    }

    let specs: Vec<_> = std::fs::read_dir(&project.tests_dir)?
        .filter_map(|e| e.ok())
        .filter(|e| {
            e.path()
                .extension()
                .map_or(false, |ext| ext == "yaml" || ext == "yml")
        })
        .collect();

    if specs.is_empty() {
        println!("## Test Specs: None\n");
        return Ok(());
    }

    println!("## Test Specs ({}):\n", specs.len());

    for entry in specs {
        let path = entry.path();
        let name = path.file_stem().unwrap_or_default().to_string_lossy();

        // Try to load spec to get test count
        if let Ok(spec) = TestSpec::from_file(path.to_string_lossy().as_ref()) {
            let suite_name = spec.name.as_deref().unwrap_or(&name);
            println!("- **{}** ({} tests)", suite_name, spec.tests.len());
        } else {
            println!("- **{}**", name);
        }
    }
    println!();

    Ok(())
}

fn print_recent_history(project: &CheckmateProject) -> Result<(), Box<dyn std::error::Error>> {
    let records = history::load_history_filtered(project, None, None, 5)?;

    if records.is_empty() {
        println!("## Recent Runs: None\n");
        return Ok(());
    }

    println!("## Recent Runs:\n");
    println!("```");
    for record in &records {
        println!("{}", format_run_short(record));
    }
    println!("```\n");

    // Check for failures
    let recent_failures: Vec<_> = records.iter()
        .filter(|r| r.summary.failed > 0 || r.summary.errors > 0)
        .collect();

    if !recent_failures.is_empty() {
        println!("**Warning**: {} of last {} runs had failures.\n",
            recent_failures.len(), records.len());
    }

    Ok(())
}

fn print_quick_reference() {
    println!("## Quick Reference:\n");
    println!("```bash");
    println!("cm test run              # Run all tests");
    println!("cm test run <name>       # Run specific spec");
    println!("cm test list             # List all tests");
    println!("cm history               # Show run history");
    println!("cm config show           # Show configuration");
    println!("cm docs                  # Full documentation");
    println!("```");
}