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