# QA 에이전트 설계 가이드

빌드 하네스에 QA 에이전트를 포함할 때 참고하는 가이드. 실제 프로젝트(SatangSlide)에서 발견된 버그 패턴과 그 근본 원인 분석을 바탕으로, QA가 놓치기 쉬운 결함을 체계적으로 잡는 검증 방법론을 제공한다.

---

## 목차

1. QA 에이전트가 놓치는 결함의 패턴
2. 통합 정합성 검증 (Integration Coherence Verification)
3. QA 에이전트 설계 원칙
4. 검증 체크리스트 템플릿
5. QA 에이전트 정의 템플릿

---

## 1. QA 에이전트가 놓치는 결함의 패턴

### 1-1. 경계면 불일치 (Boundary Mismatch)

가장 빈번한 결함. 두 컴포넌트가 각각 "올바르게" 구현되어 있지만, 연결 지점에서 계약이 어긋남.

| 경계면 | 불일치 예시 | 놓치는 이유 |
|--------|-----------|-----------|
| API 응답 → 프론트 훅 | API가 `{ projects: [...] }` 반환, 훅이 `SlideProject[]` 기대 | 각각 개별 검증하면 정상, 교차 비교 안 함 |
| API 응답 필드명 → 타입 정의 | API가 `thumbnailUrl`(camelCase), 타입이 `thumbnail_url`(snake_case) | TypeScript 제네릭으로 캐스팅하면 컴파일러가 못 잡음 |
| 파일 경로 → 링크 href | 페이지가 `/dashboard/create`에 있는데 링크가 `/create`로 지정 | 파일 구조와 href를 교차 비교하지 않음 |
| 상태 전이 맵 → 실제 status 업데이트 | 맵에 `generating_template → template_approved` 정의, 코드에서 전환 누락 | 맵 존재 확인만 하고, 모든 업데이트 코드를 추적하지 않음 |
| API 엔드포인트 → 프론트 훅 | API 존재하지만 대응 훅 없음 (호출 안 됨) | API 목록과 훅 목록을 1:1 매핑하지 않음 |
| 즉시 응답 → 비동기 결과 | API가 즉시 `{ status }` 반환, 프론트가 `data.failedIndices` 접근 | 동기/비동기 응답 구분 없이 타입만 확인 |

### 1-2. 왜 정적 코드 리뷰로 못 잡나

- **TypeScript 제네릭의 한계**: `fetchJson<SlideProject[]>()` — 런타임 응답이 `{ projects: [...] }`여도 컴파일 통과
- **`npm run build` 통과 ≠ 정상 동작**: 타입 캐스팅, `any`, 제네릭이 사용되면 빌드는 성공하지만 런타임에 실패
- **존재 검증 vs 연결 검증의 차이**: "API가 있는가?"와 "API의 응답이 호출측의 기대와 일치하는가?"는 전혀 다른 검증

---

## 2. 통합 정합성 검증 (Integration Coherence Verification)

QA 에이전트에 반드시 포함해야 하는 **교차 비교 검증** 영역.

### 2-1. API 응답 ↔ 프론트 훅 타입 교차 검증

**방법**: 각 API route의 `NextResponse.json()` 호출부와 대응 훅의 `fetchJson<T>` 타입 파라미터를 비교.

```
검증 단계:
1. API route에서 NextResponse.json()에 전달하는 객체의 shape 추출
2. 대응 훅에서 fetchJson<T>의 T 타입 확인
3. shape과 T가 일치하는지 비교
4. 래핑 여부 확인 (API가 { data: [...] }를 반환하면 훅이 .data를 꺼내는지)
```

**특히 주의할 패턴:**
- 페이지네이션 API: `{ items: [], total, page }` vs 프론트가 배열 기대
- snake_case DB 필드 → camelCase API 응답 → 프론트 타입 정의 간 불일치
- 즉시 응답 (202 Accepted) vs 최종 결과의 shape 차이

### 2-2. 파일 경로 ↔ 링크/라우터 경로 매핑

**방법**: `src/app/` 하위 page 파일의 URL 경로를 추출하고, 코드 내 모든 `href`, `router.push()`, `redirect()` 값과 대조.

