aiwf
Version:
AI Workflow Framework for Claude Code with multi-language support (Korean/English)
254 lines (216 loc) • 8.61 kB
JavaScript
/**
* AIWF 명령어 파일 일관성 검증 도구
*
* 이 스크립트는 다음을 검증합니다:
* 1. 한국어/영어 명령어 파일 간의 일관성
* 2. 명령어 파일명과 실제 명령어명의 매칭
* 3. 누락된 명령어 파일 식별
* 4. GitHub URL 참조 일관성
*/
import fs from 'fs/promises';
import path from 'path';
// 색상 출력을 위한 ANSI 코드
const colors = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
gray: '\x1b[90m'
};
// chalk 대체 함수
const chalk = {
red: (text) => `${colors.red}${text}${colors.reset}`,
green: (text) => `${colors.green}${text}${colors.reset}`,
yellow: (text) => `${colors.yellow}${text}${colors.reset}`,
blue: (text) => `${colors.blue}${text}${colors.reset}`,
cyan: (text) => `${colors.cyan}${text}${colors.reset}`,
gray: (text) => `${colors.gray}${text}${colors.reset}`,
bold: (text) => `\x1b[1m${text}${colors.reset}`
};
const KO_COMMANDS_DIR = 'claude-code/aiwf/ko/.claude/commands/aiwf';
const EN_COMMANDS_DIR = 'claude-code/aiwf/en/.claude/commands/aiwf';
// 명령어 파일 메타데이터 추출
async function extractCommandMetadata(filePath) {
try {
const content = await fs.readFile(filePath, 'utf-8');
const lines = content.split('\n');
// YAML frontmatter 찾기
if (lines[0] === '---') {
const endIndex = lines.findIndex((line, index) => index > 0 && line === '---');
if (endIndex !== -1) {
const frontmatter = lines.slice(1, endIndex).join('\n');
// 간단한 YAML 파싱 (name 추출)
const nameMatch = frontmatter.match(/name:\s*["']?([^"'\n]+)["']?/);
const descMatch = frontmatter.match(/description:\s*["']?([^"'\n]+)["']?/);
return {
name: nameMatch ? nameMatch[1] : null,
description: descMatch ? descMatch[1] : null,
hasFrontmatter: true
};
}
}
// 첫 번째 헤더에서 명령어명 추출
const headerMatch = content.match(/^#\s+(.+)/m);
return {
name: headerMatch ? headerMatch[1] : null,
description: null,
hasFrontmatter: false
};
} catch (error) {
return { error: error.message };
}
}
// 디렉토리의 모든 명령어 파일 스캔
async function scanCommandsDirectory(dir) {
const commands = {};
try {
const files = await fs.readdir(dir);
const mdFiles = files.filter(file => file.endsWith('.md'));
for (const file of mdFiles) {
const filePath = path.join(dir, file);
const metadata = await extractCommandMetadata(filePath);
commands[file] = {
...metadata,
path: filePath
};
}
} catch (error) {
console.error(chalk.red(`Error scanning directory ${dir}: ${error.message}`));
}
return commands;
}
// GitHub URL 참조 검증
async function validateGitHubUrls(filePath) {
try {
const content = await fs.readFile(filePath, 'utf-8');
const moonklabsRefs = content.match(/moonklabs\/aiwf/g) || [];
const aiwfRefs = content.match(/aiwf\/aiwf/g) || [];
return {
moonklabsCount: moonklabsRefs.length,
aiwfCount: aiwfRefs.length,
hasInconsistentUrls: moonklabsRefs.length > 0
};
} catch (error) {
return { error: error.message };
}
}
// 명령어 파일 검증
async function validateCommands() {
console.log(chalk.blue(chalk.bold('\n🔍 AIWF Command Files Validation\n')));
console.log('='.repeat(50));
// 1. 디렉토리 스캔
console.log(chalk.cyan('\n1. 디렉토리 스캔 중...'));
const koCommands = await scanCommandsDirectory(KO_COMMANDS_DIR);
const enCommands = await scanCommandsDirectory(EN_COMMANDS_DIR);
console.log(chalk.gray(` 한국어 명령어: ${Object.keys(koCommands).length}개`));
console.log(chalk.gray(` 영어 명령어: ${Object.keys(enCommands).length}개`));
// 2. 명령어 일관성 검증
console.log(chalk.cyan('\n2. 명령어 일관성 검증 중...'));
const allFiles = new Set([...Object.keys(koCommands), ...Object.keys(enCommands)]);
const issues = [];
for (const fileName of allFiles) {
const koExists = koCommands[fileName];
const enExists = enCommands[fileName];
if (!koExists) {
issues.push({
type: 'missing_ko',
file: fileName,
message: `한국어 버전 누락: ${fileName}`
});
}
if (!enExists) {
issues.push({
type: 'missing_en',
file: fileName,
message: `영어 버전 누락: ${fileName}`
});
}
// 명령어명 일관성 검증 (언어가 다르므로 생략)
if (koExists && enExists && koExists.name && enExists.name) {
// 언어가 다르므로 명령어명이 다른 것은 정상
// 이 검증은 제거
}
}
// 3. update_docs 명령어 검증
console.log(chalk.cyan('\n3. update_docs 명령어 검증 중...'));
const updateDocsFiles = [
'aiwf_update_docs.md',
'aiwf_docs.md'
];
const updateDocsStatus = {};
for (const file of updateDocsFiles) {
updateDocsStatus[file] = {
ko: !!koCommands[file],
en: !!enCommands[file]
};
}
// 4. GitHub URL 검증
console.log(chalk.cyan('\n4. GitHub URL 참조 검증 중...'));
const urlIssues = [];
for (const [fileName, command] of Object.entries({ ...koCommands, ...enCommands })) {
if (command.path) {
const urlCheck = await validateGitHubUrls(command.path);
if (urlCheck.hasInconsistentUrls) {
urlIssues.push({
file: fileName,
path: command.path,
moonklabsCount: urlCheck.moonklabsCount
});
}
}
}
// 결과 출력
console.log(chalk.blue(chalk.bold('\n📋 검증 결과\n')));
console.log('='.repeat(50));
// 일관성 이슈
if (issues.length > 0) {
console.log(chalk.red(`\n❌ 발견된 이슈 (${issues.length}개):`));
for (const issue of issues) {
console.log(chalk.red(` • ${issue.message}`));
}
} else {
console.log(chalk.green('\n✅ 명령어 일관성 검증 통과'));
}
// update_docs 상태
console.log(chalk.blue('\n📝 update_docs 명령어 상태:'));
for (const [file, status] of Object.entries(updateDocsStatus)) {
const koIcon = status.ko ? '✅' : '❌';
const enIcon = status.en ? '✅' : '❌';
console.log(chalk.gray(` ${file}: KO ${koIcon} EN ${enIcon}`));
}
// GitHub URL 이슈
if (urlIssues.length > 0) {
console.log(chalk.yellow(`\n⚠️ GitHub URL 이슈 (${urlIssues.length}개):`));
for (const issue of urlIssues) {
console.log(chalk.yellow(` • ${issue.file}: moonklabs/aiwf 참조 ${issue.moonklabsCount}개`));
}
} else {
console.log(chalk.green('\n✅ GitHub URL 참조 검증 통과'));
}
// 요약
console.log(chalk.blue(chalk.bold('\n📊 요약:')));
console.log(chalk.gray(` 총 명령어 파일: ${allFiles.size}개`));
console.log(chalk.gray(` 한국어/영어 쌍: ${Math.min(Object.keys(koCommands).length, Object.keys(enCommands).length)}개`));
console.log(chalk.gray(` 일관성 이슈: ${issues.length}개`));
console.log(chalk.gray(` URL 이슈: ${urlIssues.length}개`));
const overallSuccess = issues.length === 0 && urlIssues.length === 0;
console.log(overallSuccess ?
chalk.green('\n🎉 모든 검증 통과!') :
chalk.red('\n⚠️ 일부 이슈가 발견되었습니다.')
);
return {
success: overallSuccess,
issues,
urlIssues,
updateDocsStatus,
totalFiles: allFiles.size
};
}
// 메인 실행
if (import.meta.url === `file://${process.argv[1]}`) {
validateCommands().catch(console.error);
}
export { validateCommands };