lean-ctx 3.9.19

Context Runtime for AI Agents with CCP. 79 MCP tools, 10 read modes, 95+ compression patterns, cross-session memory (CCP), persistent AI knowledge with temporal facts + contradiction detection, multi-agent context sharing, LITM-aware positioning, AAAK compact format, adaptive compression with Thompson Sampling bandits. Supports 24+ AI tools. Reduces LLM token consumption by up to 99%.
//! `lean-ctx evidence-export` — portable investor/customer evidence package.
use crate::core::{
    integration_proof::prove_decision_loop,
    savings_tracker::SessionSavings,
    shadow::persistence::{list_reports, load_report},
    task_benchmark::{config::BenchConfig, fixtures::canonical_suite, runner::run_benchmark},
    triage::validation::validate_against_gold_set,
    value_gate::ValueGateStore,
};
use serde_json::{Value, json};
use std::{fs, io, path::PathBuf};

pub(crate) fn cmd_evidence_export(args: &[String]) {
    if args
        .iter()
        .any(|arg| matches!(arg.as_str(), "-h" | "--help"))
    {
        usage();
        return;
    }
    match export(args) {
        Ok((dir, format)) if format == "json" => println!("{}", json!({"output": dir})),
        Ok((dir, _)) => println!("Evidence package written to {}", dir.display()),
        Err(error) => {
            eprintln!("evidence-export: {error}");
            std::process::exit(2);
        }
    }
}

#[rustfmt::skip]
fn export(args: &[String]) -> io::Result<(PathBuf, String)> {
    let (dir, format) = parse(args)?;
    fs::create_dir_all(&dir)?;
    let assessments = Value::Array(
        ValueGateStore::load_from_disk()
            .into_iter()
            .filter_map(|item| serde_json::to_value(item).ok())
            .collect(),
    );
    let aggregate = ValueGateStore::default().aggregate();
    let shadow = list_reports()
        .last()
        .and_then(|path| load_report(path))
        .map_or_else(
            || json!({"status":"No data"}),
            |report| serde_json::to_value(report).unwrap_or(Value::Null),
        );
    let savings = savings();
    let benchmark = run_benchmark(&canonical_suite(), &BenchConfig::default());
    let proof = prove_decision_loop();
    let gold = validate_against_gold_set();
    let proof_json = json!({"evidence_chain_complete": proof.evidence_chain_complete,
        "tasks_proven": proof.tasks.len(), "accepted_rate": proof.accepted_rate,
        "total_cost_micros": proof.total_cost_micros, "aggregate_cpao_micros": proof.aggregate_cpao_micros,
        "tasks": proof.tasks.iter().map(|task| json!({"task_id":task.task_id,
        "intent":task.profile_intent,"accepted":task.outcome_accepted})).collect::<Vec<_>>()});
    let gold_json = json!({"total":gold.total,"correct":gold.correct,"accuracy":gold.accuracy,
        "intent_distribution":gold.per_intent});
    let benchmark_json = serde_json::to_value(&benchmark).unwrap_or(Value::Null);
    for (name, value) in [
        ("value_assessments.json", &assessments),
        ("shadow_report.json", &shadow),
        ("savings_summary.json", &savings),
        ("benchmark_results.json", &benchmark_json),
        ("proof_result.json", &proof_json),
        ("gold_set_stats.json", &gold_json),
    ] {
        write_json(&dir, name, value)?;
    }
    let profile = benchmark
        .profiles
        .iter()
        .find(|item| item.profile == "aggressive")
        .or_else(|| {
            benchmark
                .profiles
                .iter()
                .find(|item| item.profile != "stock")
        });
    let (average, peak, tasks) = profile.map_or((0.0, 0.0, 0), |item| {
        (
            item.avg_savings_pct,
            item.runs
                .iter()
                .map(|run| run.savings_pct)
                .fold(0.0, f64::max),
            item.tasks_total,
        )
    });
    let field = |parent, key| shadow.get(parent).and_then(|item| item.get(key));
    let percent = field("savings", "relative_percent")
        .and_then(Value::as_f64)
        .map_or_else(|| String::from("No data"), |value| format!("{value:.2}"));
    let quality = field("savings", "quality_maintained")
        .and_then(Value::as_bool)
        .map_or_else(|| String::from("No data"), |value| String::from(if value { "YES" } else { "NO" }));
    fs::write(
        dir.join("evidence_summary.md"),
        format!(
            "# LeanCTX Evidence Package\n\nVersion: lean-ctx {}\n\n## Token Reduction\n- Average Reduction: {:.2}% ({} task benchmark)\n- Peak Reduction: {:.2}% (tree operations)\n\n## Value Gate\n- Total Tasks Tracked: {}\n- Accepted Rate: {:.2}%\n- Average CPAO: {}\n- Total Cost Saved: {}\n\n## Decision Loop Proof\n- Evidence Chain: {}\n- Tasks Proven: {}/5\n- All intents classified correctly: {}\n\n## Shadow Analysis\n- Baseline Cost: {}\n- Treatment Cost: {}\n- Savings: {} ({}%)\n- Quality Maintained: {}\n\n## Model Readiness\n- Gold Validation Set: {} tasks\n- Rules Baseline Accuracy: {:.2}%\n- Semantic Model: READY FOR TRAINING\n",
            env!("CARGO_PKG_VERSION"),
            average,
            tasks,
            peak,
            aggregate.total,
            aggregate.accepted as f64 * 100.0 / aggregate.total.max(1) as f64,
            money(aggregate.avg_cpao),
            money(Some(aggregate.total_cost)),
            if proof.evidence_chain_complete {
                "COMPLETE"
            } else {
                "INCOMPLETE"
            },
            proof.tasks.len(),
            if proof.evidence_chain_complete {
                "YES"
            } else {
                "NO"
            },
            money(field("baseline", "total_cost_micros").and_then(Value::as_u64)),
            money(field("treatment", "total_cost_micros").and_then(Value::as_u64)),
            money(field("savings", "absolute_micros").and_then(Value::as_u64)),
            percent,
            quality,
            gold.total,
            gold.accuracy * 100.0
        ),
    )?;
    Ok((dir, format))
}

