diff --git a/src/lib/diagnostics.ts b/src/lib/diagnostics.ts index 89a1072..98aa1c4 100644 --- a/src/lib/diagnostics.ts +++ b/src/lib/diagnostics.ts @@ -35,6 +35,42 @@ export class AgentDataValidator { } break; } + + // 품질 점수화 (Quality Scoring) - Astra 피드백 반영 + const score = QualityScorer.evaluate(data); + if (score < 50) { + logInfo(`[QualityWarning] ${stage} 결과 품질 낮음 (Score: ${score}). 내용 보강이 필요할 수 있습니다.`); + } + } +} + +export class QualityScorer { + /** + * 수집된 데이터의 품질을 휴리스틱하게 점수화합니다 (0-100). + */ + public static evaluate(data: string): number { + if (!data) return 0; + let score = 0; + + // 1. 길이 기반 점수 (최대 40점) + score += Math.min(40, data.length / 50); + + // 2. 구조화 기반 점수 (최대 30점) + if (data.includes('##') || data.includes('**') || data.includes('- ')) { + score += 30; + } + + // 3. 밀도/정보량 기반 점수 (최대 30점) + // 코드 블록이나 구체적 링크가 있으면 가산점 + if (data.includes('```')) score += 15; + if (data.includes('[[') || data.includes('http')) score += 15; + + // 감점 요소: Placeholder 나 Empty 상태 + if (data.includes('[Placeholder]') || data.includes('TODO')) { + score -= 20; + } + + return Math.max(0, Math.min(100, score)); } } diff --git a/src/lib/engine.ts b/src/lib/engine.ts index 832d945..c830d59 100644 --- a/src/lib/engine.ts +++ b/src/lib/engine.ts @@ -1,4 +1,6 @@ import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; import { lockManager } from '../core/lock'; import { actionQueue } from '../core/queue'; import { logInfo, logError } from '../utils'; @@ -61,6 +63,7 @@ export class MissionState { private _stage: PipelineStage = 'idle'; private _auditTrail: AuditEntry[] = []; private _lastTransitionTime: number = Date.now(); + private _failureReason?: string; public readonly missionId: string; public readonly startTime: number; @@ -96,8 +99,35 @@ export class MissionState { this._lastTransitionTime = now; logInfo(`[MissionState] ${this.missionId}: ${entry.from} → ${entry.to} (${entry.durationFromPrev}ms) — ${message}`); + this.saveToDisk(); } + /** + * 진행 상태를 디스크에 영구적으로 기록합니다 (State Save). + * 크래시 발생 시 어디까지 진행되었는지 파악하는 기초 데이터가 됩니다. + */ + private saveToDisk(): void { + try { + const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || ''; + if (!workspacePath) return; + + const astraDir = path.join(workspacePath, '.astra', 'missions'); + if (!fs.existsSync(astraDir)) { + fs.mkdirSync(astraDir, { recursive: true }); + } + const filePath = path.join(astraDir, `${this.missionId}.json`); + fs.writeFileSync(filePath, JSON.stringify(this.toStructuredLog(), null, 2), 'utf-8'); + } catch (err) { + logError(`[MissionState] Failed to save state to disk for ${this.missionId}`, err); + } + } + + public setFailureReason(reason: string): void { + this._failureReason = reason; + this.saveToDisk(); + } + + /** * 전체 미션의 경과 시간을 반환합니다. */ @@ -137,6 +167,7 @@ export class MissionState { status: this._stage, startTime: new Date(this.startTime).toISOString(), totalElapsedMs: this.getElapsedMs(), + failureReason: this._failureReason, transitionCount: this._auditTrail.length, transitions: this._auditTrail.map(e => ({ from: e.from, @@ -385,6 +416,11 @@ export class AgentEngine { this.transition('error', `오류 발생: ${error.message}`, onProgress); + // 실패 원인 명시적 기록 (Astra 피드백: Record failure reason) + if (this.state) { + this.state.setFailureReason(`[${type}] ${rule.description} - 세부원인: ${error.message}`); + } + // Error Recovery Matrix 기반 세분화된 로깅 switch (type) { case ErrorType.ABORT: