import fs from "fs"; import path from "path"; import matter from "gray-matter"; interface FileNode { name: string; type: "file" | "directory"; children?: FileNode[]; } interface SkillEntry { id: string; name: string; description: string; fileStructure: FileNode[]; path: string; relatedTasks: { title: string; path: string }[]; } function getFileStructure(dirPath: string): FileNode[] { const entries = fs.readdirSync(dirPath, { withFileTypes: true }); const nodes: FileNode[] = []; for (const entry of entries) { if ( entry.name === "node_modules" || entry.name === ".git" || entry.name === ".DS_Store" ) continue; if (entry.isDirectory()) { nodes.push({ name: entry.name, type: "directory", children: getFileStructure(path.join(dirPath, entry.name)), }); } else { nodes.push({ name: entry.name, type: "file" }); } } return nodes.sort((a, b) => { if (a.type === b.type) return a.name.localeCompare(b.name); return a.type === "directory" ? -1 : 1; }); } function generateSkillsRegistry(): void { // Look for tasks in common locations const candidates = [ path.join(__dirname, "..", "..", "tasks"), path.join(__dirname, "..", "..", "skillsbench", "tasks"), path.join(__dirname, "..", "tasks"), ]; let tasksDir = candidates.find((d) => fs.existsSync(d)) || candidates[0]; const outputPath = path.join( __dirname, "..", "src", "data", "skills-registry.json" ); const skillsMap = new Map(); if (!fs.existsSync(tasksDir)) { console.warn( "Tasks directory not found, skipping skills registry generation:", tasksDir ); fs.mkdirSync(path.dirname(outputPath), { recursive: true }); fs.writeFileSync(outputPath, "[]"); return; } // Find all SKILL.md files recursively function findSkillFiles(dir: string): string[] { const results: string[] = []; const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if ( entry.name === "node_modules" || entry.name === ".git" || entry.name === ".next" ) continue; if (entry.isDirectory()) { results.push(...findSkillFiles(fullPath)); } else if (entry.name === "SKILL.md") { results.push(fullPath); } } return results; } const skillFiles = findSkillFiles(tasksDir); console.log(`Found ${skillFiles.length} SKILL.md files`); for (const file of skillFiles) { try { const fileContent = fs.readFileSync(file, "utf-8"); const { data, content } = matter(fileContent); const skillDir = path.dirname(file); const skillFolderName = path.basename(skillDir); const skillName = data.name || skillFolderName; const skillId = skillName.toLowerCase().replace(/\s+/g, "-"); // Extract task name from path // Pattern: tasks//environment/skills//SKILL.md const relPath = path.relative(tasksDir, file); const parts = relPath.split(path.sep); const taskName = parts[0]; // First component is the task name let skill = skillsMap.get(skillId); if (!skill) { // Get description from frontmatter or first paragraph of content let description = data.description || ""; if (!description && content) { // Skip heading lines, get first non-empty paragraph const lines = content.split("\n"); const nonHeadingLines: string[] = []; for (const line of lines) { if (line.startsWith("#")) continue; if (line.trim() === "" && nonHeadingLines.length > 0) break; if (line.trim()) nonHeadingLines.push(line.trim()); } description = nonHeadingLines.join(" ").slice(0, 300); } skill = { id: skillId, name: skillName, description, fileStructure: getFileStructure(skillDir), path: `tasks/${path.relative(tasksDir, skillDir).split(path.sep).join("/")}`, relatedTasks: [], }; skillsMap.set(skillId, skill); } // Add related task if (taskName && !skill.relatedTasks.some((t) => t.title === taskName)) { skill.relatedTasks.push({ title: taskName, path: `/tasks/${taskName}`, }); } } catch (error) { console.error(`Error processing skill at ${file}:`, error); } } const skills = Array.from(skillsMap.values()).sort((a, b) => a.name.localeCompare(b.name) ); const outputDir = path.dirname(outputPath); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } fs.writeFileSync(outputPath, JSON.stringify(skills, null, 2)); console.log( `Generated skills registry with ${skills.length} unique skills at ${outputPath}` ); } generateSkillsRegistry();