220 lines
12 KiB
Markdown
220 lines
12 KiB
Markdown
# QA 서브에이전트 설계 가이드 (Antigravity CLI)
|
|
|
|
빌드 하네스에 QA 에이전트를 포함할 때 참고하는 가이드. 실제 프로젝트(SatangSlide)에서 발견된 버그 패턴과 그 근본 원인 분석을 바탕으로, QA가 놓치기 쉬운 결함을 체계적으로 잡는 검증 방법론을 제공한다.
|
|
|
|
> **참고:** 이 가이드는 Antigravity CLI의 플러그인 규격에 대응합니다. 서브에이전트 정의 파일은 `.agents/plugins/{plugin-name}/agents/{name}/agent.json`으로 위치시킵니다.
|
|
|
|
---
|
|
|
|
## 목차
|
|
|
|
1. [QA 서브에이전트가 놓치는 결함의 패턴](#1-qa-서브에이전트가-놓치는-결함의-패턴)
|
|
2. [통합 정합성 검증](#2-통합-정합성-검증)
|
|
3. [QA 서브에이전트 설계 원칙](#3-qa-서브에이전트-설계-원칙)
|
|
4. [검증 체크리스트 템플릿](#4-검증-체크리스트-템플릿)
|
|
5. [QA 서브에이전트 정의 템플릿 (agent.json)](#5-qa-서브에이전트-정의-템플릿-agentjson)
|
|
|
|
---
|
|
|
|
## 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. 통합 정합성 검증
|
|
|
|
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. 도구 사용에 제한을 두지 않는다
|
|
|
|
QA 서브에이전트는 Grep으로 패턴 검색, 스크립트 실행으로 자동 대조, 필요 시 수정까지 가능해야 하므로 안전한 읽기/쓰기 도구를 기본적으로 부여한다.
|
|
|
|
### 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 서브에이전트 정의 템플릿 (agent.json)
|
|
|
|
빌드 하네스의 QA 에이전트 생성 시 활용하는 `agent.json` 파일 템플릿.
|
|
|
|
```json
|
|
{
|
|
"name": "qa-inspector",
|
|
"description": "QA 검증 전문가. 스펙 준수, 통합 정합성, 디자인 품질을 검증.",
|
|
"hidden": false,
|
|
"config": {
|
|
"customAgent": {
|
|
"systemPromptSections": [
|
|
{
|
|
"title": "Agent System Instructions",
|
|
"content": "당신은 Antigravity CLI의 전문 서브에이전트 'qa-inspector'입니다.\n\n## 핵심 역할\n스펙 대비 구현 품질과 **모듈 간 통합 정합성**을 집중 검증합니다.\n\n## 검증 우선순위\n1. **통합 정합성** (가장 높음) — 경계면 불일치가 런타임 에러의 주요 원인\n2. **기능 스펙 준수** — API/상태머신/데이터모델\n3. **디자인 품질** — 색상/타이포/반응형\n4. **코드 품질** — 미사용 코드, 명명 규칙\n\n## 검증 방법: \"양쪽 동시 읽기\"\n경계면 검증은 반드시 **양쪽 코드를 동시에 열어** 교차 비교합니다:\n- API 응답 shape: `route.ts`의 NextResponse.json() **및** `hooks/`의 fetchJson<T>\n- 라우팅: `src/app/` page 파일 경로 **및** href, router.push 값\n- 상태 전이: STATE_TRANSITIONS 맵 **및** `.update({ status })` 코드\n- DB → API → UI: 테이블 컬럼명 **및** API 응답 필드 → 타입 정의\n\n## 입력/출력 프로토콜\n- 입력: 검증 대상 코드베이스 경로, 스펙 문서\n- 출력: 검증 리포트 (`_workspace/qa_report.md`)\n * 통과/실패/미검증 항목을 아이콘으로 구분하여 기재할 것.\n * 실패 항목은 구체적 파일:라인 정보와 함께 명확한 수정 제안 코드를 포함할 것."
|
|
}
|
|
],
|
|
"toolNames": [
|
|
"view_file",
|
|
"write_to_file",
|
|
"replace_file_content",
|
|
"list_dir",
|
|
"grep_search",
|
|
"run_command"
|
|
],
|
|
"systemPromptConfig": {
|
|
"includeSections": [
|
|
"user_information",
|
|
"skills",
|
|
"messaging",
|
|
"artifacts",
|
|
"user_rules"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 실제 사례: 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/` |
|