lean_ctx/core/
rules_canonical.rs1pub fn tool_table(mode: Mode) -> &'static str {
9 match mode {
10 Mode::Hybrid => HYBRID_TABLE,
11 Mode::Mcp => MCP_TABLE,
12 }
13}
14
15pub fn dedicated_rules(mode: Mode) -> String {
17 let table = tool_table(mode);
18 let shell_note = match mode {
19 Mode::Hybrid => {
20 "Shell commands: use `lean-ctx -c \"<cmd>\"` via your Shell tool. NEVER use `ctx_shell` in Hybrid mode."
21 }
22 Mode::Mcp => "Shell commands: use `ctx_shell(command)`. NEVER use raw Shell/bash.",
23 };
24
25 format!(
26 r#"# lean-ctx — Context Engineering Layer
27<!-- lean-ctx-rules-v12 -->
28
29CRITICAL: ALWAYS use lean-ctx tools instead of native equivalents. This is NOT optional.
30
31{table}
32
33{shell_note}
34
35## Workflow: Orient → Locate → Read → Edit → Verify → Record
361. Orient: ctx_overview(task) or ctx_compose(task, path)
372. Locate: ctx_search(pattern, path) or ctx_semantic_search(query)
383. Read: ctx_read(path, mode) — omit mode for auto; full only right before edits
394. Edit: ctx_edit(path, old_string, new_string) or native Edit
405. Verify: ctx_read(path, "diff") + ctx_shell("test command")
416. Record: ctx_knowledge(action="remember", content="...")
42
43File editing: use native Edit/StrReplace. Write, Delete, Glob → use normally.
44NEVER loop on Edit failures — switch to ctx_edit immediately.
45
46NEVER use native Read/Grep/Shell when ctx_* equivalents are available.
47<!-- /lean-ctx -->"#
48 )
49}
50
51pub fn mcp_instructions(mode: Mode) -> &'static str {
53 match mode {
54 Mode::Hybrid => MCP_INSTRUCTIONS_HYBRID,
55 Mode::Mcp => MCP_INSTRUCTIONS_MCP,
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum Mode {
61 Hybrid,
62 Mcp,
63}
64
65impl Mode {
66 pub fn from_hook_mode(hook: &crate::hooks::HookMode) -> Self {
67 match hook {
68 crate::hooks::HookMode::Hybrid => Mode::Hybrid,
69 crate::hooks::HookMode::Mcp => Mode::Mcp,
70 }
71 }
72}
73
74const HYBRID_TABLE: &str = "\
75| MUST USE | NEVER USE | Why |
76|----------|-----------|-----|
77| `ctx_read(path, mode)` | `Read` / `cat` / `head` / `tail` | Cached, 10 read modes, re-reads ~13 tokens |
78| `ctx_search(pattern, path)` | `Grep` / `rg` | Compact, token-efficient results |
79| `lean-ctx -c \"<cmd>\"` (via Shell) | `ctx_shell` / raw `Shell` | CLI compression, no MCP overhead |
80| `lean-ctx ls [path]` (via Shell) | `ctx_tree` / `ls` / `find` | Compact directory maps |";
81
82const MCP_TABLE: &str = "\
83| MUST USE | NEVER USE | Why |
84|----------|-----------|-----|
85| `ctx_read(path, mode)` | `Read` / `cat` / `head` / `tail` | Cached, 10 read modes, re-reads ~13 tokens |
86| `ctx_search(pattern, path)` | `Grep` / `rg` | Compact, token-efficient results |
87| `ctx_shell(command)` | `Shell` / `bash` / terminal | Pattern compression for git/npm/cargo output |
88| `ctx_tree(path, depth)` | `ls` / `find` | Compact directory maps |";
89
90const MCP_INSTRUCTIONS_HYBRID: &str = "\
91lean-ctx tools replace Read/Grep/Shell/ls. Workflow: Orient(ctx_overview) → Locate(ctx_search) → Read(ctx_read) → Edit(ctx_edit/native) → Verify(ctx_read diff + lean-ctx -c test) → Record(ctx_knowledge). Edit/Write/Glob: native.";
92
93const MCP_INSTRUCTIONS_MCP: &str = "\
94lean-ctx tools replace Read/Grep/Shell/ls. Workflow: Orient(ctx_overview) → Locate(ctx_search) → Read(ctx_read) → Edit(ctx_edit/native) → Verify(ctx_read diff + ctx_shell test) → Record(ctx_knowledge). Edit/Write/Glob: native.";
95
96pub fn tool_mapping_bullets(mode: Mode) -> &'static str {
98 match mode {
99 Mode::Hybrid => HYBRID_BULLETS,
100 Mode::Mcp => MCP_BULLETS,
101 }
102}
103
104const MCP_BULLETS: &str = "\
108lean-ctx MCP — MANDATORY tool mapping:\n\
109• Read/cat/head/tail -> ctx_read(path, mode)\n\
110• Shell/bash -> ctx_shell(command)\n\
111• Grep/rg -> ctx_search(pattern, path)\n\
112• ls/find -> ctx_tree(path, depth)\n\
113• Edit/Write/Delete/Glob -> native (lean-ctx replaces READ only); if Edit fails, switch to ctx_edit(path, old, new) — never loop";
114
115const HYBRID_BULLETS: &str = "\
116lean-ctx — MANDATORY tool mapping:\n\
117• Read/cat/head/tail -> ctx_read(path, mode)\n\
118• Shell commands -> lean-ctx -c \"<cmd>\" (via Shell) [NEVER ctx_shell]\n\
119• Grep/rg -> ctx_search(pattern, path)\n\
120• ls/find -> lean-ctx ls [path] (via Shell)\n\
121• Edit/Write/Delete/Glob -> native (lean-ctx replaces READ only); if Edit fails, switch to ctx_edit(path, old, new) — never loop";
122
123pub fn ctx_read_modes_block() -> &'static str {
126 "ctx_read modes: auto(default)|full|map|signatures|diff|task|reference|aggressive|entropy|lines:N-M. Re-reads ~13 tok; fresh=true forces disk re-read."
127}
128
129pub fn automation_block() -> &'static str {
132 "Auto: preload/dedup/compress run in background. ctx_session=memory, ctx_knowledge=facts, ctx_semantic_search=meaning search, ctx_shell raw=true=uncompressed. Details: LEAN-CTX.md"
133}
134
135pub fn cep_block() -> &'static str {
136 "CEP v1: 1.ACT FIRST 2.DELTA ONLY (Fn refs) 3.STRUCTURED (+/-/~) 4.ONE LINE PER ACTION 5.QUALITY ANCHOR"
137}
138
139pub fn litm_end_block(mode: Mode) -> &'static str {
140 match mode {
141 Mode::Hybrid => {
142 "TOOL PREFERENCE (END): ctx_read>Read ctx_search>Grep lean-ctx_-c>Shell lean-ctx_ls>ls | Edit/Write/Glob=native"
143 }
144 Mode::Mcp => {
145 "TOOL PREFERENCE (END): ctx_read>Read ctx_shell>Shell ctx_search>Grep ctx_tree>ls | Edit/Write/Glob=native"
146 }
147 }
148}
149
150pub fn unified_tool_mode_block() -> &'static str {
151 "UNIFIED TOOL MODE (active):\n\
152 Additional tools are accessed via ctx() meta-tool: ctx(tool=\"<name>\", ...params).\n\
153 See the ctx() tool description for available sub-tools."
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn hybrid_table_contains_must() {
162 assert!(HYBRID_TABLE.contains("MUST USE"));
163 assert!(!HYBRID_TABLE.contains("PREFER"));
164 }
165
166 #[test]
167 fn mcp_table_contains_must() {
168 assert!(MCP_TABLE.contains("MUST USE"));
169 assert!(!MCP_TABLE.contains("PREFER"));
170 }
171
172 #[test]
173 fn hybrid_table_uses_cli() {
174 assert!(HYBRID_TABLE.contains("lean-ctx -c"));
175 for line in HYBRID_TABLE.lines() {
176 assert!(
177 !line.starts_with("| `ctx_shell"),
178 "Hybrid table must not list ctx_shell in MUST USE column"
179 );
180 }
181 }
182
183 #[test]
184 fn mcp_table_uses_ctx_shell() {
185 assert!(MCP_TABLE.contains("ctx_shell"));
186 assert!(!MCP_TABLE.contains("lean-ctx -c"));
187 }
188
189 #[test]
190 fn dedicated_rules_have_markers() {
191 let rules = dedicated_rules(Mode::Hybrid);
192 assert!(rules.contains("lean-ctx-rules-v12"));
193 assert!(rules.contains("<!-- /lean-ctx -->"));
194 }
195
196 #[test]
197 fn dedicated_rules_litm_structure() {
198 for mode in [Mode::Hybrid, Mode::Mcp] {
199 let rules = dedicated_rules(mode);
200 let lines: Vec<&str> = rules.lines().collect();
201 let first_5 = lines[..5.min(lines.len())].join("\n");
202 assert!(
203 first_5.contains("CRITICAL") || first_5.contains("MUST"),
204 "LITM: MUST instruction near start for {mode:?}"
205 );
206 let last_3 = lines[lines.len().saturating_sub(3)..].join("\n");
207 assert!(
208 last_3.contains("MUST") || last_3.contains("NEVER"),
209 "LITM: reinforcement near end for {mode:?}"
210 );
211 }
212 }
213
214 #[test]
215 fn no_prefer_in_any_output() {
216 for mode in [Mode::Hybrid, Mode::Mcp] {
217 let rules = dedicated_rules(mode);
218 assert!(
219 !rules.contains("PREFER"),
220 "canonical rules must use MUST, not PREFER for {mode:?}"
221 );
222 let instructions = mcp_instructions(mode);
223 assert!(
224 !instructions.contains("PREFER"),
225 "MCP instructions must use MUST, not PREFER for {mode:?}"
226 );
227 }
228 }
229
230 #[test]
231 fn hybrid_bullets_use_cli() {
232 let bullets = tool_mapping_bullets(Mode::Hybrid);
233 for line in bullets.lines() {
234 if line.starts_with('•') {
235 assert!(
236 !line.starts_with("• Shell/bash -> ctx_shell"),
237 "Hybrid bullets must not map Shell to ctx_shell"
238 );
239 }
240 }
241 assert!(bullets.contains("lean-ctx -c"));
242 }
243
244 #[test]
245 fn mcp_bullets_no_lean_ctx_c() {
246 let bullets = tool_mapping_bullets(Mode::Mcp);
247 assert!(
248 !bullets.contains("lean-ctx -c"),
249 "MCP bullets must not reference lean-ctx -c"
250 );
251 assert!(bullets.contains("ctx_shell"));
252 }
253
254 #[test]
255 fn shared_sections_not_empty() {
256 assert!(!ctx_read_modes_block().is_empty());
257 assert!(!automation_block().is_empty());
258 assert!(!cep_block().is_empty());
259 assert!(!litm_end_block(Mode::Mcp).is_empty());
260 assert!(!litm_end_block(Mode::Hybrid).is_empty());
261 assert!(!unified_tool_mode_block().is_empty());
262 }
263
264 #[test]
265 fn bullets_carry_edit_failure_path() {
266 for mode in [Mode::Hybrid, Mode::Mcp] {
270 assert!(
271 tool_mapping_bullets(mode).contains("ctx_edit"),
272 "edit-failure path missing for {mode:?}"
273 );
274 }
275 }
276}