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
pub fn compress(cmd: &str, output: &str) -> Option<String> {
    let trimmed = output.trim();
    if trimmed.is_empty() {
        return Some("ok".to_string());
    }

    if cmd.contains("test") {
        return Some(compress_test(trimmed));
    }
    if cmd.contains("build") {
        return Some(compress_build(trimmed));
    }
    if cmd.contains("package resolve") || cmd.contains("package update") {
        return Some(compress_resolve(trimmed));
    }

    Some(compact_lines(trimmed, 15))
}

fn compress_test(output: &str) -> String {
    let mut passed = 0u32;
    let mut failed = 0u32;
    let mut failures = Vec::new();
    let mut time = String::new();

    for line in output.lines() {
        let trimmed = line.trim();
        if trimmed.contains("Test Case") && trimmed.contains("passed") {
            passed += 1;
        } else if trimmed.contains("Test Case") && trimmed.contains("failed") {
            failed += 1;
            failures.push(trimmed.to_string());
        }
        if trimmed.starts_with("Test Suite") && trimmed.contains("Executed") {
            time = trimmed.to_string();
        }
        if trimmed.contains("Executed") && trimmed.contains("tests") {
            if let Some(pos) = trimmed.find("Executed") {
                time = trimmed[pos..].to_string();
            }
        }
    }

    if passed == 0 && failed == 0 {
        return compact_lines(output, 10);
    }

    let mut result = format!("swift test: {passed} passed");
    if failed > 0 {
        result.push_str(&format!(", {failed} failed"));
    }
    if !time.is_empty() {
        result.push_str(&format!("\n  {time}"));
    }
    for f in failures.iter().take(5) {
        result.push_str(&format!("\n  FAIL: {f}"));
    }
    result
}

fn compress_build(output: &str) -> String {
    let mut compiling = 0u32;
    let mut linking = false;
    let mut errors = Vec::new();
    let mut warnings = 0u32;

    for line in output.lines() {
        let trimmed = line.trim();
        if trimmed.starts_with("Compiling") || trimmed.contains("[") && trimmed.contains("]") {
            compiling += 1;
        }
        if trimmed.starts_with("Linking") || trimmed.contains("Linking") {
            linking = true;
        }
        if trimmed.contains("error:") {
            errors.push(trimmed.to_string());
        }
        if trimmed.contains("warning:") {
            warnings += 1;
        }
    }

    if !errors.is_empty() {
        let mut result = format!("{} errors", errors.len());
        if warnings > 0 {
            result.push_str(&format!(", {warnings} warnings"));
        }
        for e in errors.iter().take(10) {
            result.push_str(&format!("\n  {e}"));
        }
        return result;
    }

    let mut result = format!("Build ok ({compiling} compiled");
    if linking {
        result.push_str(", linked");
    }
    if warnings > 0 {
        result.push_str(&format!(", {warnings} warnings"));
    }
    result.push(')');
    result
}

fn compress_resolve(output: &str) -> String {
    let mut fetched = 0u32;
    let mut resolved = 0u32;
    for line in output.lines() {
        if line.contains("Fetching") {
            fetched += 1;
        }
        if line.contains("Resolving") || line.contains("resolved") {
            resolved += 1;
        }
    }
    if fetched == 0 && resolved == 0 {
        return compact_lines(output, 5);
    }
    format!("{fetched} fetched, {resolved} resolved")
}

fn compact_lines(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
    )
}