magi-code 0.63.4

Repository-aware CLI coding agent for terminal work
Documentation
use super::{ToolRuntime, args::FILE_READ_MAX_BYTES, contract::metadata_key as meta};
use serde_json::{Map, Value, json};
use std::{
    fs,
    path::{Component, Path, PathBuf},
};

#[derive(Debug)]
pub(super) struct LoadedSkillFile {
    pub(super) kind: &'static str,
    pub(super) reference: Option<String>,
    pub(super) content: String,
    pub(super) metadata: Map<String, Value>,
}

impl ToolRuntime {
    pub(super) fn load_skill_markdown(&self, name: &str) -> anyhow::Result<LoadedSkillFile> {
        let skill = self.skills.get(name).ok_or_else(|| {
            anyhow::anyhow!(
                "unknown skill '{}'; use one of the skills listed in the system prompt",
                name
            )
        })?;
        let skill_dir = skill
            .path
            .parent()
            .ok_or_else(|| anyhow::anyhow!("selected skill has no directory"))?;
        let canonical_skill_dir = canonical_skill_dir(skill_dir)?;
        if skill.path.file_name().and_then(|name| name.to_str()) != Some("SKILL.md") {
            anyhow::bail!("selected skill file must be named SKILL.md");
        }
        load_skill_file(
            name,
            "skill",
            None,
            &canonical_skill_dir,
            skill.path.clone(),
        )
    }

    pub(super) fn load_skill_reference(
        &self,
        name: &str,
        reference: &str,
    ) -> anyhow::Result<LoadedSkillFile> {
        let skill = self.skills.get(name).ok_or_else(|| {
            anyhow::anyhow!(
                "unknown skill '{}'; use one of the skills listed in the system prompt",
                name
            )
        })?;
        let skill_dir = skill
            .path
            .parent()
            .ok_or_else(|| anyhow::anyhow!("selected skill has no directory"))?;
        let canonical_skill_dir = canonical_skill_dir(skill_dir)?;
        load_skill_file(
            name,
            "reference",
            Some(reference),
            &canonical_skill_dir,
            resolve_reference_path(skill_dir, reference)?,
        )
    }
}

fn canonical_skill_dir(skill_dir: &Path) -> anyhow::Result<PathBuf> {
    let metadata = fs::symlink_metadata(skill_dir)?;
    if metadata.file_type().is_symlink() {
        anyhow::bail!("selected skill directory must not be a symlink");
    }
    if !metadata.is_dir() {
        anyhow::bail!("selected skill directory is not a directory");
    }
    Ok(skill_dir.canonicalize()?)
}

fn resolve_reference_path(skill_dir: &Path, reference: &str) -> anyhow::Result<PathBuf> {
    if reference.trim().is_empty() {
        anyhow::bail!("reference path must not be empty");
    }
    let reference_path = PathBuf::from(reference);
    if reference_path.is_absolute() {
        anyhow::bail!("reference path must be relative to the selected skill directory");
    }
    for component in reference_path.components() {
        match component {
            Component::Normal(_) | Component::CurDir => {}
            Component::ParentDir => anyhow::bail!("reference path must not contain '..'"),
            Component::RootDir => anyhow::bail!("reference path must not contain a root component"),
            Component::Prefix(_) => anyhow::bail!("reference path must not contain a path prefix"),
        }
    }
    Ok(skill_dir.join(reference_path))
}

