gstack/scripts/skill-frontmatter-check.ts

55 lines
1.6 KiB
TypeScript

#!/usr/bin/env bun
/**
* Validate YAML frontmatter for generated SKILL.md files.
*
* This is intentionally narrower than skill:check so CI can run it after
* regenerating only the host outputs relevant to a workflow.
*/
import { discoverSkillFiles } from './discover-skills';
import { validateSkillFrontmatter } from '../test/helpers/skill-parser';
import * as fs from 'fs';
import * as path from 'path';
const ROOT = path.resolve(import.meta.dir, '..');
function walkSkillFiles(dir: string, acc: string[]): void {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (entry.name === '.git' || entry.name === 'node_modules') continue;
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
walkSkillFiles(fullPath, acc);
continue;
}
if (entry.name === 'SKILL.md') {
acc.push(path.relative(ROOT, fullPath));
}
}
}
const files = new Set(discoverSkillFiles(ROOT));
for (const hostDir of ['.agents', '.factory']) {
const fullPath = path.join(ROOT, hostDir);
if (fs.existsSync(fullPath)) {
const hostFiles: string[] = [];
walkSkillFiles(fullPath, hostFiles);
for (const file of hostFiles) files.add(file);
}
}
const errors: string[] = [];
for (const file of [...files].sort()) {
const fullPath = path.join(ROOT, file);
for (const error of validateSkillFrontmatter(fullPath)) {
errors.push(`${file}:${error.line}: ${error.message}`);
}
}
if (errors.length > 0) {
console.error('Invalid SKILL.md frontmatter:');
for (const error of errors) console.error(` ${error}`);
process.exit(1);
}
console.log(`Validated frontmatter for ${files.size} SKILL.md files.`);