use lean_ctx::core::auto_mode_resolver::{self, AutoModeContext};
use lean_ctx::core::cache::SessionCache;
use lean_ctx::core::tokens::count_tokens;
pub fn select_mode(cache: &SessionCache, path: &str) -> String {
select_mode_with_task(cache, path, None)
}
pub fn select_mode_with_task(cache: &SessionCache, path: &str, task: Option<&str>) -> String {
if let Ok(meta) = std::fs::metadata(path) {
let cap = lean_ctx::core::limits::max_read_bytes() as u64;
if meta.len() > cap {
return "full".to_string();
}
}
let token_count = cache
.get(path)
.filter(|e| !lean_ctx::core::cache::is_cache_entry_stale(path, e.stored_mtime))
.map(|e| e.original_tokens)
.filter(|t| *t > 0)
.unwrap_or_else(|| {
crate::tools::ctx_read::read_file_lossy(path).map_or(0, |c| count_tokens(&c))
});
let ctx = AutoModeContext {
path,
token_count,
line_count: None,
task,
cache: Some(cache),
};
auto_mode_resolver::resolve(&ctx).mode
}
pub fn select_unranged_mode(cache: &SessionCache, path: &str, task: Option<&str>) -> String {
if std::fs::metadata(path)
.is_ok_and(|meta| meta.len() > lean_ctx::core::limits::max_read_bytes() as u64)
{
return "full".to_string();
}
let (token_count, line_count) = cache
.get(path)
.filter(|e| !lean_ctx::core::cache::is_cache_entry_stale(path, e.stored_mtime))
.filter(|e| e.original_tokens > 0)
.map(|e| (e.original_tokens, e.line_count))
.unwrap_or_else(|| {
crate::tools::ctx_read::read_file_lossy(path)
.map_or((0, 0), |c| (count_tokens(&c), c.lines().count()))
});
let ctx = AutoModeContext {
path,
token_count,
line_count: Some(line_count),
task,
cache: Some(cache),
};
auto_mode_resolver::unranged_view(auto_mode_resolver::resolve(&ctx), path, line_count).mode
}
pub fn is_code_ext(ext: &str) -> bool {
matches!(
ext,
"rs" | "ts"
| "tsx"
| "js"
| "jsx"
| "py"
| "go"
| "java"
| "c"
| "cpp"
| "cc"
| "h"
| "hpp"
| "rb"
| "cs"
| "kt"
| "swift"
| "php"
| "zig"
| "ex"
| "exs"
| "scala"
| "sc"
| "dart"
| "sh"
| "bash"
| "svelte"
| "vue"
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_code_detection() {
assert!(is_code_ext("rs"));
assert!(is_code_ext("py"));
assert!(is_code_ext("tsx"));
assert!(!is_code_ext("json"));
}
#[test]
fn a_withheld_source_does_not_steer_the_mode() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let policy = lean_ctx::core::policy::load(
"name = \"ctx-smart-read-test\"\nversion = \"1.0.0\"\ndescription = \"test\"\n\
[filters]\nclassification = \"block\"\nblocked_labels = [\"CONFIDENTIAL\"]\n",
)
.unwrap();
let _policy = lean_ctx::core::policy::runtime::TestPolicyOverride::set(Some(policy));
let project = tempfile::tempdir().unwrap();
let root = project.path().to_string_lossy().into_owned();
let body = "pub fn generated_handler(input: &str) -> usize { input.len() }\n".repeat(4000);
let blocked = project.path().join("blocked.rs");
let open = project.path().join("open.rs");
std::fs::write(&blocked, format!("CONFIDENTIAL\n{body}")).unwrap();
std::fs::write(&open, &body).unwrap();
let missing = project.path().join("missing.rs");
let cache = SessionCache::new();
let mode = |path: &std::path::Path| {
crate::tools::admitted_sources::as_agent_acquisition(&root, || {
select_mode(&cache, &path.to_string_lossy())
})
};
assert_eq!(mode(&blocked), mode(&missing));
let counted = crate::tools::admitted_sources::as_agent_acquisition(&root, || {
crate::tools::ctx_read::read_file_lossy(&open.to_string_lossy())
.map_or(0, |text| count_tokens(&text))
});
assert!(counted > 10_000, "{counted}");
}
}