Skip to main content

lean_ctx/tools/
ctx_smart_read.rs

1use crate::core::cache::SessionCache;
2use crate::core::mode_predictor::{FileSignature, ModePredictor};
3use crate::core::tokens::count_tokens;
4use crate::tools::CrpMode;
5
6pub fn select_mode(cache: &SessionCache, path: &str) -> String {
7    select_mode_with_task(cache, path, None)
8}
9
10pub fn select_mode_with_task(cache: &SessionCache, path: &str, task: Option<&str>) -> String {
11    let content = match std::fs::read_to_string(path) {
12        Ok(c) => c,
13        Err(_) => return "full".to_string(),
14    };
15
16    let token_count = count_tokens(&content);
17    let ext = std::path::Path::new(path)
18        .extension()
19        .and_then(|e| e.to_str())
20        .unwrap_or("");
21
22    if cache.get(path).is_some() {
23        let cached = cache.get(path).unwrap();
24        if cached.hash == compute_hash(&content) {
25            return "full".to_string();
26        }
27        return "diff".to_string();
28    }
29
30    if token_count <= 200 {
31        return "full".to_string();
32    }
33
34    if is_config_or_data(ext, path) {
35        return "full".to_string();
36    }
37
38    if let Some(t) = task {
39        if !t.is_empty() && token_count > 1000 && is_code(ext) {
40            return "task".to_string();
41        }
42    }
43
44    let sig = FileSignature::from_path(path, token_count);
45    let predictor = ModePredictor::new();
46    if let Some(predicted) = predictor.predict_best_mode(&sig) {
47        return predicted;
48    }
49
50    heuristic_mode(ext, token_count)
51}
52
53fn heuristic_mode(ext: &str, token_count: usize) -> String {
54    if token_count > 5000 {
55        if is_code(ext) {
56            return "signatures".to_string();
57        }
58        return "aggressive".to_string();
59    }
60    if token_count > 2000 && is_code(ext) {
61        return "map".to_string();
62    }
63    "full".to_string()
64}
65
66pub fn handle(cache: &mut SessionCache, path: &str, crp_mode: CrpMode) -> String {
67    let mode = select_mode(cache, path);
68    let result = crate::tools::ctx_read::handle(cache, path, &mode, crp_mode);
69    format!("[auto:{mode}] {result}")
70}
71
72fn compute_hash(content: &str) -> String {
73    use md5::{Digest, Md5};
74    let mut hasher = Md5::new();
75    hasher.update(content.as_bytes());
76    format!("{:x}", hasher.finalize())
77}
78
79pub fn is_code_ext(ext: &str) -> bool {
80    is_code(ext)
81}
82
83fn is_code(ext: &str) -> bool {
84    matches!(
85        ext,
86        "rs" | "ts"
87            | "tsx"
88            | "js"
89            | "jsx"
90            | "py"
91            | "go"
92            | "java"
93            | "c"
94            | "cpp"
95            | "cc"
96            | "h"
97            | "hpp"
98            | "rb"
99            | "cs"
100            | "kt"
101            | "swift"
102            | "php"
103            | "zig"
104            | "ex"
105            | "exs"
106            | "scala"
107            | "sc"
108            | "dart"
109            | "sh"
110            | "bash"
111            | "svelte"
112            | "vue"
113    )
114}
115
116fn is_config_or_data(ext: &str, path: &str) -> bool {
117    if matches!(
118        ext,
119        "json" | "yaml" | "yml" | "toml" | "xml" | "ini" | "cfg" | "env" | "lock"
120    ) {
121        return true;
122    }
123    let name = std::path::Path::new(path)
124        .file_name()
125        .and_then(|n| n.to_str())
126        .unwrap_or("");
127    matches!(
128        name,
129        "Cargo.toml"
130            | "package.json"
131            | "tsconfig.json"
132            | "Makefile"
133            | "Dockerfile"
134            | "docker-compose.yml"
135            | ".gitignore"
136            | ".env"
137            | "pyproject.toml"
138            | "go.mod"
139            | "build.gradle"
140            | "pom.xml"
141    )
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_config_detection() {
150        assert!(is_config_or_data("json", "package.json"));
151        assert!(is_config_or_data("toml", "Cargo.toml"));
152        assert!(!is_config_or_data("rs", "main.rs"));
153    }
154
155    #[test]
156    fn test_code_detection() {
157        assert!(is_code("rs"));
158        assert!(is_code("py"));
159        assert!(is_code("tsx"));
160        assert!(!is_code("json"));
161    }
162}