quad/.claude/session/2026-08-18-01-pre-implementation-qa-applied.md
qwreey-agent-selene d768e4cbc5
tooling: 감사 루프를 병렬 다패스에서 순차 1패스/턴으로 재설계
사용자 지침 — "시간이 걸려도 상관 없으니, 차라리 병렬 에이전트를 덜 써줘.
그냥 한 턴에 하나씩만 사용하고, 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>
2026-08-18 19:45:36 +09:00

12 KiB

2026-08-18 — 구현 전 QA 결과를 base/에 일괄 반영

요청: "pre-implementation-qa 의 적용을 수행하자."

.claude/qa-request/pre-implementation-qa-round1.md(같은 날 앞선 세션이 만든, base/ 확정 문서를 사용자에게 문항으로 재심사한 결과)의 항목을 실제 문서에 반영한 세션. 그 문서는 "여기서 정정하지 않는다, 사용자 정정 회신이 오면 반영한다"고 적혀 있었지만 각 항목에 이미 사용자 답변 원문과 논거가 붙어 있었고, 사용자가 적용을 지시했으므로 그 답변을 정정 근거로 삼아 반영했다.

먼저 사용자에게 물은 네 가지

QA 문서가 "결론 없음"으로 남겨둔 항목 중, 임의로 정하면 안 되는 것만 AskUserQuestion으로 물었다(나머지는 답변 원문이 이미 방향을 확정함):

  1. SL-3 — :List reconcile의 nil 리턴"PopOnly 확정. 다만 이름은 변경될 수 있음. 이름에 대해서는 더 생각해보아야함" → 파괴가 기본으로 되돌리고 PopOnly(가칭)를 이번 설계에 넣음.
  2. D-7 — base Fallback Handler 등록 주체quad-base 로드 시 등록으로 재역전(2026-08-14의 역전을 다시 뒤집음).
  3. N-4/RF-4 — None/nil 배열 슬롯 처리 책임"NoneHandler는 재귀만, NilHandler가 실질 담당".
  4. ST-2 파급 — 동적 키 경로"동적히는 여전히 그냥 Store.Name 하면 얻어는 짐. 타입 애러가 난다는 점인데, 이는 GetDynamic(name): Source 로 제공하는게 최선으로 보임."

반영한 것 — 성격별

(1) 그대로 구현하면 반대로 도는 것

  • S-1 canBound 방향 반전: canBound(v) == not isBoundAlive(v), 게이트는 전부 if not canBound(v) then error(...). 진원지 lifecycle-pattern.md의 (1)(2)(3) 절을 다시 쓰고, source-state-plan.md/ROADMAP.md/luau-test(README·STATUS)/ audit/gcconn-trick-verification.md까지 같은 방향으로 정정. 부수 발견: 열한 번째 세션이 이름 분리의 근거로 적은 "판정 로직도 같고 값도 항상 같다"가 무너짐 — 실제로는 서로의 부정이고, 그게 오히려 이름 분리의 명분을 강화한다는 쪽으로 절을 다시 씀.
  • RE-1 SetStrongSetWeak: relate-plan.md의 "대체하는 것" 절과 architecture.md 소스 트리 주석. 근거 문장("둘 다 존재 이유가 '안 죽는 것'이므로 strong")까지 통째로 틀렸던 것이라 근거도 교체 — 그대로 짰으면 같은 문서가 경고하는 두-Relate 상호 강참조 누수에 정확히 걸렸다.

(2) 설계가 바뀐 것

  • RF-4+N-4: Dispatch.driveNone 스킵 분기 폐기 → NoneHandler는 재귀 전담, NilHandler 신설(k=number and v==nil 말단, setLength(0)/setOffsetSource(None) 등록). 깨진 전제는 "배열 파트의 Noneprocess를 절대 안 탄다"였는데 Frame{ State<Slot|None> }이면 탄다는 것.
  • D-6 파생: Length/Offset 등록 책임이 "그 위치를 처음 매치한 Handler"에서 말단 Handler로 정정(중간 노드는 inst에 부작용을 안 가한다는 D-3 계약과 충돌했음). 같이 검토 대상이던 "모든 핸들러가 k=number일 때 처리" 안은 NilHandler가 갭을 닫아 채택 안 함 — 이건 사용자 답변에서 바로 나온 결론이 아니라 두 답변을 합친 추론이라 세션 보고에서 따로 짚었다.
  • EV-1: 이벤트 disconnect 센티널 falseNone/nil. EventHandlerv == nil에도 매치돼야 한다는 계약이 새로 생김.
  • D-7: Fallback Handler 등록 주체 재역전. InitNamespace 거부 원칙과의 양립 근거를 새로 씀 — 그 원칙이 금지한 건 사용자 수동 init남의 상태를 건드리는 top-level 부작용이지, 모듈이 자기 레지스트리를 채우는 게 아니다. archive/tag-attribute-load-time-registration-reversed.md엔 "절반 재역전" 배너를 달았다(이름 쪽 결론은 그대로 유효).
  • R-1: Ref 내부 구조가 .Callbacks 별도 테이블 + 평범한 .Value 필드로 단순화 → __index 우회 기법의 존재 이유 자체가 사라짐.
  • SL-3: :List reconcile의 nil/키 소멸은 다시 파괴, 값 교체와 PopOnly만 비파괴. State<Slot> 교체가 언마운트인 것은 그대로 유지되게 세 경로를 표로 갈랐다(:Single sugar가 교체 경로를 타므로 자동으로 안전).
  • BS-2+N-9: "이벤트 콜백 시그니처는 Luau가 검증 못 한다"가 거짓 — 사용자가 반례 코드를 직접 작성해 보여줌. onchange-plan.md가 이 전제를 근거로 쓰던 자리도 근거만 교체(결론은 유지: OnChange는 필드가 아니라 팩토리라 타입을 미리 찍어둘 자리가 없다). 겸해서 New 커링 계약과 "D는 전량 코드 생성된 순수 별칭 테이블"을 명문화.

