lean-ctx 3.6.6

Context Runtime for AI Agents with CCP. 51 MCP tools, 10 read modes, 60+ compression patterns, cross-session memory (CCP), persistent AI knowledge with temporal facts + contradiction detection, multi-agent context sharing, 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, PathBuf};

use crate::core::knowledge::{KnowledgeFact, ProjectKnowledge};

pub fn run(args: &[String]) {
    let format = args
        .iter()
        .position(|a| a == "--format")
        .and_then(|i| args.get(i + 1))
        .map_or("mdc", |s| s.as_str());

    let root_arg = args
        .iter()
        .position(|a| a == "--root")
        .and_then(|i| args.get(i + 1))
        .map_or(".", |s| s.as_str());

    let project_root = std::fs::canonicalize(root_arg).unwrap_or_else(|_| PathBuf::from(root_arg));
    let project_root_str = project_root.to_string_lossy();

    let Some(knowledge) = ProjectKnowledge::load(&project_root_str) else {
        eprintln!("No knowledge found for project: {project_root_str}");
        eprintln!("Run `lean-ctx` in a session first to accumulate knowledge.");
        std::process::exit(1);
    };

    let high_confidence: Vec<&KnowledgeFact> = knowledge
        .facts
        .iter()
        .filter(|f| f.confidence >= 0.6 && f.retrieval_count >= 2)
        .collect();

    if high_confidence.is_empty() {
        eprintln!("No high-confidence knowledge facts found (need confidence >= 0.6 and at least 2 retrievals).");
        std::process::exit(0);
    }

    match format {
        "mdc" => export_mdc(&high_confidence, &project_root),
        "agents-md" => export_agents_md(&high_confidence, &project_root),
        "claude-md" => export_claude_md(&high_confidence, &project_root),
        _ => {
            eprintln!("Unknown format: {format}. Supported: mdc, agents-md, claude-md");
            std::process::exit(1);
        }
    }
}

fn export_mdc(facts: &[&KnowledgeFact], project_root: &Path) {
    let output_path = project_root.join(".cursor/rules/lean-ctx-knowledge.mdc");
    let content = build_mdc_content(facts);

    if let Some(parent) = output_path.parent() {
        let _ = std::fs::create_dir_all(parent);
    }

    match std::fs::write(&output_path, &content) {
        Ok(()) => {
            println!(
                "Exported {} rules to {}",
                facts.len(),
                output_path.display()
            );
        }
        Err(e) => eprintln!("Error writing {}: {e}", output_path.display()),
    }
}

fn export_agents_md(facts: &[&KnowledgeFact], project_root: &Path) {
    let output_path = project_root.join("AGENTS.md");
    let section = build_agents_section(facts);

    if output_path.exists() {
        let existing = std::fs::read_to_string(&output_path).unwrap_or_default();
        let marker_start = "<!-- lean-ctx-knowledge-start -->";
        let marker_end = "<!-- lean-ctx-knowledge-end -->";

        let new_content = if existing.contains(marker_start) {
            let before = existing.split(marker_start).next().unwrap_or("");
            let after = existing.split(marker_end).nth(1).unwrap_or("");
            format!("{before}{marker_start}\n{section}\n{marker_end}{after}")
        } else {
            format!("{existing}\n\n{marker_start}\n{section}\n{marker_end}\n")
        };

        match std::fs::write(&output_path, &new_content) {
            Ok(()) => println!("Updated {} rules in {}", facts.len(), output_path.display()),
            Err(e) => eprintln!("Error writing {}: {e}", output_path.display()),
        }
    } else {
        let content = format!(
            "# Project Knowledge (auto-generated by lean-ctx)\n\n\
             <!-- lean-ctx-knowledge-start -->\n{section}\n<!-- lean-ctx-knowledge-end -->\n"
        );
        match std::fs::write(&output_path, &content) {
            Ok(()) => println!(
                "Created {} with {} rules",
                output_path.display(),
                facts.len()
            ),
            Err(e) => eprintln!("Error writing {}: {e}", output_path.display()),
        }
    }
}

fn export_claude_md(facts: &[&KnowledgeFact], project_root: &Path) {
    let output_path = project_root.join("CLAUDE.md");
    let section = build_agents_section(facts);

    if output_path.exists() {
        let existing = std::fs::read_to_string(&output_path).unwrap_or_default();
        let marker_start = "<!-- lean-ctx-knowledge-start -->";
        let marker_end = "<!-- lean-ctx-knowledge-end -->";

        let new_content = if existing.contains(marker_start) {
            let before = existing.split(marker_start).next().unwrap_or("");
            let after = existing.split(marker_end).nth(1).unwrap_or("");
            format!("{before}{marker_start}\n{section}\n{marker_end}{after}")
        } else {
            format!("{existing}\n\n{marker_start}\n{section}\n{marker_end}\n")
        };

        match std::fs::write(&output_path, &new_content) {
            Ok(()) => println!("Updated {} rules in {}", facts.len(), output_path.display()),
            Err(e) => eprintln!("Error writing {}: {e}", output_path.display()),
        }
    } else {
        let content = format!(
            "# Project Rules (auto-generated by lean-ctx)\n\n\
             <!-- lean-ctx-knowledge-start -->\n{section}\n<!-- lean-ctx-knowledge-end -->\n"
        );
        match std::fs::write(&output_path, &content) {
            Ok(()) => println!(
                "Created {} with {} rules",
                output_path.display(),
                facts.len()
            ),
            Err(e) => eprintln!("Error writing {}: {e}", output_path.display()),
        }
    }
}

fn build_mdc_content(facts: &[&KnowledgeFact]) -> String {
    let mut out = String::from(
        "---\ndescription: \"Project knowledge (auto-generated by lean-ctx export-rules)\"\n\
         globs: \"**/*\"\nalwaysApply: true\n---\n\n\
         # Project Knowledge\n\n",
    );

    let mut by_category: std::collections::BTreeMap<&str, Vec<&&KnowledgeFact>> =
        std::collections::BTreeMap::new();

    for fact in facts {
        by_category.entry(&fact.category).or_default().push(fact);
    }

    for (category, cat_facts) in &by_category {
        out.push_str(&format!("## {category}\n\n"));
        for fact in cat_facts {
            out.push_str(&format!("- **{}**: {}\n", fact.key, fact.value));
        }
        out.push('\n');
    }

    out
}

fn build_agents_section(facts: &[&KnowledgeFact]) -> String {
    let mut out = String::from("## Project Knowledge (lean-ctx)\n\n");

    let mut by_category: std::collections::BTreeMap<&str, Vec<&&KnowledgeFact>> =
        std::collections::BTreeMap::new();

    for fact in facts {
        by_category.entry(&fact.category).or_default().push(fact);
    }

    for (category, cat_facts) in &by_category {
        out.push_str(&format!("### {category}\n\n"));
        for fact in cat_facts {
            out.push_str(&format!("- **{}**: {}\n", fact.key, fact.value));
        }
        out.push('\n');
    }

    out.trim_end().to_string()
}