tooling: 코퍼스 정합성 감사 서브에이전트/워크플로 신설

doc-check.py가 못 잡는 의미론적 stale/모순(뒤집힌 결정을 여전히 서술하는
본문 문장, 개수/목록 이중 소스 드리프트)을 신선한 맥락에서 찾는 계층 추가.

- .claude/agents/quad-doc-auditor.md — 읽기 전용 감사자(발견만 리포트,
  수정은 호출한 세션이 함). memory: project로 반복 패턴 축적.
- .claude/workflows/quad-handover-audit.js — 단일 패스가 비결정적이라
  라운드당 병렬 3회 + 파일별 즉시 반영을, 새 발견 없는 라운드가 연속
  2번 나올 때까지(최대 6라운드) 반복해 수렴시킴.
- CLAUDE.md "작업 방식"에 두 도구의 트리거 조건 명시 —
  "핸드오버 준비하고 커밋해" 류 요청 시 워크플로부터 돌릴 것,
  실제 git commit은 항상 메인 세션이 직접.
- .claude/README.md 폴더 기준 표에 agents//workflows/ 행 추가.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HvEKn9f67kkx2nGLG8PsP
This commit is contained in:
qwreey 2026-08-16 01:16:19 +09:00
parent 6bfd93fd62
commit a1c0e44258
Signed by: qwreey
GPG key ID: D28DB79297A214BD
4 changed files with 232 additions and 8 deletions

View file

