1use std::fmt;
2use std::path::PathBuf;
3
4use fallow_types::serde_path;
5
6#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
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(
182 Debug,
183 Clone,
184 Copy,
185 Default,
186 PartialEq,
187 Eq,
188 PartialOrd,
189 Ord,
190 serde::Serialize,
191 serde::Deserialize,
192)]
193#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
194#[serde(rename_all = "kebab-case")]
195pub enum CoverageIntelligenceMatchConfidence {
196 PathFunctionLine,
198 PathLine,
200 #[default]
202 Direct,
203}
204
205impl CoverageIntelligenceMatchConfidence {
206 #[must_use]
208 pub const fn as_str(self) -> &'static str {
209 match self {
210 Self::PathFunctionLine => "path-function-line",
211 Self::PathLine => "path-line",
212 Self::Direct => "direct",
213 }
214 }
215}
216
217impl fmt::Display for CoverageIntelligenceMatchConfidence {
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219 f.write_str(self.as_str())
220 }
221}
222
223#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
225#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
226pub struct CoverageIntelligenceAction {
227 #[serde(rename = "type")]
229 pub kind: String,
230 pub description: String,
232 pub auto_fixable: bool,
234}
235
236#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
238#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
239pub struct CoverageIntelligenceEvidence {
240 #[serde(default, skip_serializing_if = "Option::is_none")]
242 pub coverage_pct: Option<f64>,
243 #[serde(default, skip_serializing_if = "Option::is_none")]
245 pub crap: Option<f64>,
246 #[serde(default, skip_serializing_if = "Option::is_none")]
248 pub runtime_verdict: Option<String>,
249 #[serde(default, skip_serializing_if = "Option::is_none")]
251 pub invocations: Option<u64>,
252 #[serde(default, skip_serializing_if = "Option::is_none")]
254 pub static_status: Option<String>,
255 #[serde(default, skip_serializing_if = "Option::is_none")]
257 pub test_coverage: Option<String>,
258 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
261 #[cfg_attr(feature = "schema", schemars(default))]
262 pub changed: bool,
263 #[serde(default, skip_serializing_if = "Option::is_none")]
265 pub ownership_state: Option<String>,
266 pub match_confidence: CoverageIntelligenceMatchConfidence,
268}
269
270#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
272#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
273pub struct CoverageIntelligenceFinding {
274 pub id: String,
276 #[serde(serialize_with = "serde_path::serialize")]
278 pub path: PathBuf,
279 #[serde(default, skip_serializing_if = "Option::is_none")]
281 pub identity: Option<String>,
282 pub line: u32,
284 pub verdict: CoverageIntelligenceVerdict,
286 pub signals: Vec<CoverageIntelligenceSignal>,
288 pub recommendation: CoverageIntelligenceRecommendation,
290 pub confidence: CoverageIntelligenceConfidence,
292 #[serde(default, skip_serializing_if = "Vec::is_empty")]
294 #[cfg_attr(feature = "schema", schemars(default))]
295 pub related_ids: Vec<String>,
296 pub evidence: CoverageIntelligenceEvidence,
298 pub actions: Vec<CoverageIntelligenceAction>,
300}
301
302#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
304#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
305pub struct CoverageIntelligenceSummary {
306 pub findings: usize,
308 pub risky_changes: usize,
310 pub high_confidence_deletes: usize,
312 pub review_required: usize,
314 pub refactor_carefully: usize,
316 pub skipped_ambiguous_matches: usize,
318}
319
320#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
322#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
323pub struct CoverageIntelligenceReport {
324 pub schema_version: CoverageIntelligenceSchemaVersion,
326 pub verdict: CoverageIntelligenceVerdict,
329 pub summary: CoverageIntelligenceSummary,
331 pub findings: Vec<CoverageIntelligenceFinding>,
333}