gcop_rs/config/structs/app.rs
1//! Top-level application configuration and remaining command structures.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::error::{GcopError, Result};
8
9use super::commit::CommitConfig;
10use super::llm::LLMConfig;
11use super::network::NetworkConfig;
12
13/// Application configuration.
14///
15/// Top-level runtime configuration for `gcop-rs`.
16///
17/// Effective configuration is merged from multiple sources (low to high):
18/// 1. Rust defaults (`Default` + `serde(default)`)
19/// 2. User-level config file (platform-specific config directory)
20/// 3. Project-level config (`.gcop/config.toml`, discovered from repository root)
21/// 4. `GCOP__*` environment variables
22/// 5. CI mode overrides (`CI=1` + `GCOP_CI_*`)
23///
24/// # Configuration File Locations
25/// - Linux: `~/.config/gcop/config.toml`
26/// - macOS: `~/Library/Application Support/gcop/config.toml`
27/// - Windows: `%APPDATA%\gcop\config\config.toml`
28/// - Project level (optional): `<repo>/.gcop/config.toml`
29///
30/// # Example
31/// ```toml
32/// [llm]
33/// default_provider = "claude"
34/// fallback_providers = ["openai"]
35///
36/// [llm.providers.claude]
37/// api_key = "sk-ant-..."
38/// model = "claude-sonnet-4-5-20250929"
39///
40/// [commit]
41/// max_retries = 10
42/// show_diff_preview = true
43///
44/// [ui]
45/// colored = true
46/// ```
47#[derive(Debug, Clone, Deserialize, Serialize, Default)]
48pub struct AppConfig {
49 /// LLM provider and prompt settings.
50 #[serde(default)]
51 pub llm: LLMConfig,
52
53 /// Commit command behavior.
54 #[serde(default)]
55 pub commit: CommitConfig,
56
57 /// Review command behavior.
58 #[serde(default)]
59 pub review: ReviewConfig,
60
61 /// Terminal UI behavior.
62 #[serde(default)]
63 pub ui: UIConfig,
64
65 /// HTTP timeout and retry settings.
66 #[serde(default)]
67 pub network: NetworkConfig,
68
69 /// File I/O limits.
70 #[serde(default)]
71 pub file: FileConfig,
72
73 /// Workspace detection and scope inference (monorepo support).
74 #[serde(default)]
75 pub workspace: WorkspaceConfig,
76}
77
78impl AppConfig {
79 /// Validates configuration consistency.
80 pub fn validate(&self) -> Result<()> {
81 // Ensure the configured default provider exists.
82 if !self.llm.providers.is_empty()
83 && !self.llm.providers.contains_key(&self.llm.default_provider)
84 {
85 return Err(GcopError::Config(format!(
86 "default_provider '{}' not found in [llm.providers]",
87 self.llm.default_provider
88 )));
89 }
90
91 // Ensure all configured fallback providers exist.
92 for name in &self.llm.fallback_providers {
93 if !self.llm.providers.contains_key(name) {
94 return Err(GcopError::Config(format!(
95 "fallback_providers: '{}' not found in [llm.providers]",
96 name
97 )));
98 }
99 }
100
101 for (name, provider) in &self.llm.providers {
102 provider.validate(name)?;
103 }
104 self.network.validate()?;
105 Ok(())
106 }
107}
108
109/// Review command configuration.
110///
111/// Controls code-review behavior.
112///
113/// # Fields
114/// - `min_severity`: minimum issue severity shown in text output (`"info"`, `"warning"`, `"critical"`)
115/// - `custom_prompt`: review system prompt override (optional; JSON constraints are always appended)
116///
117/// # Example
118/// ```toml
119/// [review]
120/// min_severity = "warning"
121/// custom_prompt = "Focus on security issues"
122/// ```
123#[derive(Debug, Clone, Deserialize, Serialize)]
124pub struct ReviewConfig {
125 /// Minimum issue severity displayed in text output.
126 ///
127 /// Note: this filter currently applies only to `review --format text`.
128 /// `json` and `markdown` output keep the full issue list.
129 #[serde(default = "default_severity")]
130 pub min_severity: String,
131
132 /// Review system prompt override.
133 ///
134 /// The provided text replaces the default review system prompt.
135 /// JSON output constraints are always appended automatically.
136 ///
137 /// No placeholder substitution is performed (`{diff}` is passed literally).
138 #[serde(default)]
139 pub custom_prompt: Option<String>,
140}
141
142impl Default for ReviewConfig {
143 fn default() -> Self {
144 Self {
145 min_severity: "info".to_string(),
146 custom_prompt: None,
147 }
148 }
149}
150
151/// UI configuration.
152///
153/// Controls terminal display behavior.
154///
155/// # Fields
156/// - `colored`: enable colored output (default: `true`)
157/// - `streaming`: enable streaming output (typewriter effect, default: `true`)
158/// - `language`: UI language in BCP 47 format (for example `"en"`, `"zh-CN"`), auto-detected by default
159///
160/// # Example
161/// ```toml
162/// [ui]
163/// colored = true
164/// streaming = true
165/// language = "zh-CN"
166/// ```
167#[derive(Debug, Clone, Deserialize, Serialize)]
168pub struct UIConfig {
169 /// Whether to enable color output.
170 #[serde(default = "default_true")]
171 pub colored: bool,
172
173 /// Whether to enable streaming output (real-time typing effect).
174 #[serde(default = "default_true")]
175 pub streaming: bool,
176
177 /// UI language in BCP 47 format (for example `"en"`, `"zh-CN"`).
178 /// `None` means auto-detect from system locale.
179 #[serde(default)]
180 pub language: Option<String>,
181}
182
183impl Default for UIConfig {
184 fn default() -> Self {
185 Self {
186 colored: true,
187 streaming: true,
188 language: None,
189 }
190 }
191}
192
193/// File configuration.
194///
195/// Controls local file-read limits.
196///
197/// # Fields
198/// - `max_size`: max file size in bytes (default: 10 MiB)
199/// Used by `review file <PATH>` when reading workspace files.
200///
201/// # Example
202/// ```toml
203/// [file]
204/// max_size = 10485760 # 10MB
205/// ```
206#[derive(Debug, Clone, Deserialize, Serialize)]
207pub struct FileConfig {
208 /// Maximum file size in bytes.
209 ///
210 /// Current read limit for `review file <PATH>`.
211 #[serde(default = "default_max_file_size")]
212 pub max_size: u64,
213}
214
215impl Default for FileConfig {
216 fn default() -> Self {
217 Self {
218 max_size: default_max_file_size(),
219 }
220 }
221}
222
223/// Workspace configuration (monorepo support).
224///
225/// Controls workspace detection and scope inference.
226/// Auto-detection is enabled by default; this section is for manual overrides.
227///
228/// # Example
229/// ```toml
230/// [workspace]
231/// enabled = true
232/// members = ["packages/*", "apps/*"]
233/// scope_mappings = { "packages/core" = "core", "packages/ui" = "ui" }
234/// ```
235#[derive(Debug, Clone, Deserialize, Serialize)]
236pub struct WorkspaceConfig {
237 /// Whether workspace detection is enabled (default: `true`).
238 #[serde(default = "default_true")]
239 pub enabled: bool,
240
241 /// Manual scope mapping: package path -> scope name.
242 ///
243 /// Overrides automatically inferred package short names.
244 #[serde(default)]
245 pub scope_mappings: HashMap<String, String>,
246
247 /// Explicit workspace member globs.
248 ///
249 /// When set, auto-detection is skipped and this list is used directly.
250 #[serde(default)]
251 pub members: Option<Vec<String>>,
252}
253
254impl Default for WorkspaceConfig {
255 fn default() -> Self {
256 Self {
257 enabled: true,
258 scope_mappings: HashMap::new(),
259 members: None,
260 }
261 }
262}
263
264fn default_true() -> bool {
265 true
266}
267
268fn default_severity() -> String {
269 "info".to_string()
270}
271
272fn default_max_file_size() -> u64 {
273 10 * 1024 * 1024 // 10MB
274}