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}