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