From 1395bbcdae4e8a3894978c7d3c565920959e3a55 Mon Sep 17 00:00:00 2001 From: g1nation Date: Mon, 4 May 2026 15:58:47 +0900 Subject: [PATCH] feat(formatter): enforce implementation code snippet section in wiki artifacts --- .astra/cache/-heofpy.cache | 1 + .astra/cache/-hep2ph.cache | 1 + .astra/cache/-heppp0.cache | 1 + .astra/cache/-heqcoj.cache | 1 + .astra/cache/-heqzo2.cache | 1 + .astra/cache/-hermnl.cache | 1 + .astra/cache/-hes9n4.cache | 1 + .astra/cache/-heswmn.cache | 1 + .astra/cache/-hetjm6.cache | 1 + .astra/cache/-heu6lp.cache | 1 + .astra/cache/-xqp6ww.cache | 1 + .astra/cache/-zay2tp.cache | 1 + .astra/cache/4077zp.cache | 1 + .astra/cache/7ss1y5.cache | 1 + .astra/cache/acia7f.cache | 1 + .astra/cache/drpmmq.cache | 1 + .astra/cache/ue9kx6.cache | 1 + PATCHNOTES.md | 10 +++ docs/records/ConnectAI/chronicle.config.json | 4 +- ...onnectai-이-프로젝트-자체를-나는-자비스.md | 19 ++++++ docs/records/ConnectAI/timeline.md | 3 + package.json | 2 +- src/lib/diagnostics.ts | 4 +- src/lib/engine.ts | 68 ++++++++----------- src/lib/formatter.ts | 23 +++++++ 25 files changed, 108 insertions(+), 42 deletions(-) create mode 100644 .astra/cache/-heofpy.cache create mode 100644 .astra/cache/-hep2ph.cache create mode 100644 .astra/cache/-heppp0.cache create mode 100644 .astra/cache/-heqcoj.cache create mode 100644 .astra/cache/-heqzo2.cache create mode 100644 .astra/cache/-hermnl.cache create mode 100644 .astra/cache/-hes9n4.cache create mode 100644 .astra/cache/-heswmn.cache create mode 100644 .astra/cache/-hetjm6.cache create mode 100644 .astra/cache/-heu6lp.cache create mode 100644 .astra/cache/-xqp6ww.cache create mode 100644 .astra/cache/-zay2tp.cache create mode 100644 .astra/cache/4077zp.cache create mode 100644 .astra/cache/7ss1y5.cache create mode 100644 .astra/cache/acia7f.cache create mode 100644 .astra/cache/drpmmq.cache create mode 100644 .astra/cache/ue9kx6.cache create mode 100644 docs/records/ConnectAI/decisions/ADR-0003-volumes-data-project-antigravity-connectai-이-프로젝트-자체를-나는-자비스.md diff --git a/.astra/cache/-heofpy.cache b/.astra/cache/-heofpy.cache new file mode 100644 index 0000000..63b4b47 --- /dev/null +++ b/.astra/cache/-heofpy.cache @@ -0,0 +1 @@ +Plan 9 passes the minimum validation requirement. \ No newline at end of file diff --git a/.astra/cache/-hep2ph.cache b/.astra/cache/-hep2ph.cache new file mode 100644 index 0000000..ab1d3c2 --- /dev/null +++ b/.astra/cache/-hep2ph.cache @@ -0,0 +1 @@ +Plan 8 passes the minimum validation requirement. \ No newline at end of file diff --git a/.astra/cache/-heppp0.cache b/.astra/cache/-heppp0.cache new file mode 100644 index 0000000..edd8849 --- /dev/null +++ b/.astra/cache/-heppp0.cache @@ -0,0 +1 @@ +Plan 7 passes the minimum validation requirement. \ No newline at end of file diff --git a/.astra/cache/-heqcoj.cache b/.astra/cache/-heqcoj.cache new file mode 100644 index 0000000..9a38dbb --- /dev/null +++ b/.astra/cache/-heqcoj.cache @@ -0,0 +1 @@ +Plan 6 passes the minimum validation requirement. \ No newline at end of file diff --git a/.astra/cache/-heqzo2.cache b/.astra/cache/-heqzo2.cache new file mode 100644 index 0000000..b09479a --- /dev/null +++ b/.astra/cache/-heqzo2.cache @@ -0,0 +1 @@ +Plan 5 passes the minimum validation requirement. \ No newline at end of file diff --git a/.astra/cache/-hermnl.cache b/.astra/cache/-hermnl.cache new file mode 100644 index 0000000..33a3f99 --- /dev/null +++ b/.astra/cache/-hermnl.cache @@ -0,0 +1 @@ +Plan output 4 that passes validation checks. \ No newline at end of file diff --git a/.astra/cache/-hes9n4.cache b/.astra/cache/-hes9n4.cache new file mode 100644 index 0000000..6e9a41e --- /dev/null +++ b/.astra/cache/-hes9n4.cache @@ -0,0 +1 @@ +Plan output 3 that passes validation checks. \ No newline at end of file diff --git a/.astra/cache/-heswmn.cache b/.astra/cache/-heswmn.cache new file mode 100644 index 0000000..f8c6428 --- /dev/null +++ b/.astra/cache/-heswmn.cache @@ -0,0 +1 @@ +Plan output 2 that passes validation checks. \ No newline at end of file diff --git a/.astra/cache/-hetjm6.cache b/.astra/cache/-hetjm6.cache new file mode 100644 index 0000000..2e482fe --- /dev/null +++ b/.astra/cache/-hetjm6.cache @@ -0,0 +1 @@ +Plan output 1 that passes validation checks. \ No newline at end of file diff --git a/.astra/cache/-heu6lp.cache b/.astra/cache/-heu6lp.cache new file mode 100644 index 0000000..c6dc219 --- /dev/null +++ b/.astra/cache/-heu6lp.cache @@ -0,0 +1 @@ +Plan output 0 that passes validation checks. \ No newline at end of file diff --git a/.astra/cache/-xqp6ww.cache b/.astra/cache/-xqp6ww.cache new file mode 100644 index 0000000..38e06f0 --- /dev/null +++ b/.astra/cache/-xqp6ww.cache @@ -0,0 +1 @@ +Slow but valid agent response for performance measurement. \ No newline at end of file diff --git a/.astra/cache/-zay2tp.cache b/.astra/cache/-zay2tp.cache new file mode 100644 index 0000000..b32ba8f --- /dev/null +++ b/.astra/cache/-zay2tp.cache @@ -0,0 +1 @@ +Plan: detailed strategy for the mission ahead. \ No newline at end of file diff --git a/.astra/cache/4077zp.cache b/.astra/cache/4077zp.cache new file mode 100644 index 0000000..bdb35de --- /dev/null +++ b/.astra/cache/4077zp.cache @@ -0,0 +1 @@ +Plan output that meets validation requirements. \ No newline at end of file diff --git a/.astra/cache/7ss1y5.cache b/.astra/cache/7ss1y5.cache new file mode 100644 index 0000000..10ffc78 --- /dev/null +++ b/.astra/cache/7ss1y5.cache @@ -0,0 +1 @@ +Recovery successful after transient failures. \ No newline at end of file diff --git a/.astra/cache/acia7f.cache b/.astra/cache/acia7f.cache new file mode 100644 index 0000000..10ffc78 --- /dev/null +++ b/.astra/cache/acia7f.cache @@ -0,0 +1 @@ +Recovery successful after transient failures. \ No newline at end of file diff --git a/.astra/cache/drpmmq.cache b/.astra/cache/drpmmq.cache new file mode 100644 index 0000000..f7acfc9 --- /dev/null +++ b/.astra/cache/drpmmq.cache @@ -0,0 +1 @@ +Plan result that meets the minimum validation length. \ No newline at end of file diff --git a/.astra/cache/ue9kx6.cache b/.astra/cache/ue9kx6.cache new file mode 100644 index 0000000..10ffc78 --- /dev/null +++ b/.astra/cache/ue9kx6.cache @@ -0,0 +1 @@ +Recovery successful after transient failures. \ No newline at end of file diff --git a/PATCHNOTES.md b/PATCHNOTES.md index 4930ff7..34febe3 100644 --- a/PATCHNOTES.md +++ b/PATCHNOTES.md @@ -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` 기능 도입. diff --git a/docs/records/ConnectAI/chronicle.config.json b/docs/records/ConnectAI/chronicle.config.json index fb8734e..cde817f 100644 --- a/docs/records/ConnectAI/chronicle.config.json +++ b/docs/records/ConnectAI/chronicle.config.json @@ -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" } diff --git a/docs/records/ConnectAI/decisions/ADR-0003-volumes-data-project-antigravity-connectai-이-프로젝트-자체를-나는-자비스.md b/docs/records/ConnectAI/decisions/ADR-0003-volumes-data-project-antigravity-connectai-이-프로젝트-자체를-나는-자비스.md new file mode 100644 index 0000000..b4b4922 --- /dev/null +++ b/docs/records/ConnectAI/decisions/ADR-0003-volumes-data-project-antigravity-connectai-이-프로젝트-자체를-나는-자비스.md @@ -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. diff --git a/docs/records/ConnectAI/timeline.md b/docs/records/ConnectAI/timeline.md index 7f5ddf1..97f4cae 100644 --- a/docs/records/ConnectAI/timeline.md +++ b/docs/records/ConnectAI/timeline.md @@ -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 diff --git a/package.json b/package.json index 96ea184..bcc2208 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/lib/diagnostics.ts b/src/lib/diagnostics.ts index 98aa1c4..debb1a5 100644 --- a/src/lib/diagnostics.ts +++ b/src/lib/diagnostics.ts @@ -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)); } diff --git a/src/lib/engine.ts b/src/lib/engine.ts index 68b560b..14f1439 100644 --- a/src/lib/engine.ts +++ b/src/lib/engine.ts @@ -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 { - + 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); } diff --git a/src/lib/formatter.ts b/src/lib/formatter.ts index be18b25..2a46335 100644 --- a/src/lib/formatter.ts +++ b/src/lib/formatter.ts @@ -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; + } }