wiki: Topic_Blog 신규 문서 일괄 추가 + ASTRA 성장 자산 동기화
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
---
|
||||
id: connectai-architecture-overview
|
||||
title: "ConnectAI 아키텍처 개요"
|
||||
category: "Architecture"
|
||||
status: "draft"
|
||||
verification_status: "applied"
|
||||
canonical_id: ""
|
||||
aliases: ["Astra", "ConnectAI", "feature-based architecture", "전체 구조", "모듈 아키텍처", "제2뇌 OS"]
|
||||
duplicate_of: ""
|
||||
source_trust_level: "S"
|
||||
confidence_score: 0.95
|
||||
created_at: 2026-06-13
|
||||
updated_at: 2026-06-13
|
||||
review_reason: ""
|
||||
merge_history: []
|
||||
tags: ["architecture", "connectai", "feature-based", "layering", "overview"]
|
||||
raw_sources: ["ConnectAI/src/extension.ts", "ConnectAI/src 트리(308 TS 파일)", "ConnectAI/package.json", "ConnectAI/src/core/services.ts"]
|
||||
applied_in: ["ConnectAI"]
|
||||
github_commit: ""
|
||||
---
|
||||
|
||||
# [[ConnectAI 아키텍처 개요]]
|
||||
|
||||
## 🎯 한 줄 통찰 (One-line insight)
|
||||
ConnectAI(Astra)는 "로컬 LLM 을 두뇌로 쓰는 VS Code 확장형 자기진화 지식 OS"이며, **기능별 폴더 경계 + 얇은 entry point + 핵심 인프라(core) 위에 도메인(features)을 쌓는 계층형 모듈 아키텍처**로 308개 파일을 조직한다 [S1][S2].
|
||||
|
||||
## 🧠 핵심 개념 (Core concepts)
|
||||
1. **Feature-based 구조:** `src/features/` 아래 각 폴더가 독립 기능(stocks, calendar, company, datacollect, devilAgent…). 기능 간 결합을 최소화하고, 공통 인프라만 `core/`/`lib/` 에서 공유 [S2].
|
||||
2. **계층 분리:** `core`(인프라) → `lib`/`memory`/`retrieval`/`intelligence`(역량) → `features`(기능) → `extension.ts`(조립). 아래 계층은 위를 모른다 [S2].
|
||||
3. **얇은 entry point:** `extension.ts` 의 `activate()` 는 *조립과 등록만* 한다 — 객체 생성, 의존성 주입, 명령 등록, 워처 시작. 로직은 전부 모듈에 위임 [S1].
|
||||
4. **의존성 주입:** 생성자에 옵션 객체로 협력자를 주입(`new AgentExecutor(context, { lmStudioStreamer, approvalQueue, ... })`) — 테스트·교체 용이 [S1].
|
||||
5. **싱글톤 인프라:** 프로세스 전역이 자연스러운 자원(`lockManager`, `actionQueue`)은 모듈 싱글톤으로 export [S1].
|
||||
|
||||
## 🧩 추출된 패턴 (Extracted patterns)
|
||||
- **God-class 분해:** 거대해지는 orchestrator(`agent.ts`)를 `handlePrompt/`, `llm/`, `actions/`, `multiAgent/`, `sessions/` 하위 모듈로 쪼개 함수 단위로 추출하고, orchestrator 는 이들을 호출만 한다 [참조: [[Agent 오케스트레이터 분해]]].
|
||||
- **인터페이스 우선 서비스:** `IAIService`/`IBrainService` 인터페이스를 두고 구현(`AIService`)을 분리 — 엔진 폴백 같은 정책을 구현체에 캡슐화 [S3].
|
||||
- **자기등록 핸들러:** slashRouter 에 핸들러가 side-effect import 로 자기 등록 — 새 명령 추가 시 중앙 등록표를 건드리지 않음 [S1].
|
||||
- **부트스트랩 분리:** 활성화 시 필요한 초기화(brain 디렉터리, 임베딩 감지, 기능 인벤토리)를 `extension/*Bootstrap.ts` 로 분리해 entry point 를 얇게 유지 [S1].
|
||||
- **disposable 수명 관리:** 생성한 모든 자원을 `context.subscriptions.push(...)` 로 등록해 확장 종료 시 일괄 정리 [S1].
|
||||
|
||||
## 📖 세부 내용 (Details)
|
||||
### 디렉터리 지도 (역할별)
|
||||
- **core/** — 횡단 인프라: `lock`, `queue`, `transaction`, `errors`, `errorHandler`, `services`(AI/Brain 서비스), `session`, `events`, `health`, `telemetry`, `statusBar`.
|
||||
- **memory/** — 5계층 인지 메모리(Short/Long/Project/Procedural/Episodic) + `distillation`. → [[5계층 메모리 시스템]].
|
||||
- **retrieval/** — RAG 파이프라인: `chunker`, `scoring`(TF-IDF), `embeddings`, `brainIndex`, `contextBudget`, rerank 류. → [[RAG 검색 파이프라인]].
|
||||
- **intelligence/** — 검증·자기평가: `criticAgent`, `confidenceEngine`, `correctionLoop`, `epistemicGuardBlock`, `knowledgeValidation`. → [[Intelligence 검증 레이어]].
|
||||
- **lib/contextBuilders/** — 프롬프트에 들어갈 컨텍스트 블록을 조립하는 순수 함수 모음(메모리·프로젝트·일정·자기인식 등 30+개).
|
||||
- **features/** — 도메인 기능. 각자 `index.ts`(공개 API) + store + handler + prompt 로 구성되는 경향.
|
||||
- **agent/** + **agents/** — 에이전트 실행 세부와 다중 에이전트 워크플로 매니저.
|
||||
- **integrations/** — 외부 연동(telegram).
|
||||
- **sidebar/** + **sidebarProvider.ts** — 웹뷰 UI 와 메시지 핸들러.
|
||||
- **system/**, **lmstudio/**, **scaffolder/**, **skills/** — 시스템 사양 탐지, LM Studio SDK 수명관리, 프로젝트 스캐폴딩, 스킬 로딩.
|
||||
|
||||
### 제어 흐름 (한 턴의 처리)
|
||||
1. 웹뷰(사이드바)에서 사용자 입력 → `sidebar/chatHandlers` → `AgentExecutor`.
|
||||
2. `agent/handlePrompt/*` 가 컨텍스트 블록(메모리·RAG·프로젝트·일정)을 조립.
|
||||
3. 모델 라우팅: 클라우드 prefix 면 provider 어댑터, 아니면 로컬 엔진(LM Studio/Ollama). → [[LLM 프로바이더 추상화]].
|
||||
4. 스트리밍 응답 → 후처리(sanitize, devil rebuttal) → post-answer hooks(critic 검수, 교정 캡처).
|
||||
5. 세션 종료 시 메모리 추출/증류, 성장 사이클 워처가 폐루프로 학습.
|
||||
|
||||
### 빌드/런타임
|
||||
- esbuild 단일 번들(`out/extension.js`), `vscode` external. 런타임 의존성은 `@lmstudio/sdk`, `pdf-parse` 둘뿐 — 나머지는 native API(fetch 등)로 자급 [S2].
|
||||
|
||||
## ⚖️ 모순 및 업데이트 (Contradictions & updates)
|
||||
- **단일 거대 agent.ts(1681줄):** 분해를 했음에도 orchestrator 가 여전히 크다. 이는 "한 턴의 제어 흐름을 한 곳에서 읽을 수 있게" 하려는 의도적 트레이드오프 — 세부는 추출하되 흐름의 골격은 남긴다.
|
||||
- **features 간 결합:** 일부 기능(company, stocks)은 텔레그램·시트 등 여러 통합을 동시에 참조해 완전 독립은 아니다. core/lib 를 통한 간접 결합으로 완화.
|
||||
|
||||
## 🛠️ 적용 사례 (Applied in summary)
|
||||
- `ConnectAI/src/extension.ts` 의 `activate()` 전체가 "얇은 조립 entry point" 의 교과서적 예 — 25개+ 모듈을 생성·주입·등록하지만 비즈니스 로직은 0 [S1].
|
||||
|
||||
## 💻 코드 패턴 (Code patterns)
|
||||
```typescript
|
||||
// activate(): 조립과 등록만 — 로직은 모듈에 위임 (src/extension.ts 발췌)
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
await ensureDefaultBrainConfigured(); // 부트스트랩 분리
|
||||
void ensureEmbeddingConfigured(context); // 비차단 best-effort
|
||||
initAstraPathResolver(context);
|
||||
|
||||
const lmStudioClient = new LMStudioClient(getConfig().ollamaUrl);
|
||||
const agent = new AgentExecutor(context, { // 생성자 의존성 주입
|
||||
onStreamLifecycle: { start: () => lifecycle.onStreamStart(), end: () => lifecycle.onStreamEnd() },
|
||||
lmStudioStreamer, approvalQueue,
|
||||
});
|
||||
const provider = new SidebarChatProvider(context.extensionUri, context, agent, { lifecycle, /* ... */ });
|
||||
|
||||
context.subscriptions.push( // 수명 관리: 종료 시 일괄 dispose
|
||||
vscode.commands.registerCommand('g1nation.openChat', () => provider.openAsPanel(vscode.ViewColumn.Three)),
|
||||
...registerProviderCommands(context, { getProvider: () => provider }),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 🔗 지식 그래프 (Knowledge Graph)
|
||||
- **상위/루트:** [[ConnectAI 아키텍처 개요]]
|
||||
- **관련 개념:** [[VSCode 확장 구조와 생명주기]], [[의존성 주입과 서비스 인터페이스]], [[Agent 오케스트레이터 분해]], [[모듈 시스템과 프로젝트 구성]]
|
||||
- **참조 맥락:** 로컬 LLM 이 새 기능을 "어느 계층/폴더에 어떤 형태로" 추가할지 판단할 때 최상위 지도로 참조.
|
||||
|
||||
## 📚 출처 (Sources)
|
||||
- [S1] ConnectAI/src/extension.ts — activate/deactivate, 조립·주입·등록·부트스트랩
|
||||
- [S2] ConnectAI/src 디렉터리 트리(308 TS 파일) + package.json — 계층/번들 구성
|
||||
- [S3] ConnectAI/src/core/services.ts — IAIService/IBrainService 인터페이스 우선 설계
|
||||
|
||||
## 📝 변경 이력 (Change history)
|
||||
- 2026-06-13: ConnectAI 전체 코드 분석 기반 초안 생성.
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
id: vscode-extension-structure-lifecycle
|
||||
title: "VSCode 확장 구조와 생명주기"
|
||||
category: "Architecture"
|
||||
status: "draft"
|
||||
verification_status: "applied"
|
||||
canonical_id: ""
|
||||
aliases: ["VS Code Extension", "activate", "deactivate", "Disposable", "Webview", "command 등록", "extension API"]
|
||||
duplicate_of: ""
|
||||
source_trust_level: "A"
|
||||
confidence_score: 0.93
|
||||
created_at: 2026-06-13
|
||||
updated_at: 2026-06-13
|
||||
review_reason: ""
|
||||
merge_history: []
|
||||
tags: ["vscode", "extension", "lifecycle", "disposable", "webview", "connectai"]
|
||||
raw_sources: ["ConnectAI/src/extension.ts", "ConnectAI/package.json", "ConnectAI/src/features/_shared/eventSourcedStore.ts"]
|
||||
applied_in: ["ConnectAI"]
|
||||
github_commit: ""
|
||||
---
|
||||
|
||||
# [[VSCode 확장 구조와 생명주기]]
|
||||
|
||||
## 🎯 한 줄 통찰 (One-line insight)
|
||||
VS Code 확장은 `activate(context)` 에서 깨어나 명령·뷰·이벤트를 등록하고 `deactivate()` 에서 정리되며, 모든 자원을 **`context.subscriptions` 에 Disposable 로 등록**해 두면 확장 종료 시 메모리/타이머/리스너가 누수 없이 일괄 정리된다 [S1].
|
||||
|
||||
## 🧠 핵심 개념 (Core concepts)
|
||||
1. **`activate(context)`:** 확장 진입점. `package.json` 의 activationEvents 조건이 충족되면 호출된다. `context: ExtensionContext` 가 전역 상태·구독·시크릿 저장소를 제공 [S1].
|
||||
2. **`deactivate()`:** 종료 훅. 명시적으로 풀어야 하는 자원(외부 프로세스, 봇, 모델 언로드)을 여기서 정리 [S1].
|
||||
3. **Disposable 패턴:** `{ dispose(): void }` 를 가진 객체. `context.subscriptions.push(disposable)` 하면 VS Code 가 종료 시 `dispose()` 를 호출 [S1].
|
||||
4. **Command:** `vscode.commands.registerCommand(id, handler)` 로 등록, `package.json` 의 `contributes.commands` 에 선언. 반환값이 Disposable [S1].
|
||||
5. **Webview:** HTML 기반 커스텀 UI. 확장↔웹뷰는 `postMessage` 로 통신. ConnectAI 는 사이드바 대신 *에디터 컬럼* 에 패널로 띄운다 [S1].
|
||||
6. **Configuration:** `vscode.workspace.getConfiguration('g1nation')` 으로 설정 읽기, `onDidChangeConfiguration` 으로 변경 반응 [S1].
|
||||
7. **SecretStorage:** 토큰 등 민감정보는 `context.secrets` 에 저장(평문 설정 금지) [S1].
|
||||
|
||||
## 🧩 추출된 패턴 (Extracted patterns)
|
||||
- **모든 자원을 subscriptions 로:** 명령·리스너·상태바·타이머·커스텀 dispose 콜백까지 전부 `context.subscriptions.push(...)` — 정리를 잊지 않는 구조적 보장 [S1].
|
||||
- **설정 변경 반응:** `onDidChangeConfiguration((e) => { if (!e.affectsConfiguration('g1nation.ollamaUrl')) return; ... })` — 관심 키만 필터링해 재설정 [S1].
|
||||
- **lazy webview 전송:** 매우 이른 활성화 시점엔 웹뷰가 아직 없을 수 있어 `provider?._sendModels(...)` 옵셔널 체이닝 + best-effort [S1].
|
||||
- **getter 콜백으로 늦은 바인딩:** 아직 안 만들어진 객체는 `getProvider: () => provider` 처럼 getter 로 주입 — 순환 의존/초기화 순서 문제 회피 [S1].
|
||||
- **워처 = disposable 반환 함수:** `startStocksWatcher(context)` 가 타이머를 만들고 disposable 을 반환해 subscriptions 에 등록 → 종료 시 timer cleanup [S1].
|
||||
|
||||
## 📖 세부 내용 (Details)
|
||||
### activate 의 책임 순서 (ConnectAI 실제 순서)
|
||||
1. 가시화(버전 popup/console) → 환경 진단.
|
||||
2. 부트스트랩(brain 디렉터리, 임베딩 감지, 기능 인벤토리) — 일부는 `void` 비차단.
|
||||
3. 인프라 초기화(health monitor, path resolver, config 검증).
|
||||
4. 핵심 객체 생성·주입(LMStudioClient, lifecycle, AgentExecutor, SidebarChatProvider, BridgeServer).
|
||||
5. 명령·리스너 등록.
|
||||
6. 워처 시작(stocks, daily briefing, growth cycle, sleep digest).
|
||||
7. 웹뷰 자동 오픈.
|
||||
|
||||
### deactivate 의 책임
|
||||
`HealthCheckMonitor.dispose()`, 인메모리 인덱스 해제(`clearBrainTokenIndex()` — Map 이 프로세스 수명 동안 안 비는 것 방지), 텔레그램 봇 stop, 모델 lifecycle dispose+unload. *명시적 정리가 필요한 것만* 여기서 처리하고, subscriptions 로 등록된 것은 VS Code 가 알아서 정리한다 [S1].
|
||||
|
||||
### 설정·시크릿
|
||||
```typescript
|
||||
const cfg = vscode.workspace.getConfiguration('g1nation');
|
||||
const timeout = cfg.get<number>('lmStudio.idleTimeoutMs', 300000); // 기본값 동반
|
||||
const token = await context.secrets.get(TELEGRAM_TOKEN_SECRET_KEY); // 민감정보는 secrets
|
||||
```
|
||||
|
||||
### Webview 통신
|
||||
확장→웹뷰는 `webview.postMessage({ type: 'streamChunk', value })`, 웹뷰→확장은 메시지 핸들러(`sidebar/chatHandlers`). 스트리밍은 `streamStart`/`streamChunk`/`streamEnd` 의 메시지 프로토콜로 표현 [참조: [[Agent 오케스트레이터 분해]]].
|
||||
|
||||
## ⚖️ 모순 및 업데이트 (Contradictions & updates)
|
||||
- **사이드바 뷰 제거(v2.81):** 과거엔 activity bar 사이드바에 webview view 를 등록했으나, 현재는 에디터 컬럼 3에 패널로 띄운다. 대신 activity bar 에는 명령 링크만 있는 빈 TreeView 를 둔다 — 탭이 닫혀도 다시 열 수 있게 [S1].
|
||||
- **activationEvents:** 너무 광범위하면(예: `*`) 시작이 느려진다. 필요한 이벤트로 좁히는 것이 좋다(ConnectAI 는 명령/뷰 기반).
|
||||
|
||||
## 🛠️ 적용 사례 (Applied in summary)
|
||||
- `ConnectAI/src/extension.ts` — activate/deactivate 의 전 과정, subscriptions 등록, 설정 반응, secrets 사용, 워처 등록이 모두 한 파일에 [S1].
|
||||
- `ConnectAI/src/features/_shared/eventSourcedStore.ts` — `vscode.workspace.workspaceFolders` 로 워크스페이스 경로 해석(확장에서 파일 경로 다루는 표준 방식) [S3].
|
||||
|
||||
## 💻 코드 패턴 (Code patterns)
|
||||
```typescript
|
||||
// 1) 명령 등록 → 반환된 Disposable 을 subscriptions 로 (src/extension.ts)
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('g1nation.clearChat', () => provider.clearChat()),
|
||||
);
|
||||
|
||||
// 2) 설정 변경 반응 — 관심 키만 (src/extension.ts)
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeConfiguration((e) => {
|
||||
if (!e.affectsConfiguration('g1nation.ollamaUrl')) return;
|
||||
const newUrl = vscode.workspace.getConfiguration('g1nation').get<string>('ollamaUrl', '');
|
||||
lmStudioClient.setBaseUrl(newUrl);
|
||||
}),
|
||||
);
|
||||
|
||||
// 3) 커스텀 dispose 콜백도 disposable 로 등록
|
||||
context.subscriptions.push({ dispose: () => activityTracker.dispose() });
|
||||
|
||||
// 4) getter 콜백으로 늦은 바인딩 (순환/초기화 순서 회피)
|
||||
const telegramBot = createTelegramBot(context, { telegramClient, getProvider: () => provider });
|
||||
|
||||
// 5) deactivate — 명시 정리가 필요한 것만
|
||||
export async function deactivate() {
|
||||
HealthCheckMonitor.dispose();
|
||||
clearBrainTokenIndex();
|
||||
if (_telegramBot) { try { await _telegramBot.stop(); } catch {} }
|
||||
}
|
||||
```
|
||||
|
||||
## ✅ 검증 상태 및 신뢰도
|
||||
- **상태:** draft
|
||||
- **검증 단계:** applied
|
||||
- **출처 신뢰도:** A
|
||||
- **신뢰 점수:** 0.93
|
||||
- **중복 검사 결과:** 신규 생성 (New discovery)
|
||||
|
||||
## 🔗 지식 그래프 (Knowledge Graph)
|
||||
- **상위/루트:** [[ConnectAI 아키텍처 개요]]
|
||||
- **관련 개념:** [[의존성 주입과 서비스 인터페이스]], [[모듈 시스템과 프로젝트 구성]], [[동시성 제어 Lock Queue Transaction]]
|
||||
- **참조 맥락:** 로컬 LLM 이 VS Code 확장의 명령/뷰/설정/수명관리 코드를 작성·수정할 때 참조.
|
||||
|
||||
## 📚 출처 (Sources)
|
||||
- [S1] ConnectAI/src/extension.ts — activate/deactivate, subscriptions, 명령/설정/시크릿/웹뷰/워처
|
||||
- [S2] ConnectAI/package.json — engines.vscode, main, contributes(명령/뷰)
|
||||
- [S3] ConnectAI/src/features/_shared/eventSourcedStore.ts — workspaceFolders 경로 해석
|
||||
|
||||
## 📝 변경 이력 (Change history)
|
||||
- 2026-06-13: ConnectAI 코드 분석 기반 초안 생성.
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
id: concurrency-lock-queue-transaction
|
||||
title: "동시성 제어 Lock Queue Transaction"
|
||||
category: "Architecture"
|
||||
status: "draft"
|
||||
verification_status: "applied"
|
||||
canonical_id: ""
|
||||
aliases: ["concurrency", "AsyncLock", "mutex", "race condition", "동시성", "큐", "concurrency limit", "트랜잭션"]
|
||||
duplicate_of: ""
|
||||
source_trust_level: "A"
|
||||
confidence_score: 0.94
|
||||
created_at: 2026-06-13
|
||||
updated_at: 2026-06-13
|
||||
review_reason: ""
|
||||
merge_history: []
|
||||
tags: ["architecture", "concurrency", "lock", "queue", "transaction", "connectai"]
|
||||
raw_sources: ["ConnectAI/src/core/lock.ts", "ConnectAI/src/core/queue.ts", "ConnectAI/src/core/transaction.ts"]
|
||||
applied_in: ["ConnectAI"]
|
||||
github_commit: ""
|
||||
---
|
||||
|
||||
# [[동시성 제어 Lock Queue Transaction]]
|
||||
|
||||
## 🎯 한 줄 통찰 (One-line insight)
|
||||
단일 스레드 JavaScript 도 `await` 사이에 다른 작업이 끼어들어 *경쟁 상태(race condition)* 가 생기며, ConnectAI 는 **자원별 비동기 락(AsyncLock)·동시성 제한 큐·파일 보상 트랜잭션** 세 가지로 "한 번에 하나만·자원을 폭주시키지 않고·실패하면 되돌리는" 동시성 안전을 확보한다 [S1][S2][S3].
|
||||
|
||||
## 🧠 핵심 개념 (Core concepts)
|
||||
1. **JS 의 동시성:** 단일 스레드라도 `await` 지점에서 제어가 넘어가므로, 같은 파일을 동시에 읽고-수정-쓰면 갱신 손실이 난다. 직렬화가 필요 [S1].
|
||||
2. **비동기 뮤텍스 (AsyncLock):** 자원 ID 별로 "이전 작업이 끝나야 다음이 시작"되는 Promise 체인. `acquire` 가 release 함수를 반환, `try/finally` 로 반드시 해제 [S1].
|
||||
3. **데드락 방지 타임아웃:** 락 대기를 `Promise.race([previous, timeout])` 로 감싸 무한 대기 차단 [S1].
|
||||
4. **동시성 제한 큐:** 동시에 실행되는 작업 수를 `max(2, cpus-1)` 로 제한해 I/O·메모리 폭주 방지 [S2].
|
||||
5. **보상 트랜잭션:** 파일시스템에 트랜잭션이 없으므로, 변경 전 백업→실패 시 복원으로 원자성을 흉내 [S3].
|
||||
|
||||
## 🧩 추출된 패턴 (Extracted patterns)
|
||||
- **고유 토큰으로 안전한 정리:** 각 락 entry 에 `Symbol` 토큰을 부여하고, cleanup/release 시 "내 토큰이 아직 Map 의 최신인지" 확인 후에만 삭제 — 연쇄 호출 시 다른 작업 entry 를 지우는 race 방지 [S1].
|
||||
- **release 는 반드시 try/finally:** `const release = await lock.acquire(id); try { ... } finally { release(); }` — 예외가 나도 락이 풀리게 [S1].
|
||||
- **enqueue 가 Promise 반환:** `enqueue<T>(task)` 가 작업 결과 Promise 를 돌려주되 실행은 슬롯이 빌 때 — 호출부는 평소처럼 await [S2].
|
||||
- **micro-delay 로 숨통:** 무거운 I/O 사이 `await sleep(10)` 로 시스템에 여유 [S2].
|
||||
- **begin/record/commit/rollback:** 변경 전 `record(파일)` 로 백업, 성공 `commit`(백업 폐기), 실패 `rollback`(원복) [S3].
|
||||
|
||||
## 📖 세부 내용 (Details)
|
||||
### AsyncLock 의 핵심 — Promise 체인 + 토큰
|
||||
```typescript
|
||||
const previousPromise = this.locks.get(resourceId)?.promise ?? Promise.resolve();
|
||||
const token = Symbol(`lock:${resourceId}`);
|
||||
let release!: () => void;
|
||||
const newPromise = new Promise<void>((resolve) => { release = resolve; });
|
||||
this.locks.set(resourceId, { promise: previousPromise.then(() => newPromise), token });
|
||||
await Promise.race([previousPromise, timeoutPromise]); // 앞 작업 끝날 때까지(타임아웃 보호)
|
||||
return () => { release(); if (this.locks.get(resourceId)?.token === token) this.locks.delete(resourceId); };
|
||||
```
|
||||
**버그 사후기록(주석)**: 옛 구현은 `.then(...)` 이 매번 새 Promise 를 반환해 동일성 비교가 *항상 false* → cleanup 실패. 또 release 시 무조건 `delete` 해서 연쇄 호출 시 다른 작업 entry 를 지우는 race. → 각 entry 에 고유 symbol 을 부여하고 "내 토큰이 최신일 때만" 정리하도록 수정 [S1].
|
||||
|
||||
### 동시성 제한 큐
|
||||
```typescript
|
||||
function defaultConcurrencyLimit() { return Math.max(2, (os.cpus()?.length ?? 4) - 1); } // UI 스레드 여유
|
||||
public async enqueue<T>(task: () => Promise<T>): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.queue.push(async () => { try { resolve(await task()); } catch (e) { reject(e); } });
|
||||
this.processNext();
|
||||
});
|
||||
}
|
||||
```
|
||||
`processNext` 는 `activeCount < limit` 일 때만 다음 작업을 꺼내 실행하고, `finally` 에서 `activeCount--` 후 재귀로 다음을 당긴다. 무거운 LLM 호출은 큐가 아니라 `missionId` 락으로 직렬화하므로 큐는 단순하게 유지 [S2].
|
||||
|
||||
### 보상 트랜잭션 (파일 원자성)
|
||||
`isTransactionActive` 가드로 중복 begin 방지. `record` 는 같은 파일을 한 번만 백업(이미 있으면 skip). rollback 은 `created` 파일은 삭제, `modified` 는 원본 내용으로 복원. 외부 API 호출 성공 여부도 `recordExternalAction` 으로 추적해 `isFullyVerified()` 로 전체 검증 [S3].
|
||||
|
||||
## ⚖️ 비교 및 선택 기준 (Comparison & decision criteria)
|
||||
|
||||
| 도구 | 막는 문제 | 사용 시점 |
|
||||
|---|---|---|
|
||||
| AsyncLock (자원별) | 같은 자원 동시 수정(갱신 손실) | 파일/세션 등 공유 자원 read-modify-write |
|
||||
| 동시성 제한 큐 | 자원 폭주(메모리/IO/소켓) | 대량 작업을 일정 동시성으로 처리 |
|
||||
| 보상 트랜잭션 | 부분 실패로 인한 불일치 | 여러 파일을 한 단위로 변경 |
|
||||
| 외부 abort signal | 취소 불가 | 사용자 Stop / 타임아웃 ([[비동기 프로그래밍 Promise async await]]) |
|
||||
|
||||
## ⚖️ 모순 및 업데이트 (Contradictions & updates)
|
||||
- **락 타임아웃의 부작용:** 타임아웃으로 깨어난 작업은 자원을 못 얻은 채 throw 한다. 호출부는 이 실패를 재시도/사용자 안내로 처리해야 한다(조용히 진행 금지).
|
||||
- **큐의 static 동시성:** 동적 조정이 없어 부하 급변에 둔감하지만, 무거운 작업이 락으로 직렬화되므로 큐는 단순함을 택했다 — 의도적 단순성.
|
||||
- **메모리 내 트랜잭션:** 백업이 메모리(Map)에 있어 프로세스가 죽으면 롤백이 불가능하다. 진짜 내구성이 필요하면 WAL/DB 가 필요.
|
||||
|
||||
## 🛠️ 적용 사례 (Applied in summary)
|
||||
- `ConnectAI/src/core/lock.ts` — `lockManager` 싱글톤. agent 가 파일/세션 작업 직렬화에 사용 [S1].
|
||||
- `ConnectAI/src/core/queue.ts` — `actionQueue` 싱글톤. 대량 액션 처리 [S2].
|
||||
- `ConnectAI/src/core/transaction.ts` — AgentExecutor 가 파일 다중 변경을 묶을 때 [S3].
|
||||
|
||||
## 💻 코드 패턴 (Code patterns)
|
||||
```typescript
|
||||
// 1) 락 — 반드시 try/finally 로 해제 (src/core/lock.ts)
|
||||
const release = await lockManager.acquire(filePath);
|
||||
try {
|
||||
const cur = fs.readFileSync(filePath, 'utf-8');
|
||||
fs.writeFileSync(filePath, transform(cur)); // read-modify-write 직렬화
|
||||
} finally {
|
||||
release(); // 예외가 나도 반드시 해제
|
||||
}
|
||||
|
||||
// 2) 동시성 제한 큐 (src/core/queue.ts)
|
||||
const result = await actionQueue.enqueue(() => heavyTask(item)); // 슬롯 빌 때 실행
|
||||
|
||||
// 3) 보상 트랜잭션 (src/core/transaction.ts)
|
||||
tx.begin();
|
||||
try {
|
||||
await tx.record(fileA); fs.writeFileSync(fileA, nextA);
|
||||
await tx.record(fileB); fs.writeFileSync(fileB, nextB);
|
||||
tx.commit();
|
||||
} catch (e) { tx.rollback(); throw e; }
|
||||
```
|
||||
|
||||
## ✅ 검증 상태 및 신뢰도
|
||||
- **상태:** draft
|
||||
- **검증 단계:** applied
|
||||
- **출처 신뢰도:** A
|
||||
- **신뢰 점수:** 0.94
|
||||
- **중복 검사 결과:** 신규 생성 (New discovery)
|
||||
|
||||
## 🔗 지식 그래프 (Knowledge Graph)
|
||||
- **상위/루트:** [[ConnectAI 아키텍처 개요]]
|
||||
- **관련 개념:** [[비동기 프로그래밍 Promise async await]], [[에러 처리와 커스텀 에러]], [[이벤트 소싱 스토어 패턴]]
|
||||
- **참조 맥락:** 로컬 LLM 이 공유 자원·대량 작업·다중 파일 변경의 동시성 안전 코드를 작성할 때 참조.
|
||||
|
||||
## 📚 출처 (Sources)
|
||||
- [S1] ConnectAI/src/core/lock.ts — AsyncLockManager(토큰 기반), race 타임아웃, 버그 사후기록
|
||||
- [S2] ConnectAI/src/core/queue.ts — ActionQueueManager 동시성 제한
|
||||
- [S3] ConnectAI/src/core/transaction.ts — 보상 트랜잭션(begin/record/commit/rollback)
|
||||
|
||||
## 📝 변경 이력 (Change history)
|
||||
- 2026-06-13: ConnectAI 코드 분석 기반 초안 생성.
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
id: dependency-injection-service-interface
|
||||
title: "의존성 주입과 서비스 인터페이스"
|
||||
category: "Architecture"
|
||||
status: "draft"
|
||||
verification_status: "applied"
|
||||
canonical_id: ""
|
||||
aliases: ["DI", "dependency injection", "인터페이스", "service interface", "느슨한 결합", "testability", "전략 패턴"]
|
||||
duplicate_of: ""
|
||||
source_trust_level: "A"
|
||||
confidence_score: 0.93
|
||||
created_at: 2026-06-13
|
||||
updated_at: 2026-06-13
|
||||
review_reason: ""
|
||||
merge_history: []
|
||||
tags: ["architecture", "dependency-injection", "interface", "design-pattern", "connectai"]
|
||||
raw_sources: ["ConnectAI/src/core/services.ts", "ConnectAI/src/extension.ts", "ConnectAI/src/intelligence/criticAgent.ts", "ConnectAI/src/agent/multiAgent/workflow.ts"]
|
||||
applied_in: ["ConnectAI"]
|
||||
github_commit: ""
|
||||
---
|
||||
|
||||
# [[의존성 주입과 서비스 인터페이스]]
|
||||
|
||||
## 🎯 한 줄 통찰 (One-line insight)
|
||||
의존성 주입은 "객체가 협력자를 *직접 만들지 않고 밖에서 받는*" 설계이며, ConnectAI 는 **인터페이스로 계약을 정의하고, 생성자 옵션 객체·함수 타입으로 구현을 주입**해 모듈을 순수하고 교체·테스트 가능하게 만든다 [S1][S3].
|
||||
|
||||
## 🧠 핵심 개념 (Core concepts)
|
||||
1. **인터페이스 = 계약:** `IAIService`/`IBrainService` 는 "무엇을 할 수 있는가"만 정의하고 "어떻게" 는 구현에 맡긴다. 호출부는 인터페이스에만 의존 [S1].
|
||||
2. **생성자 주입 (Constructor injection):** 협력자를 `new X(deps)` 처럼 생성 시점에 옵션 객체로 받는다 — 객체가 자기 의존을 숨기지 않고 시그니처에 드러낸다 [S2].
|
||||
3. **함수 주입 (Function injection):** 무거운 의존(LLM 호출)을 *함수 타입* 으로 받아, 모듈 자체는 LLM 을 모른 채 순수 함수로 남는다 (`CritiqueLlmCall`) [S3].
|
||||
4. **getter 주입 (Lazy):** 아직 생성되지 않은 의존은 `getProvider: () => provider` 처럼 getter 로 — 초기화 순서/순환 의존 회피 [S2].
|
||||
5. **deps 번들 객체:** 여러 협력자를 `WorkflowDeps` 같은 인터페이스로 묶어 한 번에 전달 [S4].
|
||||
|
||||
## 🧩 추출된 패턴 (Extracted patterns)
|
||||
- **인터페이스 선언 → 구현 클래스:** `interface IAIService { ... }` + `class AIService implements IAIService { ... }`. 정책(엔진 폴백)은 구현에 캡슐화, 호출부는 인터페이스만 본다 [S1].
|
||||
- **옵션 객체로 생성자 주입:** `new AgentExecutor(context, { onStreamLifecycle, lmStudioStreamer, approvalQueue })` — 위치 인자 대신 명명 옵션으로 가독성·확장성 확보 [S2].
|
||||
- **함수 타입으로 LLM 추상화:** `type CritiqueLlmCall = (system, user, maxTokens) => Promise<string>` 를 주입받아, criticAgent 는 어떤 엔진이든 무관하게 동작하고 테스트 시 가짜 함수를 끼운다 [S3].
|
||||
- **deps 인터페이스 + 콜백 게터:** `WorkflowDeps { getWebview, getAbortSignal, chatHistory, ... }` — 동적으로 바뀌는 상태는 게터로 전달해 stale 참조 방지 [S4].
|
||||
- **싱글톤 vs 주입 구분:** 프로세스 전역 자원(lock/queue)은 싱글톤, 정책·상태를 가진 협력자는 주입 [S2].
|
||||
|
||||
## 📖 세부 내용 (Details)
|
||||
### 왜 인터페이스인가
|
||||
`AIService` 는 "LM Studio 먼저, 실패 시 Ollama 폴백, 빈 응답은 soft failure" 같은 *정책* 을 담는다. 호출부(텔레그램 핸들러 등)는 `IAIService.chat()` 만 알면 되고, 정책이 바뀌어도 호출부는 안 바뀐다. 이것이 "구현이 아니라 추상에 의존하라"(DIP)의 실천 [S1].
|
||||
|
||||
### 함수 주입으로 순수성 유지
|
||||
`criticAgent.ts` 헤더 주석은 "모든 LLM 의존은 주입(critique caller) — 모듈 자체는 순수, 테스트 가능" 이라고 명시한다. `runCriticReview({ ..., callLlm })` 는 실제 LLM 을 직접 부르지 않고 주입된 `callLlm` 을 부른다. 덕분에:
|
||||
- 프로덕션: `agent.ts` 의 `callNonStreaming` 을 주입.
|
||||
- 테스트: 고정 문자열을 반환하는 가짜 함수를 주입 → LLM 없이 단위 테스트 [S3].
|
||||
|
||||
### deps 번들 + getter 의 이유
|
||||
멀티에이전트 워크플로는 실행 도중 webview/abort signal 이 바뀔 수 있다. 그래서 값이 아니라 *게터* 를 주입한다:
|
||||
```typescript
|
||||
export interface WorkflowDeps {
|
||||
getWebview: () => vscode.Webview | undefined; // 호출 시점의 최신 webview
|
||||
getAbortSignal: () => AbortSignal | undefined; // 새 controller 의 최신 signal
|
||||
chatHistory: ChatMessage[];
|
||||
}
|
||||
```
|
||||
주석은 "호출자가 stop()+new AbortController() 를 먼저 마쳐야 한다 — getAbortSignal() 은 그 새 controller 의 signal 을 반환해야 함" 이라고 함정을 경고한다 [S4].
|
||||
|
||||
## ⚖️ 비교 및 선택 기준 (Comparison & decision criteria)
|
||||
|
||||
| 항목 (Option) | 장점 | 단점 | 언제 선택 |
|
||||
|---|---|---|---|
|
||||
| 생성자 주입(옵션 객체) | 의존 명시, 교체 쉬움 | 생성 코드 장황 | 상태/정책 가진 협력자 |
|
||||
| 함수 주입 | 모듈 순수, 테스트 최상 | 콜백 시그니처 관리 | LLM·I/O 같은 외부 효과 |
|
||||
| getter 주입 | 늦은 바인딩, 순환 회피 | 호출 시점 의존 | 동적/초기화 순서 문제 |
|
||||
| 싱글톤 import | 간결, 전역 공유 | 테스트 격리 어려움 | 프로세스 전역 인프라(lock/queue) |
|
||||
|
||||
## ⚖️ 모순 및 업데이트 (Contradictions & updates)
|
||||
- **DI 컨테이너 없음:** ConnectAI 는 별도 DI 프레임워크를 쓰지 않고 *수동 주입* (activate 에서 직접 조립)한다. 규모가 작고 조립 지점이 한 곳이라 프레임워크 비용이 불필요 — 큰 시스템이면 컨테이너가 유리할 수 있다.
|
||||
- **싱글톤의 비용:** `lockManager`/`actionQueue` 싱글톤은 편하지만 테스트 격리를 어렵게 한다. ConnectAI 는 전역성이 본질인 자원에만 한정해 사용한다.
|
||||
|
||||
## 🛠️ 적용 사례 (Applied in summary)
|
||||
- `ConnectAI/src/core/services.ts` — IAIService/IBrainService 인터페이스 + 구현 [S1].
|
||||
- `ConnectAI/src/extension.ts` — AgentExecutor/SidebarChatProvider 생성자 옵션 주입, getProvider 게터 [S2].
|
||||
- `ConnectAI/src/intelligence/criticAgent.ts` — CritiqueLlmCall 함수 주입 [S3].
|
||||
- `ConnectAI/src/agent/multiAgent/workflow.ts` — WorkflowDeps 게터 번들 [S4].
|
||||
|
||||
## 💻 코드 패턴 (Code patterns)
|
||||
```typescript
|
||||
// 1) 인터페이스 계약 + 구현 분리 (src/core/services.ts)
|
||||
export interface IAIService {
|
||||
call(prompt: string): Promise<string>;
|
||||
chat(req: AIChatRequest): Promise<AIChatResult>;
|
||||
}
|
||||
export class AIService implements IAIService { /* 엔진 폴백 정책 캡슐화 */ }
|
||||
|
||||
// 2) 생성자 옵션 객체 주입 (src/extension.ts)
|
||||
const agent = new AgentExecutor(context, {
|
||||
onStreamLifecycle: { start: () => lifecycle.onStreamStart(), end: () => lifecycle.onStreamEnd() },
|
||||
lmStudioStreamer, approvalQueue,
|
||||
});
|
||||
|
||||
// 3) 함수 주입으로 순수 모듈 (src/intelligence/criticAgent.ts)
|
||||
export type CritiqueLlmCall = (system: string, user: string, maxTokens: number) => Promise<string>;
|
||||
export async function runCriticReview(params: { /* ... */ callLlm: CritiqueLlmCall }) {
|
||||
const raw = await params.callLlm(system, user, opts.maxTokens); // 어떤 엔진인지 모름
|
||||
return parseCritique(raw);
|
||||
}
|
||||
|
||||
// 4) getter 번들 (src/agent/multiAgent/workflow.ts)
|
||||
export interface WorkflowDeps {
|
||||
getWebview: () => vscode.Webview | undefined;
|
||||
getAbortSignal: () => AbortSignal | undefined;
|
||||
}
|
||||
```
|
||||
|
||||
## ✅ 검증 상태 및 신뢰도
|
||||
- **상태:** draft
|
||||
- **검증 단계:** applied
|
||||
- **출처 신뢰도:** A
|
||||
- **신뢰 점수:** 0.93
|
||||
- **중복 검사 결과:** 신규 생성 (New discovery)
|
||||
|
||||
## 🔗 지식 그래프 (Knowledge Graph)
|
||||
- **상위/루트:** [[ConnectAI 아키텍처 개요]]
|
||||
- **관련 개념:** [[TypeScript 고급 타입]], [[Intelligence 검증 레이어]], [[VSCode 확장 구조와 생명주기]]
|
||||
- **참조 맥락:** 로컬 LLM 이 협력 객체를 가진 모듈을 테스트 가능하고 교체 가능하게 설계할 때 참조.
|
||||
|
||||
## 📚 출처 (Sources)
|
||||
- [S1] ConnectAI/src/core/services.ts — IAIService/IBrainService + AIService 구현
|
||||
- [S2] ConnectAI/src/extension.ts — 생성자 옵션 주입, getProvider 게터
|
||||
- [S3] ConnectAI/src/intelligence/criticAgent.ts — CritiqueLlmCall 함수 주입(순수 모듈)
|
||||
- [S4] ConnectAI/src/agent/multiAgent/workflow.ts — WorkflowDeps 게터 번들
|
||||
|
||||
## 📝 변경 이력 (Change history)
|
||||
- 2026-06-13: ConnectAI 코드 분석 기반 초안 생성.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
id: event-sourced-store-pattern
|
||||
title: "이벤트 소싱 스토어 패턴"
|
||||
category: "Architecture"
|
||||
status: "draft"
|
||||
verification_status: "applied"
|
||||
canonical_id: ""
|
||||
aliases: ["event sourcing", "append-only", "JSONL", "이벤트 스토어", "팩토리 함수", "computeStates"]
|
||||
duplicate_of: ""
|
||||
source_trust_level: "A"
|
||||
confidence_score: 0.92
|
||||
created_at: 2026-06-13
|
||||
updated_at: 2026-06-13
|
||||
review_reason: ""
|
||||
merge_history: []
|
||||
tags: ["architecture", "event-sourcing", "persistence", "factory", "connectai"]
|
||||
raw_sources: ["ConnectAI/src/features/_shared/eventSourcedStore.ts", "ConnectAI/src/memory/types.ts"]
|
||||
applied_in: ["ConnectAI"]
|
||||
github_commit: ""
|
||||
---
|
||||
|
||||
# [[이벤트 소싱 스토어 패턴]]
|
||||
|
||||
## 🎯 한 줄 통찰 (One-line insight)
|
||||
이벤트 소싱은 "현재 상태를 덮어쓰지 않고 *일어난 일(event)을 추가만(append-only)* 기록"하는 영속화 방식이며, ConnectAI 는 이를 **JSONL 파일 + 제네릭 팩토리(`createEventStore<E>`)** 로 구현해 4개 도메인 스토어의 ~240줄 중복을 한 곳으로 통합한다 [S1].
|
||||
|
||||
## 🧠 핵심 개념 (Core concepts)
|
||||
1. **Append-only:** 데이터를 수정·삭제하지 않고 끝에 줄을 추가만 한다. 이력이 전부 남아 감사·재현·디버깅에 강하다 [S1].
|
||||
2. **JSONL (JSON Lines):** 한 줄에 JSON 객체 하나. 스트리밍 append 가 쉽고, 한 줄이 손상돼도 나머지는 읽을 수 있다 [S1].
|
||||
3. **이벤트 → 상태 계산:** 저장은 이벤트로, *현재 상태* 는 이벤트들을 재생(`computeStates`)해 도출한다 — 도메인 파일의 책임 [S1].
|
||||
4. **제네릭 팩토리 + 검증 주입:** I/O(읽기/추가/카운트)는 공통 모듈이, 도메인 로직은 호출부가 — 관심사 분리 [S1].
|
||||
5. **내결함 파싱:** 손상된 줄은 skip 하고 계속 — append-only 라 1줄 손상이 전체를 무력화하면 안 된다 [S1].
|
||||
|
||||
## 🧩 추출된 패턴 (Extracted patterns)
|
||||
- **중복 4벌 → 제네릭 1벌:** customers/hire/runway/feedback 이 같은 `getXFilePath/readX/appendX/countX` 를 반복 → `createEventStore<E>` 로 흡수. BOM/인코딩 등 edge case fix 도 한 번에 전파 [S1].
|
||||
- **팩토리 함수 + 클로저:** 클래스 대신 함수가 내부 함수들을 담은 객체를 반환 — 캡슐화는 클로저로, `new` 불필요 [S1].
|
||||
- **타입 가드 검증을 옵션으로:** `validate: (e) => e is E` 를 주입해 파싱 결과의 유효성을 도메인이 정의 [S1].
|
||||
- **결과를 판별 유니온으로:** `append` 가 `{ ok: true; filePath } | { ok: false; error }` 반환 — 워크스페이스 없음 등 흔한 실패를 예외 없이 전달 [S1].
|
||||
- **워크스페이스 상대경로:** `relPath: '.astra/customers.jsonl'` 를 워크스페이스 루트 기준으로 해석 [S1].
|
||||
|
||||
## 📖 세부 내용 (Details)
|
||||
### 왜 이벤트 소싱인가 (이 코드에서)
|
||||
고객/채용/런웨이/피드백 같은 도메인은 "변경 이력 자체가 가치" 다. 마지막 상태만 저장하면 "언제 무엇이 바뀌었나"를 잃는다. 이벤트를 append 하면 전체 타임라인이 보존되고, 현재 상태는 필요할 때 재생으로 만든다 [S1].
|
||||
|
||||
### 공통 I/O vs 도메인 로직 경계
|
||||
모듈 헤더 주석이 경계를 명확히 한다: "도메인별 로직(computeStates 등)은 그대로 도메인 파일에 남음 — 본 모듈은 I/O 만 추상화." 즉 `createEventStore` 는 read/append/count/getFilePath 만 제공하고, "이벤트들로 현재 고객 목록을 만드는" 로직은 customers 도메인에 둔다 [S1].
|
||||
|
||||
### 내결함 읽기
|
||||
```typescript
|
||||
for (const line of content.split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) continue;
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed);
|
||||
if (opts.validate(parsed)) out.push(parsed);
|
||||
} catch { /* skip malformed — append-only 라 손상 1줄이 전체 무력화하면 안 됨 */ }
|
||||
}
|
||||
```
|
||||
한 줄 파싱 실패나 검증 실패는 그 줄만 버리고 계속한다 — 견고성의 핵심 [S1].
|
||||
|
||||
### 안전한 append
|
||||
`fs.mkdirSync(dirname, { recursive: true })` 로 디렉터리 보장 후 `appendFileSync(... + '\n')`. 실패는 throw 가 아니라 `{ ok: false, error }` 로 반환해 호출부가 사용자에게 안내할 수 있게 한다 [S1].
|
||||
|
||||
## ⚖️ 비교 및 선택 기준 (Comparison & decision criteria)
|
||||
|
||||
| 항목 (Option) | 장점 | 단점 | 언제 선택 |
|
||||
|---|---|---|---|
|
||||
| Append-only 이벤트(JSONL) | 이력 보존, 내결함, 단순 | 상태 재생 비용, 파일 증가 | 변경 이력이 가치 있을 때 |
|
||||
| 상태 덮어쓰기(JSON 1개) | 읽기 즉시, 작음 | 이력 손실, 동시쓰기 충돌 | 마지막 값만 중요할 때 |
|
||||
| SQLite/DB | 쿼리·인덱스·트랜잭션 | 의존성·운영 비용 | 대량·복잡 쿼리 |
|
||||
|
||||
## ⚖️ 모순 및 업데이트 (Contradictions & updates)
|
||||
- **파일 무한 증가:** append-only 는 파일이 계속 커진다. 주기적 compaction(스냅샷 + 이후 이벤트만 유지)이 필요할 수 있다 — 현재 모듈은 compaction 을 제공하지 않으므로 도메인이 관리.
|
||||
- **동시 append:** 단일 프로세스 내 순차 append 는 안전하나, 멀티 프로세스/동시 쓰기는 잠금이 필요하다. ConnectAI 는 무거운 작업을 [[동시성 제어 Lock Queue Transaction]] 의 lockManager 로 직렬화한다.
|
||||
|
||||
## 🛠️ 적용 사례 (Applied in summary)
|
||||
- `ConnectAI/src/features/_shared/eventSourcedStore.ts` — 제네릭 이벤트 스토어 본체. customers/hire/runway/feedback 도메인이 이를 인스턴스화해 사용 [S1].
|
||||
- 메모리 계층의 episodic/long-term 도 유사하게 버전 필드를 가진 직렬화 스토어 형태(`EpisodicStore { version, episodes, lastUpdated }`)를 쓴다 [S2].
|
||||
|
||||
## 💻 코드 패턴 (Code patterns)
|
||||
```typescript
|
||||
// 제네릭 이벤트 스토어 (src/features/_shared/eventSourcedStore.ts)
|
||||
export function createEventStore<E>(opts: EventStoreOptions<E>): EventStore<E> {
|
||||
function getFilePath(): string | null {
|
||||
const folders = vscode.workspace.workspaceFolders;
|
||||
if (!folders?.length) return null;
|
||||
return path.join(folders[0].uri.fsPath, opts.relPath); // 워크스페이스 상대경로
|
||||
}
|
||||
function read(): E[] {
|
||||
const fp = getFilePath();
|
||||
if (!fp || !fs.existsSync(fp)) return [];
|
||||
const out: E[] = [];
|
||||
for (const line of fs.readFileSync(fp, 'utf-8').split('\n')) {
|
||||
const t = line.trim(); if (!t) continue;
|
||||
try { const p = JSON.parse(t); if (opts.validate(p)) out.push(p); }
|
||||
catch { /* 손상 줄 skip */ }
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function append(event: E) {
|
||||
const fp = getFilePath();
|
||||
if (!fp) return { ok: false, error: '워크스페이스 폴더가 없어 저장 불가.' } as const;
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(fp), { recursive: true });
|
||||
fs.appendFileSync(fp, JSON.stringify(event) + '\n', 'utf-8');
|
||||
return { ok: true, filePath: fp } as const;
|
||||
} catch (e: any) { return { ok: false, error: e?.message || String(e) } as const; }
|
||||
}
|
||||
return { getFilePath, read, append, count };
|
||||
}
|
||||
// 도메인 사용:
|
||||
const store = createEventStore<CustomerEvent>({
|
||||
relPath: '.astra/customers.jsonl',
|
||||
validate: (e): e is CustomerEvent => typeof (e as any).id === 'string',
|
||||
});
|
||||
```
|
||||
|
||||
## ✅ 검증 상태 및 신뢰도
|
||||
- **상태:** draft
|
||||
- **검증 단계:** applied
|
||||
- **출처 신뢰도:** A
|
||||
- **신뢰 점수:** 0.92
|
||||
- **중복 검사 결과:** 신규 생성 (New discovery)
|
||||
|
||||
## 🔗 지식 그래프 (Knowledge Graph)
|
||||
- **상위/루트:** [[ConnectAI 아키텍처 개요]]
|
||||
- **관련 개념:** [[TypeScript 고급 타입]], [[5계층 메모리 시스템]], [[동시성 제어 Lock Queue Transaction]]
|
||||
- **참조 맥락:** 로컬 LLM 이 이력·로그·상태를 파일로 영속화하는 스토어를 설계할 때 참조.
|
||||
|
||||
## 📚 출처 (Sources)
|
||||
- [S1] ConnectAI/src/features/_shared/eventSourcedStore.ts — createEventStore 제네릭 팩토리, 내결함 파싱, 판별 유니온 결과
|
||||
- [S2] ConnectAI/src/memory/types.ts — 버전 필드를 가진 직렬화 스토어(EpisodicStore/LongTermStore)
|
||||
|
||||
## 📝 변경 이력 (Change history)
|
||||
- 2026-06-13: ConnectAI 코드 분석 기반 초안 생성.
|
||||
Reference in New Issue
Block a user