UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes CodeSearch (hybrid SQLite + pgvector), mem0/memgraph specialists, and all CFN skills.

234 lines (195 loc) • 6.61 kB
#!/usr/bin/env tsx /** * Skill Markdown Linting Utility * * Validates all SKILL.md files against standardized structure. * Integrates with CI/CD pipeline for automated validation. * * Usage: * tsx scripts/lint-skill-markdown.ts * tsx scripts/lint-skill-markdown.ts --skill=cfn-coordination * tsx scripts/lint-skill-markdown.ts --strict * * Exit codes: * 0 - All validations passed * 1 - Validation errors found * 2 - Warnings found (strict mode only) * * @module scripts/lint-skill-markdown * @version 1.0.0 */ import * as fs from 'fs'; import * as path from 'path'; import { fileURLToPath } from 'url'; import { validateSkillMarkdown, getValidationSummary } from '../src/lib/skill-markdown-validator'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); interface LintOptions { skillName?: string; strict?: boolean; verbose?: boolean; } interface LintResult { skillName: string; valid: boolean; errorCount: number; warningCount: number; errors: string[]; warnings: string[]; } const SKILLS_DIR = path.resolve(__dirname, '../.claude/skills'); function parseArgs(): LintOptions { const args = process.argv.slice(2); const options: LintOptions = { strict: false, verbose: false, }; args.forEach((arg) => { if (arg.startsWith('--skill=')) { options.skillName = arg.split('=')[1]; } else if (arg === '--strict') { options.strict = true; } else if (arg === '--verbose' || arg === '-v') { options.verbose = true; } else if (arg === '--help' || arg === '-h') { printUsage(); process.exit(0); } }); return options; } function printUsage(): void { console.log(` Skill Markdown Linting Utility Usage: tsx scripts/lint-skill-markdown.ts [options] Options: --skill=NAME Lint specific skill by name --strict Treat warnings as errors --verbose, -v Enable verbose output --help, -h Show this help message Examples: tsx scripts/lint-skill-markdown.ts tsx scripts/lint-skill-markdown.ts --skill=cfn-coordination tsx scripts/lint-skill-markdown.ts --strict --verbose `); } function findSkillDirectories(): string[] { if (!fs.existsSync(SKILLS_DIR)) { return []; } return fs .readdirSync(SKILLS_DIR, { withFileTypes: true }) .filter((dirent) => dirent.isDirectory()) .map((dirent) => dirent.name) .filter((name) => !name.startsWith('.') && !name.startsWith('_')); } function lintSkill(skillName: string, options: LintOptions): LintResult { const result: LintResult = { skillName, valid: false, errorCount: 0, warningCount: 0, errors: [], warnings: [], }; const skillPath = path.join(SKILLS_DIR, skillName, 'SKILL.md'); if (!fs.existsSync(skillPath)) { result.errors.push('SKILL.md file not found'); result.errorCount = 1; return result; } try { const content = fs.readFileSync(skillPath, 'utf-8'); const basePath = path.join(SKILLS_DIR, skillName); const validation = validateSkillMarkdown(content, basePath); result.valid = validation.valid; result.errors = validation.errors; result.warnings = validation.warnings; result.errorCount = validation.errors.length; result.warningCount = validation.warnings.length; } catch (error) { result.errors.push( `Validation failed: ${error instanceof Error ? error.message : String(error)}` ); result.errorCount = result.errors.length; } return result; } async function main(): Promise<void> { const options = parseArgs(); const results: LintResult[] = []; let totalErrors = 0; let totalWarnings = 0; console.log('========================================'); console.log('Skill Markdown Linting'); console.log('========================================\n'); if (options.strict) { console.log('šŸ”’ STRICT MODE - Warnings treated as errors\n'); } // Determine which skills to lint let skillsToLint: string[] = []; if (options.skillName) { skillsToLint = [options.skillName]; } else { skillsToLint = findSkillDirectories(); } console.log(`Linting ${skillsToLint.length} skills...\n`); // Lint each skill for (const skillName of skillsToLint) { const skillPath = path.join(SKILLS_DIR, skillName, 'SKILL.md'); if (!fs.existsSync(skillPath)) { console.log(`⊘ ${skillName} - No SKILL.md file`); continue; } const result = lintSkill(skillName, options); results.push(result); totalErrors += result.errorCount; totalWarnings += result.warningCount; // Print result if (result.errorCount === 0 && result.warningCount === 0) { console.log(`āœ“ ${skillName}`); } else if (result.errorCount === 0) { console.log(`⚠ ${skillName} (${result.warningCount} warnings)`); } else { console.log(`āœ— ${skillName} (${result.errorCount} errors, ${result.warningCount} warnings)`); } // Print details in verbose mode if (options.verbose) { if (result.errors.length > 0) { console.log(' Errors:'); result.errors.forEach((error) => console.log(` - ${error}`)); } if (result.warnings.length > 0) { console.log(' Warnings:'); result.warnings.forEach((warning) => console.log(` - ${warning}`)); } } } // Print summary console.log('\n========================================'); console.log('Linting Summary'); console.log('========================================'); console.log(`Total skills: ${results.length}`); console.log(`Errors: ${totalErrors}`); console.log(`Warnings: ${totalWarnings}`); const passedCount = results.filter((r) => r.errorCount === 0).length; const failedCount = results.filter((r) => r.errorCount > 0).length; console.log(`Passed: ${passedCount}`); console.log(`Failed: ${failedCount}`); // Exit with appropriate code if (totalErrors > 0) { console.log('\nāœ— Linting failed with errors'); process.exit(1); } else if (options.strict && totalWarnings > 0) { console.log('\nāœ— Linting failed with warnings (strict mode)'); process.exit(2); } else { console.log('\nāœ“ All linting checks passed'); process.exit(0); } } main().catch((error) => { console.error('Fatal error:', error); process.exit(1); });