aida-core 0.2.0

Core library for AIDA — AI-native requirements management with structured context for AI coding agents
Documentation
// trace:FR-0269 - Template embedding at compile time | ai:claude:high
//! Build script to embed template files at compile time.
//!
//! This allows the binary to be self-contained while keeping templates
//! as separate editable files during development.

use std::env;
use std::fs;
use std::path::Path;

fn main() {
    // Rerun if any template files change
    println!("cargo:rerun-if-changed=templates/");

    let out_dir = env::var("OUT_DIR").unwrap();
    let dest_path = Path::new(&out_dir).join("embedded_templates.rs");

    let mut code = String::new();
    code.push_str("// Auto-generated by build.rs - DO NOT EDIT\n");
    code.push_str("// This file embeds template contents at compile time\n\n");

    // Collect all templates
    code.push_str("pub static EMBEDDED_TEMPLATES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {\n");
    code.push_str("    let mut m = HashMap::new();\n");

    // Skills
    embed_directory(&mut code, "templates/skills", "skills");

    // Commands
    embed_directory(&mut code, "templates/commands", "commands");

    // Hooks
    embed_directory(&mut code, "templates/hooks", "hooks");

    // Settings (Claude Code configuration)
    embed_file(&mut code, "templates/settings.json", "settings.json");

    code.push_str("    m\n");
    code.push_str("});\n");

    // Generate a list of template categories for introspection
    code.push_str("\n/// Categories of embedded templates\n");
    code.push_str("pub static TEMPLATE_CATEGORIES: &[(&str, &str)] = &[\n");
    code.push_str(
        "    (\"skills\", \"Claude Code Skills - Requirements-driven development workflows\"),\n",
    );
    code.push_str("    (\"commands\", \"Slash Commands - Quick actions for common tasks\"),\n");
    code.push_str("    (\"hooks\", \"Hooks - Git and Claude Code integration hooks\"),\n");
    code.push_str("    (\"settings.json\", \"Settings - Claude Code configuration\"),\n");
    code.push_str("];\n");

    fs::write(&dest_path, code).unwrap();
}

fn embed_file(code: &mut String, file_path: &str, key: &str) {
    let path = Path::new(file_path);
    if path.exists() {
        let rel_path = file_path.replace("\\", "/");
        code.push_str(&format!(
            "    m.insert(\"{}\", include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/{}\")).trim_end());\n",
            key, rel_path
        ));
    }
}

fn embed_directory(code: &mut String, dir: &str, prefix: &str) {
    let dir_path = Path::new(dir);
    if !dir_path.exists() {
        return;
    }

    if let Ok(entries) = fs::read_dir(dir_path) {
        // Sort entries by filename for deterministic build output
        let mut sorted_entries: Vec<_> = entries.flatten().collect();
        sorted_entries.sort_by_key(|e| e.file_name());

        for entry in sorted_entries {
            let path = entry.path();
            if path.is_file() {
                let file_name = path.file_name().unwrap().to_str().unwrap();
                let key = format!("{}/{}", prefix, file_name);

                // Use include_str! for compile-time embedding
                let rel_path = path.to_str().unwrap().replace("\\", "/");
                code.push_str(&format!(
                    "    m.insert(\"{}\", include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/{}\")).trim_end());\n",
                    key, rel_path
                ));
            }
        }
    }
}