Skip to content
Back to skills

Harness Setup

ASecurity

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 붙여줘"라고 하거나, 마스터 플랜 검토를 마치고 다음 ...

  • 3 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 23, 2026
ai-agentsapibackendfullstack

Works with

  • claude code
  • api
  • mcp

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned September 23, 2026

npx -y skills add GangJaeYu/harness_loop_full_korean --skill harness-setup --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Harness Setup?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Harness Setup
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/gangjaeyu-harness-setup/badge)](https://www.skillsdirectory.com/skills/gangjaeyu-harness-setup)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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·아키텍처 — 돌아가서 고친다 |
| 테스트 러너·브라우저 자동화 같은 **도구 선택** | TRD (`trd-generator`) 의 TD. 하네스는 고르지 않고 **구성**한다 |
| 태스크·phase 를 나누거나 수용 기준을 고쳐 쓰는 것 | Layer 3 (`plan-generator`) — 판정 불가능한 기준을 만나면 되돌려 보낸다 |
| 태스크 코드를 실제로 구현하는 것 | Layer 5 개발 루프 |
| 루프의 실행 순서·실패 재시도 3회·워크트리 운용 | Layer 5 (`loop-setup`) |
| STATE.md·LOG.md 의 갱신 | Layer 5 개발 루프 — 하네스는 **무엇을 기록할지의 형식**만 정한다 |
| 하네스를 고쳐 나가는 것 | `harness-update` — 게이트와 스크립트는 phase 가 진행되며 계속 바뀐다 |

**도구를 새로 고르지 않는 이유가 있다.** TRD가 TD-0xx 로 고른 도구에는 근거 요구사항과 되돌리기 비용이 붙어 있다.
하네스가 여기서 다른 것을 고르면 그 근거가 끊기고, 되돌리기 비용을 아무도 계산하지 않은 결정이 파이프라인 한복판에 들어간다.
TRD가 정한 것은 그대로 쓴다. **TRD가 침묵한 것 중 되돌리기 비용이 낮은 것**(포매터 규칙, 커버리지 보고 형식 등)은
하네스가 정하고 `(하네스 결정)` 이라고 표시한 뒤 보고한다. **비용이 큰 것**(러너 교체, 브라우저 도구 추가)은 TRD로 돌려보낸다.

### 하네스는 코드가 아직 없는 상태에서 세워진다

플랜 직후에 서기 때문에, 하네스를 세우는 시점에 `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 완료를 자동 판정 단위로 만들어 두었으므로 걸어 둘 자리가 있다

**아직 화면이 없는 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로 돌려보낸다
- **스크립트는 표의 한 행을 같은 모양의 블록 하나로 구현한다** — 이미 준비됐는지 확인 → 없으면 준비 →
  **준비될 때까지 대기** → 실패하면 무엇을 해야 하는지 출력. 새 구성 요소는 블록 하나 추가로 끝난다
- **필요한 것의 목록을 스크립트 여기저기에 흩뿌리지 않는다.** 상단에 모아 두면 표와 대조할 수 있고,
  대조할 수 있어야 `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` 는 그 판정을 **띄우지 않고 출력만** 하면 된다
- **비밀값을 파일에 적지 않는다** — 예시 환경변수 파일에는 키 이름과 형식만 두고, 실제 값은 두지 않는다

**여러 에이전트가 동시에 도는 경우는 여기서 정하지 않는다.** 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`
- 뒤 문서는 앞 문서를 근거로 한다. 충돌하면 앞 문서가 이기고, 앞 문서를 고치려면 그 레이어의 스킬을 다시 부른다
- 현재 단계: <마지막으로 끝낸 스킬> → 다음: <다음 스킬>

## 공통 규칙
- 앞 레이어의 **파일**만 입력이다. 대화 기억이나 추측으로 요구사항·기술 결정을 만들지 않는다
- ID(`FR-`·`NFR-`·`US-`·`OQ-`·`TD-`·`phase-NN-task-MM`)는 참조만 한다. 새 ID 는 그 레이어의 스킬만 만든다
- 사용자가 말하지 않은 값은 `(가정)` 으로 표시한다. 모호하면 추측하지 말고 묻는다
- 요청된 범위를 조용히 넓히거나 줄이지 않는다. 범위 판단은 보고한다
```

`## 하네스 (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 가 적혀 있음 | 채운다 |
| 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` 가 준비됨을 보고함 | 고친다. 아니면 검증 세션마다 앱이 죽어 있다 |
| 건너뜀 | 스키마·앱 단계가 대상이 없을 때 비0이 아니라 "건너뜀"과 0으로 끝나고, 판정 시작 태스크가 `HARNESS.md` 2절에 있음 | 고친다. 아니면 첫 태스크부터 환경 문제로 찍힌다 |
| 대기 | 각 의존 서비스에 준비 판정이 있음 (포트 확인만으로 끝내지 않음) | 채운다 |
| 상태 확인 | 띄우기 말고 상태·로그·내리기 수단이 있음 | 채운다. 없으면 검증 세션이 환경 문제를 구현 실패로 보고한다 |
| 종료 코드 | 다섯 단계가 서로 다른 0이 아닌 코드로 끝나고, 그 표가 `HARNESS.md` 2절에 있으며 스크립트와 일치 | 맞춘다. 루프가 이 숫자만 보고 "사람이 고칠 것 / 재준비할 것"을 가른다 |
| 비밀값 | 스크립트·예시 파일에 실제 비밀값이 0건 | 지운다 |
| 플랜 침범 | 태스크·수용 기준을 고쳐 쓴 곳이 0건 | 되돌리고 Layer 3으로 보고한다 |
| 루프 침범 | 실패 재시도 횟수·태스크 선택 규칙·커밋 규칙을 **정한** 곳이 0건 (Layer 5가 정한 것을 근거로 참조하는 것은 괜찮다) | 지운다. Layer 5의 몫이다 |
| `CLAUDE.md` · `AGENTS.md` | 두 파일이 같고 `## 하네스` 절이 갱신됐고 각 200줄 이하 | 자기 절만 고친다. 넘치면 규칙을 문서로 돌려보내고 가리키는 줄만 남긴다 |

**자체 점검 결과를 하네스 문서 안에 남기지 않는다.** 점검은 작업 기록이고 문서는 산출물이다.
문서에 남기면 게이트가 바뀌어도 점검표가 남아, 통과한 적 없는 상태를 통과했다고 말하게 된다. **결과는 10단계 보고로 간다.**

**고칠 것과 보고할 것이 다르다.** 형식 문제(빠진 명령, 잘못된 파일 이름, 없는 활성 시점)는 고친다.
**범위 판단은 보고한다** — 도구를 바꾸는 것, 게이트를 늘리는 것, 덮기 어려운 요구사항을 목록에서 빼는 것은
사용자가 요청한 검증을 사용자 모르게 줄이는 일이다.

## 10단계. 작성 후 보고

이 순서로 보고한다.

1. **네 게이트가 각각 무엇으로 도는가** — 명령과 판정 기준을 한 줄씩. **활성 시점(어느 태스크가 끝나야 실제로 도는가)**을 함께
2. **task_validation 의 분리 방식** — 검증자가 무엇을 받고 무엇을 받지 않는지, 이 환경에서 세션을 어떻게 분리하는지.
   **분리가 보장되지 않는 환경이면 그 사실을 여기서 명시한다.** 분리된 척하는 검증은 안 하느니만 못하다
3. **E2E 흐름 목록** — 몇 개이고 어느 phase 에서 활성화되는지. 차단 때문에 `대기` 인 흐름
4. **`local-dev`** — 어느 OS 용이고 무엇을 준비하며 어떻게 돌리는지
5. **PLAN.md 7절 항목의 배치 결과** — 항목마다 어느 게이트가 받았는지. **하네스로도 못 덮는 것**과 그 이유
6. **TRD·플랜과 어긋난 것** — 도구가 비어 있어 하네스가 정한 것, 판정 불가능한 수용 기준, 없는 태스크를 가리키는 참조
7. **하네스에 더 필요해 보이는 것** — 아래를 보라. **만들지 않고 제안만 한다**
8. 다음 단계 안내: "검토하시고, 괜찮으면 개발 루프 세팅으로 넘어갈까요? (`loop-setup`)"

### 더 필요해 보이는 것은 제안만 한다

원본 사양은 하네스에 무엇이 더 필요한지를 열어 두었다. 네 게이트와 로컬 개발 환경 밖에서
이 프로젝트에 필요해 보이는 것이 생기면 **만들지 말고 목록으로 제안한다.** 흔히 후보가 되는 것은
성능 목표(응답 시간·생성 시간)를 **회귀로** 지키는 측정 묶음, 브라우저 여러 종을 도는 실행 구성,
접근성 검사 같은 것들이다.

**검증 증거를 파일로 쌓는 것은 제안하지 않는다.** 증거는 `LOG.md` 에 텍스트로 남기기로 이미 정해져 있고,
기록처를 늘리면 STATE.md·LOG.md 와 어긋나는 세 번째 출처가 생긴다.

**게이트를 순서대로 돌리는 실행, 실패 시 재시도, 하네스 갱신 시점 판단은 제안 목록에 넣지 않는다.**
원본 사양이 그것을 Layer 5 루프의 일로 이미 정해 두었다.

**근거 요구사항이 없는 검사에 통과선을 만들지 않는다.** 접근성이 대표적이다 —
PRD에 접근성 요구사항이 없는데 하네스가 기준을 정하면, 사용자가 합의한 적 없는 관문이 선다.
그때는 "PRD에 요구사항을 넣는 것이 먼저"라고 보고한다.

**임의로 넣지 않는 이유가 있다.** 하네스는 모든 태스크가 매번 통과해야 하는 관문이라,
여기 들어간 것 하나가 앞으로의 모든 세션에 비용으로 붙는다. 무엇을 관문에 세울지는 사용자의 결정이다.

---

## 산출물 템플릿

### `harness_setup/HARNESS.md`

````markdown
# 검증 하네스: (제품 이름)

- **입력 문서**: docs/PRD.md, docs/TRD.md, docs/ARCHITECTURE/, plan_setup/PLAN.md
- **상태**: 사람 검토 대기

## 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 완료 묶음**: (시간이 걸리는 측정)
> 차단된 태스크에 걸린 흐름은 `대기` 이고 실행 대상이 아니다.

## 흔들림을 줄이는 규칙
> 스펙을 쓰기 전에 읽는다. 흔들림을 안정화할 때마다 `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` 가 가리키는 설계 절

### 검증자가 받지 않는 것
- 구현 세션의 대화·요약·"무엇을 했는지" 설명
- 구현자가 쓴 통과 보고나 자체 점검 결과
- 원인 추정이나 확인 요청

## 절차
1. `local-dev` 로 환경을 준비한다 (필요하면 초기화 옵션 `-Reset`/`--reset`). 뭔가 이상하면 상태 옵션 `-Status`/`--status` 로
   무엇이 안 떴는지 먼저 본다 — **환경 문제를 구현 실패로 보고하지 않기 위해서다** (옵션 표기는 이 프로젝트의 스크립트 것을 쓴다)
2. 태스크 문서의 수용 기준을 번호대로 읽는다
3. 기준마다 애플리케이션을 실제로 조작해 결과를 관찰한다 — 아래 '관찰 수단' 순서대로
4. 수용 기준이 코드를 대상으로 할 때만 코드를 검사한다
5. 아래 형식으로 판정을 출력한다

## 관찰 수단
1. (이 환경의 브라우저 조작 도구 — `HARNESS.md` 에 적힌 것) — 화면이 있는 기준의 기본
2. 없으면 e2e 도구로 일회성 스크립트를 써서 실행. 저장소에 남기지 않고 제품 코드를 건드리지 않는다. **판정 블록에 `관찰 수단: 2` 를 적는다**
3. 화면이 없는 기준은 HTTP 호출·DB 질의로 관찰한다
1도 2도 없으면 화면 기준은 `판정 불가` 다. 코드를 읽고 PASS 를 주지 않는다.

## 판정 형식
```
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` 는 루프가 **잘린 판정 파일**을 알아보는 표식이다. 검증 세션이 도중에 죽으면 이 줄이 없다.

## 판정 규칙
- 증거 없는 PASS 는 PASS 가 아니다
- 검증자는 코드를 고치지 않는다 — 고치면 다음 기준부터 자기 코드를 검증하게 된다
- 원인 분석과 수정 제안을 하지 않는다 — 틀린 추정은 엉뚱한 수정을 부른다
- 기준이 관찰 가능하게 쓰이지 않았으면 `판정 불가` 로 돌려보낸다 (Layer 3의 일이다)

## 결과 기록
루프가 이 블록을 두 곳으로 옮긴다.
- `plan_setup/STATE.md` 의 `Last Verification` — 지금 상태 한 줄
- `plan_setup/LOG.md` — **관찰한 증거.** 통과는 한 줄, 실패는 기준 번호와 관찰을 함께

관찰은 재현 가능한 문장으로 쓴다 — "정상 동작함"이 아니라 "계정 B로 A의 인보이스 URL을 열었더니 200과 본문이 보였다".
**하네스는 별도의 상태 파일이나 증거 디렉터리를 만들지 않는다.** 증거는 LOG.md 에 텍스트로 남는다.
````

### `harness_setup/scripts/local-dev.*`

````markdown
스크립트는 아래 단계를 이 순서로 갖는다. 각 의존 서비스는 **같은 모양의 블록 하나**다.

1. 전제 확인      — 필요한 런타임·엔진이 없으면 설치 방법을 출력하고 종료(비0)
2. 환경변수 준비  — 예시 파일에서 만들고, 빠진 필수 키를 이름으로 나열
3. 의존 서비스    — 각 구성 요소마다: 이미 준비됐는가 → 없으면 준비 → 준비될 때까지 대기 → 실패 시 안내
4. 스키마·시드    — 마이그레이션 적용, 시드 투입 (대량 시드는 옵션). 마이그레이션이 아직 없으면 "건너뜀" 출력 후 계속
5. 앱 실행        — 개발 서버를 **분리된 프로세스**로 실행(스크립트가 끝나도 살아 있다). 응답 한 번으로 준비 판정.
                    앱 진입점이 아직 없으면 "건너뜀" 출력 후 0으로 종료 (실패가 아니다)
6. 요약 출력      — 접속 주소, 예시 계정, 다음에 할 것

옵션: `-Reset` / `--reset` (데이터 초기화 후 처음부터), 대량 시드 옵션,
`-Status` / `--status` (구성 요소별 준비 여부만 출력, 아무것도 띄우지 않는다),
`-Logs` / `--logs` (안 뜬 구성 요소의 로그), `-Down` / `--down` (띄운 것을 내린다).
종료 코드: 성공 0, 실패는 **단계마다 다른** 0이 아닌 값(전제 / 환경변수 / 의존 서비스 / 스키마·시드 / 앱) + 실패한 단계 이름.
그 표를 `HARNESS.md` 2절에 그대로 적는다 — 루프가 읽는 곳이 거기다.

`-Status` 는 분리된 검증 세션이 **실패를 '환경 문제'로 분류할 수 있게 하는 수단**이다.
그것이 없으면 환경 문제가 구현 실패로 보고되고, 루프가 멀쩡한 코드를 재시도 한도까지 고친다.
````

---

## 에이전트별 참고

- Claude Code에서 실행 중이면: `references/claude-code.md`
- Codex에서 실행 중이면: `references/codex.md`

**둘 다 반드시 확인할 것은 하나다** — 이 환경에서 `task_validation` 을 **어떻게 분리된 세션으로 돌리는가**.
분리 수단이 없는 환경이면 그 사실을 사용자에게 알린다. 분리된 척하는 검증은 안 하느니만 못하다 —
통과했다는 기록만 남고 실제로는 구현자가 자기 코드를 승인한 것이기 때문이다.

Files in this skill

  • SKILL.md62.2 KB
  • references/claude-code.md8.5 KB
  • references/codex.md6.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.