Compare commits
10 commits
298dac2510
...
0942c1ca2c
| Author | SHA1 | Date | |
|---|---|---|---|
| 0942c1ca2c | |||
| 40a5daf694 | |||
| d23d46f746 | |||
| 01c4abf727 | |||
| ebb1fd0f5a | |||
| e0b6c992fa | |||
| 1935dd4670 | |||
| 3721692ed4 | |||
| 07c77e5a5a | |||
| 134379632f |
46 changed files with 1631 additions and 365 deletions
|
|
@ -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/` — 완료됐거나 완전히 뒤집힌 것, 능동 참고 불필요
|
||||
|
||||
|
|
|
|||
7
.claude/agent-memory/quad-doc-auditor/MEMORY.md
Normal file
7
.claude/agent-memory/quad-doc-auditor/MEMORY.md
Normal 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.
|
||||
|
|
@ -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으로 찾아 전수 확인할 것 — 수정된 파일 목록에 없는 형제 인용이
|
||||
꼭 하나는 남아 있다.
|
||||
|
|
@ -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/`에 남은 동일 숫자는 스코프 밖이니 제외).
|
||||
|
|
@ -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`가
|
||||
디렉토리 안 실제 파일 전부를 인덱싱하는지 확인하고 누락되면 직접 보완할
|
||||
것(이건 코퍼스 발견이 아니라 내 메모리 유지보수이므로 리포트에는 안 올림).
|
||||
|
|
@ -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]].
|
||||
|
|
@ -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에서 변경 규모에 따라 증가). 워크플로가 있다고 전제하지 말 것.
|
||||
|
|
@ -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 추가] "사용자 판단 필요"를 별도로 표시해라.** 설계 판단이
|
||||
섞였거나(어느 쪽이 맞는지가 취향/방향 문제), 출처가 불분명한 인용이거나,
|
||||
고치는 방법이 둘 이상인데 고르는 근거가 문서에 없는 발견은 `의심`에 묻지
|
||||
말고 **`사용자 판단`**으로 따로 빼라. 너를 부른 메인 세션은 사용자에게
|
||||
직접 물을 수 있고, 그게 지금 감사 루프의 핵심 설계다 — 네가 애매한 걸
|
||||
그냥 "이렇게 고치면 됨"으로 넘기면 그 강점이 죽는다.
|
||||
|
||||
발견이 없으면 "발견 없음"이라고 그대로 보고해라. 그럴듯해 보이려고 사소한
|
||||
문체 지적을 지어내지 마라 — 이 감사의 가치는 정확도지 발견 개수가 아니다.
|
||||
|
|
|
|||
|
|
@ -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번(사용자가 시점을
|
||||
파악하거나, 가능해질 때 에이전트에 알림)으로 옮겼다.
|
||||
|
|
|
|||
|
|
@ -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 동일성을 고정한다"**는 조치의 전반부도 같이
|
||||
|
|
|
|||
|
|
@ -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번 절의 대표 용례라
|
||||
|
|
|
|||
|
|
@ -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를 쓴다"고 오해하지
|
||||
|
|
|
|||
|
|
@ -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가 인터페이스로
|
||||
소유한다"는 원칙에 정면으로 어긋남.
|
||||
"패키지 경계" 절이 세운 원칙에 정면으로 어긋남.
|
||||
|
||||
**확정된 배치**:
|
||||
|
||||
|
|
|
|||
|
|
@ -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 다른 팩토리로 유일 슬롯 충돌(에러)이라는
|
||||
|
|
|
|||
|
|
@ -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`은 이 레지스트리에 안 들어감 — 싱글턴이라 항등 비교로 충분.**
|
||||
|
|
|
|||
|
|
@ -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`이 아닌 값은
|
||||
|
|
|
|||
|
|
@ -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 흐름에도 좋을 게
|
||||
|
|
|
|||
|
|
@ -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
|
||||
**내부 배관**이 "이전 처리를 무른다".
|
||||
|
|
|
|||
|
|
@ -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 다섯 번째 세션 후속)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
함.** 콜백 파라미터 타입은 호출부가 인라인으로 직접 명시
|
||||
(`function(v: UDim2) ... end`) — Luau가 그 타입이 실제 프로퍼티 타입과
|
||||
일치하는지 검증해주지 않음. 이미 확정된 "이벤트 바인딩은 콜백 시그니처를
|
||||
Luau가 검증 못 하는 대가를 받아들인다"는 결정(`event-plan.md` "이벤트
|
||||
Luau가 검증 못 하는 대가를 받아들인다"는 결정(`base/bind-system-plan.md` "이벤트
|
||||
바인딩 — `On.EventName` 도트액세스 안 씀" 절, "타입 안전성을 어느 정도
|
||||
포기하는 대가")과 같은 급의 트레이드오프라 새로 정당화할 것 없음 — 오히려
|
||||
`AttributeKey<<T>>`처럼 제네릭으로 정확히 맞추려는 시도는 이벤트 키보다 더
|
||||
|
|
|
|||
|
|
@ -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 함수가 없어도 됨 —
|
||||
앞서 "이게 실제 작업량"이라고 적었던 판단은 과했음.
|
||||
|
||||
|
|
|
|||
|
|
@ -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처럼 원천 없이는
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ Store는 "이름 붙은 Source 모음, 그 이상 아님"으로 더 단순해짐
|
|||
전용 미타입 폴백으로 격하돼 있었으므로 이번 정정과 무관하게 그대로 유지.
|
||||
`:` 체이닝 원칙도 `:Set()` 자체가 그 사례라 유지.
|
||||
|
||||
`base/architecture.md`의 "복사 구현 지양, 팩토리 함수로 대체" 원칙과 함께
|
||||
`base/architecture.md`의 "복사(clone) 구현 지양, 팩토리 함수로 대체" 원칙과 함께
|
||||
읽을 것 — v1의 문제는 metatable 체이닝으로 매번 새 테이블을 할당하며
|
||||
"불변 빌더"를 흉내낸 것이었지, `:` 체이닝 문법 자체나 커링 문법 자체가
|
||||
아니었음.
|
||||
|
|
|
|||
|
|
@ -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) 전용으로 국한됨 — 그 계정/원격이 설정되기 전까지는
|
||||
|
|
|
|||
|
|
@ -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` 생략 캐비엇 |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?)`" 절
|
||||
참고, 여기서 반복 안 함.
|
||||
|
||||
이 아래 있던 "왜 매핑 함수 직관이 안 통하는가"/"메커니즘 스케치"/
|
||||
|
|
|
|||
|
|
@ -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 아키텍처 확인(사용자 질문에 대한 답 — 맞음)**: 세 개의
|
||||
|
|
|
|||
|
|
@ -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줄
|
||||
요약 + 링크가 **별도 텍스트로** 적혀있음 — 한쪽만 갱신되면 어긋날 수
|
||||
있는 구조.
|
||||
- **적용 후**: 각 세션 파일 안에 "이게 이 세션의 정본 요약"이라고 표시하는
|
||||
|
|
|
|||
|
|
@ -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·다른 팩토리 충돌 에러 내부 안전장치
|
||||
|
|
|
|||
37
.claude/research/fastscroll-plan.md
Normal file
37
.claude/research/fastscroll-plan.md
Normal 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가 잘 작동하게 될 때 내가 검토해볼
|
||||
예정"으로 직접 후순위 지정. 지금 단계에서 추가 설계 논의 불필요.
|
||||
|
|
@ -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인가" 문서는 아직 그 콘텐츠
|
||||
맵에 항목으로 없음 — 쓰게 되면 거기 먼저 추가할 것.
|
||||
|
|
|
|||
|
|
@ -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`에
|
||||
|
|
|
|||
|
|
@ -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 버전의 실제
|
||||
|
|
|
|||
48
.claude/research/spring-plan.md
Normal file
48
.claude/research/spring-plan.md
Normal 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
|
||||
설계 게이트와 무관.
|
||||
|
|
@ -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이 형제로 섞일 때 순서 보장`
|
||||
항목과 같은 시점 — 이미 해소돼 아카이브로 옮겨졌으나 실제 구현 시점의
|
||||
참고 자료로는 여전히 유효)에서 이 두 가지를 실제 구현과 함께 재확인해야 함.
|
||||
|
||||
|
|
|
|||
|
|
@ -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`에 "문서 표기 규약" 절 신설.
|
||||
|
|
|
|||
|
|
@ -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`).
|
||||
|
|
|
|||
270
.claude/session/2026-08-16-03-doc-check-section-convention.md
Normal file
270
.claude/session/2026-08-16-03-doc-check-section-convention.md
Normal 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:` 필드가 그대로
|
||||
반영되지 않는 기존 미해결 항목) 이런 규약이 유일한 방어선이다.
|
||||
|
|
@ -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 -- <경로>`.
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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에
|
||||
묶여 있었다. 의역 인용을 규약으로 금지하고 불일치를 78→0으로 정리한
|
||||
뒤 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}" 절 없음')
|
||||
|
||||
|
|
|
|||
|
|
@ -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
4
.gitignore
vendored
|
|
@ -1 +1,5 @@
|
|||
.claude/initreq
|
||||
|
||||
# Python 바이트코드 — doc-check.py 실행 시 생김(32e9db0에 실수로 딸려 들어갔었음)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
|
|
|||
|
|
@ -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는 **새 솔버가 기본값**이지만 에디터가 쓰는
|
||||
|
|
|
|||
10
ROADMAP.md
10
ROADMAP.md
|
|
@ -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 세션,
|
||||
|
|
|
|||
Loading…
Reference in a new issue