lean-ctx 3.1.3

Context Runtime for AI Agents with CCP. 42 MCP tools, 10 read modes, 90+ compression patterns, cross-session memory (CCP), persistent AI knowledge with temporal facts + contradiction detection, multi-agent context sharing + diaries, LITM-aware positioning, AAAK compact format, adaptive compression with Thompson Sampling bandits. Supports 24 AI tools. Reduces LLM token consumption by up to 99%.
Documentation
use regex::Regex;
use std::sync::OnceLock;

static BUILD_SUMMARY_ERR: OnceLock<Regex> = OnceLock::new();
static BUILD_SUMMARY_WARN: OnceLock<Regex> = OnceLock::new();
static BUILD_RESULT: OnceLock<Regex> = OnceLock::new();
static RESTORED_PROJ: OnceLock<Regex> = OnceLock::new();
static RESTORED_PKG: OnceLock<Regex> = OnceLock::new();
static TEST_TOTAL: OnceLock<Regex> = OnceLock::new();
static PUBLISH_ARROW: OnceLock<Regex> = OnceLock::new();

fn build_summary_err_re() -> &'static Regex {
    BUILD_SUMMARY_ERR.get_or_init(|| Regex::new(r"(?i)^\s*(\d+)\s+Error\(s\)\s*$").unwrap())
}
fn build_summary_warn_re() -> &'static Regex {
    BUILD_SUMMARY_WARN.get_or_init(|| Regex::new(r"(?i)^\s*(\d+)\s+Warning\(s\)\s*$").unwrap())
}
fn build_result_re() -> &'static Regex {
    BUILD_RESULT.get_or_init(|| Regex::new(r"(?i)^(Build succeeded\.|Build FAILED\.)").unwrap())
}
fn restored_proj_re() -> &'static Regex {
    RESTORED_PROJ.get_or_init(|| {
        Regex::new(r"(?i)^\s*Restored\s+(.+\.csproj[^(\n]*)(?:\s*\([^)]*\))?\s*\.?\s*$").unwrap()
    })
}
fn restored_pkg_re() -> &'static Regex {
    RESTORED_PKG.get_or_init(|| Regex::new(r"(?i)Restored\s+(\d+)\s+package").unwrap())
}
fn test_total_re() -> &'static Regex {
    TEST_TOTAL.get_or_init(|| Regex::new(r"(?i)^\s*Total tests:\s*(\d+)\s*$").unwrap())
}
fn publish_arrow_re() -> &'static Regex {
    PUBLISH_ARROW.get_or_init(|| Regex::new(r"\s+->\s+").unwrap())
}

fn is_msbuild_noise(line: &str) -> bool {
    let t = line.trim_start();
    let tl = t.to_ascii_lowercase();
    if tl.starts_with("microsoft (r) build engine")
        || tl.starts_with("copyright (c) microsoft")
        || tl.contains("version ") && tl.contains("msbuild")
    {
        return true;
    }
    if tl.starts_with("verbosity:") || tl == "build started." {
        return true;
    }
    // Progress / target spam (typical minimal+ still has some)
    if tl.starts_with("time elapsed") && tl.contains("00:00:") {
        return true;
    }
    false
}

fn is_dotnet_restore_noise(line: &str) -> bool {
    let tl = line.trim().to_ascii_lowercase();
    tl.starts_with("determining projects to restore")
        || tl.contains("assets file has not changed")
        || tl.starts_with("writing assets file")
}

fn looks_like_build_error_line(line: &str) -> bool {
    let t = line.trim();
    let tl = t.to_ascii_lowercase();
    if tl.contains(": error ") || tl.starts_with("error ") {
        return true;
    }
    if tl.contains("msbuild : error") || tl.contains("error msb") {
        return true;
    }
    if tl.contains(": error cs") || tl.contains(": error mc") {
        return true;
    }
    false
}

fn looks_like_build_warning_line(line: &str) -> bool {
    let t = line.trim();
    let tl = t.to_ascii_lowercase();
    (tl.contains(": warning ") || tl.starts_with("warning ")) && !tl.contains("warning(s)")
}

pub fn compress(command: &str, output: &str) -> Option<String> {
    let cl = command.trim().to_ascii_lowercase();
    if !cl.starts_with("dotnet ") {
        return None;
    }

    let sub = cl
        .split_whitespace()
        .nth(1)
        .unwrap_or("")
        .trim_start_matches('-');
    match sub {
        "build" | "msbuild" => return Some(compress_build(output)),
        "test" | "vstest" => return Some(compress_test(output)),
        "restore" => return Some(compress_restore(output)),
        "publish" => return Some(compress_publish(output)),
        _ => {}
    }

    None
}

