Skip to main content

lean_ctx/tools/
ctx_read.rs

1use std::path::Path;
2
3use crate::core::cache::SessionCache;
4use crate::core::compressor;
5use crate::core::deps;
6use crate::core::entropy;
7use crate::core::protocol;
8use crate::core::signatures;
9use crate::core::symbol_map::{self, SymbolMap};
10use crate::core::tokens::count_tokens;
11use crate::tools::CrpMode;
12
13const COMPRESSED_HINT: &str = "[compressed — use mode=\"full\" for complete source]";
14
15fn append_compressed_hint(output: &str, file_path: &str) -> String {
16    format!("{output}\n{COMPRESSED_HINT}\n  ctx_read(\"{file_path}\", mode=\"full\")")
17}
18
19pub fn read_file_lossy(path: &str) -> Result<String, std::io::Error> {
20    let bytes = std::fs::read(path)?;
21    match String::from_utf8(bytes) {
22        Ok(s) => Ok(s),
23        Err(e) => Ok(String::from_utf8_lossy(e.as_bytes()).into_owned()),
24    }
25}
26
27pub fn handle(cache: &mut SessionCache, path: &str, mode: &str, crp_mode: CrpMode) -> String {
28    handle_with_options(cache, path, mode, false, crp_mode, None)
29}
30
31pub fn handle_fresh(cache: &mut SessionCache, path: &str, mode: &str, crp_mode: CrpMode) -> String {
32    handle_with_options(cache, path, mode, true, crp_mode, None)
33}
34
35pub fn handle_with_task(
36    cache: &mut SessionCache,
37    path: &str,
38    mode: &str,
39    crp_mode: CrpMode,
40    task: Option<&str>,
41) -> String {
42    handle_with_options(cache, path, mode, false, crp_mode, task)
43}
44
45pub fn handle_fresh_with_task(
46    cache: &mut SessionCache,
47    path: &str,
48    mode: &str,
49    crp_mode: CrpMode,
50    task: Option<&str>,
51) -> String {
52    handle_with_options(cache, path, mode, true, crp_mode, task)
53}
54
55fn handle_with_options(
56    cache: &mut SessionCache,
57    path: &str,
58    mode: &str,
59    fresh: bool,
60    crp_mode: CrpMode,
61    task: Option<&str>,
62) -> String {
63    let file_ref = cache.get_file_ref(path);
64    let short = protocol::shorten_path(path);
65    let ext = Path::new(path)
66        .extension()
67        .and_then(|e| e.to_str())
68        .unwrap_or("");
69
70    if fresh {
71        cache.invalidate(path);
72    }
73
74    if mode == "diff" {
75        return handle_diff(cache, path, &file_ref);
76    }
77
78    if let Some(existing) = cache.get(path) {
79        if mode == "full" {
80            return handle_full_with_auto_delta(cache, path, &file_ref, &short, ext, crp_mode);
81        }
82        let content = existing.content.clone();
83        let original_tokens = existing.original_tokens;
84        return process_mode(
85            &content,
86            mode,
87            &file_ref,
88            &short,
89            ext,
90            original_tokens,
91            crp_mode,
92            path,
93            task,
94        );
95    }
96
97    let content = match read_file_lossy(path) {
98        Ok(c) => c,
99        Err(e) => return format!("ERROR: {e}"),
100    };
101
102    let similar_hint = find_semantic_similar(path, &content);
103
104    let (entry, _is_hit) = cache.store(path, content.clone());
105
106    update_semantic_index(path, &content);
107
108    if mode == "full" {
109        let mut output =
110            format_full_output(cache, &file_ref, &short, ext, &content, &entry, crp_mode);
111        if let Some(hint) = similar_hint {
112            output.push_str(&format!("\n{hint}"));
113        }
114        return output;
115    }
116
117    let mut output = process_mode(
118        &content,
119        mode,
120        &file_ref,
121        &short,
122        ext,
123        entry.original_tokens,
124        crp_mode,
125        path,
126        task,
127    );
128    if let Some(hint) = similar_hint {
129        output.push_str(&format!("\n{hint}"));
130    }
131    output
132}
133
134fn find_semantic_similar(path: &str, content: &str) -> Option<String> {
135    let project_root = detect_project_root(path);
136    let index = crate::core::semantic_cache::SemanticCacheIndex::load(&project_root)?;
137
138    let similar = index.find_similar(content, 0.7);
139    let relevant: Vec<_> = similar
140        .into_iter()
141        .filter(|(p, _)| p != path)
142        .take(3)
143        .collect();
144
145    if relevant.is_empty() {
146        return None;
147    }
148
149    let hints: Vec<String> = relevant
150        .iter()
151        .map(|(p, score)| format!("  {p} ({:.0}% similar)", score * 100.0))
152        .collect();
153
154    Some(format!(
155        "[semantic: {} similar file(s) in cache]\n{}",
156        relevant.len(),
157        hints.join("\n")
158    ))
159}
160
161fn update_semantic_index(path: &str, content: &str) {
162    let project_root = detect_project_root(path);
163    let session_id = format!("{}", std::process::id());
164    let mut index = crate::core::semantic_cache::SemanticCacheIndex::load_or_create(&project_root);
165    index.add_file(path, content, &session_id);
166    let _ = index.save(&project_root);
167}
168
169fn detect_project_root(path: &str) -> String {
170    crate::core::protocol::detect_project_root_or_cwd(path)
171}
172
173const AUTO_DELTA_THRESHOLD: f64 = 0.6;
174
175/// Re-reads from disk; if content changed and delta is compact, sends auto-delta.
176fn handle_full_with_auto_delta(
177    cache: &mut SessionCache,
178    path: &str,
179    file_ref: &str,
180    short: &str,
181    ext: &str,
182    crp_mode: CrpMode,
183) -> String {
184    let disk_content = match read_file_lossy(path) {
185        Ok(c) => c,
186        Err(_) => {
187            cache.record_cache_hit(path);
188            let existing = cache.get(path).unwrap();
189            return format!(
190                "[using cached version — file read failed]\n{file_ref}={short} cached {}t {}L",
191                existing.read_count, existing.line_count
192            );
193        }
194    };
195
196    let old_content = cache.get(path).unwrap().content.clone();
197    let (entry, is_hit) = cache.store(path, disk_content.clone());
198
199    if is_hit {
200        return format!(
201            "{file_ref}={short} cached {}t {}L",
202            entry.read_count, entry.line_count
203        );
204    }
205
206    let diff = compressor::diff_content(&old_content, &disk_content);
207    let diff_tokens = count_tokens(&diff);
208    let full_tokens = entry.original_tokens;
209
210    if full_tokens > 0 && (diff_tokens as f64) < (full_tokens as f64 * AUTO_DELTA_THRESHOLD) {
211        let savings = protocol::format_savings(full_tokens, diff_tokens);
212        return format!(
213            "{file_ref}={short} [auto-delta] ∆{}L\n{diff}\n{savings}",
214            disk_content.lines().count()
215        );
216    }
217
218    format_full_output(cache, file_ref, short, ext, &disk_content, &entry, crp_mode)
219}
220
221fn format_full_output(
222    _cache: &mut SessionCache,
223    file_ref: &str,
224    short: &str,
225    ext: &str,
226    content: &str,
227    entry: &crate::core::cache::CacheEntry,
228    _crp_mode: CrpMode,
229) -> String {
230    let tokens = entry.original_tokens;
231    let metadata = build_header(file_ref, short, ext, content, entry.line_count, true);
232
233    let mut sym = SymbolMap::new();
234    let idents = symbol_map::extract_identifiers(content, ext);
235    for ident in &idents {
236        sym.register(ident);
237    }
238
239    let sym_beneficial = if sym.len() >= 3 {
240        let sym_table = sym.format_table();
241        let compressed = sym.apply(content);
242        let original_tok = count_tokens(content);
243        let compressed_tok = count_tokens(&compressed) + count_tokens(&sym_table);
244        let net_saving = original_tok.saturating_sub(compressed_tok);
245        original_tok > 0 && net_saving * 100 / original_tok >= 5
246    } else {
247        false
248    };
249
250    if sym_beneficial {
251        let compressed_content = sym.apply(content);
252        let sym_table = sym.format_table();
253        let output = format!("{compressed_content}{sym_table}\n{metadata}");
254        let sent = count_tokens(&output);
255        let savings = protocol::format_savings(tokens, sent);
256        return format!("{output}\n{savings}");
257    }
258
259    let output = format!("{content}\n{metadata}");
260    let sent = count_tokens(&output);
261    let savings = protocol::format_savings(tokens, sent);
262    format!("{output}\n{savings}")
263}
264
265fn build_header(
266    file_ref: &str,
267    short: &str,
268    ext: &str,
269    content: &str,
270    line_count: usize,
271    include_deps: bool,
272) -> String {
273    let mut header = format!("{file_ref}={short} {line_count}L");
274
275    if include_deps {
276        let dep_info = deps::extract_deps(content, ext);
277        if !dep_info.imports.is_empty() {
278            let imports_str: Vec<&str> = dep_info
279                .imports
280                .iter()
281                .take(8)
282                .map(|s| s.as_str())
283                .collect();
284            header.push_str(&format!("\n deps {}", imports_str.join(",")));
285        }
286        if !dep_info.exports.is_empty() {
287            let exports_str: Vec<&str> = dep_info
288                .exports
289                .iter()
290                .take(8)
291                .map(|s| s.as_str())
292                .collect();
293            header.push_str(&format!("\n exports {}", exports_str.join(",")));
294        }
295    }
296
297    header
298}
299
300#[allow(clippy::too_many_arguments)]
301fn process_mode(
302    content: &str,
303    mode: &str,
304    file_ref: &str,
305    short: &str,
306    ext: &str,
307    original_tokens: usize,
308    crp_mode: CrpMode,
309    file_path: &str,
310    task: Option<&str>,
311) -> String {
312    let line_count = content.lines().count();
313
314    match mode {
315        "auto" => {
316            let sig =
317                crate::core::mode_predictor::FileSignature::from_path(file_path, original_tokens);
318            let predictor = crate::core::mode_predictor::ModePredictor::new();
319            let resolved = predictor
320                .predict_best_mode(&sig)
321                .unwrap_or_else(|| "full".to_string());
322            process_mode(
323                content,
324                &resolved,
325                file_ref,
326                short,
327                ext,
328                original_tokens,
329                crp_mode,
330                file_path,
331                task,
332            )
333        }
334        "signatures" => {
335            let sigs = signatures::extract_signatures(content, ext);
336            let dep_info = deps::extract_deps(content, ext);
337
338            let mut output = format!("{file_ref}={short} {line_count}L");
339            if !dep_info.imports.is_empty() {
340                let imports_str: Vec<&str> = dep_info
341                    .imports
342                    .iter()
343                    .take(8)
344                    .map(|s| s.as_str())
345                    .collect();
346                output.push_str(&format!("\n deps {}", imports_str.join(",")));
347            }
348            for sig in &sigs {
349                output.push('\n');
350                if crp_mode.is_tdd() {
351                    output.push_str(&sig.to_tdd());
352                } else {
353                    output.push_str(&sig.to_compact());
354                }
355            }
356            let sent = count_tokens(&output);
357            let savings = protocol::format_savings(original_tokens, sent);
358            append_compressed_hint(&format!("{output}\n{savings}"), file_path)
359        }
360        "map" => {
361            if ext == "php" {
362                if let Some(php_map) = crate::core::patterns::php::compress_php_map(content, short)
363                {
364                    let mut output = format!("{file_ref}={short} {line_count}L\n{php_map}");
365                    let sent = count_tokens(&output);
366                    let savings = protocol::format_savings(original_tokens, sent);
367                    output.push('\n');
368                    output.push_str(&savings);
369                    return append_compressed_hint(&output, file_path);
370                }
371            }
372
373            let sigs = signatures::extract_signatures(content, ext);
374            let dep_info = deps::extract_deps(content, ext);
375
376            let mut output = format!("{file_ref}={short} {line_count}L");
377
378            if !dep_info.imports.is_empty() {
379                output.push_str("\n  deps: ");
380                output.push_str(&dep_info.imports.join(", "));
381            }
382
383            if !dep_info.exports.is_empty() {
384                output.push_str("\n  exports: ");
385                output.push_str(&dep_info.exports.join(", "));
386            }
387
388            let key_sigs: Vec<&signatures::Signature> = sigs
389                .iter()
390                .filter(|s| s.is_exported || s.indent == 0)
391                .collect();
392
393            if !key_sigs.is_empty() {
394                output.push_str("\n  API:");
395                for sig in &key_sigs {
396                    output.push_str("\n    ");
397                    if crp_mode.is_tdd() {
398                        output.push_str(&sig.to_tdd());
399                    } else {
400                        output.push_str(&sig.to_compact());
401                    }
402                }
403            }
404
405            let sent = count_tokens(&output);
406            let savings = protocol::format_savings(original_tokens, sent);
407            append_compressed_hint(&format!("{output}\n{savings}"), file_path)
408        }
409        "aggressive" => {
410            let raw = compressor::aggressive_compress(content, Some(ext));
411            let compressed = compressor::safeguard_ratio(content, &raw);
412            let header = build_header(file_ref, short, ext, content, line_count, true);
413
414            let mut sym = SymbolMap::new();
415            let idents = symbol_map::extract_identifiers(&compressed, ext);
416            for ident in &idents {
417                sym.register(ident);
418            }
419
420            let sym_beneficial = if sym.len() >= 3 {
421                let sym_table = sym.format_table();
422                let sym_applied = sym.apply(&compressed);
423                let orig_tok = count_tokens(&compressed);
424                let comp_tok = count_tokens(&sym_applied) + count_tokens(&sym_table);
425                let net = orig_tok.saturating_sub(comp_tok);
426                orig_tok > 0 && net * 100 / orig_tok >= 5
427            } else {
428                false
429            };
430
431            if sym_beneficial {
432                let sym_output = sym.apply(&compressed);
433                let sym_table = sym.format_table();
434                let sent = count_tokens(&sym_output) + count_tokens(&sym_table);
435                let savings = protocol::format_savings(original_tokens, sent);
436                return append_compressed_hint(
437                    &format!("{header}\n{sym_output}{sym_table}\n{savings}"),
438                    file_path,
439                );
440            }
441
442            let sent = count_tokens(&compressed);
443            let savings = protocol::format_savings(original_tokens, sent);
444            append_compressed_hint(&format!("{header}\n{compressed}\n{savings}"), file_path)
445        }
446        "entropy" => {
447            let result = entropy::entropy_compress_adaptive(content, file_path);
448            let avg_h = entropy::analyze_entropy(content).avg_entropy;
449            let header = build_header(file_ref, short, ext, content, line_count, false);
450            let techs = result.techniques.join(", ");
451            let output = format!("{header} H̄={avg_h:.1} [{techs}]\n{}", result.output);
452            let sent = count_tokens(&output);
453            let savings = protocol::format_savings(original_tokens, sent);
454            append_compressed_hint(&format!("{output}\n{savings}"), file_path)
455        }
456        "task" => {
457            let task_str = task.unwrap_or("");
458            if task_str.is_empty() {
459                let header = build_header(file_ref, short, ext, content, line_count, true);
460                return format!("{header}\n{content}\n[task mode: no task set — returned full]");
461            }
462            let (_files, keywords) = crate::core::task_relevance::parse_task_hints(task_str);
463            if keywords.is_empty() {
464                let header = build_header(file_ref, short, ext, content, line_count, true);
465                return format!(
466                    "{header}\n{content}\n[task mode: no keywords extracted — returned full]"
467                );
468            }
469            let filtered =
470                crate::core::task_relevance::information_bottleneck_filter(content, &keywords, 0.3);
471            let filtered_lines = filtered.lines().count();
472            let header = format!(
473                "{file_ref}={short} {line_count}L [task-filtered: {line_count}→{filtered_lines}]"
474            );
475            let sent = count_tokens(&filtered) + count_tokens(&header);
476            let savings = protocol::format_savings(original_tokens, sent);
477            append_compressed_hint(&format!("{header}\n{filtered}\n{savings}"), file_path)
478        }
479        "reference" => {
480            let tok = count_tokens(content);
481            let output = format!("{file_ref}={short}: {line_count} lines, {tok} tok ({ext})");
482            let sent = count_tokens(&output);
483            let savings = protocol::format_savings(original_tokens, sent);
484            format!("{output}\n{savings}")
485        }
486        mode if mode.starts_with("lines:") => {
487            let range_str = &mode[6..];
488            let extracted = extract_line_range(content, range_str);
489            let header = format!("{file_ref}={short} {line_count}L lines:{range_str}");
490            let sent = count_tokens(&extracted);
491            let savings = protocol::format_savings(original_tokens, sent);
492            format!("{header}\n{extracted}\n{savings}")
493        }
494        unknown => {
495            let header = build_header(file_ref, short, ext, content, line_count, true);
496            format!(
497                "[WARNING: unknown mode '{unknown}', falling back to full]\n{header}\n{content}"
498            )
499        }
500    }
501}
502
503fn extract_line_range(content: &str, range_str: &str) -> String {
504    let lines: Vec<&str> = content.lines().collect();
505    let total = lines.len();
506    let mut selected = Vec::new();
507
508    for part in range_str.split(',') {
509        let part = part.trim();
510        if let Some((start_s, end_s)) = part.split_once('-') {
511            let start = start_s.trim().parse::<usize>().unwrap_or(1).max(1);
512            let end = end_s.trim().parse::<usize>().unwrap_or(total).min(total);
513            for i in start..=end {
514                if i >= 1 && i <= total {
515                    selected.push(format!("{i:>4}| {}", lines[i - 1]));
516                }
517            }
518        } else if let Ok(n) = part.parse::<usize>() {
519            if n >= 1 && n <= total {
520                selected.push(format!("{n:>4}| {}", lines[n - 1]));
521            }
522        }
523    }
524
525    if selected.is_empty() {
526        "No lines matched the range.".to_string()
527    } else {
528        selected.join("\n")
529    }
530}
531
532fn handle_diff(cache: &mut SessionCache, path: &str, file_ref: &str) -> String {
533    let short = protocol::shorten_path(path);
534    let old_content = cache.get(path).map(|e| e.content.clone());
535
536    let new_content = match read_file_lossy(path) {
537        Ok(c) => c,
538        Err(e) => return format!("ERROR: {e}"),
539    };
540
541    let original_tokens = count_tokens(&new_content);
542
543    let diff_output = if let Some(old) = &old_content {
544        compressor::diff_content(old, &new_content)
545    } else {
546        format!("[first read]\n{new_content}")
547    };
548
549    cache.store(path, new_content);
550
551    let sent = count_tokens(&diff_output);
552    let savings = protocol::format_savings(original_tokens, sent);
553    format!("{file_ref}={short} [diff]\n{diff_output}\n{savings}")
554}
555
556#[cfg(test)]
557mod tests {
558    use super::*;
559
560    #[test]
561    fn test_header_toon_format_no_brackets() {
562        let content = "use std::io;\nfn main() {}\n";
563        let header = build_header("F1", "main.rs", "rs", content, 2, false);
564        assert!(!header.contains('['));
565        assert!(!header.contains(']'));
566        assert!(header.contains("F1=main.rs 2L"));
567    }
568
569    #[test]
570    fn test_header_toon_deps_indented() {
571        let content = "use crate::core::cache;\nuse crate::tools;\npub fn main() {}\n";
572        let header = build_header("F1", "main.rs", "rs", content, 3, true);
573        if header.contains("deps") {
574            assert!(
575                header.contains("\n deps "),
576                "deps should use indented TOON format"
577            );
578            assert!(
579                !header.contains("deps:["),
580                "deps should not use bracket format"
581            );
582        }
583    }
584
585    #[test]
586    fn test_header_toon_saves_tokens() {
587        let content = "use crate::foo;\nuse crate::bar;\npub fn baz() {}\npub fn qux() {}\n";
588        let old_header = "F1=main.rs [4L +] deps:[foo,bar] exports:[baz,qux]".to_string();
589        let new_header = build_header("F1", "main.rs", "rs", content, 4, true);
590        let old_tokens = count_tokens(&old_header);
591        let new_tokens = count_tokens(&new_header);
592        assert!(
593            new_tokens <= old_tokens,
594            "TOON header ({new_tokens} tok) should be <= old format ({old_tokens} tok)"
595        );
596    }
597
598    #[test]
599    fn test_tdd_symbols_are_compact() {
600        let symbols = [
601            "⊕", "⊖", "∆", "→", "⇒", "✓", "✗", "⚠", "λ", "§", "∂", "τ", "ε",
602        ];
603        for sym in &symbols {
604            let tok = count_tokens(sym);
605            assert!(tok <= 2, "Symbol {sym} should be 1-2 tokens, got {tok}");
606        }
607    }
608
609    #[test]
610    fn test_task_mode_filters_content() {
611        let content = (0..200)
612            .map(|i| {
613                if i % 20 == 0 {
614                    format!("fn validate_token(token: &str) -> bool {{ /* line {i} */ }}")
615                } else {
616                    format!("fn unrelated_helper_{i}(x: i32) -> i32 {{ x + {i} }}")
617                }
618            })
619            .collect::<Vec<_>>()
620            .join("\n");
621        let full_tokens = count_tokens(&content);
622        let task = Some("fix bug in validate_token");
623        let result = process_mode(
624            &content,
625            "task",
626            "F1",
627            "test.rs",
628            "rs",
629            full_tokens,
630            CrpMode::Off,
631            "test.rs",
632            task,
633        );
634        let result_tokens = count_tokens(&result);
635        assert!(
636            result_tokens < full_tokens,
637            "task mode ({result_tokens} tok) should be less than full ({full_tokens} tok)"
638        );
639        assert!(
640            result.contains("task-filtered"),
641            "output should contain task-filtered marker"
642        );
643    }
644
645    #[test]
646    fn test_task_mode_without_task_returns_full() {
647        let content = "fn main() {}\nfn helper() {}\n";
648        let tokens = count_tokens(content);
649        let result = process_mode(
650            content,
651            "task",
652            "F1",
653            "test.rs",
654            "rs",
655            tokens,
656            CrpMode::Off,
657            "test.rs",
658            None,
659        );
660        assert!(
661            result.contains("no task set"),
662            "should indicate no task: {result}"
663        );
664    }
665
666    #[test]
667    fn test_reference_mode_one_line() {
668        let content = "fn main() {}\nfn helper() {}\nfn other() {}\n";
669        let tokens = count_tokens(content);
670        let result = process_mode(
671            content,
672            "reference",
673            "F1",
674            "test.rs",
675            "rs",
676            tokens,
677            CrpMode::Off,
678            "test.rs",
679            None,
680        );
681        let lines: Vec<&str> = result.lines().collect();
682        assert!(
683            lines.len() <= 3,
684            "reference mode should be very compact, got {} lines",
685            lines.len()
686        );
687        assert!(result.contains("lines"), "should contain line count");
688        assert!(result.contains("tok"), "should contain token count");
689    }
690
691    #[test]
692    fn benchmark_task_conditioned_compression() {
693        let content = generate_benchmark_code(500);
694        let full_tokens = count_tokens(&content);
695        let task = Some("fix authentication in validate_token");
696
697        let full_output = process_mode(
698            &content,
699            "full",
700            "F1",
701            "server.rs",
702            "rs",
703            full_tokens,
704            CrpMode::Off,
705            "server.rs",
706            task,
707        );
708        let task_output = process_mode(
709            &content,
710            "task",
711            "F1",
712            "server.rs",
713            "rs",
714            full_tokens,
715            CrpMode::Off,
716            "server.rs",
717            task,
718        );
719        let sig_output = process_mode(
720            &content,
721            "signatures",
722            "F1",
723            "server.rs",
724            "rs",
725            full_tokens,
726            CrpMode::Off,
727            "server.rs",
728            task,
729        );
730        let ref_output = process_mode(
731            &content,
732            "reference",
733            "F1",
734            "server.rs",
735            "rs",
736            full_tokens,
737            CrpMode::Off,
738            "server.rs",
739            task,
740        );
741
742        let full_tok = count_tokens(&full_output);
743        let task_tok = count_tokens(&task_output);
744        let sig_tok = count_tokens(&sig_output);
745        let ref_tok = count_tokens(&ref_output);
746
747        eprintln!("\n=== Task-Conditioned Compression Benchmark ===");
748        eprintln!("Source: 500-line Rust file, task='fix authentication in validate_token'");
749        eprintln!("  full:       {full_tok:>6} tokens (baseline)");
750        eprintln!(
751            "  task:       {task_tok:>6} tokens ({:.0}% savings)",
752            (1.0 - task_tok as f64 / full_tok as f64) * 100.0
753        );
754        eprintln!(
755            "  signatures: {sig_tok:>6} tokens ({:.0}% savings)",
756            (1.0 - sig_tok as f64 / full_tok as f64) * 100.0
757        );
758        eprintln!(
759            "  reference:  {ref_tok:>6} tokens ({:.0}% savings)",
760            (1.0 - ref_tok as f64 / full_tok as f64) * 100.0
761        );
762        eprintln!("================================================\n");
763
764        assert!(task_tok < full_tok, "task mode should save tokens");
765        assert!(sig_tok < full_tok, "signatures should save tokens");
766        assert!(ref_tok < sig_tok, "reference should be most compact");
767    }
768
769    fn generate_benchmark_code(lines: usize) -> String {
770        let mut code = Vec::with_capacity(lines);
771        code.push("use std::collections::HashMap;".to_string());
772        code.push("use crate::core::auth;".to_string());
773        code.push(String::new());
774        code.push("pub struct Server {".to_string());
775        code.push("    config: Config,".to_string());
776        code.push("    cache: HashMap<String, String>,".to_string());
777        code.push("}".to_string());
778        code.push(String::new());
779        code.push("impl Server {".to_string());
780        code.push(
781            "    pub fn validate_token(&self, token: &str) -> Result<Claims, AuthError> {"
782                .to_string(),
783        );
784        code.push("        let decoded = auth::decode_jwt(token)?;".to_string());
785        code.push("        if decoded.exp < chrono::Utc::now().timestamp() {".to_string());
786        code.push("            return Err(AuthError::Expired);".to_string());
787        code.push("        }".to_string());
788        code.push("        Ok(decoded.claims)".to_string());
789        code.push("    }".to_string());
790        code.push(String::new());
791
792        let remaining = lines.saturating_sub(code.len());
793        for i in 0..remaining {
794            if i % 30 == 0 {
795                code.push(format!(
796                    "    pub fn handler_{i}(&self, req: Request) -> Response {{"
797                ));
798            } else if i % 30 == 29 {
799                code.push("    }".to_string());
800            } else {
801                code.push(format!("        let val_{i} = self.cache.get(\"key_{i}\").unwrap_or(&\"default\".to_string());"));
802            }
803        }
804        code.push("}".to_string());
805        code.join("\n")
806    }
807}