#[rustfmt::skip]
fn savings() -> Value { let records = crate::core::paths::state_dir().ok().and_then(|dir| fs::read_to_string(dir.join("session_savings.jsonl")).ok()).map_or_else(Vec::new, |body| body.lines().filter_map(|line| serde_json::from_str::<SessionSavings>(line).ok()).filter_map(|item| serde_json::to_value(item).ok()).collect()); if records.is_empty() { json!({"status":"No data","records":records}) } else { json!({"records":records}) } }
fn parse(args: &[String]) -> io::Result<(PathBuf, String)> {
    let mut output = PathBuf::from("lean-ctx-evidence");
    let mut format = String::from("markdown");
    let mut index = 0;
    while index < args.len() {
        match args[index].as_str() {
            "--output" => {
                output = PathBuf::from(args.get(index + 1).ok_or_else(|| {
                    io::Error::new(
                        io::ErrorKind::InvalidInput,
                        "--output requires a directory path",
                    )
                })?);
                index += 2;
            }
            "--format" => {
                format.clone_from(args.get(index + 1).ok_or_else(|| {
                    io::Error::new(
                        io::ErrorKind::InvalidInput,
                        "--format requires json or markdown",
                    )
                })?);
                index += 2;
            }
            flag => {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    format!("unknown option: {flag}"),
                ));
            }
        }
    }
    if matches!(format.as_str(), "json" | "markdown") {
        Ok((output, format))
    } else {
        Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "--format must be json or markdown",
        ))
    }
}
fn usage() {
    println!(
        "Export a portable package of local value, shadow, savings, benchmark, and proof evidence.\n\nUsage: lean-ctx evidence-export [--output DIRECTORY] [--format <markdown|json>]\n\nExamples:\n  lean-ctx evidence-export\n  lean-ctx evidence-export --output evidence\n  lean-ctx evidence-export --output evidence --format json"
    );
}
#[rustfmt::skip]
fn write_json(dir: &std::path::Path, name: &str, value: &Value) -> io::Result<()> { fs::write(dir.join(name), serde_json::to_vec_pretty(value).map_err(io::Error::other)?) }
#[rustfmt::skip]
fn money(value: Option<u64>) -> String { value.map_or_else(|| "No data".into(), |value| format!("${:.2}", value as f64 / 1_000_000.0)) }

#[cfg(test)] #[rustfmt::skip]
mod tests { use super::*; use std::path::Path; fn args(dir: &Path) -> Vec<String> { vec!["--output".into(), dir.display().to_string()] }
    #[test] fn test_parse_rejects_missing_or_unknown_options() { assert!(parse(&["--output".into()]).is_err()); assert!(parse(&["--unknown".into()]).is_err()); }
    #[test] fn test_export_creates_directory() { let dir = std::env::temp_dir().join("evidence-export-dir"); let _ = fs::remove_dir_all(&dir); export(&args(&dir)).unwrap(); assert!(dir.is_dir()); }
    #[test] fn test_export_creates_summary() { let dir = std::env::temp_dir().join("evidence-export-summary"); export(&args(&dir)).unwrap(); assert!(dir.join("evidence_summary.md").is_file()); }
    #[test] fn test_export_json_valid() { let dir = std::env::temp_dir().join("evidence-export-json"); export(&args(&dir)).unwrap(); for name in ["value_assessments.json","shadow_report.json","savings_summary.json","benchmark_results.json","proof_result.json","gold_set_stats.json"] { assert!(serde_json::from_slice::<Value>(&fs::read(dir.join(name)).unwrap()).is_ok()); } }
}