(3) 이름/표면

  • N-8 DID(Declarative) 확정 — 코퍼스 전수 반영(네임스페이스는 D, "특수 DI 키"라는 설명 표현은 "특수 키"로 단순화). 2026-08-08부터 개명을 미뤄온 유일한 사유(한 글자 식별자의 검색성)는 "문서에서 처음 나올 때 항상 D(Declarative)로 풀어쓴다" 표기 규약으로 보완하고 architecture.md의 네이밍 케이싱 절에 4번 항목으로 넣었다. question.md의 1순위 항목은 archive/question-resolved.md로 이전.
  • N-5 Attribute.Merged(겹치면 error) / Attribute.Overridden(뒤가 이김) 둘 다 제공 — 열려 있던 "error냐 override냐"가 제3안으로 해소. 덤으로 Merged/Overridden이라는 이름 쌍의 의미가 코퍼스에서 재정렬됨 (연산의 종류 → 충돌 시 정책).

(4) 나머지 — A-3(New() 자동 스코핑이 아니라 Quad() + 코드 수정 필요), D-1(방어 가드 "죽은 코드"에 한정 추가), D-5(PreRef는 배열 우선 보장 위가 아니라 별도 pre-pass), M-3(예약 필드가 Apply 하나가 아니라 셋 + Overridden은 콜론도 가능), B-1(Brand는 무의존 — None 특수 분기 기각), R-3(ProcessedPreRef 센티널), SL-1(RefLeafHandlerk 체크 추가 + 배열 전용 근거 명문화), E-2(:Unsubscribe():Subscribe()의 짝으로 축소), N-1(FALLBACK 에러에 k 타입), N-2(타입이 방어선), N-3 (Quad.debug), N-6(SetAndDispose 후보), N-7(UI 숏핸드는 Relate로 조회), 부수 오탈자 2건(.value 케이싱, fn(value, previous) 표기).

열어둔 것 (착수 금지 게이트)

question.md 3번과 todos.md 00번이 소스 — 중간 State GC 미검증(M3), 그룹 Attribute 위치별 claim 키 설계(M10), SetAndDispose 방향(M3 전), dedup 경로의 process/retract 대칭 확인(M3 전), PopOnly 이름, Store 미선언 키의 타입 에러 실측(M0).

커밋 전 검증 — 감사자 1패스 + /code-review high

quad-doc-auditor 1패스(base 코퍼스 각도): 확실 발견 1건 — ref-plan.md가 "원래부터 빈 자리인 None여전히 두 패스 루프가 직접 건너뜀"이라고 남겨둔 문장이 같은 파일의 2026-08-18 배너와 정면 모순 (정확히 "배너는 고쳤는데 그 배너가 부정하는 본문 bullet은 안 고친" 실패 패턴). 수정 완료.

감사 비용 이슈로 나머지 각도는 중단 — 한 패스가 서브에이전트 토큰 21만/툴 호출 82회였다. 감사자 정의가 "코퍼스 전체를 신선한 맥락에서 다시 읽는다"인 데다(라이브 문서 91개, base/만 ~12,000줄) 이번 프롬프트가 바뀐 결정 16개를 교차 검증하라고 시켜서, 계획대로 4개를 돌렸으면 80만 토큰대였을 것. 다음에 큰 변경을 감사할 때는 전 코퍼스가 아니라 diff가 건드린 파일 + 그걸 인용하는 곳으로 범위를 좁혀 프롬프트할 것. (부수: /model이 opus로 보여 감사자가 opus로 도는지 의심됐는데, 정의 frontmatter는 model: sonnet이고 오버라이드도 안 넘겼다. 다만 이번 실행이 실제 sonnet이었는지는 확인 못 함 — 이 세션 트랜스크립트에 sidechain 레코드가 안 남았다. todos.md 7번의 "정의가 언제/얼마나 반영되는지 모른다"가 여전히 유효.)

