bookworm-smart-assistant/scripts/validate-registry.js

92 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
/**
* SKILL-REGISTRY 自动校验脚本
* 对比 SKILL-REGISTRY.md 声明 vs 磁盘实际状态
* 用法: node scripts/validate-registry.js
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..');
const issues = [];
let passed = 0;
function check(name, condition, detail) {
if (condition) { passed++; }
else { issues.push({ severity: detail.startsWith('[CRITICAL]') ? 'CRITICAL' : 'WARNING', name, detail }); }
}
// 1. 钩子磁盘文件数
const hooksDir = path.join(ROOT, 'hooks');
const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.js'));
const registry = fs.readFileSync(path.join(ROOT, 'SKILL-REGISTRY.md'), 'utf8');
const hookCountMatch = registry.match(/磁盘文件\s*(\d+)\s*个/);
if (hookCountMatch) {
const declared = parseInt(hookCountMatch[1]);
check('hooks-disk-count', declared === hookFiles.length,
`[WARNING] REGISTRY 声明 ${declared} 个钩子文件,实际 ${hookFiles.length}`);
} else {
check('hooks-disk-count', false, '[WARNING] REGISTRY 中未找到钩子磁盘文件数声明');
}
// 2. 注册钩子文件存在性
const settings = JSON.parse(fs.readFileSync(path.join(ROOT, 'settings.json'), 'utf8'));
const allHookPaths = [];
for (const stage of Object.values(settings.hooks || {})) {
for (const entry of (Array.isArray(stage) ? stage : [])) {
for (const hook of (entry.hooks || [])) {
const m = (hook.command || '').match(/hooks\/([^\s"]+\.js)/);
if (m) allHookPaths.push(m[1]);
}
}
}
for (const hp of allHookPaths) {
const exists = fs.existsSync(path.join(hooksDir, hp));
check(`hook-exists:${hp}`, exists, `[CRITICAL] 注册钩子文件不存在: hooks/${hp}`);
}
// 3. 技能目录数
const skillsDir = path.join(ROOT, 'skills');
const skillDirs = fs.readdirSync(skillsDir).filter(f => {
return !f.startsWith('_') && !f.startsWith('.') && fs.statSync(path.join(skillsDir, f)).isDirectory();
});
const stats = JSON.parse(fs.readFileSync(path.join(ROOT, 'stats-compiled.json'), 'utf8'));
check('skills-count', skillDirs.length === stats.summary.skills,
`[WARNING] 技能目录 ${skillDirs.length} vs stats 声明 ${stats.summary.skills}`);
// 4. Agent 文件数
const agentsDir = path.join(ROOT, 'agents');
const agentFiles = fs.readdirSync(agentsDir).filter(f => f.endsWith('.md'));
check('agents-count', agentFiles.length === stats.summary.agents,
`[WARNING] Agent 文件 ${agentFiles.length} vs stats 声明 ${stats.summary.agents}`);
// 5. 版本一致性
const claudeMd = fs.readFileSync(path.join(ROOT, 'CLAUDE.md'), 'utf8');
const claudeVersion = (claudeMd.match(/Smart Assistant[^v]*(v[\d.]+)/) || [])[1] || 'unknown';
const registryVersion = (registry.match(/v[\d.]+/) || [])[0] || 'unknown';
const statsVersion = stats.version || 'unknown';
check('version-consistency',
claudeVersion === registryVersion && registryVersion === statsVersion,
`[WARNING] 版本不一致: CLAUDE.md=${claudeVersion} REGISTRY=${registryVersion} stats=${statsVersion}`);
// 6. 每个技能都有 SKILL.md
for (const sd of skillDirs) {
const skillMd = path.join(skillsDir, sd, 'SKILL.md');
check(`skill-md:${sd}`, fs.existsSync(skillMd),
`[WARNING] 技能 ${sd} 缺少 SKILL.md`);
}
// 输出
console.log(`\n=== SKILL-REGISTRY 校验报告 ===`);
console.log(`通过: ${passed} 问题: ${issues.length}\n`);
if (issues.length === 0) {
console.log('✓ 全部通过,无偏差');
process.exit(0);
} else {
for (const i of issues) {
console.log(`[${i.severity}] ${i.name}: ${i.detail}`);
}
process.exit(issues.some(i => i.severity === 'CRITICAL') ? 2 : 1);
}