lean_ctx/tools/
ctx_heatmap.rs1use crate::core::gain::GainEngine;
2use crate::core::heatmap;
3
4pub fn handle(action: &str, _path: Option<&str>) -> String {
5 let engine = GainEngine::load();
6
7 match action {
8 "status" => heatmap::format_heatmap_status(&engine.heatmap, 20),
9 "directory" | "dirs" => heatmap::format_directory_summary(&engine.heatmap),
10 "cold" => {
11 let all = collect_project_files(_path);
12 let cold = engine.heatmap.cold_files(&all, 20);
13 if cold.is_empty() {
14 "No cold files found (all files have been accessed).".to_string()
15 } else {
16 let mut lines = vec![format!(
17 "Cold files (never accessed, {} total):",
18 cold.len()
19 )];
20 for f in &cold {
21 lines.push(format!(" {f}"));
22 }
23 lines.join("\n")
24 }
25 }
26 "json" => {
27 serde_json::to_string_pretty(&engine.heatmap).unwrap_or_else(|_| "{}".to_string())
28 }
29 _ => heatmap::format_heatmap_status(&engine.heatmap, 20),
30 }
31}
32
33fn collect_project_files(path: Option<&str>) -> Vec<String> {
34 let root = path.unwrap_or(".");
35 let mut files = Vec::new();
36 let walker = walkdir::WalkDir::new(root)
37 .max_depth(5)
38 .into_iter()
39 .filter_entry(|e| {
40 let name = e.file_name().to_string_lossy();
41 !name.starts_with('.')
42 && name != "node_modules"
43 && name != "target"
44 && name != "dist"
45 && name != "__pycache__"
46 && name != ".git"
47 });
48 for entry in walker.flatten() {
49 if entry.file_type().is_file() {
50 if let Some(ext) = entry.path().extension().and_then(|e| e.to_str()) {
51 if is_source_ext(ext) {
52 files.push(entry.path().to_string_lossy().to_string());
53 }
54 }
55 }
56 }
57 files
58}
59
60fn is_source_ext(ext: &str) -> bool {
61 matches!(
62 ext,
63 "rs" | "ts"
64 | "tsx"
65 | "js"
66 | "jsx"
67 | "py"
68 | "go"
69 | "java"
70 | "c"
71 | "cpp"
72 | "h"
73 | "rb"
74 | "cs"
75 | "kt"
76 | "swift"
77 | "php"
78 | "svelte"
79 | "vue"
80 )
81}