1use crate::{AgentError, Result};
7use forge_core::Forge;
8use std::process::Command;
9use std::sync::Arc;
10
11#[derive(Clone)]
15pub struct Verifier {
16 forge: Arc<Forge>,
18}
19
20impl Verifier {
21 pub fn new(forge: Forge) -> Self {
23 Self {
24 forge: Arc::new(forge),
25 }
26 }
27
28 pub async fn compile_check(&self, working_dir: &std::path::Path) -> Result<Vec<Diagnostic>> {
34 let output = Command::new("cargo")
35 .args(["check", "--message-format=short"])
36 .current_dir(working_dir)
37 .output()
38 .map_err(|e| AgentError::VerificationFailed(format!("Cargo check failed: {}", e)))?;
39
40 let stdout = String::from_utf8_lossy(&output.stdout);
41 let stderr = String::from_utf8_lossy(&output.stderr);
42
43 let mut diagnostics = Vec::new();
44 for line in stdout.lines().chain(stderr.lines()) {
45 if line.contains("error:") {
46 diagnostics.push(Diagnostic {
47 level: DiagnosticLevel::Error,
48 message: line.trim().to_string(),
49 });
50 } else if line.contains("warning:") {
51 diagnostics.push(Diagnostic {
52 level: DiagnosticLevel::Warning,
53 message: line.trim().to_string(),
54 });
55 }
56 }
57
58 Ok(diagnostics)
59 }
60
61 pub async fn test_check(&self, working_dir: &std::path::Path) -> Result<Vec<Diagnostic>> {
67 let output = Command::new("cargo")
68 .args(["test", "--message-format=short"])
69 .current_dir(working_dir)
70 .output()
71 .map_err(|e| AgentError::VerificationFailed(format!("Cargo test failed: {}", e)))?;
72
73 let stdout = String::from_utf8_lossy(&output.stdout);
74 let stderr = String::from_utf8_lossy(&output.stderr);
75
76 let mut diagnostics = Vec::new();
77
78 for line in stdout.lines().chain(stderr.lines()) {
80 if line.contains("test result:") && line.contains("FAILED") {
81 diagnostics.push(Diagnostic {
82 level: DiagnosticLevel::Error,
83 message: line.trim().to_string(),
84 });
85 } else if line.contains("test result:") && line.contains("ok") {
86 }
88 }
89
90 Ok(diagnostics)
91 }
92
93 pub async fn graph_check(&self, _working_dir: &std::path::Path) -> Result<Vec<Diagnostic>> {
97 let mut diagnostics = Vec::new();
98
99 diagnostics.push(Diagnostic {
104 level: DiagnosticLevel::Info,
105 message: "Graph consistency check: skipped (not yet implemented)".to_string(),
106 });
107
108 Ok(diagnostics)
109 }
110
111 pub async fn verify(&self, working_dir: &std::path::Path) -> Result<VerificationReport> {
117 let mut all_diagnostics = Vec::new();
118
119 let compile_diags = self.compile_check(working_dir).await?;
121 all_diagnostics.extend(compile_diags);
122
123 let test_diags = self.test_check(working_dir).await?;
125 all_diagnostics.extend(test_diags);
126
127 let graph_diags = self.graph_check(working_dir).await?;
129 all_diagnostics.extend(graph_diags);
130
131 let errors = all_diagnostics
132 .iter()
133 .filter(|d| d.level == DiagnosticLevel::Error)
134 .count();
135
136 let passed = errors == 0;
137
138 Ok(VerificationReport {
139 passed,
140 diagnostics: all_diagnostics,
141 })
142 }
143}
144
145#[derive(Clone, Debug)]
147pub struct VerificationReport {
148 pub passed: bool,
150 pub diagnostics: Vec<Diagnostic>,
152}
153
154#[derive(Clone, Debug)]
156pub struct Diagnostic {
157 pub level: DiagnosticLevel,
159 pub message: String,
161}
162
163#[derive(Clone, Debug, PartialEq, Eq)]
165pub enum DiagnosticLevel {
166 Info,
168 Warning,
170 Error,
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use tempfile::TempDir;
178
179 #[tokio::test]
180 async fn test_verifier_creation() {
181 let temp_dir = TempDir::new().unwrap();
182 let forge = Forge::open(temp_dir.path()).await.unwrap();
183 let _verifier = Verifier::new(forge);
184
185 assert!(true);
187 }
188
189 #[test]
190 fn test_diagnostic_level_equality() {
191 assert_eq!(DiagnosticLevel::Error, DiagnosticLevel::Error);
192 assert_ne!(DiagnosticLevel::Error, DiagnosticLevel::Warning);
193 }
194}