1use std::collections::BTreeMap;
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct RepoMetadata {
9 pub repo_name: String,
10 pub repo_root: String,
11 pub branch: Option<String>,
12 pub head_commit: Option<String>,
13 pub head_commit_timestamp: Option<String>,
14 pub git_remote_url: Option<String>,
15 pub is_shallow: bool,
16 pub detached_head: bool,
17 pub worktree_clean: bool,
18 pub staged_change_count: usize,
19 pub modified_tracked_file_count: usize,
20 pub untracked_file_count: usize,
21 pub worktree_state_digest: String,
22 pub analyzed_content_digest: Option<String>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
26pub struct ScopeIdentity {
27 pub mode: String,
28 pub path: Option<String>,
29 pub selected_path_count: usize,
30 pub selected_path_digest: String,
31}
32
33#[derive(Debug, Clone, Default, Serialize, Deserialize)]
34pub struct SkippedCounts {
35 pub ignored: usize,
36 pub missing: usize,
37 pub binary: usize,
38 pub undecodable: usize,
39}
40
41#[derive(Debug, Clone)]
42pub struct InventoryFile {
43 pub path: String,
44 pub bytes: usize,
45 pub lines: usize,
46 pub blank_lines: usize,
47 pub code_lines: usize,
48 pub comment_lines: usize,
49 pub language: String,
50 pub profile: String,
51 pub classification: String,
52 pub text: String,
53}
54
55#[derive(Debug, Clone, Default, Serialize, Deserialize)]
56pub struct HistoryMetrics {
57 pub first_seen_timestamp: Option<i64>,
58 pub age_days: u64,
59 pub revisions_window: usize,
60 pub recency_weighted_commits: f64,
61 pub added_window: usize,
62 pub deleted_window: usize,
63 pub line_churn_window: usize,
64 pub token_churn_window: usize,
65 pub relative_churn_window: f64,
66 pub late_churn_spike: f64,
67 pub author_count_window: usize,
68 pub author_entropy: f64,
69 pub top_author_share: f64,
70 pub days_since_non_bot_edit: Option<u64>,
71 pub recent_maintainer_diversity: usize,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct CommitRecord {
76 pub commit: String,
77 pub timestamp: i64,
78 pub author: String,
79 pub paths: Vec<String>,
80 pub line_churn_by_path: BTreeMap<String, usize>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct FileAnalysis {
85 pub path: String,
86 pub bytes: usize,
87 pub lines: usize,
88 pub blank_lines: usize,
89 pub code_lines: usize,
90 pub comment_lines: usize,
91 pub language: String,
92 pub profile: String,
93 pub classification: String,
94 pub has_inline_tests: bool,
95 pub tokens: usize,
96 pub context_band: String,
97 pub context_pressure: f64,
98 pub content_fingerprint: String,
101 #[serde(skip)]
102 pub structural_tokens: Vec<String>,
103 pub structural_token_count: usize,
104 pub top_structural_terms: Vec<String>,
105 pub age_days: u64,
106 pub revisions_window: usize,
107 pub recency_weighted_commits: f64,
108 pub added_window: usize,
109 pub deleted_window: usize,
110 pub churn_lines_window: usize,
111 pub line_churn_window: usize,
112 pub token_churn_window: usize,
113 pub relative_churn_window: f64,
114 pub late_churn_spike: f64,
115 pub author_count_window: usize,
116 pub author_entropy: f64,
117 pub top_author_share: f64,
118 pub days_since_non_bot_edit: Option<u64>,
119 pub recent_maintainer_diversity: usize,
120 pub age_pressure: f64,
121 pub revision_norm: f64,
122 pub relative_churn_norm: f64,
123 pub churn_pressure: f64,
124 pub slop_score: f64,
125 pub slop_band: String,
126 pub reason_codes: Vec<String>,
127 pub costs: Value,
128 pub overlays: Value,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct FolderAnalysis {
133 pub path: String,
134 pub descendant_file_count: usize,
135 pub direct_file_count: usize,
136 pub bytes: usize,
137 pub lines: usize,
138 pub tokens: usize,
139 pub direct_tokens: usize,
140 pub context_band: String,
141 pub health_band: String,
142 pub context_pressure: f64,
143 pub slop_score: f64,
144 pub slop_band: String,
145 pub reason_codes: Vec<String>,
146 pub top_file_path: String,
147 pub classification: String,
148 pub costs: Value,
149 pub overlays: Value,
150}
151
152#[derive(Debug, Clone, Default, Serialize, Deserialize)]
153pub struct OrganizationAnalysis {
154 pub organization_metrics: Value,
155 pub relationships: Value,
156 pub clusters: Value,
157 pub file_overlays: BTreeMap<String, Value>,
158 pub folder_overlays: BTreeMap<String, Value>,
159 pub top_structural_files: Vec<Value>,
160}
161
162#[derive(Debug, Clone)]
163pub struct Analysis {
164 pub repo_root: PathBuf,
165 pub repo: RepoMetadata,
166 pub config: Value,
167 pub generated_at: String,
168 pub analyzed_revision_at: Option<String>,
169 pub skipped: SkippedCounts,
170 pub tracked_file_count: usize,
171 pub scope: ScopeIdentity,
172 pub files: Vec<FileAnalysis>,
173 pub folders: Vec<FolderAnalysis>,
174 pub organization: OrganizationAnalysis,
175 pub action_queue: Vec<Value>,
176 pub diagnostics: Value,
177}
178
179#[derive(Debug, Clone)]
180pub struct FindResult {
181 pub report: Value,
182 pub report_json: PathBuf,
183 pub report_yaml: PathBuf,
185 pub summary_md: PathBuf,
186 pub health_md: PathBuf,
187 pub terminal: String,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct Finding {
192 pub path: String,
193 pub profile: String,
194 pub severity: String,
195 pub title: String,
196 pub message: String,
197 pub next_command: String,
198 pub slop_band: String,
199 pub context_band: String,
200 pub slop_score: f64,
201 pub tokens: usize,
202 pub reasons: Vec<String>,
203}
204
205#[derive(Debug, Clone, Default, Serialize, Deserialize)]
206pub struct HealthRollup {
207 pub file_band_counts: BTreeMap<String, usize>,
208 pub folder_band_counts: BTreeMap<String, usize>,
209 pub profile_rollups: Vec<Value>,
210 pub language_rollups: BTreeMap<String, Vec<Value>>,
211 pub file_distribution: Value,
212 pub folder_distribution: Value,
213 pub refactor_candidates: Vec<Value>,
214 pub watchlist: Vec<Value>,
215 pub findings: Vec<Finding>,
216}
217
218pub fn parent_folders(path: &str) -> Vec<String> {
219 let normalized = path.trim_matches('/');
220 let mut result = vec![".".to_string()];
221 let parts: Vec<&str> = normalized.split('/').collect();
222 if parts.len() <= 1 {
223 return result;
224 }
225 for index in 1..parts.len() {
226 result.push(parts[..index].join("/"));
227 }
228 result
229}
230
231pub fn top_level_root(path: &str) -> String {
232 path.trim_matches('/')
233 .split('/')
234 .next()
235 .filter(|part| !part.is_empty())
236 .unwrap_or(".")
237 .to_string()
238}