fn compress_build(output: &str) -> String {
    let mut parts = Vec::new();
    let mut errors = Vec::new();
    let mut warnings = Vec::new();
    let mut result_line: Option<String> = None;
    let mut summary_errors: Option<String> = None;
    let mut summary_warnings: Option<String> = None;

    for line in output.lines() {
        let t = line.trim_end();
        if t.trim().is_empty() || is_msbuild_noise(t) {
            continue;
        }
        let trim = t.trim();
        if build_result_re().is_match(trim) {
            result_line = Some(trim.to_string());
            continue;
        }
        if let Some(caps) = build_summary_err_re().captures(trim) {
            summary_errors = Some(format!("{} Error(s)", &caps[1]));
            continue;
        }
        if let Some(caps) = build_summary_warn_re().captures(trim) {
            summary_warnings = Some(format!("{} Warning(s)", &caps[1]));
            continue;
        }
        if looks_like_build_error_line(trim) {
            errors.push(trim.to_string());
            continue;
        }
        if looks_like_build_warning_line(trim) {
            warnings.push(trim.to_string());
        }
    }

    if let Some(r) = result_line {
        parts.push(r);
    }
    if let Some(s) = summary_warnings {
        parts.push(s);
    }
    if let Some(s) = summary_errors {
        parts.push(s);
    }
    if !errors.is_empty() {
        parts.push(format!("{} error lines:", errors.len()));
        parts.extend(errors.into_iter().map(|e| format!("  {e}")));
    }
    if !warnings.is_empty() && warnings.len() <= 20 {
        parts.push(format!("{} warning lines:", warnings.len()));
        parts.extend(warnings.into_iter().map(|w| format!("  {w}")));
    } else if !warnings.is_empty() {
        parts.push(format!("{} warnings (omitted detail)", warnings.len()));
    }

    if parts.is_empty() {
        compact_or_ok(output, 8)
    } else {
        parts.join("\n")
    }
}

fn compress_test(output: &str) -> String {
    let mut parts = Vec::new();
    let mut in_failure = false;
    let mut failure_block: Vec<String> = Vec::new();

    for line in output.lines() {
        let t = line.trim_end();
        let trim = t.trim();
        let tl = trim.to_ascii_lowercase();

        if tl.contains("test run failed") || tl == "failed!" {
            parts.push(trim.to_string());
        }
        if tl.starts_with("passed!") || tl.starts_with("failed!") {
            parts.push(trim.to_string());
        }
        if test_total_re().is_match(trim) {
            parts.push(trim.to_string());
        }
        if tl.starts_with("passed:")
            || tl.starts_with("failed:")
            || tl.starts_with("skipped:")
            || tl.starts_with("total:")
        {
            parts.push(trim.to_string());
        }
        if tl.contains("error message:") || tl.contains("stack trace:") {
            in_failure = true;
        }
        if looks_like_build_error_line(trim) && tl.contains("error") {
            parts.push(trim.to_string());
        }

        if in_failure && !trim.is_empty() {
            failure_block.push(trim.to_string());
            if failure_block.len() > 40 {
                in_failure = false;
            }
        }
    }

    if !failure_block.is_empty() {
        parts.push("failure detail:".to_string());
        parts.extend(failure_block.into_iter().take(25).map(|l| format!("  {l}")));
    }

    if parts.is_empty() {
        compact_or_ok(output, 12)
    } else {
        parts.join("\n")
    }
}

fn compress_restore(output: &str) -> String {
    let mut restored_projects = Vec::new();
    let mut pkg_summary: Option<String> = None;

    for line in output.lines() {
        let t = line.trim_end();
        if t.trim().is_empty() || is_dotnet_restore_noise(t) {
            continue;
        }
        let trim = t.trim();
        if let Some(caps) = restored_proj_re().captures(trim) {
            restored_projects.push(caps[1].trim().to_string());
            continue;
        }
        if let Some(caps) = restored_pkg_re().captures(trim) {
            pkg_summary = Some(format!("Restored {} packages (summary line)", &caps[1]));
        }
        if looks_like_build_error_line(trim) {
            restored_projects.push(format!("ERR: {trim}"));
        }
    }

    let mut parts = Vec::new();
    if !restored_projects.is_empty() {
        parts.push(format!("Restored {} project(s):", restored_projects.len()));
        for p in restored_projects {
            parts.push(format!("  {p}"));
        }
    }
    if let Some(s) = pkg_summary {
        parts.push(s);
    }

    if parts.is_empty() {
        compact_or_ok(output, 10)
    } else {
        parts.join("\n")
    }
}

fn compress_publish(output: &str) -> String {
    let mut parts = Vec::new();

    for line in output.lines() {
        let t = line.trim_end();
        if t.trim().is_empty() || is_msbuild_noise(t) {
            continue;
        }
        let trim = t.trim();
        if publish_arrow_re().is_match(trim) {
            parts.push(trim.to_string());
            continue;
        }
        if trim.to_ascii_lowercase().contains("published to")
            || trim.to_ascii_lowercase().contains("output path")
        {
            parts.push(trim.to_string());
        }
        if build_result_re().is_match(trim) {
            parts.push(trim.to_string());
        }
        if looks_like_build_error_line(trim) {
            parts.push(trim.to_string());
        }
    }

    if parts.is_empty() {
        compact_or_ok(output, 10)
    } else {
        parts.join("\n")
    }
}

fn compact_or_ok(output: &str, max: usize) -> String {
    let lines: Vec<&str> = output.lines().filter(|l| !l.trim().is_empty()).collect();
    if lines.is_empty() {
        return "ok".to_string();
    }
    if lines.len() <= max {
        return lines.join("\n");
    }
    format!(
        "{}\n... ({} more lines)",
        lines[..max].join("\n"),
        lines.len() - max
    )
}