Skip to main content

forge_agent/
verify.rs

1//! Verification engine - Post-mutation validation.
2//!
3//! This module implements the verification phase, validating that
4//! mutations meet quality and policy requirements.
5
6use crate::{AgentError, Result};
7use std::process::Command;
8
9/// Verifier for post-mutation validation.
10///
11/// The Verifier runs compile checks, tests, and graph validation.
12#[derive(Clone, Default)]
13pub struct Verifier {}
14
15impl Verifier {
16    /// Creates a new verifier.
17    pub fn new() -> Self {
18        Self::default()
19    }
20
21    /// Runs compile check via cargo.
22    ///
23    /// # Arguments
24    ///
25    /// * `working_dir` - Directory to check
26    pub async fn compile_check(&self, working_dir: &std::path::Path) -> Result<Vec<Diagnostic>> {
27        let output = Command::new("cargo")
28            .args(["check", "--message-format=short"])
29            .current_dir(working_dir)
30            .output()
31            .map_err(|e| AgentError::VerificationFailed(format!("Cargo check failed: {}", e)))?;
32
33        let stdout = String::from_utf8_lossy(&output.stdout);
34        let stderr = String::from_utf8_lossy(&output.stderr);
35
36        let mut diagnostics = Vec::new();
37        for line in stdout.lines().chain(stderr.lines()) {
38            if line.contains("error:") {
39                diagnostics.push(Diagnostic {
40                    level: DiagnosticLevel::Error,
41                    message: line.trim().to_string(),
42                });
43            } else if line.contains("warning:") {
44                diagnostics.push(Diagnostic {
45                    level: DiagnosticLevel::Warning,
46                    message: line.trim().to_string(),
47                });
48            }
49        }
50
51        Ok(diagnostics)
52    }
53
54    /// Runs tests via cargo.
55    ///
56    /// # Arguments
57    ///
58    /// * `working_dir` - Directory to test
59    pub async fn test_check(&self, working_dir: &std::path::Path) -> Result<Vec<Diagnostic>> {
60        let output = Command::new("cargo")
61            .args(["test", "--message-format=short"])
62            .current_dir(working_dir)
63            .output()
64            .map_err(|e| AgentError::VerificationFailed(format!("Cargo test failed: {}", e)))?;
65
66        let stdout = String::from_utf8_lossy(&output.stdout);
67        let stderr = String::from_utf8_lossy(&output.stderr);
68
69        let mut diagnostics = Vec::new();
70
71        // Parse test results
72        for line in stdout.lines().chain(stderr.lines()) {
73            if line.contains("test result:") && line.contains("FAILED") {
74                diagnostics.push(Diagnostic {
75                    level: DiagnosticLevel::Error,
76                    message: line.trim().to_string(),
77                });
78            } else if line.contains("test result:") && line.contains("ok") {
79                // Tests passed
80            }
81        }
82
83        Ok(diagnostics)
84    }
85
86    /// Validates graph consistency.
87    ///
88    /// Checks for orphan references and broken symbol links.
89    pub async fn graph_check(&self, _working_dir: &std::path::Path) -> Result<Vec<Diagnostic>> {
90        let mut diagnostics = Vec::new();
91
92        // For v0.4, this is a simplified check
93        // In production, would query graph for orphan references
94
95        // Placeholder: graph is assumed consistent
96        diagnostics.push(Diagnostic {
97            level: DiagnosticLevel::Info,
98            message: "Graph consistency check: skipped (not yet implemented)".to_string(),
99        });
100
101        Ok(diagnostics)
102    }
103
104    /// Runs full verification.
105    ///
106    /// # Arguments
107    ///
108    /// * `working_dir` - Directory to verify
109    pub async fn verify(&self, working_dir: &std::path::Path) -> Result<VerificationReport> {
110        let mut all_diagnostics = Vec::new();
111
112        // Run compile check
113        let compile_diags = self.compile_check(working_dir).await?;
114        all_diagnostics.extend(compile_diags);
115
116        // Run test check
117        let test_diags = self.test_check(working_dir).await?;
118        all_diagnostics.extend(test_diags);
119
120        // Run graph check
121        let graph_diags = self.graph_check(working_dir).await?;
122        all_diagnostics.extend(graph_diags);
123
124        let errors = all_diagnostics
125            .iter()
126            .filter(|d| d.level == DiagnosticLevel::Error)
127            .count();
128
129        let passed = errors == 0;
130
131        Ok(VerificationReport {
132            passed,
133            diagnostics: all_diagnostics,
134        })
135    }
136}
137
138/// Verification report.
139#[derive(Clone, Debug)]
140pub struct VerificationReport {
141    /// Whether verification passed
142    pub passed: bool,
143    /// Any diagnostics or errors
144    pub diagnostics: Vec<Diagnostic>,
145}
146
147/// Diagnostic message.
148#[derive(Clone, Debug)]
149pub struct Diagnostic {
150    /// Severity level
151    pub level: DiagnosticLevel,
152    /// Diagnostic message
153    pub message: String,
154}
155
156/// Diagnostic severity level.
157#[derive(Clone, Debug, PartialEq, Eq)]
158pub enum DiagnosticLevel {
159    /// Info message
160    Info,
161    /// Warning message
162    Warning,
163    /// Error message
164    Error,
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[tokio::test]
172    async fn test_verifier_creation() {
173        let _verifier = Verifier::new();
174
175        // Should create successfully
176        assert!(true);
177    }
178
179    #[test]
180    fn test_diagnostic_level_equality() {
181        assert_eq!(DiagnosticLevel::Error, DiagnosticLevel::Error);
182        assert_ne!(DiagnosticLevel::Error, DiagnosticLevel::Warning);
183    }
184}