# 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()` — 런타임 응답이 `{ projects: [...] }`여도 컴파일 통과. - **`npm run build` 통과 ≠ 정상 동작**: 타입 캐스팅, `any`, 제네릭이 사용되면 빌드는 성공하지만 런타임에 실패. - **존재 검증 vs 연결 검증의 차이**: "API가 있는가?"와 "API의 응답이 호출측의 기대와 일치하는가?"는 전혀 다른 검증. --- ## 2. 통합 정합성 검증 QA 서브에이전트에 반드시 포함해야 하는 **교차 비교 검증** 영역. ### 2-1. API 응답 ↔ 프론트 훅 타입 교차 검증 **방법**: 각 API route의 `NextResponse.json()` 호출부와 대응 훅의 `fetchJson` 타입 파라미터를 비교. ``` 검증 단계: 1. API route에서 NextResponse.json()에 전달하는 객체의 shape 추출 2. 대응 훅에서 fetchJson의 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\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/` |