1use super::serde_defaults;
9#[allow(clippy::wildcard_imports)]
10use super::*;
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(default)]
15pub struct SecretDetectionConfig {
16 pub enabled: bool,
17 pub redact: bool,
18 pub custom_patterns: Vec<String>,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
26#[serde(default)]
27pub struct SetupConfig {
28 pub auto_inject_rules: Option<bool>,
32 pub auto_inject_skills: Option<bool>,
35 #[serde(default = "serde_defaults::default_true")]
37 pub auto_update_mcp: bool,
38}
39
40impl Default for SetupConfig {
41 fn default() -> Self {
42 Self {
43 auto_inject_rules: None,
44 auto_inject_skills: None,
45 auto_update_mcp: true,
46 }
47 }
48}
49
50impl SetupConfig {
51 pub fn should_inject_rules(&self) -> bool {
55 match self.auto_inject_rules {
56 Some(v) => v,
57 None => Self::rules_already_present(),
58 }
59 }
60
61 pub fn should_inject_skills(&self) -> bool {
63 match self.auto_inject_skills {
64 Some(v) => v,
65 None => Self::rules_already_present(),
66 }
67 }
68
69 fn rules_already_present() -> bool {
71 let Some(home) = dirs::home_dir() else {
72 return false;
73 };
74 let marker = crate::rules_inject::RULES_MARKER;
75 let check_paths = [
76 home.join(".cursor/rules/lean-ctx.mdc"),
77 crate::core::editor_registry::claude_rules_dir(&home).join("lean-ctx.md"),
78 home.join(".gemini/GEMINI.md"),
79 home.join(".codeium/windsurf/rules/lean-ctx.md"),
80 ];
81 for p in &check_paths {
82 if let Ok(content) = std::fs::read_to_string(p) {
83 if content.contains(marker) {
84 return true;
85 }
86 }
87 }
88 false
89 }
90}
91
92impl Default for SecretDetectionConfig {
93 fn default() -> Self {
94 Self {
95 enabled: true,
96 redact: true,
97 custom_patterns: Vec::new(),
98 }
99 }
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104#[serde(default)]
105pub struct ArchiveConfig {
106 pub enabled: bool,
107 pub threshold_chars: usize,
108 pub max_age_hours: u64,
109 pub max_disk_mb: u64,
110 pub ephemeral: bool,
111}
112
113impl Default for ArchiveConfig {
114 fn default() -> Self {
115 Self {
116 enabled: true,
117 threshold_chars: 800,
118 max_age_hours: 48,
119 max_disk_mb: 500,
120 ephemeral: true,
121 }
122 }
123}
124
125impl ArchiveConfig {
126 pub fn ephemeral_effective(&self) -> bool {
127 if let Ok(v) = std::env::var("LEAN_CTX_EPHEMERAL") {
128 return !matches!(v.trim(), "0" | "false" | "off");
129 }
130 self.ephemeral && self.enabled
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
138#[serde(default)]
139pub struct ProvidersConfig {
140 pub enabled: bool,
142 pub github: ProviderEntryConfig,
144 pub gitlab: ProviderEntryConfig,
146 pub auto_index: bool,
148 pub cache_ttl_secs: u64,
150 #[serde(default)]
152 pub mcp_bridges: std::collections::HashMap<String, McpBridgeEntry>,
153}
154
155impl Default for ProvidersConfig {
156 fn default() -> Self {
157 Self {
158 enabled: true,
159 github: ProviderEntryConfig::default(),
160 gitlab: ProviderEntryConfig::default(),
161 auto_index: true,
162 cache_ttl_secs: 120,
163 mcp_bridges: std::collections::HashMap::new(),
164 }
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct McpBridgeEntry {
170 #[serde(default)]
172 pub url: Option<String>,
173 #[serde(default)]
175 pub command: Option<String>,
176 #[serde(default)]
178 pub args: Vec<String>,
179 #[serde(default)]
181 pub description: Option<String>,
182 #[serde(default)]
184 pub auth_env: Option<String>,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189#[serde(default)]
190pub struct ProviderEntryConfig {
191 pub enabled: bool,
193 pub token: Option<String>,
195 pub api_url: Option<String>,
197 pub project: Option<String>,
199}
200
201impl Default for ProviderEntryConfig {
202 fn default() -> Self {
203 Self {
204 enabled: true,
205 token: None,
206 api_url: None,
207 project: None,
208 }
209 }
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214#[serde(default)]
215pub struct AutonomyConfig {
216 pub enabled: bool,
217 pub auto_preload: bool,
218 pub auto_dedup: bool,
219 pub auto_related: bool,
220 pub auto_consolidate: bool,
221 pub silent_preload: bool,
222 pub dedup_threshold: usize,
223 pub consolidate_every_calls: u32,
224 pub consolidate_cooldown_secs: u64,
225 #[serde(default = "serde_defaults::default_true")]
226 pub cognition_loop_enabled: bool,
227 #[serde(default = "serde_defaults::default_cognition_loop_interval")]
228 pub cognition_loop_interval_secs: u64,
229 #[serde(default = "serde_defaults::default_cognition_loop_max_steps")]
230 pub cognition_loop_max_steps: u8,
231}
232
233impl Default for AutonomyConfig {
234 fn default() -> Self {
235 Self {
236 enabled: true,
237 auto_preload: true,
238 auto_dedup: true,
239 auto_related: true,
240 auto_consolidate: true,
241 silent_preload: true,
242 dedup_threshold: 8,
243 consolidate_every_calls: 25,
244 consolidate_cooldown_secs: 120,
245 cognition_loop_enabled: true,
246 cognition_loop_interval_secs: 3600,
247 cognition_loop_max_steps: 8,
248 }
249 }
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
255#[serde(default)]
256pub struct UpdatesConfig {
257 pub auto_update: bool,
258 pub check_interval_hours: u64,
259 pub notify_only: bool,
260}
261
262impl Default for UpdatesConfig {
263 fn default() -> Self {
264 Self {
265 auto_update: false,
266 check_interval_hours: 6,
267 notify_only: false,
268 }
269 }
270}
271
272impl UpdatesConfig {
273 pub fn from_env() -> Self {
274 let mut cfg = Self::default();
275 if let Ok(v) = std::env::var("LEAN_CTX_AUTO_UPDATE") {
276 cfg.auto_update = v == "1" || v.eq_ignore_ascii_case("true");
277 }
278 if let Ok(v) = std::env::var("LEAN_CTX_UPDATE_INTERVAL_HOURS") {
279 if let Ok(h) = v.parse::<u64>() {
280 cfg.check_interval_hours = h.clamp(1, 168);
281 }
282 }
283 if let Ok(v) = std::env::var("LEAN_CTX_UPDATE_NOTIFY_ONLY") {
284 cfg.notify_only = v == "1" || v.eq_ignore_ascii_case("true");
285 }
286 cfg
287 }
288}
289
290impl AutonomyConfig {
291 pub fn from_env() -> Self {
293 let mut cfg = Self::default();
294 if let Ok(v) = std::env::var("LEAN_CTX_AUTONOMY") {
295 if v == "false" || v == "0" {
296 cfg.enabled = false;
297 }
298 }
299 if let Ok(v) = std::env::var("LEAN_CTX_AUTO_PRELOAD") {
300 cfg.auto_preload = v != "false" && v != "0";
301 }
302 if let Ok(v) = std::env::var("LEAN_CTX_AUTO_DEDUP") {
303 cfg.auto_dedup = v != "false" && v != "0";
304 }
305 if let Ok(v) = std::env::var("LEAN_CTX_AUTO_RELATED") {
306 cfg.auto_related = v != "false" && v != "0";
307 }
308 if let Ok(v) = std::env::var("LEAN_CTX_AUTO_CONSOLIDATE") {
309 cfg.auto_consolidate = v != "false" && v != "0";
310 }
311 if let Ok(v) = std::env::var("LEAN_CTX_SILENT_PRELOAD") {
312 cfg.silent_preload = v != "false" && v != "0";
313 }
314 if let Ok(v) = std::env::var("LEAN_CTX_DEDUP_THRESHOLD") {
315 if let Ok(n) = v.parse() {
316 cfg.dedup_threshold = n;
317 }
318 }
319 if let Ok(v) = std::env::var("LEAN_CTX_CONSOLIDATE_EVERY_CALLS") {
320 if let Ok(n) = v.parse() {
321 cfg.consolidate_every_calls = n;
322 }
323 }
324 if let Ok(v) = std::env::var("LEAN_CTX_CONSOLIDATE_COOLDOWN_SECS") {
325 if let Ok(n) = v.parse() {
326 cfg.consolidate_cooldown_secs = n;
327 }
328 }
329 if let Ok(v) = std::env::var("LEAN_CTX_COGNITION_LOOP_ENABLED") {
330 cfg.cognition_loop_enabled = v != "false" && v != "0";
331 }
332 if let Ok(v) = std::env::var("LEAN_CTX_COGNITION_LOOP_INTERVAL_SECS") {
333 if let Ok(n) = v.parse() {
334 cfg.cognition_loop_interval_secs = n;
335 }
336 }
337 if let Ok(v) = std::env::var("LEAN_CTX_COGNITION_LOOP_MAX_STEPS") {
338 if let Ok(n) = v.parse() {
339 cfg.cognition_loop_max_steps = n;
340 }
341 }
342 cfg
343 }
344
345 pub fn load() -> Self {
347 let file_cfg = Config::load().autonomy;
348 let mut cfg = file_cfg;
349 if let Ok(v) = std::env::var("LEAN_CTX_AUTONOMY") {
350 if v == "false" || v == "0" {
351 cfg.enabled = false;
352 }
353 }
354 if let Ok(v) = std::env::var("LEAN_CTX_AUTO_PRELOAD") {
355 cfg.auto_preload = v != "false" && v != "0";
356 }
357 if let Ok(v) = std::env::var("LEAN_CTX_AUTO_DEDUP") {
358 cfg.auto_dedup = v != "false" && v != "0";
359 }
360 if let Ok(v) = std::env::var("LEAN_CTX_AUTO_RELATED") {
361 cfg.auto_related = v != "false" && v != "0";
362 }
363 if let Ok(v) = std::env::var("LEAN_CTX_SILENT_PRELOAD") {
364 cfg.silent_preload = v != "false" && v != "0";
365 }
366 if let Ok(v) = std::env::var("LEAN_CTX_DEDUP_THRESHOLD") {
367 if let Ok(n) = v.parse() {
368 cfg.dedup_threshold = n;
369 }
370 }
371 if let Ok(v) = std::env::var("LEAN_CTX_COGNITION_LOOP_ENABLED") {
372 cfg.cognition_loop_enabled = v != "false" && v != "0";
373 }
374 if let Ok(v) = std::env::var("LEAN_CTX_COGNITION_LOOP_INTERVAL_SECS") {
375 if let Ok(n) = v.parse() {
376 cfg.cognition_loop_interval_secs = n;
377 }
378 }
379 if let Ok(v) = std::env::var("LEAN_CTX_COGNITION_LOOP_MAX_STEPS") {
380 if let Ok(n) = v.parse() {
381 cfg.cognition_loop_max_steps = n;
382 }
383 }
384 cfg
385 }
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize, Default)]
390#[serde(default)]
391pub struct CloudConfig {
392 pub contribute_enabled: bool,
393 pub last_contribute: Option<String>,
394 pub last_sync: Option<String>,
395 pub last_gain_sync: Option<String>,
396 pub last_model_pull: Option<String>,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
406#[serde(default)]
407pub struct GainConfig {
408 pub auto_publish: bool,
411 pub leaderboard: bool,
413 pub display_name: Option<String>,
415 pub auto_publish_interval_hours: u64,
417 pub last_auto_publish: Option<String>,
420}
421
422impl Default for GainConfig {
423 fn default() -> Self {
424 Self {
425 auto_publish: false,
426 leaderboard: true,
427 display_name: None,
428 auto_publish_interval_hours: 24,
429 last_auto_publish: None,
430 }
431 }
432}
433
434#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct AliasEntry {
437 pub command: String,
438 pub alias: String,
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize)]
443#[serde(default)]
444pub struct LoopDetectionConfig {
445 pub normal_threshold: u32,
446 pub reduced_threshold: u32,
447 pub blocked_threshold: u32,
448 pub window_secs: u64,
449 pub search_group_limit: u32,
450 pub tool_total_limits: HashMap<String, u32>,
451}
452
453impl Default for LoopDetectionConfig {
454 fn default() -> Self {
455 let mut tool_total_limits = HashMap::new();
456 tool_total_limits.insert("ctx_read".to_string(), 100);
457 tool_total_limits.insert("ctx_search".to_string(), 80);
458 tool_total_limits.insert("ctx_shell".to_string(), 50);
459 tool_total_limits.insert("ctx_semantic_search".to_string(), 60);
460 Self {
461 normal_threshold: 2,
462 reduced_threshold: 4,
463 blocked_threshold: 0,
464 window_secs: 300,
465 search_group_limit: 10,
466 tool_total_limits,
467 }
468 }
469}
470
471#[derive(Debug, Clone, Default, Serialize, Deserialize)]
480#[serde(default)]
481pub struct EmbeddingConfig {
482 #[serde(default, skip_serializing_if = "Option::is_none")]
483 pub model: Option<String>,
484}