diff --git a/.claude/README.md b/.claude/README.md index 3eb8129..b53ed88 100644 --- a/.claude/README.md +++ b/.claude/README.md @@ -50,7 +50,7 @@ | `source-state-plan.md` | **[2026-08-14 신설 — `bind-system-plan.md` 3단계 분할 + 구 store-semantics.md 흡수]** 반응형 코어: `Source`⊇`State` 구조적 서브타입(`RefSource` 폐기, 단방향 의존으로 Luau 솔버 회피 — 스파이크 `08` 통과), **push-invalidate/pull-recompute** 전파 모델과 "관측해야 실체화된다" 전역 원칙, State 체인 플래튼 기각(캐싱이 State의 존재 이유), `:With`도 매번 새 노드(clone 계열인 `Tag`/`Modifier`와 혼동 주의), `:Compute`의 lazy 핸들 계약(`:Get()` 누락이 반복되는 실수)·trailing args sugar·`fn(self, previous?, ...deps)` 순서·`previous`, `:Apply`, `:Emit()`(Source 원천 전용 하드 경계)과 `Store`/`Source`의 `T`가 Modifier일 수 없는 따름정리, `state:Observer(fn)`, `:Subscribe()`/`:Unsubscribe()`, **이중 바인딩 금지 게이트**(`canBound`, State emit 전파 게이팅은 `canExecute` — `base/lifecycle-pattern.md`의 "`canBound` vs `canExecute`" 절이 소스), PA님 코드 교차검증. **[2026-08-14 열두 번째 세션]** 새 절 "Observer/Effect Leaf dedup" — `RefLeafHandler`와 같은 `old ~= v` dedup(성능 최적화, correctness엔 불필요). **[2026-08-18 구현 전 QA 반영]** `canBound` 방향 정정, `:Compute` 콜백 표기 정정(`fn(self, previous?, ...deps)`), FALLBACK 가드 에러에 `k` 타입 싣기, 그리고 **⚠️ 미해결로 신설된 "중간 State가 살아남는가"**(상류 strong/하류 weak 불변식 — M3 착수 전 결론 필요) | | `dispatch-core-plan.md` | **[2026-08-13 열네 번째 세션 신설 — `bind-system-plan.md` 2단계 분할 + 0-A/0-Z 반영]** 디스패치 코어: 핸들러 계약(`isHandlable`/`priority`/`process`가 retract 클로저를 반환) / **하강 diff 재디스패치**(래핑 핸들러의 `retractFrom` 선행 호출 폐기, `Dispatch.process`가 슬롯의 `handler`를 먼저 비교해 — 같으면 그 자리 클로저에 새 값을 넘기고 재`process`, 다르면 그 자리부터 전량 철거) / `chains` 인덱스 체인과 **3-인자** `Dispatch.retractFrom(inst,k,index)`(힌트 인자 소멸 — 값 전달 경로가 (A) 분기 하나로 통일) / `None` 센티널 / Handler 작성 체크리스트 8개 / Length·Offset 형제 순서 보장 / "store 바인드는 래핑" 결론. **새 결정 둘**: `HANDLER_PRIORITY_FALLBACK`(base 제공 핸들러의 기본 밴드 — 백엔드가 평범한 우선순위로 덮어쓰면 언제나 이김), **"base가 소유하는 핸들러와 주입되는 엔진 op"**(부기가 엔진 지식을 요구하지 않으면 알고리즘은 base, 마지막 한 줄만 주입 — `addTag`/`removeTag`/`setAttribute`, **[2026-08-14 열 번째 세션]** 같은 패턴을 Dispatch 밖의 `dispose(value)`/`disposeInst`에도 재사용). 옛 힌트 모델은 `archive/dispatch-hintvalue-model-reversed.md`. **[2026-08-14 열두 번째 세션]** Observer/Effect Leaf도 `Ref`와 같은 identical-value dedup 채택(성능 최적화). **[2026-08-18 구현 전 QA 반영]** **`Dispatch.drive`의 `None` 스킵 분기 폐기**(반응형 값이 내놓는 `None`은 어차피 `process`에 도착) → `NoneHandler`는 재귀 전담, **`NilHandler` 신설**(`k=number and v==nil` 말단, `setLength(0)`/`setOffsetSource(None)` 등록 담당). Length/Offset 등록 책임도 "처음 매치한 Handler"→**말단 Handler**로 정정. base 소유 Fallback Handler **등록 주체는 백엔드 팩토리→quad-base 자신으로 재역전**. "방어 가드는 죽은 코드" 서술에 한정 추가(한 핸들러가 여러 값 모양을 받으면 판별은 그 핸들러 몫), `PreRef`가 "배열 먼저" 보장 위에 성립한다는 근거 정정(별도 pre-pass라 독립), `Quad.debug` 게이팅. **[2026-08-18 구현 전 QA 2라운드 후속]** "Length/Offset" 절에 크래시하던 `recompute` 트리거 모델(`RC-1`)을 owner별 `Blocker` 게이팅으로 고친 "배치 등록을 안전하게 만드는 Blocker 게이팅" 절 신설 — `setLength`/`setOffsetSource` 재작성, `Dispatch.drive`도 자기 Blocker로 배열 파트 순회를 감쌈. **[2026-08-18 구현 전 QA 3라운드]** "저장 위치" 절에 `bk.N`(recompute 순회 상한) 수명주기 신설(그때그때 실제 개수, `inst`/Slot 두 owner 타입 동일 규칙 — `setLength`가 갱신, `setOffsetSource`는 안 건드림) — 부수로 `RC-1`의 원래 크래시 서술도 정정("N이 배치 전에 고정"이라는 옛 전제의 부산물이었을 뿐, 지금 Blocker 게이팅이 필요한 이유는 크래시 방지가 아니라 비용) | | `bind-system-plan.md` | **[2026-08-14, 3단계 분할로 203줄까지 축소 — 지금은 "인스턴스 생성/이벤트 네이밍 인체공학 + 분할 색인" 문서]** 반응형 코어는 `source-state-plan.md`, Store는 `store-plan.md`, 디스패치 코어는 `dispatch-core-plan.md`로 나갔음. 아래 이력은 분할 전 이 파일이 담고 있던 결정들의 기록(현행 소스는 각 분할 문서). pluggable key/value 핸들러 레지스트리 — `process`/`retract` 디스패치 모델, Ref, Store/State/Source 온톨로지 + 인체공학 질문 전부 확정. 디스패치 엔진은 `quad-base`가 인터페이스로 소유(2026-08-04 5차 라운드). **[2026-08-11 세션, 여섯 번째]** `Dispatch.setLength`/`setOffsetSource`의 owner 키가 물리 Instance로 한정될 필요 없음을 명시(Slot-in-Slot 재귀의 근거) — 같은 절 `recompute`의 off-by-one 버그 발견·수정(`offset`이 자기 자신을 포함해 누적되던 것), 재진입 방지 가드는 검토 후 기각(`Source⊇State` 단방향 원칙과 같은 카테고리의 UB로 명명, 각 Slot이 독립 `bk`를 가져 nesting만으로는 재진입 경로 자체가 없음을 확인). **[2026-08-12 열한 번째 세션, 전면 정정]** "핸들러 타입이 안 바뀌면 retract 없이 process가 diff"는 틀렸음 — `retract`는 store 재발행마다(핸들러 타입 무관) 항상 불림, `v`는 대체 값 자체일 수 있어 `nil`로 가정 금지. `Tag`/`Ref`/`Slot`/`Attribute` 전부 이 오류로 설계돼 있었음이 드러나 한 세션에 전부 정정(`archive/retract-always-fires-reversed.md`). **[2026-08-12 세션 후속]** `retractUnder`의 `A and B or C` 삼항 관용구 버그(`v`가 `false`일 때 `nil`로 새던 것)를 `if-then-else`로 수정한 게 계기가 되어 `and`/`or` 삼항 전면 금지 규칙으로 발전(`architecture.md` "코드 스타일" 절). **[2026-08-12 열일곱 번째 세션]** 우선순위 동률/매치 실패 처리(`HANDLER_PRIORITY_*` 상수+디버그 동률 감지, 매치실패는 즉시 error) 확정, `store.key` 레코드 필드 타이핑이 Luau `type function`으로 가능함을 스케치로 확인(`pre-implementation-audit.md` 1-3/1-4/1-10 해소). **[2026-08-12 스무 번째 세션]** Ref 사용 관례 명문화 — React `useRef`급 스코프 감각(만든 컴포넌트 자신이 쓰거나 자식에게 넘기는 용도, 경계 밖 반출·전역 장기 보관은 비권장). **[2026-08-12 스물한 번째 세션]** `:With`가 `Tag`/`Modifier`의 `:` clone 체이닝과 겉보기엔 같은 문법이지만 실제로는 정반대(clone 아니라 매번 새 State 노드)라는 혼동 경고 추가, `Compute`가 `-ed`(`Computed`)가 아닌 이유 절 신설(quad 자기 관례상 `Tag.Added`/`Modifier.Overridden`이 이미 "-ed = clone 후 즉시 확정된 값"을 선점해 lazy한 State에 재사용하면 충돌). **[2026-08-13 세션, 두 번째]** `State>`(store가 emit하는 값 자체가 또 State/Source)가 같은 `(inst,k)`에 같은 핸들러를 중복 push시켜 `retractUnder`의 첫-매치 cutoff가 안쪽 자신을 잘못 retract하는 실제 체인 파손 버그로 확인됨(손 트레이싱, `luau-test/04`가 no-op `retract` 스텁 때문에 이 증상을 못 잡던 사각지대였음도 같이 발견) — `Dispatch.process`에 중복 핸들러 즉시 error 가드 추가, "동일한 재귀적 디스패치로 처리 가능"이라던 낙관적 서술과 "Store가 Store를 저장 가능한가" 절도 정정. **[2026-08-13 세션, 네 번째]** 사각지대 손 트레이싱 라운드에서 `isHandlable` 필드를 선택적으로 허용(생략하면 스캔에 안 걸림)하고, 그런 "체크포인트" 핸들러를 명시적으로 체인에 꽂는 `Dispatch.processAs`/`Dispatch.retractSelfAndUnder`(target 자신 포함 철거) 신설 — `attribute-plan.md`의 그룹/직접쓰기 이름 소유권 충돌을 별도 레지스트리 없이 기존 재진입 가드로 흡수하는 데 씀. **[2026-08-13 세션, 다섯 번째, 전면 재설계 — 위 processAs/retractSelfAndUnder 대체]** `chains`를 핸들러 객체 identity가 아니라 **재귀 깊이 인덱스**로 추적하도록 재설계 — `Dispatch.process(inst,k,v,index)`가 핸들러 호출 *전에* 그 인덱스 점유 여부를 체크(핸들러 부작용 낭비 없음), `process`는 이제 `retract` 필드 대신 자기 retract 클로저(`(hintValue)->()`)를 반환. 같은 키 재귀는 `index+1`, 다른 키 위임은 항상 `1`부터 — 이걸로 `State>`가 UB에서 정상 지원 대상으로 재정정됨(각 재귀 단계가 다른 슬롯을 쓰니 identity 충돌 자체가 없어짐), `retractUnder`/`retractSelfAndUnder`도 `Dispatch.retractFrom(inst,k,index,v)` 하나로 통합(자기 포함/미만은 호출자가 넘기는 인덱스로 표현)되며 체크포인트 패턴 자체가 불필요해짐(`archive/checkpoint-handler-pattern-reversed.md`). 계기: `AttributeGroupHandler` 소유권 버그를 체크포인트로 고치다, 그 근본 원인(identity 기반 추적)을 되짚은 사용자 지적. **[2026-08-13 감사]** 위 재설계 의사코드에서 실제 버그 셋 발견·수정 — (1) `chains:SetStrong`이 `handler.process` *뒤*에 있어 최초 마운트에서 하위 위임 retractor가 통째로 유실되던 것(재귀가 자기 테이블을 만들었다 바깥이 덮어씀), (2) `Ref` retractor가 spurious 재발행에서도 `relate`를 지워 dedup이 무력화되던 것, (3) `Dispatch.drive`의 진입 인덱스(`1`) 미명시. 덧붙여 retractor 안에서는 *같은* 키에 대한 `retractFrom`도 `process`와 똑같이 금지(진행 중인 루프가 `#list`를 이미 캡처)임을 명문화 **[2026-08-13 열네 번째 세션] 2단계 분할 + 모델 교체 — 디스패치 코어 전체가 `dispatch-core-plan.md`로 나갔고(이 문서엔 반응형 코어와 인체공학만 남음), 나가면서 **하강 diff**로 재작성됨. 따라서 위 5차 세션 서술 중 "`Dispatch.process`가 인덱스 **점유 여부**를 먼저 체크"와 "`retractFrom(inst,k,index,v)` **4-인자**"는 **더 이상 현행이 아님**(점유 체크 폐지 → 핸들러 비교, 힌트 인자 소멸 → 3-인자) — 현행은 `dispatch-core-plan.md`. **[2026-08-18 구현 전 QA 반영]** 남아 있던 인체공학 절이 크게 갱신됨 — 네임스페이스 **`DI`→`D`(Declarative) 확정**(코퍼스 전수 반영, "특수 DI 키"라는 설명 표현은 "특수 키"로 단순화), **`New`는 커링**(`New "Frame" {...}`)이고 **`D`는 전량 코드 생성된 순수 별칭 테이블**(생성 범위는 "GUI에 쓰이는 모든 인스턴스", 밖은 `any`), 그리고 **"이벤트 콜백 시그니처는 Luau가 검증 못 한다"는 옛 전제가 거짓**임이 사용자 반례로 확인돼 "생성기가 이벤트 필드의 콜백 타입까지 만든다"로 바뀜 | -| `module-lifecycle-plan.md` | 프로바이더 패턴, bind/store 구현 책임 분리 — 확정. **[2026-08-18 구현 전 QA 반영]** 모듈 표면에 **`Quad.debug`(기본 `false`)** 신설(지금은 핸들러 우선순위 동률 경고를 게이팅), base 소유 Fallback Handler 등록 주체가 quad-base 자신이라는 **명시적 예외** 반영 | +| `module-lifecycle-plan.md` | 프로바이더 패턴, bind/store 구현 책임 분리 — 확정. **[2026-08-18 구현 전 QA 반영]** 모듈 표면에 **`Quad.debug`(기본 `false`)** 신설(지금은 핸들러 우선순위 동률 경고를 게이팅), base 소유 Fallback Handler 등록 주체가 quad-base 자신이라는 **명시적 예외** 반영. **[2026-08-19 신설]** "New()의 내부 구성" 절 — `InitXxx(module)` 팩토리 체이닝 + `Relate` 기반 인스턴스별 멱등 Init 가드(순서 의존성 해소) | | `slot-plan.md` | 뮤터블 자식 배열, 엄격한 단일 마운트 소유권, 재마운트 시 throw, base/roblox 패키지 경계까지 확정. **[2026-08-09 세 번째 세션]** `Add`/`Remove`/`Extract`/`Clear`/`Move`/`Swap` CRUD(복잡도 표기 포함), `isMounted` 이중 추적 분리, 요소 타입 제약(`nil`/`None`/핸들러 계층 값 금지, `Slot()` 제네릭), 키 기반 동적 컬렉션 재조정(`Slot:List(data, updateFn, keyFn?)`)까지 전부 확정 통합, base/roblox 경계에 reposition 훅 추가. **[2026-08-09 열한 번째 세션, 중간검토]** CRUD 식별 기준을 element 레퍼런스에서 인덱스 기준으로 재정정(`Remove(index)`/`Extract(index, newElement?)`/`Move(oldIndex, newIndex)`), `ExtractAll`/`Get`/`IndexOf` 신설. **[2026-08-11 세션]** `updateFn(item, index: number, offset: Source, prev: T?, userdata: UD?): (T|nil, UD?)`로 시그니처 확정(`Slot.Offset`도 `Length`처럼 공개 필드로 신설) — `LayoutOrder` 등은 Slot이 자동으로 안 세팅, `index`/`offset` raw 값만 전달하고 실제 반영은 `updateFn`이 "버림/다시 그림/source만 갱신" 세 갈래로 직접 처리(재사용 Source에 미리 `Set` 후 결국 다시 그리면 무의미한 연산이 되므로). **[2026-08-11 세션, 여섯 번째]** `Slot:Single(state, updateFn)` 확정(`:List` 위의 순수 sugar) — Slot-in-Slot 중첩도 확정, 요소 타입 제약에서 `Slot` 배제 해제(`T = Instance | Slot`), `Dispatch.setLength`/`setOffsetSource`를 Slot 자신을 owner 키로 재사용하는 재귀 `attachSlot`(새 프리미티브 없음), 파괴는 재귀 `Clear()` 대신 flat `destroySlotTree`+명시적 `unbindLifetime`. `Slot(initial?: {T})` 생성자 부활(순수 `:Add` sugar) + `_crudUsed`↔`_listed` 상호 배타 가드 신설. `base/dispatch-core-plan.md`의 `recompute` off-by-one 버그도 이 세션에 같이 수정됨. **[2026-08-11 세션, 일곱 번째]** 반응형 raw 요소(`Slot:Add`가 `State`/`Source`도 받음) 확정 — 새 메커니즘 아니라 `isState(element)`면 내부적으로 `Slot():Single(element)`(nested Slot)를 대신 삽입하는 순수 sugar(최초 검토했던 별도 position-keyed StoreBind 구독 안은 `None`/Length/Move-Swap 문제로 기각). `Slot:Single(state, updateFn?)`도 `updateFn` 선택 인자화(기본값 identity)로 이 sugar를 지지. `:List`의 `reconcile`도 nested-Slot을 반환하는 아이템의 `.Length`만큼 다음 형제 `index`가 건너뛰도록 `pos` 커밋 공식 수정. **[2026-08-12 열두 번째 세션]** 소유권 판정을 위치별 relate 비교에서, Slot 자신이 지금 어느 `inst`에 바인딩됐는지 직접 추적하는 `slotOwner`(slot→inst)로 전환(같은 Slot이 동시에 다른 위치에 마운트되는 경우까지 잡기 위함) — `owner==inst`면 emit 전파로 무시, 다른 inst면 즉시 error. **[2026-08-12 열세 번째 세션]** `slotOwner`/`kSlotMap`이 서로를 강하게 참조하는 두-`Relate` 상호 GC 순환 발견·수정 — 둘 다 `SetWeak`로 낮추고 실제 GC 앵커는 `bindLifetime`/`unbindLifetime` 하나로 통일(`attachSlot`에 `bindLifetime(physicalTarget, slot)` 추가, `destroySlotTree`에 짝인 `unbindLifetime` 추가). **[2026-08-12 열네 번째 세션]** 위 순환이 Luau에 ephemeron이 없어 실제로 GC 안 되는 게 공식 문서(luau.org/compatibility)로 확인됨 — "혹시 몰라서"가 아니라 확정된 필수 조치로 격상(`relate-plan.md`에 일반 규칙으로도 승격). **[2026-08-12 열다섯 번째 세션]** `Slot:Splice(index, removeCount, ...newElements)` CRUD 신설(구간 제거+삽입을 shift/recompute 1회로 묶는 순수 최적화, `newElements`는 의도적으로 vararg 유지 — `Tag:Added`의 `string|{string}` 전환과는 다른 이유). **[2026-08-12 열여섯 번째 세션]** `slotOwner`를 top-level/nested 이중 마운트 gap까지 잡는 `elementOwner`로 일반화, `bindLifetime`을 top-level 전용으로 축소(nested는 `_elements` 강참조로 transitively 생존). **[2026-08-13 세션]** `releaseOwner`가 소유권 불일치를 조용히 무시하던 걸 즉시 error로 강화, `bindLifetime`을 `attachSlot`의 조건 분기에서 `SlotHandler.process`(Handler 층위)로 이동해 `unbindLifetime`과 대칭을 맞춤. **[2026-08-13 세션, 다섯 번째, 전면 반영]** `Dispatch`가 핸들러 identity 대신 인덱스로 재추적되며 `SlotHandler.process`가 `retract` 별도 필드 없이 자기 retract 클로저를 반환하는 계약으로 전환 — `kSlotMap`이 완전히 불필요해짐(어느 `process` 호출이 반환한 클로저든 `slotValue`/`inst`를 동일하게 캡처해 대칭적으로 동작하므로), `base/dispatch-core-plan.md` "Dispatch 체인" 절 참고. **[2026-08-13 감사]** 그 "대칭적으로 동작"이 `claimOwner`의 false가 *같은 (inst,k) 재발행*일 때만 참이었음이 드러나 소유권 판정을 둘로 분리 — nested(`rawAdd`)는 엄격 `claimOwner`(같은 owner 재클레임도 error, `Slot{a,a}`가 조용히 통과하던 것 차단), top-level은 `claimOwnerAt(element,inst,k)`으로 위치까지 봐서 `Frame{slot,slot}`을 error로 잡음. 추가로 `rawRemove`의 `releaseOwner` 누락(산문엔 있고 의사코드엔 없었음)과 `destroySlotTree`가 자식 소유권/`_mounted`를 안 되돌려 GC 타이밍 의존 오류를 내던 것도 수정. `State` 재설정 경로가 안전함(reconcile이 제거→`rawAdd` 순서라 release→claim)은 별도 절로 확인 기록. **[2026-08-13 세션, 여섯 번째 — 전면 역전]** `State` 교체가 **파괴에서 언마운트로 뒤집힘**(`state`와 동일 — "이전 값을 지울지는 그 값을 만든 쪽이 정한다"는 `Ref`/`Attribute`와 같은 철학) — 이에 따라 (a) 비파괴 짝 `rawUnmount`/`unmountSlotTree` 신설(`rawRemove`/`destroySlotTree`와 딱 하나만 다름: 안 죽임)되고 `reconcile`이 직접 부르는 게 `rawAdd`/`rawUnmount`/`rawMove`로 바뀜, (b) **오래 "오버엔지니어링"으로 기각돼 있던 포탈이 별도 기능이 아니라 이 결정의 자연스러운 귀결이 됨**(옛 "폐기, 옮기지 않음" 결정은 역전, `archive/slot-discard-no-portal-reversed.md`), (c) 명시적 파괴 수단으로 base 탑레벨 `dispose(value)` 신설 — 아직 트리가 살아있길 요구하는 값이면 파괴를 **거부하고 error** **[2026-08-13 열네 번째 세션]** 하강 diff 반영 — `SlotHandler`의 클로저가 받는 값이 항상 `Slot`이거나 `nil`임이 계약으로 보장되고, 언마운트 경로의 `setOffsetSource(None)`/`setLength(0)` 순서는 그대로. **[2026-08-14 열 번째 세션]** `dispose`의 시그니처/범위(`question.md` 0-B) 확정 — `dispose(value: Slot | Instance)`, `isSlot`이 아니면 백엔드 주입 op `disposeInst(inst)`로 위임(`addTag`/`removeTag`/`setAttribute`와 같은 패턴), `Observer`/`Effect`는 GC-native lifecycle만으로 충분해 범위에서 명시적으로 제외. **[2026-08-18 구현 전 QA 반영]** **`:List` reconcile의 `nil` 리턴은 다시 파괴가 기본**(값 교체와 새 `PopOnly`(가칭)만 비파괴 — 2026-08-13의 "전부 비파괴" 일반화가 `:List`엔 안 맞았음), `dispose` 절에 `SetAndDispose` 백로그 후보 추가. **[2026-08-18 구현 전 QA 2라운드 후속]** "재귀 메커니즘" 절의 `attachSlot`이 자기 flush 루프를 자기 자신의 `Blocker`로 감싸도록 재작성돼 `RC-1` 해결(부모와 별도 Blocker, 런타임 단건 `Add`는 게이팅 불필요). **[2026-08-18 구현 전 QA 3라운드]** `attachSlot`이 `slot._mounted = true`를 `activateList` 호출 뒤로 미루도록 재정렬 — `:List` 최초 population이 무게이팅 recompute를 태우던 것(`RC-3`)과 nested Slot이 이중 `attachSlot`되던 것(`RC-4`) 둘 다 해결. `spliceArraysDown`이 밀어야 할 배열에 `bk.observers`/`bk.N` 갱신도 명문화 | | `modifier-plan.md` | Modifier는 런타임 plug 아닌 정적 merge, immutable+clone 기반 체이닝 — 메커니즘 확정. **[2026-08-07 다섯 번째 세션 추가]** `:Apply(factory)` 팩토리 체이닝, `Overridden`(구 `Merge`→`Override`, 2026-08-08 세션에서 이름까지 확정) 값 결합+성능 기준, `:Peek`/`isState` 필드 읽기까지 전부 확정(`Peek`/`isState`는 이름만 용어 정리 라운드까지 잠정). **[2026-08-12 열일곱 번째 세션]** `table.clone`이 메타테이블을 참조로 공유한다는 핵심 전제(M7 "클래스별 코드 없이 제네릭 `__index` 하나로 충분" 설계의 근거)가 실제 Luau 동작으로 확인됨(`pre-implementation-audit.md` 1-11 해소). Property에 Attribute식 이름 소유권 레지스트리를 적용하는 안은 검토 후 기각(엔진이 정한 유한 프로퍼티 이름 집합은 전용 키를 못 만들어 소유권 판정 자체가 성립 안 함 — Property가 override 우선순위를 쓰는 이유). **[2026-08-18 구현 전 QA 반영]** 고정 메소드(=Modifier 필드 이름 예약)는 `Apply` 하나가 아니라 **`Apply`/`Peek`/`Overridden` 셋**(M7 타입 생성 스크립트 제외 목록에 반영 필요), `Overridden`은 닷/콜론 둘 다 가능 | | `purity-and-effects-plan.md` | 컴포넌트 "순수성"이 아니라 "이식성" 문제로 재정의 — 문서 경고 수준으로 확정 | diff --git a/.claude/base/architecture.md b/.claude/base/architecture.md index 42b32b8..ce29eeb 100644 --- a/.claude/base/architecture.md +++ b/.claude/base/architecture.md @@ -154,11 +154,18 @@ quad는 이제 "스크립트"가 아니라 **라이브러리**다. DOMless Roblo 인자로 받도록 **손을 대야** 한다 — `New()`가 실제로 호출되면(위 opt-in 경로) 그 순간 만들어지는 새 `BaseModule` 테이블에 대해 이 손질이 필요. **지금은 `New()` 자체가 노출 안 된 싱글톤 단계라 `Quad.Dispatch`로 - 바로 접근**한다. - - **M0 스캐폴딩에 주는 함의**: 레지스트리를 module-level upvalue로 - 직접 잡아두면 나중에 다중 인스턴스화할 때 전면 수정이 된다. 지금 - 싱글톤으로 가되, **그 참조 형태를 나중에 인자 하나 받는 걸로 바꾸기 - 쉬운 모양으로 둘지**를 M0에서 정할 것. + 바로 접근**한다. `New()` 자신이 내부적으로 어떤 형태로 조립되는지(v1 + 스타일 `InitXxx(module)` 팩토리 체이닝, 타입 재익스포트)는 + `module-lifecycle-plan.md`의 "New()의 내부 구성" 절 참고. + - **M0 스캐폴딩에 주는 함의 — [2026-08-19] 정해짐, 실제로는 M0가 아니라 + `ROADMAP.md` M1(실제 스캐폴딩)에 적용됨.** 레지스트리를 module-level + upvalue로 직접 잡아두면 나중에 다중 인스턴스화할 때 전면 수정이 + 된다는 우려가 있었는데, 바로 위에서 가리키는 InitXxx 패턴(각 + `InitXxx(module)`가 `module`을 upvalue가 아니라 **파라미터로 받아** + 뮤테이션)이 처음부터 그 형태다 — 나중에 바꿀 일 자체가 없게 M1 + 스캐폴딩부터 이 모양으로 짠다. M0는 독립 스파이크 파일로 개별 + 가설만 검증하는 단계라 이 구조 자체를 아직 안 씀(`ROADMAP.md`의 + "M0 — 스켈레톤 + 기술검증" 절 참고). 14. **pluggable 초기화는 팩토리 함수로.** rbvm처럼 네임스페이스 하나하나 수동 init 하는 방식(`base/lifecycle-pattern.md` 5번 항목 참고)은 피하고, `InitRoblox(Module)` 같은 팩토리 함수가 생성된 모듈을 뮤테이션하는 도구를 diff --git a/.claude/base/dispatch-core-plan.md b/.claude/base/dispatch-core-plan.md index fbad0f6..fb1959a 100644 --- a/.claude/base/dispatch-core-plan.md +++ b/.claude/base/dispatch-core-plan.md @@ -623,7 +623,12 @@ end state를 참조하는 코드들이 모듈 인스턴스를 인자로 받도록 (`InitModule(module)` 류) 손을 봐야 한다(`base/architecture.md` "확정된 결정" 13번). 지금은 `New()` 자체가 노출 안 된 싱글톤 단계라 - `Quad.Dispatch`로 바로 접근한다. + `Quad.Dispatch`로 바로 접근한다. **[2026-08-19 추가]** 이 문단이 말하는 + "`InitModule(module)` 류"의 정확한 형태(각 서브시스템별 `InitXxx(module)` + 팩토리 체이닝 + `Relate` 기반 인스턴스별 멱등 가드)가 + `module-lifecycle-plan.md`의 "New()의 내부 구성" 절에 구체화됨 — + `Dispatch/init.luau`도 그 패턴을 따르는 `InitDispatch(module)` 하나로 + 구현된다. ### base가 소유하는 핸들러와 주입되는 엔진 op (2026-08-13 열네 번째 세션 신설) diff --git a/.claude/base/module-lifecycle-plan.md b/.claude/base/module-lifecycle-plan.md index 669cbe5..4244f8d 100644 --- a/.claude/base/module-lifecycle-plan.md +++ b/.claude/base/module-lifecycle-plan.md @@ -30,6 +30,123 @@ RBVM처럼 `init namespace` 하나하나 부르는 방식은 별로(`base/lifecy 주고 사용자가 호출하도록. `base/architecture.md` 14번 항목과 동일한 결정 — 여기서는 "왜"만 보강. +## New()의 내부 구성 — InitXxx 팩토리 체이닝 (2026-08-19 신설) + +바로 위 절이 확정한 `InitRoblox(Module)` 패턴(팩토리가 모듈 테이블을 +뮤테이션)은 지금까지 서술상 **backend 주입**에만 적용되는 것처럼 보였는데, +`New()` **자신**이 quad-base 내부 서브시스템(Dispatch 등)을 구성하는 +방식도 대칭적으로 같은 패턴을 쓴다 — 새 설계가 아니라 이미 확정된 원칙을 +quad-base 자기 자신에도 적용한 구체화. 논의 원문은 +`session/2026-08-19-01-new-initxxx-composition-relate-guard.md`. + +**사용자 제안 원문 요지**(2026-08-19): *"생성형식 자체는 비싱글톤이고, +Dispatch 같은것도 `Init(module)` 을 받는 함수로써 ... `module.Dispatch = ...` +형식들로 구현되고 ... 재익스포트식으로 구현하겠다는 이야기였음. 처음부터 +`InitModuleName` 식으로 구현하여 팩토리를 쌓아 모듈을 리턴하는 방식으로, +quad v1 의 방식을 가져와봄직 하다."* + +**형태**(각 서브시스템 파일이 `Init` 함수를 export하고, 타입도 재익스포트): + +```lua +-- Dispatch/init.luau +local function Init(module) + local ... -- 여기서 레지스트리/릴레이션 생성 + module.Dispatch = ... +end +export type Dispatch = ... +return Init +``` + +```lua +-- 최상위 init.luau +local InitDispatch = require(...) +type Dispatch = InitDispatch.Dispatch -- 재익스포트 + +local function New(): { Dispatch: Dispatch, New: typeof(New), ... } + local module = { New = New } + InitDispatch(module) + -- 서브시스템 개수만큼 InitXxx(module) 을 순서대로 쌓음 + return module +end + +return New() +``` + +`module = {New = New}` 자기참조는 `base/architecture.md` "확정된 결정" +13번이 이미 확정해둔 것과 정확히 같은 형태 — 별도 결정이 아니라 그 결정이 +실제로 어떻게 코드로 나오는지를 구체화한 것뿐. + +**타입 재익스포트는 실측 확인됨**(2026-08-19, 사용자: "그거 타입 익스포트 +잘 됨") — `type Dispatch = InitDispatch.Dispatch` 형태로 서브모듈의 +export 타입을 최상위에서 그대로 재노출하는 게 Luau에서 문제없이 동작한다. +`base/typing-limits.md`가 우려하는 "명시 바인딩 필요" 케이스와는 다른 +자리라는 뜻 — 거긴 재귀 제네릭이 자기를 다른 타입 인자로 반환하는 게 +문제였고, 여긴 단순 alias라 그 한계에 안 걸린다. + +**순서 의존성은 각 `InitXxx`를 `require`처럼 멱등하게 만들어서 해소한다** +(2026-08-19, 사용자 제안) — 서브시스템 간 호출 순서를 최상위 `New()`가 +직접 관리할 필요가 없다: 각 `InitXxx` 파일이 자기 톱레벨(함수 클로저 +**밖**, 파일 스코프)에 `local relate = Relate()`를 하나 두고, `module`을 +weak key 삼아 "이 `module` 인스턴스에 이미 Init됐는지"를 기록한다. 이미 +됐으면 그대로 스킵, 아니면 실제 작업을 한 번만 수행 — Lua의 `require` +캐시와 같은 발상이지만, `require` 캐시는 **파일** 단위(Init 함수 자체는 +한 번만 로드)인 반면 `New()`는 여러 번 호출돼 서로 다른 `module` 테이블을 +여러 개 만들 수 있어서, "이 particular 인스턴스에" 멱등하려면 파일 스코프 +캐시로는 부족하고 `module`을 키로 하는 이 `Relate`가 따로 필요하다. + +```lua +-- Dispatch/init.luau +local Relate = require(...) +local relate = Relate() -- 파일 스코프, 클로저 밖 — 이 Init 전체가 공유하는 단 하나의 인스턴스 + +local function Init(module) + if relate:GetStrong(module, INITED) then + return -- 이미 이 module 인스턴스엔 Init됨, no-op + end + relate:SetStrong(module, INITED, true) -- 실제 작업 전에 먼저 표시(순환 의존 대비, 아래 참고) + -- 자신의 의존성도 그냥 require+호출 — 상대도 멱등하므로 중복/순서 걱정 없음 + -- 예: InitLifetime(module) + local ... + module.Dispatch = ... +end +return Init +``` + +이렇게 하면 **의존하는 쪽이 자기 의존성을 직접 호출**하면 되고(`require`가 +의존 그래프를 알아서 풀어주는 것과 같은 감각), 최상위 `New()`는 순서를 +신경 쓰지 않고 아는 `InitXxx(module)`을 전부 호출해도 된다 — 이미 누가 +먼저 채웠으면 알아서 스킵된다. + +- **GC와도 자연히 맞물림**: `relate-plan.md`의 "API" 절에 따르면 `Relate`의 + **첫 인자(`inst`, 여기선 `module`)는 항상 weak**다(선택의 여지가 없는 + 고정 동작 — `Weak`/`Strong` 구분은 오직 `value` 쪽 보관 방식만 가리킴). + 그래서 `value` 쪽을 `SetWeak`으로 두든 `SetStrong`으로 두든 상관없이, + 어떤 `Quad` 인스턴스(전체 `module` 테이블)가 더 이상 참조되지 않아 + 수거되면 이 Init-완료 기록도 같이 사라진다 — 별도 정리 로직 불필요. + `relate-plan.md`가 이미 확정해둔 "각 모듈이 자기 톱레벨에 `Relate()` + 하나를 두고 재사용" 관례를 그대로 쓰는 것이라 새 메커니즘 아님. + (`relate-plan.md`가 별도로 명시한 "명시적으로 만든 기록은 명시적으로 + 지울 것" 원칙과 충돌하는 게 아니라 — 이 경우는 기록의 키 자체가 죽으면 + 그 기록을 다시 조회할 주체 자체가 사라지므로 지울 대상이 없어지는, + 원칙이 애초에 상정하지 않은 자리다.) +- **`value`는 `SetStrong`으로 통일**: 위 문단대로 GC 결과엔 차이가 없지만 + (boolean 리터럴은 애초에 GC 대상이 아님), `relate-plan.md`의 일반 규칙 + "다른 곳에서 안전하게 유지되는 것은 항상 `SetWeak`" 기준으로는 이 `true` + 플래그를 다른 어디도 붙잡고 있지 않으므로 `SetStrong`이 그 규칙에 맞는 + 선택이다. +- **`_initializedBy` 가드(아래 "Bind는 누가, 어떻게 구현하는가" 절, 실제 + 정의는 `base/bind-system-plan.md`)와는 다른 층위** — 그건 backend + 팩토리가 유일 슬롯을 채웠는지 **누가** 채웠는지까지 구분해야 하는 공개 + 계약(같은 팩토리 재호출=no-op, 다른 팩토리=에러)이고, 이건 quad-base + 내부 서브시스템 각각이 **한 번만** 도는지만 보면 되는 사적 구현 + 디테일이라 "다른 호출자면 에러" 같은 분기 자체가 없다. 이름이 겹치지 + 않게 구분해서 쓸 것. +- **플래그를 실제 작업 전에 먼저 세우는 이유**: 나중에 `InitA`↔`InitB`처럼 + 상호 의존이 생기면([2026-08-19 기준] 지금은 없음, 대비만), 먼저 + 표시해두지 않으면 무한 재귀에 빠진다 — `require`가 순환 참조 시 + 미완성 exports를 돌려주는 것과 같은 이유로, 실제 작업 시작 전에 먼저 + "완료"로 표시해둔다. + ## Bind는 누가, 어떻게 구현하는가 인터페이스 상 `bind`를 두고 이것도 pluggable하게 할지 고민 — 단 **1개만 존재할 diff --git a/.claude/base/relate-plan.md b/.claude/base/relate-plan.md index 4935fb1..62e5725 100644 --- a/.claude/base/relate-plan.md +++ b/.claude/base/relate-plan.md @@ -166,6 +166,11 @@ Handler 계약이 "`process`가 자기 retract 클로저를 반환"으로 바뀌 - **소유권/멤버십 전역 판정** — `Slot`의 `elementOwner`. - **"언제까지 실행돼도 되는가"** — `bindLifetime`/`canExecute` (`base/lifecycle-pattern.md`). 애초에 클로저 수명과 무관한 질문. +- **"이 인스턴스에 이미 했는가"류 인스턴스별 멱등 가드**(2026-08-19 + 신설) — `New()`가 만드는 각 `module` 인스턴스별로 `InitXxx(module)`가 + 이미 실행됐는지 기록하는 것도 여러 호출 지점(다른 `InitXxx`가 자기 + 의존성으로 호출하는 경우 포함)을 가로질러야 해서 클로저 캡처로 대체 + 불가 — `base/module-lifecycle-plan.md`의 "New()의 내부 구성" 절. **쓸 때 같이 지킬 것**: - **정리 조건을 실제 정리와 묶을 것.** `Relate` 엔트리를 지우는 코드가 diff --git a/.claude/session-summary.md b/.claude/session-summary.md index 8d987ba..d53e8db 100644 --- a/.claude/session-summary.md +++ b/.claude/session-summary.md @@ -1485,3 +1485,22 @@ Store의 lazy `__index`와 충돌 / `PopOnly` 홀드 중 키가 사라지면 파 중단했음. **그래서 사용자 지침으로 감사 절차 자체를 바꿨다** — 병렬 금지(한 턴에 하나), 범위는 diff로 좁히고 라운드마다 각도를 바꿈, 종료 조건은 무발견 1회(옛 "2연속"은 병렬 전제라 완화). `conventions.md`의 감사 루프 절이 소스. + +## 2026-08-19 — `New()` 내부 구성(InitXxx + Relate 멱등 가드) 확정, 세션 기록 공백 발견 + +원문: `session/2026-08-19-01-new-initxxx-composition-relate-guard.md` + +사용자가 `New()`를 v1 스타일 `InitXxx(module)` 팩토리 체이닝으로 짜자고 +제안(이미 확정된 `InitRoblox(Module)` backend 주입 패턴을 quad-base 자기 +내부에도 대칭 적용) → 채택, `module-lifecycle-plan.md`에 "New()의 내부 +구성" 절 신설. 이어서 서브시스템 간 호출 순서 문제를 "Init을 `require`처럼 +멱등하게"(각 `InitXxx` 파일 톱레벨에 `Relate()` 하나 두고 `module`을 weak +key로 완료 여부 기록) 방식으로 직접 해소하는 아이디어도 제안·반영 — +`relate-plan.md`의 기존 확정 API/관례와 정확히 부합함을 확인. 핸드오버 +감사 2라운드를 거치며 라운드 1의 수정 자체가 절 인용 사각지대를 새로 +만든 걸 라운드 2가 잡는 등 실제로 반복 라운드가 필요함을 다시 확인. + +**부수 발견 — session/ 기록 공백**: 2026-08-18에 커밋 10개(QA 1~2라운드 +포함)가 있었는데 그날 session/ 파일은 1개뿐, 2026-08-19는 이 세션 전까지 +(QA 3라운드 커밋 1개가 있었음에도) 0개였음. 과거 대화 트랜스크립트에 접근 불가라 그 공백을 사후 재구성하는 건 +허위 기록 위험이 있어 보류 — 처리 방침은 사용자 확인 대기. diff --git a/.claude/session/2026-08-19-01-new-initxxx-composition-relate-guard.md b/.claude/session/2026-08-19-01-new-initxxx-composition-relate-guard.md new file mode 100644 index 0000000..61db7ac --- /dev/null +++ b/.claude/session/2026-08-19-01-new-initxxx-composition-relate-guard.md @@ -0,0 +1,115 @@ +# 2026-08-19 — `New()`의 내부 구성: InitXxx 팩토리 체이닝 + `Relate` 기반 멱등 Init 가드 + +**요청**: `Quad.New()`가 실제로 어떻게 구현돼야 하는지에 대한 사용자 +아이디어 검토 요청으로 시작 — 결론까지 나서 `base/`에 반영, 이어서 +핸드오버 감사 루프와 세션 기록 공백 점검까지 같은 세션에서 처리. + +## 1. 제안 — `New()`를 InitXxx 팩토리 체이닝으로 + +사용자 원문: *"New() 가 실행되면 Quad 를 만드는 함수가 있는것으로 처음부터 +구현하는게 맞음. ... New 결과 안에 .New 함수를 넣어줌. 즉, 생성형식 자체는 +비싱글톤이고, Dispatch 같은것도 Init(module) 을 받는 함수로써 ... +module.Dispatch = ... 형식들로 구현되고 ... 재익스포트식으로 구현하겠다는 +이야기였음. 처음부터 InitModuleName 식으로 구현하여 팩토리를 쌓아 모듈을 +리턴하는 방식으로, quad v1 의 방식을 가져와봄직 하다는것."* + +**조사**: 기존 `base/architecture.md` 13번("모듈은 기본 싱글톤, `New()`는 +추가 인스턴스가 필요할 때만")과 14번("pluggable 초기화는 팩토리 함수로"), +`module-lifecycle-plan.md`가 이미 `InitRoblox(Module)` 형태의 backend 주입 +패턴을 확정해뒀다는 걸 확인 — 이번 제안은 그 패턴을 quad-base **자기 +자신의 내부 구성**(Dispatch 등)에도 대칭 적용하자는 것이라 새 설계가 +아니라 기존 원칙의 자연스러운 확장으로 판단. `lifecycle-pattern.md`가 +거부한 rbvm `InitNamespace` 패턴(소비자가 라이브러리마다 수동으로 init을 +부르는 것)과도 안 겹침 — 여기선 `New()` 하나만 외부에 노출되고 내부에서만 +`InitXxx(module)`를 부름. + +**결론**: 채택 추천 — `module = {New = New}` 자기참조도 이미 확정된 결정과 +정확히 일치, `type Dispatch = InitDispatch.Dispatch` 재익스포트만 실제 +Luau 동작 확인 필요하다고 남겨둠. + +**사용자 확인**: "그거 타입 익스포트 잘 됨. 구체화 반영해줘." → 실측 +확인됐다는 뜻으로 받아 `module-lifecycle-plan.md`에 "New()의 내부 구성" +절 신설, `architecture.md` 13번에서 포인터 연결. + +## 2. 정제 — Init을 `require`처럼 멱등하게 + +사용자 원문: *"Init 은 require 처럼 생각 가능한듯. Init 여러번은 한번만 +작동하게 자신 모듈 최상단에 Relate 를 (함수 안 아님. 클로저 바깥) 놓고, +자신 모듈의 init 여부를 저장해. 그리고 한번만 작동하도록 두고, 자신 init +에선 필요한것들을 init 해줘. 디펜던시 느낌인거지. ... 맨 바깥 quad-base +진입점의 New 에선 모든 Init 을 그냥 실행해도 돼."* + +이게 1절 문서화 때 "구현 단계에서 정할 것"으로 남겨뒀던 "서브시스템 간 +`InitXxx` 호출 순서 의존성" 문제를 실제로 푼다 — 각 `InitXxx` 파일이 자기 +톱레벨(클로저 밖)에 `local relate = Relate()`를 두고 `module`을 weak key로 +"이미 이 인스턴스에 Init됐는지"를 기록하면, 의존하는 쪽이 자기 의존성을 +직접 호출해도 중복/순서 걱정이 없어짐(멱등) — `require`가 파일 단위로 하는 +캐싱을, `New()`가 여러 `module` 인스턴스를 만들 수 있다는 차이 때문에 +인스턴스 단위로 다시 구현하는 것. + +`relate-plan.md`의 확정 API(`Relate()`/`SetWeak`/`GetWeak`/`SetStrong`/ +`GetStrong`, "각 모듈이 자기 톱레벨에 `Relate()` 하나 재사용" 관례)와 +정확히 부합함을 확인 — 새 메커니즘이 아니라 기존 프리미티브의 정확한 +용례. `module-lifecycle-plan.md`에 반영, 순환 의존 대비를 위해 플래그를 +실제 작업 전에 먼저 세우는 규칙도 같이 명문화. + +## 3. 핸드오버 감사 루프 (2라운드, `conventions.md`의 "핸드오버 준비하고 +커밋해" 절차) + +바뀐 파일: `base/architecture.md`, `base/module-lifecycle-plan.md`, +`base/dispatch-core-plan.md`, `README.md`, `ROADMAP.md`. + +**라운드 1**(`quad-doc-auditor`, agentId `ad95c24230306ab0f`) — 확실 3건 + +의심 3건 + 사용자판단 1건: +- 확실: `architecture.md`의 "M0 스캐폴딩에 주는 함의" 불릿이 이미 InitXxx + 절이 답한 질문을 여전히 미결정으로 서술 / `README.md` 색인에 새 절 요약 + 누락 / `module-lifecycle-plan.md`의 "지금은 없음"이 날짜 없는 시한부 + 주장. +- 의심: `_initializedBy` 상호 참조가 정의를 못 찾게 함(`bind-system-plan.md` + 누락) / GC 인과 서술이 `relate-plan.md`의 "`inst`는 항상 weak" 규칙과 + 어긋나게 읽힘 / `dispatch-core-plan.md`가 새 절을 안 가리켜 상호참조 누락. +- 사용자판단(문서만으론 못 정함, 이번엔 직접 판단해 처리): InitXxx 구조가 + M0/M1 중 어느 마일스톤부터인지 → `ROADMAP.md`의 "M0 — 스켈레톤 + + 기술검증" 절 실제 내용(스파이크 전용, "진짜 마일스톤 아님")을 근거로 + **M1**로 확정, `ROADMAP.md` M1 체크리스트에 항목 신설. + +전부 반영(위 6곳 수정 + M1 체크박스 추가). + +**라운드 2**(agentId `a1d60d10fc36621ed`) — 라운드 1 수정 자체가 새 stale +2건을 만든 걸 발견: +- `architecture.md`가 인용하던 "M0 스캐폴딩에 주는 함의"라는 절 제목 + 문구를 라운드 1 수정이 지워버려 절 인용 규약 사각지대(파일명 없는 같은 + 문서 내 인용이라 `doc-check.py`가 안 잡음) 발생 → 원래 제목 문구를 불릿 + 맨 앞에 복원하면서 내용만 정정. +- "위 'M0 — 스켈레톤 + 기술검증' 절 참고"가 실제로는 `ROADMAP.md` 안의 + 절인데 파일명이 빠져 같은 문서 안인 것처럼 읽힘 → `ROADMAP.md`의 명시. +- 추가로 "확실": 이 새 절 자체가 사용자 발언 3건을 인용하면서 + `session/2026-08-19-*.md` 포인터가 없음(이 문서가 그 포인터). +- 의심: `relate-plan.md`의 "언제 Relate를 쓰는가" 체크리스트에 새 용례가 + 안 실림 → 다섯 번째 불릿 추가. +- 사용자판단: Init-완료 플래그 값(`true`)을 `SetWeak`/`SetStrong` 중 뭘로 + 적을지가 문서 간 안 맞음 → boolean은 GC 대상이 아니라 실질 차이는 없지만 + `relate-plan.md`의 일반 규칙("다른 곳에서 안 붙잡는 값은 Strong") 기준 + **`SetStrong`으로 통일**. + +전부 반영. 라운드 3은 이 문서 작성 이후 진행 예정(아래 미해결 참고). + +## 4. 세션 기록 공백 발견 (사용자가 감사 진행 중 별도로 제기) + +사용자 질문: *"세션 기록들 요즘 왜 안 적어? ... 18일 자가 하나 뿐이네."* + +확인 결과 — `git log`엔 2026-08-18에 커밋 10개(QA 1~2라운드, 감사 루프 +재설계, GitHub co-author 정책, git 원격 정책 등), 2026-08-19에 커밋 1개 +(QA 3라운드)가 있는데, `session/`엔 2026-08-18 파일이 `pre-implementation-qa-applied.md` +**하나뿐**이고 2026-08-19 파일은 이 문서 이전엔 **0개**였다. 즉 QA +2라운드/3라운드(`todos.md` 00번이 상세히 서술하는, RC-1/RC-3/RC-4를 실제로 +찾아 해결한 세션들)와 tooling/research 커밋 다수가 session/ 원문 없이 +커밋됨 — 실제 공백. + +**한계**: 이 세션은 그 과거 대화의 실제 트랜스크립트에 접근할 수 없다(커밋 +메시지와 현재 파일 상태만 볼 수 있음) — `session/`의 정의 자체가 "시행착오 +포함 원문"이라, 원문을 못 본 채로 "raw log"를 지어내면 오히려 그 자체가 +허위 기록이 된다. 그래서 이 문서는 **이번 세션분만** 원문으로 채웠고, +과거 공백(08-18 QA 2/3라운드 등)을 어떻게 처리할지는 사용자에게 별도로 +물어야 함(요약만 `session-summary.md`에 사후 추가할지, 아예 공백으로 +인정하고 넘어갈지 등 — 이 문서 자체가 그 판단의 근거 자료). diff --git a/ROADMAP.md b/ROADMAP.md index 506a032..822eb25 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -85,6 +85,10 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검 소스 트리 구조 확정" 절 그대로) - [ ] quad-base용 최소 mock 테스트 하네스(Vide `test/mock.luau` 선례, 순수 `luau` CLI, `architecture.md` "테스트 전략" 절 참고) +- [ ] 최상위 `New()`/`InitXxx(module)` 팩토리 체이닝 골격 — 각 서브시스템 + Init이 `module`을 파라미터로 받아 뮤테이션, `Relate` 기반 인스턴스별 + 멱등 가드(`base/module-lifecycle-plan.md`의 "New()의 내부 구성" 절 + 그대로, 2026-08-19 확정) - [ ] 이 시점부터 `.claude/qa-request/`/`.claude/archive/` 폴더 실사용 시작 ## M2 — 디스패치 엔진