4라운드 종결 때 "안 만든다"고 했던 5라운드를 사용자 요청으로 신설(205문항).
범위를 셋으로 좁힘 — (1) 4라운드에 문항이 아예 없던 영역(project-setup /
quad-types, 그리고 문서가 아니라 실제 커밋된 M1 코드), (2) 그 이후 확정된 것
(Detach/KeyGone/Owned/attachSlot 분해), (3) 큰 문서의 심화. 회신을 4차에 걸쳐
받아 전량 반영했고, 커밋 전 감사를 각도를 바꿔 2라운드 돌렸다.
주요 확정/역전:
- slot._detached lazy화, KeyGone엔 새 값 반환도 error,
Owned=false에서 Detach는 _detached에 안 들어감(rawUnmount)
- Slot:Replace 신설 + rawReplace/rawAdd 의사코드 신설(문서에 정의가 없었음)
- raw* 인자를 index로 전부 통일 — 오래 열려 있던 캐비엇 종결.
래핑은 raw* 바깥에서만(공개 표면 + settle), raw*는 물리 요소만 다룸
- 물리 조작을 주입 op로(mountInst/unmountInst/disposeInst, 이름 가칭) —
base는 Parent를 모른다는 지적. mountInst는 0-based 절대 offset을 받음
- Dispatch.setLength에 anchor(생략 시 ownerKey) — 부기 키와 생명주기 앵커
분리, 4라운드 D-56 역전(archive로)
- Dispatch.getOffsetAt 신설(pull) + 접두합 캐시(offsetDirtyFrom),
setOffsetSource(None)은 얼리 리턴, None의 뜻을 "발행 채널 없음"으로 정정
- recompute가 owner 베이스에서 시작(중첩 offset이 부모 베이스를 못 받던
결함), _baseObserver로 깊은 전파, Offset Source identity 재사용(포탈),
bk.N or 0(빈 Slot 크래시)
- Effect(fn, ...deps) 확정 — Ref도 의존성(옛 "trailing args sugar 안 만듦"
역전), Tween:Mapped, groupClaimKeys 키 = (inst, groupValue) → k
- 게이팅 먼저(M2로 앞당김) — 다만 대상이 Blocker가 아니라 공용 Gate 노드로
바뀌었고, 설계는 사용자 지시로 다음 세션(M2 착수를 막는 유일한 항목)
새 research 둘: gate-primitive.md(다음 세션이 이어받을 재료),
state-epoch-validation.md(전파 중 Get이 섞인 값을 캐시하는 glitch — 정확성
결정이라 M3 전 결론 필요).
감사가 잡은 것 중 큰 것: 확정한 Owned가 Slot:List 시그니처에 배선이 안 돼
코드에 도달 못 하던 것, effect-plan.md의 역전 배너 없는 자기모순,
그리고 손대지 않은 문서(ROADMAP 백로그·debounce-throttle-plan)가 "Gate는
M3에서"로 남아 있던 사각지대.
doc-check.py ERROR 0. 상세는 qa-request/pre-implementation-qa-round5-followup.md
(A~K절, 마지막이 최신).
Co-authored-by: qwreey <me@qwreey.moe>
커밋 9b7f847(Detach/KeyGone/Owned/attachSlot 분해) 반영 후 각도를 바꿔가며
quad-doc-auditor를 6라운드 돌린 결과. 라운드별 확실 발견 4/6/2/3/3/0으로
6라운드에서 새 발견 0건 — 수렴 확인 후 종료. 경위 전량은
qa-request/pre-implementation-qa-round4-followup.md의 I절이 소스.
트레이싱 라운드(4~5)가 잡은 실제 크래시 — 셋 다 Detach가 신설한 경로가
기존 불변식과 부딪히는데 그쪽이 안 고쳐진 것:
- I-1 (치명): rawDetach가 소유권을 유지하는데 재마운트는 rawAdd →
claimOwner를 거치고, claimOwner는 같은 owner의 재클레임도 무조건 error다
(2026-08-13 감사가 Slot{a,a}를 막으려고 넣은 것). 문서가 권장하는
"prev를 그대로 반환하면 재마운트" 패턴이 그대로 죽었음. fromDetached
플래그로 그 경로만 좁게 예외 처리.
- I-2: 재마운트된 자식 Slot이 activateList를 두 번 실행해 구독이 이중으로
생기고 mounted/keyIndex 클로저 상태가 통째로 새로 만들어짐 → 멱등 가드.
가드만으로는 :List 구독이 옛 physicalTarget에 앵커된 채 남아 포탈
재마운트 후 조용히 멈추므로, _listObserver 핸들 보관 + 재앵커까지 처리.
- I-3: _detachCleanup이 releaseOwner를 안 불러 Owned=false 요소가 죽은
Slot을 owner로 달고 남음 → 두 분기 공통으로 호출.
- I-6: 위 수정의 회귀 트레이싱 — destroySlotTree에 _listObserver 해제 누락,
claimOwner의 옛 논증 두 문단이 fromDetached와 정면 모순, 소유권 예시
코드가 C-4와 모순.
- I-7: 사용자가 별도 상의해 가져온 두 건 — _detachCleanup 설치를
mountSlotTree → activateList로 이관(:List 없는 Slot마다 no-op Effect를
트리 크기만큼 심고 있었음), activateList의 inst → physicalTarget 리네이밍.
이관 근거가 멱등 가드 이전 동작을 전제하고 있어 가드 분기의 재앵커까지
같이 반영. 이로써 _listObserver/_detachCleanup이 같은 범주로 통일됨.
I-4(materializeSlotTree 중 예외 시 Blocker 잔류)는 사용자 판단으로 pcall
없이 문서화만 — 아직 밟은 적 없는 경로이고 옛 단일 attachSlot에도 있었을
구조적 갭.
문서 정합성 라운드(1~3)에서 나온 것: slot-plan의 "값 교체는 비파괴" 잔존,
분해 완료 후에도 남아 있던 "논의 대기 중" 배너, attachSlot의 flush 루프를
가리키던 문장 5곳, README 색인 행이 2026-08-19에서 멈춰 있던 것,
qa-round4 문항지/followup의 "회신 대기" 상태줄, todos의 용어 목록 이중 소스,
dispatch-core의 raw* 일반 계약에 rawDetach 누락.
luau-test: 스파이크 01이 "재작성 필요" 마커를 단 채 done/에 남아 있어
STATUS.md 자신의 "폴더가 곧 상태" 규칙을 어기고 있었음 → rewrite-required/로
이동하고 개수 정정. "만들어야 할 스파이크" 절 신설(아직 파일조차 없는 실측
항목이 어느 폴더로도 표현되지 않아 구조적으로 잊히던 자리).
doc-check.py ERROR 0.
Co-authored-by: qwreey <me@qwreey.moe>
사용자 회신을 (a) 바로 반영 (b) 설명 부족으로 재질문 (c) 사용자 판단 필요
(d) 조사로 닫힘으로 갈라 followup 문서로 정리했다. A절(반영 완료)은 앞
커밋에서 이미 base/에 들어갔고, 이 커밋은 그 기록과 남은 항목이다.
가장 큰 두 미결:
- C-1 KeyGone 센티널 — 키 소멸 시 updateFn을 한 번 더 불러 처분을 묻는
사용자 제안. SL-45의 "파괴도 반환도 안 되고 참조만 끊기는" 상태를 정확히
메우지만, 반환값 의미/userdata 수명/소멸 루프 순회 대상/index 인자 넷이
안 정해지면 구현이 못 나감.
- C-2 "밀려난 prev는 dispose" vs state<Frame> 의미론 충돌 — 회신의 SL-43과
SL-51이 서로 반대를 가리켰는데, 따져보니 둘 다 맞고 갈리는 축이 "누가 그
요소를 만들었는가"였다. :List의 updateFn이 만든 건 reconcile이 지워야 하고
(만든 쪽이 자기 손으로 못 지움), Slot:Add(state) sugar로 온 건 사용자
소유라 죽이면 안 됨. 지금 설계엔 후자를 표현할 방법이 없어 선택지 셋을
올리고 per-installation 소유권 옵션을 추천.
부수로 사용자가 문서에 아예 없던 갭 둘을 찾아냄 — Attribute 생성자 자신의
이름 겹침 정책(앞 커밋에서 "뒤가 이김"으로 명시)과, flatten이 소진한
Modifier 자리 처리(C-3, ProcessedModifier 센티널 vs flatten 압축).
Co-authored-by: qwreey <me@qwreey.moe>
사용자 요청("모든 확정 부분에 있어서 예가 되어야하는 질문들을 계속")으로
base/ 26개 문서를 서브에이전트 없이 한 맥락에서 의존성 순서로 읽으며,
확정으로 적힌 주장을 전부 "예가 나와야 정상인 단정문"으로 뽑았다.
결론만이 아니라 근거까지 문항에 넣었는데, 코퍼스에 "결론은 그대로인데
근거가 뒤집힌" 사례가 여럿 있어서 결론만 물으면 그런 걸 못 잡기 때문.
⚠️로 열려 있다고 적힌 항목은 "확정이 맞나"가 아니라 "아직 열려 있다는
인식이 맞나"를 묻는 문항으로 따로 표시했다.
정정은 하나도 안 했다 — 사용자 지시대로 기록만 하고 회신 대기 상태.
Co-authored-by: qwreey <me@qwreey.moe>
session-summary.md에 오늘 세션 04~08 색인 항목이 통째로 빠져 있던 걸 발견해
신설. 더 크게는 CLAUDE.md/project-context.md/ROADMAP.md가 "구현 아직 시작
전"이라는 낡은 전제를 깔고 있었는데, 실측해보니 M0 스파이크 4개와 M1
스캐폴딩 대부분이 이미 완료돼 있어 전부 정정(ROADMAP 체크박스 갱신 포함,
wally.toml→pesde.toml 표기도 같이 정정). quad-roblox-types 백로그는
todos.md/ROADMAP.md M5에 짧은 포인터 보강.
Co-authored-by: qwreey <me@qwreey.moe>
quad-spring-roblox류 독립 게시 플러그인엔 정확 버전 일치가 과하다는 지적에
따라, 버전 패턴 매칭(글롭 "*"/캐럿 "N^")을 quad에 종속되지 않은 범용
워크스페이스 멤버 type-version-check로 분리하고 quad-types의 CheckedQuad를
CheckedQuad<T, Pattern>으로 확장. 새 Luau 함정 2건(type function의 outer
local 참조 불가, cross-package엔 export type function + 이중 꺾쇠 제네릭
인스턴스화 필요) 발견·문서화. 독립 저장소 분리는 HUMAN_TODO로 위임.
Co-authored-by: qwreey <me@qwreey.moe>
quad-roblox가 quad-base를 런타임 주입(QuadRoblox(Quad): QuadRoblox)으로만
받으면 pesde 의존 선언이 필요 없어 보이지만, 타입 참조용 require도
런타임에 실제 실행됨을 실측 확인 — dev-dependency로 두면 게시 후
소비자 환경에서 크래시함. 해법으로 구현 없는 타입 계약 전용 워크스페이스
패키지 quad-types 신설, quad-base/quad-roblox 모두 이것만 의존하도록
전환.
AddPlugin<Self,P>(self:Self,fn:(Self)->P):Self&P — 제네릭 self로 둬야
체이닝이 누적됨을 실측 확인(고정하면 이전 확장을 잃음), quad-base에
실제 mutate 기반 구현 반영.
CheckedQuad<T> 버전 체크는 배선하며 세 번 깨짐 — error() 대신
print+types.never, 함수 본문 로컬 별칭 대신 리턴 타입 표현식에 직접,
그리고 가장 중요하게 type function을 한 번이라도 거친 값(패스스루
포함)은 이후 AddPlugin 같은 제네릭 self 체이닝이 조용히 깨진다는 새
Luau 함정 발견 — typing-limits.md §6으로 승격. 최종 설계(검증 결과를
원본과 격리된 가상 필드로)만 AddPlugin과 완전히 호환.
부수로 quad-base 자신도 quad-types workspace 의존 때문에 CLI symlink
함정(지난 세션 발견)에 걸림 — 로컬 테스트용 symlink 실체화로 임시 우회.
Co-authored-by: qwreey <me@qwreey.moe>
New()의 멱등 Init 가드를 파일마다 Relate+센티널을 두던 방식에서
module:RunInit(initFn) 하나로 통합 — 함수 자체를 릴레이션 키로 써서
"이 함수, 이 모듈에 실행했는가"를 (module, initFn) -> boolean? 하나로
표현(사용자 제안). Debug/init.luau는 가드 없이 순수 뮤테이션만 하도록
단순화. smoke.init.luau로 재호출 무시/인스턴스별 독립/함수별 독립 3개
시나리오 검증(luau/luau-analyze/selene 클린).
darklua process를 실제로 돌려 @self/@game은 안 건드리고 커스텀 .luaurc
alias(@pkg)만 script.Parent류로 치환한다는 걸 확인 — project-setup-plan.md의
darklua 기각 근거를 "지금은 커스텀 alias를 안 쓰니 불필요, 나중에 도입하면
그때 필요해짐"으로 정밀화.
⚠️ 미결: RunInit을 backend 설치 진입점(QuadRoblox(Quad))에도 재사용할지
— 함수 identity 추적으로는 "다른 팩토리 재호출은 에러" 계약을 못 만족.
module-lifecycle-plan.md에 반영, M2/M5 착수 전 확인 필요.
Co-authored-by: qwreey <me@qwreey.moe>
Word30210/roblox-project-example(initreq/에 클론)를 참고해 두 가지 채택:
(1) rokit.toml → mise.toml — mise install이 pesde/rojo/luau-lsp/selene을
GitHub attestation+SLSA provenance 검증까지 거쳐 설치하는 걸 이 샌드박스
에서 직접 확인(rokit은 끝내 검증 불가), 이 환경 자체가 이미 mise로
luau를 관리 중이라 더 자연스러움. (2) selene 린터 — 참고 레포의
selene.toml을 패키지별로 채택, 단 CWD 상대 config 탐색 함정을 발견
(루트 단일 설정으로 두면 다른 디렉토리에서 실행 시 조용히 Lua 5.1
std로 폴백해 Luau 타입 문법 전체가 파싱 에러로 잘못 보임) — 참고 레포
그대로 패키지별 selene.toml + 패키지 안에서 실행하는 걸로 확정. 도입
즉시 smoke.mock.luau의 assert 메시지 누락 3건을 잡아 수정.
darklua의 convert_require 변환은 검토 후 기각 — 사용자 판단: Roblox
엔진 자체도 이미 같은 require-by-string 의미론(@self/@game)을 지원해
변환 계층이 불필요.
Co-authored-by: qwreey <me@qwreey.moe>
luau-lsp 1.69.0을 pesde/rojo와 같은 방식으로 직접 설치해
--flag:LuauSolverV2=true/false로 spike 08을 대조(옛 솔버 에러 3건 vs
새 솔버 1건) — HUMAN_TODO 6번이 사람에게 넘겨뒀던 "실제 에디터에서
확인"을 CLI 분석 모드로 대신 검증. quad/.vscode/settings.json에
enableNewSolver:true 반영, rokit.toml에 luau-lsp 핀 추가. 부수로
typing-limits.md 1번의 핵심 주장(0 진단으로 조용히 새는 것)이 Luau
0.734에서도 그대로 재현됨을 별도로 재확인(정정 불필요).
Co-authored-by: qwreey <me@qwreey.moe>
pesde처럼 rojo도 /code/.local/bin에 직접 설치(7.7.0, rokit.toml 핀과
일치). rojo sourcemap/build가 quad-roblox/roblox_packages의 심볼릭
링크(quad-base로의 workspace 의존성)를 실제 파일까지 투명하게 따라감을
확인 — 이전 세션이 찾은 "luau CLI가 symlink를 안 따라간다"는 문제는
standalone CLI 전용이고 Rojo/Studio 배포 경로엔 영향 없음이 확정됨.
덤으로 luau-lsp가 rojo를 감지해 자동으로 sourcemap을 watch하는 것도
확인 — wally가 안고 있던 에디터 타입 링킹 단절 문제가 이 구성에서
재현 안 됨.
Co-authored-by: qwreey <me@qwreey.moe>
의미론은 (A) emit-gate로 확정(value-hold 안은 laziness와 상충해 철회),
제어 핸들은 개별 Ref 아웃파라미터 + 전체 팩토리 브로드캐스트(weak
레지스트리)로 수렴, Time/MaxTime은 number|State<number> 허용(스케줄
시점에만 폴링), 이름은 Debounce/Throttle 유지로 확정. 전부 닫히면서
quad-base에 새 코어 메커니즘을 안 더하는 순수 슈가로 재평가됨(Blocker의
gated state + Ref + 주입 op 2개 위에 전부 얹힘) — 우선순위 서술도 갱신.
Co-authored-by: qwreey <me@qwreey.moe>
`:List` reconcile의 비파괴 반환 sentinel 이름을 사용자와 후보 검토(Bench/
Stash/Hold/Detach 등) 끝에 Detach로 확정 — 이미 있는 Extract(호출자 직접
호출, 명령형 소유권 이관)와 동사가 겹쳐도 "화면에서만 떼고 관리 주체는
reconcile"이라는 의미가 자연스럽게 구분됨. 공개 표면 위치도 같이 확정 —
Slot이 함수라 Slot.Detach로 못 붙이므로, None sentinel의 선례(공개 표면은
패키지 최상위 export, 정의는 관련 로직 옆)를 그대로 따름.
base/slot-plan.md 전량 반영(가칭 표기 제거, 이름/배치 두 결정 불릿 신설),
question.md/todos.md/ROADMAP.md/archive/question-resolved.md/README.md
인덱스 갱신, session/2026-08-19-02-*.md로 논의 원문 남김. 키 소멸 시 홀드
중이던 요소 처분 문제는 이름과 무관한 별개 항목으로 여전히 미결.
quad-doc-auditor 1라운드(무발견) 거쳐 doc-check.py ERROR 0 확인 후 커밋.
Co-authored-by: qwreey <me@qwreey.moe>
InitRoblox(Module) backend 주입 패턴을 quad-base 자기 내부(Dispatch 등)에도
대칭 적용 — 각 서브시스템이 InitXxx(module)로 module을 뮤테이션. 서브시스템
간 호출 순서 문제는 각 InitXxx 파일 톱레벨에 Relate() 하나를 두고 module을
weak key 삼아 인스턴스별 완료 여부를 기록해 require처럼 멱등하게 만들어
해소(relate-plan.md 체크리스트에도 용례 추가). module-lifecycle-plan.md에
"New()의 내부 구성" 절 신설, architecture.md/dispatch-core-plan.md/
ROADMAP.md(M1 체크리스트)에서 상호 참조.
핸드오버 감사 루프 4라운드(무발견 1회로 수렴) — 라운드 1~2는 절 인용
사각지대·상호참조 누락·SetWeak/SetStrong 일관성을 잡았고, 라운드 3~4는 그
수정 자체가 남긴 커밋 개수/날짜 오기, 원문 인용 파라프레이즈 등을 추가로
잡음. 부수로 session/ 기록 공백(2026-08-18/19 다수 커밋에 원문 누락)을
발견해 이번 세션분만 session/2026-08-19-01-*.md로 남김 — 과거 공백 처리는
사용자 확인 대기.
Co-authored-by: qwreey <me@qwreey.moe>
사용자 지침 — "시간이 걸려도 상관 없으니, 차라리 병렬 에이전트를 덜 써줘.
그냥 한 턴에 하나씩만 사용하고, 0 이 나올때 까지 턴 수를 늘리는게
나아보임. 토큰을 너무 많이 소비해서 다른 작업을 못 하고, 세션 한도에
닿더라고."
- quad-doc-auditor는 한 턴에 하나만 호출(병렬 금지) — 2026-08-16의
"최소 2개, 변경이 많으면 3~4개" 지침을 대체. 커버리지는 병렬 폭이
아니라 턴 수로 얻는다. 근거 실측: 전 코퍼스 한 패스가 서브에이전트
토큰 21만/툴 호출 82회, 4개 병렬이면 한 번에 80만대.
- 프롬프트로 범위를 diff로 좁힐 것을 신설(바뀐 파일 + 그걸 인용하는 곳).
전 코퍼스 스윕은 오래 안 돌렸을 때만. 라운드마다 각도를 바꿔 병렬로
얻던 폭을 턴으로 대체.
- 종료 조건을 "무발견 2연속"에서 "무발견 1회"로 완화(2연속은 병렬 다패스
전제였음). 비용 때문에 중간에 멈출 때도 몇 라운드에서 왜 멈췄는지
반드시 보고.
- /code-review는 감사자를 대체하지 않는다를 실측과 함께 명문화 — 이번
핸드오버에서 감사자 1패스(1건) 뒤 /code-review high가 10건을 더 잡았고
전부 유효했다(보는 축이 다름). 사용자만 호출 가능하므로 큰 변경 커밋
전엔 돌릴지 물어볼 것.
Co-authored-by: qwreey <me@qwreey.moe>
`.claude/pre-implementation-qa.md`(사용자가 base/ 확정 문서를 문항으로
재심사한 결과)를 실제 문서에 반영하고, 그 문서를 qa-request/로 옮기며
1라운드임을 파일명·제목에 명시(2라운드는 새 파일).
그대로 구현하면 반대로 돌던 것 2건:
- canBound의 판정 방향이 이름과 반대였음 → canBound(v) == not
isBoundAlive(v), 게이트는 전부 `if not canBound(v) then error(...)`.
canExecute와는 값이 같은 게 아니라 서로의 부정이고, 그게 오히려 이름
분리의 명분이 됨(옛 근거 "값이 항상 같다"는 폐기).
- gcconn/gchold 보관이 SetStrong으로 적혀 있었음 → SetWeak. 근거 문장까지
틀렸던 것이라 같이 교체(그대로 짰으면 두-Relate 상호 강참조 누수).
설계가 바뀐 것:
- Dispatch.drive의 None 스킵 분기 폐기 → NoneHandler는 재귀 전담,
NilHandler 신설(k=number and v==nil 말단이 setLength/setOffsetSource
등록). 깨진 전제는 "배열 파트의 None은 process를 안 탄다".
- Length/Offset 등록 책임이 "처음 매치한 Handler" → 말단 Handler.
- 이벤트 disconnect 센티널 false → None/nil.
- Ref 내부 구조를 .Callbacks 분리 + 평범한 .Value 필드로 단순화,
RefLeafHandler에 빠져 있던 type(k)=="number" 추가(leaf는 배열 전용).
- :List reconcile의 nil 리턴은 다시 파괴가 기본, 값 교체와 PopOnly(가칭)만
비파괴.
- base 소유 Fallback Handler 등록 주체를 백엔드 팩토리 → quad-base 자신으로
재역전(백엔드 미로드 시 안내 에러 경로가 안 돌았음).
- "이벤트 콜백 시그니처는 Luau가 검증 못 한다"가 거짓임이 사용자 반례로
확인 → onchange-plan.md의 파생 근거까지 교체(결론은 유지).
이름/표면: DI → D(Declarative) 확정 및 전수 반영, New 커링 + D는 전량
코드 생성, Attribute.Merged/Overridden 둘 다 제공, Quad.debug 신설,
store "key" 문자열 커링 기각(→ store:GetDynamic).
판단이 갈리던 4건(PopOnly 채택 / D-7 재역전 / NoneHandler·NilHandler 역할
분담 / 동적 키 경로)은 사용자에게 물어 확정.
커밋 전 검증: quad-doc-auditor 1패스가 1건, 사용자가 돌린
`/code-review high`가 10건을 더 잡아 전부 반영(ROADMAP이 SL-3 역전을 안
따라오던 것, 설계 갭 2건은 새 열린 질문으로 등록). doc-check.py ERROR 0.
Co-authored-by: qwreey <me@qwreey.moe>
사용자 제기 — doc-check.py가 정규식으로 결정론적 판정을 하는데 표기가 흔들리면
문제가 커지니, 정규식을 늘리기보다 "예상 가능 범위"를 컨벤션으로 좁히는 게 싸지
않냐. 실측해보니 날짜 표기는 이미 100% 균일해서 고칠 게 없었고(강제 장치 없이),
드리프트는 절 인용 쪽이었다 — WARN 86건 중 78건(91%)이 절 참조 불일치.
핵심은 그 78건이 코퍼스가 지저분한 게 아니라 **검사기가 못 읽는 것**이었다는
점이다. 이 코퍼스는 `**볼드**` 줄을 하위 절로 쓰는데 headings()가 `#`만 봤다.
## 동작 변화 (문서 정정으로만 보이지만 게이트가 바뀐다)
- 절 참조 불일치가 **WARN → ERROR**. 이제 절 인용 오류가 커밋을 막는다.
- 절 인식이 `#` 헤딩 + `**볼드**` 절로 확장. 단 볼드는 **빈 줄 다음이나
리스트 항목 머리**만 인정 — 문단이 줄바꿈되며 우연히 줄머리에 걸린 강조를
절로 오인하던 걸 커밋 전 감사가 잡아 조였다.
- 인용 길이 상한 60→160자. 60자를 넘으면 매칭 자체가 안 걸려 검사에서
**조용히** 빠져나갔음(위양성보다 나쁜 구멍).
- 비교를 공백 무시로(줄바꿈 인용 대응), 선두 장식·상태/날짜 태그 정규화,
`initreq/` 대상 인용은 절 검사 면제(읽기 전용 외부 원본).
## 규약
`conventions.md`에 "문서 표기 규약" 절 신설 — 절 인용 규약(의역 금지, 헤딩은
부분문자열/볼드는 앞부분일치, 태그로 닫히는 볼드 캐비엇, blockquote 함정),
세션은 산문 서수 말고 파일 ID로 지칭. 날짜 마커 라벨 어휘 닫기는 사용자 판단
으로 기각(기계 검사 대상이 아니라 읽는 쪽 판단 재료).
## 결과
절 참조 불일치 78 → 0. 36건은 검사기 수정으로 사라졌고(애초에 위양성), 42건은
인용을 실제 절 제목으로 손으로 고쳤다. 마지막 15건은 서브에이전트 3개에 병렬
위임해 추적 — **설계 서술이 유실된 건은 0건**, 대부분 코드 주석·본문 산문·
주제명처럼 애초에 절이 아닌 걸 절로 인용해온 것이었다.
부수로 드러나 같이 고친 것: onchange-plan이 9차 분할 때 일부러 안 옮긴 절을
잘못된 파일로 가리키던 것, brand-plan이 이미 이행된 정정을 "정정 대상"이라
부르던 것, ROADMAP의 blockquote가 인용 줄바꿈 때문에 깨져 있던 것,
pre-implementation-audit의 해소된 항목이 "아직 안 고침" 절에 남아 있던 것
(사용자 결정으로 "이미 고침"으로 이동).
커밋 전 감사 4라운드(에이전트 8개)를 돌렸고, 발견 추이는 2→2→1→0이다.
매 라운드 발견이 "직전 라운드 수정이 만든 새 결함"이었던 게 특징 — 규약을
세우는 커밋이 그 규약의 첫 위반자가 된다는 걸 실측으로 확인했다. 상세는
.claude/session/2026-08-16-03-doc-check-section-convention.md.
부수: __pycache__를 .gitignore에 추가하고 추적 해제(32e9db0에 실수로 딸려
들어가 있었음). quad-doc-auditor에 작업 트리를 바꾸는 git 명령 금지 규약 추가
— 감사자가 git stash를 걸어 메인 세션 스테이지가 반복적으로 풀렸다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011zk7XHkSfiBfdPLZUQHdZf
수렴 조건(무발견 2연속)은 못 채웠으나 사용자 결정으로 중단. 발견 추이
6→5→2→2이고 3·4라운드분은 이 세션 변경의 stale이 아니라 코퍼스에 오래
있던 일반 부채(개수 하드코딩, 날짜 없는 시한부 주장)라, 계속 돌리면 수렴이
아니라 옛 부채를 끝없이 캐는 쪽이 된다는 판단. 이 세션 변경분 자체는
안정적(4라운드 설계 코퍼스 각도에서 확실 발견 0건).
todos.md 7번에 중단 사유와 "이번 미수렴 때문에 이어서 돌릴 필요 없음"을
명시하고, 세션 로그에 다음 세션 핸드오버 절(§18) 추가.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
사용자 결정 — const 바인딩 재검토는 에이전트가 정할 문제가 아님. 논거:
pesde의 타입 추출(d.ts처럼 types emit)류 툴링이 미성숙해 const를 제공 못
하고 언제 가능해지는지도 불명확하며, 이는 사용자가 확인된 정보로 제공하는
게 맞다는 것. question.md에서 닫고 archive로 옮긴 뒤 HUMAN_TODO.md 8번
신설 — 사용자가 시점을 파악하거나 가능해질 때 에이전트에 알림. 그때까지
에이전트는 "일단 local로" 원칙 유지. architecture.md에 날짜와 논거 반영.
감사 4라운드(설계 코퍼스 각도):
- todos.md의 개수 하드코딩 2건 제거("체크리스트 8개", "11개 전원 완료")
— 이 프로젝트 자신의 "개수는 소스 하나만" 규칙 위반이던 기존 항목
- 코퍼스 확실 발견 0건(설계 문서 쪽은 깨끗)
주입 경로 실측 둘:
- 에이전트 정의는 이번엔 HEAD보다 1커밋 전 — 뒤처지는 폭이 실행마다 다름을
관측표에 4번째 행으로 추가
- CLAUDE.md @import 컨텍스트는 세션 시작 시점에 고정됨(감사자가 받은 것이
세션 시작 커밋과 일치, 그 사이 7커밋). 메인 세션도 동일해 옛 conventions.md
를 들고 있었음. 동작이 명확한 별개 경로라 conventions.md에 규칙으로 명시 —
@import 파일을 고친 세션은 기억이 아니라 파일을 직접 Read해서 따를 것
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
두 감사자가 독립적으로 같은 확실 발견: agent-memory의 caching 메모리가
"커밋된 HEAD에서 읽힌다"를 여전히 확정 사실로 서술. 그 결론을 반증한 커밋
1935dd4가 agent-memory/ 아래를 하나도 안 건드린 탓 — "변경한 세션 자신은
자기가 뭘 안 건드렸는지 모른다"의 교과서적 사례이고, 같은 파일이 한 세션에
두 번 연속 stale이 된 것이기도 함.
고치면서 그 메모리가 결론을 복제하지 않고 정의 배너를 가리키게만 바꿈 —
같은 사실이 두 곳에 있어 두 번 갈라졌으므로 근본 원인 제거.
그 외:
- todos.md의 매달린 포인터("아래 부수 확정 참고" → 그 헤딩이 직전 라운드에
"미해결 1/2"로 개명됨)를 정의 배너 참조로 교체
- 관측표에 3라운드 행 추가: 감사자 2개가 마커로 확인한 결과 디스크 현재
내용과 바이트 동일 → 일관되게 낡은 게 아님이 확인돼 "모른다" 유지 근거가
늘고, 마커 확인 방식이 작동한다는 것도 재확인
감사자 둘 다 자기 모델을 claude-sonnet-5로 보고 — 트랜스크립트 실측과 일치.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
서브에이전트 뷰 최상단의 'Opus 5 · Claude Max'는 Claude Code 세션 헤더(메인
모델)이지 서브에이전트 모델이 아님(사용자가 화면 직접 확인). 감사자는 다섯
실행 전부 message.model=claude-sonnet-5이고 frontmatter model: sonnet은 정상
동작. 모델을 잘못 읽을 자리 셋(문자열 grep이 usage.iterations에 낚임 / 세션
헤더 / 폐기된 워크플로의 픽스 에이전트는 실제 opus였음)과 신뢰할 소스
하나(트랜스크립트 최상위 message.model)를 세션 로그에 기록.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2라운드 감사자 둘이 독립적으로 같은 반증을 가져옴: 그들이 받은 정의
지시문이 어느 커밋과도 일치하지 않는 하이브리드였음(배너는 구버전,
"출력 형식"의 사용자 판단 문단은 신버전). git log -S로 독립 확인 — 그
텍스트는 커밋된 적 없는 중간 워킹트리 상태였다.
이 세션은 같은 문제에 세 번 결론을 냈고 앞의 둘("세션 시작 스냅샷",
"커밋된 HEAD")이 다 틀렸으므로 세 번째 가설을 세우지 않는다. 관측표와
실무 규칙 하나만 남김 — 정의를 고쳐도 반영됐다고 가정하지 말고, 중요하면
마커 문구를 넣어 감사자에게 물어 확인할 것(이 반증이 정확히 그 방법으로
나왔음: 2라운드 프롬프트에 마커 확인을 끼워 물었다).
memory: project → Write/Edit 결론은 유지 — 제거 이후 후보 텍스트가 전부
그 옵션을 안 가지므로 정의 불확실성의 영향을 안 받음. 미해결은 tools:
필드 미반영뿐.
2라운드 감사 발견 반영:
- todos.md 항목 7 헤더가 "남은 건 (d) 하나"인데 본문은 (d) 해소라 자기모순
- README.md의 conventions.md 색인 행에 신설된 "설계 원칙" 절 누락
- conventions.md의 "출처 없는 원칙" 경위 서술 중복 축약
- 사용자 판단 등급 판정 기준을 quad-doc-auditor.md "출력 형식"으로 단일화
- session 로그 §2에 "뒤에서 정정됨" 포인터(구체 결론은 안 적음 — 두 번
뒤집힌 자리라 또 stale해짐)
감사자 모델 재확인: 다섯 실행 전부 message.model=sonnet. 실행마다
message.usage.iterations[]에 opus 항목이 1개씩 붙는 게 오독 원인.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
사용자 결정으로 .claude/agent-memory/(감사 에이전트의 영속 메모리)를 커밋해
추적하기로 함. 논거: 개발 환경이 다수라 메모리가 레포를 따라다녀야 하고,
실 기록이지 빌드 디펜던시가 아니며, 환경 노출 위험은 SAFETY.md의 파이프라인
(컨테이너 개발 → 프라이빗 git → 검토 후 머징)의 마지막 사람 감사가 방어선.
커밋 전 처리:
- 절대경로/이메일/키/토큰 패턴 스캔 — 노출 소지 없음(히트는 전부 frontmatter
name: 과 마크다운 링크)
- project_agent_prompt_caching_bug_reproduced.md가 아직 "캐시 가설"로 서술돼
있어 커밋된 HEAD 모델로 다시 씀(같은 정정으로 memory: project → Write/Edit
진단이 지지됨을 반영), MEMORY.md 색인 줄도 갱신
README.md의 agent-memory/ 행에 결정과 사용자 논거를 기록 — 추적하는 쪽으로
정해졌으므로 이 폴더 내용도 감사 대상임을 명시.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
사용자 결정 반영:
- 감사 루프를 Workflow에서 "메인이 quad-doc-auditor를 병렬 호출 → 메인이
일괄 수정 → 반복"으로 재설계. .claude/workflows/quad-handover-audit.js 삭제,
절차 소스는 conventions.md "작업 방식". 폐기 근거 셋 — 토큰 과다, 파일별
픽스 에이전트가 또 부정확한 서술을 생산, 서브에이전트는 사용자에게 못 물음.
- 패스 수는 최소 2에서 변경 규모에 따라 증가. 감사자 모델은 sonnet 유지
(haiku 배제).
- 출처 없던 원칙 "드문 오용/가상 미래 요구까지 방어·최적화하려고 구조를
복잡하게 만들지 않는다"를 conventions.md "설계 원칙" 절로 명문화(선택지 a).
modifier-plan.md 인용을 그쪽으로 재조준, question.md 항목은 archive로 이전.
- 신설 관례: 사용자 발언을 근거로 인용할 때 결론만 적지 말고 논거까지 남길 것
(논거 원문은 session/에, 라이브 문서는 결론+짧은 논거+포인터).
⭐ 재정정 — 직전 커밋의 "정의 파일은 세션 시작 시점 스냅샷" 결론은 틀렸음.
정의는 워킹트리가 아니라 **커밋된 HEAD**에서 읽힌다(감사 패스가 받은 지시문이
세션 도중 만든 HEAD 커밋의 blob과 바이트 단위로 동일, git rev-parse로 독립
확인). 규칙이 "재시작"에서 "고쳤으면 커밋 후 실행"으로 싸짐. 이 정정으로
오래 미확정이던 (d)도 해소 — memory: project가 Write/Edit을 딸려온다는 진단이
맞았고, "빼도 그대로"로 보였던 건 제거가 아직 커밋 안 됐던 탓. 남은 미해결은
tools: 필드 미반영뿐.
첫 감사 라운드(새 절차) 반영: 자기 메모리 2건의 stale 서술,
documentation-content-map.md "943줄, 최대 문서"(실측 203줄, 최대는 slot-plan
1970줄), README.md 패스 수 하드코딩. 직전 커밋의 미재감사 6건은 회귀 없음으로
확인해 todos.md ⚠️ 블록 닫음.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
직전 커밋(298dac2)이 "정의 파일은 세션 시작 시점 스냅샷으로 고정된다
(에이전트·워크플로 공통)"고 일반화했는데, 그건 증거보다 센 주장이었다 —
scriptPath 경로를 테스트한 적이 없었다. 프로브로 갈랐음:
- Workflow({name}) = 세션 시작 시점 스냅샷 (실행된 스크립트가 세션 시작
상태와 바이트 단위 동일, 같은 세션 편집 반영 0)
- Workflow({scriptPath}) = 디스크 실시간 (세션 시작 후 새로 만든 스크립트가
실행되고, 고친 뒤 다시 부르니 고친 값이 반환됨)
따라서 워크플로 쪽 해법은 세션 재시작이 아니라 scriptPath다 — conventions.md의
핸드오버 감사 절차에 반영. 에이전트 정의 stale은 자기 보고뿐이라 근거 등급이
낮음을 명시하고(Grep/Glob 불일치와 같은 등급), 우회 수단이 없으니 재시작을
보수적 해법으로 유지.
또 첫 실동이 수렴 못 하고 최대 라운드로 끊긴 결과, 마지막 라운드의 발견
6건이 반영만 되고 재감사되지 않은 채 커밋됐다는 것을 todos.md/세션 로그에
명시 — 다음 실동의 첫 임무. README.md의 audit/ "현재 6개"(실제 7개)도
폴더-소스로 전환.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
전 세션이 "재시작해야만 확인 가능"으로 남긴 3건을 전부 닫음:
- @import 3개(conventions/project-context/todos) 실제 로드 확인
- quad-doc-auditor 레지스트리 등록 확인(전 세션 전멸은 .claude/agents/가
세션 도중 생긴 디렉토리였던 탓)
- frontmatter model: sonnet 반영 확인(트랜스크립트에 claude-sonnet-5 기록,
워크플로에 opts.model 명시 불필요)
정의 파일은 세션 시작 시점 스냅샷으로 고정된다는 것을 1차 증거로 확정 —
quad-handover-audit이 실제 실행한 스크립트가 세션 시작 시점 상태와 바이트
단위로 동일했고 같은 세션의 편집은 반영 0. 에이전트 정의도 동일.
정의를 고쳤으면 재시작 뒤에 감사를 돌릴 것(안 그러면 거짓 초록불).
전 세션 감사가 남긴 긴 가설 배너(80줄)를 검증된 것만 남겨 압축.
quad-handover-audit 첫 실동: 에이전트 67개/6라운드, 수렴 실패
(새 발견 28→15→16→7→11→6, 라운드5에서 되레 증가). MAX_ROUNDS와
"연속 dry 2회" 조건 재검토 필요 — 결과 자체는 위 스냅샷 문제로 옛
스크립트가 돈 것이라 재시작 후 재실동 대상.
감사가 잡은 것 반영: slot-plan.md 정정 배너가 그 뒤 재역전(retract=언마운트)을
놓치고 있던 것, "spikes 44개"(실제 48개) 류 하드코딩 개수의 단일 소스화,
doc-check.py docstring이 검사 심각도를 실제 코드와 다르게 서술하던 것 등.
인용 출처 3건 재분류 — 2건은 인용 대상만 틀린 것이라 실제 소스로 재조준
(v1-compat-plan.md→component-composition-plan.md+store-plan.md,
pre-implementation-audit.md→ROADMAP.md). 진짜 출처가 없는 1건
(modifier-plan.md:536)만 question.md 3번으로 올려 사용자 판단 대기.
워크플로 개선: 반환값에 findings 추가(커밋 전 diff 리뷰 근거),
totalFindingsFixed→findingsSentToFix 개명(과대계상), 반영 에이전트 sonnet 명시.
.claude/agent-memory/는 의도적으로 커밋 제외(추적 여부는 사용자 판단).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
## CLAUDE.md 분할
1537줄이라 (a) 사람이 검토 불가, (b) 공식 권장치(파일당 200줄) 7.7배
초과로 지침 준수도 자체가 저하, (c) 긴 파일 편집 시 에이전트 실수 증가.
CLAUDE.md (39줄, 진입점)
├─ @.claude/conventions.md 언어/모델 관례 + 작업 방식
├─ @.claude/project-context.md 프로젝트 설명 + 문서 구조
└─ @.claude/todos.md 지금 할 일
.claude/session-summary.md ← import 안 함(의도적, 온디맨드)
세션 히스토리 1231줄(전체의 80%)은 그 문서 스스로 "항상 읽을 필요 없음,
base/가 소스"라고 명시해온 색인이라 @import에서 뺐음. 내용 유실 없음
(1537→1647줄, 추가 헤더만큼 증가).
주의: @import는 컨텍스트를 줄이지 않음(전부 로드됨). 분할이 사는 건
사람 검토성 + 편집 정확도 + 파일 단위 자동생성 가능성.
CLAUDE.md 계열의 블록 HTML 주석은 주입 전 제거되므로 지시는 본문에 쓸 것.
## 워크플로 가짜 초록불 수정
첫 실측에서 감사 에이전트 6개 전원 실패했는데 converged:true가 나왔음
(전멸하면 fresh가 비어 "깨끗한 라운드"와 구분 불가). 감사 도구 최악의
실패 모드라 (1) 전멸이면 throw, (2) 반영 에이전트 실패 시 그 발견을
seen에서 빼 다음 라운드가 재시도하도록 수정.
## 부수
- 분할로 깨진 상호참조 20여 곳 정정(병렬 에이전트 3개).
ref-plan.md:541의 사전 존재 오류(→ pre-implementation-audit 1-5)도 정정.
- doc-check.py: 새 파일 4개를 OURS에 등록(안 하면 깨진 참조가 WARN으로만
잡힘), is_history()로 session-summary.md를 archive/와 같이 면제.
- doc-include-plan.md: 목적지가 통째로 생성되는 파일이 되면서 양방향
마커 설계의 절반(목적지 마커)이 불필요해져 단방향 생성으로 단순화.
doc-check.py ERROR 0 유지.
미해결: session-summary.md 자동생성 미착수(91개 세션 파일 마커 삽입 필요),
orphan 인용 3건(modifier-plan.md:536, v1-compat-plan.md:50,
pre-implementation-audit.md:434 — 분할 이전부터 존재).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HvEKn9f67kkx2nGLG8PsP
type-recursive-issue-try-callback/ 신설(spikes 35개) — type function/
메타테이블/제네릭/이중 꺾쇠 명시 인스턴스화까지 전방위로 재시도했으나
quad의 state:Compute(fn) 단일 호출 모양을 유지한 채로는 여전히 안 됨,
근본 원인은 재귀 특유가 아니라 "제네릭 콜백 인자엔 컨텍스트 타입
전파가 안 됨"이라는 더 일반적 Luau 한계임을 확인. typing-limits.md
§1/§7에 반영, 원칙 자체는 안 바뀜.
이어진 /code-review 2회전이 이 작업과 luau-test/16 복구 세션
전반에서 정합성 문제 다수 발견 — 스파이크 결과 표 stale(16번 실패로
잔존), 폴더 개수 off-by-one 여러 건, type-recursion-issue/REPORT.md의
실제 오류(self가 제네릭이면 자유 함수로 빼도 여전히 실패한다는 사실이
"통과"로 잘못 서술됨), 옛 솔버 캐비엇 누락, 체크리스트 항목 간 모순,
이 세션 자체의 히스토리 누락 등 — 전부 수정. doc-check.py ERROR 0 유지.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HvEKn9f67kkx2nGLG8PsP
typing-limits.md §1에 검증된 선언 스타일 규약(③) 추가 — 재귀 메소드를
인라인 대신 이름 붙은 함수 + typeof로 선언하면 LHS 명시 없이도
다운스트림이 안전해짐(체이닝 50단·타입 변경·중첩 self 호출 확인).
setmetatable 확장은 quad 계약에서 솔버 버그를 만나 채택 안 함.
luau-test/16(type function으로 Store<T> 필드 합성)을 복구해 done/으로
이동 — 원인은 설계 문제가 아니라 API 버전 드리프트, §5를 검증 완료로
승격.
전체 실측: audit/type-recursive-issue-with-typeof/(REPORT.md + spikes
15개). code-review 지적 4건(불건전 판정 오류, 음성 대조군 개수, 체이닝
깊이 주장의 재현 스파이크 누락, store-plan.md stale 참조) 전부 반영.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
반복된 stale 문제(같은 사실이 여러 곳에 중복 서술)를 근본적으로 줄이는
방법으로 마커 기반 include 도구를 논의 — AsciiDoc tagged include/
markdown-magic 선례 확인 후 build-vs-buy 검토, doc-check.py와 짝인 Python
직접 제작 채택. research/doc-include-plan.md에 플랜 초안(마커 문법,
CLAUDE.md 세션 히스토리 파일럿 범위, 열린 질문) 작성 — 세부는 사용자가
다듬을 예정, 구현 착수는 안 함. README.md 색인/CLAUDE.md 백로그·세션
히스토리 동기화.
State<Observer>/State<Effect>가 재-dispatch될 때 안쪽 값이 안 바뀌어도
Dispatch가 값 비교 없이 매번 재바인딩하던 것에 RefLeafHandler와 같은
old ~= v dedup을 추가(correctness 아니라 순수 성능 최적화 — == 비교가
매번 도는 Relate weak-table 쓰기보다 항상 쌈).
가장 큰 변경은 Tag/Attribute 핸들러 등록 모델 역전: 직전 커밋이 확정한
"TagHandler/AttributeKeyHandler/AttributeGroupHandler가 quad-base 모듈
로드 시점에 스스로 등록한다"는 결론 자체가 틀렸음이 드러남 — 이건
lifecycle-pattern.md가 이미 거부해둔 InitNamespace류 top-level 부작용
패턴과 같은 클래스였고, module-lifecycle-plan.md가 이미 확정해둔 "등록은
백엔드 팩토리가 BaseModule을 뮤테이션하는 시점" 원칙과 정면으로 어긋났음.
정정: 저 이름들은 참조 카운트/이름 claim 알고리즘 구현일 뿐이고,
HANDLER_PRIORITY_FALLBACK에 실제로 꽂히는 건 이를 감싸는 별도 이름의
TagFallbackHandler/AttributeKeyFallbackHandler/AttributeGroupFallbackHandler
— 등록 주체는 quad-base 모듈이 아니라 백엔드 팩토리. dispatch-core-plan.md/
tag-plan.md/attribute-plan.md/module-lifecycle-plan.md/architecture.md
전부 재반영, ROADMAP.md M10 체크리스트에 새 Fallback 파일 3개 추가(빠져
있으면 구현자가 만들 필요를 몰랐을 갭), 뒤집힌 원문은
archive/tag-attribute-load-time-registration-reversed.md.
/code-review를 두 라운드 돌려 findings 15건 확정 반영 — 죽어있던
Observer/Effect FALLBACK 동적 경로 가드(k 타입 미체크), bindLifetime
pseudocode의 canExecute/canBound 혼용, "결정 대기" 절이 삭제됐는데 비어
있다고 서술하던 3개 파일, "동적 경로 가드"가 볼드 텍스트뿐 실제 헤딩이
아니라 깨져있던 절 참조 6곳(###으로 승격), CLAUDE.md 11번째 세션 기록
~103줄→~13줄 압축, Tag/Attribute 정정이 프로즈만 고치고 놓친 pseudocode
2곳. 별개로 RefLeafHandler.isHandlable이 PostRef 도입 이후 안 갱신돼
`and not isPostRef(v)`를 빠뜨렸던 사전 존재 버그도 같이 발견·정정.
doc-check.py ERROR 0 유지.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
전체 .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
직전 커밋(bcd02f1)에서 미보장으로 뒤집었던 것을 같은 세션에 철회.
반례는 `FastQuery(...) -> PreRef`류 조합 — 앞자리 항목이 뒤 항목의
전제를 만들어주는 정당한 합성이고, children 마운트 선후가 배열 위치로
정해지는 것과 같은 성격. 보장 비용 0(pre-pass가 어차피 index 순서)이고
배열 파트 index 순서는 이미 백엔드 이식성 때문에 명시적 계약이라 새로
내주는 자유도 없음. 보장 + 스타일 권고를 같이 기재.
- archive/preref-order-guaranteed-reversed.md → preref-order-unguaranteed-withdrawn.md
(제안·철회 양쪽 논거 보존)
- OnRendered의 "부모에 붙기 전에 불림" 캐비엇은 사용자 지시대로 이름 유지 +
명시만(ref-plan "보장 범위" 항목, lifecycle-hooks-plan ⚠️ 항목)
- ref-plan/lifecycle-hooks-plan/ROADMAP/README/CLAUDE.md/세션 로그 동기화
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- base/ref-plan.md: "PostRef" 절 신설(PreRef의 거울상 — pre-pass 공동 수집,
ProcessedPostRef 센티널+전담 Handler, 동적 경로 가드, _fired, 타입 차단).
보장 범위를 명시: 자기 서브트리 완성은 보장하되 이 인스턴스가 부모에
붙기 전에 불림(React componentDidMount와 다름).
- 복수 PreRef/PostRef의 계열 안 fire 순서를 "배열 index 순서 보장"에서
미보장으로 역전 — 구현이 아니라 계약만 좁힘
(archive/preref-order-guaranteed-reversed.md 신설).
- research/lifecycle-hooks-plan.md → base/ 승격, OnRendered 채택 반영
(마지막 열린 항목이던 채택 여부/메커니즘/스코프/패키지 전부 확정).
- dispatch-core-plan/brand/architecture/slot/modifier/typing-limits/README/
ROADMAP/question.md 전파. doc-check.py ERROR 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
워크트리(worktree-debounce-throttle-plan)에서 네 라운드로 다듬은 결과를
메인의 3단계 분할 구조에 맞춰 필요한 변경만 이식.
## 신설: research/debounce-throttle-plan.md
- Blocker가 이미 쓰는 게이티드 노드의 릴리스 트리거만 타이머로 바꾼 것.
공개 Blocker API엔 "상류 신호 도착" 통지가 없어 그 위엔 못 얹음 →
M3에서 게이트를 공용 Gate로 뺄 것.
- 두 도구의 차이는 "신호가 창 타이머를 리셋하는가" 한 비트뿐.
공개 생성자 2개 + 내부 구현 1개(초안이 옮겨온 lodash식 maxWait 공식엔
trailing 통과 직후 이중 발화 버그가 있었음).
- quad-base + 주입 op 2개: setTimeout(func, delay) -> Timeout /
clearTimeout. Roblox는 task.delay/task.cancel로 배선(인자 순서 반대).
os.clock()은 Luau 표준 라이브러리라 주입 대상 아님(diff 전용).
Timeout = { __type_timeout: true, _native: any }.
## 역전: emit은 자기 invalid 상태와 무관하게 항상 전파된다
source-state-plan.md의 "이미 invalid였다면 그 아래로 더 전파하지 않는다"가
확정된 Observer 계약(fn이 :Get()을 안 불러도 됨)과 정면 충돌 — 액면대로면
:Get() 안 하는 Observer는 한 번 울고 영구 침묵. architecture.md가 같은
다이아몬드 문제를 pull-recompute로 설명하는 것과도 어긋나 있었음.
정정 모델: invalid는 캐시 낡음 표시일 뿐, 중복 재계산은 pull-recompute+
캐시가 막고 중복 통지는 안 접음(접으려면 Blocker 같은 명시적 게이트).
- source-state-plan.md: 전파 규칙 재작성, "다이아몬드 의존성은 무엇이
푸는가" 절 신설, Observer 절 상호 참조. 플래튼 기각/:With 빌더 기각
근거를 캐시 공유로 재작성(두 결론 유지, 근거 강도는 상승)
- architecture.md, blocker-plan.md(전파를 지연시키는 유일한 요소로 위치
명문화), comparison-fusion-vide.md, framework-comparison-findings.md
- ROADMAP M0 체크리스트: 확인할 것이 정반대가 됨
- luau-test 05 → rewrite-required/(옛 모델을 통과 상태로 검증 중이었음),
STATUS.md 개수 동기화(rewrite 6→7, done 14→13)
- audit: 05 행 정정 + "12개 전원 통과"를 액면대로 읽지 말라는 경고
- archive/invalidate-dedup-propagation-reversed.md 신설
doc-check: ERROR 0 / WARN 84(작업 전 85).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
세 건을 한 커밋에 처리:
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>
pcall 기반 Fallback과 xpcall+debug.traceback 기반 Traceback으로 분리,
정확한 제네릭 시그니처(OkComp/ErrComp/Args... 독립 제네릭)와 err: any를
확정. 패키지(quad-base)·이름(Fallback/Traceback) 확정으로 남은 열린
질문이 없어져 base/로 승격. 스파이크를 audit/fallback-xpcall-spike.luau로
옮기고 base/fallback-plan.md와 이름을 맞춰 내부 함수를 Traceback으로
정정, audit/fallback-xpcall-verification.md에 실측 결과 기록. README/
question.md/archive/question-resolved.md/lifecycle-hooks-plan.md의 상호
참조 동기화.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
session/2026-08-14-06-lifecycle-hooks-plan.md로 OnCreated/OnDestroyed
백로그 세션 전체 기록(워크트리 조사, 사용자 refinement 두 차례, 메인
병합, 동시 편집 충돌 대응까지) 신설. 다른 세션이 04/05를 이미 써서
CLAUDE.md 세션 히스토리에서 이 항목을 원래 "세 번째" 자리에서 맨 끝
"여섯 번째"로 옮기고 파일명(03→06)/역참조를 재조정 — 실제 작업 순서와
문서 번호가 어긋나는 이유를 양쪽에 메모로 남김.
Co-Authored-By: Claude Sonnet 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>
component-fallback-plan.md가 열어뒀던 "xpcall 에러 핸들러 배선 실측
필요" 질문을 luau 스파이크(component-fallback-xpcall-spike.luau)로
확인 — 클로저 업밸류 배선, 3단 중첩 debug.traceback 캡처 등 10개 검증
전부 통과. 부수적으로 error(msg) 기본 호출이 위치 접두("파일:줄: ")를
자동으로 붙인다는 캐비엇을 새로 확인해 "프로덕션에서의 동작" 절에 반영.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
컴포넌트 함수를 감싸 에러 시 플레이스홀더를 그려주는 pcall/xpcall 래퍼
아이디어를 백로그로 문서화(research/component-fallback-plan.md), README/
question.md/CLAUDE.md 인덱스 반영. 후속 code-review로 발견된 결함(코드
스팬이 줄바꿈에 걸쳐 깨진 곳 4군데, 워크트리가 계획 문서 없이 시작되는
원인을 "git 미추적"으로 오진단했던 세션 로그 서술)도 같이 정정.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
다른 에이전트 감사와 사용자 트레이싱으로 69466ab에서 나온 지적 반영.
핵심 알고리즘(하강 diff/인덱스 체인)엔 버그 없음이 확인됐고, 의사코드와
문서 정합성에서 나온 것들:
1. `nameClaims`가 Relate의 3-인자 계약 위반(`GetStrong(inst)` /
`SetStrong(inst, claims)`) → `(inst, name[, key])`로 정정. 같은 커밋의
`tagNameMap`은 이미 3-인자였어서 대조로 드러남.
2. TagHandler가 생존 이름의 홀더를 비웠다가 곧이은 process에서 `addTag`를
헛되이 재호출 — 문서 서술("addTag 자체가 안 불림")과 정반대였음.
생존 이름은 홀더를 유지하도록 정정하고, `addTag`도 `removeTag`처럼
배치 호출로 통일(`{string}` 시그니처 도입 근거와 맞춤).
3. 그룹 process의 부분 실패 경로(순회 중 충돌 error) 문서화 — 피해가
인스턴스 수명으로 한정되고 재현이 시끄럽게 반복됨을 트레이싱으로
확인, 롤백 장치는 안 넣고 question.md 3번에 열어둠.
doc-check.py: REF 정규식이 줄 단위라 파일명과 절 제목이 줄바꿈에 걸친
인용을 통째로 놓치고 있었음(이번 2단계 분할뿐 아니라 9차 세션 1단계
분할 stale까지 숨어 있던 원인). 파일 전체 스캔 + 개행 허용으로 고치고,
새로 드러난 stale 참조 30여 곳을 dispatch-core/event-plan/ref-plan/
typing-limits로 정정. 오탐 없음(참조–인용 스팬 3줄 미만 전수 확인).
부수: 세션 로그에 리뷰 라운드 절 추가(커밋 메시지 줄수 오기 2291→1213
정정 포함), CLAUDE.md의 스파이크 이동 서술에 `19` 누락 보강,
luau-test/README 상단 요약표를 STATUS.md와 동기화.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KckSawrsJSJmDBcSojJPxZ
`question.md` 최우선 두 건을 한 패스로 닫고, 배너를 달고 있던 7개 문서 +
인덱스 레이어 전체를 갱신했다.
0-Z (Attribute 이름 소유권):
- 그룹은 비공개 `GetKey`로 이름마다 자기 전용 키를 써서 위임 → 교차 오염
구조적 제거. 이름 소유권은 `AttributeKeyHandler`의 이름 claim이 판정
(`nameClaims` Relate, 다른 키가 같은 이름을 노리면 즉시 error).
- 권고안 (a)(그룹 안 claimant Relate)는 그룹↔직접 쓰기를 못 잡아 기각 —
두 경로가 만나는 말단 핸들러에서 공개 키는 같은 객체라 소유자 구분 불가.
0-A (재디스패치 = 하강 diff):
- 래핑 핸들러의 선행 `retractFrom` 폐기, `Dispatch.process`가 슬롯의
`handler`를 먼저 비교(같으면 클로저에 새 값 전달 후 재process, 다르면
그 자리부터 전량 철거).
- 귀결: `retractFrom`이 3-인자로 축소(힌트를 외부에서 만들어 넣을 자리
소멸), `isX(hintValue)` 가드 규칙 폐지, 깊은 체인 힌트 유실 캐비엇 삭제,
Dispatch의 점유 체크 폐지.
- 9차 세션이 미뤄둔 2단계 분할을 같이 수행 — 디스패치 코어를
`base/dispatch-core-plan.md`로 분리하며 재작성(bind-system-plan은
2263→1219줄). 옛 모델은 archive/dispatch-hintvalue-model-reversed.md.
패키지 재배치 (사용자 제기):
- Tag/Attribute의 부기 알고리즘 전체를 quad-base로, 백엔드는
`addTag`/`removeTag(inst,{string})`/`setAttribute(inst,name,v)` 3개 op만
주입(웹 className/data-* 대응). 엔진 고유 타입 패밀리만 백엔드.
- `HANDLER_PRIORITY_FALLBACK` 신설 — base 제공 핸들러의 밴드, 백엔드가
평범한 우선순위로 덮어쓰면 언제나 이김.
부수: 스파이크 04/19가 옛 모델을 검증 중이라 rewrite-required로 이동,
question.md 최우선 칸 비움, 새 소소 항목 2건 등록(Merged 이름 중복,
`hintValue` 이름 재검토). doc-check ERROR 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KckSawrsJSJmDBcSojJPxZ
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
서브 에이전트 없이 직접 코퍼스 전체를 순회한 감사(base/research/reference/
luau-test/audit/archive 전부 확인, doc-check.py ERROR 0 유지). 발견한
부정확성은 전부 9~10차 세션의 문서 재편 이후 반영이 누락된 stale 카운트/
포인터:
- question.md 0-Z/0-A: 반영 대상 문서 개수가 "6개"로 남아 ref-plan.md가
누락돼 있던 것을 "7개"로 정정(dispatch-redispatch-diff-plan.md 6절과
일치).
- HUMAN_TODO.md 4번: 동일한 6→7 stale 카운트 정정.
- ROADMAP.md 백로그: 2026-08-13 세 번째 세션에 이미 불필요로 해소된
objectListClass.__newIndex 재현 테스트가 여전히 할 일로 남아있던 것 정정.
세션 로그(session/2026-08-13-11-...)와 CLAUDE.md 세션 히스토리 요약도 추가.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa