govctl 0.9.0

Project governance CLI for RFC, ADR, and Work Item management
use crate::config::Config;
use crate::diagnostic::DiagnosticResult;
use crate::model::ProjectIndex;

pub(super) fn clause_deletion_referrers(
    config: &Config,
    index: &ProjectIndex,
    clause_id: &str,
) -> DiagnosticResult<Vec<String>> {
    let mut referrers = project_referrers(index, clause_id, None, false);
    referrers.extend(guard_referrers(config, clause_id)?);
    referrers.sort();
    referrers.dedup();
    Ok(referrers)
}

pub(super) fn work_item_deletion_referrers(index: &ProjectIndex, work_id: &str) -> Vec<String> {
    project_referrers(index, work_id, Some(work_id), true)
}

fn project_referrers(
    index: &ProjectIndex,
    target_id: &str,
    skip_work_id: Option<&str>,
    include_work_dependencies: bool,
) -> Vec<String> {
    let mut referenced_by = Vec::new();

    for rfc in &index.rfcs {
        if rfc.rfc.refs.iter().any(|ref_id| ref_id == target_id) {
            referenced_by.push(rfc.rfc.rfc_id.clone());
        }
    }

    for adr in &index.adrs {
        if adr
            .spec
            .govctl
            .refs
            .iter()
            .any(|ref_id| ref_id == target_id)
        {
            referenced_by.push(adr.spec.govctl.id.clone());
        }
    }

    for work in &index.work_items {
        if skip_work_id == Some(work.spec.govctl.id.as_str()) {
            continue;
        }

        let has_ref = work
            .spec
            .govctl
            .refs
            .iter()
            .any(|ref_id| ref_id == target_id);
        let has_dependency = include_work_dependencies
            && work
                .spec
                .govctl
                .depends_on
                .iter()
                .any(|dep_id| dep_id == target_id);

        if has_ref || has_dependency {
            referenced_by.push(work.spec.govctl.id.clone());
        }
    }

    referenced_by
}

fn guard_referrers(config: &Config, target_id: &str) -> DiagnosticResult<Vec<String>> {
    Ok(crate::parse::load_guards(config)?
        .into_iter()
        .filter(|guard| guard.meta().refs.iter().any(|ref_id| ref_id == target_id))
        .map(|guard| guard.meta().id.clone())
        .collect())
}