UNPKG

creatrip-agent-rules-builder

Version:

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

219 lines (180 loc) 6.88 kB
import chalk from "chalk"; import * as path from "path"; import * as fs from "fs"; import { parseAgentRulesFile, getDefaultRulesFilePath } from "./parser"; import { generateCursorRules } from "./generators/cursor"; import { generateWindsurfRules } from "./generators/windsurf"; import { generateClaudeRules } from "./generators/claude"; import { BuildOptions } from "./types"; function findAgentRulesFiles(dir: string): string[] { const gitignorePath = path.join(dir, ".gitignore"); let gitignorePatterns: string[] = []; if (fs.existsSync(gitignorePath)) { const gitignoreContent = fs.readFileSync(gitignorePath, "utf8"); gitignorePatterns = gitignoreContent .split("\n") .map((line) => line.trim()) .filter((line) => line && !line.startsWith("#")); } const defaultIgnorePatterns = [ "node_modules", ".git", "dist", "build", "out", ".next", ".nuxt", "coverage", ".nyc_output", ]; const allIgnorePatterns = [ ...new Set([...defaultIgnorePatterns, ...gitignorePatterns]), ]; function shouldIgnoreDir(dirPath: string): boolean { const dirName = path.basename(dirPath); const relativePath = path.relative(dir, dirPath); return allIgnorePatterns.some((pattern) => { // /sub-app/ 같은 절대 경로 패턴 (슬래시로 끝남) if (pattern.startsWith("/") && pattern.endsWith("/")) { const cleanPattern = pattern.slice(1, -1); return ( relativePath === cleanPattern || relativePath.startsWith(cleanPattern + "/") ); } // /sub-app 같은 절대 경로 패턴 (슬래시로 끝나지 않음) if (pattern.startsWith("/")) { const cleanPattern = pattern.slice(1); return ( relativePath === cleanPattern || relativePath.startsWith(cleanPattern + "/") ); } // sub-app/ 같은 상대 경로 패턴 if (pattern.endsWith("/")) { const cleanPattern = pattern.slice(0, -1); return ( dirName === cleanPattern || relativePath === cleanPattern || relativePath.startsWith(cleanPattern + "/") ); } // 단순 이름 패턴 return dirName === pattern || relativePath === pattern; }); } function searchRecursively(currentDir: string): string[] { const results: string[] = []; try { const entries = fs.readdirSync(currentDir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(currentDir, entry.name); if (entry.isDirectory()) { if (!shouldIgnoreDir(fullPath)) { results.push(...searchRecursively(fullPath)); } } else if (entry.name === "AGENTS.md") { results.push(currentDir); } } } catch (error) { // 권한 문제 등으로 읽을 수 없는 디렉토리는 무시 } return results; } return searchRecursively(dir); } function processRulesFile(filePath: string, showDetails: boolean = true): void { const baseDir = path.dirname(filePath); if (showDetails) { console.log(chalk.gray(`처리 중: ${filePath}`)); } const parsedContent = parseAgentRulesFile(filePath); generateCursorRules(parsedContent, baseDir); generateWindsurfRules(parsedContent, baseDir); generateClaudeRules(parsedContent, baseDir); } export async function buildRules(options: BuildOptions): Promise<void> { console.log(chalk.blue("🚀 Agent Rules Builder")); // -f와 -r 옵션 동시 사용 체크 if (options.file && options.recursive) { console.error( chalk.red.bold( "❌ 오류: -f(--file)와 -r(--recursive) 옵션은 동시에 사용할 수 없습니다", ), ); console.log(chalk.yellow("\n💡 사용법:")); console.log( chalk.gray("특정 파일 처리: agent-rules build -f YOUR_FILE.md"), ); console.log(chalk.gray("재귀 처리: agent-rules build -r")); process.exit(1); } try { if (options.recursive) { const startDir = process.cwd(); console.log(chalk.gray(`재귀 탐색 시작: ${startDir}`)); const agentRulesFiles = findAgentRulesFiles(startDir); if (agentRulesFiles.length === 0) { console.log(chalk.yellow("⚠️ AGENTS.md 파일을 찾을 수 없습니다")); return; } console.log( chalk.yellow( `📝 ${agentRulesFiles.length}개 위치에서 AGENTS.md 발견\n`, ), ); for (const filePath of agentRulesFiles) { const relativePath = path.relative(startDir, filePath) || "."; console.log(chalk.cyan(`📁 /${relativePath}`)); processRulesFile(path.join(filePath, "AGENTS.md"), false); console.log(chalk.green(` ✅ Cursor 규칙 생성 완료`)); console.log(chalk.green(` ✅ Windsurf 규칙 생성 완료`)); console.log(chalk.green(` ✅ Claude 규칙 생성 완료`)); console.log(""); // 위치 간 구분 } console.log("========================================"); console.log( chalk.green.bold( `🎉 ${agentRulesFiles.length}개 위치에서 모든 규칙 파일이 생성되었습니다!`, ), ); } else { const filePath = options.file || getDefaultRulesFilePath(); const baseDir = path.dirname(filePath); console.log(chalk.gray(`입력 파일: ${filePath}`)); console.log(chalk.gray(`출력 디렉토리: ${baseDir}`)); console.log(chalk.yellow("📝 파일 파싱 중...")); const parsedContent = parseAgentRulesFile(filePath); console.log(chalk.yellow("🔄 변환 중...")); generateCursorRules(parsedContent, baseDir); console.log(chalk.green(`✅ Cursor 규칙 생성 완료`)); generateWindsurfRules(parsedContent, baseDir); console.log(chalk.green(`✅ Windsurf 규칙 생성 완료`)); generateClaudeRules(parsedContent, baseDir); console.log(chalk.green(`✅ Claude 규칙 생성 완료`)); console.log( chalk.green.bold("\n🎉 모든 규칙 파일이 성공적으로 생성되었습니다!"), ); } } catch (error) { console.error(chalk.red.bold("❌ 오류 발생:")); console.error( chalk.red(error instanceof Error ? error.message : String(error)), ); if ( error instanceof Error && error.message.includes("파일을 찾을 수 없습니다") ) { console.log(chalk.yellow("\n💡 사용법:")); console.log(chalk.gray("1. AGENTS.md 파일을 생성하세요")); console.log(chalk.gray("2. agent-rules build 명령어를 실행하세요")); console.log( chalk.gray( "3. 또는 agent-rules build --file YOUR_FILE.md로 다른 파일을 지정하세요", ), ); } process.exit(1); } }