lean_ctx/tools/
ctx_context.rs1use crate::core::cache::SessionCache;
2use crate::tools::CrpMode;
3
4pub fn handle_status(cache: &SessionCache, turn_count: usize, crp_mode: CrpMode) -> String {
5 let entries = cache.get_all_entries();
6 let mut result = Vec::new();
7
8 result.push(format!("Multi-turn context (turn {turn_count}):"));
9 result.push(format!(" Cached files: {}", entries.len()));
10
11 let total_tokens: usize = entries.iter().map(|(_, e)| e.original_tokens).sum();
12 let total_reads: u32 = entries.iter().map(|(_, e)| e.read_count).sum();
13 result.push(format!(" Total original tokens: {total_tokens}"));
14 result.push(format!(" Total reads: {total_reads}"));
15
16 let frequent: Vec<_> = entries.iter().filter(|(_, e)| e.read_count > 1).collect();
17 if !frequent.is_empty() {
18 result.push(format!("\n Frequently accessed ({}):", frequent.len()));
19 for (path, entry) in &frequent {
20 result.push(format!(
21 " {} ({}x, {} tok)",
22 crate::core::protocol::shorten_path(path),
23 entry.read_count,
24 entry.original_tokens
25 ));
26 }
27 }
28
29 let mode_label = match crp_mode {
30 CrpMode::Off => "off",
31 CrpMode::Compact => "compact",
32 CrpMode::Tdd => "tdd",
33 };
34 result.push(format!("\n CRP mode: {mode_label}"));
35
36 let complexity = crate::core::adaptive::classify_from_context(cache);
37 result.push(format!("\n {}", complexity.encoded_suffix()));
38
39 let hints = generate_prefill_hints(cache);
40 if !hints.is_empty() {
41 result.push("\nSMART HINTS:".to_string());
42 for hint in &hints {
43 result.push(format!(" → {hint}"));
44 }
45 }
46
47 result.join("\n")
48}
49
50fn generate_prefill_hints(cache: &SessionCache) -> Vec<String> {
51 let entries = cache.get_all_entries();
52 let mut hints = Vec::new();
53
54 let read_heavy: Vec<_> = entries
55 .iter()
56 .filter(|(_, e)| e.read_count >= 3 && e.original_tokens > 500)
57 .collect();
58 for (path, entry) in &read_heavy {
59 let short = crate::core::protocol::shorten_path(path);
60 hints.push(format!(
61 "{short} read {}x ({} tok) — consider mode=map for future reads",
62 entry.read_count, entry.original_tokens
63 ));
64 }
65
66 let large: Vec<_> = entries
67 .iter()
68 .filter(|(_, e)| e.original_tokens > 2000 && e.read_count <= 1)
69 .collect();
70 for (path, entry) in &large {
71 let short = crate::core::protocol::shorten_path(path);
72 hints.push(format!(
73 "{short} is large ({} tok) — consider mode=signatures or aggressive",
74 entry.original_tokens
75 ));
76 }
77
78 let stale_count = entries.iter().filter(|(_, e)| e.read_count == 1).count();
79 if stale_count > 5 {
80 hints.push(format!(
81 "{stale_count} files read only once — ctx_cache clear to free context"
82 ));
83 }
84
85 hints.truncate(5);
86 hints
87}