--- name: harness-update description: 이미 있는 harness_setup/ 을 프로젝트 진행에 맞춰 갱신한다. 활성 시점에 도달한 게이트를 미활성에서 활성으로 올리고, 대기 중이던 E2E 흐름과 필수 커버 영역을 활성화하며, 차단(OQ·CF)이 풀린 태스크를 검증 대상에 넣고, 새로 생긴 의존 서비스(Redis·API 서버 등)를 구성 요소 표와 local-dev 스크립트에 더하고, 게이트 명령이 실제와 어긋난 곳을 맞춘다. 사용자가 "하네스 업데이트해줘", "게이트 갱신해줘", "이 흐름도 E2E에 넣어줘", "Redis 추가됐으니 local-dev 고쳐줘", "테스트가 계속 깨지는데 하네스 좀 봐줘", "차단 풀렸으니 검증 대상에 넣어줘"라고 하거나, 개발 루프가 태스크·phase 를 완료한 뒤 하네스가 실제와 맞지 않는다고 판단했을 때 반드시 이 스킬을 사용한다. 게이트가 실패하는데 그 원인이 코드가 아니라 하네스 쪽(낡은 흐름 정의, 준비되지 않는 개발 환경)일 때도 이 스킬로 고친다. harness_setup/ 이 아직 없으면 이 스킬이 아니라 harness-setup 을 쓴다. --- # 하네스 갱신 (Layer 4) ## 이 스킬의 목적 하네스는 한 번 세우고 끝나는 것이 아니다. **phase 가 진행되면 검증할 수 있는 것이 늘어나고, 설계가 바뀌면 흐름이 낡고, 새 의존 서비스가 붙는다.** 원본 사양이 "필요한 경우 적극적으로 업데이트되어야 한다"고 적은 것이 이 자리다. 갱신되지 않는 하네스는 두 방향으로 망가진다. ``` 갱신이 늦으면 → 만들어진 기능이 검증 대상에 안 들어간다. 아무도 그것을 확인하지 않는다 갱신이 헐거우면 → 실패하는 게이트를 통과시키려고 기준을 낮춘다. 하네스가 조용히 죽는다 ``` **두 번째가 이 스킬의 진짜 위험이다.** 게이트를 고칠 권한이 있는 쪽이 그 게이트에 걸려 있는 쪽과 같으면, 가장 빠른 해결은 언제나 기준을 낮추는 것이다. 그래서 이 스킬의 절반은 **무엇을 고쳐도 되는지**가 아니라 **무엇을 고치면 안 되는지**에 대한 것이다. ### 여기서 하는 일과 하지 않는 일 | 하지 않는 것 | 어디서 하는가 | |---|---| | 하네스를 처음 세우는 것 | `harness-setup` — `harness_setup/` 이 없으면 그쪽이다 | | 게이트를 새로 만들거나 없애는 것 | 아무 데서도 하지 않는다. 네 게이트는 원본 사양이 정했다 | | 도구를 바꾸는 것 (테스트 러너, 브라우저 자동화) | TRD (`trd-generator`) — 사람이 부른 갱신이면 사용자가 정한 뒤 TRD 부터 고치고 이어 간다, 루프가 부른 것이면 `질문:` (5단계 뒤 "앞 문서 반영") | | 태스크·수용 기준·`verification` 값을 고치는 것 | Layer 3 (`plan-generator` 10단계 부분 갱신) | | 실패한 코드를 고치는 것 | Layer 5 개발 루프 — **검증하는 쪽이 고치면 검증이 아니다** | | 게이트를 실행하고 재시도를 세는 것 | Layer 5 (`loop-setup`) | | STATE.md 를 고치는 것 | Layer 5 — 여기서는 **읽기만** 한다 | **형식은 새로 정하지 않는다.** 게이트 정의서의 절 구성, 구성 요소 표의 열, 판정 블록의 모양은 `harness-setup` 의 산출물 템플릿에 이미 있다. 여기서 다시 정하면 두 스킬이 다른 형식을 만들고, 그러면 갱신을 한 번 돌릴 때마다 문서가 조금씩 달라진다. **기존 파일의 형식을 그대로 따라 쓴다.** --- ## 0단계. 언제 이 스킬을 부르는가 ### 하네스는 처음부터 완성될 수 없다 `harness-setup` 은 **코드가 0줄인 상태**에서 선다. 플랜 전체를 읽고 세우지만, phase-04 의 태스크가 실제로 무엇을 요구하는지는 **그것을 구현할 때 드러난다.** 그래서 첫 태스크에 들어갈 때 하네스가 미완성인 것은 결함이 아니라 **전제**다. 게이트마다 활성 시점을 적어 둔 것이 그 전제를 문서로 만든 것이고, 이 스킬이 그것을 메워 나가는 수단이다. **두 종류의 부족을 구별한다.** 다루는 방법이 다르다. | | 계획된 성숙 | 계획에 없던 부족 | |---|---|---| | 무엇인가 | 활성 시점 도달, 흐름 활성화, 커버 영역이 덮임 | 하네스가 그 태스크를 **검증할 수단을 아예 안 갖고 있다** | | 왜 생기나 | 그렇게 설계했다. 시간이 지나면 풀린다 | `harness-setup` 이 놓쳤거나, 그때는 알 수 없었다 | | 등급 | ① 상태 반영 | ② 범위 확대 | | 보고 | 한 줄 | **무엇을 왜 놓쳤는지 올린다** | 뒤쪽이 사용자가 걱정할 자리다. 앞쪽은 저절로 드러나지만(활성 시점 표에 적혀 있다), 뒤쪽은 **그 태스크를 검증하려 할 때 비로소 보인다.** 그래서 시점을 나눠 확인한다. ### 태스크를 시작하기 전 — 지금 하네스로 이 태스크를 판정할 수 있는가 **구현 전에 본다.** 구현이 끝난 뒤에 "검증할 수단이 없다"를 알면 그 세션을 버리게 되고, 한 태스크에 한 세션을 쓰는 구조에서 그건 가장 비싼 실수다. | 확인 | 아니오이면 | |---|---| | 이 태스크의 **수용 기준을 판정할 수단**이 있는가 — 관찰 경로, 픽스처, 시드 데이터, 시간 조작 | 그 수단을 먼저 갖춘다 (②) | | 이 태스크가 만드는 것을 **덮는 흐름·커버 영역**이 목록에 있는가 | 더한다. `harness-setup` 이 놓친 것이므로 보고한다 (②) | | 이 태스크가 쓰는 **의존 서비스**가 구성 요소 표와 `local-dev` 에 있는가 | 행 하나 + 블록 하나를 더한다 (②) | | **게이트 명령이 이 태스크 유형에 실제로 걸리는가** | 맞춘다 (①) | **전부 예이면 이 스킬을 부르지 않는다.** 확인 자체는 비용이 거의 없고, 부르는 것은 비용이 있다. ### 태스크를 완료한 뒤 — 진행을 문서에 반영한다 | 확인 | 예이면 | |---|---| | 방금 완료한 태스크가 **어느 게이트의 활성 시점**인가 | 부른다 (①) | | 이 태스크로 **어떤 흐름의 마지막 조각**이 채워졌나 | 부른다 (①) | | 이 태스크가 **필수 커버 영역** 중 하나를 덮었나 | 부른다 (①. 일부만이면 `부분 활성`) | | 이번에 **새 의존 서비스·새 환경변수**가 생겼나 | 부른다 (②) | ### 시점과 무관하게 부르는 것 둘 - **차단(OQ·CF)이 해소됐을 때** — 태스크 완료가 아니라 사용자가 미결을 푼 시점이다. **가장 잘 빠진다.** 차단은 태스크 문서에서 풀리는데 `대기` 표시는 하네스 문서에 있어서, 둘을 잇는 것이 사람의 기억밖에 없기 때문이다 - **같은 흐름·검사 ID의 실패가 두 태스크에 걸쳐 나왔을 때** — 루프의 재시도 카운터는 태스크마다 리셋되므로, 태스크를 넘어 반복되는 것은 루프가 `LOG.md` 의 흐름·검사 ID로 잡아 여기로 올린다. 코드를 또 고치기 전에 하네스를 의심하는 자리다 **"게이트가 실패했다"는 그 자체로 트리거가 아니다.** 먼저 4단계로 분류하고, '낡은 스펙'·'환경 문제'일 때만 이 스킬이다. 분류 없이 실패마다 하네스를 부르면, 실패를 없애는 가장 빠른 길이 기준을 낮추는 것이 된다. ## 1단계. 무엇이 바뀌었는지부터 읽는다 **고칠 곳을 정하기 전에 현재 상태를 읽는다.** 하네스는 진행 상황을 반영한 문서라, 지금 어디까지 왔는지를 모르면 활성화해야 할 것과 아직 이른 것을 구별할 수 없다. | 읽을 것 | 무엇을 얻는가 | |---|---| | `harness_setup/HARNESS.md` | 게이트별 활성 시점과 현재 상태, 구성 요소 표, '하네스 밖', '하네스 결정' | | `harness_setup/quality_gates/*.md` | 지금 무엇이 활성이고 무엇이 `대기` 인가 | | `harness_setup/scripts/` | 지금 준비하는 구성 요소와 옵션 | | `plan_setup/STATE.md` | **지금 어느 phase·어느 태스크인가.** 완료된 태스크 목록 | | `plan_setup/LOG.md` | 무슨 일이 있었는가. **반복되는 실패가 있으면 여기 쌓여 있다** | | 갱신을 부른 태스크 문서 | `status`·`verification`·`architecture`·`acceptance_criteria` 의 현재 값 | **앞 문서가 바뀌었다면 그것도 읽는다** — PRD에 요구사항이 늘었거나, TRD의 결정이 바뀌었거나, 플랜에 태스크가 추가된 경우다. 하네스는 그 셋에서 파생된 것이라, 원본이 움직였는데 하네스가 그대로면 게이트가 존재하지 않는 것을 검증하거나 새로 생긴 것을 놓친다. **무엇이 움직였는지는 버전으로 안다** — `HARNESS.md` 머리의 `입력 문서` 버전보다 실제 PRD·TRD·아키텍처·PLAN.md 버전이 높으면, 그 문서의 `변경 이력` 에서 기록된 버전 뒤의 줄과 그 `영향` 칸을 읽는다. 다 고친 뒤 `입력 문서` 버전을 맞춘다. **`harness_setup/` 이 없으면 이 스킬을 쓰지 않는다.** 갱신할 대상이 없다. `harness-setup` 을 제안한다. ## 2단계. 갱신을 세 등급으로 나눈다 **모든 갱신을 같은 무게로 다루면 안 된다.** 대부분은 그냥 하면 되는 상태 반영이고, 일부는 비용이 붙어 알려야 하고, 일부는 하네스를 약하게 만들어 승인이 필요하다. | 등급 | 무엇인가 | 어떻게 하는가 | |---|---|---| | **① 상태 반영** | 이미 일어난 사실을 문서에 맞추는 것 — 활성 시점 도달, 흐름 활성화, 커버 영역이 덮임 | **그냥 한다.** 보고에 한 줄 | | **② 범위 확대** | 검증 대상을 넓히는 것 — 흐름 추가, 검사 추가, 구성 요소 추가 | 한다. **매 태스크에 얼마나 붙는지**를 보고한다 | | **③ 약화** | 검증 대상을 좁히거나 기준을 낮추는 것 | **사용자 승인 없이는 하지 않는다** | ①이 갱신의 대부분이고, 원본 사양이 "적극적으로 업데이트"라고 한 것이 주로 이것이다. **①을 미루면 하네스가 실제보다 뒤처져서, 만들어진 기능이 검증 없이 지나간다.** 망설일 이유가 없다. ### 무엇이 ③ 약화인가 | 약화 | 왜 약화인가 | |---|---| | E2E 흐름을 지우거나 단계를 줄인다 | 그 흐름이 덮던 요구사항이 검증 대상에서 사라진다 | | 실패 경로 단언을 뺀다 | 성공 경로만 남으면 절반만 검증된다 | | 필수 커버 영역을 목록에서 뺀다 | TRD가 "반드시 덮어야 한다"고 적은 것이다 | | 임계를 낮춘다 (취약점 심각도, 성능 목표, 경고 허용) | 통과선을 내리는 것이다 | | **활성 시점을 뒤로 미룬다** | 가장 눈에 안 띈다. 게이트가 계속 `미활성` 이면 아무것도 막지 않는다 | | 활성이던 것을 `대기` 로 되돌린다 | 위와 같다 | | 게이트 명령의 대상 범위를 좁힌다 | "수정한 파일만"으로 바꾸면 다른 곳이 깨진 채로 통과한다 | **③은 거절이 아니라 확인이다.** 사용자가 직접 낮춰 달라고 요청한 경우에도 마찬가지다 — **결과를 제시하고 확인받은 뒤 적용한다.** 확인을 받는 이유는 요청을 막으려는 것이 아니라, "단언 하나를 뺀다"와 "앞으로 아무도 이것을 확인하지 않는다"가 같은 말인데 앞쪽으로만 들리기 때문이다. 확인 뒤에는 그대로 하고, 무엇을 왜 낮췄는지를 기록에 남긴다. 대화할 수 없는 상황이면 적용하지 말고 결과와 대안을 적어 사용자에게 넘긴다. **"지금 실패하고 있어서"는 약화의 이유가 아니다.** 그건 게이트가 제 일을 하고 있다는 뜻이다. 약화가 정당한 경우는 **그 검증이 더 이상 유효하지 않을 때**뿐이다 — 요구사항이 실제로 없어졌거나, 설계가 의도적으로 바뀌어 그 흐름이 존재하지 않게 된 경우. 그때는 **앞 문서에 그 변경이 이미 반영되어 있어야 하고**, 반영되지 않았으면 하네스가 아니라 앞 문서를 먼저 고칠 일이다 — 5단계 뒤의 "앞 문서 반영"이 그 절차다. ## 3단계. 무엇이 바뀌면 무엇을 고치는가 | 일어난 일 | 고치는 곳 | 등급 | |---|---|---| | 활성 시점으로 적어 둔 태스크가 완료됨 | `HARNESS.md` 게이트 표: `미활성` → 활성 | ① | | 어떤 흐름의 마지막 태스크가 완료됨 | `e2e.md` 그 흐름: `대기` → 활성. 매 태스크 묶음에 넣을지 phase 완료 묶음에 둘지 정한다. phase 완료 묶음이면 `## phase 완료 묶음 명령` 절의 명령(또는 그것이 읽는 설정)이 그 흐름을 돌게 하고, 절이 비어 있었으면 명령을 채운다(루프는 비어 있으면 `미활성` 으로 읽는다). 차단으로 `대기` 가 된 흐름은 거기서 뺀다 | ① | | 필수 커버 영역에 실제 테스트가 생김 | `unit.md` 표의 그 행 상태와 담당 태스크. 걸린 태스크 중 일부만 끝났으면 `부분 활성` 이고 **남은 태스크를 함께 적는다** | ① | | 차단(OQ·CF)이 풀려 **루프의 차단 해소 절차가** 태스크를 `blocked` → `pending` 으로 바꿈 (`LOOP.md` 2절) | `대기` 였던 흐름·커버 영역을 활성 후보로. **그 태스크가 이제 검증 대상이다.** 태스크 문서는 여기서 고치지 않는다 — 루프가 이미 고쳤다 | ① | | phase 가 완료됨 | phase 완료 묶음이 실제로 돌았는지(루프의 구동 스크립트가 돌리고 결과는 `LOG.md` 의 phase 기록에 있다) 확인하고 결과를 반영. 구동 스크립트는 `e2e.md` 의 `## phase 완료 묶음 명령` 절을 실행할 때마다 읽으므로 그 절만 맞으면 루프 쪽은 고칠 것이 없다. 옛 하네스에 그 절이 없으면 지금 묶음 명령으로 만든다 | ① | | 새 의존 서비스가 필요해짐 (캐시, 큐, 추가 API) | `HARNESS.md` 구성 요소 표에 행 하나 + `local-dev` 에 같은 모양의 블록 하나. 종료 코드 표는 **단계가 늘 때만** 바뀐다 — 같은 단계(의존 서비스) 안의 추가는 코드를 늘리지 않는다 | ② | | **태스크의 수용 기준을 판정할 수단이 하네스에 없다** (관찰 경로·픽스처·시드·시간 조작) | 그 수단을 더한다. 흐름이 필요하면 흐름을, 데이터가 필요하면 `local-dev` 를 | ② | | **태스크가 만드는 것을 덮는 흐름·커버 영역이 목록에 없다** | 더한다. `harness-setup` 이 놓친 것이므로 **무엇을 왜 놓쳤는지 보고한다** | ② | | 새 요구사항이 PRD에 들어옴 | 그것을 덮는 흐름이나 커버 영역을 더한다 | ② | | E2E 실패가 **'낡은 스펙'** 으로 분류됨 | 그 흐름 정의를 현재 화면·규약에 맞춘다. **단언을 지우는 게 아니라 옮기는 것이다** | ①~③ | | E2E 실패가 **'환경 문제'** 로 반복됨 | `local-dev` 의 준비 판정·대기 조건. 게이트가 아니라 스크립트를 고친다 | ① | | 실패가 **'흔들림'** 으로 분류됨 (같은 코드로 결과가 다름) | **그 테스트를 안정화한다** — 역할 기반 선택자, 실행마다 고유한 데이터, 인증 상태 재사용, 명시적 대기 조건, 외부 값은 실제 경로에서 읽기(`e2e.md` 의 흔들림 규칙). **그리고 원인 패턴을 그 게이트 정의서의 흔들림 규칙 표에 한 줄 더한다**(출처 = 태스크 ID·검사 ID. `unit.md` 에 표가 없으면 절을 만든다) — 테스트 하나만 고치면 다음 태스크의 새 스펙이 같은 실수를 한다. **단언을 빼거나 재시도 횟수를 늘려 통과시키는 것은 ③이다** | ①(안정화·규칙 추가) / ③(단언 제거·재시도 추가) | | **`loop-update` 가 격리를 1·2로 정함** (격리 3에서 DB 테스트가 반복해 흔들려 올린 경우가 흔하다) | `local-dev` 에 슬롯 번호(`HARNESS.md` 2절의 슬롯 변수)에서 포트·DB 이름을 파생하는 분기, 격리 2면 슬롯별 데이터베이스·계정 발급 단계. `HARNESS.md` 2절의 슬롯 변수·공유 DB 줄을 갱신하고, 슬롯 둘로 동시에 `-Status` 가 따로 나오는지 돌려 본다 | ② | | **워커가 오류 기록 없이 사라진다** (병렬 루프 — 다른 슬롯의 게이트·`local-dev -Down` 이 끝난 시각과 겹친다), 또는 하네스 스크립트가 프로세스를 이름이나 PID 만으로 끈다 (1.15 이전 산출물) | `local-dev`·게이트 실행기의 정리 코드를 `harness-setup` 7단계 "프로세스는 자기가 띄운 것만, 같은 프로세스인지 확인하고 끈다" 대로 — PID 와 시작 시각을 함께 기록해 끄기 직전에 대조, 자손은 부모보다 늦게 시작한 것만, 이름으로 끄지 않기(Windows 면 잡 오브젝트). 고친 뒤 슬롯 둘에서 게이트를 동시에 돌리며 다른 슬롯의 에이전트가 살아 있는지 본다 | ① (검증 범위는 그대로다. 남의 프로세스를 끄던 결함을 없앤다) | | 게이트 명령이 실제 프로젝트 스크립트와 어긋남 | 게이트 정의서의 명령을 실제와 맞춘다 | ① | | TRD의 도구 결정이 바뀜 | 게이트 명령과 설정 파일 경로. **바꾸는 결정 자체는 TRD가 이미 했어야 한다** | ② | | 요구사항이 없어짐 | 그것만 덮던 흐름·커버 영역을 뺀다. **PRD에 그 변경이 있는지 먼저 확인한다** | ③ | | 태스크가 폐기됨 (`blocked_by: [폐기]`, plan-generator 10단계) 또는 건너뛰어 `failed` 로 남음 | 그 태스크에 걸린 흐름·커버 영역을 `대기` 로 두되 **해소를 기다리는 대기가 아니라는 표시**를 붙인다. 그 태스크만 덮던 것을 빼는 것은 ③이라 승인을 받는다. `[폐기]` 는 OQ·CF 가 아니라 검색해도 하네스 문서에 없다 — 태스크 ID 로 찾는다 | ①~③ | **'낡은 스펙' 이 ①~③ 인 이유가 있다.** 화면이 바뀌어 선택자를 고치는 것은 ①이지만, "그 단계가 없어졌으니 단언도 뺀다"는 ③이다. **무엇이 사라졌는지를 앞 문서에서 확인하지 않으면 구별되지 않는다** — 설계가 바뀐 것인지 구현이 아직 안 된 것인지가 다르고, 뒤쪽이면 그 흐름은 실패한 채로 있는 것이 맞다. ## 4단계. 실패 때문에 부른 갱신은 원인부터 가른다 게이트가 실패해서 이 스킬이 불렸다면, **고치기 전에 넷 중 무엇인지 정한다.** `e2e.md` 에 적힌 셋에 루프(`LOOP.md` 3절)가 더한 **흔들림**을 합친 넷이고, 여기서도 같은 것을 쓴다. | 분류 | 무엇을 고치는가 | 이 스킬의 일인가 | |---|---|---| | **진짜 결함** | 코드 | **아니다.** Layer 5 루프로 돌려보낸다 | | **낡은 스펙** | 흐름 정의·커버 영역 | 그렇다 | | **환경 문제** | `local-dev` 의 준비 판정·대기·구성 요소 | 그렇다 | | **흔들림** | 그 테스트의 안정화와 흔들림 규칙 표 한 줄 (3단계 표의 흔들림 행) | 그렇다. **안정화는 ①이라 무인 자식도 한다** — 흔들림은 e2e 가 있으면 가장 흔한 실패라 매번 사람에게 올리면 루프가 서 있다. **단, 격리 3에서 DB·앱을 쓰는 테스트가 여러 태스크에 걸쳐 흔들리면** 테스트가 아니라 격리 부족이다 — 안정화를 거듭하지 말고 `loop-update`(격리 1·2)로 넘긴다(무인이면 `질문:`) | **분류하지 않고 하네스를 고치기 시작하면 거의 항상 ③ 약화로 끝난다.** 실패를 없애는 가장 빠른 길이 기준을 낮추는 것이기 때문이다. **분류 근거를 보고에 적는다** — 무엇을 보고 그렇게 판단했는지. **같은 흐름·검사 ID의 실패가 LOG.md 에 두 태스크에 걸쳐 쌓여 있으면 그 자체가 신호다.** 매번 코드를 고쳐 넘겼는데 또 실패한다면 게이트 쪽이 잘못되었을 가능성이 높고(흔들리는 선택자, 남은 테스트 데이터, 준비 판정 누락), 그건 코드를 네 번째로 고칠 일이 아니라 하네스를 고칠 일이다. ## 5단계. 고친다 **기존 파일을 부분 수정한다. 통째로 다시 쓰지 않는다.** 하네스 문서에는 그동안 쌓인 것이 들어 있다 — 활성 시점, 커버 영역의 담당 태스크, '하네스 결정' 목록, '하네스 밖' 목록. 다시 쓰면 그게 조용히 사라지고, 사라진 것은 아무도 눈치채지 못한다. 지킬 것 넷. - **게이트는 넷 그대로다.** 다섯 번째를 만들지 않는다. 새 검사는 네 게이트 중 하나 안에 넣는다 - **파일 이름을 바꾸지 않는다.** 게이트 문서 이름은 `verification` 값과 같아야 한다 - **`local-dev` 는 구성 요소 표와 1:1을 유지한다.** 표에 행을 더했으면 스크립트에 블록을 더하고, 스크립트에 넣었으면 표에도 적는다. 둘이 어긋나면 다음 갱신이 무엇을 고쳐야 할지 모른다. `scripts/` 에 있는 `dag-render.*` 는 예외다 — `loop-setup` 이 `loop_setup/DAG.md` 를 만들려고 둔 것이라 구성 요소 표와 무관하고, 지우거나 표에 넣지 않는다 - **새 기록처를 만들지 않는다.** 갱신 이력은 `LOG.md` 한 줄이다. `HARNESS.md` 는 **현재 상태**만 보여준다 ### 갱신을 기록에 남긴다 `LOG.md` 에 한 줄 남긴다. 형식은 그 파일의 기존 형식을 따른다 — **태스크 ID로 시작하는 한 줄**이고, 태스크와 무관한 갱신이면 ID 없이 쓴다. **병렬로 운용 중이면 이 스킬은 코디네이터(orchestration)만 주 트리에서 부른다** — `LOOP.md` 가 병렬에서는 `STATE.md`·`LOG.md`·하네스 문서의 필자를 코디네이터 하나로 정해 두었다. 슬롯 워크트리의 워커가 `HARNESS.md` 를 고치면 병합에서 충돌하므로, 워커는 부족을 발견해도 고치지 않고 `하네스 부족` 으로 돌려보내고 코디네이터가 여기로 온다. 코디네이터가 부른 것이면 `LOG.md` 한 줄도 직접 쓴다. **약화(③)는 이유를 함께 남긴다.** "무엇을 왜 낮췄고 사용자가 무엇에 동의했는지". 이게 없으면 몇 달 뒤에 하네스가 왜 이렇게 헐거운지 아무도 설명하지 못한다. **하네스가 약해진 이력은 하네스 자체보다 중요하다** — 지금 초록불인 게이트가 원래 무엇을 보던 것인지가 거기 있다. ### `CLAUDE.md` · `AGENTS.md` 갱신 하네스를 고친 뒤 프로젝트 루트의 `CLAUDE.md` 와 `AGENTS.md` 를 **같은 내용으로** 갱신한다. 사용자가 직접 연 세션과 루프의 자식 세션은 이 스킬을 읽지 않고 시작하는데, 그때 자동으로 읽히는 파일은 Claude Code 는 `CLAUDE.md`, Codex 는 `AGENTS.md` 뿐이다. 그래서 **지금 어느 호스트에서 돌든 둘 다 쓴다** — 한쪽만 쓰면 다른 호스트의 세션이 규칙을 모른 채 시작한다. 내용은 **어느 세션에서든 반드시 지킬 것**만 두고 세부 규칙은 문서를 가리킨다 — 규칙을 옮겨 적으면 두 곳이 곧 어긋나고, 어긋나면 세션은 짧은 쪽을 믿는다. - 둘 다 없으면 아래 뼈대로 만든다. 있으면 **`## 하네스 (harness_setup/)` 절의 바뀐 값만** 고친다. `현재 단계` 줄은 건드리지 않는다 — 루프 중간의 갱신이지 단계가 바뀐 것이 아니다. 다른 절과 사용자가 손으로 적은 내용은 건드리지 않는다 - 두 파일이 서로 다르면 지금 호스트의 파일을 기준으로 다른 쪽을 맞춘다. 손으로 적은 줄은 양쪽에 남긴다 - 파일은 있는데 `## 문서 지도`·`## 공통 규칙` 이 없으면(예: `/init` 이 만든 파일) 기존 내용 **아래에** 뼈대 두 절을 더한다. 기존 내용은 손대지 않는다 - 절이 없으면 `공통 규칙` 뒤에 제품·기술·구조·플랜·하네스·루프 순서의 자리에 더한다 - 전체 **100줄 안팎, 200줄을 넘기지 않는다.** 넘치면 규칙을 문서 쪽으로 돌려보내고 가리키는 줄만 남긴다 ```markdown # <프로젝트 이름> > harness-loop-fullstack 스킬이 만들고 갱신하는 파일이다. 각 절은 그 레이어의 스킬만 고친다. > `CLAUDE.md` 와 `AGENTS.md` 는 같은 내용이다. 한쪽을 손으로 고치면 다른 쪽도 같이 고친다. > 세부 규칙은 각 문서에 있다 — 여기에는 어느 세션에서든 반드시 지킬 것만 둔다. 200줄을 넘기지 않는다. ## 문서 지도 - `docs/IDEA.md` → `docs/PRD.md` → `docs/TRD.md` → `docs/ARCHITECTURE/` → `plan_setup/` → `harness_setup/` → `loop_setup/LOOP.md` - 뒤 문서는 앞 문서를 근거로 한다. 어긋나면 앞 문서가 이긴다 — 바꿔야 하면 사용자 확인 뒤 **가장 앞 문서부터** 고치고(버전·`변경 이력` 한 줄. 프로젝트 완료 뒤에는 `feature-add`·`refactor-plan` 의 후미 절로) 사이 문서를 맞춘 다음 이어 간다(변경 전파). 손으로 고칠 때도 같다. 루프 세션은 앞 문서를 고치지 않고 LOOP.md 대로 멈춘다 - 현재 단계: <마지막으로 끝낸 스킬> → 다음: <다음 스킬> ## 공통 규칙 - 앞 레이어의 **파일**만 입력이다. 대화 기억이나 추측으로 요구사항·기술 결정을 만들지 않는다 - ID(`FR-`·`NFR-`·`US-`·`OQ-`·`TD-`·`phase-NN-task-MM`)는 참조만 한다. 새 ID 는 마지막 번호 다음으로만 만든다 — OQ 는 프로젝트 전체(`HARNESS.md`·`LOOP.md` 의 미결 표까지)에서, 나머지는 그 문서에서 센다(변경 전파로 앞 문서에 더할 때도) - 사용자가 말하지 않은 값은 `(가정)` 으로 표시한다. 모호하면 추측하지 말고 묻는다 - 요청된 범위를 조용히 넓히거나 줄이지 않는다. 범위 판단은 보고한다 ``` `## 하네스 (harness_setup/)` 절에서 **바뀐 값만** 고친다 — 게이트 명령, `활성 게이트` 줄, local-dev 경로. 문장 규칙(건너뛰기 금지·분리 세션)은 바뀌지 않으므로 손대지 않는다. 절 형식은 `harness-setup` 이 만든 것 그대로다. ```markdown ## 하네스 (harness_setup/) - 로컬 환경: `` 한 명령. 게이트를 돌리기 전에 먼저 - lint `<명령>` · unit `<명령>` · e2e `<명령>` · task_validation 은 분리 세션(`harness_setup/HARNESS.md`) - 게이트를 건너뛰거나 테스트를 약하게 고쳐 통과시키지 않는다. 실패 원인이 하네스 쪽이면 `harness-update` 를 부른다 - 구현한 세션은 task_validation 을 하지 않는다. 검증 세션은 실행 중인 서비스를 관찰해 판정한다. 코드를 근거로 PASS 를 주지 않는다 - 활성 게이트: <지금 활성인 게이트와 다음 활성 시점> ← harness-update 가 갱신 ``` ### 앞 문서 반영 — 하네스만 고치고 끝내지 않는다 갱신하다 보면 하네스가 아니라 앞 문서를 바꿔야 맞는 것이 나온다 — 요구사항이 없어졌는데 PRD 가 그대로다, 도구를 바꿔야 하는데 TRD 가 그대로다, 새 구성 요소(Redis)가 TRD 9.3절에 없다, 수용 기준이 판정 불가능하다. 하네스만 맞추면 하네스와 앞 문서가 다른 말을 하고, 다음 갱신이 같은 어긋남을 또 만난다. **누가 불렀는가**로 갈린다. - **사람이 대화형 세션에서 불렀다**(직접, 또는 `loop-setup`·`plan-generator` 처럼 사람과 대화 중인 스킬이 불렀다) — 결정이면 사용자에게 묻고(선택지 셋 이하. 답을 받기 전에는 반영하지 않는다 — 대화할 수 없는 세션(무인·평가)이면 결정이든 기록이든 다른 레이어 문서는 고치지 않고(그 스킬도 부르지 않고) 지금처럼 보고·CF·미결로 남긴다.) 정해지면 **가장 앞 문서부터** 고친다: 요구사항은 `prd-generator` 8단계, 도구·구성 요소는 `trd-generator` 9단계, 구조는 `architecture-generator` 9단계, 수용 기준은 `plan-generator` 10단계. 부를 수 없으면 그 문서의 부분 갱신 규칙대로 직접(바뀐 항목만, 번호 유지, 추적표, 버전과 `변경 이력` 한 줄). 사이 문서의 바뀐 ID 를 맞춘 뒤 하네스 갱신으로 돌아온다. 단 루프가 도는 중(`Phase Overview` 에 `in_progress`)이면 앞 문서는 고쳐도 `plan-generator` 10단계는 부르지 않고 무엇이 낡았는지 보고한다 — PRD 8단계·TRD 9단계와 같은 판정이다(플랜의 필자는 루프다). 다른 스킬이 불렀으면 `loop-update` 를 이어 부르지 않고 그 스킬로 돌아간다. Layer 6(`feature-add`·`refactor-plan`)이 이 스킬을 부른 중이면 앞 문서는 본문이 아니라 **그 요청의 후미 절**(`## 추가 N`·`## 리팩토링 N`)에 적는다 — 원본 규칙이다. `refactor-plan` 이 부른 중에 PRD 를 바꿔야 하면 그것은 요구 변경이라 반영하지 않고 "`feature-add` 로 다룰 일"로 보고한다(리팩토링은 PRD 를 쓰지 않는다). `bug-fix` 가 부른 중이거나 프로젝트가 완료됐으면(`Phase Overview` 전부 `completed`) 앞 문서는 고치지 않고 어긋난 곳을 보고만 한다 — `bug-fix` 는 문서를 건드리지 않고, 완료 뒤 문서 변경은 `feature-add`·`refactor-plan` 의 후미 절이다. - **루프가 불렀다**(LOOP.md 를 읽고 도는 코디네이터·사이클 세션·무인 자식 — `in_progress` 가 잠시 없는 phase 사이여도 마찬가지) — 앞 문서를 고치지 않는다. 루프 세션은 요구사항을 해석하지 않는다(LOOP.md). 하네스에서 할 수 있는 ①만 하고, 앞 문서 쪽은 `Next Action` 에 `질문: <무엇이> <문서 절>과 어긋난다. <스킬> 로 반영한 뒤 "답: 반영함" 으로 알려 달라` 를 남긴다 — 어느 스킬로 고치면 되는지 적어야 사람이 한 번에 처리한다 ## 6단계. 자체 점검 | 확인할 것 | 통과 조건 | 걸렸을 때 | |---|---|---| | 게이트 개수 | 여전히 넷이고 파일 이름이 `verification` 값과 같음 | 되돌린다 | | 활성 시점 | 인용된 태스크 ID가 전부 실재하고, 뒤로 미뤄진 것이 0건 | 맞춘다. 미룬 것은 ③이다 | | 상태 일관성 | `HARNESS.md` 게이트 표의 활성 여부가 각 게이트 문서와 같음 | 맞춘다 | | 상태 어휘 | `활성`·`부분 활성`·`미활성`·`대기` 넷 밖의 상태를 새로 만든 곳이 0건 | 넷 안으로 맞춘다 | | 대기 해제 | 차단이 풀린 태스크에 걸린 흐름·커버 영역이 `대기` 로 남아 있지 않음 | 활성화한다 | | 판정 수단 | 지금 진행할 태스크의 수용 기준을 판정할 수단이 전부 있음 | 갖춘다. 구현 후에 알면 그 세션을 버린다 | | 커버 영역 | TRD의 필수 커버 영역이 하나도 빠지지 않음 | 되돌린다 | | 실패 경로 | 흐름마다 실패 경로가 여전히 최소 1개 | 되돌린다 | | 구성 요소 1:1 | 구성 요소 표의 행과 `local-dev` 의 블록이 일치 | 맞춘다 | | 스크립트 동작 | 고친 스크립트가 실제로 돌아감 (최소한 상태 확인 경로) | 돌려 본다. 못 돌렸으면 보고에 적는다 | | 흔들림 규칙 | 흔들림을 안정화했다면 그 원인 패턴이 게이트 정의서의 흔들림 규칙 표에 출처와 함께 한 줄 더해졌음. 격리 3의 DB 테스트 반복 흔들림은 안정화 대신 `loop-update` 로 넘겼음 | 더한다. 테스트 하나만 고치면 같은 실수가 다음 스펙에서 반복된다 | | 약화 승인 | ③에 해당하는 변경이 전부 사용자 승인을 받았음 | 되돌리고 묻는다 | | 기록 | 갱신이 `LOG.md` 에 남았고, 약화는 이유가 함께 있음 | 채운다 | | 침범 | 태스크 문서·STATE.md·코드를 이 스킬이 직접 고친 곳이 0건 ("앞 문서 반영"으로 `plan-generator` 10단계를 불러 — 부를 수 없으면 그 규칙대로 — 고친 것은 침범이 아니다) | 되돌린다 | | 형식 | 고친 문서가 기존 절 구성과 표 열을 그대로 유지 | 맞춘다 | | `CLAUDE.md` · `AGENTS.md` | 두 파일이 같고 `## 하네스` 절이 갱신됐고 각 200줄 이하 | 자기 절만 고친다. 넘치면 규칙을 문서로 돌려보내고 가리키는 줄만 남긴다 | **보존이 이 스킬의 점검 항목 절반인 이유가 있다.** 갱신은 늘 "지금 필요한 것"을 보고 하는데, 하네스 문서에 쌓인 것은 **과거에 필요했던 판단**이라 지금 눈에는 군더더기로 보인다. 그걸 지우면 다음에 같은 문제를 처음부터 다시 겪는다. ## 7단계. 보고 1. **무엇을 왜 고쳤는가** — 등급(①②③)과 함께. ①은 묶어서 한 줄씩이면 된다 2. **③ 약화가 있었으면 맨 앞에.** 무엇이 검증 대상에서 빠졌고, 그래서 **지금부터 무엇이 확인되지 않는지** 3. **실패 때문에 불린 경우 분류 결과와 근거** — 진짜 결함이면 고치지 않고 루프로 돌려보냈다는 사실 4. **아직 `미활성`·`대기` 인 것** — 무엇이 언제 풀리는지. 차단 때문이면 어느 OQ·CF 인지 5. **② 범위 확대의 비용** — 매 태스크에 얼마나 붙는가. **하네스가 그 태스크를 검증할 수단을 아예 안 갖고 있었던 경우는 따로 적는다** — 무엇을 왜 놓쳤는지. 같은 종류가 반복되면 `harness-setup` 단계의 판단에 구멍이 있다는 뜻이고, 그건 이 갱신으로 안 고쳐진다 6. **앞 문서 반영** — 이번에 고친 앞 문서의 새 버전과 바뀐 ID, 누가 무엇을 골랐는가. 루프가 불러 고치지 못하고 `질문:` 으로 남긴 것(요구사항이 없어졌는데 PRD 가 그대로, 도구가 바뀌었는데 TRD 가 그대로, 판정 불가능한 수용 기준) **하네스가 실제보다 헐거워졌으면 그 사실이 보고의 맨 앞이다.** 통과율이 올라간 것을 성과로 적지 않는다 — 게이트를 낮춰서 올라간 통과율은 아무것도 뜻하지 않고, 그걸 성과로 읽으면 다음에도 같은 방식으로 올린다. --- ## 자주 만나는 갱신 세 가지 ### 차단이 풀렸을 때 `blocked_by` 에 있던 OQ·CF 가 해소되면 **루프의 차단 해소 절차**(`LOOP.md` 2절)가 태스크 문서를 `pending` 으로 바꾸고 마지막 단계에서 이 스킬을 부른다. 그러면 그 태스크에 걸려 **`대기` 로 빼 두었던 것들**이 한꺼번에 돌아온다 — E2E 흐름, 필수 커버 영역, 그 흐름이 덮던 요구사항. 태스크 문서가 아직 `blocked` 인데 이 스킬이 불렸다면 순서가 틀린 것이다 — 루프 쪽 절차를 먼저 하라고 돌려보낸다. **빠뜨리기 쉽다.** 차단은 태스크 문서에서 풀리는데 `대기` 표시는 하네스 문서에 있어서, 둘을 잇는 것이 사람의 기억밖에 없기 때문이다. `blocked_by` 에 적힌 ID로 하네스 문서를 검색해 **그 ID가 언급된 곳을 전부 찾는 것**이 확실하다. ### 새 의존 서비스가 붙었을 때 캐시나 큐가 추가되면 고칠 곳이 셋이다 — 구성 요소 표에 행 하나, `local-dev` 에 블록 하나, 그리고 그 서비스를 전제로 하는 게이트의 **전제** 절. 세 번째를 빼먹으면 게이트가 실패하는데 원인이 코드에 없어서 루프가 헤맨다. `.env.example` 에 새 키가 필요하면 함께 더한다. **값은 넣지 않는다.** ### 게이트가 계속 실패할 때 먼저 4단계로 분류한다. 그리고 **하네스를 고치는 것이 답이라는 결론이 나와도, 기준을 낮추는 방향인지 확인한다.** 같은 실패를 없애는 방법이 보통 둘 있다 — 흔들림의 원인을 없애는 것(고유 데이터, 인증 재사용, 준비 판정 보강)과 단언을 느슨하게 하는 것. **앞쪽을 먼저 다 해 보고, 그래도 안 되면 그때 사용자에게 묻는다.** --- ## 에이전트별 참고 - Claude Code에서 실행 중이면: `references/claude-code.md` - Codex에서 실행 중이면: `references/codex.md` **형식과 원칙은 `harness-setup` 의 SKILL.md 에 있다.** 게이트 정의서의 절 구성, 구성 요소 표의 열, task_validation 의 분리 원칙과 판정 형식이 필요하면 그쪽을 본다. 여기서 다시 정하지 않는다.