Skip to main content

lean_ctx/
instructions.rs

1use crate::tools::CrpMode;
2
3/// Claude Code truncates MCP server instructions at 2048 characters.
4/// Full instructions are installed as `~/.claude/rules/lean-ctx.md` instead.
5const CLAUDE_CODE_INSTRUCTION_CAP: usize = 2048;
6
7pub fn build_instructions(crp_mode: CrpMode) -> String {
8    build_instructions_with_client(crp_mode, "")
9}
10
11pub fn build_instructions_with_client(crp_mode: CrpMode, client_name: &str) -> String {
12    if is_claude_code_client(client_name) {
13        return build_claude_code_instructions();
14    }
15    build_full_instructions(crp_mode, client_name)
16}
17
18fn is_claude_code_client(client_name: &str) -> bool {
19    let lower = client_name.to_lowercase();
20    lower.contains("claude") && !lower.contains("cursor")
21}
22
23fn build_claude_code_instructions() -> String {
24    let instr = "\
25ALWAYS use lean-ctx MCP tools instead of native equivalents.
26
27Tool mapping (MANDATORY):
28• Read/cat/head/tail -> ctx_read(path, mode)
29• Shell/bash -> ctx_shell(command)
30• Grep/rg -> ctx_search(pattern, path)
31• ls/find -> ctx_tree(path, depth)
32• Edit/StrReplace -> native (lean-ctx=READ only)
33• Write, Delete, Glob -> normal
34
35ctx_read modes: full|map|signatures|diff|task|reference|aggressive|entropy|lines:N-M
36Auto-selects mode. Re-reads ~13 tok. File refs F1,F2.. persist.
37Cached? fresh=true or lines:N-M.
38
39Auto: ctx_overview, ctx_preload, ctx_dedup, ctx_compress behind the scenes.
40Multi-agent: ctx_agent(action=handoff|sync|diary).
41ctx_semantic_search for meaning search. ctx_session for memory.
42ctx_knowledge: remember|recall|timeline|rooms|search|wakeup.
43ctx_shell raw=true for uncompressed.
44
45CEP: 1.ACT FIRST 2.DELTA ONLY 3.STRUCTURED(+/-/~) 4.ONE LINE 5.QUALITY
46
47Prefer: ctx_read>Read | ctx_shell>Shell | ctx_search>Grep | ctx_tree>ls
48Edit: native Edit/StrReplace preferred, ctx_edit if Edit unavailable.
49Never echo tool output. Never narrate. Show only changed code.
50Full instructions at ~/.claude/rules/lean-ctx.md";
51
52    debug_assert!(
53        instr.len() <= CLAUDE_CODE_INSTRUCTION_CAP,
54        "Claude Code instructions exceed {CLAUDE_CODE_INSTRUCTION_CAP} chars: {} chars",
55        instr.len()
56    );
57    instr.to_string()
58}
59
60fn build_full_instructions(crp_mode: CrpMode, client_name: &str) -> String {
61    let profile = crate::core::litm::LitmProfile::from_client_name(client_name);
62    let (session_block, session_end_block) = match crate::core::session::SessionState::load_latest()
63    {
64        Some(ref session) => {
65            let positioned = crate::core::litm::position_optimize(session);
66            let begin = format!(
67                "\n\n--- ACTIVE SESSION (LITM P1: begin position, profile: {}) ---\n{}\n---\n",
68                profile.name, positioned.begin_block
69            );
70            let end = if positioned.end_block.is_empty() {
71                String::new()
72            } else {
73                format!(
74                    "\n--- SESSION CONTEXT (LITM P2: end position) ---\n{}\n---\n",
75                    positioned.end_block
76                )
77            };
78            (begin, end)
79        }
80        None => (String::new(), String::new()),
81    };
82
83    let project_root_for_blocks = crate::core::session::SessionState::load_latest()
84        .and_then(|s| s.project_root)
85        .or_else(|| {
86            std::env::current_dir()
87                .ok()
88                .map(|p| p.to_string_lossy().to_string())
89        });
90
91    let knowledge_block = match &project_root_for_blocks {
92        Some(root) => {
93            let knowledge = crate::core::knowledge::ProjectKnowledge::load(root);
94            match knowledge {
95                Some(k) if !k.facts.is_empty() || !k.patterns.is_empty() => {
96                    let aaak = k.format_aaak();
97                    if aaak.is_empty() {
98                        String::new()
99                    } else {
100                        format!("\n--- PROJECT MEMORY (AAAK) ---\n{}\n---\n", aaak.trim())
101                    }
102                }
103                _ => String::new(),
104            }
105        }
106        None => String::new(),
107    };
108
109    let gotcha_block = match &project_root_for_blocks {
110        Some(root) => {
111            let store = crate::core::gotcha_tracker::GotchaStore::load(root);
112            let files: Vec<String> = crate::core::session::SessionState::load_latest()
113                .map(|s| s.files_touched.iter().map(|ft| ft.path.clone()).collect())
114                .unwrap_or_default();
115            let block = store.format_injection_block(&files);
116            if block.is_empty() {
117                String::new()
118            } else {
119                format!("\n{block}\n")
120            }
121        }
122        None => String::new(),
123    };
124
125    let mut base = format!("\
126CRITICAL: ALWAYS use lean-ctx MCP tools instead of native equivalents for token savings.\n\
127\n\
128lean-ctx MCP — MANDATORY tool mapping:\n\
129• Read/cat/head/tail -> ctx_read(path, mode)  [NEVER use native Read]\n\
130• Shell/bash -> ctx_shell(command)  [NEVER use native Shell]\n\
131• Grep/rg -> ctx_search(pattern, path)  [NEVER use native Grep]\n\
132• ls/find -> ctx_tree(path, depth)\n\
133• Edit/StrReplace -> use native (lean-ctx only replaces READ, not WRITE)\n\
134• Write, Delete, Glob -> use normally\n\
135\n\
136COMPATIBILITY: lean-ctx replaces READ operations only. Edit/Write/StrReplace stay native.\n\
137FILE EDITING: Native Edit/StrReplace preferred. If Edit fails, use ctx_edit immediately.\n\
138\n\
139ctx_read modes: full|map|signatures|diff|task|reference|aggressive|entropy|lines:N-M. Auto-selects. Re-reads ~13 tok. Fn refs F1,F2.. persist.\n\
140Cached? Use fresh=true, start_line=N, or lines:N-M.\n\
141\n\
142Auto: ctx_overview, ctx_preload, ctx_dedup, ctx_compress run behind the scenes. Checkpoint every 15 calls.\n\
143Multi-agent: ctx_agent(action=handoff|sync). Diary: ctx_agent(action=diary, category=discovery|decision|blocker|progress|insight).\n\
144ctx_semantic_search for meaning-based search. ctx_session for memory. ctx_knowledge: remember|recall|timeline|rooms|search|wakeup.\n\
145ctx_shell raw=true for uncompressed output.\n\
146\n\
147CEP v1: 1.ACT FIRST 2.DELTA ONLY (Fn refs) 3.STRUCTURED (+/-/~) 4.ONE LINE PER ACTION 5.QUALITY ANCHOR\n\
148\n\
149{decoder_block}\n\
150\n\
151{session_block}\
152{knowledge_block}\
153{gotcha_block}\
154\n\
155--- ORIGIN ---\n\
156{origin}\n\
157{session_end_block}\
158\n\
159--- TOOL PREFERENCE (LITM-END) ---\n\
160Prefer: ctx_read over Read | ctx_shell over Shell | ctx_search over Grep | ctx_tree over ls\n\
161Edit files: native Edit/StrReplace if available, ctx_edit if Edit requires unavailable Read.\n\
162Write, Delete, Glob -> use normally. NEVER loop on Edit failures — use ctx_edit.",
163        decoder_block = crate::core::protocol::instruction_decoder_block(),
164        origin = crate::core::integrity::origin_line()
165    );
166
167    if should_use_unified(client_name) {
168        base.push_str(
169            "\n\n\
170UNIFIED TOOL MODE (active):\n\
171Additional tools are accessed via ctx() meta-tool: ctx(tool=\"<name>\", ...params).\n\
172See the ctx() tool description for available sub-tools.\n",
173        );
174    }
175
176    let intelligence_block = build_intelligence_block();
177
178    let base = base;
179    match crp_mode {
180        CrpMode::Off => format!("{base}\n\n{intelligence_block}"),
181        CrpMode::Compact => {
182            format!(
183                "{base}\n\n\
184CRP MODE: compact\n\
185Omit filler. Abbreviate: fn,cfg,impl,deps,req,res,ctx,err,ret,arg,val,ty,mod.\n\
186Diff lines (+/-) only. TARGET: <=200 tok. Trust tool outputs.\n\n\
187{intelligence_block}"
188            )
189        }
190        CrpMode::Tdd => {
191            format!(
192                "{base}\n\n\
193CRP MODE: tdd\n\
194Max density. Every token carries meaning. Fn refs only, diff lines (+/-) only.\n\
195Abbreviate: fn,cfg,impl,deps,req,res,ctx,err,ret,arg,val,ty,mod.\n\
196+F1:42 param(timeout:Duration) | -F1:10-15 | ~F1:42 old->new\n\
197BUDGET: <=150 tok. ZERO NARRATION. Trust tool outputs.\n\n\
198{intelligence_block}"
199            )
200        }
201    }
202}
203
204pub fn claude_code_instructions() -> String {
205    build_claude_code_instructions()
206}
207
208pub fn full_instructions_for_rules_file(crp_mode: CrpMode) -> String {
209    build_full_instructions(crp_mode, "")
210}
211
212fn build_intelligence_block() -> String {
213    "\
214OUTPUT EFFICIENCY:\n\
215• Never echo tool output code. Never add narration comments. Show only changed code.\n\
216• [TASK:type] and SCOPE hints included. Architecture=thorough, generate=code."
217        .to_string()
218}
219
220fn should_use_unified(client_name: &str) -> bool {
221    if std::env::var("LEAN_CTX_FULL_TOOLS").is_ok() {
222        return false;
223    }
224    if std::env::var("LEAN_CTX_UNIFIED").is_ok() {
225        return true;
226    }
227    let _ = client_name;
228    false
229}