lean_ctx/core/verbosity/
recommender.rs1use super::signals::BehaviorSignal;
7use crate::core::config::CompressionLevel;
8
9#[derive(Debug, Clone)]
11pub struct VerbosityProfile {
12 pub level: CompressionLevel,
14 pub confidence: f64,
16 pub reason: String,
18}
19
20pub(crate) fn recommend_level(signals: &[BehaviorSignal]) -> VerbosityProfile {
25 let mut priors: [(f64, f64); 5] = [(1.0, 1.0), (3.0, 1.0), (2.0, 1.0), (1.5, 1.0), (1.0, 1.5)];
26
27 for signal in signals {
28 match signal {
29 BehaviorSignal::ReRead { .. } => {
30 priors[0].0 += 1.0;
31 priors[2].1 += 0.5;
32 priors[3].1 += 1.0;
33 priors[4].1 += 1.5;
34 }
35 BehaviorSignal::ModeSwitch { from, to } => {
36 if let Some(index) = level_index(from) {
37 priors[index].1 += 0.5;
38 }
39 if let Some(index) = level_index(to) {
40 priors[index].0 += 0.5;
41 }
42 }
43 BehaviorSignal::FullContentRequest { .. } => {
44 priors[0].0 += 1.5;
45 priors[2].1 += 0.5;
46 priors[3].1 += 1.0;
47 priors[4].1 += 1.0;
48 }
49 BehaviorSignal::TaskComplete { reads_count } => {
50 if *reads_count <= 3 {
51 priors[2].0 += 1.0;
52 priors[3].0 += 0.5;
53 } else {
54 priors[1].0 += 0.25;
55 }
56 }
57 BehaviorSignal::ExpandFollowUp { .. } => {
58 priors[0].0 += 0.5;
59 priors[2].1 += 0.5;
60 priors[3].1 += 1.0;
61 }
62 }
63 }
64
65 let mut best_idx = 1;
66 let mut best_score = 0.0;
67 for (index, (alpha, beta)) in priors.iter().enumerate() {
68 let score = alpha / (alpha + beta);
69 if score >= best_score {
70 best_score = score;
71 best_idx = index;
72 }
73 }
74
75 let level = match best_idx {
76 0 => CompressionLevel::Off,
77 2 => CompressionLevel::Standard,
78 3 => CompressionLevel::Max,
79 4 => CompressionLevel::Raw,
80 _ => CompressionLevel::Lite, };
82
83 VerbosityProfile {
84 level,
85 confidence: best_score.clamp(0.0, 1.0),
86 reason: format!("Based on {} behavioral signals", signals.len()),
87 }
88}
89
90fn level_index(level: &str) -> Option<usize> {
91 match level.to_ascii_lowercase().as_str() {
92 "off" | "full" => Some(0),
93 "lite" => Some(1),
94 "standard" => Some(2),
95 "max" => Some(3),
96 "raw" => Some(4),
97 _ => None,
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn no_signals_recommends_lite() {
107 assert_eq!(recommend_level(&[]).level, CompressionLevel::Lite);
108 }
109
110 #[test]
111 fn many_rereads_recommends_less_compression() {
112 let signals: Vec<_> = (0..4)
113 .map(|_| BehaviorSignal::ReRead {
114 path: "a.rs".to_owned(),
115 gap_seconds: 1.0,
116 })
117 .collect();
118 assert_eq!(recommend_level(&signals).level, CompressionLevel::Off);
119 }
120
121 #[test]
122 fn task_complete_boosts_current_level() {
123 let profile = recommend_level(&[BehaviorSignal::TaskComplete { reads_count: 2 }]);
124 assert_eq!(profile.level, CompressionLevel::Standard);
125 }
126
127 #[test]
128 fn confidence_between_zero_and_one() {
129 let confidence = recommend_level(&[]).confidence;
130 assert!((0.0..=1.0).contains(&confidence));
131 }
132}