/**
 * Skill discovery for cloned GitHub repositories
 *
 * Searches for SKILL.md files and parses their YAML frontmatter
 * to identify valid skills within a cloned repo.
 */
import * as fs from "fs/promises";
import * as path from "path";
const SKIP_DIRS = new Set([
    "node_modules",
    ".git",
    "dist",
    "build",
    "__pycache__",
]);
const MAX_RECURSIVE_DEPTH = 5;
/**
 * Parse YAML frontmatter from a SKILL.md file to extract name and description.
 *
 * Uses regex parsing to avoid adding a gray-matter dependency.
 * Handles both quoted and unquoted values.
 *
 * @param args - The function arguments
 * @param args.content - The raw SKILL.md content
 *
 * @returns Parsed name and description, or null if invalid
 */
export const parseSkillFrontmatter = (args) => {
    const { content } = args;
    // Match frontmatter block between --- delimiters
    const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
    if (frontmatterMatch == null) {
        return null;
    }
    const frontmatter = frontmatterMatch[1];
    // Extract name (handles quoted and unquoted values)
    const nameMatch = frontmatter.match(/^name:\s*(?:"([^"]*?)"|'([^']*?)'|(.+?))\s*$/m);
    if (nameMatch == null) {
        return null;
    }
    const name = nameMatch[1] ?? nameMatch[2] ?? nameMatch[3];
    // Extract description (handles quoted and unquoted values)
    const descMatch = frontmatter.match(/^description:\s*(?:"([^"]*?)"|'([^']*?)'|(.+?))\s*$/m);
    if (descMatch == null) {
        return null;
    }
    const description = descMatch[1] ?? descMatch[2] ?? descMatch[3];
    if (name == null || description == null) {
        return null;
    }
    return { name: name.trim(), description: description.trim() };
};
/**
 * Check if a directory contains a SKILL.md file
 * @param args - The function arguments
 * @param args.dir - Directory path to check
 *
 * @returns True if the directory contains a SKILL.md file
 */
const hasSkillMd = async (args) => {
    const { dir } = args;
    try {
        const skillPath = path.join(dir, "SKILL.md");
        const stats = await fs.stat(skillPath);
        return stats.isFile();
    }
    catch {
        return false;
    }
};
/**
 * Parse a SKILL.md file at the given directory path
 * @param args - The function arguments
 * @param args.dir - Directory containing the SKILL.md file
 *
 * @returns Parsed skill or null if invalid
 */
const parseSkillAt = async (args) => {
    const { dir } = args;
    try {
        const skillPath = path.join(dir, "SKILL.md");
        const content = await fs.readFile(skillPath, "utf-8");
        const parsed = parseSkillFrontmatter({ content });
        if (parsed == null) {
            return null;
        }
        return {
            name: parsed.name,
            description: parsed.description,
            dirPath: dir,
            rawContent: content,
        };
    }
    catch {
        return null;
    }
};
/**
 * Recursively find directories containing SKILL.md files
 * @param args - The function arguments
 * @param args.dir - Directory to search from
 * @param args.depth - Current recursion depth
 *
 * @returns Array of directory paths containing SKILL.md files
 */
const findSkillDirsRecursive = async (args) => {
    const { dir } = args;
    const depth = args.depth ?? 0;
    if (depth > MAX_RECURSIVE_DEPTH) {
        return [];
    }
    try {
        const results = [];
        if (await hasSkillMd({ dir })) {
            results.push(dir);
        }
        const entries = await fs.readdir(dir, { withFileTypes: true });
        const subResults = await Promise.all(entries
            .filter((entry) => entry.isDirectory() && !SKIP_DIRS.has(entry.name))
            .map((entry) => findSkillDirsRecursive({
            dir: path.join(dir, entry.name),
            depth: depth + 1,
        })));
        return [...results, ...subResults.flat()];
    }
    catch {
        return [];
    }
};
/**
 * Discover skills within a cloned repository
 *
 * Searches for SKILL.md files following the Agent Skills specification:
 * 1. Root directory (if it has SKILL.md, return immediately)
 * 2. Recursive search (up to 5 levels deep)
 *
 * Deduplicates by skill name.
 *
 * @param args - The function arguments
 * @param args.basePath - Root path of the cloned repository
 * @param args.subpath - Optional subpath to search within
 *
 * @returns Array of discovered skills
 */
export const discoverSkills = async (args) => {
    const { basePath } = args;
    const searchPath = args.subpath != null ? path.join(basePath, args.subpath) : basePath;
    // 1. Check root for SKILL.md — if the repo itself is a single skill
    if (await hasSkillMd({ dir: searchPath })) {
        const skill = await parseSkillAt({ dir: searchPath });
        if (skill != null) {
            return [skill];
        }
    }
    // 2. Recursive search for all SKILL.md files
    const skills = [];
    const seenNames = new Set();
    const allDirs = await findSkillDirsRecursive({ dir: searchPath });
    for (const dir of allDirs) {
        const skill = await parseSkillAt({ dir });
        if (skill != null && !seenNames.has(skill.name)) {
            skills.push(skill);
            seenNames.add(skill.name);
        }
    }
    return skills;
};
//# sourceMappingURL=skillDiscovery.js.map