Skip to main content

tokmd_analysis_types/
git.rs

1//! Git-derived analysis receipt DTOs.
2//!
3//! These contract types remain re-exported from the crate root to preserve
4//! existing `tokmd_analysis_types::...` names.
5
6use serde::{Deserialize, Serialize};
7
8use crate::churn::TrendClass;
9
10// ---------
11// Git report
12// ---------
13
14#[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    /// Code age bucket distribution plus recent refresh trend.
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub age_distribution: Option<CodeAgeDistributionReport>,
25    /// Commit intent classification (feat/fix/refactor/etc.).
26    #[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    /// Jaccard similarity: count / (n_left + n_right - count). Range (0.0, 1.0].
67    #[serde(default, skip_serializing_if = "Option::is_none")]
68    pub jaccard: Option<f64>,
69    /// Lift: (count * N) / (n_left * n_right), where N = commits_considered.
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub lift: Option<f64>,
72    /// Commits touching left module (within commits_considered universe).
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub n_left: Option<usize>,
75    /// Commits touching right module (within commits_considered universe).
76    #[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
97// --------------------------
98// Commit intent classification
99// --------------------------
100
101// Re-export from tokmd-types (Tier 0) so existing consumers keep working.
102pub use tokmd_types::CommitIntentKind;
103
104/// Overall commit intent classification report.
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct CommitIntentReport {
107    /// Aggregate counts across all scanned commits.
108    pub overall: CommitIntentCounts,
109    /// Per-module intent breakdown.
110    pub by_module: Vec<ModuleIntentRow>,
111    /// Percentage of commits classified as "other" (unrecognized).
112    pub unknown_pct: f64,
113    /// Corrective ratio: (fix + revert) / total. Range [0.0, 1.0].
114    #[serde(default, skip_serializing_if = "Option::is_none")]
115    pub corrective_ratio: Option<f64>,
116}
117
118/// Counts per intent kind.
119#[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    /// Increment the count for a given intent kind.
138    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/// Per-module intent breakdown row.
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct ModuleIntentRow {
160    pub module: String,
161    pub counts: CommitIntentCounts,
162}