lean_ctx/tools/
ctx_smart_read.rs1use 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 Ok(content) = std::fs::read_to_string(path) else {
12 return "full".to_string();
13 };
14
15 let token_count = count_tokens(&content);
16 let ext = std::path::Path::new(path)
17 .extension()
18 .and_then(|e| e.to_str())
19 .unwrap_or("");
20
21 if let Some(cached) = cache.get(path) {
22 if cached.hash == compute_hash(&content) {
23 return "full".to_string();
24 }
25 return "diff".to_string();
26 }
27
28 if token_count <= 200 {
29 return "full".to_string();
30 }
31
32 if is_config_or_data(ext, path) {
33 return "full".to_string();
34 }
35
36 if let Some(recommended) = intent_recommended_mode(task) {
40 return recommended;
41 }
42
43 let sig = FileSignature::from_path(path, token_count);
44 let predictor = ModePredictor::new();
45 if let Some(predicted) = predictor.predict_best_mode(&sig) {
46 return predicted;
47 }
48
49 heuristic_mode(ext, token_count)
50}
51
52fn intent_recommended_mode(task: Option<&str>) -> Option<String> {
56 let task_desc = task?;
57 let classification = crate::core::intent_engine::classify(task_desc);
58 if classification.confidence < 0.4 {
59 return None;
60 }
61 let route = crate::core::intent_engine::route_intent(task_desc, &classification);
62 let mode =
63 crate::core::intent_router::read_mode_for_tier(route.model_tier, classification.task_type);
64 if mode == "auto" {
65 return None;
66 }
67 Some(mode)
68}
69
70fn heuristic_mode(ext: &str, token_count: usize) -> String {
71 if token_count > 8000 {
72 if is_code(ext) {
73 return "map".to_string();
74 }
75 return "aggressive".to_string();
76 }
77 if token_count > 3000 && is_code(ext) {
78 return "map".to_string();
79 }
80 "full".to_string()
81}
82
83pub fn handle(cache: &mut SessionCache, path: &str, crp_mode: CrpMode) -> String {
84 let mode = select_mode(cache, path);
85 let result = crate::tools::ctx_read::handle(cache, path, &mode, crp_mode);
86 format!("[auto:{mode}] {result}")
87}
88
89fn compute_hash(content: &str) -> String {
90 use md5::{Digest, Md5};
91 let mut hasher = Md5::new();
92 hasher.update(content.as_bytes());
93 format!("{:x}", hasher.finalize())
94}
95
96pub fn is_code_ext(ext: &str) -> bool {
97 is_code(ext)
98}
99
100fn is_code(ext: &str) -> bool {
101 matches!(
102 ext,
103 "rs" | "ts"
104 | "tsx"
105 | "js"
106 | "jsx"
107 | "py"
108 | "go"
109 | "java"
110 | "c"
111 | "cpp"
112 | "cc"
113 | "h"
114 | "hpp"
115 | "rb"
116 | "cs"
117 | "kt"
118 | "swift"
119 | "php"
120 | "zig"
121 | "ex"
122 | "exs"
123 | "scala"
124 | "sc"
125 | "dart"
126 | "sh"
127 | "bash"
128 | "svelte"
129 | "vue"
130 )
131}
132
133fn is_config_or_data(ext: &str, path: &str) -> bool {
134 if matches!(
135 ext,
136 "json" | "yaml" | "yml" | "toml" | "xml" | "ini" | "cfg" | "env" | "lock"
137 ) {
138 return true;
139 }
140 let name = std::path::Path::new(path)
141 .file_name()
142 .and_then(|n| n.to_str())
143 .unwrap_or("");
144 matches!(
145 name,
146 "Cargo.toml"
147 | "package.json"
148 | "tsconfig.json"
149 | "Makefile"
150 | "Dockerfile"
151 | "docker-compose.yml"
152 | ".gitignore"
153 | ".env"
154 | "pyproject.toml"
155 | "go.mod"
156 | "build.gradle"
157 | "pom.xml"
158 )
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn test_config_detection() {
167 assert!(is_config_or_data("json", "package.json"));
168 assert!(is_config_or_data("toml", "Cargo.toml"));
169 assert!(!is_config_or_data("rs", "main.rs"));
170 }
171
172 #[test]
173 fn test_code_detection() {
174 assert!(is_code("rs"));
175 assert!(is_code("py"));
176 assert!(is_code("tsx"));
177 assert!(!is_code("json"));
178 }
179
180 #[test]
181 fn intent_mode_for_explore_task() {
182 let mode = intent_recommended_mode(Some("how does the session cache work?"));
183 assert_eq!(mode, Some("map".to_string()));
184 }
185
186 #[test]
187 fn intent_mode_for_fix_task() {
188 let mode = intent_recommended_mode(Some("fix the bug in auth.rs"));
189 assert_eq!(mode, Some("full".to_string()));
190 }
191
192 #[test]
193 fn intent_mode_none_without_task() {
194 assert_eq!(intent_recommended_mode(None), None);
195 }
196
197 #[test]
198 fn intent_mode_none_for_low_confidence() {
199 let mode = intent_recommended_mode(Some("xyz qqq"));
200 assert_eq!(mode, None);
201 }
202}