feat(formatter): enforce implementation code snippet section in wiki artifacts
This commit is contained in:
Vendored
+1
@@ -0,0 +1 @@
|
||||
Plan 9 passes the minimum validation requirement.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Plan 8 passes the minimum validation requirement.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Plan 7 passes the minimum validation requirement.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Plan 6 passes the minimum validation requirement.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Plan 5 passes the minimum validation requirement.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Plan output 4 that passes validation checks.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Plan output 3 that passes validation checks.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Plan output 2 that passes validation checks.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Plan output 1 that passes validation checks.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Plan output 0 that passes validation checks.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Slow but valid agent response for performance measurement.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Plan: detailed strategy for the mission ahead.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Plan output that meets validation requirements.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Recovery successful after transient failures.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Recovery successful after transient failures.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Plan result that meets the minimum validation length.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Recovery successful after transient failures.
|
||||
@@ -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` 기능 도입.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
+19
@@ -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.
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user