magi-code 0.63.0

Repository-aware CLI coding agent for terminal work
Documentation
use crate::{tools::ToolResult, tools::contract::tool_name};
use serde_json::Value;
use std::collections::HashSet;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ContextInjectionDefinition {
    pub(crate) name: &'static str,
    pub(crate) tag: &'static str,
    pub(crate) command: &'static str,
    pub(crate) description: &'static str,
}

const DEFINITIONS: [ContextInjectionDefinition; 2] = [
    ContextInjectionDefinition {
        name: "tree",
        tag: "#tree",
        command: "tree",
        description: "current directory tree",
    },
    ContextInjectionDefinition {
        name: "git-status",
        tag: "#git-status",
        command: "git status",
        description: "current Git status",
    },
];

pub(crate) fn definitions() -> &'static [ContextInjectionDefinition] {
    &DEFINITIONS
}

pub(crate) fn injection_tags_in_prompt(prompt: &str) -> Vec<ContextInjectionDefinition> {
    let mut seen = HashSet::new();
    let mut matches = Vec::new();
    for (index, _) in prompt.match_indices('#') {
        for definition in definitions() {
            if standalone_tag_at(prompt, index, definition.tag) && seen.insert(definition.name) {
                matches.push(*definition);
            }
        }
    }
    matches
}

pub(crate) fn expand_prompt_with_context_injections(
    prompt: &str,
    mut run: impl FnMut(&ContextInjectionDefinition) -> ToolResult,
) -> Option<String> {
    let definitions = injection_tags_in_prompt(prompt);
    if definitions.is_empty() {
        return None;
    }
    let results = definitions
        .iter()
        .map(|definition| (*definition, run(definition)))
        .collect::<Vec<_>>();
    Some(render_effective_prompt(prompt, &results))
}

fn standalone_tag_at(prompt: &str, start: usize, tag: &str) -> bool {
    let end = start + tag.len();
    prompt
        .as_bytes()
        .get(start..end)
        .is_some_and(|candidate| candidate == tag.as_bytes())
        && prompt[..start]
            .bytes()
            .next_back()
            .is_none_or(|byte| !is_identifier_byte(byte))
        && prompt[end..]
            .bytes()
            .next()
            .is_none_or(|byte| !is_identifier_byte(byte))
}

fn is_identifier_byte(byte: u8) -> bool {
    byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-')
}

fn render_effective_prompt(
    prompt: &str,
    results: &[(ContextInjectionDefinition, ToolResult)],
) -> String {
    let mut rendered = prompt.to_string();
    rendered.push_str("\n\n<context_injections>");
    for (definition, result) in results {
        rendered.push('\n');
        rendered.push_str(&format!(
            "<context_injection name=\"{}\" tag=\"{}\" command=\"{}\" success=\"{}\">\n",
            definition.name, definition.tag, definition.command, result.success
        ));
        if result.tool_name != tool_name::BASH {
            rendered.push_str("tool_error:\n");
            rendered.push_str(&result.content);
            rendered.push('\n');
        } else {
            rendered.push_str("stdout:\n");
            rendered.push_str(metadata_string(&result.metadata, "stdout").unwrap_or_default());
            rendered.push_str("\nstderr:\n");
            rendered.push_str(metadata_string(&result.metadata, "stderr").unwrap_or_default());
            rendered.push_str("\nstatus:\n");
            rendered.push_str(&format!("success: {}\n", result.success));
            rendered.push_str(&format!(
                "exit_code: {}\n",
                metadata_exit_code(&result.metadata).unwrap_or_else(|| "unknown".to_string())
            ));
            rendered.push_str(&format!(
                "timed_out: {}\n",
                metadata_bool(&result.metadata, "timed_out")
                    .map(|value| value.to_string())
                    .unwrap_or_else(|| "unknown".to_string())
            ));
            rendered.push_str(&format!(
                "stdout_truncated: {}\n",
                metadata_bool(&result.metadata, "stdout_truncated")
                    .map(|value| value.to_string())
                    .unwrap_or_else(|| "unknown".to_string())
            ));
            rendered.push_str(&format!(
                "stderr_truncated: {}\n",
                metadata_bool(&result.metadata, "stderr_truncated")
                    .map(|value| value.to_string())
                    .unwrap_or_else(|| "unknown".to_string())
            ));
            if !result.success && result.content != "stdout:\n\nstderr:\n" {
                rendered.push_str("content:\n");
                rendered.push_str(&result.content);
                rendered.push('\n');
            }
        }
        rendered.push_str("</context_injection>");
    }
    rendered.push_str("\n</context_injections>");
    rendered
}

