use std::collections::HashSet;
use std::path::Path;
use super::{SkillError, SkillPackage, SkillsDirScanner};
pub fn discover_from_dir<P: AsRef<Path>>(dir: P) -> Result<Vec<SkillPackage>, SkillError> {
let dir = dir.as_ref();
if !dir.exists() {
return Err(SkillError::Io(format!(
"Directory does not exist: {:?}",
dir
)));
}
if !dir.is_dir() {
return Err(SkillError::Io(format!(
"Path is not a directory: {:?}",
dir
)));
}
let entries = std::fs::read_dir(dir)
.map_err(|e| SkillError::Io(format!("Failed to read directory: {}", e)))?;
let mut packages = Vec::new();
for entry in entries {
let entry = entry
.map_err(|e| SkillError::Io(format!("Failed to read directory entry: {}", e)))?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("json") {
continue;
}
match SkillPackage::load_from_file(&path) {
Ok(package) => {
tracing::info!(
"Loaded skill package: {} from {:?}",
package.metadata.name,
path
);
packages.push(package);
}
Err(e) => {
tracing::warn!("Failed to load skill package from {:?}: {}", path, e);
continue;
}
}
}
Ok(packages)
}
pub fn discover_skill_md_from_dir<P: AsRef<Path>>(dir: P) -> Result<Vec<SkillPackage>, SkillError> {
let dir = dir.as_ref();
if !dir.exists() {
tracing::debug!("Skills directory does not exist: {:?}", dir);
return Ok(Vec::new());
}
if !dir.is_dir() {
return Err(SkillError::Io(format!(
"Path is not a directory: {:?}",
dir
)));
}
let scanner = SkillsDirScanner::new(dir);
let skill_md_files = scanner.scan()
.map_err(|e| SkillError::Io(format!("Failed to scan skills directory: {}", e)))?;
let mut packages = Vec::new();
for skill_md in skill_md_files {
let package = skill_md.to_skill_package();
tracing::info!(
"Loaded SKILL.md: {} from {:?}",
package.metadata.name,
skill_md.skill_dir
);
packages.push(package);
}
Ok(packages)
}
pub fn discover_from_multiple_dirs<P: AsRef<Path>>(dirs: Vec<P>) -> Result<Vec<SkillPackage>, SkillError> {
let mut all_packages = Vec::new();
let mut seen_ids = HashSet::new();
for dir in dirs {
let dir = dir.as_ref();
if let Ok(mut packages) = discover_skill_md_from_dir(dir) {
packages.retain(|p| seen_ids.insert(p.metadata.id.clone()));
all_packages.extend(packages);
}
if let Ok(mut packages) = discover_from_dir(dir) {
packages.retain(|p| seen_ids.insert(p.metadata.id.clone()));
all_packages.extend(packages);
}
}
Ok(all_packages)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_discover_from_dir() {
let temp_dir = std::env::temp_dir().join("skills_test_discover_module");
fs::create_dir_all(&temp_dir).unwrap();
let package1 = SkillPackage {
metadata: crate::skills::SkillMetadata {
id: "test1".to_string(),
name: "Test Skill 1".to_string(),
description: "First test skill".to_string(),
version: "1.0.0".to_string(),
author: None,
dependencies: vec![],
tags: vec![],
},
instructions: "Test instructions 1".to_string(),
scripts: vec![],
resources: crate::skills::SkillResources::default(),
};
let package2 = SkillPackage {
metadata: crate::skills::SkillMetadata {
id: "test2".to_string(),
name: "Test Skill 2".to_string(),
description: "Second test skill".to_string(),
version: "1.0.0".to_string(),
author: None,
dependencies: vec![],
tags: vec![],
},
instructions: "Test instructions 2".to_string(),
scripts: vec![],
resources: crate::skills::SkillResources::default(),
};
let file1 = temp_dir.join("skill1.json");
let file2 = temp_dir.join("skill2.json");
package1.save_to_file(&file1).unwrap();
package2.save_to_file(&file2).unwrap();
let packages = discover_from_dir(&temp_dir).unwrap();
assert_eq!(packages.len(), 2);
fs::remove_file(&file1).unwrap();
fs::remove_file(&file2).unwrap();
fs::remove_dir(&temp_dir).unwrap();
}
#[test]
fn test_discover_from_nonexistent_dir() {
let result = discover_from_dir("/nonexistent/path/that/does/not/exist");
assert!(result.is_err());
}
#[test]
fn test_discover_skill_md_from_nonexistent_dir() {
let result = discover_skill_md_from_dir("/nonexistent/path/that/does/not/exist");
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
}