lean_ctx/core/
tool_lifecycle.rs1use crate::core::context_ledger::ContextLedger;
12use crate::core::heatmap;
13use crate::core::intent_engine::StructuredIntent;
14use crate::core::session::SessionState;
15use crate::core::stats;
16
17pub fn record_file_read(
19 path: &str,
20 mode: &str,
21 original_tokens: usize,
22 output_tokens: usize,
23 is_cache_hit: bool,
24) {
25 let saved = original_tokens.saturating_sub(output_tokens);
26 let tool_key = format!("cli_{mode}");
27
28 stats::record(&tool_key, original_tokens, output_tokens);
29 heatmap::record_file_access(path, original_tokens, saved);
30
31 if let Some(mut session) = SessionState::load_latest() {
32 session.touch_file(path, None, mode, original_tokens);
33 if is_cache_hit {
34 session.record_cache_hit();
35 }
36
37 if session.active_structured_intent.is_none() && session.files_touched.len() >= 2 {
38 let touched: Vec<String> = session
39 .files_touched
40 .iter()
41 .map(|ft| ft.path.clone())
42 .collect();
43 let inferred = StructuredIntent::from_file_patterns(&touched);
44 if inferred.confidence >= 0.4 {
45 session.active_structured_intent = Some(inferred);
46 }
47 }
48
49 let project_root = session.project_root.clone();
50 let calls = session.stats.total_tool_calls;
51 let _ = session.save();
52
53 maybe_consolidate(project_root.as_deref(), calls);
54 }
55
56 let mut ledger = ContextLedger::load();
57 ledger.record(path, mode, original_tokens, output_tokens);
58 ledger.save();
59}
60
61pub fn record_search(original_tokens: usize, output_tokens: usize) {
63 stats::record("cli_grep", original_tokens, output_tokens);
64
65 if let Some(mut session) = SessionState::load_latest() {
66 session.record_command();
67 let project_root = session.project_root.clone();
68 let calls = session.stats.total_tool_calls;
69 let _ = session.save();
70
71 maybe_consolidate(project_root.as_deref(), calls);
72 }
73}
74
75pub fn record_tree(original_tokens: usize, output_tokens: usize) {
77 stats::record("cli_ls", original_tokens, output_tokens);
78
79 if let Some(mut session) = SessionState::load_latest() {
80 session.record_command();
81 let _ = session.save();
82 }
83}
84
85pub fn record_shell_command(original_tokens: usize, output_tokens: usize) {
89 if original_tokens > 0 || output_tokens > 0 {
90 stats::record("cli_shell", original_tokens, output_tokens);
91 }
92
93 if let Some(mut session) = SessionState::load_latest() {
94 session.record_command();
95 let project_root = session.project_root.clone();
96 let calls = session.stats.total_tool_calls;
97 let _ = session.save();
98
99 if original_tokens > 0 {
100 maybe_consolidate(project_root.as_deref(), calls);
101 }
102 }
103}
104
105fn maybe_consolidate(project_root: Option<&str>, calls: u32) {
106 let Some(root) = project_root else { return };
107 let autonomy = crate::tools::autonomy::AutonomyState::new();
108 if crate::tools::autonomy::should_auto_consolidate(&autonomy, calls) {
109 let root = root.to_string();
110 let _ = crate::core::consolidation_engine::consolidate_latest(
111 &root,
112 crate::core::consolidation_engine::ConsolidationBudgets::default(),
113 );
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn record_file_read_does_not_panic_without_session() {
123 record_file_read("/tmp/nonexistent.rs", "full", 100, 50, false);
124 }
125
126 #[test]
127 fn record_search_does_not_panic_without_session() {
128 record_search(200, 150);
129 }
130
131 #[test]
132 fn record_tree_does_not_panic_without_session() {
133 record_tree(100, 80);
134 }
135
136 #[test]
137 fn record_shell_does_not_panic_without_session() {
138 record_shell_command(500, 200);
139 }
140}