# Sprint Contract — generic-sut-split (3-tier) ## Goal `CLAUDE.md §8.1` 신규 규칙(**Generic / HmEG-aware / App-specific 3계층 분리**)을 코드베이스에 실제로 반영한다. 향후 EG-BIM Modeler 외에도 HmEG를 호스팅하는 다양한 WPF 응용을 추가할 때 Generic 코어와 HmEG-aware 미들 계층을 재사용하고, 앱마다 다른 부분(플러그인 진입점, 명령 lifecycle 어댑터)만 새로 작성하면 되도록 한다. ## Background 지금까지 모든 모듈이 `src/Recordingtest.*` 평면에 있고, `Recordingtest.EgPlugin`/`Recordingtest.EngineBridge`가 "EG-BIM 전용"과 "HmEG 일반"과 "Generic"이 섞인 채로 존재한다. 사용자 디렉티브: 1. 처음부터 Generic vs SUT-specific 분리 2. **HmEG는 사용자 WPF 앱군의 공통 엔진** — HmEG-aware 미들 계층을 따로 두어 앱 간 재사용 따라서 분리는 2-tier가 아니라 **3-tier**: Generic → HmEG-aware → App-specific (EgBim). ## Definition of Done ### D1. 폴더/csproj 이동·분할 **Generic 신설**: - `src/Recordingtest.Bridge.Abstractions/` — `IEngineStateProvider`, `CameraSnapshot`, `SceneSnapshot`, 향후 `IFocusProbe`/`IHitTestProbe`/`ICommandLifecycle`. **`HmEG.dll` 참조 금지**. - `src/Recordingtest.Bridge.Client/` — generic HTTP 클라이언트 (`HttpClient` 래퍼, `BridgeClientException`, `IBridgeClient`) **HmEG-aware 신설** (`src/Hmeg/` 하위): - `src/Hmeg/Recordingtest.Hmeg.Bridge/` — `HmegDirectStateProvider : IEngineStateProvider` (HmEG `Space`/`HmEGViewport`/`CameraCore` 직접 호출), HTTP 서버는 별도. **`HmEG.dll`만 참조**. - `src/Hmeg/Recordingtest.Hmeg.TargetResolver/` — 씬 노드 hit-test/포커스 식별 (Gap I 우회의 HmEG-aware 레이어) - `src/Hmeg/Recordingtest.Hmeg.Catalog/` — `Recordingtest.EngineBridge`의 정적 분석/CandidateFinder를 이쪽으로 이동 - `src/Hmeg/Recordingtest.Hmeg.Bridge.Client/` — `HmEgHttpSnapshot` 등 HmEG-shaped 응답 타입 (현 `Recordingtest.EngineBridge.Client`에서 분리) **App-specific (EgBim) 신설** (`src/Sut/EgBim/` 하위): - `src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost/` — MEF entry. `EditorPlugin` 베이스 상속, `BridgeHttpServer` 부팅. 현재 `Recordingtest.EgPlugin.HmEgBridgePlugin` 이동. - `src/Sut/EgBim/Recordingtest.Sut.EgBim.Adapter/` — EG-BIM Modeler `AppManager` 진입점, command lifecycle 어댑터. 현재 `ReflectionAppManagerAccessor`는 여기로 (CI fallback 용도). EG-BIM 전용 SUT 멤버 이름 후보(예: `Editor.AppManager.AppManager`)는 이 모듈에만 등장. **테스트 이동**: - `tests/Recordingtest.Bridge.Abstractions.Tests/` (신규) - `tests/Hmeg/Recordingtest.Hmeg.Bridge.Tests/` (현 EgPlugin 테스트의 HmEG 부분) - `tests/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost.Tests/` (현 EgPlugin 테스트의 EgBim 부분) - `tests/Hmeg/Recordingtest.Hmeg.Catalog.Tests/` (현 EngineBridge.Tests 이동) - `tests/Hmeg/Recordingtest.Hmeg.Catalog.IntegrationTests/` (현 EngineBridge.IntegrationTests 이동) **제거**: - 기존 `src/Recordingtest.EgPlugin/`, `src/Recordingtest.EngineBridge/`, `src/Recordingtest.EngineBridge.Client/` 디렉터리 (내용물은 위 3계층으로 분배) ### D2. 네임스페이스 rename - `Recordingtest.Bridge.*` (Generic) - `Recordingtest.Hmeg.*` (HmEG-aware) - `Recordingtest.Sut.EgBim.*` (App-specific) - 모든 `using Recordingtest.EgPlugin;` 정리 - `recordingtest.sln` 프로젝트 경로/이름 갱신 ### D3. 인터페이스 추출 - `IEngineStateProvider`, `CameraSnapshot`, `SceneSnapshot` → `Recordingtest.Bridge.Abstractions` (Generic) - 현재 `Recordingtest.EgPlugin` 내 정의 제거, 새 위치를 참조 ### D4. HmegDirectStateProvider 골격 신설 - `src/Hmeg/Recordingtest.Hmeg.Bridge/HmegDirectStateProvider.cs` 작성 (구현은 람다 주입 형태, `docs/hmeg-api-survey.md` §"v3 구현 방향" 참고) - 단위 테스트 — fake viewport/space 람다로 D2/D3/D4(원래 v3 contract)에 해당하는 동작 검증 - **본 contract에서는 골격만**. 실제 HmEG 라이브 검증은 `engine-bridge-v3` 후속 contract. ### D5. EgBim PluginHost 보존 - 현 `HmEgBridgePlugin`의 동작(MEF 발견, BridgeHttpServer 부팅, ReflectionEngineStateProvider 폴백)은 동일해야 함 - provider 결정 로직: `HmegDirectStateProvider` 가능하면 사용, 실패 시 `ReflectionEngineStateProvider` 폴백, 최종 실패 시 `NullEngineStateProvider` ### D6. 의존 그래프 검증 - Generic 모듈의 csproj는 `HmEG.dll` 또는 `Editor*PluginInterface.dll` 을 **직간접 참조하지 않음** - HmEG-aware 모듈의 csproj는 `HmEG.dll`만 참조, 특정 앱 어셈블리 참조 금지 - App-specific 모듈만이 자기 앱 어셈블리 참조 - 신규 `Recordingtest.Architecture.Tests` — `Assembly.GetReferencedAssemblies()` 검사로 위반 검출. 각 계층별 expected reference set assert. ### D7. 빌드/테스트 green - `dotnet build recordingtest.sln` 성공 - `dotnet test recordingtest.sln` 100+ tests 모두 pass - 신규 ArchitectureTests + HmegDirectStateProvider 단위 테스트 통과 ### D6. 빌드/테스트 green - `dotnet build recordingtest.sln` 성공 - `dotnet test recordingtest.sln` 100+ tests 모두 pass (현재 상태 유지) - 신규 ArchitectureTests 통과 ### D8. PROGRESS/PLAN 갱신 + history - PROGRESS.md Done에 항목 추가 - PLAN.md에서 본 contract 제거 - `docs/history/YYYY-MM-DD_generic-sut-split.md` 작성 - CLAUDE.md §8.1 표(현재 모듈 분류)를 마이그레이션 후 상태로 갱신 ### D9. 단일 커밋(권장) 또는 2단 커밋 - 옵션 A: 단일 커밋 — sln 무결성 보장, BREAKING 명시 - 옵션 B: (1) git mv만, (2) 내용 변경 — git rename detection 보존 - Generator 판단. 어느 쪽이든 메시지 prefix `BREAKING:` ## Out of scope - 새 SUT(다른 WPF 앱) 추가 — 본 contract는 구조만 만든다 - engine-bridge v3의 `HmEgDirectStateProvider` 구현 — 그건 별도 contract `engine-bridge-v3` 후속 - generic 코어의 기능 변경 — 순수 rename/이동만 ## Interfaces ```csharp // src/Recordingtest.Bridge.Abstractions/IEngineStateProvider.cs (generic) namespace Recordingtest.Bridge; public interface IEngineStateProvider { IReadOnlyList GetSelectedIds(); CameraSnapshot GetCamera(); SceneSnapshot GetScene(); bool GetRenderComplete(); } public sealed record CameraSnapshot(double[] Eye, double[] Target, double[] Up, double Fov); public sealed record SceneSnapshot(int ObjectCount, string? DocumentPath); ``` ```csharp // src/Sut/EgBim/Recordingtest.Sut.EgBim.Plugin/HmEgDirectStateProvider.cs (SUT) namespace Recordingtest.Sut.EgBim.Plugin; using Recordingtest.Bridge; public sealed class HmEgDirectStateProvider : IEngineStateProvider { ... } ``` ## Risks - **sln 경로 깨짐**: csproj 이동 시 sln 갱신을 빠뜨리면 빌드 깨짐. 완화: D6에서 솔루션 빌드 + 테스트 강제. - **using 구문 누락**: 네임스페이스 rename 시 다른 프로젝트의 using이 깨짐. 완화: 빌드가 잡아냄. - **engine-bridge v3 진행 중 방해**: scaffold 미커밋 상태(IAppManagerAccessor 등). 본 refactor 전에 v3 scaffold를 먼저 커밋해 두는 게 안전. - **git rename detection**: 폴더 이동 + 내용 변경이 동시에 들어가면 git이 rename 인식을 못 할 수 있음. 완화: 가능한 한 "이동만" 한 번 커밋, "내용 변경"은 후속 커밋. (단일 커밋 vs rename 보존 trade-off — D8과 충돌 가능. Generator 판단.) ## Estimated complexity 중. 코드 로직 변경은 거의 없고 mass rename + 폴더 이동 + sln 정리. 시간보다 손실 위험 관리가 핵심. ## Evaluation plan Evaluator는: 1. D1~D5를 파일/폴더/csproj 검사로 채점 2. D5의 ArchitectureTests 실행 3. D6의 전체 build/test 실행 4. D8의 git log 단일 커밋 확인 fail 1회 → Generator 재작업. 누적 3회 → 자동 중단.