lean-ctx 3.6.15

Context Runtime for AI Agents with CCP. 62 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 super::helpers::detect_project_root_for_dashboard;

pub(super) fn handle(
    path: &str,
    _query_str: &str,
    _method: &str,
    _body: &str,
) -> Option<(&'static str, &'static str, String)> {
    if path != "/api/context-risk" {
        return None;
    }

    let ledger = crate::core::context_ledger::ContextLedger::load();
    let session = crate::core::session::SessionState::load_latest();

    let edited_paths: Vec<String> = session
        .as_ref()
        .map(|s| s.files_touched.iter().map(|f| f.path.clone()).collect())
        .unwrap_or_default();

    let mut warnings: Vec<serde_json::Value> = Vec::new();

    let mut files_read_full = 0usize;
    let mut files_read_compressed = 0usize;
    let mut files_edited_after_compressed: Vec<String> = Vec::new();

    for entry in &ledger.entries {
        let is_full = entry.mode == "full";
        if is_full {
            files_read_full += 1;
        } else {
            files_read_compressed += 1;
        }

        if !is_full
            && edited_paths
                .iter()
                .any(|ep| entry.path.ends_with(ep) || ep.ends_with(&entry.path))
        {
            files_edited_after_compressed.push(entry.path.clone());
            warnings.push(serde_json::json!({
                "severity": "high",
                "path": entry.path,
                "mode": entry.mode,
                "message": format!("Edited file was only read in '{}' mode", entry.mode),
                "suggestion": "Consider a full read before editing to ensure complete context",
            }));
        }
    }

    let project_root = detect_project_root_for_dashboard();
    let overlays = crate::core::context_overlay::OverlayStore::load_project(
        &std::path::PathBuf::from(&project_root),
    );
    let pinned_count = overlays
        .all()
        .iter()
        .filter(|o| {
            matches!(
                o.operation,
                crate::core::context_overlay::OverlayOp::Pin { .. }
            )
        })
        .count();

    let excluded_count = overlays
        .all()
        .iter()
        .filter(|o| {
            matches!(
                o.operation,
                crate::core::context_overlay::OverlayOp::Exclude { .. }
            )
        })
        .count();

    let payload = serde_json::json!({
        "warnings": warnings,
        "compression_health": {
            "files_read_full": files_read_full,
            "files_read_compressed": files_read_compressed,
            "files_edited_after_compressed": files_edited_after_compressed.len(),
            "potential_risk_files": files_edited_after_compressed,
        },
        "overlay_summary": {
            "pinned": pinned_count,
            "excluded": excluded_count,
        },
    });

    let json = serde_json::to_string(&payload).unwrap_or_else(|_| "{}".to_string());
    Some(("200 OK", "application/json", json))
}