ai-dispatch 8.28.0

Multi-AI CLI team orchestrator
use std::process::Command;

fn git(args: &[&str]) -> Option<String> {
    let out = Command::new("git").args(args).output().ok()?;
    if !out.status.success() {
        return None;
    }
    Some(String::from_utf8_lossy(&out.stdout).trim().to_string())
}

fn is_version_tag(tag: &str) -> bool {
    let mut parts = tag.strip_prefix('v').unwrap_or("").split('.');
    matches!(
        (parts.next(), parts.next(), parts.next(), parts.next()),
        (Some(a), Some(b), Some(c), None)
            if a.chars().all(|ch| ch.is_ascii_digit())
                && b.chars().all(|ch| ch.is_ascii_digit())
                && c.chars().all(|ch| ch.is_ascii_digit())
    )
}

fn build_embedded_changelog() -> Option<String> {
    let tags = git(&["tag", "--sort=-version:refname"])?
        .lines()
        .filter(|t| is_version_tag(t))
        .map(str::to_string)
        .collect::<Vec<_>>();
    let count = tags.len().min(10);
    let mut sections = Vec::with_capacity(count);
    for i in 0..count {
        let tag = &tags[i];
        let prev = tags.get(i + 1).map(String::as_str);
        let range = prev.map_or_else(|| tag.to_string(), |p| format!("{p}..{tag}"));

        let commits_out = git(&["log", "--no-merges", "--format=%s", &range])?;
        let commits = commits_out.lines().map(str::to_string).collect::<Vec<_>>();
        let commits = if commits.is_empty() {
            vec!["No commits found".to_string()]
        } else {
            commits
        };

        let date_out = git(&["log", "-1", "--format=%ci", tag])?;
        let date = date_out.split_whitespace().next().unwrap_or("").to_string();
        let commits_text = commits
            .iter()
            .map(|c| format!("- {c}"))
            .collect::<Vec<_>>()
            .join("\n");

        sections.push(format!("## {} ({})\n{}\n", tag, date, commits_text));
    }
    Some(sections.join("\n"))
}

fn escape_newlines(s: &str) -> String {
    s.replace('\n', "__AID_NL__")
}

fn main() {
    println!("cargo:rerun-if-changed=.git/refs/tags");
    let text = build_embedded_changelog().unwrap_or_default();
    println!("cargo:rustc-env=AID_CHANGELOG={}", escape_newlines(&text));
}