1use serde::{Deserialize, Serialize};
7
8use crate::churn::TrendClass;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct GitReport {
16 pub commits_scanned: usize,
17 pub files_seen: usize,
18 pub hotspots: Vec<HotspotRow>,
19 pub bus_factor: Vec<BusFactorRow>,
20 pub freshness: FreshnessReport,
21 pub coupling: Vec<CouplingRow>,
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub age_distribution: Option<CodeAgeDistributionReport>,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub intent: Option<CommitIntentReport>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct HotspotRow {
32 pub path: String,
33 pub commits: usize,
34 pub lines: usize,
35 pub score: usize,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct BusFactorRow {
40 pub module: String,
41 pub authors: usize,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct FreshnessReport {
46 pub threshold_days: usize,
47 pub stale_files: usize,
48 pub total_files: usize,
49 pub stale_pct: f64,
50 pub by_module: Vec<ModuleFreshnessRow>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ModuleFreshnessRow {
55 pub module: String,
56 pub avg_days: f64,
57 pub p90_days: f64,
58 pub stale_pct: f64,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct CouplingRow {
63 pub left: String,
64 pub right: String,
65 pub count: usize,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub jaccard: Option<f64>,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub lift: Option<f64>,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub n_left: Option<usize>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub n_right: Option<usize>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct CodeAgeDistributionReport {
82 pub buckets: Vec<CodeAgeBucket>,
83 pub recent_refreshes: usize,
84 pub prior_refreshes: usize,
85 pub refresh_trend: TrendClass,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct CodeAgeBucket {
90 pub label: String,
91 pub min_days: usize,
92 pub max_days: Option<usize>,
93 pub files: usize,
94 pub pct: f64,
95}
96
97pub use tokmd_types::CommitIntentKind;
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct CommitIntentReport {
107 pub overall: CommitIntentCounts,
109 pub by_module: Vec<ModuleIntentRow>,
111 pub unknown_pct: f64,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
115 pub corrective_ratio: Option<f64>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, Default)]
120pub struct CommitIntentCounts {
121 pub feat: usize,
122 pub fix: usize,
123 pub refactor: usize,
124 pub docs: usize,
125 pub test: usize,
126 pub chore: usize,
127 pub ci: usize,
128 pub build: usize,
129 pub perf: usize,
130 pub style: usize,
131 pub revert: usize,
132 pub other: usize,
133 pub total: usize,
134}
135
136impl CommitIntentCounts {
137 pub fn increment(&mut self, kind: CommitIntentKind) {
139 match kind {
140 CommitIntentKind::Feat => self.feat += 1,
141 CommitIntentKind::Fix => self.fix += 1,
142 CommitIntentKind::Refactor => self.refactor += 1,
143 CommitIntentKind::Docs => self.docs += 1,
144 CommitIntentKind::Test => self.test += 1,
145 CommitIntentKind::Chore => self.chore += 1,
146 CommitIntentKind::Ci => self.ci += 1,
147 CommitIntentKind::Build => self.build += 1,
148 CommitIntentKind::Perf => self.perf += 1,
149 CommitIntentKind::Style => self.style += 1,
150 CommitIntentKind::Revert => self.revert += 1,
151 CommitIntentKind::Other => self.other += 1,
152 }
153 self.total += 1;
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct ModuleIntentRow {
160 pub module: String,
161 pub counts: CommitIntentCounts,
162}