```
검증 단계:
1. src/app/ 하위 page.tsx 파일 경로에서 URL 패턴 추출
   - (group) → URL에서 제거
   - [param] → 동적 세그먼트
2. 코드 내 모든 href=, router.push(, redirect( 값 수집
3. 각 링크가 실제 존재하는 page 경로와 매칭되는지 확인
4. route group 내부 페이지의 URL 접두사 주의 (예: dashboard/ 하위)
```

### 2-3. 상태 전이 완전성 추적

**방법**: 코드에서 모든 `status:` 업데이트를 추출하여 상태 전이 맵과 대조.

```
검증 단계:
1. 상태 전이 맵(STATE_TRANSITIONS)에서 허용된 전이 목록 추출
2. 모든 API route에서 .update({ status: "..." }) 패턴 검색
3. 각 전이가 맵에 정의되어 있는지 확인
4. 맵에 정의된 전이 중 코드에서 실행되지 않는 것 식별 (죽은 전이)
5. 특히: 중간 상태(예: generating_template)에서 최종 상태(template_approved)로의 전환이 누락되지 않았는지
```

### 2-4. API 엔드포인트 ↔ 프론트 훅 1:1 매핑

**방법**: 모든 API route와 프론트 훅을 나열하여 짝이 맞는지 확인.

```
검증 단계:
1. src/app/api/ 하위 route.ts에서 HTTP 메서드별 엔드포인트 목록 추출
2. src/hooks/ 하위 use*.ts에서 fetch 호출 URL 목록 추출
3. API 엔드포인트 중 훅에서 호출하지 않는 것 식별 → "사용 안 됨" 플래그
4. "사용 안 됨"이 의도적인지 (관리 API 등) 아닌지 (호출 누락) 판단
```

---

## 3. QA 에이전트 설계 원칙

### 3-1. Explore 타입이 아닌 general-purpose 타입을 사용하라

QA 에이전트가 `Explore` 타입이면 읽기만 가능하다. 하지만 효과적인 QA는:
- Grep으로 패턴 검색 (모든 `NextResponse.json()` 추출)
- 스크립트 실행으로 자동 대조 (API shape vs 훅 타입)
- 필요 시 수정까지 가능

**권장**: `general-purpose` 타입으로 설정하되, 에이전트 정의에서 "검증 → 리포트 → 수정 요청" 프로토콜을 명시.

### 3-2. 체크리스트는 "존재 확인"보다 "교차 비교"를 우선하라

| 약한 체크리스트 | 강한 체크리스트 |
|---------------|---------------|
| API 엔드포인트가 존재하는가? | API 엔드포인트의 응답 shape과 대응 훅의 타입이 일치하는가? |
| 상태 전이 맵이 정의되어 있는가? | 모든 status 업데이트 코드가 맵의 전이와 일치하는가? |
| 페이지 파일이 존재하는가? | 코드 내 모든 링크가 실제 존재하는 페이지를 가리키는가? |
| TypeScript strict mode인가? | 제네릭 캐스팅으로 우회된 타입 안전성이 없는가? |

### 3-3. "양쪽을 동시에 읽어라" 원칙

QA가 경계면 버그를 잡으려면, 한쪽만 읽어선 안 된다. 반드시:
- API route **와** 대응 훅을 **같이** 읽고
- 상태 전이 맵 **와** 실제 업데이트 코드를 **같이** 읽고
- 파일 구조 **와** 링크 경로를 **같이** 읽어야 한다

에이전트 정의에 이 원칙을 명시적으로 기재하라.

### 3-4. QA는 빌드 후가 아니라, 각 모듈 완성 직후에 실행하라

오케스트레이터에서 QA를 "Phase 4: 전체 완성 후"에만 배치하면:
- 버그가 누적되어 수정 비용이 높아짐
- 초기 경계면 불일치가 후속 모듈에 전파됨

**권장 패턴**: 각 백엔드 API 완성 시 즉시 해당 API + 대응 훅의 교차 검증 수행 (incremental QA).

---

## 4. 검증 체크리스트 템플릿

QA 에이전트 정의에 포함할 웹 애플리케이션용 통합 정합성 체크리스트.

