1use std::fmt;
2use std::path::PathBuf;
3
4use fallow_types::serde_path;
5
6#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)]
10#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
11pub enum CoverageIntelligenceSchemaVersion {
12 #[default]
14 #[serde(rename = "1")]
15 V1,
16}
17
18#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)]
20#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
21#[serde(rename_all = "kebab-case")]
22pub enum CoverageIntelligenceVerdict {
23 RiskyChangeDetected,
24 HighConfidenceDelete,
25 ReviewRequired,
26 RefactorCarefully,
27 Clean,
28 #[default]
29 Unknown,
30}
31
32impl CoverageIntelligenceVerdict {
33 #[must_use]
34 pub const fn as_str(self) -> &'static str {
35 match self {
36 Self::RiskyChangeDetected => "risky-change-detected",
37 Self::HighConfidenceDelete => "high-confidence-delete",
38 Self::ReviewRequired => "review-required",
39 Self::RefactorCarefully => "refactor-carefully",
40 Self::Clean => "clean",
41 Self::Unknown => "unknown",
42 }
43 }
44}
45
46impl fmt::Display for CoverageIntelligenceVerdict {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 f.write_str(self.as_str())
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
54#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
55#[serde(rename_all = "snake_case")]
56pub enum CoverageIntelligenceSignal {
57 Changed,
58 HotPath,
59 LowTestCoverage,
60 HighCrap,
61 StaticUnused,
62 RuntimeCold,
63 NoTestPath,
64 RuntimeReachable,
65 OwnershipDrift,
66 TestCovered,
67}
68
69impl CoverageIntelligenceSignal {
70 #[must_use]
71 pub const fn as_str(self) -> &'static str {
72 match self {
73 Self::Changed => "changed",
74 Self::HotPath => "hot_path",
75 Self::LowTestCoverage => "low_test_coverage",
76 Self::HighCrap => "high_crap",
77 Self::StaticUnused => "static_unused",
78 Self::RuntimeCold => "runtime_cold",
79 Self::NoTestPath => "no_test_path",
80 Self::RuntimeReachable => "runtime_reachable",
81 Self::OwnershipDrift => "ownership_drift",
82 Self::TestCovered => "test_covered",
83 }
84 }
85}
86
87impl fmt::Display for CoverageIntelligenceSignal {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 f.write_str(self.as_str())
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
95#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
96#[serde(rename_all = "kebab-case")]
97pub enum CoverageIntelligenceRecommendation {
98 AddTestOrSplitBeforeMerge,
99 DeleteAfterConfirmingOwner,
100 ReviewBeforeChanging,
101 RefactorCarefullyKeepBehavior,
102}
103
104impl CoverageIntelligenceRecommendation {
105 #[must_use]
106 pub const fn as_str(self) -> &'static str {
107 match self {
108 Self::AddTestOrSplitBeforeMerge => "add-test-or-split-before-merge",
109 Self::DeleteAfterConfirmingOwner => "delete-after-confirming-owner",
110 Self::ReviewBeforeChanging => "review-before-changing",
111 Self::RefactorCarefullyKeepBehavior => "refactor-carefully-keep-behavior",
112 }
113 }
114}
115
116impl fmt::Display for CoverageIntelligenceRecommendation {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 f.write_str(self.as_str())
119 }
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
124#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
125#[serde(rename_all = "snake_case")]
126pub enum CoverageIntelligenceConfidence {
127 High,
128 Medium,
129 Low,
130}
131
132impl CoverageIntelligenceConfidence {
133 #[must_use]
134 pub const fn as_str(self) -> &'static str {
135 match self {
136 Self::High => "high",
137 Self::Medium => "medium",
138 Self::Low => "low",
139 }
140 }
141}
142
143impl fmt::Display for CoverageIntelligenceConfidence {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 f.write_str(self.as_str())
146 }
147}
148
149#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
151#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
152#[serde(rename_all = "kebab-case")]
153pub enum CoverageIntelligenceMatchConfidence {
154 PathFunctionLine,
155 PathLine,
156 #[default]
157 Direct,
158}
159
160impl CoverageIntelligenceMatchConfidence {
161 #[must_use]
162 pub const fn as_str(self) -> &'static str {
163 match self {
164 Self::PathFunctionLine => "path-function-line",
165 Self::PathLine => "path-line",
166 Self::Direct => "direct",
167 }
168 }
169}
170
171impl fmt::Display for CoverageIntelligenceMatchConfidence {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 f.write_str(self.as_str())
174 }
175}
176
177#[derive(Debug, Clone, serde::Serialize)]
179#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
180pub struct CoverageIntelligenceAction {
181 #[serde(rename = "type")]
183 pub kind: String,
184 pub description: String,
185 pub auto_fixable: bool,
187}
188
189#[derive(Debug, Clone, Default, serde::Serialize)]
191#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
192pub struct CoverageIntelligenceEvidence {
193 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub coverage_pct: Option<f64>,
195 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub crap: Option<f64>,
197 #[serde(default, skip_serializing_if = "Option::is_none")]
198 pub runtime_verdict: Option<String>,
199 #[serde(default, skip_serializing_if = "Option::is_none")]
200 pub invocations: Option<u64>,
201 #[serde(default, skip_serializing_if = "Option::is_none")]
202 pub static_status: Option<String>,
203 #[serde(default, skip_serializing_if = "Option::is_none")]
204 pub test_coverage: Option<String>,
205 #[serde(skip_serializing_if = "std::ops::Not::not")]
206 #[cfg_attr(feature = "schema", schemars(default))]
207 pub changed: bool,
208 #[serde(default, skip_serializing_if = "Option::is_none")]
209 pub ownership_state: Option<String>,
210 pub match_confidence: CoverageIntelligenceMatchConfidence,
211}
212
213#[derive(Debug, Clone, serde::Serialize)]
215#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
216pub struct CoverageIntelligenceFinding {
217 pub id: String,
219 #[serde(serialize_with = "serde_path::serialize")]
221 pub path: PathBuf,
222 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub identity: Option<String>,
225 pub line: u32,
227 pub verdict: CoverageIntelligenceVerdict,
228 pub signals: Vec<CoverageIntelligenceSignal>,
229 pub recommendation: CoverageIntelligenceRecommendation,
230 pub confidence: CoverageIntelligenceConfidence,
231 #[serde(default, skip_serializing_if = "Vec::is_empty")]
232 #[cfg_attr(feature = "schema", schemars(default))]
233 pub related_ids: Vec<String>,
234 pub evidence: CoverageIntelligenceEvidence,
235 pub actions: Vec<CoverageIntelligenceAction>,
236}
237
238#[derive(Debug, Clone, Default, serde::Serialize)]
240#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
241pub struct CoverageIntelligenceSummary {
242 pub findings: usize,
243 pub risky_changes: usize,
244 pub high_confidence_deletes: usize,
245 pub review_required: usize,
246 pub refactor_carefully: usize,
247 pub skipped_ambiguous_matches: usize,
248}
249
250#[derive(Debug, Clone, serde::Serialize)]
252#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
253pub struct CoverageIntelligenceReport {
254 pub schema_version: CoverageIntelligenceSchemaVersion,
255 pub verdict: CoverageIntelligenceVerdict,
256 pub summary: CoverageIntelligenceSummary,
257 pub findings: Vec<CoverageIntelligenceFinding>,
258}