Skip to main content

code_repo_wiki/config/
schema.rs

1use serde::{Deserialize, Serialize};
2
3/// v22 硬编码常量:以下配置项属「算法内部细节 / 无调优需求 / 必填负担」,
4/// 从配置文件移除、以代码常量固定,减少用户配置心智负担(用户拍板:
5/// 推荐 10 项全部硬编码)。如需调整须改代码重新编译。
6pub const LLM_MAX_CONCURRENT: usize = 16;
7/// None=模型默认,不随请求发送
8pub const EMBED_BATCH_SIZE: usize = 20;
9/// 索引目录,相对 output.dir
10pub const SEARCH_INDEX_DIR: &str = ".search";
11/// 默认搜索引擎:v36 起为 Hybrid(BM25 召回 + 向量语义 + RRF 融合 + 调用链
12/// 补全)。个人仓库场景下混合召回显著优于单一引擎,且
13/// 无 embed key 时 hybrid 自动降级纯 text(search 层已验证),默认值
14/// 不会让无 key 用户受损。
15pub const SEARCH_DEFAULT_ENGINE: SearchEngineType = SearchEngineType::Hybrid;
16pub const SEARCH_DEFAULT_TOP_K: usize = 10;
17/// RRF 融合常数 k(控制排序权重衰减)
18pub const SEARCH_RRF_K: f64 = 60.0;
19/// BFS 传播变更影响的最大深度
20pub const IMPACT_MAX_DEPTH: usize = 3;
21/// v30 硬编码常量:傻瓜式全自动(用户拍板「彻底硬编码删字段」)——
22/// 以下配置项从配置文件移除、以代码常量固定,用户零配置开箱即用。
23pub const OUTPUT_DIR: &str = ".code-repo-wiki";
24
25/// 全局配置
26///
27/// v30 精简后的配置面:只保留「凭据 / 模型选择 / 主语言」三类
28/// 用户真正需要决策的项;输出目录、扫描范围、增量策略、搜索/嵌入开关、
29/// 计划文件等算法细节全部硬编码(见常量区与扫描器内置过滤)。
30#[derive(Debug, Clone, Serialize, Deserialize, Default)]
31pub struct WikiConfig {
32    #[serde(default)]
33    pub wiki: WikiSection,
34    #[serde(default)]
35    pub llm: LlmSection,
36    #[serde(default)]
37    pub embed: EmbedSection,
38    /// 运行时输出目录(serde(skip):配置文件中不可写,由
39    /// load_config_with_output 注入——CLI --output 覆盖或 root 化后的
40    /// 绝对路径;None 时由 output_dir() 方法兜底硬编码常量)。
41    /// 使用场景:bench 跑分把产物写到隔离目录,不污染真实 .code-repo-wiki。
42    #[serde(skip)]
43    pub output_dir: Option<std::path::PathBuf>,
44}
45
46impl WikiConfig {
47    /// 输出目录解析:运行时注入优先(--output 覆盖 / root 化),
48    /// 缺省回退硬编码常量 OUTPUT_DIR(相对当前工作目录)。
49    pub fn output_dir(&self) -> &std::path::Path {
50        match &self.output_dir {
51            Some(p) => p,
52            None => std::path::Path::new(crate::config::schema::OUTPUT_DIR),
53        }
54    }
55}
56
57/// Wiki 基本配置(v30:多语言扩展已删除——恒只生成主语言,避免维护
58/// 多语言产物面;如需其他语言改 language 主键即可;缺键默认 zh)
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct WikiSection {
61    #[serde(default = "default_language")]
62    pub language: String,
63    /// v32 9.1:生成引导段([wiki.guide])——空=现行为零破坏。
64    /// 缺段或缺键时全部回退空 Vec,不报错(傻瓜式零配置原则)。
65    #[serde(default)]
66    pub guide: WikiGuideSection,
67}
68
69/// v32 9.1 生成引导([wiki.guide]):
70/// - `pages`:要生成的模块页路径前缀白名单(空=全部模块)。匹配按
71///   模块路径前缀(如 "src/net" 匹配 "src/net/tcp.rs" 模块页);未匹配
72///   的模块不生成独立页,但仍保留 overview 汇总;全部为空匹配时报错。
73/// - `priority`:模块页确定性排序列表(优先在前的路径前缀),用于把
74///   核心模块排在文档前面;不在列表中的模块保持默认顺序。
75/// - `notes`:注入模块页生成 prompt 的引导说明(逐条列出),引导 LLM
76///   按项目约定撰写页面内容(如命名规范、必写小节、注意事项)。
77#[derive(Debug, Clone, Serialize, Deserialize, Default)]
78pub struct WikiGuideSection {
79    #[serde(default)]
80    pub pages: Vec<String>,
81    #[serde(default)]
82    pub priority: Vec<String>,
83    #[serde(default)]
84    pub notes: Vec<String>,
85}
86
87fn default_language() -> String {
88    "zh".to_string()
89}
90
91impl Default for WikiSection {
92    fn default() -> Self {
93        Self {
94            language: "zh".to_string(),
95            guide: WikiGuideSection::default(),
96        }
97    }
98}
99
100/// LLM 提供商配置(v30:字段级 serde 默认=Default 阵营——缺键即用
101/// 默认可用组合,项目级配置可只写想覆盖的键)
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct LlmSection {
104    #[serde(default = "default_llm_provider")]
105    pub provider: LlmProviderType,
106    #[serde(default = "default_llm_model")]
107    pub model: String,
108    #[serde(default = "default_llm_base_url")]
109    pub base_url: Option<String>,
110    /// 直接指定 API Key(优先级高于 api_key_env)
111    #[serde(default)]
112    pub api_key: Option<String>,
113    /// 从环境变量读取 API Key(当 api_key 为 None 时使用)
114    #[serde(default = "default_llm_api_key_env")]
115    pub api_key_env: String,
116}
117
118fn default_llm_model() -> String {
119    "deepseek-v4-flash".to_string()
120}
121
122fn default_llm_provider() -> LlmProviderType {
123    LlmProviderType::OpenAiCompatible
124}
125
126fn default_llm_base_url() -> Option<String> {
127    Some("https://opencode.ai/zen/go/v1".to_string())
128}
129
130fn default_llm_api_key_env() -> String {
131    "OPENCODEGO2_API_KEY".to_string()
132}
133
134impl Default for LlmSection {
135    fn default() -> Self {
136        // v29 用户确认的实际可用配置:opencode 网关(openai-compatible 协议
137        // chat/completions)——schema 缺省填充与模板(config.toml)
138        // 严格同源,保证项目级 config.toml 缺 [llm] 段合并回退时仍可用。
139        // 不得回退为旧阵营(openai 协议 + DeepSeek 官方端点 + DEEPSEEK_API_KEY):
140        // 那是初始示例,实际不可用(v28 t11 实测端点断裂)。
141        Self {
142            provider: LlmProviderType::OpenAiCompatible,
143            model: "deepseek-v4-flash".to_string(),
144            base_url: Some("https://opencode.ai/zen/go/v1".to_string()),
145            api_key: None,
146            api_key_env: "OPENCODEGO2_API_KEY".to_string(),
147        }
148    }
149}
150
151/// LLM Provider 类型(v17 t02 拆分:协议按 provider 类型显式绑定)
152///
153/// - `openai`:OpenAI **Responses API** 协议(base_url 可配——DeepSeek 等
154///   支持 Responses 的服务通过 base_url 接入;默认官方端点)
155/// - `openai-compatible`:**chat/completions** 协议(OpenAI 兼容端点:
156///   阿里云/自建/无 /responses 的服务;v17 起 custom 并入此值)
157/// - `anthropic`:Anthropic Messages API(不变)
158/// - `mock`:本地模拟(测试/CI/无 Key 演示,不触网)
159///
160/// 拆分原因:Responses 与 chat/completions 的请求/响应/SSE 差异大,
161/// 且不是所有 OpenAI 兼容端点都提供 /responses——按 provider 显式绑定
162/// 协议,避免"无脑默认切换"破坏兼容端点。
163#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
164pub enum LlmProviderType {
165    #[serde(rename = "openai")]
166    OpenAI,
167    #[serde(rename = "openai-compatible")]
168    OpenAiCompatible,
169    #[serde(rename = "anthropic")]
170    Anthropic,
171    /// 本地模拟 Provider(测试/CI/无 API Key 演示),
172    /// 返回固定文本,不发起任何网络请求。
173    #[serde(rename = "mock")]
174    Mock,
175}
176
177/// 嵌入模型配置(v30:enabled 开关已硬编码恒开启——无 Key 环境由
178/// 运行时降级处理,见 lib.rs attach_features 与 build_search_index;
179/// 字段级 serde 默认=Default 阵营,缺键即用默认可用组合)
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct EmbedSection {
182    #[serde(default = "default_embed_model")]
183    pub model: String,
184    #[serde(default = "default_embed_base_url")]
185    pub base_url: Option<String>,
186    #[serde(default)]
187    pub api_key: Option<String>,
188    #[serde(default = "default_embed_api_key_env")]
189    pub api_key_env: String,
190}
191
192fn default_embed_model() -> String {
193    "qwen3.7-text-embedding".to_string()
194}
195
196fn default_embed_base_url() -> Option<String> {
197    Some(
198        "https://llm-q0265e4he9m0qs23.cn-beijing.maas.aliyuncs.com/compatible-mode/v1"
199            .to_string(),
200    )
201}
202
203fn default_embed_api_key_env() -> String {
204    "BAILIAN_API_KEY".to_string()
205}
206
207impl Default for EmbedSection {
208    fn default() -> Self {
209        // v29 用户确认的实际可用配置:阿里百炼兼容端点。schema 缺省与模板
210        // 同源(model/base_url/api_key_env 三键),合并回退时嵌入仍可用。
211        Self {
212            model: "qwen3.7-text-embedding".to_string(),
213            base_url: Some(
214                "https://llm-q0265e4he9m0qs23.cn-beijing.maas.aliyuncs.com/compatible-mode/v1"
215                    .to_string(),
216            ),
217            api_key: None,
218            api_key_env: "BAILIAN_API_KEY".to_string(),
219        }
220    }
221}
222
223/// 搜索引擎类型
224#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
225pub enum SearchEngineType {
226    /// BM25 全文搜索
227    #[serde(rename = "text")]
228    Text,
229    /// 向量语义搜索
230    #[serde(rename = "semantic")]
231    Semantic,
232    /// RRF 混合排序
233    #[serde(rename = "hybrid")]
234    Hybrid,
235}