feat(formatter): enforce implementation code snippet section in wiki artifacts

This commit is contained in:
g1nation
2026-05-04 15:58:47 +09:00
parent a74c881375
commit 1395bbcdae
25 changed files with 108 additions and 42 deletions
+1
View File
@@ -0,0 +1 @@
Plan 9 passes the minimum validation requirement.
+1
View File
@@ -0,0 +1 @@
Plan 8 passes the minimum validation requirement.
+1
View File
@@ -0,0 +1 @@
Plan 7 passes the minimum validation requirement.
+1
View File
@@ -0,0 +1 @@
Plan 6 passes the minimum validation requirement.
+1
View File
@@ -0,0 +1 @@
Plan 5 passes the minimum validation requirement.
+1
View File
@@ -0,0 +1 @@
Plan output 4 that passes validation checks.
+1
View File
@@ -0,0 +1 @@
Plan output 3 that passes validation checks.
+1
View File
@@ -0,0 +1 @@
Plan output 2 that passes validation checks.
+1
View File
@@ -0,0 +1 @@
Plan output 1 that passes validation checks.
+1
View File
@@ -0,0 +1 @@
Plan output 0 that passes validation checks.
+1
View File
@@ -0,0 +1 @@
Slow but valid agent response for performance measurement.
+1
View File
@@ -0,0 +1 @@
Plan: detailed strategy for the mission ahead.
+1
View File
@@ -0,0 +1 @@
Plan output that meets validation requirements.
+1
View File
@@ -0,0 +1 @@
Recovery successful after transient failures.
+1
View File
@@ -0,0 +1 @@
Recovery successful after transient failures.
+1
View File
@@ -0,0 +1 @@
Plan result that meets the minimum validation length.
+1
View File
@@ -0,0 +1 @@
Recovery successful after transient failures.
+10
View File
@@ -1,5 +1,15 @@
# Astra Patch Notes
## v2.64.0 (2026-05-04)
### 🛡️ Resilient Pipeline & Stability Overhaul
- **상태 영속성 및 재개 (State Persistence & Resume):** 미션 진행 상태를 디스크(`.astra/missions/`)에 실시간 저장하며, 크래시나 오류 발생 시 마지막 단계부터 자율 재개하는 기능 도입.
- **중복 수집 방지 (Deduplication):** 동일한 프롬프트와 컨텍스트에 대한 LLM 응답을 해시 기반으로 캐싱하여 불필요한 중복 호출을 차단하는 `CacheManager` 탑재.
- **실패 원인 명시적 기록:** Transient(일시적) 및 Permanent(영구적) 오류 분류와 함께 세부 실패 원인을 로그에 기록하여 진단 편의성 강화.
- **수집 결과 품질 점수화 (Quality Scoring):** 데이터의 길이, 구조, 정보 밀도를 기반으로 수집 결과의 품질을 자동 평가하는 `QualityScorer` 도입.
- **위키 포맷 표준화 (Standardization):** 최종 결과물에 P-Reinforce v3.0 표준 프론트매터 및 헤더를 자동 주입하는 `WikiFormatter` 연동.
---
## v2.63.0 (2026-05-04)
### 🛡️ Agent Handoff Tracing & Diagnostics
- **데이터 무결성 검증 (Data Integrity):** 에이전트 간(Planner → Researcher → Writer) 핸드오프 시 발생할 수 있는 데이터 누락을 감지하고 차단하는 `AgentDataValidator` 기능 도입.
+2 -2
View File
@@ -6,6 +6,6 @@
"description": "Auto-detected from the local project path in the conversation.",
"corePurpose": "Capture project direction, architecture discussion, decisions, and development notes as Markdown.",
"detailLevel": "standard",
"createdAt": "2026-05-04T04:35:30.989Z",
"updatedAt": "2026-05-04T04:35:30.991Z"
"createdAt": "2026-05-04T06:49:36.436Z",
"updatedAt": "2026-05-04T06:49:36.437Z"
}
@@ -0,0 +1,19 @@
# ADR: /Volumes/Data/project/Antigravity/ConnectAI 이 프로젝트 자체를 나는 자비스와 같은 툴로 만드는 것이 내 목표...
## Status
accepted
## Context
/Volumes/Data/project/Antigravity/ConnectAI 이 프로젝트 자체를 나는 자비스와 같은 툴로 만드는 것이 내 목표야. 이런 목표를 기반으로 봤을때 우리 프로젝트는 위치는 지금 어느정도에 있고. 어느 부분을 더 개선해야하는지 알려줘
## Decision
## Astra 판단 우리 프로젝트는 현재 **'고성능의 안정적인 실행 엔진'**을 구축하는 데 있어서는 매우 훌륭한 토대를 갖췄습니다. 하지만 '자비스(Jarvis)'와 같은 **완벽한 개인 비서**라는 궁극적인 목표에 도달하기 위해서는, 현재의 **'실행 능력(Execution Capability)'**을 넘어 **'능동적인 사고와 선제적 행동(Proactive Cognition)'** 영역으로 확장해야 합니다. ## 간단 요약 현재 프로젝트는 복잡한 작업을 안정적으로 처리하는 '엔진'을 성공적으로 만들었습니다. 자비스처럼 되기 위해서는 이 엔진이 단순히 명령을 수행하는 것을 넘어, 사용자의 의도를 **미리 예측하고(Predict), 맥락을 종합하여(Synthesize), 능동적으로 다음 행동을 제안**하는 '사고 능력'을 추가해야 합니다. ## 요청 요약 사용자님은 현재의 구현 상태를 기준으로, 궁극적인 목표인 '자비스형 툴'을 만들기 위해 현재 프로젝트가 어느 정도 위치에 있...
## Reason
Captured automatically because the conversation contained decision-oriented language.
## Alternatives
Not captured yet.
## Consequences
- Future prompts should treat this as project context unless the user changes direction.
+3
View File
@@ -30,3 +30,6 @@
## 2026-05-04
- Auto decision record created: decisions/ADR-0002-지금-너의-제2뇌-지식을-이용해서-아래-프로젝트-평가하고-앞으로-어느부분을-더-집중해서-개선을-하면-좋을지-.md
## 2026-05-04
- Auto decision record created: decisions/ADR-0003-volumes-data-project-antigravity-connectai-이-프로젝트-자체를-나는-자비스.md
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "astra",
"displayName": "Astra",
"description": "A local Jarvis-style project operating assistant for VS Code. Connects memory, project context, tools, and a single thinking-partner voice.",
"version": "2.63.0",
"version": "2.64.0",
"publisher": "connectailab",
"license": "MIT",
"icon": "assets/icon.png",
+3 -1
View File
@@ -63,12 +63,14 @@ export class QualityScorer {
// 3. 밀도/정보량 기반 점수 (최대 30점)
// 코드 블록이나 구체적 링크가 있으면 가산점
if (data.includes('```')) score += 15;
if (data.includes('[[') || data.includes('http')) score += 15;
if (data.includes('## 💻 실전 구현') || data.includes('Implementation')) score += 10;
if (data.includes('[[') || data.includes('http')) score += 5;
// 감점 요소: Placeholder 나 Empty 상태
if (data.includes('[Placeholder]') || data.includes('TODO')) {
score -= 20;
}
return Math.max(0, Math.min(100, score));
}
+30 -38
View File
@@ -416,8 +416,6 @@ export class CacheManager {
* - Error Recovery Matrix 기반의 Transient/Permanent 오류 자동 분류 및 복구
*/
export class AgentEngine {
private state: MissionState | null = null;
constructor(
private readonly planner: IAgent,
private readonly researcher: IAgent,
@@ -435,14 +433,15 @@ export class AgentEngine {
signal: AbortSignal,
onProgress: (stage: PipelineStage, message: string) => void
): Promise<string> {
let state: MissionState;
// 0. 상태 복원 시도 (Resumption)
const existingState = MissionState.loadFromDisk(missionId);
if (existingState && existingState.stage !== 'completed') {
logInfo(`[AgentEngine] 기존 미션 발견. '${existingState.stage}' 단계부터 재개합니다.`);
this.state = existingState;
state = existingState;
} else {
this.state = new MissionState(missionId);
state = new MissionState(missionId);
}
// 1. 명시적 락 획득 (Mutex) - 동일 미션의 중복 실행 방지
@@ -454,9 +453,9 @@ export class AgentEngine {
logInfo(`[AgentEngine] 미션 시작: ${missionId}`);
// --- Phase 1: Planner ---
let plan = this.state!.getResult('plan');
let plan = state.getResult('plan');
if (!plan) {
this.transition('planner', '전략 수립 중...', onProgress);
this.transition(state, 'planner', '전략 수립 중...', onProgress);
this.checkAbort(signal);
// Deduplication: 동일 프롬프트 캐시 확인
@@ -466,26 +465,26 @@ export class AgentEngine {
plan = cachedPlan;
} else {
plan = await this.resilientExecute(
this.planner, 'Planner', prompt, brainContext, signal, onProgress,
state, this.planner, 'Planner', prompt, brainContext, signal, onProgress,
{ context: brainContext, signal, config: { role: 'planner' } }
);
CacheManager.set(prompt, brainContext, plan);
}
this.state!.setResult('plan', plan);
state.setResult('plan', plan);
}
this.validateResult(plan, 'Planner');
// --- Phase 2 & 3: Researcher ---
let research = this.state!.getResult('research');
let writerPrep = this.state!.getResult('writerPrep');
let research = state.getResult('research');
let writerPrep = state.getResult('writerPrep');
if (!research || !writerPrep) {
this.transition('researcher', '핵심 정보 수집 및 분석 중...', onProgress);
this.transition(state, 'researcher', '핵심 정보 수집 및 분석 중...', onProgress);
this.checkAbort(signal);
const [res, prep] = await Promise.all([
research || this.resilientExecute(
this.researcher, 'Researcher', plan, brainContext, signal, onProgress,
state, this.researcher, 'Researcher', plan, brainContext, signal, onProgress,
{ context: brainContext, signal, config: { role: 'researcher' } }
),
writerPrep || this.prepareWriterContext(prompt, plan, brainContext)
@@ -493,16 +492,16 @@ export class AgentEngine {
research = res;
writerPrep = prep;
this.state!.setResult('research', research);
this.state!.setResult('writerPrep', writerPrep);
state.setResult('research', research);
state.setResult('writerPrep', writerPrep);
}
this.validateResult(research, 'Researcher');
// --- Phase 3: Writer ---
this.transition('writer', '최종 리포트 작성 및 편집 중...', onProgress);
this.transition(state, 'writer', '최종 리포트 작성 및 편집 중...', onProgress);
this.checkAbort(signal);
const finalReport = await this.resilientExecute(
this.writer, 'Writer', research, prompt, signal, onProgress,
state, this.writer, 'Writer', research, prompt, signal, onProgress,
{ context: brainContext, signal, config: { role: 'writer' }, priorResults: { plan, writerPrep } }
);
this.validateResult(finalReport, 'Writer');
@@ -510,22 +509,21 @@ export class AgentEngine {
// 3. 지식 저장 포맷 표준화 (Standardization: Astra 피드백)
const standardizedReport = WikiFormatter.format(finalReport, missionId);
this.transition('completed', '미션 완료', onProgress);
logInfo(`[AgentEngine] 미션 완료: ${missionId} (총 ${this.state!.getElapsedMs()}ms)`);
this.transition(state, 'completed', '미션 완료', onProgress);
logInfo(`[AgentEngine] 미션 완료: ${missionId} (총 ${state.getElapsedMs()}ms)`);
return standardizedReport;
});
} catch (error: any) {
const { type, rule } = ErrorClassifier.classify(error);
const stageName = (this.state?.stage || 'unknown').toUpperCase();
const stageName = (state!.stage || 'unknown').toUpperCase();
this.transition('error', `오류 발생: ${error.message}`, onProgress);
this.transition(state!, 'error', `오류 발생: ${error.message}`, onProgress);
// 실패 원인 명시적 기록 (Astra 피드백: Record failure reason)
if (this.state) {
this.state.setFailureReason(`[${type}] ${rule.description} - 세부원인: ${error.message}`);
}
state!.setFailureReason(`[${type}] ${rule.description} - 세부원인: ${error.message}`);
// Error Recovery Matrix 기반 세분화된 로깅
switch (type) {
@@ -541,27 +539,22 @@ export class AgentEngine {
}
// 감사 이력 덤프 (디버깅용)
if (this.state) {
logError(`[AgentEngine] Audit Trail for ${missionId}:\n${this.state.summarizeAudit()}`);
}
logError(`[AgentEngine] Audit Trail for ${missionId}:\n${state!.summarizeAudit()}`);
throw error;
} finally {
// 3. 락 해제
release();
if (this.state) {
this.state = null;
}
}
}
/**
* 현재 미션 상태 객체를 외부에서 읽을 수 있도록 노출합니다.
* 외부 모니터링 시스템 연동에 활용됩니다.
* @deprecated 이제 미션 상태는 로컬 스코프에서 관리됩니다.
*/
public getMissionState(): MissionState | null {
return this.state;
return null;
}
// ─── Resilience Layer ───
/**
@@ -572,6 +565,7 @@ export class AgentEngine {
* - Abort: 조용하게 예외를 전파 (Graceful Exit).
*/
private async resilientExecute(
state: MissionState,
agent: IAgent,
agentName: string,
input: string,
@@ -589,7 +583,7 @@ export class AgentEngine {
if (attempt > 0) {
const backoffMs = transientRule.backoffBaseMs * Math.pow(2, attempt - 1);
logInfo(`[AgentEngine] [RETRY] ${agentName} 재시도 ${attempt}/${transientRule.maxRetries} (${backoffMs}ms 후)`);
onProgress(this.state?.stage || 'error', `${agentName} 재시도 중... (${attempt}/${transientRule.maxRetries})`);
onProgress(state.stage, `${agentName} 재시도 중... (${attempt}/${transientRule.maxRetries})`);
await new Promise(r => setTimeout(r, backoffMs));
this.checkAbort(signal);
}
@@ -647,10 +641,8 @@ export class AgentEngine {
/**
* MissionState를 통한 상태 전환 + 외부 콜백 호출.
*/
private transition(stage: PipelineStage, message: string, onProgress: (stage: PipelineStage, message: string) => void) {
if (this.state) {
this.state.transition(stage, message);
}
private transition(state: MissionState, stage: PipelineStage, message: string, onProgress: (stage: PipelineStage, message: string) => void) {
state.transition(stage, message);
onProgress(stage, message);
}
+23
View File
@@ -32,6 +32,29 @@ export class WikiFormatter {
formatted = formatted.replace(/---\n\n/, `---\n\n${summarySection}`);
}
// 3. 코드 스니펫 섹션 보정 (Astra 피드백: 실전 구현 코드 강제)
const hasImplementationHeader = formatted.includes('## 💻 Practical Implementation') || formatted.includes('## 💻 실전 구현 코드');
if (!hasImplementationHeader) {
// 코드 블록이 이미 있다면 그 위에 헤더를 붙여줌
if (formatted.includes('```')) {
// 첫 번째 코드 블록 앞에 헤더 삽입
formatted = formatted.replace(/```/, '\n## 💻 실전 구현 코드\n\n```');
} else {
// 코드 블록이 전혀 없는 경우 하단에 플레이스홀더 추가 (추후 보강 유도)
const boilerplatePlaceholder = [
'',
'---',
'## 💻 실전 구현 코드 (Boilerplate)',
'> [!TIP]',
'> 이 문서의 개념을 즉시 적용할 수 있는 **실전 구현 코드**나 **보일러플레이트**가 아직 포함되지 않았습니다.',
'> 에이전트에게 "React 예제 코드 포함해줘" 또는 "MSA 구조도 코드로 표현해줘"와 같이 구체적인 구현체 생성을 요청하세요.',
'',
].join('\n');
formatted += boilerplatePlaceholder;
}
}
return formatted;
}
}