claudix 0.2.0

Local semantic search plugin for Claude Code
use serde::Deserialize;

use super::EmbeddingProvider;

#[derive(Debug, Clone, Default, Deserialize)]
pub struct PartialConfig {
    #[serde(default)]
    pub watch: Option<bool>,
    #[serde(default)]
    pub development_mode: Option<bool>,
    #[serde(default)]
    pub embedding: PartialEmbeddingConfig,
    #[serde(default)]
    pub indexing: PartialIndexingConfig,
    #[serde(default)]
    pub search: PartialSearchConfig,
    #[serde(default)]
    pub hooks: PartialHooksConfig,
    #[serde(default)]
    pub paths: PartialPathsConfig,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct PartialEmbeddingConfig {
    #[serde(default)]
    pub provider: Option<EmbeddingProvider>,
    #[serde(default)]
    pub endpoint: Option<String>,
    #[serde(default)]
    pub model: Option<String>,
    #[serde(default)]
    pub dimensions: Option<u16>,
    #[serde(default)]
    pub batch_size: Option<usize>,
    #[serde(default)]
    pub timeout_ms: Option<u64>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct PartialIndexingConfig {
    #[serde(default)]
    pub respect_gitignore: Option<bool>,
    #[serde(default)]
    pub follow_symlinks: Option<bool>,
    #[serde(default)]
    pub max_file_size_kb: Option<u64>,
    #[serde(default)]
    pub chunk_overlap_lines: Option<usize>,
    #[serde(default)]
    pub reindex_after_hours: Option<u64>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct PartialSearchConfig {
    #[serde(default)]
    pub top_k: Option<usize>,
    #[serde(default)]
    pub hybrid_weights: PartialHybridWeights,
    #[serde(default)]
    pub identifier_boost: Option<f32>,
    #[serde(default)]
    pub similarity_threshold: Option<f32>,
    #[serde(default)]
    pub min_score: Option<f32>,
    #[serde(default)]
    pub cross_repos: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct PartialHybridWeights {
    #[serde(default)]
    pub dense: Option<f32>,
    #[serde(default)]
    pub bm25: Option<f32>,
    #[serde(default)]
    pub rrf: Option<f32>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct PartialHooksConfig {
    #[serde(default)]
    pub intercept_grep: Option<bool>,
    #[serde(default)]
    pub auto_reembed_on_edit: Option<bool>,
    #[serde(default)]
    pub auto_index_on_session_start: Option<bool>,
    #[serde(default)]
    pub surface_related_on_edit: Option<bool>,
    #[serde(default)]
    pub surface_related_on_read: Option<bool>,
    #[serde(default)]
    pub related_top_k: Option<usize>,
    #[serde(default)]
    pub related_min_similarity: Option<f32>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct PartialPathsConfig {
    #[serde(default)]
    pub index_dir: Option<String>,
    #[serde(default)]
    pub log_dir: Option<String>,
}

impl PartialConfig {
    pub fn merge(self, other: Self) -> Self {
        Self {
            watch: other.watch.or(self.watch),
            development_mode: other.development_mode.or(self.development_mode),
            embedding: self.embedding.merge(other.embedding),
            indexing: self.indexing.merge(other.indexing),
            search: self.search.merge(other.search),
            hooks: self.hooks.merge(other.hooks),
            paths: self.paths.merge(other.paths),
        }
    }
}

impl PartialEmbeddingConfig {
    fn merge(self, other: Self) -> Self {
        Self {
            provider: other.provider.or(self.provider),
            endpoint: other.endpoint.or(self.endpoint),
            model: other.model.or(self.model),
            dimensions: other.dimensions.or(self.dimensions),
            batch_size: other.batch_size.or(self.batch_size),
            timeout_ms: other.timeout_ms.or(self.timeout_ms),
        }
    }
}

impl PartialIndexingConfig {
    fn merge(self, other: Self) -> Self {
        Self {
            respect_gitignore: other.respect_gitignore.or(self.respect_gitignore),
            follow_symlinks: other.follow_symlinks.or(self.follow_symlinks),
            max_file_size_kb: other.max_file_size_kb.or(self.max_file_size_kb),
            chunk_overlap_lines: other.chunk_overlap_lines.or(self.chunk_overlap_lines),
            reindex_after_hours: other.reindex_after_hours.or(self.reindex_after_hours),
        }
    }
}

impl PartialSearchConfig {
    fn merge(self, other: Self) -> Self {
        Self {
            top_k: other.top_k.or(self.top_k),
            hybrid_weights: self.hybrid_weights.merge(other.hybrid_weights),
            identifier_boost: other.identifier_boost.or(self.identifier_boost),
            similarity_threshold: other.similarity_threshold.or(self.similarity_threshold),
            min_score: other.min_score.or(self.min_score),
            cross_repos: other.cross_repos.or(self.cross_repos),
        }
    }
}

impl PartialHybridWeights {
    fn merge(self, other: Self) -> Self {
        Self {
            dense: other.dense.or(self.dense),
            bm25: other.bm25.or(self.bm25),
            rrf: other.rrf.or(self.rrf),
        }
    }
}

impl PartialHooksConfig {
    fn merge(self, other: Self) -> Self {
        Self {
            intercept_grep: other.intercept_grep.or(self.intercept_grep),
            auto_reembed_on_edit: other.auto_reembed_on_edit.or(self.auto_reembed_on_edit),
            auto_index_on_session_start: other
                .auto_index_on_session_start
                .or(self.auto_index_on_session_start),
            surface_related_on_edit: other
                .surface_related_on_edit
                .or(self.surface_related_on_edit),
            surface_related_on_read: other
                .surface_related_on_read
                .or(self.surface_related_on_read),
            related_top_k: other.related_top_k.or(self.related_top_k),
            related_min_similarity: other.related_min_similarity.or(self.related_min_similarity),
        }
    }
}

impl PartialPathsConfig {
    fn merge(self, other: Self) -> Self {
        Self {
            index_dir: other.index_dir.or(self.index_dir),
            log_dir: other.log_dir.or(self.log_dir),
        }
    }
}