use super::serde_defaults;
#[allow(clippy::wildcard_imports)]
use super::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct SecretDetectionConfig {
pub enabled: bool,
pub redact: bool,
pub custom_patterns: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct SetupConfig {
pub auto_inject_rules: Option<bool>,
pub auto_inject_skills: Option<bool>,
#[serde(default = "serde_defaults::default_true")]
pub auto_update_mcp: bool,
}
impl Default for SetupConfig {
fn default() -> Self {
Self {
auto_inject_rules: None,
auto_inject_skills: None,
auto_update_mcp: true,
}
}
}
impl SetupConfig {
pub fn should_inject_rules(&self) -> bool {
match self.auto_inject_rules {
Some(v) => v,
None => Self::rules_already_present(),
}
}
pub fn should_inject_skills(&self) -> bool {
match self.auto_inject_skills {
Some(v) => v,
None => Self::rules_already_present(),
}
}
fn rules_already_present() -> bool {
let Some(home) = dirs::home_dir() else {
return false;
};
let marker = crate::rules_inject::RULES_MARKER;
let check_paths = [
home.join(".cursor/rules/lean-ctx.mdc"),
crate::core::editor_registry::claude_rules_dir(&home).join("lean-ctx.md"),
home.join(".gemini/GEMINI.md"),
home.join(".codeium/windsurf/rules/lean-ctx.md"),
];
for p in &check_paths {
if let Ok(content) = std::fs::read_to_string(p) {
if content.contains(marker) {
return true;
}
}
}
false
}
}
impl Default for SecretDetectionConfig {
fn default() -> Self {
Self {
enabled: true,
redact: true,
custom_patterns: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct ArchiveConfig {
pub enabled: bool,
pub threshold_chars: usize,
pub max_age_hours: u64,
pub max_disk_mb: u64,
pub ephemeral: bool,
}
impl Default for ArchiveConfig {
fn default() -> Self {
Self {
enabled: true,
threshold_chars: 800,
max_age_hours: 48,
max_disk_mb: 500,
ephemeral: true,
}
}
}
impl ArchiveConfig {
pub fn ephemeral_effective(&self) -> bool {
if let Ok(v) = std::env::var("LEAN_CTX_EPHEMERAL") {
return !matches!(v.trim(), "0" | "false" | "off");
}
self.ephemeral && self.enabled
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct ProvidersConfig {
pub enabled: bool,
pub github: ProviderEntryConfig,
pub gitlab: ProviderEntryConfig,
pub auto_index: bool,
pub cache_ttl_secs: u64,
#[serde(default)]
pub mcp_bridges: std::collections::HashMap<String, McpBridgeEntry>,
}
impl Default for ProvidersConfig {
fn default() -> Self {
Self {
enabled: true,
github: ProviderEntryConfig::default(),
gitlab: ProviderEntryConfig::default(),
auto_index: true,
cache_ttl_secs: 120,
mcp_bridges: std::collections::HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpBridgeEntry {
#[serde(default)]
pub url: Option<String>,
#[serde(default)]
pub command: Option<String>,
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub auth_env: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct ProviderEntryConfig {
pub enabled: bool,
pub token: Option<String>,
pub api_url: Option<String>,
pub project: Option<String>,
}
impl Default for ProviderEntryConfig {
fn default() -> Self {
Self {
enabled: true,
token: None,
api_url: None,
project: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct AutonomyConfig {
pub enabled: bool,
pub auto_preload: bool,
pub auto_dedup: bool,
pub auto_related: bool,
pub auto_consolidate: bool,
pub silent_preload: bool,
pub dedup_threshold: usize,
pub consolidate_every_calls: u32,
pub consolidate_cooldown_secs: u64,
#[serde(default = "serde_defaults::default_true")]
pub cognition_loop_enabled: bool,
#[serde(default = "serde_defaults::default_cognition_loop_interval")]
pub cognition_loop_interval_secs: u64,
#[serde(default = "serde_defaults::default_cognition_loop_max_steps")]
pub cognition_loop_max_steps: u8,
}
impl Default for AutonomyConfig {
fn default() -> Self {
Self {
enabled: true,
auto_preload: true,
auto_dedup: true,
auto_related: true,
auto_consolidate: true,
silent_preload: true,
dedup_threshold: 8,
consolidate_every_calls: 25,
consolidate_cooldown_secs: 120,
cognition_loop_enabled: true,
cognition_loop_interval_secs: 3600,
cognition_loop_max_steps: 8,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct UpdatesConfig {
pub auto_update: bool,
pub check_interval_hours: u64,
pub notify_only: bool,
}
impl Default for UpdatesConfig {
fn default() -> Self {
Self {
auto_update: false,
check_interval_hours: 6,
notify_only: false,
}
}
}
impl UpdatesConfig {
pub fn from_env() -> Self {
let mut cfg = Self::default();
if let Ok(v) = std::env::var("LEAN_CTX_AUTO_UPDATE") {
cfg.auto_update = v == "1" || v.eq_ignore_ascii_case("true");
}
if let Ok(v) = std::env::var("LEAN_CTX_UPDATE_INTERVAL_HOURS") {
if let Ok(h) = v.parse::<u64>() {
cfg.check_interval_hours = h.clamp(1, 168);
}
}
if let Ok(v) = std::env::var("LEAN_CTX_UPDATE_NOTIFY_ONLY") {
cfg.notify_only = v == "1" || v.eq_ignore_ascii_case("true");
}
cfg
}
}
impl AutonomyConfig {
pub fn from_env() -> Self {
let mut cfg = Self::default();
if let Ok(v) = std::env::var("LEAN_CTX_AUTONOMY") {
if v == "false" || v == "0" {
cfg.enabled = false;
}
}
if let Ok(v) = std::env::var("LEAN_CTX_AUTO_PRELOAD") {
cfg.auto_preload = v != "false" && v != "0";
}
if let Ok(v) = std::env::var("LEAN_CTX_AUTO_DEDUP") {
cfg.auto_dedup = v != "false" && v != "0";
}
if let Ok(v) = std::env::var("LEAN_CTX_AUTO_RELATED") {
cfg.auto_related = v != "false" && v != "0";
}
if let Ok(v) = std::env::var("LEAN_CTX_AUTO_CONSOLIDATE") {
cfg.auto_consolidate = v != "false" && v != "0";
}
if let Ok(v) = std::env::var("LEAN_CTX_SILENT_PRELOAD") {
cfg.silent_preload = v != "false" && v != "0";
}
if let Ok(v) = std::env::var("LEAN_CTX_DEDUP_THRESHOLD") {
if let Ok(n) = v.parse() {
cfg.dedup_threshold = n;
}
}
if let Ok(v) = std::env::var("LEAN_CTX_CONSOLIDATE_EVERY_CALLS") {
if let Ok(n) = v.parse() {
cfg.consolidate_every_calls = n;
}
}
if let Ok(v) = std::env::var("LEAN_CTX_CONSOLIDATE_COOLDOWN_SECS") {
if let Ok(n) = v.parse() {
cfg.consolidate_cooldown_secs = n;
}
}
if let Ok(v) = std::env::var("LEAN_CTX_COGNITION_LOOP_ENABLED") {
cfg.cognition_loop_enabled = v != "false" && v != "0";
}
if let Ok(v) = std::env::var("LEAN_CTX_COGNITION_LOOP_INTERVAL_SECS") {
if let Ok(n) = v.parse() {
cfg.cognition_loop_interval_secs = n;
}
}
if let Ok(v) = std::env::var("LEAN_CTX_COGNITION_LOOP_MAX_STEPS") {
if let Ok(n) = v.parse() {
cfg.cognition_loop_max_steps = n;
}
}
cfg
}
pub fn load() -> Self {
let file_cfg = Config::load().autonomy;
let mut cfg = file_cfg;
if let Ok(v) = std::env::var("LEAN_CTX_AUTONOMY") {
if v == "false" || v == "0" {
cfg.enabled = false;
}
}
if let Ok(v) = std::env::var("LEAN_CTX_AUTO_PRELOAD") {
cfg.auto_preload = v != "false" && v != "0";
}
if let Ok(v) = std::env::var("LEAN_CTX_AUTO_DEDUP") {
cfg.auto_dedup = v != "false" && v != "0";
}
if let Ok(v) = std::env::var("LEAN_CTX_AUTO_RELATED") {
cfg.auto_related = v != "false" && v != "0";
}
if let Ok(v) = std::env::var("LEAN_CTX_SILENT_PRELOAD") {
cfg.silent_preload = v != "false" && v != "0";
}
if let Ok(v) = std::env::var("LEAN_CTX_DEDUP_THRESHOLD") {
if let Ok(n) = v.parse() {
cfg.dedup_threshold = n;
}
}
if let Ok(v) = std::env::var("LEAN_CTX_COGNITION_LOOP_ENABLED") {
cfg.cognition_loop_enabled = v != "false" && v != "0";
}
if let Ok(v) = std::env::var("LEAN_CTX_COGNITION_LOOP_INTERVAL_SECS") {
if let Ok(n) = v.parse() {
cfg.cognition_loop_interval_secs = n;
}
}
if let Ok(v) = std::env::var("LEAN_CTX_COGNITION_LOOP_MAX_STEPS") {
if let Ok(n) = v.parse() {
cfg.cognition_loop_max_steps = n;
}
}
cfg
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct CloudConfig {
pub contribute_enabled: bool,
pub last_contribute: Option<String>,
pub last_sync: Option<String>,
pub last_gain_sync: Option<String>,
pub last_model_pull: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct GainConfig {
pub auto_publish: bool,
pub leaderboard: bool,
pub display_name: Option<String>,
pub auto_publish_interval_hours: u64,
pub last_auto_publish: Option<String>,
}
impl Default for GainConfig {
fn default() -> Self {
Self {
auto_publish: false,
leaderboard: true,
display_name: None,
auto_publish_interval_hours: 24,
last_auto_publish: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AliasEntry {
pub command: String,
pub alias: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct LoopDetectionConfig {
pub normal_threshold: u32,
pub reduced_threshold: u32,
pub blocked_threshold: u32,
pub window_secs: u64,
pub search_group_limit: u32,
pub tool_total_limits: HashMap<String, u32>,
}
impl Default for LoopDetectionConfig {
fn default() -> Self {
let mut tool_total_limits = HashMap::new();
tool_total_limits.insert("ctx_read".to_string(), 100);
tool_total_limits.insert("ctx_search".to_string(), 80);
tool_total_limits.insert("ctx_shell".to_string(), 50);
tool_total_limits.insert("ctx_semantic_search".to_string(), 60);
Self {
normal_threshold: 2,
reduced_threshold: 4,
blocked_threshold: 0,
window_secs: 300,
search_group_limit: 10,
tool_total_limits,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct EmbeddingConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
}