fastskill_core/core/
project.rs1use super::manifest::{FileResolutionResult, ProjectContext};
4use std::path::Path;
5
6pub fn resolve_project_file(start_path: &Path) -> FileResolutionResult {
17 let mut current = start_path.to_path_buf();
19
20 loop {
22 let project_file = current.join("skill-project.toml");
23
24 if project_file.exists() {
25 let file_context = detect_context(&project_file);
27
28 let final_context = file_context;
32
33 return FileResolutionResult {
34 path: project_file,
35 context: final_context,
36 found: true,
37 };
38 }
39
40 if let Some(parent) = current.parent() {
42 current = parent.to_path_buf();
43 } else {
44 break;
45 }
46 }
47
48 let default_path = start_path.join("skill-project.toml");
50 FileResolutionResult {
51 path: default_path,
52 context: ProjectContext::Project, found: false,
54 }
55}
56
57pub fn detect_context(project_file: &Path) -> ProjectContext {
64 let dir = project_file.parent().unwrap_or_else(|| Path::new("."));
65 let skill_md = dir.join("SKILL.md");
66
67 if skill_md.exists() {
68 return ProjectContext::Skill;
69 }
70
71 ProjectContext::Project
75}
76
77pub fn detect_context_from_content(project: &super::manifest::SkillProjectToml) -> ProjectContext {
84 if let Some(ref metadata) = project.metadata {
86 if metadata.id.is_some() {
87 return ProjectContext::Skill;
88 }
89 }
90
91 if let Some(ref deps) = project.dependencies {
93 if !deps.dependencies.is_empty() {
94 return ProjectContext::Project;
95 }
96 }
97
98 ProjectContext::Ambiguous
100}
101
102#[cfg(test)]
103#[allow(clippy::unwrap_used)]
104mod tests {
105 use super::*;
106 use std::fs;
107 use tempfile::TempDir;
108
109 #[test]
110 fn test_resolve_project_file_found() {
111 let temp_dir = TempDir::new().unwrap();
112 let project_file = temp_dir.path().join("skill-project.toml");
113 fs::write(&project_file, "[dependencies]\n").unwrap();
114
115 let result = resolve_project_file(temp_dir.path());
116
117 assert!(result.found);
118 assert_eq!(result.path, project_file);
119 }
120
121 #[test]
122 fn test_resolve_project_file_not_found() {
123 let temp_dir = TempDir::new().unwrap();
124
125 let result = resolve_project_file(temp_dir.path());
126
127 assert!(!result.found);
128 assert_eq!(result.path, temp_dir.path().join("skill-project.toml"));
129 }
130
131 #[test]
132 fn test_detect_context_skill() {
133 let temp_dir = TempDir::new().unwrap();
134 let project_file = temp_dir.path().join("skill-project.toml");
135 fs::write(&project_file, "[metadata]\n").unwrap();
136 fs::write(temp_dir.path().join("SKILL.md"), "# Skill\n").unwrap();
137
138 let context = detect_context(&project_file);
139
140 assert_eq!(context, ProjectContext::Skill);
141 }
142
143 #[test]
144 fn test_detect_context_project() {
145 let temp_dir = TempDir::new().unwrap();
146 let project_file = temp_dir.path().join("skill-project.toml");
147 fs::write(&project_file, "[dependencies]\n").unwrap();
148 let context = detect_context(&project_file);
151
152 assert_eq!(context, ProjectContext::Project);
153 }
154
155 #[test]
156 fn test_resolve_project_file_walk_up() {
157 let temp_dir = TempDir::new().unwrap();
159 let root_file = temp_dir.path().join("skill-project.toml");
160 fs::write(&root_file, "[dependencies]\n").unwrap();
161
162 let subdir = temp_dir.path().join("subdir");
164 fs::create_dir_all(&subdir).unwrap();
165
166 let result = resolve_project_file(&subdir);
168
169 assert!(result.found);
170 assert_eq!(result.path, root_file);
171 assert_eq!(result.context, ProjectContext::Project);
172 }
173
174 #[test]
175 fn test_resolve_project_file_closest_wins() {
176 let temp_dir = TempDir::new().unwrap();
178
179 let root_file = temp_dir.path().join("skill-project.toml");
181 fs::write(&root_file, "[dependencies]\nroot = \"1.0.0\"\n").unwrap();
182
183 let subdir = temp_dir.path().join("subdir");
185 fs::create_dir_all(&subdir).unwrap();
186 let subdir_file = subdir.join("skill-project.toml");
187 fs::write(&subdir_file, "[dependencies]\nsubdir = \"2.0.0\"\n").unwrap();
188
189 let result = resolve_project_file(&subdir);
191
192 assert!(result.found);
193 assert_eq!(result.path, subdir_file);
194 }
195}