Skip to main content

codemem_core/
config.rs

1//! Persistent configuration for Codemem.
2//!
3//! Loads/saves a TOML config at `~/.codemem/config.toml`.
4
5use crate::{CodememError, GraphConfig, ScoringWeights, VectorConfig};
6use serde::{Deserialize, Serialize};
7use std::path::{Path, PathBuf};
8
9/// Top-level Codemem configuration.
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11#[serde(default)]
12pub struct CodememConfig {
13    pub scoring: ScoringWeights,
14    pub vector: VectorConfig,
15    pub graph: GraphConfig,
16    pub embedding: EmbeddingConfig,
17    pub storage: StorageConfig,
18    pub chunking: ChunkingConfig,
19    pub enrichment: EnrichmentConfig,
20}
21
22impl CodememConfig {
23    /// Load configuration from the given path.
24    pub fn load(path: &Path) -> Result<Self, CodememError> {
25        let content = std::fs::read_to_string(path)?;
26        toml::from_str(&content).map_err(|e| CodememError::Config(e.to_string()))
27    }
28
29    /// Save configuration to the given path.
30    pub fn save(&self, path: &Path) -> Result<(), CodememError> {
31        let content =
32            toml::to_string_pretty(self).map_err(|e| CodememError::Config(e.to_string()))?;
33        if let Some(parent) = path.parent() {
34            std::fs::create_dir_all(parent)?;
35        }
36        std::fs::write(path, content)?;
37        Ok(())
38    }
39
40    /// Load from the default path, or return defaults if the file doesn't exist.
41    pub fn load_or_default() -> Self {
42        let path = Self::default_path();
43        if path.exists() {
44            Self::load(&path).unwrap_or_default()
45        } else {
46            Self::default()
47        }
48    }
49
50    /// Default config path: `~/.codemem/config.toml`.
51    pub fn default_path() -> PathBuf {
52        dirs::home_dir()
53            .unwrap_or_else(|| PathBuf::from("."))
54            .join(".codemem")
55            .join("config.toml")
56    }
57}
58
59/// Embedding provider configuration.
60#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(default)]
62pub struct EmbeddingConfig {
63    /// Provider name: "candle" (default), "ollama", or "openai".
64    pub provider: String,
65    /// Model name (provider-specific).
66    pub model: String,
67    /// API URL for remote providers.
68    pub url: String,
69    /// Embedding dimensions.
70    pub dimensions: usize,
71    /// LRU cache capacity.
72    pub cache_capacity: usize,
73}
74
75impl Default for EmbeddingConfig {
76    fn default() -> Self {
77        Self {
78            provider: "candle".to_string(),
79            model: "BAAI/bge-base-en-v1.5".to_string(),
80            url: String::new(),
81            dimensions: 768,
82            cache_capacity: 10_000,
83        }
84    }
85}
86
87/// Storage configuration.
88#[derive(Debug, Clone, Serialize, Deserialize)]
89#[serde(default)]
90pub struct StorageConfig {
91    /// Path to the database file.
92    pub db_path: String,
93    /// SQLite cache size in MB.
94    pub cache_size_mb: u32,
95    /// SQLite busy timeout in seconds.
96    pub busy_timeout_secs: u64,
97}
98
99impl Default for StorageConfig {
100    fn default() -> Self {
101        Self {
102            db_path: dirs::home_dir()
103                .unwrap_or_else(|| PathBuf::from("."))
104                .join(".codemem")
105                .join("codemem.db")
106                .to_string_lossy()
107                .into_owned(),
108            cache_size_mb: 64,
109            busy_timeout_secs: 5,
110        }
111    }
112}
113
114/// CST-aware code chunking configuration.
115#[derive(Debug, Clone, Serialize, Deserialize)]
116#[serde(default)]
117pub struct ChunkingConfig {
118    /// Whether chunking is enabled during indexing.
119    pub enabled: bool,
120    /// Maximum chunk size in non-whitespace characters.
121    pub max_chunk_size: usize,
122    /// Minimum chunk size in non-whitespace characters.
123    pub min_chunk_size: usize,
124    /// Whether to auto-compact the graph after indexing.
125    pub auto_compact: bool,
126    /// Maximum number of retained chunk graph-nodes per file after compaction.
127    pub max_retained_chunks_per_file: usize,
128    /// Minimum chunk score (0.0–1.0) to survive compaction.
129    pub min_chunk_score_threshold: f64,
130    /// Maximum number of retained symbol graph-nodes per file after compaction.
131    pub max_retained_symbols_per_file: usize,
132    /// Minimum symbol score (0.0–1.0) to survive compaction.
133    pub min_symbol_score_threshold: f64,
134}
135
136impl Default for ChunkingConfig {
137    fn default() -> Self {
138        Self {
139            enabled: true,
140            max_chunk_size: 1500,
141            min_chunk_size: 50,
142            auto_compact: true,
143            max_retained_chunks_per_file: 10,
144            min_chunk_score_threshold: 0.2,
145            max_retained_symbols_per_file: 15,
146            min_symbol_score_threshold: 0.15,
147        }
148    }
149}
150
151/// Enrichment pipeline configuration for controlling insight generation thresholds.
152#[derive(Debug, Clone, Serialize, Deserialize)]
153#[serde(default)]
154pub struct EnrichmentConfig {
155    /// Minimum commit count for a file to generate a high-activity insight.
156    pub git_min_commit_count: usize,
157    /// Minimum co-change count for a file pair to generate a coupling insight.
158    pub git_min_co_change_count: usize,
159    /// Minimum coupling degree for a node to generate a high-coupling insight.
160    pub perf_min_coupling_degree: usize,
161    /// Minimum symbol count for a file to generate a complexity insight.
162    pub perf_min_symbol_count: usize,
163    /// Default confidence for auto-generated insights.
164    pub insight_confidence: f64,
165    /// Cosine similarity threshold for deduplicating insights.
166    pub dedup_similarity_threshold: f64,
167}
168
169impl Default for EnrichmentConfig {
170    fn default() -> Self {
171        Self {
172            git_min_commit_count: 25,
173            git_min_co_change_count: 5,
174            perf_min_coupling_degree: 25,
175            perf_min_symbol_count: 30,
176            insight_confidence: 0.5,
177            dedup_similarity_threshold: 0.90,
178        }
179    }
180}
181
182#[cfg(test)]
183#[path = "tests/config_tests.rs"]
184mod tests;