lean_ctx/core/
cognitive_load.rs1#[derive(Debug, Clone, PartialEq)]
5pub struct CognitiveLoadScore {
6 pub intrinsic: f64,
7 pub extraneous: f64,
8 pub germane: f64,
9 pub total: f64,
10 pub recommendation: String,
11}
12
13fn max_brace_depth(s: &str) -> usize {
14 let mut d = 0usize;
15 let mut maxd = 0usize;
16 let mut line_comment = false;
17 let mut block = 0usize;
18 let chars: Vec<char> = s.chars().collect();
19 let mut i = 0;
20 while i < chars.len() {
21 let c = chars[i];
22 if line_comment {
23 if c == '\n' {
24 line_comment = false;
25 }
26 i += 1;
27 continue;
28 }
29 if block > 0 {
30 if c == '/' && i + 1 < chars.len() && chars[i + 1] == '*' {
31 block += 1;
32 i += 2;
33 continue;
34 }
35 if c == '*' && i + 1 < chars.len() && chars[i + 1] == '/' {
36 block = block.saturating_sub(1);
37 i += 2;
38 continue;
39 }
40 i += 1;
41 continue;
42 }
43 if c == '/' && i + 1 < chars.len() {
44 if chars[i + 1] == '/' {
45 line_comment = true;
46 i += 2;
47 continue;
48 }
49 if chars[i + 1] == '*' {
50 block += 1;
51 i += 2;
52 continue;
53 }
54 }
55 match c {
56 '{' | '(' | '[' => {
57 d += 1;
58 maxd = maxd.max(d);
59 }
60 '}' | ')' | ']' => {
61 d = d.saturating_sub(1);
62 }
63 _ => {}
64 }
65 i += 1;
66 }
67 maxd
68}
69
70fn intrinsic_raw(content: &str) -> f64 {
71 let depth = max_brace_depth(content) as f64;
72 let lets = content.matches("let ").count() as f64;
73 let ctrl = ["for ", "while ", "match ", "if ", "else", "loop {"]
74 .iter()
75 .map(|k| content.matches(*k).count() as f64)
76 .sum::<f64>();
77 depth * 0.22 + lets * 0.06 + ctrl * 0.05
78}
79
80fn extraneous_raw(content: &str) -> f64 {
81 let lines: Vec<&str> = content.lines().collect();
82 let n = lines.len().max(1) as f64;
83 let blank = lines.iter().filter(|l| l.trim().is_empty()).count() as f64 / n;
84 let mut comment_lines = 0usize;
85 let mut block = false;
86 for line in &lines {
87 let t = line.trim_start();
88 if block {
89 comment_lines += 1;
90 if t.contains("*/") {
91 block = false;
92 }
93 continue;
94 }
95 if t.starts_with("//") {
96 comment_lines += 1;
97 } else if t.starts_with("/*") {
98 comment_lines += 1;
99 block = !t.contains("*/");
100 }
101 }
102 let comment_ratio = comment_lines as f64 / n;
103 let boiler = [
104 "todo!",
105 "unwrap()",
106 "expect(",
107 "derive(Default)",
108 "println!",
109 "#[",
110 ]
111 .iter()
112 .map(|p| content.matches(*p).count() as f64)
113 .sum::<f64>();
114 comment_ratio * 1.1 + blank * 0.9 + boiler * 0.08
115}
116
117fn camel_type_tokens(content: &str) -> usize {
118 content
119 .split(|c: char| !c.is_ascii_alphanumeric() && c != '_')
120 .filter(|tok| tok.len() >= 2 && tok.chars().next().is_some_and(|c| c.is_ascii_uppercase()))
121 .count()
122}
123
124fn germane_raw(content: &str) -> f64 {
125 let n_types = camel_type_tokens(content);
126 let apiish = content.matches('.').count() as f64 * 0.02;
127 let algo = [
128 "sort",
129 "binary",
130 "hash",
131 "graph",
132 "dfs",
133 "bfs",
134 "heap",
135 "recursive",
136 ]
137 .iter()
138 .map(|k| content.to_ascii_lowercase().matches(k).count() as f64)
139 .sum::<f64>();
140 n_types as f64 * 0.09 + apiish + algo * 0.11
141}
142
143fn norm(x: f64) -> f64 {
144 (x / (x + 1.2)).min(1.0)
145}
146
147fn recommend(intr: f64, extr: f64, germ: f64) -> String {
148 if extr >= intr * 1.35 && extr >= 0.28 {
149 return "entropy or aggressive — dominant extraneous noise".to_string();
150 }
151 if intr >= germ * 1.2 && intr >= 0.32 {
152 return "signatures or map — high intrinsic complexity".to_string();
153 }
154 if germ >= intr * 1.05 && germ >= 0.28 {
155 return "full or reference — strong germane / API signal".to_string();
156 }
157 if extr >= 0.22 && extr >= intr {
158 return "aggressive — moderate clutter".to_string();
159 }
160 "auto — balanced load profile".to_string()
161}
162
163pub fn score_cognitive_load(content: &str) -> CognitiveLoadScore {
165 let i = norm(intrinsic_raw(content));
166 let e = norm(extraneous_raw(content));
167 let g = norm(germane_raw(content));
168 let total = i + e + g;
169 let recommendation = recommend(i, e, g);
170 CognitiveLoadScore {
171 intrinsic: i,
172 extraneous: e,
173 germane: g,
174 total,
175 recommendation,
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn simple_script_low_scores() {
185 let s = score_cognitive_load("fn main() {}\n");
186 assert!(s.total < 2.5);
187 assert!(s.intrinsic > 0.0);
188 }
189
190 #[test]
191 fn nested_logic_raises_intrinsic() {
192 let code = r"
193fn x() {
194 if true {
195 for _ in 0..10 {
196 while false {}
197 }
198 }
199}
200";
201 let s = score_cognitive_load(code);
202 assert!(s.intrinsic > score_cognitive_load("fn y() {}").intrinsic);
203 }
204
205 #[test]
206 fn comments_and_blank_lines_raise_extraneous() {
207 let noisy = "// head\n\n// more\nfn z() {}\n";
208 let plain = "fn z() {}\n";
209 assert!(score_cognitive_load(noisy).extraneous > score_cognitive_load(plain).extraneous);
210 }
211
212 #[test]
213 fn types_and_algo_boost_germane() {
214 let api = "fn k(a: HashMap<String, Vec<MyDto>>) { a.sort(); }\n";
215 let s = score_cognitive_load(api);
216 assert!(s.germane > 0.15);
217 }
218
219 #[test]
220 fn recommendation_prefers_entropy_on_noise() {
221 let wall = "// ...\n".repeat(40);
222 let s = score_cognitive_load(&(wall + "\nfn q() {}\n"));
223 assert!(
224 s.recommendation.contains("entropy") || s.recommendation.contains("aggressive"),
225 "{}",
226 s.recommendation
227 );
228 }
229}