fn metadata_string<'a>(metadata: &'a Value, key: &str) -> Option<&'a str> {
    metadata.get(key)?.as_str()
}

fn metadata_bool(metadata: &Value, key: &str) -> Option<bool> {
    metadata.get(key)?.as_bool()
}

fn metadata_exit_code(metadata: &Value) -> Option<String> {
    let value = metadata.get("exit_code")?;
    if value.is_null() {
        return Some("none".to_string());
    }
    value.as_i64().map(|code| code.to_string())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::tools::ToolResultDisplay;
    use serde_json::json;

    fn result(success: bool, stdout: &str, stderr: &str, exit_code: Option<i32>) -> ToolResult {
        ToolResult {
            tool_name: tool_name::BASH.to_string(),
            success,
            content: format!("stdout:\n{stdout}\nstderr:\n{stderr}"),
            metadata: json!({
                "exit_code": exit_code,
                "timed_out": false,
                "stdout": stdout,
                "stderr": stderr,
                "stdout_truncated": false,
                "stderr_truncated": false,
                "stdout_limit_bytes": 65536,
                "stderr_limit_bytes": 16384,
                "cleanup_warning": null,
            }),
            display: ToolResultDisplay::default(),
        }
    }

    #[test]
    fn parser_recognizes_supported_standalone_tags_in_first_seen_order_once() {
        let tags = injection_tags_in_prompt("use #git-status then (#tree), then #git-status again");
        let names = tags.iter().map(|tag| tag.name).collect::<Vec<_>>();
        assert_eq!(names, vec!["git-status", "tree"]);
    }

    #[test]
    fn parser_ignores_unknown_bare_issue_heading_and_embedded_forms() {
        let ignored = [
            "#",
            "#40",
            "# Heading",
            "abc#tree",
            "#treehouse",
            "#git-status-old",
            "#unknown #vibes",
        ];
        for prompt in ignored {
            assert!(
                injection_tags_in_prompt(prompt).is_empty(),
                "unexpected tag in {prompt:?}"
            );
        }
    }

    #[test]
    fn parser_allows_supported_tags_adjacent_to_punctuation() {
        let tags = injection_tags_in_prompt("see #tree, then (#git-status).");
        let names = tags.iter().map(|tag| tag.name).collect::<Vec<_>>();
        assert_eq!(names, vec!["tree", "git-status"]);
    }

    #[test]
    fn no_supported_tags_returns_none_without_running_commands() {
        let expanded = expand_prompt_with_context_injections("plain prompt #40", |_| {
            panic!("runner should not execute")
        });
        assert_eq!(expanded, None);
    }

    #[test]
    fn expansion_renders_command_context_blocks() {
        let expanded = expand_prompt_with_context_injections("inspect #tree", |definition| {
            assert_eq!(definition.command, "tree");
            result(true, "file.rs\n", "", Some(0))
        })
        .unwrap();

        assert!(expanded.starts_with("inspect #tree\n\n<context_injections>"));
        assert!(expanded.contains(
            "<context_injection name=\"tree\" tag=\"#tree\" command=\"tree\" success=\"true\">"
        ));
        assert!(expanded.contains("stdout:\nfile.rs\n\nstderr:\n"));
        assert!(expanded.contains("exit_code: 0"));
    }

    #[test]
    fn expansion_renders_failure_status_and_content() {
        let expanded = expand_prompt_with_context_injections("inspect #git-status", |_| {
            result(false, "", "fatal: not a git repository\n", Some(128))
        })
        .unwrap();

        assert!(expanded.contains("success=\"false\""));
        assert!(expanded.contains("stderr:\nfatal: not a git repository\n"));
        assert!(expanded.contains("exit_code: 128"));
        assert!(expanded.contains("content:\nstdout:\n\nstderr:\nfatal: not a git repository\n"));
    }
}