govctl 0.9.4

Project governance CLI for RFC, ADR, and Work Item management
use std::path::Path;

use crate::config::Config;
use crate::diagnostic::{DiagnosticResult, Diagnostics};
use crate::ui;
use crate::write::{WriteOp, create_dir_all, write_file};

// Skill bundle assets are generated recursively from .claude/skills/** by build.rs.
// Implements [[RFC-0002:C-GLOBAL-COMMANDS]] and [[ADR-0028]].
include!(concat!(env!("OUT_DIR"), "/skill_assets.rs"));

/// Agent templates: (relative_path, content) pairs.
/// Source of truth: .claude/agents/; embedded at compile time.
const AGENT_TEMPLATES: &[(&str, &str)] = &[
    (
        "agents/rfc-reviewer.md",
        include_str!("../../../.claude/agents/rfc-reviewer.md"),
    ),
    (
        "agents/adr-reviewer.md",
        include_str!("../../../.claude/agents/adr-reviewer.md"),
    ),
    (
        "agents/wi-reviewer.md",
        include_str!("../../../.claude/agents/wi-reviewer.md"),
    ),
    (
        "agents/compliance-checker.md",
        include_str!("../../../.claude/agents/compliance-checker.md"),
    ),
];

// Codex agent templates generated by build.rs from .claude/agents/*.md
include!(concat!(env!("OUT_DIR"), "/agent_codex_templates.rs"));

/// Install agent skills and agents into the project's agent directory. [[ADR-0035]]
pub fn sync_skills(
    config: &Config,
    force: bool,
    format: &crate::SkillFormat,
    dir_override: Option<&Path>,
    op: WriteOp,
) -> DiagnosticResult<Diagnostics> {
    // Resolution: --dir flag > config agent_dir > format-implied default
    let format_default = match format {
        crate::SkillFormat::Claude => std::path::Path::new(".claude"),
        crate::SkillFormat::Codex => std::path::Path::new(".codex"),
    };
    let agent_dir_owned;
    let agent_dir = if let Some(dir) = dir_override {
        if dir.is_relative() {
            agent_dir_owned = config
                .gov_root
                .parent()
                .unwrap_or(std::path::Path::new("."))
                .join(dir);
            &agent_dir_owned
        } else {
            agent_dir_owned = dir.to_path_buf();
            &agent_dir_owned
        }
    } else if config.paths.agent_dir != crate::config::default_agent_dir() {
        // Config has an explicit agent_dir
        &config.paths.agent_dir
    } else {
        // Use format-implied default
        agent_dir_owned = config
            .gov_root
            .parent()
            .unwrap_or(std::path::Path::new("."))
            .join(format_default);
        &agent_dir_owned
    };

    let agent_templates: &[(&str, &str)] = match format {
        crate::SkillFormat::Claude => AGENT_TEMPLATES,
        crate::SkillFormat::Codex => AGENT_TEMPLATES_CODEX,
    };

    let mut synced = 0;
    let mut skipped = 0;

    for (rel_path, template) in SKILL_ASSETS.iter().chain(agent_templates.iter()) {
        let path = agent_dir.join(rel_path);
        let display_path = config.display_path(&path);

        // Create parent directory if needed
        if let Some(parent) = path.parent() {
            let display_parent = config.display_path(parent);
            create_dir_all(parent, op, Some(&display_parent))?;
        }

        // Check if file exists and skip if not forcing
        if path.exists() && !force && !op.is_preview() {
            skipped += 1;
            if !op.is_preview() {
                ui::sub_info(format!(
                    "Skipped {} (already exists, use -f to overwrite)",
                    path.display()
                ));
            }
            continue;
        }

        // Write template
        write_file(&path, template, op, Some(&display_path))?;

        if !op.is_preview() {
            if path.exists() && force {
                ui::info(format!("Updated {}", display_path.display()));
            } else {
                ui::created_path(&display_path);
            }
        }
        synced += 1;
    }

    if !op.is_preview() {
        if synced > 0 {
            ui::success(format!("Synced {} asset(s)", synced));
        }
        if skipped > 0 {
            ui::info(format!(
                "{} asset(s) skipped (use -f to overwrite)",
                skipped
            ));
        }
        if synced == 0 && skipped == 0 {
            ui::info("No assets to sync");
        }
    }

    Ok(vec![])
}