- pre-implementation-handtrace-round11(.md/-brief.md) → m2-implementation-round11*
(사용자: 구현 중 문서라 pre-implementation이 안 맞음), 인용처 22곳 일괄 치환
- 명명 규약(사용자 확정, README qa-request/ 행): mN-implementation-roundNN —
라운드 번호는 마일스톤을 가로질러 단순 증가("m3 진행 중 m2 하자로 돌아가는
경우도 있을 것"), 마일스톤 접두가 소속을 담음. round1~10 옛 이름은 실제로
구현 전 라운드라 유지
- CLAUDE.md/project-context.md 머리말: M0~M2 완료, 다음은 M3(디스패치 엔진);
todos 00 종결 표기 — 다음 액션은 M3 착수 규약 문항(사용자와 정할 것)
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01LF78pXeFGD1ZSVD3ifteYG
13 KiB
M2 자율 구현 규약 — 11라운드 지시서
이 파일이 무엇인가: [2026-08-28 신설] M2(반응형 코어) 구현을 자율 구간으로 돌리기 위한 규약이자, 단위가 끝날 때마다 띄우는 11라운드 탐사자에게 그대로 주는 지시서다. 산출물은
m2-implementation-round11.md(발견 원문 + §4 배치 문항지). 9·10라운드 지시서(-round9-brief.md/-round10-brief.md)와 같은 관례이되, 이번 라운드는 종이가 아니라 실제 코드가 감사 도구다 — 10라운드 지시서가 *"이후로는 M2 구현 자체가 더 나은 감사 도구"*라고 예고한 그 자리.왜 이 규약인가: 사용자 결정(2026-08-28) — 별도 에이전트가 초안한 규약을 검토해 순서 오류 하나(
EpochMap이Source/State뒤에 가 있던 것)와 소스 단일화 몇 건을 고친 뒤 채택. 사용자 원문: "수정하고 너가 진행하자. epochmap 순서 하나 고치고 진행할 수 있겠니?" 원칙은 10라운드와 같다 — "인간을 기다리는거 엄청 비효율이라서 … batch 로 처리될 필요가 있는듯" (-round10-brief.md). 발견을 하나씩 물으러 오지 말 것.
conventions.md의 "작업 방식"에 짧은 항목이 있고 본문은 이 파일이 소스다.
§1 범위와 순서
- 단위(unit) 넷. 단위 하나가 끝날 때마다 §4 "관여 시점"으로 온다.
- 공통 기반 —
ROADMAP.md의 "공통 기반 — 반응형보다 먼저" 절 전부:Brand.luau/Relate.luau(M1에 이미 커밋됨 — 남은 건base/relate-plan.md대조 + 테스트) /LifetimeHandle.luau인터페이스 /Ref.luau최소형(H-128) /Void(H-162). 여기에H-97의 mock 생명주기 4종(bindLifetime/unbindLifetime/canBound/canExecute의 mock 백엔드 구현)을 끼운다 —ROADMAP.md엔 M2 꼬리에 있지만 이게 없으면 두 번째 단위부터 "구현 + 테스트 짝" 원칙이 성립하지 않는다(전파 루프가 매 발화마다canExecute를 부른다). EpochMap→Source/State/Store—EpochMap.luau가 State 본체보다 먼저다.ROADMAP.md"반응형 본체" 절이 *"Source.luau/State.luau가 이걸 전제로 짜여야 하므로EpochMap.luau(위 항목)가 State 본체보다 먼저 온다"*로 못 박아 뒀다(State가valueEpochMap/emitEpochMap을 컴포지션). 초안 규약이EpochMap을Effect뒤에 뒀던 것이 이 규약이 고친 유일한 순서 오류다.Observer→Effect—Effect는 Observer 뒤(의존).GateNode→Blocker+quad-types의Quad탑레벨 값 전부(H-80) + mock 대상 전파 루프 테스트(ROADMAP.mdM2 마지막 항목). 단위 안의 순서는ROADMAP.md체크박스 순서를 따른다.
- 공통 기반 —
- 각 모듈은 "
base/의사코드를 그대로 옮긴 구현 + 그 절의 계약을 검증하는 테스트"로 짝지어 진행한다. 테스트는./scripts/test.sh로만 돌린다 —luauCLI가 심볼릭 링크를 못 타서 그냥 돌리면 거짓 클린이 난다 (project-context.md). - 코드 배치와 테스트 파일 이름은 첫 단위 계획(§6)이 정한다 — 이 규약이 아니라 그 계획이 소스.
§2 세 갈래 — 발견은 이렇게 분류해서 처리한다
- 자율로 고치고 넘어가는 것:
base/의사코드를 옮기다 드러나는 오류(순서, 빠진end, 이름 불일치, 문서 간 stale), 테스트 실패 중 "문서가 이미 답을 갖고 있는 것". →H-nnn번호를 매겨m2-implementation-round11.md에 기록하고base/와 코드를 같은 커밋에서 고친다. 묻지 않는다. 번호는H-165부터 (10라운드 후속이H-164까지 썼다). - 모아서 올리는 것: 새 필드·인자·이름·표면·메커니즘이 필요해지는 것, 확정을
뒤집어야 하는 것, "이 갈래들이 공유하는 전제"가 흔들리는 것.
→ 코드에 넣지 말고
round11.md§4 표에 갈래 + 권고 + 권고 근거로 쌓는다. 권고가 "옛 메커니즘 복원"이면 그 표시를 단다 — 2026-08-28의Refresh복원 권고(H-159)가_rerunRequired로 뒤집혔듯, 그런 자리는 사용자가 다른 모양을 갖고 있을 가능성이 높다. 막힌 부분은 코드에-- TODO(H-nnn): 한 줄 요지마커 + 문항 번호로 남기고 그 모듈의 나머지를 계속 진행한다. 마커 형식을 이 하나로 고정하는 이유:quad-doc-auditor는 코드를 안 보므로grep -rn "TODO(H-" quad-base/src가 잔여를 전수 확인하는 유일한 수단이다. - 즉시 멈추고 사용자를 부르는 것: 2번 중에서 이미 짠 코드의 상당 부분을 무효화할 규모의 전제 흔들림 — 그 위에 코드를 더 쌓으면 손해인 경우만. 멈추고 상황을 한 문단으로 보고한다(자율 구간이라 사용자가 즉답할 수 없으니, 보고 자체가 그 세션의 끝이다).
§3 리뷰·감사 발견의 취급
/code-review와quad-doc-auditor가 "새 메커니즘"으로 분류한 발견은 반영하지 말고 2번으로 쌓는다. 이 코퍼스의 반복 실패 모드(token, 전용 에러 문구,wasAlive)가 정확히 "리뷰 제안을 승인된 것처럼 넣은 것"이었다 (conventions.md의 "새 필드·인자·이름·메커니즘은 발견이지 결정이 아니다" 항목이 소스).- 반영 전에 (a) 그 책임의 현재 소유자와 (b) 그 모양이 과거에 기각된 적 있는지를
먼저 grep한다 —
base/의 "검토 후 안 만들기로 한 것"류 목록과archive/.
§4 관여 시점
- 커밋 게이트는 두 층이다.
- 매 커밋:
python3 .claude/tools/doc-check.pyERROR 0. 1번 갈래는 커밋 단위가 작아 단위 하나에 커밋이 여럿 생기는데, 그 중간 커밋도 이 게이트를 탄다. - 단위 끝: 감사 루프(
quad-doc-auditor한 턴에 하나, diff 범위,git stash금지를 프롬프트에 직접 명시, 새 발견 0건까지) → doc-check ERROR 0 →/code-review high→ 커밋. 그 뒤 신선한 컨텍스트의 fable 탐사자 하나를 이 파일(§5)을 지시서로 띄워 실제 코드를 돌려보고round11.md에 발견을 이어 붙이게 한 다음, 사용자에게 "round11.md§4를 보라"고 한 줄로 알린다. 사용자는 §4 표만 읽고 갈래를 배치로 회신한다.
- 매 커밋:
- 세션이 일단락될 때마다
session/YYYY-MM-DD-NN-slug.md원문을 남기고,todos.md00번·project-context.md·CLAUDE.md머리말을 **"M2 진행 중"**으로 갱신한다(conventions.md의 세션 원문 규율 그대로). - 비용 참고: 단위당 감사 루프 +
/code-review high+ 탐사자 조합은 수십만 토큰이다. 그래서 단위를 넷으로 쪼갰다 — 관여 시점이 촘촘해지고 3번 갈래의 손실도 작아진다.
§5 탐사자 지시 (단위 끝마다 띄우는 fable 탐사자에게)
당신은 Roblox 엔진용 DOMless UI 렌더러 quad의 M2 구현을 감사한다. 저장소
루트가 작업 디렉토리다. 당신은 신선한 컨텍스트에서 시작한다 — 앞선 세션의 가정을
물려받지 않는 것이 당신의 가치다. git stash를 쓰지 말 것(작업 트리 대조는
git show HEAD:<경로> / git diff HEAD -- <경로>).
- 먼저 읽을 것:
CLAUDE.md→.claude/conventions.md(특히 *"새 필드·인자·이름· 메커니즘은 발견이지 결정이 아니다"*와 "하나의 무언가가 두 일을 하고 있지 않은지") → 이 파일 §1~§3 →m2-implementation-round11.md(지금까지의 발견, 당신의 번호는 마지막 번호 다음부터). - 대상: 이번 단위의 커밋 범위(
git log로 확인)에 들어온quad-base/src/·quad-base/test/와, 그 코드가 옮겨 적은base/절. - 할 일: (a) 코드를 실제로 돌린다 —
./scripts/test.sh전체, 그리고 계약 절이 요구하는데 테스트가 없는 경로는 임시 스크립트로 직접 태운다(임시 파일은quad-base/test/에 남기지 말고 발견 본문에 인라인). (b) 코드와base/절을 한 줄씩 대조한다 — 옮기다 바뀐 것, 문서에 없는 분기, 문서엔 있는데 코드에 없는 것. (c)grep -rn "TODO(H-" quad-base/src로 남은 마커를 전수 확인한다. - 산출물:
round11.md에H-nnn으로 이어 붙인다. §2의 세 갈래로 분류해서 적되, 1번 갈래도 직접 고치지 말고 발견으로만 남긴다(수정은 메인 세션이 한다). 2번 갈래는 §4 표에 갈래 + 권고 + 권고 근거, "옛 메커니즘 복원" 표시. - 이상 없다고 확인한 자리도 §5 형식으로 적는다(다음 탐사자가 다시 파지 않게).
§6 첫 단위(공통 기반) 작업 계획 — [2026-08-28 사용자 확정] ("진행하면 될것 같아")
소스는 ROADMAP.md "공통 기반 — 반응형보다 먼저" 절 + H-97. 여기 적힌 배치
결정 셋(브랜드 인스턴스 위치 / mock 생명주기 위치 / 테스트 파일 이름)은 base/가
정하지 않은 코드 배치라 이 계획이 소스다 — 설계 결정이 아니다.
소스 (quad-base/src/)
| 파일 | 내용 | 옮겨 적는 절 |
|---|---|---|
Brand.luau |
Brand() 생성자 + 브랜드 인스턴스 전부를 이 잎 파일에(개수는 그 파일이 소스)(brand-plan.md 스니펫이 한 자리에 선언하는 그대로 — EpochBrand를 Source/Ref/GateNode가 공유하므로 타입 모듈마다 두면 순환 require) + M2 타입의 is*(isEpoch/isSource/isState/isStore/isObserver/isEffect/isBlocker/isRef/isPreRef/isPostRef/isModifier). isTag/isAttribute*/isTween/isSlot은 그 타입의 마일스톤에서 |
base/brand-plan.md "구현 — 인스턴스 브랜드" / isRef 계층 절 |
Relate.luau |
기존 파일(M1). 코드 변경 없음 — base/relate-plan.md "API"/"실제 구조" 대조 + 테스트만 |
base/relate-plan.md |
LifetimeHandle.luau |
4종 타입 시그니처 + 미주입 에러 스텁(영어, error(…, 2)) + 공유 술어 isBoundAlive는 백엔드 몫이라 여기 없음 |
base/lifecycle-pattern.md "확정" 절, module-lifecycle-plan.md 주입 절 |
Ref.luau |
최소형 — .Value/.Revision/:Set/:Callback/:WeakCallback/:Uncallback, Callbacks(강) + WeakCallbacks(weak-key), :Set 순서 값→리비전(bit32.bnot(-rev))→스냅샷 순회·함수키 dedup·thread 소진, EpochBrand+RefBrand 등록 |
base/ref-plan.md "Ref는 Epoch를 만족한다" 절 + H-128 |
Void.luau |
return function() end |
H-162, architecture.md 잎 모듈 |
init.luau |
Relate/Void/Ref/is*/생명주기 4종 스텁 재export |
H-80 |
quad-types/src/init.luau |
Quad 타입에 위 탑레벨 값 추가(Source/Store/Effect/Blocker는 각 단위에서) |
H-80/H-25 |
mock (quad-base/test/mock.luau) — H-97 4종을 이 파일 안에 installLifetime(quad)로
둔다(quad-roblox가 할 모듈 뮤테이션을 그대로 흉내; Destroying:Connect의 Connection을
Relate weak 슬롯 "gcconn"에 두고 .Connected로 판정, .Subscribed 경로는 단위 3에서
합류). 별도 파일을 안 만드는 이유: mock 백엔드가 곧 이 파일 하나다.
테스트 (quad-base/test/spec.<module>.luau) — scripts/test.sh의 glob을
smoke.* + spec.*로 넓히고, relink 뒤 luau-analyze quad-base/src도 같이 돌린다
(relink가 거짓 클린의 원인을 없애므로 analyze가 이제 의미 있다).
| 파일 | 검증하는 계약 |
|---|---|
spec.brand.luau |
register/is · 다중 태깅 · 브랜드 간 독립 · weak-key(GC 뒤 사라짐) · isRef(PreRef 등록값) == true, isPreRef/isPostRef 배타 |
spec.relate.luau |
4 메서드 · 서브테이블 lazy 생성 · WeakMap 값 GC · inst weak-key GC · 공유 메타테이블 |
spec.lifetime.luau |
미주입 스텁이 영어 메시지로 error · 주입 후 bindLifetime → canBound false/canExecute true · Destroy → 반대 · unbindLifetime 조기 해제 · 이중 바인드 게이트 모양 if not canBound then error(…, 2) |
spec.ref.luau |
초기 Revision · :Set 순서(콜백이 볼 때 이미 새 값·새 리비전) · bit32 랩(0 → 4294967295) · Callback/WeakCallback/Uncallback · fn(value, ref) · 같은 fn 양쪽 등록 시 1회 · weak 콜백 GC 뒤 침묵 · 발화 중 Uncallback 안전(스냅샷) · thread 콜백 1회 소진 · isRef/isEpoch |
spec.void.luau |
반환값 없음 · 항등(항상 같은 함수) |
smoke.init.luau(갱신) |
탑레벨 값 존재·타입 |
커밋 단위: 모듈마다 하나(구현 + spec + base/ 정정이 있으면 같은 커밋), 마지막에
test.sh/quad-types 커밋. 단위 끝 절차는 §4.