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    /// Dependency identifiers declared in package manifests located directly
191    /// under the project root (`package.json`, `Cargo.toml`, `go.mod`, …).
192    /// Sorted + de-duplicated. Empty when no recognized manifest is present.
193    #[serde(default)]
194    pub available_dependencies: Vec<String>,
195}
196
197/// One edge of the file-level dependency graph.
198#[derive(Clone, Debug, Serialize, Deserialize)]
199pub struct DependencyEdge {
200    /// File where the import statement appears.
201    pub from_file: String,
202    /// Module/path the import names.
203    pub to_module: String,
204}
205
206/// Detected sub-project marker (Cargo.toml, package.json, etc.).
207#[derive(Clone, Debug, Serialize, Deserialize)]
208pub struct SubProject {
209    /// Absolute path.
210    pub path: String,
211    /// Human name.
212    pub name: String,
213    /// Primary language (matches the marker).
214    pub language: String,
215    /// Marker file name.
216    pub project_marker: String,
217    /// Dependency identifiers declared in this sub-project's manifests,
218    /// sorted + de-duplicated. Empty when no recognized manifest is present.
219    #[serde(default)]
220    pub dependencies: Vec<String>,
221}
222
223/// Path-delta accompanying a [`scan_incremental`](super::scan_incremental)
224/// response.
225#[derive(Clone, Debug, Serialize, Deserialize, Default)]
226pub struct ScanDelta {
227    /// Paths newly present since the snapshot.
228    pub added: Vec<String>,
229    /// Paths whose content changed since the snapshot.
230    pub modified: Vec<String>,
231    /// Paths absent since the snapshot.
232    pub removed: Vec<String>,
233    /// True when the diff exceeded ~30% of the snapshot or the snapshot
234    /// was missing/stale, forcing a full rescan.
235    pub full_rescan: bool,
236}
237
238/// Top-level scanner output.
239#[derive(Clone, Debug, Serialize, Deserialize)]
240pub struct ScanResult {
241    /// Opaque cookie identifying the persisted snapshot for this scan.
242    pub snapshot_token: String,
243    /// True when `max_files` truncated the file list.
244    pub truncated: bool,
245    /// Project-level metadata.
246    pub project: ProjectMetadata,
247    /// Folder aggregates, sorted desc by `line_count`.
248    pub folders: Vec<FolderRecord>,
249    /// File records, sorted asc by `relative_path`.
250    pub files: Vec<FileRecord>,
251    /// Symbol records, sorted asc by `id` for deterministic output.
252    pub symbols: Vec<SymbolRecord>,
253    /// Import-derived dependency edges.
254    pub dependencies: Vec<DependencyEdge>,
255    /// Detected sub-projects beneath `root` (max 2 levels deep).
256    pub sub_projects: Vec<SubProject>,
257    /// Token-budgeted text repo map.
258    pub repo_map: String,
259}