@ -18,6 +18,8 @@
| `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` | | `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/` 44개. 다른 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/` 35개(00~19는 최초 라운드, `/code-review high`가 이중 꺾쇠 명시적 제네릭 인스턴스화를 안 시도했음을 지적해 20~33(+21b) 후속 조사 추가). 결론: 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 호출 여전히 실패라 순손해로 채택 안 함) | | `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/` 44개. 다른 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/` 35개(00~19는 최초 라운드, `/code-review high`가 이중 꺾쇠 명시적 제네릭 인스턴스화를 안 시도했음을 지적해 20~33(+21b) 후속 조사 추가). 결론: 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은 판단 대상 | | `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.md` "작업 방식" 참고). `tools/`의 기계 점검과 짝을 이루는 의미론적 점검 계층 |
| `workflows/` | **[2026-08-16 신설]** Claude Code Workflow 정의. 현재 `quad-handover-audit.js` 하나 — `quad-doc-auditor` 단일 패스는 비결정적이라 매번 다 잡는다는 보장이 없어서, 라운드마다 병렬 3회 감사+파일별 반영을 새 발견이 없는 라운드가 연속 2번 나올 때까지(최대 6라운드) 반복해 수렴시키는 다회·병렬 감사 루프. "핸드오버 준비하고 커밋해" 류 요청 시 자동으로 먼저 돌림(`CLAUDE.md` "작업 방식" 참고), 실제 `git commit`은 워크플로 밖 메인 세션이 함 |
| `session/` | **[2026-08-11 신설]** 세션별 상세 로그 원문(시행착오·정정 전 서술 포함, `quadnomicon` 개발로그 소재용) — 루트 `CLAUDE.md`가 3196줄까지 불어나 성능 저하를 유발해서 분리함. 파일명 `YYYY-MM-DD-NN-slug.md`, CLAUDE.md의 "세션 히스토리" 절에서 각 항목이 여기로 링크. 항상 읽을 필요 없음 — 결정의 논의 과정이 궁금할 때만 | | `session/` | **[2026-08-11 신설]** 세션별 상세 로그 원문(시행착오·정정 전 서술 포함, `quadnomicon` 개발로그 소재용) — 루트 `CLAUDE.md`가 3196줄까지 불어나 성능 저하를 유발해서 분리함. 파일명 `YYYY-MM-DD-NN-slug.md`, CLAUDE.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`) — 읽기 전용 리서치 소스, 여기 내용을 옮기지 말고 항상 원본 그대로 유지 | | `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`) — 읽기 전용 리서치 소스, 여기 내용을 옮기지 말고 항상 원본 그대로 유지 |

View file

@ -0,0 +1,71 @@
---
name: quad-doc-auditor
description: quad 프로젝트의 `.claude/` 설계 문서 코퍼스(base/research/reference/archive, README.md, question.md, CLAUDE.md, 루트 ROADMAP.md/HUMAN_TODO.md)에서 `doc-check.py`가 못 잡는 의미론적 stale/모순을 찾는다. 설계 결정이 뒤집히거나 확정되는 등 이 코퍼스에 중대한 변경이 있은 뒤, 특히 그런 변경을 커밋하기 전에 사용. 읽기 전용 — 문제를 리포트만 하고 직접 고치지 않는다.
tools: Read, Grep, Glob, Bash
model: sonnet
memory: project
---
너는 quad(Roblox 엔진용 DOMless UI 렌더러 재작성 프로젝트)의 `.claude/` 설계
문서 코퍼스를 감사하는 전담 에이전트다. 이 프로젝트는 설계 단계가 길고,
같은 사실이 여러 문서에 흩어져 서술되는 구조라 한 세션이 결정을 뒤집거나
확정해도 다른 문서의 본문 문장이 안 따라오는 일이 반복돼왔다. **핵심
전제: 방금 그 변경을 만든 세션 자신의 self-audit은 이걸 잘 못 잡는다** —
자기가 뭘 안 건드렸는지는 모르기 때문이다. 너는 그 변경과 무관한 신선한
맥락에서 코퍼스 전체를 다시 읽어 그 사각지대를 메우는 역할이다.
너는 파일을 고치지 않는다(Edit/Write 도구가 없다). 발견한 것만 구조화해서
보고하고, 실제 반영은 너를 호출한 세션이 한다.
## 절차
1. 먼저 `python3 .claude/tools/doc-check.py`를 돌려라. 깨진 파일/절 참조,
README 색인 누락, 날짜 없는 시한부 주장, 미반영 배너는 이미 기계가
잡는다 — 그 결과를 그대로 네 리포트 맨 위에 포함하고, **같은 종류의
문제를 네가 다시 손으로 찾으려 하지 마라**(중복 노력).
2. `git status`/`git diff`(스테이지 여부 상관없이)로 최근 변경, 그리고
대화 맥락(너를 호출한 프롬프트)으로 "이번에 뒤집히거나 새로 확정된
핵심 주장이 뭔지"를 먼저 파악해라. 없으면(예: 정기 점검 목적으로
호출된 경우) `.claude/session/`의 가장 최근 파일 1~2개를 훑어 최근
결정을 파악해라.
3. 그 주장의 핵심 키워드로 코퍼스 전체를 grep해서, **옛 주장을 여전히
확정된 것처럼 서술하는 본문 문장**이 남아있는지 확인해라. 가장 잦은
실패 유형: 헤더/배너에는 "[정정, ...]" 표시가 붙었는데 그 배너가
부정하는 *본문 bullet*은 안 고쳐진 경우 — 배너만 보고 넘어가지 말고
반드시 본문까지 읽어라.
4. 뒤집힌 결정의 원문이 `archive/`로 옮겨지지 않고 라이브 문서(`base/`,
`research/`, `reference/`, `README.md`, `CLAUDE.md`, `ROADMAP.md`)에
"히스토리로만 보존" 같은 말과 함께 전체 서술로 남아있는지 확인해라.
앞에서부터 읽는 구현자가 그걸 "확정"으로 오인할 여지가 있으면 문제다.
5. 이번 변경으로 개수·목록·상태 서술("N개 문서", "남은 항목은 이것뿐",
"전부 확정됨" 류)이 두 곳 이상에 나오게 됐는지 확인해라. 이런 서술은 반드시
소스가 하나여야 한다(예: 개수는 폴더 구조나 `STATUS.md` 하나만 소스로
삼고 나머지는 가리키기만 해야 함) — 두 곳 이상에 적혀 있으면 그 자체가
발견이다(값이 지금 일치하더라도, 구조적으로 갈라질 수 있으면 지적해라).
6. 인덱스 레이어 3개가 이번 변경을 반영했는지 확인해라: `.claude/README.md`
(색인), `.claude/question.md`(사용자가 답할 질문만), 루트
`ROADMAP.md`/`HUMAN_TODO.md`. 설계가 바뀌었는데 이 중 하나만 갱신되고
나머지가 안 따라온 경우가 실제로 반복됐다.
7. 시간이 지나면 거짓이 될 수 있는 서술인데 날짜가 없는 것 — `doc-check.py`
정규식 패턴(TEMPORAL)에 안 걸리는 자연어 변형(예: "지금은", "당분간")도
찾아라. 날짜/세션 번호를 붙이라고 권고해라.
## 스코프 밖
- `.claude/session/`(세션 원문 보존용, stale 여부를 따질 대상이 아님),
`.claude/initreq/`(읽기 전용 클론), `.claude/worktrees/`는 감사 대상이
아니다.
- `archive/` 안의 문서 자체는 "뒤집힌 결정을 원문 그대로 보존"하는 게
목적이라 낡은 서술이 있어도 정상이다 — 문제는 **라이브 문서가 archive
항목을 아직 유효한 것처럼 인용**하는 경우뿐이다.
- 설계 자체의 옳고 그름(quad 아키텍처가 맞는 선택인지)은 네 스코프가
아니다. 오직 "코퍼스가 스스로와 모순되지 않는가"만 본다.
## 출력 형식
발견마다: `파일:줄` — 무슨 문장이 무엇과 모순/stale인지 한 문장 — 어떻게
고치면 되는지 한 문장. **확실**(다른 문장과 직접 모순되거나 doc-check.py급
확신)과 **의심**(사람 판단 필요, 애매한 경우)으로 나눠라.
발견이 없으면 "발견 없음"이라고 그대로 보고해라. 그럴듯해 보이려고 사소한
문체 지적을 지어내지 마라 — 이 감사의 가치는 정확도지 발견 개수가 아니다.

View file

@ -0,0 +1,125 @@
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.md의 관례(한국어 서술, 최소 수정, 뒤집힌 결정은 ` +
`archive/로 이전+포인터, 날짜 없는 시한부 주장엔 날짜 붙이기)에 맞춰 직접 ` +
`고쳐라. "의심"으로 표시된 항목은 실제로 문제인지 먼저 확인하고, 문제가 ` +
`아니면 건드리지 말고 넘어가라(억지로 고치지 말 것).\n\n${lines}`
)
}
const seen = new Set()
const roundLog = []
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,
})
)
)
const all = passes.filter(Boolean).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')
await parallel(
Object.entries(byFile).map(([file, items]) => () =>
agent(fixPrompt(file, items), {
label: `fix:${file}`,
phase: 'Fix',
agentType: 'general-purpose',
})
)
)
roundLog.push({ round, fresh: fresh.length, files: Object.keys(byFile) })
}
const converged = dry >= DRY_ROUNDS_TO_CONVERGE
if (!converged) {
log(`${MAX_ROUNDS}라운드 안에 수렴 못 함 — 남은 문제는 사람이 볼 것`)
}
return {
converged,
rounds: round,
totalFindingsFixed: seen.size,
roundLog,
}

