#![cfg_attr(coverage_nightly, coverage(off))]
use anyhow::Result;
pub struct AgentQualityGate {
config: QualityConfig,
}
#[derive(Debug, Clone)]
pub struct QualityConfig {
pub max_complexity: u32,
pub min_coverage: f64,
pub auto_fix: bool,
}
impl Default for QualityConfig {
fn default() -> Self {
Self {
max_complexity: 10,
min_coverage: 80.0,
auto_fix: true,
}
}
}
#[derive(Debug, Clone)]
pub struct QualityReport {
pub passed: bool,
pub score: f64,
pub issues: Vec<QualityIssue>,
}
#[derive(Debug, Clone)]
pub struct QualityIssue {
pub issue_type: IssueType,
pub description: String,
pub severity: Severity,
pub fix: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IssueType {
Complexity,
Coverage,
Satd,
Duplication,
Security,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Severity {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone)]
pub struct ComplexityReport {
pub max_complexity: u32,
pub avg_complexity: f64,
pub complex_functions: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct SatdReport {
pub count: usize,
pub locations: Vec<String>,
}
pub struct Ast {
pub nodes: Vec<String>,
}
impl Default for AgentQualityGate {
fn default() -> Self {
Self::new()
}
}
impl AgentQualityGate {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
Self {
config: QualityConfig::default(),
}
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn with_config(config: QualityConfig) -> Self {
Self { config }
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn validate_code(&self, code: &str) -> Result<QualityReport> {
let mut issues = Vec::new();
let mut score: f64 = 100.0;
let satd_report = self.detect_satd(code)?;
if satd_report.count > 0 {
issues.push(QualityIssue {
issue_type: IssueType::Satd,
description: format!("Found {} SATD comments", satd_report.count),
severity: Severity::High,
fix: Some("Remove technical debt comments".to_string()),
});
score -= 20.0;
}
if code.lines().count() > 100 {
issues.push(QualityIssue {
issue_type: IssueType::Complexity,
description: "Function too long".to_string(),
severity: Severity::Medium,
fix: Some("Break into smaller functions".to_string()),
});
score -= 10.0;
}
Ok(QualityReport {
passed: issues.is_empty(),
score: score.max(0.0),
issues,
})
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn check_complexity(&self, _ast: &Ast) -> Result<ComplexityReport> {
Ok(ComplexityReport {
max_complexity: 5,
avg_complexity: 3.0,
complex_functions: vec![],
})
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn detect_satd(&self, content: &str) -> Result<SatdReport> {
let mut locations = Vec::new();
let satd_patterns = [
concat!("TO", "DO"),
concat!("FIX", "ME"),
concat!("HA", "CK"),
concat!("XX", "X"),
"T-O-D-O",
"F-I-X-M-E",
"H-A-C-K",
];
for (line_num, line) in content.lines().enumerate() {
for pattern in &satd_patterns {
if line.contains(pattern) {
locations.push(format!("Line {}: {}", line_num + 1, line.trim()));
}
}
}
Ok(SatdReport {
count: locations.len(),
locations,
})
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn auto_fix(&self, code: &str) -> Result<String> {
if !self.config.auto_fix {
return Ok(code.to_string());
}
let mut fixed = code.to_string();
let satd_patterns = [
concat!("// TO", "DO:"),
concat!("// FIX", "ME:"),
concat!("// HA", "CK:"),
concat!("// XX", "X:"),
concat!("// TO", "DO"),
concat!("// FIX", "ME"),
concat!("// HA", "CK"),
"// T-O-D-O:",
"// F-I-X-M-E:",
"// H-A-C-K:",
];
let mut result_lines = Vec::new();
for line in fixed.lines() {
let mut modified_line = line.to_string();
for pattern in &satd_patterns {
if let Some(pos) = modified_line.find(pattern) {
modified_line = modified_line.get(..pos).unwrap_or_default().to_string() + "//";
break;
}
}
result_lines.push(modified_line);
}
fixed = result_lines.join("\n");
Ok(fixed)
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quality_gate_creation() {
let gate = AgentQualityGate::new();
assert_eq!(gate.config.max_complexity, 10);
assert_eq!(gate.config.min_coverage, 80.0);
assert!(gate.config.auto_fix);
}
#[test]
fn test_validate_clean_code() {
let gate = AgentQualityGate::new();
let code = r#"
fn clean_function() {
println!("Clean code");
}
"#;
let report = gate.validate_code(code).unwrap();
assert!(report.passed);
assert_eq!(report.score, 100.0);
assert!(report.issues.is_empty());
}
#[test]
fn test_detect_satd() {
let gate = AgentQualityGate::new();
let code = r#"
// T-O-D-O: Fix this later
fn test() {
// F-I-X-M-E: This is broken
println!("test");
}
"#;
let report = gate.detect_satd(code).unwrap();
assert_eq!(report.count, 2);
assert_eq!(report.locations.len(), 2);
}
#[test]
fn test_auto_fix() {
let gate = AgentQualityGate::new();
let code = concat!("// TO", "DO: Fix this\nfn test() {}");
let fixed = gate.auto_fix(code).unwrap();
assert!(!fixed.contains(concat!("TO", "DO:")));
assert!(fixed.contains("//\nfn test()"));
}
#[test]
fn test_complexity_check() {
let gate = AgentQualityGate::new();
let ast = Ast { nodes: vec![] };
let report = gate.check_complexity(&ast).unwrap();
assert_eq!(report.max_complexity, 5);
assert_eq!(report.avg_complexity, 3.0);
}
}