UNPKG

creatrip-agent-rules-builder

Version:

Unified converter for AI coding agent rules across Cursor, Windsurf, and Claude

267 lines (239 loc) 8.07 kB
import { Command } from "commander"; import chalk from "chalk"; import { verifyAgents } from "../verifiers"; import { buildRules } from "../build"; import { LocationVerificationResult } from "../verifiers/types"; interface CIResult { status: "pass" | "fail"; summary: { total: number; synced: number; autoBuild: number; needsReview: number; }; needsReview?: Array<{ path: string; issue: string; files?: string[]; }>; message: string; } export function createCICommand(): Command { const command = new Command("ci"); command .description("스마트 동기화: 자동으로 검증하고 필요시 빌드 실행") .option("-r, --recursive", "하위 디렉토리를 재귀적으로 처리") .option("--json", "JSON 형식으로 결과 출력") .action(async (options) => { const isRecursive = options.recursive || false; const isJson = options.json || false; try { const result = await runCI(isRecursive, isJson); if (isJson) { console.log(JSON.stringify(result, null, 2)); } process.exit(result.status === "pass" ? 0 : 1); } catch (error) { if (!isJson) { console.error( chalk.red( `오류: ${error instanceof Error ? error.message : "알 수 없는 오류"}`, ), ); } process.exit(1); } }); return command; } async function runCI(isRecursive: boolean, isJson: boolean): Promise<CIResult> { const rootPath = process.cwd(); if (!isJson) { console.log(chalk.blue("🔍 일관성 검증 중...")); } // 1단계: 검증 const verifyResult = await verifyAgents(rootPath, "json", isRecursive); const ciResult: CIResult = { status: "pass", summary: { total: 0, synced: 0, autoBuild: 0, needsReview: 0, }, message: "", }; const needsReviewList: Array<{ path: string; issue: string; files?: string[]; }> = []; // 각 위치별로 처리 for (const location of verifyResult.locations) { ciResult.summary.total++; if (location.status === "pass") { // 이미 동기화됨 ciResult.summary.synced++; if (!isJson) { const relativePath = location.path.replace(rootPath, "") || "/"; if (isRecursive) { console.log(chalk.green(`📁 ${relativePath}`)); console.log(chalk.green(" ✅ 동기화됨")); } } } else { // 패턴에 따라 처리 switch (location.diagnosis?.pattern) { case "all_outdated": // 자동 빌드 실행 if (!isJson) { const relativePath = location.path.replace(rootPath, "") || "/"; if (isRecursive) { console.log(chalk.cyan(`📁 ${relativePath}`)); console.log(chalk.yellow(" ⚠️ 오래됨 → 자동 빌드")); } else { console.log( chalk.yellow("⚠️ 모든 파일이 오래됨 (AGENTS.md가 더 최신)"), ); console.log(chalk.blue("🔨 자동으로 빌드를 실행합니다...")); } } // 빌드 실행 await buildRulesForLocation(location.path, isJson); ciResult.summary.autoBuild++; if (!isJson) { if (isRecursive) { console.log(chalk.green(" ✅ 빌드 완료")); } else { console.log( chalk.green("✅ 빌드 완료 - 모든 파일이 동기화되었습니다"), ); } } break; case "single_diverged": case "multiple_diverged": // 수동 검토 필요 ciResult.status = "fail"; ciResult.summary.needsReview++; const relativePath = location.path.replace(rootPath, "") || "/"; needsReviewList.push({ path: relativePath, issue: location.diagnosis.pattern, files: location.diagnosis.diverged, }); if (!isJson) { if (isRecursive) { console.log(chalk.red(`📁 ${relativePath}`)); if (location.diagnosis.pattern === "single_diverged") { console.log( chalk.red( ` ❌ ${location.diagnosis.diverged?.[0]} 파일 수동 편집 감지`, ), ); } else { console.log(chalk.red(` ❌ 여러 파일 수동 편집 감지`)); } } else { if (location.diagnosis.pattern === "single_diverged") { console.log( chalk.red( `❌ ${location.diagnosis.diverged?.[0]} 파일에 수동 편집 감지`, ), ); console.log(""); console.log(chalk.yellow("수동 검토가 필요합니다:")); console.log( chalk.yellow( `- ${location.diagnosis.diverged?.[0]} 파일이 직접 수정되었습니다`, ), ); console.log( chalk.yellow( "- AGENTS.md를 수정하려던 것이 아닌지 확인하세요", ), ); } else { console.log(chalk.red("❌ 여러 파일에서 수동 편집 감지")); console.log(""); console.log(chalk.yellow("수동 검토가 필요합니다:")); location.diagnosis.diverged?.forEach((file) => { console.log(chalk.yellow(`- ${file} 파일이 직접 수정됨`)); }); } } } break; } } if (!isJson && isRecursive) { console.log(""); // 위치 간 구분 } } // 결과 메시지 설정 if (ciResult.status === "pass") { if (ciResult.summary.autoBuild > 0) { ciResult.message = `Auto-build completed for ${ciResult.summary.autoBuild} location(s)`; } else { ciResult.message = "All files are already synchronized"; } if (!isJson) { if (isRecursive) { console.log("========================================"); console.log( chalk.green( `📊 요약: ${ciResult.summary.total}개 위치 모두 동기화됨`, ), ); if (ciResult.summary.autoBuild > 0) { console.log( chalk.green(` (${ciResult.summary.autoBuild}개 자동 빌드됨)`), ); } } else if (ciResult.summary.autoBuild === 0) { console.log(chalk.green("✅ 이미 모든 파일이 동기화되어 있습니다")); } } } else { ciResult.needsReview = needsReviewList; ciResult.message = `Manual review required for ${ciResult.summary.needsReview} location(s)`; if (!isJson && isRecursive) { console.log("========================================"); console.log( chalk.yellow( `📊 요약: ${ciResult.summary.total}개 위치 중 ${ciResult.summary.needsReview}개 수동 검토 필요`, ), ); console.log(chalk.red("❌ CI 실패: 수동 검토가 필요한 파일이 있습니다")); console.log(""); console.log(chalk.yellow("문제 위치:")); needsReviewList.forEach((item) => { console.log( chalk.yellow( `- ${item.path}: ${item.files?.join(", ")} 파일 수동 편집`, ), ); }); } } return ciResult; } async function buildRulesForLocation( locationPath: string, isJson: boolean, ): Promise<void> { // build.ts의 로직 재사용 const options = { file: `${locationPath}/AGENTS.md`, }; // 빌드 실행 (출력 억제) const originalLog = console.log; if (isJson) { console.log = () => {}; // JSON 모드에서는 출력 억제 } try { await buildRules(options); } finally { console.log = originalLog; } }