cordance-emit 0.1.1

Cordance target emitters: AGENTS.md, CLAUDE.md, .cursor/rules, .codex, axiom harness-target.
Documentation
//! `.codex/AGENTS.md` + `agents/codex/AGENTS.md` emitter.

use camino::Utf8PathBuf;
use cordance_core::pack::CordancePack;

use crate::agents_md::{axiom_load_order_body, resolve_axiom_info};
use crate::{EmitError, TargetEmitter};

pub struct CodexEmitter;

/// Render the `.codex/AGENTS.md` body.
///
/// `version` and `axiom_source` are target-controlled. Both pass through
/// [`cordance_core::fence::sanitise_fenced_value`] inside `axiom_load_order_body`, but this
/// function also defends in depth: the rendered codex file is parsed by
/// `cordance_core::fence::find_regions` during merge, and a stray begin/end
/// marker in either string would silently change the merge shape. Sanitising
/// here is idempotent (no fence markers / newlines remain after the first
/// pass) and keeps the invariant local. (Round-2 fix-C.)
fn codex_agents_content(version: &str, axiom_source: &str) -> String {
    let version = cordance_core::fence::sanitise_fenced_value(version);
    let axiom_source = cordance_core::fence::sanitise_fenced_value(axiom_source);
    let load_order = axiom_load_order_body(&version, &axiom_source);
    format!(
        "# AGENTS.md — Codex\n\
         \n\
         <!-- Generated by Cordance. -->\n\
         \n\
         {load_order}\n"
    )
}

impl TargetEmitter for CodexEmitter {
    fn name(&self) -> &'static str {
        "codex"
    }

    fn render(&self, pack: &CordancePack) -> Result<Vec<(Utf8PathBuf, Vec<u8>)>, EmitError> {
        let (version, axiom_source) = resolve_axiom_info(pack);
        let content = codex_agents_content(&version, &axiom_source);
        let bytes = content.into_bytes();

        Ok(vec![
            (".codex/AGENTS.md".into(), bytes.clone()),
            ("agents/codex/AGENTS.md".into(), bytes),
        ])
    }
}