--- name: bug-fix description: 완료된 프로젝트(plan_setup·harness_setup·loop_setup/LOOP.md 가 있고 STATE.md 의 Phase Overview 에 in_progress 가 없는 상태)에서 실제 사용 중에 발견된 문제 하나를 고친다. "xx 가 안 돼" 로 시작하는 신고를 받아 버그인지 요구 변경인지 먼저 가르고(문서·OQ 근거), 재현 절차와 기대 동작을 LOG.md 에 "### BUG-NN" 한 줄로 먼저 적고, 원인을 확인하고, 그 버그를 잡는 회귀 검사를 harness-update 로 게이트에 즉시 활성으로 넣고(고치기 전 실패를 실제로 확인), 코드를 고치고, 게이트 넷을 통과시키고(task_validation 은 분리 세션이 그 재현 줄을 수용 기준으로 판정), 커밋 1회(메시지에 BUG-NN) 한다. 문서·플랜·phase·STATE.md 는 건드리지 않는다. 사용자가 "필터가 안 돼", "저장했는데 사라져", "이 버튼 누르면 에러 나", "쓰다 보니 이상한 게 있어", "배포한 앱에서 xx 가 이상하게 나와", "사용자한테 버그 신고 들어왔어", "이 버그 고쳐줘" 처럼 다 만든 앱을 쓰다가 발견한 문제를 고쳐 달라고 할 때 반드시 이 스킬을 사용한다. 코드를 바로 고치지 말고 이 스킬로 먼저 기록과 회귀 검사를 세운다. 다만 개발 루프 안에서 게이트가 실패한 것은 이 스킬을 부를 이유가 아니다 — 그건 LOOP.md 의 실패 분류와 재시도 3회가 다룬다. 이 스킬은 루프 밖에서(Phase Overview 에 in_progress 없음), 실제 사용 중에 발견된 문제를 받는다. 문서에 적힌 대로 동작하는데 바꾸고 싶은 것은 feature-add, docs/PRD.md 가 없는 코드는 code-to-docs 가 먼저다. --- # 버그 수정 (Layer 6 · 사용 중 발견된 문제) ## 이 스킬의 목적 프로젝트가 끝나고 실제로 쓰기 시작하면 "xx 가 안 돼" 가 온다. 스킬 없이도 코드는 고쳐진다. 그러나 그렇게 고치면 **셋이 빠진다** — 같은 버그를 다시 잡는 검사가 게이트에 안 들어가고, `LOG.md` 에 무엇을 왜 고쳤는지 안 남고, 고친 사람이 아닌 세션의 확인(분리 검증)을 안 거친다. 이 스킬은 그 셋을 지키려고 있다. 코드를 고치는 것 자체는 이 스킬의 가장 작은 부분이다. ``` 진입 조건 — 루프가 도는 중인가 ← 0단계 → 버그인가, 요구 변경인가 (아니면 feature-add 로 돌리고 끝) ← 1단계 → BUG-NN 배정, 재현하고 LOG.md 에 재현 줄 (재현 절차 · 관찰 · 기대 동작) ← 2단계 (고치기 전에) → 원인 확인, LOG.md 에 원인 줄 ← 3단계 → 회귀 검사를 쓰고 고치기 전 실패를 본다 → harness-update ② 로 게이트에 등록 ← 4단계 → 수정 → 게이트 넷 (task_validation 은 분리 세션, 재현 줄이 수용 기준) ← 5·6단계 → 커밋 1회 (메시지에 BUG-NN), LOG.md 에 게이트·커밋 줄 ← 7단계 ``` 플랜·phase·태스크를 만들지 않는다. 버그 하나는 태스크가 아니다 — 태스크 ID 가 없으니 LOOP.md 의 커밋 규칙과 하네스의 활성 시점이 걸 곳이 없고, 그래서 그 자리를 **`BUG-NN`** 이 대신한다. ### 여기서 하는 일과 하지 않는 일 | 하지 않는 것 | 어디서 하는가 | |---|---| | 개발 중 게이트 실패를 고치는 것 | 개발 루프. `LOOP.md` 의 실패 분류(진짜 결함·낡은 스펙·환경 문제·흔들림·판정 불가)와 재시도 3회가 다룬다 — 이 스킬이 끼면 재시도 카운터와 분류를 건너뛰고, 하네스 갱신이 라운드 사이가 아닌 곳에 끼어 문서의 필자가 둘이 된다 | | 문서에 적힌 대로 동작하는 것을 바꾸는 것 | `feature-add` — 후미 절에 "FR-00x 수용 기준 n 을 다음으로 대체". 요구 변경이다 | | PRD·TRD·ARCHITECTURE·IDEA 를 고치는 것 | 하지 않는다. 문서와 어긋나게 된 곳은 보고한다(8단계) | | phase·태스크·`STATE.md` 를 고치는 것 | 하지 않는다. 루프·플랜은 이 버그와 무관하게 그대로다 | | 게이트 정의서를 직접 고치는 것 | `harness-update` ② — 회귀 검사 등록은 이 스킬이 호출 지시만 넘긴다 | | 신고되지 않은 옆 버그까지 고치는 것 | 하지 않는다. 발견한 것은 보고에 "다음 BUG 후보" 로 적는다 — 한 요청은 한 `BUG-NN` 이다 | | 리팩토링 ("고치는 김에 정리도") | `refactor-plan`. 동작이 안 바뀌는 정리는 버그 수정 커밋에 섞지 않는다 | --- ## 0단계. 진입 조건 `plan_setup/STATE.md` 를 읽는다. 하나라도 걸리면 **멈추고 보고한다.** 선택지로 묻지 않는다 — 진입 조건은 고를 것이 아니다. | 확인 | 걸렸을 때 | |---|---| | `Phase Overview` 에 `in_progress` 인 phase 가 **없는가** | 있으면 멈춘다 — **"지금은 루프가 도는 중이다. 이 문제가 현재 태스크의 것이면 루프가 고치고(LOOP.md 3절 실패 분류·재시도 3회), 아니면 phase 완료 뒤에 다시 부르라."** 이유는 필자 충돌이다 — 루프가 도는 동안 `LOG.md`·하네스 문서의 필자는 코디네이터 하나여야 하고(Layer 5), 회귀 검사를 넣는 harness-update ② 는 라운드 사이에만 한다. 신고 문장은 보고에 그대로 옮겨 나중에 다시 설명하지 않게 한다 | | code-to-docs 로 진입한 프로젝트(PRD 머리의 `> 코드에서 역으로 작성 (as-is` 또는 `phase-01-baseline`)면 phase-01 이 `completed` 인가 | 아니면 멈춘다 — 현행 고정 게이트가 아직 활성이 아니라 "기존 게이트 통과" 가 아무것도 지키지 않는다. 루프로 phase-01 을 먼저 끝내라고 답한다 | | `plan_setup/LOG.md`·`harness_setup/`·`loop_setup/LOOP.md` 가 있는가 | 없으면 무엇이 없는지 보고한다. `docs/PRD.md` 부터 없으면 `code-to-docs` 가 먼저다 — 기대 동작을 판정할 문서가 없다 | `pending` phase 는 막지 않는다 — feature-add 가 phase 를 더한 직후에도 이 스킬을 부를 수 있어야 한다. "전부 completed" 를 조건으로 삼으면 그 상태에서 막힌다. **`HARNESS.md` 1절의 게이트 상태를 본다.** 전부 `미활성` 이면 멈추고 보고한다 — 회귀 검사를 넣을 게이트가 돌지 않는다. 일부만 미활성이면 진행하되 7단계에서 그 게이트의 결과를 `미활성` 으로 적는다(통과로 적지 않는다). **git 상태를 본다.** 미커밋 변경이 있으면 알리고 사용자가 정할 때까지 기다린다 — 이 스킬의 커밋 1회에 남의 변경이 섞이면 `BUG-NN` 커밋이 무엇을 고쳤는지 말하지 못한다. 병렬 운용 프로젝트여도 이 스킬은 루프 밖이므로 **사용자가 연 트리(보통 `main`)** 에서 하고 거기에 커밋한다. 다음 코디네이터는 라운드를 열 때 `main` 을 먼저 병합한다(LOOP.md 8절). --- ## 1단계. 버그인가, 요구 변경인가 이 판정이 이 스킬의 첫 산출물이다. 틀리면 둘 중 하나가 된다 — 요구 변경을 버그로 고치면 문서(PRD)가 모르는 동작이 코드에 생기고 다음 스킬이 낡은 문서를 근거로 움직인다. 버그를 요구 변경으로 보내면 회귀 검사·BUG 기록·분리 검증 없이 phase 태스크로 들어간다. 신고를 읽고 관련 FR 을 찾는다 — PRD 4절과, 있으면 후미의 `## 추가 N` 절(같은 문서 안에서는 **뒤 절이 앞 절을 이긴다**). 그리고 두 질문을 순서대로 묻는다. **질문 1 — 기대 동작에 근거가 있는가.** 아래 중 하나가 "지금 동작은 틀렸다" 고 말해야 한다. | 근거 | 예 | |---|---| | **합의된 문서의 기준과 다르게 동작한다** — to-be PRD 의 수용 기준(as-is 가 아닌 문서, 또는 as-is 문서의 `## 추가 N` 절) | "FR-012 는 '완료 항목은 목록 아래로' 인데 위에 있다" | | **as-is 문서가 그 동작을 적으며 "의도 여부: OQ-xxx" 를 붙였고, 그 OQ 의 답이 "의도가 아니다"** | code-to-docs 는 버그로 보이는 동작도 판단 없이 관찰대로 적고 OQ(`확인`)를 붙인다 — 그 OQ 가 바로 이 질문이다. 답을 모르면 사용자에게 묻는다(아래) | | **코드가 스스로 드러낸 의도와 결과가 모순된다** — 파라미터 이름·화면 문구·주석이 말하는 동작을 어떤 입력으로도 얻을 수 없다 | `?done=false` 를 받는 필터가 어떤 값에도 빈 배열 · "안 끝난 것만" 체크박스가 늘 빈 목록 | | 문서와 무관하게 명백히 망가짐 — 500·크래시·데이터 손실·저장한 것이 사라짐 | | as-is 문서에 관찰대로 적혀 있고 OQ 가 없거나 답이 "의도였다" 면 → **요구 변경**(`feature-add`). 지금 동작이 합의된 것이고, 바꾸고 싶은 것은 새 요구다. **질문 2 — 고친 뒤의 동작을 새 결정 없이 한 줄로 쓸 수 있는가.** 기대 동작 한 줄을 써 본다. 쓰다가 아래 중 하나를 **정해야** 하면 → **요구 변경**(`feature-add`). 질문 1 이 "틀렸다" 여도 그렇다. - 누가 할 수 있는가(권한 규칙 — 소유자만? 멤버도? 관리자?) - 새 응답 코드·오류 형식(지금 없는 401·403·409) - 새 화면 요소·메시지·흐름(오류 안내, 확인 대화상자) - 새 필드·저장 형식·설정값 이 질문이 필요한 이유 — "소유자만 지울 수 있어야 하는데" 는 흔히 버그라고 불리지만, 고치려면 소유자의 정의·거절 응답·화면 처리를 새로 정해야 한다. 그건 수용 기준을 새로 쓰는 일이고 수용 기준은 PRD 가 정한다. 이 스킬이 정하면 PRD 에 없는 규칙이 코드에만 생긴다. 반대로 "`done=false` 면 미완료 할 일만 온다" 는 파라미터 이름이 이미 정해 둔 동작이라 새로 정할 것이 없다. 경계 입력(예: `done=abc`)처럼 부수적으로 정할 것이 생기면 **지금 동작을 유지**하는 쪽으로 쓰고 보고에 적는다 — 그것까지 정하는 것은 요구 변경이다. | 질문 1 | 질문 2 | 판정 | |---|---|---| | 예 | 예 | **버그** — 2단계로 | | 예 | 아니오 | **요구 변경** — feature-add. "무엇이 틀렸는가" 는 맞지만 "어떻게 고치는가" 가 새 요구다 | | 아니오 | — | **요구 변경** — feature-add | | 판정할 문서 기준이 없다(기능이 PRD 에 아예 없다) | — | **요구 변경** — 없는 기능이 없는 것은 버그가 아니다 | ### OQ 의 답을 모를 때 — 묻는다 as-is 문서의 "의도 여부: OQ-xxx" 는 종류가 `확인` 이다 — 사용자도 모를 수 있다. 그래서 **답 대신 "모른다" 를 고를 수 있게** 묻는다. ``` FR-007 의 as-is 기준 4("done=true/false 는 빈 배열")에 OQ-002(의도 여부)가 걸려 있습니다. 이 동작이 의도였습니까? ① 의도가 아니었다(버그) ② 의도였다 ③ 모른다 ③이면 코드가 스스로 드러낸 의도(파라미터 이름 done, 화면 문구 "안 끝난 것만")로 판정해 버그로 진행합니다 (가정). ``` "모른다" 일 때는 **코드가 스스로 드러낸 의도**가 하나로 정해지면 그것으로 판정하고 `(가정)` 으로 기록한다. 하나로 안 정해지면 요구 변경으로 보낸다 — 추측으로 코드를 바꾸는 것보다 feature-add 가 묻는 편이 싸다. 대화할 수 없는 환경이면 질문을 기록으로 남기고(`questions.md` 등) 위 기본값으로 진행한다. ### feature-add 로 돌릴 때 여기서 끝난다. 코드·테스트·하네스·`LOG.md`·문서를 **하나도 고치지 않는다.** 보고에 넣을 것: 1. 판정과 근거 — 질문 1·2 각각의 답, 인용한 FR 기준·OQ·코드 위치 2. feature-add 에 넘길 요청 문장 — 신고를 그대로 + "FR-00x 수용 기준 n 을 대체하는 요구 변경" + 새로 정해야 할 것 목록(질문 2 에서 걸린 것) 3. 같은 모양의 다른 곳(예: 삭제 API 가 둘이면 둘 다) — feature-add 가 한 요청으로 묶을지 정한다 --- ## 2단계. BUG-NN 배정과 재현 줄 — 고치기 전에 **NN** 은 `LOG.md` 전체에서 `BUG-(\d+)` 의 최댓값 + 1, 두 자리(`BUG-01`). LOG 가 유일한 기록처라 여기서 센다. 재현한다. 수단은 게이트와 같은 순서다 — 실행 중인 앱에서 HTTP·브라우저로 신고한 절차를 밟는다(`local-dev` 로 준비, 테스트 데이터 모드). **앱을 띄울 수 없으면**(`local-dev` 가 0이 아닌 코드 — 포트 점유·의존성 미설치) 그것은 환경 문제다. 남의 프로세스를 죽이지 않는다. 그때는 실행할 수 있는 가장 가까운 층에서 재현한다 — 결함이 있는 식을 그 입력값으로 직접 돌려 보는 것(예: 저장된 `false` 와 쿼리 문자열 `"false"` 의 `===` 비교를 `node -e` 로). 재현 수단을 줄에 적는다. 코드를 읽고 "그럴 것이다" 만으로는 재현이 아니다. `LOG.md` 에 **재현 줄**을 쓴다. 기존 형식 그대로 — 오늘 날짜의 `## YYYY-MM-DD` 가 마지막 제목이 아니면 새로 달고, 그 아래 `### BUG-NN` 으로 시작하는 한 줄. **`## BUG-NN` 같은 새 H2 를 만들지 않는다** — plan-generator 가 만든 LOG 형식과 어긋나고, 중간에 harness-update 가 자기 한 줄을 쓰므로 "BUG 항목 아래에 붙인다" 는 구조가 서지 않는다. 모든 줄은 append 다. ``` ### BUG-01 할 일 완료 필터 — 재현: 완료 1·미완료 1 을 만든 뒤 GET /api/tasks?done=false (화면: "안 끝난 것만" 체크) → 관찰: [] (빈 배열, 목록이 빈다) / 기대: 미완료 할 일 1개만 온다, done=true 면 완료 1개만. 재현 수단: HTTP (앱 미기동이면 "코드 단위 — local-dev 50") · 근거: FR-007 기준 4 · OQ-002 답 "의도 아님"(사용자, 2026-09-23) ``` **이 줄이 분리 검증의 수용 기준이다**(6단계). 그래서 고치기 전에 쓴다 — 고친 뒤에 쓰면 기대 동작이 "고친 코드가 하는 일" 로 적힌다. 쓰는 규칙은 수용 기준과 같다: 관찰 가능한 결과, 숫자가 필요한 곳엔 숫자, **기대 동작에 실패하면 안 되는 쪽 하나**(done=true 쪽처럼 고치다 깨지기 쉬운 옆 경로). --- ## 3단계. 원인을 확인하고 원인 줄을 쓴다 재현 절차를 거꾸로 따라가 결함이 있는 줄을 찾는다. **원인은 가설이 아니라 확인된 것이어야 한다** — 그 줄을 바꾸면 재현 결과가 바뀐다는 것을 보거나(4단계의 실패하는 검사가 그 증거가 된다), 최소한 그 식이 재현 입력에서 틀린 값을 낸다는 것을 실행으로 본다. ``` ### BUG-01 원인: routes/tasks.js 21행 — 저장값 done(불리언)과 req.query.done(문자열 "true"/"false")을 === 로 비교해 어떤 값이든 거짓. 같은 OQ-002 의 PATCH !!"false" (85행)는 신고 범위 밖 — 다음 BUG 후보로 보고 ``` 원인이 문서 쪽에 있으면(수용 기준 자체가 모순) 멈추고 1단계로 돌아간다 — 요구 변경이다. 원인을 찾다 신고되지 않은 다른 결함을 보면 **고치지 않는다.** 원인 줄이나 보고에 "다음 BUG 후보" 로 남긴다. 한 커밋에 두 버그가 들어가면 회귀 검사가 무엇을 지키는지 흐려진다. --- ## 4단계. 회귀 검사 — 고치기 전에 실패하는 것을 본다 ### 검사를 쓴다 회귀 검사는 **게이트가 이미 돌리는 테스트 파일**에 들어간다 — 같은 러너, 같은 스타일, 같은 위치. 게이트는 넷 고정이라 회귀 검사는 `unit` 또는 `e2e` 안에 들어간다(다섯째 게이트를 만들지 않는다). - **층은 재현이 드러난 가장 낮은 곳** — API 로 재현되면 unit(API 테스트), 화면에서만 드러나면 e2e. 신고가 화면에서 왔어도 API 에서 재현되면 unit 이 주 검사이고, 화면 쪽 기존 케이스가 버그 동작을 붙잡고 있으면 그것도 뒤집는다(아래) - **케이스 이름에 `BUG-NN`** — `LOG.md` 의 실패 기록이 케이스 이름으로 남으므로, 나중에 이 검사가 깨지면 이 줄로 거슬러 올라온다 - **진짜 단언** — 재현 줄의 기대 동작을 그대로 단언한다. 실패 경로 쪽(done=true 쪽)도 단언한다 - **기존 현행 고정 케이스가 버그 동작을 단언하고 있으면 뒤집는다** — code-to-docs 로 들어온 프로젝트는 as-is 동작("빈 배열")을 붙잡은 케이스가 있다. 그대로 두면 고친 뒤 그 케이스가 실패한다. **지우지 않고 기대값을 바꾼다**(케이스 이름에 `BUG-NN` 병기) — 단언을 빼는 것은 약화이고, 뒤집는 것은 같은 자리를 옳은 값으로 지키는 것이다. 무엇을 뒤집었는지 보고한다. unit·e2e **양쪽을 다 찾는다**(`grep` 으로 버그 동작을 적은 케이스 이름·주석 — "빈 배열", "비고"). 회귀 검사를 한 층에만 두더라도, 다른 층의 케이스가 이름으로 버그 동작을 기준처럼 적고 있으면 그 이름과 기대값도 뒤집는다 — 스텁이라 지금은 안 깨져도, 나중에 누가 스텁을 채우면 그 이름대로 버그를 다시 붙잡는다 **기존 테스트가 스텁인지 본다.** `assert.ok(true)`·`expect(true)` 처럼 아무것도 확인하지 않는 케이스는 게이트를 초록으로 만들 뿐 지키는 것이 없다. 그 파일에 회귀 검사를 더할 때 스텁 옆에 진짜 단언을 두고, 스텁이 있다는 사실(파일·케이스 수)을 보고에 적는다 — 게이트가 실제보다 강해 보이는 것은 하네스의 문제이고 이 스킬의 범위 밖이다(`harness-update` 또는 사용자 판단). ### 고치기 전 실패를 본다 **검사를 쓴 다음, 코드를 고치기 전에 그 게이트 명령을 돌린다.** 회귀 검사가 **재현 줄과 같은 이유로** 실패해야 한다(기대 [미완료 1개] / 실제 []). 통과하면 그 검사는 버그를 못 잡는다 — 다시 쓴다. 이 확인이 없으면 "고친 뒤 통과" 는 아무 뜻이 없다. 처음부터 통과하는 검사도 고친 뒤에는 통과한다. **게이트 밖에서 코드 단위로 돌려 보는 것**(의존성을 대신하는 스텁, 다른 포트)은 참고 증거로 적어도 되지만 정본 게이트의 결과를 대신하지 않는다. 그리고 그런 실행은 **실데이터 파일을 건드리지 않게** 사본이나 메모리 데이터를 쓴다 — 하네스가 게이트를 테스트 데이터 모드에서만 돌리게 한 이유(실데이터일 수 있다)가 게이트 밖 실행에도 그대로 걸린다. 끝나면 `git status` 로 코드·테스트·하네스·LOG 외에 바뀐 파일이 없는지 본다. **게이트가 환경 때문에 돌지 않으면**(앱 미기동으로 연결 거부, 도구 미설치) 그것은 "실패 확인" 이 아니다 — 연결 거부는 고친 뒤에도 연결 거부다. `LOOP.md` 의 환경 문제 규칙대로 `local-dev` 종료 코드로 확정하고, 재준비 1회(10·20·포트 점유는 사용자에게), 그래도 안 되면 **환경 문제로 기록하고 계속하되 커밋하지 않는다**(7단계). 러너 자체가 도는지(`node --test` 가 기존 파일을 실행하는지)는 따로 적는다 — "러너는 돈다, 앱이 없다" 와 "아무것도 안 돈다" 는 다음 사람이 할 일이 다르다. ### harness-update 로 게이트에 등록한다 테스트 파일은 코드이고 이 스킬이 쓴다. **게이트 정의서에 그 검사를 올리는 것은 harness-update 다**(②, 범위 확대 — 강화 방향이라 승인 없이 한다). 넘길 지시: ``` harness-update 호출 지시 (bug-fix 에서) - BUG-NN 회귀 검사를 게이트에 더한다(②). 검사: <게이트> <파일> 의 케이스 "" (뒤집은 기존 케이스: <파일::케이스>) - 근거: plan_setup/LOG.md 의 "### BUG-NN … 재현:" 줄. 태스크 ID 가 없으므로 **즉시 활성**이고, 커버 영역 표(또는 검사 목록)의 활성 시점·담당 열에는 `BUG-NN` 을 적는다 - 그 게이트 정의서·HARNESS.md·task_validation.md 에 버그 동작을 기준·예시로 적은 문장(예: "as-is 동작 'done=false 가 빈 배열' 이 기준이다")이 있으면 BUG-NN 기준으로 바꾼다 — 단언을 빼는 것이 아니라 옳은 값으로 옮기는 것이라 ③ 이 아니다 - task_validation 의 "검증자가 받는 것" 에 BUG-NN 검증의 경우를 더한다(없으면) — 태스크 문서 경로 대신 LOG.md 의 BUG-NN 재현 줄을 지시문에 옮겨 준다. phase 전용 확인(예: "앱 코드 무변경")은 그 phase 의 태스크에만 걸린다고 적는다 - LOG.md 에 남기는 한 줄은 `### BUG-NN` 으로 시작한다(예: "### BUG-NN 회귀 검사 추가 — unit test/api/tasks-list.test.js 2케이스, 활성 시점 BUG-NN, 고치기 전 fail 2 확인"). 태스크 ID 로 시작하지 않는다 — 루프가 LOG 를 훑을 때 태스크로 읽는다 - STATE.md·태스크 문서·코드는 고치지 않는다. CLAUDE.md·AGENTS.md 의 `## 하네스` 절은 바뀐 값이 있을 때만 ``` --- ## 5단계. 고친다 원인 줄이 가리킨 곳을 **최소한으로** 고친다. 고치는 김에 이름을 바꾸거나 정리하지 않는다 — 그건 refactor-plan 이고, 섞이면 회귀 검사가 통과했을 때 무엇이 고쳐서 통과했는지 말할 수 없다. 화면과 API 가 둘 다 결함이 아니라면 결함이 있는 한 곳만 고친다(done 필터는 API 한 줄을 고치면 화면이 그대로 맞는다). --- ## 6단계. 게이트 넷 `LOOP.md` 3절 그대로 돈다 — 이 스킬은 그 안에서 움직인다. `local-dev` 로 준비(테스트 데이터 모드), `lint → unit → e2e` 를 순서대로, 앞이 실패하면 뒤를 돌리지 않는다. 명령은 정의서의 `## 명령` 에서 꺼낸다(여기 베껴 두지 않는다). - **실패하면 분류부터** — 진짜 결함·낡은 스펙·환경 문제·흔들림·판정 불가(`LOOP.md` 3절). 진짜 결함만 수정 횟수로 세고 **3회**에서 멈춘다. 나머지는 확인 실행 1회 - **전체 스위트**가 대상이다 — 회귀 검사만 돌려 통과시키지 않는다. 다른 케이스가 깨졌으면 그것도 이 수정의 결과다 - 결과는 명령·종료 코드·케이스 수(`tests N / fail 0`)로 적는다. "통과함" 은 결과가 아니다 ### task_validation — 분리 세션, 재현 줄이 수용 기준 평소처럼 **고친 이 세션이 아닌 세션**이 판정한다. 세션을 여는 방법은 `LOOP.md` 의 구동기가 정한 것(헤드리스 자식 `claude -p`·`codex exec` / 오르카 워커 / 서브에이전트)을 따른다. 태스크 문서가 없으므로 **검증자에게 태스크 문서 경로 대신 재현 줄을 준다.** `LOG.md` 경로를 주지 않고 **그 줄의 문장을 지시문에 옮긴다** — LOG 에는 원인 줄이 같이 있고, 원인 추정은 검증자가 받지 않는 것이다. ``` 다음 버그의 기대 동작을 실행 중인 애플리케이션에서 관찰해 판정하세요. - 수용 기준(재현 절차와 기대 동작): "" - 절차와 판정 형식: harness_setup/quality_gates/task_validation.md (TASK 칸에는 BUG-NN) - 애플리케이션: <주소>. 으로 테스트 데이터 모드로 준비돼 있습니다 - 이 문장, 그 문서, 실행 중인 앱 외의 정보는 찾지 마세요. 최근 커밋·변경 파일·LOG.md·작업 메모를 참고하지 말고, 코드도 테스트도 고치지 말고, 원인 추정을 하지 마세요. - 판정 블록을 그대로 출력하세요 (END 로 끝냅니다). ``` 판정 블록의 `RESULT` 가 `환경 문제` 면 FAIL 이 아니다(재준비 1회). `FAIL` 이면 진짜 결함으로 5단계로 돌아간다(3회 안에서). 분리 세션을 열 수단이 이 환경에 없으면 **이 세션이 대신 판정하지 않는다** — `task_validation` 미실행으로 적고 커밋하지 않는다. --- ## 7단계. 커밋 1회와 마지막 줄 **게이트 넷이 전부 통과한 뒤에만** 커밋한다 — 활성 게이트는 통과, 미활성 게이트는 `미활성` 으로 적힌 상태(0단계). 넷 중 하나라도 실패·환경 문제·미실행이면 **커밋하지 않고** 변경을 그대로 둔 채 멈춰 보고한다(`LOOP.md` 의 "3회 소진 시 미커밋 변경은 그대로 둔다" 와 같다). 게이트를 못 돌린 채 커밋하면 `BUG-NN` 커밋이 "검증된 수정" 이라는 뜻을 잃는다. 통과했으면 **마지막 줄**을 `LOG.md` 에 쓰고, 그 줄까지 포함해 **한 번** 커밋한다. ``` ### BUG-01 게이트 넷 통과 — lint 0 · unit tests 64 fail 0 (회귀 2) · e2e 12 passed · task_validation PASS (관찰 수단 3). 고친 파일: routes/tasks.js. 커밋 "BUG-01: 완료 필터가 문자열과 불리언을 비교해 늘 빈 배열" (해시는 git log --grep BUG-01) ``` 해시를 줄에 박지 않는 이유 — 그 줄이 들어간 커밋의 해시는 커밋 전에 알 수 없다. 커밋 메시지의 `BUG-NN` 이 LOG 와 커밋을 잇는다. 해시는 보고에 적는다. 커밋에는 이 버그로 바뀐 파일만 경로로 add 한다(`git add -A` 금지 — 0단계에서 확인한 뒤 생긴 파일이 섞인다): 고친 코드, 테스트 파일, harness-update 가 고친 하네스 문서, `LOG.md`. **멈출 때의 마지막 줄**: ``` ### BUG-01 게이트 미통과 — lint 0 · unit 환경 문제(local-dev 50: 포트 3000 을 다른 프로세스가 점유, 회귀 검사 미판정) · e2e·task_validation 미실행. 커밋하지 않음 — 고친 파일 routes/tasks.js 는 미커밋 상태로 둠 ``` 다시 불리면(환경을 고친 뒤 "BUG-01 이어서") `LOG.md` 의 `BUG-01` 줄들을 읽어 마지막 단계 다음부터 잇는다 — 재현·원인 줄을 다시 쓰지 않는다. 세션이 끊겨도 LOG 만 있으면 이어진다. `STATE.md` 는 쓰지 않는다. 루프 상태(`Current Task`·`Last Verification`·`Retry`)는 태스크의 것이고 버그는 태스크가 아니다. `CLAUDE.md`·`AGENTS.md` 의 `현재 단계` 줄도 건드리지 않는다 — 버그 수정은 단계가 바뀐 것이 아니다. 하네스 절은 harness-update 가 필요하면 갱신한다. --- ## 8단계. 자체 점검과 보고 전부 끝나면(또는 멈추면) 다시 읽으면서 확인한다. | 확인할 것 | 통과 조건 | 걸렸을 때 | |---|---|---| | 진입 조건 | `in_progress` 0건, code-to-docs 진입이면 phase-01 completed | 0단계로 돌아가 멈춘다 | | 판정 근거 | 질문 1·2 의 답과 인용(FR 기준·OQ·코드 위치)이 보고에 있음 | 채운다 | | LOG 형식 | `## 날짜` 아래 `### BUG-NN` 으로 시작하는 줄만 더해짐. 재현 → 원인 → 회귀 검사(harness-update) → 게이트·커밋 순서. 새 H2 0개, 기존 줄 변경 0 | 고친다 | | 재현 줄 | 원인·수정보다 먼저. 재현 절차·관찰·기대 동작·재현 수단이 있고 기대 동작이 관찰 가능 | 고친다 — 고친 뒤 기대 동작을 바꿔 쓰지 않는다 | | 회귀 검사 | 진짜 단언(스텁 아님), 케이스 이름에 BUG-NN, **고치기 전 실패를 실제 실행으로 확인**(못 했으면 그 사실과 이유) | 다시 쓴다 | | 버그 동작을 적은 기존 케이스 | unit·e2e 어디에도 이름·주석·기대값이 버그 동작을 기준으로 적은 케이스가 남지 않음(뒤집힘). 게이트 밖 실행 뒤 실데이터 파일 변경 0 | 뒤집는다. 데이터는 `git checkout` 으로 되돌리고 보고 | | 게이트 등록 | 정의서에 BUG-NN 검사가 즉시 활성, 활성 시점 열 `BUG-NN`, 게이트 수 넷 그대로 | harness-update | | 수정 범위 | 원인 줄의 파일만. 옆 버그·정리 0건 | 되돌리고 보고로 | | 불변 | `docs/`·`plan_setup/`(LOG.md 제외)·`STATE.md`·`loop_setup/` 변경 0 | 되돌린다 | | 게이트 결과 | 넷의 결과가 명령·숫자로 LOG 마지막 줄에. 미실행·환경 문제를 통과로 적은 곳 0 | 고친다 | | 커밋 | 통과했으면 정확히 1회, 메시지에 BUG-NN, 바뀐 파일만. 미통과면 0회 | 고친다 | **자체 점검 결과는 문서에 남기지 않는다.** 보고로 간다. 보고는 이렇게 한다. 1. 판정 — 버그 / 요구 변경, 질문 1·2 의 답과 근거. 사용자에게 물은 것과 답(또는 가정한 기본값) 2. `BUG-NN` 과 LOG 에 쓴 줄(그대로 옮긴다) 3. 원인과 고친 파일 4. 회귀 검사 — 파일·케이스, 고치기 전 실패 결과, 뒤집은 기존 케이스, 스텁을 발견했으면 그 사실 5. 게이트 넷 결과와 분리 검증 판정 블록. 커밋 해시와 `git log --oneline -1` 한 줄. 커밋하지 않았으면 이유와 이어서 할 일 6. **문서와 어긋나게 된 곳** — as-is 문서가 버그 동작을 관찰대로 적어 두었으면(예: PRD FR-007 기준 4, FR-014 의 화면 기준, OQ-002) 그 줄은 이제 과거의 관찰이다. 이 스킬은 문서를 고치지 않으므로 목록으로 넘긴다 — OQ 의 답은 LOG 의 BUG-NN 줄에 있고, PRD 를 다음에 부분 갱신하는 스킬(feature-add 가 prd-generator 를 부를 때)이 미결 표의 그 행을 "해소 — BUG-NN" 으로 옮길 수 있게 7. 다음 BUG 후보 — 원인을 찾다 본 다른 결함(신고 범위 밖) 8. 다음: 커밋했으면 "끝 — 루프·플랜은 그대로다". 멈췄으면 무엇을 고친 뒤 "BUG-NN 이어서" 로 다시 부르라 --- ## 범위를 넘는 요청이 함께 오면 | 요청 | 이 스킬이 하는 것 | 왜 | |---|---|---| | "고치는 김에 이 부분도 정리해" | 버그만. 정리는 `refactor-plan` | 정리가 섞이면 회귀 검사의 통과가 무엇을 증명하는지 흐려진다 | | "버그 셋 한꺼번에" | 하나에 `BUG-NN` 하나씩, 순서대로. 커밋도 각각 | 커밋과 회귀 검사가 버그 하나를 가리켜야 나중에 되돌리거나 추적할 수 있다 | | "소유자만 지우게 해 줘"(권한이 없는 것을 버그로 신고) | 1단계 질문 2 로 판정 — 대개 feature-add | 누가 무엇을 할 수 있는지는 수용 기준이고 PRD 가 정한다 | | "테스트는 됐고 그냥 고쳐 줘" | 재현 줄과 회귀 검사는 한다 | 이 스킬이 있는 이유가 그 둘이다. 스킬 없이 고치는 것과 같아진다 | | "지금 루프 도는 중인데 이것도 고쳐 줘" | 0단계에서 멈춘다 | 현재 태스크의 것이면 루프가, 아니면 phase 완료 뒤 이 스킬이 | --- ## 에이전트별 참고 - Claude Code에서 실행 중이면: `references/claude-code.md` - Codex에서 실행 중이면: `references/codex.md`