vv-agent 0.6.2

VectorVein agent runtime, SDK, CLI, tools, and workspace backends
Documentation
use std::collections::BTreeSet;
use std::path::{Path, PathBuf};

pub fn find_skill_md(skill_dir: impl AsRef<Path>) -> Option<PathBuf> {
    let skill_dir = skill_dir.as_ref();
    for name in ["SKILL.md", "skill.md"] {
        let candidate = skill_dir.join(name);
        if candidate.is_file() {
            return Some(candidate);
        }
    }
    None
}

pub fn discover_skill_dirs(root: impl AsRef<Path>) -> Vec<PathBuf> {
    let root = root.as_ref();
    if !root.is_dir() {
        return Vec::new();
    }

    let mut discovered = Vec::new();
    let mut seen = BTreeSet::new();
    add_if_skill(root, &mut seen, &mut discovered);
    for candidate in recursive_dirs(root) {
        add_if_skill(&candidate, &mut seen, &mut discovered);
    }
    discovered
}

fn add_if_skill(dir_path: &Path, seen: &mut BTreeSet<PathBuf>, discovered: &mut Vec<PathBuf>) {
    let normalized = dir_path
        .canonicalize()
        .unwrap_or_else(|_| dir_path.to_path_buf());
    if seen.contains(&normalized) || find_skill_md(&normalized).is_none() {
        return;
    }
    seen.insert(normalized.clone());
    discovered.push(normalized);
}

fn recursive_dirs(root: &Path) -> Vec<PathBuf> {
    let mut dirs = Vec::new();
    let mut entries = match std::fs::read_dir(root) {
        Ok(entries) => entries
            .flatten()
            .map(|entry| entry.path())
            .filter(|path| path.is_dir())
            .collect::<Vec<_>>(),
        Err(_) => return dirs,
    };
    entries.sort();
    for entry in entries {
        dirs.push(entry.clone());
        dirs.extend(recursive_dirs(&entry));
    }
    dirs
}