aiwf
Version:
AI Workflow Framework for Claude Code with multi-language support (Korean/English)
598 lines (504 loc) • 18.1 kB
JavaScript
/**
* 페르소나 인식 컨텍스트 압축기
* 현재 활성화된 페르소나에 따라 압축 전략을 조정합니다
*/
import { ContextCompressor } from './context-compressor.js';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export class PersonaAwareCompressor extends ContextCompressor {
constructor(mode = 'balanced') {
super(mode);
this.currentPersona = null;
this.personaStrategies = this.initializePersonaStrategies();
}
/**
* 페르소나별 압축 전략 초기화
*/
initializePersonaStrategies() {
return {
architect: {
preservePatterns: [
'시스템', '아키텍처', '설계', '구조', '패턴', '확장성',
'API', 'interface', 'component', 'module', 'layer'
],
focusAreas: ['system_design', 'architecture', 'patterns'],
compressionWeights: {
implementation_details: 0.3, // 구현 세부사항은 적게
design_concepts: 0.9, // 설계 개념은 많이 보존
code_examples: 0.5, // 코드 예제는 중간
theoretical_content: 0.8 // 이론적 내용은 많이 보존
},
summarizationFocus: 'high-level structure and design patterns'
},
security: {
preservePatterns: [
'보안', '취약점', '위협', '암호화', '인증', '권한',
'vulnerability', 'threat', 'attack', 'defense', 'exploit'
],
focusAreas: ['security_threats', 'vulnerabilities', 'mitigation'],
compressionWeights: {
security_warnings: 1.0, // 보안 경고는 모두 보존
vulnerability_details: 1.0, // 취약점 세부사항 모두 보존
general_info: 0.4, // 일반 정보는 압축
examples: 0.6 // 예제는 중간 정도 보존
},
summarizationFocus: 'security implications and mitigation strategies'
},
frontend: {
preservePatterns: [
'UI', 'UX', '사용자', '화면', '컴포넌트', '스타일',
'react', 'vue', 'component', 'style', 'responsive', 'accessibility'
],
focusAreas: ['user_interface', 'user_experience', 'visual_design'],
compressionWeights: {
visual_descriptions: 0.8, // 시각적 설명 많이 보존
code_snippets: 0.7, // 코드 스니펫 보존
backend_details: 0.2, // 백엔드 세부사항은 압축
styling_info: 0.9 // 스타일링 정보 많이 보존
},
summarizationFocus: 'UI components and user interaction flows'
},
backend: {
preservePatterns: [
'API', '데이터베이스', '서버', '성능', '트랜잭션', '캐시',
'endpoint', 'database', 'query', 'performance', 'optimization'
],
focusAreas: ['api_design', 'data_processing', 'performance'],
compressionWeights: {
api_specifications: 0.9, // API 명세 많이 보존
database_schemas: 0.8, // DB 스키마 보존
frontend_details: 0.2, // 프론트엔드 세부사항 압축
performance_metrics: 0.9 // 성능 지표 많이 보존
},
summarizationFocus: 'API endpoints and data flow'
},
data_analyst: {
preservePatterns: [
'데이터', '분석', '통계', '지표', '시각화', '인사이트',
'data', 'analysis', 'statistics', 'metrics', 'visualization'
],
focusAreas: ['data_analysis', 'insights', 'visualization'],
compressionWeights: {
data_descriptions: 0.9, // 데이터 설명 많이 보존
analysis_methods: 0.8, // 분석 방법 보존
raw_data: 0.3, // 원시 데이터는 압축
insights: 1.0, // 인사이트는 모두 보존
visualizations: 0.7 // 시각화 정보 보존
},
summarizationFocus: 'data patterns and analytical insights'
}
};
}
/**
* 현재 페르소나 로드
*/
loadCurrentPersona() {
try {
const projectRoot = this.findProjectRoot();
const personaPath = path.join(projectRoot, '.aiwf', 'current_persona.json');
if (fs.existsSync(personaPath)) {
const personaData = JSON.parse(fs.readFileSync(personaPath, 'utf8'));
this.currentPersona = personaData.persona;
return personaData;
}
} catch (error) {
console.warn('페르소나 정보를 로드할 수 없습니다:', error.message);
}
return null;
}
/**
* 프로젝트 루트 찾기
*/
findProjectRoot() {
let currentDir = process.cwd();
while (currentDir !== path.dirname(currentDir)) {
if (fs.existsSync(path.join(currentDir, '.aiwf'))) {
return currentDir;
}
currentDir = path.dirname(currentDir);
}
return process.cwd();
}
/**
* 페르소나 인식 압축 수행
*/
async compress(content, options = {}) {
// 현재 페르소나 로드
const personaData = this.loadCurrentPersona();
if (!personaData || !this.personaStrategies[personaData.persona]) {
// 페르소나가 없으면 기본 압축 수행
return super.compress(content, options);
}
// 페르소나별 전략 적용
const strategy = this.personaStrategies[personaData.persona];
const enhancedOptions = {
...options,
personaStrategy: strategy,
currentPersona: personaData.persona,
preservePatterns: [
...(options.preservePatterns || []),
...strategy.preservePatterns
]
};
// 압축 수행
const result = await this.performPersonaAwareCompression(content, enhancedOptions);
// 페르소나 메타데이터 추가
if (result.success) {
result.metadata = {
...result.metadata,
persona: personaData.persona,
personaStrategy: {
focusAreas: strategy.focusAreas,
summarizationFocus: strategy.summarizationFocus
}
};
}
return result;
}
/**
* 페르소나 인식 압축 수행
*/
async performPersonaAwareCompression(content, options) {
const { personaStrategy, currentPersona } = options;
try {
// 1. 컨텐츠 분석
const contentAnalysis = this.analyzeContent(content, personaStrategy);
// 2. 페르소나별 중요도 점수 계산
const importanceScores = this.calculateImportanceScores(
contentAnalysis,
personaStrategy
);
// 3. 선택적 압축
let compressed = content;
// 섹션별 압축
if (contentAnalysis.sections.length > 0) {
compressed = this.compressSectionsByImportance(
contentAnalysis.sections,
importanceScores,
personaStrategy
);
}
// 4. 페르소나 특화 요약
if (options.strategy === 'aggressive') {
const summary = await this.generatePersonaFocusedSummary(
compressed,
personaStrategy
);
if (summary) {
compressed = this.integratePersonaSummary(compressed, summary, currentPersona);
}
}
// 5. 토큰 수 계산
const originalTokens = this.tokenCounter.countTokens(content);
const compressedTokens = this.tokenCounter.countTokens(compressed);
return {
success: true,
original: content,
compressed: compressed,
originalTokens: originalTokens,
compressedTokens: compressedTokens,
compressionRatio: ((originalTokens - compressedTokens) / originalTokens * 100).toFixed(1),
metadata: {
validation: this.validateCompression(content, compressed),
contentAnalysis: contentAnalysis,
importanceScores: importanceScores,
personaFocus: personaStrategy.summarizationFocus
}
};
} catch (error) {
return {
success: false,
error: error.message,
original: content,
compressed: content
};
}
}
/**
* 컨텐츠 분석
*/
analyzeContent(content, strategy) {
const lines = content.split('\n');
const sections = [];
let currentSection = null;
lines.forEach((line, index) => {
// 섹션 헤더 감지
if (line.match(/^#+\s/)) {
if (currentSection) {
sections.push(currentSection);
}
currentSection = {
header: line,
startLine: index,
content: [],
relevanceScore: 0
};
} else if (currentSection) {
currentSection.content.push(line);
}
});
if (currentSection) {
sections.push(currentSection);
}
// 각 섹션의 관련성 점수 계산
sections.forEach(section => {
section.relevanceScore = this.calculateSectionRelevance(
section,
strategy
);
});
return {
sections,
totalLines: lines.length,
hasCode: content.includes('```'),
hasTables: content.includes('|'),
hasLists: /^[-*]\s/.test(content)
};
}
/**
* 섹션 관련성 점수 계산
*/
calculateSectionRelevance(section, strategy) {
const fullText = section.header + '\n' + section.content.join('\n');
let score = 0;
// 보존 패턴 매칭
strategy.preservePatterns.forEach(pattern => {
const regex = new RegExp(pattern, 'gi');
const matches = fullText.match(regex);
if (matches) {
score += matches.length * 2;
}
});
// 포커스 영역 확인
strategy.focusAreas.forEach(area => {
if (fullText.toLowerCase().includes(area.replace('_', ' '))) {
score += 5;
}
});
// 섹션 헤더 레벨에 따른 가중치
const headerLevel = (section.header.match(/^#+/) || [''])[0].length;
score *= (5 - Math.min(headerLevel, 4)) / 4;
return score;
}
/**
* 중요도 점수 계산
*/
calculateImportanceScores(analysis, strategy) {
const scores = {};
analysis.sections.forEach((section, index) => {
// 기본 점수
let importance = section.relevanceScore;
// 컨텐츠 타입별 가중치 적용
const sectionText = section.content.join('\n');
if (sectionText.includes('```') && strategy.compressionWeights.code_examples) {
importance *= strategy.compressionWeights.code_examples;
}
if (section.header.toLowerCase().includes('security') &&
strategy.compressionWeights.security_warnings) {
importance *= strategy.compressionWeights.security_warnings;
}
scores[index] = importance;
});
return scores;
}
/**
* 중요도에 따른 섹션 압축
*/
compressSectionsByImportance(sections, importanceScores, strategy) {
const compressed = [];
// 중요도 순으로 정렬
const sortedIndices = Object.keys(importanceScores)
.sort((a, b) => importanceScores[b] - importanceScores[a]);
// 평균 중요도 계산
const avgImportance = Object.values(importanceScores)
.reduce((sum, score) => sum + score, 0) / Object.keys(importanceScores).length;
sections.forEach((section, index) => {
const importance = importanceScores[index];
// 중요도가 평균 이상이면 전체 보존
if (importance >= avgImportance * 1.2) {
compressed.push(section.header);
compressed.push(...section.content);
}
// 중요도가 낮으면 요약
else if (importance < avgImportance * 0.5) {
compressed.push(section.header);
compressed.push(this.summarizeSection(section, strategy));
}
// 중간이면 부분 압축
else {
compressed.push(section.header);
const compressedContent = this.partiallyCompressSection(section, strategy);
compressed.push(...compressedContent);
}
compressed.push(''); // 섹션 구분
});
return compressed.join('\n');
}
/**
* 섹션 요약
*/
summarizeSection(section, strategy) {
const content = section.content.join(' ');
const sentences = content.split(/[.!?]+/).filter(s => s.trim());
if (sentences.length <= 2) {
return content;
}
// 페르소나 관련 문장 우선 선택
const relevantSentences = sentences.filter(sentence => {
return strategy.preservePatterns.some(pattern =>
sentence.toLowerCase().includes(pattern.toLowerCase())
);
});
if (relevantSentences.length > 0) {
return `[요약] ${relevantSentences.slice(0, 2).join('. ')}.`;
}
// 첫 문장과 마지막 문장
return `[요약] ${sentences[0]}... ${sentences[sentences.length - 1]}.`;
}
/**
* 부분 압축
*/
partiallyCompressSection(section, strategy) {
const compressed = [];
let skipNext = false;
section.content.forEach((line, index) => {
// 빈 줄은 유지
if (!line.trim()) {
compressed.push(line);
return;
}
// 중요 패턴이 포함된 줄은 유지
const hasImportantPattern = strategy.preservePatterns.some(pattern =>
line.toLowerCase().includes(pattern.toLowerCase())
);
if (hasImportantPattern) {
compressed.push(line);
skipNext = false;
} else if (!skipNext) {
// 일반 줄은 2줄 중 1줄만 유지
compressed.push(line);
skipNext = true;
} else {
skipNext = false;
}
});
return compressed;
}
/**
* 페르소나 중심 요약 생성
*/
async generatePersonaFocusedSummary(content, strategy) {
try {
// 여기서는 간단한 추출 기반 요약 구현
// 실제로는 AI 기반 요약을 사용할 수 있습니다
const sentences = content.split(/[.!?]+/).filter(s => s.trim());
const scoredSentences = [];
sentences.forEach(sentence => {
let score = 0;
// 페르소나 패턴 점수
strategy.preservePatterns.forEach(pattern => {
if (sentence.toLowerCase().includes(pattern.toLowerCase())) {
score += 3;
}
});
// 포커스 영역 점수
strategy.focusAreas.forEach(area => {
if (sentence.toLowerCase().includes(area.replace('_', ' '))) {
score += 2;
}
});
if (score > 0) {
scoredSentences.push({ sentence, score });
}
});
// 점수 순으로 정렬하고 상위 문장 선택
const topSentences = scoredSentences
.sort((a, b) => b.score - a.score)
.slice(0, 5)
.map(item => item.sentence);
if (topSentences.length > 0) {
return `주요 내용 (${strategy.summarizationFocus}):\n` +
topSentences.map(s => `- ${s.trim()}`).join('\n');
}
return null;
} catch (error) {
console.error('요약 생성 실패:', error);
return null;
}
}
/**
* 페르소나 요약 통합
*/
integratePersonaSummary(content, summary, persona) {
const personaNames = {
architect: 'Architect',
security: 'Security Expert',
frontend: 'Frontend Developer',
backend: 'Backend Developer',
data_analyst: 'Data Analyst'
};
const header = `\n\n---\n### ${personaNames[persona]} 관점 요약\n\n`;
return content + header + summary + '\n---\n';
}
/**
* 압축 결과 검증
*/
validateCompression(original, compressed) {
const validation = super.validateCompression(original, compressed);
// 페르소나별 추가 검증
if (this.currentPersona) {
const strategy = this.personaStrategies[this.currentPersona];
// 중요 패턴 보존율 확인
let preservedPatterns = 0;
let totalPatterns = 0;
strategy.preservePatterns.forEach(pattern => {
const originalCount = (original.match(new RegExp(pattern, 'gi')) || []).length;
const compressedCount = (compressed.match(new RegExp(pattern, 'gi')) || []).length;
totalPatterns += originalCount;
preservedPatterns += Math.min(originalCount, compressedCount);
});
validation.patternPreservationRate = totalPatterns > 0
? (preservedPatterns / totalPatterns * 100).toFixed(1)
: 100;
validation.personaAligned = validation.patternPreservationRate > 70;
}
return validation;
}
/**
* 페르소나별 압축 통계
*/
getPersonaCompressionStats() {
return {
currentPersona: this.currentPersona,
strategy: this.currentPersona ? this.personaStrategies[this.currentPersona] : null,
compressionHistory: this.compressionHistory || [],
averageRatioByPersona: this.calculateAverageRatioByPersona()
};
}
/**
* 페르소나별 평균 압축률 계산
*/
calculateAverageRatioByPersona() {
if (!this.compressionHistory || this.compressionHistory.length === 0) {
return {};
}
const ratiosByPersona = {};
this.compressionHistory.forEach(record => {
const persona = record.metadata?.persona || 'unknown';
if (!ratiosByPersona[persona]) {
ratiosByPersona[persona] = [];
}
ratiosByPersona[persona].push(parseFloat(record.compressionRatio));
});
const averages = {};
Object.keys(ratiosByPersona).forEach(persona => {
const ratios = ratiosByPersona[persona];
averages[persona] = (ratios.reduce((sum, r) => sum + r, 0) / ratios.length).toFixed(1);
});
return averages;
}
}
export default PersonaAwareCompressor;