d3ro-voice/docs/design/06-gap-analysis.md
Yun Chan e24bb8378c 초기 프로젝트 설정: 하네스 시스템 + 설계서 + RE 노하우
- CLAUDE.md: 프로젝트 규칙, 기술 스택, 코딩 규칙, 페이즈 로드맵
- .claude/settings.json: 권한, 강제 훅 (매 프롬프트 설계서 규칙 주입)
- .claude/skills/: implement-phase, review-phase, scaffold, test-commit, debug
- .claude/agents/: electron-architect, voice-pipeline-expert, ui-specialist
- docs/design/00-09: 마스터 아키텍처, 서비스 명세(16개), IPC(113채널),
  DB스키마, UI컴포넌트, 검증리포트, 외부엔진연동, 갭분석,
  VoiceMode패턴, 디자인시스템, 히스토리팝업
- docs/phases/1-7+3.5: 전체 구현 페이즈 문서
- docs/re-findings/: Speakly RE 노하우 5개 문서
2026-04-05 01:03:03 +09:00

230 lines
17 KiB
Markdown

# 06. 갭 분석 리포트
> 작성일: 2026-04-04
> 분석 대상: 00-master-architecture.md, 01-service-specifications.md, 02-ipc-and-types.md, 03-db-and-ui.md, CLAUDE.md
---
## 1. 설계서 간 불일치/충돌
### 1.1 서비스 이름 불일치
| 항목 | 00 (마스터 아키텍처) | 01 (서비스 명세) | 02 (IPC 채널) | 수정 제안 |
|------|---------------------|-----------------|--------------|----------|
| TTS 서비스 | `TTSService` | `LocalTTSService` (섹션 3) | `LocalTTSService` (tts:* 담당) | **`LocalTTSService`로 통일** — 00에서 수정 |
| LLM 서비스 | `LLMService` | `LocalLLMService` (섹션 4) | `OllamaService` (llm:* 담당) | **`LocalLLMService`로 통일** — 00, 02에서 수정 |
| STT 서비스 | `LocalSTTService` | `LocalSTTService` | `LocalSTTService` | 일치 (OK) |
### 1.2 서비스 개수 불일치
- **00**: 15개 서비스 (명시적 목록: 표 2.1)
- **01**: 10개 서비스만 상세 명세 (AudioCapture, LocalSTT, LocalTTS, LocalLLM, VoiceMode, TextInsert, Hotkey, Config, History, WindowManager)
- **빠진 서비스 명세**: I18nService, LoggerService, DictionaryService, SoundEffectService, CustomInstructionService, AutoLaunchService (6개)
- **01에만 있는 서비스**: WindowManagerService (00의 WindowManager를 서비스로 격상)
**수정 제안**: 01에 빠진 6개 서비스의 상세 명세를 추가하거나, 별도 문서로 분리
### 1.3 02의 IPC 채널에서만 등장하는 서비스/네임스페이스
| 02 IPC 채널 | 담당 서비스 명 | 00/01 존재 여부 | 비고 |
|-------------|--------------|----------------|------|
| `system:*` | SystemService, PermissionService | 00/01에 없음 | 00에서 제거된 PermissionService가 02에 부활 |
| `stats:*` | StatsService | 00/01에 없음 | 03의 stats 테이블과 관련되지만 서비스 명세 없음 |
| `dictionary:*` | DictionaryService | 00에 있지만 01에 명세 없음 | |
**수정 제안**:
- `system:*` 채널의 담당 서비스를 명확히 정의 (신규 SystemService? 또는 기존 서비스에 분배?)
- StatsService를 00의 서비스 목록에 추가하거나 HistoryService에 통합 명시
- 01에 DictionaryService 명세 추가
### 1.4 IPC 네임스페이스 규칙 불일치
**00의 규칙**: `${feature}:${action}` (예: `hotkey:setDictation`, `history:getRecent`, `app:getVersion`)
**02의 실제 채널**: `hotkey:setDictationShortcut`, `history:getAll`, `system:getVersion`
| 00 예시 | 02 실제 | 차이 |
|---------|---------|------|
| `hotkey:setDictation` | `hotkey:setDictationShortcut` | action 이름 불일치 |
| `history:getRecent` | `history:getAll` | 채널 자체가 다름 (getRecent 없음) |
| `app:getVersion` | `system:getVersion` | 네임스페이스 다름 (`app:` vs `system:`) |
| `dictionary:addWord` | `dictionary:add` | action 이름 불일치 |
| `instruction:*` (00에 명시) | 02에 없음 | CustomInstruction 채널 전체 누락 |
**수정 제안**: 00의 5.1절 예시를 02의 실제 채널명과 일치하도록 갱신
### 1.5 이벤트 타입 이름/구조 불일치 (00/01 vs 02)
| 항목 | 00/01 (서비스 이벤트) | 02 (IPC 타입) | 불일치 |
|------|----------------------|---------------|--------|
| 에러 타입 | `ServiceError` (01 공통타입) | `ErrorCode` enum (00 §6.2) | 01의 ServiceError는 문자열 코드, 00의 ErrorCode는 숫자 enum |
| AudioDevice | `{ id, name, isDefault }` (01) | `{ deviceId, label, isDefault }` (02) | 필드명 불일치: `id`/`deviceId`, `name`/`label` |
| STTModel | `{ id, name, size, language, downloaded }` (01) | `{ id, name, sizeBytes, downloaded, languages, accuracy, speed }` (02) | 02가 더 풍부. `language` (단일) vs `languages` (배열) 차이 |
| RecognitionState | `Idle/Preparing/Ready/Recognizing/Processing/Completing/Completed/Cancelled/Error` (01) | `IDLE/PREPARING/READY/RECOGNIZING/COMPLETING/COMPLETED/CANCELLED/ERROR` (02) | 01에 `Processing` 있음, 02에 없음. 케이싱도 다름 (PascalCase vs UPPER_CASE) |
| VoiceMode | `Dictation/HandsFree` (01, const enum) | `'dictation' / 'hands-free'` (02, string literal) | 타입 표현 방식 차이 |
**수정 제안**: 02의 `shared/types.ts`를 정본(source of truth)으로 하고, 01의 타입을 02에 맞춰 갱신
### 1.6 DB 스키마 vs HistoryService/IPC 필드 불일치
| 03 DB history 컬럼 | 02 HistoryEntry 타입 | 불일치 |
|-------------------|---------------------|--------|
| `original_text` | `originalText` | OK (camelCase 변환) |
| `polished_text` | `processedText` | **이름 불일치**: `polished` vs `processed` |
| `mode` (dictation/translate/command) | `llmAction` (refine/translate/...) | **의미 불일치**: DB의 mode는 녹음 모드, IPC의 llmAction은 LLM 처리 유형 |
| `focused_app`, `focused_app_name`, `focused_app_window_title` | `targetApp` (단일 필드) | **세분화 불일치**: DB는 3개 필드, IPC는 1개 |
| `duration` (초, REAL) | `durationMs` (ms) | **단위 불일치**: 초 vs 밀리초 |
| `stt_model`, `llm_model`, `stt_latency_ms`, `llm_latency_ms` | 해당 필드 없음 | **02에 누락** |
| `error_code`, `status`, `audio_local_path`, `detected_language`, `mic_device`, `app_version` | 해당 필드 없음 | **02에 누락** |
| 해당 컬럼 없음 | `sessionId` | **03에 누락** (id가 sessionId와 동일하다는 주석이 있으나 별도 필드는 없음) |
**수정 제안**: 02의 HistoryEntry 타입을 03의 DB 스키마와 1:1 매핑되도록 확장
### 1.7 Dictionary 스키마 vs IPC 타입 불일치
| 03 DB dictionary 컬럼 | 02 DictionaryEntry 타입 | 불일치 |
|----------------------|------------------------|--------|
| `word`, `pronunciation`, `category`, `usage_count` | `from`, `to`, `caseSensitive`, `enabled`, `useCount` | **완전히 다른 구조**: DB는 "단어+발음" 패턴, IPC는 "교정(from→to)" 패턴 |
**수정 제안**: 사전의 목적을 명확히 한 후 어느 한쪽으로 통일. "커스텀 단어 사전"(03)과 "자동 교정 사전"(02)은 다른 기능이므로, 두 테이블이 필요할 수 있음
### 1.8 IPC 방향 불일치
| 채널 | 00 §5.2 예시 | 02 채널 테이블 | 불일치 |
|------|-------------|---------------|--------|
| `voice:startRecording` | `on` (fire-and-forget) | `handle` (요청→응답) | 00은 fire-and-forget, 02는 양방향 |
| `voice:stopRecording` | `on` (fire-and-forget) | `handle` (요청→응답) | 동일 불일치 |
**수정 제안**: 02의 handle 방식이 더 적절 (sessionId 반환 필요). 00 §5.2를 갱신
---
## 2. 빠진 설계 영역
### 우선순위 High
| # | 영역 | 현황 | 필요한 내용 |
|---|------|------|------------|
| 1 | **빌드 설정** | CLAUDE.md에 `electron-vite` 언급만 있고, `electron.vite.config.ts`의 구체적 설정 없음 | electron-vite 설정 (main/preload/renderer 엔트리, external 모듈, 네이티브 모듈 처리), tsconfig.json (paths, target, module), vite.config.ts (proxy, define, alias) |
| 2 | **패키징 설정** | 00에 `electron-builder.yml` 파일 트리에 없음 | electron-builder.yml (app ID, productName, files, nsis/msi 설정, extraResources: Whisper/Piper 바이너리 번들링, afterSign hook) |
| 3 | **프로젝트 초기화** | CLAUDE.md에 `npm run dev` 등 명령어만 있음 | package.json 의존성 전체 목록, postinstall (네이티브 모듈 리빌드), scripts 정의, electron-rebuild 설정 |
| 4 | **보안 (preload 안전성, IPC 검증)** | 00에 contextIsolation:true 언급만 있음 | preload에서 expose할 채널 화이트리스트, IPC 입력값 검증 (zod schema), webPreferences 전체 설정 (sandbox, webSecurity), CSP 헤더 |
### 우선순위 Medium
| # | 영역 | 현황 | 필요한 내용 |
|---|------|------|------------|
| 5 | **테스트 전략** | CLAUDE.md에 `vitest` 언급만 있음 | 테스트 피라미드 (단위/통합/E2E 비율), 서비스별 테스트 파일 매핑, mock 전략 (Electron IPC, better-sqlite3, child_process), E2E 프레임워크 선택 (Playwright? Spectron?), CI 파이프라인 |
| 6 | **로깅 전략** | 00에 `LoggerService = electron-log 래퍼` 정도만 있음 | 로그 레벨 정책 (info/warn/error 기준), 로그 파일 위치/로테이션/최대 크기, 카테고리별 로거 (audio, stt, llm, ipc), 민감 정보 마스킹, 디버그 모드 활성화 방법 |
| 7 | **성능 기준** | 설계서에 타이밍 상수는 있으나 성능 목표 없음 | STT 지연 목표 (base 모델 기준 < Xms), LLM 응답 시간 목표, UI 응답 시간 (FID < 100ms), 메모리 사용량 상한, CPU 사용량 기준 |
| 8 | **Sidecar 바이너리 관리** | STT/TTS sidecar 언급은 있으나 바이너리 배포 방식 미정의 | faster-whisper Python 환경 번들링 (PyInstaller? embedded Python?), Piper 바이너리 배포 방식, 모델 파일 저장 경로 (userData), 버전 관리, 자동 업데이트 |
### 우선순위 Low
| # | 영역 | 현황 | 필요한 내용 |
|---|------|------|------------|
| 9 | **접근성** | 설계서에 언급 없음 | 키보드 네비게이션 (Tab order, focus trap), aria-label/role, 스크린 리더 호환성, 고대비 모드, 폰트 크기 조정 |
| 10 | **에러 복구 전략** | 에러 코드 정의는 있으나 복구 흐름 미정의 | 사이드카 크래시 자동 재시작 정책, 네트워크(Ollama) 끊김 재연결 로직, DB 손상 복구 절차 |
| 11 | **마이그레이션 전략** | 03에 drizzle-orm migrate 호출만 있음 | 마이그레이션 파일 생성/관리 방법, 스키마 버전 관리, 하위 호환성 정책 |
| 12 | **국제화(i18n) 상세** | 00에 I18nService 서비스 목록만 있음 | 번역 관리 방식 (JSON? ts?), 번역 파일 구조, fallback 언어, 날짜/숫자 포맷 |
---
## 3. 실현 가능성 리스크
### 3.1 faster-whisper sidecar의 실시간성
| 항목 | 내용 |
|------|------|
| **리스크** | 01의 설계는 "오디오 버퍼 전체를 한번에 전사"하는 배치 방식. 실시간 스트리밍 전사 불가. |
| **영향도** | **High** 사용자가 문장을 말할 녹음 종료 전사 지연이 체감됨 |
| **현재 설계** | stdin으로 base64 인코딩된 오디오를 보내고 stdout으로 결과 수신 (01 §2 Sidecar 통신 프로토콜) |
| **문제점** | (1) base64 인코딩 오버헤드 (~33% 크기 증가), (2) 전체 오디오를 버퍼링 전송하므로 first-token latency가 높음, (3) faster-whisper 자체가 파일/버퍼 단위 처리 (진정한 스트리밍 미지원) |
| **대안** | (A) WAV 파일 임시 저장 파일 경로 전달 (base64 오버헤드 제거), (B) whisper.cpp의 stream 모드 사용 (진정한 실시간), (C) 청크 분할 전사 + 결합 (VAD 기반 세그먼트 단위), (D) faster-whisper의 `--live` 모드 활용 (커뮤니티 fork) |
| **권장** | 초기에는 WAV 파일 전달 방식(A)으로 구현. 지연이 문제 whisper.cpp stream(B)으로 전환 |
### 3.2 uiohook-napi Windows 호환성
| 항목 | 내용 |
|------|------|
| **리스크** | uiohook-napi v1.x는 Electron 33+에서 context-aware 네이티브 모듈로 빌드 필요 |
| **영향도** | **Medium** 빌드 실패 핫키 기능 전체 불가 |
| **문제점** | (1) N-API 버전 호환성 확인 필요, (2) electron-rebuild로 리빌드 빌드 도구(MSVC, Python 3) 필요, (3) Windows Defender가 키보드 후킹을 위협으로 감지할 있음 |
| **대안** | (A) Electron의 globalShortcut API (제한적이지만 네이티브 모듈 불필요), (B) iohook ( 오래된 포크, 유지보수 우려), (C) PowerShell 스크립트로 후킹 (복잡도 높음) |
| **권장** | uiohook-napi를 우선 시도하되, `electron-rebuild` 설정을 빌드 문서에 명시. globalShortcut은 hold-to-talk 미지원이므로 fallback으로 부적합 |
### 3.3 @nut-tree/nut-js 관리자 권한 이슈
| 항목 | 내용 |
|------|------|
| **리스크** | Windows에서 키보드 시뮬레이션(Ctrl+V) 관리자 권한이 필요할 있음 |
| **영향도** | **Medium** UAC 프롬프트 없이 텍스트 삽입 불가 핵심 기능 차질 |
| **문제점** | (1) 일부 (관리자 권한으로 실행된 ) 입력 불가, (2) nut-js v3은 prebuild 바이너리 제공하지만 Electron과의 호환성 미확인, (3) Windows UAC 설정에 따라 동작 불일치 |
| **대안** | (A) Electron의 `clipboard.writeText()` + `robot.js` Ctrl+V 시뮬레이션, (B) PowerShell `SendKeys`, (C) Windows Input Simulator (C++ addon), (D) `node-key-sender` |
| **권장** | nut-js로 우선 구현. 관리자 권한 대상 실패 `app.setAsDefaultProtocolClient` 매니페스트에 `uiAccess: true` 설정 검토 |
### 3.4 better-sqlite3 + Electron 네이티브 모듈 빌드
| 항목 | 내용 |
|------|------|
| **리스크** | better-sqlite3는 C++ 네이티브 모듈로 Electron 버전과 Node.js ABI 불일치 빌드 실패 |
| **영향도** | **High** DB 초기화 실패 전체 불가 (critical step) |
| **문제점** | (1) electron-rebuild가 필요하지만 MSVC 빌드 도구 필수, (2) Electron 33의 Node.js 버전과 better-sqlite3 prebuild 버전 일치 여부 불확실, (3) asar 패키징 .node 파일 제외 필요 |
| **대안** | (A) `@neondatabase/sql.js` (WASM 기반, 빌드 불필요하지만 성능 저하), (B) `sql.js` (WASM), (C) `drizzle-orm/libsql` (libsql WASM 바인딩) |
| **권장** | better-sqlite3 유지. `electron-builder` extraFiles에 .node 파일 포함, `postinstall` `electron-rebuild` 스크립트 추가. package.json에 `"build": { "asarUnpack": ["**/better-sqlite3/**"] }` 설정 |
### 3.5 electron-vite vs vite-plugin-electron
| 항목 | 내용 |
|------|------|
| **리스크** | 선택 근거가 문서화되지 않음 |
| **영향도** | **Low** 성숙한 도구이며 전환 비용은 초기에 낮음 |
| **비교** | electron-vite: 공식 Electron 지원 느낌, main/preload/renderer 분리 빌드 기본 지원, 네이티브 모듈 external 자동 처리. vite-plugin-electron: 가벼움, Vite 생태계 플러그인, 커스터마이징 유연. |
| **권장** | CLAUDE.md에 `electron-vite` 명시됨. 선택 근거를 설계 문서에 추가: "electron-vite 채택 main/preload/renderer 3-entry 빌드 기본 지원, 네이티브 모듈 external 자동 처리" |
---
## 4. 보강 권장사항 (액션 아이템)
### 4.1 설계서 수정 (기존 문서)
| # | 대상 문서 | 액션 | 우선순위 |
|---|----------|------|---------|
| A1 | 00-master-architecture.md | 서비스명 통일: `TTSService` `LocalTTSService`, `LLMService` `LocalLLMService` | High |
| A2 | 00-master-architecture.md | §5.1 IPC 예시를 02의 실제 채널명과 동기화 | Medium |
| A3 | 00-master-architecture.md | §5.2 `voice:startRecording`/`stopRecording` 방향을 `handle` 수정 | Medium |
| A4 | 01-service-specifications.md | 누락된 6개 서비스 명세 추가 (I18n, Logger, Dictionary, SoundEffect, CustomInstruction, AutoLaunch) | High |
| A5 | 01-service-specifications.md | 01의 타입 정의를 02와 통일 (AudioDevice, STTModel, RecognitionState ) | High |
| A6 | 02-ipc-and-types.md | `instruction:*` (CustomInstruction) IPC 채널 추가 | Medium |
| A7 | 02-ipc-and-types.md | HistoryEntry 타입을 03 DB 스키마와 매핑되도록 확장 | High |
| A8 | 02-ipc-and-types.md | DictionaryEntry 타입과 03 DB dictionary 스키마 불일치 해결 | High |
| A9 | 02-ipc-and-types.md | SystemService, StatsService를 00 서비스 목록에 반영하거나, 기존 서비스에 역할 배분 | Medium |
| A10 | 03-db-and-ui.md | `polished_text` `processed_text` 변경 (또는 02 타입을 `polishedText` 변경) | Medium |
| A11 | 03-db-and-ui.md | `duration` 단위를 ms (INTEGER) 통일 (02의 durationMs와 일치) | Medium |
### 4.2 신규 설계 문서 작성
| # | 문서명 | 내용 | 우선순위 |
|---|--------|------|---------|
| B1 | `04-build-and-packaging.md` | electron-vite 설정, tsconfig, electron-builder.yml, 네이티브 모듈 빌드 설정, extraResources (sidecar 바이너리), scripts 정의, CI/CD 파이프라인 | High |
| B2 | `05-testing-strategy.md` | 테스트 프레임워크 (vitest), 서비스별 테스트 파일, mock 전략, E2E 전략, 커버리지 목표 | Medium |
| B3 | `07-sidecar-management.md` | faster-whisper/Piper 바이너리 배포, Python 환경 관리, 모델 저장 경로, 버전 관리, 헬스체크 상세, 크래시 복구 | High |
| B4 | `08-security-checklist.md` | preload 화이트리스트, IPC 입력 검증 (zod), webPreferences, CSP, sandbox 설정, 외부 URL 열기 제한 | Medium |
### 4.3 CLAUDE.md 보강
| # | 액션 | 우선순위 |
|---|------|---------|
| C1 | electron-vite 선택 근거 추가 | Low |
| C2 | 네이티브 모듈 리빌드 명령어 (`npx electron-rebuild`) 추가 | Medium |
| C3 | 환경 요구사항 추가: Node.js 버전, Python 버전 (faster-whisper용), MSVC Build Tools | Medium |
---
## 5. 요약 매트릭스
| 카테고리 | High | Medium | Low | 합계 |
|---------|------|--------|-----|------|
| 설계서 불일치 | 4 | 5 | 0 | 9 |
| 빠진 설계 영역 | 4 | 4 | 4 | 12 |
| 실현 가능성 리스크 | 2 | 2 | 1 | 5 |
| 보강 액션 아이템 | 7 | 9 | 2 | 18 |