1use std::path::Path;
2
3use anyhow::Result;
4use camino::Utf8PathBuf;
5use serde::{Deserialize, Serialize};
6
7pub trait LanguagePlugin: Send + Sync {
8 fn id(&self) -> &'static str;
9
10 fn display_name(&self) -> &'static str;
11
12 fn detect_project(&self, root: &Path) -> Result<ProjectInfo>;
13
14 fn discover_targets(&self, root: &Path) -> Result<Vec<VerificationTarget>>;
15
16 fn generate_tests(
17 &self,
18 target: &VerificationTarget,
19 plan: &VerificationPlan,
20 ) -> Result<Vec<GeneratedArtifact>>;
21
22 fn run_tests(
23 &self,
24 root: &Path,
25 artifacts: &[GeneratedArtifact],
26 plan: &VerificationPlan,
27 ) -> Result<TestRunResult>;
28
29 fn collect_coverage(&self, root: &Path) -> Result<Option<CoverageReport>>;
30}
31
32pub trait VerificationPlanner: Send + Sync {
33 fn plan(&self, project: &ProjectInfo, target: &VerificationTarget) -> Result<VerificationPlan>;
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
37pub struct ProjectInfo {
38 pub language: String,
39 pub name: String,
40 pub root: Utf8PathBuf,
41 pub manifests: Vec<Utf8PathBuf>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
45pub struct VerificationTarget {
46 pub id: String,
47 pub language: String,
48 pub kind: TargetKind,
49 pub path: Utf8PathBuf,
50 pub symbol: Option<String>,
51 pub signature: Option<String>,
52 pub line_range: Option<LineRange>,
53 pub description: String,
54 pub risk: RiskLevel,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
58pub struct LineRange {
59 pub start: usize,
60 pub end: usize,
61}
62
63impl LineRange {
64 pub fn overlaps(&self, other: &LineRange) -> bool {
65 self.start <= other.end && other.start <= self.end
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
70#[serde(rename_all = "snake_case")]
71pub enum TargetKind {
72 Project,
73 Package,
74 File,
75 Function,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
79#[serde(rename_all = "snake_case")]
80pub enum RiskLevel {
81 Low,
82 Medium,
83 High,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
87#[serde(rename_all = "snake_case")]
88pub enum FailureSeverity {
89 Info,
90 Warning,
91 Error,
92 Critical,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
96pub struct VerificationPlan {
97 pub target_id: String,
98 pub strategies: Vec<VerificationStrategy>,
99 pub budget_seconds: u64,
100 pub write_generated_tests: bool,
101 pub run_existing_tests: bool,
102 pub run_generated_tests: bool,
103 pub fail_on_generated_test_failure: bool,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
107#[serde(rename_all = "snake_case")]
108pub enum VerificationStrategy {
109 ExistingTests,
110 UnitTests,
111 PropertyTests,
112 Fuzzing,
113 DifferentialTests,
114 MutationChecks,
115 CoverageFeedback,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
119pub struct GeneratedArtifact {
120 pub id: String,
121 pub language: String,
122 pub kind: ArtifactKind,
123 pub target_id: String,
124 pub path: Utf8PathBuf,
125 pub contents: String,
126 pub description: String,
127 pub status: ArtifactStatus,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
131#[serde(rename_all = "snake_case")]
132pub enum ArtifactKind {
133 UnitTest,
134 PropertyTest,
135 FuzzHarness,
136 HarnessIndex,
137 MutationCheck,
138 CoverageFeedback,
139 DifferentialBaseline,
140 ReproCase,
141 PackageAwareness,
142 PackageGraph,
143 SymbolGraph,
144 ChangeDigest,
145 AiFeedback,
146 CandidatePatch,
147 FindingBaseline,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
151#[serde(rename_all = "snake_case")]
152pub enum ArtifactStatus {
153 Planned,
154 Written,
155 Skipped,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
159pub struct TestRunResult {
160 pub language: String,
161 pub status: RunStatus,
162 pub commands: Vec<CommandRecord>,
163 pub failures: Vec<Failure>,
164 pub duration_ms: u128,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
168#[serde(rename_all = "snake_case")]
169pub enum RunStatus {
170 Passed,
171 Failed,
172 Skipped,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
176pub struct CommandRecord {
177 pub program: String,
178 pub args: Vec<String>,
179 pub cwd: Utf8PathBuf,
180 pub exit_code: Option<i32>,
181 pub status: RunStatus,
182 pub stdout: String,
183 pub stderr: String,
184 pub duration_ms: u128,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
188pub struct Failure {
189 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub id: Option<String>,
191 pub message: String,
192 #[serde(default = "default_failure_severity")]
193 pub severity: FailureSeverity,
194 pub target_id: Option<String>,
195 pub artifact_id: Option<String>,
196 pub command: String,
197 pub stdout_excerpt: String,
198 pub stderr_excerpt: String,
199 pub repro: Option<ReproCase>,
200}
201
202fn default_failure_severity() -> FailureSeverity {
203 FailureSeverity::Error
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
207pub struct ReproCase {
208 pub command: String,
209 pub input: Option<String>,
210 pub path: Option<Utf8PathBuf>,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
214pub struct CoverageReport {
215 pub tool: String,
216 pub summary: String,
217 pub files: Vec<CoverageFile>,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
221pub struct CoverageFile {
222 pub path: Utf8PathBuf,
223 pub line_coverage_percent: Option<u8>,
224 pub uncovered_ranges: Vec<String>,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
228pub struct VerificationReport {
229 pub project: Option<ProjectInfo>,
230 pub targets: Vec<VerificationTarget>,
231 pub plan: Option<VerificationPlan>,
232 pub artifacts: Vec<GeneratedArtifact>,
233 pub runs: Vec<TestRunResult>,
234 pub coverage: Vec<CoverageReport>,
235 pub findings: Vec<Failure>,
236 pub suggested_next_steps: Vec<String>,
237}
238
239impl VerificationReport {
240 pub fn empty() -> Self {
241 Self {
242 project: None,
243 targets: Vec::new(),
244 plan: None,
245 artifacts: Vec::new(),
246 runs: Vec::new(),
247 coverage: Vec::new(),
248 findings: Vec::new(),
249 suggested_next_steps: Vec::new(),
250 }
251 }
252}