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