selfware 0.6.3

Your personal AI workshop — software you own, software that lasts
Documentation
use selfware::evolve::diagnostics::{AnalysisKind, AnalysisReport};
use selfware::evolve::git::{GitFileState, GitStatusReport};
use selfware::evolve::readiness::{
    DeterministicRecommendation, GateEvidence, GateState, ReadinessGate, ReadinessReport,
    RecommendationHop,
};

#[test]
fn test_readiness_report_preserves_gate_evidence_and_multi_hop_actions() {
    let evidence = GateEvidence {
        source: "cargo check --all-targets".to_string(),
        detail: "unused function".to_string(),
        path: Some("src/dead.rs".to_string()),
        line: Some(14),
    };
    let report = ReadinessReport {
        ready: false,
        status: "blocked".to_string(),
        gates: vec![
            ReadinessGate {
                id: "compile".to_string(),
                label: "Cargo check".to_string(),
                state: GateState::Fail,
                summary: "1 error".to_string(),
                evidence: vec![evidence.clone()],
            },
            ReadinessGate {
                id: "coverage".to_string(),
                label: "Coverage delta".to_string(),
                state: GateState::Unknown,
                summary: "No fresh coverage run".to_string(),
                evidence: Vec::new(),
            },
        ],
        recommendations: vec![DeterministicRecommendation {
            id: "R1".to_string(),
            severity: "warning".to_string(),
            title: "Investigate possible dead code".to_string(),
            rationale: "Compiler evidence marks it unused".to_string(),
            evidence: vec![evidence],
            evidence_complete: true,
            hops: vec![
                RecommendationHop {
                    order: 1,
                    action: "Inspect declaration and references".to_string(),
                    target: "src/dead.rs:14".to_string(),
                    verification: "References are enumerated".to_string(),
                },
                RecommendationHop {
                    order: 2,
                    action: "Stage deletion preview".to_string(),
                    target: "src/dead.rs".to_string(),
                    verification: "Preview remains non-executable".to_string(),
                },
            ],
        }],
        git: GitStatusReport {
            branch: Some("self-evolve-feature".to_string()),
            head: "abc123".to_string(),
            dirty: true,
            files: vec![GitFileState {
                path: "src/dead.rs".to_string(),
                index: "clean".to_string(),
                worktree: "modified".to_string(),
            }],
        },
        analyses: vec![AnalysisReport {
            kind: AnalysisKind::Check,
            label: "Cargo check".to_string(),
            command: vec!["cargo".to_string(), "check".to_string()],
            success: false,
            exit_code: Some(101),
            duration_ms: 20,
            diagnostics: Vec::new(),
            errors: 1,
            warnings: 0,
            stdout_tail: String::new(),
            stderr_tail: String::new(),
            evidence_complete: true,
        }],
    };

    let value = serde_json::to_value(&report).unwrap();

    assert_eq!(value["ready"], false);
    assert_eq!(value["status"], "blocked");
    assert_eq!(value["gates"][0]["state"], "fail");
    assert_eq!(value["gates"][1]["state"], "unknown");
    assert_eq!(value["gates"][0]["evidence"][0]["path"], "src/dead.rs");
    assert_eq!(value["recommendations"][0]["hops"][1]["order"], 2);
    assert_eq!(value["recommendations"][0]["evidence_complete"], true);
    assert_eq!(value["git"]["files"][0]["worktree"], "modified");
    assert_eq!(value["analyses"][0]["kind"], "cargo_check");

    let decoded: ReadinessReport = serde_json::from_value(value).unwrap();
    assert!(!decoded.ready);
    assert_eq!(decoded.gates[0].state, GateState::Fail);
    assert_eq!(decoded.gates[1].state, GateState::Unknown);
    assert_eq!(decoded.recommendations[0].hops.len(), 2);
}