git:20261014.ebba227 to git:20261029.9a25015

77 added, 788 removed. Audit A to A.

---
name: harness-setup
- description: docs/PRD.md·docs/TRD.md·docs/ARCHITECTURE/·plan_setup/(PLAN.md·phase·task) 을 읽고 프로젝트의 검증 하네스를 harness_setup/ 아래에 세운다. quality_gates/ 에 lint·unit·e2e·task_validation 네 게이트의 정의서(무엇을 검사하고 어떤 명령으로 돌며 무엇이 통과인가, 언제부터 활성인가)를 만들고, scripts/ 에 사용자 OS에 맞는 local-dev 스크립트(DB·API·앱 서버·Redis 같은 의존 서비스를 한 명령으로 준비)를 만들며, task_validation 이 구현 세션과 분리된 독립 세션에서 돌도록 절차를 고정한다. 사용자가 "하네스 세팅해줘", "품질 게이트 만들어줘", "테스트 파이프라인 구성해줘", "검증 환경 세팅해줘", "로컬 개발 환경 세팅해줘", "lint/테스트 어떻게 돌릴지 정해줘", "E2E 붙여줘"라고 하거나, 마스터 플랜 검토를 마치고 다음 단계로 넘어갈 때 반드시 이 스킬을 사용한다. 태스크 문서의 verification 이 [lint, unit, e2e, task_validation] 인데 그 게이트를 실제로 무엇으로 돌지가 문서로 없는 상태에서 구현을 시작하거나 개발 루프(loop-setup)를 세우려 할 때도 이 스킬로 먼저 하네스를 고정한다.
- ---
-
- # 하네스 세팅 (Layer 4)
-
- ## 이 스킬의 목적
-
- 하네스는 **"태스크 하나가 끝났다고 말해도 되는 조건"**을 코드가 아니라 문서와 명령으로 고정한 것이다.
- 마스터 플랜의 모든 태스크가 `verification: [lint, unit, e2e, task_validation]` 을 달고 있는데,
- **그 네 이름이 실제로 무엇인지는 아직 아무 데도 없다.** Layer 3이 일부러 비워 두고 여기로 넘겼다.
-
- 여기서 만든 것이 뒤에서 이렇게 쓰인다.
-
- ```
- 태스크 하나 (phase-02-task-03.md, verification: [lint, unit, e2e, task_validation])
- → 루프가 구현을 끝내고 quality_gates/lint.md 의 명령을 돌린다 (Layer 5)
- → unit → e2e 순으로 이어 돌린다
- → task_validation 은 구현한 세션을 닫고 새 세션이 돌린다 ← 이게 이 레이어의 핵심
- → 넷 다 통과해야 completed. 실패하면 수정 후 재실행 (최대 3회, Layer 5)
- ```
-
- 그래서 이 하네스의 품질은 "도구를 잘 골랐는가"가 아니라 세 가지로 판정된다.
-
- 1. **명령 한 줄로 돌고 종료 코드로 성패가 갈리는가** — 사람이 로그를 읽어야 판정되는 게이트는 게이트가 아니다
- 2. **구현한 쪽이 스스로 통과 판정을 내릴 수 없는가** — task_validation 이 분리된 세션에서 도는 이유가 이것이다
- 3. **아직 못 도는 게이트가 '통과'로 보이지 않는가** — 테스트 0건 실행은 초록불이 되고, 초록불은 아무도 다시 안 본다
-
- ### 여기서 하는 일과 하지 않는 일
-
- | 하지 않는 것 | 어디서 하는가 |
- |---|---|
- | 요구사항·기술 결정·설계를 새로 정하거나 바꾸는 것 | PRD·TRD·아키텍처 — 사용자가 정하면 이 세션이 그 문서부터 고치고 이어 간다(10단계 앞의 "앞 문서 반영") |
- | 테스트 러너·브라우저 자동화 같은 **도구 선택** | TRD (`trd-generator`) 의 TD. 하네스는 고르지 않고 **구성**한다 |
- | 태스크·phase 를 나누거나 수용 기준을 고쳐 쓰는 것 | Layer 3 (`plan-generator`) — 판정 불가능한 기준을 만나면 그 10단계로 반영하고 이어 간다 |
- | 태스크 코드를 실제로 구현하는 것 | Layer 5 개발 루프 |
- | 루프의 실행 순서·실패 재시도 3회·워크트리 운용 | Layer 5 (`loop-setup`) |
- | STATE.md·LOG.md 의 갱신 | Layer 5 개발 루프 — 하네스는 **무엇을 기록할지의 형식**만 정한다 |
- | 하네스를 고쳐 나가는 것 | `harness-update` — 게이트와 스크립트는 phase 가 진행되며 계속 바뀐다 |
-
- **도구를 새로 고르지 않는 이유가 있다.** TRD가 TD-0xx 로 고른 도구에는 근거 요구사항과 되돌리기 비용이 붙어 있다.
- 하네스가 여기서 다른 것을 고르면 그 근거가 끊기고, 되돌리기 비용을 아무도 계산하지 않은 결정이 파이프라인 한복판에 들어간다.
- TRD가 정한 것은 그대로 쓴다. **TRD가 침묵한 것 중 되돌리기 비용이 낮은 것**(포매터 규칙, 커버리지 보고 형식 등)은
- 하네스가 정하고 `(하네스 결정)` 이라고 표시한 뒤 보고한다. **비용이 큰 것**(러너 교체, 브라우저 도구 추가)은 사용자에게 묻고, 정해지면 TRD 에 TD 로 먼저 적은 뒤 구성한다("앞 문서 반영").
-
- ### 하네스는 코드가 아직 없는 상태에서 세워진다
-
- 플랜 직후에 서기 때문에, 하네스를 세우는 시점에 `package.json` 도 없고 테스트 파일도 0개인 것이 정상이다.
- 여기서 가장 흔한 사고가 이것이다 — **테스트 0건을 실행하면 대부분의 러너가 성공으로 끝난다.**
- 그러면 lint 도 unit 도 e2e 도 초록불이고, 하네스는 처음부터 아무것도 막지 않는 장식이 된다.
-
- 그래서 게이트마다 **활성 시점**을 적는다. "이 태스크가 완료되면 이 게이트가 실제로 판정을 시작한다"는 태스크 ID다.
- 활성 전에는 **초록불이 아니라 `미활성`** 으로 구별되어야 한다. 미활성 게이트는 통과가 아니라 "아직 판정하지 않음"이고,
- 그 상태로 태스크를 완료시킬지는 루프가 아니라 사람이 정할 일이다.
-
- **플랜에 이미 도구 설정 태스크가 있으면 하네스가 그것을 대신 구현하지 않는다.**
- 플랜의 phase-01 은 보통 프로젝트 뼈대와 타입 검사·마이그레이션 실행기를 세우는 태스크로 시작한다.
- 하네스는 게이트를 **정의**하고 그 태스크를 활성 시점으로 가리킨다. 같은 것을 두 곳에서 만들면 한쪽이 반드시 낡는다.
-
- ---
-
- ## 1단계. 입력 확보와 사전 점검
-
- 다섯 곳을 읽는다. 앞 세 개는 **무엇을 검증해야 하는가**를 주고, 뒤 두 개는 **무엇을 언제 검증할 수 있는가**를 준다.
-
- | 입력 | 여기서 무엇을 꺼내는가 |
- |---|---|
- | `docs/PRD.md` | 유저 스토리와 P0 흐름 → **E2E 흐름 목록의 재료**. NFR 중 관찰로만 확인되는 것 |
- | `docs/TRD.md` | 테스트 절의 도구(TD)와 **필수 커버 영역 표**(TRD 8.1절 "반드시 덮어야 할 영역" — 하네스에서는 이 이름으로 부른다) → unit 게이트가 덮어야 할 목록. 로컬 개발 환경 절의 **구성 요소 표** → local-dev 가 준비할 것 |
- | `docs/ARCHITECTURE/*.md` | 계층 경계와 의존 방향(lint 로 강제할 수 있는 규칙), 각 설계 단위의 검증 기준 |
- | `plan_setup/PLAN.md` | **`Layer 4로 넘기는 검증` 절 — 이것을 위해 여기 왔다.** 태스크 수용 기준으로 담기지 못한 NFR 목록이 여기 있다. phase 구성과 P0 완료 지점 |
- | `plan_setup/phase-*/phase-*-tasks/*.md` | 각 태스크의 `verification`·`files`·`architecture`·`status`·`acceptance_criteria`. 게이트가 실제로 무엇에 걸리는지 |
-
- **PLAN.md 의 `Layer 4로 넘기는 검증` 절을 요약하지 말고 항목 단위로 처리한다.** 그 표의 각 행은
- "태스크 하나로는 판정할 수 없어서 하네스가 받아 가야 하는 요구사항"이고, 여기서 배치하지 않으면 그 요구사항은
- 플랜에서도 빠지고 하네스에서도 빠져 **아무도 검증하지 않는 상태**가 된다. 배치처는 셋 중 하나다 —
- ①어느 게이트가 덮는다 ②phase 완료 시에만 도는 측정으로 둔다 ③하네스로도 못 덮는다(운영 관측 등).
- ③이면 그 사실을 `HARNESS.md` 의 '하네스 밖' 절과 보고에 남긴다. **조용히 빠뜨리는 것과 명시적으로 밖에 두는 것은 다르다.**
-
- **PLAN 7절만 보고 끝내지 않는다.** 그 절은 "태스크로 담기지 못한 것"의 목록이라,
- **태스크 수용 기준 안에 들어간 채로 측정 수단이 필요한 것**은 거기 없다 —
- "질의가 3개이고 응답 P95가 1.5초 이내다", "PDF 생성이 10초 이내다" 같은 기준이다.
- 이런 기준은 태스크에 배분되어 있어서 하네스가 안 봐도 되는 것처럼 보이지만,
- **그것을 재는 수단은 게이트가 갖고 있어야 한다.** 없으면 그 기준은 판정할 수 없고,
- 판정할 수 없는 기준은 통과로 기록되거나 태스크를 세운다.
- 그래서 태스크 수용 기준을 훑을 때 **숫자가 붙은 기준**을 따로 모아, 그것을 잴 수단이 어느 게이트에 있는지 정한다.
-
- **앞 문서가 없으면 하네스를 세우지 않는다.** 특히 플랜이 없으면 게이트가 무엇에 걸리는지, 활성 시점이 언제인지를
- 정할 근거가 없어 "일반적으로 좋은 파이프라인"을 만들게 되고, 그건 이 프로젝트의 어떤 요구사항과도 연결되지 않는다.
- `plan-generator` 를 먼저 제안한다.
-
- ### 사용자 OS 를 먼저 확인한다
-
- `scripts/` 의 로컬 개발 스크립트가 OS 에 직접 걸린다. **지금 세션이 도는 OS를 확인하고 그것을 기본으로 삼는다.**
- TRD가 개발자 OS를 미결(OQ)로 남겨 두었으면 그 사실을 기억해 두었다가, 확인된 값과 다르면 보고에 올린다.
-
- ### blocked 태스크는 검증 대상이 아니다
-
- `status: blocked` 인 태스크는 `blocked_by` 에 OQ·CF ID를 갖고 있고, **아직 정해지지 않은 것 위에 있다.**
- 그 태스크의 수용 기준을 E2E 흐름이나 필수 커버 영역에 넣으면 하네스는 처음부터 실패한 채로 서고,
- 빨간불이 상시화되면 게이트 전체가 무시된다. 차단된 것은 **`대기`** 로 표시해 목록에 남기되 실행 대상에서 뺀다.
- 차단이 풀리면 `harness-update` 가 활성으로 올린다.
- 같은 취급을 받는 것이 둘 더 있다 — Layer 3이 폐기한 태스크(`blocked_by: [폐기]`)와 루프가 3회 소진 뒤 사용자 답으로 건너뛰어 `failed` 로 남긴 태스크.
- 둘 다 그 태스크에 걸린 흐름을 실행 대상에 두면 상시 빨간불이 된다. `harness-update` 가 `대기` 로 빼되 "해소를 기다리는 것이 아니다"라고 표시한다.
-
- ## 2단계. 네 게이트의 공통 규칙
-
- 개별 게이트를 설계하기 전에, 넷 모두에 걸리는 것을 먼저 정한다.
-
- **순서는 lint → unit → e2e → task_validation 이고, 앞이 실패하면 뒤를 돌리지 않는다.**
- 비용이 싼 순서이기도 하지만 더 중요한 이유가 있다 — 타입이 안 맞는 코드로 돌린 E2E 실패는
- 그 흐름이 깨졌다는 뜻이 아니라 그냥 컴파일이 안 된다는 뜻이라, 원인을 찾는 데 시간만 쓴다.
-
- **게이트는 읽기 전용 판정이다.** 자동 수정(`--fix`, 포매터 쓰기, 스냅샷 갱신)을 게이트 안에서 돌리지 않는다.
- 게이트가 코드를 고치면 "무엇이 통과했는가"가 흐려지고, 고친 결과를 다시 자기가 검사해 항상 통과한다.
- 자동 수정은 구현 단계에서 사람이나 루프가 돌리는 것이고, 게이트는 그 결과를 판정만 한다.
-
- **명령 한 줄, 종료 코드로 판정.** 각 게이트 정의서에는 실제로 붙여 넣어 돌릴 수 있는 명령이 정확히 하나 있어야 한다.
- "테스트를 돌린다" 같은 서술은 루프가 매번 다르게 해석하고, 다르게 해석된 게이트는 비교할 수 없다.
-
- **게이트 문서의 이름은 `verification` 값과 같다.** 태스크에 `verification: [lint, unit, e2e, task_validation]` 이 있으면
- `quality_gates/lint.md`·`unit.md`·`e2e.md`·`task_validation.md` 다. 대응표를 두지 않기 위해서다 —
- Layer 3이 태스크 ID를 파일 이름과 같게 만든 것과 같은 이유이고, 표는 게이트가 늘거나 이름이 바뀔 때 낡는다.
-
- ### 상태 어휘는 넷이다
-
- 게이트·E2E 흐름·필수 커버 영역의 상태를 이 넷으로만 적는다. 갱신할 때마다 새 이름을 만들면
- `harness-update` 가 무엇을 무엇으로 올려야 하는지 매번 다시 정하게 된다.
-
- | 상태 | 뜻 |
- |---|---|
- | **활성** | 지금 판정한다. 실패하면 태스크가 완료되지 않는다 |
- | **부분 활성** | 걸린 태스크 중 일부만 완료되어 **그만큼만 판정한다.** 남은 태스크가 무엇인지 함께 적는다 |
- | **미활성** | 활성 시점에 아직 도달하지 않았다. **통과가 아니라 "아직 판정하지 않음"이다** |
- | **대기** | `blocked` 태스크에 걸려 있다. 차단이 풀릴 때까지 실행 대상이 아니다 |
-
- **`부분 활성` 이 필요한 이유가 있다.** 필수 커버 영역 하나가 태스크 여럿에 걸리는 일이 흔하다
- (소유권 판정은 상세·목록·검색·PDF 경로에 각각 걸린다). 그중 둘만 끝난 상태를 `활성` 으로 적으면
- 나머지 경로가 안 덮인 채로 덮였다고 말하게 되고, `미활성` 으로 적으면 이미 도는 검증이 없는 것처럼 보인다.
- **둘 다 사실과 다르고, 사실과 다른 표는 다음 갱신에서 잘못된 판단의 근거가 된다.**
-
- **미활성과 대기를 구별한다.** 미활성은 시간이 지나면 저절로 풀리고, 대기는 사람이 미결을 풀어야 풀린다.
- 같은 이름으로 적으면 "언젠가 되겠지"로 묶여서 아무도 차단을 해소하지 않는다.
-
- **게이트를 늘리지 않는다.** 원본 사양이 정한 4단계가 전부다. 타입 검사·보안 스캔·접근성 검사처럼
- 추가하고 싶은 것은 네 게이트 중 하나 안으로 넣는다(타입 검사는 lint, 계층 위반 검사는 lint 또는 unit).
- 다섯 번째 게이트를 만들면 태스크의 `verification` 기본값과 어긋나고, 그 값을 고치는 것은 Layer 3의 일이다.
- **게이트를 늘리는 편이 낫다고 판단되면 만들지 말고 보고한다.**
-
- ## 3단계. lint 게이트
-
- **무엇을 판정하는가** — 코드가 프로젝트의 형식·타입·구조 규칙을 지켰는가. 동작은 보지 않는다.
-
- 세 가지를 이 게이트에 넣는다.
-
- - **정적 검사와 형식 검사** — 검사만 하고 고치지 않는다
- - **타입 검사** — 있으면 반드시 넣는다. 파일 하나를 고쳤을 때 **다른 파일이 깨지는 것**은 여기서만 잡힌다
- - **아키텍처가 정한 구조 규칙** 중 기계로 검사되는 것 — 계층 간 의존 방향, 금지된 import 경로 등
- - **의존성 취약점 스캔** — 외부 패키지에 알려진 결함이 있는지. 내 코드가 아니라 **남의 코드**를 보는 검사이고,
- 명령 한 줄에 몇 초라 여기 붙일 수 있다. **임계 수준(어느 심각도부터 막을 것인가)은 근거가 있어야 한다** —
- TRD·PRD에 보안 요구사항이 있으면 그것을 근거로 적고, 없으면 하네스가 정한 값임을 `(하네스 결정)` 으로 표시한다
-
- **구조 규칙을 lint 로 강제할 때는 오류 메시지에 고치는 방법을 넣는다.** "이 import는 금지됨"만 나오면
- 루프가 규칙을 우회하는 쪽으로 고치고, 그건 규칙이 막으려던 것을 그대로 통과시킨다.
- 어느 계층을 거쳐야 하는지까지 메시지에 있으면 한 번에 맞게 고친다.
-
- **수정한 파일만이 아니라 프로젝트 전체를 돈다.** 태스크는 파일 하나의 수정이지만 그 영향은 하나가 아니고,
- lint 는 전체를 돌아도 빠르다. 범위를 좁혀 얻는 몇 초는 "내 파일은 깨끗한데 빌드가 안 되는" 상태를 만든다.
-
- ## 4단계. unit 게이트
-
- **무엇을 판정하는가** — 수정된 단위가 의도대로 동작하고, 이미 되던 것이 깨지지 않았는가.
-
- **TRD의 필수 커버 영역 표가 이 게이트의 목표다.** TRD는 도구와 "반드시 덮어야 할 영역"까지만 정해 두었고
- (금액 계산, 소유권 판정, 상태 전이 같은 것들), **각 영역에 실제로 실행되는 테스트가 최소 1건씩 있는지**가
- 이 게이트가 성숙했다는 뜻이다. 그 대응을 `unit.md` 에 표로 남긴다 — 영역 / 근거 요구사항 / 그 영역을 덮는 태스크 / 상태.
-
- **두 묶음을 구분한다.** 그 태스크의 `files` 에 딸린 테스트 파일과, 전체 스위트(회귀)다.
- 전체 스위트가 게이트의 판정 대상이고, 딸린 테스트만 돌려서 통과시키면 다른 곳이 깨진 채로 완료된다.
-
- **커버리지 숫자를 게이트로 두지 않는다.** 숫자를 맞추라고 하면 숫자를 맞추는 테스트가 생기고,
- 그건 정작 틀리면 안 되는 것을 덮지 않는다. 판정 기준은 숫자가 아니라 **필수 커버 영역이 실제로 덮였는가**다.
- 커버리지는 보고 값으로만 남긴다.
-
- **통합 테스트가 실제 DB에 붙는다면 그 전제를 명시한다** — `local-dev` 로 의존 서비스가 떠 있어야 이 게이트가 돈다.
- 전제가 안 적히면 루프는 실패 원인을 코드에서 찾고, 원인은 DB가 안 떠 있는 것이다.
-
- **그리고 공유 DB 를 쓰는지를 `HARNESS.md` 2절에 한 줄로 적는다** — 어느 게이트가 DB 에 붙는지, 그중 **스키마를 바꾸는 테스트**(제약을 떼었다 붙이기, 테이블 생성·삭제)가 있는지.
- 병렬 격리를 정하는 `loop-setup` 이 이 줄을 보고 슬롯별 DB(격리 1·2)를 추천할지 정한다. 실전(ERP, 2026-09)에서 이 사실이 어디에도 안 적혀 격리 3(공유 DB)으로 시작했고,
- 두 슬롯의 unit 이 한 DB 에서 서로의 데이터와 제약을 건드려 여섯 phase 동안 흔들렸다.
-
- ## 5단계. e2e 게이트 — 흐름 목록은 PRD를 읽고 정한다
-
- **무엇을 판정하는가** — 사용자가 브라우저에서 실제로 하는 흐름이 끝까지 동작하는가.
- 단위 테스트가 전부 통과해도 화면과 서버를 이어 붙이면 안 되는 일이 흔하고, 그것만 잡는 게이트다.
-
- **흐름 목록을 정하는 것이 이 단계의 실제 작업이다.** TRD는 도구만 정했고, 어떤 흐름을 덮을지는 여기로 넘어왔다.
- PRD의 유저 스토리와 P0 요구사항에서 뽑는다. 흐름 하나마다 이렇게 적는다.
-
- | 항목 | 왜 필요한가 |
- |---|---|
- | 흐름 ID (`FLOW-01`) 와 이름 | 태스크·보고·실패 기록이 같은 문자열로 이 흐름을 가리킨다 |
- | 단계 (사용자가 하는 행동 순서) | 재현 가능해야 다른 세션이 같은 것을 돌린다 |
- | 덮는 요구사항 (FR·NFR) | 이 흐름이 없어지면 무엇이 안 덮이는지가 보인다 |
- | **실패 경로 최소 1개** | 성공 경로만 도는 E2E는 절반만 검증한다. 권한 없는 접근, 잘못된 입력, 중복 요청 |
- | 활성 phase | 이 흐름을 구성하는 마지막 태스크가 완료되는 phase. 그전에는 `대기` |
-
- **흐름을 많이 만들지 않는다.** E2E는 태스크마다 회귀로 도는 가장 느린 게이트다.
- 느려지면 사람이 게이트를 끄고, 꺼진 게이트는 없는 것과 같다. **P0 흐름을 먼저 채우고 기능 전수 나열은 하지 않는다** —
- 기능 단위 검증은 unit 의 일이고, E2E 는 "여러 태스크가 이어 붙어야 비로소 성립하는 것"만 본다.
-
- **두 묶음으로 나눈다.**
-
- - **매 태스크 묶음** — 활성화된 흐름 중 빠른 것. 이게 게이트의 기본 판정 대상이다
- - **phase 완료 묶음** — 시간이 걸리는 측정(전 구간 소요 시간, 브라우저 여러 종, 대량 데이터)은 여기 둔다.
- Layer 3이 phase 완료를 자동 판정 단위로 만들어 두었으므로 걸어 둘 자리가 있다.
- **돌리는 명령은 `e2e.md` 의 `## phase 완료 묶음 명령` 절 하나에 둔다** — 루프의 구동 스크립트가 그 절을 그때그때 읽어 판단 없이 돌린다(판단 자식은 허용 목록·10분 상한 때문에 못 돌린다).
- 그래서 그 절에는 지금 활성인 묶음 흐름만 도는 명령이 있어야 하고, 흐름이 활성되거나 `대기` 로 빠지면 `harness-update` 가 그 명령(또는 그것이 읽는 설정)을 함께 고친다
-
- **아직 화면이 없는 phase 에서도 이 게이트를 빼지 않는다** — 그때 e2e 는 회귀로 돈다.
- 활성 흐름이 0개면 그 사실이 `미활성` 으로 드러나야 하고, 조용한 통과가 되면 안 된다.
-
- ### 흔들리는 E2E 는 꺼진 게이트와 같다
-
- 같은 코드로 두 번 돌렸는데 한 번은 통과하고 한 번은 실패하면, 사람은 곧 **실패를 무시하기 시작한다.**
- 느려서 꺼지는 것과 결과가 같고, 오히려 나쁘다 — 꺼진 게이트는 꺼진 줄이라도 안다.
- 그래서 흔들림을 줄이는 규칙을 `e2e.md` 에 함께 적는다.
-
- - **선택자는 역할과 이름으로 잡는다** — 사용자에게 보이는 것(버튼 이름, 라벨)으로 찾고,
- 그것으로 안 되는 곳에만 최소한의 표식을 붙인다. 화면 구조를 따라가는 선택자는 관계없는 수정에서 깨진다
- - **실행마다 고유한 테스트 데이터를 만든다** — 고정된 이메일·번호를 쓰면 두 번째 실행이 첫 번째가 남긴 것과 부딪힌다.
- 유일 제약이 걸린 프로젝트에서는 특히 그렇다
- - **인증 상태는 한 번 만들어 재사용한다** — 흐름마다 로그인을 다시 하면 그만큼 느려지고,
- 로그인 화면이 바뀔 때마다 모든 흐름이 함께 깨진다. 인증 흐름 자체를 검증하는 흐름 하나만 진짜로 로그인한다
- - **외부에서 와야 하는 값은 실제 경로에서 가져온다** — 메일로 오는 재설정 링크 같은 것은
- 로컬 메일 캡처에서 읽는다. 값을 하드코딩하면 그 흐름은 실제로 무엇도 검증하지 않는다
- - **값은 한 번 읽어 비교하지 않고, 조건이 될 때까지 기다리는 단언으로 본다** — 화면은 비동기로 바뀐다. 한 번 읽은 값이 맞는지는 읽은 순간의 운이다
- - **크기·위치·색 같은 측정은 전환·로드가 끝난 뒤에 한다** — 애니메이션 중간이나 이미지가 뜨기 전에 잰 값은 매번 다르다
- - **목록에서 특정 행을 찾을 때 순서나 첫 페이지를 가정하지 않는다** — 다른 흐름이 만든 데이터가 섞이면 자리가 바뀐다. 검색·필터로 좁혀 찾는다
-
- **이 목록은 세팅 때로 끝나지 않고 자란다.** 흔들림을 안정화할 때마다 `harness-update` 가 그 원인 패턴을 `e2e.md`(unit 이면 `unit.md`)의 흔들림 규칙 표에 한 줄 더한다.
- 그러지 않으면 같은 실수를 다음 태스크의 테스트가 반복하고, 루프는 그 규칙을 워커 지시문에 매번 손으로 적게 된다. 스펙을 쓰는 세션은 이 표를 먼저 읽는다.
-
- ### 단언은 세 층으로 한다
-
- 화면만 보면 **"성공 메시지는 떴는데 저장은 안 된"** 상태를 통과시킨다. 흐름마다 세 층을 확인한다.
-
- 1. **화면 상태** — 사용자가 보는 것 (메시지, 이동한 화면, 목록에 나타남)
- 2. **서버 상태** — 실제로 남았는가 (DB 행, 발급된 토큰, 기록된 이력)
- 3. **사용자에게 보이는 결과** — 다시 조회했을 때·다른 계정에서 볼 때 기대대로인가
-
- ### 실패는 고치기 전에 분류한다
-
- E2E 실패를 없애는 가장 빠른 길은 **단언을 느슨하게 만드는 것**이고, 그렇게 하면 게이트가 조용히 죽는다.
- 그래서 실패를 만나면 먼저 셋 중 무엇인지 정한다.
-
- | 분류 | 어떻게 알아보는가 | 무엇을 고치는가 |
- |---|---|---|
- | **진짜 결함** | 수동으로 같은 순서를 밟아도 같은 결과 | 코드. 태스크 수정으로 돌아간다 |
- | **낡은 스펙** | 화면이나 규약이 의도적으로 바뀌었고 흐름이 옛 모습을 기대한다 | 흐름 정의. `harness-update` 의 일이다 |
- | **환경 문제** | 의존 서비스가 안 떴거나 데이터가 남아 있다 | `local-dev` 로 환경을 다시 준비한다. 코드도 스펙도 고치지 않는다 |
-
- **분류 결과를 기록에 남긴다.** Layer 5가 실패를 3회까지만 재시도하는데, 세 번이 전부 환경 문제였다면
- 그건 구현이 잘못된 것이 아니라 하네스가 잘못된 것이고, 그 구별이 기록에 없으면 아무도 하네스를 고치지 않는다.
-
- ## 6단계. task_validation 게이트 — 분리된 세션에서 돈다
-
- **이 게이트가 하네스의 핵심이고, 나머지 셋과 성격이 다르다.** 앞의 셋은 도구가 판정하지만 이것은 에이전트가 판정한다.
- 그리고 **그 에이전트는 그 태스크를 구현한 세션이 아니어야 한다.**
-
- ### 왜 분리하는가
-
- 구현한 세션은 자기가 무엇을 **의도했는지** 알고 있다. 그래서 화면이 실제로 그렇게 동작하지 않아도 그렇게 보이고,
- 수용 기준의 애매한 문장을 자기 구현에 맞는 쪽으로 읽는다. 나쁜 의도가 아니라 **정보의 문제**다 —
- 구현 중에 형성된 판단이 관찰을 덮어쓴다.
-
- 그래서 코드 작성과 검증을 의도적으로 떼어 놓는다. 검증자에게는 **태스크 문서에 적힌 문장이 정보의 전부**이고,
- 그건 사용자가 나중에 그 기능을 처음 써 볼 때와 같은 조건이다.
-
- ### 검증자에게 주는 것과 주지 않는 것
-
- | 준다 | 주지 않는다 |
- |---|---|
- | 태스크 문서 경로 (`plan_setup/phase-NN/phase-NN-tasks/phase-NN-task-MM.md`) | 구현 세션의 대화·요약·"무엇을 했는지" 설명 |
- | 실행 중인 애플리케이션 (`local-dev` 로 띄운 것) | 구현자가 작성한 통과 보고나 자체 점검 결과 |
- | `quality_gates/task_validation.md` (절차와 판정 형식) | "이 부분은 이렇게 동작하도록 만들었다" 류의 안내 |
- | 필요하면 `architecture` 가 가리키는 설계 절 | 앞 게이트가 통과했다는 사실 이상의 실행 로그 |
-
- **주지 않는 목록이 이 게이트의 실질이다.** 하나라도 새면 분리한 의미가 없어진다.
- "참고하라고 요약만 붙이는 것"이 가장 흔한 누수인데, 그 요약이 바로 구현 중에 형성된 판단이다.
-
- ### 어떻게 판정하는가
-
- **기본은 실행 관찰이다.** 애플리케이션을 실제로 조작해서 수용 기준 한 줄마다 결과를 본다.
- **코드를 읽는 것은 수용 기준이 코드를 대상으로 할 때만** 한다 — "이 모듈 밖에 해당 테이블 INSERT가 0건이다" 같은 기준은
- 관찰로 판정되지 않으므로 검색이 정당한 수단이다. 그 외에는 코드를 근거로 PASS를 주지 않는다.
- **코드가 그렇게 쓰여 있는 것과 그렇게 동작하는 것은 다르고**, 그 차이를 잡으라고 이 게이트가 있다.
-
- ### 무엇으로 관찰하는가
-
- 원본 사양이 이 게이트를 **"서비스(브라우저)를 통해 해당 기능을 검증"**하는 것으로 정했다. 그러니 관찰 수단의 기본은 브라우저이고,
- **검증자가 무엇으로 브라우저를 조작하는지가 정해져 있지 않으면 이 게이트는 코드 읽기로 조용히 내려앉는다** — 그게 이 게이트가 막으려던 것이다.
- 하네스를 세울 때 이 환경에 어떤 수단이 있는지 확인해 `HARNESS.md` 에 적고, `task_validation.md` 에 그 순서를 둔다.
-
- | 순위 | 수단 | 언제 |
- |---|---|---|
- | ① | 에이전트가 직접 조작하는 브라우저 도구 (브라우저 자동화 MCP 등) | 화면이 있는 기준 전부. **기본** |
- | ② | e2e 게이트의 브라우저 자동화 도구로 검증자가 **그 자리에서 일회성 스크립트**를 써서 실행 | ①이 없을 때. 스크립트는 저장소에 남기지 않고, 제품 코드를 건드리지 않는다 |
- | ③ | HTTP 호출·DB 질의 | 화면이 없는 기준 (API 응답, 저장된 값, 권한 오류) |
-
- ②로 내려간 판정은 **판정 블록에 그 사실을 적는다**(`관찰 수단: 2`). 조용히 내려가면 ①로 본 것과 구별되지 않는다.
- ①도 ②도 없으면 화면 기준은 `판정 불가` 이고 그 사실을 사용자에게 알린다 — 코드를 읽고 PASS 를 주는 것보다 낫다.
- ③은 강등이 아니다. 기준이 화면을 말하지 않으면 HTTP·DB 가 곧 관찰이다.
-
- 판정의 규칙 넷.
-
- - **수용 기준 한 줄마다 PASS / FAIL 과 관찰한 증거를 적는다.** 무엇을 했고 무엇이 보였는지.
- **증거 없는 PASS 는 PASS 가 아니다** — 그건 구현 문서를 읽고 동의한 것에 불과하다
- - **검증자는 고치지 않는다.** 고치는 순간 검증자가 구현자가 되고, 다음 기준부터는 자기 코드를 검증한다.
- 실패는 실패로 돌려보내고 수정은 구현 쪽에서 한다
- - **원인 분석이나 수정 제안을 하지 않는다.** 관찰한 것만 적는다. 원인 추정이 붙으면 구현 세션이 그것을 따라가고,
- 틀린 추정이면 엉뚱한 곳을 고친 뒤 다시 실패한다
- - **판정할 수 없으면 PASS 도 FAIL 도 아니다.** 기준이 관찰 가능하게 쓰이지 않았으면 `판정 불가` 로 돌려보낸다.
- 그건 구현의 문제가 아니라 태스크 문서의 문제이고, Layer 3으로 돌아갈 일이다.
- **환경 때문에 관찰 자체를 못 했으면 `환경 문제`** 로 적는다 — 넷째 값이 있는 이유는 루프가 그것을 FAIL 로 읽어 코드를 고치지 않게 하기 위해서다
- - **판정 블록은 `END` 줄로 끝낸다.** 루프는 이 줄로 잘린 파일을 알아본다
-
- ### 결과를 어디에 남기는가
-
- **새 상태 파일도 증거 디렉터리도 만들지 않는다.** 진행 상태는 `STATE.md`, 이력은 `LOG.md` 가 이미 갖고 있고
- (Layer 3이 만들고 Layer 5가 갱신한다), 세 번째 기록처가 생기면 셋이 서로 어긋난다.
-
- 검증자는 판정 블록을 출력하고, 루프가 그것을 두 곳으로 옮긴다.
-
- - `STATE.md` 의 `Last Verification` — **지금 상태 한 줄.** 태스크 / 결과 / 실패면 사유
- - `LOG.md` — **관찰한 증거가 여기 남는다.** 실패한 기준 번호와 "무엇을 했더니 무엇이 보였는가".
- 통과는 한 줄이면 되지만 **실패는 관찰을 남긴다** — 그게 없으면 다음 세션이 같은 것을 처음부터 다시 확인한다
-
- `task_validation.md` 는 **그 블록의 형식**만 고정한다. 증거가 텍스트로 남는다는 뜻이므로,
- 관찰을 "정상 동작함" 대신 **재현 가능한 문장**으로 쓰게 한다 — "계정 B로 A의 인보이스 URL을 열었더니 200과 본문이 보였다".
-
- ## 7단계. scripts/ — 로컬 개발 환경
-
- **무엇을 하는 스크립트인가** — 명령 하나로 개발에 필요한 것이 전부 준비된 상태를 만든다.
- DB·메일 캡처·캐시 같은 의존 서비스를 띄우고, 스키마와 시드를 적용하고, 앱 개발 서버를 실행한다.
-
- 이게 하네스의 일부인 이유는 **unit 의 통합 테스트와 e2e·task_validation 이 전부 실행 중인 환경을 전제하기 때문**이다.
- 환경을 띄우는 방법이 사람 머릿속에만 있으면 분리된 검증 세션이 그것을 재현하지 못한다.
-
- ### 파일 이름과 OS
-
- **원본 사양이 적은 이름을 그대로 쓴다** — `local-dev.sh`, `local-dev.bat` 또는 `local-dev.ps1`.
- **사용자 OS 를 우선한다**: Windows 면 `local-dev.ps1` 을, macOS·Linux 면 `local-dev.sh` 를 주 스크립트로 만든다.
-
- **기본은 한 OS 용 하나다.** 두 벌을 만들면 한쪽만 갱신되어 갈라지고, 갈라진 스크립트는 "내 컴퓨터에서는 되는데"를
- 그대로 재현한다. 다른 OS 가 실제로 필요해지면 그때 `harness-update` 가 추가한다.
-
- ### 환경이 바뀌어도 알아서 준비되게 하는 방법
-
- DB만 있던 환경에 캐시가 붙고 API 서버가 하나 더 생기는 일은 phase 가 진행되며 반드시 일어난다.
- 그때마다 스크립트를 다시 쓰지 않으려면, **구성 요소 목록이 단일 출처이고 스크립트가 그것을 그대로 반영**해야 한다.
-
- - **구성 요소 표를 `HARNESS.md` 에 둔다** — 이름 / 형태(컨테이너·호스트 실행·로컬 설치) / 준비 여부를 판정하는 명령 /
- 왜 필요한가. TRD 9.3절의 표(구성 요소 / 왜 필요한가 / 근거 결정)를 **그대로 받아** 형태와 준비 판정 열을 더한다.
- TRD에 없는 구성 요소를 여기서 조용히 넣으면 그건 설계 유발 충돌이다 — 사용자에게 묻고, 넣기로 하면 TRD 9.3절과 근거 TD 부터 고친 뒤 표에 넣는다("앞 문서 반영")
- - **스크립트는 표의 한 행을 같은 모양의 블록 하나로 구현한다** — 이미 준비됐는지 확인 → 없으면 준비 →
- **준비될 때까지 대기** → 실패하면 무엇을 해야 하는지 출력. 새 구성 요소는 블록 하나 추가로 끝난다
- - **필요한 것의 목록을 스크립트 여기저기에 흩뿌리지 않는다.** 상단에 모아 두면 표와 대조할 수 있고,
- 대조할 수 있어야 `harness-update` 가 무엇을 고칠지 안다
- - **포트·계정·경로를 코드에 박지 않는다.** 환경변수 파일에서 읽는다. 값이 바뀌었을 때 고칠 곳이 한 군데여야 한다
-
- ### 스크립트가 지켜야 할 것
-
- - **아직 없는 단계는 실패가 아니라 건너뜀이다** — 코드가 0줄인 phase 에서는 스키마도 앱 진입점도 없다. 그때 스키마·시드 단계와 앱 실행 단계는
- "대상 없음 — 건너뜀"을 출력하고 **0으로** 끝난다(그 단계의 활성 시점 태스크가 완료되기 전에는 판정 대상이 아니다 — 게이트의 `미활성` 과 같은 논리).
- 안 그러면 첫 태스크부터 `local-dev` 가 비0으로 끝나고, 루프는 "0이 아니면 환경 문제"로 읽어 멀쩡한 태스크를 재준비와 `harness-update` 로 몰고 간다.
- 어느 태스크부터 각 단계가 판정 대상이 되는지를 `HARNESS.md` 2절 종료 코드 표에 함께 적는다
- - **띄운 것은 세션이 끝나도 살아 있다** — 의존 서비스는 물론 앱 개발 서버도 **스크립트를 부른 셸과 분리된 프로세스**로 띄운다.
- 루프는 구현 세션이 닫힌 뒤 다른 세션이 검증하므로, 서버가 부른 셸에 매여 있으면 검증 세션이 열릴 때마다 앱이 죽어 있고
- 그 실패는 전부 '환경 문제'로 찍힌다. 포그라운드로 붙잡고 있는 스크립트는 헤드리스 세션을 영원히 안 끝나게도 만든다
- - **멱등하다** — 두 번 돌려도 안전하다. 이미 떠 있는 것을 다시 띄우지 않는다.
- 루프는 세션마다 이 명령을 돌리므로, 두 번째 실행이 환경을 망가뜨리면 두 번째 태스크부터 막힌다
- - **준비될 때까지 기다린다** — 포트가 열린 것과 쓸 수 있는 것은 다르다. DB는 실제 질의 한 번,
- 앱 서버는 응답 한 번으로 판정한다. 여기를 건너뛰면 첫 테스트만 이유 없이 실패하는 증상이 상시화된다
- - **전제부터 확인하고 멈춘다** — 필요한 런타임·컨테이너 엔진이 없으면 절반쯤 진행하지 말고
- **무엇이 없는지와 어떻게 설치하는지**를 출력하고 종료한다
- - **어디서 멈췄는지 말한다** — 실패한 단계 이름과 다음 행동을 출력하고, **종료 코드를 단계마다 다르게** 둔다.
- 성공 0, 그리고 전제 / 환경변수 / 의존 서비스 / 스키마·시드 / 앱 실행의 다섯 단계에 서로 다른 0이 아닌 값.
- 이 표를 `HARNESS.md` 2절에 적는다. **루프와 분리된 검증 세션은 이 숫자만 보고 실패를 가른다** —
- 0이 아니면 그 자체로 환경 문제이고, 전제·환경변수 계열은 사람이 설치·설정할 일이라 사용자에게,
- 의존 서비스·스키마·앱 계열은 재준비 후 `harness-update` 로 간다. 코드가 하나뿐이면 이 갈림이 불가능해
- 출력을 읽고 추측하게 되고, 추측은 세션마다 다르다
- - **초기화 수단을 둔다** — `-Reset` / `--reset` 으로 데이터를 지우고 처음부터. 검증이 남긴 데이터가
- 다음 검증의 결과를 바꾸는 것을 막는다
- - **띄우는 것 말고도 세 가지를 할 수 있어야 한다** — 지금 무엇이 떠 있는지(`-Status` / `--status`),
- 안 뜬 것의 로그(`-Logs` / `--logs`), 내리기(`-Down` / `--down`).
- **이게 없으면 실패 분류가 불가능하다.** 분리된 검증 세션은 구현 과정을 모르는 채로 들어오는데,
- 화면이 안 뜰 때 코드가 깨진 것인지 의존 서비스가 죽은 것인지 구별할 수단이 없으면 **환경 문제를 구현 실패로 보고**하고,
- 루프는 멀쩡한 코드를 세 번 고치다 멈춘다. 원본 사양의 재시도 3회가 엉뚱한 이유로 소모되는 것이다.
- 준비 판정은 이미 스크립트 안에 있으므로 `-Status` 는 그 판정을 **띄우지 않고 출력만** 하면 된다
- - **비밀값을 파일에 적지 않는다** — 예시 환경변수 파일에는 키 이름과 형식만 두고, 실제 값은 두지 않는다
- - **프로세스는 자기가 띄운 것만, 같은 프로세스인지 확인하고 끈다** — `local-dev -Down`·게이트 실행기가 남은 서버·드라이버를 정리할 때 이 PC 에는 다른 슬롯의 워커 에이전트(claude·codex — codex 는 `node` 로 뜬다)와 사람의 프로그램이 함께 돈다.
- **이름으로 끄지 않고**(`node`·`cargo` 를 이름으로 끄면 워커가 죽는다), **PID 하나만 믿지도 않는다** — Windows 는 끝난 프로세스의 PID 를 곧바로 다른 프로세스에 다시 준다.
- 끌 PID 는 기록할 때 그 프로세스의 **시작 시각을 함께 적고**, 끄기 직전에 시작 시각이 같을 때만 끈다. 자손을 부모 PID 로 모을 때는 **자식의 시작 시각이 부모보다 늦은 것만** 자손으로 친다(부모가 끝나도 자식의 부모 PID 칸은 그대로 남아,
- 그 번호를 다시 받은 프로세스의 "자식" 처럼 보인다). Windows 에서 가장 확실한 길은 잡 오브젝트(작업 개체)로 띄워 그 잡째 끄는 것이다.
- 실전(2026-10-13, 동시 3): 게이트 실행기가 `cargo test --workspace` 의 자손 PID 를 15분 동안 모아 두었다가 끝날 때 전부 껐는데, 그사이 끝난 자손의 PID 를 다시 받은 다른 슬롯의 codex 검증 워커가
- 함께 꺼졌다 — 오류 기록 없이 사라졌고 1시간 반 동안 아무도 몰랐다. 병렬 루프에서 워커를 죽이는 가장 흔한 길은 에이전트가 아니라 하네스 스크립트다
-
- **여러 에이전트가 동시에 도는 경우는 여기서 정하지 않는다.** Layer 5가 멀티 에이전트로 갈 때
- 워크트리 둘이 같은 DB와 같은 포트를 쓰게 되는데, 그것을 격리로 풀지 권한으로 풀지는
- 루프의 운용 방식이 정해진 뒤에야 답이 나온다. 하네스가 지금 할 일은 **포트·DB 이름을 환경변수에서 읽게 해 두는 것**과 **그 환경변수 이름과 공유 DB 사용 여부를 `HARNESS.md` 2절에 적는 것**이고
- (값을 스크립트에 박지 않는 이유가 여기에도 있다), 실제 격리 방식은 `loop-setup` 으로 넘긴다. 격리 1·2로 정해지면 슬롯 번호에서 DB 이름·포트를 파생하는 분기는 `harness-update` 가 더한다.
-
- ## 8단계. 파일 쓰기
-
- `harness_setup/` 아래에 만든다. **원본 사양이 그린 구조가 `quality_gates/` 와 `scripts/` 이고, 그 이름을 그대로 쓴다.**
-
- ```
- project_name/harness_setup/
- ├── HARNESS.md
- ├── quality_gates/
- │ ├── lint.md
- │ ├── unit.md
- │ ├── e2e.md
- │ └── task_validation.md
- └── scripts/
- ├── local-dev.ps1 (사용자 OS 가 Windows 인 경우. 그 외에는 local-dev.sh)
- └── .env.example (필요한 키 이름과 형식만. 값은 두지 않는다)
- ```
-
- **도구 설정 파일은 도구가 요구하는 자리에 둔다.** 대부분의 린터·테스트 러너·브라우저 자동화 도구는
- 프로젝트 루트의 정해진 이름을 읽으므로, 그 파일들을 `quality_gates/` 로 옮기면 도구가 찾지 못한다.
- 게이트 정의서가 **그 설정 파일의 경로를 가리키는 것**으로 연결한다. `quality_gates/` 에 들어가는 것은
- **게이트의 정의**(무엇을 검사하고 어떤 명령이며 무엇이 통과인가)이고, 흐름 목록처럼 도구가 위치를 강제하지 않는 것은
- `quality_gates/` 아래 하위 폴더에 둘 수 있다.
-
- **이미 `harness_setup/` 이 있으면 덮어쓰기 전에 읽는다.** phase 가 진행되며 `harness-update` 가 갱신해 둔 것이라면
- 활성 시점·커버 영역·흐름 목록의 진행 상태가 거기 쌓여 있고, 초기값으로 덮으면 그게 사라진다.
- 그럴 때는 이 스킬이 아니라 `harness-update` 를 쓴다.
-
- ### `CLAUDE.md` · `AGENTS.md` 갱신
-
- `harness_setup/` 을 쓴 뒤 프로젝트 루트의 `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/)` 절에는 이것만 적는다. 명령은 정의서의 것을 그대로 옮긴다 — 여기 적힌 명령이 실제와 다르면 세션은 이쪽을 돌린다.
-
- ```markdown
- ## 하네스 (harness_setup/)
- - 로컬 환경: `<local-dev 경로>` 한 명령. 게이트를 돌리기 전에 먼저
- - lint `<명령>` · unit `<명령>` · e2e `<명령>` · task_validation 은 분리 세션(`harness_setup/HARNESS.md`)
- - 게이트를 건너뛰거나 테스트를 약하게 고쳐 통과시키지 않는다. 실패 원인이 하네스 쪽이면 `harness-update` 를 부른다
- - 구현한 세션은 task_validation 을 하지 않는다. 검증 세션은 실행 중인 서비스를 관찰해 판정한다. 코드를 근거로 PASS 를 주지 않는다
- - 활성 게이트: <예: lint·unit 활성, e2e·task_validation 은 phase-02-task-01 부터> ← harness-update 가 갱신
- ```
-
- ## 9단계. 자체 점검
-
- | 확인할 것 | 통과 조건 | 걸렸을 때 |
- |---|---|---|
- | 구조 | `harness_setup/quality_gates/` 와 `harness_setup/scripts/` 가 원본 이름 그대로 존재 | 맞춘다 |
- | 게이트 파일 이름 | `verification` 의 네 값과 정확히 같은 이름의 문서 4개 | 맞춘다. 대응표를 만들지 않는다 |
- | 실행 가능성 | 게이트마다 붙여 넣어 돌릴 수 있는 명령이 정확히 하나 | 채운다. 서술만 있으면 매번 다르게 해석된다 |
- | 판정 방법 | 게이트마다 종료 코드로 성패가 갈림 | 고친다. 로그를 읽어야 알면 게이트가 아니다 |
- | 활성 시점 | 게이트마다 활성 시점이 **실제 태스크 ID** 로 적혀 있음 | 채운다. 없는 ID를 지어내지 않는다 |
- | 미활성 구분 | 활성 전 상태가 통과가 아니라 `미활성` 로 표시됨 | 고친다. 0건 실행은 초록불이 된다 |
- | 상태 어휘 | 상태가 `활성`·`부분 활성`·`미활성`·`대기` 넷 안에서만 쓰임 | 맞춘다. 새 이름을 만들면 갱신이 매번 재정의한다 |
- | 자동 수정 | 게이트 명령에 자동 수정이 0건 | 뺀다. 게이트는 판정만 한다 |
- | 필수 커버 영역 | TRD의 모든 필수 커버 영역이 unit 게이트 표에 있고 담당 태스크가 붙음 | 채운다 |
- | E2E 흐름 | 흐름마다 실패 경로가 최소 1개, 덮는 요구사항과 활성 phase 가 적혀 있음 | 채운다 |
- | phase 완료 묶음 명령 | `e2e.md` 에 `## phase 완료 묶음 명령` 절이 있고, 코드 블록이 한 줄 = 명령 하나이며 활성인 묶음 흐름만 돌고(`대기` 흐름 제외), 활성 시점 전이면 비어 있음 | 채운다. 루프의 구동 스크립트가 이 절을 읽어 돌린다 — 없거나 흐름 ID 만 있으면 phase 가 완료로 찍히지 않는다 |
- | E2E 흔들림 | 선택자·테스트 데이터·인증 재사용·기다리는 단언·안정 뒤 측정·순서 비가정 규칙이 `e2e.md` 에 **출처 열이 있는 표**로 있고, 실패 분류 기준이 있음 | 채운다. 흔들리는 게이트는 꺼진 게이트다. 표여야 `harness-update` 가 한 줄씩 더한다 |
- | 공유 DB 표시 | `HARNESS.md` 2절에 공유 DB 를 쓰는 게이트(스키마를 바꾸는 테스트 유무)와 슬롯 변수 이름이 있음 | 적는다. `loop-setup` 이 격리 추천의 근거로 읽는다 |
- | 단언 층 | 흐름의 확인이 화면 상태에서 끝나지 않고 서버 상태까지 감 | 채운다 |
- | 증거 | 판정 형식이 기준마다 관찰한 증거를 요구하고, 실패 시 그것이 LOG.md 로 감 | 채운다. 증거 디렉터리를 새로 만들지 않는다 |
- | 차단 취급 | `status: blocked` 태스크의 기준이 실행 대상에 0건 | 뺀다. 상시 빨간불은 게이트를 무력화한다 |
- | PLAN 7절 | `Layer 4로 넘기는 검증` 의 모든 항목이 게이트·phase 측정·'하네스 밖' 중 하나로 배치됨 | 배치한다. 못 덮으면 밖에 두고 보고한다 |
- | 측정 수단 | 숫자가 붙은 수용 기준마다 그것을 잴 수단이 어느 게이트에 있는지 정해짐 | 정한다. 못 재면 '하네스 밖'에 두고 보고한다 |
- | 분리 세션 | `task_validation.md` 에 검증자에게 **주지 않는 것** 목록이 있음 | 채운다. 이게 없으면 분리가 말뿐이다 |
- | 관찰 수단 | 이 환경의 브라우저 조작 도구를 확인해 `HARNESS.md` 에 적었고, `task_validation.md` 에 수단 순서와 강등 표기 규칙이 있음 | 채운다. 없으면 검증이 코드 읽기로 내려앉는다 |
- | 근거 없는 기준 | 통과선을 세운 검사마다 근거 요구사항이 있거나 `(하네스 결정)` 표시가 있음 | 표시하거나 뺀다. 합의된 적 없는 관문은 세우지 않는다 |
- | 도구 침범 | TRD가 정한 도구를 바꾼 곳이 0건 | 되돌린다. 바꾸고 싶으면 TRD로 보고한다 |
- | 게이트 개수 | 네 게이트 외에 새로 만든 게이트가 0건 | 안으로 넣거나 보고한다 |
- | local-dev OS | 주 스크립트가 확인된 사용자 OS 용이고 확장자가 원본 이름과 같음 | 맞춘다 |
- | 멱등성 | 두 번 실행해도 안전하다는 것이 스크립트에 실제로 구현됨 | 고친다 |
- | 분리 실행 | 앱 서버와 의존 서비스가 스크립트를 부른 셸과 분리된 프로세스로 떠서, 스크립트가 끝나도 `-Status` 가 준비됨을 보고함 | 고친다. 아니면 검증 세션마다 앱이 죽어 있다 |
- | 프로세스 정리 | `local-dev`·게이트 실행기가 프로세스를 끄는 곳마다 이름이 아니라 자기가 띄운 PID 이고, 끄기 직전에 기록한 시작 시각과 같은지 보며, 자손은 부모보다 늦게 시작한 것만 모음(또는 잡 오브젝트) | 고친다. PID 재사용으로 다른 슬롯의 워커 에이전트를 죽인다(실전 2026-10-13) |
- | 건너뜀 | 스키마·앱 단계가 대상이 없을 때 비0이 아니라 "건너뜀"과 0으로 끝나고, 판정 시작 태스크가 `HARNESS.md` 2절에 있음 | 고친다. 아니면 첫 태스크부터 환경 문제로 찍힌다 |
- | 대기 | 각 의존 서비스에 준비 판정이 있음 (포트 확인만으로 끝내지 않음) | 채운다 |
- | 상태 확인 | 띄우기 말고 상태·로그·내리기 수단이 있음 | 채운다. 없으면 검증 세션이 환경 문제를 구현 실패로 보고한다 |
- | 종료 코드 | 다섯 단계가 서로 다른 0이 아닌 코드로 끝나고, 그 표가 `HARNESS.md` 2절에 있으며 스크립트와 일치 | 맞춘다. 루프가 이 숫자만 보고 "사람이 고칠 것 / 재준비할 것"을 가른다 |
- | 비밀값 | 스크립트·예시 파일에 실제 비밀값이 0건 | 지운다 |
- | 플랜 침범 | 태스크·수용 기준을 이 스킬이 직접 고쳐 쓴 곳이 0건 ("앞 문서 반영"으로 `plan-generator` 10단계를 불러 — 부를 수 없으면 그 규칙대로 — 고친 것은 침범이 아니다) | 되돌리고 Layer 3으로 보고한다 |
- | 루프 침범 | 실패 재시도 횟수·태스크 선택 규칙·커밋 규칙을 **정한** 곳이 0건 (Layer 5가 정한 것을 근거로 참조하는 것은 괜찮다) | 지운다. Layer 5의 몫이다 |
- | `CLAUDE.md` · `AGENTS.md` | 두 파일이 같고 `## 하네스` 절이 갱신됐고 각 200줄 이하 | 자기 절만 고친다. 넘치면 규칙을 문서로 돌려보내고 가리키는 줄만 남긴다 |
-
- **자체 점검 결과를 하네스 문서 안에 남기지 않는다.** 점검은 작업 기록이고 문서는 산출물이다.
- 문서에 남기면 게이트가 바뀌어도 점검표가 남아, 통과한 적 없는 상태를 통과했다고 말하게 된다. **결과는 10단계 보고로 간다.**
-
- **고칠 것과 보고할 것이 다르다.** 형식 문제(빠진 명령, 잘못된 파일 이름, 없는 활성 시점)는 고친다.
- **범위 판단은 보고한다** — 도구를 바꾸는 것, 게이트를 늘리는 것, 덮기 어려운 요구사항을 목록에서 빼는 것은
- 사용자가 요청한 검증을 사용자 모르게 줄이는 일이다.
-
- ### 앞 문서 반영 — 보고하고 멈추지 않는다
-
- 하네스를 세우다 보면 앞 문서를 바꿔야 풀리는 것이 나온다 — TRD 에 없는 도구·구성 요소(Redis, 브라우저 자동화), 관찰할 수 없게 쓰인 태스크 수용 기준,
- 없는 태스크를 가리키는 참조, 하네스로 잴 수 없는 NFR 수치. 보고만 하고 넘어가면 루프가 그 자리에서 3회 실패한 뒤에야 같은 문제가 다시 올라온다.
-
- 1. **결정이면 사용자에게 묻는다**(선택지 셋 이하, 여러 건이면 묶어서). 이미 합의된 것이 문서에 안 적힌 것이면 묻지 않는다. 답을 받기 전에는 반영하지 않는다 — 대화할 수 없는 세션(무인·평가)이면 결정이든 기록이든 다른 레이어 문서는 고치지 않고(그 스킬도 부르지 않고) 지금처럼 보고·CF·미결로 남긴다.
- 2. **가장 앞 문서부터 고친다** — 요구사항·NFR 수치는 `prd-generator` 8단계, 도구·구성 요소는 `trd-generator` 9단계, 구조는 `architecture-generator` 9단계, 수용 기준·태스크 참조는 `plan-generator` 10단계.
- 부를 수 없으면 그 문서의 부분 갱신 규칙대로 직접 고친다(바뀐 항목만, 번호 유지, 새 것은 끝번호, 추적표, 버전과 `변경 이력` 한 줄)
- 3. **사이 문서를 맞춘다** — 고친 문서와 플랜 사이에서 바뀐 ID 를 찾아 그 자리만. TRD 에 TD 를 더했으면 아키텍처에 그것을 쓰는 설계 단위가 있는지, 플랜에 그것을 준비하는 태스크가 필요한지
- 4. 하네스 작성으로 돌아가고, 10단계 보고 6번에 적는다. `HARNESS.md` 머리의 `입력 문서` 버전을 실제와 맞춘다
-
- ## 10단계. 작성 후 보고
-
- 이 순서로 보고한다.
-
- 1. **네 게이트가 각각 무엇으로 도는가** — 명령과 판정 기준을 한 줄씩. **활성 시점(어느 태스크가 끝나야 실제로 도는가)**을 함께
- 2. **task_validation 의 분리 방식** — 검증자가 무엇을 받고 무엇을 받지 않는지, 이 환경에서 세션을 어떻게 분리하는지.
- **분리가 보장되지 않는 환경이면 그 사실을 여기서 명시한다.** 분리된 척하는 검증은 안 하느니만 못하다
- 3. **E2E 흐름 목록** — 몇 개이고 어느 phase 에서 활성화되는지. 차단 때문에 `대기` 인 흐름
- 4. **`local-dev`** — 어느 OS 용이고 무엇을 준비하며 어떻게 돌리는지
- 5. **PLAN.md 7절 항목의 배치 결과** — 항목마다 어느 게이트가 받았는지. **하네스로도 못 덮는 것**과 그 이유
- 6. **앞 문서 반영과 남은 어긋남** — 이번에 고친 TRD·플랜(태스크 수용 기준)의 새 버전과 바뀐 ID, 누가 무엇을 골랐는가. 사용자가 보류해 남은 것. 도구가 비어 있어 하네스가 정한 것
- 7. **하네스에 더 필요해 보이는 것** — 아래를 보라. **만들지 않고 제안만 한다**
- 8. 다음 단계 안내: "검토하시고, 괜찮으면 개발 루프 세팅으로 넘어갈까요? (`loop-setup`)"
-
- ### 더 필요해 보이는 것은 제안만 한다
-
- 원본 사양은 하네스에 무엇이 더 필요한지를 열어 두었다. 네 게이트와 로컬 개발 환경 밖에서
- 이 프로젝트에 필요해 보이는 것이 생기면 **만들지 말고 목록으로 제안한다.** 흔히 후보가 되는 것은
- 성능 목표(응답 시간·생성 시간)를 **회귀로** 지키는 측정 묶음, 브라우저 여러 종을 도는 실행 구성,
- 접근성 검사 같은 것들이다.
-
- **검증 증거를 파일로 쌓는 것은 제안하지 않는다.** 증거는 `LOG.md` 에 텍스트로 남기기로 이미 정해져 있고,
- 기록처를 늘리면 STATE.md·LOG.md 와 어긋나는 세 번째 출처가 생긴다.
-
- **게이트를 순서대로 돌리는 실행, 실패 시 재시도, 하네스 갱신 시점 판단은 제안 목록에 넣지 않는다.**
- 원본 사양이 그것을 Layer 5 루프의 일로 이미 정해 두었다.
-
- **근거 요구사항이 없는 검사에 통과선을 만들지 않는다.** 접근성이 대표적이다 —
- PRD에 접근성 요구사항이 없는데 하네스가 기준을 정하면, 사용자가 합의한 적 없는 관문이 선다.
- 그때는 "PRD에 요구사항을 넣는 것이 먼저"라고 보고한다.
-
- **임의로 넣지 않는 이유가 있다.** 하네스는 모든 태스크가 매번 통과해야 하는 관문이라,
- 여기 들어간 것 하나가 앞으로의 모든 세션에 비용으로 붙는다. 무엇을 관문에 세울지는 사용자의 결정이다.
-
+ description: 프로젝트의 수용 기준에 맞는 검증 하네스와 로컬 실행 환경을 harness_setup/에 준비한다. 하네스·테스트 파이프라인·로컬 환경을 처음 세팅할 때 사용하며 기존 하네스 갱신은 harness-update로 다룬다.
---
- ## 산출물 템플릿
-
- ### `harness_setup/HARNESS.md`
-
- ````markdown
- # 검증 하네스: (제품 이름)
-
- - **입력 문서**: docs/PRD.md (0.1), docs/TRD.md (0.1), docs/ARCHITECTURE/ (0.1), plan_setup/PLAN.md (0.1)
- - **상태**: 사람 검토 대기
-
- ## 1. 게이트 개요
- 태스크 하나는 아래 네 게이트를 **순서대로** 통과해야 완료된다. 앞이 실패하면 뒤를 돌리지 않는다.
-
- | 순서 | 게이트 | 정의서 | 명령 | 활성 시점 | 현재 |
- |---|---|---|---|---|---|
- | 1 | lint | `quality_gates/lint.md` | | phase-01-task-01 | 미활성 |
- | 2 | unit | `quality_gates/unit.md` | | | 미활성 |
- | 3 | e2e | `quality_gates/e2e.md` | | | 미활성 |
- | 4 | task_validation | `quality_gates/task_validation.md` | (분리 세션) | | 미활성 |
-
- > `미활성` 은 통과가 아니라 "아직 판정하지 않음"이다.
- > **task_validation 의 관찰 수단**: (이 환경의 브라우저 조작 도구. 없으면 "없음 — e2e 도구로 일회성 스크립트" 라고 적는다)
- > `status: blocked` 인 태스크는 어느 게이트의 대상도 아니다.
-
- ## 2. 로컬 개발 환경
- - **명령**: `scripts/local-dev.ps1` (또는 `.sh`) — 초기화 `-Reset`, 상태 `-Status`, 로그 `-Logs`, 내리기 `-Down`
- - **전제**: (필요한 런타임·컨테이너 엔진)
- - **종료 코드**: 0 성공 / (n) 전제 / (n) 환경변수 / (n) 의존 서비스 / (n) 스키마·시드 / (n) 앱.
- 실패하면 실패한 단계 이름과 다음 행동을 출력한다. 전제·환경변수는 사용자가 고치고, 나머지는 재준비 후 `harness-update`
- - **단계별 판정 시작 시점**: 스키마·시드는 `phase-NN-task-MM` 완료부터, 앱 실행은 `phase-NN-task-MM` 완료부터. 그전에는 "건너뜀"으로 0
- - **공유 DB 를 쓰는 게이트**: (예: unit 통합 테스트 — 스키마를 바꾸는 테스트 있음 / e2e / task_validation). 병렬 격리를 정할 때 `loop-setup` 이 이 줄을 읽는다
- - **슬롯 변수**: (`local-dev` 가 읽는 포트·DB 이름 환경변수 이름. 격리 1·2면 슬롯 번호에서 파생하는 분기 — 없으면 "없음(격리 3 전제)")
-
- > 루프는 이 표의 숫자로 환경 문제를 확정한다. 단계가 늘면 코드도 여기와 스크립트에 함께 더한다.
-
- | 구성 요소 | 형태 | 준비 판정 | 왜 필요한가 |
- |---|---|---|---|
- | | 컨테이너 / 호스트 실행 / 로컬 설치 | | |
-
- > 이 표가 단일 출처다. 구성 요소가 늘면 여기에 행을 더하고 스크립트에 블록 하나를 더한다.
-
- ## 3. 하네스 밖
- (요구사항 중 이 하네스로 판정할 수 없는 것과, 대신 무엇으로 확인하는가.)
-
- | 요구사항 | 왜 게이트로 못 덮는가 | 무엇으로 확인하는가 |
- |---|---|---|
-
- ## 4. 하네스 결정
- (TRD가 비워 둔 자리를 하네스가 채운 것. 근거 없이 세운 통과선도 여기 적는다.)
-
- | 정한 것 | 왜 하네스가 정했는가 | 되돌리기 비용 |
- |---|---|---|
- ````
-
- ### `harness_setup/quality_gates/lint.md` (unit·e2e 도 같은 뼈대)
-
- ````markdown
- # 게이트 1 — lint
-
- ## 무엇을 판정하는가
- (한 문단. 무엇을 보고 무엇을 보지 않는가.)
-
- ## 명령
- ```
- (붙여 넣어 그대로 돌릴 수 있는 명령 한 줄)
- ```
-
- ## 통과 조건
- - 종료 코드 0
- - (그 외 판정 기준)
-
- ## 활성 시점
- - `phase-NN-task-MM` 완료 시부터. 그전에는 `미활성`
-
- ## 무엇을 검사하는가
- | 검사 | 근거 | 설정 파일 |
- |---|---|---|
- | | `backend.md §3.1` / TD-0xx | `프로젝트 루트/…` |
-
- ## 전제
- (이 게이트가 돌기 전에 필요한 것. 없으면 '없음')
-
- ## 하지 않는 것
- - 자동 수정 — 게이트는 판정만 한다
- ````
-
- ### `harness_setup/quality_gates/e2e.md` 의 흐름 목록
-
- ````markdown
- ## 덮는 흐름
- | ID | 흐름 | 단계 요약 | 덮는 요구사항 | 실패 경로 | 활성 phase | 상태 |
- |---|---|---|---|---|---|---|
- | FLOW-01 | | | FR-00x, NFR-00x | | 02 | 대기 |
-
- > **매 태스크 묶음**: (빠른 흐름 ID들)
- > **phase 완료 묶음**: (시간이 걸리는 측정)
- > 차단된 태스크에 걸린 흐름은 `대기` 이고 실행 대상이 아니다.
-
- ## phase 완료 묶음 명령
- > 게이트 명령이 아니다 — 루프의 구동 스크립트가 phase 끝에 **이 절의 코드 블록을 그때 읽어** 한 줄씩 차례로 돌리고 줄마다 종료 코드를 남긴다(판단 없이).
- > 한 줄 = 붙여 넣어 그대로 돌릴 수 있는 명령 하나. 느린 lint·unit 묶음이 있으면 그것도 한 줄씩. **활성인 묶음 흐름만 돈다** — `대기` 흐름은 명령·설정에서 빠져 있어야 한다(구동 스크립트는 거르지 않는다).
- > 활성 시점: `phase-NN-task-MM` 완료 시부터. 그전에는 코드 블록을 비워 둔다 — 비어 있으면 루프가 `미활성` 으로 읽는다.
- ```
- (명령 한 줄씩. 아직 없으면 빈 블록)
- ```
-
- ## 흔들림을 줄이는 규칙
- > 스펙을 쓰기 전에 읽는다. 흔들림을 안정화할 때마다 `harness-update` 가 원인 패턴을 한 줄 더한다(①).
-
- | 규칙 | 출처 |
- |---|---|
- | 선택자는 역할과 이름으로. 표식은 그것으로 안 되는 곳에만 | 세팅 |
- | 실행마다 고유한 테스트 데이터 | 세팅 |
- | 인증 상태는 한 번 만들어 재사용 (인증 흐름 자체를 보는 흐름만 진짜로 로그인) | 세팅 |
- | 외부에서 오는 값(메일 링크 등)은 실제 경로에서 읽는다. 하드코딩하지 않는다 | 세팅 |
- | 값은 조건이 될 때까지 기다리는 단언으로 본다. 한 번 읽어 비교하지 않는다 | 세팅 |
- | 크기·위치·색 측정은 전환·로드가 끝난 뒤에 | 세팅 |
- | 목록의 순서·첫 페이지를 가정하지 않는다. 검색·필터로 좁혀 찾는다 | 세팅 |
-
- ## 단언 세 층
- 1. 화면 상태 / 2. 서버 상태(DB·토큰·이력) / 3. 다시 조회하거나 다른 계정에서 볼 때의 결과
-
- ## 실패 분류 (고치기 전에 정한다)
- | 분류 | 알아보는 법 | 고칠 곳 |
- |---|---|---|
- | 진짜 결함 | 수동으로 같은 순서를 밟아도 같은 결과 | 코드 |
- | 낡은 스펙 | 화면·규약이 의도적으로 바뀌었다 | 흐름 정의 (`harness-update`) |
- | 환경 문제 | 의존 서비스 미기동, 남은 데이터 | `local-dev` 재준비 |
-
- > 분류를 `LOG.md` 에 남긴다. 세 번의 실패가 전부 환경 문제였다면 고칠 것은 코드가 아니라 하네스다.
- ````
-
- ### `harness_setup/quality_gates/task_validation.md`
-
- ````markdown
- # 게이트 4 — task_validation
-
- ## 무엇을 판정하는가
- 그 태스크의 수용 기준이 **실행 중인 애플리케이션에서 실제로 관찰되는가**.
+ # 하네스 세팅
- ## 분리 원칙
- 이 검증은 **그 태스크를 구현한 세션이 아닌 세션**에서 돈다.
- 구현 중에 형성된 판단이 관찰을 덮어쓰는 것을 막기 위해서다.
- 그 세션을 **어떻게 여는지(헤드리스 자식 세션·오르카 워커·서브에이전트)는 `loop_setup/LOOP.md` 의 구동기가 정한다** —
- 기본은 컨텍스트 없는 새 프로세스(`claude -p` / `codex exec`)다. 이 문서는 그 세션에 무엇을 주고 무엇을 주지 않는지만 고정한다.
+ 수용 기준을 실제로 검사할 수단을 갖추고, 다음 실행자가 같은 환경과 결과를 재현하게 한다.
- ### 검증자가 받는 것
- 1. 태스크 문서 경로 — `plan_setup/phase-NN/phase-NN-tasks/phase-NN-task-MM.md`
- 2. 실행 중인 애플리케이션 (`scripts/local-dev…` 로 준비)
- 3. 이 문서
- 4. (필요 시) 태스크의 `architecture` 가 가리키는 설계 절
+ ## 입력과 범위
- ### 검증자가 받지 않는 것
- - 구현 세션의 대화·요약·"무엇을 했는지" 설명
- - 구현자가 쓴 통과 보고나 자체 점검 결과
- - 원인 추정이나 확인 요청
+ - 코드·기존 실행/테스트 설정, 관련 `docs/PRD.md`·`docs/TRD.md`·`docs/ARCHITECTURE/`, `plan_setup/`을 읽는다.
+ - PRD의 `## 자율운영 합의`와 기존 허용·금지·예산을 먼저 확인한다. 대화에서 확정한 내용도 문서에 보존한다.
+ - 문서가 부족하면 요청 범위의 목표·수용 기준·실행 전제만 보완한다. 전체 문서 파이프라인을 선행 조건으로 삼지 않는다.
+ - `harness_setup/`이 이미 있으면 현재 상태를 읽고 필요한 갱신만 수행한다.
- ## 절차
- 1. `local-dev` 로 환경을 준비한다 (필요하면 초기화 옵션 `-Reset`/`--reset`). 뭔가 이상하면 상태 옵션 `-Status`/`--status` 로
- 무엇이 안 떴는지 먼저 본다 — **환경 문제를 구현 실패로 보고하지 않기 위해서다** (옵션 표기는 이 프로젝트의 스크립트 것을 쓴다)
- 2. 태스크 문서의 수용 기준을 번호대로 읽는다
- 3. 기준마다 애플리케이션을 실제로 조작해 결과를 관찰한다 — 아래 '관찰 수단' 순서대로
- 4. 수용 기준이 코드를 대상으로 할 때만 코드를 검사한다
- 5. 아래 형식으로 판정을 출력한다
+ ## 환경 조사와 결정
- ## 관찰 수단
- 1. (이 환경의 브라우저 조작 도구 — `HARNESS.md` 에 적힌 것) — 화면이 있는 기준의 기본
- 2. 없으면 e2e 도구로 일회성 스크립트를 써서 실행. 저장소에 남기지 않고 제품 코드를 건드리지 않는다. **판정 블록에 `관찰 수단: 2` 를 적는다**
- 3. 화면이 없는 기준은 HTTP 호출·DB 질의로 관찰한다
- 1도 2도 없으면 화면 기준은 `판정 불가` 다. 코드를 읽고 PASS 를 주지 않는다.
+ 1. OS·셸·런타임·패키지 명령·외부 의존·현재 권한을 확인한다. 기존 실행·검증 명령을 우선 사용한다.
+ 요구가 다른 OS 동작을 포함하면 그 OS에서 실제 실행할 수단을 직접 확인해(Windows면 `wsl -l -v`의 배포판 등) 게이트와 독립 검증에 넣고, 수단이 없을 때만 미검증으로 남긴다.
+ 2. 현재 제공된 도구·CLI 도움말에서 브라우저/HTTP/CLI/DB 관찰, 필요시 독립 검토 수단을 확인한다.
+ 제품 이름만으로 UI·서브에이전트·자동 호출의 가능 여부를 단정하지 않는다.
+ 3. 조사로 알 수 없는 목적·제약·권한·비용/데이터 경계·예산·실패 대안만 Loop 전에 묶어 해소한다.
+ 기존 합의로 결정할 수 있는 도구 구성과 통상적 로컬 준비는 재승인 없이 수행한다.
+ 4. 합의 밖 필수 결정이 남으면 준비 완료로 표시하지 않는다. Loop 중 호출됐다면 질문 없이 허용된 대안·독립 작업·최종 미완료 처리를 따른다.
- ## 판정 형식
- ```
- TASK: phase-NN-task-MM
- GATE: task_validation
- 관찰 수단: 1 | 2 | 3
- RESULT: PASS | FAIL | 판정 불가 | 환경 문제
- - [1] PASS — (무엇을 했고 무엇이 보였는가)
- - [2] FAIL — (관찰한 것. 원인 추정 없이)
- END
- ```
+ ## 최소 산출물
- `환경 문제` 는 `local-dev` 상태 확인이 비0이거나 앱이 응답하지 않아 **관찰 자체를 못 한 경우**다. FAIL 이 아니다 — 안 뜬 화면을 FAIL 로 적으면 멀쩡한 코드를 고치게 된다.
- 마지막 줄 `END` 는 루프가 **잘린 판정 파일**을 알아보는 표식이다. 검증 세션이 도중에 죽으면 이 줄이 없다.
+ `harness_setup/HARNESS.md`에 다음 정보를 짧게 남긴다.
- ## 판정 규칙
- - 증거 없는 PASS 는 PASS 가 아니다
- - 검증자는 코드를 고치지 않는다 — 고치면 다음 기준부터 자기 코드를 검증하게 된다
- - 원인 분석과 수정 제안을 하지 않는다 — 틀린 추정은 엉뚱한 수정을 부른다
- - 기준이 관찰 가능하게 쓰이지 않았으면 `판정 불가` 로 돌려보낸다 (Layer 3의 일이다)
+ - 실행 전제, 로컬 준비/실행·상태 확인·로그·종료 방법과 실제 접속 주소.
+ - 검증 목록과 관련 수용 기준/요구 ID, 실행 시점, 검사 정의 위치.
+ - 브라우저 등 실제 관찰 수단, 필요한 테스트 데이터와 의존 서비스, 현재 미검증 범위·한계.
- ## 결과 기록
- 루프가 이 블록을 두 곳으로 옮긴다.
- - `plan_setup/STATE.md` 의 `Last Verification` — 지금 상태 한 줄
- - `plan_setup/LOG.md` — **관찰한 증거.** 통과는 한 줄, 실패는 기준 번호와 관찰을 함께
+ `harness_setup/quality_gates/`에는 요청 범위의 검사 정의를 둔다. 제품 Loop는 네 이름의 정의를 모두 두고 HARNESS·태스크에서 연결한다.
+ 각 정의는 대상·근거 기준, 실제 명령 또는 관찰 방법, 통과 조건, 전제와 활성 범위를 담는다.
+ 제품 Loop는 `lint → unit → e2e → task_validation` 판정 순서와 태스크 `verification` 연결을 유지한다.
+ 앞 실패 복구 전에 뒤의 완료 판정을 열지 않는다. 현재 코드·설정·의존성·데이터 조건과 영향이 같다는
+ 근거로 결과를 재사용하거나 같은 코드 상태의 태스크를 묶어 검사할 수 있다. 각 수용 기준의 태스크별
+ 대응을 남기며 검증 전 `completed` 처리나 후행의 완료 선행 사용을 금한다.
+ 준비 미활성은 PASS가 아니다. 준비 자체의 증거와 후속 활성 담당/시점을 연결하고 제품 완료와 구분한다.
+ 문서·세팅만 요청과 작은 독립 수정에 전체 파이프라인을 강제하지 않는다. 기존 Loop의 작업 범위에 속하는 제품 변경은 계약을 계승한다.
+ LOOP.md의 존재만으로 새 독립 요청을 편입하지 않으며 현재 요청·유효한 합의·태스크 연결로 범위를 판단해 기록한다.
+ 사용자 명시 기본값 변경·자식 금지가 우선하며 바뀐 보장과 미검증을 기록한다.
+ 구판 PLAN의 `Layer 4로 넘기는 검증`이나 숫자 기준도 누락 없이 검사·묶음 측정·한계 중 어디에서 다룰지 연결한다.
- 관찰은 재현 가능한 문장으로 쓴다 — "정상 동작함"이 아니라 "계정 B로 A의 인보이스 URL을 열었더니 200과 본문이 보였다".
- **하네스는 별도의 상태 파일이나 증거 디렉터리를 만들지 않는다.** 증거는 LOG.md 에 텍스트로 남는다.
- ````
+ `harness_setup/scripts/`에는 기존 명령으로 해결되지 않는 준비·실행 스크립트만 만든다.
+ 필요하면 확인한 OS에 맞는 `local-dev.ps1` 또는 `local-dev.sh`를 사용한다.
+ 기존 패키지 스크립트·서비스 관리 기능으로 충분하면 새 래퍼를 만들지 않는다.
+ 복잡한 범용 구동기·감시·질문 플랫폼을 생성하지 않는다.
- ### `harness_setup/scripts/local-dev.*`
+ 화면 제품은 디자인 정본의 승인 방향·버전·품질 기준과 대표 화면·주요 상태의 실제 렌더링을 대조할 수단과 지속 증거를 준비한다.
+ 사람의 디자인 승인은 기능 게이트·독립 task_validation을 대체하지 않는다. 검증자는 승인 기준선을 바꾸지 않고 불일치를 반환한다.
- ````markdown
- 스크립트는 아래 단계를 이 순서로 갖는다. 각 의존 서비스는 **같은 모양의 블록 하나**다.
+ ## 필수 제약
- 1. 전제 확인 — 필요한 런타임·엔진이 없으면 설치 방법을 출력하고 종료(비0)
- 2. 환경변수 준비 — 예시 파일에서 만들고, 빠진 필수 키를 이름으로 나열
- 3. 의존 서비스 — 각 구성 요소마다: 이미 준비됐는가 → 없으면 준비 → 준비될 때까지 대기 → 실패 시 안내
- 4. 스키마·시드 — 마이그레이션 적용, 시드 투입 (대량 시드는 옵션). 마이그레이션이 아직 없으면 "건너뜀" 출력 후 계속
- 5. 앱 실행 — 개발 서버를 **분리된 프로세스**로 실행(스크립트가 끝나도 살아 있다). 응답 한 번으로 준비 판정.
- 앱 진입점이 아직 없으면 "건너뜀" 출력 후 0으로 종료 (실패가 아니다)
- 6. 요약 출력 — 접속 주소, 예시 계정, 다음에 할 것
+ 제품 최종 완료는 필요한 네 게이트와 포함 범위 수용 기준의 실제 증거로 닫는다.
+ `lint`는 정적 오류, `unit`은 기능/계약, `e2e`는 실제 성공·실패 흐름, `task_validation`은 독립 수용 관찰을 담당한다.
- 옵션: `-Reset` / `--reset` (데이터 초기화 후 처음부터), 대량 시드 옵션,
- `-Status` / `--status` (구성 요소별 준비 여부만 출력, 아무것도 띄우지 않는다),
- `-Logs` / `--logs` (안 뜬 구성 요소의 로그), `-Down` / `--down` (띄운 것을 내린다).
- 종료 코드: 성공 0, 실패는 **단계마다 다른** 0이 아닌 값(전제 / 환경변수 / 의존 서비스 / 스키마·시드 / 앱) + 실패한 단계 이름.
- 그 표를 `HARNESS.md` 2절에 그대로 적는다 — 루프가 읽는 곳이 거기다.
+ - 모든 게이트의 판정 중 제품 소스·수용 기준·통과 단언을 자동 수정하지 않는다(`--fix`·포매터 쓰기·스냅샷 갱신 등).
+ 수정은 구현 단계에서 수행하고 영향받은 판정을 무효화해 재검증한다. 기준을 바꾸지 않는 허용된 테스트 데이터 준비는 가능하다.
+ - 화면 기준은 브라우저/실행 E2E로, 비화면 기준은 HTTP·CLI·DB 등 적합한 수단으로 확인한다.
+ 중요한 실패 경로와 실제 저장·다시 조회 결과도 기준에 맞게 검사한다.
+ - 코드 작성·종료 코드 0·테스트 0건·미실행을 기능 완료 증거로 대신하지 않는다.
+ 기존 `활성/부분 활성/미활성/대기`는 보존하고 적용 범위를 적는다. 미활성·대기는 통과가 아니다.
+ - 제품 Loop의 task_validation은 해당 변경을 구현하지 않은 비상속 별도 세션이 맡는다. 같은 모델은 가능하나
+ 구현 대화·추론·자체 통과 결론을 상속한 자식과 같은 세션 역할 변경은 독립 검증이 아니다.
+ 요구·수용 기준·필요 설계·대상 코드 식별·환경/접속·허용 데이터/권한·잔여 예산을 주고 코드/diff 열람은 허용한다.
+ 운영 제약은 필요한 절만 전달하고, 구현 추론·자체 판정이 담긴 STATE/LOG 전체를 검증 입력으로 넘기지 않는다.
+ 검증자는 제품·수용 기준·통과 단언을 고치지 않는다. 직접 수정한 변경은 다른 독립 검증자가 판정한다.
+ 실제 관찰에 따른 기준별 PASS/FAIL·환경 문제/판정 불가·재현 증거를 완전히 회수한다.
+ 세션 생성·테스트 재실행·코드 리뷰·구현자 보고만으로 제품 PASS를 대신하지 않는다.
+ - 초기 실행·도구 누락은 preflight에서 발견해 허용된 준비와 실제 실행으로 확인한다.
+ 공유 DB 초기화·마이그레이션·시드 명령과 실제 접속 대상을 확인하고 충돌 데이터만 격리한다.
+ 앱이 대상 코드/미커밋 변경·데이터를 서비스하는지 확인하고 검사 중 교체를 막는다.
+ 포트 열림·프로세스 존재만으로 readiness를 판정하지 않고 실제 요청과 필요한 준비 상태를 확인한다.
+ - 준비 명령은 반복 실행에 안전해야 한다. 데이터 초기화는 합의한 테스트 데이터 범위에 한정한다.
+ 프로세스 종료는 직접 띄운 프로세스의 신원을 확인하고 수행한다. 이름이나 재사용될 수 있는 PID만으로 종료하지 않는다.
+ - 비밀값은 예시·로그에 넣지 않는다. 병렬 실행을 쓰면 파일·DB·포트 충돌을 격리하거나 해당 검사를 직렬화한다.
+ - 실패를 숨기려고 검사·수용 기준을 낮추지 않는다. 합의 안의 코드·테스트·환경·문서 불일치는 원인에 맞게 고친다.
+ 영향 있는 앞 문서부터 필요한 부분만 맞추고 기존 ID·절 참조·사용자 변경·완료 상태를 보존한다.
+ - 루트 지침은 후속 실행에 필요한 하네스 포인터만 보완한다. 양쪽 루트 문서를 매번 전체 동기화하지 않는다.
- `-Status` 는 분리된 검증 세션이 **실패를 '환경 문제'로 분류할 수 있게 하는 수단**이다.
- 그것이 없으면 환경 문제가 구현 실패로 보고되고, 루프가 멀쩡한 코드를 재시도 한도까지 고친다.
- ````
+ ## 완료 증거와 연결
- ---
+ 작성·변경한 실행 방법과 선택한 검사를 실제로 돌려 명령·대상·관찰·판정·미실행 이유를 남긴다.
+ 준비만 가능하고 제품 코드가 없다면 하네스 준비 결과와 기능 미검증을 구분한다.
+ 전부 skip·서비스 미실행도 PASS가 아니다. 중요한 전환마다 HARNESS·태스크·STATE·LOG의 영향 부분을 증분 저장한다.
+ 채택 로그·스크린샷·트레이스와 대상 코드/환경 식별을 지속 경로에 보존하며 임시 경로만 남기지 않는다.
- ## 에이전트별 참고
+ 보고에는 준비된 수단, 확인한 결과, 남은 한계와 재개 조건을 적는다.
+ 사용자가 세팅·문서화만 요청하면 그 범위에서 끝내고 다음 스킬을 제안한다.
+ 다음 단계는 `loop-setup`이다. 사용자가 그 단계 이후 진행을 명시적으로 승인했고 합의가 충족됐으면 이어 수행해 실행·검증·복구·최종 보고까지 진행한다.
+ 아니면 결과와 다음 스킬을 제안해 승인을 받는다. 승인된 실행을 사용자에게 넘기는 것으로 끝내지 않는다. 명시 승인은 요청이나 사용자가 확정한 합의가 이후 단계 진행을 권한·예산과 함께 밝힌 경우이며 '만들어줘'만이나 침묵, 에이전트가 채운 합의는 해당하지 않는다.
- - Claude Code에서 실행 중이면: `references/claude-code.md`
- - Codex에서 실행 중이면: `references/codex.md`
+ ## 환경 참고
- **둘 다 반드시 확인할 것은 하나다** — 이 환경에서 `task_validation` 을 **어떻게 분리된 세션으로 돌리는가**.
- 분리 수단이 없는 환경이면 그 사실을 사용자에게 알린다. 분리된 척하는 검증은 안 하느니만 못하다 —
- 통과했다는 기록만 남고 실제로는 구현자가 자기 코드를 승인한 것이기 때문이다.
+ 현재 호스트에서 실제 차이를 확인할 때만 읽는다.
+ - [Claude Code](references/claude-code.md)
+ - [Codex](references/codex.md)