lean-ctx 3.1.5

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 ROUTE_RE: OnceLock<Regex> = OnceLock::new();
static SIZE_RE: OnceLock<Regex> = OnceLock::new();
static BUILD_TIME_RE: OnceLock<Regex> = OnceLock::new();
static VITE_CHUNK_RE: OnceLock<Regex> = OnceLock::new();

fn route_re() -> &'static Regex {
    ROUTE_RE.get_or_init(|| Regex::new(r"^[○●λƒ◐]\s+(/\S*)").unwrap())
}
fn size_re() -> &'static Regex {
    SIZE_RE.get_or_init(|| Regex::new(r"(\d+\.?\d*)\s*(kB|MB|B)\b").unwrap())
}
fn build_time_re() -> &'static Regex {
    BUILD_TIME_RE.get_or_init(|| {
        Regex::new(r"(?:compiled|built|done)\s+(?:in\s+)?(\d+\.?\d*\s*[ms]+)").unwrap()
    })
}
fn vite_chunk_re() -> &'static Regex {
    VITE_CHUNK_RE.get_or_init(|| Regex::new(r"dist/(\S+)\s+(\d+\.?\d*\s*[kKMm]?B)").unwrap())
}

pub fn compress(command: &str, output: &str) -> Option<String> {
    if command.contains("vite") {
        return Some(compress_vite(output));
    }
    Some(compress_next(output))
}

fn compress_next(output: &str) -> String {
    let trimmed = output.trim();
    if trimmed.is_empty() {
        return "ok".to_string();
    }

    let mut routes = Vec::new();
    let mut total_size = 0f64;
    let mut build_time = String::new();
    let mut errors = Vec::new();

    for line in trimmed.lines() {
        if let Some(caps) = route_re().captures(line) {
            let route = &caps[1];
            let size = extract_size(line);
            routes.push(format!("{route} ({size})"));
        }
        if let Some(caps) = build_time_re().captures(line) {
            build_time = caps[1].to_string();
        }
        if line.to_lowercase().contains("error") && !line.contains("0 error") {
            errors.push(line.trim().to_string());
        }
        if let Some(caps) = size_re().captures(line) {
            let val: f64 = caps[1].parse().unwrap_or(0.0);
            let unit = &caps[2];
            total_size += match unit {
                "MB" => val * 1024.0,
                "kB" => val,
                _ => val / 1024.0,
            };
        }
    }

    if !errors.is_empty() {
        return format!("BUILD ERROR:\n{}", errors.join("\n"));
    }

    let mut parts = Vec::new();
    if !build_time.is_empty() {
        parts.push(format!("built ({build_time})"));
    } else {
        parts.push("built".to_string());
    }

    if !routes.is_empty() {
        parts.push(format!("{} routes:", routes.len()));
        for r in routes.iter().take(15) {
            parts.push(format!("  {r}"));
        }
        if routes.len() > 15 {
            parts.push(format!("  ... +{} more", routes.len() - 15));
        }
    }

    if total_size > 0.0 {
        if total_size > 1024.0 {
            parts.push(format!("total: {:.1} MB", total_size / 1024.0));
        } else {
            parts.push(format!("total: {:.0} kB", total_size));
        }
    }

    if parts.len() == 1 && parts[0] == "built" {
        return compact_output(trimmed, 10);
    }

    parts.join("\n")
}

fn compress_vite(output: &str) -> String {
    let trimmed = output.trim();
    if trimmed.is_empty() {
        return "ok".to_string();
    }

    let mut chunks = Vec::new();
    let mut build_time = String::new();

    for line in trimmed.lines() {
        if let Some(caps) = vite_chunk_re().captures(line) {
            chunks.push(format!("{}: {}", &caps[1], &caps[2]));
        }
        if let Some(caps) = build_time_re().captures(line) {
            build_time = caps[1].to_string();
        }
    }

    let mut parts = Vec::new();
    if !build_time.is_empty() {
        parts.push(format!("built ({build_time})"));
    } else {
        parts.push("built".to_string());
    }

    if !chunks.is_empty() {
        parts.push(format!("{} chunks:", chunks.len()));
        for c in chunks.iter().take(10) {
            parts.push(format!("  {c}"));
        }
        if chunks.len() > 10 {
            parts.push(format!("  ... +{} more", chunks.len() - 10));
        }
    }

    if parts.len() == 1 {
        return compact_output(trimmed, 10);
    }
    parts.join("\n")
}

fn extract_size(line: &str) -> String {
    if let Some(caps) = size_re().captures(line) {
        format!("{} {}", &caps[1], &caps[2])
    } else {
        "?".to_string()
    }
}

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