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 is_table_output(trimmed) {
        return Some(compress_table(trimmed));
    }

    if cmd.contains("show databases") || cmd.contains("show tables") {
        return Some(compress_show(trimmed));
    }

    if trimmed.starts_with("Query OK") || trimmed.starts_with("Empty set") {
        return Some(trimmed.lines().next().unwrap_or(trimmed).to_string());
    }

    Some(compact_lines(trimmed, 20))
}

fn is_table_output(output: &str) -> bool {
    let lines: Vec<&str> = output.lines().collect();
    lines.len() >= 3
        && lines
            .iter()
            .any(|l| l.starts_with('+') && l.contains("---"))
}

fn compress_table(output: &str) -> String {
    let lines: Vec<&str> = output.lines().collect();
    let data_lines: Vec<&&str> = lines
        .iter()
        .filter(|l| !l.starts_with('+') && !l.trim().is_empty())
        .collect();

    let row_count = if data_lines.len() > 1 {
        data_lines.len() - 1
    } else {
        0
    };

    if row_count <= 20 {
        return output.to_string();
    }

    let header_end = lines
        .iter()
        .enumerate()
        .filter(|(_, l)| l.starts_with('+'))
        .nth(1)
        .map(|(i, _)| i + 1)
        .unwrap_or(3);

    let preview_end = (header_end + 10).min(lines.len());
    let preview = &lines[..preview_end];
    format!("{}\n... ({row_count} rows total)", preview.join("\n"))
}

fn compress_show(output: &str) -> String {
    let items: Vec<&str> = output
        .lines()
        .filter(|l| !l.starts_with('+') && !l.trim().is_empty() && !l.contains("---"))
        .filter(|l| !l.contains("Database") && !l.contains("Tables_in"))
        .map(|l| l.trim().trim_matches('|').trim())
        .filter(|l| !l.is_empty())
        .collect();

    if items.is_empty() {
        return "empty".to_string();
    }
    if items.len() <= 30 {
        return format!("{} items: {}", items.len(), items.join(", "));
    }
    format!(
        "{} items: {}, ... +{} more",
        items.len(),
        items[..20].join(", "),
        items.len() - 20
    )
}

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