//! Claude Code-specific emitter helpers.
//!
//! Pure functions: no I/O. PR 2 uses the prompt-composition helpers only.
//! PR 3 will extend this module with `~/.claude/agents/<role>.md` writer
//! and minimums check.

use std::path::{Path, PathBuf};

use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};

/// Frontmatter marker identifying files that were auto-generated by mission_spawn.
/// Files without this marker are treated as operator-authored and never overwritten
/// or deleted automatically.
pub const AUTO_GENERATED_MARKER: &str = "colmena_auto_generated: true";

/// Tools that MUST appear in a reviewer's `tools:` frontmatter for ELO to close.
pub const REVIEWER_REQUIRED_TOOLS: &[&str] = &[
    "mcp__colmena__review_evaluate",
    "mcp__colmena__findings_query",
];

/// Tools that MUST appear in a worker's `tools:` frontmatter for ELO to close.
pub const WORKER_REQUIRED_TOOLS: &[&str] = &[
    "mcp__colmena__review_submit",
    "mcp__colmena__findings_query",
];

/// MCP tools that every mission role needs to guarantee the ELO cycle can close
/// regardless of whether the role is assigned as author, reviewer, or both.
///
/// Union of [`WORKER_REQUIRED_TOOLS`] and [`REVIEWER_REQUIRED_TOOLS`]. Used by
/// `mission_spawn` to bundle delegations and auto-generated subagent `tools:`
/// frontmatter so a role YAML that only declares `review_submit` still gets
/// `review_evaluate` when it participates in a mission (and vice versa).
pub const ELO_CYCLE_TOOLS: &[&str] = &[
    "mcp__colmena__review_submit",
    "mcp__colmena__review_evaluate",
    "mcp__colmena__findings_query",
];

/// Return a tool set that is the union of `role_tools` and [`ELO_CYCLE_TOOLS`],
/// preserving original order and appending any missing cycle tools at the end.
///
/// Rationale: the role YAML is the operator's intent, but mission participation
/// adds structural requirements the operator may not have thought about (a role
/// can be selected as reviewer even if its YAML only lists author tools). This
/// helper makes `mission_spawn`'s generated delegations + subagent files and the
/// CLAUDE.md pre-approved-ops block consistent with that reality.
pub fn mission_tool_set(role_tools: &[String]) -> Vec<String> {
    let mut out: Vec<String> = role_tools.to_vec();
    for required in ELO_CYCLE_TOOLS {
        if !out.iter().any(|existing| existing == required) {
            out.push((*required).to_string());
        }
    }
    out
}

/// Outcome of validating an existing subagent .md file against minimums.
#[derive(Debug, Clone, PartialEq)]
pub enum MinimumsCheck {
    /// File covers all structural contracts. Respect it, do not overwrite.
    Pass,
    /// File is missing one or more contracts. Caller aborts unless --overwrite.
    Fail { reasons: Vec<String> },
    /// File does not exist yet. Safe to create.
    Absent,
}

/// Parsed subset of subagent .md frontmatter (enough to evaluate minimums).
///
/// `tools` accepts BOTH the Claude Code idiomatic comma-separated string format
/// (`tools: Read, Write, Edit`) and the YAML list format (`tools:\n  - Read\n  - Write`).
/// CC subagent files in the wild use comma-separated; our writer emits the same
/// for compatibility.
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct SubagentFrontmatter {
    #[serde(default)]
    pub name: Option<String>,
    #[serde(default, deserialize_with = "deserialize_tools")]
    pub tools: Vec<String>,
    #[serde(default)]
    pub colmena_auto_generated: bool,
}

/// Custom deserializer for `tools`: accepts either a comma-separated string
/// (CC idiomatic, used by nearly every existing subagent file) OR a YAML list.
fn deserialize_tools<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    use serde::de::{self, Visitor};
    use std::fmt;

    struct ToolsVisitor;

    impl<'de> Visitor<'de> for ToolsVisitor {
        type Value = Vec<String>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("a comma-separated string or a YAML list of tool names")
        }

        fn visit_str<E>(self, value: &str) -> Result<Vec<String>, E>
        where
            E: de::Error,
        {
            Ok(value
                .split(',')
                .map(|s| s.trim().to_string())
                .filter(|s| !s.is_empty())
                .collect())
        }

        fn visit_seq<A>(self, mut seq: A) -> Result<Vec<String>, A::Error>
        where
            A: de::SeqAccess<'de>,
        {
            let mut tools = Vec::new();
            while let Some(item) = seq.next_element::<String>()? {
                tools.push(item);
            }
            Ok(tools)
        }

        // Treat YAML `null` / missing as empty (won't fire given #[serde(default)], but defensive).
        fn visit_unit<E>(self) -> Result<Vec<String>, E>
        where
            E: de::Error,
        {
            Ok(Vec::new())
        }
    }

    deserializer.deserialize_any(ToolsVisitor)
}

