Skip to main content

fallow_output/
health_coverage_intelligence.rs

1use std::fmt;
2use std::path::PathBuf;
3
4use fallow_types::serde_path;
5
6/// Coverage-intelligence JSON contract version. Scoped to the
7/// `coverage_intelligence` block and independent of the top-level fallow
8/// JSON `schema_version`.
9#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)]
10#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
11pub enum CoverageIntelligenceSchemaVersion {
12    /// First release of the coverage-intelligence block contract.
13    #[default]
14    #[serde(rename = "1")]
15    V1,
16}
17
18/// Headline verdict for the combined coverage-intelligence report.
19#[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/// Ordered evidence signals behind a coverage-intelligence finding.
53#[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/// Recommended action family for a combined finding.
94#[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/// Confidence in the joined evidence and resulting recommendation.
123#[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/// Confidence tier for the cross-surface evidence match.
150#[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/// Machine-actionable next step for a coverage-intelligence finding.
178#[derive(Debug, Clone, serde::Serialize)]
179#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
180pub struct CoverageIntelligenceAction {
181    /// Action identifier, normalized to `type` in JSON output.
182    #[serde(rename = "type")]
183    pub kind: String,
184    pub description: String,
185    /// Whether fallow can apply this action automatically.
186    pub auto_fixable: bool,
187}
188
189/// Compact evidence values that led to a recommendation.
190#[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/// One combined coverage-intelligence finding.
214#[derive(Debug, Clone, serde::Serialize)]
215#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
216pub struct CoverageIntelligenceFinding {
217    /// Stable finding ID of the form `fallow:coverage-intel:<hash>`.
218    pub id: String,
219    /// File path relative to the project root.
220    #[serde(serialize_with = "serde_path::serialize")]
221    pub path: PathBuf,
222    /// Function or export identity when known.
223    #[serde(default, skip_serializing_if = "Option::is_none")]
224    pub identity: Option<String>,
225    /// 1-indexed source line.
226    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/// Aggregate metadata for coverage-intelligence output.
239#[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/// Combined coverage, runtime, complexity, and change-scope verdicts.
251#[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}