// Sync category frontmatter in skills/SKILL.md files to canonical taxonomy. // Run: npx tsx scripts/sync-skill-categories.ts [--dry-run] import fs from 'fs'; import path from 'path'; import matter from 'gray-matter'; import { fileURLToPath } from 'url'; import { resolveCategory } from './category-rules.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const SKILLS_DIR = path.resolve(__dirname, '../../skills'); const dryRun = process.argv.includes('--dry-run'); function findSkillFiles(dir: string): string[] { const files: string[] = []; if (!fs.existsSync(dir)) return files; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { if (entry.isDirectory()) { if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue; const fullPath = path.join(dir, entry.name); const skillPath = path.join(fullPath, 'SKILL.md'); if (fs.existsSync(skillPath)) files.push(skillPath); files.push(...findSkillFiles(fullPath)); } } return files; } function main() { const files = findSkillFiles(SKILLS_DIR); let updated = 0; let skipped = 0; for (const filePath of files) { const content = fs.readFileSync(filePath, 'utf-8'); let parsed; try { parsed = matter(content); } catch (error) { skipped++; console.warn(`⚠ Skipping ${path.relative(SKILLS_DIR, filePath)} - invalid frontmatter`); continue; } if (!parsed.data.name || !parsed.data.description) { skipped++; continue; } const relativePath = path.relative(SKILLS_DIR, filePath); const category = resolveCategory(relativePath, parsed.data.category as string | undefined); if (parsed.data.category === category) continue; parsed.data.category = category; const next = matter.stringify(parsed.content, parsed.data); if (!dryRun) { fs.writeFileSync(filePath, next); } updated++; console.log(`${dryRun ? '[dry-run] ' : ''}${relativePath} → ${category}`); } console.log(`\n${dryRun ? 'Would update' : 'Updated'} ${updated} files (${skipped} skipped, ${files.length} total)`); } main();