diff --git a/.claude/README.md b/.claude/README.md index fd16406..e22fcb5 100644 --- a/.claude/README.md +++ b/.claude/README.md @@ -16,8 +16,8 @@ | `archive/` | 완료 + 사용자가 실사용/실기기로 직접 검증까지 마침 (구현 대상). **[2026-08-06 확장]** 완전히 뒤집힌 설계 결정을 원문+역전 이유+diff와 함께 보존하는 용도로도 사용(제목 `[역전됨]` — 한 번 확정했다가 뒤집힌 것) — 더 이상 능동적으로 참고 안 해도 되지만(토큰 낭비 방지 위해 `base/`/`research/`에서 뺌) `quadnomicon` 소재로는 나중에 쓸 수 있음. **[2026-08-07 확장]** 후보였다가 채택 안 된 것(확정한 적 없이 검토 후 기각)도 같은 방식으로 보존, 제목은 구분을 위해 `[기각됨]` — `[역전됨]`과 의미가 다르므로 혼동하지 말 것. **[2026-08-07 세 번째 확장]** 설계 반전/기각과 별개로, 에이전트가 문서 작성 중 스스로 낸 개념 혼동을 정정한 이력은 `[에이전트 실수]` 태그로 `agent-mistake.md` 하나에 모음(CLAUDE.md 세션 로그 중복 방지) | | `feedback/` | 실사용 피드백을 정리한 긴 로그 — 지금은 비어있음(구현 시작 전) | | `luau-test/` | **[2026-08-09 신설]** `base/` 확정 사항 중 "추론만으로 확정하고 실제 Luau로 부딪혀본 적 없는 것"(M0 스파이크 대상)을 `luau`/`luau-analyze`/`luau-lsp`/Roblox Studio로 사용자가 직접 돌려볼 독립 실행 스크립트 모음. **[2026-08-13 여섯 번째 세션, 첫 실측]** `luau`/`luau-analyze` 바이너리가 생겨 처음으로 실제 실행 — **런타임 12개 전원 통과**, 타입 쪽에서 `:Compute(fn)` lazy 핸들 계약이 Luau 추론과 충돌하는 게 드러남(`question.md` 0-Y). **`STATUS.md`가 상태의 소스**(pass / 사람 결정 필요 / 스파이크 깨짐 / 미실행 분류 — 사람이 먼저 볼 것만 위에), `luau-test/README.md`는 각 파일의 검증 의도·배경, 실행 결과 상세는 `audit/luau-test-first-run-2026-08-13.md` | -| `audit/` | **[2026-08-13 신설]** `luau-test/` 등 스파이크를 실제로 돌려본 뒤 "무엇이 확인됐고 무엇이 아직 안 됐는지"를 기록하는 곳 — 스크립트/계획 자체가 아니라 **실측 결과**만 다룸. base/luau-test와 달리 부분 확인(일부만 통과)도 있는 그대로 기록, 완전히 해소되면 관련 `base/`/`luau-test/README.md` 캐비엇을 지우고 이 문서는 근거로 남김. **현재 2개**: `luau-test-first-run-2026-08-13.md`(첫 실측 라운드 전체 — 런타임 12개 통과, `question.md` 0-Y의 1차 근거), `gcconn-trick-verification.md`(사용자가 Studio에서 직접 돌린 gcconn 트릭 부분 확인 — `luau-test/10`의 A 섹션 앞부분만, A-1/A-2/B/C는 미확인) | -| `tools/` | **[2026-08-13 여덟 번째 세션 신설]** 코퍼스 기계 점검 — `doc-check.py`가 깨진 파일/절 참조, README 색인 누락, 날짜 없는 시한부 주장("아직 안 돌려봄" 등), 미반영 ⚠️ 배너를 한 번에 훑음. **중대 변경 후 커밋 전에 돌릴 것**(`python3 .claude/tools/doc-check.py`) — 수동 감사에서 나온 발견의 대부분이 이 종류였고, 실제로 문서를 쪼개다 잘못 옮긴 참조를 이게 잡아냄. ERROR는 고치고 WARN은 판단 대상 | +| `audit/` | **[2026-08-13 신설]** `luau-test/` 등 스파이크를 실제로 돌려본 뒤 "무엇이 확인됐고 무엇이 아직 안 됐는지"를 기록하는 곳 — 스크립트/계획 자체가 아니라 **실측 결과**만 다룸. base/luau-test와 달리 부분 확인(일부만 통과)도 있는 그대로 기록, 완전히 해소되면 관련 `base/`/`luau-test/README.md` 캐비엇을 지우고 이 문서는 근거로 남김. **현재 2개**: `luau-test-first-run-2026-08-13.md`(첫 실측 라운드 전체 — 런타임 12개 통과, `question.md` 0-Y의 1차 근거), `gcconn-trick-verification.md`(사용자가 Studio에서 직접 돌린 gcconn 트릭 부분 확인 — `10`의 A 섹션 앞부분만, A-1/A-2/B/C는 미확인) | +| `tools/` | **[2026-08-13 아홉 번째 세션 신설]** 코퍼스 기계 점검 — `doc-check.py`가 깨진 파일/절 참조, README 색인 누락, 날짜 없는 시한부 주장("아직 안 돌려봄" 등), 미반영 ⚠️ 배너를 한 번에 훑음. **중대 변경 후 커밋 전에 돌릴 것**(`python3 .claude/tools/doc-check.py`) — 수동 감사에서 나온 발견의 대부분이 이 종류였고, 실제로 문서를 쪼개다 잘못 옮긴 참조를 이게 잡아냄. ERROR는 고치고 WARN은 판단 대상 | | `session/` | **[2026-08-11 신설]** 세션별 상세 로그 원문(시행착오·정정 전 서술 포함, `quadnomicon` 개발로그 소재용) — 루트 `CLAUDE.md`가 3196줄까지 불어나 성능 저하를 유발해서 분리함. 파일명 `YYYY-MM-DD-NN-slug.md`, CLAUDE.md의 "세션 히스토리" 절에서 각 항목이 여기로 링크. 항상 읽을 필요 없음 — 결정의 논의 과정이 궁금할 때만 | | `initreq/` | 프로젝트 착수 시 클론해둔 참고 레포(quad v1, fusion, vide, rbvm, tbox, code-docker) + PA님 실 코드(`artworks/`, 4차 라운드 교차검증 근거) + 원본 요청(`req.md`, `raw-userinput.md`) + `quad2-try`(이전에 시도했다 폐기한 v2 재작성 시도 — 리서치 완료, 결론은 `base/bind-system-plan.md`) — 읽기 전용 리서치 소스, 여기 내용을 옮기지 말고 항상 원본 그대로 유지 | @@ -45,9 +45,9 @@ | `attribute-plan.md` | **[2026-08-07 여덟 번째 세션 신설]** 단일 키 `[AttributeKey "Name"]`(구 `Attribute`) — `SetAttribute(name, nil)`이 네이티브 지우기라 `None` 센티널과 가장 깔끔하게 맞아떨어짐. **[2026-08-11 아홉 번째 세션]** 여러 Store를 한 번에 attribute로 묶는 그룹 `Attribute(...)` 프리미티브 신설(`Tag`와 동형 array-part 값 객체, `Merged`로 헤테로지니어스 Store 합성), 이름 충돌 방지로 단일 키를 `AttributeKey`로 리네임(잠정). **[같은 세션 후속]** `AttributeKey(name)`이 이름별 weak 캐시로 동등성 보장하도록 확정되며, 그룹 Handler는 자기 완결형 재구현 대신 메모이즈된 키로 기존 단일 키 경로에 재귀 위임하는 걸로 개정(중복 구현 제거). **[2026-08-12 열 번째 세션]** 그룹/직접 쓰기가 같은 이름을 동시에 관리하는 충돌을 막기 위해 그룹은 공개 캐시 대신 `rawNew(name)` 전용 키+소유권 `Relate`로 전환. **[열한 번째 세션]** `retract`가 store 재발행마다 항상 불린다는 정정에 맞춰 `AttributeKeyHandler.retract`를 손봄(이 시점엔 `v==nil` 가드 버전 — 아래 열여섯 번째 세션에서 최종 재정정됨), 그룹의 "남아있는 이름" 위임도 매번 `retractUnder`를 먼저 부르도록 정정(체인 누수 방지). **[2026-08-12 열여섯 번째 세션, 최종 재정정]** `retract`는 완전 no-op으로 굳어짐(`SetAttribute`는 오직 `process(inst,k,nil)`에서만) — Attribute는 명시적 `None`/`nil`로만 지워지고, 그룹 diff나 컴포넌트 언마운트로 이름이 조용히 사라져도 값은 자동으로 안 지워짐(`Ref`의 "Destroy 무관, 정리는 명시적으로" 철학과 통일), 단 사라진 이름의 *구독*은 끊어 자원 누수는 막음 — 위 "v==nil 가드" 버전은 이걸로 폐기. **[2026-08-13 세션, 전면 재정정]** `rawNew`+`owners` 수동 레지스트리 방식이 "그룹이 이름을 놓았다 다시 포함하면 자기 자신과 충돌"하는 실제 버그로 확인됨 — `AttributeGroupKeyHandler`라는 `isHandlable` 없는 순수 체크포인트 핸들러를 `Dispatch.processAs`로 명시 push하고 `Dispatch.retractSelfAndUnder`로 통째 철거하는 방식으로 전면 재설계, 소유권 충돌 감지도 별도 레지스트리 없이 기존 재진입 가드가 대신 잡아줌(`bind-system-plan.md` 참고). `AttributeKeyHandler`는 다시 완전 무상태로 단순화됨. **[2026-08-13 세션, 다섯 번째, 전면 재설계 — 체크포인트조차 불필요해짐]** `Dispatch`가 인덱스 기반으로 재설계되며 `AttributeGroupKeyHandler`/`processAs`/`retractSelfAndUnder`를 전부 걷어냄 — 그룹이 그냥 공개 `AttributeKey(name)`으로 항상 인덱스 1부터 `Dispatch.process`/`retractFrom`을 직접 부르면 끝(점유 체크 자체가 소유권 충돌 감지), `groupState` Relate도 필요 없어짐(반환 클로저가 이름 집합을 직접 캡처) — 중간 버전은 `archive/checkpoint-handler-pattern-reversed.md`. **[2026-08-13 감사, 정정]** 그런데 그 의사코드가 `process` 안에서 이름마다 `retractFrom(...,1,...)`을 먼저 부르고 있어 **인덱스 1이 무조건 비워지는 바람에 점유 체크가 전혀 작동하지 않았음**(그룹↔그룹 사이에서 조용한 last-write-wins가 그대로 남아 있었음) — `process`는 `Dispatch.process`만 부르고 철거는 반환 클로저가 자기가 등록한 이름 전부에 대해 하도록 정정. 그룹 Handler 시그니처가 계약과 안 맞던 것(`process(inst,index,v)` 3-인자)도 같이 수정 | | `onchange-plan.md` | **[2026-08-10 세션 신설]** `OnChange(name)` — `GetPropertyChangedSignal` 바인딩 전용 DI 키, `Attribute`와 달리 제네릭 타입 파라미터 없음(콜백 타입은 인라인 명시, 이벤트 바인딩과 같은 급 트레이드오프). 전부 quad-roblox(`Handlers/OnChange.luau`), `State`은 기존 이벤트 store-bind 메커니즘 재사용. **[2026-08-11 아홉 번째 세션 후속]** `AttributeKey`와 동일한 이름별 weak 캐시로 `OnChange(a) == OnChange(a)` 동등성 보장 | | `relate-plan.md` | **[2026-08-08 신설]** `Relate` — `inst`를 weak 키로 하는 범용 릴레이션 프리미티브(`SetWeak`/`GetWeak`/`SetStrong`/`GetStrong`, 비싱글톤 생성자). 구 `base.perInstanceState(inst)` placeholder를 대체·정식 승격, `lifecycle-pattern.md`의 `bindLifetime`/`canExecute`가 그 위에 얹힘. **[2026-08-12 열세/열네 번째 세션]** 서로 다른 두 `Relate`가 서로의 키를 상대방 값으로 강하게 붙잡는 상호 순환 패턴 경고 신설 — Luau에 ephemeron 테이블이 없어(공식 확인, luau.org/compatibility) 이런 순환은 실제로 GC가 안 됨, `Slot`의 `kSlotMap`/`slotOwner`가 실제 사례이자 수정 사례 | -| `ref-plan.md` | **[2026-08-13 여덟 번째 세션, `bind-system-plan.md`에서 분리]** `Ref`/`PreRef` — 지연 없는 확정 값 박스. 용도 재정의(leaf 노드를 담는 용도로도, leaf 노드에 바인딩하는 용도로도), `.Value`+`:Set`/`:Callback`/`:Wait`(전부 self 반환), `Ref`의 retract가 `TagHandler`와 같은 `Relate` diff 패턴이라는 것, 이중 바인딩 금지(`canBound`), `PreRef` 호이스팅 pre-pass와 1회용 `_fired` 가드. **분리는 순수 이동 — 결정은 하나도 안 바뀜** | -| `event-plan.md` | **[2026-08-13 여덟 번째 세션, `bind-system-plan.md`에서 분리 — 사용자가 직접 지목]** 이벤트 바인딩 — 핸들러가 self(Instance)를 **안** 받는다는 확정(Ref가 이미 커버, 이중 쓰기 경로 방지), 이벤트도 store-bind 가능하며 `false`를 넣으면 disconnect. 이벤트 *네이밍* 관례는 인스턴스 생성과 한 절에 섞여 있어 `bind-system-plan.md`에 남음, `GetPropertyChangedSignal`은 `onchange-plan.md`. **분리는 순수 이동** | -| `brand-plan.md` | **[2026-08-13 여덟 번째 세션, `bind-system-plan.md`에서 분리]** `Brand` — 런타임 nominal 타입 판별 통합 메커니즘(`Brand.set`/`Brand.get`), `isState`를 10종 branded 타입 전부로 일반화. 동작/구현은 확정, **이름 `Brand` 자체만 용어 정리 대기**(`question.md` 1번). **분리는 순수 이동** | +| `ref-plan.md` | **[2026-08-13 아홉 번째 세션, `bind-system-plan.md`에서 분리]** `Ref`/`PreRef` — 지연 없는 확정 값 박스. 용도 재정의(leaf 노드를 담는 용도로도, leaf 노드에 바인딩하는 용도로도), `.Value`+`:Set`/`:Callback`/`:Wait`(전부 self 반환), `Ref`의 retract가 `TagHandler`와 같은 `Relate` diff 패턴이라는 것, 이중 바인딩 금지(`canBound`), `PreRef` 호이스팅 pre-pass와 1회용 `_fired` 가드. **분리는 순수 이동 — 결정은 하나도 안 바뀜** | +| `event-plan.md` | **[2026-08-13 아홉 번째 세션, `bind-system-plan.md`에서 분리 — 사용자가 직접 지목]** 이벤트 바인딩 — 핸들러가 self(Instance)를 **안** 받는다는 확정(Ref가 이미 커버, 이중 쓰기 경로 방지), 이벤트도 store-bind 가능하며 `false`를 넣으면 disconnect. 이벤트 *네이밍* 관례는 인스턴스 생성과 한 절에 섞여 있어 `bind-system-plan.md`에 남음, `GetPropertyChangedSignal`은 `onchange-plan.md`. **분리는 순수 이동** | +| `brand-plan.md` | **[2026-08-13 아홉 번째 세션, `bind-system-plan.md`에서 분리]** `Brand` — 런타임 nominal 타입 판별 통합 메커니즘(`Brand.set`/`Brand.get`), `isState`를 10종 branded 타입 전부로 일반화. 동작/구현은 확정, **이름 `Brand` 자체만 용어 정리 대기**(`question.md` 1번). **분리는 순수 이동** | | `tween-plan.md` | **[2026-08-12 세션, `research/`에서 승격]** 값-레벨 `Tween` 래퍼(PropertyHandler가 소비, 구 특수 bind key 모델은 `archive/tween-special-bind-key-reversed.md`). 3-상태 릴레이션 슬롯(`{Tween,Value}\|true\|nil`), `T'=T\|Tween` 타입 치환. 옵션 값 모양은 `Info: TweenInfo?` 우선+편의 필드 폴백, override는 `Tween.Cancel`(기본)/`Tween.Finish` 2값. `Animate(info)`는 `Tween` opts를 `T\|State`로 받아 `:Apply`로 꽂는 sugar. 자연완료 시 per-instance 북키핑은 정리 안 해도 됨으로 확정(목표값 도달 상태라 부작용 없음, Completed 이벤트 구독 장치는 오버엔지니어링으로 판단). `initValue`는 사용자가 직접 처리(에이전트 범위 제외) | ## `reference/` — 온디맨드 참고 자료 (2026-08-07 신설) @@ -93,7 +93,7 @@ | `onchange-per-property-codegen-rejected.md` | **[기각됨, 2026-08-10 신설]** `OnChange.PropertyName` 프로퍼티별 정적 코드 생성 — Attribute의 정적 지름길과 달리 (클래스 수 × 프로퍼티 수) 규모로 폭발해 기각, `OnChange(name)` 단일 팩토리로 대체 | | `retract-always-fires-reversed.md` | **[역전됨, 2026-08-12 열한 번째 세션 신설]** "핸들러 타입이 안 바뀌면 retract 없이 process가 diff" — 실제로는 `retract`가 store 재발행마다 항상 불림(핸들러 타입 무관). `Tag`/`Ref`/`Slot`/`Attribute` 전부 이 오류 위에서 설계돼 있었음이 드러나 한 세션에 전부 정정 | | `slot-discard-no-portal-reversed.md` | **[역전됨, 2026-08-13 일곱 번째 세션 신설]** Slot의 **"retract = 폐기, 옮기지 않음"(2026-08-04 확정) + "portal은 오버엔지니어링이라 안 함"** — 여섯 번째 세션에 `State` 교체가 파괴에서 **언마운트**로 뒤집히며 portal이 별도 기능이 아니라 그 귀결이 됨(`state`와 동일한 시맨틱). `base/slot-plan.md`에 히스토리로 남아 있던 세 덩어리(확정 문단 + `State` 왕복 분석 + 포탈 검토와 숙제 셋)를 원문 그대로 이전, 숙제 셋이 각각 어떻게 결말났는지도 정리 | -| `question-resolved.md` | **[해소 아카이브, 2026-08-13 여덟 번째 세션 신설]** `question.md`에서 걷어낸 **결정 완료** 항목 전부(당시 32개 `[해소됨]` 마커) — 추가 프리미티브 필요성 라운드, 구현 착수 직전 감사 요약, 확정된 용어들(`State`/`Relate`/`List`/`canBound`/`Ref`/`PreRef`/`Peek`/`isState`/`None`/`Handler`), 포탈·`State` 왕복 해소 등. 분리 직전 전문을 그대로 보존. **`question.md`는 이제 사용자가 답해야 할 것만 담음** — 항목이 해소되면 여기로 옮길 것 | +| `question-resolved.md` | **[해소 아카이브, 2026-08-13 아홉 번째 세션 신설]** `question.md`에서 걷어낸 **결정 완료** 항목 전부(당시 32개 `[해소됨]` 마커) — 추가 프리미티브 필요성 라운드, 구현 착수 직전 감사 요약, 확정된 용어들(`State`/`Relate`/`List`/`canBound`/`Ref`/`PreRef`/`Peek`/`isState`/`None`/`Handler`), 포탈·`State` 왕복 해소 등. 분리 직전 전문을 그대로 보존. **`question.md`는 이제 사용자가 답해야 할 것만 담음** — 항목이 해소되면 여기로 옮길 것 | | `checkpoint-handler-pattern-reversed.md` | **[역전됨, 2026-08-13 다섯 번째 세션 신설]** `AttributeGroupHandler`의 이름 소유권 충돌을 고치려고 만든 `Dispatch.processAs`/`Dispatch.retractSelfAndUnder` 체크포인트 핸들러 패턴(같은 날 네 번째 세션 신설) — `chains`를 핸들러 identity가 아니라 재귀 깊이 인덱스로 추적하는 더 근본적인 재설계로 대체되며 같은 날 바로 불필요해짐. `State>`도 이 재설계로 UB에서 정상 지원 대상으로 바뀜 | ## 참고 diff --git a/.claude/archive/question-resolved.md b/.claude/archive/question-resolved.md index b51a5d6..45427d7 100644 --- a/.claude/archive/question-resolved.md +++ b/.claude/archive/question-resolved.md @@ -1,6 +1,6 @@ # [해소 아카이브] `question.md`에서 걷어낸 결정 완료 항목 -**분리 시점**: 2026-08-13 여덟 번째 세션. 사용자 지적 — +**분리 시점**: 2026-08-13 아홉 번째 세션. 사용자 지적 — > "question.md 또한, 사람이 봐야하는 문서인데 해결된 것이 많아서 필터해서 > 필요한 부분만 읽어보기 힘듦. archive 되어야할 요소는 archive 에 diff --git a/.claude/audit/gcconn-trick-verification.md b/.claude/audit/gcconn-trick-verification.md index 5852a9b..0e68e55 100644 --- a/.claude/audit/gcconn-trick-verification.md +++ b/.claude/audit/gcconn-trick-verification.md @@ -1,6 +1,6 @@ # gcconn 트릭 — 부분 실측 검증 결과 -**상태**: 부분 확인(2026-08-13). `luau-test/10-roblox-studio-checks.server.luau`의 +**상태**: 부분 확인(2026-08-13). `10-roblox-studio-checks.server.luau`(`luau-test/not-run/`)의 공식 스크립트가 아니라 사용자가 별도로 작성한 저수준 검증 스크립트로 확인됨 — `bindLifetime`/`canBound`/`unbindLifetime` 자체의 이중 바인딩 로직(A-1/A-2)과 Part B/C는 여전히 미검증. **아래 "아직 확인 안 된 것"이 @@ -32,7 +32,7 @@ Studio에서 실행된 사용자 자작 스크립트(공식 `10` 파일이 아 트릭과 동일한 신호를 구독, 콜백 클로저가 `target`을 업밸류로 캡처. - Connection을 변수로 안 잡는 경우(Test 1)와 잡는 경우(Test 2) 둘 다 확인. - `triggerGC()`로 GC 완료를 간접 관찰(기법 상세는 - `luau-test/gc-trigger-helper.server.luau`). + `gc-trigger-helper.server.luau`, `luau-test/not-run/`). ## 확인된 것 @@ -54,8 +54,8 @@ Studio에서 실행된 사용자 자작 스크립트(공식 `10` 파일이 아 - **A-1/A-2 (`canBound` 이중 바인딩 게이트, unbind 후 재바인딩 허용)** — `bindLifetime`/`canBound`/`unbindLifetime`/`Subscribed` 로직 자체는 - 이 스크립트에 없음(순수 GC/Connection 메커니즘만 테스트함). `luau-test/ - README.md`가 명시한 두 번째 "심각한 발견" 트리거 조건(A-2 실패 시 + 이 스크립트에 없음(순수 GC/Connection 메커니즘만 테스트함). `luau-test/README.md`가 + 명시한 두 번째 "심각한 발견" 트리거 조건(A-2 실패 시 `canBound`/`unbindLifetime` 설계 재검토)은 미해소 — 공식 `10` 파일을 그대로 실행해서 확인해야 함. - **Part B (Attribute의 Instance 참조 타입 지원)**, **Part C @@ -80,7 +80,7 @@ Studio에서 실행된 사용자 자작 스크립트(공식 `10` 파일이 아 결론은 이번에 반증됨** — weak-value 테이블에 canary를 넣어두고 할당 압력(`table.create` 반복)을 걸며 `task.wait`로 기다리면, incremental GC가 실제로 완료되는 시점을 간접 관찰 가능함(사용자가 이번 검증에서 -확인). `07`의 docstring과 이 기법 자체는 `luau-test/gc-trigger-helper.server.luau`로 +확인). `07`의 docstring과 이 기법 자체는 `gc-trigger-helper.server.luau`(`luau-test/not-run/`)로 분리해뒀음 — Studio 기반 스파이크(`10` 등)가 GC 완료를 기다려야 할 때 그 파일의 `waitForGC`를 그대로 복붙하면 됨. diff --git a/.claude/base/architecture.md b/.claude/base/architecture.md index ec5b583..41ef8f4 100644 --- a/.claude/base/architecture.md +++ b/.claude/base/architecture.md @@ -49,7 +49,7 @@ quad는 이제 "스크립트"가 아니라 **라이브러리**다. DOMless Roblo 복잡도가 너무 올라간다고 판단 — 당장은 `CollectionService` 그대로 사용. **대신 Ref가 도입됨** — 단 Ref의 용도는 "id로 조회"가 아니라 "외부에서 이미 관리되고 있는 instance를 quad로 점진적으로 마이그레이션/래핑하기 위해 - 직접 참조를 얻는 것"(`base/bind-system-plan.md`의 Ref 절 참고) — 둘을 + 직접 참조를 얻는 것"(`base/ref-plan.md`의 Ref 절 참고) — 둘을 혼동하지 말 것. - **2026-08-04 6차: 네임스페이싱 충돌을 심각하게 안 보는 이유 확정.** 충돌을 피해야 하는 단위는 보통 컴포넌트 단위로 나오고, 그 경우는 Ref로 diff --git a/.claude/base/bind-system-plan.md b/.claude/base/bind-system-plan.md index c263de9..d59ddc7 100644 --- a/.claude/base/bind-system-plan.md +++ b/.claude/base/bind-system-plan.md @@ -1,6 +1,6 @@ # Bind 시스템 — pluggable key/value 핸들러 (base로 승격됨) -> **📄 [2026-08-13 여덟 번째 세션] 이 문서는 분할 중입니다 — 1단계 완료.** +> **📄 [2026-08-13 아홉 번째 세션] 이 문서는 분할 중입니다 — 1단계 완료.** > 2989줄까지 불어나 사람이 검토할 수 없고 한 곳의 실수가 미치는 범위가 > 너무 크다는 사용자 지적으로 쪼개는 중. **1단계로 분리된 것(내용/결정은 > 하나도 안 바뀜, 순수 이동)**: @@ -164,8 +164,8 @@ retract 클로저를 반환하는 1-메소드 계약으로 합쳐짐 — 이 절 - 모든 핸들러는 대상 **Instance를 직접, 항상** 받는다. quad는 "인스턴스를 생성하고 그 인스턴스를 처리하는" 라이브러리다 — 다른 라이브러리가 만든 값(예: Store)을 그 인스턴스에 적용하도록 돕는 역할에 가깝다. 그래서 핸들러가 "나중에 생길 - 대상"을 비동기로 기다릴 필요 자체가 없음(아래 Ref 절 참고 — Ref는 다른 이유로 - 존재). + 대상"을 비동기로 기다릴 필요 자체가 없음(`ref-plan.md`의 Ref 절 참고 — Ref는 + 다른 이유로 존재). - **보강(2026-08-04)**: `inst`가 항상 살아있는 엔진 객체(Roblox Instance)일 필요는 없음 — 특정 백엔드에서 실제 엔진 객체 생성/바인딩 비용이 비싸면 (예: 웹 DOM) 중간 표현으로 평범한 테이블을 만들고 나중에 그 테이블을 @@ -329,8 +329,8 @@ retract 클로저를 반환하는 1-메소드 계약으로 합쳐짐 — 이 절 키(프로퍼티/이벤트)를 다른 의미로 취급해야 하니 구분 비용이 이미 드는 참에 순서까지 명시적으로 고정하는 게 거의 공짜. **결과적으로 배열 슬롯에 놓인 어떤 값(Ref 포함)이든 모든 프로퍼티/이벤트 세팅보다 항상 - 먼저 처리된다는 게 base 자체의 보장**이 됨 — 아래 "Ref 일반화" 절 뒤에 - 이어지는 "PreRef" 절이 이 보장 위에서 성립. **M0 스파이크에서 실제 + 먼저 처리된다는 게 base 자체의 보장**이 됨 — `ref-plan.md`의 "Ref 일반화" 절 + 뒤에 이어지는 "PreRef" 절이 이 보장 위에서 성립. **M0 스파이크에서 실제 Luau로 이 순회 동작 자체를 검증할 것**(지금까지 추론/관찰만으로 확정된 항목 — `research/pre-implementation-audit.md`가 짚은 "실제 Luau로 부딪혀본 적 없는 것" 범주와 같은 급이라 신중하게 다룸). @@ -860,7 +860,7 @@ lazy 생성. `nil`을 넣으면 (1) 그 자리가 "안 채워짐"과 구별이 안 되고 (2) 배열이 구멍 나면서 순수 array 취급이 깨져 접근 비용이 올라감(해시 파트로 밀림) — `None`은 실재하는 값이라 자리를 "채워짐"으로 유지시켜줌, PreRef -pre-pass 소진 슬롯에 이미 적용된 것과 같은 원칙(위 "PreRef" 절의 +pre-pass 소진 슬롯에 이미 적용된 것과 같은 원칙(`ref-plan.md`의 "PreRef" 절의 "왜 `None`이 아니라 `nil`인가" 참고 — **단, 그 절에서 최종적으로 `nil`로 되돌아간 건 Ref 콜백/대기자 배열 한정**이고 `sourceList`/PreRef pre-pass처럼 순서가 실제로 중요하거나 "채워짐 여부"를 엄밀히 구별해야 @@ -1098,8 +1098,8 @@ end - **반환하는 클로저가 할 일은 `unbindLifetime(inst, observer)` 호출뿐 — 위임 대상까지 수동으로 안 쫓아가도 됨.** `Dispatch.retractFrom`이 자기 밑에 위임된 걸 알아서 정리해주므로(위 "Dispatch 체인" 절), 이 - 클로저는 정확히 자기 자신의 자원(Observer)만 정리하면 끝 — 이게 위 - "이벤트도 store-bind 가능" 절에서 이미 "엔지니어링 비용이 낮다"고 + 클로저는 정확히 자기 자신의 자원(Observer)만 정리하면 끝 — 이게 + `event-plan.md`의 "이벤트도 store-bind 가능" 절에서 이미 "엔지니어링 비용이 낮다"고 서술한 것과 같은 이유(새 디스패치 메커니즘 없이 기존 계약만 구현). **[2026-08-13 다섯 번째 세션] 별도 `Relate`가 더 이상 필요 없음** — `observer`는 `process` 안의 로컬 변수를 반환 클로저가 upvalue로 그대로 @@ -1146,14 +1146,14 @@ ref 타입처럼 생각하는 게 맞는 거 같음 — 그걸 처리하는 플 의미가 "조용히 UB"도 "즉시 실패"도 아니라 "그냥 정상적으로 동작함"으로 다시 한번 바뀜. -## Ref / PreRef — 전용 문서로 분리됨 (2026-08-13 여덟 번째 세션) +## Ref / PreRef — 전용 문서로 분리됨 (2026-08-13 아홉 번째 세션) `Ref`/`PreRef`(용도 재정의, `.Value`/`:Set`/`:Callback`/`:Wait` API, `Ref`의 retract, 이중 바인딩 금지, PreRef 호이스팅/1회용 가드)는 **`base/ref-plan.md`로 분리**됨 — 이 문서가 3000줄에 육박해 분할한 1단계. 내용/결정은 안 바뀜. -## 이벤트 바인딩 — 전용 문서로 분리됨 (2026-08-13 여덟 번째 세션) +## 이벤트 바인딩 — 전용 문서로 분리됨 (2026-08-13 아홉 번째 세션) "이벤트 핸들러는 self(Instance)를 받지 않는다"와 "이벤트도 store-bind 가능 — `false`로 disconnect" 두 절은 **`base/event-plan.md`로 분리**됨 @@ -2243,7 +2243,7 @@ vs `[BooleanAttribute "name"]`)뿐 아니라 `None`/`process`/`retract` 동작 동형)가 추가되며, 단일 키 생성자는 이름 충돌 방지로 `AttributeKey<>`로 리네임됨. -## `Brand` — 전용 문서로 분리됨 (2026-08-13 여덟 번째 세션) +## `Brand` — 전용 문서로 분리됨 (2026-08-13 아홉 번째 세션) 런타임 nominal 타입 판별 통합 메커니즘(`Brand.set`/`Brand.get`, `isState`를 10종 branded 타입 전부로 일반화)은 **`base/brand-plan.md`로 분리**됨 — diff --git a/.claude/base/brand-plan.md b/.claude/base/brand-plan.md index 6250467..227b59e 100644 --- a/.claude/base/brand-plan.md +++ b/.claude/base/brand-plan.md @@ -1,6 +1,6 @@ # `Brand` — 런타임 nominal 타입 판별 통합 메커니즘 -> **[2026-08-13 여덟 번째 세션] `bind-system-plan.md`에서 분리됨.** +> **[2026-08-13 아홉 번째 세션] `bind-system-plan.md`에서 분리됨.** > 자기 완결적인 유틸이라 디스패치 코어와 같은 파일에 있을 이유가 없었음. > **내용은 옮기기만 했고 결정은 하나도 안 바뀜.** diff --git a/.claude/base/component-composition-plan.md b/.claude/base/component-composition-plan.md index 8fe1c8d..e5f14be 100644 --- a/.claude/base/component-composition-plan.md +++ b/.claude/base/component-composition-plan.md @@ -303,7 +303,7 @@ caller가 named parameter 하나에 여러 modifier를 몰아넣고 싶을 때 내부는 항상 이미 합쳐진 단일 값만 받으므로 컴포넌트 저작자가 배열 처리를 신경 쓸 필요 없음. Ref는 필드 충돌 개념이 없어 이 문제 자체가 없음(여러 Ref를 받으면 그냥 전부 실행하면 됨 — Ref 콜백 리스트는 애초에 여러 등록을 -누적하도록 설계돼 있음, `bind-system-plan.md`의 Ref 콜백/대기자 절) — 별도 +누적하도록 설계돼 있음, `ref-plan.md`의 Ref 콜백/대기자 절) — 별도 결합 유틸 불필요. **정확한 동작(baked 값 교체 경고, 순서 의존성, `Apply`와의 역할 구분, `:Peek`/`isState`)은 `base/modifier-plan.md` 9번 절이 최종 소스** — `Merge`로 전부 대체해 `Apply`만 강제하는 방안도 이번에 검토했으나, diff --git a/.claude/base/event-plan.md b/.claude/base/event-plan.md index 4afa3b7..be6f9f6 100644 --- a/.claude/base/event-plan.md +++ b/.claude/base/event-plan.md @@ -1,6 +1,6 @@ # 이벤트 바인딩 — self 미전달, `false`로 disconnect -> **[2026-08-13 여덟 번째 세션] `bind-system-plan.md`에서 분리됨.** +> **[2026-08-13 아홉 번째 세션] `bind-system-plan.md`에서 분리됨.** > 사용자가 "이벤트 연결은 다른 base 문서가 되어야 할 듯"이라고 직접 > 지목한 부분. **내용은 옮기기만 했고 결정은 하나도 안 바뀜.** diff --git a/.claude/base/lifecycle-pattern.md b/.claude/base/lifecycle-pattern.md index 96db162..4b0956f 100644 --- a/.claude/base/lifecycle-pattern.md +++ b/.claude/base/lifecycle-pattern.md @@ -189,7 +189,7 @@ function bindLifetime(inst, value) local _ = gchold -- 발화 안 함, 클로저 생존이 곧 gchold 생존 -- 2026-08-13 부분 실측 확인(미발화 + Destroy 시 Connected 즉시 -- 전환) — audit/gcconn-trick-verification.md. canBound 이중 - -- 바인딩 게이트(A-1/A-2)는 아직 공식 luau-test/10으로 미확인. + -- 바인딩 게이트(A-1/A-2)는 아직 공식 10(`luau-test/not-run/`)으로 미확인. end) relate:SetStrong(inst, GCCONN, gcconn) end diff --git a/.claude/base/modifier-plan.md b/.claude/base/modifier-plan.md index f23341c..55e21c2 100644 --- a/.claude/base/modifier-plan.md +++ b/.claude/base/modifier-plan.md @@ -221,7 +221,7 @@ self 최상위가 아니라 **내부 저장소**에 써야 함 — 역시 아래 > 패턴**이라 실사용에서 바로 터지는 경로임. 해법: 필드 데이터를 유일 테이블 > identity 키(`FieldsKey` 등)로 분리된 내부 테이블에 담고, `table.clone`이 > 얕은 복사라 setter 안에서 그 내부 테이블도 따로 `table.clone` 할 것. -> `luau-test/17`이 이 구조를 실측 검증함(원래 스파이크가 리터럴 키 방식으로 +> `17`(`luau-test/done/`)이 이 구조를 실측 검증함(원래 스파이크가 리터럴 키 방식으로 > 짜여 있다가 바로 이 이유로 크래시했고, 그 과정에서 이 문서의 "데이터를 > 테이블에 직접 두고"라는 표현이 두 가지로 읽힌다는 게 드러남). 즉 `:FontSize`/ `:Round`/앞으로 생길 어떤 필드 이름이든 전부 이 **하나의 제네릭 `__index` @@ -537,7 +537,7 @@ Modifier 값을 변수/모듈 상수로 만들어 재사용하는, 기존에도 ### 9-2. `Overridden`가 서로 다른(그러나 상하위 관계인) Modifier 타입을 섞는 경우 — 타입 시그니처 미확정 (2026-08-07 다섯 번째 세션 후속) -**[해소됨, 2026-08-13 첫 실측 라운드]** `luau-test/09-type-modifier-overridden-subtype.luau`로 +**[해소됨, 2026-08-13 첫 실측 라운드]** `09-type-modifier-overridden-subtype.luau`(`luau-test/done/`)로 실측 완료 — 아래에서 우려한 지점이 그대로 재현됨(`Overridden`의 리턴 타입 불일치로 서브타입이 깨짐), fallback(`any`)은 정상 작동 확인. "실 Luau 테스트 필요"는 더 이상 유효하지 않음 — 아래는 그 결론에 이른 diff --git a/.claude/base/ref-plan.md b/.claude/base/ref-plan.md index 52cb0dc..66e58a0 100644 --- a/.claude/base/ref-plan.md +++ b/.claude/base/ref-plan.md @@ -1,6 +1,6 @@ # Ref / PreRef — 지연 없는 확정 값 박스 -> **[2026-08-13 여덟 번째 세션] `bind-system-plan.md`에서 분리됨.** 그 +> **[2026-08-13 아홉 번째 세션] `bind-system-plan.md`에서 분리됨.** 그 > 문서가 2989줄까지 불어나 사람이 검토하기 어렵고 한 곳의 실수가 미치는 > 범위가 너무 커진다는 사용자 지적에 따른 1단계 분할. **내용은 옮기기만 > 했고 결정은 하나도 안 바뀜.** @@ -210,6 +210,17 @@ tween-plan.md`도 이에 맞춰 갱신됨). Ref의 진짜 용도는 다름: ### `Ref`의 retract — `State` 재바인드 시 이전 Ref에 `nil` (2026-08-12 여덟 번째 세션, `TagHandler`와 같은 메커니즘 재사용) +> **⚠️ [2026-08-13 감사 보강] 이 절의 "이전 `process`가 반환한 클로저가 +> `hintValue`로 먼저 불려 언바인딩하고, 그 다음 `process`가 바인딩"이라는 +> 서술은 곧 교체될 예정 — 아직 반영 안 됨. `research/dispatch-redispatch-diff-plan.md`가 +> 폐기 대상으로 지목한 바로 그 **선행 `retractFrom` 호출** 패턴이다 — 이 문서가 +> `bind-system-plan.md`에서 분리(2026-08-13 아홉 번째 세션, 순수 이동)될 +> 때 원문이 그대로 옮겨오면서 `bind-system-plan.md`/`tag-plan.md`/ +> `slot-plan.md`/`attribute-plan.md`가 이미 달고 있는 것과 같은 0-Z +> 배너가 같이 안 옮겨졌음. `question.md` **0-Z**(Attribute 이름 소유권) +> 해소 시 "하강 diff" 모델대로 이 절도 같이 재작성할 것 — **여기 적힌 +> 대로 구현하면 옛 모델로 짜게 됨.** + **배경**: `Ref`는 이미 "일반 프로퍼티/Modifier 필드/Store 값 어디든 자유롭게 들어감"(아래 "동적 경로 가드" 절)이 확정돼 있어 — `State`가 실제로 가능하고, 그러면 Store 값이 `refA`에서 `refB`로 바뀌는 경우가 생김. 이때 diff --git a/.claude/base/relate-plan.md b/.claude/base/relate-plan.md index 64e74e7..a9ec1d9 100644 --- a/.claude/base/relate-plan.md +++ b/.claude/base/relate-plan.md @@ -121,7 +121,7 @@ Handler 계약이 "`process`가 자기 retract 클로저를 반환"으로 바뀌 - **정리 조건을 실제 정리와 묶을 것.** `Relate` 엔트리를 지우는 코드가 "실제로 물러날 때"라는 조건 **밖**에 있으면, spurious 재발행에서 기록만 날아가 dedup이 조용히 무력화됨 — `RefLeafHandler`가 실제로 이 - 버그를 냈음(`bind-system-plan.md` "`Ref`의 retract" 절). + 버그를 냈음(`ref-plan.md` "`Ref`의 retract" 절). - **weak하다고 "언젠간 알아서 사라진다"에 기대지 말 것.** 값이 `SetWeak` 이어도 **언제 사라지는지는 GC 타이밍**이라, 그 전에 같은 키를 다시 쓰려는 코드가 비결정적으로 실패함 — `Slot`의 `destroySlotTree`가 자식 diff --git a/.claude/base/store-semantics.md b/.claude/base/store-semantics.md index 5de6a2c..ad8fbe4 100644 --- a/.claude/base/store-semantics.md +++ b/.claude/base/store-semantics.md @@ -188,7 +188,7 @@ State를 만족하도록 만들고, RefSource라는 별도 타입은 폐기**하 반환**하는 것으로 자연히 갱신됨(위 "타입 추론 문제" 절과 연동). **[해소됨, 2026-08-13 첫 실측 라운드]** 핵심 질문(Source가 State를 구조적으로 -만족하는 제네릭 메소드 체이닝)은 `luau-test/08-type-source-satisfies-state.luau`로 +만족하는 제네릭 메소드 체이닝)은 `08-type-source-satisfies-state.luau`(`luau-test/review-required/`)로 실측 통과 확인됨 — 아래 우려대로 "두 제네릭 타입 별칭이 서로를 참조하는 상호 재귀"는 실제로 위험했지만, 그 아래 제안한 단방향 의존(`State`가 `Source`를 참조 안 함) 회피책이 그대로 맞아떨어짐. **다만 좁은 잔여 diff --git a/.claude/luau-test/README.md b/.claude/luau-test/README.md index 98f89a4..b7499ad 100644 --- a/.claude/luau-test/README.md +++ b/.claude/luau-test/README.md @@ -4,7 +4,7 @@ > README는 각 파일이 **무엇을 왜 검증하는지**(의도·배경)만 담고, 상태는 > **폴더 구조 + STATUS.md**가 소스. -**[2026-08-13 여덟 번째 세션] 폴더가 곧 상태** — 파일이 평평하게 21개 +**[2026-08-13 아홉 번째 세션] 폴더가 곧 상태** — 파일이 평평하게 21개 쌓여 있어 사람이 "지금 내가 볼 게 뭔지" 못 고르겠다는 사용자 지적으로 재편: diff --git a/.claude/luau-test/STATUS.md b/.claude/luau-test/STATUS.md index d4e5d2b..bbc8735 100644 --- a/.claude/luau-test/STATUS.md +++ b/.claude/luau-test/STATUS.md @@ -1,6 +1,6 @@ # 스파이크 상태판 — **폴더가 곧 상태** -> 마지막 갱신: 2026-08-13 여덟 번째 세션(폴더 재편). +> 마지막 갱신: 2026-08-13 아홉 번째 세션(폴더 재편). > 첫 실측은 여섯 번째 세션 — 상세 결과는 `.claude/audit/luau-test-first-run-2026-08-13.md`. > 실행법: `luau <파일>` (런타임) / `luau-analyze <파일>` (타입 전용). diff --git a/.claude/luau-test/done/11-modifier-illegal-value-error.luau b/.claude/luau-test/done/11-modifier-illegal-value-error.luau index a0a608c..9d96a2a 100644 --- a/.claude/luau-test/done/11-modifier-illegal-value-error.luau +++ b/.claude/luau-test/done/11-modifier-illegal-value-error.luau @@ -20,7 +20,7 @@ 실행: `luau 11-modifier-illegal-value-error.luau` ]] --- ===== Brand 흉내 — 실제로는 base/bind-system-plan.md의 Brand 절이 다루는 +-- ===== Brand 흉내 — 실제로는 base/brand-plan.md의 Brand 절이 다루는 -- weak-key 레지스트리 기반이지만, 이 스파이크에선 태그 필드로 단순화 ===== local function tag(name) @@ -305,7 +305,7 @@ end 3. B에서 Slot 같은 "Modifier가 아닌 다른 핸들러 계층 값"은 State/Source에 여전히 자유롭게 들어갈 수 있는가(Modifier만의 예외라는 걸 재확인). 4. 이 스파이크는 Brand/isState를 태그 필드로 단순화한 것 — 실제 구현은 - base/bind-system-plan.md의 weak-key 레지스트리 기반 Brand를 씀, + base/brand-plan.md의 weak-key 레지스트리 기반 Brand를 씀, 여기선 그 판별 로직 자체가 아니라 "체크 지점 배치가 실제로 동작하는가"만 검증 대상. ]] diff --git a/.claude/luau-test/rewrite-required/13-type-ref-preref-subtype.luau b/.claude/luau-test/rewrite-required/13-type-ref-preref-subtype.luau index 146adf7..8244063 100644 --- a/.claude/luau-test/rewrite-required/13-type-ref-preref-subtype.luau +++ b/.claude/luau-test/rewrite-required/13-type-ref-preref-subtype.luau @@ -15,7 +15,7 @@ 이제 `isHandlable = isRef(v) and not isPreRef(v)`로 **명시적으로 좁혀야만** PreRef를 잘못 삼키지 않는다는 것. - 배경: .claude/base/bind-system-plan.md의 `Brand` 절 + 배경: .claude/base/brand-plan.md의 `Brand` 절 ("isRef(x)는 그 위에 Brand.get(x)==RefTag를 OR로 얹은 상위 개념")와 "`(v=Ref)` children 배열 leaf 매치 핸들러... isRef(v) and not isPreRef(v)로 명시적으로 좁혀야 함" 부분. diff --git a/.claude/question.md b/.claude/question.md index 2e4b548..2f8e851 100644 --- a/.claude/question.md +++ b/.claude/question.md @@ -1,7 +1,7 @@ # 확인/결정 필요 목록 **이 문서는 사용자가 답해야 할 것만 담습니다.** 이미 결정이 끝난 항목은 -2026-08-13 여덟 번째 세션에 `archive/question-resolved.md`로 옮겼음 — +2026-08-13 아홉 번째 세션에 `archive/question-resolved.md`로 옮겼음 — "해결된 게 너무 많아 필요한 부분만 읽기 어렵다"는 사용자 지적에 따른 것. 결정 내용은 안 바뀌었고 읽는 자리만 옮겼으니, 어떤 항목이 왜 그렇게 정해졌는지 되짚고 싶으면 그 문서를 볼 것(지금 유효한 설계 자체는 항상 @@ -241,6 +241,13 @@ Attribute가 직접 해야 함.** 엘비스 연산자류) 후보 추가 — Haskell 비교 리서치 중 나옴, 카탈로그 확정 규칙에 그대로 맞아 포함 근거는 있음. 상세는 `research/operator-sugar-plan.md`. 구현 자체는 맨 마지막 우선순위(순수 슈가, 없어도 무방) — 여전함. +- **중첩 State 평탄화 `State>` → `State`(2026-08-13 여섯 번째 + 세션 신설, 백로그)** — 인덱스 기반 `Dispatch` 재설계로 `State>`는 + UB에서 정상 지원 대상이 됐지만, 깊이가 늘수록 `retractFrom`의 힌트가 + 더 깊은 인덱스엔 `nil`로 전달돼 `Tag`/`Ref`/`Slot` 등의 힌트 기반 + 최적화(깜빡임 방지)가 무력화되는 실제 기능 손실이 있음 — 값 층에서 + 평탄화하는 `state:Flatten()`류 콤비네이터 아이디어는 나왔으나 착수 안 + 함. 상세는 `research/operator-sugar-plan.md` 마지막 절. - `research/existing-instance-bind-plan.md` — 스코프 논의만 필요, 구현 착수를 막지 않음. - **`quad-debug` 세부 API 이름** — `research/debug-tooling-plan.md` 참고. @@ -248,7 +255,7 @@ Attribute가 직접 해야 함.** 넘는지)까지 사용자가 Studio에서 직접 실측 검증 완료 — 기술적 불확실성은 다 해소됨, 남은 건 세부 API 이름뿐("이벤트 함수가 self로 instance를 읽는 게 quad 관습"이라는 언급은 2026-08-06 후속 세션에서 해소 — - 채택 안 함으로 확정, `base/bind-system-plan.md` "이벤트 핸들러는 + 채택 안 함으로 확정, `base/event-plan.md` "이벤트 핸들러는 self(Instance)를 받지 않는다" 절 참고). 사용자가 "quad 개발 완료 전엔 착수 못 함"으로 직접 후순위 지정한 건 여전함 — base 설계(M2 Dispatch/ M3 Source/M5 DI 생성자) 시점에 훅 확장 지점만 고려해두면 됨. diff --git a/.claude/research/dispatch-redispatch-diff-plan.md b/.claude/research/dispatch-redispatch-diff-plan.md index 6bb5f51..95b0a1b 100644 --- a/.claude/research/dispatch-redispatch-diff-plan.md +++ b/.claude/research/dispatch-redispatch-diff-plan.md @@ -251,6 +251,11 @@ Dispatch.process(inst, k, realv, index + 1) -- retractFrom 선행 호출 없 M6의 "`SlotHandler.process`는 claim 실패 시에도 파괴적 클로저를 반환해야 함")이 선행 `retractFrom` 전제로 쓰여 있음 — base 4개를 옮길 때 같이 갱신하고 배너를 걷을 것. +- **[2026-08-13 감사에서 추가]** `ref-plan.md` — `bind-system-plan.md` + 1단계 분할(2026-08-13 아홉 번째 세션, 순수 이동) 때 "`Ref`의 retract" + 절이 옮겨오면서 선행 `retractFrom` 호출 서술도 그대로 딸려왔음. 분할 + 당시 이 문서(section 6, 당시 아직 안 쓰임)가 없어 배너가 안 붙었던 + 것 — 반영 시 나머지 6개와 같이 재작성하고 배너 걷을 것. - **[2026-08-13 8차 감사에서 추가] 같은 패스에서 `bind-system-plan.md` 2단계 분할도 할 것** — 이 문서가 지시하는 재작성 범위(핸들러 계약 / @@ -259,6 +264,6 @@ Dispatch.process(inst, k, realv, index + 1) -- retractFrom 선행 호출 없 전면 재작성하므로, **재작성하면서 새 파일로 옮기면 인바운드 참조 (~37곳)를 한 번만 고치면 됨.** 따로 하면 같은 곳을 두 번 만짐. -**요약**: 배너를 달고 있는 파일 = 반영 대상. 위 6개(`bind-system-plan`/ -`tag-plan`/`slot-plan`/`attribute-plan`/`architecture`/`ROADMAP`)가 -전부이고, 반영이 끝나면 각 파일의 ⚠️ 배너도 같이 제거할 것. +**요약**: 배너를 달고 있는 파일 = 반영 대상. 위 7개(`bind-system-plan`/ +`tag-plan`/`slot-plan`/`attribute-plan`/`architecture`/`ROADMAP`/ +`ref-plan`)가 전부이고, 반영이 끝나면 각 파일의 ⚠️ 배너도 같이 제거할 것. diff --git a/.claude/research/pre-implementation-audit.md b/.claude/research/pre-implementation-audit.md index adca973..8e48863 100644 --- a/.claude/research/pre-implementation-audit.md +++ b/.claude/research/pre-implementation-audit.md @@ -357,7 +357,7 @@ M7의 전제가 Luau 공식 동작대로 성립함. 별도로, 프로퍼티에 A ### 2-1. Source가 State를 만족하는 제네릭 검증이 실패했을 때의 대안(Plan B)이 전혀 없음 **[해소됨, 2026-08-13 첫 실측 라운드]** 검증 자체는 -`luau-test/08-type-source-satisfies-state.luau`로 실행돼 핵심 질문(Source가 +`08-type-source-satisfies-state.luau`(`luau-test/review-required/`)로 실행돼 핵심 질문(Source가 State를 구조적으로 만족)이 통과했음 — 아래 "검증이 실패했을 때"라는 전제 자체가 (핵심 케이스에 한해) 더 이상 미래형이 아님. 다만 통과와 별개로 좁은 잔여 케이스(`State`가 자기 자신을 다른 타입 인자로 diff --git a/.claude/research/v1-compat-plan.md b/.claude/research/v1-compat-plan.md index 02b8cb0..74d21db 100644 --- a/.claude/research/v1-compat-plan.md +++ b/.claude/research/v1-compat-plan.md @@ -17,8 +17,8 @@ compat.lua나 어댑터 코드는 전혀 없고, README/커밋 메시지에도 "왜 포기했는지" 단서가 없음 — 애초에 착수된 적이 없다는 뜻. -→ `question.md:110`이 "OOP 상속/커스텀 파서/Slot 스텁/`Pipe` COW는 확인된 -죽은 접근"이라고 명시한 목록에 compat은 **포함돼 있지 않음**(정확한 서술). +→ `archive/question-resolved.md`가 "OOP 상속/커스텀 파서/Slot 스텁/`Pipe` COW는 +확인된 죽은 접근"이라고 명시한 목록에 compat은 **포함돼 있지 않음**(정확한 서술). 즉 CLAUDE.md의 "반복 조사 금지"는 compat에는 적용되지 않는다 — 이 문서를 쓰는 게 규칙 위반이 아님. @@ -242,8 +242,9 @@ v2 트리 안에 과거 v1 컴포넌트를 리프로 박아넣는 것, (B) 기 "직접 Destroy 금지, Unmount 경유" 규칙을 Dispatch 엔진의 어느 지점에 훅으로 강제할지도 Slot 실제 구현 시점 확인 필요. - **결론: 지금 결정 불가.** M0 이후 Slot 코어 로직 구현 라운드 - (`question.md`의 "여러 Slot이 형제로 섞일 때 순서 보장" 항목과 같은 - 시점)에서 이 두 가지를 실제 구현과 함께 재확인해야 함. + (`archive/question-resolved.md`의 "여러 Slot이 형제로 섞일 때 순서 보장" + 항목과 같은 시점 — 이미 해소돼 아카이브로 옮겨졌으나 실제 구현 시점의 + 참고 자료로는 여전히 유효)에서 이 두 가지를 실제 구현과 함께 재확인해야 함. ## 8. 남은 확인 사항 (추가 리서치 후보, 지금 결정 불필요) diff --git a/.claude/session/2026-08-13-10-corpus-audit-parallel-agents.md b/.claude/session/2026-08-13-10-corpus-audit-parallel-agents.md new file mode 100644 index 0000000..7b4b4af --- /dev/null +++ b/.claude/session/2026-08-13-10-corpus-audit-parallel-agents.md @@ -0,0 +1,125 @@ +# 2026-08-13 열 번째 세션 — 병렬 에이전트 코퍼스 감사, 실제 부정확성 7건 발견·수정 + +## 배경 + +사용자가 "세션 기록들을 전부 읽어보며 문서 전체에 문제가 되는 부분이 +있는지" 요청 — 단, 미확정 항목(0-Y/0-Z 등)은 문제로 세지 말고 **문서가 +실제로 부정확한 것만** 짚어달라는 조건. 직전 세션(아홉 번째)이 `luau-test/` +상태별 폴더 재편, `bind-system-plan.md` 1단계 분할, `question.md` 트림, +`doc-check.py` 신설을 한 번에 처리한 큰 구조 변경 세션이었기 때문에, 그 +변경들의 반영 누락이 남아있을 가능성이 가장 높은 지점으로 판단. + +## 방법 + +1. 먼저 `python3 .claude/tools/doc-check.py`로 기계가 잡는 것부터 확인 — + ERROR 0, WARN 60건은 전부 기존에 허용 범위로 확인된 것(패러프레이즈 + 인용, 이미 날짜/범위가 명시된 완결 주장)이라 새 이슈 없음. +2. 기계가 못 잡는 의미론적 문제를 6개 병렬 Explore 에이전트로 분담 + (CLAUDE.md "작업 방식"의 병렬 Agent 원칙 그대로): + - A: `bind-system-plan.md` 분할(9차 세션) 정합성 + - B: `luau-test/` 폴더 재편(9차 세션) 정합성 + - C: `question.md` 트림(9차 세션) 정합성 + - D: 배너 달린 base 4종(`bind-system-plan`/`tag`/`slot`/`attribute`) + 정밀 감사 — 배너 범위 밖 서술이 다른 최근 결정과 모순되는지 + - E: 나머지 base 16개 문서 전수 — retract-always-fires/ + `State>`/인덱스 기반 chains/Slot 언마운트 전환/and·or + 금지 등 최근 결정 위반 여부 + - F: 루트 문서(`ROADMAP.md`/`HUMAN_TODO.md`/`.claude/README.md`)와 + `archive/` 정합성, 0-Y/0-Z 게이트 표시 여부 +3. 각 결과를 실시간으로 검토하며 발견 즉시 직접 수정(에이전트에 위임 + 안 함 — CLAUDE.md "중대 변경 핸드오버 체크리스트"의 "그 자리에서 + 닫을 것" 원칙). + +## 발견 및 수정 + +**1. `luau-test/` 재편 후 깨진 flat 경로 참조** — `research/ +pre-implementation-audit.md`, `base/store-semantics.md`, +`base/modifier-plan.md`(2곳), `base/lifecycle-pattern.md`, +`.claude/README.md`, `audit/gcconn-trick-verification.md`(4곳)가 옛 +`luau-test/08-...`/`/09-...`/`/10`/`/17`/`/gc-trigger-helper...` 경로를 +그대로 참조 — 실제 파일은 `done`/`review-required`/`not-run` 하위로 +이동해 전부 깨진 링크였음. 파일명+실제 소속 폴더 표기로 정정(경로 +대신 파일명 참조라는 9차 세션 원칙 준수). + +**2. `bind-system-plan.md` 분할 후 참조 정합성 깨짐**: +- 분할된 문서 **자기 자신** 안의 "아래 Ref 절"/"위 PreRef 절"/"위 + 이벤트 절" 같은 위치 참조 4곳(167/332/863/1102줄)이 실제로 이동된 + 절을 못 따라감 — "순수 이동"이 텍스트 상호참조까지는 보장 못 한다는 + 사례. +- 외부 문서(`architecture.md`/`relate-plan.md`/ + `component-composition-plan.md`/`question.md`)와 luau-test 파일 2개가 + 여전히 `bind-system-plan.md`의 Ref/Brand 절을 가리킴 — 전부 + `ref-plan.md`/`event-plan.md`/`brand-plan.md`로 정정. +- **가장 중요한 발견**: `ref-plan.md`의 "`Ref`의 retract" 절이 옛 + 재디스패치 모델(선행 `retractFrom` 호출)을 그대로 서술하는데, 분할 + 때 다른 4개 base 문서가 이미 달고 있던 0-Z ⚠️ 배너가 안 옮겨와 있었음 + — 이 상태로 구현하면 옛 모델로 짜일 위험. `research/ + dispatch-redispatch-diff-plan.md` 6절 검토로 실제 대상 메커니즘이 + 맞는지 교차검증 후(단순 키워드 매치가 아니라 "선행 호출 후 process" + 패턴이 정확히 일치함을 확인) 같은 형식의 배너 추가, 반영 대상을 + 6개→**7개**로 `dispatch-redispatch-diff-plan.md`/`CLAUDE.md` 양쪽 갱신. + +**3. 세션 번호 오기(8차→9차) 대량 발견** — `bind-system-plan.md` 분할/ +`luau-test/` 재편/`question.md` 트림/`tools/` 신설이 실제로는 전부 +**9차 세션**(`session/2026-08-13-09-structure-and-guardrails.md`) 작업인데, +git 커밋 타임스탬프로 교차검증한 결과 총 17곳(`.claude/README.md` 4곳, +`question.md`, `archive/question-resolved.md`, `luau-test/STATUS.md`, +`luau-test/README.md`, `base/ref-plan.md`/`event-plan.md`/ +`brand-plan.md`/`bind-system-plan.md`의 분할 배너 6곳, `doc-check.py` +자기 설명 2곳)가 "8차 세션"으로 잘못 표기돼 있었음(같은 날 2026-08-07의 +진짜 8차 세션과 혼동된 것으로 추정) — 전부 정정. + +**4. `question.md` 트림 중 열린 질문 하나 누락** — `State>` +평탄화(`state:Flatten()`) 백로그 항목이 `research/ +operator-sugar-plan.md`엔 있는데 트림된 `question.md`에서 빠져 있었음 — +3번 절(낮은 우선순위)에 복원. + +**5. 트림 후 깨진 참조** — `research/v1-compat-plan.md`가 이미 +`archive/question-resolved.md`로 옮겨진 "여러 Slot이 형제로 섞일 때 +순서 보장" 항목을 옛 `question.md:110` 경로로 가리키고 있었음(그 +줄번호도 트림 후 빈 줄이 됨) — 2곳 정정. + +**6. `ROADMAP.md`의 M0 섹션에 0-Y/0-Z 게이트 표시 누락** — M2/M4/M6/M10엔 +"0-Z 먼저 해소할 것" 배너가 있는데, 정작 M0 자체가 두 결정에 막혀 +있다는 사실이 서두/M0 섹션 어디에도 안 적혀 있었음(`HUMAN_TODO.md`/ +`CLAUDE.md`/`question.md`는 이미 정확히 서술 중이었음 — `ROADMAP.md`만 +누락) — M0 섹션 시작 부분에 게이트 배너 추가. + +## 문제 없음으로 확인된 것 + +배너 달린 4개 base 문서의 배너 **범위 자체**는 정확 — 배너 밖 서술 +(retract 항상 호출, `State>` 정상 지원, 인덱스 기반 `chains`, +Slot 언마운트 전환, `and`/`or` 금지)은 전부 최신 결정과 일치, 모호해서 +독자를 오도할 소지 없음. 나머지 base 16개 문서, `HUMAN_TODO.md`, +`luau-test/STATUS.md`의 상태 표는 실측과 정합. `question.md`/ +`question-resolved.md` 사이 항목 재분류 오류나 번호 충돌 없음. + +## 교훈 + +- **"순수 이동"이라고 선언해도 상호참조 검증은 별도로 필요** — + bind-system-plan.md 분할이 "내용은 안 바꿈"을 지켰어도, 그 내용이 + 참조하던 방향(위/아래, 같은 파일 안 절 이름)과 그 내용을 참조하던 + 외부 문서 양쪽 다 별도로 grep 전수 스윕이 필요했음. 이번엔 doc-check.py가 + 없던 시절 방식(agent가 직접 정독)으로 잡았지만, doc-check.py의 절 + 참조 검사가 왜 이 6곳을 못 잡았는지도 짚어둘 것 — "Ref 절"처럼 헤딩 + 전체가 아니라 **파일명이 함께 안 적힌** 참조는 정규식이 "그 파일 + 안에 있다"고 가정하고 검사하므로, 파일명 없는 방향 참조(아래/위) + 자체가 애초에 이 검사의 사각지대. 파일을 쪼갤 땐 grep으로 "아래/위 + OO 절"류 방향 참조부터 훑는 걸 체크리스트에 추가할 가치 있음. +- **배너가 파일 단위로 붙어있으면, 그 파일에서 분할된 새 파일에 + 배너 대상 내용이 섞여 있는지 별도 확인이 필요** — `ref-plan.md` + 사례처럼, 분할 시점에 배너 자체를 "옮길지 말지"를 판단할 근거 + 문서(`dispatch-redispatch-diff-plan.md`)가 그 새 파일을 아직 몰랐던 + 경우 특히 위험. +- **날짜/세션 번호는 한 번 잘못 적히면 복붙되며 퍼진다** — 이번 + 17곳도 대부분 서로를 참고하며 같은 오기를 반복한 것으로 보임(README + 색인이 base 파일 배너를 그대로 요약하는 식). 세션 번호는 가능하면 + 파일명(`session/YYYY-MM-DD-NN-slug.md`)에서 기계적으로 뽑아 쓰는 게 + 안전. + +## 반영 상태 + +base/README/question.md/archive/ROADMAP/CLAUDE.md/tools/ 전부 이 세션 +안에서 즉시 반영 완료 — 24개 파일 수정, `doc-check.py` ERROR 0 유지 +확인. 새로 연 설계 질문 없음(전부 기존 서술 정합성 문제), 0-Y/0-Z 상태 +불변. diff --git a/.claude/tools/doc-check.py b/.claude/tools/doc-check.py index a006246..d50cfb9 100644 --- a/.claude/tools/doc-check.py +++ b/.claude/tools/doc-check.py @@ -14,7 +14,7 @@ 검사 항목 1. [ERROR] 깨진 파일 참조 — 라이브 문서가 가리키는 .md/.luau가 실제로 없음 2. [ERROR] 깨진 절 참조 — `foo.md` "절 제목" 이 그 파일에 없음 - (문서를 쪼개거나 헤딩을 고칠 때 가장 잘 깨지는 것 — 여덟 번째 세션에 + (문서를 쪼개거나 헤딩을 고칠 때 가장 잘 깨지는 것 — 아홉 번째 세션에 bind-system-plan.md를 분할하며 20곳이 여기 걸렸음) 3. [ERROR] 색인 누락 — base/research/archive/reference 파일이 README에 없음 4. [WARN] 날짜 없는 시한부 주장 — "아직 안 돌려봄", "열린 질문 없음" 등 diff --git a/CLAUDE.md b/CLAUDE.md index 93daf7e..b0da1b3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -183,9 +183,11 @@ modifier/Ref의 컴포넌트 경계 통과 방식) 논의도 2026-08-04 세션 새 모델은 `research/dispatch-redispatch-diff-plan.md`에만 있음 — base만 읽고 구현하면 옛 모델로 짜게 됨. 0-Z가 정해지면 그 문서 6절의 파일별 반영 목록대로 **한 번에** 옮길 것 — 대상은 ⚠️ 배너를 달고 - 있는 **6개**(`bind-system-plan.md`/`tag-plan.md`/`slot-plan.md`/ + 있는 **7개**(`bind-system-plan.md`/`tag-plan.md`/`slot-plan.md`/ `attribute-plan.md` + **`architecture.md`/`ROADMAP.md`** — 뒤 둘은 - 2026-08-13 7차 감사에서 6절 목록에 빠져 있던 걸 발견해 추가), + 2026-08-13 7차 감사에서 6절 목록에 빠져 있던 걸 발견해 추가 + + **`ref-plan.md`** — 9차 세션 분할 때 "`Ref`의 retract" 절이 배너 + 없이 옮겨간 걸 이번 감사에서 발견해 추가), 반영 후 각 배너도 같이 제거. - 반대로 **`base/slot-plan.md`의 "언마운트/`dispose`/해제 순서"는 이미 확정 반영됨**(재디스패치 모델과 독립적인 결정이라 먼저 들어감). @@ -1020,3 +1022,17 @@ Slot 언마운트 전환 미반영 6곳. 뒤집힌 "폐기, 옮기지 않음 + p 누락, 날짜 없는 시한부 주장, 미반영 배너). **같은 세션에 실효 증명**: 분할 중 "이중 바인딩 금지" 절 참조 4곳을 잘못 옮긴 걸 스크립트가 잡아내 되돌림. CLAUDE.md "작업 방식"에 중대 변경 핸드오버 체크리스트 6단계도 명문화. + +**2026-08-13 열 번째 세션 — 병렬 에이전트 코퍼스 감사, 실제 부정확성 7건** +(`session/2026-08-13-10-corpus-audit-parallel-agents.md`) +사용자 요청으로 세션 기록 대조 전수 감사(미확정 항목은 문제로 안 셈, +`doc-check.py` 선실행 후 기계가 못 잡는 것만 6개 병렬 Explore 에이전트로 +분담). 아홉 번째 세션의 구조 변경(폴더 재편/분할/트림) 반영 누락이 +대부분: luau-test 재편 후 깨진 flat 경로 참조 9곳, `bind-system-plan.md` +분할 후 자기참조/외부참조 깨짐 8곳, **`ref-plan.md`에 0-Z 배너가 안 +옮겨와 옛 재디스패치 모델을 무배너로 서술 중이던 것**(반영 대상 +6개→7개로 정정), "8차 세션"으로 잘못 표기된 9차 세션 작업 17곳(git +커밋 타임스탬프로 교차검증), `question.md` 트림 중 빠진 열린 질문 1건, +트림 후 깨진 참조 2곳, `ROADMAP.md` M0 섹션의 0-Y/0-Z 게이트 표시 누락. +발견 즉시 24개 파일에 직접 반영, `doc-check.py` ERROR 0 유지 확인. 새로 +연 설계 질문 없음. diff --git a/ROADMAP.md b/ROADMAP.md index 3839f14..ce7f8c2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -8,6 +8,13 @@ quad-v2 구현 단계 실행 계획. 설계 근거/아키텍처 자체는 여기 확정될 때마다 각 마일스톤 체크박스가 계속 갱신돼왔음 — 그래도 아직 M0 자체는 시작 안 함.** 다음 세션은 바로 M0부터. +> **⚠️ M0 착수 자체가 두 결정으로 막혀 있음 — `.claude/question.md` 0-Y +> (`:Compute(fn)` lazy 핸들 계약과 Luau 타입 추론 충돌)/0-Z(Attribute +> 이름 소유권). 둘 다 "M0 착수 전에 정할 것"으로 명시돼 있음 — +> `HUMAN_TODO.md` 4번, `CLAUDE.md` "지금 할 일" 0번 항목 참고. 아래 +> M0 체크리스트 자체는 이 두 결정과 무관하게 유효하지만, 순서상 이 +> 둘이 먼저.** + ## M0 — 스켈레톤 + 기술검증 (스파이크, "진짜" 마일스톤 아님) 최종 소스 트리를 그대로 만들기 전에, 지금까지 **추론만으로 확정하고 실제