arct_core/
context.rs

1//! Context detection and environment awareness
2
3use crate::types::Result;
4use serde::{Deserialize, Serialize};
5use std::path::{Path, PathBuf};
6
7/// Represents the current execution context
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Context {
10    pub working_directory: PathBuf,
11    pub project_type: Option<ProjectType>,
12    pub vcs: Option<VcsType>,
13    pub suggestions: Vec<Suggestion>,
14}
15
16/// Types of projects we can detect
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub enum ProjectType {
19    Rust { has_cargo_toml: bool },
20    Node { has_package_json: bool },
21    Python { has_pyproject: bool, has_requirements: bool },
22    Go { has_go_mod: bool },
23    Java { has_pom: bool, has_gradle: bool },
24    Generic,
25}
26
27/// Version control systems
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub enum VcsType {
30    Git,
31    Mercurial,
32    Svn,
33}
34
35/// Context-aware suggestions
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct Suggestion {
38    pub command: String,
39    pub description: String,
40    pub category: SuggestionCategory,
41    pub relevance: f32, // 0.0 to 1.0
42}
43
44/// Categories of suggestions
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46pub enum SuggestionCategory {
47    Navigation,
48    Build,
49    Test,
50    VCS,
51    FileManagement,
52    Development,
53}
54
55/// Detects and analyzes the current context
56pub struct ContextDetector;
57
58impl ContextDetector {
59    /// Detect the full context for a given directory
60    pub fn detect(path: &Path) -> Result<Context> {
61        let working_directory = path.to_path_buf();
62        let project_type = Self::detect_project_type(path);
63        let vcs = Self::detect_vcs(path);
64        let suggestions = Self::generate_suggestions(path, &project_type, &vcs);
65
66        Ok(Context {
67            working_directory,
68            project_type,
69            vcs,
70            suggestions,
71        })
72    }
73
74    /// Detect the project type based on files present
75    fn detect_project_type(path: &Path) -> Option<ProjectType> {
76        // Rust project
77        if path.join("Cargo.toml").exists() {
78            return Some(ProjectType::Rust { has_cargo_toml: true });
79        }
80
81        // Node.js project
82        if path.join("package.json").exists() {
83            return Some(ProjectType::Node { has_package_json: true });
84        }
85
86        // Python project
87        let has_pyproject = path.join("pyproject.toml").exists();
88        let has_requirements = path.join("requirements.txt").exists();
89        if has_pyproject || has_requirements {
90            return Some(ProjectType::Python { has_pyproject, has_requirements });
91        }
92
93        // Go project
94        if path.join("go.mod").exists() {
95            return Some(ProjectType::Go { has_go_mod: true });
96        }
97
98        // Java project
99        let has_pom = path.join("pom.xml").exists();
100        let has_gradle = path.join("build.gradle").exists() || path.join("build.gradle.kts").exists();
101        if has_pom || has_gradle {
102            return Some(ProjectType::Java { has_pom, has_gradle });
103        }
104
105        None
106    }
107
108    /// Detect version control system
109    fn detect_vcs(path: &Path) -> Option<VcsType> {
110        if path.join(".git").exists() {
111            return Some(VcsType::Git);
112        }
113        if path.join(".hg").exists() {
114            return Some(VcsType::Mercurial);
115        }
116        if path.join(".svn").exists() {
117            return Some(VcsType::Svn);
118        }
119        None
120    }
121
122    /// Generate context-aware suggestions
123    fn generate_suggestions(
124        _path: &Path,
125        project_type: &Option<ProjectType>,
126        vcs: &Option<VcsType>,
127    ) -> Vec<Suggestion> {
128        let mut suggestions = Vec::new();
129
130        // VCS suggestions
131        if let Some(VcsType::Git) = vcs {
132            suggestions.push(Suggestion {
133                command: "git status".to_string(),
134                description: "Check repository status".to_string(),
135                category: SuggestionCategory::VCS,
136                relevance: 0.9,
137            });
138            suggestions.push(Suggestion {
139                command: "git log --oneline -10".to_string(),
140                description: "View recent commits".to_string(),
141                category: SuggestionCategory::VCS,
142                relevance: 0.7,
143            });
144            suggestions.push(Suggestion {
145                command: "git diff".to_string(),
146                description: "See uncommitted changes".to_string(),
147                category: SuggestionCategory::VCS,
148                relevance: 0.8,
149            });
150        }
151
152        // Project-specific suggestions
153        match project_type {
154            Some(ProjectType::Rust { .. }) => {
155                suggestions.push(Suggestion {
156                    command: "cargo build".to_string(),
157                    description: "Build the Rust project".to_string(),
158                    category: SuggestionCategory::Build,
159                    relevance: 0.95,
160                });
161                suggestions.push(Suggestion {
162                    command: "cargo test".to_string(),
163                    description: "Run tests".to_string(),
164                    category: SuggestionCategory::Test,
165                    relevance: 0.85,
166                });
167                suggestions.push(Suggestion {
168                    command: "cargo run".to_string(),
169                    description: "Build and run the project".to_string(),
170                    category: SuggestionCategory::Development,
171                    relevance: 0.9,
172                });
173            }
174            Some(ProjectType::Node { .. }) => {
175                suggestions.push(Suggestion {
176                    command: "npm install".to_string(),
177                    description: "Install dependencies".to_string(),
178                    category: SuggestionCategory::Build,
179                    relevance: 0.9,
180                });
181                suggestions.push(Suggestion {
182                    command: "npm test".to_string(),
183                    description: "Run tests".to_string(),
184                    category: SuggestionCategory::Test,
185                    relevance: 0.85,
186                });
187                suggestions.push(Suggestion {
188                    command: "npm start".to_string(),
189                    description: "Start the application".to_string(),
190                    category: SuggestionCategory::Development,
191                    relevance: 0.9,
192                });
193            }
194            Some(ProjectType::Python { .. }) => {
195                suggestions.push(Suggestion {
196                    command: "pip install -r requirements.txt".to_string(),
197                    description: "Install dependencies".to_string(),
198                    category: SuggestionCategory::Build,
199                    relevance: 0.9,
200                });
201                suggestions.push(Suggestion {
202                    command: "pytest".to_string(),
203                    description: "Run tests".to_string(),
204                    category: SuggestionCategory::Test,
205                    relevance: 0.85,
206                });
207            }
208            Some(ProjectType::Go { .. }) => {
209                suggestions.push(Suggestion {
210                    command: "go build".to_string(),
211                    description: "Build the Go project".to_string(),
212                    category: SuggestionCategory::Build,
213                    relevance: 0.95,
214                });
215                suggestions.push(Suggestion {
216                    command: "go test ./...".to_string(),
217                    description: "Run all tests".to_string(),
218                    category: SuggestionCategory::Test,
219                    relevance: 0.85,
220                });
221            }
222            _ => {}
223        }
224
225        // Always add general navigation suggestions
226        suggestions.push(Suggestion {
227            command: "ls -la".to_string(),
228            description: "List all files with details".to_string(),
229            category: SuggestionCategory::Navigation,
230            relevance: 0.6,
231        });
232
233        // Sort by relevance
234        suggestions.sort_by(|a, b| b.relevance.partial_cmp(&a.relevance).unwrap());
235
236        suggestions
237    }
238
239    /// Check if we're in the home directory
240    pub fn is_home_directory(path: &Path) -> bool {
241        if let Some(home) = dirs::home_dir() {
242            path == home
243        } else {
244            false
245        }
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252    use std::fs;
253
254    #[test]
255    fn test_detect_rust_project() {
256        let temp_dir = std::env::temp_dir().join("test_rust_project");
257        fs::create_dir_all(&temp_dir).unwrap();
258        fs::write(temp_dir.join("Cargo.toml"), "").unwrap();
259
260        let project_type = ContextDetector::detect_project_type(&temp_dir);
261        assert!(matches!(project_type, Some(ProjectType::Rust { .. })));
262
263        fs::remove_dir_all(temp_dir).ok();
264    }
265
266    #[test]
267    fn test_generate_suggestions_for_rust() {
268        let temp_dir = std::env::temp_dir().join("test_rust_suggestions");
269        fs::create_dir_all(&temp_dir).unwrap();
270        fs::write(temp_dir.join("Cargo.toml"), "").unwrap();
271
272        let context = ContextDetector::detect(&temp_dir).unwrap();
273        assert!(context.suggestions.iter().any(|s| s.command.contains("cargo")));
274
275        fs::remove_dir_all(temp_dir).ok();
276    }
277}