aiwf
Version:
AI Workflow Framework for Claude Code with multi-language support (Korean/English)
645 lines (568 loc) • 19.4 kB
JavaScript
import { TokenCounter } from './token-counter.js';
/**
* 중요도 분류 및 점수 계산 시스템
*/
export class ImportanceClassifier {
constructor() {
this.tokenCounter = new TokenCounter();
this.initializeWeights();
}
/**
* 가중치 및 기준을 초기화합니다
*/
initializeWeights() {
// 중요도 레벨 정의
this.importanceLevels = {
critical: { weight: 1.0, threshold: 8 },
high: { weight: 0.8, threshold: 5 },
medium: { weight: 0.6, threshold: 3 },
low: { weight: 0.4, threshold: 0 }
};
// 키워드 가중치
this.keywordWeights = {
critical: {
keywords: ['urgent', 'critical', 'blocking', 'error', 'failed', 'must', 'required', 'essential'],
weight: 5,
multiplier: 1.5
},
high: {
keywords: ['important', 'priority', 'deadline', 'milestone', 'goal', 'objective', 'key', 'main'],
weight: 4,
multiplier: 1.3
},
medium: {
keywords: ['enhancement', 'improvement', 'optimize', 'should', 'planned', 'task', 'feature'],
weight: 3,
multiplier: 1.1
},
low: {
keywords: ['nice-to-have', 'future', 'consider', 'optional', 'example', 'note'],
weight: 2,
multiplier: 1.0
}
};
// 한국어 키워드 가중치
this.koreanKeywordWeights = {
critical: {
keywords: ['긴급', '중대', '차단', '오류', '실패', '필수', '요구', '필수적'],
weight: 5,
multiplier: 1.5
},
high: {
keywords: ['중요', '우선순위', '마감일', '마일스톤', '목표', '목적', '핵심', '주요'],
weight: 4,
multiplier: 1.3
},
medium: {
keywords: ['개선', '향상', '최적화', '해야', '계획된', '태스크', '기능'],
weight: 3,
multiplier: 1.1
},
low: {
keywords: ['좋으면', '미래', '고려', '선택적', '예시', '참고'],
weight: 2,
multiplier: 1.0
}
};
// 섹션 타입 가중치
this.sectionWeights = {
critical: {
sections: ['subtasks', 'acceptance criteria', 'requirements', 'goal', 'objectives', 'current sprint', 'active tasks'],
weight: 5
},
high: {
sections: ['description', 'specifications', 'architecture', 'implementation', 'planned tasks'],
weight: 4
},
medium: {
sections: ['testing', 'documentation', 'notes', 'examples', 'guidelines'],
weight: 3
},
low: {
sections: ['output log', 'history', 'templates', 'completed tasks', 'old logs'],
weight: 2
}
};
// 구조적 가중치
this.structuralWeights = {
header: {
levels: {
1: 5, // # 메인 헤더
2: 4, // ## 섹션 헤더
3: 3, // ### 서브섹션
4: 2, // #### 세부사항
5: 1, // ##### 최하위
6: 1 // ###### 최하위
}
},
list: {
checkbox_unchecked: 4, // - [ ] 미완료 체크박스
checkbox_checked: 2, // - [x] 완료 체크박스
bullet: 2, // - 불릿 포인트
numbered: 2 // 1. 번호 목록
},
emphasis: {
bold: 2, // **굵게**
italic: 1, // *기울임*
code: 1, // `코드`
link: 1 // [링크]
}
};
// 위치 가중치
this.positionWeights = {
start: 1.2, // 문서 시작 부분
middle: 1.0, // 문서 중간 부분
end: 0.8 // 문서 끝 부분
};
// 길이 가중치
this.lengthWeights = {
optimal: { min: 30, max: 150, weight: 1.2 },
acceptable: { min: 15, max: 300, weight: 1.0 },
penalty: { min: 0, max: 500, weight: 0.8 }
};
}
/**
* 콘텐츠의 중요도를 분석합니다
* @param {string} content - 분석할 콘텐츠
* @param {Object} options - 분석 옵션
* @returns {Object} 중요도 분석 결과
*/
analyzeImportance(content, options = {}) {
const {
preserveStructure = true,
includeMetadata = true,
detailedScoring = false
} = options;
const sections = this.parseContentSections(content);
const analyzedSections = sections.map((section, index) => {
const score = this.calculateSectionScore(section, index, sections.length);
const importance = this.scoreToImportance(score);
return {
...section,
score,
importance,
tokens: this.tokenCounter.countTokens(section.content),
...(detailedScoring && { scoreBreakdown: this.getScoreBreakdown(section, index, sections.length) })
};
});
// 중요도별 정렬
const sortedSections = analyzedSections.sort((a, b) => b.score - a.score);
const result = {
sections: preserveStructure ? analyzedSections : sortedSections,
summary: this.generateImportanceSummary(analyzedSections),
recommendations: this.generateRecommendations(analyzedSections)
};
if (includeMetadata) {
result.metadata = this.generateMetadata(analyzedSections, content);
}
return result;
}
/**
* 콘텐츠를 섹션으로 파싱합니다
* @param {string} content - 파싱할 콘텐츠
* @returns {Array<Object>} 섹션 배열
*/
parseContentSections(content) {
const sections = [];
const lines = content.split('\n');
let currentSection = null;
let currentContent = [];
let currentLevel = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
if (headerMatch) {
// 이전 섹션 저장
if (currentSection) {
sections.push({
name: currentSection,
level: currentLevel,
content: currentContent.join('\n').trim(),
type: this.identifySectionType(currentSection),
startLine: i - currentContent.length,
endLine: i - 1
});
}
// 새 섹션 시작
currentLevel = headerMatch[1].length;
currentSection = headerMatch[2].trim();
currentContent = [line];
} else {
if (currentSection) {
currentContent.push(line);
} else {
// 헤더가 없는 초기 콘텐츠
if (line.trim()) {
if (!currentSection) {
currentSection = 'Introduction';
currentLevel = 1;
currentContent = [];
}
currentContent.push(line);
}
}
}
}
// 마지막 섹션 저장
if (currentSection) {
sections.push({
name: currentSection,
level: currentLevel,
content: currentContent.join('\n').trim(),
type: this.identifySectionType(currentSection),
startLine: lines.length - currentContent.length,
endLine: lines.length - 1
});
}
return sections;
}
/**
* 섹션 타입을 식별합니다
* @param {string} sectionName - 섹션 이름
* @returns {string} 섹션 타입
*/
identifySectionType(sectionName) {
const lowerName = sectionName.toLowerCase();
for (const [importance, config] of Object.entries(this.sectionWeights)) {
for (const sectionType of config.sections) {
if (lowerName.includes(sectionType.toLowerCase())) {
return sectionType;
}
}
}
return 'general';
}
/**
* 섹션의 점수를 계산합니다
* @param {Object} section - 섹션 객체
* @param {number} index - 섹션 인덱스
* @param {number} totalSections - 전체 섹션 수
* @returns {number} 점수
*/
calculateSectionScore(section, index, totalSections) {
let score = 0;
const content = section.content;
const lowerContent = content.toLowerCase();
// 1. 키워드 기반 점수
score += this.calculateKeywordScore(lowerContent);
// 2. 섹션 타입 기반 점수
score += this.calculateSectionTypeScore(section.type);
// 3. 구조적 점수
score += this.calculateStructuralScore(content);
// 4. 위치 기반 점수
score += this.calculatePositionScore(index, totalSections);
// 5. 길이 기반 점수
score += this.calculateLengthScore(content);
// 6. 헤더 레벨 점수
score += this.calculateHeaderLevelScore(section.level);
// 7. 특수 패턴 점수
score += this.calculateSpecialPatternScore(content);
return Math.max(0, score);
}
/**
* 키워드 기반 점수를 계산합니다
* @param {string} content - 콘텐츠
* @returns {number} 키워드 점수
*/
calculateKeywordScore(content) {
let score = 0;
// 영어 키워드 점수
for (const [level, config] of Object.entries(this.keywordWeights)) {
for (const keyword of config.keywords) {
const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
const matches = content.match(regex);
if (matches) {
score += matches.length * config.weight * config.multiplier;
}
}
}
// 한국어 키워드 점수
for (const [level, config] of Object.entries(this.koreanKeywordWeights)) {
for (const keyword of config.keywords) {
const regex = new RegExp(keyword, 'gi');
const matches = content.match(regex);
if (matches) {
score += matches.length * config.weight * config.multiplier;
}
}
}
return score;
}
/**
* 섹션 타입 기반 점수를 계산합니다
* @param {string} sectionType - 섹션 타입
* @returns {number} 섹션 타입 점수
*/
calculateSectionTypeScore(sectionType) {
for (const [importance, config] of Object.entries(this.sectionWeights)) {
if (config.sections.includes(sectionType)) {
return config.weight;
}
}
return 1; // 기본 점수
}
/**
* 구조적 점수를 계산합니다
* @param {string} content - 콘텐츠
* @returns {number} 구조적 점수
*/
calculateStructuralScore(content) {
let score = 0;
// 체크박스 점수
const uncheckedCheckboxes = content.match(/- \[ \]/g);
if (uncheckedCheckboxes) {
score += uncheckedCheckboxes.length * this.structuralWeights.list.checkbox_unchecked;
}
const checkedCheckboxes = content.match(/- \[x\]/gi);
if (checkedCheckboxes) {
score += checkedCheckboxes.length * this.structuralWeights.list.checkbox_checked;
}
// 불릿 포인트 점수
const bulletPoints = content.match(/^\s*[-*+]\s+/gm);
if (bulletPoints) {
score += bulletPoints.length * this.structuralWeights.list.bullet;
}
// 번호 목록 점수
const numberedLists = content.match(/^\s*\d+\.\s+/gm);
if (numberedLists) {
score += numberedLists.length * this.structuralWeights.list.numbered;
}
// 강조 표시 점수
const boldText = content.match(/\*\*[^*]+\*\*/g);
if (boldText) {
score += boldText.length * this.structuralWeights.emphasis.bold;
}
const italicText = content.match(/\*[^*]+\*/g);
if (italicText) {
score += italicText.length * this.structuralWeights.emphasis.italic;
}
const codeText = content.match(/`[^`]+`/g);
if (codeText) {
score += codeText.length * this.structuralWeights.emphasis.code;
}
const links = content.match(/\[[^\]]+\]\([^)]+\)/g);
if (links) {
score += links.length * this.structuralWeights.emphasis.link;
}
return score;
}
/**
* 위치 기반 점수를 계산합니다
* @param {number} index - 섹션 인덱스
* @param {number} totalSections - 전체 섹션 수
* @returns {number} 위치 점수
*/
calculatePositionScore(index, totalSections) {
const relativePosition = index / totalSections;
if (relativePosition < 0.3) {
return this.positionWeights.start;
} else if (relativePosition > 0.7) {
return this.positionWeights.end;
} else {
return this.positionWeights.middle;
}
}
/**
* 길이 기반 점수를 계산합니다
* @param {string} content - 콘텐츠
* @returns {number} 길이 점수
*/
calculateLengthScore(content) {
const length = content.length;
if (length >= this.lengthWeights.optimal.min && length <= this.lengthWeights.optimal.max) {
return this.lengthWeights.optimal.weight;
} else if (length >= this.lengthWeights.acceptable.min && length <= this.lengthWeights.acceptable.max) {
return this.lengthWeights.acceptable.weight;
} else {
return this.lengthWeights.penalty.weight;
}
}
/**
* 헤더 레벨 점수를 계산합니다
* @param {number} level - 헤더 레벨
* @returns {number} 헤더 레벨 점수
*/
calculateHeaderLevelScore(level) {
return this.structuralWeights.header.levels[level] || 1;
}
/**
* 특수 패턴 점수를 계산합니다
* @param {string} content - 콘텐츠
* @returns {number} 특수 패턴 점수
*/
calculateSpecialPatternScore(content) {
let score = 0;
// 날짜 패턴 (최신성)
const recentDates = content.match(/2025-\d{2}-\d{2}/g);
if (recentDates) {
score += recentDates.length * 2;
}
// 상태 패턴
const statusPatterns = {
'in_progress': 4,
'pending': 3,
'completed': 1,
'planned': 2,
'blocked': 5
};
for (const [status, weight] of Object.entries(statusPatterns)) {
if (content.includes(status)) {
score += weight;
}
}
// 수치 패턴 (메트릭, 통계)
const numberPatterns = content.match(/\d+%|\d+\/\d+|\d+\.\d+/g);
if (numberPatterns) {
score += Math.min(numberPatterns.length * 0.5, 3); // 최대 3점
}
// 질문 패턴
const questions = content.match(/\?/g);
if (questions) {
score += questions.length * 0.5;
}
return score;
}
/**
* 점수를 중요도 레벨로 변환합니다
* @param {number} score - 점수
* @returns {string} 중요도 레벨
*/
scoreToImportance(score) {
if (score >= this.importanceLevels.critical.threshold) {
return 'critical';
} else if (score >= this.importanceLevels.high.threshold) {
return 'high';
} else if (score >= this.importanceLevels.medium.threshold) {
return 'medium';
} else {
return 'low';
}
}
/**
* 점수 세부 분석을 제공합니다
* @param {Object} section - 섹션 객체
* @param {number} index - 섹션 인덱스
* @param {number} totalSections - 전체 섹션 수
* @returns {Object} 점수 세부 분석
*/
getScoreBreakdown(section, index, totalSections) {
const content = section.content;
const lowerContent = content.toLowerCase();
return {
keyword: this.calculateKeywordScore(lowerContent),
sectionType: this.calculateSectionTypeScore(section.type),
structural: this.calculateStructuralScore(content),
position: this.calculatePositionScore(index, totalSections),
length: this.calculateLengthScore(content),
headerLevel: this.calculateHeaderLevelScore(section.level),
specialPattern: this.calculateSpecialPatternScore(content)
};
}
/**
* 중요도 요약을 생성합니다
* @param {Array<Object>} sections - 분석된 섹션 배열
* @returns {Object} 중요도 요약
*/
generateImportanceSummary(sections) {
const summary = {
total: sections.length,
critical: 0,
high: 0,
medium: 0,
low: 0,
averageScore: 0,
maxScore: 0,
minScore: Infinity,
totalTokens: 0
};
let totalScore = 0;
sections.forEach(section => {
summary[section.importance]++;
summary.totalTokens += section.tokens;
totalScore += section.score;
summary.maxScore = Math.max(summary.maxScore, section.score);
summary.minScore = Math.min(summary.minScore, section.score);
});
summary.averageScore = totalScore / sections.length;
summary.minScore = summary.minScore === Infinity ? 0 : summary.minScore;
// 백분율 계산
summary.percentages = {
critical: (summary.critical / summary.total) * 100,
high: (summary.high / summary.total) * 100,
medium: (summary.medium / summary.total) * 100,
low: (summary.low / summary.total) * 100
};
return summary;
}
/**
* 압축 권장사항을 생성합니다
* @param {Array<Object>} sections - 분석된 섹션 배열
* @returns {Object} 압축 권장사항
*/
generateRecommendations(sections) {
const recommendations = {
compressionStrategy: '',
preserveSections: [],
removeSections: [],
compressSections: [],
estimatedSavings: 0
};
const criticalSections = sections.filter(s => s.importance === 'critical');
const highSections = sections.filter(s => s.importance === 'high');
const mediumSections = sections.filter(s => s.importance === 'medium');
const lowSections = sections.filter(s => s.importance === 'low');
// 압축 전략 결정
const totalTokens = sections.reduce((sum, s) => sum + s.tokens, 0);
const lowTokens = lowSections.reduce((sum, s) => sum + s.tokens, 0);
const mediumTokens = mediumSections.reduce((sum, s) => sum + s.tokens, 0);
if (lowTokens / totalTokens > 0.4) {
recommendations.compressionStrategy = 'aggressive';
recommendations.preserveSections = [...criticalSections, ...highSections];
recommendations.removeSections = lowSections;
recommendations.compressSections = mediumSections;
recommendations.estimatedSavings = (lowTokens + mediumTokens * 0.5) / totalTokens * 100;
} else if (lowTokens / totalTokens > 0.2) {
recommendations.compressionStrategy = 'balanced';
recommendations.preserveSections = [...criticalSections, ...highSections, ...mediumSections];
recommendations.removeSections = lowSections;
recommendations.compressSections = [];
recommendations.estimatedSavings = lowTokens / totalTokens * 100;
} else {
recommendations.compressionStrategy = 'minimal';
recommendations.preserveSections = sections;
recommendations.removeSections = [];
recommendations.compressSections = [];
recommendations.estimatedSavings = 10; // 기본 포맷 정리
}
return recommendations;
}
/**
* 메타데이터를 생성합니다
* @param {Array<Object>} sections - 분석된 섹션 배열
* @param {string} content - 원본 콘텐츠
* @returns {Object} 메타데이터
*/
generateMetadata(sections, content) {
return {
analysisDate: new Date().toISOString(),
totalSections: sections.length,
totalTokens: this.tokenCounter.countTokens(content),
averageScore: sections.reduce((sum, s) => sum + s.score, 0) / sections.length,
scoreRange: {
min: Math.min(...sections.map(s => s.score)),
max: Math.max(...sections.map(s => s.score))
},
importanceDistribution: this.generateImportanceSummary(sections),
processingTime: Date.now() - this.startTime || 0
};
}
/**
* 리소스를 정리합니다
*/
cleanup() {
if (this.tokenCounter) {
this.tokenCounter.cleanup();
}
}
}
export default ImportanceClassifier;