Skip to main content

lean_ctx/tools/
ctx_context.rs

1use 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}