UNPKG

aiwf

Version:

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

562 lines (484 loc) 18.3 kB
/** * 페르소나 컨텍스트 적용 명령어 * AI 페르소나 컨텍스트를 실제로 적용하고 관리하는 CLI 명령어 */ const fs = require('fs').promises; const path = require('path'); const ContextRuleParser = require('../utils/context-rule-parser'); const PromptInjector = require('../utils/prompt-injector'); const PersonaBehaviorValidator = require('../utils/persona-behavior-validator'); const ContextUpdateManager = require('../utils/context-update-manager'); const ContextTokenMonitor = require('../utils/context-token-monitor'); const PersonaQualityEvaluator = require('../utils/persona-quality-evaluator'); class PersonaContextApplyCommand { constructor() { this.contextParser = new ContextRuleParser(); this.promptInjector = new PromptInjector(); this.behaviorValidator = new PersonaBehaviorValidator(); this.updateManager = new ContextUpdateManager(); this.tokenMonitor = new ContextTokenMonitor(); this.qualityEvaluator = new PersonaQualityEvaluator(); } /** * 명령어 정의 * @returns {Object} 명령어 설정 */ static command() { return { command: 'persona-context <action>', description: 'AI 페르소나 컨텍스트 적용 및 관리', builder: (yargs) => { return yargs .positional('action', { describe: '수행할 작업', type: 'string', choices: ['apply', 'switch', 'validate', 'monitor', 'evaluate', 'watch', 'report'] }) .option('persona', { alias: 'p', describe: '대상 페르소나', type: 'string', choices: ['architect', 'frontend', 'backend', 'data_analyst', 'security'] }) .option('prompt', { describe: '적용할 프롬프트', type: 'string' }) .option('response', { describe: '검증할 응답', type: 'string' }) .option('output', { alias: 'o', describe: '결과 출력 형식', type: 'string', choices: ['json', 'table', 'detailed'], default: 'table' }) .option('save', { alias: 's', describe: '결과를 파일로 저장', type: 'boolean', default: false }); }, handler: async (argv) => { const command = new PersonaContextApplyCommand(); await command.execute(argv); } }; } /** * 명령어 실행 * @param {Object} argv - 명령어 인자 */ async execute(argv) { const { action, persona, prompt, response, output, save } = argv; try { switch (action) { case 'apply': await this.applyContext(persona, prompt, output, save); break; case 'switch': await this.switchPersona(persona); break; case 'validate': await this.validateResponse(persona, response, prompt, output); break; case 'monitor': await this.monitorTokenUsage(persona, output); break; case 'evaluate': await this.evaluateQuality(persona, prompt, response, output); break; case 'watch': await this.watchContextChanges(); break; case 'report': await this.generateReport(persona, output, save); break; default: console.error(`알 수 없는 작업: ${action}`); process.exit(1); } } catch (error) { console.error('오류 발생:', error.message); process.exit(1); } } /** * 컨텍스트 적용 * @param {string} persona - 페르소나 이름 * @param {string} prompt - 프롬프트 * @param {string} outputFormat - 출력 형식 * @param {boolean} save - 저장 여부 */ async applyContext(persona, prompt, outputFormat, save) { if (!persona || !prompt) { throw new Error('페르소나와 프롬프트를 모두 지정해야 합니다.'); } console.log(`\n📋 페르소나 컨텍스트 적용: ${persona}`); console.log('━'.repeat(50)); // 컨텍스트 적용 const result = await this.promptInjector.injectContext(prompt, persona); if (!result.success) { throw new Error(`컨텍스트 적용 실패: ${result.error}`); } // 토큰 사용량 추적 const tokenUsage = await this.tokenMonitor.trackContextUsage(result); // 결과 출력 this.displayApplyResult(result, tokenUsage, outputFormat); // 저장 if (save) { await this.saveResult({ type: 'context_application', persona, result, tokenUsage }); } } /** * 페르소나 전환 * @param {string} persona - 새 페르소나 */ async switchPersona(persona) { if (!persona) { throw new Error('전환할 페르소나를 지정해야 합니다.'); } console.log(`\n🔄 페르소나 전환: ${persona}`); console.log('━'.repeat(50)); const switchResult = await this.promptInjector.switchPersona(persona); console.log(`이전 페르소나: ${switchResult.previousPersona || '없음'}`); console.log(`현재 페르소나: ${switchResult.currentPersona}`); console.log(`전환 시각: ${switchResult.switchedAt}`); // 새 컨텍스트 정보 표시 if (switchResult.context) { console.log('\n새 페르소나 컨텍스트:'); console.log(`- 분석 접근법: ${switchResult.context.analysis_approach}`); console.log(`- 소통 스타일: ${switchResult.context.communication_style}`); if (switchResult.context.design_principles) { console.log(`- 설계 원칙: ${switchResult.context.design_principles.join(', ')}`); } } } /** * 응답 검증 * @param {string} persona - 페르소나 * @param {string} response - 응답 * @param {string} prompt - 원본 프롬프트 * @param {string} outputFormat - 출력 형식 */ async validateResponse(persona, response, prompt, outputFormat) { if (!persona || !response) { throw new Error('페르소나와 응답을 모두 지정해야 합니다.'); } console.log(`\n✅ 페르소나 행동 패턴 검증: ${persona}`); console.log('━'.repeat(50)); const validation = this.behaviorValidator.validateResponse( response, persona, prompt || '검증용 프롬프트' ); this.displayValidationResult(validation, outputFormat); } /** * 토큰 사용량 모니터링 * @param {string} persona - 페르소나 (선택) * @param {string} outputFormat - 출력 형식 */ async monitorTokenUsage(persona, outputFormat) { console.log(`\n📊 토큰 사용량 모니터링${persona ? `: ${persona}` : ''}`); console.log('━'.repeat(50)); const report = this.tokenMonitor.generateReport({ personaName: persona, format: outputFormat === 'detailed' ? 'detailed' : 'summary' }); this.displayTokenReport(report, outputFormat); // 최적화 제안 if (persona) { const suggestions = this.tokenMonitor.getOptimizationSuggestions(persona); if (suggestions.suggestions && suggestions.suggestions.length > 0) { console.log('\n💡 최적화 제안:'); suggestions.suggestions.forEach((suggestion, idx) => { console.log(`${idx + 1}. ${suggestion.message}`); }); } } } /** * 품질 평가 * @param {string} persona - 페르소나 * @param {string} prompt - 프롬프트 * @param {string} response - 응답 * @param {string} outputFormat - 출력 형식 */ async evaluateQuality(persona, prompt, response, outputFormat) { if (!persona || !prompt || !response) { throw new Error('페르소나, 프롬프트, 응답을 모두 지정해야 합니다.'); } console.log(`\n⭐ 응답 품질 평가: ${persona}`); console.log('━'.repeat(50)); // 컨텍스트 로드 const context = await this.contextParser.parseContextRules(persona); // 품질 평가 const evaluation = await this.qualityEvaluator.evaluateResponse({ prompt, response, personaName: persona, context }); this.displayQualityEvaluation(evaluation, outputFormat); } /** * 컨텍스트 변경 감시 */ async watchContextChanges() { console.log('\n👁️ 컨텍스트 파일 실시간 감시 시작'); console.log('━'.repeat(50)); // 이벤트 핸들러 설정 this.updateManager.on('contextUpdated', (data) => { console.log(`\n[${new Date().toISOString()}] 컨텍스트 업데이트 감지`); console.log(`- 페르소나: ${data.persona}`); console.log(`- 업데이트 유형: ${data.updateType}`); }); this.updateManager.on('error', (data) => { console.error(`\n[오류] ${data.error.message}`); }); // 감시 시작 this.updateManager.startWatching(); console.log('모든 페르소나 컨텍스트 파일을 감시 중입니다.'); console.log('종료하려면 Ctrl+C를 누르세요.'); // 프로세스 종료 처리 process.on('SIGINT', () => { console.log('\n감시 중지 중...'); this.updateManager.stopWatching(); process.exit(0); }); // 계속 실행 await new Promise(() => {}); } /** * 종합 리포트 생성 * @param {string} persona - 페르소나 (선택) * @param {string} outputFormat - 출력 형식 * @param {boolean} save - 저장 여부 */ async generateReport(persona, outputFormat, save) { console.log(`\n📑 종합 리포트 생성${persona ? `: ${persona}` : ''}`); console.log('━'.repeat(50)); // 검증 통계 const validationStats = this.behaviorValidator.generateStatistics(persona); // 품질 평가 통계 const qualityStats = this.qualityEvaluator.generateStatistics(persona); // 토큰 사용량 리포트 const tokenReport = this.tokenMonitor.generateReport({ personaName: persona, format: 'summary' }); const report = { generatedAt: new Date().toISOString(), persona: persona || 'all', validation: validationStats, quality: qualityStats, tokenUsage: tokenReport }; this.displayComprehensiveReport(report, outputFormat); if (save) { const filename = `persona-report-${persona || 'all'}-${Date.now()}.json`; await this.saveResult(report, filename); } } /** * 적용 결과 표시 * @param {Object} result - 적용 결과 * @param {Object} tokenUsage - 토큰 사용량 * @param {string} format - 출력 형식 */ displayApplyResult(result, tokenUsage, format) { if (format === 'json') { console.log(JSON.stringify({ result, tokenUsage }, null, 2)); return; } console.log('\n✅ 컨텍스트 적용 완료'); console.log(`페르소나: ${result.personaName}`); console.log(`원본 프롬프트 토큰: ${tokenUsage.originalTokens}`); console.log(`컨텍스트 토큰: ${tokenUsage.contextTokens}`); console.log(`전체 토큰: ${tokenUsage.totalTokens}`); console.log(`토큰 증가율: ${tokenUsage.percentageIncrease}`); if (tokenUsage.alert) { console.log(`\n⚠️ 알림: ${tokenUsage.alert.message}`); } if (format === 'detailed') { console.log('\n주입된 프롬프트:'); console.log('─'.repeat(40)); console.log(result.injectedPrompt); console.log('─'.repeat(40)); } } /** * 검증 결과 표시 * @param {Object} validation - 검증 결과 * @param {string} format - 출력 형식 */ displayValidationResult(validation, format) { if (format === 'json') { console.log(JSON.stringify(validation, null, 2)); return; } console.log(`\n검증 결과: ${validation.valid ? '✅ 유효' : '❌ 무효'}`); console.log(`전체 점수: ${(validation.overallScore * 100).toFixed(1)}%`); console.log('\n세부 점수:'); console.log(`- 키워드 일치도: ${(validation.scores.keywordMatch * 100).toFixed(1)}%`); console.log(`- 구조 일치도: ${(validation.scores.structureMatch * 100).toFixed(1)}%`); console.log(`- 소통 스타일: ${(validation.scores.communicationMatch * 100).toFixed(1)}%`); console.log(`- 관심 영역: ${(validation.scores.focusMatch * 100).toFixed(1)}%`); } /** * 토큰 리포트 표시 * @param {Object} report - 토큰 리포트 * @param {string} format - 출력 형식 */ displayTokenReport(report, format) { if (format === 'json') { console.log(JSON.stringify(report, null, 2)); return; } if (report.message) { console.log(report.message); return; } console.log(`\n기간: ${report.period.start} ~ ${report.period.end}`); console.log(`총 사용 횟수: ${report.totalUsages}`); console.log(`총 컨텍스트 토큰: ${report.tokenStats.total}`); console.log(`평균 컨텍스트 토큰: ${report.tokenStats.average}`); if (report.personaSummary) { console.log('\n페르소나별 요약:'); Object.entries(report.personaSummary).forEach(([persona, stats]) => { console.log(`\n${persona}:`); console.log(` - 사용 횟수: ${stats.count}`); console.log(` - 총 토큰: ${stats.totalTokens}`); console.log(` - 평균 토큰: ${Math.round(stats.totalTokens / stats.count)}`); if (stats.alerts.warning > 0 || stats.alerts.critical > 0) { console.log(` - 알림: 경고 ${stats.alerts.warning}, 위험 ${stats.alerts.critical}`); } }); } if (format === 'detailed' && report.trendAnalysis) { console.log(`\n트렌드: ${report.trendAnalysis.direction} (${report.trendAnalysis.changePercentage})`); if (report.trendAnalysis.prediction !== null) { console.log(`예상 다음 사용량: ${report.trendAnalysis.prediction} 토큰`); } } } /** * 품질 평가 표시 * @param {Object} evaluation - 평가 결과 * @param {string} format - 출력 형식 */ displayQualityEvaluation(evaluation, format) { if (format === 'json') { console.log(JSON.stringify(evaluation, null, 2)); return; } console.log(`\n품질 수준: ${this.getQualityEmoji(evaluation.quality)} ${evaluation.quality}`); console.log(`최종 점수: ${(evaluation.scores.final * 100).toFixed(1)}%`); console.log('\n평가 항목:'); Object.entries(evaluation.scores.individual).forEach(([metric, score]) => { const emoji = score >= 0.8 ? '✅' : score >= 0.6 ? '⚠️' : '❌'; console.log(`${emoji} ${this.getMetricName(metric)}: ${(score * 100).toFixed(1)}%`); }); if (evaluation.feedback && evaluation.feedback.length > 0) { console.log('\n피드백:'); evaluation.feedback.forEach((fb) => { console.log(`- ${fb.message}`); }); } if (evaluation.recommendations && evaluation.recommendations.length > 0) { console.log('\n권장사항:'); evaluation.recommendations.forEach((rec) => { const priorityEmoji = rec.priority === 'high' ? '🔴' : '🟡'; console.log(`${priorityEmoji} [${rec.area}] ${rec.suggestion}`); }); } } /** * 종합 리포트 표시 * @param {Object} report - 종합 리포트 * @param {string} format - 출력 형식 */ displayComprehensiveReport(report, format) { if (format === 'json') { console.log(JSON.stringify(report, null, 2)); return; } console.log('\n=== 행동 패턴 검증 통계 ==='); if (report.validation.message) { console.log(report.validation.message); } else { console.log(`검증 횟수: ${report.validation.totalValidations}`); console.log(`유효율: ${report.validation.validationRate}`); console.log(`평균 점수: ${report.validation.averageScore}`); } console.log('\n=== 응답 품질 평가 통계 ==='); if (report.quality.message) { console.log(report.quality.message); } else { console.log(`평가 횟수: ${report.quality.evaluationCount}`); console.log(`평균 최종 점수: ${report.quality.averageScores.final}`); console.log(`최고 성과 지표: ${this.getMetricName(report.quality.bestPerformingMetric)}`); console.log(`개선 필요 지표: ${this.getMetricName(report.quality.worstPerformingMetric)}`); } console.log('\n=== 토큰 사용량 요약 ==='); if (report.tokenUsage.message) { console.log(report.tokenUsage.message); } else { console.log(`총 사용 횟수: ${report.tokenUsage.totalUsages}`); console.log(`평균 컨텍스트 토큰: ${report.tokenUsage.tokenStats.average}`); console.log(`알림 발생: 경고 ${report.tokenUsage.alerts.warning}, 위험 ${report.tokenUsage.alerts.critical}`); } } /** * 결과 저장 * @param {Object} data - 저장할 데이터 * @param {string} filename - 파일명 (선택) */ async saveResult(data, filename) { const reportsDir = path.join(process.cwd(), '.aiwf', 'reports', 'persona-context'); await fs.mkdir(reportsDir, { recursive: true }); const defaultFilename = `${data.type || 'report'}-${Date.now()}.json`; const filepath = path.join(reportsDir, filename || defaultFilename); await fs.writeFile(filepath, JSON.stringify(data, null, 2), 'utf-8'); console.log(`\n💾 결과 저장됨: ${filepath}`); } /** * 품질 수준 이모지 반환 * @param {string} quality - 품질 수준 * @returns {string} 이모지 */ getQualityEmoji(quality) { const emojis = { excellent: '🌟', good: '✨', fair: '⭐', poor: '💫' }; return emojis[quality] || '❓'; } /** * 지표 이름 한글 변환 * @param {string} metric - 지표 키 * @returns {string} 한글 이름 */ getMetricName(metric) { const names = { relevance: '관련성', consistency: '일관성', completeness: '완전성', clarity: '명확성', actionability: '실행가능성' }; return names[metric] || metric; } } module.exports = PersonaContextApplyCommand;