/// Compose the `[SCOPE]` section of a generated prompt.
///
/// `owns` is the list of files this role can touch; `forbidden` is the explicit
/// "do NOT touch" list. `extra_allow` and `extra_deny` are Bash command patterns
/// that are auto-approved or blocked for this agent. Empty lists yield an empty
/// string (no section emitted).
pub fn scope_block(
    owns: &[String],
    forbidden: &[String],
    extra_allow: &[String],
    extra_deny: &[String],
) -> String {
    if owns.is_empty() && forbidden.is_empty() && extra_allow.is_empty() && extra_deny.is_empty() {
        return String::new();
    }
    let mut s = String::from("\n## Scope — ONLY these files\n\n");
    if owns.is_empty() {
        s.push_str("_(no file ownership declared — ask the operator before writing)_\n");
    } else {
        for o in owns {
            s.push_str(&format!("- {}\n", o));
        }
    }
    if !forbidden.is_empty() {
        s.push_str("\nDo NOT touch:\n");
        for f in forbidden {
            s.push_str(&format!("- {}\n", f));
        }
    }
    if !extra_allow.is_empty() {
        s.push_str("\nAuto-approved Bash commands:\n");
        for p in extra_allow {
            s.push_str(&format!("- `{}`\n", p));
        }
    }
    if !extra_deny.is_empty() {
        s.push_str("\nBlocked Bash commands:\n");
        for p in extra_deny {
            s.push_str(&format!("- `{}`\n", p));
        }
    }
    s
}

/// Compose the `[TASK]` section. Empty task → empty string.
pub fn task_block(task: &str) -> String {
    if task.trim().is_empty() {
        String::new()
    } else {
        format!("\n## Task\n\n{}\n", task.trim())
    }
}

/// Compose the `[REVIEW PROTOCOL]` section with review_submit parameters pre-filled.
///
/// `author_role` is this role's id. `mission_id` is the current mission.
/// `available_roles` is the reviewer pool to pass in the review_submit call.
pub fn review_protocol_block(
    mission_id: &str,
    author_role: &str,
    available_roles: &[String],
) -> String {
    let roles_arr = available_roles
        .iter()
        .map(|r| format!("\"{}\"", r))
        .collect::<Vec<_>>()
        .join(", ");

    format!(
        "\n## Review Protocol — MANDATORY\n\n\
         When you finish, call:\n\n\
         ```\n\
         mcp__colmena__review_submit(\n  \
             mission: \"{mission}\",\n  \
             author_role: \"{author}\",\n  \
             artifact_path: \"<worktree branch path or diff of your changes>\",\n  \
             available_roles: [{roles}]\n\
         )\n\
         ```\n\n\
         If `review_submit` fails: DO NOT Stop. Report the error to the operator and wait.\n\
         Your Stop is gated until `review_submit` succeeds (when `enforce_missions` is active).\n",
        mission = mission_id,
        author = author_role,
        roles = roles_arr,
    )
}

/// Parse the YAML frontmatter block from a subagent .md file.
/// Returns a default (empty) struct if the file has no frontmatter.
pub fn read_subagent_frontmatter(path: &Path) -> Result<SubagentFrontmatter> {
    let content = std::fs::read_to_string(path)
        .with_context(|| format!("reading subagent file {}", path.display()))?;

    let Some(stripped) = content.strip_prefix("---\n") else {
        return Ok(SubagentFrontmatter::default());
    };
    let Some(end) = stripped.find("\n---\n") else {
        return Ok(SubagentFrontmatter::default());
    };
    let yaml = &stripped[..end];
    let fm: SubagentFrontmatter = serde_yml::from_str(yaml)
        .with_context(|| format!("parsing frontmatter in {}", path.display()))?;
    Ok(fm)
}

