ccswarm 0.9.0

AI Agent Workflow DevOps toolchain complementing Claude Code Agent Teams
Documentation
use super::super::*;
use crate::hitl::{ApprovalRecord, ApprovalStatus, ApprovalStore, Gate};

impl CliRunner {
    pub(crate) async fn handle_approve(&self, action: &ApproveAction) -> Result<()> {
        match action {
            ApproveAction::Plan { id, reject, reason } => {
                self.process_approval(Gate::Plan, id, *reject, reason.as_deref())
                    .await?;
            }
            ApproveAction::RiskyEdit { id, reject, reason } => {
                self.process_approval(Gate::RiskyEdit, id, *reject, reason.as_deref())
                    .await?;
            }
            ApproveAction::Deploy { id, reject, reason } => {
                self.process_approval(Gate::Deploy, id, *reject, reason.as_deref())
                    .await?;
            }
            ApproveAction::Merge { id, reject, reason } => {
                self.process_approval(Gate::Merge, id, *reject, reason.as_deref())
                    .await?;
            }
            ApproveAction::Commit { id, reject, reason } => {
                self.process_approval(Gate::Commit, id, *reject, reason.as_deref())
                    .await?;
            }
            ApproveAction::List { status } => {
                self.list_approvals(status.as_deref()).await?;
            }
        }

        Ok(())
    }

    async fn process_approval(
        &self,
        gate: Gate,
        id: &str,
        reject: bool,
        reason: Option<&str>,
    ) -> Result<()> {
        let store = ApprovalStore::new(&self.repo_path);
        let record = store.decide(id, gate, !reject, reason).await?;
        let status = if reject { "rejected" } else { "approved" };

        if self.json_output {
            println!(
                "{}",
                serde_json::to_string_pretty(&serde_json::json!({
                    "status": "success",
                    "message": format!("Decision recorded: {}", status),
                    "data": record,
                }))?
            );
        } else {
            let status_colored = if reject {
                status.bright_red().bold()
            } else {
                status.bright_green().bold()
            };
            println!(
                "{} [{}] {} {}",
                "OK".bright_green().bold(),
                gate.to_string().bright_cyan(),
                id.bright_yellow(),
                status_colored
            );
            if let Some(r) = reason {
                println!("  Reason: {}", r);
            }
        }

        Ok(())
    }

    async fn list_approvals(&self, status_filter: Option<&str>) -> Result<()> {
        let store = ApprovalStore::new(&self.repo_path);
        let approvals: Vec<ApprovalRecord> = store
            .list()
            .await?
            .into_iter()
            .filter(|r| {
                status_filter.is_none_or(|filter| {
                    let st = match r.status {
                        ApprovalStatus::Pending => "pending",
                        ApprovalStatus::Approved => "approved",
                        ApprovalStatus::Rejected => "rejected",
                    };
                    st == filter
                })
            })
            .collect();

        if self.json_output {
            println!(
                "{}",
                serde_json::to_string_pretty(&serde_json::json!({
                    "status": "success",
                    "data": approvals,
                }))?
            );
        } else if approvals.is_empty() {
            println!("No approval records found.");
        } else {
            println!("{}", "Approvals".bright_cyan().bold());
            println!("{}", "=========".bright_cyan());
            for record in &approvals {
                let status_colored = match record.status {
                    ApprovalStatus::Approved => "approved".bright_green(),
                    ApprovalStatus::Rejected => "rejected".bright_red(),
                    ApprovalStatus::Pending => "pending".bright_yellow(),
                };
                let ts = record
                    .decided_at
                    .as_deref()
                    .or(record.requested_at.as_deref())
                    .unwrap_or("?");
                println!(
                    "  {} [{}] {} ({})",
                    record.id.bright_yellow(),
                    record.gate.to_string().bright_cyan(),
                    status_colored,
                    ts.bright_black()
                );
            }
            println!("\nTotal: {} records", approvals.len());
        }

        Ok(())
    }
}