agent-migrate-adapters 0.6.1

Codex, Pi, and Claude Code configuration adapters for agent-migrate
Documentation
use std::path::{Path, PathBuf};

use agent_migrate_core::{AgentKind, BundleRelativePath, FileKind, Scope};

use crate::error::AdapterError;

#[derive(Debug, Clone, Copy)]
pub(crate) struct TargetLayout {
    target: AgentKind,
    scope: Scope,
}

impl TargetLayout {
    pub(crate) const fn new(target: AgentKind, scope: Scope) -> Self {
        Self { target, scope }
    }

    pub(crate) fn path(
        self,
        kind: FileKind,
        source: &BundleRelativePath,
    ) -> Result<BundleRelativePath, AdapterError> {
        if self.is_materialized(source) {
            return Ok(source.clone());
        }
        let suffix = source.as_path().components().skip(2).collect::<PathBuf>();
        if suffix.as_os_str().is_empty() {
            return Err(AdapterError::InternalInvariant);
        }
        let prefix = self.prefix(kind);
        let target = if matches!(
            kind,
            FileKind::Mcp | FileKind::Settings | FileKind::Instructions
        ) {
            PathBuf::from(prefix)
        } else {
            Path::new(prefix).join(suffix)
        };
        target
            .to_string_lossy()
            .parse::<BundleRelativePath>()
            .map_err(Into::into)
    }

    fn is_materialized(self, source: &BundleRelativePath) -> bool {
        let value = source.to_string();
        match self.target {
            AgentKind::Codex => value.starts_with(".codex/") || value == "AGENTS.md",
            AgentKind::Pi => value.starts_with(".pi/") || value == "AGENTS.md",
            AgentKind::Claude => {
                value.starts_with(".claude/") || matches!(value.as_str(), ".mcp.json" | "CLAUDE.md")
            }
        }
    }

    fn prefix(self, kind: FileKind) -> &'static str {
        match (self.target, self.scope, kind) {
            (AgentKind::Codex, _, FileKind::Mcp | FileKind::Settings) => ".codex/config.toml",
            (AgentKind::Codex, Scope::Global, FileKind::Instructions) => ".codex/AGENTS.md",
            (AgentKind::Codex | AgentKind::Pi, Scope::Project, FileKind::Instructions) => {
                "AGENTS.md"
            }
            (AgentKind::Codex, _, FileKind::Skill) => ".codex/skills",
            (AgentKind::Codex, _, FileKind::Command) => ".codex/prompts",
            (AgentKind::Codex, _, FileKind::Agent) => ".codex/agents",
            (AgentKind::Codex, _, FileKind::Hook) => ".codex/hooks",
            (AgentKind::Pi, Scope::Global, FileKind::Settings) => ".pi/agent/settings.json",
            (AgentKind::Pi, Scope::Project, FileKind::Settings) => ".pi/settings.json",
            (AgentKind::Pi, Scope::Global, FileKind::Mcp) => ".pi/agent/agent-migrate-mcp.json",
            (AgentKind::Pi, Scope::Project, FileKind::Mcp) => ".pi/agent-migrate-mcp.json",
            (AgentKind::Pi, Scope::Global, FileKind::Instructions) => ".pi/agent/AGENTS.md",
            (AgentKind::Pi, Scope::Global, FileKind::Skill) => ".pi/agent/skills",
            (AgentKind::Pi, Scope::Project, FileKind::Skill) => ".pi/skills",
            (AgentKind::Pi, Scope::Global, FileKind::Command) => ".pi/agent/prompts",
            (AgentKind::Pi, Scope::Project, FileKind::Command) => ".pi/prompts",
            (AgentKind::Pi, _, FileKind::Agent) => ".pi/agent/prompts/agents",
            (AgentKind::Pi, _, FileKind::Hook) => ".agent-migrate/preserved/hooks",
            (AgentKind::Claude, _, FileKind::Settings) => ".claude/settings.json",
            (AgentKind::Claude, Scope::Global, FileKind::Mcp) => ".claude/mcp.json",
            (AgentKind::Claude, Scope::Project, FileKind::Mcp) => ".mcp.json",
            (AgentKind::Claude, Scope::Global, FileKind::Instructions) => ".claude/CLAUDE.md",
            (AgentKind::Claude, Scope::Project, FileKind::Instructions) => "CLAUDE.md",
            (AgentKind::Claude, _, FileKind::Skill) => ".claude/skills",
            (AgentKind::Claude, _, FileKind::Command) => ".claude/commands",
            (AgentKind::Claude, _, FileKind::Agent) => ".claude/agents",
            (AgentKind::Claude, _, FileKind::Hook) => ".claude/hooks",
            (_, _, FileKind::Passthrough) => ".agent-migrate/preserved/passthrough",
        }
    }
}