--- name: vibe-debug description: 이 스킬은 어려운 버그와 성능 회귀의 원인을 진단한다. 사용자가 "진단"이나 "디버그"라고 말하거나, 무언가 망가졌다고 보고할 때 사용한다. --- # 바이브 디버그 어려운 버그를 진단하는 규칙이다. 정당한 사유를 명시했을 때만 단계를 건너뛴다. 코드베이스를 탐색할 때 `CONTEXT.md`가 있으면 읽고 수정할 영역과 관련된 모듈을 명확히 파악한다. 수정할 영역의 아키텍처 결정 기록(ADR)도 확인한다. ## 진단 자료의 비밀값 보호 모든 진단 자료를 공유하거나 저장하기 전에 토큰, 비밀번호, 인증 헤더, 쿠키, 개인 식별 정보를 ``로 가린다. 자료에는 명령, 출력, 로그, HAR 파일이 포함된다. 재현 명령에서는 자격증명을 환경변수로 참조한다. 확장된 값이나 환경변수 전체를 출력하지 않는다. 자료 전체 대신 증상을 판단하는 데 필요한 구간만 인용한다. 사람 개입형(HITL) 스크립트의 `capture` 함수는 입력을 에이전트가 읽는 출력으로 돌려준다. 비밀값을 가린 관찰 결과만 수집한다. 사용자는 `step` 함수로 직접 로그인한다. 가린 자료만으로 진단할 수 없으면 부족한 정보를 설명하고 비밀값이 없는 추가 관찰을 요청한다. 원본 자격증명을 요청하거나 다시 출력하지 않는다. ## 의도와 권한 경계 루프를 시작하기 전에 요청된 행동을 분류한다: - **수정 모드**는 사용자가 고치기, 수리, 변경, 복원(모호하지 않은 동등 표현 포함)을 명시적으로 요청할 때만 적용된다. 영구적인 제품 변경과 회귀 테스트 변경을 허용한다. 아래 모든 단계를 따른다. - **진단 전용 모드**는 진단이나 디버그, 조사, 원인 파악만 요청한 경우에 적용한다. 모호한 요청에도 이 모드를 기본으로 적용한다. 버그 보고나 긴급성, 계속 진행 요청만으로 수정을 추론하지 않는다. 진단 전용 모드는 **권한 경계**를 지킨다. 소스와 설정, 이력을 읽고 런타임을 읽기만 하는 관찰은 허용한다. 영구적인 제품, 테스트, 설정, 산출물은 변경하지 않는다. 진단 전용 작업에 임시 계측, 일회성 재현, 프로토타입이나 하네스, 또는 실패 테스트가 필요한 경우: 1. 격리된 작업공간에 만들 정확한 변경을 쓰기 전에, 경로와 diff 또는 전체 내용, 목적, 제거할 모든 산출물을 정리한 계획을 사용자에게 미리 보여 준다. 2. 명시적 승인을 요청한다. 일반적인 진단 요청, 침묵, 거부는 승인이 아니다. 3. 사용자의 명시적 승인을 기다린다. 4. 승인 뒤에만 임시 진단 자료를 격리된 작업공간에 쓴다. 사용자의 체크아웃에는 쓰지 않는다. 5. 일회성 재현, 프로토타입, 하네스와 그 모든 로컬 소스를 승인된 격리 작업공간의 `.agents/prototype//` 아래에 둔다. 6. 의존성 매니페스트와 잠금 파일, 픽스처, 스크래치 데이터, 라우트 설정, 실행 메타데이터, 실행 지침도 같은 위치에 둔다. 7. 프로덕션 소스는 읽기 전용으로 검사할 수 있다. 8. 프로덕션 소스를 가져오거나 수정하지 않는다. 9. 프로젝트 루트 매니페스트, 작업 실행기, 라우트, 설정, 공유 컴포넌트는 편집하지 않는다. 10. 이 위치 규칙은 프로토타입 자료에만 적용한다. 11. 일반 영구 회귀 테스트와 디버그 스크립트는 기존 위치에 둔다. 12. 보고하기 전에 승인된 임시 산출물을 모두 제거한다. 13. 임시 산출물을 제품이나 테스트 변경으로 유지하지 않는다. ## Phase 1: 피드백 루프 만들기 **이것이 핵심이다.** 나머지는 기계적이다. 버그에 **밀착된** 통과 여부와 실패 여부를 판별하는 신호(이 버그에서 red가 되는 신호)가 있으면 원인을 찾을 수 있다. 이등분, 가설 검증, 계측은 모두 그 신호를 사용한다. 신호가 없으면 코드를 읽기만 해서는 원인을 찾을 수 없다. 이 단계에는 특히 많은 노력을 들여 적극적이고 창의적으로 시도하며 포기하지 않는다. ### 루프 만드는 방법: 대략 이 순서로 시도 1. 버그에 닿는 모든 경계에서 **실패 테스트**: 단위, 통합, e2e. 2. 실행 중인 개발 서버에 대해 **Curl 또는 HTTP 스크립트**를 실행한다. 3. 픽스처 입력으로 **CLI를 호출**한다. 4. stdout을 정상 동작으로 확인한 스냅샷과 비교한다. 5. **헤드리스 브라우저 스크립트**(Playwright 또는 Puppeteer)로 UI를 구동한다. 6. DOM, 콘솔, 네트워크를 검증한다. 7. 실제 네트워크 요청, 페이로드, 이벤트 로그를 디스크에 저장한다. 8. 코드 경로를 격리해 캡처한 트레이스를 재생한다. 9. **일회성 하네스.** 진단 전용 모드에서는 위 승인 규칙을 지키고 하네스를 일회성 프로토타입으로 사용한다. 하네스는 승인된 격리 작업공간의 `.agents/prototype//` 아래에 둔다. 10. 시스템의 최소 하위 집합만 띄운다. 한 서비스와 모의 의존성을 사용한다. 11. 버그 코드 경로를 단일 함수 호출로 실행한다. 12. **속성 또는 퍼즈 루프.** 버그가 가끔 잘못된 출력을 내면 무작위 입력 1000개를 실행해 실패 양상을 찾는다. 13. **이등분 하네스.** 버그가 두 알려진 상태(커밋, 데이터셋, 버전) 사이에 나타났다면 상태 X에서 부팅하고 검사하는 과정을 자동화해 `git bisect run`으로 반복한다. 14. **차등 루프.** 같은 입력을 이전 버전과 새 버전, 또는 두 설정에 적용하고 출력을 비교한다. 15. **HITL Bash 스크립트.** 최후의 수단이다. 사람이 클릭해야 하면 `scripts/hitl-loop.template.sh`를 사용해 루프의 순서를 정한다. 스크립트가 캡처한 출력을 에이전트에게 돌려준다. 진단 전용 모드에서는 기존 테스트, 스크립트, 명령, 읽기 전용 관찰로 루프를 만든다. 위의 임시 쓰기 승인을 받지 않은 한 테스트, 하네스, 트레이스, 스크립트, 계측, 설정, 제품 코드를 만들거나 수정하지 않는다. 모든 명령은 영구적인 제품 상태 변경을 피해야 한다. 올바른 피드백 루프를 만들면, 버그는 90% 해결된다. ### 루프 다듬기 루프가 하나 있으면 반복해 더 빠르고 정확하게 다듬는다: - 더 빠르게 만들 수 있는가? (설정 캐시, 무관한 초기화 건너뛰기, 테스트 범위 좁히기.) - 신호가 이 증상에서만 실패하게 만들 수 있는가? - 시간을 고정하고 난수 생성기 시드를 지정하며 파일시스템을 격리하고 네트워크를 동결해 루프를 더 결정적으로 만들 수 있는가? 30초 걸리는 불안정한 루프는 루프가 없는 것과 같다. 버그에 **밀착된** 2초 결정적 루프는 원인을 찾는 시간을 줄인다. ### 비결정적 버그 목표는 불필요한 요소가 없는 재현을 만드는 데 있지 않다. **더 높은 재현율**을 확보한다. 재현율을 높이는 방법은 다음과 같다: - 트리거를 100번 반복한다. - 실행을 병렬로 돌린다. - 부하를 더한다. - 타이밍 간격을 좁힌다. - `sleep`을 주입한다. 재현율이 50%인 버그는 디버그할 수 있다. 재현율이 1%인 버그는 디버그할 수 없다. 디버그할 수 있을 때까지 재현율을 높인다. ### 정말로 루프를 만들 수 없을 때 루프를 만들 수 없으면 작업을 명시적으로 중단한다. 사용자에게 중단 사실과 시도한 방법을 알린다. 다음 중 하나를 요청한다: - 재현 환경에 접근할 권한 - 비밀값을 가린 HAR 파일, 로그 덤프, 코어 덤프, 시간 표시가 있는 화면 녹화 - 격리된 작업공간에서 할 정확한 임시 진단 변경안과 정리 계획의 미리보기, 명시적 승인 루프 없이 가설을 세우지 **않는다**. ### 완료 기준: red가 되는 밀착된 루프 Phase 1은 버그에 **밀착된** 루프와 red-capable 명령을 확보하면 끝난다. 이미 한 번 실행한 명령 하나와 비밀값을 가린 호출 및 출력을 제시한다. 명령은 스크립트 경로, 테스트 호출 또는 `curl` 명령이다. - [ ] **Red-capable**: 실제 버그 코드 경로를 실행한다. 사용자의 정확한 증상을 검증한다. 버그가 있으면 이 명령은 red가 된다. 버그를 고치면 green이 된다. "에러 없이 실행"만으로는 충분하지 않다. 이 명령은 이 특정 버그를 잡아야 한다. - [ ] **결정적**: 매 실행마다 같은 판정 (불안정한 버그: 위의 고정된 높은 재현율). - [ ] **빠름**: 분이 아닌 초. - [ ] **에이전트 실행 가능**: 무인으로 실행할 수 있어야 한다. 사람이 개입할 때는 `scripts/hitl-loop.template.sh`만 사용한다. 이 명령이 생기기 전에 코드를 읽으며 가설을 세우면 멈춘다. 이는 이 스킬이 막으려는 실패다. red-capable 명령이 없으면 Phase 2로 진행하지 않는다. ## Phase 2: 재현 + 최소화 루프를 실행한다. red가 되는지 지켜본다(버그가 나타나는지 확인한다). 확인: - [ ] 루프가 **사용자**가 설명한 실패 모드를 만든다. 근처에서 일어나는 다른 실패는 만들지 않는다. 잘못된 버그 = 잘못된 수정. - [ ] 둘 이상의 실행에서 재현한다. 비결정적 버그는 디버그할 수 있을 만큼 높은 재현율을 확보한다. - [ ] 정확한 증상(에러 메시지, 잘못된 출력, 느린 타이밍)을 캡처했다, 나중 단계가 수정이 실제로 해결하는지 검증할 수 있게. ### 최소화 red가 되면 재현을 **여전히 red가 되는 가장 작은 시나리오**로 줄인다. 입력, 호출자, 설정, 데이터, 단계를 **한 번에 하나씩** 줄인다. 하나를 줄일 때마다 루프를 다시 실행한다. 실패에 필요한 요소만 남긴다. 최소 재현은 Phase 3에서 검토할 가설 수를 줄인다. 남은 의심 대상이 적기 때문이다. 최소 재현은 Phase 5에서 불필요한 요소가 없는 회귀 테스트가 된다. **모든 남은 요소가 필수**일 때 완료한다: 그중 하나를 제거하면 루프가 green이 된다. 재현 **하고** 최소화하기 전에는 진행하지 않는다. ## Phase 3: 가설화 어느 것을 테스트하기 전에 **3–5개의 순위 가설**을 만든다. 단일 가설 생성은 첫 번째 그럴듯한 생각에 고정된다. 각 가설은 **반증 가능**해야 한다: 그것이 내는 예측을 말한다. > 형식: "가 원인이라면, 버그가 사라진다 / 버그가 악화된다." 예측을 말할 수 없다면 그 가설은 검증할 수 없다. 그 가설을 버리거나 예측을 구체화한다. **테스트 전에 순위 목록을 사용자에게 보여준다.** 사용자는 순위를 바로 바꿀 만한 도메인 지식을 갖춘 경우가 많다("#3에 방금 변경을 배포했다"). 사용자는 이미 배제한 가설도 알고 있을 수 있다. 이런 확인은 짧게 끝나고 이후 작업 시간을 크게 줄인다. 여기서 작업을 멈추지 않는다. 사용자가 자리를 비웠으면 자신의 순위로 진행한다. ## Phase 4: 계측 각 프로브는 Phase 3의 특정 예측을 검증한다. **한 번에 하나의 변수만 바꾼다.** 도구 선호: 1. 환경이 지원하면 **디버거나 읽기-평가-출력 루프(REPL)에서 읽기만 하는 검사**를 한다. 하나의 중단점이 열 개의 로그보다 낫다. 2. 가설을 구분하는 경계에서 **표적 로그**. 3. "모든 것을 로깅하고 grep"하지 않는다. 진단 전용 모드에서는 디버거나 REPL로 읽기만 하는 검사와 기존 관찰 자료부터 사용한다. 표적 로그나 다른 임시 자료를 쓰려면 먼저 미리보기를 제시하고 승인을 받는다. 보고 전에 격리된 작업공간에서 그 자료를 제거한다. **모든 디버그 로그에** 고유 접두사(`[DEBUG-a4f2]`)를 붙인다. 태그가 있는 로그는 `grep` 한 번으로 찾아 삭제한다. 태그가 없는 로그는 찾기 어렵다. **성능 분기.** 성능 회귀에서는 로그가 원인을 잘못 가리키는 경우가 많다. 기준 측정(타이밍 하네스, `performance.now()`, 프로파일러, 쿼리 계획)을 세운 뒤 이등분한다. 먼저 측정하고 나중에 수정한다. ## 진단 전용 종료 원인이 확인된 뒤에는 진단 전용 모드를 끝낸다. 수정 단계로 넘어가지 않는다. 다음 내용을 보고한다: - 확인된 원인과 증거를 보고한다. 증거가 가설을 뒷받침하는 데 그치지 않고 원인을 입증하는 이유를 설명한다. - 최소 재현 명령과 관찰 결과, 또는 정확한 재현 한계, 시도한 것, 빠진 접근이나 산출물. - 수집한 증거: red-루프 출력, 읽기 전용 디버거 값, 기존 로그, 소스 사실, 또는 측정. - 원인을 다루는 가장 작은 수정을 제안하고 근거를 설명한다. 제안한 수정은 적용하지 않는다. - 에이전트는 보고 전에 승인된 임시 산출물을 모두 제거한다. 에이전트는 제거를 확인한다. 증거로 원인을 확인할 수 없거나 red-capable 명령을 만들 수 없다면 제한된 진단을 보고한다. 가설을 확인된 원인으로 단정하지 않는다. 수정을 적용하지 않는다. ## Phase 5: 수정 (고치기 + 회귀 테스트) 이 단계는 수정 모드에서만 실행한다. 진단 전용 보고는 위에서 끝난다. 고치기 **전에** 회귀 테스트를 작성한다. 다만 올바른 테스트 경계가 있을 때만 작성한다. 올바른 경계는 테스트가 호출 지점에서 실제 버그 패턴을 실행하는 곳이다. 버그가 둘 이상의 호출자를 거쳐야 하는데 테스트가 호출자 하나만 실행한다면 그 경계는 너무 좁다. 버그를 일으킨 호출 경로를 재현할 수 없는 단위 테스트도 너무 좁다. 이 경계에서 회귀 테스트를 작성하지 않는다. 그 테스트가 통과해도 버그가 없다는 근거가 되지 않는다. **올바른 경계가 없다면, 그것 자체가 발견이다.** 그 사실을 기록한다. 코드베이스 구조로는 이 버그의 재발을 막는 테스트를 만들 수 없다. 다음 단계에서 검토하도록 표시한다. 올바른 경계가 있으면: 1. 최소화한 재현을 그 경계에서 실패 테스트로 만든다. 2. 실패를 지켜본다. 3. 확인된 원인을 설명하는 가장 작은 수정을 적용한다. 인접 코드를 리팩터하지 않는다. 4. 통과를 지켜본다. 5. Phase 1 피드백 루프를 원래(최소화하지 않은) 시나리오로 다시 실행한다. ## Phase 6: 수정 정리 + 포스트모템 이 단계는 수정 모드에서만 실행한다. 진단 전용 모드에서는 보고 전에 `.agents/prototype//`의 프로토타입과 승인된 임시 진단 산출물을 모두 제거한다. 다른 정리는 하지 않는다. 완료 선언 전 필수: - [ ] 원래 재현이 더 이상 재현되지 않는다 (Phase 1 루프 다시 실행) - [ ] 에이전트는 회귀 테스트를 실행하고 통과를 확인한다. 테스트 경계가 없으면 에이전트는 그 사실을 문서화한다. - [ ] 에이전트는 모든 `[DEBUG-...]` 계측을 제거한다. `grep`으로 접두사가 남지 않았는지 확인한다. - [ ] 에이전트는 일회성 프로토타입을 삭제하거나 `.agents/prototype//` 아래에만 보존한다. - [ ] 에이전트는 맞았던 가설을 커밋 메시지나 PR 메시지에 적는다. 다음 디버거는 이 기록에서 배운다. **그리고 묻는다: 이 버그를 무엇이 막았을까?** 답이 구조 변경(좋은 테스트 경계 없음, 얽힌 호출자, 숨겨진 결합)을 포함하면 구체적인 내용을 사용자에게 알리고 `/vibe-refactor` 실행을 안내한다. 에이전트는 수정을 적용한 뒤에만 이 안내를 한다. 수정 전에 안내하지 않는다. 수정한 지금은 시작할 때보다 더 많은 정보를 안다.