/// Validate an existing subagent .md file against the ELO structural contracts.
///
/// For the role to close the ELO cycle when CC propagates its name to the hook,
/// the .md file must satisfy:
/// - `name` frontmatter equals `role_id`
/// - `tools` includes the required set (pass `WORKER_REQUIRED_TOOLS` for workers,
///   `REVIEWER_REQUIRED_TOOLS` for pure reviewers, or the union for hybrid roles
///   like auditor that both author and evaluate).
pub fn check_subagent_minimums(
    path: &Path,
    role_id: &str,
    required_tools: &[&str],
) -> Result<MinimumsCheck> {
    if !path.exists() {
        return Ok(MinimumsCheck::Absent);
    }
    let fm = read_subagent_frontmatter(path)?;
    let mut reasons = Vec::new();

    match &fm.name {
        Some(n) if n == role_id => {}
        Some(n) => reasons.push(format!(
            "frontmatter `name: {}` does not match role id `{}`",
            n, role_id
        )),
        None => reasons.push(format!(
            "frontmatter is missing `name:` field (expected `{}`)",
            role_id
        )),
    }

    for required in required_tools {
        if !fm.tools.iter().any(|t| t == required) {
            reasons.push(format!("`tools:` is missing `{}`", required));
        }
    }

    if reasons.is_empty() {
        Ok(MinimumsCheck::Pass)
    } else {
        Ok(MinimumsCheck::Fail { reasons })
    }
}

/// Write a subagent .md file with the auto-generated marker in frontmatter.
/// If `overwrite_existing` is false and the file exists, errors out (caller
/// must have called `check_subagent_minimums` first and made the decision).
///
/// On overwrite, writes a `.md.colmena-backup` next to the original before
/// replacing it.
/// Escape a string as a YAML double-quoted scalar. Escapes `\` and `"`.
/// Safe for any UTF-8 input; multi-line descriptions are flattened to a
/// single `\n` escape inside the quoted string.
fn yaml_double_quote(s: &str) -> String {
    let mut out = String::with_capacity(s.len() + 2);
    out.push('"');
    for ch in s.chars() {
        match ch {
            '"' => out.push_str("\\\""),
            '\\' => out.push_str("\\\\"),
            '\n' => out.push_str("\\n"),
            '\r' => out.push_str("\\r"),
            '\t' => out.push_str("\\t"),
            c => out.push(c),
        }
    }
    out.push('"');
    out
}

pub fn write_subagent_file(
    path: &Path,
    role_id: &str,
    description: &str,
    tools_allowed: &[String],
    body: &str,
    overwrite_existing: bool,
) -> Result<PathBuf> {
    if path.exists() && !overwrite_existing {
        bail!(
            "subagent file already exists and --overwrite was not specified: {}",
            path.display()
        );
    }

    // If we're overwriting, back up the existing first
    if path.exists() && overwrite_existing {
        let backup = path.with_extension("md.colmena-backup");
        std::fs::copy(path, &backup)
            .with_context(|| format!("backing up existing {}", path.display()))?;
    }

    let parent = path.parent().unwrap_or_else(|| Path::new("."));
    std::fs::create_dir_all(parent)
        .with_context(|| format!("creating dir {}", parent.display()))?;

    // Emit tools as comma-separated string (Claude Code idiomatic format).
    let tools_inline = tools_allowed.join(", ");

    // YAML-escape the description by double-quoting and escaping interior
    // double-quotes + backslashes. Claude Code refuses to load subagents
    // whose frontmatter lacks `description:`, so this field is mandatory
    // for auto-generated files.
    let description_yaml = yaml_double_quote(description);

    let content = format!(
        "---\n\
         name: {}\n\
         description: {}\n\
         {}\n\
         tools: {}\n\
         ---\n\n\
         {}\n",
        role_id,
        description_yaml,
        AUTO_GENERATED_MARKER,
        tools_inline,
        body.trim_start_matches('\n'),
    );

    // Atomic write
    let tmp = path.with_extension("md.tmp");
    std::fs::write(&tmp, &content)
        .with_context(|| format!("writing temp file {}", tmp.display()))?;
    std::fs::rename(&tmp, path)
        .with_context(|| format!("renaming {} -> {}", tmp.display(), path.display()))?;

    Ok(path.to_path_buf())
}