fn load_skill_file(
    skill_name: &str,
    kind: &'static str,
    reference: Option<&str>,
    canonical_skill_dir: &Path,
    candidate: PathBuf,
) -> anyhow::Result<LoadedSkillFile> {
    let canonical_candidate = match candidate.canonicalize() {
        Ok(path) => path,
        Err(error) if reference.is_some() && error.kind() == std::io::ErrorKind::NotFound => {
            anyhow::bail!(
                "reference '{}' was not found in the selected skill directory",
                reference.unwrap()
            )
        }
        Err(error) => return Err(error.into()),
    };
    if !canonical_candidate.starts_with(canonical_skill_dir) {
        anyhow::bail!("{kind} path escapes the selected skill directory");
    }

    let metadata = match fs::symlink_metadata(&candidate) {
        Ok(metadata) => metadata,
        Err(error) if reference.is_some() && error.kind() == std::io::ErrorKind::NotFound => {
            anyhow::bail!(
                "reference '{}' was not found in the selected skill directory",
                reference.unwrap()
            )
        }
        Err(error) => return Err(error.into()),
    };
    if metadata.file_type().is_symlink() {
        anyhow::bail!("{kind} target must not be a symlink");
    }
    if metadata.is_dir() {
        anyhow::bail!("{kind} target must be a regular file, not a directory");
    }
    if !metadata.is_file() {
        anyhow::bail!("{kind} target must be a regular file");
    }
    if metadata.len() > FILE_READ_MAX_BYTES {
        anyhow::bail!(
            "{kind} target is {} bytes; skill read limit is {FILE_READ_MAX_BYTES} bytes",
            metadata.len()
        );
    }
    let content = fs::read_to_string(&canonical_candidate)
        .map_err(|error| anyhow::anyhow!("{kind} target must be UTF-8 text: {error}"))?;
    let bytes = content.len();
    let reference = reference.map(str::to_string);
    let mut metadata = Map::new();
    metadata.insert(meta::SKILL.to_string(), json!(skill_name));
    metadata.insert(meta::KIND.to_string(), json!(kind));
    metadata.insert(meta::REFERENCE.to_string(), json!(reference.clone()));
    metadata.insert(meta::BYTES.to_string(), json!(bytes));
    metadata.insert(meta::BYTE_LIMIT.to_string(), json!(FILE_READ_MAX_BYTES));
    Ok(LoadedSkillFile {
        kind,
        reference,
        content,
        metadata,
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        skills::{SkillDiscovery, load_skill},
        tools::ToolRuntime,
    };
    use std::collections::{BTreeMap, BTreeSet};
    use tempfile::TempDir;

    fn runtime_with_skill(temp: &TempDir, name: &str, content: &str) -> ToolRuntime {
        let skill_dir = temp.path().join("skills").join(name);
        fs::create_dir_all(&skill_dir).unwrap();
        fs::write(skill_dir.join("SKILL.md"), content).unwrap();
        let skill = load_skill(&skill_dir.join("SKILL.md")).unwrap();
        let mut skills = BTreeMap::new();
        skills.insert(name.to_string(), skill);
        ToolRuntime::new(temp.path())
            .unwrap()
            .with_skills(&SkillDiscovery {
                skills,
                diagnostics: Vec::new(),
            })
    }

    #[test]
    fn skill_helper_loads_markdown_and_reference() {
        let temp = TempDir::new().unwrap();
        let runtime = runtime_with_skill(&temp, "test", "# Skill\n");
        let reference = temp.path().join("skills/test/references/example.md");
        fs::create_dir_all(reference.parent().unwrap()).unwrap();
        fs::write(&reference, "reference body").unwrap();

        let skill = runtime.load_skill_markdown("test").unwrap();
        assert_eq!(skill.content, "# Skill\n");
        assert_eq!(skill.kind, "skill");
        assert_eq!(skill.metadata["skill"], "test");

        let reference = runtime
            .load_skill_reference("test", "references/example.md")
            .unwrap();
        assert_eq!(reference.content, "reference body");
        assert_eq!(reference.kind, "reference");
        assert_eq!(
            reference.reference.as_deref(),
            Some("references/example.md")
        );
    }

    #[test]
    fn skill_unknown_name_rejects_without_path_fallback() {
        let temp = TempDir::new().unwrap();
        fs::create_dir_all(temp.path().join("unknown")).unwrap();
        fs::write(temp.path().join("unknown/SKILL.md"), "secret").unwrap();
        let runtime = ToolRuntime::new(temp.path()).unwrap();

        let error = runtime
            .load_skill_markdown("unknown")
            .unwrap_err()
            .to_string();

        assert!(error.contains("unknown skill 'unknown'"));
        assert!(!error.contains("secret"));
    }

    #[test]
    fn skill_runtime_receives_only_filtered_enabled_skills() {
        let temp = TempDir::new().unwrap();
        let mut discovered = SkillDiscovery::default();
        for name in ["enabled", "disabled"] {
            let skill_dir = temp.path().join("skills").join(name);
            fs::create_dir_all(&skill_dir).unwrap();
            fs::write(skill_dir.join("SKILL.md"), format!("# {name}\n")).unwrap();
            discovered.skills.insert(
                name.to_string(),
                load_skill(&skill_dir.join("SKILL.md")).unwrap(),
            );
        }
        let enabled = crate::skills::filter_enabled_skills(
            &discovered,
            &BTreeSet::from(["disabled".to_string()]),
        );
        let runtime = ToolRuntime::new(temp.path()).unwrap().with_skills(&enabled);

        assert!(runtime.load_skill_markdown("enabled").is_ok());
        let error = runtime
            .load_skill_markdown("disabled")
            .unwrap_err()
            .to_string();
        assert!(error.contains("unknown skill 'disabled'"));
    }

    #[test]
    fn skill_helper_rejects_missing_traversal_absolute_and_directory_references() {
        let temp = TempDir::new().unwrap();
        let runtime = runtime_with_skill(&temp, "test", "# Skill\n");
        fs::create_dir_all(temp.path().join("skills/test/dir")).unwrap();
        fs::create_dir_all(temp.path().join("skills/other")).unwrap();
        fs::write(temp.path().join("skills/other/SKILL.md"), "other").unwrap();

        for (reference, expected) in [
            ("missing.md", "was not found"),
            ("../other/SKILL.md", "must not contain '..'"),
            (
                temp.path().join("outside.txt").to_str().unwrap(),
                "must be relative",
            ),
            ("/rooted", "must be relative"),
            ("dir", "not a directory"),
        ] {
            let error = runtime
                .load_skill_reference("test", reference)
                .unwrap_err()
                .to_string();
            assert!(
                error.contains(expected),
                "expected {expected:?} for {reference:?}, got {error}"
            );
        }
    }

    #[test]
    fn skill_helper_rejects_oversized_and_non_utf8_targets() {
        let temp = TempDir::new().unwrap();
        let runtime = runtime_with_skill(&temp, "test", "# Skill\n");
        let skill_dir = temp.path().join("skills/test");

        fs::write(
            skill_dir.join("big.txt"),
            vec![b'x'; FILE_READ_MAX_BYTES as usize + 1],
        )
        .unwrap();
        let error = runtime
            .load_skill_reference("test", "big.txt")
            .unwrap_err()
            .to_string();
        assert!(error.contains("skill read limit"));

        fs::write(skill_dir.join("bad.bin"), [0xff]).unwrap();
        let error = runtime
            .load_skill_reference("test", "bad.bin")
            .unwrap_err()
            .to_string();
        assert!(error.contains("UTF-8"));
    }

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

        let temp = TempDir::new().unwrap();
        let outside = TempDir::new().unwrap();
        fs::write(outside.path().join("outside.md"), "outside").unwrap();
        let runtime = runtime_with_skill(&temp, "test", "# Skill\n");
        let skill_dir = temp.path().join("skills/test");
        symlink(outside.path().join("outside.md"), skill_dir.join("link.md")).unwrap();

        let error = runtime
            .load_skill_reference("test", "link.md")
            .unwrap_err()
            .to_string();
        assert!(error.contains("escapes the selected skill directory"));
    }

    #[test]
    fn cloned_runtime_preserves_skills_and_debug_redacts_records() {
        let temp = TempDir::new().unwrap();
        let content = "# Secret Skill Path Test\n";
        let runtime = runtime_with_skill(&temp, "test", content);
        let cloned = runtime
            .clone_for_cwd_with_subagent_depth(temp.path(), 1)
            .unwrap();

        let result = cloned.load_skill_markdown("test").unwrap();
        assert_eq!(result.content, content);
        let debug = format!("{runtime:?}");
        assert!(debug.contains("skills_count"));
        assert!(!debug.contains("Secret Skill Path Test"));
        assert!(!debug.contains("SKILL.md"));
    }
}