#![cfg_attr(coverage_nightly, coverage(off))]
use crate::cli::commands::{QaOutputFormat, QaTaskType, QaWorkCommands};
use anyhow::Result;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QaChecklist {
pub task_id: String,
pub task_type: String,
pub generated: DateTime<Utc>,
pub categories: ChecklistCategories,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExampleScript {
pub name: String,
pub content: String,
pub description: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum EpicStatus {
Complete,
InProgress,
Pending,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EpicSummary {
pub epic_id: String,
pub total_tasks: usize,
pub total_checks: u32,
pub passed_checks: u32,
pub overall_score: f64,
pub status: EpicStatus,
pub task_scores: Vec<(String, f64)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChecklistCategories {
pub safety_ethics: Vec<ChecklistItem>,
pub code_quality: Vec<ChecklistItem>,
pub testing: Vec<ChecklistItem>,
pub documentation: Vec<ChecklistItem>,
pub process: Vec<ChecklistItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChecklistItem {
pub id: String,
pub description: String,
#[serde(deserialize_with = "deserialize_bool_lenient")]
pub checked: bool,
#[serde(deserialize_with = "deserialize_bool_lenient")]
pub automated: bool,
pub evidence: Option<String>,
}
fn deserialize_bool_lenient<'de, D>(deserializer: D) -> std::result::Result<bool, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de;
#[derive(Deserialize)]
#[serde(untagged)]
enum BoolOrString {
Bool(bool),
Str(String),
}
match BoolOrString::deserialize(deserializer)? {
BoolOrString::Bool(b) => Ok(b),
BoolOrString::Str(s) => match s.to_lowercase().as_str() {
"true" | "yes" | "1" => Ok(true),
"false" | "no" | "0" => Ok(false),
other => Err(de::Error::custom(format!("invalid bool string: {other}"))),
},
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QaValidationResult {
pub task_id: String,
pub timestamp: DateTime<Utc>,
pub categories: HashMap<String, CategoryResult>,
pub overall_score: f64,
pub passed: bool,
pub manual_checks_required: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CategoryResult {
pub name: String,
pub passed: u32,
pub total: u32,
pub items: Vec<ValidationItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationItem {
pub id: String,
pub description: String,
pub status: ValidationStatus,
pub value: Option<String>,
pub threshold: Option<String>,
pub evidence: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ValidationStatus {
Passed,
Failed,
Warning,
Skipped,
Manual,
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn handle_qa_work_command(command: QaWorkCommands) -> Result<()> {
match command {
QaWorkCommands::GenerateChecklist {
task_id,
task_type,
path,
output,
} => handle_generate_checklist(&task_id, task_type, &path, output.as_deref()).await,
QaWorkCommands::Validate {
task_id,
path,
strict,
format,
} => handle_validate(&task_id, &path, strict, format).await,
QaWorkCommands::Report {
task_id,
path,
with_evidence,
output,
format,
} => handle_report(&task_id, &path, with_evidence, output.as_deref(), format).await,
QaWorkCommands::Summary {
task_id,
path,
epic,
} => handle_summary(task_id.as_deref(), &path, epic.as_deref()).await,
QaWorkCommands::GenerateExamples {
task_id,
feature_name,
path,
output,
} => handle_generate_examples(&task_id, &feature_name, &path, output.as_deref()).await,
QaWorkCommands::Spec {
target,
path,
full,
format,
output,
threshold,
gateway_threshold,
} => {
handle_spec(
&target,
&path,
full,
format,
output.as_deref(),
threshold,
gateway_threshold,
)
.await
}
}
}
async fn handle_generate_checklist(
task_id: &str,
task_type: QaTaskType,
project_path: &Path,
output: Option<&Path>,
) -> Result<()> {
println!("Generating QA checklist for task: {}", task_id);
let checklist = generate_checklist(task_id, task_type);
let qa_dir = project_path.join(".pmat-qa").join(task_id);
fs::create_dir_all(&qa_dir)?;
let output_path = output
.map(PathBuf::from)
.unwrap_or_else(|| qa_dir.join("checklist.yaml"));
let yaml = serde_yaml_ng::to_string(&checklist)?;
fs::write(&output_path, &yaml)?;
println!("\n{}", format_checklist_text(&checklist));
println!("\nChecklist saved to: {}", output_path.display());
Ok(())
}
include!("impl_checklist_gen.rs");
include!("impl_validation.rs");
include!("impl_print.rs");
include!("impl_epic.rs");
include!("impl_spec.rs");
#[cfg(all(test, feature = "broken-tests"))]
#[path = "tests.rs"]
mod tests;