acp/sync/
tool.rs

1//! @acp:module "Sync Tool Types"
2//! @acp:summary "Supported AI tool definitions and metadata"
3//! @acp:domain cli
4//! @acp:layer model
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// Supported AI development tools
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "kebab-case")]
12pub enum Tool {
13    Cursor,
14    ClaudeCode,
15    Copilot,
16    Continue,
17    Windsurf,
18    Cline,
19    Aider,
20    Generic,
21}
22
23impl Tool {
24    /// Get all built-in tools
25    pub fn all() -> &'static [Tool] {
26        &[
27            Tool::Cursor,
28            Tool::ClaudeCode,
29            Tool::Copilot,
30            Tool::Continue,
31            Tool::Windsurf,
32            Tool::Cline,
33            Tool::Aider,
34            Tool::Generic,
35        ]
36    }
37
38    /// Get the default output path for this tool
39    pub fn output_path(&self) -> &'static str {
40        match self {
41            Tool::Cursor => ".cursorrules",
42            Tool::ClaudeCode => "CLAUDE.md",
43            Tool::Copilot => ".github/copilot-instructions.md",
44            Tool::Continue => ".continue/config.json",
45            Tool::Windsurf => ".windsurfrules",
46            Tool::Cline => ".clinerules",
47            Tool::Aider => ".aider.conf.yml",
48            Tool::Generic => "AGENTS.md",
49        }
50    }
51
52    /// Get the human-readable name
53    pub fn name(&self) -> &'static str {
54        match self {
55            Tool::Cursor => "Cursor",
56            Tool::ClaudeCode => "Claude Code",
57            Tool::Copilot => "GitHub Copilot",
58            Tool::Continue => "Continue.dev",
59            Tool::Windsurf => "Windsurf",
60            Tool::Cline => "Cline",
61            Tool::Aider => "Aider",
62            Tool::Generic => "Generic (AGENTS.md)",
63        }
64    }
65
66    /// Check if this tool supports MCP
67    pub fn supports_mcp(&self) -> bool {
68        matches!(self, Tool::ClaudeCode | Tool::Continue | Tool::Cline)
69    }
70
71    /// Get the output format for this tool
72    pub fn format(&self) -> OutputFormat {
73        match self {
74            Tool::Continue => OutputFormat::Json,
75            Tool::Aider => OutputFormat::Yaml,
76            _ => OutputFormat::Markdown,
77        }
78    }
79
80    /// Parse tool name from string
81    pub fn from_name(name: &str) -> Option<Tool> {
82        match name.to_lowercase().as_str() {
83            "cursor" => Some(Tool::Cursor),
84            "claude-code" | "claudecode" | "claude" => Some(Tool::ClaudeCode),
85            "copilot" | "github-copilot" => Some(Tool::Copilot),
86            "continue" | "continue-dev" => Some(Tool::Continue),
87            "windsurf" => Some(Tool::Windsurf),
88            "cline" => Some(Tool::Cline),
89            "aider" => Some(Tool::Aider),
90            "generic" | "agents" => Some(Tool::Generic),
91            _ => None,
92        }
93    }
94}
95
96impl fmt::Display for Tool {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        write!(f, "{}", self.name())
99    }
100}
101
102/// Output format types
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum OutputFormat {
105    Markdown,
106    Json,
107    Yaml,
108}
109
110/// Merge strategy for existing files
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum MergeStrategy {
113    /// Replace entire file
114    Replace,
115    /// Replace only marked section
116    Section,
117    /// Append to end of file
118    Append,
119    /// Deep merge (for JSON/YAML)
120    Merge,
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn test_tool_output_paths() {
129        assert_eq!(Tool::Cursor.output_path(), ".cursorrules");
130        assert_eq!(Tool::ClaudeCode.output_path(), "CLAUDE.md");
131        assert_eq!(
132            Tool::Copilot.output_path(),
133            ".github/copilot-instructions.md"
134        );
135        assert_eq!(Tool::Generic.output_path(), "AGENTS.md");
136    }
137
138    #[test]
139    fn test_tool_from_name() {
140        assert_eq!(Tool::from_name("cursor"), Some(Tool::Cursor));
141        assert_eq!(Tool::from_name("Claude-Code"), Some(Tool::ClaudeCode));
142        assert_eq!(Tool::from_name("unknown"), None);
143    }
144
145    #[test]
146    fn test_tool_mcp_support() {
147        assert!(Tool::ClaudeCode.supports_mcp());
148        assert!(Tool::Continue.supports_mcp());
149        assert!(!Tool::Cursor.supports_mcp());
150    }
151}