use figment::{
Figment,
providers::{Env, Format, Serialized, Toml},
};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
mod defaults;
mod init;
mod paths;
use defaults::*;
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Settings {
#[serde(default = "default_version")]
pub version: u32,
#[serde(default = "default_index_path")]
pub index_path: PathBuf,
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace_root: Option<PathBuf>,
#[serde(default)]
pub indexing: IndexingConfig,
#[serde(skip)]
pub indexed_paths_cache: Vec<PathBuf>,
#[serde(default)]
pub languages: IndexMap<String, LanguageConfig>,
#[serde(default)]
pub mcp: McpConfig,
#[serde(default)]
pub semantic_search: SemanticSearchConfig,
#[serde(default)]
pub file_watch: FileWatchConfig,
#[serde(default)]
pub server: ServerConfig,
#[serde(default)]
pub logging: LoggingConfig,
#[serde(default)]
pub guidance: GuidanceConfig,
#[serde(default)]
pub documents: crate::documents::DocumentsConfig,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct IndexingConfig {
#[serde(default = "default_parallelism")]
pub parallelism: usize,
#[serde(default = "default_tantivy_heap_mb")]
pub tantivy_heap_mb: usize,
#[serde(default = "default_max_retry_attempts")]
pub max_retry_attempts: u32,
#[serde(default)]
pub ignore_patterns: Vec<String>,
#[serde(default)]
pub indexed_paths: Vec<PathBuf>,
#[serde(default = "default_batch_size")]
pub batch_size: usize,
#[serde(default = "default_batches_per_commit")]
pub batches_per_commit: usize,
#[serde(default)]
pub pipeline_tracing: bool,
#[serde(default = "default_true")]
pub show_progress: bool,
}
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Default)]
#[serde(rename_all = "kebab-case")]
pub enum SourceLayout {
#[default]
Jvm,
StandardKmp,
FlatKmp,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ProjectConfig {
pub config_file: PathBuf,
#[serde(default)]
pub source_layout: SourceLayout,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct LanguageConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default)]
pub extensions: Vec<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub parser_options: HashMap<String, serde_json::Value>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub config_files: Vec<PathBuf>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub projects: Vec<ProjectConfig>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct McpConfig {
#[serde(default = "default_max_context_size")]
pub max_context_size: usize,
#[serde(default)]
pub allowed_hosts: Option<Vec<String>>,
#[serde(default)]
pub allowed_origins: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct SemanticSearchConfig {
#[serde(default = "default_false")]
pub enabled: bool,
#[serde(default = "default_embedding_model")]
pub model: String,
#[serde(default = "default_similarity_threshold")]
pub threshold: f32,
#[serde(default = "default_embedding_threads")]
pub embedding_threads: usize,
#[serde(default)]
pub remote_url: Option<String>,
#[serde(default)]
pub remote_model: Option<String>,
#[serde(default)]
pub remote_dim: Option<usize>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct FileWatchConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_debounce_ms")]
pub debounce_ms: u64,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ServerConfig {
#[serde(default = "default_server_mode")]
pub mode: String,
#[serde(default = "default_bind_address")]
pub bind: String,
#[serde(default = "default_watch_interval")]
pub watch_interval: u64,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct LoggingConfig {
#[serde(default = "default_log_level")]
pub default: String,
#[serde(default)]
pub modules: IndexMap<String, String>,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
default: default_log_level(),
modules: default_logging_modules(),
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GuidanceConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default)]
pub templates: IndexMap<String, GuidanceTemplate>,
#[serde(default)]
pub variables: IndexMap<String, String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GuidanceTemplate {
#[serde(skip_serializing_if = "Option::is_none")]
pub no_results: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub single_result: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub multiple_results: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub custom: Vec<GuidanceRange>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GuidanceRange {
pub min: usize,
pub max: Option<usize>,
pub template: String,
}
impl Default for Settings {
fn default() -> Self {
Self {
version: default_version(),
index_path: default_index_path(),
workspace_root: None,
indexing: IndexingConfig::default(),
indexed_paths_cache: Vec::new(),
languages: generate_language_defaults(), mcp: McpConfig::default(),
semantic_search: SemanticSearchConfig::default(),
file_watch: FileWatchConfig::default(),
server: ServerConfig::default(),
logging: LoggingConfig::default(),
guidance: GuidanceConfig::default(),
documents: crate::documents::DocumentsConfig::default(),
}
}
}
impl Default for IndexingConfig {
fn default() -> Self {
Self {
parallelism: default_parallelism(),
tantivy_heap_mb: default_tantivy_heap_mb(),
max_retry_attempts: default_max_retry_attempts(),
ignore_patterns: vec![
"target/**".to_string(),
"node_modules/**".to_string(),
".git/**".to_string(),
"*.generated.*".to_string(),
],
indexed_paths: Vec::new(),
batch_size: default_batch_size(),
batches_per_commit: default_batches_per_commit(),
pipeline_tracing: false,
show_progress: true,
}
}
}
impl Default for McpConfig {
fn default() -> Self {
Self {
max_context_size: default_max_context_size(),
allowed_hosts: None,
allowed_origins: None,
}
}
}
impl Default for SemanticSearchConfig {
fn default() -> Self {
Self {
enabled: true, model: default_embedding_model(),
threshold: default_similarity_threshold(),
embedding_threads: default_embedding_threads(),
remote_url: None,
remote_model: None,
remote_dim: None,
}
}
}
impl Default for FileWatchConfig {
fn default() -> Self {
Self {
enabled: true, debounce_ms: default_debounce_ms(),
}
}
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
mode: default_server_mode(),
bind: default_bind_address(),
watch_interval: default_watch_interval(),
}
}
}
impl Default for GuidanceConfig {
fn default() -> Self {
Self {
enabled: true,
templates: default_guidance_templates(),
variables: default_guidance_variables(),
}
}
}
impl Settings {
pub fn for_init() -> Result<Self, Box<dyn std::error::Error>> {
let settings = Self {
workspace_root: Some(std::env::current_dir()?),
..Self::default()
};
Ok(settings)
}
pub fn load() -> Result<Self, Box<figment::Error>> {
let local_dir = crate::init::local_dir_name();
let config_path = Self::find_workspace_config()
.unwrap_or_else(|| PathBuf::from(local_dir).join("settings.toml"));
Figment::new()
.merge(Serialized::defaults(Settings::default()))
.merge(Toml::file(config_path))
.merge(Env::prefixed("CI_").map(|key| {
key.as_str()
.to_lowercase()
.replace("__", ".") .into()
}))
.extract()
.map_err(Box::new)
.map(|mut settings: Settings| {
if settings.workspace_root.is_none() {
settings.workspace_root = Self::workspace_root();
}
settings.sync_indexed_path_cache();
settings
})
}
pub fn find_workspace_config() -> Option<PathBuf> {
let current = std::env::current_dir().ok()?;
let local_dir = crate::init::local_dir_name();
for ancestor in current.ancestors() {
let config_dir = ancestor.join(local_dir);
if config_dir.exists() && config_dir.is_dir() {
return Some(config_dir.join("settings.toml"));
}
}
None
}
pub fn check_init() -> Result<(), String> {
let config_path = if let Some(path) = Self::find_workspace_config() {
path
} else {
PathBuf::from(".codanna/settings.toml")
};
if !config_path.exists() {
return Err("No configuration file found".to_string());
}
match std::fs::read_to_string(&config_path) {
Ok(content) => {
if let Err(e) = toml::from_str::<Settings>(&content) {
return Err(format!(
"Configuration file is corrupted: {e}\nRun 'codanna init --force' to regenerate."
));
}
}
Err(e) => {
return Err(format!("Cannot read configuration file: {e}"));
}
}
Ok(())
}
pub fn workspace_root() -> Option<PathBuf> {
let current = std::env::current_dir().ok()?;
let local_dir = crate::init::local_dir_name();
for ancestor in current.ancestors() {
let config_dir = ancestor.join(local_dir);
if config_dir.exists() && config_dir.is_dir() {
return Some(ancestor.to_path_buf());
}
}
None
}
pub fn load_from(path: impl AsRef<std::path::Path>) -> Result<Self, Box<figment::Error>> {
Figment::new()
.merge(Serialized::defaults(Settings::default()))
.merge(Toml::file(path))
.merge(Env::prefixed("CI_").split("_"))
.extract()
.map(|mut settings: Settings| {
settings.sync_indexed_path_cache();
settings
})
.map_err(Box::new)
}
pub fn save(
&self,
path: impl AsRef<std::path::Path>,
) -> Result<(), Box<dyn std::error::Error>> {
let parent = path.as_ref().parent().ok_or("Invalid path")?;
std::fs::create_dir_all(parent)?;
let toml_string = toml::to_string_pretty(self)?;
let toml_with_comments = Self::add_config_comments(toml_string);
std::fs::write(path, toml_with_comments)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_guidance_templates_serialize_in_insertion_order() {
let settings = Settings::default();
let toml_string = toml::to_string_pretty(&settings).unwrap();
let expected_order = [
"semantic_search_docs",
"find_symbol",
"get_calls",
"find_callers",
"analyze_impact",
"search_symbols",
"semantic_search_with_context",
"get_index_info",
];
let positions: Vec<usize> = expected_order
.iter()
.map(|name| {
let header = format!("[guidance.templates.{name}]");
toml_string
.find(&header)
.unwrap_or_else(|| panic!("missing table header {header}"))
})
.collect();
assert!(
positions.windows(2).all(|w| w[0] < w[1]),
"guidance templates serialized out of insertion order: {positions:?}"
);
}
#[test]
fn test_default_settings() {
let settings = Settings::default();
assert_eq!(settings.version, 1);
let expected_index_path = PathBuf::from(format!("{}/index", crate::init::local_dir_name()));
assert_eq!(settings.index_path, expected_index_path);
assert!(settings.indexing.parallelism > 0);
assert!(settings.languages.contains_key("rust"));
}
#[test]
fn test_load_from_toml() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("settings.toml");
let toml_content = r#"
version = 2
[indexing]
parallelism = 4
ignore_patterns = ["custom/**"]
include_tests = false
[mcp]
max_context_size = 200000
[languages.rust]
enabled = false
"#;
fs::write(&config_path, toml_content).unwrap();
let settings = Settings::load_from(&config_path).unwrap();
assert_eq!(settings.version, 2);
assert_eq!(settings.indexing.parallelism, 4);
assert_eq!(settings.indexing.ignore_patterns, vec!["custom/**"]);
assert_eq!(settings.indexing.ignore_patterns.len(), 1);
assert_eq!(settings.mcp.max_context_size, 200000);
assert!(!settings.languages["rust"].enabled);
}
#[test]
fn test_save_settings() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("settings.toml");
let mut settings = Settings::default();
settings.indexing.parallelism = 2;
settings.mcp.max_context_size = 50000;
settings.save(&config_path).unwrap();
let loaded = Settings::load_from(&config_path).unwrap();
assert_eq!(loaded.indexing.parallelism, 2);
assert_eq!(loaded.mcp.max_context_size, 50000);
}
#[test]
fn test_partial_config() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("settings.toml");
let toml_content = r#"
[indexing]
parallelism = 16
[languages.python]
enabled = true
"#;
fs::write(&config_path, toml_content).unwrap();
let settings = Settings::load_from(&config_path).unwrap();
assert_eq!(settings.indexing.parallelism, 16);
assert!(settings.languages["python"].enabled);
assert_eq!(settings.version, 1);
assert_eq!(settings.mcp.max_context_size, 100_000);
assert!(!settings.indexing.ignore_patterns.is_empty());
}
#[test]
#[ignore = "mutates process CWD via set_current_dir; races with subprocess tests under parallel execution; run via cargo test -- --ignored --test-threads=1"]
fn test_layered_config() {
let temp_dir = TempDir::new().unwrap();
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(&temp_dir).unwrap();
let config_dir = temp_dir.path().join(crate::init::local_dir_name());
fs::create_dir_all(&config_dir).unwrap();
let toml_content = r#"
[indexing]
parallelism = 8
include_tests = true
[mcp]
max_context_size = 50000
[logging]
default = "info"
"#;
fs::write(config_dir.join("settings.toml"), toml_content).unwrap();
unsafe {
std::env::set_var("CI_INDEXING__PARALLELISM", "16");
std::env::set_var("CI_LOGGING__DEFAULT", "debug");
}
let settings = Settings::load().unwrap();
assert_eq!(settings.indexing.parallelism, 16);
assert_eq!(settings.mcp.max_context_size, 50000);
assert_eq!(settings.logging.default, "debug");
assert!(!settings.indexing.ignore_patterns.is_empty());
unsafe {
std::env::remove_var("CI_INDEXING__PARALLELISM");
std::env::remove_var("CI_LOGGING__DEFAULT");
}
std::env::set_current_dir(original_dir).unwrap();
}
#[test]
fn test_file_watch_config_defaults() {
println!("\n=== TEST: FileWatchConfig Defaults ===");
let config = FileWatchConfig::default();
assert!(config.enabled); assert_eq!(config.debounce_ms, 500);
println!(
" ✓ Default config: enabled={}, debounce_ms={}",
config.enabled, config.debounce_ms
);
println!("=== TEST PASSED ===");
}
#[test]
fn test_file_watch_config_from_toml() {
println!("\n=== TEST: FileWatchConfig from TOML ===");
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("settings.toml");
let config_content = r#"
[file_watch]
enabled = true
debounce_ms = 1000
"#;
fs::write(&config_path, config_content).unwrap();
println!(" Created test config: {}", config_path.display());
let settings: Settings = Figment::new()
.merge(Serialized::defaults(Settings::default()))
.merge(Toml::file(config_path))
.extract()
.unwrap();
assert!(settings.file_watch.enabled);
assert_eq!(settings.file_watch.debounce_ms, 1000);
println!(
" ✓ Loaded config: enabled={}, debounce_ms={}",
settings.file_watch.enabled, settings.file_watch.debounce_ms
);
println!("=== TEST PASSED ===");
}
#[test]
fn test_file_watch_partial_config() {
println!("\n=== TEST: FileWatchConfig Partial Configuration ===");
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("settings.toml");
let config_content = r#"
[file_watch]
enabled = true
"#;
fs::write(&config_path, config_content).unwrap();
let settings: Settings = Figment::new()
.merge(Serialized::defaults(Settings::default()))
.merge(Toml::file(config_path))
.extract()
.unwrap();
assert!(settings.file_watch.enabled);
assert_eq!(settings.file_watch.debounce_ms, 500);
println!(
" ✓ Partial config works: enabled={}, debounce_ms={} (default)",
settings.file_watch.enabled, settings.file_watch.debounce_ms
);
println!("=== TEST PASSED ===");
}
#[test]
fn test_add_indexed_path() {
let temp_dir = TempDir::new().unwrap();
let test_folder = temp_dir.path().join("test_folder");
fs::create_dir(&test_folder).unwrap();
let mut settings = Settings::default();
assert!(settings.add_indexed_path(test_folder.clone()).is_ok());
assert_eq!(settings.indexing.indexed_paths.len(), 1);
let result = settings.add_indexed_path(test_folder.clone());
assert!(result.is_err());
assert_eq!(settings.indexing.indexed_paths.len(), 1);
}
#[test]
fn test_remove_indexed_path() {
let temp_dir = TempDir::new().unwrap();
let test_folder = temp_dir.path().join("test_folder");
fs::create_dir(&test_folder).unwrap();
let mut settings = Settings::default();
settings.add_indexed_path(test_folder.clone()).unwrap();
assert_eq!(settings.indexing.indexed_paths.len(), 1);
assert!(settings.remove_indexed_path(&test_folder).is_ok());
assert_eq!(settings.indexing.indexed_paths.len(), 0);
let result = settings.remove_indexed_path(&test_folder);
assert!(result.is_err());
}
#[test]
fn test_multiple_indexed_paths() {
let temp_dir = TempDir::new().unwrap();
let folder1 = temp_dir.path().join("folder1");
let folder2 = temp_dir.path().join("folder2");
let folder3 = temp_dir.path().join("folder3");
fs::create_dir(&folder1).unwrap();
fs::create_dir(&folder2).unwrap();
fs::create_dir(&folder3).unwrap();
let mut settings = Settings::default();
settings.add_indexed_path(folder1.clone()).unwrap();
settings.add_indexed_path(folder2.clone()).unwrap();
settings.add_indexed_path(folder3.clone()).unwrap();
assert_eq!(settings.indexing.indexed_paths.len(), 3);
settings.remove_indexed_path(&folder2).unwrap();
assert_eq!(settings.indexing.indexed_paths.len(), 2);
let canonical_folder1 = folder1.canonicalize().unwrap();
let canonical_folder3 = folder3.canonicalize().unwrap();
let remaining_paths: Vec<_> = settings
.indexing
.indexed_paths
.iter()
.filter_map(|p| p.canonicalize().ok())
.collect();
assert!(remaining_paths.contains(&canonical_folder1));
assert!(remaining_paths.contains(&canonical_folder3));
}
#[test]
fn test_add_indexed_path_skips_child_when_parent_exists() {
let temp_dir = TempDir::new().unwrap();
let parent = temp_dir.path().join("parent");
let child = parent.join("child");
fs::create_dir_all(&child).unwrap();
let mut settings = Settings::default();
settings.add_indexed_path(parent.clone()).unwrap();
assert_eq!(settings.indexing.indexed_paths.len(), 1);
let result = settings.add_indexed_path(child.clone());
assert!(result.is_err());
assert_eq!(settings.indexing.indexed_paths.len(), 1);
let error_message = result.unwrap_err();
assert!(
error_message.contains("already indexed"),
"expected duplicate error, got: {error_message}"
);
}
#[test]
fn test_add_indexed_path_replaces_children_when_adding_parent() {
let temp_dir = TempDir::new().unwrap();
let parent = temp_dir.path().join("parent");
let child = parent.join("child");
fs::create_dir_all(&child).unwrap();
let mut settings = Settings::default();
settings.add_indexed_path(child.clone()).unwrap();
assert_eq!(settings.indexing.indexed_paths.len(), 1);
settings.add_indexed_path(parent.clone()).unwrap();
assert_eq!(settings.indexing.indexed_paths.len(), 1);
let stored = settings
.indexing
.indexed_paths
.first()
.expect("expected parent path");
assert_eq!(stored, &parent.canonicalize().unwrap());
}
#[test]
fn test_get_indexed_paths_with_default() {
let settings = Settings::default();
let paths = settings.get_indexed_paths();
assert_eq!(paths.len(), 0);
}
#[test]
fn test_get_indexed_paths_with_configured_paths() {
let temp_dir = TempDir::new().unwrap();
let test_folder = temp_dir.path().join("test_folder");
fs::create_dir(&test_folder).unwrap();
let mut settings = Settings::default();
settings.add_indexed_path(test_folder.clone()).unwrap();
let paths = settings.get_indexed_paths();
assert_eq!(paths.len(), 1);
let canonical_test = test_folder.canonicalize().unwrap();
let canonical_returned = paths[0].canonicalize().unwrap();
assert_eq!(canonical_returned, canonical_test);
}
#[test]
fn test_indexed_paths_from_toml() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("settings.toml");
let test_folder1 = temp_dir.path().join("src");
let test_folder2 = temp_dir.path().join("lib");
fs::create_dir(&test_folder1).unwrap();
fs::create_dir(&test_folder2).unwrap();
let path1_str = test_folder1.display().to_string().replace('\\', "/");
let path2_str = test_folder2.display().to_string().replace('\\', "/");
let toml_content = format!(
r#"
version = 1
[indexing]
indexed_paths = ["{path1_str}", "{path2_str}"]
"#
);
fs::write(&config_path, toml_content).unwrap();
let settings = Settings::load_from(&config_path).unwrap();
assert_eq!(settings.indexing.indexed_paths.len(), 2);
assert_eq!(settings.indexing.indexed_paths[0], test_folder1);
assert_eq!(settings.indexing.indexed_paths[1], test_folder2);
}
#[test]
fn test_save_indexed_paths_to_toml() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("settings.toml");
let test_folder = temp_dir.path().join("test_folder");
fs::create_dir(&test_folder).unwrap();
let mut settings = Settings::default();
settings.add_indexed_path(test_folder.clone()).unwrap();
settings.save(&config_path).unwrap();
let loaded_settings = Settings::load_from(&config_path).unwrap();
assert_eq!(loaded_settings.indexing.indexed_paths.len(), 1);
let canonical_test = test_folder.canonicalize().unwrap();
let canonical_loaded = loaded_settings.indexing.indexed_paths[0]
.canonicalize()
.unwrap();
assert_eq!(canonical_loaded, canonical_test);
}
#[test]
fn test_documents_config_loading() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("settings.toml");
let toml_content = r#"
[documents]
enabled = true
[documents.defaults]
min_chunk_chars = 300
max_chunk_chars = 2000
overlap_chars = 150
[documents.collections.project-docs]
paths = ["docs/", "README.md"]
patterns = ["**/*.md"]
[documents.collections.external-books]
paths = ["/path/to/books"]
max_chunk_chars = 2500
"#;
fs::write(&config_path, toml_content).unwrap();
let settings = Settings::load_from(&config_path).unwrap();
assert!(settings.documents.enabled);
assert_eq!(settings.documents.defaults.min_chunk_chars, 300);
assert_eq!(settings.documents.defaults.max_chunk_chars, 2000);
assert_eq!(settings.documents.defaults.overlap_chars, 150);
assert_eq!(settings.documents.collections.len(), 2);
let project_docs = settings.documents.collections.get("project-docs").unwrap();
assert_eq!(project_docs.paths.len(), 2);
assert_eq!(project_docs.patterns, vec!["**/*.md"]);
let external = settings
.documents
.collections
.get("external-books")
.unwrap();
assert_eq!(external.max_chunk_chars, Some(2500));
}
#[test]
fn test_documents_config_defaults() {
let settings = Settings::default();
assert!(!settings.documents.enabled);
assert_eq!(settings.documents.defaults.min_chunk_chars, 200);
assert_eq!(settings.documents.defaults.max_chunk_chars, 1500);
assert_eq!(settings.documents.defaults.overlap_chars, 100);
assert!(settings.documents.collections.is_empty());
}
}