lean-ctx 3.9.19

Context Runtime for AI Agents with CCP. 79 MCP tools, 10 read modes, 95+ 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%.
use serde::Serialize;

use crate::core::provenance::{CheckpointRecord, ProvenanceRecord, ProvenanceTracker};

use super::helpers::detect_project_root_for_dashboard;

const RECENT_RECORD_LIMIT: usize = 50;

#[derive(Serialize)]
struct ProvenanceResponse {
    records: Vec<ProvenanceRecord>,
    checkpoints: Vec<CheckpointRecord>,
}

pub(super) fn handle(
    path: &str,
    _query: &str,
    method: &str,
    _body: &str,
) -> Option<(&'static str, &'static str, String)> {
    match path {
        "/api/provenance" if method.eq_ignore_ascii_case("GET") => {
            let root = detect_project_root_for_dashboard();
            let response = ProvenanceTracker::new(root)
                .map(|tracker| ProvenanceResponse {
                    records: tracker
                        .store()
                        .query_recent_file_touches(RECENT_RECORD_LIMIT),
                    checkpoints: recent_checkpoints(&tracker),
                })
                .unwrap_or_else(|_| ProvenanceResponse {
                    records: Vec::new(),
                    checkpoints: Vec::new(),
                });
            let body = serde_json::to_string(&response)
                .unwrap_or_else(|_| r#"{"records":[],"checkpoints":[]}"#.to_string());
            Some(("200 OK", "application/json", body))
        }
        "/api/provenance" => Some((
            "405 Method Not Allowed",
            "application/json",
            r#"{"error":"method not allowed"}"#.to_string(),
        )),
        _ => None,
    }
}

fn recent_checkpoints(tracker: &ProvenanceTracker) -> Vec<CheckpointRecord> {
    let mut checkpoints = tracker.store().query_checkpoints(None);
    checkpoints.sort_by_key(|cp| std::cmp::Reverse(cp.observed_at));
    checkpoints.truncate(RECENT_RECORD_LIMIT);
    checkpoints
}

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

    #[test]
    fn provenance_route_rejects_non_get_requests() {
        let response = handle("/api/provenance", "", "POST", "").expect("route response");
        assert_eq!(response.0, "405 Method Not Allowed");
    }
}