Skip to main content

aft/
config.rs

1use std::collections::{HashMap, HashSet};
2use std::path::PathBuf;
3
4/// Runtime configuration for the aft process.
5///
6/// Holds project-scoped settings and tuning knobs. Values are set at startup
7/// and remain immutable for the lifetime of the process.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum SemanticBackend {
10    Fastembed,
11    OpenAiCompatible,
12    Ollama,
13}
14
15impl SemanticBackend {
16    pub const fn as_str(&self) -> &'static str {
17        match self {
18            Self::Fastembed => "fastembed",
19            Self::OpenAiCompatible => "openai_compatible",
20            Self::Ollama => "ollama",
21        }
22    }
23
24    pub fn from_name(name: &str) -> Option<Self> {
25        match name {
26            "fastembed" => Some(Self::Fastembed),
27            "openai_compatible" => Some(Self::OpenAiCompatible),
28            "ollama" => Some(Self::Ollama),
29            _ => None,
30        }
31    }
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct SemanticBackendConfig {
36    pub backend: SemanticBackend,
37    pub model: String,
38    pub base_url: Option<String>,
39    pub api_key_env: Option<String>,
40    pub timeout_ms: u64,
41    pub max_batch_size: usize,
42}
43
44#[derive(Debug, Clone, Default, PartialEq, Eq)]
45pub struct UserServerDef {
46    pub id: String,
47    pub extensions: Vec<String>,
48    pub binary: String,
49    pub args: Vec<String>,
50    pub root_markers: Vec<String>,
51    pub env: HashMap<String, String>,
52    pub initialization_options: Option<serde_json::Value>,
53    pub disabled: bool,
54}
55
56impl Default for SemanticBackendConfig {
57    fn default() -> Self {
58        Self {
59            backend: SemanticBackend::Fastembed,
60            model: DEFAULT_SEMANTIC_MODEL.to_string(),
61            base_url: None,
62            api_key_env: None,
63            // Keep the default below the plugin bridge timeout to avoid bridge-killed
64            // semantic_search requests when callers do not set an explicit timeout.
65            timeout_ms: 25_000,
66            max_batch_size: 64,
67        }
68    }
69}
70
71pub const DEFAULT_SEMANTIC_MODEL: &str = "all-MiniLM-L6-v2";
72
73impl Config {
74    pub fn semantic_backend_label(&self) -> &'static str {
75        self.semantic.backend.as_str()
76    }
77}
78
79#[derive(Debug, Clone)]
80pub struct Config {
81    /// Root directory of the project being analyzed. `None` if not scoped.
82    pub project_root: Option<PathBuf>,
83    /// How many levels of call-graph edges to follow during validation (default: 1).
84    pub validation_depth: u32,
85    /// Hours before a checkpoint expires and is eligible for cleanup (default: 24).
86    pub checkpoint_ttl_hours: u32,
87    /// Maximum depth for recursive symbol resolution (default: 10).
88    pub max_symbol_depth: u32,
89    /// Seconds before killing a formatter subprocess (default: 10).
90    pub formatter_timeout_secs: u32,
91    /// Seconds before killing a type-checker subprocess (default: 30).
92    pub type_checker_timeout_secs: u32,
93    /// Whether to auto-format files after edits (default: true).
94    pub format_on_edit: bool,
95    /// Whether to auto-validate files after edits (default: false).
96    /// When "syntax", only tree-sitter parse check. When "full", runs type checker.
97    pub validate_on_edit: Option<String>,
98    /// Per-language formatter overrides. Keys: "typescript", "python", "rust", "go".
99    /// Values: "biome", "prettier", "deno", "ruff", "black", "rustfmt", "goimports", "gofmt", "none".
100    pub formatter: HashMap<String, String>,
101    /// Per-language type checker overrides. Keys: "typescript", "python", "rust", "go".
102    /// Values: "tsc", "biome", "pyright", "ruff", "cargo", "go", "staticcheck", "none".
103    pub checker: HashMap<String, String>,
104    /// Whether to restrict file operations to within `project_root` (default: false).
105    /// When true, write-capable commands reject paths outside the project root.
106    pub restrict_to_project_root: bool,
107    /// Enable the trigram search index (default: false).
108    pub search_index: bool,
109    /// Enable semantic search (default: false).
110    pub semantic_search: bool,
111    /// Enable experimental bash command rewriting (default: false).
112    pub experimental_bash_rewrite: bool,
113    /// Enable experimental bash command compression (default: false).
114    pub experimental_bash_compress: bool,
115    /// Enable experimental bash background execution (default: false).
116    pub experimental_bash_background: bool,
117    /// Maximum number of background bash tasks allowed to run concurrently (default: 8).
118    pub max_background_bash_tasks: usize,
119    /// Enable OpenCode-style bash permission prompts (default: false).
120    pub bash_permissions: bool,
121    /// Maximum file size to fully index in bytes (default: 1MB).
122    pub search_index_max_file_size: u64,
123    /// Maximum number of source files allowed for call-graph operations
124    /// (`callers`, `trace_to`, `trace_data`, `impact`). When a project
125    /// exceeds this count the reverse index is not built and those
126    /// commands return a `project_too_large` error. Does not affect
127    /// `grep`, `glob`, `read`, `edit`, or other non-callgraph features.
128    /// Default: 20_000 (covers typical monorepos; rejects OS-wide roots).
129    pub max_callgraph_files: usize,
130    pub semantic: SemanticBackendConfig,
131    /// Enable Astral ty as an experimental Python LSP server (default: false).
132    pub experimental_lsp_ty: bool,
133    /// User-defined LSP servers registered by the OpenCode plugin.
134    pub lsp_servers: Vec<UserServerDef>,
135    /// Lowercase LSP server IDs disabled by user config.
136    pub disabled_lsp: HashSet<String>,
137    /// Extra directories to search when resolving LSP binaries.
138    /// The plugin populates these from its own auto-install cache (e.g.
139    /// `~/.cache/aft/lsp-packages/<pkg>/node_modules/.bin/`) so a binary AFT
140    /// installed itself is discoverable without needing it on PATH.
141    /// Resolution order: `<project_root>/node_modules/.bin/<bin>` →
142    /// `lsp_paths_extra/<bin>` (in order) → PATH via `which`.
143    pub lsp_paths_extra: Vec<PathBuf>,
144    /// Binary names the hosting plugin knows how to auto-install.
145    ///
146    /// Built-in LSPs discovered from files only emit missing-binary warnings
147    /// when their binary is in this set. User-configured `lsp_servers` keep
148    /// warning unconditionally.
149    pub lsp_auto_install_binaries: HashSet<String>,
150    /// Binary names with plugin-managed auto-installs currently in flight.
151    ///
152    /// Missing-binary warnings are suppressed while the install is actively
153    /// running; install failure reporting is handled by the plugin after the
154    /// background work settles.
155    pub lsp_inflight_installs: HashSet<String>,
156    /// Persistent storage directory for indexes (trigram, semantic).
157    /// Set by the plugin to the XDG-compliant path (e.g. ~/.local/share/opencode/storage/plugin/aft/).
158    /// Falls back to ~/.cache/aft/ if not set.
159    pub storage_dir: Option<PathBuf>,
160    /// Maximum number of (server, file) entries kept in the in-memory
161    /// diagnostic cache. Older entries are evicted in LRU order when the
162    /// cap is exceeded. Set to 0 to disable the cap entirely.
163    /// Default: 5000 (covers very large monorepos with bounded memory).
164    pub diagnostic_cache_size: usize,
165}
166
167impl Default for Config {
168    fn default() -> Self {
169        Config {
170            project_root: None,
171            validation_depth: 1,
172            checkpoint_ttl_hours: 24,
173            max_symbol_depth: 10,
174            formatter_timeout_secs: 10,
175            type_checker_timeout_secs: 30,
176            format_on_edit: true,
177            validate_on_edit: None,
178            formatter: HashMap::new(),
179            checker: HashMap::new(),
180            // Default to false to match OpenCode's existing permission-based model.
181            // The plugin opts into root restriction explicitly when desired.
182            restrict_to_project_root: false,
183            search_index: false,
184            semantic_search: false,
185            experimental_bash_rewrite: false,
186            experimental_bash_compress: false,
187            experimental_bash_background: false,
188            max_background_bash_tasks: 8,
189            bash_permissions: false,
190            search_index_max_file_size: 1_048_576,
191            // Projects larger than this skip call-graph reverse index construction.
192            // Chosen to cover typical monorepos (AFT ~2K, OpenCode ~5K, Reth ~8K)
193            // while rejecting OS-wide roots (/home, ~/Work) that would otherwise
194            // walk hundreds of thousands of files per callers/trace_to query.
195            max_callgraph_files: 20_000,
196            semantic: SemanticBackendConfig::default(),
197            experimental_lsp_ty: false,
198            lsp_servers: Vec::new(),
199            disabled_lsp: HashSet::new(),
200            lsp_paths_extra: Vec::new(),
201            lsp_auto_install_binaries: HashSet::new(),
202            lsp_inflight_installs: HashSet::new(),
203            storage_dir: None,
204            diagnostic_cache_size: 5000,
205        }
206    }
207}