lean-ctx 3.3.5

Context Runtime for AI Agents with CCP. 46 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 std::path::Path;

pub fn upsert(path: &Path, start: &str, end: &str, block: &str, quiet: bool, label: &str) {
    let existing = std::fs::read_to_string(path).unwrap_or_default();

    if existing.contains(start) {
        let cleaned = remove_content(&existing, start, end);
        let mut out = cleaned.trim_end().to_string();
        if !out.is_empty() {
            out.push('\n');
        }
        out.push('\n');
        out.push_str(block);
        out.push('\n');
        std::fs::write(path, &out).ok();
        if !quiet {
            println!("  Updated {label}");
        }
    } else {
        let mut out = existing;
        if !out.is_empty() && !out.ends_with('\n') {
            out.push('\n');
        }
        if !out.is_empty() {
            out.push('\n');
        }
        out.push_str(block);
        out.push('\n');
        std::fs::write(path, &out).ok();
        if !quiet {
            println!("  Installed {label}");
        }
    }
}

pub fn remove_from_file(path: &Path, start: &str, end: &str, quiet: bool, label: &str) {
    let existing = match std::fs::read_to_string(path) {
        Ok(c) => c,
        Err(_) => return,
    };
    if !existing.contains(start) {
        return;
    }
    let cleaned = remove_content(&existing, start, end);
    std::fs::write(path, cleaned.trim_end().to_owned() + "\n").ok();
    if !quiet {
        println!("  Removed {label}");
    }
}

pub fn remove_content(content: &str, start: &str, end: &str) -> String {
    let s = content.find(start);
    let e = content.find(end);
    match (s, e) {
        (Some(si), Some(ei)) if ei >= si => {
            let after_end = ei + end.len();
            let before = content[..si].trim_end_matches('\n');
            let after = content[after_end..].trim_start_matches('\n');
            let mut out = before.to_string();
            if !after.is_empty() {
                out.push('\n');
                out.push_str(after);
            }
            out
        }
        _ => content.to_string(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn remove_content_works() {
        let content = "before\n# >>> start >>>\nhook content\n# <<< end <<<\nafter\n";
        let cleaned = remove_content(content, "# >>> start >>>", "# <<< end <<<");
        assert!(!cleaned.contains("hook content"));
        assert!(cleaned.contains("before"));
        assert!(cleaned.contains("after"));
    }

    #[test]
    fn remove_content_preserves_when_missing() {
        let content = "no hook here\n";
        let cleaned = remove_content(content, "# >>> start >>>", "# <<< end <<<");
        assert_eq!(cleaned, content);
    }
}