1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use std::path::PathBuf;
/// Runtime configuration for the aft process.
///
/// Holds project-scoped settings and tuning knobs. Values are set at startup
/// and remain immutable for the lifetime of the process.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SemanticBackend {
Fastembed,
OpenAiCompatible,
Ollama,
}
impl SemanticBackend {
pub const fn as_str(&self) -> &'static str {
match self {
Self::Fastembed => "fastembed",
Self::OpenAiCompatible => "openai_compatible",
Self::Ollama => "ollama",
}
}
pub fn from_name(name: &str) -> Option<Self> {
match name {
"fastembed" => Some(Self::Fastembed),
"openai_compatible" => Some(Self::OpenAiCompatible),
"ollama" => Some(Self::Ollama),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SemanticBackendConfig {
pub backend: SemanticBackend,
pub model: String,
pub base_url: Option<String>,
pub api_key_env: Option<String>,
pub timeout_ms: u64,
pub max_batch_size: usize,
}
impl Default for SemanticBackendConfig {
fn default() -> Self {
Self {
backend: SemanticBackend::Fastembed,
model: DEFAULT_SEMANTIC_MODEL.to_string(),
base_url: None,
api_key_env: None,
// Keep the default below the plugin bridge timeout to avoid bridge-killed
// semantic_search requests when callers do not set an explicit timeout.
timeout_ms: 25_000,
max_batch_size: 64,
}
}
}
pub const DEFAULT_SEMANTIC_MODEL: &str = "all-MiniLM-L6-v2";
impl Config {
pub fn semantic_backend_label(&self) -> &'static str {
self.semantic.backend.as_str()
}
}
pub struct Config {
/// Root directory of the project being analyzed. `None` if not scoped.
pub project_root: Option<PathBuf>,
/// How many levels of call-graph edges to follow during validation (default: 1).
pub validation_depth: u32,
/// Hours before a checkpoint expires and is eligible for cleanup (default: 24).
pub checkpoint_ttl_hours: u32,
/// Maximum depth for recursive symbol resolution (default: 10).
pub max_symbol_depth: u32,
/// Seconds before killing a formatter subprocess (default: 10).
pub formatter_timeout_secs: u32,
/// Seconds before killing a type-checker subprocess (default: 30).
pub type_checker_timeout_secs: u32,
/// Whether to auto-format files after edits (default: true).
pub format_on_edit: bool,
/// Whether to auto-validate files after edits (default: false).
/// When "syntax", only tree-sitter parse check. When "full", runs type checker.
pub validate_on_edit: Option<String>,
/// Per-language formatter overrides. Keys: "typescript", "python", "rust", "go".
/// Values: "biome", "prettier", "deno", "ruff", "black", "rustfmt", "goimports", "gofmt", "none".
pub formatter: std::collections::HashMap<String, String>,
/// Per-language type checker overrides. Keys: "typescript", "python", "rust", "go".
/// Values: "tsc", "biome", "pyright", "ruff", "cargo", "go", "staticcheck", "none".
pub checker: std::collections::HashMap<String, String>,
/// Whether to restrict file operations to within `project_root` (default: false).
/// When true, write-capable commands reject paths outside the project root.
pub restrict_to_project_root: bool,
/// Enable the experimental trigram search index (default: false).
pub experimental_search_index: bool,
/// Enable the experimental semantic search index (default: false).
pub experimental_semantic_search: bool,
/// Maximum file size to fully index in bytes (default: 1MB).
pub search_index_max_file_size: u64,
/// Maximum number of source files allowed for call-graph operations
/// (`callers`, `trace_to`, `trace_data`, `impact`). When a project
/// exceeds this count the reverse index is not built and those
/// commands return a `project_too_large` error. Does not affect
/// `grep`, `glob`, `read`, `edit`, or other non-callgraph features.
/// Default: 20_000 (covers typical monorepos; rejects OS-wide roots).
pub max_callgraph_files: usize,
pub semantic: SemanticBackendConfig,
/// Persistent storage directory for indexes (trigram, semantic).
/// Set by the plugin to the XDG-compliant path (e.g. ~/.local/share/opencode/storage/plugin/aft/).
/// Falls back to ~/.cache/aft/ if not set.
pub storage_dir: Option<PathBuf>,
}
impl Default for Config {
fn default() -> Self {
Config {
project_root: None,
validation_depth: 1,
checkpoint_ttl_hours: 24,
max_symbol_depth: 10,
formatter_timeout_secs: 10,
type_checker_timeout_secs: 30,
format_on_edit: true,
validate_on_edit: None,
formatter: std::collections::HashMap::new(),
checker: std::collections::HashMap::new(),
// Default to false to match OpenCode's existing permission-based model.
// The plugin opts into root restriction explicitly when desired.
restrict_to_project_root: false,
experimental_search_index: false,
experimental_semantic_search: false,
search_index_max_file_size: 1_048_576,
// Projects larger than this skip call-graph reverse index construction.
// Chosen to cover typical monorepos (AFT ~2K, OpenCode ~5K, Reth ~8K)
// while rejecting OS-wide roots (/home, ~/Work) that would otherwise
// walk hundreds of thousands of files per callers/trace_to query.
max_callgraph_files: 20_000,
semantic: SemanticBackendConfig::default(),
storage_dir: None,
}
}
}