Skip to main content

lean_ctx_server/tools/
ctx_response.rs

1// SPDX-License-Identifier: Apache-2.0
2use crate::tools::CrpMode;
3use lean_ctx::core::tokens::count_tokens;
4
5pub fn handle(response: &str, crp_mode: CrpMode) -> String {
6    handle_with_context(response, crp_mode, None)
7}
8
9pub fn handle_with_context(
10    response: &str,
11    crp_mode: CrpMode,
12    input_context: Option<&str>,
13) -> String {
14    let original_tokens = count_tokens(response);
15
16    if original_tokens <= 100 {
17        return response.to_string();
18    }
19
20    let compressed = if crp_mode.is_tdd() {
21        compress_tdd(response, input_context)
22    } else {
23        compress_standard(response, input_context)
24    };
25
26    let compressed_tokens = count_tokens(&compressed);
27    let savings = original_tokens.saturating_sub(compressed_tokens);
28    let pct = if original_tokens > 0 {
29        (savings as f64 / original_tokens as f64 * 100.0).round() as usize
30    } else {
31        0
32    };
33
34    if pct < 3 {
35        return response.to_string();
36    }
37
38    lean_ctx::core::protocol::append_savings(&compressed, original_tokens, compressed_tokens)
39}
40
41fn compress_standard(text: &str, input_context: Option<&str>) -> String {
42    let echo_lines = input_context.map(build_echo_set);
43
44    let mut result = Vec::new();
45    let mut prev_empty = false;
46
47    for line in text.lines() {
48        let trimmed = line.trim();
49
50        if trimmed.is_empty() {
51            if !prev_empty {
52                result.push(String::new());
53                prev_empty = true;
54            }
55            continue;
56        }
57        prev_empty = false;
58
59        if is_filler_line(trimmed) {
60            continue;
61        }
62        if is_boilerplate_code(trimmed) {
63            continue;
64        }
65        if let Some(ref echoes) = echo_lines
66            && is_context_echo(trimmed, echoes)
67        {
68            continue;
69        }
70
71        result.push(line.to_string());
72    }
73
74    strip_edge_pleasantries(&mut result);
75    result.join("\n")
76}
77
78fn compress_tdd(text: &str, input_context: Option<&str>) -> String {
79    let echo_lines = input_context.map(build_echo_set);
80
81    let mut result = Vec::new();
82    let mut in_fence = false;
83
84    for line in text.lines() {
85        let trimmed = line.trim();
86
87        if trimmed.starts_with("```") || trimmed.starts_with("~~~") {
88            in_fence = !in_fence;
89            result.push(line.to_string());
90            continue;
91        }
92        // Code blocks are content, never prose: verbatim, indentation kept.
93        if in_fence {
94            if !(trimmed.is_empty() && result.last().is_some_and(String::is_empty)) {
95                result.push(line.to_string());
96            }
97            continue;
98        }
99
100        if trimmed.is_empty() {
101            continue;
102        }
103
104        if is_filler_line(trimmed) {
105            continue;
106        }
107        if is_boilerplate_code(trimmed) {
108            continue;
109        }
110        if let Some(ref echoes) = echo_lines
111            && is_context_echo(trimmed, echoes)
112        {
113            continue;
114        }
115
116        // A rewrite that does not save tokens in context is pure readability
117        // loss, so each line keeps whichever form is cheaper (#1892).
118        let compressed = apply_tdd_shortcuts(trimmed);
119        if count_tokens(&compressed) < count_tokens(trimmed) {
120            result.push(compressed);
121        } else {
122            result.push(trimmed.to_string());
123        }
124    }
125
126    strip_edge_pleasantries(&mut result);
127    result.join("\n")
128}
129
130fn build_echo_set(context: &str) -> std::collections::HashSet<String> {
131    context
132        .lines()
133        .map(normalize_for_echo)
134        .filter(|l| l.len() > 10)
135        .collect()
136}
137
138fn normalize_for_echo(line: &str) -> String {
139    line.trim().to_lowercase().replace(char::is_whitespace, " ")
140}
141
142fn is_context_echo(line: &str, echo_set: &std::collections::HashSet<String>) -> bool {
143    let normalized = normalize_for_echo(line);
144    if normalized.len() <= 10 {
145        return false;
146    }
147    echo_set.contains(&normalized)
148}
149
150fn is_boilerplate_code(line: &str) -> bool {
151    let trimmed = line.trim();
152
153    if trimmed.starts_with("//")
154        && !trimmed.starts_with("// TODO")
155        && !trimmed.starts_with("// FIXME")
156        && !trimmed.starts_with("// SAFETY")
157        && !trimmed.starts_with("// NOTE")
158    {
159        let comment_body = trimmed.trim_start_matches("//").trim();
160        if is_narration_comment(comment_body) {
161            return true;
162        }
163    }
164
165    if trimmed.starts_with('#') && !trimmed.starts_with("#[") && !trimmed.starts_with("#!") {
166        let comment_body = trimmed.trim_start_matches('#').trim();
167        if is_narration_comment(comment_body) {
168            return true;
169        }
170    }
171
172    false
173}
174
175fn is_narration_comment(body: &str) -> bool {
176    let b = body.to_lowercase();
177
178    let what_prefixes = [
179        "import ",
180        "define ",
181        "create ",
182        "set up ",
183        "initialize ",
184        "declare ",
185        "add ",
186        "get ",
187        "return ",
188        "check ",
189        "handle ",
190        "call ",
191        "update ",
192        "increment ",
193        "decrement ",
194        "loop ",
195        "iterate ",
196        "print ",
197        "log ",
198        "convert ",
199        "parse ",
200        "read ",
201        "write ",
202        "send ",
203        "receive ",
204        "validate ",
205        "set ",
206        "start ",
207        "stop ",
208        "open ",
209        "close ",
210        "fetch ",
211        "load ",
212        "save ",
213        "store ",
214        "delete ",
215        "remove ",
216        "calculate ",
217        "compute ",
218        "render ",
219        "display ",
220        "show ",
221        "this function ",
222        "this method ",
223        "this class ",
224        "the following ",
225        "here we ",
226        "now we ",
227    ];
228    if what_prefixes.iter().any(|p| b.starts_with(p)) {
229        return true;
230    }
231
232    let what_patterns = [" the ", " a ", " an "];
233    if b.len() < 60 && what_patterns.iter().all(|p| !b.contains(p)) {
234        return false;
235    }
236    if b.len() < 40
237        && b.split_whitespace().count() <= 5
238        && b.chars().filter(|c| c.is_uppercase()).count() == 0
239    {
240        return false;
241    }
242
243    false
244}
245
246fn is_filler_line(line: &str) -> bool {
247    let l = line.to_lowercase();
248
249    // Preserve lines with genuine information signals
250    if l.starts_with("note:")
251        || l.starts_with("hint:")
252        || l.starts_with("warning:")
253        || l.starts_with("error:")
254        || l.starts_with("however,")
255        || l.starts_with("but ")
256        || l.starts_with("caution:")
257        || l.starts_with("important:")
258    {
259        return false;
260    }
261
262    // H=0 patterns: carry zero task-relevant information
263    let prefix_fillers = [
264        // Narration / preamble
265        "here's what i",
266        "here is what i",
267        "let me explain",
268        "let me walk you",
269        "let me break",
270        "i'll now",
271        "i will now",
272        "i'm going to",
273        "first, let me",
274        "allow me to",
275        // Hedging
276        "i think",
277        "i believe",
278        "i would say",
279        "it seems like",
280        "it looks like",
281        "it appears that",
282        // Meta-commentary
283        "that's a great question",
284        "that's an interesting",
285        "good question",
286        "great question",
287        "sure thing",
288        "sure,",
289        "of course,",
290        "absolutely,",
291        // Transitions (zero-info)
292        "now, let's",
293        "now let's",
294        "next, i'll",
295        "moving on",
296        "going forward",
297        "with that said",
298        "with that in mind",
299        "having said that",
300        "that being said",
301        // Closings
302        "hope this helps",
303        "i hope this",
304        "let me know if",
305        "feel free to",
306        "don't hesitate",
307        "happy to help",
308        // Filler connectives
309        "as you can see",
310        "as we can see",
311        "this is because",
312        "the reason is",
313        "in this case",
314        "in other words",
315        "to summarize",
316        "to sum up",
317        "basically,",
318        "essentially,",
319        "it's worth noting",
320        "it should be noted",
321        "as mentioned",
322        "as i mentioned",
323        // Acknowledgments
324        "understood.",
325        "got it.",
326        "i understand.",
327        "i see.",
328        "right,",
329        "okay,",
330        "ok,",
331    ];
332
333    prefix_fillers.iter().any(|f| l.starts_with(f))
334}
335
336/// Meaning-preserving abbreviations only (#1892). Operator rewrites
337/// (`is not`→`!=`, `and`→`&`, `returns`→`->`) and status words
338/// (`completed`/`successfully`→`ok`, `failed`→`FAIL`) were dropped: they
339/// changed meaning in prose ("ok ok", "!= being read") for ~0 BPE tokens.
340const TDD_ABBREVIATIONS: &[(&str, &str)] = &[
341    ("function", "fn"),
342    ("functions", "fns"),
343    ("configuration", "cfg"),
344    ("implementation", "impl"),
345    ("dependencies", "deps"),
346    ("dependency", "dep"),
347    ("parameter", "param"),
348    ("parameters", "params"),
349    ("argument", "arg"),
350    ("arguments", "args"),
351    ("variable", "var"),
352    ("variables", "vars"),
353    ("directory", "dir"),
354    ("directories", "dirs"),
355    ("repository", "repo"),
356    ("repositories", "repos"),
357    ("application", "app"),
358    ("environment", "env"),
359    ("description", "desc"),
360    ("information", "info"),
361    ("approximately", "~"),
362    ("package", "pkg"),
363    ("packages", "pkgs"),
364];
365
366/// The abbreviations that actually save tokens under the active tokenizer,
367/// measured once per process (#1892: most common words are already a single
368/// BPE token, so an unmeasured rule only costs readability).
369fn effective_tdd_abbreviations() -> &'static [(&'static str, &'static str)] {
370    static RULES: std::sync::OnceLock<Vec<(&'static str, &'static str)>> =
371        std::sync::OnceLock::new();
372    RULES.get_or_init(|| {
373        TDD_ABBREVIATIONS
374            .iter()
375            .copied()
376            .filter(|(from, to)| {
377                // Measured mid-sentence (leading space), where BPE merges differ.
378                count_tokens(&format!("the {from} is")) > count_tokens(&format!("the {to} is"))
379            })
380            .collect()
381    })
382}
383
384/// Punctuation that may wrap a prose word without making it part of an
385/// identifier. `.`/`:` only count when they end the word (sentence
386/// punctuation), never inside it (`config.function`, `mod::function`).
387const LEADING_WRAP: &[char] = &['(', '[', '"', '\''];
388const TRAILING_WRAP: &[char] = &['.', ',', ';', ':', '!', '?', ')', ']', '"', '\''];
389
390/// Whole-word, prose-only abbreviation. Words inside inline code, paths,
391/// identifiers (`request_id`, `functionName`, `a.function`) and any word that
392/// is not purely lowercase ASCII letters are left untouched.
393fn apply_tdd_shortcuts(line: &str) -> String {
394    apply_tdd_shortcuts_with(line, effective_tdd_abbreviations())
395}
396
397fn apply_tdd_shortcuts_with(line: &str, rules: &[(&str, &str)]) -> String {
398    if rules.is_empty() {
399        return line.to_string();
400    }
401    let mut out = String::with_capacity(line.len());
402    let mut in_code = false;
403    let mut word = String::new();
404    let flush = |word: &mut String, out: &mut String, in_code: bool| {
405        if word.is_empty() {
406            return;
407        }
408        let ticks = word.matches('`').count();
409        if in_code || ticks > 0 {
410            out.push_str(word);
411        } else {
412            out.push_str(&abbreviate_word(word, rules));
413        }
414        word.clear();
415    };
416    for ch in line.chars() {
417        if ch.is_whitespace() {
418            let ticks = word.matches('`').count();
419            flush(&mut word, &mut out, in_code);
420            if ticks % 2 == 1 {
421                in_code = !in_code;
422            }
423            out.push(ch);
424        } else {
425            word.push(ch);
426        }
427    }
428    flush(&mut word, &mut out, in_code);
429    out
430}
431
432fn abbreviate_word(word: &str, rules: &[(&str, &str)]) -> String {
433    let core_start = word.len() - word.trim_start_matches(LEADING_WRAP).len();
434    let core_end = word.trim_end_matches(TRAILING_WRAP).len();
435    if core_start >= core_end {
436        return word.to_string();
437    }
438    let core = &word[core_start..core_end];
439    if !core.bytes().all(|b| b.is_ascii_lowercase()) {
440        return word.to_string();
441    }
442    match rules.iter().find(|(from, _)| *from == core) {
443        Some((_, to)) => format!("{}{to}{}", &word[..core_start], &word[core_end..]),
444        None => word.to_string(),
445    }
446}
447
448/// Opening pleasantries ("Sure! I'd be happy to help.") and closing offers
449/// ("Let me know if you have any other questions!") carry no task content
450/// and are where most removable response tokens sit (#1892).
451const LEADING_PLEASANTRIES: &[&str] = &[
452    "sure",
453    "certainly",
454    "absolutely",
455    "of course",
456    "great question",
457    "good question",
458    "i'd be happy to",
459    "i would be happy to",
460    "i'm happy to",
461    "happy to help",
462    "thanks for",
463    "thank you for",
464];
465const TRAILING_OFFERS: &[&str] = &[
466    "let me know if",
467    "feel free to",
468    "don't hesitate",
469    "hope this helps",
470    "i hope this",
471    "if you have any other questions",
472    "if you have any questions",
473    "happy to help",
474    "is there anything else",
475];
476
477/// Splits after `.`/`!`/`?` followed by whitespace; the delimiter stays with
478/// its sentence so the text round-trips exactly.
479fn split_sentences(line: &str) -> Vec<&str> {
480    let mut out = Vec::new();
481    let mut start = 0;
482    let bytes = line.as_bytes();
483    for (i, b) in bytes.iter().enumerate() {
484        if matches!(b, b'.' | b'!' | b'?') && bytes.get(i + 1).is_some_and(u8::is_ascii_whitespace)
485        {
486            out.push(&line[start..=i]);
487            start = i + 1;
488        }
489    }
490    if start < line.len() {
491        out.push(&line[start..]);
492    }
493    out
494}
495
496fn is_pleasantry(sentence: &str, patterns: &[&str]) -> bool {
497    let s = sentence.trim().to_lowercase();
498    // Short sentences only: a long sentence that starts with "Sure" still
499    // carries content ("Sure enough, the cache was stale because …").
500    s.split_whitespace().count() <= 12 && patterns.iter().any(|p| s.starts_with(p))
501}
502
503fn strip_edge_pleasantries(lines: &mut Vec<String>) {
504    if let Some(first) = lines.iter().position(|l| !l.trim().is_empty()) {
505        let sentences = split_sentences(&lines[first]);
506        let keep = sentences
507            .iter()
508            .position(|s| !is_pleasantry(s, LEADING_PLEASANTRIES))
509            .unwrap_or(sentences.len());
510        if keep > 0 {
511            let indent_len = lines[first].len() - lines[first].trim_start().len();
512            let indent = lines[first][..indent_len].to_string();
513            let rest = sentences[keep..].concat();
514            lines[first] = format!("{indent}{}", rest.trim_start());
515        }
516    }
517    if let Some(last) = lines.iter().rposition(|l| !l.trim().is_empty()) {
518        let sentences = split_sentences(&lines[last]);
519        let keep = sentences
520            .iter()
521            .rposition(|s| !is_pleasantry(s, TRAILING_OFFERS))
522            .map_or(0, |i| i + 1);
523        if keep < sentences.len() {
524            lines[last] = sentences[..keep].concat().trim_end().to_string();
525        }
526    }
527    while lines.last().is_some_and(|l| l.trim().is_empty()) {
528        lines.pop();
529    }
530    while lines.first().is_some_and(|l| l.trim().is_empty()) {
531        lines.remove(0);
532    }
533}
534
535#[cfg(test)]
536mod tests {
537    use super::*;
538
539    #[test]
540    fn test_filler_detection_original() {
541        assert!(is_filler_line("Here's what I found"));
542        assert!(is_filler_line("Let me explain how this works"));
543        assert!(!is_filler_line("fn main() {}"));
544        assert!(!is_filler_line("Note: important detail"));
545    }
546
547    #[test]
548    fn test_filler_hedging_patterns() {
549        assert!(is_filler_line("I think the issue is here"));
550        assert!(is_filler_line("I believe this is correct"));
551        assert!(is_filler_line("It seems like the problem is"));
552        assert!(is_filler_line("It looks like we need to"));
553        assert!(is_filler_line("It appears that something broke"));
554    }
555
556    #[test]
557    fn test_filler_meta_commentary() {
558        assert!(is_filler_line("That's a great question!"));
559        assert!(is_filler_line("Good question, let me check"));
560        assert!(is_filler_line("Sure thing, I'll do that"));
561        assert!(is_filler_line("Of course, here's the code"));
562        assert!(is_filler_line("Absolutely, that makes sense"));
563    }
564
565    #[test]
566    fn test_filler_closings() {
567        assert!(is_filler_line("Hope this helps!"));
568        assert!(is_filler_line("Let me know if you need more"));
569        assert!(is_filler_line("Feel free to ask questions"));
570        assert!(is_filler_line("Don't hesitate to reach out"));
571        assert!(is_filler_line("Happy to help with anything"));
572    }
573
574    #[test]
575    fn test_filler_transitions() {
576        assert!(is_filler_line("Now, let's move on"));
577        assert!(is_filler_line("Moving on to the next part"));
578        assert!(is_filler_line("Going forward, we should"));
579        assert!(is_filler_line("With that said, here's what"));
580        assert!(is_filler_line("Having said that, let's"));
581    }
582
583    #[test]
584    fn test_filler_acknowledgments() {
585        assert!(is_filler_line("Understood."));
586        assert!(is_filler_line("Got it."));
587        assert!(is_filler_line("I understand."));
588        assert!(is_filler_line("I see."));
589    }
590
591    #[test]
592    fn test_filler_false_positive_protection() {
593        assert!(!is_filler_line("Note: this is critical"));
594        assert!(!is_filler_line("Warning: deprecated API"));
595        assert!(!is_filler_line("Error: connection refused"));
596        assert!(!is_filler_line("However, the edge case fails"));
597        assert!(!is_filler_line("But the second argument is wrong"));
598        assert!(!is_filler_line("Important: do not skip this step"));
599        assert!(!is_filler_line("Caution: this deletes all data"));
600        assert!(!is_filler_line("Hint: use --force flag"));
601        assert!(!is_filler_line("fn validate_token()"));
602        assert!(!is_filler_line("  let result = parse(input);"));
603        assert!(!is_filler_line("The token is expired after 24h"));
604    }
605
606    #[test]
607    fn test_tdd_shortcuts_whole_words_only() {
608        // Explicit rules: under o200k no built-in rule saves tokens, so the
609        // measured list may legitimately be empty.
610        let rules: &[(&str, &str)] = &[("function", "fn")];
611        let apply = |s: &str| apply_tdd_shortcuts_with(s, rules);
612        assert_eq!(apply("the function."), "the fn.");
613        assert_eq!(apply("(function) works"), "(fn) works");
614        // Identifiers, paths, code spans and capitalized/compound words are untouched.
615        for keep in [
616            "function_id",
617            "myfunction",
618            "a.function",
619            "mod::function",
620            "`function`",
621            "functions_total",
622            "FUNCTION",
623            "Function",
624            "src/function/x",
625            "call `the function here` now",
626        ] {
627            assert_eq!(apply(keep), keep, "rewrote `{keep}`");
628        }
629    }
630
631    /// #1892 artifacts from the issue must never reappear.
632    #[test]
633    fn test_tdd_no_meaning_changing_rewrites() {
634        let line = "The tests completed successfully and there were no warnings. \
635            The value is not being read; it returns the initialized env variable.";
636        let out = apply_tdd_shortcuts(line);
637        for bad in ["ok ok", "WARNs", "initd", "!=", " & ", "->", " val "] {
638            assert!(!out.contains(bad), "artifact `{bad}` in: {out}");
639        }
640    }
641
642    #[test]
643    fn test_every_active_rule_saves_tokens() {
644        for (from, to) in effective_tdd_abbreviations() {
645            assert!(
646                count_tokens(&format!("the {to} is")) < count_tokens(&format!("the {from} is")),
647                "{from}→{to} saves nothing"
648            );
649        }
650    }
651
652    const FIXTURES: &[&str] = &[
653        "Sure! I'd be happy to help with that.\n\n\
654         After reviewing the configuration, I found that the function `calculate_total` \
655         returns an incorrect value because the environment variable for the tax rate is \
656         not being read. The dependency on the configuration module was initialized after \
657         the request handler.\n\n\
658         - I moved the initialization of the configuration module before the request handler.\n\
659         - I added a test for the missing environment variable.\n\
660         - I updated the documentation in the repository.\n\n\
661         The tests completed successfully and there were no warnings.\n\n\
662         Let me know if you have any other questions!",
663        "I've updated the implementation of the parser.\n\n\
664         ```rust\nfn parse(configuration: &str) -> Result<Config, Error> {\n\n\n    \
665         let parameters = configuration.split(',');\n    todo!()\n}\n```\n\n\
666         The function now validates every parameter and argument before use, and the \
667         description of each package is stored in the application directory. \
668         Hope this helps! Feel free to ask if anything is unclear.",
669        "The build failed on the CI runner because the repository cache directory was \
670         missing. I recreated the directory, re-ran the pipeline, and the application \
671         now starts in approximately two seconds. The information in the environment \
672         file was stale, so I refreshed it from the deployment dependencies manifest \
673         and verified that every variable resolves.",
674    ];
675
676    #[test]
677    fn test_tdd_never_costs_more_than_off() {
678        for fixture in FIXTURES {
679            let off = compress_standard(fixture, None);
680            let tdd = compress_tdd(fixture, None);
681            assert!(
682                count_tokens(&tdd) <= count_tokens(&off),
683                "tdd {} > off {}\n--- off\n{off}\n--- tdd\n{tdd}",
684                count_tokens(&tdd),
685                count_tokens(&off)
686            );
687        }
688    }
689
690    #[test]
691    fn test_tdd_keeps_code_blocks_verbatim() {
692        let tdd = compress_tdd(FIXTURES[1], None);
693        assert!(tdd.contains("fn parse(configuration: &str) -> Result<Config, Error> {"));
694        assert!(tdd.contains("    let parameters = configuration.split(',');"));
695    }
696
697    #[test]
698    fn test_edge_pleasantries_removed_content_kept() {
699        for compress in [compress_standard, compress_tdd] {
700            let out = compress(FIXTURES[0], None);
701            assert!(!out.contains("happy to help"), "{out}");
702            assert!(!out.contains("Let me know"), "{out}");
703            assert!(out.contains("calculate_total"), "{out}");
704            assert!(out.contains("no warnings"), "{out}");
705            let out = compress(FIXTURES[1], None);
706            assert!(!out.contains("Hope this helps"), "{out}");
707            assert!(!out.contains("Feel free"), "{out}");
708            assert!(out.contains("application"), "{out}");
709        }
710    }
711
712    #[test]
713    fn test_contentful_sure_sentence_kept() {
714        let text = "Sure enough, the cache was stale because the watcher dropped the inotify event \
715            after the directory was renamed during the rebuild of the project index.";
716        let mut lines = vec![text.to_string()];
717        strip_edge_pleasantries(&mut lines);
718        assert_eq!(lines, vec![text.to_string()]);
719    }
720
721    #[test]
722    fn test_handle_tdd_saves_at_least_as_much_as_off() {
723        for fixture in FIXTURES {
724            let off = count_tokens(&handle(fixture, CrpMode::Off));
725            let tdd = count_tokens(&handle(fixture, CrpMode::Tdd));
726            assert!(tdd <= off, "tdd {tdd} > off {off}");
727        }
728    }
729
730    #[test]
731    fn test_compress_integration() {
732        let response = "Let me explain how this works.\n\
733            I think this is correct.\n\
734            Hope this helps!\n\
735            \n\
736            The function returns an error when the token is expired.\n\
737            Note: always check the expiry first.";
738
739        let compressed = compress_standard(response, None);
740        assert!(!compressed.contains("Let me explain"));
741        assert!(!compressed.contains("I think"));
742        assert!(!compressed.contains("Hope this helps"));
743        assert!(compressed.contains("error when the token"));
744        assert!(compressed.contains("Note:"));
745    }
746
747    #[test]
748    fn test_echo_detection() {
749        let context = "fn shannon_entropy(text: &str) -> f64 {\n    let freq = HashMap::new();\n}";
750        let response = "Here's the code:\nfn shannon_entropy(text: &str) -> f64 {\n    let freq = HashMap::new();\n}\nI added the new function below.";
751
752        let compressed = compress_standard(response, Some(context));
753        assert!(!compressed.contains("fn shannon_entropy"));
754        assert!(compressed.contains("added the new function"));
755    }
756
757    #[test]
758    fn test_boilerplate_comment_removal() {
759        let response = "// Import the module\nuse std::io;\n// Define the function\nfn main() {}\n// NOTE: important edge case\nlet x = 1;";
760        let compressed = compress_standard(response, None);
761        assert!(!compressed.contains("Import the module"));
762        assert!(!compressed.contains("Define the function"));
763        assert!(compressed.contains("NOTE: important edge case"));
764        assert!(compressed.contains("use std::io"));
765        assert!(compressed.contains("fn main()"));
766    }
767}