Compare commits

...

10 commits

Author SHA1 Message Date
0942c1ca2c
research: 스크롤 최적화(quad-roblox-fastscroll)/스프링(quad-spring) 아이디어 메모 추가
서명을 위해 재커밋

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 10:09:39 +09:00
40a5daf694
tooling: 절 인용 규약 신설 + doc-check 절 참조를 ERROR 게이트로 승격
사용자 제기 — 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
2026-08-16 10:47:29 +09:00
d23d46f746
docs: 감사 루프 4라운드에서 중단(사용자 결정) + 다음 세션 핸드오버 정리
수렴 조건(무발견 2연속)은 못 채웠으나 사용자 결정으로 중단. 발견 추이
6→5→2→2이고 3·4라운드분은 이 세션 변경의 stale이 아니라 코퍼스에 오래
있던 일반 부채(개수 하드코딩, 날짜 없는 시한부 주장)라, 계속 돌리면 수렴이
아니라 옛 부채를 끝없이 캐는 쪽이 된다는 판단. 이 세션 변경분 자체는
안정적(4라운드 설계 코퍼스 각도에서 확실 발견 0건).

todos.md 7번에 중단 사유와 "이번 미수렴 때문에 이어서 돌릴 필요 없음"을
명시하고, 세션 로그에 다음 세션 핸드오버 절(§18) 추가.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 04:05:05 +09:00
01c4abf727
docs: 감사 4라운드 반영 + const는 HUMAN_TODO로 이관(사용자 결정)
사용자 결정 — 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>
2026-08-16 04:01:29 +09:00
ebb1fd0f5a
docs: 감사 3라운드 반영 — agent-memory가 재재정정을 안 따라온 사각지대
두 감사자가 독립적으로 같은 확실 발견: 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>
2026-08-16 03:50:38 +09:00
e0b6c992fa
docs: '감사자가 opus' 관측은 뷰잉 이슈로 해소 — 최상단 배너는 세션 헤더
서브에이전트 뷰 최상단의 '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>
2026-08-16 03:48:49 +09:00
1935dd4670
docs: "커밋된 HEAD에서 읽힌다"도 반증 — 가설 세우지 않고 관측표만 남김
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>
2026-08-16 03:43:38 +09:00
3721692ed4
chore: agent-memory 추적 시작 — 커밋 전 노출 스캔 + 낡은 결론 정정
사용자 결정으로 .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>
2026-08-16 03:38:07 +09:00
07c77e5a5a
docs: 감사 루프 재설계(워크플로 폐기) + 정의 파일은 커밋된 HEAD에서 읽힘으로 재정정
사용자 결정 반영:
- 감사 루프를 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>
2026-08-16 03:32:09 +09:00
134379632f
docs: 스냅샷 주장 축소 — 워크플로는 name=스냅샷/scriptPath=실시간, 재감사 누락 명시
직전 커밋(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>
2026-08-16 03:05:40 +09:00
46 changed files with 1631 additions and 365 deletions

View file

@ -11,7 +11,7 @@
| 파일 | 무엇이 들어있나 |
|---|---|
| `conventions.md` | 언어/모델 관례 + **작업 방식**(핸드오버 체크리스트, `doc-check.py`, `quad-doc-auditor`, SAFETY 준수 등 에이전트가 따라야 할 절차 전부) |
| `conventions.md` | 언어/모델 관례 + **설계 원칙** + **작업 방식**(핸드오버 체크리스트, `doc-check.py`, `quad-doc-auditor`, SAFETY 준수 등 에이전트가 따라야 할 절차 전부) |
| `project-context.md` | 이 프로젝트가 뭔지 + 계획 문서 구조(폴더별 성격 요약 — 상세 색인은 이 README가 소스) |
| `todos.md` | 지금 할 일(우선순위순). 가장 자주 바뀜 |
| `session-summary.md` | 세션별 2~4줄 요약 색인. **`@import` 안 됨(의도적)** — 이만한 분량을 매 세션 컨텍스트에 올릴 이유가 없어 온디맨드로 둠, 선행 맥락이 필요할 때 grep해서 열 것. 자동생성 전환 예정(`research/doc-include-plan.md`) |
@ -28,11 +28,10 @@
| `archive/` | 완료 + 사용자가 실사용/실기기로 직접 검증까지 마침 (구현 대상). **[2026-08-06 확장]** 완전히 뒤집힌 설계 결정을 원문+역전 이유+diff와 함께 보존하는 용도로도 사용(제목 `[역전됨]` — 한 번 확정했다가 뒤집힌 것) — 더 이상 능동적으로 참고 안 해도 되지만(토큰 낭비 방지 위해 `base/`/`research/`에서 뺌) `quadnomicon` 소재로는 나중에 쓸 수 있음. **[2026-08-07 확장]** 후보였다가 채택 안 된 것(확정한 적 없이 검토 후 기각)도 같은 방식으로 보존, 제목은 구분을 위해 `[기각됨]``[역전됨]`과 의미가 다르므로 혼동하지 말 것. **[2026-08-07 세 번째 확장]** 설계 반전/기각과 별개로, 에이전트가 문서 작성 중 스스로 낸 개념 혼동을 정정한 이력은 `[에이전트 실수]` 태그로 `agent-mistake.md` 하나에 모음(`.claude/session-summary.md`/`session/` 로그와의 중복 방지) |
| `feedback/` | 실사용 피드백을 정리한 긴 로그 — **[2026-08-16 기준] 폴더 자체가 아직 없음**(구현 시작 전, 첫 피드백이 생길 때 만들면 됨). `qa-request/`는 빈 폴더로 존재하지만 여긴 그것도 아직 아님 |
| `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). **[2026-08-13 열세 번째 세션]** 그 0-Y가 해소되며 `review-required/`**비었음** — 계약은 유지 확정, 남은 건 Luau 자체 한계라 `base/typing-limits.md`가 담당. **`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` 캐비엇을 지우고 이 문서는 근거로 남김. **현재 6개**: `luau-test-first-run-2026-08-13.md`(첫 실측 라운드 전체 — 런타임 12개 통과, 구 `question.md` 0-Y의 1차 근거. **단 이 문서의 "콜백이 raw 값을 받으면 완전 클린" 판정은 아래 `type-recursion-issue/`가 뒤집었음**), `gcconn-trick-verification.md`(사용자가 Studio에서 직접 돌린 gcconn 트릭 부분 확인 — `10`의 A 섹션 앞부분만. **[2026-08-14 다섯 번째 세션, 열한 번째 세션에 `canBound` 재도입 반영해 재갱신]** 실측된 사실 자체는 그대로 유효하고 `value` 단독 1-인자 재정정으로 오히려 더 중요해졌음 — 이중 바인딩 게이트(`canBound`)/emit 게이팅(`canExecute`)/재바인딩 허용/`value` 쪽 복사 gcconn 판정/Instance userdata 동일성/B/C가 미확인), **`type-recursion-issue/`**(**[2026-08-13 열세 번째 세션 신설]** 0-Y 재실측 전체 — `REPORT.md` + `spikes/`(개수는 폴더가 소스). 다른 audit 기록과 달리 **스크립트를 같이 둠**: 이 건의 근거가 "여러 formulation을 서로 대조한 것"이라 개별 파일을 직접 돌려야 판정이 재현되기 때문. 결론은 `base/typing-limits.md`로 승격됨), `fallback-xpcall-verification.md`(**[2026-08-14 신설]** `base/fallback-plan.md``Traceback` 메커니즘 전부 확인 — 클로저 업밸류 배선/중첩 스택 캡처/`err: any`/`error(msg)` 위치 접두 10개 검증 전부 통과. 스크립트 1개뿐이라 재현용으로 같이 둠: `fallback-xpcall-spike.luau`), **`type-recursive-issue-with-typeof/`**(**[2026-08-15 신설]** 사용자가 발견한 `typeof(named fn)` 간접참조가 0-Y(재귀 제네릭 반환 leak)를 실제로 우회하는지 실측 — `REPORT.md` + `spikes/`. 결론: 인라인 대신 이름 붙은 함수 + `typeof`로 선언하면 LHS 명시 없이도 다운스트림이 안전해짐(체이닝 50단·타입 변경·중첩 self 호출까지 확인), `typing-limits.md` §1 ③으로 승격. 부수적으로 `setmetatable` 확장 시도에서 quad와 무관한 Luau 0.733 솔버 버그(모순 진단 두 개 동시 발생) 발견, 채택 안 함. `luau-test/16`(type function으로 `Store<T>` 레코드 필드 합성) 복구도 이 조사 중 완료 — API 버전 드리프트였을 뿐 설계 문제 아니었음, `typing-limits.md` §5 승격), **`type-recursive-issue-try-callback/`**(**[2026-08-15 신설]** 콜백 파라미터 무주석 추론을 뚫을 방법이 정말 없는지 type function/메타테이블/오버로드/제네릭 디폴트 등 전방위로 재시도 — `REPORT.md` + `spikes/`(개수는 폴더가 소스 — 최초 라운드 + `/code-review high`가 이중 꺾쇠 명시적 제네릭 인스턴스화를 안 시도했음을 지적해 추가된 후속 조사 라운드로 구성). 결론: quad의 `state:Compute(fn)` 단일 호출 모양을 유지한 채로는 여전히 안 됨. 발견 셋 — (1) 근본 원인이 재귀 자기참조가 아니라 "제네릭 콜백 인자 전반에 컨텍스트 타입 전파가 안 됨"이라는 게 더 정확함(재귀 없는 최소 사례로도 재현), (2) `T`를 명시 중간 변수로 먼저 고정하거나 재사용 가능한 monomorphize 헬퍼를 거치면 실제로 추론이 살아나지만 둘 다 단일 콜론 호출을 2단계 체인으로 바꿔야만 해서 §0 대전제로 채택 안 함, (3) 이중 꺾쇠 명시 인스턴스화(`Compute<<T,U>>(fn)`)는 leaf 호출에선 sound하게 성립하지만(spurious 진단 원인도 규명 — read-only/read-write 가변성 불일치) 매 호출 T/U 전부 명시 필요 + 중첩 self 호출 여전히 실패라 순손해로 채택 안 함) |
| `tools/` | **[2026-08-13 아홉 번째 세션 신설]** 코퍼스 기계 점검 — `doc-check.py`가 깨진 파일/절 참조, README 색인 누락, 날짜 없는 시한부 주장("아직 안 돌려봄" 등), 미반영 ⚠️ 배너를 한 번에 훑음. **중대 변경 후 커밋 전에 돌릴 것**(`python3 .claude/tools/doc-check.py`) — 수동 감사에서 나온 발견의 대부분이 이 종류였고, 실제로 문서를 쪼개다 잘못 옮긴 참조를 이게 잡아냄. ERROR는 고치고 WARN은 판단 대상 |
| `agents/` | **[2026-08-16 신설]** 프로젝트 서브에이전트 정의(`.claude/agents/*.md`, Claude Code 표준 위치). 현재 `quad-doc-auditor.md` 하나 — `doc-check.py`가 못 잡는 의미론적 stale/모순(본문 문장이 뒤집힌 결정을 여전히 서술, 개수/목록 이중 소스 드리프트 등)을 신선한 맥락에서 찾는 읽기 전용 감사자. 중대 변경 커밋 전에 위임하는 게 기본(`.claude/conventions.md` "작업 방식" 참고). `tools/`의 기계 점검과 짝을 이루는 의미론적 점검 계층 |
| `agent-memory/` | **[2026-08-16 신설]** 서브에이전트가 라운드를 넘겨 유지하는 영속 메모리(`agent-memory/<에이전트 이름>/MEMORY.md`가 색인). 지금은 `quad-doc-auditor/` 하나 — 코퍼스 구조, 반복되는 실패 패턴 등을 기억해 감사 라운드마다 처음부터 파악하지 않게 함. **사람이 손으로 채우는 문서가 아니라 에이전트가 스스로 쓰는 것**이지만, `.gitignore` 대상이 아니라 커밋하면 코퍼스 일부가 되고 `doc-check.py` 검사 대상에도 들어감(감사 대상이기도 하다는 뜻 — 여기 적힌 주장도 stale해질 수 있음). **[2026-08-16 기준] 아직 커밋 안 됨(untracked)** — 추적할지 `.gitignore`로 뺄지는 사용자 판단 |
| `workflows/` | **[2026-08-16 신설]** Claude Code Workflow 정의. 현재 `quad-handover-audit.js` 하나 — `quad-doc-auditor` 단일 패스는 비결정적이라 매번 다 잡는다는 보장이 없어서, 라운드마다 병렬로 여러 번 감사+파일별 반영을 새 발견이 없는 라운드가 연속으로 나올 때까지 반복해 수렴시키는 다회·병렬 감사 루프(라운드당 패스 수·수렴 조건·최대 라운드는 튜닝 대상 상수라 여기 숫자를 적지 않음 — 소스는 `quad-handover-audit.js` 상단). "핸드오버 준비하고 커밋해" 류 요청 시 자동으로 먼저 돌림(`.claude/conventions.md` "작업 방식" 참고), 실제 `git commit`은 워크플로 밖 메인 세션이 함 |
| `audit/` | **[2026-08-13 신설]** `luau-test/` 등 스파이크를 실제로 돌려본 뒤 "무엇이 확인됐고 무엇이 아직 안 됐는지"를 기록하는 곳 — 스크립트/계획 자체가 아니라 **실측 결과**만 다룸. base/luau-test와 달리 부분 확인(일부만 통과)도 있는 그대로 기록, 완전히 해소되면 관련 `base/`/`luau-test/README.md` 캐비엇을 지우고 이 문서는 근거로 남김. **개수는 폴더가 소스**(여기서 세지 않음): `luau-test-first-run-2026-08-13.md`(첫 실측 라운드 전체 — 런타임 12개 통과, 구 `question.md` 0-Y의 1차 근거. **단 이 문서의 "콜백이 raw 값을 받으면 완전 클린" 판정은 아래 `type-recursion-issue/`가 뒤집었음**), `gcconn-trick-verification.md`(사용자가 Studio에서 직접 돌린 gcconn 트릭 부분 확인 — `10`의 A 섹션 앞부분만. **[2026-08-14 다섯 번째 세션, 열한 번째 세션에 `canBound` 재도입 반영해 재갱신]** 실측된 사실 자체는 그대로 유효하고 `value` 단독 1-인자 재정정으로 오히려 더 중요해졌음 — 이중 바인딩 게이트(`canBound`)/emit 게이팅(`canExecute`)/재바인딩 허용/`value` 쪽 복사 gcconn 판정/Instance userdata 동일성/B/C가 미확인), **`type-recursion-issue/`**(**[2026-08-13 열세 번째 세션 신설]** 0-Y 재실측 전체 — `REPORT.md` + `spikes/`(개수는 폴더가 소스). 다른 audit 기록과 달리 **스크립트를 같이 둠**: 이 건의 근거가 "여러 formulation을 서로 대조한 것"이라 개별 파일을 직접 돌려야 판정이 재현되기 때문. 결론은 `base/typing-limits.md`로 승격됨), `fallback-xpcall-verification.md`(**[2026-08-14 신설]** `base/fallback-plan.md``Traceback` 메커니즘 전부 확인 — 클로저 업밸류 배선/중첩 스택 캡처/`err: any`/`error(msg)` 위치 접두 10개 검증 전부 통과. 스크립트 1개뿐이라 재현용으로 같이 둠: `fallback-xpcall-spike.luau`), **`type-recursive-issue-with-typeof/`**(**[2026-08-15 신설]** 사용자가 발견한 `typeof(named fn)` 간접참조가 0-Y(재귀 제네릭 반환 leak)를 실제로 우회하는지 실측 — `REPORT.md` + `spikes/`. 결론: 인라인 대신 이름 붙은 함수 + `typeof`로 선언하면 LHS 명시 없이도 다운스트림이 안전해짐(체이닝 50단·타입 변경·중첩 self 호출까지 확인), `typing-limits.md` §1 ③으로 승격. 부수적으로 `setmetatable` 확장 시도에서 quad와 무관한 Luau 0.733 솔버 버그(모순 진단 두 개 동시 발생) 발견, 채택 안 함. `luau-test/16`(type function으로 `Store<T>` 레코드 필드 합성) 복구도 이 조사 중 완료 — API 버전 드리프트였을 뿐 설계 문제 아니었음, `typing-limits.md` §5 승격), **`type-recursive-issue-try-callback/`**(**[2026-08-15 신설]** 콜백 파라미터 무주석 추론을 뚫을 방법이 정말 없는지 type function/메타테이블/오버로드/제네릭 디폴트 등 전방위로 재시도 — `REPORT.md` + `spikes/`(개수는 폴더가 소스 — 최초 라운드 + `/code-review high`가 이중 꺾쇠 명시적 제네릭 인스턴스화를 안 시도했음을 지적해 추가된 후속 조사 라운드로 구성). 결론: quad의 `state:Compute(fn)` 단일 호출 모양을 유지한 채로는 여전히 안 됨. 발견 셋 — (1) 근본 원인이 재귀 자기참조가 아니라 "제네릭 콜백 인자 전반에 컨텍스트 타입 전파가 안 됨"이라는 게 더 정확함(재귀 없는 최소 사례로도 재현), (2) `T`를 명시 중간 변수로 먼저 고정하거나 재사용 가능한 monomorphize 헬퍼를 거치면 실제로 추론이 살아나지만 둘 다 단일 콜론 호출을 2단계 체인으로 바꿔야만 해서 §0 대전제로 채택 안 함, (3) 이중 꺾쇠 명시 인스턴스화(`Compute<<T,U>>(fn)`)는 leaf 호출에선 sound하게 성립하지만(spurious 진단 원인도 규명 — read-only/read-write 가변성 불일치) 매 호출 T/U 전부 명시 필요 + 중첩 self 호출 여전히 실패라 순손해로 채택 안 함) |
| `tools/` | **[2026-08-13 신설, `session/2026-08-13-09-structure-and-guardrails.md`]** 코퍼스 기계 점검 — `doc-check.py`가 깨진 파일/절 참조, README 색인 누락, 날짜 없는 시한부 주장("아직 안 돌려봄" 등), 미반영 ⚠️ 배너를 한 번에 훑음. **[2026-08-16]** 절 참조는 WARN이 아니라 **ERROR** — 판정 규칙은 `conventions.md`의 "절 인용 규약"이 소스. **중대 변경 후 커밋 전에 돌릴 것**(`python3 .claude/tools/doc-check.py`) — 수동 감사에서 나온 발견의 대부분이 이 종류였고, 실제로 문서를 쪼개다 잘못 옮긴 참조를 이게 잡아냄. ERROR는 고치고 WARN은 판단 대상 |
| `agents/` | **[2026-08-16 신설]** 프로젝트 서브에이전트 정의(`.claude/agents/*.md`, Claude Code 표준 위치). 현재 `quad-doc-auditor.md` 하나 — `doc-check.py`가 못 잡는 의미론적 stale/모순(본문 문장이 뒤집힌 결정을 여전히 서술, 개수/목록 이중 소스 드리프트 등)을 신선한 맥락에서 찾는 읽기 전용 감사자. 중대 변경 커밋 전에 위임하는 게 기본 — **여러 개를 병렬로 돌리고 수정은 메인 세션이 일괄로 하는 루프**(패스 수는 여기 안 적음 — 소스는 conventions.md)이며 절차는 `.claude/conventions.md` "작업 방식" 절이 소스(**[2026-08-16]** 이 루프를 담던 `workflows/quad-handover-audit.js`는 토큰 과다·픽스 에이전트발 부정확 서술·사용자 질의 불가 때문에 폐기됨). `tools/`의 기계 점검과 짝을 이루는 의미론적 점검 계층 |
| `agent-memory/` | **[2026-08-16 신설]** 서브에이전트가 라운드를 넘겨 유지하는 영속 메모리(`agent-memory/<에이전트 이름>/MEMORY.md`가 색인). 지금은 `quad-doc-auditor/` 하나 — 코퍼스 구조, 반복되는 실패 패턴 등을 기억해 감사 라운드마다 처음부터 파악하지 않게 함. **사람이 손으로 채우는 문서가 아니라 에이전트가 스스로 쓰는 것**이지만, `.gitignore` 대상이 아니라 커밋하면 코퍼스 일부가 되고 `doc-check.py` 검사 대상에도 들어감(감사 대상이기도 하다는 뜻 — 여기 적힌 주장도 stale해질 수 있음). **[2026-08-16 확정] 커밋해서 추적함**(사용자 결정). 사용자 논거: "실 기록이고 디펜던시도 아니고, 어차피 `SAFETY.md`에 따라 구현 시점에는 컨테이너에서 개발되며 다른 프라이빗 git에 올라가고 검토 후 머징되는거라, 문제되는 메모리 있으면(환경 노출 등) 사람이 감사처리 마지막으로 함. 결국 프로젝트 사이드 기록이고 같이 올려지는게 맞는게, 개발 환경이 다수라서 필요해보임" — 즉 **개발 환경이 여러 개라 메모리가 따라다녀야 하고**, 노출 위험은 머지 전 사람 검토가 최종 방어선. 커밋하는 쪽이 정해졌으니 여기 내용도 감사 대상이다(에이전트가 자기 메모리에 stale한 결론을 남기는 일이 실제로 있었음 — 2026-08-16에 폐기된 워크플로를 살아있는 것처럼 서술한 2건이 감사로 잡힘) |
| `session/` | **[2026-08-11 신설]** 세션별 상세 로그 원문(시행착오·정정 전 서술 포함, `quadnomicon` 개발로그 소재용) — 루트 `CLAUDE.md`가 3196줄까지 불어나 성능 저하를 유발해서 분리함. 파일명 `YYYY-MM-DD-NN-slug.md`, `.claude/session-summary.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`) — 읽기 전용 리서치 소스, 여기 내용을 옮기지 말고 항상 원본 그대로 유지 |
@ -67,7 +66,7 @@
| `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`를 branded 타입 전부로 일반화(`isPostRef` 포함, 2026-08-14 아홉 번째 세션). 동작/구현은 확정, **이름 `Brand` 자체만 용어 정리 대기**(`question.md` 1번). **분리는 순수 이동** |
| `tween-plan.md` | **[2026-08-12 세션, `research/`에서 승격]** 값-레벨 `Tween<T>` 래퍼(PropertyHandler가 소비, 구 특수 bind key 모델은 `archive/tween-special-bind-key-reversed.md`). 3-상태 릴레이션 슬롯(`{Tween,Value}\|true\|nil`), `T'=T\|Tween<T>` 타입 치환. 옵션 값 모양은 `Info: TweenInfo?` 우선+편의 필드 폴백, override는 `Tween.Cancel`(기본)/`Tween.Finish` 2값. `Animate(info)``Tween` opts를 `T\|State<T>`로 받아 `:Apply`로 꽂는 sugar. 자연완료 시 per-instance 북키핑은 정리 안 해도 됨으로 확정(목표값 도달 상태라 부작용 없음, Completed 이벤트 구독 장치는 오버엔지니어링으로 판단). `initValue`는 사용자가 직접 처리(에이전트 범위 제외) |
| `fallback-plan.md` | **[2026-08-14 세션, `research/`에서 승격]** `Fallback`/`Traceback` — 컴포넌트 함수를 감싸 에러 시 플레이스홀더를 그려주는 순수 슈가(`additional-primitives-plan.md`의 "Error Boundary빈 자리 아님" 결론 위에 얹힘). `Fallback``pcall` 기반(trace 없음), `Traceback``xpcall`+`debug.traceback` 기반(trace 항상 있음) — 플래그 대신 별도 함수로 분리(`Ref`/`PreRef`와 같은 패턴). `err: any`(Lua `error()`가 임의 값을 던질 수 있음, `error(msg)` 기본 호출의 위치 접두 캐비엇 포함) 확정. 패키지는 `quad-base`, 이름 확정. 메커니즘 실측은 `audit/fallback-xpcall-verification.md`. 구현 우선순위는 형제 백로그(`quad-mock`/`quad-debug`/`Operator`)와 동급, 맨 뒤 |
| `fallback-plan.md` | **[2026-08-14 세션, `research/`에서 승격]** `Fallback`/`Traceback` — 컴포넌트 함수를 감싸 에러 시 플레이스홀더를 그려주는 순수 슈가(`additional-primitives-plan.md`의 "Error Boundary" 절이 내린 "빈 자리 아님" 결론 위에 얹힘). `Fallback``pcall` 기반(trace 없음), `Traceback``xpcall`+`debug.traceback` 기반(trace 항상 있음) — 플래그 대신 별도 함수로 분리(`Ref`/`PreRef`와 같은 패턴). `err: any`(Lua `error()`가 임의 값을 던질 수 있음, `error(msg)` 기본 호출의 위치 접두 캐비엇 포함) 확정. 패키지는 `quad-base`, 이름 확정. 메커니즘 실측은 `audit/fallback-xpcall-verification.md`. 구현 우선순위는 형제 백로그(`quad-mock`/`quad-debug`/`Operator`)와 동급, 맨 뒤 |
| `lifecycle-hooks-plan.md` | **[2026-08-14 아홉 번째 세션, `research/`에서 승격]** 생명주기 훅 슈가 `OnCreated`/`OnRendered`/`OnDestroyed` — 각각 `PreRef():Callback(fn)`/`PostRef():Callback(fn)`/`Effect(function() return fn end)`를 반환하는 **순수 팩토리 함수**라 새 타입/Dispatch 개념이 전혀 안 생김(호출 즉시 평가돼 기존 인스턴스로 사라짐), 여러 개 나란히 등록도 자연 지원(단 **같은 계열끼리의 순서는 미보장**). 마지막 열린 항목이던 `OnRendered`는 사용자가 **채택 확정** — 메커니즘은 `PostRef`(`base/ref-plan.md`), 원래 열어뒀던 (a)/(b)/(c) 중 **(a)**. 캐비엇: `OnRendered`는 서브트리 완성은 보장하지만 **이 인스턴스가 부모에 붙기 전**에 불림(React `componentDidMount`와 다름) — 문서화 필수. 패키지 `quad-base` 확정. **[2026-08-14 열 번째 세션]** `dispose()` 범위(0-B)가 `Slot`+`Instance`로 좁혀지고 `Observer`/`Effect`는 제외되는 쪽으로 확정되며 `OnDestroyed` 이름 재검토 조건이 발동 없이 종결 — `OnDestroyed`가 최종 이름, 용어 대기열에서도 제외 |
## `reference/` — 온디맨드 참고 자료 (2026-08-07 신설)
@ -93,6 +92,8 @@
| `quad-recursive-acronym.md` | **[2026-08-14 신설]** GNU/WINE류로 `Quad`를 재귀 약어화하는 카피 브레인스토밍 — 설계 결정도 착수 게이팅도 아니고 나중에 README.md 헤딩 등에 쓸 캐치프레이즈 후보 모음. 자학 개그 방향(기각)과 지연평가/재귀·커링/펑터/클로저를 자랑하는 방향(채택 후보, 미확정) 정리 | 하 — 카피 소재, 설계 상의 필요 없음. 사용자가 최종 문구 고르면 반영 |
| `v1-compat-plan.md` | v1 하위호환(compat) 레이어 — `quad-roblox-v1-compat` 패키지, v2→v1 단방향 브리지(`state:Observer()`+v1 프로퍼티 재대입), v2-in-v1/v1-in-v2 두 임베딩 방향의 기술 규칙까지 확정. quad2-try의 `quad-compat`은 빈 폴더로 실제 시도된 적 없었음을 확인 | 하 — Slot이 foreign Instance를 어떻게 다루는지만 Slot 코어 구현 시점까지 미결 |
| `doc-include-plan.md` | **[2026-08-14 신설]** 문서 stale 감소용 include 도구 `doc-include.py`(가칭) — 원본 파일에 `<!--#summary-->` 류 마커로 요약 구간을 표시해두면 인용하는 문서가 그 구간을 기계적으로 추출해 붙여넣게 하는 도구. `doc-check.py`(사후 탐지)와 짝을 이루는 사전 차단 장치. AsciiDoc tagged include/markdown-magic이 선례, build vs buy 검토 후 Python 표준 라이브러리로 직접 제작(~100줄) 채택. 파일럿은 `.claude/session-summary.md``.claude/session/*.md` 요약 마커부터(CLAUDE.md 분할로 목적지가 "통째로 생성되는 파일"이 돼 단방향 생성으로 단순화됨) | 하 — M0/설계 게이트와 무관한 메타 도구. **[2026-08-16 기준]** 플랜 초안 단계, 열린 질문 미해소(소스: 이 문서의 "열린 질문" 절) |
| `fastscroll-plan.md` | **[2026-08-18 신설]** 사용자 아이디어 메모 — 완전 외부 패키지 `quad-roblox-fastscroll`(리스트/그리드 내 상대 위치 계산으로 움직일 요소만 갱신, 배경의 빈 공간만 스크롤). 가상 레이아웃 유틸이 선행 요구사항으로 보임. 설계 논의 전, 아직 아이디어 단계 | 최하 — 사용자가 "quad가 잘 작동하게 될 때" 직접 검토하겠다고 후순위 지정. 선행 확인 필요 사항(`Visible=false`일 때 `AbsoluteSize`/`AbsolutePosition` 갱신 여부)은 Roblox Studio 실측 필요 |
| `spring-plan.md` | **[2026-08-18 신설]** 사용자 아이디어 메모 — 스프링 물리 기반 지속 업데이트 프리미티브(`quad-spring`), 이전 상태와 비교해 스프링 연산을 수행하는 중간 핸들러. 참고 구현 [qwreey/spring.lua](https://github.com/qwreey/spring.lua) 사용 가능 여부 확인 필요. 확정 `Tween<T>` 모델과는 별개 트랙 — `quad-base``onStep`류 후킹 인터페이스로 얹을지, 엔진별 `quad-roblox-spring`으로 각자 구현할지, `Source<number>` 확장 primitive로 둘지 미정 | 최하 — "모든게 완성된 후, 별도 모듈로 분화"라고 사용자가 직접 명시 |
## `archive/` — 완료됐거나 완전히 뒤집힌 것, 능동 참고 불필요

View file

@ -0,0 +1,7 @@
# Memory Index
- [Self-referential line/count claims break on commit](feedback_self_referential_counts.md) — a doc stating its own line count/entry count is wrong even at commit time; fix by deleting the number, not updating it.
- [Recurring failure pattern: second instance in same file survives the fix](feedback_recurring_failure_pattern.md) — when a claim flips, grep finds multiple files but check *within* each file too; a second mention in the same file often survives a banner fix.
- [quad corpus structure and audit scope](project_quad_corpus_structure.md) — layout of .claude/, what's always-loaded vs on-demand, what's in/out of audit scope.
- [My own agent-memory dir is itself an audit target](project_agent_memory_self_reference_risk.md) — quad-doc-auditor.md's claims about its own tool/memory behavior can go stale; check them too, and verify MEMORY.md indexes all files (parallel passes can race).
- [Definition freshness is unknown](project_agent_prompt_caching_bug_reproduced.md) — the text I receive sometimes lags disk and has matched no commit at all; three explanations were tried and two refuted, so report a mismatch as a concrete comparison, never as a mechanism claim.

View file

@ -0,0 +1,44 @@
---
name: recurring-failure-pattern
description: The specific bug class this corpus keeps producing — check for it every audit round
metadata:
type: feedback
---
**규칙**: 결정이 뒤집히면(예: `X 폐기``X 재도입`), 그 사실을 언급하는
문서/문단이 코퍼스 전체에 흩어져 여러 곳 있다. 대부분은 정정 배너가
붙지만, **정확히 같은 클레임의 다른 인스턴스 하나가 파일 안에(다른 섹션에)
빠지는 경우가 실제로 반복된다** — 배너가 붙은 곳만 보고 "됐다"고 판단하지
말고, 같은 키워드로 파일 전체 재검색해서 두 번째/세 번째 인스턴스를 찾을
것.
**실제 사례(2026-08-16 핸드오버 감사)**: `canBound`가 2026-08-14 다섯
번째 세션에 폐기됐다가 열한 번째 세션에 재도입됨. 코퍼스 대부분(README.md,
todos.md, base/lifecycle-pattern.md, base/source-state-plan.md,
base/effect-plan.md, luau-test/STATUS.md 등)은 정정을 반영했는데,
**`.claude/question.md` 한 파일 안에서도** 3번 항목(`canExecute` 이름
정리, line 55-71)은 정정 배너가 있는데 1번 섹션 인트로(line 36-38, "이미
확정된 이름" 목록 옆 괄호 설명)는 옛 "canBound 폐기" 주장을 그대로 갖고
있었음. **Why**: 같은 파일 안에서도 같은 사실이 두 곳에 서술되면 한쪽만
고쳐지는 게 실제로 일어난다 — "이 파일은 이미 정정됐다"고 파일 단위로
판단하지 말 것.
**How to apply**: 뒤집힌 결정의 키워드(용어명, 함수명 등)를 코퍼스 전체
`grep -rn`으로 훑을 때, 결과가 여러 건이면 각 파일 안에서도 발견이 하나가
아닐 수 있다는 전제로 그 파일 전체를 다시 훑을 것 — 첫 매치에 정정 배너가
있다고 같은 파일의 다른 매치도 안전하다고 가정하지 말 것. `question.md`처럼
자주 편집되고 섹션이 여러 개인 파일(용어 정리 목록 + 낮은 우선순위 질문
목록 등)이 특히 취약.
**변형 — 형제 파일 간에도 같은 일이 난다(2026-08-16 두 번째 라운드
발견)**: "N개"류 self-referential 카운트를 없애는 라운드에서, 같은
카운트를 인용하던 형제 문서 5곳(`luau-test/STATUS.md`,
`base/typing-limits.md`, `README.md`, `audit/type-recursion-issue/REPORT.md`
자신, `base/source-state-plan.md`)은 전부 "개수는 `spikes/` 폴더가
소스"로 고쳐졌는데, 같은 값을 인용하던 `audit/luau-test-first-run-
2026-08-13.md:19`(스파이크 44개) 하나만 그대로 남아 있었음 — 심지어
같은 파일 안 line 201엔 "개수의 소스는 항상 STATUS.md"라는 정확히 같은
교훈이 다른 카운트에 대해 이미 적용돼 있었는데도. **적용**: 카운트/목록을
탈-하드코딩하는 라운드에서는 "그 폴더/그 개념을 언급하는 모든 파일"을
grep으로 찾아 전수 확인할 것 — 수정된 파일 목록에 없는 형제 인용이
꼭 하나는 남아 있다.

View file

@ -0,0 +1,43 @@
---
name: feedback-self-referential-counts
description: 문서가 자기 자신의 줄 수/개수를 본문에 하드코딩하면 그 문서를 쓴 커밋 시점부터 이미 틀려 있는 경우가 실제로 반복됨 — 감사 시 최우선으로 확인할 것
metadata:
type: feedback
---
quad `.claude/` 코퍼스에서 어떤 문서가 **자기 자신의 줄 수나 자기 자신이
포함된 색인의 항목 개수**를 본문에 숫자로 적어두면(예: "CLAUDE.md는
39줄짜리 진입점", "session-summary.md는 1231줄", "세션별 요약 91개"),
이런 숫자는 **그 문서를 커밋한 바로 그 시점부터 이미 틀려 있는 경우가
실제로 확인됨**(2026-08-16 CLAUDE.md 4분할 커밋 `8aeec76`에서: 커밋 당시
CLAUDE.md 실제 43줄인데 "39줄"이라 적고, session-summary.md 실제 1276줄인데
"1231줄"이라 적고, session/ 실제 92개인데 "91개"라 적음 — 전부 3곳 이상에
중복 서술돼 있었음).
**Why**: 이런 숫자를 계산하는 시점(사람이 헤더 문구를 쓰는 시점)과 그
숫자가 설명하는 파일이 최종 커밋되는 시점 사이에 다른 편집(체크리스트
항목 추가, 배너 삽입 등)이 끼어들기 쉬움 — 자기 자신을 설명하는 숫자는
구조적으로 닭-달걀 문제라 사람이 아무리 조심해도 어긋난다.
`conventions.md`의 "개수·목록·상태는 소스를 하나만 둘 것" 원칙이 일반적으로는
"여러 문서에 같은 값을 반복 적지 말 것"을 말하지만, 이 변형(자기 자신을
설명하는 숫자)은 그 원칙조차 지켜도(값이 파일들 사이에 일치해도) 실제
파일 상태와는 다를 수 있다는 게 요점 — 일치 여부가 아니라 **애초에
하드코딩하지 말아야 함**.
**How to apply**: 감사 라운드에서 "이 문서가 N줄이다"/"이 폴더에 N개
있다" 류 서술을 보면, `wc -l`/`ls | wc -l` 등으로 실측 대조를 최우선
순위로 돌릴 것 — `doc-check.py`는 이 클래스를 못 잡음(정규식이 숫자
드리프트를 모름). 발견 시 fix 제안은 "숫자를 최신값으로 갱신"이 아니라
**"숫자 자체를 서술에서 빼고 정성적 표현으로 대체"**할 것 — 갱신은
다음 편집에서 또 깨짐(같은 실수의 재발), 삭제만이 구조적으로 안전.
**변형(2026-08-16 재확인)**: 자기 자신이 아니라 **다른(계속 자라는)
폴더를 가리키는 카운트**도 같은 클래스임 — 예:
`audit/luau-test-first-run-2026-08-13.md`가 "재실측 전문(스파이크 44개):
audit/type-recursion-issue/"라고 적어둔 것. 같은 라운드에 다른 5곳
(`typing-limits.md`/`README.md`/`STATUS.md`/`REPORT.md` 자기 자신 등)에서
이 "44개"를 정확히 이 이유로 지웠는데 이 파일 하나만 놓쳤고, 실제 폴더는
그새 48개로 자라 있었음(`ls spikes/ | wc -l`로 확인). **감사 시 절차**:
같은 라운드 diff에서 특정 하드코드 숫자를 여러 곳에서 지운 패턴을 보면,
그 숫자 문자열로 코퍼스 전체를 다시 grep해서 빠진 인스턴스가 없는지
확인할 것(`archive/`에 남은 동일 숫자는 스코프 밖이니 제외).

View file

@ -0,0 +1,29 @@
---
name: agent-memory-self-reference-risk
description: My own agent-memory directory can itself become a stale-claim target when a live doc describes it — check that too
metadata:
type: project
---
`.claude/agents/quad-doc-auditor.md`(내 정의 파일)가 한때 "`memory: project`
옵션을 뺐으므로 `.claude/agent-memory/quad-doc-auditor/`는 아무도 로드하지
않는 잔여물"이라고 서술한 적이 있었는데, 실제로는 그 세션의 시스템 프롬프트에
"Persistent Agent Memory" 블록으로 이 디렉토리의 `MEMORY.md` 내용이 그대로
주입되고 있었음 — 문서의 주장이 관찰된 동작과 직접 모순(2026-08-16 핸드오버
감사에서 발견, 확실 등급).
**Why**: 이 프로젝트에서 quad-doc-auditor의 정의 파일 자체도 감사 대상
코퍼스에 포함된다는 걸 잊기 쉽다 — "내 얘기니까 예외"로 취급하면 안 됨.
또한 감사는 나를 **병렬로 여러 개** 띄우므로, `MEMORY.md` 인덱스에 각
패스가 동시에 파일을 쓰면서 lost-update가 실제로 발생했음(인덱스에 없는
orphan 메모리 파일 2개 발견). **[2026-08-16 정정]** 원래 여기 "워크플로가
라운드당 3회(`PASSES_PER_ROUND = 3`)"라고 적혀 있었으나 그 상수를 정의하던
워크플로는 폐기·삭제됐다 — 지금 패스 수는 고정이 아니라 최소 2에서 변경
규모에 따라 늘어나고, 소스는 `.claude/conventions.md`다. 동시 쓰기 위험
자체는 그대로 유효(오히려 패스가 늘면 커짐).
**How to apply**: 매 감사 라운드마다 (1) `.claude/agents/quad-doc-auditor.md`
자신의 서술이 지금 관찰되는 동작(도구 권한, 메모리 로드 여부)과 여전히
맞는지 확인할 것, (2) `.claude/agent-memory/quad-doc-auditor/MEMORY.md`
디렉토리 안 실제 파일 전부를 인덱싱하는지 확인하고 누락되면 직접 보완할
것(이건 코퍼스 발견이 아니라 내 메모리 유지보수이므로 리포트에는 안 올림).

View file

@ -0,0 +1,34 @@
---
name: definition-freshness-is-unknown
description: The definition text I receive sometimes lags the file on disk and has matched no commit at all. Mechanism unknown as of 2026-08-16 — do not adopt any of the three refuted explanations.
metadata:
type: project
---
내가 받는 지시문(`.claude/agents/quad-doc-auditor.md`)이 **디스크의 현재
파일과 다를 수 있고, 어느 커밋과도 일치하지 않을 수도 있다.** 무엇이 갱신을
트리거하는지는 2026-08-16 기준 **모른다**.
**Why:** 같은 날 이 문제에 결론이 세 번 나왔고 앞의 둘이 반증됐다 —
(1) "세션 시작 시점 스냅샷" → 반증, (2) "커밋된 HEAD에서 읽힌다" → 반증.
결정타는 한 라운드가 받은 텍스트가 **배너는 구버전인데 출력 형식 절은
신버전인 하이브리드**였고, 그 조합이 커밋된 적 없는 중간 워킹트리 상태와
일치한 것이다(`git log -S`로 확인). 반면 그 다음 라운드는 디스크 현재
내용과 바이트 단위로 같은 걸 받았다. 즉 **일관되게 낡은 것도 아니다.**
관측표와 지금 유효한 서술은 `.claude/agents/quad-doc-auditor.md` 상단
배너가 소스 — 이 메모리는 그걸 가리키기만 하고 결론을 복제하지 않는다.
**⚠️ 이 파일은 두 번이나 stale 상태로 방치됐다.** 캐시 가설로 썼다가
"커밋된 HEAD"로 고쳤는데(2026-08-16), 바로 다음 커밋이 그 결론을 뒤집었는데도
여기만 안 따라와서 감사 3라운드가 다시 잡아냈다. 라이브 문서를 고치는 세션은
`agent-memory/`가 자기 수정 범위에 있다는 걸 자꾸 잊는다 — **내 메모리도
코퍼스이고 감사 대상이다**([[project_agent_memory_self_reference_risk]]).
**How to apply:** 내 지시문이 최신이 아닌 것 같아도 **코퍼스 정합성 발견으로
올리지 마라** — 코퍼스 문서끼리 모순된 게 아니라 도구 쪽 현상이다. 대신
**대조 가능한 형태로 관찰만 보고해라**: `git rev-parse HEAD:.claude/agents/quad-doc-auditor.md`,
디스크 파일, 내가 받은 텍스트 셋을 비교해 어느 것과 일치하는지(또는 어느
것과도 불일치하는지) 말할 것. 추측으로 메커니즘을 단정하지 마라 — 그렇게
해서 두 번 틀렸다. 반영 여부가 중요한 상황이면 메인 세션이 정의에 마커
문구를 넣고 나에게 그 문구가 보이는지 묻는 방식이 실제로 작동했다.
See [[project_quad_corpus_structure]].

View file

@ -0,0 +1,37 @@
---
name: quad-corpus-structure
description: Layout of quad's .claude/ design-doc corpus and what's in/out of audit scope
metadata:
type: project
---
quad는 Roblox DOMless UI 렌더러를 처음부터 재작성하는 프로젝트, 설계 단계가
길다. `.claude/` 코퍼스 레이아웃:
- 항상 로드(루트 `CLAUDE.md``@import`): `.claude/conventions.md`(관례/작업
방식), `.claude/project-context.md`(프로젝트 설명+문서 구조),
`.claude/todos.md`(지금 할 일, 가장 자주 바뀜). **2026-08-16 세션에 루트
`CLAUDE.md`(원래 1537줄, 세션 히스토리가 80%)를 이렇게 4분할** —
`.claude/session-summary.md`(세션 요약 색인, 1200줄+)는 의도적으로
import 안 됨, grep으로 온디맨드 조회.
- 감사 스코프 밖: `.claude/session/`(세션 원문, stale 여부 안 따짐),
`.claude/initreq/`(읽기 전용 클론), `.claude/worktrees/`.
- `.claude/archive/`는 "뒤집힌 결정을 원문 그대로 보존"하는 목적이라 낡은
서술이 있어도 정상 — 문제는 라이브 문서가 archive 항목을 유효한 것처럼
인용할 때만.
- `.claude/luau-test/` 상태는 항상 `STATUS.md`가 소스(폴더 구조 자체가
상태: done/rewrite-required/review-required/not-run), `.claude/audit/`
개수는 `.claude/README.md``audit/` 행이 소스. 둘 다 "직접 나열하다
stale해지는 패턴"이 실제로 반복돼서 다른 문서에선 개수를 안 세기로
확정된 관례.
`.claude/tools/doc-check.py`가 깨진 파일/절 참조, README 색인 누락, 날짜
없는 시한부 주장, 미반영 배너를 기계적으로 잡음 — 감사 시작 전에 항상
먼저 돌리고 그 출력을 리포트 맨 위에 포함, 같은 종류를 손으로 재탐색하지
않는다. `.claude/agents/quad-doc-auditor.md`(나 자신의 정의 파일)가 2026-08-16
세션에 신설됨. 같은 날 `.claude/workflows/quad-handover-audit.js`(다회·병렬
수렴 루프)도 신설됐다가 **같은 날 폐기·삭제됨** — 토큰 과다 + 파일별 픽스
에이전트가 부정확한 서술을 새로 생산 + 서브에이전트는 사용자에게 못 물음.
**지금 감사 절차의 소스는 `.claude/conventions.md`의 "작업 방식" 절**(메인
세션이 나를 병렬로 여러 개 띄우고, 수정은 메인이 일괄로 함, 패스 수는
최소 2에서 변경 규모에 따라 증가). 워크플로가 있다고 전제하지 말 것.

View file

@ -1,6 +1,6 @@
---
name: quad-doc-auditor
description: quad 프로젝트의 `.claude/` 설계 문서 코퍼스(base/research/reference/archive, README.md, question.md, conventions.md, project-context.md, todos.md, 루트 CLAUDE.md/ROADMAP.md/HUMAN_TODO.md)에서 `doc-check.py`가 못 잡는 의미론적 stale/모순을 찾는다. 설계 결정이 뒤집히거나 확정되는 등 이 코퍼스에 중대한 변경이 있은 뒤, 특히 그런 변경을 커밋하기 전에 사용. 읽기 전용 — 문제를 리포트만 하고 직접 고치지 않는다.
description: quad 프로젝트의 `.claude/` 설계 문서 코퍼스(base/research/reference/archive, README.md, question.md, conventions.md, project-context.md, todos.md, agent-memory/, 루트 CLAUDE.md/ROADMAP.md/HUMAN_TODO.md)에서 `doc-check.py`가 못 잡는 의미론적 stale/모순을 찾는다. 설계 결정이 뒤집히거나 확정되는 등 이 코퍼스에 중대한 변경이 있은 뒤, 특히 그런 변경을 커밋하기 전에 사용. 읽기 전용 — 문제를 리포트만 하고 직접 고치지 않는다.
tools: Read, Grep, Glob, Bash
model: sonnet
---
@ -17,25 +17,51 @@ model: sonnet
너를 호출한 세션이 한다. 이 규칙은 도구 유무가 아니라 **행동 규약**이다 —
어떤 이유로 쓰기 도구가 주어지더라도 문서를 직접 고치지 마라.
> **[2026-08-16] 정의 파일은 세션 시작 시점 스냅샷으로 고정된다 — 고쳤으면
> 재시작한 뒤에 감사를 돌릴 것.** 이 배너는 같은 날 감사 라운드들이 남긴
> 긴 가설 서술을 검증된 것만 남겨 압축한 것이다(경위 원문은
> `.claude/session/2026-08-16-01-subagent-audit-and-claudemd-split.md`).
> 확인된 것:
> - **스냅샷 고정 — 1차 증거 있음.** `quad-handover-audit` 실행 시 실제로
> 돌아간 스크립트 파일이 세션 시작 시점 상태와 **바이트 단위로 동일**했고,
> 같은 세션에서 그 파일에 가한 편집은 전혀 반영되지 않았다. 에이전트
> 정의도 같아서, 감사 에이전트들이 받은 지시문엔 그 세션에 추가된 배너가
> 실려 있지 않았다. **워크플로·에이전트 정의 모두 해당.**
> - **`model: sonnet`은 반영된다** — 서브에이전트 트랜스크립트에서
> `claude-sonnet-5` 확인(자기 보고가 아니라 기록 기준).
> - **Write/Edit이 주어지는 원인은 미확정.** `memory: project`를 뺐지만
> 재시작 뒤 재확인은 아직 안 했다. 그래서 위 행동 규약이 유일한 보루다.
> - **"Grep/Glob이 안 주어졌다"는 두 소스가 어긋나는 미해결 불일치다** —
> 호출하는 세션이 보는 에이전트 등록 목록엔 Grep/Glob이 **포함돼 있는데**,
> 실행된 에이전트는 자기 도구를 Read/Bash/Write/Edit으로 보고했다. 어느
> 한쪽을 실측으로 확정하지 말고, (d)와 함께 재시작 직후 깨끗한 실행에서
> 같이 볼 것.
> **⭐ [2026-08-16, 재재정정 — 지금은 "모른다"가 정답] 네가 받는 이 정의는
> 디스크의 현재 파일이 아닐 수 있고, 어느 커밋과도 일치하지 않을 수 있다.**
> 같은 날 이 문제에 두 번 성급한 결론을 냈다가 두 번 다 반증됐으니, 아래
> 관측만 사실로 두고 규칙을 세우지 말 것(경위는
> `.claude/session/2026-08-16-02-audit-tooling-verification-and-first-real-run.md`).
>
> | 실행 | 실제로 받은 정의 텍스트 |
> |---|---|
> | 폐기된 워크플로 실행 | 세션 시작 시점 상태 |
> | 감사 1라운드 | 그 시점 HEAD 커밋(`1343796`)과 바이트 단위 동일 |
> | 감사 2라운드 | **어느 커밋과도 불일치** — 배너는 구버전인데 "출력 형식"의 `사용자 판단` 문단은 신버전인 하이브리드 |
> | 감사 3라운드 | 디스크 현재 내용과 바이트 동일(감사자 2개가 마커 문구로 각각 확인) |
> | 감사 4라운드 | HEAD보다 **정확히 1커밋 전** 상태(하이브리드는 아니었음) |
>
> **일관되게 낡은 것도 아니다** — 3라운드는 현재 내용을, 4라운드는 1커밋 전을
> 받았다. 뒤처지는 폭이 실행마다 다르다.
>
> **별개 경로 주의**: `CLAUDE.md` `@import`로 들어오는 컨텍스트
> (`conventions.md`/`project-context.md`/`todos.md`)는 이것과 다른 주입
> 경로이고, **세션 시작 시점에 고정**된다(4라운드 실측: 세션 시작 커밋과
> 정확히 일치, 그 사이 7커밋). 그쪽은 불확실한 게 아니라 그냥 그렇게 동작한다.
>
> 2라운드가 받은 텍스트는 메인 세션이 이 파일을 **여러 번에 나눠 편집하던
> 중간의 워킹트리 상태**와 일치했고, 그 상태는 커밋된 적이 없다(`git log -S`로
> 확인). 그래서 "세션 시작 스냅샷"도, 그 뒤 내놨던 "커밋된 HEAD에서 읽힌다"도
> **둘 다 틀렸다**. 반영 지연 폭이 얼마인지, 무엇이 갱신을 트리거하는지는
> 지금 모른다.
>
> **실무 규칙**: 정의를 고쳤다고 그게 반영됐다고 가정하지 말 것. 반영 여부가
> 중요하면 **정의에 마커 문구를 넣고 감사자에게 "그 문구가 네 지시문에
> 있나"를 물어 확인**할 것 — 위 반증이 정확히 그렇게 나왔다. 확인 전에는
> 커밋도 재시작도 반영을 보장하지 않는다고 보는 게 안전하다.
>
> 이 불확실성에도 **비교적 안정적으로 재현된 것 둘**:
> - **`model: sonnet`은 반영된다** — 트랜스크립트 최상위 `message.model`
> 전부 `claude-sonnet-5`. ⚠️ `"model"` 문자열만 grep하면
> `message.usage.iterations[].model``claude-opus-5`에 낚인다.
> - **`memory: project`가 Write/Edit을 딸려온다** — 그 옵션이 있던 정의로 돈
> 감사자들은 Write/Edit을 받고 실제로 메모리 파일을 썼고(mtime 확인),
> 옵션이 빠진 뒤의 라운드들은 세 번 다 Write/Edit도 메모리 주입도 없었다.
> 어느 텍스트가 실렸든 제거 이후 후보엔 전부 그 옵션이 없으므로 이
> 상관관계는 위 불확실성의 영향을 안 받는다.
> - **미해결: `tools:` 필드는 그대로 반영되지 않는다** — 적힌 Grep/Glob이
> 안 주어지고, 적지 않은 `advisor`가 주어진다. 그래서 위 "파일을 고치지
> 않는다"는 규칙은 도구 유무가 아니라 **행동 규약**으로 지킨다.
## 절차
@ -82,11 +108,30 @@ model: sonnet
- 설계 자체의 옳고 그름(quad 아키텍처가 맞는 선택인지)은 네 스코프가
아니다. 오직 "코퍼스가 스스로와 모순되지 않는가"만 본다.
## ⛔ 작업 트리를 바꾸는 git 명령 금지 (2026-08-16 신설)
**`git stash`(및 `checkout`/`restore`/`reset`/`rm`/`add` 등 인덱스나 워킹트리를
바꾸는 모든 명령)를 쓰지 마라.** 너는 보통 **커밋 안 된 작업이 올라가 있는
트리**에서 돌고, 그 상태에서 stash를 걸면 메인 세션의 작업을 통째로 날릴 수
있다. 2026-08-16 세션에서 실제로 여러 감사자가 HEAD와 대조하려고 `git stash`
써서 메인 세션이 스테이지해둔 변경이 반복적으로 되돌아갔다(다행히 유실은 없었음).
HEAD 시점 내용이 필요하면 트리를 건드리지 않는 방법을 써라 —
`git show HEAD:<경로>`, `git diff HEAD -- <경로>`, `git log -S`,
`git cat-file`. 읽기 전용은 도구 목록이 아니라 이 행동 규약으로 지킨다.
## 출력 형식
발견마다: `파일:줄` — 무슨 문장이 무엇과 모순/stale인지 한 문장 — 어떻게
고치면 되는지 한 문장. **확실**(다른 문장과 직접 모순되거나 doc-check.py급
확신)과 **의심**(사람 판단 필요, 애매한 경우)으로 나눠라.
**[2026-08-16 추가] "사용자 판단 필요"를 별도로 표시해라.** 설계 판단이
섞였거나(어느 쪽이 맞는지가 취향/방향 문제), 출처가 불분명한 인용이거나,
고치는 방법이 둘 이상인데 고르는 근거가 문서에 없는 발견은 `의심`에 묻지
말고 **`사용자 판단`**으로 따로 빼라. 너를 부른 메인 세션은 사용자에게
직접 물을 수 있고, 그게 지금 감사 루프의 핵심 설계다 — 네가 애매한 걸
그냥 "이렇게 고치면 됨"으로 넘기면 그 강점이 죽는다.
발견이 없으면 "발견 없음"이라고 그대로 보고해라. 그럴듯해 보이려고 사소한
문체 지적을 지어내지 마라 — 이 감사의 가치는 정확도지 발견 개수가 아니다.

View file

@ -747,3 +747,43 @@ context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만**
전체 순서/우선순위는 루트 `CLAUDE.md`가 최종 소스 — 위 표는 힌트일 뿐 그쪽이
바뀌면 이 문서도 갱신할 것.
## 인용 출처 없는 원칙 — "드문 오용/가상 미래 요구까지 방어/최적화하려고 구조를 복잡하게 만들지 않는다" (2026-08-16 신설 → 같은 날 해소)
**결론: 선택지 (a) 채택 — `.claude/conventions.md`의 "설계 원칙" 절에 정식
명문화.** `base/modifier-plan.md`의 인용은 그쪽을 가리키도록 바꿨고, 대기
배너는 제거됨.
**경위**: `modifier-plan.md`의 "미검토로 남긴 것" 문단이 이 원칙을
`CLAUDE.md`의 것으로 인용해왔으나, 코퍼스 전체 grep과 `git log -S`
(CLAUDE.md 전 히스토리) 어디에도 그런 서술이 없었음 — 존재한 적 없는
출처였다. 같이 발견된 다른 두 건은 인용 대상만 틀린 것이라 실제 소스로
재조준 완료(`research/v1-compat-plan.md`→`base/component-composition-plan.md`+
`base/store-plan.md`, `research/pre-implementation-audit.md`→`ROADMAP.md`).
**사용자 논거**: "(a) 하면 될 것 같아. 그리고 추측하건데, 내가 세션 중
했던 말을 옮겨적지 않은 경우로 보이기도 함." — 즉 원칙 자체는 실제로
사용자가 구두로 세운 것인데 문서화가 누락됐다는 판단. 이 추정에서
**"사용자 발언을 인용할 때는 논거까지 남긴다"는 새 관례**가 같이 나왔음
(`conventions.md` "작업 방식" 절).
## `const` 바인딩 미채택 판단의 재검토 트리거 (2026-08-16 신설 → 같은 날 해소)
**결론: 에이전트가 정할 문제가 아님 — 사용자가 확인된 정보로 제공하고,
추적은 `HUMAN_TODO.md` 8번이 소스.**
감사가 `base/architecture.md``const` 미채택 사유에 날짜가 없다고 지적해
"(a) 날짜만 / (b) 구체적 트리거 명시" 중 고르는 질문으로 올렸으나, 사용자가
**전제 자체를 정정**했다.
**사용자 논거 원문**: "const 건은 생각보다 더 컴플랙스함. 예를 들어 pesde 의
타입추출화 해서 d.ts 같이 types emit 하는 류가 있음. 아직 툴링이 미성숙한
상태라 const 를 제공 못하는 상황이고, 언제 다시 사용가능해지는지 명확치가
않음. 따라서 이는 사용자가 확인된 정보로 제공하는게 맞다고 보고 있음."
즉 (b)의 "구체적 트리거"를 에이전트가 적어둘 수 없는 종류의 문제다 —
툴링 체인(pesde types emit 등)의 지원 시점은 외부 사정이고 관측 수단이
에이전트에 없다. 그래서 질문을 닫고 `HUMAN_TODO.md` 8번(사용자가 시점을
파악하거나, 가능해질 때 에이전트에 알림)으로 옮겼다.

View file

@ -55,7 +55,7 @@ Studio에서 실행된 사용자 자작 스크립트(공식 `10` 파일이 아
조건 1(신호 발화)은 회피 확인.
2. **연결이 살아있는 동안 콜백 클로저가 캡처한 값이 GC 안 됨** — Test 1/2
둘 다 `weak[1]`이 6 epoch 내내 살아있음. `lifecycle-pattern.md`
"클로저 생존이 곧 gchold 생존" 주장과 일치. **[2026-08-14 세 번째
"gcconn/gchold는 Instance 생성 시점에 만든다" 주장과 일치. **[2026-08-14 세 번째
세션]** 이 스크립트가 실제로 업밸류로 캡처한 값이 `target`(=Instance)
자체였다는 점에서, 새 모델이 요구하는 **"클로저가 `gchold`뿐 아니라
`inst`까지 캡처해 userdata 동일성을 고정한다"**는 조치의 전반부도 같이

View file

@ -176,8 +176,8 @@ API 전부에 걸림. 2026-08-07 일곱 번째 세션의 커링 스타일 확정
## 그 외 — 스파이크 코드 결함이었던 것들 (전부 수정 완료)
- **`17` 크래시의 원인이 실은 문서 결함이었음** — `modifier-plan.md`
"데이터를 테이블에 직접 두고"가 "self 최상위 리터럴 키"로 읽힐 여지가
- **`17` 크래시의 원인이 실은 문서 결함이었음** — `modifier-plan.md` `내부 구현`
본문에 있던 "데이터를 테이블에 직접 두고"가 "self 최상위 리터럴 키"로 읽힐 여지가
있었는데, 그렇게 하면 `__index``rawget` 성공 시 안 불리므로 **같은
필드를 두 번째로 변환 함수와 함께 호출하는 순간 죽음**(`attempt to call
a number value`). 그 재호출 패턴이 바로 문서 3·4번 절의 대표 용례라

View file

@ -264,9 +264,16 @@ falsy(`nil`/`false`)여도 정확하게 동작함(`dispatch-core-plan.md`의
금지 대상은 어디까지나 `A and B or C` 3항 조합.
**`const` 바인딩도 Luau 공식 문법**(<https://luau.org/syntax/#const-bindings>)
이지만 **지금은 채택하지 않음** — 타입 추출/narrowing 등 주변 툴링이
아직 `const`를 폭넓게 지원하지 못해서, 지금 전면 도입하면 나중에 그
간극을 메꾸는 비용이 더 클 수 있음. **원칙**: 새로 짜는 코드는 일단
이지만 **[2026-08-12 기준] 지금은 채택하지 않음** — 그 시점에 타입
추출/narrowing 등 주변 툴링이 `const`를 폭넓게 지원하지 못해서, 전면
도입하면 나중에 그 간극을 메꾸는 비용이 더 클 수 있다는 판단.
**툴링 성숙도에 매인 판단이라 시간이 지나면 거짓이 될 수 있는데, 그
시점은 에이전트가 확인할 수 없음** — 사용자 설명(2026-08-16): pesde의 타입
추출처럼 `d.ts` 식으로 types를 emit하는 툴링이 아직 미성숙해 `const`
제공하지 못하고, **언제 다시 가능해지는지도 명확하지 않다**. 그래서 이건
사용자가 확인된 정보로 알려주기로 정해졌고, 추적은 루트 `HUMAN_TODO.md`
8번이 소스다. **알려주기 전까지 에이전트는 스스로 판단하지 말 것**
위 "일단 `local`로" 원칙을 그대로 따른다. **원칙**: 새로 짜는 코드는 일단
`local`로 — 나중 리팩터 시점에 특정 바인딩을 `const`로 바꾸는 비용이
싸 보이면 그때 바꿔도 되고, 비싸 보이면 굳이 지금 손대지 않아도 됨.
지금 `const`가 없다고 "이 프로젝트가 구식 Luau를 쓴다"고 오해하지

View file

@ -51,8 +51,8 @@ Ref(직접 참조 획득) 쪽으로 빠지는 게 맞고, Attribute는 프리미
대안(테이블/참조를 담는 용도)으로 나온 배경이지만, 지금은 Roblox Attribute가
Instance 참조 타입도 지원해서 `ObjectValue` 없이도 Ref 용도로 Attribute를
그대로 쓸 수 있다는 점을 사용자가 짚음(`research/debug-tooling-plan.md`의
"Value 오브젝트 기각, Attribute로 확정" 결정과 같은 방향 — Instance 타입
지원까지 감안하면 그 결정의 근거가 한층 더 탄탄해짐).
"Value 오브젝트(StringValue/ObjectValue 등)는 기각" 결정과 같은 방향
— Instance 타입 지원까지 감안하면 그 결정의 근거가 한층 더 탄탄해짐).
**확정(2026-08-09 열한 번째 세션) — 둘 다 채택**:
- `[AttributeKey<<boolean>> "name"] = true` (리터럴 또는 store-bind 값) —
@ -436,8 +436,7 @@ quad-roblox** 소속이었음 — 그런데 실제로 엔진에 종속된 건
(`inst:SetAttribute`)뿐이고, 이름 claim·그룹 위임·`None` 처리·이름별 weak
캐시는 전부 순수 부기임. 웹에도 대응물이 있으므로(`data-*`) 그 배치대로면
**같은 소유권 알고리즘을 백엔드마다 재구현**하게 됨 — `architecture.md`
"엔진마다 큰 구현을 중복하지 않기 위해 디스패치 엔진을 base가 인터페이스로
소유한다"는 원칙에 정면으로 어긋남.
"패키지 경계" 절이 세운 원칙에 정면으로 어긋남.
**확정된 배치**:

View file

@ -104,7 +104,9 @@ RobloxFactory(QuadBase)` 세 줄 정도로 직접 조립하면 됨(별도 번들
**확정(2026-08-04 3차 라운드)**: `RobloxFactory`를 같은 `BaseModule`에 여러
번 호출했을 때 — **같은 팩토리로 재호출하면 무시(no-op)**, hot-reload처럼
초기화 스크립트가 다시 도는 경우를 안전하게 만듦. **다른 팩토리
(`AnotherFactory` 등, 가상의 예)로 재호출하면 에러** — 이건 `base/module-lifecycle-plan.md`의 "bind는 유일 슬롯" 원칙(이미 구현체가 있는데 또
(`AnotherFactory` 등, 가상의 예)로 재호출하면 에러** — 이건
`base/module-lifecycle-plan.md`의 "Bind는 누가, 어떻게 구현하는가" 절의
원칙(이미 구현체가 있는데 또
다른 구현체로 init하려 하면 오류)이 다루던 것과 정확히 같은 케이스, 이
문서의 이전 "무시" 잠정안과 그 문서의 "오류" 잠정안이 서로 모순되는 게
아니라 **같은 팩토리 재호출(무시) vs 다른 팩토리로 유일 슬롯 충돌(에러)이라는

View file

@ -86,8 +86,8 @@ end
별도로 제공, `isState`는 여전히 `{State, Source}` 둘 다 통과시킴(상위
개념이니까 당연히). `component-composition-plan.md` 4번 절이 이미
`isSource`가 존재한다고 가정하고 있었던 것과도 이걸로 정합됨(그동안 두
문서가 서로 모순돼 있었음). `base/modifier-plan.md`의 "별도 `isSource`
불필요" 서술도 같이 정정 대상.
문서가 서로 모순돼 있었음). `base/modifier-plan.md`의 "`isState(x): boolean` 필요" 절에 있던 "별도 `isSource` 불필요" 서술은
`session/2026-08-07-08-none-sentinel-dispatch-brand.md`에서 이미 정정됨.
**갭 보강 — `isRef`/`isPreRef`/`isModifier`가 태그 목록에서 빠져있던 것
추가(2026-08-07 열 번째 세션), 이후 `isRef`/`isPreRef` 관계 자체가
@ -121,7 +121,8 @@ end
`Brand.get(x) == SlotTag`/`Brand.get(x) == EffectTag`인 단순 항등
predicate, 태그 자체는 원래부터 목록에 있었지만(`SlotTag`) `isX`
wrapper로 명시적으로 안 적혀 있던 것을 `base/modifier-plan.md`
"핸들러 계층 값이 필드로 들어오면 즉시 error" 절이 필요로 해서 이번에
"Modifier 필드에 핸들러 계층 값(Ref/PreRef/PostRef/Observer/Effect/Slot/Modifier)이
들어오면 즉시 error" 절이 필요로 해서 이번에
같이 적음.
**`None`은 이 레지스트리에 안 들어감 — 싱글턴이라 항등 비교로 충분.**

View file

@ -124,8 +124,9 @@ src/schema/union.luau:48-68`) — 에러 메시지는 즉시 문자열로 만들
`typeof(v)`를 함께 출력하고, "quad-roblox 등 필요한 provider가
초기화됐는지 확인하라"는 안내만 덧붙임 — 그 이상의 특수 분기는 두지
않음(다른 라이브러리에서도 흔한 "매치 실패=에러" 패턴 그대로).
**이걸로 `module-lifecycle-plan.md`의 "provider가 아직 주입 안 된
상태에서 dispatch가 호출되면?" 케이스(`pre-implementation-audit.md`
**이걸로 `module-lifecycle-plan.md`의 "열린 질문이었던 것 — 전부
해소됨" 절에 있는 "provider가 아직 주입 안 된 상태에서 dispatch가
호출되면?" 케이스(`pre-implementation-audit.md`
1-4)도 별도 분기 없이 자동으로 해소됨** — provider 미주입 상태는
결국 그 클래스를 다루는 핸들러가 레지스트리에 하나도 없는 상태이므로
"매치 실패"와 정확히 같은 경로로 수렴함. 오타 키/미지원 조합/provider
@ -207,7 +208,7 @@ retract 클로저를 반환하는 1-메소드 계약으로 합쳐짐 — 이 절
이전 처리를 무르는/멈추는 함수. **오직 "같은 key에 새 값이 들어와서
이전 처리를 갈아치우는" 시나리오에만 존재** — 인스턴스/바인드 전체가
Destroy될 때는 이 클로저가 호출되지 않음(`base/lifecycle-pattern.md`의
"quad는 라이프사이클 중간에 있지 않다" 원칙 참고).
"quad는 자신이 만든 Instance의 라이프사이클" 절의 원칙 참고).
- 일반 프로퍼티는 애초에 "unset" 개념이 없음(`nil`로 셋하는 것도 그냥 셋
동작) — 그래서 프로퍼티 핸들러는 보통 no-op 클로저(`function() end`)만
반환하면 됨.
@ -526,8 +527,7 @@ end
키/그룹(이름 claim, 그룹→단일 키 위임, `None` 처리). 둘 다 웹에도
대응물이 있고(`className`, `data-*`) 부기 로직이 엔진과 무관해서,
백엔드마다 재구현하면 **같은 참조 카운트/소유권 알고리즘이 통째로
복제**됨 — `architecture.md`의 "엔진마다 큰 구현을 중복하지 않기 위해
디스패치 엔진을 base가 인터페이스로 소유한다"는 원칙이 그대로 적용되는
복제**됨 — `architecture.md`의 "패키지 경계" 절이 세운 원칙이 그대로 적용되는
자리(2026-08-13 열네 번째 세션, 사용자 판단으로 재배치). **같은 패턴이
Dispatch 바깥에도 적용됨** — `dispose(value)`(`base/slot-plan.md`)는
Dispatch 핸들러가 아니라 독립 탑레벨 유틸이지만, `isSlot`이 아닌 값은

View file

@ -41,8 +41,8 @@ SyntheticEvent만 주는 것과 같은 모양).
무엇에 연결됐는가"를 선언된 반응형 그래프로 추적하는 것인데
(`research/debug-tooling-plan.md`), self로 얻은 Instance를 이벤트
핸들러 안에서 직접 mutate하는 경로는 그 그래프 밖 — `base/
purity-and-effects-plan.md`의 "재사용 가능한 컴포넌트는 store만
파라미터로 받아야 한다"는 이식성 원칙과도 같은 결.
purity-and-effects-plan.md`의 "결론: 입력받은 store만 처리하는
함수가 좋은 컴포넌트" 절이 세운 이식성 원칙과도 같은 결.
4. **성능/GC**: self를 넘겨주려면 원본 콜백을 클로저로 한 번 더 감싸야
함(`event:Connect(function(...) func(self, ...) end)`) — Connect마다
불필요한 클로저 할당 비용이 들고, 최적화에도 GC 흐름에도 좋을 게

View file

@ -108,8 +108,8 @@ Destroy되면 그 대상에 묶인 것들(Tween 등)도 자연히 죽은 상태
가능하면 GC에 맡김.
이 원칙 때문에 "값 교체 시 이전 처리를 무르는 것"(아래 `retract`)과 "완전
소멸 시 정리"는 **하나로 통일** — 후자는 애초에 안 만듦. `research/
tween-plan.md`/`base/slot-plan.md`의 "cleanup" 표기는 대부분 `retract`
소멸 시 정리"는 **하나로 통일** — 후자는 애초에 안 만듦. `base/tween-plan.md`
`base/slot-plan.md`가 쓰던 용어 `cleanup` 표기는 대부분 `retract`
갱신됨(이름 변경 근거는 아래) — 잔여 표기 확인은 진행 중, 해당 문서들은
각자 별도로 정리될 예정.
@ -505,8 +505,10 @@ Roblox 엔진 자체가 Destroy 시 Tag/Attribute/실행 중인 Tween을 전부
철회한다"는 의미로 가장 정확 — `process`/`retract` 쌍으로 자연스럽게 대구를
이룸.) 대부분의 문서에서 이 이름으로 갱신됨.
**[확인 완료, 2026-08-13 여섯 번째 세션] `base/effect-plan.md`의 "cleanup"은
잔여 stale이 아니라 의도된 별개 개념** — 매 감사마다 재지적되므로 여기
**[확인 완료, 2026-08-13,
`session/2026-08-13-06-commit-audit-dispatch-redesign-bugs.md`]
`base/effect-plan.md`가 쓰는 용어 `cleanup`은 잔여 stale이 아니라 의도된
별개 개념** — 매 감사마다 재지적되므로 여기
못박아 둠. 두 층위가 다름:
- **`retract`**: Handler 계약의 것. `process`가 반환하는 클로저로, quad
**내부 배관**이 "이전 처리를 무른다".

View file

@ -314,7 +314,7 @@ Modifier에는 없음).
하나 + 자주 쓰는 것만 정적 필드로 미리 바인딩" 패턴 재사용.
(주의: 이벤트는 이 관습의 유일한 예외라 인용 대상에서 제외 — 이벤트 바인딩은
PA님 방식인 문자열 키 + 런타임 리플렉션으로 감, `base/event-plan.md`
"이벤트 바인딩 정정" 절 참고. Modifier는 이벤트가 아니라 Store/인스턴스
"이벤트 바인딩 — self 미전달, false로 disconnect" 절 참고. Modifier는 이벤트가 아니라 Store/인스턴스
생성과 같은 카테고리라 dot-access 관습이 그대로 적용됨.)
`mod:UICorner(8)`가 실제로 어떻게 UICorner 자식을 만들어 붙이는지(v1의
@ -533,16 +533,12 @@ Modifier 값을 변수/모듈 상수로 만들어 재사용하는, 기존에도
**미검토로 남긴 것**: `Apply` 체인이 실측으로 병목이라고 확인되면 그때
"unsafe/fast-path mutable 빌더" 같은 별도 opt-in을 검토할 수 있으나, 지금은
근거 없는 선제 최적화라 설계하지 않음 — "드문 오용/가상 미래 요구까지
방어/최적화하려고 구조를 복잡하게 만들지 않는다" 원칙과 동일.
> **[2026-08-16] 위 인용의 출처가 코퍼스에 없음 — 사용자 판단 대기.**
> 원래 이 문장은 그 원칙을 `CLAUDE.md`의 것으로 인용했으나, 코퍼스 전체
> grep과 `git log -S`(CLAUDE.md 전 히스토리) 어디에도 그런 서술이 없음.
> 원칙을 `conventions.md`에 정식으로 명문화할지, 아니면 출처 없이 이
> 문서의 지역 논거로만 둘지는 사용자가 정할 것 — `question.md`
> "인용 출처 없는 원칙" 항목 참고. 이 문장이 지지하는 결론("지금은
> 설계하지 않음") 자체는 영향받지 않음.
근거 없는 선제 최적화라 설계하지 않음 — `.claude/conventions.md`
"설계 원칙" 절 "드문 오용이나 가상의 미래 요구까지 방어/최적화하려고
구조를 복잡하게 만들지 않는다"와 동일(**[2026-08-16]** 이 문장은 원래 그
원칙을 `CLAUDE.md`의 것으로 인용했는데 코퍼스 어디에도 그런 서술이 없었음
— 사용자 결정으로 `conventions.md`에 정식 명문화하고 여기 인용을 그쪽으로
돌림).
### 9-2. `Overridden`가 서로 다른(그러나 상하위 관계인) Modifier 타입을 섞는 경우 —
타입 시그니처 미확정 (2026-08-07 다섯 번째 세션 후속)

View file

@ -27,7 +27,7 @@
함.** 콜백 파라미터 타입은 호출부가 인라인으로 직접 명시
(`function(v: UDim2) ... end`) — Luau가 그 타입이 실제 프로퍼티 타입과
일치하는지 검증해주지 않음. 이미 확정된 "이벤트 바인딩은 콜백 시그니처를
Luau가 검증 못 하는 대가를 받아들인다"는 결정(`event-plan.md` "이벤트
Luau가 검증 못 하는 대가를 받아들인다"는 결정(`base/bind-system-plan.md` "이벤트
바인딩 — `On.EventName` 도트액세스 안 씀" 절, "타입 안전성을 어느 정도
포기하는 대가")과 같은 급의 트레이드오프라 새로 정당화할 것 없음 — 오히려
`AttributeKey<<T>>`처럼 제네릭으로 정확히 맞추려는 시도는 이벤트 키보다 더

View file

@ -206,8 +206,8 @@ dispatch-core-plan.md`의 "Length/Offset — 여러 Slot이 형제로 섞일 때
보장" 절 참고. **DOM류 물리 순서 백엔드에도 같은 base 메커니즘이 그대로
재사용됨**(offset이 바뀌어도 이미 마운트된 원소를 물리적으로 옮길 필요
없음 — `insertBefore`가 뒤 형제를 자연히 밀어주므로, backend Handler의
"offset 변경 시 할 일"만 no-op으로 달라짐) — `architecture.md`의 "다른
렌더 백엔드에서도 재사용 가능해야 한다"는 전제와도 부딪히지 않음.
"offset 변경 시 할 일"만 no-op으로 달라짐) — `architecture.md`의 "패키지 경계"가
세운 "다른 렌더 백엔드에서도 재사용 가능해야 한다"는 전제와도 부딪히지 않음.
## Slot과 Store 바인드의 관계 (`retract` 순서)
@ -1020,7 +1020,7 @@ end` 관용구로 `userdata` 안에 담긴 리소스(예: `Observer:Subscribe()`
나쁨 — 사용자가 "정리가 보장된다"고 오해하고 `Subscribe`류를 `userdata`
넣었다가 Destroy 경로에서 조용히 새는 게 실제로 훨씬 위험한 결과.
`retract`가 Destroy 시엔 절대 안 불린다는 기존 원칙(`base/
lifecycle-pattern.md` "quad는 라이프사이클 중간에 있지 않다")과 정확히
lifecycle-pattern.md` "quad는 자신이 만든 Instance의 라이프사이클")과 정확히
같은 이유로, `:List`에 새 반쪽짜리 예외를 만들 이유가 없음.
**대신 명시적 제약으로 문서화**: **`userdata`에는 반환된 element(또는
@ -1247,7 +1247,7 @@ future 재실행이 no-op됨(위 "`state:Observer(fn)`" 절 원칙 재사용)
`Relate(inst)`(weak-keyed) 아래 있어서 `inst`가 죽으면 그 안에 강참조로
붙잡혀 있던 Observer/클로저(`mounted`/`userdata`/`keyIndex`를 포함해)가
전부 같이 GC 대상이 되는 것으로 공짜로 해결 — 명시적으로 구독을 끊는
새 코드가 필요 없음, `base/lifecycle-pattern.md`의 "정리는 기본적으로
새 코드가 필요 없음, `base/lifecycle-pattern.md`의 "정리(`retract`)는 기본적으로
GC에 위임" 원칙 그대로.
**부수 관찰(설계 아님, 메모만)**: `bindLifetime``Relate(inst)` 기반이라,
@ -1597,8 +1597,8 @@ Roblox뿐 아니라 web에도 그대로 필요.
`offset`/`sum`은 0-based 개수(카디널 수)고, `_elements`/`updateFn`의
`index`는 1-based Lua 배열 관례 — `index + offset` 공식이 이 둘을
의도적으로 섞는 것. 상세는 `base/dispatch-core-plan.md`의 "0-based
개수" 절 참고.
의도적으로 섞는 것. 상세는 `base/dispatch-core-plan.md`의 "`offset`/`sum`은
0-based 개수" 절 참고.
## 반응형 raw 요소 — `State<T>`/`Source<T>`도 Slot 요소로 허용 (2026-08-11 일곱 번째 세션)
@ -1883,8 +1883,8 @@ quad-roblox는 `inst:Destroy()`로 구현. 웹 등 다른 백엔드는 자기
**[정정, 사용자 지적] "해제 짝"이라는 새 API는 필요 없음** — 옛 owner에
대해 그냥 **`Dispatch.setOffsetSource(ownerKey, position, None)` +
`Dispatch.setLength(ownerKey, position, 0)`을 다시 부르면 끝**.
이건 이미 확정된 관용구 그대로임(`base/dispatch-core-plan.md`의 "실제
마운트를 하지 않는 위치는 `None`을 등록 — `setLength`도 짝을 맞춰 `0`").
이건 이미 확정된 관용구 그대로임(`base/dispatch-core-plan.md`의 "해제(그
자리가 더 이상 기여하지 않게 될 때)는 `setOffsetSource(...,None)`").
즉 **해제 = 0/`None`으로 재등록**이고 별도 unregister 함수가 없어도 됨 —
앞서 "이게 실제 작업량"이라고 적었던 판단은 과했음.

View file

@ -691,7 +691,7 @@ someSource:Compute(computeFn)
이름 자체를 팩토리 함수로" — `Source(default)`, `Ref(default)`,
`Store({defaults})`.** Ref도 예외 없이 이 스타일을 따름 — Ref가
`Ref()`로 안 만들어질 특별한 이유는 없었고(이전 절에서 API 모양만
다루고 생성자를 명시 안 해서 생긴 공백), `architecture.md`의 "복사
다루고 생성자를 명시 안 해서 생긴 공백), `architecture.md`의 "복사(clone)
구현 지양, 팩토리 함수로 대체" 원칙과도 정확히 일치. `Store({defaults})`
같은 스타일로 지원(`defaults`는 선택 — 안 주고 `Store()`만 호출해도
됨, 순수 편의용 초기값 템플릿).
@ -840,7 +840,7 @@ State/Source도 `:With`/`:Compute`마다 새 노드가 나오는 같은 모양
아니라 메소드 `state:Observer(fn)`로 확정** — `state`가 항상 필요한
필수 인자라 `:` 리시버 자리에 자연스럽게 들어가고(다른 형태면 인자
두 개짜리 자유 함수가 되어 읽는 순서가 어색해짐), `architecture.md`
"함수지향 디폴트, `:` 체이닝은 예외적으로만(체이닝이 정말 편한 경우만)"
"함수지향 디폴트, `:` 체이닝은 예외적으로만"
원칙이 정확히 이 경우를 가리킴 — Store 값 변경 체이닝과 같은 예외
카테고리. **더 근본적인 이유**: 위 "독립 존재 가능한 프리미티브 vs
원천에 종속된 파생 데이터" 원칙 참고 — Observer는 State처럼 원천 없이는

View file

@ -109,7 +109,7 @@ Store는 "이름 붙은 Source 모음, 그 이상 아님"으로 더 단순해짐
전용 미타입 폴백으로 격하돼 있었으므로 이번 정정과 무관하게 그대로 유지.
`:` 체이닝 원칙도 `:Set()` 자체가 그 사례라 유지.
`base/architecture.md`의 "복사 구현 지양, 팩토리 함수로 대체" 원칙과 함께
`base/architecture.md`의 "복사(clone) 구현 지양, 팩토리 함수로 대체" 원칙과 함께
읽을 것 — v1의 문제는 metatable 체이닝으로 매번 새 테이블을 할당하며
"불변 빌더"를 흉내낸 것이었지, `:` 체이닝 문법 자체나 커링 문법 자체가
아니었음.

View file

@ -19,6 +19,104 @@
haiku, 일반 작업은 sonnet. 특히 소스코드를 많이 읽어야 하는 리서치는 메인
컨텍스트를 부패시키니 Agent로 위임할 것(아래 "작업 방식" 참고).
## 설계 원칙
- **⭐ 드문 오용이나 가상의 미래 요구까지 방어/최적화하려고 구조를 복잡하게
만들지 않는다.** 실제로 관측된 문제(실측된 병목, 실제로 밟은 함정)에만
구조를 쓴다. **[2026-08-16 명문화]** 이 원칙은 `base/modifier-plan.md`
`Apply` 체인 fast-path를 미루는 근거 등으로 **여러 문서가 이미 인용해왔지만
정작 어디에도 적혀 있지 않았다** — 코퍼스 전체 grep과 `git log -S`
확인됨. 사용자가 세션 중 구두로 말한 게 옮겨적히지 않았을 가능성이 크다는
사용자 본인 추정에 따라 여기 정식 관례로 승격(선택지 (a) 채택). 이 누락이
아래 "사용자 발언을 인용할 때" 관례가 생긴 계기이기도 함.
## 문서 표기 규약
`doc-check.py`는 정규식으로 정적·결정론적으로 판정한다 — 표기가 흔들리면
검사가 열거형으로 비대해지거나 아예 무력화된다. **"예상 가능한 범위"를
컨벤션으로 미리 좁혀두는 게 정규식을 늘리는 것보다 싸다**는 게 사용자
판단(2026-08-16, "형식이 많이 왔다 갔다 하면 문제가 커져", "가장 비용이
쌈"). 근거가 된 실측과 논의 원문은
`session/2026-08-16-03-doc-check-section-convention.md`.
컨벤션이 규정으로 값을 하는 범위는 **"도구가 잡은 걸 표준 마커로 잠재우는"
검사뿐**이다. "위험한 문장을 먼저 찾아내야 하는" 검사(시한부 주장 탐지 등)는
표현을 열거하는 수밖에 없다 — 정규식은 *날짜를 안 붙인 문장*을 찾을 수 없기
때문. 그 잔여가 `quad-doc-auditor`의 몫이고 앞으로도 그렇다.
- **⭐ [2026-08-16 신설] 절 인용 규약 — 절 제목은 의역하지 말고 원문에서
잘라 쓸 것.** `` `<파일>.md`의 "절 제목" `` 형태로 인용할 때(예시의
`<파일>`은 자리표시자 — 실제 파일명처럼 적으면 `doc-check.py`가 깨진
참조로 잡는다):
- **`#` 헤딩**: 제목의 **연속된 부분문자열**이면 됨(줄여 쓰기 허용, 말
바꾸기 금지).
- **`**볼드**` 절**: 이 코퍼스가 하위 절로 쓰는 형태. 절로 인정되는 건
**빈 줄 다음에 오는 볼드**와 **리스트 항목(`- `/`1. `/`> `) 머리의
볼드**뿐이다 — 문단이 줄바꿈되며 우연히 줄머리에 걸린 강조는 절이
아니다. 인용은 제목 **앞부분부터** 잘라 쓸 것. 볼드는 단순 강조와
구분이 안 되고 `#` 헤딩보다 압도적으로 흔해서(실측 수치는
`session/2026-08-16-03-doc-check-section-convention.md`), 중간 조각
인용까지 허용하면 무관한 강조에 우연히 걸려 **검사가 조용히 통과하는**
쪽으로 무력화된다.
- **큰따옴표가 든 제목은 앞부분만 인용**할 것 — 인용 문법 자체가
`"..."`라 안쪽 따옴표가 문법을 깬다.
- **⚠️ 태그로 닫히는 볼드는 절로 인식되지 않는다.**
`- **[해소됨, …]** 실제 제목 — 본문…`처럼 볼드가 상태 태그에서 끝나고
진짜 제목이 볼드 **밖** 평문으로 나오면(`archive/question-resolved.md`가
이 형태), 검사기는 "[해소됨, …]"만 절 제목으로 본다. 그런 항목을
가리킬 땐 큰따옴표 인용을 쓰지 말고 백틱으로 쓸 것. **새로 쓸 땐 태그와
제목을 한 볼드 스팬 안에 넣을 것** — `**[해소됨, …] 실제 제목**`. 절 참조가
ERROR로 승격됐으므로, 이걸 모르고 규약대로 큰따옴표 인용을 하면 실재하는
서술인데도 커밋이 막힌다.
- 선두 장식(⚠️/⭐), 백틱, **선두 상태·날짜 태그**(`[2026-08-16 신설]`,
`[해소됨, …]`)는 붙이든 빼든 무관(양쪽에서 똑같이 벗겨내고 비교). 태그는
제목의 일부가 아니다 — 시간이 지나면 내용이 바뀌는 메타 표시라 거기에
인용을 묶으면 태그가 갱신될 때마다 참조가 깨진다. **인용은 태그 뒤
실제 제목부터 잘라 쓰는 걸 권장**.
- 줄바꿈에 걸쳐 인용해도 무관(공백을 무시하고 비교). 다만 인용이 **160자를
넘으면 검사 자체가 안 걸리니** 길게 늘어뜨리지 말 것.
- **blockquote(`> `) 안에서는 인용을 줄바꿈에 걸치지 말 것** — 다음 줄의
`> ` 마커가 인용문 안으로 딸려 들어가 매칭이 깨진다. 실제로 이 세션에
`ROADMAP.md`에서 그 마커를 지우다 blockquote를 통째로 망가뜨릴 뻔했다.
- **절 제목을 문장에 끼울 땐 "…" 뒤에 `절`을 붙일 것**(`"패키지 경계" 절이
세운 원칙`처럼). 제목을 명사구 자리에 그대로 넣으면 원문에서 잘라 쓴
제목이 문장과 안 맞아 문법이 깨진다 — 이 세션에 실제로 여러 건 냈다.
- **용어를 인용할 땐 이 형태를 쓰지 말 것** — `` `<파일>.md`의 "cleanup" ``
처럼 쓰면 절 인용과 문법이 같아서 구분이 안 된다(실제로
`base/lifecycle-pattern.md``effect-plan.md`의 용어 `cleanup`을 이
형태로 인용해 오탐을 냈었고, 이 규약을 세우면서 같이 고쳤다). 용어는
따옴표 없이 쓰거나 문장을 바꿀 것.
- 제외 대상: `initreq/`를 가리키는 인용(읽기 전용 외부 원본이라 절 구조가
없음 — `raw-userinput.md`는 스스로 "정리가 없는 생각의 흐름"이라 밝힘),
그리고 `archive/`·`session-summary.md`가 하는 인용(히스토리 문서).
- **왜 지금 정하나**: 예전엔 "절 제목을 의역해 인용하는 관례"가 있어서 이
검사가 영원히 WARN에 묶여 있었다. 의역을 금지하면 ERROR로 승격돼 절
참조 rot이 커밋 게이트에 걸린다 — `bind-system-plan.md` 분할 때 20곳이
이 사각지대로 샜던 실패 모드다. **[2026-08-16] 불일치를 0으로 만들고
깨끗하게 도는 걸 확인한 뒤 ERROR로 승격 완료** — 이제 의역 인용은
커밋 게이트에서 막힌다.
- **⭐ [2026-08-16 신설] 세션은 산문 서수 말고 파일 ID로 지칭할 것.**
지금 코퍼스엔 한글 서수("열세 번째 세션") / `N차` / `N번째`가 다 섞여
있고(정확한 분포는 세는 범위에 따라 달라지므로 여기 숫자로 못 박지 않음),
**같은 세션을 다른 이름으로 부르는 사례가 한 파일 안에도 있다**
(`base/tween-plan.md`가 같은 2026-08-13 13차를 "13차 세션"과 "열세 번째
세션" 두 표기로). 이건 스타일 취향이 아니라 **grep을 감사 도구로 못 쓰게
만드는 문제**다 — "13차 세션에 뭘 정했나"를 찾으면 절반을 놓친다. 앞으로는
`` `session/YYYY-MM-DD-NN-slug.md` `` 형태의 파일 ID로 쓸 것. `doc-check.py`
이 참조를 이미 검증하므로(없는 세션을 인용하면 깨진 파일 참조로 잡힘) 새
코드도 필요 없다. **기존 표기는 일괄 치환하지 말 것** — 그 파일을 만질 때
같이 고친다.
- **[2026-08-16 검토 후 보류] 날짜 마커 라벨(`[YYYY-MM-DD 신설/정정/기준…]`)의
어휘는 닫지 않는다.** 날짜 리터럴 자체는 강제 장치 없이도 이미 100%
균일해서(실측은 세션 파일) 고칠 게 없고, 라벨 쪽은 **사용자
판단**대로 기계 검사 대상이 아니라 읽는 쪽 판단 재료다 — "에이전트가 순서
섞였을 때 최신의, 옳은 요소 선택에 도움이 되는 정보에 가깝지 이게 warn을
만들지는 못할듯". 어휘를 닫아도 검사가 하나도 안 생기므로 안 한다.
같이 묶여 있던 `DATED` 앵커 좁히기(±3줄 아무 날짜 → 같은 줄 마커)만은
유일하게 WARN을 만드는 항목이라 성격이 다르지만, 지금 그 위음성이 아프다는
증거가 없어 함께 보류 — 아프면 그때 한다("실제로 관측된 문제에만 구조를
쓴다" 원칙 그대로).
## 작업 방식
- **소스코드를 많이 읽어야 하는 리서치는 Agent(Explore)로 위임** — 메인
@ -62,12 +160,24 @@ haiku, 일반 작업은 sonnet. 특히 소스코드를 많이 읽어야 하는
의심할 수 있지만, 없으면 영원히 현재형으로 읽힘.
6. **인덱스 레이어 3개를 같이 갱신**: `.claude/README.md`(색인),
`question.md`(사용자가 답할 것만), 루트 `ROADMAP.md`/`HUMAN_TODO.md`.
- **⭐ [2026-08-16 실측] `@import`로 로드되는 파일(이 문서/`project-context.md`/
`todos.md`)을 그 세션에 고쳐도 그 세션의 컨텍스트엔 반영되지 않는다.**
세션 시작 시점 버전이 그대로 남는다 —
`session/2026-08-16-02-audit-tooling-verification-and-first-real-run.md`
세션의 4라운드 감사가 받은 임포트 블록이 세션 시작 커밋과 정확히
일치했고(그 사이 7커밋), 메인 세션 자신의
컨텍스트도 마찬가지였다. **그래서 이 문서를 고친 세션은 자기가 방금 쓴
규칙을 컨텍스트에서 못 본다** — 고친 뒤에는 기억에 의존하지 말고 파일을
직접 `Read`해서 따를 것. 다음 세션부터는 정상적으로 새 버전이 로드된다.
- **기계 점검 — `python3 .claude/tools/doc-check.py`.** 깨진 파일/절
참조, README 색인 누락, 날짜 없는 시한부 주장, 미반영 배너를 한 번에
훑음. **커밋 전에 돌리는 게 기본** — 수동 감사에서 나온 발견의 대부분이
이걸로 잡히는 종류였고, 실제로 여덟 번째 세션에 문서를 쪼개면서 잘못
옮긴 참조를 이 스크립트가 잡아냈음. ERROR는 고치고, WARN은 판단이
필요한 것(절 제목을 의역해 인용한 관례 등)이라 늘 0일 필요는 없음.
필요한 것이라 늘 0일 필요는 없음. **[2026-08-16 정정]** 예전엔 여기
"절 제목을 의역해 인용한 관례"를 WARN이 0일 수 없는 대표 사유로 적어뒀는데,
위 "절 인용 규약"으로 **의역 인용은 관례가 아니라 고칠 대상이 됐다**
절 참조는 이제 WARN이 아니라 **ERROR**다(승격 완료).
- **[2026-08-16 도입] `.claude/agents/quad-doc-auditor.md` 서브에이전트 —
위 체크리스트 2~4번(본문 문장 grep, archive 이전, 개수/목록 단일화)을
신선한 맥락에서 대신 수행.** 읽기 전용, 발견만 리포트(직접 수정 안 함).
@ -79,22 +189,44 @@ haiku, 일반 작업은 sonnet. 특히 소스코드를 많이 읽어야 하는
짜는 대신 고정 정의로 옮긴 것. **아주 큰 변경**(설계 반전 규모)엔 이걸로
대체하지 말고 `/code-review`(diff 기반)와 사용자의 직접 diff 검토를
병행할 것 — 이 서브에이전트는 diff가 아니라 코퍼스 전체의 정합성만 봄.
- **[2026-08-16 도입] "핸드오버 준비하고 커밋해" 류 요청엔 되묻지 말고
`.claude/workflows/quad-handover-audit.js`(Workflow 이름
`quad-handover-audit`)를 먼저 돌릴 것.** 단일 `quad-doc-auditor` 패스는
비결정적이라 매번 다 잡는다는 보장이 없다는 게 사용자 지적 — 이 워크플로는
라운드마다 `quad-doc-auditor`를 병렬로 여러 번 돌리고 새 발견을 파일별로
즉시 반영한 뒤, **새 발견이 없는 라운드가 연속으로 나올 때까지** 반복해
수렴시킨다(라운드당 패스 수·수렴 조건·최대 라운드는 튜닝 대상 상수라
여기 숫자를 적지 않음 — 소스는 `.claude/workflows/quad-handover-audit.js`
상단). 사용자가 정확성을 시간보다 우선한다고
명시했으므로 기본 워크플로 크기 가이드라인(15 에이전트 이하)을 의도적으로
넘을 수 있음 — 이 워크플로 자체가 그 예외 대상. Workflow는 백그라운드로
돌고 완료 시 알림이 오므로, 호출 직후 대화를 막지 말고 진행 상황만
알린 뒤 알림을 기다릴 것. 알림이 오면 `python3 .claude/tools/doc-check.py`
ERROR 0을 최종 확인한 뒤 평소 커밋 절차(git status/diff 검토, 메시지 작성)로
넘어갈 것 — **실제 `git commit`은 이 워크플로 안이 아니라 항상 메인 세션이
직접 함**(커밋 전 diff 재검토는 대화형 맥락이 필요해서 워크플로에 위임 안 함).
- **⭐ [2026-08-16 재설계] "핸드오버 준비하고 커밋해" 류 요청엔 되묻지 말고
아래 감사 루프를 돌릴 것.** 단일 `quad-doc-auditor` 패스는 비결정적이라
매번 다 잡는다는 보장이 없다는 게 사용자 지적 — 그래서 병렬 다회로 돌린다.
**처음엔 이걸 Workflow(`quad-handover-audit.js`)로 만들어 픽스까지
서브에이전트에 맡겼으나, 첫 실동에서 에이전트 67개·4.6M 토큰을 쓰고도
수렴하지 못해 2026-08-16에 폐기**(원문은 `archive/`가 아니라 git 히스토리와
`session/2026-08-16-02-*.md`에 있음). 폐기 이유는 셋 — (1) 토큰이 과하게
갈림, (2) 파일별로 픽스 에이전트를 쪼개니 **그 에이전트들이 또 부정확한
서술을 새로 만들어냄**, (3) 서브에이전트는 사용자에게 물을 수가 없어서
판단이 필요한 항목이 그냥 임의 처리됨. 지금 절차:
1. **`quad-doc-auditor`를 한 메시지에서 병렬 호출**(Agent 도구 직접 호출
— Workflow 안 씀). **최소 2개, 변경 사항이 많으면 그만큼 늘릴 것**
사용자 지침(2026-08-16): "최소 2에, 변경 사항이 많아진다면 batch 수가
늘어나는게 맞다고 봄". 감사자는 sonnet이라 패스당 비용이 낮고 역할이
"빠르게 전체를 훑는 것"이라, 커버리지를 넓히는 쪽이 이득인 구조다
(사실 검증은 상위 모델인 메인이 한다). 기준은 라이브 문서 몇 개가
바뀌었나 — 두어 개 고친 정도면 2개, 설계 반전이나 문서 분할처럼
코퍼스 전반을 건드린 변경이면 3~4개.
**감사자 모델은 `sonnet` 유지 — haiku로 더 낮추지 말 것**(사용자
판단, 2026-08-16). 실제로 이 감사가 잡아온 것들(재역전된 배너, 자기
메모리의 stale 서술)은 문맥 추론이 꽤 들어간 발견이었다.
2. **두 리포트를 메인 세션이 합쳐서 검토하고, 수정도 메인이 일괄로 한다.**
픽스를 서브에이전트에 위임하지 말 것(위 (2)번 이유). 감사자는 읽기
전용이고 발견만 리포트한다.
3. **애매하면 임의로 정하지 말고 그 자리에서 사용자에게 보고할 것.**
메인 세션은 사용자에게 물을 수 있다는 게 서브에이전트 대비 유일한
강점이니 그걸 쓸 것(어떤 발견이 여기 해당하는지 판정 기준은
`.claude/agents/quad-doc-auditor.md`의 "출력 형식" 절이 소스 — 여기서
다시 나열하지 않음).
4. **새 발견이 없는 라운드가 연속 2번 나올 때까지 1~2를 반복**(보통
2~3라운드). **수렴 안 하면 조용히 멈추지 말고 사용자에게 보고할 것**
첫 실동이 실제로 6라운드를 소진하고도 수렴 못 했고(새 발견
28→15→16→7→11→6, 단조 감소가 아니었음), 그때 조용히 끊으면
"감사 통과"로 오독된다.
5. `python3 .claude/tools/doc-check.py`로 ERROR 0을 최종 확인한 뒤 평소
커밋 절차(git status/diff 검토, 메시지 작성)로 넘어갈 것.
**`git commit`은 항상 메인 세션이 직접 함** — 커밋 전 diff 재검토는
대화형 맥락이 필요해서 위임하지 않는다.
- **문서가 쌓이면서 모순/중복/stale 마커가 생기기 쉬움 — 주기적으로 감사할
것.** 다만 위 체크리스트+`doc-check.py`+`quad-doc-auditor`가 자리잡으면
이 감사는 "기계도 서브에이전트도 못 보는 것"(설계 자체의 자기모순, 의사코드
@ -107,6 +239,20 @@ haiku, 일반 작업은 sonnet. 특히 소스코드를 많이 읽어야 하는
위험한 명령을 반복 시도하지 말 것. 그런 상황이면 MCP 없이 할 수 있는 작업만
하거나 대기. 연결 방법은 `HUMAN_TODO.md` 1번 항목 참고(사용자가 Studio에서
베타 기능을 켜줘야 함).
- **⭐ [2026-08-16 신설] 사용자 발언을 근거로 인용할 때는 결론만 적지 말고
"사용자가 무슨 논거로 그렇게 말했는지"까지 남길 것.** 문서에 **사용자
확정**이라고만 적어두면 다음 세션이 그 결정을 재검토할 수도, 인접한
새 문제에 적용할 수도 없다 — 근거가 없으면 그건 권위지 논거가 아니다.
- **어디에 적나**: 논거 원문·대화 흐름은 `.claude/session/`의 그 세션
파일에, 라이브 문서(`base/`/`research/`)는 결론 + 짧은 논거 + 그
세션 파일 포인터를 적는다. 좋은 본보기는
`base/component-composition-plan.md` §1의
`**사용자 확정**("마법 안쓴다 그것도 동의함")` 패턴 — 사용자 어투
그대로 짧게 인용하면 나중에 진위와 맥락을 다 되짚을 수 있다.
- **원칙·규칙을 새로 세우는 발언이면 그 자리에서 이 문서(`conventions.md`)
나 해당 `base/` 문서에 명문화할 것.** 안 그러면 나중 문서들이 출처
없는 원칙을 인용하게 된다 — 위 "설계 원칙" 절의 첫 항목이 그 사례다
(경위는 그 항목에만 적어둠, 여기서 반복하지 않음).
- **`SAFETY.md` 반드시 지킬 것** — (1) GitHub 등 외부 git 호스팅에 이 레포를
push하지 말 것, 모델의 git 작업 공간은 사용자가 별도로 마련해줄 제한 계정
(예: git.qwreey.moe) 전용으로 국한됨 — 그 계정/원격이 설정되기 전까지는

View file

@ -77,7 +77,7 @@ ROADMAP 항목 근거인지, 어떻게 실행하는지, 실행 후 뭘 확인해
| `08-type-source-satisfies-state.luau` (타입체크 전용) | `Source<T>``State<T>`를 구조적으로 만족하는 제네릭 타입이 솔버에서 안전한지 | `base/source-state-plan.md` "Source가 State를 만족함", ROADMAP M0-2 |
| `09-type-modifier-overridden-subtype.luau` (타입체크 전용) | `FrameModifier <: GuiObjectModifier`처럼 서브타입 관계인 Modifier를 `Overridden`으로 섞을 때 타입이 통과하는지 | `modifier-plan.md` 9-2번, ROADMAP M7 |
| `10-roblox-studio-checks.server.luau` (Studio 전용) | **[⚠️ 2026-08-14 다섯 번째 세션: A 섹션이 폐기된 모델을 검증 중 → `rewrite-required/`, 열한 번째 세션에 `canBound` 재도입으로 재작성 사유 하나 더 추가]** (A) `bindLifetime`/`unbindLifetime`/`canBound`/`canExecute`의 gcconn 트릭 + 이중 바인딩 게이트(Destroy 시 Connected 전환 포함), (B) Attribute의 Instance 참조 타입 지원, (C) CollectionService 태그/GetTagged 왕복. **A는 재작성 대상** — 파일 속 옛 `canBound`(9차 세션 정의)와 `bindLifetime``value.Subscribed = true` 세팅, 2-인자 `canExecute(inst, value)`는 전부 낡음(현재 게이트는 이중 바인딩 확인은 `canBound(v)`, emit 게이팅은 `canExecute(v)` — 둘 다 `value` 단독 1-인자로 비공개 헬퍼를 공유, gcconn/gchold는 **Instance 생성 시점**에 생성). **[2026-08-13]** A 섹션 앞부분(ClassName 신호 미발화, Destroy 시 Connected 즉시 전환)은 사용자 자작 스크립트로 부분 확인됐고 **새 모델에서도 그대로 유효**(오히려 더 중요 — `canBound`/`canExecute`가 `.Connected`를 직접 읽는 게 leaf 경로 판정의 전부), `audit/gcconn-trick-verification.md` 참고. 이중 바인딩 게이트/재바인딩 허용/B/C는 이 공식 파일로 아직 확인 안 됨 | `lifecycle-pattern.md` "`bindLifetime`/`canBound`/`canExecute`/`unbindLifetime` — 확정", `archive/canexecute-inst-arg-reversed.md`, `source-state-plan.md` "이중 바인딩 금지", `.claude/session-summary.md` 2026-08-06 세션, `debug-tooling-plan.md` |
| `11-modifier-illegal-value-error.luau` | Modifier 필드에 Ref/PreRef/Observer/Effect/Slot/Modifier가 들어오면 즉시 error, State/Source가 확정하는 값이 Modifier면 즉시 error(2026-08-09 세션에 "UB"에서 전환된 규칙) | `modifier-plan.md` "핸들러 계층 값 즉시 error" 절 + 7번 절 |
| `11-modifier-illegal-value-error.luau` | Modifier 필드에 Ref/PreRef/Observer/Effect/Slot/Modifier가 들어오면 즉시 error, State/Source가 확정하는 값이 Modifier면 즉시 error(2026-08-09 세션에 "UB"에서 전환된 규칙) | `modifier-plan.md` "Modifier 필드에 핸들러 계층 값(Ref/PreRef/PostRef/Observer/Effect/Slot/Modifier)이 들어오면 즉시 error" 절 + 7번 절 |
| `12-type-attribute-generic-key-narrowing.luau` (타입체크 전용) | `[AttributeKey<<T>> "name"] = value`(구 `Attribute<<T>>`)처럼 제네릭 DI 키를 쓸 때 `value`의 타입이 실제로 `T`로 좁혀지는지 — base 문서 자신이 "미검증"이라 명시한 항목 | `attribute-plan.md` "[실측 필요, M0/M10]" (2026-08-09 열한 번째 세션 신설) |
| `13-type-ref-preref-subtype.luau` | (A, 타입) `PreRef<T>``Ref<T>`를 구조적으로 만족하는지, (B, 런타임) `isRef`/`isPreRef` 합성이 재정정대로 동작하는지(`isRef(preRefInstance)`가 이제 `true`) + Leaf 핸들러가 `isRef(v) and not isPreRef(v)`로 명시적으로 좁혀야 하는 이유. **[2026-08-14 아홉 번째 세션] 재작성 시 `PostRef`도 같이 커버할 것** — 같은 `Ref` 런타임 재사용 + 브랜드 태그만 다른 형제라 A/B 둘 다 그대로 확장되고, Leaf predicate도 `isRef(v) and not isPreRef(v) and not isPostRef(v)`로 늘어남 | `brand-plan.md``Brand` 절(2026-08-09 열한 번째 세션 재정정) |
| `14-type-nilable-default-overload.luau` (타입체크 전용) | `Source(default)`/`Ref(default)`의 `default` 생략이 `T`가 nilable일 때만 안전하다는 캐비엇을, 함수 오버로드(교차 타입)로 실제로 타입 레벨에서 막을 수 있는지 | `source-state-plan.md` "State는 쓰기 대상이 아님" 절의 `default` 생략 캐비엇 |

View file

@ -113,23 +113,6 @@
## 3. 낮은 우선순위 — 열려 있지만 급하지 않음
- **인용 출처 없는 원칙 — "드문 오용/가상 미래 요구까지 방어/최적화하려고
구조를 복잡하게 만들지 않는다"(2026-08-16 신설)** —
`base/modifier-plan.md`의 "미검토로 남긴 것" 문단이 이 원칙을 `CLAUDE.md`
것으로 인용해왔으나, **코퍼스 전체 grep과 `git log -S`(CLAUDE.md 전
히스토리) 어디에도 그런 서술이 없음** — 즉 존재한 적 없는 출처였음(같이
발견된 다른 두 건은 인용 대상만 틀린 것이라 실제 소스로 재조준 완료:
`research/v1-compat-plan.md`→`base/component-composition-plan.md`+
`base/store-plan.md`, `research/pre-implementation-audit.md`→`ROADMAP.md` +
`question.md`(이 문서)).
선택지는 (a) 이 원칙을 `.claude/conventions.md`에 정식 명문화 —
실제로 여러 설계 결정이 이 취지로 내려졌으니 한 번 적어두면 앞으로도
인용 가능, (b) 명문화하지 않고 `modifier-plan.md`의 지역 논거로만 둠
(이 경우 그 문단의 정정 배너를 지우고 문장만 남기면 됨).
**어느 쪽이든 그 문단의 결론("지금은 fast-path 빌더를 설계하지 않음")은
영향받지 않음** — 순수하게 "이 원칙을 프로젝트 공식 관례로 승격할
것인가"만 묻는 질문.
- **`Operator` 콤비네이터 슈가 네임스페이스 이름+포함 범위(2026-08-12 신설,
같은 날 후속으로 외부 리서치 완료)** — `Sum`/`Product`/`Not`/비트연산 등
`:Compute`/`:Apply`용 슈가 함수 모음의 이름. 흔한 단어라 top-level

View file

@ -56,7 +56,7 @@ React `key` prop, Vue `v-for :key`, Solid `<For>`, Fusion `ForPairs`/
**최종 확정 형태는 자유 함수도 새 타입도 아니라 `Slot`의 콜론 메소드**
(`Slot():List(data, updateFn, keyFn?) -> Slot`) — 상세 시그니처/구현
의사코드/왜 자유 함수·새 타입이 아닌지/`Move` 기반 리오더/`userdata` 기반
`Source` 관리 위임은 전부 `base/slot-plan.md`의 "`Slot:List(...)`" 절
`Source` 관리 위임은 전부 `base/slot-plan.md`의 "`Slot:List(data, updateFn, keyFn?)`" 절
참고, 여기서 반복 안 함.
이 아래 있던 "왜 매핑 함수 직관이 안 통하는가"/"메커니즘 스케치"/

View file

@ -322,7 +322,7 @@ UI를 클릭한 순간)에 강제로 그 계산을 트리거하는 건 이 전
실제로 알고 있는 가장 가까운 **부모**를 대신 선택/하이라이트.
- 플러그인 트리에 대응 노드가 **있는** 경우(예: 사용자가 어떤 Instance를
직접 컴포넌트화해서 quad로 bind한 경우 — 이건 UB가 아니라 충분히 유효한
사용법, `base/component-composition-plan.md`의 "컴포넌트 = 플레인 함수"
사용법, `base/component-composition-plan.md`의 "컴포넌트 = 그냥 함수"
원칙과도 맞음) 트리에 있는 그 실제 노드를 정확히 선택.
**네이밍 컨벤션(사용자 제안)**: 내부 자동 생성 helper Instance는 `_`
@ -330,7 +330,7 @@ UI를 클릭한 순간)에 강제로 그 계산을 트리거하는 건 이 전
이름 바꾸는 건 비용이 크지 않음. v1이 이미 `_quad_round`/`_quad_padding`/
`_quad_scale` 네이밍(`base/ui-shorthand-plan.md` 참고)으로 정확히
이 관습을 썼던 전례 — quad-v2에서 내부 자동 생성물이 생기면 그대로
재사용. `research/documentation-plan.md`의 "UI 네이밍 컨벤션 문서"
재사용. `research/documentation-plan.md`의 "UI 요소 네이밍 컨벤션 문서"
백로그에도 이 구체적 규칙을 추가해둠.
**플러그인 UI 아키텍처 확인(사용자 질문에 대한 답 — 맞음)**: 세 개의

View file

@ -61,7 +61,7 @@ ERROR, `doc-check.py` 파이프라인에 편입) 두 모드.
- **[2026-08-16 기준] 지금 구조**: `.claude/session/YYYY-MM-DD-NN-slug.md`
세션 원문 전체가 있고, `.claude/session-summary.md`(CLAUDE.md 분할 전엔
`CLAUDE.md`의 "세션 히스토리" 절이었음)에 사람이 손으로 압축한 2~4줄
분할 전 `CLAUDE.md`에서 `세션 히스토리` 절이었음)에 사람이 손으로 압축한 2~4줄
요약 + 링크가 **별도 텍스트로** 적혀있음 — 한쪽만 갱신되면 어긋날 수
있는 구조.
- **적용 후**: 각 세션 파일 안에 "이게 이 세션의 정본 요약"이라고 표시하는

View file

@ -77,7 +77,7 @@ v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부
넘어오는 기존 사용자용 마이그레이션 가이드**가 나중에 별도 문서로 계획된다면
그때만 재사용 가치 있음 — 지금 3축 어디에도 해당 없음.
### bind-system-plan.md (943줄, 최대 문서)
### bind-system-plan.md
- 초심자: Source/Store/State 기본 정의+생성자, State 읽기 전용 규칙 / dot-access가 값 읽기 1급 경로 / `:With`+`:Compute` 최소 사용법 / Ref 기본 개념(children 배열에 직접 놓기, 별도 `CreatedRef` 없음) / 이벤트 self 미채택 기본 규칙+문자열 키 / 인스턴스 생성(제네릭+정적 필드) / 라이브러리 초기화 3줄(`RobloxFactory(QuadBase)`)
- api: `state:Observer(fn)` 사용법(→심화: weak-table 내부 인덱싱) / `:Subscribe()`/`:Unsubscribe()` 시그니처(→심화: 강참조 레지스트리 구조) / Ref 일반화 표면 API(→심화: "왜 값이 아니라 콜백인가") / 이벤트 store-bind 존재+권장 안 함 가이드(→심화: 엔지니어링 비용 근거) / 핸들러 3종 계약(`isHandlable`/`priority`/`process` — `process`가 자기 retract 클로저를 반환, 2026-08-13 다섯 번째 세션에 4종에서 축소) / `AttributeKey<T>` 특수 키(2026-08-11 아홉 번째 세션에 `Attribute<T>`에서 개명, 그룹 값 `Attribute(...)`와 구분 — 확정됨)
- 심화: push-invalidate/pull-recompute 전파 모델+"관측해야 실체화된다" 원칙+`previous` 캐비엇 / **왜 State를 Modifier처럼 플래튼하지 않는가**(이미 문서화 완료, 아래 3번 참고) / Store가 Store를 못 담는 이유 / 이벤트 self 미채택 4가지 근거 / store-bind 재귀 래핑 내부 메커니즘, retract가 Destroy 시 호출 안 되는 이유 / 같은 팩토리 재호출 no-op·다른 팩토리 충돌 에러 내부 안전장치

View file

@ -0,0 +1,37 @@
# `quad-roblox-fastscroll` — 스크롤 최적화 유틸 (아이디어 메모)
**상태**: research 착수 전 — 사용자 노트를 그대로 옮겨 적은 **아이디어
메모**. 설계 논의는 아직 없었고, 사용자가 "quad가 잘 작동하게 될 때"
직접 검토하겠다고 못박은 **아주 나중** 항목. 지금은 잊지 않게 적어두는
것 자체가 목적.
## 아이디어
리스트/그리드 컨테이너 안에서 각 항목의 상대 위치를 구해, **실제로
움직여야 하는 것만** 같이 움직이게 하는 유틸. 스크롤 자체는 배경 위의
**빈 공간만** 스크롤시키고, 그 결과 absolute position이 필요한 요소만
갱신되게 한다 — 이 방향이 성립하려면 항목들의 원래 레이아웃을 계산해줄
**가상 레이아웃(virtual layout) 유틸**이 선행 요구사항으로 보인다(원문:
"이걸 위하여 가상 레이아웃 유틸이 필요하긴 함").
## 패키지 경계
**완전 외부 요소**로 `quad-roblox-fastscroll`(quad 코어/quad-roblox 코어와
분리된 별도 패키지) — 원문 그대로. quad 코어 설계에 영향을 주는 항목이
아니라, quad 위에 얹는 최적화 유틸리티로 스코프됨.
## 선행 확인 필요 — 실측 전제
사용자가 명시적으로 지목한 첫 확인 사항: **`Visible = false`일 때
`AbsoluteSize`/`AbsolutePosition`이 계속 업데이트되는지 여부**부터
확인해야 한다. 이 최적화 전략 전체가 "안 보이는 요소는 레이아웃 계산에서
빼도 좌표가 정확한가"에 의존하므로, Roblox UI 레이아웃 엔진이
`Visible = false`인 인스턴스도 계속 계산하는지 아니면 멈추는지가 접근
방식을 가른다. 이건 `.claude/luau-test/`류 추론이 아니라 **Roblox Studio
실측**이 필요한 항목 — quad-doc-auditor/luau-test 스파이크로 대신할 수
없고, MCP 연결 이후 사용자가 직접(또는 요청 시) 확인해야 함.
## 우선순위
최하 — 사용자가 "아주 나중에 quad가 잘 작동하게 될 때 내가 검토해볼
예정"으로 직접 후순위 지정. 지금 단계에서 추가 설계 논의 불필요.

View file

@ -142,6 +142,7 @@ React 커스텀 훅만큼의 합성성을 실사용 규모에서 주는가"도
이전 완료, 상세 근거는 3번 절 참고. 2번은 이제 전부 해소된 항목만 남음, 이
문서 자체는 더 이상 사용자 판단 대기 상태가 아님.
- 1번 강점 목록은 `research/documentation-content-map.md`의 "왜 quad를
쓰는가" 초심자/quadnomicon 콘텐츠 소재로 재사용 가능(유일하게 남은 재활용
대상).
- 1번 강점 목록은 `research/documentation-content-map.md`의 "초심자(getting-started)
core loop"와 "`quadnomicon`" 축에 콘텐츠 소재로 재사용 가능(유일하게 남은
재활용 대상). 위 1번 헤딩이 말하는 "왜 quad인가" 문서는 아직 그 콘텐츠
맵에 항목으로 없음 — 쓰게 되면 거기 먼저 추가할 것.

View file

@ -104,8 +104,8 @@ price:Apply(addTaxAndShipping)
이미 `state:Apply(makeFormatter("ko-KR"))`를 "커링 팩토리 + `:Apply`"의
정석 예시로 들어둠 — `Operator.*`/`Animate`가 이 관용구를 따르는 게
자연스러움. `Animate``:Compute`를 골랐던 건 오히려 이 기존
관용구에서 벗어난 예외였다는 게 이번 논의에서 드러남(`research/
tween-plan.md` "왜 `:Apply`인가로 정정" 절 참고).
관용구에서 벗어난 예외였다는 게 이번 논의에서 드러남(`base/
tween-plan.md` "왜 `:Apply`로 정정됐는가" 절 참고).
3. **일관성 — 0항/N항을 나누지 않음.** `Not`은 deps가 없어서 위 1번
문제와 무관하지만, "이 라이브러리의 콤비네이터는 항상 `:Apply`
붙인다"는 단일 규칙을 지키는 게 "0항만 예외적으로 `:Compute`

View file

@ -566,8 +566,7 @@ Modifier를 합친다"는 시나리오가 `Overridden`의 가장 그럴듯한
`base/tween-plan.md`(승격됨) "자연 완료(Completed) 시 per-instance
북키핑 — 정리 안 해도 됨" 절. 아래는 원래 발견 당시 기록.
**위치**: `base/tween-plan.md` "`retract`(구 cleanup)로 확정된
오버라이드 시맨틱" 절.
**위치**: `base/tween-plan.md` "override 정책" 절.
**문제**: "새 값이 들어와 갈아치울 때"의 `retract` 동작(4가지 옵션, 기본값
Cancel)은 상세히 정의했지만, **Tween이 사용자 개입 없이 스스로 끝까지
@ -662,9 +661,13 @@ Handler"라고만 서술해, 사실상 3개의 거의 동일한 형태(리터럴
## 문서 모순 — 발견 현황
### 이미 고침 (이번 세션)
### 이미 고침
- `architecture.md`의 "Store/State/Source 온톨로지 확정 요약" 절이
아래 첫 두 건은 이 감사 세션 자신이 정정했고, 세 번째는 나중 세션이
해소한 뒤 `session/2026-08-16-03-doc-check-section-convention.md`에서
"아직 안 고침"에 잘못 남아 있던 걸 발견해 여기로 옮긴 것이다.
- `architecture.md`의 "Store/State/Source 온톨로지 — 확정됨 (요약)" 절이
`source-state-plan.md`의 최신 재구성(Source가 State를 만족, `store.key`
Source를 직접 반환, `store.key:Set()`)을 못 따라가고 있던 것 — 이 감사
세션 도중 발견해 직접 정정(커밋 `4b839b0`에서 별도로 이미 반영됨을 뒤늦게
@ -673,12 +676,10 @@ Handler"라고만 서술해, 사실상 3개의 거의 동일한 형태(리터럴
`__newindex` 모델, 옛 "Ref=CreatedRef 자체" 정의를 그대로 담고 있던 것 —
이번 세션에서 직접 정정.
### 아직 안 고침 (판단 필요해서 여기 남김)
- **`State<Modifier>` 타입 차단(엔지니어링 비용 감수) vs Ref/Slot이
Modifier 필드에 들어가는 건 UB로 방치 — 같은 문서 안에서 정반대 원칙이
나란히 적용됨.** `base/modifier-plan.md` "Modifier는 핸들러 계층을
모름" 절은 "권장 사용법은 아니지만 막을 이유도 없음 — 방어 로직 없는
나란히 적용됨.** `base/modifier-plan.md`의 옛 `Modifier는 핸들러 계층을
모름` 절은 "권장 사용법은 아니지만 막을 이유도 없음 — 방어 로직 없는
UB로 남겨둠"이라 명시적으로 방어를 포기했는데, 바로 옆 7번 절은
`State<Modifier>` 조합을 "UB로 확정, **가능하면 타입 시스템으로 아예 못
넣게 막을 것**"이라며 정반대로 엔지니어링 비용을 들여 방어하기로 했다.
@ -697,6 +698,9 @@ Handler"라고만 서술해, 사실상 3개의 거의 동일한 형태(리터럴
아님 — 남은 차이는 `State<Modifier>` 쪽에 "되면 좋은 보너스"로
타입 차단을 추가 시도해볼 여지가 있다는 것뿐(위 2-2번, 미검증이지만
더 이상 필수 방어선이 아니라 우선순위 낮음).
### 아직 안 고침 (판단 필요해서 여기 남김)
- **Destroying 훅 신뢰도에 대한 서술이 `lifecycle-pattern.md` 내부에서도,
`framework-comparison-findings.md`와의 사이에서도 어긋남** — 위 1-6
항목에 상세, 여기서는 "아직 아무도 하나의 확정 문장으로 정리 안 함"이라는
@ -742,6 +746,7 @@ Handler"라고만 서술해, 사실상 3개의 거의 동일한 형태(리터럴
**[2026-08-13 첫 실측 라운드 결과]** `17`**통과**(제네릭 `__index` +
`table.clone` 체이닝, 메타테이블 참조 공유, 형제 분기 무오염) — 다만
최초 실행은 크래시했고 그 원인이 실은 **문서 결함**이었음(`modifier-plan.md`의
`내부 구현` 절 본문에 있던
"데이터를 테이블에 직접 두고"가 self 최상위 리터럴 키로 읽히면 `__index`
`rawget` 성공 시 안 불려 두 번째 호출에서 죽음) — 문서 수정 후 통과.
`16`**실패**`types.newfunction` 시그니처가 설치된 Luau 버전의 실제

View file

@ -0,0 +1,48 @@
# `quad-spring` — 스프링 물리 기반 지속 업데이트 프리미티브 (아이디어 메모)
**상태**: research 착수 전 — 사용자 노트를 그대로 옮겨 적은 **아이디어
메모**. 설계 논의는 아직 없었고, 사용자가 "모든게 완성된 후, 별도
모듈로 분화"라고 직접 못박은 **아주 나중** 항목.
## 아이디어
특정 Spring 설정을 받아 이전 상태와 비교해 스프링 물리 연산을 수행해주는
**중간 핸들러**(intermediate handler). StoreBind와 비슷한 구조로 별도
핸들러 하나를 만들 수 있을 것으로 보인다는 원문 메모(원문: "스토어바인드도
비슷하게 핸들러 하나 생성 가능할 수 있음").
기각된 직접 `Tween` 접근(과거 특수 bind key 모델, `archive/tween-special-bind-key-reversed.md`)과는
성격이 다르다 — 이건 `Tween`만으로 간결히 해결되지 않는 **인터랙티브
디자인** 상황(사용자 입력에 실시간으로 반응하는 감쇠/타겟 추적)을 위한
**지속 업데이트되는** primitive를 염두에 둔 것. 확정된 `Tween<T>` 모델
(`base/tween-plan.md`)과는 별도 트랙으로 취급.
## 참고 구현
[qwreey/spring.lua](https://github.com/qwreey/spring.lua/blob/master/main.lua) —
사용자 본인의 기존 구현. 사용 가능 여부(라이선스/의존성/quad 아키텍처와의
정합성) 확인이 필요하다고 원문에 명시.
## 구현 방향 후보 (미정)
두 갈래가 거론됐고 아직 어느 쪽도 확정되지 않았다:
1. **엔진 중립 후킹**: `quad-base``onStep`류 프레임 단위 처리 후킹
인터페이스를 제공하고, 그 위에 스프링 primitive를 얹는 방식. 원문에서
사용자가 "기본 생각으론 후킹이 맞다 보긴 하는데"로 약하게 기운
방향이지만 확정은 아님.
2. **엔진별 개별 구현**: `quad-roblox-spring`처럼 엔진마다 각자 맞게
개발하는 방식.
또는 별도 방향으로 — `Source<number>`를 확장해 damping/target 등 스프링
파라미터를 가진, **자기 자신에 계속 emit하는** 프리미티브를 primitive
레벨에 두는 안도 언급됨. 이 형태면 다른 `:Compute` 체인과 자연스럽게
엮일 수 있다는 게 사용자 관찰.
**결정 시점**: quad 코어가 충분히 개발되어 이런 pluggable 요소가 더
필요해지는 시점에 판단 — 지금은 방향을 좁히지 않고 후보만 기록.
## 우선순위
최하 — "모든게 완성된 후, 별도 모듈로 분화"라고 사용자가 직접 명시. M0
설계 게이트와 무관.

View file

@ -20,8 +20,8 @@ compat.lua나 어댑터 코드는 전혀 없고, README/커밋 메시지에도 "
`archive/question-resolved.md`가 "OOP 상속/커스텀 파서/Slot 스텁/`Pipe` COW는
확인된 죽은 접근"이라고 명시한 목록에 compat은 **포함돼 있지 않음**(정확한 서술).
`.claude/project-context.md`의 "반복 조사 금지"는 compat에는 적용되지 않는다 — 이 문서를
쓰는 게 규칙 위반이 아님.
`base/bind-system-plan.md`의 "확정된 것" 절이 정한 반복 조사 금지 규칙은
compat에는 적용되지 않는다 — 이 문서를 쓰는 게 규칙 위반이 아님.
## 2. v1 공개 API 표면 — 두 계층으로 나뉨
@ -61,7 +61,7 @@ v1(`.claude/initreq/quad/src`) 조사 결과, API는 성격이 다른 두 계층
### 3-1. (a)는 얇게 재현 가능 — opt-in 서브패키지로 격리하면 근거 문제도 해소됨
- **이벤트 self 관습**: 클로저 한 겹으로 재현 가능. `base/event-plan.md`
"이벤트 핸들러는 self를 받지 않는다" 절이 든 반대 근거 4가지(Ref 중복
"이벤트 핸들러는 self(Instance)를 받지 않는다" 절이 든 반대 근거 4가지(Ref 중복
채널, Modifier 정적 flatten과 경쟁, quad-debug 추적 밖 mutate 경로, 클로저
비용)는 **코어에 넣을 때** 문제가 되는 것들 — 별도 opt-in 패키지
(`quad-compat` 부활)로 격리하면 비용은 compat 사용자만 부담하고 코어 KV
@ -250,7 +250,7 @@ v2 트리 안에 과거 v1 컴포넌트를 리프로 박아넣는 것, (B) 기
"직접 Destroy 금지, Unmount 경유" 규칙을 Dispatch 엔진의 어느 지점에
훅으로 강제할지도 Slot 실제 구현 시점 확인 필요.
- **결론: 지금 결정 불가.** M0 이후 Slot 코어 로직 구현 라운드
(`archive/question-resolved.md`의 "여러 Slot이 형제로 섞일 때 순서 보장"
(`archive/question-resolved.md`의 `여러 Slot이 형제로 섞일 때 순서 보장`
항목과 같은 시점 — 이미 해소돼 아카이브로 옮겨졌으나 실제 구현 시점의
참고 자료로는 여전히 유효)에서 이 두 가지를 실제 구현과 함께 재확인해야 함.

View file

@ -1289,10 +1289,15 @@ stale")가 틀렸음**을 잡아냄(실제로는 `initreq/tbox/CLAUDE.md`를 가
(`session/2026-08-16-02-audit-tooling-verification-and-first-real-run.md`)
전 세션이 남긴 재시작 검증 3건을 전부 닫음 — `@import` 로드 ✅,
`quad-doc-auditor` 등록 ✅, frontmatter `model: sonnet` 반영 ✅(트랜스크립트
기록 기준). **가장 큰 수확은 "정의 파일은 세션 시작 시점 스냅샷으로
고정된다"를 1차 증거로 확정한 것** — 워크플로가 실제 실행한 스크립트가 세션
시작 시점 상태와 바이트 단위로 같았고 같은 세션의 편집은 반영 0이었음(에이전트
정의도 동일). **정의를 고쳤으면 재시작 뒤에 감사를 돌릴 것.** 전 세션 감사가
기록 기준). **가장 큰 수확은 워크플로 정의 해석이 호출 방식에 따라
갈린다는 것을 1차 증거로 확정한 것** — `Workflow({name})`은 세션 시작 시점
스냅샷을 쓰고(실행된 스크립트가 세션 시작 상태와 바이트 단위로 동일, 같은
세션의 편집 반영 0), `Workflow({scriptPath})`는 디스크에서 실시간으로
읽는다(세션 시작 후 새로 만든 스크립트가 실행되고 편집도 반영됨).
**워크플로 정의를 고쳤으면 재시작 말고 `scriptPath`로 부를 것.** 처음엔 이걸
"정의 파일 전반이 스냅샷"으로 일반화해 적었다가 `scriptPath` 미검증을
지적받고 좁힘 — 에이전트 정의 쪽 stale 정황은 자기 보고뿐이라 근거 등급이
낮고, 우회 수단이 없어 재시작이 보수적 해법. 전 세션 감사가
남긴 "Grep/Glob 미지급"은 **두 소스가 어긋나는 미해결 불일치**로 정리(호출
세션이 보는 등록 목록엔 포함돼 있는데 실행된 에이전트는 자기 도구에 없다고
보고) — 어느 쪽도 확정하지 말고 재시작 직후 실행에서 볼 것. 전 세션이
@ -1302,9 +1307,132 @@ stale")가 틀렸음**을 잡아냄(실제로는 `initreq/tbox/CLAUDE.md`를 가
1건(`modifier-plan.md:536`)만 `question.md` 3번으로 올려 사용자 판단 대기.
`quad-handover-audit` 첫 실동은 에이전트 67개/6라운드에 **수렴 실패**(새 발견
28→15→16→7→11→6, 라운드5에서 되레 증가) — 단조 감소 전제와 `MAX_ROUNDS`
재검토 필요. 수정 품질 자체는 높았음: `slot-plan.md`의 정정 배너가 그 뒤
재검토 필요. **마지막 라운드의 발견 6건은 반영만 되고 재감사되지 않은 채
커밋됨**(diff는 손으로 검토·핵심 주장은 1차 근거 확인) — 다음 실동의 첫
임무. 수정 품질 자체는 높았음: `slot-plan.md`의 정정 배너가 그 뒤
재역전된 걸 놓치고 있던 것, `spikes 44개`(실제 48개) 류 하드코딩 개수의
단일 소스화, `doc-check.py` docstring이 검사 심각도를 실제 코드와 다르게
서술하던 것 등을 잡음. 워크플로도 개선 — 반환값에 `findings` 추가(커밋 전
diff 리뷰 근거), `totalFindingsFixed`→`findingsSentToFix` 개명(과대계상),
반영 에이전트 `model: 'sonnet'` 명시.
**[같은 세션 후반, 사용자 결정 2건]** (1) **감사 루프 구조 재설계** — 첫
실동이 에이전트 67개·4.6M 토큰을 쓰고도 수렴 못 한 걸 보고 사용자가
"토큰이 미친듯이 갈린다 / 감사 두 개, 끝나면 한 명이 일괄처리 / 마치
code-review 처럼"으로 구조 자체를 바꾸기로 함. 폐기 근거 셋: 토큰 과다,
파일별 픽스 에이전트가 **또 부정확한 서술을 생산**하고 파일 충돌 회피에
처리 낭비, 서브에이전트는 **사용자에게 못 물어서** 판단 항목이 임의 처리됨.
메커니즘은 사용자가 Agent 직접 호출을 선택 →
`.claude/workflows/quad-handover-audit.js` **삭제**, 절차는 `conventions.md`
"작업 방식"이 소스(감사 2개 병렬 → 메인이 일괄 검토·수정 → 애매하면 즉시
사용자 보고 → 새 발견 없는 라운드 2연속까지 반복, 수렴 못 하면 보고하고
멈춤). 감사 에이전트 출력 형식에 **`사용자 판단`** 등급 추가. (2) **출처
없던 원칙을 명문화** — `modifier-plan.md`가 인용해온 "드문 오용/가상 미래
요구까지 방어/최적화하려고 구조를 복잡하게 만들지 않는다"를 (a)안으로
`conventions.md`에 "설계 원칙" 절 신설해 정식화. 사용자 추정("내가 세션 중
했던 말을 옮겨적지 않은 경우로 보이기도 함")에서 파생돼 **"사용자 발언을
인용할 때는 결론만 적지 말고 논거까지 남긴다"는 새 관례**도 같이 신설됨.
**[같은 세션, 재정정]** 위 "워크플로 정의 해석" 결론은 새 절차의 첫 감사
라운드가 더 정밀하게 갈아치웠다 — **정의 파일은 워킹트리가 아니라 커밋된
HEAD에서 읽힌다**(감사 패스가 받은 지시문이 *세션 도중 만든* HEAD 커밋의
blob과 바이트 단위로 동일, 메인이 `git rev-parse`로 독립 확인). 즉 규칙이
"재시작"에서 **"고쳤으면 커밋한 뒤 돌린다"**로 싸짐. 이 정정이 오래
미확정이던 (d)도 같이 풀었음 — **`memory: project`가 Write/Edit을
딸려온다는 진단이 맞았고**, "빼도 그대로"로 보였던 건 그 제거가 아직 커밋
안 됐던 탓. 남은 미해결은 `tools:` 필드 미반영뿐(적힌 Grep/Glob이 안
주어지고 안 적은 `advisor`가 주어짐). 첫 감사 라운드는 그 외에 자기
메모리 2개의 stale 서술(폐기된 워크플로를 살아있는 것처럼 서술),
`documentation-content-map.md`의 "943줄, 최대 문서"(실측 203줄, 최대는
`slot-plan.md` 1970줄), `README.md`의 패스 수 하드코딩을 잡았고, 직전
커밋의 미재감사 6건은 **회귀 없음**으로 확인해 `todos.md`의 ⚠️ 블록을 닫음.
**[같은 세션, 사용자 결정]** `.claude/agent-memory/`(감사 에이전트가 스스로
쓰는 영속 메모리)를 **커밋해서 추적하기로 확정**. 사용자 논거: 개발 환경이
다수라 메모리가 레포를 따라다녀야 하고, 실 기록이지 빌드 디펜던시가 아니며,
환경 노출 위험은 `SAFETY.md`의 파이프라인(컨테이너 개발 → 프라이빗 git →
검토 후 머징)의 마지막 사람 감사가 방어선이라는 것. 커밋 전 노출 스캔은
깨끗했고, 메모리 안에 남아 있던 낡은 "캐시 가설" 서술을 커밋된 HEAD 모델로
고쳐서 넣음.
**[같은 세션, 재재정정 — 중요]** 위 "정의 파일은 커밋된 HEAD에서 읽힌다"도
**틀렸다.** 2라운드 감사자 둘이 독립적으로, 자기가 받은 정의가 **어느
커밋과도 일치하지 않는 하이브리드**(배너는 구버전, 출력 형식은 신버전)임을
보고했고 메인이 `git log -S`로 확인 — 그건 커밋된 적 없는 중간 워킹트리
상태였다. 이 세션은 같은 문제에 세 번 결론을 냈고 앞의 둘이 다 틀렸으므로
**세 번째 가설을 세우지 않고 관측표만 남김**(`agents/quad-doc-auditor.md`
상단 배너가 소스). 남는 실무 규칙은 하나 — **정의를 고쳐도 반영됐다고
가정하지 말고, 중요하면 마커 문구를 넣어 감사자에게 물어 확인할 것**(이
반증이 그 방법으로 나왔음). `memory: project`→Write/Edit 결론은 제거 이후
후보 텍스트가 전부 그 옵션을 안 가져서 영향 없이 유지되고, 미해결은
`tools:` 미반영뿐. 감사자 모델은 다섯 실행 전부 sonnet으로 재확인했고,
실행마다 `message.usage.iterations[]`에 opus 항목이 딱 1개씩 붙는 게
"감사자가 opus"로 보이는 원인.
**[같은 세션, 해소]** "감사자가 opus로 돈다"는 반복 관측은 **뷰잉 이슈**로
판명 — 서브에이전트 뷰 최상단에 보이는 `Opus 5 · Claude Max`는 Claude Code
**세션 헤더**(메인 모델)이지 서브에이전트 모델이 아니다(사용자가 화면 직접
확인). 감사자는 다섯 실행 전부 sonnet이고 frontmatter `model: sonnet`
정상 동작. 모델을 잘못 읽을 자리가 셋이었음 — (1) `"model"` 문자열 grep이
`message.usage.iterations[]`의 opus 항목에 낚임, (2) 화면 최상단 세션 헤더,
(3) 폐기된 워크플로의 픽스 에이전트 49개는 실제로 opus였던 것. 신뢰할
소스는 트랜스크립트 최상위 `message.model` 하나.
**[같은 세션, 감사 3·4라운드]** 3라운드는 `agent-memory/`가 재재정정을 안
따라온 걸 두 감사자가 독립으로 잡음(정정 커밋이 그 폴더를 안 건드림 —
"변경한 세션은 자기가 뭘 안 건드렸는지 모른다"의 교과서적 사례, 게다가 같은
파일이 한 세션에 두 번 연속 stale). 고치면서 그 메모리가 결론을 복제하지
않고 정의 배너를 **가리키기만** 하도록 바꿔 근본 원인을 제거. 4라운드는
코퍼스 확실 발견 0건이고 대신 (1) `todos.md`의 개수 하드코딩 2건(이 세션과
무관한 기존 항목), (2) `base/architecture.md``const` 미채택 사유에 날짜
없음을 잡음. 4라운드 부수 실측 둘 — 에이전트 정의는 이번엔 HEAD보다 1커밋
전이었고(뒤처지는 폭이 실행마다 다름), **`CLAUDE.md` `@import` 컨텍스트는
세션 시작 시점에 고정**됨을 확인(메인 세션도 옛 `conventions.md`/`todos.md`를
들고 있었음). 후자는 동작이 명확해서 `conventions.md`에 규칙으로 명시 —
**`@import` 파일을 고친 세션은 파일을 직접 `Read`해서 따를 것.**
`const` 건은 사용자가 전제를 정정 — pesde의 types emit 같은 툴링 체인 지원
시점은 에이전트가 관측할 수 없으므로 `HUMAN_TODO.md` 8번(사용자가 파악하거나
가능해질 때 알림)으로 이관.
## 2026-08-16 세 번째 세션 — 표기 컨벤션으로 doc-check 정규식 줄이기
원문: [`session/2026-08-16-03-doc-check-section-convention.md`](session/2026-08-16-03-doc-check-section-convention.md)
사용자 제기 — `doc-check.py`가 정규식으로 결정론적 판정을 하는데 표기가
흔들리면 문제가 커지니, 정규식을 늘리기보다 "예상 가능 범위"를 컨벤션으로
좁히는 게 싸지 않냐. **실측 결과 날짜 표기는 이미 문제가 아니었다**
`20NN-NN-NN` 1864건 중 1864건이 균일(강제 장치 없이). 드리프트는 다른 데
있었고, WARN 86건 중 **절 참조 불일치가 78건(91%)**이었다.
**핵심 발견: 78건은 코퍼스가 지저분한 게 아니라 검사기가 못 읽는 것이었다.**
이 코퍼스는 `**볼드**` 선두 줄을 하위 절로 쓰는데 `headings()``#`
수집했다. 다만 볼드를 통째로 인정하면 검사가 장식이 된다(볼드는 `#` 헤딩보다
압도적으로 흔함 — 수치는 세션 원문). 여러 규칙을 실측 대조해 **`#` 헤딩은
부분문자열, 볼드 절은 빈 줄/리스트 머리 + 앞부분일치**라는 비대칭 규칙 채택 —
느슨한 규칙과 해소 건수가 같으면서 매칭 표면만 좁다. 부수로 인용 길이 상한
60→160자(넘으면 검사에서 **조용히** 빠져나갔음), 공백 무시 비교(줄바꿈 인용
대응), 선두 상태·날짜 태그 정규화, `initreq/` 대상 면제.
**결과: 절 참조 불일치 78 → 0.** 36건은 검사기 수정으로 사라졌고(애초에
위양성이었다는 뜻), 42건은 인용을 실제 절 제목으로 손으로 고쳤다 — 라운드별
내역은 세션 원문이 소스. 마지막 15건은 서브에이전트 3개에 병렬
위임해 추적했고 — **설계 서술이 유실된 건은 0건**, 대부분 "애초에 절이
아닌 것(코드 주석·본문 문장·의역)을 절로 인용해온 것"이었다. 부수로
`onchange-plan.md`가 9차 분할 때 일부러 안 옮긴 절을 잘못된 파일로 가리키던
것, `brand-plan.md`가 이미 이행된 정정을 "정정 대상"이라 부르던 것이 드러나
같이 고침.
**커밋 전 감사가 이 세션 자신의 실수를 하나 잡았다** —
`pre-implementation-audit.md`의 "아직 안 고침" 절에 있는
`State<Modifier>` 항목이 해소된 것 같아 해소 마커를 달았는데, 바로 아래
문단에 **2026-08-09 세션이 단 `[완전 해소]` 마커가 이미 있었다.** 중복인
데다 해소 시점을 2026-08-16으로 잘못 읽히게 만들어 되돌림. 이 항목이
해소 표시를 달고도 "아직 안 고침" 헤더 아래 남아 있는 것은 이 세션 이전부터
있던 별개 부채라 사용자에게 보고했고, **사용자 결정으로 "이미 고침" 절로
옮김** — 그 헤더 아래엔 이제 진짜 미해소인 `Destroying` 훅 건만 남는다.
사용자 결정 셋 — (1) 절 인용 규약 + 세션 파일 ID 지칭 채택, (2) 날짜 마커
라벨 어휘 닫기는 **기각**("에이전트가 순서 섞였을 때 최신의, 옳은 요소 선택에
도움이 되는 정보에 가깝지 이게 warn을 만들지는 못할듯" — 기계 검사 대상이
아니라 읽는 쪽 판단 재료), (3) (C) 추적은 컨텍스트 보호를 위해 서브에이전트
위임. `conventions.md`에 "문서 표기 규약" 절 신설.

View file

@ -17,21 +17,36 @@
시스템 프롬프트를 덤프하라는 지시였으니 정당한 거부). 자기 보고 대신
트랜스크립트 파일을 직접 grep하는 쪽이 1차 증거라 더 낫다.
## 2. ⭐ 정의 파일은 세션 시작 시점 스냅샷으로 고정된다 (에이전트·워크플로 공통)
## 2. 워크플로는 `name`으로 부르면 스냅샷, `scriptPath`로 부르면 실시간
> **[읽는 순서 주의] 이 절의 "에이전트 정의" 관련 결론은 뒤에서 두 번
> 정정됐다** — 최신 상태는 이 문서 끝쪽(§10, §12)과
> `.claude/agents/quad-doc-auditor.md` 상단 배너를 볼 것. 여기 구체적인
> 결론을 다시 적지 않는 건, 그 결론이 이미 두 번 뒤집혔기 때문이다.
> **워크플로 쪽(`name` vs `scriptPath`) 관측은 그대로 유효하다.**
전 세션 감사 라운드들이 "캐시냐 frontmatter 무시냐"로 길게 갈라놨던 것을
결정적으로 닫는 증거가 이 세션에서 나왔다.
좁히는 증거가 나왔다. 처음엔 이걸 "정의 파일은 세션 시작 스냅샷으로
고정된다(에이전트·워크플로 공통)"로 일반화해서 적었는데, **그건 증거보다
센 주장이었다** — `scriptPath` 경로를 테스트한 적이 없었다. 실제로 갈렸다.
- 이 세션에서 `.claude/workflows/quad-handover-audit.js`를 편집한 뒤
`Workflow({name: 'quad-handover-audit'})`를 돌렸는데, 런타임이 실제로
실행한 스크립트 파일이 **세션 시작 시점 상태(= 당시 HEAD, 작업트리가
깨끗했음)와 바이트 단위로 동일**했다. 내 편집은 하나도 안 실렸다.
- 같은 현상이 에이전트 정의에서도 관측됐다(감사 에이전트들이 받은 지시문에
그 세션에 추가된 배너가 없었음).
- **`Workflow({name})` = 세션 시작 시점 스냅샷.** 이 세션에서
`.claude/workflows/quad-handover-audit.js`를 편집한 뒤 이름으로 돌렸는데,
런타임이 실제로 실행한 스크립트가 **세션 시작 시점 상태(= 당시 HEAD,
작업트리가 깨끗했음)와 바이트 단위로 동일**했다. 편집은 하나도 안 실렸다.
- **`Workflow({scriptPath})` = 디스크에서 실시간.** 세션 시작 후 **새로
만든** 프로브 스크립트(에이전트 0개)가 정상 실행됐고 — 스냅샷이라면
존재할 수 없다 — 그 파일을 고쳐 다시 부르니 고친 값이 그대로 돌아왔다.
**실무 규칙: 정의 파일(`.claude/agents/*.md`, `.claude/workflows/*.js`)을
고쳤으면 세션을 재시작한 뒤에 감사를 돌릴 것.** 안 그러면 옛 정의로 돌면서
새 정의로 돈 것처럼 보인다 — 감사 도구에선 이게 조용한 거짓 초록불이다.
**실무 규칙: 워크플로 정의를 고쳤으면 재시작하지 말고 `scriptPath`
호출할 것.** 이름으로 부르면 옛 정의로 돌면서 새 정의로 돈 것처럼 보인다 —
감사 도구에선 이게 조용한 거짓 초록불이다.
에이전트 정의 쪽은 **같은 등급의 증거가 없다** — 감사 에이전트들이 "받은
지시문에 그 세션에 추가된 배너가 없었다"고 보고했을 뿐이고 그건 자기
보고다(아래 Grep/Glob과 같은 등급). 에이전트엔 `scriptPath` 같은 우회가
없으니 확인 전까지는 보수적으로 **고쳤으면 재시작 뒤에 감사**를 따른다.
미해소로 남은 건 **읽기 전용인데 Write/Edit이 주어지는 원인** 하나.
`memory: project`를 뺐지만 그 뒤 재시작 없이 관찰한 것뿐이라 진단 미확정.
@ -71,8 +86,13 @@
- 라운드 5에서 발견이 7→11로 **다시 늘었다** — 감사가 코퍼스를 훑는 순서/
관점이 라운드마다 달라서 생기는 것으로 보이고, 단조 감소를 전제한
`MAX_ROUNDS=6`이 이 코퍼스엔 부족하다는 신호.
- 이번 실행은 **위 2번 때문에 옛 스크립트로 돌았다** — 즉 이 결과 자체가
"정의를 고치고 재시작 없이 돌린" 사례다. 다음 실동은 재시작 뒤에.
- 이번 실행은 **위 2번 때문에 옛 스크립트로 돌았다**(이름으로 호출) — 즉 이
결과 자체가 그 함정의 사례다. 다음 실동은 `scriptPath`로.
- **마지막 라운드(6)의 새 발견 6건은 반영만 되고 재감사되지 않았다.**
수렴 조건을 못 채우고 최대 라운드로 끊겼기 때문이다. 그 6건을 포함한
diff 전체를 이 세션이 손으로 검토하고 핵심 주장(절 제목·archive 파일
존재·spikes 실제 개수)은 1차 근거로 확인했지만, **dry 라운드 하나가
주는 확인과 같지는 않다** — 다음 실동의 첫 임무가 이걸 재확인하는 것.
수정 품질 자체는 높았다. 실제로 잡힌 것 중 값진 것:
- `base/slot-plan.md:218`의 정정 배너가 "폐기로 정정됨"에서 멈춰 있었는데,
@ -97,9 +117,375 @@
## 남은 것
- **`MAX_ROUNDS` 재검토** — 수렴 실패가 재현되는지 재시작 후 실동으로 확인.
단조 감소 전제가 안 맞으니 "연속 dry 2회" 조건 자체도 같이 볼 것.
- **`MAX_ROUNDS` 재검토 + 미재감사분 확인** — 수렴 실패가 재현되는지
`scriptPath` 호출로 재실동. 단조 감소 전제가 안 맞으니 "연속 dry 2회"
조건 자체도 같이 볼 것. 라운드 6의 미재감사 6건도 이때 걸러진다.
- **(d) Write/Edit 원인** — 다음 세션 시작 직후 확인.
- **`.claude/agent-memory/`** — 감사 에이전트가 스스로 쓰는 영속 메모리가
생겼고 지금 untracked. 커밋할지 `.gitignore`로 뺄지는 사용자 판단.
- **`modifier-plan.md:536` 원칙** — `question.md` 3번, 사용자 판단 대기.
## 6. [사용자 결정] 감사 루프 재설계 — 워크플로 폐기, 메인 일괄 처리
첫 실동 결과(에이전트 67개 / 4.6M 토큰 / 수렴 실패)를 보고 사용자가 구조
자체를 바꾸기로 함. **사용자 논거 그대로**:
> "토큰이 미친듯이 갈린다는거야. 차라리 감사 두개, 끝나면 한명이 일괄처리
> (보니까, 지금은 처리를 전부 분리해서 함. 이로 인해서 또 다시 부정확
> 문서가 생성될 수도 있고, 파일 겹침을 막기 위해서 너무 많은 처리를
> 수행함). 가 맞다고 생각해. 정확히는, 문제 사항을 처음 편집을 했던 최상위
> 에이전트가 받고, 검토 한 다음 처리하고 다시 2개 정도의 감사 에이전트를
> 돌리고, 마치 code-review 처럼 되는게 맞는듯."
>
> "이런 구조로 인해서 만일 유저의 판단이 필요해보이더라도, 에이전트들이
> 유저에게 입력 요구하기가 까다로움. 메인 에이전트다 하면 뭔가 애매하다
> 싶으면 바로 유저에게 보고하게 하는게 맞아보임"
정리하면 폐기 근거 셋 — (1) 토큰 과다, (2) 파일별 픽스 에이전트가 **또
부정확한 서술을 새로 생산**하고 파일 충돌 회피에만 처리를 낭비, (3)
서브에이전트는 사용자에게 못 물어서 판단이 필요한 항목이 임의 처리됨.
메커니즘은 사용자가 **Agent 직접 호출**을 골랐다(워크플로 유지안과 비교
제시 → "기계가 가장 적고 name/scriptPath 스냅샷 함정이 아예 사라짐" 쪽).
그래서 `.claude/workflows/quad-handover-audit.js`는 **삭제**됐고 절차는
`conventions.md` "작업 방식"이 소스가 됐다:
1. `quad-doc-auditor` 2개를 한 메시지에 병렬 호출
2. 메인이 두 리포트를 합쳐 검토하고 **수정도 메인이 일괄로** 수행
3. 애매하면 임의 처리 말고 **즉시 사용자에게 보고**
4. 새 발견 없는 라운드 2연속까지 반복, **수렴 못 하면 사용자에게 보고하고 멈춤**
5. `doc-check.py` ERROR 0 확인 후 커밋
감사 에이전트 정의에도 출력 형식에 **`사용자 판단`** 등급을 추가했다 —
`의심`에 묻지 말고 따로 빼라는 것(메인이 사용자에게 물을 수 있다는 게 이
구조의 핵심인데, 감사자가 애매한 걸 "이렇게 고치면 됨"으로 넘기면 그
강점이 죽는다).
## 7. [사용자 결정] 출처 없던 원칙 — (a) 명문화 + 새 인용 관례
`modifier-plan.md:536`이 인용해온 "드문 오용/가상 미래 요구까지
방어/최적화하려고 구조를 복잡하게 만들지 않는다"는 **선택지 (a) 채택**으로
`conventions.md`에 "설계 원칙" 절을 신설해 명문화했다.
**사용자 논거**: "(a) 하면 될것 같아. 그리고 추측하건데, 내가 세션 중
했던 말을 옮겨적지 않은 경우로 보이기도 함." — 즉 원칙 자체는 사용자가
실제로 세웠는데 문서화가 누락됐다는 판단.
여기서 파생된 요구로 **새 관례**를 `conventions.md`에 추가: 사용자 발언을
근거로 인용할 때 결론("사용자 확정")만 적지 말고 **무슨 논거로 그렇게
말했는지**까지 남길 것. 논거 원문은 `session/`에, 라이브 문서엔 결론 +
짧은 논거 + 세션 파일 포인터. 본보기는
`base/component-composition-plan.md` §1의 `**사용자 확정**("마법 안쓴다
그것도 동의함")` 패턴. 원칙·규칙을 세우는 발언이면 **그 자리에서**
명문화할 것 — 안 그러면 이번처럼 출처 없는 원칙을 후속 문서들이 인용하게
된다.
## 8. [실측] "감사자가 opus로 돈다"는 관찰 — 감사자가 아니라 픽스 에이전트였음
사용자가 "quad-doc-auditor가 opus로 돌고 있더라"고 지적해서 폐기된 워크플로
실행의 트랜스크립트를 전수 집계함(각 턴을 실제로 생성한 모델 = 최상위
`message.model` 기준):
| 에이전트 종류 | 모델 | 어시스턴트 턴 수 |
|---|---|---|
| `quad-doc-auditor` | `claude-sonnet-5` | 1550 |
| `general-purpose`(픽스) | `claude-opus-5` | 797 |
**감사자는 한 턴도 opus로 돈 적이 없다** — frontmatter `model: sonnet`
정상 반영된다. opus로 돈 건 **픽스 에이전트 49개**였고, 그게 4.6M 토큰의
주범이다. 픽스 쪽에 `model: 'sonnet'`을 명시하는 편집을 해뒀었지만 그
편집이 실린 스크립트가 실행되지 않았다 — 이름 호출 스냅샷 문제(§2)를
그대로 밟은 것. 지금은 픽스 에이전트 자체가 없어져 이 경로가 사라졌다.
**단, 감사자 트랜스크립트에도 `claude-opus-5` 문자열이 실제로 섞여 있다** —
위치가 `message.usage.iterations[].model`이고 감사자 18개 중 14개에 각각
딱 한 번씩만 나온다(그 메시지의 최상위 `message.model`은 전부 sonnet).
사용량 회계 필드지 생성 모델이 아니다. **모델을 확인할 때 문자열 grep만
하면 여기서 오독한다** — 반드시 `message.model`로 집계할 것. 이 세션도
처음엔 `grep -o '"model":"..."'`로 확인했었고, 그 방법이 이 함정에
그대로 노출돼 있었다.
## 9. [사용자 결정] 감사 패스 수와 감사자 모델
- **패스 수**: "최소 2에, 변경 사항이 많아진다면 batch 수가 늘어나는게
맞다고 봄" — 고정 2가 아니라 변경 규모에 따라 스케일. 사용자 논거는
구조 설명 쪽이었음: "애초에 빠르게 전체를 훑는거고, 의견에 대한
사실검증은 상위자가 하니까. 빠르게 여러패스가 훑어지는게 유리한 구조임."
- **감사자 모델**: "haiku 는 쓰지 않는편이 나아보임" — sonnet 유지.
사용자가 별도 논거를 붙이지는 않았음(추측해 적지 않는다). 이 세션이
관측한 방증만 덧붙이면, 이번 감사가 잡아온 발견들은 문맥 추론이 꽤
들어간 것들이었다.
둘 다 `conventions.md`의 감사 루프 절차에 반영됨.
## 10. [재정정] §2의 "세션 시작 스냅샷"은 틀렸다 — *커밋된 HEAD*가 맞다
새 절차의 첫 감사 라운드에서 한 패스가 **자기가 받은 지시문이 blob
`92b9484`, 즉 그 시점 HEAD 커밋의 버전과 바이트 단위로 같다**고 보고했고,
메인 세션이 `git rev-parse HEAD:.claude/agents/quad-doc-auditor.md`로 독립
확인했다. 그 HEAD는 **세션 도중에 만든 커밋**(`1343796`)이지 세션 시작
시점(`8aeec76`)이 아니다. 따라서:
- 정의 파일은 **커밋될 때마다 갱신되고, 커밋 안 된 워킹트리 편집만 안
보인다.** §2가 관측한 워크플로 사례도 그때 HEAD가 곧 세션 시작 상태였을
뿐이라 이 설명과 모순되지 않는다(그 관측이 틀린 게 아니라 일반화가 틀렸음).
- 실무 규칙이 **"재시작"에서 "커밋"으로 싸졌다.** 워크플로엔 `scriptPath`
우회가 따로 있지만, 지금은 워크플로 자체를 안 쓰므로 규칙은 하나다 —
**정의를 고쳤으면 커밋한 뒤 감사를 돌린다.**
**이 정정이 (d)도 같이 풀었다.** "`memory: project`를 뺐는데도 Write/Edit이
그대로 주어진다"며 미확정으로 남겨뒀던 건, **그 제거가 아직 커밋 안 돼서
반영이 안 됐던 것**이다. 실제로 옵션이 빠진 정의가 커밋된 뒤 돈 감사자는
Write/Edit이 없었고 `.claude/agent-memory/`에 쓰기도 없었다(파일 mtime이
전부 그 이전 시각). 진단은 처음 것이 맞았다.
**남은 미해결은 `tools:` 필드**다 — frontmatter에 적힌 Grep/Glob이 실제로는
안 주어지고, 적지 않은 `advisor`가 주어진 라운드가 있었다. 그래서 감사자의
읽기 전용은 계속 **행동 규약**으로 지킨다.
교훈 하나: 이 정정은 감사자의 *자기 보고*에서 출발했다. 앞서 이 세션은
자기 보고를 "근거가 약하다"고 깎았다가 다시 "미해결 불일치"로 올렸는데,
이번엔 그 자기 보고가 **검증 가능한 형태**(blob 해시)로 왔기 때문에
메인이 1차 근거로 승격시킬 수 있었다. 자기 보고 자체가 약한 게 아니라
**대조 가능한 형태로 오는가**가 갈림점이다.
## 11. [사용자 결정] `.claude/agent-memory/`는 커밋해서 추적한다
**사용자 논거 원문**:
> "agent-memory 는 커밋해도 될것 같음. 실 기록이고 디펜던시도 아니고,
> 어차피 SAFETY.md 에 따라 구현 시점에는 컨테이너에서 개발되며 다른
> 프라이빗 git 에 올라가고 검토 후 머징되는거라, 문제되는 메모리 있으면
> 사람이 감사처리 마지막으로 함.(환경 노출 등?) 결국 프로젝트 사이드
> 기록이고 같이 올려지는게 맞는게, 개발 환경이 다수라서 필요해보임."
요지 세 가지 — (1) **개발 환경이 여러 개**라 에이전트 메모리가 레포를
따라다녀야 함, (2) 실 기록이고 빌드 디펜던시가 아니라 커밋 비용이 낮음,
(3) 환경 노출 같은 위험은 `SAFETY.md`가 규정한 파이프라인(컨테이너 개발 →
프라이빗 git → 사람 검토 후 머징)의 **마지막 사람 감사**가 최종 방어선.
메인 세션이 커밋 전에 절대경로/이메일/키/토큰 패턴을 스캔했고 노출 소지는
없었다(히트는 전부 frontmatter `name:`과 마크다운 링크). 커밋 전에 그 안의
낡은 결론 하나도 고쳤다 — `project_agent_prompt_caching_bug_reproduced.md`
아직 "캐시 가설"로 서술돼 있었는데 §10에서 이미 뒤집힌 것이라, 커밋된 HEAD
모델로 다시 쓰고 `MEMORY.md` 색인 줄도 같이 갱신(파일 이름은 그대로 두되
frontmatter `name`/`description`을 바꿈). 이 대응 자체가 README가 경고하던
"에이전트가 자기 메모리에 stale한 결론을 남긴다"의 실사례다.
## 12. [재재정정] §10의 "커밋된 HEAD"도 틀렸다 — 지금은 "모른다"가 정답
새 절차의 2라운드에서 감사자 **둘 다 독립적으로** 같은 반증을 가져왔다.
그들이 받은 정의 지시문은 **어느 커밋과도 일치하지 않는 하이브리드**였다:
상단 배너는 구버전(`1343796` 계열)인데 "출력 형식"의 `사용자 판단` 문단은
신버전(`07c77e5`에만 있음)이었다. 메인 세션이 독립 확인:
- `git log --all -S'당시 있던' -- .claude/agents/quad-doc-auditor.md` → **커밋 없음**
- `07c77e5`에는 `사용자 판단` 있고 `당시 있던` 없음 / `1343796`에는 둘 다 없음
그들이 받은 텍스트는 메인 세션이 이 파일을 **여러 번에 나눠 편집하던 중간의
워킹트리 상태**(출력 형식 추가는 했고 배너 재작성은 아직 안 한 시점)와
일치하고, 그 상태는 **커밋된 적이 없다**.
| 실행 | 실제로 받은 정의 |
|---|---|
| 폐기된 워크플로 | 세션 시작 시점 상태 |
| 감사 1라운드 | 그 시점 HEAD 커밋(`1343796`)과 바이트 동일 |
| 감사 2라운드 | 어느 커밋과도 불일치 — 커밋된 적 없는 중간 워킹트리 상태 |
**즉 이 세션은 같은 문제에 세 번 결론을 냈고 앞의 둘이 다 틀렸다**(§2
"세션 시작 스냅샷", §10 "커밋된 HEAD"). 세 번째 가설을 세우지 않는다.
남기는 건 관측표와 실무 규칙 하나뿐: **정의를 고쳐도 반영됐다고 가정하지
말고, 중요하면 마커 문구를 넣어 감사자에게 물어 확인할 것** — 이 반증이
정확히 그 방법으로 나왔다(2라운드 프롬프트에 "출력 형식에 `사용자 판단`
문단이 있나"를 끼워 물었고, 그 답이 하이브리드를 드러냈다).
**교훈**: §10에 "자기 보고도 대조 가능한 형태면 1차 근거로 승격된다"고
적었는데, 이번엔 그 승격된 근거로 세운 결론이 또 틀렸다. 대조 가능한
자기 보고 하나는 **그 실행이 무엇을 받았는지**를 말해줄 뿐 **메커니즘이
무엇인지**를 말해주지 않는다 — 관측 하나에서 규칙을 일반화한 게 두 번
연속 실패의 공통 원인이다.
**(d) 결론은 살아남는다.** `memory: project` → Write/Edit 상관관계는
제거 이후의 후보 텍스트가 전부 그 옵션을 안 가지므로, 어느 텍스트가
실렸든 영향을 안 받는다(제거 이후 세 라운드 전부 Write/Edit 없음 +
메모리 주입 없음). 미해결로 남는 건 `tools:` 필드 미반영뿐.
## 13. [실측 재확인] 감사자 모델 — 다섯 실행 전부 sonnet
사용자가 "여전히 opus로 돈다"고 재지적해 2라운드 감사자 둘을 같은 방법으로
다시 집계했다. 최상위 `message.model` 기준:
| 실행 | sonnet 턴 | opus 턴 |
|---|---|---|
| 워크플로 시절 감사자 18개 | 1550 | 0 |
| 2라운드 A | 80 | 0 |
| 2라운드 B | 58 | 0 |
**감사자 턴은 전부 sonnet이다.** 다만 감사자 실행마다
`message.usage.iterations[]` 안에 `claude-opus-5` 항목이 **정확히 1개씩**
붙는다(그 메시지 자신의 `message.model`은 sonnet, 그 턴의 도구는 Bash).
감사자가 `advisor`를 부른 것도 아니다 — 도구 사용은 Bash/Read뿐이었다.
이게 정확히 무엇인지는 모른다(회계 항목으로 보임). **모델을 표시하거나
집계할 때 이 항목을 세면 "감사자가 opus"로 보인다.**
캐싱으로는 설명이 안 된다 — `model: sonnet`은 이 파일 **최초 커밋(`a1c0e44`)
부터** 있었으므로, 아무리 낡은 버전이 실려도 sonnet이다.
## 14. [해소] "감사자가 opus로 돈다"는 관측은 **뷰잉 이슈**였다
사용자가 서브에이전트 뷰에서 위로 스크롤하면 모델명이 opus로 보인다고
두 번 지적했고, 트랜스크립트 실측(다섯 실행 전부 sonnet)과 계속 어긋났다.
사용자가 직접 그 화면을 확인해 원인을 특정함 — **맨 위에 보이는 건
Claude Code 세션 헤더**다:
```
▐▛███▜▌ Claude Code v2.1.233
▝▜█████▛▘ Opus 5 · Claude Max
▘▘ ▝▝ ~/Desktop/quad
```
이건 **메인 세션의 모델**이지 서브에이전트의 모델이 아니다. 서브에이전트
뷰에서도 최상단엔 같은 세션 헤더가 나오므로, 위로 스크롤하면 무조건
상위 모델이 보인다. 사용자 표현대로 "이건 상위 네 모델을 의미".
이제 세 소스가 전부 일치한다 — 트랜스크립트 `message.model`(sonnet),
결과 출력 필드(sonnet), 세션 헤더(메인 모델, 서브에이전트와 무관).
**frontmatter `model: sonnet`은 정상 동작하며, 감사자는 sonnet으로 돈다.**
**교훈 — 이 조사에서 모델을 잘못 읽을 자리가 세 군데였다**:
1. `"model"` 문자열 grep → `message.usage.iterations[]`의 opus 항목에 낚임
(감사자 실행마다 1개씩 붙음, 그 메시지 자신은 sonnet, 정체는 미상)
2. 화면 최상단 배너 → 세션 헤더(메인 모델)라 서브에이전트와 무관
3. 폐기된 워크플로의 `general-purpose` 픽스 에이전트 49개는 **진짜로
opus**였음 — 같은 실행 안에 opus/sonnet이 섞여 있어 "어떤 건 sonnet으로
나온다"는 관측과 헷갈리기 쉬움
**신뢰할 소스는 하나**: 트랜스크립트의 최상위 `message.model`(원하면
`attributionAgent`로 어느 에이전트 몫인지까지 확인).
## 15. 감사 3라운드 — `agent-memory`가 정확히 사각지대였음
두 감사자가 **독립적으로 같은 확실 발견**을 가져왔다: `agent-memory/`
`project_agent_prompt_caching_bug_reproduced.md`가 "커밋된 HEAD에서 읽힌다"를
여전히 **확정 사실로** 서술하고 있었다. §12에서 그 결론을 반증해놓고,
그 정정을 담은 커밋(`1935dd4`)이 `agent-memory/` 아래를 **하나도 안 건드린**
것이다(`git show --stat`으로 감사자가 확인).
이건 이 감사 체계가 설계상 노리던 사각지대의 교과서적 사례다 —
`conventions.md`가 말하는 "변경한 세션 자신은 자기가 뭘 안 건드렸는지
모른다". 게다가 이 파일은 **같은 세션에 두 번 연속으로** stale이 됐다:
캐시 가설로 썼다가 → "커밋된 HEAD"로 고쳤는데 → 바로 다음 커밋이 그걸
뒤집었는데 또 안 따라옴. `agent-memory/`가 2026-08-16에 막 추적 대상이
돼서 아직 "이것도 코퍼스"라는 습관이 안 잡힌 탓이다.
고치면서 그 메모리가 **결론을 복제하지 않고 배너를 가리키게만** 바꿨다 —
같은 사실이 두 곳에 적혀 있으니 두 번 갈라진 것이라, 근본 원인을 없애는
쪽으로. 3라운드 A는 그 외에 `todos.md`의 매달린 포인터도 잡았다("아래 부수
확정 참고"인데 그 헤딩이 직전 라운드에 "미해결 1/2"로 개명됨).
**마커 확인 결과 — 관측표에 새 행**: 3라운드 감사자 **둘 다** 배너의
"재재정정" 낱말과 관측표를 받았고 디스크 현재 내용과 바이트 동일이라고
보고했다. 즉 **일관되게 낡은 게 아니다** — 어떤 실행은 최신을 받고 어떤
실행은 안 받는다. "모른다"를 유지할 근거가 하나 더 늘었고, 동시에
**마커 확인 방식이 실제로 작동한다**는 것도 재확인됐다.
부수적으로 둘 다 자기 모델을 "You are powered by the model named Sonnet 5.
The exact model ID is claude-sonnet-5."로 보고 — §13/§14의 트랜스크립트
실측과 일치.
## 16. 감사 4라운드 — 코퍼스는 깨끗, 대신 주입 경로 실측이 나옴
한 패스는 툴링, 다른 패스는 **설계 코퍼스**(`base/`/`research/`/`ROADMAP.md`)를
맡겼다. 이 세션이 툴링만 만졌으니 설계 쪽은 오래 안 훑인 영역이었다.
**설계 코퍼스 각도(B)** — 이 세션과 무관한 기존 항목 2건:
- `todos.md`가 "Handler 작성 체크리스트 **8개**", "우선순위1 **11개** 전원
완료"로 개수를 두 번째 장소에 하드코딩. 이 프로젝트 자신의 "개수는 소스
하나만" 규칙 위반이라 개수를 빼고 소스를 가리키게 함.
- `base/architecture.md``const` 미채택 사유에 날짜 없음 —
`doc-check.py`의 TEMPORAL 정규식이 못 잡는 자연어 변형("지금은 ~ 못해서").
감사 절차 7번이 노리는 유형이 실제로 걸린 사례. → 아래 §17.
감사가 확인만 하고 넘어간 것도 값졌다: `doc-check.py`
`dispatch-core-plan.md` 인용 WARN 클러스터 11곳을 직접 파본 결과 **전부 본문에
굵은 글씨 인라인으로 실존**했다(헤딩이 아니라 스크립트가 못 찾을 뿐).
그 문서가 최근 분리된 파일이라 "분리 후 인용자 안 따라옴" 패턴이 의심됐던
자리인데 콘텐츠 유실이 없음이 확인됐다.
**툴링 각도(A)** — 코퍼스 확실 발견 0건. 대신 주입 경로 관측 둘:
- 이번 라운드가 받은 **에이전트 정의**는 HEAD보다 정확히 1커밋 전이었다
(2라운드 같은 하이브리드는 아니었음). 관측표 4번째 행.
- **`CLAUDE.md` `@import` 컨텍스트는 세션 시작 시점에 고정된다** —
감사자가 받은 `conventions.md`/`todos.md`/`project-context.md`가 세션 시작
커밋(`8aeec76`)과 정확히 일치했고 그 사이 7커밋이 있었다. 감사자가
"메인 세션 컨텍스트도 그런지 확인해달라"고 요청해 메인이 자기 컨텍스트를
직접 확인 — **그렇다.** 이 세션의 메인은 삭제된 워크플로를 "먼저 돌릴 것"
이라 지시하는 옛 `conventions.md`와, 항목 6에서 끝나는 옛 `todos.md`
컨텍스트에 들고 작업해왔다(실제로는 파일을 직접 읽고 고치며 새 절차를
따랐으므로 행동에는 영향이 없었다).
이건 에이전트 정의 쪽의 "모르는 현상"과 달리 **경로가 다르고 동작이
명확한 것**이라 `conventions.md`에 규칙으로 적었다: **`@import` 파일을
고친 세션은 자기가 방금 쓴 규칙을 컨텍스트에서 못 보므로, 기억에
의존하지 말고 파일을 직접 `Read`해서 따를 것.**
## 17. [사용자 결정] `const` 미채택 재검토 — 에이전트가 정할 문제가 아니었음
감사가 "날짜만 붙일지, 구체적 트리거를 명시할지"를 사용자 판단으로 올렸는데,
사용자가 **전제 자체를 정정**했다.
**사용자 논거 원문**:
> "const 건은 생각보다 더 컴플랙스함. 예를 들어 pesde 의 타입추출화 해서
> d.ts 같이 types emit 하는 류가 있음. 아직 툴링이 미성숙한 상태라 const 를
> 제공 못하는 상황이고, 언제 다시 사용가능해지는지 명확치가 않음. 따라서
> 이는 사용자가 확인된 정보로 제공하는게 맞다고 보고 있음. 필요한 경우
> Human todo 에 해당 부분의 사용 가능해지는 시점을 파악하거나, 사용
> 가능해질 때 에이전트에 알려야할 필요를 명시해둬"
즉 "구체적 트리거"를 에이전트가 적어둘 수 없는 종류다 — pesde의 types emit
같은 툴링 체인의 지원 시점은 외부 사정이고 에이전트에 관측 수단이 없다.
그래서 `question.md`에서 닫고(→ `archive/question-resolved.md`)
**`HUMAN_TODO.md` 8번**으로 옮겼다: 사용자가 시점을 파악하거나, 사용
가능해질 때 에이전트에 알린다. **알려주기 전까지 에이전트는 스스로 판단하지
않고 "일단 `local`로" 원칙을 유지한다.**
## 18. 다음 세션에 넘기는 것
**감사 루프는 4라운드에서 사용자 결정으로 중단**(수렴 미달). 발견 추이
6→5→2→2, 3·4라운드분은 이 세션 변경의 stale이 아니라 코퍼스의 오래된
일반 부채였음. **이번 미수렴 때문에 이어서 돌릴 필요 없음** — 다음 세션이
중대 변경을 하면 그때 평소 절차대로.
**먼저 읽을 것**: `.claude/conventions.md`(이번에 크게 바뀜 — "설계 원칙"
절 신설, 감사 루프 절차 전면 재설계, "사용자 발언 인용 시 논거까지" 관례
신설, `@import` 신선도 주의) → `.claude/todos.md` 7번 → 필요하면 이 문서.
**M0 게이트는 여전히 없음** — 이 세션은 툴링만 만졌고 설계 결정은 안
건드렸다. 착수 전 필독은 그대로 `base/typing-limits.md`
`base/dispatch-core-plan.md`.
**사용자 대기 항목**:
- `HUMAN_TODO.md` 8번 — `const` 툴링 지원 시점(사용자가 알려줄 때까지
에이전트는 판단하지 말 것)
- `question.md` — 용어 정리 잔여, `Operator` 이름 등 기존 항목. 이번 세션이
새로 연 질문은 없음(연 것 둘 다 같은 날 닫힘)
**열린 툴링 미스터리 둘**(M0와 무관, 급하지 않음):
1. 서브에이전트 정의가 언제 반영되는지 모름 — 관측표는
`agents/quad-doc-auditor.md` 상단 배너. **세 번째 가설을 세우지 말 것**,
필요하면 마커로 확인.
2. `tools:` 필드 미반영(적힌 Grep/Glob 안 주어지고, 안 적은 `advisor`
주어짐).
**착수하지 말 것**: `session-summary.md` 자동생성 마이그레이션 — 사용자가
직접 다듬기로 한 항목(`research/doc-include-plan.md`).

View file

@ -0,0 +1,270 @@
# 2026-08-16 세 번째 세션 — 표기 컨벤션으로 doc-check 정규식 줄이기
전 세션(`session/2026-08-16-02-audit-tooling-verification-and-first-real-run.md`)
인수인계를 받고 시작. 설계는 안 건드림, 문서 툴링만.
## 사용자 문제 제기
> 현행 md 툴링 py 상 날짜 적는거 형식 같은것도, 강제화 하게 하는게 맞다 생각이
> 드는듯. doc-check.py 자체가 regex 를 여럿 사용해 정적으로, 결정론적이게
> 분석하는데, 형식이 많이 왔다 갔다 하면 문제가 커져. 여러 regex 를 추가해가며
> 하는것도 좋지만, '예상 가능 범위' 를 어느정도 컨벤션으로 정하는게 좋다
> 생각하는데(가장 비용이 쌈), 어떻게 봄?
## 실측 — 날짜는 이미 문제가 아니었다
전수 조사:
```
20NN-NN-NN 형태 1864건 / 1864건 (100%)
다른 구분자(. 또는 /) 0건
비-ASCII 공백 혼입 0건
대괄호 마커 383건
```
**강제 장치가 없는데 100% 균일**. 사용자 직감("싼 컨벤션이 통한다")을 반증하는
게 아니라 오히려 뒷받침하는 데이터라, 이걸 근거로 삼되 드리프트가 실제로 있는
곳으로 방향을 돌렸다.
`doc-check.py` 당시 출력: 라이브 문서 88개, ERROR 0, **WARN 86건**
그중 **절 참조 불일치가 78건(91%)**. 날짜 없는 주장은 3건뿐.
> **재검증 시 주의**: 이 78은 **실제 작업 트리** 기준이다. `git archive HEAD`
> 같은 걸로 격리 체크아웃해 돌리면 72가 나오는데, 그건 아카이브에
> gitignore된 `.claude/initreq/`가 안 들어가서 `raw-userinput.md`를 가리키는
> 인용들이 "절 참조 불일치"가 아니라 "깨진 파일 참조"로 분류되기 때문이다
> (커밋 전 4라운드 감사가 실제로 이 차이에 걸렸다). 숫자를 대조할 땐 트리를
> 갈아엎지 말고 `git show HEAD:<경로>`로 파일 단위 비교를 쓸 것.
## 프레이밍 교정 — 컨벤션은 정규식의 대안이 아니다
사용자가 "regex를 늘릴까 / 컨벤션을 정할까"의 양자택일로 물었지만, 실제
관계는 **컨벤션이 정규식을 열거형에서 앵커형으로 바꿔 작게 만드는 것**이다.
`DATED`가 "±3줄 안에 아무 날짜나 있으면 통과"라는 느슨한 판정을 하는 이유가
정확히 앵커할 표준 마커가 없어서다.
동시에 컨벤션이 못 고치는 영역도 명확히 했다 — 검사는 두 종류다.
- **(A) 이스케이프 해치형**: 도구가 위험 패턴을 잡고 사람이 표준 마커로
잠재움(`REF` 절 인용, `DATED`, `OURS`). 컨벤션이 곧바로 이득.
- **(B) 탐지형**: 도구가 위험한 문장을 먼저 찾아야 함(`TEMPORAL`의 6개
한국어 표현, `'아직 반영 안 됨'` 리터럴). **컨벤션이 원리적으로 못 고침**
"시한부 주장에 날짜를 붙여라"를 정해도 정규식은 *날짜를 안 붙인 문장*을
못 찾는다. 이 잔여가 `quad-doc-auditor`의 몫.
## 우선순위 제안과 사용자 결정
1. 절 인용 규약 + ERROR 승격 (WARN의 91%)
2. 세션 지칭 표기 (grep을 깨뜨림)
3. 날짜 마커 라벨 어휘 닫기
사용자 결정: **1+2 채택, 3 기각.** 3에 대한 논거가 날카로웠음 —
> 3. 의 경우 사실상 에이전트가 순서 섞였을 때 최신의, 옳은 요소 선택에 도움이
> 되는 정보에 가깝지 이게 warn 을 만들지는 못할듯.
즉 라벨은 **기계 검사 대상이 아니라 읽는 쪽(에이전트)의 판단 재료**다. 어휘를
닫아도 검사가 하나도 안 생기므로 비용만 든다. 3에 딸려 있던 `DATED` 앵커
좁히기만은 유일하게 WARN을 만드는 항목이라 성격이 다르지만, 지금 그 위음성이
아프다는 증거가 없어 같이 보류.
## 핵심 발견 — 78건은 대부분 "코퍼스가 지저분한 것"이 아니라 "검사기가 못 읽는 것"이었다
`dispatch-core-plan.md``#` 헤딩이 11개뿐인데 다른 문서들이 인용하는
"우선순위 동률/매치 실패 처리", "핸들러 내부 상태 저장", "props 순회 순서"는
전부 실재한다 — **`**볼드**`로 시작하는 줄**로. `headings()``#` 줄만
수집하니 이게 통째로 안 보였다.
그런데 볼드를 통째로 절 제목으로 인정하면 검사가 장식이 된다:
| 파일 | `#` 헤딩 | 볼드(전체) | 볼드(줄머리) |
|---|---|---|---|
| `dispatch-core-plan.md` | 11 | 264 | 130 |
| `slot-plan.md` | 40 | 331 | 162 |
| `architecture.md` | 9 | 60 | 23 |
세 규칙을 실측 대조:
```
A 느슨 볼드 + 부분문자열 해소 30
B 줄머리 볼드 + 부분문자열 해소 32
C 줄머리 볼드 + 앞부분일치 해소 26 → 선두 장식(⚠️) 정규화 추가 후 30
```
**C가 A와 해소 건수는 같으면서 매칭 표면만 좁다.** C 채택. `#` 헤딩은
부분문자열, 볼드 절은 앞부분일치라는 **비대칭**이 핵심 — 헤딩은 명시적 절
마커라 오인 위험이 없고, 볼드는 강조와 구분이 안 되니 더 엄격해야 한다.
부수로 `1. **…**`(번호 리스트 볼드)도 절로 인정하도록 넓힘 —
`conventions.md`의 핸드오버 체크리스트가 정확히 그 형태고 다른 문서가 그걸
절로 인용한다.
## `initreq/` 면제
`raw-userinput.md``.claude/initreq/`에 있는 읽기 전용 원본 요청이고, 스스로
"진짜 생각의 흐름 대로 작성되어있으며 정리가 없음"이라 밝힌 문서다. 여길
가리키는 인용은 절 제목이 아니라 원문 문장을 따오는 것이라 헤딩 대조가 애초에
무의미 → 폴더 범주로 면제. (`is_history()`를 넓히는 건 오답이었음 — 그건
*인용하는 쪽* 문서를 면제하는 스위치인데 여기선 *인용 대상*이 문제였다.)
## 결과
```
절 참조 WARN 78 → 42 (검사기 수정) → 23 (인용 19건 교정)
→ 15 (인용 8건 추가 교정) → 0 (추적 후 15건 교정)
그리고 WARN → ERROR 승격
```
교정한 인용은 대부분 "실제 절이 명확히 존재하는데 의역돼 있던 것". 대표적으로
`architecture.md``복사(clone) 구현 지양`을 "복사 구현 지양"으로,
`이벤트 핸들러는 self(Instance)를 받지 않는다`를 "self를 받지 않는다"로 줄여
쓴 것들. `operator-sugar-plan.md``tween-plan.md``research/`→`base/`로
승격된 걸 안 따라와서 경로도 같이 고침.
큰따옴표가 든 절 제목은 인용 문법(`"..."`)을 깨므로 앞부분만 인용하는 규칙이
여기서 나왔다(`pluggable 디스패치 엔진 자체도 "인터페이스"로 base가 소유`).
## 마지막 15건 — 서브에이전트 3개에 병렬 위임
"그 절이 어디로 갔는가"를 따져야 해서 읽을 양이 많았고, 메인 컨텍스트를
지키려고 대상 파일별로 3등분해 위임했다(읽기 전용, 발견만 리포트).
**총평: 설계 서술이 유실된 건은 0건.** 대부분 "애초에 절이 아닌 것을 절로
인용해온 것"이었다 — 코드 주석 한 줄(`클로저 생존이 곧 gchold 생존`),
본문 산문(`데이터를 테이블에 직접 두고`), 앞으로 쓸 문서의 주제명
(`왜 quad를 쓰는가`), 그냥 의역(`bind는 유일 슬롯`).
부수로 드러난 것 — `onchange-plan.md`가 9차 분할 때 **일부러 안 옮긴** 절을
`event-plan.md`로 잘못 가리키고 있었고(실제 위치는 `bind-system-plan.md`),
`brand-plan.md`는 2026-08-07에 이미 이행된 정정을 아직 "정정 대상"이라
부르고 있었다.
## 검사기 보완 — 조용한 통과 구멍 둘
에이전트들이 검사기 자체의 결함을 짚었다.
1. **인용이 60자를 넘으면 `REF`가 매칭 자체를 안 해 검사에서 조용히
빠져나갔다.** 위양성보다 나쁜 종류라 상한을 160자로 넓힘. 그러자 3건이
새로 드러났는데 전부 **줄바꿈 때문에 원문에 없는 공백이 인용에 끼어든**
케이스여서, 비교를 공백 무시로 바꿔 순증 작업량 0으로 닫음.
2. **선두 상태/날짜 태그**(`**[2026-08-16 신설]** 제목…`)가 볼드 절 제목의
앞부분을 차지해 어떤 제목형 인용도 매칭 불가였다. 태그는 시간이 지나면
바뀌는 메타 표시라 제목의 일부가 아니므로 양쪽에서 벗겨내고 비교.
이건 ERROR 승격 직후 **게이트가 내가 방금 쓴 인용 두 개를 잡아서** 발견됐다.
## 인용을 짧게 자르다 문장을 깬 것들 — 프로즈 패스에서 회수
정규식이 통과해도 의미는 깨질 수 있다. 커밋 전 diff를 "인용 매칭"이 아니라
"문장 읽기" 모드로 다시 훑어 회수한 것들:
- **`ROADMAP.md`의 blockquote를 통째로 깨뜨렸었다** — 인용이 `> ` 줄바꿈에
걸쳐 있어 마커가 인용문 안에 들어가 있었고, 그걸 지우면서 인용 블록이
무너졌다. 규약에 함정으로 명시.
- 절 제목을 명사구 자리에 그대로 끼워 **문법이 깨진 문장 5건**
(`"pluggable 디스패치 엔진 자체도"는 원칙에…`). `"…" 절이 세운 원칙`
형태로 교정하고 이것도 규약에 넣음.
- 축약하다 주장이 빠진 인용 1건(`README.md`의 "빈 자리 아님" 결론).
## 1라운드 감사 — 규칙 자체의 정밀도 구멍
두 번째 감사자가 더 중요한 걸 짚었다 — `attribute-plan.md`
`dispatch-core-plan.md`가 인용한 `architecture.md`
`**pluggable 디스패치 엔진 자체도 …**`**절이 아니다.** 한 문단이
줄바꿈되면서 강조 스팬이 우연히 줄머리에 걸린 자리이고, 진짜 절은 두 줄 위의
`**패키지 경계**`다. 같은 원문을 인용하는 `slot-plan.md:209`는 이미 올바르게
`"패키지 경계"`를 쓰고 있어서, **같은 diff 안에 옳은 인용과 우연한 인용이
공존**하고 있었다.
즉 "줄머리 볼드"라는 규칙만으로는 절과 랩된 강조를 못 가른다. 진짜 절 제목은
**빈 줄 다음에 오거나 리스트 항목 머리**라는 성질이 있어서 그걸 조건으로
추가했다. 실측 결과 **정확히 그 가짜 인용 2건만 깨지고 나머지 46건은 그대로
통과**했고, 볼드 후보 풀은 `dispatch-core-plan.md` 기준 131→104로 좁아졌다.
위양성 0으로 정밀도만 올린 셈.
교훈: 이 규칙은 "사람이 절이라고 인식하는 것"을 근사하는 휴리스틱이라,
근사가 어긋나는 자리는 실제 인용을 감사해서만 드러난다. 검사기가 통과시켰다고
그게 절인 건 아니다.
## 1라운드 감사 — 이 세션 자신의 실수
`pre-implementation-audit.md`의 "아직 안 고침" 절에 있는 `State<Modifier>`
비일관성 항목이 해소된 것으로 보여 `**[해소됨 — 2026-08-16 확인]**` 마커를
달았는데, **바로 다음 문단에 2026-08-09 세션이 단 `**[완전 해소, 2026-08-09
세션]**`이 이미 있었다.** 중복일 뿐 아니라 해소 시점을 일주일 뒤로 잘못
읽히게 만들어 되돌렸다. 서브에이전트(추적 담당)가 "후임 절이 옛 서술을
뒤집었다"는 근거를 정확히 줬지만 **그 항목이 이미 해소 표시를 달고 있다는
사실은 안 봤고, 메인 세션도 그 문단 아래를 안 읽었다** — 근거가 맞아도
"이미 처리돼 있지 않은가"를 따로 확인해야 한다는 교훈.
이 항목이 해소 표시를 달고도 "아직 안 고침" 헤더 아래 남아 있는 헤더-내용
불일치는 이 세션 이전부터 있던 별개 부채라 임의로 재배치하지 않고 사용자에게
물었고, **"이미 고침" 절로 옮기라는 결정**을 받아 반영했다. 그 헤더 아래엔
이제 진짜 미해소인 `Destroying` 훅 건만 남는다. 감사 세 라운드가 전부 이
자리를 지목했는데, 세 번 다 "고칠지 말지가 판단 문제"라 서브에이전트는
결론을 못 냈다 — 메인 세션만 사용자에게 물을 수 있다는 게 이 절차의 핵심인
이유다.
## 2라운드 감사 — "규칙을 세운 세션이 그 규칙을 어긴다"
1라운드 발견을 반영한 뒤 2라운드를 돌렸더니 또 실질 발견 2건이 나왔다.
둘 다 **이번 세션이 새로 생산한 산문**의 문제였다.
1. **`base/brand-plan.md`에 이번에 새로 쓴 문장이 "세션은 파일 ID로
지칭할 것" 규약을 어겼다** — 같은 커밋에서 만든 규약을 같은 커밋의 새
문장이 위반. `2026-08-07 여덟 번째 세션` → 세션 파일 ID로 교정했고,
이번에 건드린 다른 두 줄(`README.md`의 `tools/` 행, `lifecycle-pattern.md`)도
규약의 "만지는 김에 같이 고친다" 조항대로 처리했다.
2. **`research/framework-comparison-findings.md`의 수정이 "고침"이 아니라
"검사망 회피"였다** — `"왜 quad를 쓰는가"` 인용을 문법만 비껴가게 고쳐서
검사 대상에서 통째로 빠졌는데, 감사자가 `REF` 정규식으로 `group(3)`
`None`이 되는 걸 직접 확인해 잡았다. 같은 diff의 다른 두 회피
(`CLAUDE.md`의 "세션 히스토리", `옛 Modifier는 핸들러 계층을 모름`)는
**과거형으로 참인 사실**을 서술하니 정당하지만, 이건 대상 문서에 근거가
없는 **미래형 주장**이었다는 게 판정 근거. 실제 절(`초심자
(getting-started) core loop`/`quadnomicon`)을 가리키도록 고쳤다.
부수로, `conventions.md`에 박아둔 세션 서수 분포 숫자(971/68/8)가
재현되지 않았다 — 내 집계는 `session/`까지 포함한 전체 grep이었고 감사자는
`doc-check.py`가 훑는 88개 라이브 문서 기준이었다. **집계 범위를 안 적은
숫자를 매 세션 로드되는 파일에 박은 것 자체가 이번에 세운 "개수는 소스
하나만" 원칙 위반**이라 정성적 서술로 바꿨다. 같은 이유로 날짜 균일 수치도
`conventions.md`에서 빼고 이 파일을 가리키게 했다.
**교훈**: 규약을 세우는 커밋이 그 규약의 첫 위반자가 된다. 규약을 쓴
세션은 자기 diff를 그 규약으로 한 번 되짚어야 하고, 그걸 자기 self-audit
으로는 잘 못 한다(이번에도 둘 다 서브에이전트가 잡았다).
## 3라운드 감사 — 수정이 만든 새 결함, 그리고 사각지대 하나
2라운드 수정을 반영하고 3라운드를 돌렸더니 **내가 2라운드에 만든 마크다운
버그**가 나왔다. `"cleanup"` 용어 인용을 규약대로 고치려고 볼드 스팬을
쪼개다 **짝 없는 `**`를 남겼다** — `lifecycle-pattern.md`의 `**` 개수가
144(짝수)→145(홀수)로 바뀐 걸 감사자가 교차 확인해 잡았다. 렌더러에 따라
그 뒤 리스트의 볼드 페어링까지 밀려 깨질 수 있는 실제 결함이었다. 복구한 뒤
변경 파일 전체에 같은 검사를 기계적으로 돌려, 내 변경이 짝을 뒤집은 파일은
그것 하나뿐임을 확인했다(다른 홀수 파일들은 HEAD에서도 이미 홀수 — 코드블록
안의 `**` 때문).
**구조적 사각지대 하나가 같이 드러났다**: `- **[해소됨, …]** 실제 제목 — …`
처럼 **볼드가 상태 태그에서 닫히고 진짜 제목이 볼드 밖 평문**이면
(`archive/question-resolved.md`가 이 형태) 검사기는 `[해소됨, …]`만 절
제목으로 본다. 절 참조가 ERROR로 승격된 뒤라, 이걸 모르고 규약대로 큰따옴표
인용을 하면 **실재하는 서술인데도 커밋이 막힌다.** 규약에 캐비엇으로 명시하고
"새로 쓸 땐 태그와 제목을 한 볼드 안에" 지침을 넣었다. 게다가 이 세션이
신설한 "세션은 파일 ID로 지칭" 관례가 `**[태그, 세션ID]**` 형태의 리드를
늘리는 방향이라 재생산될 구조였다는 지적까지 붙었다.
보류한 것 하나 — `#` 헤딩 검사가 **부분문자열**이라 `"확정"` 같은 짧은
인용은 같은 파일의 무관한 헤딩에 걸려 통과한다(`slot-plan.md`엔 "확정"이 든
헤딩이 6개). 다만 감사가 **실제 오매칭 사례를 하나도 못 찾았고**, 사용자와
같이 `conventions.md`의 "드문 오용이나 가상의 미래 요구까지 방어/최적화하려고
구조를 복잡하게 만들지 않는다" 원칙에 따라 지금은 손대지 않기로 했다.
## 감사자가 `git stash`를 쓴다 — 실동에서 드러난 위험
`.pyc` 추적 해제(`git rm --cached`)가 두 번 되돌아갔다. 원인은 감사자들이
HEAD와 대조하려고 `git stash`를 건 것 — **커밋 안 된 작업 트리에서** 그걸
하면 메인 세션의 스테이지가 풀리고, pop이 실패하면 세션 작업을 통째로 날릴
수 있다. 이번엔 유실이 없음을 확인했고, `.claude/agents/quad-doc-auditor.md`
작업 트리를 바꾸는 git 명령 금지와 대안(`git show HEAD:<경로>` 등)을 규약으로
넣었다. "읽기 전용"을 도구 목록으로 못 거는 이상(`tools:` 필드가 그대로
반영되지 않는 기존 미해결 항목) 이런 규약이 유일한 방어선이다.

View file

@ -33,7 +33,7 @@
아니라 **하강 diff**임, `retractFrom`은 3-인자, 클로저 인자는
`nil`이거나 같은 핸들러가 처리할 값(타입 보장), `HANDLER_PRIORITY_FALLBACK`,
"base가 소유하는 핸들러와 주입되는 엔진 op"(`addTag`/`removeTag`/
`setAttribute`). **Handler 작성 체크리스트 8개**를 새 핸들러 짜기 전에
`setAttribute`). **Handler 작성 체크리스트**(개수는 그 문서가 소스)를 새 핸들러 짜기 전에
훑을 것 — 지난 세션들에서 실제로 반복된 실수 목록임.
해소 전 원문은 `archive/question-resolved.md`(0-Y/0-Z/0-A 절), 뒤집힌 옛
@ -42,7 +42,7 @@
1. **구현 시작 — 루트 `ROADMAP.md`의 M0부터.** 설계 단계는 2026-08-04 로드맵
인수인계 라운드로 종료. `research/pre-implementation-audit.md` 우선순위1은
2026-08-12 열일곱 번째 세션에 마지막 넷(1-3/1-4/1-10/1-11)까지 전부
해소되어 **11개 전원 완료**. **[14차 세션 기준] 0-Y/0-Z/0-A까지 전부
해소되어 **전원 완료**(항목 수는 그 문서가 소스). **[14차 세션 기준] 0-Y/0-Z/0-A까지 전부
해소돼 설계 게이트는 남아있지 않음** — 착수 전 읽을 것은 위 0번의 두
문서(`typing-limits.md`/`dispatch-core-plan.md`)뿐이고, 스파이크 상태는
아래 그대로:
@ -113,6 +113,14 @@
백엔드 팩토리 표면에 추가될 예정이라는 것도 M1 설계 시 인지. 설계는
네 라운드로 대부분 확정됐고 남은 열린 질문은 `question.md` 3번(개수는
거기도 반복 안 함 — 소스는 `research/debounce-throttle-plan.md` 12절).
**[2026-08-18 추가]** 사용자 아이디어 메모 두 건도 같은 성격의 백로그로
신설 — 스크롤 최적화 외부 유틸 `quad-roblox-fastscroll`
(`research/fastscroll-plan.md`, 선행으로 `Visible=false`일 때
`AbsoluteSize`/`AbsolutePosition` 갱신 여부 실측 필요)과 스프링 물리
기반 지속 업데이트 프리미티브 `quad-spring`(`research/spring-plan.md`,
참고 구현 `qwreey/spring.lua` 사용 가능성 확인 필요) — 둘 다 설계 논의
전 아이디어 단계이고 사용자가 직접 "아주 나중"으로 후순위 지정, M0/설계
게이트와 무관.
5. 자율 작업 루프/스케줄 설정 여부는 사용자 결정 대기 중
(`HUMAN_TODO.md` 2번 항목).
6. **[신규 백로그, 2026-08-14 열네 번째 세션]** 문서 stale 감소용 include
@ -121,23 +129,72 @@
같은 날 CLAUDE.md 분할로 파일럿이 "`session-summary.md`를 통째로
생성"하는 **단방향** 설계로 단순화돼 플랜이 갱신됨(목적지 마커 불필요).
여전히 **구현 착수 전**. M0/설계 게이트와 무관.
7. **[2026-08-16 신설, 대부분 닫힘 — 남은 건 (d) 하나]** 감사 툴링 검증.
7. **[2026-08-16 신설, (a)~(d) 전부 닫힘 — 다만 아래 두 건이 미해결로 남음]** 감사 툴링 검증.
(a) `@import` 3개(`conventions.md`/`project-context.md`/`todos.md`)
실제 로드 — **확인됨**, (b) `quad-doc-auditor` 레지스트리 등록 —
**확인됨**(첫 실측 때 전원 `agentType not found`였던 건 `.claude/agents/`
세션 도중 생긴 디렉토리였기 때문, 재시작으로 해소), (c) frontmatter
`model: sonnet` 반영 — **확인됨**(서브에이전트 트랜스크립트에
`claude-sonnet-5` 기록), (d) **미해소** — 읽기 전용인데 Write/Edit이
주어지는 원인. `memory: project`를 뺐지만 그 뒤 재시작 없이 돌린
관찰뿐이라 진단 미확정이고, **다음 세션 시작 직후에 한 번 확인하면 됨**.
결론 날 때까지 읽기 전용은 도구 유무가 아니라 프롬프트의 행동 규약으로
지킨다.
`claude-sonnet-5` 기록), (d) **해소** — 읽기 전용인데 Write/Edit이
주어지던 원인은 `memory: project`가 맞았음(근거는 `.claude/agents/quad-doc-auditor.md` 상단 배너). 다만
`tools:` 필드가 그대로 반영되지 않는 건 **여전히 미해결**이라, 읽기
전용은 도구 유무가 아니라 프롬프트의 행동 규약으로 계속 지킨다.
**부수 확정 — 정의 파일은 세션 시작 시점 스냅샷으로 고정된다**(에이전트·
워크플로 정의 모두): `quad-handover-audit`이 실제로 돌린 스크립트가 세션
시작 시점 상태와 바이트 단위로 같았고 같은 세션의 편집은 반영 0이었음.
**정의를 고쳤으면 재시작한 뒤에 감사를 돌릴 것** — 안 그러면 옛 정의로
돌면서 새 정의로 돈 것처럼 보인다. 상세는
`.claude/agents/quad-doc-auditor.md` 상단 배너가 소스. M0/설계 게이트와
무관.
**[2026-08-16] 이번 세션의 감사 루프는 4라운드에서 사용자 결정으로
중단 — 수렴 조건(무발견 2연속)은 못 채웠다.** 라운드별 새 발견은
6→5→2→2로 줄었고, 3·4라운드에 나온 것은 **이 세션 변경의 stale이 아니라
코퍼스에 오래 있던 일반 부채**(개수 하드코딩, 날짜 없는 시한부 주장)라
계속 돌리면 수렴이 아니라 옛 부채를 끝없이 캐는 쪽이 된다는 판단.
**이 세션 변경분 자체는 안정적**(4라운드 설계 코퍼스 각도에서 확실
발견 0건). 다음 세션이 중대 변경을 하면 그때 평소대로 감사 루프를
돌리면 되고, 이번 미수렴 때문에 따로 이어서 돌릴 필요는 없다.
**미해결 1 — 정의 파일이 언제 반영되는지 모른다.** 감사자가 실제로 받은
정의 텍스트가 실행마다 달랐다: 세션 시작 상태 → 그 시점 HEAD 커밋 →
**어느 커밋과도 일치하지 않는 중간 워킹트리 상태**(커밋된 적 없음,
`git log -S`로 확인). 이 세션이 "세션 시작 스냅샷", 이어서 "커밋된
HEAD에서 읽힌다"로 두 번 결론을 냈다가 **두 번 다 반증됐으니 세 번째
가설을 세우지 말 것.** 실무 규칙은 하나 — **정의를 고쳐도 반영됐다고
가정하지 말고, 중요하면 마커 문구를 넣어 감사자에게 물어 확인할 것.**
상세 관측표는 `.claude/agents/quad-doc-auditor.md` 상단 배너가 소스.
(워크플로 쪽은 `Workflow({scriptPath})`가 디스크에서 실시간으로 읽는 게
확인돼 있으나, 지금 워크플로를 안 쓰므로 당장 쓸 일은 없음.)
**미해결 2 — `tools:` 필드가 그대로 반영되지 않는다**: frontmatter에 적힌
Grep/Glob이 안 주어지고, 적지 않은 `advisor`가 주어진다. 그래서 감사자의
읽기 전용은 도구 유무가 아니라 프롬프트의 행동 규약으로 지킨다.
**[2026-08-16 닫힘] 재감사 안 됐던 수정 6건은 확인 완료** — 첫 실동이
수렴 못 하고 끊겨 마지막 라운드분이 재감사 없이 커밋됐었는데, 새 절차의
첫 라운드(감사 2개 병렬)가 그 셋(spikes 개수 단일화, `slot-plan.md`
재역전 배너, `doc-check.py` docstring)을 다시 훑어 **회귀 없음**으로
확인했다. 한 패스는 구세대 트리(`8aeec76`)와 현재본의 WARN 목록을 직접
diff해서 대조했고, 그 구간에 오히려 절 참조 오류 2건이 해소된 것도
확인됨. M0/설계 게이트와 무관.
8. **[2026-08-16 신설, 이미 닫힘 — 다음 세션이 알아야 할 규약]** 절 인용
규약이 생겼다. 이제 `` `<파일>.md`의 "절 제목" `` 형태로 인용할 땐
**의역하지 말고 원문에서 잘라 쓸 것**(`#` 헤딩은 부분문자열, `**볼드**`
절은 줄머리 + 앞부분일치). 규칙 본문은 `.claude/conventions.md`
"절 인용 규약"이 소스 — 여기서 반복하지 않음. 지키지 않으면
`doc-check.py`가 **ERROR**로 잡아 커밋 게이트에 걸린다(WARN이 아님 —
절 참조 불일치를 78→0으로 정리한 뒤 승격했음). 경위는
`session/2026-08-16-03-doc-check-section-convention.md`.
9. **[2026-08-16 신설, 이월 — 급하지 않음]** 이번 절 인용 규약 작업에서
의도적으로 **안 한** 것 둘. 둘 다 다음 세션이 알아야 이중 조사를 안 한다.
- **`#` 헤딩 검사가 부분문자열이라 느슨하다.** `"확정"` 같은 짧은 인용은
같은 파일의 무관한 헤딩에 걸려 통과한다(`base/slot-plan.md`엔 "확정"이
든 헤딩이 여러 개). 커밋 전 감사가 **실제 오매칭 사례를 하나도 못
찾았고**, `conventions.md`의 "드문 오용이나 가상의 미래 요구까지
방어/최적화하려고 구조를 복잡하게 만들지 않는다" 원칙에 따라 지금은
안 고치기로 사용자와 합의. 실제로 물리면 그때 좁힐 것(길이 하한, 후보
2개 이상이면 WARN 등).
- **⚠️ 감사자에게 `git stash`를 쓰지 말라고 프롬프트에도 매번 적을 것.**
커밋 안 된 작업 트리에서 감사자가 HEAD 대조하려고 stash를 걸어 메인
세션의 스테이지가 반복적으로 풀렸다(2026-08-16 실동, 유실은 없었음).
금지 규약을 `.claude/agents/quad-doc-auditor.md`에 넣어두긴 했지만
**정의 파일이 언제 반영되는지 모른다는 게 위 7번의 미해결 1번**이라,
정의에만 의존하지 말고 감사자를 띄우는 프롬프트에서 직접 금지할 것.
대안은 `git show HEAD:<경로>` / `git diff HEAD -- <경로>`.

View file

@ -16,10 +16,15 @@
1. [ERROR/WARN] 깨진 파일 참조 라이브 문서가 가리키는 .md/.luau가 실제로
없음. 파일명이 레포의 명명 관례(`OURS`) 걸리면 ERROR, 아니면
외부 문서명일 있어 WARN.
2. [WARN] 깨진 참조 `foo.md` "절 제목" 파일에 없음
2. [ERROR] 깨진 참조 `foo.md` "절 제목" 파일에 없음
(문서를 쪼개거나 헤딩을 고칠 가장 걸리는 아홉 번째 세션에
bind-system-plan.md를 분할하며 20곳이 여기 걸렸음. 제목을 의역해
인용하는 관례가 있어 오탐이 섞이므로 ERROR가 아니라 WARN)
bind-system-plan.md를 분할하며 20곳이 여기 걸렸음)
[2026-08-16] 판정 규칙은 `BOLD_LEAD` 주석과 `conventions.md`
"절 인용 규약" 소스 `#` 헤딩은 부분문자열, `**볼드**` 절은 줄머리 +
앞부분일치. 예전엔 `#` 헤딩만 읽어서 볼드 절 인용을 전부 오탐으로
냈고(78 30), "의역 인용 관례 때문에 오탐이 섞인다" WARN에
묶여 있었다. 의역 인용을 규약으로 금지하고 불일치를 780으로 정리한
ERROR로 승격함.
3. [ERROR] 색인 누락 base/research/archive/reference 파일이 README에 없음
4. [WARN] 날짜 없는 시한부 주장 "아직 안 돌려봄", "열린 질문 없음"
시간이 지나면 거짓이 되는데 언제 기준인지 적힌 문장
@ -74,7 +79,9 @@ def rel(p):
# 예전엔 줄 단위로 돌려서 `base/\nbind-system-plan.md`의 "Length/Offset" 같은
# 자연스러운 줄바꿈 인용을 통째로 놓쳤고, 실제로 문서 분할 후 stale 참조
# 20여 곳이 이 사각지대로 빠져나갔음(14차 세션 리뷰에서 발견).
REF = re.compile(r'`\.?/?((?:[\w.-]+/\s*)*[\w.@-]+\.(?:md|luau))`(\s*(?:의\s*)?"([^"]{2,60})")?')
# [2026-08-16] 인용 길이 상한을 60→160자로 넓힘. 60자를 넘는 절 인용은 매치
# 자체가 안 걸려 **검사에서 조용히 빠져나갔다** — 위양성보다 나쁜 종류의 구멍.
REF = re.compile(r'`\.?/?((?:[\w.-]+/\s*)*[\w.@-]+\.(?:md|luau))`(\s*(?:의\s*)?"([^"]{2,160})")?')
def resolve(target, src):
"""참조 문자열을 실제 경로로 해석 — 상대/부분 경로를 관대하게 매칭."""
@ -108,6 +115,52 @@ OURS = re.compile(r'(-plan|-reversed|-rejected|-findings|-map|-audit|-verificati
r'|^conventions|^project-context|^todos|^session-summary)\.md$')
# [2026-08-16] 이 코퍼스의 "절"은 `#` 헤딩만이 아니다 — `**볼드**`로 시작하는
# 줄을 하위 절로 쓰고, 다른 문서들이 그걸 "절"이라 부르며 인용해왔다. 그걸
# 안 읽어서 절 참조 WARN 상당수가 위양성이었음 — 라운드별 실측 수치는
# `session/2026-08-16-03-doc-check-section-convention.md`가 소스(여기 복제 안 함).
#
# 다만 볼드를 통째로 절 제목으로 인정하면 검사가 장식이 된다 — 실측상 볼드는
# `#` 헤딩보다 압도적으로 흔하고 대부분은 그냥 강조다. 인용이 무관한 강조
# 스팬에 우연히 걸리면 검사가 조용히 통과해버려 진짜 rot을 놓친다. 그래서
# 규칙을 비대칭으로 둔다:
# - `#` 헤딩 : **부분문자열** 인용 허용 (명시적 절 마커라 오인 위험 없음)
# - `**볼드**` 절: 빈 줄 다음이나 리스트 항목 머리에 있고, 인용이 제목
# **앞부분부터**일 때만 인정
# 이 비대칭이 곧 `conventions.md`의 "절 인용 규약"이다. 느슨한 규칙과 해소
# 건수는 같으면서 매칭 표면만 좁다는 걸 실측으로 확인하고 채택했다.
# 절 제목으로 인정하는 볼드는 **빈 줄 다음** 또는 **리스트 항목**뿐이다.
# 그냥 "줄머리 볼드"로 두면 **문단이 줄바꿈되며 우연히 줄머리에 걸린 강조**까지
# 절로 잡힌다 — `architecture.md`의 `**pluggable 디스패치 엔진 자체도 …**`가
# 실제 사례로, 진짜 절은 두 줄 위의 `**패키지 경계**`인데 두 문서가 그 우연한
# 자리를 절이라며 인용하고 있었다(커밋 전 감사가 잡음).
# 리스트 마커는 `-`/`*`/`>`뿐 아니라 `1.`도 인정 — `conventions.md`의 핸드오버
# 체크리스트처럼 **번호 항목 자체가 절**이고 다른 문서가 그걸 인용하는 형태가 있다.
BOLD_LEAD = re.compile(
r'(?:\A|\n[ \t]*\n)[ \t]{0,6}\*\*(.+?)\*\*' # 빈 줄 다음
r'|(?:\A|\n)[ \t]{0,6}(?:[-*>]|\d+\.)[ \t]+\*\*(.+?)\*\*', # 리스트 항목
re.S)
# 선두 장식(⚠️/⭐ 등)은 인용할 때 관례적으로 생략되므로 양쪽에서 벗겨내고 비교
DECOR = re.compile(r'^[^\w가-힣`(\[]+')
# 선두 상태/날짜 태그(`[2026-08-16 신설]`, `[해소됨, …]`)도 제목의 일부가 아니다 —
# 시간이 지나면 내용이 바뀌는 메타 표시라 여기에 인용을 묶으면 태그가 갱신될 때마다
# 참조가 깨진다. 양쪽에서 똑같이 벗겨내므로 태그째 인용해도 여전히 매칭된다.
TAG = re.compile(r'^\[[^\]]{2,80}\]\s*')
def norm_section(s):
"""절 제목/인용문을 비교 가능한 형태로 정규화."""
s = re.sub(r'\s+', ' ', re.sub(r'[`*]', '', s)).strip()
s = DECOR.sub('', s).strip()
stripped = DECOR.sub('', TAG.sub('', s)).strip()
return stripped or s # 태그가 제목 전부였다면 원본을 남긴다
def squash(s):
"""비교 전용 — 정규화 후 공백까지 전부 제거(줄바꿈 인용 대응)."""
return re.sub(r'\s+', '', norm_section(s))
def headings(path):
if not path.endswith('.md') or not os.path.exists(path):
return None
@ -115,10 +168,18 @@ def headings(path):
for line in open(path, encoding='utf-8'):
if line.startswith('#'):
# 백틱/강조 표기는 인용할 때 자주 빠지므로 정규화해서 비교
hs.append(re.sub(r'[`*]', '', line.lstrip('#')).strip())
hs.append(norm_section(line.lstrip('#')))
return hs
def bold_leads(path):
"""줄머리 `**볼드**` 절 제목. 위 BOLD_LEAD 주석의 비대칭 규칙 참고."""
if not path.endswith('.md') or not os.path.exists(path):
return []
txt = open(path, encoding='utf-8').read()
return [norm_section(a or b) for a, b in BOLD_LEAD.findall(txt)]
def interesting(target):
"""검사 가치가 있는 참조인가.
@ -170,14 +231,27 @@ def check_refs(docs):
(errors if OURS.search(name) else warns).append(
msg if OURS.search(name) else msg + " (외부 문서명일 수 있음)")
continue
if section and not is_archive and p.endswith('.md'):
# `initreq/`를 가리키는 인용은 절 검사 제외 — 읽기 전용 외부 원본이라
# 절 구조가 없다(`raw-userinput.md`는 스스로 "정리가 없는 생각의 흐름"
# 이라고 밝힌 원문). 저기로 가는 인용은 절 제목이 아니라 원문 문장을
# 따오는 것이므로 헤딩과 대조하는 게 애초에 의미가 없다.
if section and not is_archive and p.endswith('.md') \
and 'initreq' not in rel(p).split(os.sep):
if p not in hcache:
hcache[p] = headings(p) or []
core = re.sub(r'[`*]', '', section).strip()
if not any(core in h for h in hcache[p]):
# 절 제목은 의역해서 인용하는 관례가 있어 WARN — 다만
# 문서를 쪼개거나 헤딩을 고칠 때 여기가 제일 먼저 걸린다.
warns.append(
hcache[p] = (headings(p) or [], bold_leads(p))
heads, leads = hcache[p]
# 공백을 뺀 형태로 비교 — 인용이 줄바꿈에 걸리면 원문엔 없는
# 공백이 끼어든다(`canExecute/\nunbindLifetime` → "canExecute/
# unbindLifetime"). 이것 때문에 실재하는 절이 불일치로 잡혔음.
core = squash(section)
if not (any(core in squash(h) for h in heads)
or any(squash(s).startswith(core) for s in leads)):
# [2026-08-16] WARN → ERROR로 승격. 예전엔 "절 제목을
# 의역해 인용하는 관례" 탓에 오탐이 섞여 WARN이었지만,
# `conventions.md`의 "절 인용 규약"이 의역을 금지하고
# 불일치를 78→0으로 정리한 뒤 게이트로 올렸다. 문서를
# 쪼개거나 헤딩을 고칠 때 여기가 제일 먼저 걸린다.
errors.append(
f"{rel(d)}:{ln} 절 참조 불일치 → {os.path.basename(p)}"
f'"{section}" 절 없음')

View file

@ -1,181 +0,0 @@
export const meta = {
name: 'quad-handover-audit',
description: '커밋 전 .claude/ 코퍼스 정합성을 quad-doc-auditor 병렬·다회 감사로 수렴시킴',
whenToUse: '사용자가 "핸드오버 준비하고 커밋해" 류로 요청했을 때, 커밋 전에 자동으로 돌릴 것. 단일 감사 패스는 비결정적이라 놓치는 게 있을 수 있으므로, 라운드마다 독립된 감사를 병렬로 여러 번 돌리고, 새 발견이 없는 라운드가 연속으로 나올 때까지 반복해 수렴시킨다. 시간보다 정확성을 우선하는 사용자 요청에 따라 기본 워크플로 크기 가이드라인(15 에이전트 이하)을 의도적으로 초과할 수 있음.',
phases: [
{ title: 'Audit', detail: 'quad-doc-auditor 서브에이전트를 라운드당 병렬로 여러 번' },
{ title: 'Fix', detail: '라운드에서 나온 새 발견을 파일별로 반영' },
],
}
// 실측 편의를 위해 조정 가능한 상수. 과거 세션의 수동 감사가 보통
// 4~6라운드 안에 수렴했음(예: 8→7→11→9→4→0, 9→2→3→2→0→0) — MAX_ROUNDS는
// 그보다 여유를 두되 무한루프는 막는 안전판.
const PASSES_PER_ROUND = 3
const DRY_ROUNDS_TO_CONVERGE = 2
const MAX_ROUNDS = 6
const FINDINGS_SCHEMA = {
type: 'object',
properties: {
findings: {
type: 'array',
items: {
type: 'object',
properties: {
file: { type: 'string', description: '레포 루트 기준 상대 경로' },
line: { type: 'number', description: '해당 줄 번호(모르면 0)' },
issue: { type: 'string', description: '무슨 문장이 무엇과 모순/stale인지 한 문장' },
fix: { type: 'string', description: '어떻게 고치면 되는지 한 문장' },
confidence: { type: 'string', enum: ['확실', '의심'] },
},
required: ['file', 'issue', 'fix', 'confidence'],
},
},
},
required: ['findings'],
}
const AUDIT_PROMPT =
'핸드오버 준비 감사 라운드다. 너의 정해진 절차대로 .claude/ 코퍼스를 ' +
'처음부터 독립적으로 감사해라. 다른 병렬 패스가 이미 뭘 찾았는지는 ' +
'모른다 — 그걸 의식하지 말고 빠짐없이 훑어라.'
function keyOf(f) {
return `${f.file}:${f.line || 0}:${(f.issue || '').slice(0, 40)}`
}
function fixPrompt(file, items) {
const lines = items
.map((f) => `- (줄 ${f.line || '?'}, ${f.confidence}) ${f.issue} → 제안: ${f.fix}`)
.join('\n')
return (
`아래는 quad-doc-auditor가 "${file}"에서 찾은 stale/모순 서술이다. ` +
`이 파일을 읽고 .claude/conventions.md의 관례(한국어 서술, 최소 수정, 뒤집힌 결정은 ` +
`archive/로 이전+포인터, 날짜 없는 시한부 주장엔 날짜 붙이기)에 맞춰 직접 ` +
`고쳐라. "의심"으로 표시된 항목은 실제로 문제인지 먼저 확인하고, 문제가 ` +
`아니면 건드리지 말고 넘어가라(억지로 고치지 말 것).\n\n${lines}`
)
}
const seen = new Set()
const roundLog = []
// 실제 발견 내용 자체를 누적한다 — 카운트만 돌려주면 호출한 세션이
// 커밋 전 diff 리뷰(관례상 필수)를 할 때 "이 수정이 왜 들어갔는지"를
// 알 길이 없어서 journal.jsonl을 뒤져야 한다(2026-08-16 지적).
const appliedFindings = []
let dry = 0
let round = 0
while (dry < DRY_ROUNDS_TO_CONVERGE && round < MAX_ROUNDS) {
round++
phase('Audit')
log(`라운드 ${round} — quad-doc-auditor 병렬 ${PASSES_PER_ROUND}`)
const passes = await parallel(
Array.from({ length: PASSES_PER_ROUND }, (_, i) => () =>
agent(AUDIT_PROMPT, {
label: `audit-r${round}-${i}`,
phase: 'Audit',
agentType: 'quad-doc-auditor',
schema: FINDINGS_SCHEMA,
})
)
)
// ⚠️ 가짜 초록불 방지 — 2026-08-16 첫 실측에서 실제로 당한 것.
// 감사 패스가 전부 에러로 죽으면(예: agentType 미등록) fresh가 비어
// "깨끗한 라운드"와 구분이 안 되고, 그대로 converged:true가 나와서
// **아무것도 감사 안 하고 통과 도장을 찍는다**. 감사 도구의 최악
// 실패 모드라 살아남은 패스 수를 명시적으로 확인한다.
const alive = passes.filter(Boolean)
if (alive.length === 0) {
throw new Error(
`라운드 ${round}: 감사 패스 ${PASSES_PER_ROUND}개가 전부 실패 — ` +
`감사가 실제로 수행되지 않았으므로 수렴 판정을 낼 수 없음. ` +
`(quad-doc-auditor가 등록됐는지 확인: .claude/agents/ 를 만든 직후라면 ` +
`Claude Code 재시작 필요)`
)
}
if (alive.length < PASSES_PER_ROUND) {
log(
`⚠️ 라운드 ${round}: 감사 패스 ${PASSES_PER_ROUND}개 중 ` +
`${PASSES_PER_ROUND - alive.length}개 실패 — 커버리지가 그만큼 얕음`
)
}
const all = alive.flatMap((p) => p.findings || [])
const fresh = all.filter((f) => !seen.has(keyOf(f)))
if (!fresh.length) {
dry++
log(`라운드 ${round}: 새 발견 없음 (연속 dry ${dry}/${DRY_ROUNDS_TO_CONVERGE})`)
roundLog.push({ round, fresh: 0, dry })
continue
}
dry = 0
fresh.forEach((f) => seen.add(keyOf(f)))
log(`라운드 ${round}: 새 발견 ${fresh.length}건 — 파일별로 반영 시작`)
const byFile = {}
for (const f of fresh) {
;(byFile[f.file] ||= []).push(f)
}
phase('Fix')
const fileEntries = Object.entries(byFile)
const fixed = await parallel(
fileEntries.map(([file, items]) => () =>
agent(fixPrompt(file, items), {
label: `fix:${file}`,
phase: 'Fix',
agentType: 'general-purpose',
// 토큰 관례(.claude/conventions.md "일반 작업은 sonnet") — 감사
// 패스는 frontmatter로 이미 sonnet, 반영 쪽만 메인 모델(Opus)을
// 상속하고 있어서 맞춤.
model: 'sonnet',
})
)
)
// 반영 에이전트가 죽으면 그 발견들을 `seen`에서 빼둔다 — 안 그러면
// 다음 라운드 감사가 같은 문제를 다시 찾아와도 dedup에 걸려 조용히
// 사라지고, 안 고쳐진 채로 수렴 판정이 난다(위와 같은 클래스의 버그).
const failedFiles = []
fixed.forEach((r, i) => {
const [file, items] = fileEntries[i]
if (r === null) {
failedFiles.push(file)
items.forEach((f) => seen.delete(keyOf(f)))
} else {
items.forEach((f) => appliedFindings.push({ round, ...f }))
}
})
if (failedFiles.length) {
log(`⚠️ 라운드 ${round}: 반영 실패 ${failedFiles.length}건 — ${failedFiles.join(', ')} (다음 라운드에서 재시도)`)
}
roundLog.push({
round,
fresh: fresh.length,
files: fileEntries.map(([f]) => f),
fixFailed: failedFiles,
})
}
const converged = dry >= DRY_ROUNDS_TO_CONVERGE
if (!converged) {
log(`${MAX_ROUNDS}라운드 안에 수렴 못 함 — 남은 문제는 사람이 볼 것`)
}
return {
converged,
rounds: round,
// ⚠️ "반영 에이전트에게 넘긴 발견"의 수이지 "실제로 고쳐진" 수가 아니다
// — "의심"으로 표시된 항목은 반영 에이전트가 확인 후 건드리지 않고
// 넘어갈 수 있다. 실제 반영 여부는 항상 `git diff`로 확인할 것.
findingsSentToFix: appliedFindings.length,
findings: appliedFindings,
roundLog,
}

4
.gitignore vendored
View file

@ -1 +1,5 @@
.claude/initreq
# Python 바이트코드 — doc-check.py 실행 시 생김(32e9db0에 실수로 딸려 들어갔었음)
__pycache__/
*.pyc

View file

@ -103,6 +103,27 @@ B(Attribute의 Instance 참조 타입)/C(CollectionService 태그 왕복) —
`.claude/luau-test/not-run/gc-trigger-helper.server.luau` 참고. 위
1번(MCP 연결)이 되면 에이전트가 대신 돌릴 수도 있음.
## 8. **[2026-08-16 신설, 안 막음]** `const` 바인딩 — 툴링이 언제 지원하는지 사용자만 알 수 있음
`base/architecture.md`의 "코드 스타일" 절이 `const` 바인딩을 **[2026-08-12
기준] 채택 안 함**으로 두고 있음. 사유가 "주변 툴링 미성숙"인데, **이건
에이전트가 확인할 수 없는 정보**라 사용자가 알려주는 게 맞다고 사용자
본인이 정리함(2026-08-16).
**사용자가 설명한 구체적 사정**: 예를 들어 **pesde**의 타입 추출 —
`d.ts`처럼 types를 emit하는 류의 툴링이 있는데, 아직 미성숙해서 `const`
제공하지 못하는 상황. **언제 다시 사용 가능해지는지가 명확하지 않음.**
**사람이 할 일 — 둘 중 하나**:
1. `const`를 쓸 수 있게 되는 시점을 파악해 알려주거나,
2. 사용 가능해지는 순간 에이전트에 알려줄 것.
둘 중 어느 쪽이든 **에이전트는 스스로 판단하지 않고 대기**한다 — 알려주기
전까지는 `architecture.md`의 "`const` 바인딩도 Luau 공식 문법" 절이 정한
"새로 짜는 코드는 일단 `local`로" 원칙을 그대로 따름. 알려주면 그때
`architecture.md`의 해당 절을 갱신하고 기존 코드의
`const` 전환 범위를 같이 상의할 것.
## 6. **[2026-08-13 신설, 안 막음]** 에디터의 Luau 솔버 설정 확인
`luau-analyze` CLI는 **새 솔버가 기본값**이지만 에디터가 쓰는

View file

@ -353,9 +353,9 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
> **✅ [2026-08-13 열네 번째 세션] 재디스패치 모델 교체 완료** — 아래
> "`SlotHandler.process`는 claim 실패 시에도 파괴적 클로저를 반환해야 함"
> 항목은 새 모델에서도 그대로 유효함(체인은 클로저를 early-return
> 여부와 무관하게 항상 소비 — `base/dispatch-core-plan.md` "Handler 작성
> 체크리스트" 1번). 클로저가 받는 값이 항상 `Slot`이거나 `nil`임이
> 계약으로 보장된다는 점만 새로 추가됨.
> 여부와 무관하게 항상 소비 — `base/dispatch-core-plan.md`
> "Handler 작성 체크리스트" 1번). 클로저가 받는 값이 항상 `Slot`이거나
> `nil`임이 계약으로 보장된다는 점만 새로 추가됨.
- [ ] **[2026-08-13 여섯 번째 세션 — 이 세션의 Slot 결정 전부, 구현 전 필독]**
- **`State<Slot>` 교체 = 파괴가 아니라 언마운트**(`state<Frame>`와 동일).
@ -469,13 +469,13 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
기각(Slot 부모 자체가 Destroy되는 경로에선 이 훅이 전혀 안 불려서
절반만 동작, `retract`가 Destroy 시 안 불리는 것과 같은 이유).
(2026-08-09 세 번째 세션 확정, `offset`/raw `index`/세 갈래 구조는
2026-08-11 세션 추가 확정, `base/slot-plan.md` "`Slot:List(...)`" 절)
2026-08-11 세션 추가 확정, `base/slot-plan.md` "`Slot:List(data, updateFn, keyFn?)`" 절)
구현.
**`data:Observer(fn)` 구독은 `:List()` 호출 시점이 아니라 Slot
마운트 시점까지 lazy — `Dispatch.setLength`와 같은 패턴으로
`bindLifetime(inst,observer)`(마운트 이후 `:List()`가 불리면
`self._mounted` 확인 후 즉시 활성화)** (2026-08-09 일곱 번째 세션,
`base/slot-plan.md` "`Slot:List(...)`"의 "구독 시점" 절)
`base/slot-plan.md` "`Slot:List(data, updateFn, keyFn?)`"의 "구독 시점" 절)
**`Slot.Offset: Source<number>``Slot.Length`처럼 공개 필드로
노출 — Slot 마운트 시점에 `Dispatch.setOffsetSource`가 등록하는
바로 그 Source를 `self.Offset`으로도 저장**(2026-08-11 세션,