lean-ctx 3.6.5

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 serde::{Deserialize, Serialize};
use std::collections::HashSet;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Capability {
    FsRead,
    FsWrite,
    FsDelete,
    NetOutbound,
    ExecSandbox,
    ExecUnrestricted,
    KnowledgeRead,
    KnowledgeWrite,
    CrossProject,
    ConfigWrite,
    AgentManage,
}

impl Capability {
    pub fn display_name(&self) -> &'static str {
        match self {
            Self::FsRead => "fs:read",
            Self::FsWrite => "fs:write",
            Self::FsDelete => "fs:delete",
            Self::NetOutbound => "net:outbound",
            Self::ExecSandbox => "exec:sandbox",
            Self::ExecUnrestricted => "exec:unrestricted",
            Self::KnowledgeRead => "knowledge:read",
            Self::KnowledgeWrite => "knowledge:write",
            Self::CrossProject => "cross_project",
            Self::ConfigWrite => "config:write",
            Self::AgentManage => "agent:manage",
        }
    }
}

pub struct CapabilityCheckResult {
    pub allowed: bool,
    pub missing: Vec<Capability>,
}

pub fn required_capabilities(tool_name: &str) -> &'static [Capability] {
    match tool_name {
        "ctx_edit" => &[Capability::FsRead, Capability::FsWrite],
        "ctx_shell" => &[Capability::ExecUnrestricted],
        "ctx_knowledge" => &[Capability::KnowledgeRead, Capability::KnowledgeWrite],
        "ctx_handoff" => &[Capability::KnowledgeRead, Capability::AgentManage],
        "ctx_agent" | "ctx_task" => &[Capability::AgentManage],
        "ctx_session" | "ctx" => &[],
        "ctx_share" => &[Capability::KnowledgeRead, Capability::CrossProject],
        _ => &[Capability::FsRead],
    }
}

pub fn role_capabilities(role_name: &str) -> HashSet<Capability> {
    match role_name {
        "admin" => HashSet::from([
            Capability::FsRead,
            Capability::FsWrite,
            Capability::FsDelete,
            Capability::NetOutbound,
            Capability::ExecSandbox,
            Capability::ExecUnrestricted,
            Capability::KnowledgeRead,
            Capability::KnowledgeWrite,
            Capability::CrossProject,
            Capability::ConfigWrite,
            Capability::AgentManage,
        ]),
        "reviewer" | "ci" => HashSet::from([
            Capability::FsRead,
            Capability::ExecSandbox,
            Capability::KnowledgeRead,
        ]),
        "minimal" => HashSet::from([Capability::FsRead, Capability::KnowledgeRead]),
        _ => HashSet::from([
            Capability::FsRead,
            Capability::FsWrite,
            Capability::ExecSandbox,
            Capability::ExecUnrestricted,
            Capability::KnowledgeRead,
            Capability::KnowledgeWrite,
            Capability::AgentManage,
        ]),
    }
}

pub fn check_capabilities(role_name: &str, tool_name: &str) -> CapabilityCheckResult {
    let required = required_capabilities(tool_name);
    if required.is_empty() {
        return CapabilityCheckResult {
            allowed: true,
            missing: Vec::new(),
        };
    }
    let granted = role_capabilities(role_name);
    let missing: Vec<Capability> = required
        .iter()
        .filter(|c| !granted.contains(c))
        .copied()
        .collect();
    CapabilityCheckResult {
        allowed: missing.is_empty(),
        missing,
    }
}

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

    #[test]
    fn admin_has_all_capabilities() {
        let caps = role_capabilities("admin");
        assert!(caps.contains(&Capability::FsRead));
        assert!(caps.contains(&Capability::FsWrite));
        assert!(caps.contains(&Capability::FsDelete));
        assert!(caps.contains(&Capability::NetOutbound));
        assert!(caps.contains(&Capability::ExecUnrestricted));
        assert!(caps.contains(&Capability::ConfigWrite));
        assert!(caps.contains(&Capability::AgentManage));
    }

    #[test]
    fn reviewer_cannot_write() {
        let result = check_capabilities("reviewer", "ctx_edit");
        assert!(!result.allowed);
        assert!(result.missing.contains(&Capability::FsWrite));
    }

    #[test]
    fn minimal_cannot_shell() {
        let result = check_capabilities("minimal", "ctx_shell");
        assert!(!result.allowed);
        assert!(result.missing.contains(&Capability::ExecUnrestricted));
    }

    #[test]
    fn session_always_allowed() {
        let result = check_capabilities("minimal", "ctx_session");
        assert!(result.allowed);
        assert!(result.missing.is_empty());
    }

    #[test]
    fn developer_can_edit() {
        let result = check_capabilities("developer", "ctx_edit");
        assert!(result.allowed);
    }

    #[test]
    fn unknown_role_gets_defaults() {
        let result = check_capabilities("unknown_role", "ctx_read");
        assert!(result.allowed);
    }

    #[test]
    fn unknown_tool_requires_fs_read() {
        let required = required_capabilities("some_unknown_tool");
        assert_eq!(required, &[Capability::FsRead]);
    }

    #[test]
    fn display_names_are_colon_separated() {
        assert_eq!(Capability::FsRead.display_name(), "fs:read");
        assert_eq!(
            Capability::ExecUnrestricted.display_name(),
            "exec:unrestricted"
        );
        assert_eq!(Capability::AgentManage.display_name(), "agent:manage");
    }
}