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