use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fs;
use std::path::Path;
#[derive(Debug, Deserialize)]
struct AgentFrontmatter {
name: String,
description: String,
}
#[derive(Debug, Serialize)]
struct CodexAgentRole {
name: String,
description: String,
developer_instructions: String,
}
const AGENT_SOURCES: &[&str] = &[
".claude/agents/rfc-reviewer.md",
".claude/agents/adr-reviewer.md",
".claude/agents/wi-reviewer.md",
".claude/agents/compliance-checker.md",
];
pub fn generate_codex_agent_templates() -> Result<(), Box<dyn Error>> {
let mut entries = Vec::new();
for source in AGENT_SOURCES {
let content =
fs::read_to_string(source).map_err(|e| format!("failed to read {source}: {e}"))?;
let (frontmatter, body) = parse_agent_frontmatter(&content)
.map_err(|e| format!("failed to parse frontmatter in {source}: {e}"))?;
let role = CodexAgentRole {
name: frontmatter.name.clone(),
description: frontmatter.description.clone(),
developer_instructions: body.to_string(),
};
let toml_content = toml::to_string_pretty(&role)
.map_err(|e| format!("failed to serialize codex TOML for {source}: {e}"))?;
let out_filename = format!("agents/{}.toml", frontmatter.name);
entries.push((out_filename, toml_content));
}
let mut out = String::new();
out.push_str("// @generated by build.rs from .claude/agents/*.md\n");
out.push_str("// Do not edit manually.\n\n");
out.push_str("pub const AGENT_TEMPLATES_CODEX: &[(&str, &str)] = &[\n");
for (path, content) in &entries {
out.push_str(&format!(" ({:?}, {:?}),\n", path, content));
}
out.push_str("];\n");
let out_dir = std::env::var("OUT_DIR")?;
let out_path = Path::new(&out_dir).join("agent_codex_templates.rs");
fs::write(out_path, out)?;
Ok(())
}
fn parse_agent_frontmatter(content: &str) -> Result<(AgentFrontmatter, &str), Box<dyn Error>> {
let content = content.strip_prefix("---").ok_or("missing opening ---")?;
let (yaml_block, rest) = content.split_once("---").ok_or("missing closing ---")?;
let fm: AgentFrontmatter = serde_yaml::from_str(yaml_block.trim())?;
let body = rest.trim_start_matches(['\n', '\r']);
Ok((fm, body))
}