Skip to main content

git_slop/
model.rs

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