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("deps.get") || cmd.contains("deps.compile") {
        return Some(compress_deps(trimmed));
    }
    if cmd.contains("compile") || cmd.contains("build") {
        return Some(compress_compile(trimmed));
    }
    if cmd.contains("format") || cmd.contains("fmt") {
        return Some(compress_format(trimmed));
    }
    if cmd.contains("credo") || cmd.contains("dialyzer") {
        return Some(compress_lint(trimmed));
    }

    Some(compact_lines(trimmed, 15))
}

fn compress_test(output: &str) -> String {
    let summary = output
        .lines()
        .rev()
        .find(|l| l.contains("test") && (l.contains("passed") || l.contains("failure")));

    if let Some(s) = summary {
        let mut result = format!("mix test: {}", s.trim());
        let failures: Vec<&str> = output
            .lines()
            .filter(|l| {
                l.trim().starts_with("1)")
                    || l.trim().starts_with("2)")
                    || l.trim().starts_with("3)")
            })
            .collect();
        for f in failures.iter().take(5) {
            result.push_str(&format!("\n  {}", f.trim()));
        }
        return result;
    }
    compact_lines(output, 10)
}

fn compress_deps(output: &str) -> String {
    let mut resolved = 0u32;
    let mut compiled = 0u32;

    for line in output.lines() {
        if line.contains("Resolving") || line.contains("resolving") {
            resolved += 1;
        }
        if line.contains("Compiling") || line.contains("compiling") {
            compiled += 1;
        }
    }

    if resolved == 0 && compiled == 0 {
        return compact_lines(output, 5);
    }
    format!("deps: {resolved} resolved, {compiled} compiled")
}

fn compress_compile(output: &str) -> String {
    let mut compiled = 0u32;
    let mut warnings = 0u32;
    let mut errors = Vec::new();

    for line in output.lines() {
        let trimmed = line.trim();
        if trimmed.starts_with("Compiling") || trimmed.starts_with("Compiled") {
            compiled += 1;
        }
        if trimmed.contains("warning:") {
            warnings += 1;
        }
        if trimmed.contains("error") && trimmed.contains("**") {
            errors.push(trimmed.to_string());
        }
    }

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

    let mut result = format!("{compiled} compiled");
    if warnings > 0 {
        result.push_str(&format!(", {warnings} warnings"));
    }
    result
}

fn compress_format(output: &str) -> String {
    let files: Vec<&str> = output.lines().filter(|l| !l.trim().is_empty()).collect();
    if files.is_empty() {
        return "ok (formatted)".to_string();
    }
    format!("{} files", files.len())
}

fn compress_lint(output: &str) -> String {
    let issues: Vec<&str> = output
        .lines()
        .filter(|l| {
            let t = l.trim();
            t.contains("") || t.starts_with("warning:") || t.starts_with("error:")
        })
        .collect();

    if issues.is_empty() {
        if output.contains("no issues") || output.contains("Analysis finished") {
            return "clean".to_string();
        }
        return compact_lines(output, 10);
    }
    format!(
        "{} issues:\n{}",
        issues.len(),
        issues
            .iter()
            .take(10)
            .map(|i| format!("  {}", i.trim()))
            .collect::<Vec<_>>()
            .join("\n")
    )
}

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
    )
}