feat(engine): implement state persistence, failure reason tracking, and quality scoring as per Astra review
This commit is contained in:
@@ -35,6 +35,42 @@ export class AgentDataValidator {
|
|||||||
}
|
}
|
||||||
break;
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
import { lockManager } from '../core/lock';
|
import { lockManager } from '../core/lock';
|
||||||
import { actionQueue } from '../core/queue';
|
import { actionQueue } from '../core/queue';
|
||||||
import { logInfo, logError } from '../utils';
|
import { logInfo, logError } from '../utils';
|
||||||
@@ -61,6 +63,7 @@ export class MissionState {
|
|||||||
private _stage: PipelineStage = 'idle';
|
private _stage: PipelineStage = 'idle';
|
||||||
private _auditTrail: AuditEntry[] = [];
|
private _auditTrail: AuditEntry[] = [];
|
||||||
private _lastTransitionTime: number = Date.now();
|
private _lastTransitionTime: number = Date.now();
|
||||||
|
private _failureReason?: string;
|
||||||
|
|
||||||
public readonly missionId: string;
|
public readonly missionId: string;
|
||||||
public readonly startTime: number;
|
public readonly startTime: number;
|
||||||
@@ -96,8 +99,35 @@ export class MissionState {
|
|||||||
this._lastTransitionTime = now;
|
this._lastTransitionTime = now;
|
||||||
|
|
||||||
logInfo(`[MissionState] ${this.missionId}: ${entry.from} → ${entry.to} (${entry.durationFromPrev}ms) — ${message}`);
|
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,
|
status: this._stage,
|
||||||
startTime: new Date(this.startTime).toISOString(),
|
startTime: new Date(this.startTime).toISOString(),
|
||||||
totalElapsedMs: this.getElapsedMs(),
|
totalElapsedMs: this.getElapsedMs(),
|
||||||
|
failureReason: this._failureReason,
|
||||||
transitionCount: this._auditTrail.length,
|
transitionCount: this._auditTrail.length,
|
||||||
transitions: this._auditTrail.map(e => ({
|
transitions: this._auditTrail.map(e => ({
|
||||||
from: e.from,
|
from: e.from,
|
||||||
@@ -385,6 +416,11 @@ export class AgentEngine {
|
|||||||
|
|
||||||
this.transition('error', `오류 발생: ${error.message}`, onProgress);
|
this.transition('error', `오류 발생: ${error.message}`, onProgress);
|
||||||
|
|
||||||
|
// 실패 원인 명시적 기록 (Astra 피드백: Record failure reason)
|
||||||
|
if (this.state) {
|
||||||
|
this.state.setFailureReason(`[${type}] ${rule.description} - 세부원인: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Error Recovery Matrix 기반 세분화된 로깅
|
// Error Recovery Matrix 기반 세분화된 로깅
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ErrorType.ABORT:
|
case ErrorType.ABORT:
|
||||||
|
|||||||
Reference in New Issue
Block a user