/// Delete a subagent .md file IF it has the auto-generated marker.
/// Files without the marker (operator-authored) are never touched.
/// Returns true if deleted, false if skipped (missing marker or missing file).
pub fn delete_auto_generated_subagent(path: &Path) -> Result<bool> {
    if !path.exists() {
        return Ok(false);
    }
    let fm = read_subagent_frontmatter(path)?;
    if !fm.colmena_auto_generated {
        return Ok(false);
    }
    std::fs::remove_file(path)
        .with_context(|| format!("removing auto-generated subagent {}", path.display()))?;
    Ok(true)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_scope_block_empty() {
        assert!(scope_block(&[], &[], &[], &[]).is_empty());
    }

    #[test]
    fn test_mission_tool_set_adds_missing_elo_tools() {
        // Mirror of Colmena's default developer.yaml: declares review_submit
        // but not review_evaluate. mission_tool_set must fill the gap.
        let role_tools = vec![
            "Read".to_string(),
            "Write".to_string(),
            "mcp__colmena__review_submit".to_string(),
            "mcp__colmena__findings_query".to_string(),
        ];
        let out = mission_tool_set(&role_tools);
        assert!(out.contains(&"mcp__colmena__review_submit".to_string()));
        assert!(out.contains(&"mcp__colmena__review_evaluate".to_string()));
        assert!(out.contains(&"mcp__colmena__findings_query".to_string()));
        assert!(out.contains(&"Read".to_string()));
        assert!(out.contains(&"Write".to_string()));
    }

    #[test]
    fn test_mission_tool_set_preserves_order_and_dedups() {
        let role_tools = vec![
            "Read".to_string(),
            "mcp__colmena__review_submit".to_string(),
            "mcp__colmena__review_evaluate".to_string(),
            "mcp__colmena__findings_query".to_string(),
            "Bash".to_string(),
        ];
        let out = mission_tool_set(&role_tools);
        // Same length: nothing needed to be added.
        assert_eq!(out.len(), role_tools.len());
        // Order preserved.
        assert_eq!(out[0], "Read");
        assert_eq!(out.last().unwrap(), "Bash");
    }

    #[test]
    fn test_mission_tool_set_empty_input_returns_elo_tools() {
        let out = mission_tool_set(&[]);
        for required in ELO_CYCLE_TOOLS {
            assert!(
                out.contains(&(*required).to_string()),
                "missing {}",
                required
            );
        }
    }

    #[test]
    fn test_scope_block_owns_only() {
        let owns = vec!["Cargo.toml".to_string(), "docs/user/*.md".to_string()];
        let out = scope_block(&owns, &[], &[], &[]);
        assert!(out.contains("Cargo.toml"));
        assert!(out.contains("docs/user/*.md"));
        assert!(!out.contains("Do NOT touch"));
    }

    #[test]
    fn test_scope_block_owns_and_forbidden() {
        let owns = vec!["Cargo.toml".to_string()];
        let forbidden = vec!["colmena-core/src/**".to_string()];
        let out = scope_block(&owns, &forbidden, &[], &[]);
        assert!(out.contains("Cargo.toml"));
        assert!(out.contains("Do NOT touch"));
        assert!(out.contains("colmena-core/src/**"));
    }

    #[test]
    fn test_task_block_empty() {
        assert!(task_block("").is_empty());
        assert!(task_block("   ").is_empty());
    }

    #[test]
    fn test_task_block_with_content() {
        let out = task_block("Implement mission_spawn auto-closure");
        assert!(out.contains("## Task"));
        assert!(out.contains("Implement mission_spawn auto-closure"));
    }

    #[test]
    fn test_review_protocol_contains_pre_filled_params() {
        let roles = vec!["auditor".to_string(), "code_reviewer".to_string()];
        let out = review_protocol_block("m-42", "developer", &roles);
        assert!(out.contains("mission: \"m-42\""));
        assert!(out.contains("author_role: \"developer\""));
        assert!(out.contains("[\"auditor\", \"code_reviewer\"]"));
        assert!(out.contains("DO NOT Stop"));
    }

    #[test]
    fn test_review_protocol_uses_singular_artifact_path() {
        // Regression guard: the MCP tool signature is `artifact_path: String`
        // (singular). The prompt template must match — the plural
        // `artifact_paths: [...]` form trips agents into invalid calls.
        let roles = vec!["auditor".to_string()];
        let out = review_protocol_block("m-1", "developer", &roles);
        assert!(
            out.contains("artifact_path:"),
            "prompt must use singular `artifact_path:` — got:\n{out}"
        );
        assert!(
            !out.contains("artifact_paths:"),
            "prompt must NOT use plural `artifact_paths:` — the tool takes a single path.\nGot:\n{out}"
        );
    }

    use tempfile::TempDir;

    #[test]
    fn test_minimums_absent_for_missing_file() {
        let tmp = TempDir::new().unwrap();
        let path = tmp.path().join("developer.md");
        let check = check_subagent_minimums(&path, "developer", WORKER_REQUIRED_TOOLS).unwrap();
        assert_eq!(check, MinimumsCheck::Absent);
    }

    #[test]
    fn test_minimums_fail_on_name_mismatch() {
        let tmp = TempDir::new().unwrap();
        let path = tmp.path().join("developer.md");
        std::fs::write(
            &path,
            "---\nname: dev-v2\ntools:\n  - mcp__colmena__review_submit\n  - mcp__colmena__findings_query\n---\n\nbody\n",
        )
        .unwrap();
        let check = check_subagent_minimums(&path, "developer", WORKER_REQUIRED_TOOLS).unwrap();
        match check {
            MinimumsCheck::Fail { reasons } => {
                assert!(reasons.iter().any(|r| r.contains("name")));
            }
            other => panic!("expected Fail, got {:?}", other),
        }
    }

    #[test]
    fn test_minimums_fail_on_missing_tool() {
        let tmp = TempDir::new().unwrap();
        let path = tmp.path().join("developer.md");
        std::fs::write(
            &path,
            "---\nname: developer\ntools:\n  - Read\n---\n\nbody\n",
        )
        .unwrap();
        let check = check_subagent_minimums(&path, "developer", WORKER_REQUIRED_TOOLS).unwrap();
        match check {
            MinimumsCheck::Fail { reasons } => {
                assert!(reasons.iter().any(|r| r.contains("review_submit")));
            }
            other => panic!("expected Fail, got {:?}", other),
        }
    }

    #[test]
    fn test_minimums_pass_on_valid() {
        let tmp = TempDir::new().unwrap();
        let path = tmp.path().join("developer.md");
        std::fs::write(
            &path,
            "---\nname: developer\ntools:\n  - mcp__colmena__review_submit\n  - mcp__colmena__findings_query\n---\n\nbody\n",
        )
        .unwrap();
        let check = check_subagent_minimums(&path, "developer", WORKER_REQUIRED_TOOLS).unwrap();
        assert_eq!(check, MinimumsCheck::Pass);
    }

    #[test]
    fn test_minimums_pass_on_cc_comma_separated_format() {
        // Real-world CC subagent file format: tools as a single comma-separated string.
        // Discovered in dogfooding (2026-04-20) — existing ~/.claude/agents/*.md files use this format.
        let tmp = TempDir::new().unwrap();
        let path = tmp.path().join("developer.md");
        std::fs::write(
            &path,
            "---\nname: developer\ntools: Read, Write, Edit, mcp__colmena__review_submit, mcp__colmena__findings_query\n---\n\nbody\n",
        )
        .unwrap();
        let check = check_subagent_minimums(&path, "developer", WORKER_REQUIRED_TOOLS).unwrap();
        assert_eq!(check, MinimumsCheck::Pass);
    }

    #[test]
    fn test_minimums_ignores_unknown_frontmatter_fields() {
        // gsd-* and other ecosystem subagent files include `description`, `model`,
        // `color`, `skills`, `hooks` — serde must ignore unknown fields (no deny_unknown).
        let tmp = TempDir::new().unwrap();
        let path = tmp.path().join("gsd-mapper.md");
        std::fs::write(
            &path,
            "---\nname: gsd-mapper\ndescription: \"does stuff\"\nmodel: opus\ntools: Read, Bash, Grep\ncolor: cyan\nskills:\n  - foo\n# hooks: nope\n---\n\nbody\n",
        )
        .unwrap();
        // Not calling check_subagent_minimums with a real role_id here — just verifying
        // the frontmatter parses without error. Use read_subagent_frontmatter directly.
        let fm = read_subagent_frontmatter(&path).unwrap();
        assert_eq!(fm.name.as_deref(), Some("gsd-mapper"));
        assert_eq!(fm.tools, vec!["Read", "Bash", "Grep"]);
    }

    #[test]
    fn test_write_subagent_file_creates_with_marker() {
        let tmp = TempDir::new().unwrap();
        let path = tmp.path().join("developer.md");
        let tools = vec![
            "mcp__colmena__review_submit".to_string(),
            "mcp__colmena__findings_query".to_string(),
        ];
        write_subagent_file(
            &path,
            "developer",
            "Test developer role",
            &tools,
            "# Developer\nBody text.",
            false,
        )
        .unwrap();
        let content = std::fs::read_to_string(&path).unwrap();
        assert!(content.contains("name: developer"));
        assert!(content.contains("description: \"Test developer role\""));
        assert!(content.contains(AUTO_GENERATED_MARKER));
        assert!(content.contains("mcp__colmena__review_submit"));
    }

    #[test]
    fn test_write_subagent_file_errors_on_existing_without_overwrite() {
        let tmp = TempDir::new().unwrap();
        let path = tmp.path().join("developer.md");
        std::fs::write(&path, "# Pre-existing").unwrap();
        let tools = vec!["mcp__colmena__review_submit".to_string()];
        let r = write_subagent_file(&path, "developer", "desc", &tools, "new body", false);
        assert!(r.is_err());
        assert!(r.unwrap_err().to_string().contains("--overwrite"));
    }

    #[test]
    fn test_write_subagent_file_backs_up_on_overwrite() {
        let tmp = TempDir::new().unwrap();
        let path = tmp.path().join("developer.md");
        std::fs::write(&path, "# Pre-existing").unwrap();
        let tools = vec!["mcp__colmena__review_submit".to_string()];
        write_subagent_file(&path, "developer", "desc", &tools, "new body", true).unwrap();
        let backup = path.with_extension("md.colmena-backup");
        assert!(backup.exists());
        assert_eq!(std::fs::read_to_string(&backup).unwrap(), "# Pre-existing");
    }

    #[test]
    fn test_write_subagent_file_escapes_quotes_and_backslashes_in_description() {
        let tmp = TempDir::new().unwrap();
        let path = tmp.path().join("developer.md");
        let tools = vec!["mcp__colmena__review_submit".to_string()];
        // Description with embedded `"` and `\` — must survive as valid YAML.
        let desc = r#"Role for "testing" with a \backslash and newline
here"#;
        write_subagent_file(&path, "developer", desc, &tools, "body", false).unwrap();
        let content = std::fs::read_to_string(&path).unwrap();
        // Must be double-quoted and escape characters correctly.
        assert!(
            content.contains(
                r#"description: "Role for \"testing\" with a \\backslash and newline\nhere""#
            ),
            "description not escaped correctly:\n{content}"
        );
        // Re-parse via the frontmatter reader to confirm the YAML is valid.
        let fm = read_subagent_frontmatter(&path).unwrap();
        assert_eq!(fm.name.as_deref(), Some("developer"));
    }

    #[test]
    fn test_delete_auto_generated_skips_manual() {
        let tmp = TempDir::new().unwrap();
        let path = tmp.path().join("developer.md");
        std::fs::write(&path, "---\nname: developer\ntools: []\n---\n\nbody\n").unwrap();
        let deleted = delete_auto_generated_subagent(&path).unwrap();
        assert!(!deleted, "should skip files without auto-generated marker");
        assert!(path.exists(), "manual file must be preserved");
    }

    #[test]
    fn test_delete_auto_generated_removes_marked() {
        let tmp = TempDir::new().unwrap();
        let path = tmp.path().join("developer.md");
        std::fs::write(
            &path,
            "---\nname: developer\ncolmena_auto_generated: true\ntools: []\n---\n\nbody\n",
        )
        .unwrap();
        let deleted = delete_auto_generated_subagent(&path).unwrap();
        assert!(deleted);
        assert!(!path.exists());
    }
}
