pmat 3.15.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
//! QA Work Handler - Toyota Way Quality Validation (GH-102)
//!
//! Implements systematic quality validation after work completion:
//! - Generate task-specific QA checklists (25-point Toyota Way)
//! - Run automated validation (complexity, coverage, mutation)
//! - Generate audit trail reports
//! - Track QA status across tasks

#![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;

/// QA Checklist with 25-point Toyota Way validation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QaChecklist {
    pub task_id: String,
    pub task_type: String,
    pub generated: DateTime<Utc>,
    pub categories: ChecklistCategories,
}

/// Example script for QA validation (V2 feature)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExampleScript {
    pub name: String,
    pub content: String,
    pub description: String,
}

/// Epic QA summary status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum EpicStatus {
    /// All tasks complete (100%)
    Complete,
    /// At least one task in progress
    InProgress,
    /// No tasks started
    Pending,
}

/// Epic QA summary aggregation
#[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)]
/// Checklist categories.
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)]
/// Checklist item.
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>,
}

/// Deserialize a bool that may have been serialized as a string ("false"/"true")
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}"))),
        },
    }
}

/// QA Validation Result
#[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)]
/// Result of category operation.
pub struct CategoryResult {
    pub name: String,
    pub passed: u32,
    pub total: u32,
    pub items: Vec<ValidationItem>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
/// Validation item.
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)]
/// Status of validation operation.
pub enum ValidationStatus {
    Passed,
    Failed,
    Warning,
    Skipped,
    Manual,
}

/// Handle all qa-work subcommands
#[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
        }
    }
}

/// Generate a QA checklist for a task
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);

    // Ensure .pmat-qa directory exists
    let qa_dir = project_path.join(".pmat-qa").join(task_id);
    fs::create_dir_all(&qa_dir)?;

    // Output path
    let output_path = output
        .map(PathBuf::from)
        .unwrap_or_else(|| qa_dir.join("checklist.yaml"));

    // Write checklist
    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(())
}

// Implementation split for file health compliance (CB-040)
include!("impl_checklist_gen.rs");
include!("impl_validation.rs");
include!("impl_print.rs");
include!("impl_epic.rs");
include!("impl_spec.rs");

// Tests extracted to qa_work_handler_tests.rs for file health compliance (CB-040)
// TEMPORARILY DISABLED: File splitting broke syntax
#[cfg(all(test, feature = "broken-tests"))]
#[path = "tests.rs"]
mod tests;