magi-code 0.64.0

Repository-aware CLI coding agent for terminal work
Documentation
use crate::{
    config::{McPaths, read_settings},
    instructions::discover_agents_with_additional_markdown,
    providers::{PromptDumpProviderTarget, provider_prompt_dump},
    skills::{SkillRoots, discover_skills, filter_enabled_skills},
};
use anyhow::Context;
use std::{io::Write, path::Path};

pub(super) fn run_dump_prompt<W: Write>(
    writer: &mut W,
    paths: McPaths,
    cwd: &Path,
    target: PromptDumpProviderTarget,
) -> anyhow::Result<()> {
    let settings = read_settings(&paths)?;
    let instructions = discover_agents_with_additional_markdown(
        &paths.user_agents,
        cwd,
        &settings.instructions.additional_markdown_paths,
    )?;
    let mut skill_roots = SkillRoots::from_paths(&paths.root, cwd);
    skill_roots.additional_paths = settings.skills.additional_paths.clone();
    let discovered_skills = discover_skills(&skill_roots);
    let disabled_skills = crate::config::disabled_skill_names_from_settings(&settings);
    let skills = filter_enabled_skills(&discovered_skills, &disabled_skills);
    let subagent_profile_discovery =
        crate::subagents::profiles::discover_subagent_profiles(&paths.subagents);
    let agent = crate::agent::runner::new_parent_agent_with_subagent_profiles(
        crate::providers::DEFAULT_CODEX_MODEL,
        Some(&paths.prompts),
        &instructions,
        &skills,
        &subagent_profile_discovery,
    )?;
    let system_prompt = strip_eval_context_sections(agent.system_prompt());
    let dump = provider_prompt_dump(target, system_prompt.to_string())?;
    serde_json::to_writer_pretty(&mut *writer, &dump).context("failed to serialize prompt dump")?;
    writer.write_all(b"\n")?;
    Ok(())
}

/// Sections stripped from `--dump-prompt` output because they are runtime-context noise
/// (local subagent identities, installed skills, AGENTS.md context files) that varies per
/// machine and is irrelevant to tool-selection eval cases.
fn strip_eval_context_sections(prompt: &str) -> String {
    let mut result = prompt.to_string();
    for tag in [
        "Subagent-Identities",
        "Available-Skills",
        "Additional-Context-Files",
    ] {
        result = strip_xml_block(&result, tag);
    }
    result
}

fn strip_xml_block(input: &str, tag: &str) -> String {
    let open = format!("<{tag}>");
    let close = format!("</{tag}>");
    let Some(start) = input.find(&open) else {
        return input.to_string();
    };
    let Some(end) = input[start..].find(&close) else {
        return input.to_string();
    };
    let end = start + end + close.len();
    let mut out = String::with_capacity(input.len());
    out.push_str(&input[..start]);
    out.push_str(&input[end..]);
    // Collapse leftover triple-blank lines from removed section boundaries
    while out.contains("\n\n\n\n") {
        out = out.replace("\n\n\n\n", "\n\n");
    }
    out.trim().to_string()
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::Value;
    use tempfile::TempDir;

    #[test]
    fn dump_prompt_outputs_only_system_prompt_and_tools_fields() {
        let temp = TempDir::new().unwrap();
        let paths = McPaths::from_root(temp.path().join("mc"));
        let mut output = Vec::new();

        run_dump_prompt(
            &mut output,
            paths,
            temp.path(),
            PromptDumpProviderTarget::Anthropic,
        )
        .unwrap();

        let value: Value = serde_json::from_slice(&output).unwrap();
        let object = value.as_object().unwrap();
        assert_eq!(object.len(), 2);
        assert!(
            object
                .get("system_prompt")
                .unwrap()
                .as_str()
                .unwrap()
                .contains("You are")
        );
        let system_prompt = object.get("system_prompt").unwrap().as_str().unwrap();
        assert!(!system_prompt.contains("<Subagent-Identities>"));
        assert!(!system_prompt.contains("<Available-Skills>"));
        assert!(!system_prompt.contains("<Additional-Context-Files>"));
        assert!(object.get("tools").unwrap().as_array().unwrap().len() > 1);
    }

    #[test]
    fn strip_eval_context_sections_removes_all_three_blocks() {
        let input = "header\n\n<Subagent-Identities>\nstuff\n</Subagent-Identities>\n\n<Available-Skills>\nmore stuff\n</Available-Skills>\n\nmiddle\n\n<Additional-Context-Files>\nfile stuff\n</Additional-Context-Files>\n\nfooter";
        let result = strip_eval_context_sections(input);
        assert!(!result.contains("Subagent-Identities"));
        assert!(!result.contains("Available-Skills"));
        assert!(!result.contains("Additional-Context-Files"));
        assert!(result.contains("header"));
        assert!(result.contains("middle"));
        assert!(result.contains("footer"));
    }

    #[test]
    fn strip_xml_block_leaves_input_untouched_when_tag_absent() {
        let input = "nothing to remove here";
        assert_eq!(strip_xml_block(input, "Nonexistent"), input);
    }

    #[cfg(unix)]
    #[test]
    fn dump_prompt_succeeds_with_insecure_invalid_auth_file() {
        use std::fs;
        use std::os::unix::fs::PermissionsExt;

        let temp = TempDir::new().unwrap();
        let paths = McPaths::from_root(temp.path().join("mc"));
        fs::create_dir_all(&paths.root).unwrap();
        fs::write(&paths.auth_file, "not json").unwrap();
        let mut permissions = fs::metadata(&paths.auth_file).unwrap().permissions();
        permissions.set_mode(0o644);
        fs::set_permissions(&paths.auth_file, permissions).unwrap();
        let mut output = Vec::new();

        run_dump_prompt(
            &mut output,
            paths,
            temp.path(),
            PromptDumpProviderTarget::Anthropic,
        )
        .unwrap();

        let value: Value = serde_json::from_slice(&output).unwrap();
        assert!(value.get("system_prompt").unwrap().as_str().is_some());
        assert!(value.get("tools").unwrap().as_array().unwrap().len() > 1);
    }

    #[test]
    fn dump_prompt_does_not_create_runtime_dirs_or_session_files() {
        let temp = TempDir::new().unwrap();
        let paths = McPaths::from_root(temp.path().join("mc"));
        let mut output = Vec::new();

        run_dump_prompt(
            &mut output,
            paths.clone(),
            temp.path(),
            PromptDumpProviderTarget::OpenAi,
        )
        .unwrap();

        assert!(!paths.sessions.exists());
        assert!(!paths.settings_file.exists());
        assert!(!paths.auth_file.exists());
    }
}