ripr 0.8.0

Find static mutation-exposure gaps before expensive mutation testing
Documentation
use crate::app::agent_status::AgentStatusCommand;
use serde_json::Value;

use super::types::{
    AgentReviewArtifact, AgentReviewLimits, AgentReviewNextAction, AgentReviewStaticMovement,
    AgentReviewSummaryReport, AgentReviewSurface, AgentReviewTargetSeam, AgentReviewTextSummary,
};

pub(crate) fn render_agent_review_summary_json(
    report: &AgentReviewSummaryReport,
) -> Result<String, String> {
    let value = serde_json::json!({
        "schema_version": report.schema_version,
        "tool": report.tool,
        "status": report.status,
        "root": report.root,
        "target_seam": report.target_seam.as_ref().map(target_seam_json),
        "static_movement": static_movement_json(&report.static_movement),
        "next_command": report.next_command.as_ref().map(agent_status_command_json),
        "surfaces": report.surfaces.iter().map(surface_json).collect::<Vec<_>>(),
        "ci_artifacts": report.ci_artifacts.iter().map(artifact_json).collect::<Vec<_>>(),
        "reviewer_summary": reviewer_summary_json(&report.reviewer_summary),
        "limits": limits_json(&report.limits)
    });
    serde_json::to_string_pretty(&value)
        .map(|mut rendered| {
            rendered.push('\n');
            rendered
        })
        .map_err(|err| format!("failed to render agent review summary JSON: {err}"))
}

fn target_seam_json(seam: &AgentReviewTargetSeam) -> Value {
    serde_json::json!({
        "seam_id": seam.seam_id,
        "source": seam.source,
        "file": seam.file,
        "line": seam.line,
        "seam_kind": seam.seam_kind
    })
}

fn static_movement_json(movement: &AgentReviewStaticMovement) -> Value {
    serde_json::json!({
        "state": movement.state,
        "before_class": movement.before_class,
        "after_class": movement.after_class,
        "grip_class": movement.grip_class,
        "evidence_artifact": movement.evidence_artifact,
        "verify_artifact": movement.verify_artifact,
        "summary": movement.summary,
        "next_action": movement.next_action.as_ref().map(next_action_json)
    })
}

fn next_action_json(next_action: &AgentReviewNextAction) -> Value {
    serde_json::json!({
        "kind": next_action.kind,
        "summary": next_action.summary,
        "recommended_action": next_action.recommended_action
    })
}

fn surface_json(surface: &AgentReviewSurface) -> Value {
    serde_json::json!({
        "name": surface.name,
        "label": surface.label,
        "path": surface.path,
        "state": surface.state,
        "status": surface.status,
        "required": surface.required,
        "summary": surface.summary
    })
}

fn artifact_json(artifact: &AgentReviewArtifact) -> Value {
    serde_json::json!({
        "name": artifact.name,
        "path": artifact.path,
        "state": artifact.state
    })
}

fn reviewer_summary_json(summary: &AgentReviewTextSummary) -> Value {
    serde_json::json!({
        "headline": summary.headline,
        "what_changed": summary.what_changed,
        "evidence": summary.evidence,
        "remaining": summary.remaining,
        "reviewer_should_inspect": summary.reviewer_should_inspect
    })
}

fn limits_json(limits: &AgentReviewLimits) -> Value {
    serde_json::json!({
        "static_artifact_relationship": limits.static_artifact_relationship,
        "runtime_mutation_execution": limits.runtime_mutation_execution,
        "automatic_edits": limits.automatic_edits,
        "generated_tests": limits.generated_tests
    })
}

fn agent_status_command_json(command: &AgentStatusCommand) -> Value {
    serde_json::json!({
        "step": command.step,
        "artifact": command.artifact,
        "reason": command.reason,
        "command": command.command
    })
}