174 lines
4.9 KiBLFS
TypeScript
174 lines
4.9 KiBLFS
TypeScript
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<string, SkillEntry>();
|
|
|
|
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/<task-name>/environment/skills/<skill-name>/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();
|