# 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**: `(): ` (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 반환 후 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개) 위반 시 현장 장비 오동작 또는 아키텍처 붕괴. 예외 없음. ### 프로토콜/바이너리 (F3–F5, F7–F8, F16–F19) | 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/설정 (F1–F2, F9, F11) | ID | 금지 | 이유 | |----|------|------| | F1 | INI 키 오타 수정 금지 (`ColorOder`, `ShowInfomation`, `PorotocolType`) | 현장 장비 하위 호환성 | | F2 | Boolean INI 값을 `true`/`false` 변경 금지 | 레거시 앱·컨트롤러가 `0`/`1`만 읽음 | | F9 | API Key/비밀번호 하드코딩 금지 | 환경변수 사용 필수 | | F11 | AppSettings static 필드 직접 수정 금지 | `IConfigRepository` 경유 필수 | ### 아키텍처 (F6, F10, F12–F15) | 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 ├── 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 ` 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/`)를 분석했다면 그 발견을 즉시 텍스트북에 반영해야 다음 세션에서 같은 분석을 반복하지 않는다.