Skip to main content

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}