skm_cli/commands/
validate.rs1use clap::Args;
4use std::path::PathBuf;
5
6use skm_core::SkillParser;
7
8#[derive(Args)]
9pub struct ValidateArgs {
10 paths: Vec<PathBuf>,
12
13 #[arg(long)]
15 strict: bool,
16
17 #[arg(long, default_value = "text")]
19 format: String,
20}
21
22pub async fn validate(args: ValidateArgs) -> anyhow::Result<()> {
23 let parser = if args.strict {
24 SkillParser::strict()
25 } else {
26 SkillParser::new()
27 };
28
29 let mut total = 0;
30 let mut passed = 0;
31 let mut errors = Vec::new();
32
33 for path in &args.paths {
34 let skill_files = find_skill_files(path);
35
36 for file in skill_files {
37 total += 1;
38
39 match parser.parse_file(&file) {
40 Ok(skill) => {
41 passed += 1;
42 if args.format == "text" {
43 println!("✓ {}: {} ({})", file.display(), skill.name, skill.description);
44 }
45 }
46 Err(e) => {
47 errors.push((file.clone(), e.to_string()));
48 if args.format == "text" {
49 eprintln!("✗ {}: {}", file.display(), e);
50 }
51 }
52 }
53 }
54 }
55
56 if args.format == "json" {
57 let result = serde_json::json!({
58 "total": total,
59 "passed": passed,
60 "failed": total - passed,
61 "errors": errors.iter().map(|(p, e)| {
62 serde_json::json!({
63 "file": p.display().to_string(),
64 "error": e
65 })
66 }).collect::<Vec<_>>()
67 });
68 println!("{}", serde_json::to_string_pretty(&result)?);
69 } else {
70 println!("\nValidation complete: {}/{} passed", passed, total);
71 }
72
73 if passed < total {
74 std::process::exit(1);
75 }
76
77 Ok(())
78}
79
80fn find_skill_files(path: &PathBuf) -> Vec<PathBuf> {
81 let mut files = Vec::new();
82
83 if path.is_file() && path.file_name().map(|n| n == "SKILL.md").unwrap_or(false) {
84 files.push(path.clone());
85 } else if path.is_dir() {
86 if let Ok(entries) = std::fs::read_dir(path) {
88 for entry in entries.flatten() {
89 let entry_path = entry.path();
90 if entry_path.is_dir() {
91 files.extend(find_skill_files(&entry_path));
92 } else if entry_path.file_name().map(|n| n == "SKILL.md").unwrap_or(false) {
93 files.push(entry_path);
94 }
95 }
96 }
97 }
98
99 files
100}