bijux-dag-app 0.4.1

Application orchestration and response-shaping layer for Bijux DAG command surfaces.
Documentation
use crate::commands::{DagCli, DiffModeArg};
use crate::{emit_json, print_human_diff, replay_service, ExitCode};
use std::path::Path;

pub(crate) fn handle_diff_command(
    cli: &DagCli,
    run_a: &Path,
    run_b: &Path,
    mode: DiffModeArg,
    node: Option<&str>,
    explain: bool,
    command_name: &str,
) -> Result<ExitCode, ExitCode> {
    let semantic_diff = matches!(mode, DiffModeArg::Semantic)
        .then(|| replay_service::run_diff_from_dirs(run_a, run_b))
        .transpose()?;
    let payload = replay_service::run_diff_mode_payload(run_a, run_b, mode, node)?;
    if cli.json {
        return emit_json(cli, command_name, true, payload.clone(), Vec::new(), ExitCode::SUCCESS);
    }
    if matches!(mode, DiffModeArg::Semantic) {
        if let Some(diff) = semantic_diff {
            let human = serde_json::to_value(diff).map_err(|_| ExitCode::from(3))?;
            print_human_diff(&human);
        } else {
            print_human_diff(&payload);
        }
    } else {
        println!("{}", serde_json::to_string_pretty(&payload).unwrap());
    }
    if explain {
        if matches!(mode, DiffModeArg::Semantic | DiffModeArg::Summary | DiffModeArg::Raw) {
            println!("explain: graph fingerprint change implies cache invalidation");
            println!("explain: node fingerprint changes indicate recomputation scope");
            let semantic = if matches!(mode, DiffModeArg::Semantic) {
                Some(&payload)
            } else {
                payload.get("semantic").or(Some(&payload))
            };
            if let Some(semantic) = semantic {
                let replay_equivalent = semantic
                    .get("replay_equivalence")
                    .and_then(|value| value.get("equivalent"))
                    .and_then(serde_json::Value::as_bool);
                let reasons = semantic
                    .get("replay_equivalence")
                    .and_then(|value| value.get("reasons"))
                    .cloned()
                    .unwrap_or(serde_json::Value::Null);
                let cause_groups = semantic
                    .get("replay_equivalence")
                    .and_then(|value| value.get("cause_groups"))
                    .cloned()
                    .unwrap_or(serde_json::Value::Null);
                let summary = payload.get("root_cause_summary").or_else(|| {
                    semantic
                        .get("replay_equivalence")
                        .and_then(|value| value.get("reason_report"))
                        .and_then(|value| value.get("summary"))
                });
                println!("replay_equivalent: {}", replay_equivalent.unwrap_or(false));
                if reasons.is_array() && reasons.as_array().is_some_and(|items| !items.is_empty()) {
                    println!("replay_difference_reasons: {}", reasons);
                }
                if let Some(summary) = summary.and_then(serde_json::Value::as_str) {
                    println!("replay_reason: {summary}");
                }
                if cause_groups.is_object()
                    && cause_groups.as_object().is_some_and(|map| !map.is_empty())
                {
                    println!("replay_cause_groups: {}", cause_groups);
                }
            }
        }
    }
    Ok(ExitCode::SUCCESS)
}