Skip to main content

lean_ctx/core/
protocol.rs

1use std::path::Path;
2
3pub fn shorten_path(path: &str) -> String {
4    let p = Path::new(path);
5    if let Some(name) = p.file_name() {
6        return name.to_string_lossy().to_string();
7    }
8    path.to_string()
9}
10
11pub fn format_savings(original: usize, compressed: usize) -> String {
12    let saved = original.saturating_sub(compressed);
13    if original == 0 {
14        return "0 tok saved".to_string();
15    }
16    let pct = (saved as f64 / original as f64 * 100.0).round() as usize;
17    format!("[{saved} tok saved ({pct}%)]")
18}
19
20pub struct InstructionTemplate {
21    pub code: &'static str,
22    pub full: &'static str,
23}
24
25const TEMPLATES: &[InstructionTemplate] = &[
26    InstructionTemplate {
27        code: "ACT1",
28        full: "Act immediately, report result in one line",
29    },
30    InstructionTemplate {
31        code: "BRIEF",
32        full: "Summarize approach in 1-2 lines, then act",
33    },
34    InstructionTemplate {
35        code: "FULL",
36        full: "Outline approach, consider edge cases, then act",
37    },
38    InstructionTemplate {
39        code: "DELTA",
40        full: "Only show changed lines, not full files",
41    },
42    InstructionTemplate {
43        code: "NOREPEAT",
44        full: "Never repeat known context. Reference cached files by Fn ID",
45    },
46    InstructionTemplate {
47        code: "STRUCT",
48        full: "Use notation, not sentences. Changes: +line/-line/~line",
49    },
50    InstructionTemplate {
51        code: "1LINE",
52        full: "One line per action. Summarize, don't explain",
53    },
54    InstructionTemplate {
55        code: "NODOC",
56        full: "Don't add comments that narrate what code does",
57    },
58    InstructionTemplate {
59        code: "ACTFIRST",
60        full: "Execute tool calls immediately. Never narrate before acting",
61    },
62    InstructionTemplate {
63        code: "QUALITY",
64        full: "Never skip edge case analysis or error handling to save tokens",
65    },
66    InstructionTemplate {
67        code: "NOMOCK",
68        full: "Never use mock data, fake values, or placeholder code",
69    },
70    InstructionTemplate {
71        code: "FREF",
72        full: "Reference files by Fn refs only, never full paths",
73    },
74    InstructionTemplate {
75        code: "DIFF",
76        full: "For code changes: show only diff lines, not full files",
77    },
78    InstructionTemplate {
79        code: "ABBREV",
80        full: "Use abbreviations: fn, cfg, impl, deps, req, res, ctx, err",
81    },
82    InstructionTemplate {
83        code: "SYMBOLS",
84        full: "Use TDD notation: +=add -=remove ~=modify ->=returns ok/fail for status",
85    },
86];
87
88/// Build the decoder block that explains all instruction codes (sent once per session).
89pub fn instruction_decoder_block() -> String {
90    let mut lines = vec!["INSTRUCTION CODES:".to_string()];
91    for t in TEMPLATES {
92        lines.push(format!("  {} = {}", t.code, t.full));
93    }
94    lines.join("\n")
95}
96
97/// Encode an instruction suffix using short codes with budget hints.
98/// Response budget is dynamic based on task complexity to shape LLM output length.
99pub fn encode_instructions(complexity: &str) -> String {
100    match complexity {
101        "mechanical" => "MODE: ACT1 DELTA 1LINE | BUDGET: <=50 tokens, 1 line answer".to_string(),
102        "simple" => "MODE: BRIEF DELTA 1LINE | BUDGET: <=100 tokens, structured".to_string(),
103        "standard" => "MODE: BRIEF DELTA NOREPEAT STRUCT | BUDGET: <=200 tokens".to_string(),
104        "complex" => {
105            "MODE: FULL QUALITY NOREPEAT STRUCT FREF DIFF | BUDGET: <=500 tokens".to_string()
106        }
107        "architectural" => {
108            "MODE: FULL QUALITY NOREPEAT STRUCT FREF | BUDGET: unlimited".to_string()
109        }
110        _ => "MODE: BRIEF | BUDGET: <=200 tokens".to_string(),
111    }
112}
113
114/// Encode instructions with SNR metric for context quality awareness.
115pub fn encode_instructions_with_snr(complexity: &str, compression_pct: f64) -> String {
116    let snr = if compression_pct > 0.0 {
117        1.0 - (compression_pct / 100.0)
118    } else {
119        1.0
120    };
121    let base = encode_instructions(complexity);
122    format!("{base} | SNR: {snr:.2}")
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn decoder_block_contains_all_codes() {
131        let block = instruction_decoder_block();
132        for t in TEMPLATES {
133            assert!(
134                block.contains(t.code),
135                "decoder should contain code {}",
136                t.code
137            );
138        }
139    }
140
141    #[test]
142    fn encoded_instructions_are_compact() {
143        use super::super::tokens::count_tokens;
144        let full = "TASK COMPLEXITY: mechanical\nMinimal reasoning needed. Act immediately, report result in one line. Show only changed lines, not full files.";
145        let encoded = encode_instructions("mechanical");
146        assert!(
147            count_tokens(&encoded) <= count_tokens(full),
148            "encoded ({}) should be <= full ({})",
149            count_tokens(&encoded),
150            count_tokens(full)
151        );
152    }
153
154    #[test]
155    fn all_complexity_levels_encode() {
156        for level in &["mechanical", "standard", "architectural"] {
157            let encoded = encode_instructions(level);
158            assert!(encoded.starts_with("MODE:"), "should start with MODE:");
159        }
160    }
161}