UNPKG

aiwf

Version:

AI Workflow Framework for Claude Code with multi-language support (Korean/English)

602 lines (503 loc) 20.9 kB
#!/usr/bin/env node /** * AI 페르소나 명령어 (한국어) * 다양한 개발 작업을 위한 AI 행동 페르소나 관리 */ import { AIPersonaManager } from '../../ai-persona-manager.js'; import { getBackgroundMonitor } from '../utils/background-monitor.js'; import path from 'path'; import chalk from 'chalk'; // 전역 페르소나 매니저 인스턴스 let personaManager = null; /** * 페르소나 매니저 인스턴스 가져오기 또는 생성 */ async function getPersonaManager() { if (!personaManager) { personaManager = new AIPersonaManager({ personaConfigPath: path.join(process.cwd(), '.aiwf', 'personas'), metricsPath: path.join(process.cwd(), '.aiwf', 'metrics'), metricsEnabled: true, autoDetectionEnabled: true }); await personaManager.init(); } return personaManager; } /** * 페르소나 상태 포맷팅 */ function formatPersonaStatus(persona, metrics) { const lines = []; lines.push(chalk.cyan('🎭 AI 페르소나 상태')); lines.push(chalk.gray('━'.repeat(50))); lines.push(`현재 페르소나: ${chalk.yellow(persona.name)}`); lines.push(`설명: ${getKoreanDescription(persona.name)}`); if (persona.behaviors?.length) { lines.push('\n주요 동작:'); getKoreanBehaviors(persona.name).forEach(behavior => { lines.push(` • ${behavior}`); }); } if (persona.recommendedTools?.length) { lines.push(`\n권장 도구: ${persona.recommendedTools.join(', ')}`); } if (metrics && metrics.duration !== undefined) { lines.push('\n현재 세션:'); lines.push(` 진행 시간: ${Math.round(metrics.duration / 1000)}초`); lines.push(` 상호작용: ${metrics.interactions}회`); lines.push(` 토큰 사용량: ${metrics.tokenUsage.current}`); } return lines.join('\n'); } /** * 페르소나 리포트 포맷팅 */ function formatPersonaReport(report) { const lines = []; lines.push(chalk.cyan('📊 AI 페르소나 성능 리포트')); lines.push(chalk.gray('━'.repeat(50))); lines.push(`생성 시간: ${new Date(report.generatedAt).toLocaleString('ko-KR')}`); lines.push(`기간: ${getKoreanTimeRange(report.timeRange)}`); // 요약 lines.push('\n' + chalk.yellow('요약:')); lines.push(`총 세션 수: ${report.summary.totalSessions}`); lines.push(`평균 완료 시간: ${Math.round(report.summary.averageCompletionTime / 1000)}초`); // 페르소나 사용량 if (report.summary.personaUsage) { lines.push('\n' + chalk.yellow('페르소나 사용량:')); Object.entries(report.summary.personaUsage).forEach(([persona, stats]) => { lines.push(` ${getKoreanPersonaName(persona)}: ${stats.count}회 (${stats.percentage.toFixed(1)}%)`); }); } // 품질 트렌드 if (report.summary.qualityTrend) { lines.push('\n' + chalk.yellow('품질 트렌드:')); lines.push(` 추세: ${getKoreanTrend(report.summary.qualityTrend.trend)}`); if (report.summary.qualityTrend.averageScore) { lines.push(` 평균 점수: ${(report.summary.qualityTrend.averageScore * 100).toFixed(1)}%`); } } // 페르소나 분석 if (report.personaAnalysis) { lines.push('\n' + chalk.yellow('페르소나 성능:')); Object.entries(report.personaAnalysis).forEach(([persona, analysis]) => { lines.push(`\n ${chalk.green(getKoreanPersonaName(persona))}:`); lines.push(` 세션 수: ${analysis.sessionCount}`); lines.push(` 완료율: ${(analysis.completionRate * 100).toFixed(1)}%`); lines.push(` 평균 품질: ${(analysis.averageQualityScore * 100).toFixed(1)}%`); lines.push(` 토큰 효율성: ${(analysis.tokenEfficiency * 100).toFixed(1)}%`); if (analysis.bestUseCases?.length) { lines.push(` 최적 사용 사례: ${analysis.bestUseCases.join(', ')}`); } }); } // 권장사항 if (report.recommendations?.length) { lines.push('\n' + chalk.yellow('권장사항:')); report.recommendations.forEach(rec => { const icon = rec.priority === 'high' ? '🔴' : '🟡'; lines.push(` ${icon} ${translateRecommendation(rec.message)}`); }); } return lines.join('\n'); } /** * 한국어 번역 헬퍼 함수들 */ function getKoreanPersonaName(persona) { const names = { architect: '아키텍트', security: '보안 전문가', frontend: '프론트엔드 전문가', backend: '백엔드 전문가', data_analyst: '데이터 분석가' }; return names[persona] || persona; } function getKoreanDescription(persona) { const descriptions = { architect: '시스템 설계 및 아키텍처', security: '보안 취약점 분석 및 방어', frontend: 'UI/UX 및 프론트엔드 개발', backend: 'API 설계 및 백엔드 개발', data_analyst: '데이터 분석 및 인사이트 도출' }; return descriptions[persona] || ''; } function getKoreanBehaviors(persona) { const behaviors = { architect: [ '큰 그림과 전체 시스템 구조에 집중', '확장성과 유지보수성 우선시', '디자인 패턴과 아키텍처 원칙 적용', '통합 지점과 인터페이스 고려' ], security: [ 'OWASP Top 10 기준 보안 취약점 분석', '위험 기반 접근으로 우선순위 설정', '공격 시나리오 구성 및 방어 전략 수립', '최소 권한 원칙과 다층 보안 적용' ], frontend: [ '사용자 경험과 인터페이스 최적화', '성능과 접근성을 고려한 구현', '반응형 디자인과 크로스 브라우저 호환성', '컴포넌트 재사용성과 유지보수성' ], backend: [ 'API 설계와 데이터베이스 최적화', '확장 가능한 서버 아키텍처 구축', '캐싱 전략과 성능 튜닝', '비동기 처리와 트랜잭션 관리' ], data_analyst: [ '데이터 패턴과 트렌드 분석', '통계적 검증과 가설 검정', '시각화와 대시보드 설계', '예측 모델 구축과 평가' ] }; return behaviors[persona] || []; } function getKoreanTimeRange(range) { const ranges = { hour: '1시간', day: '1일', week: '1주일', month: '1개월', all: '전체' }; return ranges[range] || range; } function getKoreanTrend(trend) { const trends = { improving: '개선 중', declining: '하락 중', stable: '안정적', insufficient_data: '데이터 부족' }; return trends[trend] || trend; } function translateRecommendation(message) { // 간단한 권장사항 번역 if (message.includes('Consider using')) { return message.replace('Consider using', '다음 페르소나 사용을 고려하세요:'); } if (message.includes('has low quality scores')) { return message.replace('tasks have low quality scores', '작업의 품질 점수가 낮습니다'); } if (message.includes('has high error rate')) { return message.replace('has high error rate', '오류율이 높습니다'); } return message; } /** * 명령어 핸들러 */ export const commands = { /** * 특정 페르소나로 전환 */ '/프로젝트:aiwf:ai_페르소나:전환': async (args) => { const personaName = args[0]; if (!personaName) { return chalk.red('오류: 페르소나 이름을 지정하세요 (architect, debugger, reviewer, documenter, optimizer, developer)'); } try { const manager = await getPersonaManager(); if (!manager.isValidPersona(personaName)) { return chalk.red(`오류: 잘못된 페르소나 '${personaName}'. 사용 가능한 페르소나: ${manager.getAvailablePersonas().join(', ')}`); } await manager.switchPersona(personaName, { manual: true }); // 백그라운드 모니터 초기화 (비차단) const monitor = getBackgroundMonitor(); monitor.initialize().catch(() => {}); // 에러 무시 return chalk.green(`✅ ${getKoreanPersonaName(personaName)} 페르소나로 전환했습니다`); } catch (error) { return chalk.red(`오류: ${error.message}`); } }, /** * 특정 페르소나 단축키 */ '/프로젝트:aiwf:ai_페르소나:아키텍트': async () => { return commands['/프로젝트:aiwf:ai_페르소나:전환'](['architect']); }, '/프로젝트:aiwf:ai_페르소나:디버거': async () => { return commands['/프로젝트:aiwf:ai_페르소나:전환'](['debugger']); }, '/프로젝트:aiwf:ai_페르소나:리뷰어': async () => { return commands['/프로젝트:aiwf:ai_페르소나:전환'](['reviewer']); }, '/프로젝트:aiwf:ai_페르소나:문서화전문가': async () => { return commands['/프로젝트:aiwf:ai_페르소나:전환'](['documenter']); }, '/프로젝트:aiwf:ai_페르소나:최적화전문가': async () => { return commands['/프로젝트:aiwf:ai_페르소나:전환'](['optimizer']); }, '/프로젝트:aiwf:ai_페르소나:개발자': async () => { return commands['/프로젝트:aiwf:ai_페르소나:전환'](['developer']); }, /** * 자동 감지 활성화/비활성화 */ '/프로젝트:aiwf:ai_페르소나:자동': async (args) => { const enabled = args[0] !== 'false' && args[0] !== '끄기'; try { const manager = await getPersonaManager(); manager.options.autoDetectionEnabled = enabled; return chalk.green(`✅ 자동 감지가 ${enabled ? '활성화' : '비활성화'}되었습니다`); } catch (error) { return chalk.red(`오류: ${error.message}`); } }, /** * 현재 페르소나 상태 표시 */ '/프로젝트:aiwf:ai_페르소나:상태': async () => { try { const manager = await getPersonaManager(); const current = manager.getCurrentPersona(); if (!current) { return chalk.yellow('현재 활성화된 페르소나가 없습니다'); } const metrics = await manager.metricsCollector.getCurrentSessionMetrics(); return formatPersonaStatus(current, metrics); } catch (error) { return chalk.red(`오류: ${error.message}`); } }, /** * 페르소나 성능 리포트 생성 */ '/프로젝트:aiwf:ai_페르소나:리포트': async (args) => { const timeRange = args[0] || 'all'; try { const manager = await getPersonaManager(); const report = await manager.generateReport({ timeRange }); return formatPersonaReport(report); } catch (error) { return chalk.red(`오류: ${error.message}`); } }, /** * 사용 가능한 페르소나 목록 */ '/프로젝트:aiwf:ai_페르소나:목록': async () => { try { const manager = await getPersonaManager(); const personas = manager.getAvailablePersonas(); const current = manager.getCurrentPersona(); const lines = []; lines.push(chalk.cyan('🎭 사용 가능한 AI 페르소나')); lines.push(chalk.gray('━'.repeat(50))); for (const personaName of personas) { const isCurrent = current?.name === personaName; lines.push(`\n${isCurrent ? '▶ ' : ' '}${chalk.yellow(getKoreanPersonaName(personaName))}${isCurrent ? ' (현재)' : ''}`); lines.push(` ${getKoreanDescription(personaName)}`); const persona = manager.availablePersonas[personaName]; if (persona.focusAreas?.length) { lines.push(` 주요 영역: ${persona.focusAreas.slice(0, 3).join(', ')}`); } } lines.push('\n' + chalk.gray('/프로젝트:aiwf:ai_페르소나:전환 <이름> 명령으로 페르소나를 변경하세요')); return lines.join('\n'); } catch (error) { return chalk.red(`오류: ${error.message}`); } }, /** * 페르소나 통계 조회 */ '/프로젝트:aiwf:ai_페르소나:통계': async (args) => { const personaName = args[0]; try { const manager = await getPersonaManager(); if (personaName && !manager.isValidPersona(personaName)) { return chalk.red(`오류: 잘못된 페르소나 '${personaName}'`); } const targetPersona = personaName || manager.getCurrentPersona()?.name; if (!targetPersona) { return chalk.yellow('지정된 페르소나가 없거나 활성화된 페르소나가 없습니다'); } const stats = await manager.getPersonaStats(targetPersona); if (stats.message) { return chalk.yellow(stats.message); } const lines = []; lines.push(chalk.cyan(`📊 ${getKoreanPersonaName(targetPersona)} 페르소나 통계`)); lines.push(chalk.gray('━'.repeat(50))); lines.push(`총 세션 수: ${stats.totalSessions}`); lines.push(`완료율: ${(stats.completionRate * 100).toFixed(1)}%`); lines.push(`평균 진행 시간: ${Math.round(stats.averageDuration / 1000)}초`); lines.push(`평균 토큰 사용: ${Math.round(stats.averageTokens)}`); lines.push(`오류율: 세션당 ${stats.errorRate.toFixed(2)}회`); lines.push(`평균 품질: ${(stats.averageQualityScore * 100).toFixed(1)}%`); if (stats.taskDistribution && Object.keys(stats.taskDistribution).length > 0) { lines.push('\n작업 분포:'); Object.entries(stats.taskDistribution).forEach(([task, count]) => { lines.push(` ${task}: ${count}`); }); } return lines.join('\n'); } catch (error) { return chalk.red(`오류: ${error.message}`); } }, /** * 페르소나 시스템 리셋 */ '/프로젝트:aiwf:ai_페르소나:리셋': async () => { try { const manager = await getPersonaManager(); await manager.reset(); return chalk.green('✅ 페르소나 시스템이 기본값(개발자)으로 리셋되었습니다'); } catch (error) { return chalk.red(`오류: ${error.message}`); } }, /** * 현재 작업에 최적인 페르소나 감지 */ '/프로젝트:aiwf:ai_페르소나:감지': async (args) => { const command = args.join(' '); if (!command) { return chalk.red('오류: 작업 설명을 입력하세요'); } try { const manager = await getPersonaManager(); const taskContext = { command, description: command }; const optimal = await manager.detectOptimalPersona(taskContext); const lines = []; lines.push(chalk.cyan('🔍 페르소나 감지 결과')); lines.push(chalk.gray('━'.repeat(50))); lines.push(`작업: "${command}"`); lines.push(`권장 페르소나: ${chalk.yellow(getKoreanPersonaName(optimal))}`); const persona = manager.availablePersonas[optimal]; if (persona) { lines.push(`\n이유: ${getKoreanDescription(optimal)}`); lines.push(`주요 영역: ${persona.focusAreas.join(', ')}`); } lines.push('\n' + chalk.gray('/프로젝트:aiwf:ai_페르소나:전환 ' + optimal + ' 명령으로 활성화하세요')); return lines.join('\n'); } catch (error) { return chalk.red(`오류: ${error.message}`); } } }; // 테스트를 위한 export export { getPersonaManager }; // 명령행 인자 처리 함수 async function handlePersonaCommand(personaName) { try { const fs = await import('fs'); // 기본 모드인 경우 if (personaName === 'default') { // .aiwf 디렉토리에서 실행되는 경우 상위 디렉토리로 이동 const baseDir = process.cwd().endsWith('.aiwf') ? path.dirname(process.cwd()) : process.cwd(); const statusPath = path.join(baseDir, '.aiwf', 'current_persona.json'); const status = { active: false, persona: null, activatedAt: null, contextRules: null, description: null }; fs.writeFileSync(statusPath, JSON.stringify(status, null, 2)); console.log(chalk.cyan('🎭 기본 모드로 복원 완료')); console.log(chalk.gray('모든 페르소나 컨텍스트가 해제되었습니다')); return; } // 유효한 페르소나인지 확인 const validPersonas = ['architect', 'security', 'frontend', 'backend', 'data_analyst']; if (!validPersonas.includes(personaName)) { console.log(chalk.red(`❌ 잘못된 페르소나: ${personaName}`)); console.log(chalk.yellow('사용 가능한 페르소나: ' + validPersonas.join(', '))); return; } // .aiwf 디렉토리에서 실행되는 경우 상위 디렉토리로 이동 const baseDir = process.cwd().endsWith('.aiwf') ? path.dirname(process.cwd()) : process.cwd(); // 컨텍스트 규칙 로드 const contextRulesPath = path.join(baseDir, '.aiwf', '07_AI_PERSONAS', personaName, 'context_rules.md'); let contextRules = null; if (fs.existsSync(contextRulesPath)) { contextRules = fs.readFileSync(contextRulesPath, 'utf8'); } // 현재 페르소나 상태 저장 const statusPath = path.join(baseDir, '.aiwf', 'current_persona.json'); const status = { active: true, persona: personaName, activatedAt: new Date().toISOString(), contextRules: contextRules, description: getKoreanDescription(personaName) }; fs.writeFileSync(statusPath, JSON.stringify(status, null, 2)); console.log(chalk.cyan('🎭 AI 페르소나 활성화 완료')); console.log(chalk.yellow(`현재 페르소나: ${getKoreanPersonaName(personaName)}`)); console.log(chalk.gray(`전문 분야: ${getKoreanDescription(personaName)}`)); if (contextRules) { console.log(chalk.green('✅ 페르소나 컨텍스트 규칙이 적용되었습니다')); console.log(chalk.blue('📋 주요 동작 특성:')); getKoreanBehaviors(personaName).forEach(behavior => { console.log(chalk.blue(` • ${behavior}`)); }); } console.log(chalk.yellow('\n💡 이제 Claude Code가 ' + getKoreanPersonaName(personaName) + ' 모드로 동작합니다!')); console.log(chalk.gray('페르소나 상태 확인: node ai-persona.js status')); } catch (error) { console.log(chalk.red(`❌ 오류: ${error.message}`)); } } // 직접 실행 시 명령줄 인자 처리 if (import.meta.url === `file://${process.argv[1]}`) { const command = process.argv[2]; const args = process.argv.slice(3); // 새로운 5개 페르소나 명령어 처리 const validPersonas = ['architect', 'security', 'frontend', 'backend', 'data_analyst']; if (validPersonas.includes(command)) { handlePersonaCommand(command); } else if (command === 'status') { try { const fs = await import('fs'); const baseDir = process.cwd().endsWith('.aiwf') ? path.dirname(process.cwd()) : process.cwd(); const statusPath = path.join(baseDir, '.aiwf', 'current_persona.json'); if (fs.existsSync(statusPath)) { const status = JSON.parse(fs.readFileSync(statusPath, 'utf8')); if (status.active) { console.log(chalk.cyan('🎭 현재 페르소나 상태')); console.log(chalk.gray('━'.repeat(50))); console.log(chalk.yellow(`활성 페르소나: ${getKoreanPersonaName(status.persona)}`)); console.log(chalk.gray(`전문 분야: ${status.description}`)); console.log(chalk.blue(`활성화 시간: ${new Date(status.activatedAt).toLocaleString('ko-KR')}`)); console.log('\n📋 주요 동작 특성:'); getKoreanBehaviors(status.persona).forEach(behavior => { console.log(chalk.blue(` • ${behavior}`)); }); } else { console.log(chalk.gray('🎭 현재 기본 모드입니다')); console.log(chalk.yellow('사용 가능한 페르소나: architect, security, frontend, backend, data_analyst')); } } else { console.log(chalk.gray('🎭 페르소나가 활성화되지 않았습니다')); console.log(chalk.yellow('사용 가능한 페르소나: architect, security, frontend, backend, data_analyst')); } } catch (error) { console.log(chalk.red(`❌ 상태 확인 오류: ${error.message}`)); } } else if (command === 'default') { handlePersonaCommand('default'); } else if (commands[command]) { commands[command](args) .then(console.log) .catch(console.error); } else { console.error('알 수 없는 명령어:', command); console.log('사용 가능한 페르소나: ' + validPersonas.join(', ')); console.log('기타 명령어: status, default'); } }