```markdown
### 통합 정합성 검증 (웹 앱)

#### API ↔ 프론트엔드 연결
- [ ] 모든 API route의 응답 shape과 대응 훅의 제네릭 타입이 일치
- [ ] 래핑된 응답({ items: [...] })은 훅에서 unwrap하는지 확인
- [ ] snake_case ↔ camelCase 변환이 일관되게 적용
- [ ] 즉시 응답(202)과 최종 결과의 shape이 프론트에서 구분되는지 확인
- [ ] 모든 API 엔드포인트에 대응하는 프론트 훅이 존재하고 실제로 호출됨

#### 라우팅 정합성
- [ ] 코드 내 모든 href/router.push 값이 실제 page 파일 경로와 매칭
- [ ] route group ((group))이 URL에서 제거되는 것을 고려한 경로 검증
- [ ] 동적 세그먼트([id])가 올바른 파라미터로 채워지는지 확인

#### 상태 머신 정합성
- [ ] 정의된 모든 상태 전이가 코드에서 실행됨 (죽은 전이 없음)
- [ ] 코드의 모든 status 업데이트가 전이 맵에 정의됨 (무단 전이 없음)
- [ ] 중간 상태에서 최종 상태로의 전환이 누락되지 않음
- [ ] 프론트에서 상태 기반 분기(if status === "X")의 X가 실제 도달 가능

#### 데이터 흐름 정합성
- [ ] DB 스키마 필드명과 API 응답 필드명의 매핑이 일관됨
- [ ] 프론트 타입 정의와 API 응답의 필드명이 일치
- [ ] 옵셔널 필드에 대한 null/undefined 처리가 양쪽에서 일관됨
```

---

## 5. QA 에이전트 정의 템플릿

빌드 하네스의 QA 에이전트에 포함할 핵심 섹션.

```markdown
---
name: qa-inspector
description: "QA 검증 전문가. 스펙 준수, 통합 정합성, 디자인 품질을 검증."
---

# QA Inspector

## 핵심 역할
스펙 대비 구현 품질과 **모듈 간 통합 정합성**을 검증한다.

## 검증 우선순위

1. **통합 정합성** (가장 높음) — 경계면 불일치가 런타임 에러의 주요 원인
2. **기능 스펙 준수** — API/상태머신/데이터모델
3. **디자인 품질** — 색상/타이포/반응형
4. **코드 품질** — 미사용 코드, 명명 규칙

## 검증 방법: "양쪽 동시 읽기"

경계면 검증은 반드시 **양쪽 코드를 동시에 열어** 비교한다:

| 검증 대상 | 왼쪽 (생산자) | 오른쪽 (소비자) |
|----------|-------------|---------------|
| API 응답 shape | route.ts의 NextResponse.json() | hooks/의 fetchJson<T> |
| 라우팅 | src/app/ page 파일 경로 | href, router.push 값 |
| 상태 전이 | STATE_TRANSITIONS 맵 | .update({ status }) 코드 |
| DB → API → UI | 테이블 컬럼명 | API 응답 필드 → 타입 정의 |

## 팀 통신 프로토콜

- 발견 즉시 해당 에이전트에게 구체적 수정 요청 (파일:라인 + 수정 방법)
- 경계면 이슈는 양쪽 에이전트 **모두**에게 알림
- 리더에게: 검증 리포트 (통과/실패/미검증 항목 구분)
```

---

## 실제 사례: SatangSlide에서 발견된 버그

이 가이드의 모든 내용은 아래 실제 버그에서 추출한 교훈이다:

| 버그 | 경계면 | 원인 |
|------|--------|------|
| `projects?.filter is not a function` | API→훅 | API가 `{projects:[]}` 반환, 훅이 배열 기대 |
| 대시보드 모든 링크 404 | 파일경로→href | `/dashboard/` 접두사 누락 |
| 테마 이미지 안 보임 | API→컴포넌트 | `thumbnailUrl` vs `thumbnail_url` |
| 테마 선택 저장 안 됨 | API→훅 | select-theme API 존재, 훅 없음 |
| 생성 페이지 영원히 대기 | 상태전이→코드 | `template_approved` 전이 코드 누락 |
| `data.failedIndices` 크래시 | 즉시응답→프론트 | 백그라운드 결과를 즉시 응답에서 접근 |
| 완료 후 슬라이드 보기 404 | 파일경로→href | `/projects/` → `/dashboard/projects/` |
