ricecoder_storage/industry/
copilot.rs

1//! GitHub Copilot configuration adapter
2//!
3//! Reads and converts GitHub Copilot configuration files (.github/copilot-instructions.md)
4//! into RiceCoder's internal configuration format.
5
6use crate::config::{Config, SteeringRule};
7use crate::error::StorageResult;
8use crate::types::DocumentFormat;
9use std::path::Path;
10use tracing::debug;
11
12use super::adapter::IndustryFileAdapter;
13
14/// GitHub Copilot adapter
15pub struct CopilotAdapter;
16
17impl CopilotAdapter {
18    /// Create a new Copilot adapter
19    pub fn new() -> Self {
20        CopilotAdapter
21    }
22
23    /// Read .github/copilot-instructions.md file
24    fn read_copilot_instructions(&self, project_root: &Path) -> StorageResult<Option<String>> {
25        let copilot_path = project_root.join(".github/copilot-instructions.md");
26
27        if !copilot_path.exists() {
28            debug!(
29                "No .github/copilot-instructions.md file found at {:?}",
30                copilot_path
31            );
32            return Ok(None);
33        }
34
35        debug!(
36            "Reading .github/copilot-instructions.md from {:?}",
37            copilot_path
38        );
39        let content = std::fs::read_to_string(&copilot_path).map_err(|e| {
40            crate::error::StorageError::io_error(
41                copilot_path.clone(),
42                crate::error::IoOperation::Read,
43                e,
44            )
45        })?;
46
47        Ok(Some(content))
48    }
49}
50
51impl Default for CopilotAdapter {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57impl IndustryFileAdapter for CopilotAdapter {
58    fn name(&self) -> &'static str {
59        "copilot"
60    }
61
62    fn can_handle(&self, project_root: &Path) -> bool {
63        project_root
64            .join(".github/copilot-instructions.md")
65            .exists()
66    }
67
68    fn read_config(&self, project_root: &Path) -> StorageResult<Config> {
69        let mut config = Config::default();
70
71        if let Ok(Some(instructions)) = self.read_copilot_instructions(project_root) {
72            debug!("Adding Copilot instructions as steering rule");
73            config.steering.push(SteeringRule {
74                name: "copilot-instructions".to_string(),
75                content: instructions,
76                format: DocumentFormat::Markdown,
77            });
78        }
79
80        Ok(config)
81    }
82
83    fn priority(&self) -> u32 {
84        // Copilot has medium priority
85        50
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use std::fs;
93    use tempfile::TempDir;
94
95    #[test]
96    fn test_copilot_adapter_detects_instructions() {
97        let temp_dir = TempDir::new().unwrap();
98        let github_dir = temp_dir.path().join(".github");
99        fs::create_dir(&github_dir).unwrap();
100        let copilot_path = github_dir.join("copilot-instructions.md");
101        fs::write(&copilot_path, "# Copilot Instructions").unwrap();
102
103        let adapter = CopilotAdapter::new();
104        assert!(adapter.can_handle(temp_dir.path()));
105    }
106
107    #[test]
108    fn test_copilot_adapter_no_file() {
109        let temp_dir = TempDir::new().unwrap();
110
111        let adapter = CopilotAdapter::new();
112        assert!(!adapter.can_handle(temp_dir.path()));
113    }
114
115    #[test]
116    fn test_copilot_adapter_reads_instructions() {
117        let temp_dir = TempDir::new().unwrap();
118        let github_dir = temp_dir.path().join(".github");
119        fs::create_dir(&github_dir).unwrap();
120        let copilot_path = github_dir.join("copilot-instructions.md");
121        let instructions = "# Copilot Instructions\nBe helpful";
122        fs::write(&copilot_path, instructions).unwrap();
123
124        let adapter = CopilotAdapter::new();
125        let config = adapter.read_config(temp_dir.path()).unwrap();
126
127        assert_eq!(config.steering.len(), 1);
128        assert_eq!(config.steering[0].name, "copilot-instructions");
129        assert_eq!(config.steering[0].content, instructions);
130        assert_eq!(config.steering[0].format, DocumentFormat::Markdown);
131    }
132
133    #[test]
134    fn test_copilot_adapter_priority() {
135        let adapter = CopilotAdapter::new();
136        assert_eq!(adapter.priority(), 50);
137    }
138
139    #[test]
140    fn test_copilot_adapter_name() {
141        let adapter = CopilotAdapter::new();
142        assert_eq!(adapter.name(), "copilot");
143    }
144}