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, serde::Deserialize)]
20#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
21#[serde(rename_all = "kebab-case")]
22pub enum CoverageIntelligenceVerdict {
23 RiskyChangeDetected,
25 HighConfidenceDelete,
27 ReviewRequired,
29 RefactorCarefully,
31 Clean,
33 #[default]
35 Unknown,
36}
37
38impl CoverageIntelligenceVerdict {
39 #[must_use]
41 pub const fn as_str(self) -> &'static str {
42 match self {
43 Self::RiskyChangeDetected => "risky-change-detected",
44 Self::HighConfidenceDelete => "high-confidence-delete",
45 Self::ReviewRequired => "review-required",
46 Self::RefactorCarefully => "refactor-carefully",
47 Self::Clean => "clean",
48 Self::Unknown => "unknown",
49 }
50 }
51}
52
53impl fmt::Display for CoverageIntelligenceVerdict {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 f.write_str(self.as_str())
56 }
57}
58
59#[derive(
61 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
62)]
63#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
64#[serde(rename_all = "snake_case")]
65pub enum CoverageIntelligenceSignal {
66 Changed,
68 HotPath,
70 LowTestCoverage,
72 HighCrap,
74 StaticUnused,
76 RuntimeCold,
78 NoTestPath,
80 RuntimeReachable,
82 OwnershipDrift,
84 TestCovered,
86}
87
88impl CoverageIntelligenceSignal {
89 #[must_use]
91 pub const fn as_str(self) -> &'static str {
92 match self {
93 Self::Changed => "changed",
94 Self::HotPath => "hot_path",
95 Self::LowTestCoverage => "low_test_coverage",
96 Self::HighCrap => "high_crap",
97 Self::StaticUnused => "static_unused",
98 Self::RuntimeCold => "runtime_cold",
99 Self::NoTestPath => "no_test_path",
100 Self::RuntimeReachable => "runtime_reachable",
101 Self::OwnershipDrift => "ownership_drift",
102 Self::TestCovered => "test_covered",
103 }
104 }
105}
106
107impl fmt::Display for CoverageIntelligenceSignal {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 f.write_str(self.as_str())
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
115#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
116#[serde(rename_all = "kebab-case")]
117pub enum CoverageIntelligenceRecommendation {
118 AddTestOrSplitBeforeMerge,
120 DeleteAfterConfirmingOwner,
122 ReviewBeforeChanging,
124 RefactorCarefullyKeepBehavior,
126}
127
128impl CoverageIntelligenceRecommendation {
129 #[must_use]
131 pub const fn as_str(self) -> &'static str {
132 match self {
133 Self::AddTestOrSplitBeforeMerge => "add-test-or-split-before-merge",
134 Self::DeleteAfterConfirmingOwner => "delete-after-confirming-owner",
135 Self::ReviewBeforeChanging => "review-before-changing",
136 Self::RefactorCarefullyKeepBehavior => "refactor-carefully-keep-behavior",
137 }
138 }
139}
140
141impl fmt::Display for CoverageIntelligenceRecommendation {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 f.write_str(self.as_str())
144 }
145}
146
147#[derive(
149 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
150)]
151#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
152#[serde(rename_all = "snake_case")]
153pub enum CoverageIntelligenceConfidence {
154 High,
156 Medium,
158 Low,
160}
161
162impl CoverageIntelligenceConfidence {
163 #[must_use]
165 pub const fn as_str(self) -> &'static str {
166 match self {
167 Self::High => "high",
168 Self::Medium => "medium",
169 Self::Low => "low",
170 }
171 }
172}
173
174impl fmt::Display for CoverageIntelligenceConfidence {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 f.write_str(self.as_str())
177 }
178}
179
180#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
182#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
183#[serde(rename_all = "kebab-case")]
184pub enum CoverageIntelligenceMatchConfidence {
185 PathFunctionLine,
187 PathLine,
189 #[default]
191 Direct,
192}
193
194impl CoverageIntelligenceMatchConfidence {
195 #[must_use]
197 pub const fn as_str(self) -> &'static str {
198 match self {
199 Self::PathFunctionLine => "path-function-line",
200 Self::PathLine => "path-line",
201 Self::Direct => "direct",
202 }
203 }
204}
205
206impl fmt::Display for CoverageIntelligenceMatchConfidence {
207 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 f.write_str(self.as_str())
209 }
210}
211
212#[derive(Debug, Clone, serde::Serialize)]
214#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
215pub struct CoverageIntelligenceAction {
216 #[serde(rename = "type")]
218 pub kind: String,
219 pub description: String,
221 pub auto_fixable: bool,
223}
224
225#[derive(Debug, Clone, Default, serde::Serialize)]
227#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
228pub struct CoverageIntelligenceEvidence {
229 #[serde(default, skip_serializing_if = "Option::is_none")]
231 pub coverage_pct: Option<f64>,
232 #[serde(default, skip_serializing_if = "Option::is_none")]
234 pub crap: Option<f64>,
235 #[serde(default, skip_serializing_if = "Option::is_none")]
237 pub runtime_verdict: Option<String>,
238 #[serde(default, skip_serializing_if = "Option::is_none")]
240 pub invocations: Option<u64>,
241 #[serde(default, skip_serializing_if = "Option::is_none")]
243 pub static_status: Option<String>,
244 #[serde(default, skip_serializing_if = "Option::is_none")]
246 pub test_coverage: Option<String>,
247 #[serde(skip_serializing_if = "std::ops::Not::not")]
250 #[cfg_attr(feature = "schema", schemars(default))]
251 pub changed: bool,
252 #[serde(default, skip_serializing_if = "Option::is_none")]
254 pub ownership_state: Option<String>,
255 pub match_confidence: CoverageIntelligenceMatchConfidence,
257}
258
259#[derive(Debug, Clone, serde::Serialize)]
261#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
262pub struct CoverageIntelligenceFinding {
263 pub id: String,
265 #[serde(serialize_with = "serde_path::serialize")]
267 pub path: PathBuf,
268 #[serde(default, skip_serializing_if = "Option::is_none")]
270 pub identity: Option<String>,
271 pub line: u32,
273 pub verdict: CoverageIntelligenceVerdict,
275 pub signals: Vec<CoverageIntelligenceSignal>,
277 pub recommendation: CoverageIntelligenceRecommendation,
279 pub confidence: CoverageIntelligenceConfidence,
281 #[serde(default, skip_serializing_if = "Vec::is_empty")]
283 #[cfg_attr(feature = "schema", schemars(default))]
284 pub related_ids: Vec<String>,
285 pub evidence: CoverageIntelligenceEvidence,
287 pub actions: Vec<CoverageIntelligenceAction>,
289}
290
291#[derive(Debug, Clone, Default, serde::Serialize)]
293#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
294pub struct CoverageIntelligenceSummary {
295 pub findings: usize,
297 pub risky_changes: usize,
299 pub high_confidence_deletes: usize,
301 pub review_required: usize,
303 pub refactor_carefully: usize,
305 pub skipped_ambiguous_matches: usize,
307}
308
309#[derive(Debug, Clone, serde::Serialize)]
311#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
312pub struct CoverageIntelligenceReport {
313 pub schema_version: CoverageIntelligenceSchemaVersion,
315 pub verdict: CoverageIntelligenceVerdict,
318 pub summary: CoverageIntelligenceSummary,
320 pub findings: Vec<CoverageIntelligenceFinding>,
322}