lean-ctx 2.16.6

Context Intelligence Engine with CCP. 25 MCP tools, 90+ compression patterns, cross-session memory (CCP), persistent AI knowledge, multi-agent sharing, LITM-aware positioning. Supports 23 AI tools. Reduces LLM token consumption by up to 99%.
Documentation
use crate::core::knowledge::ProjectKnowledge;
use crate::core::session::SessionState;

#[allow(clippy::too_many_arguments)]
pub fn handle(
    project_root: &str,
    action: &str,
    category: Option<&str>,
    key: Option<&str>,
    value: Option<&str>,
    query: Option<&str>,
    session_id: &str,
    pattern_type: Option<&str>,
    examples: Option<Vec<String>>,
    confidence: Option<f32>,
) -> String {
    match action {
        "remember" => {
            let cat = match category {
                Some(c) => c,
                None => return "Error: category is required for remember".to_string(),
            };
            let k = match key {
                Some(k) => k,
                None => return "Error: key is required for remember".to_string(),
            };
            let v = match value {
                Some(v) => v,
                None => return "Error: value is required for remember".to_string(),
            };
            let conf = confidence.unwrap_or(0.8);
            let mut knowledge = ProjectKnowledge::load_or_create(project_root);
            knowledge.remember(cat, k, v, session_id, conf);
            match knowledge.save() {
                Ok(()) => format!("Remembered [{cat}] {k}: {v} (confidence: {:.0}%)", conf * 100.0),
                Err(e) => format!("Remembered but save failed: {e}"),
            }
        }

        "recall" => {
            let knowledge = match ProjectKnowledge::load(project_root) {
                Some(k) => k,
                None => return "No knowledge stored for this project yet.".to_string(),
            };

            if let Some(cat) = category {
                let facts = knowledge.recall_by_category(cat);
                if facts.is_empty() {
                    return format!("No facts in category '{cat}'.");
                }
                return format_facts(&facts, Some(cat));
            }

            if let Some(q) = query {
                let facts = knowledge.recall(q);
                if facts.is_empty() {
                    return format!("No facts matching '{q}'.");
                }
                return format_facts(&facts, None);
            }

            "Error: provide query or category for recall".to_string()
        }

        "pattern" => {
            let pt = match pattern_type {
                Some(p) => p,
                None => return "Error: pattern_type is required".to_string(),
            };
            let desc = match value {
                Some(v) => v,
                None => return "Error: value (description) is required for pattern".to_string(),
            };
            let exs = examples.unwrap_or_default();
            let mut knowledge = ProjectKnowledge::load_or_create(project_root);
            knowledge.add_pattern(pt, desc, exs, session_id);
            match knowledge.save() {
                Ok(()) => format!("Pattern [{pt}] added: {desc}"),
                Err(e) => format!("Pattern added but save failed: {e}"),
            }
        }

        "status" => {
            let knowledge = match ProjectKnowledge::load(project_root) {
                Some(k) => k,
                None => return "No knowledge stored for this project yet. Use ctx_knowledge(action=\"remember\") to start.".to_string(),
            };

            let mut out = format!(
                "Project Knowledge: {} facts, {} patterns, {} history entries\n",
                knowledge.facts.len(),
                knowledge.patterns.len(),
                knowledge.history.len()
            );
            out.push_str(&format!("Last updated: {}\n", knowledge.updated_at.format("%Y-%m-%d %H:%M UTC")));
            out.push_str(&knowledge.format_summary());
            out
        }

        "remove" => {
            let cat = match category {
                Some(c) => c,
                None => return "Error: category is required for remove".to_string(),
            };
            let k = match key {
                Some(k) => k,
                None => return "Error: key is required for remove".to_string(),
            };
            let mut knowledge = ProjectKnowledge::load_or_create(project_root);
            if knowledge.remove_fact(cat, k) {
                match knowledge.save() {
                    Ok(()) => format!("Removed [{cat}] {k}"),
                    Err(e) => format!("Removed but save failed: {e}"),
                }
            } else {
                format!("No fact found: [{cat}] {k}")
            }
        }

        "export" => {
            let knowledge = match ProjectKnowledge::load(project_root) {
                Some(k) => k,
                None => return "No knowledge to export.".to_string(),
            };
            match serde_json::to_string_pretty(&knowledge) {
                Ok(json) => json,
                Err(e) => format!("Export failed: {e}"),
            }
        }

        "consolidate" => {
            let session = match SessionState::load_latest() {
                Some(s) => s,
                None => return "No active session to consolidate.".to_string(),
            };

            let mut knowledge = ProjectKnowledge::load_or_create(project_root);
            let mut consolidated = 0u32;

            for finding in &session.findings {
                let key_text = if let Some(ref file) = finding.file {
                    if let Some(line) = finding.line {
                        format!("{file}:{line}")
                    } else {
                        file.clone()
                    }
                } else {
                    format!("finding-{consolidated}")
                };

                knowledge.remember(
                    "finding",
                    &key_text,
                    &finding.summary,
                    &session.id,
                    0.7,
                );
                consolidated += 1;
            }

            for decision in &session.decisions {
                let key_text = decision
                    .summary
                    .chars()
                    .take(50)
                    .collect::<String>()
                    .replace(' ', "-")
                    .to_lowercase();

                knowledge.remember(
                    "decision",
                    &key_text,
                    &decision.summary,
                    &session.id,
                    0.85,
                );
                consolidated += 1;
            }

            let task_desc = session
                .task
                .as_ref()
                .map(|t| t.description.clone())
                .unwrap_or_else(|| "(no task)".into());

            let summary = format!(
                "Session {}: {}{} findings, {} decisions consolidated",
                session.id,
                task_desc,
                session.findings.len(),
                session.decisions.len()
            );
            knowledge.consolidate(&summary, vec![session.id.clone()]);

            match knowledge.save() {
                Ok(()) => format!(
                    "Consolidated {consolidated} items from session {} into project knowledge.\n\
                     Facts: {}, Patterns: {}, History: {}",
                    session.id,
                    knowledge.facts.len(),
                    knowledge.patterns.len(),
                    knowledge.history.len()
                ),
                Err(e) => format!("Consolidation done but save failed: {e}"),
            }
        }

        _ => format!(
            "Unknown action: {action}. Use: remember, recall, pattern, status, remove, export, consolidate"
        ),
    }
}

fn format_facts(
    facts: &[&crate::core::knowledge::KnowledgeFact],
    category: Option<&str>,
) -> String {
    let mut out = String::new();
    if let Some(cat) = category {
        out.push_str(&format!("Facts [{cat}] ({}):\n", facts.len()));
    } else {
        out.push_str(&format!("Matching facts ({}):\n", facts.len()));
    }
    for f in facts {
        out.push_str(&format!(
            "  [{}/{}]: {} (confidence: {:.0}%, confirmed: {})\n",
            f.category,
            f.key,
            f.value,
            f.confidence * 100.0,
            f.last_confirmed.format("%Y-%m-%d")
        ));
    }
    out
}