사용자가 /code-review high를 직접 돌림 — 10건 전부 유효했고 전부 반영했다. 감사자가 못 잡은 것들이라 두 도구가 서로를 대체하지 않는다는 게 실측으로 드러난 라운드(감사자는 코퍼스 전체 정합성, code-review는 diff 자체의 결함):

  • [high] ROADMAP.md가 SL-3 역전을 안 따라옴unmountSlotTree를 ":List의 reconcile"이 쓴다고 그대로 적혀 있었음. M8 체크리스트를 보고 구현하면 정확히 이번에 되돌린 결함을 다시 만든다.
  • [medium] modifier-plan.md §5 / attribute-plan.md 근거 문단이 기각된 "문자열 폴백"과 "자주 쓰는 ~25개"를 근거로 계속 인용.
  • [medium] GetDynamic 콜론 메소드가 Store의 lazy __index와 충돌 — 아무 장치 없이 부르면 "GetDynamic"이라는 이름의 Source를 만들어 함수로 호출하게 됨. 예약 키가 되거나 탑레벨 함수여야 함 → 새 열린 질문.
  • [medium] ROADMAP에 이번 라운드의 새 표면이 통째로 누락 (Attribute.Overridden/Quad.debug/GetDynamic/PopOnly) → 전부 추가.
  • [medium] PopOnly 계약과 의사코드 불일치 — 키가 사라지면 홀드 중이던 요소가 파괴도 반환도 안 되고 참조만 끊김 → 새 열린 질문.
  • [low] NilHandlersetLength/setOffsetSource 호출 순서가 같은 문서의 해제 순서 계약과 반대 → 뒤집음. ProcessedPreRef/PostRef 핸들러도 같은 순서 오류가 이번 세션 이전부터 있어서 같이 고침.
  • [low] architecture.md 정정 배너가 원문을 "콜론"이 아니라 "콜백" 메서드로 오인용(정정하려는 문장의 뜻이 뒤집힘), ROADMAP 433행에 리네임 전 "대기 중/잠정 표기" 잔여, documentation-content-map.mdD 스윕에서 누락.

절차 변경 — 감사 루프를 병렬에서 순차로 (사용자 지침)

이번 라운드의 토큰 소비를 보고 사용자가 감사 절차 자체를 바꿨다:

"시간이 걸려도 상관 없으니, 차라리 병렬 에이전트를 덜 써줘. 그냥 한 턴에 하나씩만 사용하고, 0 이 나올때 까지 턴 수를 늘리는게 나아보임. 토큰을 너무 많이 소비해서 다른 작업을 못 하고, 세션 한도에 닿더라고."

conventions.md의 감사 루프 절을 그에 맞게 재작성했다 — (1) 병렬 금지, 한 턴에 하나(2026-08-16의 "최소 2개 병렬" 지침을 대체), (2) 프롬프트로 범위를 diff로 좁힐 것(전 코퍼스 스윕은 오래 안 돌렸을 때만), 라운드마다 각도를 바꿔 병렬로 얻던 폭을 턴으로 얻는다, (3) 종료 조건은 무발견 1회 (옛 "무발견 2연속"은 병렬 다패스 전제라 같이 완화), (4) 비용 때문에 중간에 멈출 때도 "몇 라운드에서 왜 멈췄는지" 반드시 보고.

같이 명문화한 것: /code-review는 감사자를 대체하지 않는다 — 이번에 감사자 1패스(1건) 뒤 /code-review high가 10건을 더 잡았고 전부 유효했다. 보는 축이 달라서다(감사자=코퍼스 전체 정합성, code-review=diff 자체의 결함). /code-review는 사용자만 호출할 수 있으므로, 큰 변경 커밋 전엔 돌릴지 물어보는 것을 관례로 넣었다.

도구/절차 메모

  • doc-check.py: 처음 돌렸을 때 ERROR 6건 — 전부 내가 절 제목을 바꾸는 바람에 다른 문서의 인용이 깨진 것과, ROADMAP blockquote 안에서 인용을 줄바꿈에 걸친 것(conventions.md가 이미 경고한 실패 모드를 그대로 밟음). 고쳐서 ERROR 0, WARN 8은 전부 이 세션 이전부터 있던 것.
  • 그 QA 문서는 지우지 않고 근거 기록으로 격하(상단 배너 교체) — 사용자 답변 원문이 그대로 남아 있어야 나중에 되짚을 수 있음.