lean-ctx 3.9.14

Context Runtime for AI Agents with CCP. 71 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%.
Documentation
use crate::core::knowledge::{KnowledgeFact, ProjectKnowledge};
use std::collections::HashMap;

#[allow(dead_code)]
pub(crate) struct MemoryBranch {
    #[allow(dead_code)]
    conversation_id: String,
    overrides: HashMap<String, KnowledgeFact>,
    deletions: Vec<String>,
    #[allow(dead_code)]
    created_at: chrono::DateTime<chrono::Utc>,
}

#[allow(dead_code)]
impl MemoryBranch {
    pub(crate) fn new(conversation_id: &str) -> Self {
        Self {
            conversation_id: conversation_id.to_string(),
            overrides: HashMap::new(),
            deletions: Vec::new(),
            created_at: chrono::Utc::now(),
        }
    }

    pub(crate) fn override_fact(&mut self, fact: KnowledgeFact) {
        self.deletions.retain(|k| k != &fact.key);
        self.overrides.insert(fact.key.clone(), fact);
    }

    pub(crate) fn delete_fact(&mut self, key: &str) {
        self.overrides.remove(key);
        if !self.deletions.iter().any(|k| k == key) {
            self.deletions.push(key.to_string());
        }
    }

    pub(crate) fn resolve_facts<'a>(&'a self, base: &'a [KnowledgeFact]) -> Vec<&'a KnowledgeFact> {
        let mut result: Vec<&KnowledgeFact> = base
            .iter()
            .filter(|f| !self.deletions.contains(&f.key))
            .filter(|f| !self.overrides.contains_key(&f.key))
            .collect();

        for fact in self.overrides.values() {
            result.push(fact);
        }

        result
    }

    pub(crate) fn merge_into(self, knowledge: &mut ProjectKnowledge) -> usize {
        let mut merged = 0;
        for (key, fact) in self.overrides {
            if let Some(existing) = knowledge.facts.iter_mut().find(|f| f.key == key) {
                *existing = fact;
            } else {
                knowledge.facts.push(fact);
            }
            merged += 1;
        }
        for key in &self.deletions {
            knowledge.facts.retain(|f| f.key != *key);
        }
        merged
    }

    pub(crate) fn is_empty(&self) -> bool {
        self.overrides.is_empty() && self.deletions.is_empty()
    }

    pub(crate) fn override_count(&self) -> usize {
        self.overrides.len()
    }

    pub(crate) fn deletion_count(&self) -> usize {
        self.deletions.len()
    }

    pub(crate) fn conversation_id(&self) -> &str {
        &self.conversation_id
    }
}

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

    fn test_fact(key: &str, value: &str) -> KnowledgeFact {
        let now = chrono::Utc::now();
        KnowledgeFact {
            category: "test".to_string(),
            key: key.to_string(),
            value: value.to_string(),
            source_session: "test-session".to_string(),
            confidence: 1.0,
            created_at: now,
            last_confirmed: now,
            retrieval_count: 0,
            last_retrieved: None,
            valid_from: None,
            valid_until: None,
            supersedes: None,
            confirmation_count: 0,
            feedback_up: 0,
            feedback_down: 0,
            last_feedback: None,
            privacy: Default::default(),
            sensitivity: Default::default(),
            imported_from: None,
            archetype: Default::default(),
            fidelity: Default::default(),
            revision_count: 0,
        }
    }

    #[test]
    fn branch_overrides_base_fact() {
        let base = vec![test_fact("lang", "rust"), test_fact("db", "postgres")];
        let mut branch = MemoryBranch::new("test-conv");
        branch.override_fact(test_fact("lang", "python"));

        let resolved = branch.resolve_facts(&base);
        assert_eq!(resolved.len(), 2);
        let lang = resolved.iter().find(|f| f.key == "lang").unwrap();
        assert_eq!(lang.value, "python");
    }

    #[test]
    fn branch_deletes_base_fact() {
        let base = vec![test_fact("lang", "rust"), test_fact("db", "postgres")];
        let mut branch = MemoryBranch::new("test-conv");
        branch.delete_fact("db");

        let resolved = branch.resolve_facts(&base);
        assert_eq!(resolved.len(), 1);
        assert_eq!(resolved[0].key, "lang");
    }

    #[test]
    fn empty_branch_returns_all_base_facts() {
        let base = vec![test_fact("lang", "rust")];
        let branch = MemoryBranch::new("test-conv");
        assert!(branch.is_empty());

        let resolved = branch.resolve_facts(&base);
        assert_eq!(resolved.len(), 1);
    }

    #[test]
    fn merge_applies_overrides_and_deletions() {
        let mut knowledge = ProjectKnowledge::load_or_create("/tmp/lean-ctx-test-memory-branch");
        knowledge.facts.push(test_fact("lang", "rust"));
        knowledge.facts.push(test_fact("db", "postgres"));

        let mut branch = MemoryBranch::new("test-conv");
        branch.override_fact(test_fact("lang", "python"));
        branch.delete_fact("db");

        let merged = branch.merge_into(&mut knowledge);
        assert_eq!(merged, 1);
        assert_eq!(knowledge.facts.len(), 1);
        assert_eq!(knowledge.facts[0].value, "python");
    }
}