View file

@ -120,7 +120,9 @@ modifier/Ref의 컴포넌트 경계 통과 방식) 논의도 2026-08-04 세션
줍지 말고 바꾸는 그 순간에 닫을 것: 줍지 말고 바꾸는 그 순간에 닫을 것:
1. **`python3 .claude/tools/doc-check.py`를 돌릴 것**(아래 항목 참고) — 1. **`python3 .claude/tools/doc-check.py`를 돌릴 것**(아래 항목 참고) —
ERROR 0을 유지한 채로 커밋. 이게 규율 대부분을 기계가 대신함. ERROR 0을 유지한 채로 커밋. 이게 규율 대부분을 기계가 대신함.
2. **바꾼 주장을 부정당하는 *본문 문장*을 grep으로 전수 찾을 것.** 2. **바꾼 주장을 부정당하는 *본문 문장*을 grep으로 전수 찾을 것**(또는
`.claude/agents/quad-doc-auditor.md`로 위임 — 아래 항목 참고, 신선한
맥락에서 도는 서브에이전트가 이 항목을 실제로 더 잘 잡아왔음).
헤더에 정정 배너만 달고 본문 bullet을 안 고치는 게 가장 잦은 실패 — 헤더에 정정 배너만 달고 본문 bullet을 안 고치는 게 가장 잦은 실패 —
실제로 `CLAUDE.md`가 "스파이크를 아직 안 돌려봄"이라고 서술한 채 실제로 `CLAUDE.md`가 "스파이크를 아직 안 돌려봄"이라고 서술한 채
한 라운드를 통과했음. **배너를 달았으면 그 배너가 부정하는 문장을 한 라운드를 통과했음. **배너를 달았으면 그 배너가 부정하는 문장을
@ -143,14 +145,38 @@ modifier/Ref의 컴포넌트 경계 통과 방식) 논의도 2026-08-04 세션
이걸로 잡히는 종류였고, 실제로 여덟 번째 세션에 문서를 쪼개면서 잘못 이걸로 잡히는 종류였고, 실제로 여덟 번째 세션에 문서를 쪼개면서 잘못
옮긴 참조를 이 스크립트가 잡아냈음. ERROR는 고치고, WARN은 판단이 옮긴 참조를 이 스크립트가 잡아냈음. ERROR는 고치고, WARN은 판단이
필요한 것(절 제목을 의역해 인용한 관례 등)이라 늘 0일 필요는 없음. 필요한 것(절 제목을 의역해 인용한 관례 등)이라 늘 0일 필요는 없음.
- **[2026-08-16 도입] `.claude/agents/quad-doc-auditor.md` 서브에이전트 —
위 체크리스트 2~4번(본문 문장 grep, archive 이전, 개수/목록 단일화)을
신선한 맥락에서 대신 수행.** 읽기 전용, 발견만 리포트(직접 수정 안 함).
**`.claude/base`/`.claude/research`/CLAUDE.md에 중대한 변경이 있은 뒤,
특히 커밋 전에 돌리는 게 기본** — 지난 세션들에서 "변경한 세션 자신의
self-audit은 자기가 뭘 안 건드렸는지 몰라서 놓친다"는 패턴이 반복
확인됐고(세션 히스토리 7·8·10·11차 등), 이걸 매번 즉흥적으로 프롬프트
짜는 대신 고정 정의로 옮긴 것. **아주 큰 변경**(설계 반전 규모)엔 이걸로
대체하지 말고 `/code-review`(diff 기반)와 사용자의 직접 diff 검토를
병행할 것 — 이 서브에이전트는 diff가 아니라 코퍼스 전체의 정합성만 봄.
- **[2026-08-16 도입] "핸드오버 준비하고 커밋해" 류 요청엔 되묻지 말고
`.claude/workflows/quad-handover-audit.js`(Workflow 이름
`quad-handover-audit`)를 먼저 돌릴 것.** 단일 `quad-doc-auditor` 패스는
비결정적이라 매번 다 잡는다는 보장이 없다는 게 사용자 지적 — 이 워크플로는
라운드마다 `quad-doc-auditor`를 병렬로 3회 돌리고 새 발견을 파일별로
즉시 반영한 뒤, **새 발견이 없는 라운드가 연속 2번 나올 때까지**(최대
6라운드) 반복해 수렴시킨다. 사용자가 정확성을 시간보다 우선한다고
명시했으므로 기본 워크플로 크기 가이드라인(15 에이전트 이하)을 의도적으로
넘을 수 있음 — 이 워크플로 자체가 그 예외 대상. Workflow는 백그라운드로
돌고 완료 시 알림이 오므로, 호출 직후 대화를 막지 말고 진행 상황만
알린 뒤 알림을 기다릴 것. 알림이 오면 `python3 .claude/tools/doc-check.py`
ERROR 0을 최종 확인한 뒤 평소 커밋 절차(git status/diff 검토, 메시지 작성)로
넘어갈 것 — **실제 `git commit`은 이 워크플로 안이 아니라 항상 메인 세션이
직접 함**(커밋 전 diff 재검토는 대화형 맥락이 필요해서 워크플로에 위임 안 함).
- **문서가 쌓이면서 모순/중복/stale 마커가 생기기 쉬움 — 주기적으로 감사할 - **문서가 쌓이면서 모순/중복/stale 마커가 생기기 쉬움 — 주기적으로 감사할
것.** 다만 위 체크리스트+`doc-check.py`가 자리잡으면 이 감사는 것.** 다만 위 체크리스트+`doc-check.py`+`quad-doc-auditor`가 자리잡으면
"기계가 못 보는 것"(설계 자체의 자기모순, 의사코드 손 트레이싱)에만 이 감사는 "기계도 서브에이전트도 못 보는 것"(설계 자체의 자기모순, 의사코드
집중하면 됨. 2026-08-04 세션에 실제로 전체 `.claude/` 코퍼스에서 이런 문제가 손 트레이싱)에만 집중하면 됨. 2026-08-04 세션에 실제로 전체 `.claude/`
다수 발견되어 정리함(아래 "세션 히스토리" 참고) — 여러 라운드에 걸쳐 코퍼스에서 이런 문제가 다수 발견되어 정리함(아래 "세션 히스토리" 참고) —
같은 문서를 계속 고치다 보면 "정정됨" 표시가 원래 문장에 안 반영되고 여러 라운드에 걸쳐 같은 문서를 계속 고치다 보면 "정정됨" 표시가 원래
방치되는 패턴이 반복되니, 큰 방향 전환이 있을 때마다 관련 문서 전체를 문장에 안 반영되고 방치되는 패턴이 반복되니, 큰 방향 전환이 있을 때마다
훑어 확인할 것. 관련 문서 전체를 훑어 확인할 것.
- **Roblox Studio MCP 연결 시 주의**: Studio는 잘 죽는 편 — 죽었을 때 살리려고 - **Roblox Studio MCP 연결 시 주의**: Studio는 잘 죽는 편 — 죽었을 때 살리려고
위험한 명령을 반복 시도하지 말 것. 그런 상황이면 MCP 없이 할 수 있는 작업만 위험한 명령을 반복 시도하지 말 것. 그런 상황이면 MCP 없이 할 수 있는 작업만
하거나 대기. 연결 방법은 `HUMAN_TODO.md` 1번 항목 참고(사용자가 Studio에서 하거나 대기. 연결 방법은 `HUMAN_TODO.md` 1번 항목 참고(사용자가 Studio에서