mirror of
https://github.com/daffy0208/ai-dev-standards.git
synced 2026-09-14 20:49:49 +08:00
ec35acd490
Version 2.0.0 - Major Release
=============================
Phase 3 Design System Complete (75 Resources)
- 48 MCPs (exceeded 30 MCP goal)
- 216 total resources
- 92% skill-to-MCP coverage
- Complete aesthetic design system
Infrastructure Improvements
===========================
1. Version Management System
- Added VERSION-POLICY.md with comprehensive versioning policy
- Created scripts/bump-version.cjs for automated version bumping
- Added npm scripts: version:major, version:minor, version:patch, release
- Single source of truth: package.json
2. Validation System Fixes
- Fixed scripts/validate-all.cjs (removed archived BUILD_FOCUS.md)
- All validation checks now passing (45 skills, 48 MCPs)
- 100% registry consistency
3. Documentation Enhancements
- Added DOCS/CLAUDE-CODE-AGENTS.md
* Documents Claude Code's Task tool (native subagents)
* Explains relationship to custom agents (COMPONENTS/agents)
* Clarifies parallel execution patterns
- Fixed codex-review-workflow SKILL.md (added YAML frontmatter)
4. Registry Updates
- skill-registry.json: 45 skills (added codex-review-workflow)
- registry.json: 48 MCPs, 45 skills (synced)
- All relationship mappings complete
5. Documentation Consistency
- Updated all files to reflect v2.0.0 and correct counts
- README.md, CHANGELOG.md, INSTALL.md, .cursorrules
- DOCS/INDEX.md, DOCS/MCP-DEVELOPMENT-ROADMAP.md
- .claude/claude.md (auto-synced)
Files Changed: 16
New Files: 3 (CLAUDE-CODE-AGENTS.md, VERSION-POLICY.md, bump-version.cjs)
Validation Status
=================
✅ All 22 validation checks passing
✅ 100% registry consistency
✅ 100% documentation consistency
✅ 0 critical errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
418 lines
14 KiB
JavaScript
418 lines
14 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Complete Validation Script
|
|
* Validates ALL consistency across the entire framework
|
|
* FAILS LOUDLY with clear error messages
|
|
* Single source of truth: SKILLS/ and MCP-SERVERS/ folders
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const ROOT = path.join(__dirname, '..');
|
|
const SKILLS_DIR = path.join(ROOT, 'SKILLS');
|
|
const MCP_DIR = path.join(ROOT, 'MCP-SERVERS');
|
|
|
|
// Color codes for output
|
|
const RED = '\x1b[31m';
|
|
const GREEN = '\x1b[32m';
|
|
const YELLOW = '\x1b[33m';
|
|
const RESET = '\x1b[0m';
|
|
|
|
let errors = [];
|
|
let warnings = [];
|
|
let checks = 0;
|
|
|
|
function error(message) {
|
|
errors.push(message);
|
|
console.error(`${RED}❌ ${message}${RESET}`);
|
|
}
|
|
|
|
function warn(message) {
|
|
warnings.push(message);
|
|
console.warn(`${YELLOW}⚠️ ${message}${RESET}`);
|
|
}
|
|
|
|
function success(message) {
|
|
checks++;
|
|
console.log(`${GREEN}✅ ${message}${RESET}`);
|
|
}
|
|
|
|
function section(title) {
|
|
console.log(`\n${YELLOW}=== ${title} ===${RESET}\n`);
|
|
}
|
|
|
|
// Get actual skill and MCP counts from filesystem
|
|
function getActualCounts() {
|
|
const skillFolders = fs.readdirSync(SKILLS_DIR)
|
|
.filter(f => f !== '_TEMPLATE' && fs.statSync(path.join(SKILLS_DIR, f)).isDirectory());
|
|
|
|
const mcpFolders = fs.readdirSync(MCP_DIR)
|
|
.filter(f => fs.statSync(path.join(MCP_DIR, f)).isDirectory());
|
|
|
|
// MCP names: strip -mcp suffix for comparison with registry
|
|
const mcpNames = mcpFolders.map(f => f.replace(/-mcp$/, '')).sort();
|
|
|
|
return {
|
|
skills: skillFolders.length,
|
|
skillNames: skillFolders.sort(),
|
|
mcps: mcpFolders.length,
|
|
mcpNames: mcpNames,
|
|
mcpFolders: mcpFolders.sort()
|
|
};
|
|
}
|
|
|
|
// Validate skill-registry.json
|
|
function validateSkillRegistry(actual) {
|
|
section('Validating skill-registry.json');
|
|
|
|
const registryPath = path.join(ROOT, 'META', 'skill-registry.json');
|
|
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf-8'));
|
|
|
|
const registryCount = registry.skills.length;
|
|
const registryNames = registry.skills.map(s => s.name).sort();
|
|
|
|
if (registryCount !== actual.skills) {
|
|
error(`skill-registry.json has ${registryCount} skills, but SKILLS/ has ${actual.skills}`);
|
|
} else {
|
|
success(`skill-registry.json count matches (${actual.skills})`);
|
|
}
|
|
|
|
// Check for missing skills
|
|
const missing = actual.skillNames.filter(name => !registryNames.includes(name));
|
|
const extra = registryNames.filter(name => !actual.skillNames.includes(name));
|
|
|
|
if (missing.length > 0) {
|
|
error(`Skills missing from skill-registry.json: ${missing.join(', ')}`);
|
|
}
|
|
if (extra.length > 0) {
|
|
error(`Skills in skill-registry.json but not in SKILLS/: ${extra.join(', ')}`);
|
|
}
|
|
if (missing.length === 0 && extra.length === 0) {
|
|
success('All skills present in skill-registry.json');
|
|
}
|
|
|
|
// Check all skills have triggers
|
|
const noTriggers = registry.skills.filter(s => !s.triggers || s.triggers.length === 0);
|
|
if (noTriggers.length > 0) {
|
|
warn(`Skills without triggers: ${noTriggers.map(s => s.name).join(', ')}`);
|
|
} else {
|
|
success('All skills have triggers defined');
|
|
}
|
|
|
|
return registryNames;
|
|
}
|
|
|
|
// Validate registry.json
|
|
function validateMainRegistry(actual) {
|
|
section('Validating registry.json');
|
|
|
|
const registryPath = path.join(ROOT, 'META', 'registry.json');
|
|
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf-8'));
|
|
|
|
const skillCount = registry.skills.length;
|
|
const mcpCount = registry.mcpServers.length;
|
|
const skillNames = registry.skills.map(s => s.name).sort();
|
|
const mcpNames = registry.mcpServers.map(m => m.name).sort();
|
|
|
|
if (skillCount !== actual.skills) {
|
|
error(`registry.json has ${skillCount} skills, but SKILLS/ has ${actual.skills}`);
|
|
} else {
|
|
success(`registry.json skill count matches (${actual.skills})`);
|
|
}
|
|
|
|
if (mcpCount !== actual.mcps) {
|
|
error(`registry.json has ${mcpCount} MCPs, but MCP-SERVERS/ has ${actual.mcps}`);
|
|
} else {
|
|
success(`registry.json MCP count matches (${actual.mcps})`);
|
|
}
|
|
|
|
// Check for missing/extra
|
|
const missingSkills = actual.skillNames.filter(name => !skillNames.includes(name));
|
|
const extraSkills = skillNames.filter(name => !actual.skillNames.includes(name));
|
|
const missingMcps = actual.mcpNames.filter(name => !mcpNames.includes(name));
|
|
const extraMcps = mcpNames.filter(name => !actual.mcpNames.includes(name));
|
|
|
|
if (missingSkills.length > 0) error(`Skills missing from registry.json: ${missingSkills.join(', ')}`);
|
|
if (extraSkills.length > 0) error(`Extra skills in registry.json: ${extraSkills.join(', ')}`);
|
|
if (missingMcps.length > 0) error(`MCPs missing from registry.json: ${missingMcps.join(', ')}`);
|
|
if (extraMcps.length > 0) error(`Extra MCPs in registry.json: ${extraMcps.join(', ')}`);
|
|
|
|
if (missingSkills.length === 0 && extraSkills.length === 0) {
|
|
success('All skills present in registry.json');
|
|
}
|
|
if (missingMcps.length === 0 && extraMcps.length === 0) {
|
|
success('All MCPs present in registry.json');
|
|
}
|
|
|
|
return { skillNames, mcpNames };
|
|
}
|
|
|
|
// Validate relationship-mapping.json
|
|
function validateRelationships(actual) {
|
|
section('Validating relationship-mapping.json');
|
|
|
|
const relationshipsPath = path.join(ROOT, 'META', 'relationship-mapping.json');
|
|
const relationships = JSON.parse(fs.readFileSync(relationshipsPath, 'utf-8'));
|
|
|
|
const mappedSkills = Object.keys(relationships.relationships.skills_to_mcps || {});
|
|
const unmappedCount = actual.skills - mappedSkills.length;
|
|
|
|
if (unmappedCount > 0) {
|
|
const unmapped = actual.skillNames.filter(name => !mappedSkills.includes(name));
|
|
warn(`${unmappedCount} skills without relationship mapping: ${unmapped.slice(0, 5).join(', ')}${unmapped.length > 5 ? '...' : ''}`);
|
|
} else {
|
|
success('All skills have relationship mapping');
|
|
}
|
|
|
|
success(`Relationship mapping: ${mappedSkills.length}/${actual.skills} skills mapped (${Math.round(mappedSkills.length / actual.skills * 100)}%)`);
|
|
}
|
|
|
|
// Validate documentation files
|
|
function validateDocumentation(actual) {
|
|
section('Validating Documentation Files');
|
|
|
|
const files = [
|
|
{ path: 'README.md', regex: /(\d+)\s+Specialized Skills/ },
|
|
// BUILD_FOCUS.md moved to .archive/planning/ - no longer active
|
|
{ path: 'DOCS/INDEX.md', regex: /explore\s+(\d+)\s+specialized/ },
|
|
{ path: 'DOCS/MCP-DEVELOPMENT-ROADMAP.md', regex: /Current:\*\*\s*(\d+)\s+skills/ }
|
|
];
|
|
|
|
files.forEach(({ path: filePath, regex }) => {
|
|
const fullPath = path.join(ROOT, filePath);
|
|
if (!fs.existsSync(fullPath)) {
|
|
error(`File not found: ${filePath}`);
|
|
return;
|
|
}
|
|
|
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
const match = content.match(regex);
|
|
|
|
if (!match) {
|
|
warn(`Could not find skill count in ${filePath}`);
|
|
return;
|
|
}
|
|
|
|
const count = parseInt(match[1]);
|
|
if (count !== actual.skills) {
|
|
error(`${filePath} shows ${count} skills, but actual is ${actual.skills}`);
|
|
} else {
|
|
success(`${filePath} skill count correct (${actual.skills})`);
|
|
}
|
|
});
|
|
|
|
// Check MCP counts
|
|
const mcpFiles = [
|
|
{ path: 'README.md', regex: /(\d+)\s+MCP Tools/ },
|
|
// BUILD_FOCUS.md moved to .archive/planning/ - no longer active
|
|
{ path: 'DOCS/INDEX.md', regex: /MCPs:\s*(\d+)/ }
|
|
];
|
|
|
|
mcpFiles.forEach(({ path: filePath, regex }) => {
|
|
const fullPath = path.join(ROOT, filePath);
|
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
const match = content.match(regex);
|
|
|
|
if (!match) {
|
|
warn(`Could not find MCP count in ${filePath}`);
|
|
return;
|
|
}
|
|
|
|
const count = parseInt(match[1]);
|
|
if (count !== actual.mcps) {
|
|
error(`${filePath} shows ${count} MCPs, but actual is ${actual.mcps}`);
|
|
} else {
|
|
success(`${filePath} MCP count correct (${actual.mcps})`);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Validate .claude/claude.md
|
|
function validateClaudeConfig(skillNames) {
|
|
section('Validating .claude/claude.md');
|
|
|
|
const claudePath = path.join(ROOT, '.claude', 'claude.md');
|
|
if (!fs.existsSync(claudePath)) {
|
|
error('.claude/claude.md not found');
|
|
return;
|
|
}
|
|
|
|
const content = fs.readFileSync(claudePath, 'utf-8');
|
|
const skillSections = content.match(/^### (.+)$/gm) || [];
|
|
const claudeSkills = skillSections.map(s => s.replace('### ', '').trim());
|
|
|
|
if (claudeSkills.length < 5) {
|
|
warn(`.claude/claude.md only has ${claudeSkills.length} skills documented (should have all ${skillNames.length})`);
|
|
} else {
|
|
success(`.claude/claude.md has ${claudeSkills.length} skills documented`);
|
|
}
|
|
}
|
|
|
|
// Validate root configuration files
|
|
function validateRootFiles(actual) {
|
|
section('Validating Root Configuration Files');
|
|
|
|
// Validate .cursorrules
|
|
const cursorrules = path.join(ROOT, '.cursorrules');
|
|
if (fs.existsSync(cursorrules)) {
|
|
const content = fs.readFileSync(cursorrules, 'utf-8');
|
|
|
|
// Check skill count
|
|
const skillMatch = content.match(/SKILLS\/\*\*\s*-\s*(\d+)\s+specialized skills/);
|
|
if (skillMatch && parseInt(skillMatch[1]) !== actual.skills) {
|
|
error(`.cursorrules shows ${skillMatch[1]} skills, but actual is ${actual.skills}`);
|
|
} else if (skillMatch) {
|
|
success(`.cursorrules skill count correct (${actual.skills})`);
|
|
}
|
|
|
|
// Check MCP count
|
|
const mcpMatch = content.match(/MCP-SERVERS\/\*\*\s*-\s*(\d+)\s+MCP server/);
|
|
if (mcpMatch && parseInt(mcpMatch[1]) !== actual.mcps) {
|
|
error(`.cursorrules shows ${mcpMatch[1]} MCPs, but actual is ${actual.mcps}`);
|
|
} else if (mcpMatch) {
|
|
success(`.cursorrules MCP count correct (${actual.mcps})`);
|
|
}
|
|
|
|
// Check ratio
|
|
const ratioMatch = content.match(/Ratio:\*\*\s*([\d.]+):1/);
|
|
const expectedRatio = (actual.skills / actual.mcps).toFixed(1);
|
|
if (ratioMatch && ratioMatch[1] !== expectedRatio) {
|
|
error(`.cursorrules shows ${ratioMatch[1]}:1 ratio, but actual is ${expectedRatio}:1`);
|
|
} else if (ratioMatch) {
|
|
success(`.cursorrules ratio correct (${expectedRatio}:1)`);
|
|
}
|
|
} else {
|
|
warn('.cursorrules not found');
|
|
}
|
|
|
|
// Validate CHANGELOG.md
|
|
const changelog = path.join(ROOT, 'CHANGELOG.md');
|
|
if (fs.existsSync(changelog)) {
|
|
const content = fs.readFileSync(changelog, 'utf-8');
|
|
|
|
const skillMatch = content.match(/\*\*(\d+)\s+Specialized Skills\*\*/);
|
|
if (skillMatch && parseInt(skillMatch[1]) !== actual.skills) {
|
|
error(`CHANGELOG.md shows ${skillMatch[1]} skills, but actual is ${actual.skills}`);
|
|
} else if (skillMatch) {
|
|
success(`CHANGELOG.md skill count correct (${actual.skills})`);
|
|
}
|
|
|
|
const mcpMatch = content.match(/\*\*(\d+)\s+MCP Servers\*\*/);
|
|
if (mcpMatch && parseInt(mcpMatch[1]) !== actual.mcps) {
|
|
error(`CHANGELOG.md shows ${mcpMatch[1]} MCPs, but actual is ${actual.mcps}`);
|
|
} else if (mcpMatch) {
|
|
success(`CHANGELOG.md MCP count correct (${actual.mcps})`);
|
|
}
|
|
} else {
|
|
warn('CHANGELOG.md not found');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate CLI Commands
|
|
* Check that CLI commands have no TODOs and fetch from GitHub
|
|
*/
|
|
function validateCLICommands() {
|
|
section('Validating CLI Commands');
|
|
|
|
// Check 23: No TODOs in critical CLI paths
|
|
const cliPaths = ['CLI/commands/', 'CLI/generators/', 'CLI/utils/'];
|
|
let todoCount = 0;
|
|
|
|
for (const cliPath of cliPaths) {
|
|
try {
|
|
const todos = execSync(`grep -r "TODO\\|FIXME" ${cliPath} --include="*.js" 2>/dev/null || true`, { encoding: 'utf8' });
|
|
if (todos.trim().length > 0) {
|
|
const lines = todos.trim().split('\n').filter(l => l.length > 0);
|
|
todoCount += lines.length;
|
|
error(`Found ${lines.length} TODOs in ${cliPath}:`);
|
|
lines.forEach(line => console.log(` ${RED}${line}${RESET}`));
|
|
}
|
|
} catch (e) {
|
|
// Path might not exist, that's okay
|
|
}
|
|
}
|
|
|
|
if (todoCount === 0) {
|
|
success('No TODOs in critical CLI paths');
|
|
}
|
|
|
|
// Check 24: CLI utils exist and export GitHub functions
|
|
if (fs.existsSync('CLI/utils/github-fetch.js')) {
|
|
const utilContent = fs.readFileSync('CLI/utils/github-fetch.js', 'utf8');
|
|
if (utilContent.includes('GITHUB_RAW_BASE') &&
|
|
utilContent.includes('fetchSkills') &&
|
|
utilContent.includes('fetchMCPs')) {
|
|
success('CLI GitHub fetch utility correctly configured');
|
|
} else {
|
|
error('CLI GitHub fetch utility missing required functions');
|
|
}
|
|
} else {
|
|
error('CLI/utils/github-fetch.js not found');
|
|
}
|
|
|
|
// Check 25: Critical CLI commands use GitHub fetch utility
|
|
const criticalCommands = ['CLI/commands/sync.js', 'CLI/commands/update.js'];
|
|
for (const cmdPath of criticalCommands) {
|
|
if (fs.existsSync(cmdPath)) {
|
|
const content = fs.readFileSync(cmdPath, 'utf8');
|
|
if (content.includes('github-fetch')) {
|
|
success(`${cmdPath} uses GitHub fetch utility`);
|
|
} else {
|
|
error(`${cmdPath} does not use GitHub fetch utility`);
|
|
}
|
|
} else {
|
|
warn(`${cmdPath} not found`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Main validation
|
|
function main() {
|
|
console.log(`\n${GREEN}🔍 AI Dev Standards - Complete Validation${RESET}\n`);
|
|
console.log('Checking consistency across entire framework...\n');
|
|
|
|
const actual = getActualCounts();
|
|
console.log(`${YELLOW}📊 Actual Counts:${RESET}`);
|
|
console.log(` Skills: ${actual.skills}`);
|
|
console.log(` MCPs: ${actual.mcps}`);
|
|
console.log(` Ratio: ${(actual.skills / actual.mcps).toFixed(1)}:1\n`);
|
|
|
|
// Run all validations
|
|
const registrySkills = validateSkillRegistry(actual);
|
|
const mainRegistry = validateMainRegistry(actual);
|
|
validateRelationships(actual);
|
|
validateDocumentation(actual);
|
|
validateRootFiles(actual);
|
|
validateClaudeConfig(actual.skillNames);
|
|
validateCLICommands();
|
|
|
|
// Summary
|
|
console.log(`\n${YELLOW}=== Validation Summary ===${RESET}\n`);
|
|
console.log(`${GREEN}✅ Checks passed: ${checks}${RESET}`);
|
|
if (warnings.length > 0) {
|
|
console.log(`${YELLOW}⚠️ Warnings: ${warnings.length}${RESET}`);
|
|
}
|
|
if (errors.length > 0) {
|
|
console.log(`${RED}❌ Errors: ${errors.length}${RESET}`);
|
|
}
|
|
|
|
// Exit with appropriate code
|
|
if (errors.length > 0) {
|
|
console.log(`\n${RED}❌ VALIDATION FAILED${RESET}`);
|
|
console.log(`\nTo fix: npm run validate:fix\n`);
|
|
process.exit(1);
|
|
} else if (warnings.length > 0) {
|
|
console.log(`\n${YELLOW}⚠️ VALIDATION PASSED WITH WARNINGS${RESET}`);
|
|
console.log(`\nReview warnings above and fix when possible.\n`);
|
|
process.exit(0);
|
|
} else {
|
|
console.log(`\n${GREEN}✅ ALL VALIDATIONS PASSED${RESET}\n`);
|
|
process.exit(0);
|
|
}
|
|
}
|
|
|
|
main();
|