Skip to main content

harn_hostlib/scanner/
result.rs

1//! Owned data model for the scanner output. Mirrors the Swift
2//! `ScanResult`/`FileRecord`/`SymbolRecord`/etc. shape from
3//! `Sources/BurinCore/Scanner/RepoScannerModels.swift`.
4//!
5//! Fields are renamed `snake_case` (the JSON shape is also snake_case in
6//! schemas/scanner/scan_project.response.json — burin-code's consumers
7//! map between the two via Codable's coding keys).
8
9use serde::{Deserialize, Serialize};
10
11/// Coarse symbol kinds. Matches `SymbolKindRS` on the Swift side.
12#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
13#[serde(rename_all = "snake_case")]
14pub enum SymbolKind {
15    /// Free function.
16    Function,
17    /// Method (function attached to a type).
18    Method,
19    /// Class definition.
20    #[serde(rename = "class")]
21    ClassDecl,
22    /// Struct definition.
23    #[serde(rename = "struct")]
24    StructDecl,
25    /// Enum definition.
26    #[serde(rename = "enum")]
27    EnumDecl,
28    /// Protocol definition (Swift / Obj-C-style).
29    #[serde(rename = "protocol")]
30    ProtocolDecl,
31    /// Interface definition (Java / TypeScript).
32    #[serde(rename = "interface")]
33    InterfaceDecl,
34    /// Type alias.
35    #[serde(rename = "typealias")]
36    TypeAlias,
37    /// Property / field on a type.
38    Property,
39    /// Module-level variable.
40    Variable,
41    /// Module-level constant.
42    Constant,
43    /// Module / package marker.
44    Module,
45    /// `// MARK:`-style section header.
46    Mark,
47    /// `// TODO:` annotation.
48    Todo,
49    /// `// FIXME:` annotation.
50    Fixme,
51    /// Anything else.
52    Other,
53}
54
55impl SymbolKind {
56    /// True for kinds the importance scorer treats as "type definitions".
57    pub fn is_type_definition(self) -> bool {
58        matches!(
59            self,
60            SymbolKind::ClassDecl
61                | SymbolKind::StructDecl
62                | SymbolKind::EnumDecl
63                | SymbolKind::ProtocolDecl
64                | SymbolKind::InterfaceDecl
65        )
66    }
67
68    /// Lowercase keyword used by the repo-map text builder.
69    pub fn keyword(self) -> &'static str {
70        match self {
71            SymbolKind::Function => "function",
72            SymbolKind::Method => "method",
73            SymbolKind::ClassDecl => "class",
74            SymbolKind::StructDecl => "struct",
75            SymbolKind::EnumDecl => "enum",
76            SymbolKind::ProtocolDecl => "protocol",
77            SymbolKind::InterfaceDecl => "interface",
78            SymbolKind::TypeAlias => "typealias",
79            SymbolKind::Property => "property",
80            SymbolKind::Variable => "variable",
81            SymbolKind::Constant => "constant",
82            SymbolKind::Module => "module",
83            SymbolKind::Mark => "mark",
84            SymbolKind::Todo => "todo",
85            SymbolKind::Fixme => "fixme",
86            SymbolKind::Other => "other",
87        }
88    }
89}
90
91/// One file's metadata + import list.
92#[derive(Clone, Debug, Serialize, Deserialize)]
93pub struct FileRecord {
94    /// Stable id. Equal to `relative_path`.
95    pub id: String,
96    /// Repo-relative POSIX path.
97    pub relative_path: String,
98    /// Last path component.
99    pub file_name: String,
100    /// Lowercase extension (no dot) or `""`.
101    pub language: String,
102    /// Newline-counted line count.
103    pub line_count: usize,
104    /// Raw byte size on disk.
105    pub size_bytes: u64,
106    /// Last modification time, milliseconds since unix epoch (`0` if unknown).
107    pub last_modified_unix_ms: i64,
108    /// Module/path strings extracted by the import parser.
109    pub imports: Vec<String>,
110    /// Normalized 0..1 git-churn score (0 if `include_git_history=false`).
111    pub churn_score: f64,
112    /// Repo-relative path to a paired test file, if [`super::test_mapping`]
113    /// found one.
114    #[serde(skip_serializing_if = "Option::is_none", default)]
115    pub corresponding_test_file: Option<String>,
116}
117
118/// One symbol's metadata.
119#[derive(Clone, Debug, Serialize, Deserialize)]
120pub struct SymbolRecord {
121    /// Stable id of the form `{file}:{name}:{line}`.
122    pub id: String,
123    /// Symbol name.
124    pub name: String,
125    /// Symbol kind.
126    pub kind: SymbolKind,
127    /// File this symbol lives in (repo-relative POSIX).
128    pub file_path: String,
129    /// 1-indexed line number.
130    pub line: usize,
131    /// Optional signature snippet (truncated by the extractor).
132    pub signature: String,
133    /// Enclosing type name when the symbol is a method/property.
134    #[serde(skip_serializing_if = "Option::is_none", default)]
135    pub container: Option<String>,
136    /// Cross-file reference count derived from `imports`.
137    pub reference_count: usize,
138    /// Heuristic importance score (higher = more central).
139    pub importance_score: f64,
140}
141
142/// One folder's aggregate metadata.
143#[derive(Clone, Debug, Serialize, Deserialize)]
144pub struct FolderRecord {
145    /// Always equals `relative_path` (used by the Swift consumer for `Identifiable`).
146    pub id: String,
147    /// Folder path (`"."` for repo root).
148    pub relative_path: String,
149    /// Number of indexed files in the folder.
150    pub file_count: usize,
151    /// Sum of `line_count` across files in the folder.
152    pub line_count: usize,
153    /// Most-frequent language extension.
154    pub dominant_language: String,
155    /// Top 5 type-definition symbol names sorted by importance score.
156    pub key_symbol_names: Vec<String>,
157}
158
159/// Per-language summary.
160#[derive(Clone, Debug, Serialize, Deserialize)]
161pub struct LanguageStat {
162    /// Lowercase extension.
163    pub name: String,
164    /// Number of files.
165    pub file_count: usize,
166    /// Total lines across files.
167    pub line_count: usize,
168    /// Share of total project lines, in percent.
169    pub percentage: f64,
170}
171
172/// Project-level metadata.
173#[derive(Clone, Debug, Serialize, Deserialize)]
174pub struct ProjectMetadata {
175    /// Project name (last path component of root).
176    pub name: String,
177    /// Absolute root path.
178    pub root_path: String,
179    /// Per-language line/file/percentage breakdown, sorted desc by lines.
180    pub languages: Vec<LanguageStat>,
181    /// Map: command (e.g. `pnpm test`) → human-readable label.
182    pub test_commands: std::collections::BTreeMap<String, String>,
183    /// Best-guess preferred test command, if any.
184    pub detected_test_command: Option<String>,
185    /// Heuristic project pattern hints (e.g. detected ORM, Zod, etc.).
186    pub code_patterns: Vec<String>,
187    /// Total file count.
188    pub total_files: usize,
189    /// Total line count.
190    pub total_lines: usize,
191    /// ISO-8601 UTC timestamp when scanning finished.
192    pub last_scanned_at: String,
193}
194
195/// One edge of the file-level dependency graph.
196#[derive(Clone, Debug, Serialize, Deserialize)]
197pub struct DependencyEdge {
198    /// File where the import statement appears.
199    pub from_file: String,
200    /// Module/path the import names.
201    pub to_module: String,
202}
203
204/// Detected sub-project marker (Cargo.toml, package.json, etc.).
205#[derive(Clone, Debug, Serialize, Deserialize)]
206pub struct SubProject {
207    /// Absolute path.
208    pub path: String,
209    /// Human name.
210    pub name: String,
211    /// Primary language (matches the marker).
212    pub language: String,
213    /// Marker file name.
214    pub project_marker: String,
215}
216
217/// Path-delta accompanying a [`scan_incremental`](super::scan_incremental)
218/// response.
219#[derive(Clone, Debug, Serialize, Deserialize, Default)]
220pub struct ScanDelta {
221    /// Paths newly present since the snapshot.
222    pub added: Vec<String>,
223    /// Paths whose content changed since the snapshot.
224    pub modified: Vec<String>,
225    /// Paths absent since the snapshot.
226    pub removed: Vec<String>,
227    /// True when the diff exceeded ~30% of the snapshot or the snapshot
228    /// was missing/stale, forcing a full rescan.
229    pub full_rescan: bool,
230}
231
232/// Top-level scanner output.
233#[derive(Clone, Debug, Serialize, Deserialize)]
234pub struct ScanResult {
235    /// Opaque cookie identifying the persisted snapshot for this scan.
236    pub snapshot_token: String,
237    /// True when `max_files` truncated the file list.
238    pub truncated: bool,
239    /// Project-level metadata.
240    pub project: ProjectMetadata,
241    /// Folder aggregates, sorted desc by `line_count`.
242    pub folders: Vec<FolderRecord>,
243    /// File records, sorted asc by `relative_path`.
244    pub files: Vec<FileRecord>,
245    /// Symbol records, sorted asc by `id` for deterministic output.
246    pub symbols: Vec<SymbolRecord>,
247    /// Import-derived dependency edges.
248    pub dependencies: Vec<DependencyEdge>,
249    /// Detected sub-projects beneath `root` (max 2 levels deep).
250    pub sub_projects: Vec<SubProject>,
251    /// Token-budgeted text repo map.
252    pub repo_map: String,
253}