Files
dabit-mcp/CLAUDE.md
insulee 640c9a3380 feat(mcp): Dabit.Mcp 초기 공개 (리네임 + colors 수정 + scroll 검증)
- refactor: DabitChe.Mcp → Dabit.Mcp (namespace, csproj, exe 모두)
- fix: display_message colors를 쉼표 구분 문자열로 변경 (ModelContextProtocol 1.3.0 배열 바인딩 버그 우회)
- docs: scroll 4방향(left/right/up/down) 실기 검증 완료 (DIBD600T V3.6.1)

20 도구 (읽기 4 + 콘텐츠 2 + 설정 14), stdio 전송, Windows exe.
실기 검증: scroll/fill_screen 8색/display_message 글자별색 모두 정상.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 11:20:49 +09:00

506 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md — DabitChe 모노레포 루트
LED 전광판 콘텐츠 제작 + 스케줄 관리 + 컨트롤러 전송 소프트웨어 (다빛솔루션 DABIT)
## 프로그램 정체성
DabitChe는 **LED 전광판 운영 소프트웨어**이다. 핵심 기능은 콘텐츠 제작(텍스트/이미지) → 스케줄 편집(PLA/BGP) → 컨트롤러 전송이다. 통신설정, 제어, 시뮬레이터는 보조 기능이다. dbSimulator(DBPS)는 dabitche의 부분집합으로, 프로토콜 시뮬레이터 기능에 해당한다. 사용자는 현장 설치기사/운영자이며, 프로토콜을 몰라도 사용 가능해야 한다.
## Naming 규칙 (3층) — 회사 / 제품 / 프로토콜
이름은 3층으로 나눈다. 새 프로젝트·모듈·MCP 명명 시 이 규칙을 따른다.
| 층 | 이름 | 적용 대상 | 예 |
|----|------|----------|-----|
| 회사/브랜드 | **Dabit** (다빛솔루션 DABIT) | 하드웨어 컨트롤러를 다루는 브랜드 레벨 자산 | MCP 서버 `Dabit.Mcp` (등록명 `dabit-led`) |
| 제품 | **DabitOne** (레거시명 = DabitChe) | PC 운영 앱 | `DabitOne.Desktop`, `DabitOne.AutoSend` |
| 프로토콜 | **DibdProtocol** | 제품 무관 프로토콜/전송 라이브러리 | `DibdProtocol.Core` / `.Transport` |
- **레거시 잔재 주의**: 폴더·namespace·sln은 아직 `DabitChe.*`(Desktop/AutoSend). 단 출력물(exe `DabitOne.exe`)·창 제목은 이미 `DabitOne`. 내부 `DabitChe``DabitOne` 전체 리네임은 **워크트리 다수 동시 진행**이라 충돌 위험 → main 단일 big-bang으로 일괄 처리 예정(보류).
- `Dabit.Mcp`가 형제 `DabitOne.*`와 이름이 다른 건 **의도된 계층 구분**(브랜드 vs 제품)이지 실수가 아니다. (2026-06-02 MCP만 선행 리네임: `DabitChe.Mcp``Dabit.Mcp`, 등록명 `dabitche-led``dabit-led`)
## UI/UX 원칙
| 원칙 | 설명 |
|------|------|
| **가로 우선 레이아웃** | 콘텐츠 편집 + 스케줄 그리드가 핵심 → 가로 공간을 넉넉히 확보 |
| **좁은 레이아웃 금지** | dbSimulator(580px 고정)의 좁은 배치를 그대로 가져오지 않음 — 가로 900px+ 기준으로 재배치 |
| **GroupBox 가로 병렬** | 통신설정 등에서 Serial/TCP/UDP를 세로로 쌓지 말고 가로 2~3열로 배치 |
| **입력 필드 폭 제한** | COM 포트(5글자), IP(15글자), Port(5글자) → 필드 폭을 내용에 맞춤, 우측 여백 과다 금지 |
| **전체 통일감** | 한 패널의 배치를 바꾸면 나머지 패널도 동일 원칙 적용 |
| **사이드바 5그룹** | 통신 → 설정 → 시뮬 → 편집 → 고급 (사용 순서 = 위→아래) |
| **Design Token System** | `Themes/` 폴더에 토큰 + implicit style 집중. **토큰 1곳 수정 → 전체 반영**이 핵심. 정본: `AI_Rules/080_ui-design-system.md` |
| **창 크기 기준** | 새 Window/팝업의 크기 요청 시 **메인 창(MainWindow) 대비 비율**로 확인. 예: "메인의 2/3", "메인의 1/2". 비율 미지정 시 사용자에게 "메인 창 대비 몇 분의 몇으로 할까요?" 확인 후 적용 |
### Design Token 필수 규칙
| 규칙 | 이유 |
|------|------|
| 인라인 FontSize/Height/Background/Foreground 금지 | 토큰 파일에서만 관리해야 전역 변경 가능 |
| RowDefinition Height="Auto" 필수 | 고정값은 Control.Margin을 찌그러뜨림 |
| 모든 입력 컨트롤에 FontWeight="Normal" 명시 | GroupBox SemiBold 상속 차단 |
| Window 태그에 `Background="{StaticResource Surface.Background}"` | Window는 implicit style 불가 |
| 새 컨트롤 추가 시 Themes/ implicit style 확인 | 토큰 시스템 밖에서 컨트롤이 생성되면 불일치 발생 |
### Design Token 현재 값 (참고)
값은 `Themes/_Typography.xaml`, `_Spacing.xaml`, `_Colors.xaml`에서 관리. 변경 시 해당 파일만 수정.
| 토큰 | 현재 값 | 파일 |
|------|--------|------|
| `Control.Height` | 26 (MinHeight) | `_Typography.xaml` |
| `Control.Margin` | 0,4,0,4 | `_Spacing.xaml` |
| `Font.Size.MD` | 12 | `_Typography.xaml` |
**Button 규칙:**
- 전역 스타일이 `MinHeight=26`, `Padding="12,0"`으로 설정됨 — **ComboBox/TextBox와 동일 높이**
- **인라인 Padding 금지** — 버튼마다 다른 Padding을 넣으면 높이가 달라져 이질적. 전역 스타일만 사용
- 인라인 `FontWeight`, `FontFamily`, `Height` 지정 금지 — 전역 스타일(`Themes/Button.xaml`)에서 관리
- 예외적으로 큰 버튼이 필요하면 Named Style(x:Key)로 별도 정의. 인라인 Padding은 여전히 금지
## 핵심 규칙
- **한국어 응답**, 간결함 최우선 (3~5줄), 인사말/서론/결론 생략
- **PowerShell 7.0+** 필수 (`pwsh.exe`), Bash/Linux 명령어 금지
- **C# nullable enable** (WPF), **TypeScript strict** (Electron)
- camelCase/PascalCase/SCREAMING_SNAKE_CASE, UTF-8 LF
- **Conventional Commits**: `<type>(<scope>): <desc>` (scope: protocol, ui, transport, settings, desktop, electron, test, docs)
- 환각 방지: 근거 부족 시 명시, 불확실 시 추측 표기 + 대안 제시
### AI 코딩 4대 원칙
1. **Think Before Coding** — 불확실하면 추측하지 말고 먼저 확인
2. **Simplicity First** — 요청받은 것만 구현
3. **Surgical Changes** — 수정 대상만 변경
4. **Goal-Driven Execution** — 명령보다 목표로 지시
### 모듈화(Modularity) 원칙
핵심 기능은 나중에 독립 앱/CLI/REST API로 추출할 수 있게 만든다:
- **Service 계층에 WPF 타입(Window, Dispatcher, Application 등) 직접 참조 금지** — UI 알림은 Result<T> 반환 후 ViewModel이 처리
- **DibdProtocol/은 플랫폼 무관 라이브러리로 유지** — System.Windows 참조 절대 금지
- **새 Service 추가 시 `DialogService.Instance` 직접 호출 금지** — IDialogService 주입 또는 ViewModel에서 처리
- **`DibdProtocol.Transport/Rpc/`** — DIBD JSON-RPC 2.0 envelope 96 method 본문, namespace `Dabit.LedProtocol` (Blazor WASM 회피 영역이라 Transport 어셈블리 적재, 0517a 트랙)
### 영속성(Persistence) 필수 규칙
새 기능/UI 추가 시 반드시 확인:
- **사용자가 입력한 모든 설정값은 앱 재시작 후에도 유지**되어야 한다
- 새 입력 컨트롤(ComboBox, TextBox, CheckBox 등) 추가 시 → AppSettings + config.properties 저장/복원 구현 필수
- 저장 경로: `AppSettings``PropertiesConfigRepository``config.properties`
- 슬롯 구조 데이터는 `SettingService.SetProperties()` 배치 저장 사용
- 앱 종료 시 `App.OnExit()`에서 자동 저장됨 — 새 ViewModel 추가 시 여기에 SaveSettings 호출 추가
## FORBIDDEN (19개)
위반 시 현장 장비 오동작 또는 아키텍처 붕괴. 예외 없음.
### 프로토콜/바이너리 (F3F5, F7F8, F16F19)
| ID | 금지 | 이유 |
|----|------|------|
| F3 | OPCode 값 변경 금지 | 컨트롤러 펌웨어가 고정 OPCode에 의존 |
| F4 | HEX 프레임(DLE-STX) 포맷 변경 금지 | 컨트롤러 파싱 고정 |
| F5 | ASCII 프레임(`![...!]`) 포맷 변경 금지 | 레거시 호환 |
| F7 | `pendingDle` 상태 제거 금지 | TCP 버퍼 경계 DLE(0x10) 분할 시 패킷 손실 |
| F8 | `DLE_OPCODES` Set 변경 금지 | C# `ShouldApplyDle()`과 정확히 일치 |
| F16 | DisplaySignal 시그널 코드 변경 금지 | 70+ 모델이 의존 |
| F17 | CrcCalculator 수정 금지 | 펌웨어 CRC 테이블과 일치 필수 |
| F18 | MSchedule 25바이트 구조 변경 금지 | 펌웨어 `memcpy` 직접 매핑 |
| F19 | tsParameter 792바이트 구조/오프셋 변경 금지 | 바이너리 레이아웃 고정 |
### INI/설정 (F1F2, F9, F11)
| ID | 금지 | 이유 |
|----|------|------|
| F1 | INI 키 오타 수정 금지 (`ColorOder`, `ShowInfomation`, `PorotocolType`) | 현장 장비 하위 호환성 |
| F2 | Boolean INI 값을 `true`/`false` 변경 금지 | 레거시 앱·컨트롤러가 `0`/`1`만 읽음 |
| F9 | API Key/비밀번호 하드코딩 금지 | 환경변수 사용 필수 |
| F11 | AppSettings static 필드 직접 수정 금지 | `IConfigRepository` 경유 필수 |
### 아키텍처 (F6, F10, F12F15)
| ID | 금지 | 이유 |
|----|------|------|
| F6 | BrowserRouter 사용 금지 | Electron file:// 미지원, HashRouter 필수 (WPF 해당 없음) |
| F10 | Bash 명령어 금지 | PowerShell 7.0+ 사용 |
| F12 | SerialTransport `portLock`(SemaphoreSlim) 우회 금지 | 동시 접근 시 포트 충돌 |
| F13 | DI 외부에서 Service 직접 생성 금지 | DI 컨테이너 일관성 |
| F14 | ViewModel에서 직접 Transport 호출 금지 | Service 계층 경유 필수 |
| F15 | MQTT 토픽명 변경 금지 (`/db_msg`, `/db_set`, `/db_sch`, `/db_msg_r`, `/db_sch_r`) | 장비 구독 고정 |
### 읽기 전용
| 금지 | 이유 |
|------|------|
| `references/DabitChe-V2.2.1/` 소스 수정 | 읽기 전용 레거시 참조 — 마이그레이션 대조 기준 훼손 |
## Architecture
```
dabitche/
├── DabitChe.Desktop/ ← [NEW] C# .NET 9.0 + WPF + CommunityToolkit.Mvvm
│ ├── Views/ │ XAML UI (19 FU 매핑)
│ ├── ViewModels/ │ MVVM ViewModel
│ ├── Services/ │ 비즈니스 로직
│ ├── Config/ │ AppSettings, IConfigRepository
│ └── Helpers/ │ EventBus, Result<T>
├── DabitChe.Desktop.Tests/ ← xUnit 데스크톱 테스트
├── DibdProtocol/ ← [SHARED] 프로토콜 라이브러리 (Desktop + Simulator 공유)
│ ├── Protocol/ │ HexCommand, AsciiCommand, PacketBuilder, PacketParser
│ ├── Codec/ │ CrcCalculator, DleFramer, BccCalculator, EncodingConverter
│ ├── Transport/ │ ITransport, Serial/TCP/UDP/MQTT/BT
│ ├── Model/ │ DisplayConfig, ConnectionState, MessageSlot
│ └── Image/ │ DatBuilder, RgbConverter
├── DibdProtocol.Tests/ ← xUnit 프로토콜 테스트
├── DabitChe.Electron/ ← [현행] Electron 33 + React 19 + Vite 6 + Zustand 5
│ ├── src/main/ │ Electron 메인 (IPC, services, transport)
│ ├── src/renderer/ │ React UI (10 pages, 44 components, 8 stores)
│ └── src/shared/ │ 공유 타입/프로토콜/i18n
├── references/ ← 참조 자료 집중
│ ├── DabitChe-V2.2.1/ │ [읽기 전용] C++ Builder 6 레거시
│ ├── DabitChe_Java/ │ [로컬 참조] Java 포팅 (git 추적 안 함)
│ ├── Font/ │ .fnt + FontConverter (릴리즈에 복사)
│ ├── firmware/ │ .bin 펌웨어 (릴리즈에 복사)
│ └── DabitChe_UserManual_v9.0.pdf
└── docs/ ← 통합 문서
```
### 전환 전략
- **현행**: DabitChe.Electron 운영 중
- **목표**: DabitChe.Desktop(C# WPF)으로 점진적 대체
- **공유**: DibdProtocol/ 라이브러리를 Desktop과 dbSimulator가 공유
- 프로토콜 엔진은 DibdProtocol/에 집중, 앱별 중복 구현 금지
## Build & Test
```powershell
# ── C# WPF (새) ──
cd DabitChe.Desktop
dotnet build
dotnet test ../DibdProtocol.Tests
dotnet test ../DabitChe.Desktop.Tests
# ── Electron (현행) ──
cd DabitChe.Electron
npm test # Vitest 387+개
npm run test:e2e # Playwright E2E
npm run dev # Vite dev (:5173)
npm run dev:electron # 전체 앱
npm run package:win # NSIS 인스톨러
```
### DibdProtocol.Core 변경 시 브라우저 안전성 게이트
`DibdProtocol.Core/` 하위 소스를 수정한 커밋은 CI(`.gitea/workflows/ci.yml`)에서
다음을 자동 실행·검증한다.
1. `dotnet build DabitChe.sln --configuration Release`
2. `dotnet test DibdProtocol.Tests` + `dotnet test DibdProtocol.Core.Tests`
3. `dotnet publish DibdProtocol.Core.BlazorCheck/... --configuration Release`
3번이 실패하면 Core에 브라우저 비호환 API(System.IO.Ports / System.Net.Sockets /
MQTTnet 등)가 섞인 것. 로컬에서도 Core 수정 후
`dotnet publish DibdProtocol.Core.BlazorCheck -c Release` 수동 실행을 권장한다.
`dotnet build`만으로는 WASM 링커 회귀를 감지 못 한다.
**단, publish 성공은 "WASM 링커·트림 통과"까지만 증명하고, 브라우저에서
`Pages/Home.razor``OnInitialized()` smoke가 실제 실행됨을 보장하지는 않는다.**
런타임 증명이 필요하면 `dotnet run --project DibdProtocol.Core.BlazorCheck`
브라우저에서 5-way smoke(CRC·DLE·EUC-KR·byte-font·ASCII)를 육안 확인하거나,
Phase 1+에서 Playwright 1회 접속 step을 CI에 추가한다.
## Hooks
DabitChe.Electron에 Claude Code hooks가 설정되어 있다.
| 패턴 | 동작 |
|------|------|
| 커밋 전 품질 게이트 | git commit 시 `npm run quality` 자동 실행, 실패 시 차단 |
| FORBIDDEN 위반 감지 | 파일 수정 시 FORBIDDEN 패턴 정규식 매칭, 위반 시 경고 |
| /careful 안전 모드 | `.claude/.careful` 존재 시 위험 명령 차단 |
| Gotchas 자동 수집 | 세션 종료 시 빌드/테스트 실패 패턴 분석 |
## 로그 작성 원칙
`LogService`는 4 레벨(Info/Warn/Error/Debug)을 제공한다. UI는 평소 Info/Warn/Error만 표시하고, Debug는 로그 헤더의 "Debug 보기" 토글(`MainWindow.xaml`)을 켜야 보인다. 호출부에서 적절한 레벨을 선택해 노이즈를 줄인다.
### 레벨 분류 기준
- **Info** — 사용자 시나리오 흐름. 작업 시작/완료, 핵심 결과, 세션 라이프사이클. **TX/RX 단발 명령은 Info 유지** (업체가 시스템 구현 시 protocol 참고용 — `_logService.Info($"TX: 시간동기화 ASCII ![0030...!]")` 같은 라인은 의도적으로 보임)
- **Warn** — 사용자 조치가 필요한 비정상. 파일 누락, 응답 status 비정상, 펌웨어 거부 가능성
- **Error** — 작업 실패. 통신 끊김, 예외
- **Debug** — 평소 OFF, 트러블슈팅 시 ON. 패킷 단위 hex 덤프(`TX[cmd] NB → RX MB (Tms)`), 반복 호출 진행률(블록 N/M), 분기 결정 trace, 동일값 반복 entry 출력
### 연속 전송 (펌웨어/폰트/이미지 다블록)
블록 단위 진행률은 **Debug**. 시작·요약(블록 수)·완료만 **Info**.
```
Info: 펌웨어 전송 시작 219블록
Debug: 펌웨어 전송: 1/219 (0%) ← 블록 단위 진행률
Debug: 펌웨어 전송: 51/219 (23%)
...
Info: 펌웨어 전송 완료
```
### 임시 디버그 (`DEBUG-TEMP`)
특정 버그 추적·신규 기능 검증을 위해 박은 일회성 로그는 다음 중 하나로 마킹한다:
1. 메시지 접두사: `_logService.Debug($"[DEBUG-TEMP] ...")`
2. 코드 주석: `// DEBUG-TEMP: <목적>, <날짜>`
검증 완료 시점에 두 갈래로 정리:
- "환경에 따라 또 깨질 수 있나?" (예: TCP fragment, 재부팅 응답) → **영구 Debug로 강등**`[DEBUG-TEMP]` 접두사 제거하고 `_logService.Debug`만 유지
- "한번 검증으로 끝났나?" (예: 특정 byte offset 확인) → **제거**
월 1회 또는 PR 직전 `git grep "DEBUG-TEMP"`로 잔존 검토.
### 영구 Debug 후보 — 시스템 경계
코드 안쪽 깊은 곳의 trace는 임시 디버그(검증 후 제거)지만, **시스템 경계의 객관적 사실**은 영구 Debug로 둔다:
- 외부 I/O raw byte (TX/RX hex, latency `(Tms)`)
- 분기 결정 (어떤 path 탔는지: legacy vs modern, ImageDir fallback 등)
- 재시도/fallback 작동 (`ack timeout, retry 2/3`)
- 외부 환경 로드 결과 (`config.properties NN키 로드, 인코딩=EUC-KR`)
- 변환 입출력 byte count (`ConvertToSplitDat: 3549B → 612B+893B`)
기준 한 줄: "사용자 환경에서만 재현되는 문제를 원격 진단할 때 결정적 단서인가?"
## Data Flow Chains (흐름 기반 탐색)
함수 시그니처는 AI 도구(grep/glob)로 직접 탐색한다.
### C# WPF (DabitChe.Desktop + DibdProtocol)
| 시나리오 | 호출 체인 |
|---------|----------|
| UI → 컨트롤러 명령 | View → ViewModel → Service → DibdProtocol.Protocol → DibdProtocol.Transport |
| 프로토콜 수신 파싱 | Transport → PacketParser → Service → ViewModel → View |
| INI 설정 로드 | Config → IConfigRepository → fs (EUC-KR → UTF-8) |
| 파일 블록 전송 | Service → 블록 분할 (1024B) → Protocol → Transport |
| 스케줄 파일 | Service → PLA 직렬화/역직렬화 (리틀엔디언) → fs |
### Electron (DabitChe.Electron)
| 시나리오 | 호출 체인 |
|---------|----------|
| UI → 컨트롤러 명령 | Renderer → preload (contextBridge) → IPC → DeviceCommandService → HexProtocolBuilder → Transport |
| 프로토콜 수신 파싱 | Transport → ProtocolParser (상태 머신) → CRC 검증 → DeviceCommandService → IPC → Renderer |
| INI 설정 로드 | Renderer → IPC → IniSettingsService → fs.readFileSync → iconv.decode (EUC-KR) |
| 파일 블록 전송 | Renderer → IPC → 블록 분할 (1024B) → DeviceCommandService → HexProtocolBuilder → Transport |
| 스케줄 파일 | Renderer → IPC → ScheduleFileService → PLA 직렬화/역직렬화 (리틀엔디언) |
## Symptom → Where to Look
| 증상 | 확인 위치 (C# WPF) | 확인 위치 (Electron) |
|------|--------------------|--------------------|
| 프로토콜 바이트 불일치 | `DibdProtocol/Protocol/` | `src/shared/protocol/` |
| UI 렌더링 문제 | `DabitChe.Desktop/Views/` | `src/renderer/pages/`, `src/renderer/components/` |
| 통신/시리얼 문제 | `DibdProtocol/Transport/` | `src/main/services/transport/` |
| INI 설정 문제 | `DabitChe.Desktop/Config/` | `src/main/services/settings/ini-settings-service.ts` |
| 빌드/테스트 실패 | `dotnet build` 출력 | `DabitChe.Electron/package.json` 스크립트 |
| 레거시 동작 참조 | `references/DabitChe-V2.2.1/CLAUDE.md` | 동일 |
## Decisions (왜 이렇게 했는지)
| 결정 | 이유 |
|------|------|
| C++ Builder → C# → Electron → C# WPF | 레거시 65개 TForm 직접 포팅 불가, 단계적 재작성 |
| dbSimulator DibdProtocol 공유 | 프로토콜 엔진 중복 개발 방지 |
| INI 오타(`ColorOder` 등) 보존 | 현장 장비 하위 호환성 유지 |
| INI 읽기 EUC-KR → 저장 UTF-8 | 점진적 인코딩 전환 |
| BCD 시간 인코딩 | 레거시 컨트롤러 RTC 호환 |
| 13바이트 밝기 payload | 시간대별 밝기 + CDS 센서 레거시 구조 |
| HexProtocolBuilder C# byte-identical | V3.0.0(C#)과 바이트 일치 보장 |
| Electron IPC preload.ts 경유 | contextIsolation 보안 모델 준수 |
| WPF ViewModel → Service → Transport | MVVM 계층 분리, Transport 직접 호출 금지 (F14) |
| DI 컨테이너 필수 | Service 생명주기 관리 일관성 (F13) |
## Markdown 작성 규칙 (`docs/` 편집 시)
1. **Frontmatter 필수**: title, tags, last_updated 3필드
2. **H1 1개**, 레벨 스킵 금지 (H2→H3 순서)
3. **첫 문장 = 문맥 앵커**: H2 직후 첫 문장에 주어+문맥 포함
4. **한 문단 = 한 줄** (Git Diff 가독성), 코드블록 독립 (언어 태그 필수)
5. **TL;DR**: 문서 상단에 무엇/왜/어떻게 3줄 요약
## Deep Context (심층 문서)
심볼 검색은 AI 도구(grep/glob)에 맡기고, 문서에는 코드가 말 못 하는 것만 담는다.
| 작업 유형 | 읽을 문서 |
|----------|----------|
| 정본 매트릭스 (SSOT) | `docs/00_AI_가이드/README.md` |
| 로드맵 | `docs/00_AI_가이드/10_ROADMAP.md` |
| 프로젝트 구조 | `docs/00_AI_가이드/11_프로젝트컨텍스트.md` |
| 코딩 패턴 | `docs/00_AI_가이드/20_CODE_PATTERNS.md` |
| 모듈/프로토콜 참조 | `docs/00_AI_가이드/30_MODULE_INDEX.md` |
| 워크플로우 절차 | `docs/00_AI_가이드/40_WORKFLOW.md` |
| 스킬 레퍼런스 | `docs/00_AI_가이드/41_SKILL_REFERENCE.md` |
| 빌드 함정/에러 패턴 | `docs/00_AI_가이드/50_BUILD_ERRORS.md` |
| 테스트 카탈로그 | `docs/00_AI_가이드/60_TEST_CATALOG.md` |
| 자율 루프 | `docs/00_AI_가이드/70_AUTONOMOUS_LOOP.md` |
| 시각 검증 | `docs/00_AI_가이드/72_VISUAL_VERIFY.md` |
| DeepDive (모듈별) | `docs/00_AI_가이드/Modules/` |
| 플랜+회고 | `docs/00_AI_가이드/Decisions/` |
| 프로토콜 바이트 명세 | `docs/01_protocols/protocol-reference.md` |
| 레거시 참조 | `references/DabitChe-V2.2.1/CLAUDE.md` |
| UI 디자인 시스템 | `AI_Rules/080_ui-design-system.md` (토큰 정의, 컴포넌트 스펙, 적용 절차) |
### PM 시스템 (장기 매니저 운영 + 자동 분업)
DabitONe의 통합 매니저 역할은 별도 문서·슬래시 커맨드·claude agents로 운영된다. 새 세션에서 PM 직책을 인수하려면 `/pm-start`로 부트스트랩, 종료 직전 `/pm-end`로 인계. 작업 분해 후 PM이 `@worker-isolated`, `@researcher`, `@code-reviewer`, `@dabitone-protocol-expert`를 자동 호출.
**시간축 (PM 시스템 문서·커맨드):**
| 산출물 | 용도 |
|--------|------|
| `PROJECT_BRAIN.md` (루트) | 현재 동적 상태 — 진행·보류·우선순위. 매 세션 갱신 |
| `docs/PM/PM_CONTEXT_MAP.md` | 영역 색인 — "어디에 뭐가 있나" |
| `docs/PM/PM_PROTOCOL.md` | PM 운영 룰 — 4-Role·검토·팀빌딩·가드레일 |
| `docs/PM/templates/spec.md` | Worker brief 표준 템플릿 |
| `docs/PM/templates/retrospective.md` | Phase 회고 템플릿 |
| `/pm-start` | PM 세션 부트스트랩 슬래시 커맨드 |
| `/pm-end` | PM 세션 종료 + `PROJECT_BRAIN.md` 갱신 |
**공간축 (claude agents 4종 — `.claude/agents/`):**
| Agent | 역할 | 호출 시점 |
|-------|------|---------|
| `worker-isolated` | 격리된 worktree에서 spec.md 따라 한 Phase·기능 구현. 빌드·테스트·commit | PM이 Phase 분해 후 호출 |
| `researcher` | 외부 자료·라이브러리·기술 비교 조사. WebFetch·WebSearch | 결정 분기 전 호출 |
| `code-reviewer` | Worker 완료 코드 독립 검토 (worker 대화 안 봄, 확증편향 회피) | Worker 완료 후 자동 호출 |
| `dabitone-protocol-expert` | DIBD 프로토콜 + 콘텐츠 포맷(DAT/PLA/BGP/ANI) + 전송 절차 전문가 | 프로토콜·콘텐츠·전송 영역 변경 시 자동 호출 |
**시행 강제 (Hook 1개 — `.claude/hooks/`):**
| 산출물 | 역할 | 호출 시점 |
|--------|------|---------|
| `r-pm-1-guard.ps1` | **R-PM-1 시행 강제** — PM 직접 grep/glob/read 3회 경고·5회 차단 | PreToolUse 자동 (`.claude/.pm-session-active` 존재 시 발동) |
**R-PM 직책 한도 (PM 세션에서 매번 준수)**:
- **R-PM-1**: 다중 grep/glob/read 직접 금지 — sub-agent(Explore/general-purpose)에 위임 필수. Hook이 3회 경고·5회 차단으로 시행 강제
- **R-PM-2**: 단일 핵심 문서 Read·작은 fix·사용자 인터뷰·PROJECT_BRAIN 갱신은 PM 직접 OK
- **R-PM-5**: 사용자 이미 답한 내용 재질문 금지·다음 단계 진입 전 명시 동의 받기·"잘못됐다" 지적 시 즉시 중단·결정 항목은 1~3개 선택지로 좁힘
- **R-PM-7**: Evidence Tier 강제 — 모든 결론 ✅사실(카메라/실기 확인) / 🟡추정(코드·논리 미검증) / ❌가정(미입증) 3분류 자동 분리. **코드 변경 ≠ 검증.** 사용자가 "근거는?" 묻기 *전에* 자발 분리. 사례: 2026-05-26 retry 정책 — PM이 코드 변경만 보고 "회복 + 견고" 단정했다가 사용자 지적
- 상세: `docs/PM/PM_PROTOCOL.md` 2.5 섹션, `AI_Rules/100_pm-system.md` "PM 직책 한도 R-PM-7"
**🚨 세션 종료 직전 반드시 `/pm-end`** — `PROJECT_BRAIN.md` 갱신 누락은 다음 PM 세션의 인계 단절 = 시스템 무결성 손상.
정본 가이드: `D:\Note\20_Marketing_Team\23_Resource\AI_Rules\100_pm-system.md` + `101_pm-system-guide.md`
### 프로토콜 점검 필수 참조
프로토콜 전송 코드(패킷 빌드, 바이트 매핑, msgType, /P 페이지 등)를 수정할 때 **반드시** 아래 5개 문서를 검토한다:
| 문서 | 용도 |
|------|------|
| `docs/01_protocols/ascii-reference.md` | ASCII 명령어 전체 목록 + 속성코드 |
| `docs/01_protocols/hex-protocol.md` | HEX 프레임 구조 + DLE 이스케이프 |
| `docs/01_protocols/hex-reference.md` | HEX 명령별 실전 예시 |
| `docs/01_protocols/protocol-reference.md` | DIBD 완전 레퍼런스 (ASCII+HEX 통합) |
| `docs/01_protocols/protocol-schema.json` | AI 파싱용 구조화 스키마 |
### 콘텐츠 편집 필수 참조 — **🚨 강제 규칙**
콘텐츠 편집(`TextEditorWindow`, `DatFileService`, `LegacyDatHeader`, `ConvertToSplitDat` 등) 관련 코드를 **수정·추가·리팩토링**할 때, 작업 전에 **반드시** 아래 문서를 읽는다. 호환성 가정을 깨뜨리면 레거시 파일을 읽을 수 없게 되어 마이그레이션이 실패한다.
| 문서 | 용도 |
|------|------|
| `docs/00_AI_가이드/85_EDITOR_LEGACY_COMPAT.md` | 레거시 분할 DAT 호환성 현황 + 파싱 누락 지점 + 수정 제안 |
| `docs/01_protocols/dat-bgp-pla-textbook.md` | DAT/PLA/BGP 바이트 포맷 전체 정의 (분할 DAT 2-섹션 구조 포함) |
| `memory/project_split_block_format.md` | 분할 블록 펌웨어 검증 기록 (Option[0], BFormKind=2, dsrCnt 필수) |
**원칙**: 바이트 포맷 호환성은 이미 맞춰져 있으므로 깨지 않는다. 파싱 로직은 누락되어 있으므로 수정 시 이 문서의 수정 범위(P0/P1/P2)를 따른다.
## Gotchas
- **Gitea 릴리즈 한국어 깨짐 방지** — Git Bash curl로 한국어 JSON 전송 시 인코딩 깨짐. 반드시 `pwsh.exe -NoProfile`에서 `ConvertTo-Json` + `[System.Text.Encoding]::UTF8.GetBytes()` + `Invoke-RestMethod` 조합 사용. curl 금지
- **ASCII 페이지 메시지 msgType은 항상 "1"** — 페이지 번호는 `/P{xx}` 속성(0-based)으로 지정. msgType에 페이지 번호를 넣으면 안됨
- **분할 DAT Section 2에 TextInfo 넣으면 XY 어긋남** — FCH(32B)만 기록, TextInfo(256B) 금지
- **BFormKind=1 대신 2 사용** — 펌웨어가 BFormKind=1을 다르게 처리하여 좌표 계산 오류 발생
- **분할 시 Option[0]=1 필수** — 누락 시 Y 5px 어긋남
- **분할 시 dsrCnt EDEffect 최소 0x01** — 0이면 컨트롤러가 분할을 인식 못함
- **레거시 분할 DAT의 Section1 FCH `RH`는 "메인 블록 폭"** — 전체 폭이 아님. H1F 샘플 파일의 `RH=16`인데 실제 전광판 폭은 96. `DatFileService.Load`가 현재 이를 전체 폭으로 오해하여 레거시 분할 파일을 못 읽음. 수정 전 반드시 `docs/00_AI_가이드/85_EDITOR_LEGACY_COMPAT.md` 참조
- **레거시 DAT 열기는 사이드카 기반** — `TextEditorWindow.OnLoadDat``.blocks.json` 사이드카가 없으면 `BackgroundImagePath`에 경로만 할당하고 TextInfo 파싱을 건너뜀. 레거시 파일(사이드카 없음) 호환 작업 시 이 경로 수정 필요
- **레거시 BGP/PLA의 dat 참조는 bare 파일명** — V2.x는 `.bgp`(`Data\Playlist\`)와 `.dat`(`Data\Image\`)이 다른 폴더. `ScheduleFileService.ParseLegacyBinary`가 BGP 같은 폴더만 뒤지면 못 찾고 bare name(`"test_r.dat"`)으로 fallback → 전송 단계 `File.Exists` 실패 → 0블록 전송 → 컨트롤러 슬롯 모두 동일 메모리(블록 0) 가리킴 → "1~7번 모두 RED" 증상. 수정 후 해석 우선순위: **`AppPaths.ImageDir`/`VideoDir`(레거시 V2.x DestTextPath와 동일) → BGP 같은 폴더 → bare name**. `ScheduleTransferService.ResolveContentPath`가 전송 단계에서도 한번 더 구제(2026-05-07 fix)
- **펌웨어/폰트 청크 전송은 ACK 대기 필수 — Fire-and-forget 금지** — 시리얼/TCP에서 컨트롤러 처리 지연(플래시 쓰기 등)으로 일부 청크가 누락되면 침묵 회귀(전송 성공 같지만 미적용·한글 폰트 비트맵 손상). `FirmwareService` + `FontSettingsViewModel`의 청크 루프는 반드시 `AckPolicy.Required` + 청크별 3회 retry. timeout warning이 가끔 뜨더라도 **무결성 > 속도**. 정본: 67f1fe88(Fire-and-forget 도입)의 회귀 fix (2026-05-09 카메라 검증 — 한글 폰트가 색 채움 형태로 깨졌던 증상이 ACK 대기 복원으로 해결). 향후 속도 개선 시 청크 간 sleep 등 다른 방향으로 (ACK 대기는 유지)
- **FNT 폰트 글리프 시작 ASCII는 글리프 수로 분기** — `FntRenderer.RenderEngGlyph``asciiCode * glyphSize`로 lookup하지만 폰트 파일별 시작 ASCII가 다름. 256/128 글리프 폰트(DABIT 표준, Gothic, Roman 8x16 등)는 ASCII 0부터, **95 글리프 폰트(ENG 16x16-ASCII-Roman/RN90, ENG 16x32-굴림체 등)는 ASCII 0x20(space)부터** 저장. 95 글리프는 `glyphIdx = asciiCode - 0x20` 보정 필요. 미리보기에서 "1" 입력 시 "Q"/"R" 같은 다른 글자가 나오면 이 보정 누락. 헤더 byte 12=W, byte 13=H, byte 14=glyphSize(B). 시작 ASCII는 헤더 명시 없어 글리프 수로 추정. 정본: FntRenderer 2026-05-10 fix
- **.NET 7+ SerialPort timeout은 IOException** — `SerialPort.ReadAsync` timeout이 .NET 6까지 `TimeoutException`이었으나 .NET 7부터 **`IOException`**으로 throw (GitHub dotnet/runtime#81035). 외부 catch가 `TimeoutException`만 잡으면 .NET 9 환경에서 회귀 — 시리얼 일시 timeout이 외부 retry 발동 안 함. transport retry 정책 작업 시 catch에 `IOException` + `SocketException` 추가 필수. `TransferSessionService.IsRetriable`은 이미 세 가지 다 잡으나, `FirmwareService` + `FontSettingsViewModel`의 청크 외부 retry는 좁아져 있을 수 있음. 정본: memory `reference_dotnet7_serialport_breaking`, 인계 doc `docs/00_AI_가이드/Decisions/2026-05-26-transport-retry-attempt-revert.md`
### Phase 1b Parser + VirtualControllerCore Gotchas (2026-04-21 v1.2 설계·플랜 반영)
- **Parser는 wire-faithful — text canonicalization 금지** — `PacketParser.TryParseDisplayMessagePayload`는 decode된 문자열을 **원문 그대로** 반환해야 한다. `\r`(0x0D) 제거 / `\n`→공백 변환 같은 canonicalization을 **parser 내부에서 절대 하지 말 것**. M07(CR/LF 원문 전송) Inv-1 byte stability를 보장하려면 wire-faithful 필수. Canonicalization은 `DisplayMessageSpecWireCanonicalizer.CanonicalizeForRoundTrip`에서만 수행
- **`CharColorCodec.Normalize`에 3-bit 마스킹 추가 금지** — Phase 1a `CharColorCodecTests``{1, 2, 9, 9, 9}` 보존을 기대하므로 마스킹 추가 시 breaking. 3-bit (fg·bg ∈ [0, 7]) 정책은 **Phase 1b `DisplayMessageSpecWireCanonicalizer`에만** 적용. Phase 1a 정본 Codec은 건드리지 않는다
- **`VirtualControllerCore.ApplyHexFrame`에는 framed packet만 — payload 단독 입력 금지** — `ApplyHexFrame``DleFramer.TryDeframe`을 경유하므로 입력은 `DLE STX DST LEN CMD DATA DLE ETX` 전체. payload(0x94 data)만 넣으면 `ApplyResult.MalformedFrame` 반환. payload만 있으면 `ApplyHexParsed(HexParsedResponse)`로 직접 진입할 것. 테스트 `FrameVsPayloadContractTests`가 계약 오사용을 검증
- **Empty-run canonical form 고정** — 빈 텍스트(M18, payload.Length == 16) payload는 `DefaultFg/DefaultBg/CharColors/FontGroup` 복원 불가. Parser와 `DisplayMessageSpecWireCanonicalizer` 모두 **`Text="", CharColors=null, DefaultFg=0, DefaultBg=0, FontGroup=Group0`** 으로 고정. `DisplayMessageSpecStructuralComparer`를 빈 텍스트 특례로 약화시키지 말 것
- **`VirtualControllerCore`는 Phase 1b에서 단일 스레드 전용** — 내부 `Dictionary`·`List` 기반이라 `Apply*`/`Snapshot`/`GetSection` concurrent 호출 시 레이스 → 동작 미정의. 호출자가 UI 스레드 or 전용 worker로 직렬화해야 한다. Phase 2+에서 concurrent 필요 시 `lock` 또는 `ConcurrentDictionary` 전환 검토. 구현 시 XML doc에 반드시 "Thread-safety: Phase 1b 단일 스레드 전용" 명시
- **`ControllerState.Sections`/`GetSection`은 deep-copy snapshot** — `ImmutableDictionary`/`ImmutableArray`는 컨테이너 불변성만 보장. `DisplayMessageSpec.Run.CharColors.Colors`가 가변 `int[]`이므로 `SectionManager.CloneSpec`에서 `Array.Copy`로 복제해야 내부 상태 aliasing 차단. Snapshot/GetSection 호출자는 반환값의 `NormalizedCharColors.Colors[0] = 999` 변경이 내부에 영향 주지 않음을 가정하고 설계. `SnapshotAliasMutationTests` 2건이 계약을 증명
## 커밋 시 문서 업데이트 규칙 — **🚨 강제 규칙**
### MANDATORY: 커밋 전 체크리스트
**커밋하기 직전에 반드시 다음 4가지를 확인한다. 하나라도 놓치면 커밋 금지.**
```
☐ 1. 프로토콜 바이트 구조 변경/발견이 있으면 → docs/01_protocols/*.md 업데이트
☐ 2. 레거시 대비 새 기능이면 → docs/00_AI_가이드/80_LEGACY_ENHANCEMENTS.md 업데이트
☐ 3. AI가 반복 실수한 패턴이면 → CLAUDE.md Gotchas 섹션 업데이트
☐ 4. 펌웨어 동작 검증 결과면 → memory/project_*.md 추가
```
### 발견 유형별 업데이트 대상
| 발견 유형 | 업데이트 대상 |
|----------|-------------|
| 프로토콜 바이트 구조, DAT/PLA/BGP/ANI 포맷 | `docs/01_protocols/dat-bgp-pla-textbook.md` |
| HEX/ASCII 명령 응답 포맷 | `docs/01_protocols/hex-reference.md`, `ascii-reference.md` |
| 레거시 대비 변경/확장 기능 | `docs/00_AI_가이드/80_LEGACY_ENHANCEMENTS.md` |
| AI가 반복한 실수 패턴 | 이 파일의 `Gotchas` 섹션 |
| 펌웨어 동작 검증 결과 | `memory/` (project 타입 메모리) |
| 새 ViewModel/Service 패턴 | `docs/00_AI_가이드/20_CODE_PATTERNS.md` |
### 강제 시행 절차
**커밋 프로세스:**
1. **코드 변경 완료**`git diff`로 변경 내용 확인
2. **체크리스트 순회** → 4가지 업데이트 대상 중 해당하는 것 식별
3. **문서 업데이트** → 발견 사실을 해당 문서에 기록
4. **문서도 함께 스테이징**`git add <code files> <doc files>`
5. **커밋 실행** → 코드+문서를 같은 커밋에 포함
### 위반 시 복구 절차
**이미 코드만 커밋했고 문서를 빼먹은 경우:**
```powershell
# 직후 커밋으로 문서 추가
git add docs/
git commit -m "docs: [커밋해시] 후속 — {발견 내용} 반영"
```
### Why 이 규칙이 강력한가
- **반복 실수 방지**: 세션마다 레거시 바이트를 재분석하면 시간 낭비
- **다음 세션 컨텍스트**: 문서가 최신이어야 다른 세션/에이전트가 올바른 정보로 작업
- **장기 유지보수**: 6개월 후 이 코드를 볼 때 "왜 이렇게 했지?"에 답해야 함
- **검증 이력**: "컨트롤러 검증 완료 (날짜)" 표시로 회귀 방지
### 구체적 사례 (2026-04-10)
ANI 파일 포맷 수정 세션에서 놓친 것:
- ✗ FCDavHeader 16B (32B가 아님) — 레거시 소스에서 발견
- ✗ TextInfo가 프레임 데이터 뒤에 옴 (앞이 아님)
- ✗ BlockText[1] Y=16 하드코딩 — 레거시 TFormMessageText.cpp:1936
- ✗ 0x9B BGP 비디오 opcode — 문서에 미기재
- ✗ HEX SetScreenSize 응답 [0xF1, rows, cols] — 기존 문서 [status] 오기재
**교훈**: 레거시 소스(`references/DabitChe-V2.2.1/`)를 분석했다면 그 발견을 즉시 텍스트북에 반영해야 다음 세션에서 같은 분석을 반복하지 않는다.