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; + } }