전체 .claude/ 코퍼스를 doc-check.py + 6개 병렬 서브에이전트로 감사해
stale 세션 번호, 잘못된 인용, 자기모순 배너 등 15개 파일의 실제 사실
오류를 발견·수정. 이어서 question.md 0-W(같은 Ref 객체가 두 자리에
동시에 놓이는 문제)를 선택지 (a)로 확정 — RefLeafHandler가 새 Relate
없이 bindLifetime/unbindLifetime을 재사용해 이중 배치를 방지.
이 과정에서 bindLifetime의 이중 바인딩 가드(bound 문맥)와 State emit
전파 게이팅(execute 문맥)이 서로 다른 질문인데 canExecute 하나로
뭉쳐 있던 걸 발견 — canBound를 별도 진입점으로 재도입(판정 로직은
비공개 헬퍼 하나를 공유, 코드 중복 없음). Tag/Attribute의 미지원
백엔드 처리 모델도 여러 라운드 논의 끝에 확정: TagHandler 등은
quad-base가 스스로 등록하고, addTag/removeTag/setAttribute만 백엔드
팩토리가 채우는 타입 계약 — 안 채운 슬롯은 명시적으로 에러내는 스텁.
/code-review high가 추가로 3건(gcconn-trick-verification.md 배너,
README.md 인덱스 4곳, luau-test/STATUS.md의 재작성 지침) 발견해 정정.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXqrGrh83Sh61C3tUCMdmY
세 건을 한 커밋에 처리:
1. ui-shorthand-plan.md — Tween 지원 확정. 숏핸드가 자식 프로퍼티를 직접
대입하지 않고 Dispatch.process(child, prop, ..., 1)로 위임하면 Tween이
공짜로 따라옴(해석 코드는 PropertyHandler 하나에만 남음). "process 중
inst를 바꾸는 건 키를 바꾸는 것과 같은 층위라 UB 아님"을
dispatch-core-plan.md에 일반 규칙으로 명문화. wrap을 Tween<T>.Value에만
적용되도록 들어올리는 헬퍼가 새로 필요한 유일한 부품. ROADMAP M10에
통째로 빠져 있던 UI 숏핸드 항목도 보강.
2. existing-instance-bind — 기각, research/ → archive/. 사유: Length/Offset
등 quad가 만든 트리를 전제한 부기를 바깥에서 밀고 당기는 버그 표면이
치명적으로 넓어짐. "열려 있음"을 전제로 쓰인 본문 7곳도 같이 정정
(architecture.md의 "아직 미정" 절은 유일 항목이었어서 절 자체를 갱신).
3. bind-system-plan.md 3단계 분할 + store-semantics.md 흡수(순수 이동):
- base/store-plan.md 신설 — Store = 이름 붙은 Source 모음
- base/source-state-plan.md 신설 — 반응형 코어(Source⊇State, 전파 모델,
:With/:Compute/:Apply/previous, Observer, 구독·생명주기 게이트)
- bind-system-plan.md 1238→203줄(인스턴스 생성·이벤트 네이밍 + 색인)
- store-semantics.md 삭제
참조 40여 곳 스윕. doc-check.py ERROR 0, WARN 101→84.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
canExecute(inst,value) 2-인자 시그니처를 폐기하고 canExecute(value)로 정정.
2-인자는 증상이었고 원인은 2026-08-08 세션이 .Subscribed에 "leaf 바인딩
생존"이라는 두 번째 의미를 겹쳐 얹은 것 — .Subscribed는 전역 :Subscribe()
전용 필드이고 bindLifetime과 무관함. bindLifetime이 바인딩 시점에 inst의
gcconn 참조를 value 쪽 Relate로 복사해두면 생존을 value 하나로 물을 수 있음.
- canBound(handle) 폐기 → canExecute(value)로 통합(이중 바인딩 게이트 겸함)
- unbindLifetime도 1-인자로 축소 — 호출부가 _mountedInst를 되짚을 필요 없어져
"홀더가 갈아치워지면 해제가 빗나가는" 잠재 버그 클래스 소멸(slot-plan 5곳)
- gcconn/gchold를 lazy 생성에서 Instance 생성 시점으로 전환, 클로저가 inst까지
캡처 — Instance userdata 포인터 동일성은 inst-키 Relate 전체의 전제였음
(relate-plan에 "전제" 절 + "안전히 유지되면 항상 SetWeak" 일반 규칙 신설)
- canExecute의 실제 호출부를 State 전파 루프로 명시(구독자 weak + 발화마다
게이팅) — 이게 코드로 한 번도 안 적힌 게 오류가 여섯 세션 살아남은 이유
역전 원문/오염 경로/교훈은 archive/canexecute-inst-arg-reversed.md.
luau-test/10은 폐기된 모델을 검증 중이라 rewrite-required/로 이동.
부수: 3~4차 세션이 남겨둔 CLAUDE.md 세션 히스토리 항목과 4차 세션 로그
파일도 미커밋 상태여서 같이 실림. doc-check ERROR 1건(CLAUDE.md:1156 →
session/2026-08-14-03-lifecycle-hooks-plan.md)은 그 파일이 디스크 어디에도
없어서 남음 — 3차 세션 쪽에서 채워야 함.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
44개 스파이크로 question.md 0-Y를 재실측한 결과, 여섯 번째 세션의
"콜백이 raw 값을 받으면 완전 클린" 판정이 틀렸음이 드러남 — 그건 진단
0건만 확인한 것이었고, luau-analyze --annotate로 열어보니 반환 타입이
Unifiable<Error>로 조용히 새고 있었음(틀린 대입도 안 잡힘).
진짜 원인은 콜백 계약이 아니라 Compute가 State<U>(자기 이름을 다른 타입
인자로 감싼 타입)를 반환한다는 것 자체 — RFC relax-recursive-type-restriction이
Promise<T>.andThen으로 예시 든 바로 그 패턴. 사용자 확정: quad가 타입을
비틀 일이 아니라 상위 Luau의 현 한계이고, RFC/이슈 수혜를 받을 때 해결될
일이라 당장 할 수 있는 바 없음.
- base/typing-limits.md 신설 — 흩어져 있던 타입 한계 5건 통합, 대전제
"Luau 한계를 우회하려 타입/API를 비틀지 않는다", 새 API 설계 체크리스트
- audit/type-recursion-issue/ 신설 — REPORT.md + spikes 44개(audit 폴더에
스크립트를 같이 둔 첫 예외, 판정 재현에 개별 실행이 필요해서)
- 0-Y 해소 전파: question.md(최우선 2건→1건) / archive / base 5개 /
research 2개 / 인덱스 4개 / luau-test(08을 done/으로, review-required 비움)
- audit/luau-test-first-run-2026-08-13.md: 판정이 뒤집힌 당사자라 배너뿐
아니라 본문 표·문단·결론까지 전수 수정
- HUMAN_TODO 6번 신설: luau-lsp 기본이 옛 솔버라 CLI와 진단이 다름
교훈: luau-analyze 진단 0건은 타입 해소를 뜻하지 않음 — 타입 스파이크는
--annotate로 실제 추론 타입을 확인하고 음성 대조군을 같이 둘 것.
doc-check.py ERROR 0 유지(WARN 59건, 변경 전과 동일).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KckSawrsJSJmDBcSojJPxZ
사용자 질문: "이런 깊은 감사를 재발 방지하는 방법이 있나. 중대 변경에 대해
그 사항을 아는 세션이 stale 없게 핸드오버해야 하는데 그런 게 없어서 쌓임."
진단: 7·8차 감사에서 찾은 55건이 거의 전부 **변경한 세션이 그 자리에서
안 한 일**이었고, 그중 상당수는 grep으로 잡히는 기계적 종류였음. 그래서
규율 문서만 쓰지 않고 **실제 검사기**를 만듦.
.claude/tools/doc-check.py — 라이브 문서 61개 대상:
[ERROR] 깨진 파일 참조(우리 문서 명명 관례에 맞는 것만; 아직 없는 소스
트리 .luau와 외부 문서명은 오탐이라 WARN으로 분리)
[WARN] 절 참조 불일치 — `foo.md` "절 제목"이 그 파일에 없음
[ERROR] README 색인 누락
[WARN] 날짜 없는 시한부 주장("아직 안 돌려봄"/"열린 질문 없음" 등)
[WARN] 미반영 ⚠️ 배너 단 파일 vs 반영 목록 일치
현재 ERROR 0 / WARN 58(대부분 절 제목 의역 인용, 판단 대상).
**검사기가 즉시 실효를 증명함** — 같은 세션에 bind-system-plan.md를 쪼개며
"이중 바인딩 금지" 절 참조 4곳을 ref-plan.md로 잘못 옮긴 걸(실제로는
Observer/Effect 절이라 이동 대상이 아니었음) 이 스크립트가 잡아내 되돌림.
CLAUDE.md "작업 방식"에 중대 변경 핸드오버 체크리스트 6단계 명문화:
doc-check 실행 → 부정당한 *본문 문장* 전수 grep(배너만 달고 본문 방치가
최다 실패) → 뒤집힌 원문은 archive로 → 개수/상태는 소스 하나만 → 시한부
주장엔 날짜 → 인덱스 레이어 3개 동시 갱신.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa
사용자 지적: "question.md 또한, 사람이 봐야하는 문서인데 해결된 것이 많아서
필터해서 필요한 부분만 읽어보기 힘듦."
- archive/question-resolved.md 신설 — 분리 직전 전문을 그대로 보존
(결정 내용은 하나도 안 바뀜, 읽는 자리만 이동)
- question.md는 **사용자가 답해야 할 것만** 남김:
⭐최우선 2건(0-Y/0-Z, M0를 막고 있고 사용자가 직접 판단하겠다고 이관)
→ 결정 대기 2건(0-A 컨텍스트/0-B) → 용어 미확정 6건 → 낮은 우선순위 5건
- 통째로 해소된 옛 "0번(추가 프리미티브)"/"2번(구현 착수 직전 감사)"과
맨 아래 확정 요약표(README base 표와 중복)는 archive로
- 재발 방지: "항목을 해소하면 여기서 지우고 archive로 옮길 것"을 문서 상단에
명시
인바운드 참조 보호: 절 번호 1/3은 헤딩에 유지(참조 ~10곳), 없어진 0번/2번을
가리키던 CLAUDE.md·effect-plan.md 2곳은 실제 원본(pre-implementation-audit.md,
archive)으로 재지정. 없어진 번호에 대한 안내를 question.md 하단에 남김.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa
사용자 지적: 2989줄이라 검토가 불가능하고 한 곳의 실수가 미치는 범위가
너무 큼. 특히 "이벤트 연결은 다른 base 문서가 되어야 할 듯"이라고 직접 지목.
**1단계 (완료, 2989 → 2263줄)** — 인바운드 참조가 적고 자기 완결적인 것부터:
- base/ref-plan.md (509줄) — Ref/PreRef 전체
- base/event-plan.md (103줄) — 이벤트 self 미전달, false로 disconnect
- base/brand-plan.md (137줄) — Brand 런타임 nominal 판별
내용은 **순수 이동, 결정은 하나도 안 바뀜**. 원위치엔 포인터 스텁을 남기고,
이 절들을 지목하던 인바운드 참조 20곳을 새 파일로 갱신(잔존 0 확인).
README 색인에 3개 행 추가.
**2단계는 의도적으로 미룸** — 남은 디스패치 코어(~1000줄)/반응형 코어
(~950줄)는 0-Z 확정 시 어차피 전면 재작성 대상이라, 재작성하는 그 패스에서
파일을 가르는 게 총 변경량과 실수 위험이 모두 작음(인바운드 ~37곳을 한 번만
고침). dispatch-redispatch-diff-plan.md 6절에 그 지시를 명시했고,
bind-system-plan.md 헤더에도 분할 현황표를 남김.
부수: 이벤트 *네이밍* 관례는 인스턴스 생성과 한 절에 섞여 있어 쪼개면
양쪽이 반토막 나므로 원위치 유지(event-plan.md 헤더에 명시).
luau-test/README의 "에이전트가 직접 실행 못 함" stale 서술도 정정.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa
4차 감사(반복되던 두 패턴을 grep으로 전수 스윕)에서 발견한 것 정리:
- architecture.md: 모든 세션이 "먼저 읽으라"는 진입점 파일인데 소스
트리의 chains/retractFrom 서술에 0-Z 포인터가 전혀 없었음 — 최상단
배너 추가(가장 중요한 gap, question.md 0-A가 경고하던 바로 그 함정)
- tween-plan.md/operator-sugar-plan.md/effect-plan.md: self-lazy-핸들
계약(0-Y)에 의존하는 실행 예시/메커니즘 서술에 포인터가 전혀 없었음
— 3곳 추가
- CLAUDE.md: "지금 할 일" 0번(0-Y/0-Z가 최우선 게이트)과 1번("M0 착수
전 남은 유일한 게이트는 luau-test뿐")이 정면 모순 — 0-Y/0-Z 발견
이전에 쓰인 문장이 안 갱신된 stale, 정정
- slot-plan.md 3곳/additional-primitives-plan.md/
dispatch-redispatch-diff-plan.md: reconcile/Slot:List/재귀 재emit
가드 설명이 여전히 "파괴"를 현재형으로 서술 — 2026-08-13 여섯 번째
세션의 언마운트 전환 반영해 정정(지난 세 라운드가 놓친 인스턴스들,
전수 grep으로 확인)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Compute 노드는 결과를 담을 새 State 노드를 어차피 만들어야 하므로
추가 의존성 구독을 그 노드에 얹는 건 공짜 sugar로 확정. Effect/Observer는
자기 자신이 State 노드가 아니라 다중 의존성 병합에 새 노드가 실제로
필요해서 동일 sugar를 의도적으로 제외, :With(...)를 코드에 그대로
노출하도록 유지.
.claude/base/ 전체를 배치별로 리스팅해 사용자 확인을 받는 중간검토
세션 — Ref 콜백/대기자 배열의 None 소진이 무한 성장 버그였던 것을
nil로 되돌리고, isRef/isPreRef를 isState/isSource와 같은 상위-하위
합성으로 재정정, Slot CRUD 식별 기준을 element 레퍼런스에서 인덱스
기준으로 전환(ExtractAll/Get/IndexOf 신설), 컴포넌트 리프 바인딩에서
Source 직접 사용이 정상 경로라는 정정, Dispatch 직접 호출 UB 명시,
Tag retract 전제 명시, Attribute 타입 파라미터화 확정, EffectHandle
내부 Observer cascade/Subscribe GC 예외 경고 등을 반영. CLAUDE.md에
세션 요약, stale해진 research/documentation-content-map.md 일부 항목도
동기화.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYfAz3BsaTrmMM8hnzn6mj
여러 Slot이 형제로 섞일 때 LayoutOrder 순서 보장 메커니즘을
Dispatch.setLength/setOffsetSource 누적합+리액티브 바인딩으로 확정하고
base/slot-plan.md의 관련 열린 질문을 해소. 이 과정에서 발견된 라이프사이클
게이트의 실제 모양(진짜 독립 경로는 :Subscribe()/bindLifetime 둘뿐이고
leaf 부착은 bindLifetime 호출 그 자체라는 점, canBound의 내부 플래그가
canExecute가 보는 .Subscribed와 동일 필드라는 점)을 반영해 이중 바인딩
금지 규칙과 기존 StoreBind 예제를 정정.
이미 해소된 결정이 미해결로 표시되거나 문서 간 모순되던 항목 7개 파일
수정, 뒤집힌/무효화된 설계 서술이 정정 표시만 붙은 채 본문에 남아있던
곳을 기존 archive 컨벤션대로 이전(quad2-try 리서치, Observer cleanup
계약, keyed collection state method, debug channel ReplicatedStorage).
CLAUDE.md에 세션 로그 반영.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYfAz3BsaTrmMM8hnzn6mj
- :Compute(fn)에도 Observer/Effect와 동일한 커링 권장 노트 추가
- state:Apply(factory) 확정 — ":With"/":Compute" 자동 등록 조합기였던
백로그안 기각, Modifier:Apply와 동일한 순수 체이닝 설탕으로 재정의
- EffectHandle:Subscribe()/:Unsubscribe() 신설 — leaf 없이 쓰는 독립
Effect 지원, :Unsubscribe()는 마지막 cleanup을 1회 트리거해야 함
- Observer/Effect 이중 바인딩(leaf 부착 + 수동 Subscribe) 금지 확정,
Bound 플래그로 즉시 error
- ROADMAP.md M3/question.md에 반영, effect-plan.md 오기 정정
- state:Observer(fn)는 등록 즉시 1회 실행되는 것으로 확정 — 초기화 순서
디버깅 문제를 피하고, store-bind 프로퍼티 핸들러가 "초기값 적용"과
"이후 변경 반영"을 같은 코드 경로로 통일할 수 있게 됨.
- Effect(fn, state?)로 확정 — state 생략 시 기존 스펙(설치 1회 + 확정
정리) 유지, state 지정 시 내부적으로 state:Observer(...)를 조합해
재실행 + 자동 cleanup 배선(React useEffect와 동형). 다수 의존성은
:With(...)로 묶어서 넘김. 여전히 자유 함수(메소드 아님) — leaf 생명주기
바인딩을 state가 소유하지 않아서.
- fn 커링 스타일을 Effect/Observer 공통 모듈화 관용구로 권장, state:Apply
커링 조합기 아이디어는 백로그로만 기록.
- question.md 0번의 Effect/Observer 열린 질문 해소.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
State 작업 시 Effect까지 같이 볼 필요는 없다는 지적(둘은 무관한 프리미티브)과
기존 프리미티브당 1파일 컨벤션(modifier-plan.md, slot-plan.md류)에 맞춰
base/blocker-plan.md, base/effect-plan.md로 재분리. Blocker는 store-semantics.md
교차 참조를 유지, Effect는 독립 파일로 완전히 분리. 전체 상호참조 경로 갱신.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>