aiwf
Version:
AI Workflow Framework for Claude Code with multi-language support (Korean/English)
223 lines (184 loc) • 5.55 kB
JavaScript
/**
* 백그라운드 평가 모니터
* 사용자 작업을 방해하지 않고 품질 모니터링
*/
import { SimplifiedEvaluator } from './simplified-evaluator.js';
import fs from 'fs/promises';
import path from 'path';
export class BackgroundMonitor {
constructor() {
this.evaluator = new SimplifiedEvaluator();
this.recentEvaluations = [];
this.maxHistorySize = 100;
this.statsFile = null;
this.initialized = false;
}
/**
* 모니터 초기화
*/
async initialize() {
if (this.initialized) return;
try {
// 프로젝트 루트의 .aiwf 디렉토리 찾기
const projectRoot = await this.findProjectRoot();
if (projectRoot) {
this.statsFile = path.join(projectRoot, '.aiwf', '.evaluation-stats.json');
await this.loadStats();
}
this.initialized = true;
} catch (error) {
// 초기화 실패해도 기본 기능은 작동
this.initialized = true;
}
}
/**
* 프로젝트 루트 찾기
*/
async findProjectRoot() {
let currentDir = process.cwd();
while (currentDir !== path.dirname(currentDir)) {
try {
await fs.access(path.join(currentDir, '.aiwf'));
return currentDir;
} catch {
currentDir = path.dirname(currentDir);
}
}
return null;
}
/**
* 통계 로드
*/
async loadStats() {
if (!this.statsFile) return;
try {
const data = await fs.readFile(this.statsFile, 'utf8');
const stats = JSON.parse(data);
this.recentEvaluations = stats.recentEvaluations || [];
} catch {
// 파일이 없으면 새로 시작
this.recentEvaluations = [];
}
}
/**
* 통계 저장 (비동기, 에러 무시)
*/
async saveStats() {
if (!this.statsFile) return;
try {
const stats = {
recentEvaluations: this.recentEvaluations.slice(-this.maxHistorySize),
lastUpdated: new Date().toISOString()
};
await fs.writeFile(this.statsFile, JSON.stringify(stats, null, 2));
} catch {
// 저장 실패해도 무시
}
}
/**
* 백그라운드에서 응답 모니터링
*/
async monitorResponse(response, persona, options = {}) {
// 초기화 확인
if (!this.initialized) {
await this.initialize();
}
// 빠른 평가 수행
const evaluation = this.evaluator.quickEvaluate(response, persona);
// 기록 추가
this.recentEvaluations.push({
persona,
score: evaluation.score,
timestamp: new Date().toISOString(),
responseLength: response.length
});
// 크기 제한
if (this.recentEvaluations.length > this.maxHistorySize) {
this.recentEvaluations = this.recentEvaluations.slice(-this.maxHistorySize);
}
// 비동기 저장 (차단하지 않음)
this.saveStats().catch(() => {});
// 피드백이 필요한 경우만 반환
if (evaluation.needsFeedback && !options.silent) {
return {
feedback: evaluation.feedback,
score: evaluation.score
};
}
return null;
}
/**
* 최근 통계 요약
*/
getRecentStats(persona = null) {
let evaluations = this.recentEvaluations;
if (persona) {
evaluations = evaluations.filter(e => e.persona === persona);
}
if (evaluations.length === 0) {
return { message: '아직 평가 기록이 없습니다.' };
}
const scores = evaluations.map(e => e.score);
const avgScore = scores.reduce((sum, s) => sum + s, 0) / scores.length;
// 페르소나별 통계
const personaStats = {};
evaluations.forEach(e => {
if (!personaStats[e.persona]) {
personaStats[e.persona] = { count: 0, totalScore: 0 };
}
personaStats[e.persona].count++;
personaStats[e.persona].totalScore += e.score;
});
Object.keys(personaStats).forEach(p => {
const stats = personaStats[p];
stats.avgScore = (stats.totalScore / stats.count).toFixed(2);
delete stats.totalScore;
});
return {
totalEvaluations: evaluations.length,
averageScore: avgScore.toFixed(2),
personaStats,
trend: this.calculateTrend(scores)
};
}
/**
* 점수 추세 계산
*/
calculateTrend(scores) {
if (scores.length < 2) return 'stable';
const recent = scores.slice(-10);
const older = scores.slice(-20, -10);
if (older.length === 0) return 'stable';
const recentAvg = recent.reduce((sum, s) => sum + s, 0) / recent.length;
const olderAvg = older.reduce((sum, s) => sum + s, 0) / older.length;
if (recentAvg > olderAvg + 0.1) return 'improving';
if (recentAvg < olderAvg - 0.1) return 'declining';
return 'stable';
}
/**
* 주간 요약 생성
*/
getWeeklySummary() {
const oneWeekAgo = new Date();
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
const weeklyEvals = this.recentEvaluations.filter(e =>
new Date(e.timestamp) > oneWeekAgo
);
if (weeklyEvals.length === 0) {
return { message: '최근 일주일간 평가 기록이 없습니다.' };
}
const summary = this.getRecentStats();
summary.period = 'weekly';
summary.evaluationCount = weeklyEvals.length;
return summary;
}
}
// 싱글톤 인스턴스
let monitorInstance = null;
export function getBackgroundMonitor() {
if (!monitorInstance) {
monitorInstance = new BackgroundMonitor();
}
return monitorInstance;
}
export default BackgroundMonitor;