reflex/
models.rs

1//! Core data models for Reflex
2//!
3//! These structures represent the normalized, deterministic output format
4//! that Reflex provides to AI agents and other programmatic consumers.
5
6use serde::{Deserialize, Serialize};
7use strum::{EnumString, Display};
8
9/// Represents a source code location span (line:col range)
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
11pub struct Span {
12    /// Starting line number (1-indexed)
13    pub start_line: usize,
14    /// Starting column number (0-indexed)
15    pub start_col: usize,
16    /// Ending line number (1-indexed)
17    pub end_line: usize,
18    /// Ending column number (0-indexed)
19    pub end_col: usize,
20}
21
22impl Span {
23    pub fn new(start_line: usize, start_col: usize, end_line: usize, end_col: usize) -> Self {
24        Self {
25            start_line,
26            start_col,
27            end_line,
28            end_col,
29        }
30    }
31}
32
33/// Type of symbol found in code
34#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, EnumString, Display)]
35#[strum(serialize_all = "PascalCase")]
36pub enum SymbolKind {
37    Function,
38    Class,
39    Struct,
40    Enum,
41    Interface,
42    Trait,
43    Constant,
44    Variable,
45    Method,
46    Module,
47    Namespace,
48    Type,
49    Macro,
50    Property,
51    Event,
52    Import,
53    Export,
54    /// Catch-all for symbol kinds not yet explicitly supported.
55    /// This ensures no data loss when encountering new tree-sitter node types.
56    /// The string contains the original kind name from the parser.
57    #[strum(default)]
58    Unknown(String),
59}
60
61/// Programming language identifier
62#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
63#[serde(rename_all = "lowercase")]
64pub enum Language {
65    Rust,
66    Python,
67    JavaScript,
68    TypeScript,
69    Vue,
70    Svelte,
71    Go,
72    Java,
73    PHP,
74    C,
75    Cpp,
76    CSharp,
77    Ruby,
78    Kotlin,
79    Swift,
80    Zig,
81    Unknown,
82}
83
84impl Language {
85    pub fn from_extension(ext: &str) -> Self {
86        match ext {
87            "rs" => Language::Rust,
88            "py" => Language::Python,
89            "js" | "mjs" | "cjs" | "jsx" => Language::JavaScript,
90            "ts" | "mts" | "cts" | "tsx" => Language::TypeScript,
91            "vue" => Language::Vue,
92            "svelte" => Language::Svelte,
93            "go" => Language::Go,
94            "java" => Language::Java,
95            "php" => Language::PHP,
96            "c" | "h" => Language::C,
97            "cpp" | "cc" | "cxx" | "hpp" | "hxx" | "C" | "H" => Language::Cpp,
98            "cs" => Language::CSharp,
99            "rb" | "rake" | "gemspec" => Language::Ruby,
100            "kt" | "kts" => Language::Kotlin,
101            "swift" => Language::Swift,
102            "zig" => Language::Zig,
103            _ => Language::Unknown,
104        }
105    }
106
107    /// Check if this language has a parser implementation
108    ///
109    /// Returns true only for languages with working Tree-sitter parsers.
110    /// This determines which files will be indexed by Reflex.
111    pub fn is_supported(&self) -> bool {
112        match self {
113            Language::Rust => true,
114            Language::TypeScript => true,
115            Language::JavaScript => true,
116            Language::Vue => true,
117            Language::Svelte => true,
118            Language::Python => true,
119            Language::Go => true,
120            Language::Java => true,
121            Language::PHP => true,
122            Language::C => true,
123            Language::Cpp => true,
124            Language::CSharp => true,
125            Language::Ruby => true,
126            Language::Kotlin => true,
127            Language::Swift => false,  // Temporarily disabled - requires tree-sitter 0.23
128            Language::Zig => true,
129            Language::Unknown => false,
130        }
131    }
132}
133
134/// A search result representing a symbol or code location
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct SearchResult {
137    /// Absolute or relative path to the file
138    pub path: String,
139    /// Detected programming language
140    pub lang: Language,
141    /// Type of symbol found
142    pub kind: SymbolKind,
143    /// Symbol name (e.g., function name, class name)
144    /// None for text/regex matches where symbol name cannot be accurately determined
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub symbol: Option<String>,
147    /// Location span in the source file
148    pub span: Span,
149    /// Scope context (e.g., "impl MyStruct", "class User")
150    pub scope: Option<String>,
151    /// Code preview (few lines around the match)
152    pub preview: String,
153}
154
155impl SearchResult {
156    pub fn new(
157        path: String,
158        lang: Language,
159        kind: SymbolKind,
160        symbol: Option<String>,
161        span: Span,
162        scope: Option<String>,
163        preview: String,
164    ) -> Self {
165        Self {
166            path,
167            lang,
168            kind,
169            symbol,
170            span,
171            scope,
172            preview,
173        }
174    }
175}
176
177/// Configuration for indexing behavior
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct IndexConfig {
180    /// Languages to include (empty = all supported)
181    pub languages: Vec<Language>,
182    /// Glob patterns to include
183    pub include_patterns: Vec<String>,
184    /// Glob patterns to exclude
185    pub exclude_patterns: Vec<String>,
186    /// Follow symbolic links
187    pub follow_symlinks: bool,
188    /// Maximum file size to index (bytes)
189    pub max_file_size: usize,
190    /// Number of threads for parallel indexing (0 = auto, 80% of available cores)
191    pub parallel_threads: usize,
192    /// Query timeout in seconds (0 = no timeout)
193    pub query_timeout_secs: u64,
194}
195
196impl Default for IndexConfig {
197    fn default() -> Self {
198        Self {
199            languages: vec![],
200            include_patterns: vec![],
201            exclude_patterns: vec![],
202            follow_symlinks: false,
203            max_file_size: 10 * 1024 * 1024, // 10 MB
204            parallel_threads: 0, // 0 = auto (80% of available cores)
205            query_timeout_secs: 30, // 30 seconds default timeout
206        }
207    }
208}
209
210/// Statistics about the index
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct IndexStats {
213    /// Total files indexed
214    pub total_files: usize,
215    /// Index size on disk (bytes)
216    pub index_size_bytes: u64,
217    /// Last update timestamp
218    pub last_updated: String,
219    /// File count breakdown by language
220    pub files_by_language: std::collections::HashMap<String, usize>,
221    /// Line count breakdown by language
222    pub lines_by_language: std::collections::HashMap<String, usize>,
223}
224
225/// Information about an indexed file
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct IndexedFile {
228    /// File path
229    pub path: String,
230    /// Detected language
231    pub language: String,
232    /// Last indexed timestamp
233    pub last_indexed: String,
234}
235
236/// Index status for query responses
237#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
238#[serde(rename_all = "snake_case")]
239pub enum IndexStatus {
240    /// Index is fresh and up-to-date
241    Fresh,
242    /// Index is stale (any issue: branch not indexed, commit changed, files modified)
243    Stale,
244}
245
246/// Warning details when index is stale
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct IndexWarning {
249    /// Human-readable reason why index is stale
250    pub reason: String,
251    /// Command to run to fix the issue
252    pub action_required: String,
253    /// Additional context (git branch info, etc.)
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub details: Option<IndexWarningDetails>,
256}
257
258/// Detailed information about index staleness
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct IndexWarningDetails {
261    /// Current branch (if in git repo)
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub current_branch: Option<String>,
264    /// Indexed branch (if in git repo)
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub indexed_branch: Option<String>,
267    /// Current commit SHA (if in git repo)
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub current_commit: Option<String>,
270    /// Indexed commit SHA (if in git repo)
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub indexed_commit: Option<String>,
273}
274
275/// Query response with results and index status
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct QueryResponse {
278    /// Status of the index (fresh or stale)
279    pub status: IndexStatus,
280    /// Whether the results can be trusted
281    pub can_trust_results: bool,
282    /// Warning information (only present if stale)
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub warning: Option<IndexWarning>,
285    /// Search results
286    pub results: Vec<SearchResult>,
287}