lean-ctx 3.9.18

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
const RELEVANCE_THRESHOLD: f64 = 0.05;

fn tokenize(s: &str) -> Vec<String> {
    s.to_lowercase()
        .split(|c: char| !c.is_alphanumeric())
        .filter(|t| t.len() >= 2)
        .map(str::to_string)
        .collect()
}

fn extension_for_task(ext: &str) -> &[&str] {
    match ext {
        "rs" => &["rust", "cargo", "crate", "struct", "impl", "trait"],
        "ts" | "tsx" => &["typescript", "react", "component", "hook"],
        "js" | "jsx" => &["javascript", "node", "react", "component"],
        "py" => &["python", "pip", "django", "flask"],
        "go" => &["golang", "goroutine", "channel"],
        "java" => &["java", "spring", "class", "interface"],
        "sql" => &["database", "query", "table", "migration"],
        "md" => &["doc", "readme", "documentation"],
        "toml" | "yaml" | "yml" | "json" => &["config", "configuration", "setting"],
        "css" | "scss" => &["style", "css", "layout", "theme"],
        "html" => &["html", "template", "page"],
        _ => &[],
    }
}

pub(crate) fn file_relevance_for_task(path: &str, task_description: &str) -> f64 {
    let task_tokens: std::collections::HashSet<String> =
        tokenize(task_description).into_iter().collect();
    if task_tokens.is_empty() {
        return 1.0;
    }

    let path_tokens: std::collections::HashSet<String> = tokenize(path).into_iter().collect();
    if path_tokens.is_empty() {
        return 0.0;
    }

    let intersection = task_tokens.intersection(&path_tokens).count();
    let union = task_tokens.union(&path_tokens).count();
    let mut score = if union > 0 {
        intersection as f64 / union as f64
    } else {
        0.0
    };

    let ext = path.rsplit('.').next().unwrap_or("");
    let ext_keywords = extension_for_task(ext);
    if ext_keywords
        .iter()
        .any(|kw| task_tokens.contains(*kw) || task_tokens.iter().any(|t| t.contains(kw)))
    {
        score += 0.3;
    }

    score.clamp(0.0, 1.0)
}

pub(crate) fn irrelevant_stub(path: &str, line_count: usize, byte_count: u64) -> String {
    format!(
        "[file exists: {path} ({line_count} lines, {byte_count} bytes) — \
         skipped: low relevance to current task]"
    )
}

pub(crate) fn should_gate(
    path: &str,
    mode: &str,
    task: Option<&str>,
    touched_paths: &[String],
) -> bool {
    if mode != "auto" {
        return false;
    }
    let Some(task_desc) = task else {
        return false;
    };
    if task_desc.is_empty() {
        return false;
    }
    if touched_paths.iter().any(|p| p == path) {
        return false;
    }
    file_relevance_for_task(path, task_desc) < RELEVANCE_THRESHOLD
}

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

    #[test]
    fn relevance_matches_related_path() {
        let score = file_relevance_for_task("src/auth/handler.rs", "implement auth handler");
        assert!(score > 0.15, "expected high relevance, got {score}");
    }

    #[test]
    fn relevance_misses_unrelated_path() {
        let score = file_relevance_for_task("docs/changelog.md", "implement auth handler");
        assert!(score < 0.15, "expected low relevance, got {score}");
    }

    #[test]
    fn irrelevant_stub_format() {
        let stub = irrelevant_stub("foo/bar.rs", 100, 5000);
        assert!(stub.contains("foo/bar.rs"));
        assert!(stub.contains("100 lines"));
        assert!(stub.contains("5000 bytes"));
    }

    #[test]
    fn extension_boost_for_rust_task() {
        let without = file_relevance_for_task("src/mod.txt", "build rust module");
        let with_rs = file_relevance_for_task("src/mod.rs", "build rust module");
        assert!(
            with_rs > without,
            "rs ext should boost: {with_rs} vs {without}"
        );
    }

    #[test]
    fn no_task_means_no_gate() {
        assert!(!should_gate("any/file.rs", "auto", None, &[]));
    }

    #[test]
    fn explicit_mode_skips_gate() {
        assert!(!should_gate("any/file.rs", "full", Some("some task"), &[]));
    }

    #[test]
    fn touched_file_skips_gate() {
        assert!(!should_gate(
            "src/main.rs",
            "auto",
            Some("unrelated task xyz"),
            &["src/main.rs".to_string()]
        ));
    }
}