1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::PathBuf;
9
10use crate::bridge::config as bridge_config;
11
12fn default_config_schema() -> String {
13 "https://acp-protocol.dev/schemas/v1/config.schema.json".to_string()
14}
15
16fn default_version() -> String {
17 "1.0.0".to_string()
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct Config {
24 #[serde(rename = "$schema", default = "default_config_schema")]
26 pub schema: String,
27
28 #[serde(default = "default_version")]
30 pub version: String,
31
32 #[serde(default = "default_include")]
34 pub include: Vec<String>,
35
36 #[serde(default = "default_exclude")]
38 pub exclude: Vec<String>,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub error_handling: Option<ErrorHandling>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub constraints: Option<ConstraintConfig>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub domains: Option<HashMap<String, DomainPatternConfig>>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub call_graph: Option<CallGraphConfig>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub limits: Option<LimitsConfig>,
59
60 #[serde(default = "default_root", skip_serializing_if = "is_default_root")]
63 pub root: PathBuf,
64
65 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub output: Option<OutputConfig>,
68
69 #[serde(default)]
71 pub bridge: bridge_config::BridgeConfig,
72
73 #[serde(default)]
75 pub annotate: AnnotateConfig,
76
77 #[serde(default)]
79 pub documentation: DocumentationConfig,
80}
81
82fn is_default_root(p: &std::path::Path) -> bool {
83 p == std::path::Path::new(".")
84}
85
86impl Default for Config {
87 fn default() -> Self {
88 Self {
89 schema: default_config_schema(),
90 version: default_version(),
91 include: default_include(),
92 exclude: default_exclude(),
93 error_handling: None,
94 constraints: None,
95 domains: None,
96 call_graph: None,
97 limits: None,
98 root: default_root(),
99 output: None,
100 bridge: bridge_config::BridgeConfig::default(),
101 annotate: AnnotateConfig::default(),
102 documentation: DocumentationConfig::default(),
103 }
104 }
105}
106
107impl Config {
108 pub fn load<P: AsRef<std::path::Path>>(path: P) -> crate::Result<Self> {
110 let content = std::fs::read_to_string(path)?;
111 Ok(serde_json::from_str(&content)?)
112 }
113
114 pub fn save<P: AsRef<std::path::Path>>(&self, path: P) -> crate::Result<()> {
116 let content = serde_json::to_string_pretty(self)?;
117 std::fs::write(path, content)?;
118 Ok(())
119 }
120
121 pub fn load_or_default() -> Self {
123 Self::load(".acp.config.json").unwrap_or_default()
124 }
125
126 pub fn cache_path(&self) -> PathBuf {
128 self.output
129 .as_ref()
130 .map(|o| o.cache.clone())
131 .unwrap_or_else(default_cache_path)
132 }
133
134 pub fn vars_path(&self) -> PathBuf {
136 self.output
137 .as_ref()
138 .map(|o| o.vars.clone())
139 .unwrap_or_else(default_vars_path)
140 }
141}
142
143fn default_root() -> PathBuf {
144 PathBuf::from(".")
145}
146
147fn default_include() -> Vec<String> {
148 vec![
149 "**/*.ts".to_string(),
150 "**/*.tsx".to_string(),
151 "**/*.js".to_string(),
152 "**/*.jsx".to_string(),
153 "**/*.rs".to_string(),
154 "**/*.py".to_string(),
155 "**/*.go".to_string(),
156 "**/*.java".to_string(),
157 ]
158}
159
160fn default_exclude() -> Vec<String> {
161 vec![
162 "**/node_modules/**".to_string(),
164 "**/vendor/**".to_string(),
165 "**/dist/**".to_string(),
167 "**/build/**".to_string(),
168 "**/target/**".to_string(),
169 "**/out/**".to_string(),
170 "**/.next/**".to_string(), "**/.nuxt/**".to_string(), "**/.output/**".to_string(), "**/.svelte-kit/**".to_string(), "**/.vite/**".to_string(), "**/.turbo/**".to_string(), "**/.cache/**".to_string(),
179 "**/coverage/**".to_string(),
180 "**/__pycache__/**".to_string(),
181 "**/.pytest_cache/**".to_string(),
182 "**/.git/**".to_string(),
184 "**/.idea/**".to_string(),
186 "**/.vscode/**".to_string(),
187 ]
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct ErrorHandling {
193 #[serde(default = "default_strictness")]
195 pub strictness: Strictness,
196
197 #[serde(default = "default_max_errors")]
199 pub max_errors: usize,
200
201 #[serde(default)]
203 pub auto_correct: bool,
204}
205
206fn default_strictness() -> Strictness {
207 Strictness::Permissive
208}
209
210fn default_max_errors() -> usize {
211 100
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
215#[serde(rename_all = "lowercase")]
216pub enum Strictness {
217 Permissive,
218 Strict,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct ConstraintConfig {
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub defaults: Option<ConstraintDefaults>,
227
228 #[serde(default)]
230 pub track_violations: bool,
231
232 #[serde(default = "default_audit_file")]
234 pub audit_file: String,
235}
236
237fn default_audit_file() -> String {
238 ".acp.violations.log".to_string()
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct ConstraintDefaults {
243 #[serde(default = "default_lock_level")]
245 pub lock: LockLevel,
246
247 #[serde(skip_serializing_if = "Option::is_none")]
249 pub style: Option<String>,
250
251 #[serde(default)]
253 pub behavior: Behavior,
254}
255
256fn default_lock_level() -> LockLevel {
257 LockLevel::Normal
258}
259
260#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
261#[serde(rename_all = "kebab-case")]
262#[derive(Default)]
263pub enum LockLevel {
264 Frozen,
265 Restricted,
266 ApprovalRequired,
267 TestsRequired,
268 DocsRequired,
269 ReviewRequired,
270 #[default]
271 Normal,
272 Experimental,
273}
274
275#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
276#[serde(rename_all = "lowercase")]
277pub enum Behavior {
278 Conservative,
279 #[default]
280 Balanced,
281 Aggressive,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct DomainPatternConfig {
287 pub patterns: Vec<String>,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
293pub struct CallGraphConfig {
294 #[serde(default)]
296 pub include_stdlib: bool,
297
298 #[serde(skip_serializing_if = "Option::is_none")]
300 pub max_depth: Option<usize>,
301
302 #[serde(default)]
304 pub exclude_patterns: Vec<String>,
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
309pub struct LimitsConfig {
310 #[serde(default = "default_max_file_size")]
312 pub max_file_size_mb: usize,
313
314 #[serde(default = "default_max_files")]
316 pub max_files: usize,
317
318 #[serde(default = "default_max_annotations")]
320 pub max_annotations_per_file: usize,
321
322 #[serde(default = "default_max_cache_size")]
324 pub max_cache_size_mb: usize,
325}
326
327fn default_max_file_size() -> usize {
328 10
329}
330
331fn default_max_files() -> usize {
332 100_000
333}
334
335fn default_max_annotations() -> usize {
336 1000
337}
338
339fn default_max_cache_size() -> usize {
340 100
341}
342
343#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct OutputConfig {
346 #[serde(default = "default_cache_path")]
348 pub cache: PathBuf,
349
350 #[serde(default = "default_vars_path")]
352 pub vars: PathBuf,
353
354 #[serde(default)]
356 pub sqlite: bool,
357}
358
359impl Default for OutputConfig {
360 fn default() -> Self {
361 Self {
362 cache: default_cache_path(),
363 vars: default_vars_path(),
364 sqlite: false,
365 }
366 }
367}
368
369fn default_cache_path() -> PathBuf {
370 PathBuf::from(".acp/acp.cache.json")
371}
372
373fn default_vars_path() -> PathBuf {
374 PathBuf::from(".acp/acp.vars.json")
375}
376
377fn default_true() -> bool {
382 true
383}
384
385fn default_review_threshold() -> f64 {
386 0.8
387}
388
389fn default_min_confidence() -> f64 {
390 0.5
391}
392
393#[derive(Debug, Clone, Default, Serialize, Deserialize)]
395pub struct AnnotateConfig {
396 #[serde(default)]
398 pub provenance: AnnotateProvenanceConfig,
399
400 #[serde(default)]
402 pub defaults: AnnotateDefaults,
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct AnnotateProvenanceConfig {
408 #[serde(default = "default_true")]
410 pub enabled: bool,
411
412 #[serde(default = "default_true", rename = "includeConfidence")]
414 pub include_confidence: bool,
415
416 #[serde(default = "default_review_threshold", rename = "reviewThreshold")]
418 pub review_threshold: f64,
419
420 #[serde(default = "default_min_confidence", rename = "minConfidence")]
422 pub min_confidence: f64,
423}
424
425impl Default for AnnotateProvenanceConfig {
426 fn default() -> Self {
427 Self {
428 enabled: true,
429 include_confidence: true,
430 review_threshold: 0.8,
431 min_confidence: 0.5,
432 }
433 }
434}
435
436#[derive(Debug, Clone, Default, Serialize, Deserialize)]
438pub struct AnnotateDefaults {
439 #[serde(default, rename = "markNeedsReview")]
441 pub mark_needs_review: bool,
442
443 #[serde(default, rename = "overwriteExisting")]
445 pub overwrite_existing: bool,
446}
447
448#[derive(Debug, Clone, Default, Serialize, Deserialize)]
454pub struct DocumentationConfig {
455 #[serde(default, rename = "approvedSources")]
457 pub approved_sources: Vec<ApprovedSource>,
458
459 #[serde(default, rename = "styleGuides")]
461 pub style_guides: HashMap<String, StyleGuideDefinition>,
462
463 #[serde(default)]
465 pub defaults: DocumentationDefaults,
466
467 #[serde(default)]
469 pub validation: DocumentationValidation,
470}
471
472#[derive(Debug, Clone, Serialize, Deserialize)]
474pub struct ApprovedSource {
475 pub id: String,
477
478 pub url: String,
480
481 #[serde(skip_serializing_if = "Option::is_none")]
483 pub version: Option<String>,
484
485 #[serde(skip_serializing_if = "Option::is_none")]
487 pub description: Option<String>,
488
489 #[serde(default)]
491 pub sections: HashMap<String, String>,
492
493 #[serde(default = "default_true")]
495 pub fetchable: bool,
496
497 #[serde(skip_serializing_if = "Option::is_none", rename = "lastVerified")]
499 pub last_verified: Option<String>,
500}
501
502#[derive(Debug, Clone, Default, Serialize, Deserialize)]
504pub struct StyleGuideDefinition {
505 #[serde(skip_serializing_if = "Option::is_none")]
507 pub extends: Option<String>,
508
509 #[serde(skip_serializing_if = "Option::is_none")]
511 pub source: Option<String>,
512
513 #[serde(skip_serializing_if = "Option::is_none")]
515 pub url: Option<String>,
516
517 #[serde(skip_serializing_if = "Option::is_none")]
519 pub description: Option<String>,
520
521 #[serde(default)]
523 pub languages: Vec<String>,
524
525 #[serde(default)]
527 pub rules: Vec<String>,
528
529 #[serde(default, rename = "filePatterns")]
531 pub file_patterns: Vec<String>,
532}
533
534#[derive(Debug, Clone, Default, Serialize, Deserialize)]
536pub struct DocumentationDefaults {
537 #[serde(default, rename = "fetchRefs")]
539 pub fetch_refs: bool,
540
541 #[serde(skip_serializing_if = "Option::is_none")]
543 pub style: Option<String>,
544}
545
546#[derive(Debug, Clone, Serialize, Deserialize)]
548pub struct DocumentationValidation {
549 #[serde(default, rename = "requireApprovedSources")]
551 pub require_approved_sources: bool,
552
553 #[serde(default = "default_true", rename = "warnUnknownStyle")]
555 pub warn_unknown_style: bool,
556}
557
558impl Default for DocumentationValidation {
559 fn default() -> Self {
560 Self {
561 require_approved_sources: false,
562 warn_unknown_style: true,
563 }
564 }
565}