📝 docs: 여러 문서 및 파일 삭제

This commit is contained in:
gil
2026-05-21 21:56:04 +09:00
parent 607ef1a435
commit 8876998acd
51 changed files with 7076 additions and 3085 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -0,0 +1,59 @@
# 🧠 프로젝트 자율 인지 메모리 (Dreaming Context)
이 파일은 `dreaming.js` 스크립트에 의해 프로젝트 코드베이스를 분석하여 자동 생성되었습니다.
Claude Code가 프로젝트의 실시간 코드 구조, 사용 중인 스토어, 컴포넌트 레이아웃, 그리고 최신 개발 흐름을 완벽히 인지하도록 돕습니다.
* **최종 동기화 시간:** 2026. 5. 21. 오후 9:20:21 (Asia/Seoul)
---
## 🏗 프로젝트 정보
* **프로젝트명:** `Unknown Project` (v1.0.0)
* **핵심 프레임워크:** `Unknown`
* **기술 스택 라이브러리:**
- (감지된 주요 라이브러리 없음)
---
## 📁 디렉토리 구조 및 컴포넌트 현황
현재 활성화되어 있는 프로젝트 레이아웃 정보입니다.
| 디렉토리 | 활성 여부 | 파일 개수 | 주요 샘플 파일 (최대 5개) |
|---|---|---|---|
| `components/` | ❌ | 0개 | - |
| `composables/` | ❌ | 0개 | - |
| `stores/` | ❌ | 0개 | - |
| `pages/` | ❌ | 0개 | - |
| `server/` | ❌ | 0개 | - |
| `layouts/` | ❌ | 0개 | - |
| `middleware/` | ❌ | 0개 | - |
| `plugins/` | ❌ | 0개 | - |
| `types/` | ❌ | 0개 | - |
| `assets/` | ❌ | 0개 | - |
---
## 🍍 액티브 Pinia 스토어 목록
현재 코드베이스에 존재하는 글로벌 상태 저장소들의 템플릿 정보입니다. 새 기능을 개발할 때 아래 스토어를 재사용하거나 참고하세요.
*감지된 Pinia 스토어가 없습니다. (stores/ 디렉토리 없음 혹은 비어있음)*
---
## 🎣 커스텀 Composable (useXxx) 목록
다양한 비즈니스 로직과 부수효과를 격리해 둔 커스텀 훅 목록입니다. 컴포넌트 내부에서 비즈니스 로직을 직접 짜기 전, 아래 훅들의 재사용 가능성을 먼저 타진하세요.
*감지된 커스텀 Composable이 없습니다. (composables/ 디렉토리 없음 혹은 비어있음)*
---
## 🧪 유닛 테스트 통계
현재까지 구축된 테스트 커버리지 현황입니다.
* **감지된 테스트 파일 수:** `0개`
*(새 기능을 추가할 때 반드시 Vitest 규격의 유닛 테스트를 함께 작성해야 함)*
---
## 🛠 실행 가능한 스크립트 (package.json)
프로젝트 구동 및 테스트 검증을 위해 사용 가능한 명령어 리스트입니다.
- 스크립트 없음

View File

@@ -0,0 +1,107 @@
# 🤖 프론트엔드 에이전트 자동화 시스템 가이드
이 문서는 `gameservice-fe-agent` 패키지에 탑재된 두 가지 핵심 자동화 프로세스—**프로젝트 자율 인지(Dreaming) 시스템** 및 **AI 개발 스쿼드(Squad) 오케스트레이션**의 개념과 실행 방법을 상세히 가이드합니다.
이 도구들은 일회성(Stateless) AI 코드 작성의 한계를 넘어, **지속성 있는 컨벤션 수호자(Convention Guardian)**로 동작하고 복잡한 컴포넌트를 **병렬 전문 역할 분담 스쿼드**를 통해 해결하는 것을 목적으로 합니다.
---
## 🧠 1. "AI 코더"에서 "상태 저장형 컨벤션 가디언"으로 (Dreaming)
### 💡 도입 배경 및 개념
일반적인 LLM 코딩 에이전트는 프롬프트를 보낼 때마다 컨텍스트가 초기화되는 **단발성(Stateless)** 모델로 동작합니다. 이 때문에 기존 프로젝트의 폴더 구조, 이미 만들어진 커스텀 Composable(useXxx), 활성화된 Pinia 스토어 목록, 패키지 버전 등을 매번 인지하지 못해 불필요한 코드를 중복 구현하거나 기존 컨벤션을 깨는 실수를 저지릅니다.
**Dreaming 자동화 시스템**은 에이전트가 "코드베이스 전체를 정기적으로 자율 성찰(Self-reflection)"하도록 만드는 로컬 구현체입니다. `dreaming.js`를 구동하면 프로젝트의 상태를 휴리스틱하게 분석하여 `.claude/project/dreaming-context.md` 파일에 기록하고, `CLAUDE.md` 수입 선언을 통해 에이전트가 시작부터 이 최신 상태를 인지하게 만듭니다.
### 🛠 실행 및 연동 방법
#### ① 실행 명령어
프로젝트 루트 경로에서 아래 스크립트를 구동합니다:
```bash
node .claude/skills/dreaming/scripts/dreaming.js
```
#### ② 자동 수행되는 태스크
1. `package.json` 파싱: 프레임워크 버전, 핵심 기술 스택(Nuxt, Pinia, Tailwind, Vitest 등), 실행 가능한 스크립트 명령어 수집.
2. 디렉토리 구조 스캔: 활성화된 디렉토리와 소속 파일 개수, 구조적 예시 목록 도출.
3. **Pinia 스토어 자율 추출:** `stores/` 내부의 파일들을 분석하여 각 스토어의 ID, 반응형 상태(state/computed), 비즈니스 함수(actions) 목록을 정밀 인지.
4. **커스텀 Composable 자율 추출:** `composables/` 내부의 `useXxx` 스타일 컴포저블을 검출해 노출 함수 리스트 확보.
5. **유닛 테스트 통계 파싱:** 구축된 테스트 파일 목록 및 통계 추출.
6. **마크다운 출력 및 자동 임포트:** 수집된 정보를 마크다운 리포트로 자동 빌드해 `.claude/project/dreaming-context.md` 파일에 덮어쓰고, 루트의 `CLAUDE.md``@.claude/project/dreaming-context.md` 임포트 구문이 없으면 이를 자동으로 연결합니다.
### 📌 시니어 FE 관점의 기대 효과
* **중복 코드 생성 전면 억제:** 에이전트가 이미 존재하는 Pinia 스토어나 커스텀 훅을 즉시 찾아내기 때문에, 동일한 API 바인딩이나 헬퍼 함수를 이중으로 작성하지 않습니다.
* **프로젝트 맞춤형 가상 주니어화:** 며칠 쉬고 오거나 세션이 만료되더라도, 에이전트가 단 1초 만에 프로젝트의 최신 스냅샷을 뇌리에 새긴 상태(Stateful)로 지능적인 보조를 시작합니다.
---
## 👥 2. "AI 한 명과 대화"가 아닌 "AI 개발 스쿼드(Squad) 오케스트레이션"
### 💡 도입 배경 및 개념
아무리 성능이 뛰어난 모델이라도 템플릿 마크업 작성, 복잡한 Tailwind 스타일링, WCAG 2.1 AA 접근성 마크업, 그리고 Vitest 단위 테스트 작성을 한 번에 지시하면 문맥 누락이나 결함(Bug)이 발생하기 마련입니다.
**스쿼드 오케스트레이션**은 하나의 피처 요청을 **3인의 가상 전문 개발 에이전트**로 쪼개어 단계별/병렬 협업 파이프라인으로 수행하는 시스템입니다. 시니어 프론트엔드 엔지니어(Gil)는 코드 작성에 시간을 소모하는 대신, 이 전문 에이전트들을 조율하고 합산 결과물의 비즈니스 사양과 렌더링을 최종 승인하는 **오케스트레이터(Orchestrator)**의 최고 존엄 지위를 갖게 됩니다.
```
┌──────────────────────┐
│ Gil (Orchestrator) │
└──────────┬───────────┘
│ (스쿼드 생성 지시)
┌────────────────────────────────────────────────────────┐
│ squad-orchestrator.js │
└────────────┬───────────────┬───────────────┬───────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐┌──────────────┐┌──────────────┐
│ Role 1 ││ Role 2 ││ Role 3 │
│ UI/마크업 ││ 웹 접근성 ││ QA/단위테스트│
│ 스페셜리스트 ││ 스페셜리스트 ││ 스페셜리스트 │
└───────┬──────┘└───────┬──────┘└───────┬──────┘
│ │ │
▼ ▼ ▼
[구조 & 뼈대 빌드] [ARIA & 키보드 주입] [Vitest 케이스 PASS]
```
### 🛠 실행 및 연동 방법
#### ① 스쿼드 조직하기 (인터랙션/CLI 겸용)
새로운 피처나 컴포넌트를 설계할 때 아래 스크립트를 구동합니다:
* **대화형 모드로 조직하기:**
```bash
node .claude/skills/squad-orchestration/scripts/squad-orchestrator.js
```
명령어 실행 후 터미널의 질문에 따라 컴포넌트 이름과 요구 스펙을 입력하면 스쿼드가 즉시 조직됩니다.
* **CLI 인자로 한 번에 조직하기:**
```bash
node .claude/skills/squad-orchestration/scripts/squad-orchestrator.js --name "UserScoreCard" --spec "유저 프로필과 전적, 랭킹을 보여주고 점수에 따라 테두리 색상이 바뀌는 컴포넌트 구현"
```
#### ② 스쿼드 파일 구성 및 역할
명령이 끝나면 루트에 `squad/<ComponentName>/` 폴더가 자동 생성됩니다.
* **`tasks/01_markup_agent.md` (Role 1):** Vue 3 템플릿, 데이터 상태 설계, Tailwind 반응형 레이아웃 구성 집중 지시서.
* **`tasks/02_a11y_agent.md` (Role 2):** WCAG 2.1 AA 기준 준수, ARIA 역할(role), 스크린 리더용 라벨링, 키보드 인터랙션 집중 보완 및 Surgical 정밀 수정 지시서.
* **`tasks/03_test_agent.md` (Role 3):** 생성된 컴포넌트 사양 검증을 위한 `*.spec.ts` 단위 테스트 코드 및 Mock 데이터 구축 지시서.
* **`run-squad.js`:** Claude Code CLI를 연속 구동하여 세 단계의 코딩 에이전트를 차례로 자동 실행하고, 마지막에 Vitest 테스트 엔진을 가동하여 검증을 완수해 내는 마스터 러너 스크립트.
#### ③ 스쿼드 자동 파이프라인 가동
스쿼드가 세팅되면, 해당 컴포넌트 폴더 내의 마스터 스크립트를 즉시 가동하여 오토파일럿 개발을 시작할 수 있습니다:
```bash
node squad/<ComponentName>/run-squad.js
```
### 📌 시니어 FE 관점의 기대 효과
* **결점 제로(Defect-Free) 컴포넌트 완성:** 각 역할군이 하나의 관점(UI 구조 -> 접근성 -> 테스트 품질)에 완벽히 몰입하여 단계별로 코드를 가꾸고 다듬기 때문에, 품질적으로 완벽무결한 컴포넌트가 조립됩니다.
* **테스트 주도 개발(TDD)의 정수 체득:** 최종 테스트가 완료될 때까지 에이전트 루프가 가동되므로, 코드를 올리기도 전에 모든 단위 동작과 가시적인 비즈니스 엣지 케이스들의 통과를 보장받은 채 개발이 마무리됩니다.
---
## 🚀 실무 도입 시 베스트 프랙티스
1. **지속성 확보를 위한 git ignore 추가 권장:**
`squad/` 폴더 내의 작업 지시서나 가이드 파일들은 개발 도중의 중간 생성물(Task sheets)에 해당하므로, 프로젝트의 메인 git 히스토리를 깔끔하게 유지하기 위해 `.gitignore`에 `squad/` 경로를 추가하는 것을 추천합니다.
2. **개발 전 dreaming 구동 루틴화:**
새로운 브랜치를 따거나 대규모 PR을 머지받았을 때는 에이전트에게 일을 시키기 전 `node .claude/skills/dreaming/scripts/dreaming.js`를 한 번 실행해 주는 것이 좋습니다. 에이전트의 뇌 스냅샷을 1초 만에 최신화해 줍니다.

View File

@@ -0,0 +1,62 @@
# Claude 작업 방식 지침
이 문서는 Claude가 팀 프로젝트에서 작업할 때 따라야 할 일반적인 원칙을 정의합니다.
## 기본 원칙
1. **기존 코드 존중**: 수정 전에 관련 파일과 주변 컨벤션을 먼저 파악합니다.
2. **최소 변경**: 요구사항을 충족하는 최소한의 변경만 수행합니다. 관련 없는 리팩토링은 별도 작업으로 분리합니다.
3. **가정 대신 질문**: 요구사항이 모호하면 추측하지 말고 사용자에게 확인합니다.
4. **근거 있는 수정**: 코드 변경의 이유를 설명할 수 있어야 합니다.
## 작업 순서
1. **탐색 (Explore)**
- 관련 파일을 먼저 읽고 프로젝트 구조를 파악합니다.
- 유사한 패턴이 이미 존재하는지 확인합니다.
2. **계획 (Plan)**
- 여러 파일을 수정하거나 복잡한 작업이면 할 일 목록을 만들어 공유합니다.
- 아키텍처에 영향을 주는 변경은 착수 전에 사용자 승인을 받습니다.
3. **구현 (Implement)**
- 한 번에 하나의 논리적 변경에 집중합니다.
- 공통 지침과 프로젝트 지침을 모두 준수합니다.
4. **검증 (Verify)**
- 린트 / 타입체크 / 빌드가 깨지지 않는지 확인합니다.
- 테스트가 있는 프로젝트라면 관련 테스트를 실행합니다.
- 수동 검증이 필요한 경우 확인 방법을 사용자에게 안내합니다.
## 해서는 안 되는 것
- **임의 기능 추가 금지**: 사용자가 요청하지 않은 기능을 추가하지 않습니다.
- **기존 코드 대량 리팩토링 금지**: 요청 범위를 벗어나는 변경은 하지 않습니다.
- **주석 / 문서 임의 삭제 금지**: 불필요해 보여도 삭제 전 사용자에게 확인합니다.
- **비밀정보 출력 금지**: 환경변수, 키, 토큰 등은 코드에 하드코딩하지 않습니다.
- **의존성 버전 임의 변경 금지**: 요청이 없다면 `package.json`의 버전을 변경하지 않습니다.
- **강제 푸시 / 히스토리 재작성 금지**: `push --force`, `reset --hard` 등은 사용자의 명시적 요청 없이 실행하지 않습니다.
## 커뮤니케이션
- 답변은 간결하게, 결론을 먼저 말합니다.
- 코드를 수정했다면 **어떤 파일을 어떻게 바꿨는지** 요약합니다.
- 불확실한 부분은 솔직하게 밝히고 대안을 제시합니다.
- 긴 설명보다 실제 결과물(코드/파일)을 우선합니다.
## 파일 작업 원칙
- 새 파일 생성보다 **기존 파일 수정**을 우선합니다.
- README, 문서는 사용자가 명시적으로 요청했을 때만 생성합니다.
- 파일을 읽지 않고 수정하지 않습니다.
- 대량 변경 시에는 diff를 확인할 수 있도록 단계별로 진행합니다.
## 질문이 필요한 상황
다음의 경우 반드시 사용자에게 확인을 요청합니다.
- 요구사항의 일부가 불명확할 때
- 여러 구현 방식이 있고 각각 장단점이 뚜렷할 때
- 공통 지침과 프로젝트 지침이 충돌할 때
- 파괴적 작업(파일 삭제, 데이터 마이그레이션, 스키마 변경 등)이 필요할 때
- 외부 서비스 호출이나 결제 관련 작업일 때

View File

@@ -0,0 +1,50 @@
# 코딩 컨벤션
## 기본 원칙
- **가독성 우선**: 영리한 코드보다 읽기 쉬운 코드를 선호합니다.
- **일관성 유지**: 기존 코드의 스타일을 먼저 관찰하고 그에 맞춥니다.
- **작은 단위**: 함수와 파일은 한 가지 책임만 지도록 작게 유지합니다.
## 포맷팅
- 들여쓰기: 스페이스 2칸 (탭 사용 금지)
- 문자열: 싱글 쿼터(`'`) 사용, JSX/템플릿 속성값은 더블 쿼터(`"`)
- 세미콜론: 생략하지 않고 항상 작성
- 줄 끝 공백 제거, 파일 끝 개행 1줄 유지
- 한 줄 최대 100자 (초과 시 줄바꿈)
- Prettier 설정 파일(`.prettierrc`)이 있는 경우 해당 설정을 우선합니다.
## 네이밍
- **변수/함수**: `camelCase` (예: `userProfile`, `fetchUserData`)
- **상수**: `UPPER_SNAKE_CASE` (예: `MAX_RETRY_COUNT`)
- **컴포넌트/클래스/타입**: `PascalCase` (예: `UserCard`, `OrderStatus`)
- **파일명**
- Vue 컴포넌트: `PascalCase.vue` (예: `UserCard.vue`)
- Composable: `use` 접두사 + `camelCase` (예: `useAuth.ts`)
- 일반 TS 모듈: `kebab-case.ts` (예: `format-date.ts`)
- **이벤트 핸들러**: `handle` 또는 `on` 접두사 (예: `handleClick`, `onSubmit`)
- **불리언**: `is`, `has`, `can`, `should` 접두사 (예: `isLoading`, `hasError`)
## 타입
- `any` 사용 금지. 불가피할 경우 주석으로 이유를 남기고 `unknown`을 먼저 고려합니다.
- 함수 시그니처에는 매개변수와 반환 타입을 명시합니다.
- 공개 API(타 모듈에서 import 되는 것)는 반드시 타입을 export 합니다.
- 유니온 타입은 `as const` 또는 별도 타입 alias로 관리합니다.
## 주석
- "무엇을" 보다 "왜"를 설명합니다.
- TODO/FIXME 주석에는 작성자와 날짜 또는 이슈 번호를 포함합니다.
- 공개 함수/컴포넌트에는 JSDoc 한 줄 설명을 권장합니다.
## import 순서
1. 외부 라이브러리 (예: `vue`, `nuxt`)
2. 내부 절대 경로 (예: `~/components/...`)
3. 상대 경로 (예: `./utils`)
4. 타입 only import는 각 그룹 내에서 별도 블록으로 분리
그룹 사이에는 빈 줄을 한 줄 둡니다.

View File

@@ -0,0 +1,83 @@
# 커밋 / PR 규칙
## 커밋 메시지
[Conventional Commits](https://www.conventionalcommits.org/)를 따릅니다.
```
<type>(<scope>): <subject>
<body>
<footer>
```
### type
- `feat`: 새로운 기능 추가
- `fix`: 버그 수정
- `refactor`: 기능 변화 없는 구조 개선
- `style`: 코드 포맷/세미콜론 등 스타일 변경
- `docs`: 문서 수정
- `test`: 테스트 추가/수정
- `chore`: 빌드, 설정, 패키지 업데이트 등
- `perf`: 성능 개선
- `ci`: CI 설정 변경
### 작성 규칙
- **subject**는 50자 이내, 명령형 현재 시제(예: `add`, `fix``added`, `fixes` 아님)
- subject 끝에 마침표를 찍지 않습니다.
- body는 "무엇을"보다 "왜"를 설명합니다. 한 줄 72자 이내로 줄바꿈합니다.
- 한 커밋에는 하나의 논리적 변경만 담습니다.
### 예시
```
feat(user): add profile image upload
프로필 이미지 업로드 요구사항에 따라 multipart 업로드 경로를 추가했습니다.
기존 텍스트 필드 업데이트 API는 변경하지 않았습니다.
Refs: #123
```
## Pull Request
### 제목
커밋 메시지와 동일한 컨벤션을 따릅니다. (`<type>(<scope>): <subject>`)
### 본문 템플릿
```markdown
## 변경 사항
- 무엇이 바뀌었는지 요약
## 배경 / 이유
- 왜 이 변경이 필요했는지
## 테스트
- 어떻게 검증했는지 (수동/자동 테스트 내용)
## 스크린샷 (UI 변경 시)
- Before / After
## 체크리스트
- [ ] 로컬에서 빌드/테스트 통과
- [ ] 린트/포맷 통과
- [ ] 공통 지침(gameservice-fe-agent) 준수
- [ ] 관련 문서 업데이트
```
### 리뷰 기준
- 최소 1명 이상의 승인 필요
- CI(Lint / Test / Build) 전부 통과 필요
- 머지 전략은 **Squash and merge**를 기본으로 합니다.
- 리뷰어는 변경 범위에 대해 질문이 남지 않도록 배경을 충분히 이해한 뒤 승인합니다.
### Draft PR
- 작업 중간 중간 피드백이 필요한 경우 Draft로 먼저 올리는 것을 권장합니다.
- Draft 상태에서는 CI 실패가 있어도 괜찮습니다.

View File

@@ -0,0 +1,47 @@
# 프레임워크 / 라이브러리 사용 규칙
## Vue 3 / Nuxt
### 컴포넌트 작성
- **`<script setup lang="ts">` 사용**을 기본으로 합니다. Options API는 신규 코드에서 사용하지 않습니다.
- 컴포넌트는 단일 책임 원칙을 지키며, 200줄을 넘으면 분리를 검토합니다.
- Props는 `defineProps<T>()` 제네릭 형태로 타입을 명시합니다.
- Emits는 `defineEmits<{ ... }>()` 제네릭 형태로 선언합니다.
- `ref` vs `reactive`: 원시값과 단일 객체는 `ref`, 복잡한 상태 트리는 `reactive`를 고려합니다. 일관성을 위해 팀 내에서 가능한 `ref`를 우선합니다.
### 상태 관리
- 컴포넌트 내 로컬 상태: `ref` / `reactive`
- 여러 컴포넌트가 공유하는 상태: **Pinia** 사용
- 서버 상태: Nuxt `useFetch` / `useAsyncData` 사용, 직접 `fetch` 호출은 지양합니다.
### 라우팅
- Nuxt의 파일 기반 라우팅을 사용합니다. 수동 라우트 정의는 특수한 경우에만 허용됩니다.
- 동적 라우트 파라미터는 `[param].vue` 형식을 사용합니다.
### Composable
- 재사용 가능한 로직은 `composables/` 디렉토리의 `useXxx` 함수로 추출합니다.
- Composable은 부수효과를 최소화하고, 반환 객체에 상태와 메서드를 함께 묶어 반환합니다.
## TypeScript
- `strict: true`를 유지합니다.
- 공용 타입은 `types/` 또는 각 도메인의 `types.ts`에 모아둡니다.
- 외부 API 응답은 반드시 타입을 정의하여 사용합니다.
## Tailwind CSS
- **유틸리티 클래스 우선** 사용. 공통 패턴은 컴포넌트로 추출합니다.
- `@apply`는 꼭 필요한 경우에만 사용하고, 가능한 유틸리티를 직접 나열합니다.
- 임의값 클래스(`w-[123px]`)는 디자인 시스템에 등록되지 않은 값에만 제한적으로 사용합니다.
- 조건부 클래스는 `clsx` 또는 `cn` 유틸리티를 사용하여 가독성을 확보합니다.
- 클래스 순서는 Tailwind 공식 프리셋(`prettier-plugin-tailwindcss`)을 따릅니다.
## 외부 라이브러리 도입
- 새로운 라이브러리를 추가할 때는 **PR 설명에 도입 이유, 번들 영향, 대안 검토 내용**을 기록합니다.
- 동일 기능의 라이브러리를 중복 도입하지 않습니다.
- 유지보수가 중단된 패키지(6개월 이상 업데이트 없음)는 도입하지 않습니다.

96
.claude/scripts/init-project.sh Executable file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env bash
#
# gameservice-fe-agent project template initializer
# 이미 .claude/common 이 설치된 프로젝트에서 templates/project/ 의
# 양식을 .claude/project/ 에 복사합니다.
#
# 사용법:
# bash .claude/common/scripts/init-project.sh # 없는 파일만 복사 (기본)
# bash .claude/common/scripts/init-project.sh --force # 기존 파일을 덮어씀
# bash .claude/common/scripts/init-project.sh --diff # 차이만 보여주고 복사는 안 함
#
set -euo pipefail
COMMON_PATH=".claude/common"
PROJECT_PATH=".claude/project"
TEMPLATE_DIR="$COMMON_PATH/templates/project"
MODE="safe" # safe | force | diff
for arg in "$@"; do
case "$arg" in
--force) MODE="force" ;;
--diff) MODE="diff" ;;
-h|--help)
grep '^#' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
*)
echo "❌ 알 수 없는 옵션: $arg" >&2
exit 1
;;
esac
done
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "❌ 현재 디렉토리는 Git 저장소가 아닙니다." >&2
exit 1
fi
cd "$(git rev-parse --show-toplevel)"
if [[ ! -d "$TEMPLATE_DIR" ]]; then
echo "$TEMPLATE_DIR 가 없습니다. 먼저 submodule 을 설치/업데이트하세요:" >&2
echo " git submodule update --init --recursive" >&2
exit 1
fi
mkdir -p "$PROJECT_PATH"
echo "📝 templates/project → $PROJECT_PATH (mode=$MODE)"
echo ""
shopt -s nullglob
copied=0
skipped=0
diffed=0
for f in "$TEMPLATE_DIR"/*.md; do
name="$(basename "$f")"
dest="$PROJECT_PATH/$name"
case "$MODE" in
diff)
if [[ -f "$dest" ]]; then
if ! diff -q "$f" "$dest" >/dev/null 2>&1; then
echo "📄 diff: $dest"
diff -u "$dest" "$f" || true
echo ""
diffed=$((diffed + 1))
fi
else
echo " 새 양식: $dest (복사되지 않음. --force 로 복사하세요)"
diffed=$((diffed + 1))
fi
;;
force)
cp "$f" "$dest"
echo "$dest (덮어씀)"
copied=$((copied + 1))
;;
safe)
if [[ -f "$dest" ]]; then
echo "$dest (이미 존재)"
skipped=$((skipped + 1))
else
cp "$f" "$dest"
echo "$dest"
copied=$((copied + 1))
fi
;;
esac
done
echo ""
case "$MODE" in
diff) echo "🔍 차이가 있는 파일: $diffed";;
*) echo "🎉 완료: 복사 $copied개 / 건너뜀 $skipped개";;
esac

209
.claude/scripts/install.sh Executable file
View File

@@ -0,0 +1,209 @@
#!/usr/bin/env bash
#
# gameservice-fe-agent installer
# 현재 Git 프로젝트의 .claude/common 경로에 gameservice-fe-agent 저장소를
# submodule 로 추가하고, templates/ 에서 프로젝트 지침 양식과
# CLAUDE.md 템플릿을 복사합니다.
#
# 사용법:
# bash scripts/install.sh <repo-url> [<branch>]
#
# 예:
# bash scripts/install.sh https://git.sginfra.net/sgp-web-d/gameservice-fe-agent master
#
set -euo pipefail
REPO_URL="${1:-}"
BRANCH="${2:-master}"
TARGET_PATH=".claude/common"
PROJECT_PATH=".claude/project"
if [[ -z "$REPO_URL" ]]; then
echo "❌ 사용법: bash scripts/install.sh <repo-url> [branch]" >&2
echo " 예: bash scripts/install.sh https://git.sginfra.net/sgp-web-d/gameservice-fe-agent master" >&2
exit 1
fi
# Git 프로젝트인지 확인
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "❌ 현재 디렉토리는 Git 저장소가 아닙니다. 먼저 'git init' 후 실행해주세요." >&2
exit 1
fi
# 루트로 이동
cd "$(git rev-parse --show-toplevel)"
# 1) Submodule 추가
if [[ -d "$TARGET_PATH" ]]; then
echo "⚠️ '$TARGET_PATH' 경로가 이미 존재합니다. submodule 추가를 건너뜁니다."
else
echo "📦 gameservice-fe-agent 를 submodule 로 추가합니다..."
git submodule add -b "$BRANCH" "$REPO_URL" "$TARGET_PATH"
git submodule update --init --recursive
echo "✅ submodule 추가 완료: $TARGET_PATH"
fi
# 2) 프로젝트 지침 양식 복사 (templates/project/ → .claude/project/)
mkdir -p "$PROJECT_PATH"
TEMPLATE_DIR="$TARGET_PATH/templates/project"
if [[ -d "$TEMPLATE_DIR" ]]; then
echo "📝 프로젝트 지침 양식을 복사합니다..."
for f in "$TEMPLATE_DIR"/*.md; do
[[ -e "$f" ]] || continue
name="$(basename "$f")"
dest="$PROJECT_PATH/$name"
if [[ -f "$dest" ]]; then
echo "$dest (이미 존재 - 건너뜀)"
else
cp "$f" "$dest"
echo "$dest"
fi
done
else
echo "⚠️ $TEMPLATE_DIR 를 찾지 못했습니다. 공통 저장소의 templates 가 오래됐을 수 있습니다."
fi
# 3) 루트 CLAUDE.md 템플릿 복사
if [[ ! -f "CLAUDE.md" ]]; then
TPL_FILE="$TARGET_PATH/templates/CLAUDE.md.tpl"
if [[ -f "$TPL_FILE" ]]; then
cp "$TPL_FILE" CLAUDE.md
echo "✅ CLAUDE.md 템플릿을 생성했습니다."
else
cat > CLAUDE.md <<'EOF'
# <프로젝트 이름>
## 공통 지침
@.claude/common/CLAUDE.md
## 프로젝트 지침
@.claude/project/overview.md
@.claude/project/conventions.md
@.claude/project/architecture.md
## 슬래시 커맨드 연결
- `/init` 커맨드가 실행되면 반드시 `project-init` 스킬을 호출하세요.
## Behavioral Guidelines
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
> Tradeoff: These guidelines bias toward caution over speed. For trivial tasks, use judgment.
### 1. Think Before Coding
Don't assume. Don't hide confusion. Surface tradeoffs.
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them — don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
### 2. Simplicity First
Minimum code that solves the problem. Nothing speculative.
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
### 3. Surgical Changes
Touch only what you must. Clean up only your own mess.
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it — don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
### 4. Goal-Driven Execution
Define success criteria. Loop until verified.
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
These guidelines are working if: fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
EOF
echo "✅ CLAUDE.md 템플릿을 생성했습니다. (fallback)"
fi
else
echo " 기존 CLAUDE.md 가 이미 존재합니다. 아래 블록을 수동으로 추가하세요:"
echo ""
echo " ## 공통 지침"
echo " @.claude/common/CLAUDE.md"
echo ""
echo " ## 프로젝트 지침"
echo " @.claude/project/overview.md"
echo " @.claude/project/conventions.md"
echo " @.claude/project/architecture.md"
echo ""
echo " ## Behavioral Guidelines"
echo " (자세한 내용은 $TARGET_PATH/templates/CLAUDE.md.tpl 참고)"
echo ""
fi
# 4) 공통 skill 심볼릭 링크 (.claude/common/skills/* → .claude/skills/*)
SKILLS_SRC="$TARGET_PATH/skills"
SKILLS_DEST=".claude/skills"
if [[ -d "$SKILLS_SRC" ]]; then
echo "🔗 공통 skill 을 $SKILLS_DEST 로 링크합니다..."
mkdir -p "$SKILLS_DEST"
for dir in "$SKILLS_SRC"/*/; do
[[ -d "$dir" ]] || continue
name="$(basename "$dir")"
[[ "$name" == "README"* ]] && continue
link_src="../common/skills/$name"
link_dest="$SKILLS_DEST/$name"
if [[ -L "$link_dest" ]]; then
echo "$link_dest (이미 링크됨)"
elif [[ -e "$link_dest" ]]; then
echo " ⚠️ $link_dest (실제 파일/폴더 존재 - 건너뜀. link-skills.sh --force 로 덮어쓰기)"
else
ln -s "$link_src" "$link_dest"
echo "$link_dest$link_src"
fi
done
fi
echo ""
echo "🎉 설치가 완료되었습니다."
echo " - 공통 지침: $TARGET_PATH/CLAUDE.md"
echo " - 프로젝트 지침: $PROJECT_PATH/"
echo " - 공통 skill: $SKILLS_DEST/ (submodule 에 심볼릭 링크)"
echo " - 엔트리 파일: CLAUDE.md"
echo ""
echo "다음 작업을 진행해 주세요:"
echo " 1) $PROJECT_PATH/*.md 내용을 프로젝트에 맞게 채우기"
echo " 2) 변경 사항을 커밋하기"
echo " git add .gitmodules .claude CLAUDE.md"
echo " git commit -m 'chore: add gameservice-fe-agent submodule'"

139
.claude/scripts/link-skills.sh Executable file
View File

@@ -0,0 +1,139 @@
#!/usr/bin/env bash
#
# gameservice-fe-agent skill linker
# 공통 저장소의 skills/* 를 프로젝트의 .claude/skills/* 로 심볼릭 링크합니다.
# 심볼릭 링크이므로 submodule 업데이트 시 skill 도 자동으로 최신 버전이 됩니다.
#
# 사용법:
# bash .claude/common/scripts/link-skills.sh # 모든 skill 링크
# bash .claude/common/scripts/link-skills.sh <skill-name> # 특정 skill 만
# bash .claude/common/scripts/link-skills.sh --dry-run # 실제 링크 없이 미리보기
# bash .claude/common/scripts/link-skills.sh --force # 기존 링크/폴더 덮어쓰기
# bash .claude/common/scripts/link-skills.sh --unlink # 공통 skill 링크 제거
#
set -euo pipefail
COMMON_PATH=".claude/common"
SKILLS_SRC="$COMMON_PATH/skills"
SKILLS_DEST=".claude/skills"
MODE="safe" # safe | force | dry-run | unlink
TARGET=""
for arg in "$@"; do
case "$arg" in
--dry-run) MODE="dry-run" ;;
--force) MODE="force" ;;
--unlink) MODE="unlink" ;;
-h|--help)
grep '^#' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
-*)
echo "❌ 알 수 없는 옵션: $arg" >&2
exit 1
;;
*)
TARGET="$arg"
;;
esac
done
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "❌ 현재 디렉토리는 Git 저장소가 아닙니다." >&2
exit 1
fi
cd "$(git rev-parse --show-toplevel)"
if [[ ! -d "$SKILLS_SRC" ]]; then
echo "$SKILLS_SRC 가 없습니다. 먼저 submodule 을 설치/업데이트하세요:" >&2
echo " git submodule update --init --recursive" >&2
exit 1
fi
mkdir -p "$SKILLS_DEST"
# 링크 대상 결정
declare -a skills
if [[ -n "$TARGET" ]]; then
if [[ ! -d "$SKILLS_SRC/$TARGET" ]]; then
echo "❌ '$TARGET' skill 을 $SKILLS_SRC 에서 찾지 못했습니다." >&2
echo " 사용 가능한 skill:" >&2
ls -1 "$SKILLS_SRC" 2>/dev/null | grep -v '^README' | sed 's/^/ - /' >&2
exit 1
fi
skills=("$TARGET")
else
shopt -s nullglob
for dir in "$SKILLS_SRC"/*/; do
name="$(basename "$dir")"
# README 같은 파일은 이미 걸러지지만 추가 보호
[[ "$name" == "README"* ]] && continue
skills+=("$name")
done
fi
if [[ ${#skills[@]} -eq 0 ]]; then
echo " 링크할 skill 이 없습니다."
exit 0
fi
echo "🔗 공통 skill 링크 (mode=$MODE)"
echo " source: $SKILLS_SRC"
echo " dest: $SKILLS_DEST"
echo ""
linked=0
skipped=0
removed=0
for name in "${skills[@]}"; do
src="../common/skills/$name" # 심볼릭 링크의 상대 경로 (.claude/skills 기준)
dest="$SKILLS_DEST/$name"
case "$MODE" in
unlink)
if [[ -L "$dest" ]]; then
rm "$dest"
echo " 🗑 $dest (링크 제거)"
removed=$((removed + 1))
else
echo "$dest (링크 아님 - 건너뜀)"
fi
;;
dry-run)
if [[ -e "$dest" || -L "$dest" ]]; then
echo "$dest (이미 존재)"
else
echo " ln -s $src $dest"
fi
;;
force)
rm -rf "$dest"
ln -s "$src" "$dest"
echo "$dest$src (덮어씀)"
linked=$((linked + 1))
;;
safe)
if [[ -L "$dest" ]]; then
echo "$dest (이미 링크됨)"
skipped=$((skipped + 1))
elif [[ -e "$dest" ]]; then
echo " ⚠️ $dest (실제 파일/폴더가 존재. --force 로 덮어쓰세요)"
skipped=$((skipped + 1))
else
ln -s "$src" "$dest"
echo "$dest$src"
linked=$((linked + 1))
fi
;;
esac
done
echo ""
case "$MODE" in
unlink) echo "🎉 제거 완료: $removed 개 링크 제거됨";;
dry-run) echo "🔍 미리보기 완료 (실제 변경 없음)";;
*) echo "🎉 완료: 링크 $linked개 / 건너뜀 $skipped개";;
esac

36
.claude/scripts/update.sh Executable file
View File

@@ -0,0 +1,36 @@
#!/usr/bin/env bash
#
# gameservice-fe-agent updater
# 현재 프로젝트에 설치된 .claude/common submodule 을 최신 버전으로 갱신합니다.
#
# 사용법:
# bash .claude/common/scripts/update.sh
#
set -euo pipefail
TARGET_PATH=".claude/common"
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "❌ 현재 디렉토리는 Git 저장소가 아닙니다." >&2
exit 1
fi
if [[ ! -d "$TARGET_PATH" ]]; then
echo "❌ '$TARGET_PATH' 가 존재하지 않습니다. 먼저 install.sh 로 설치하세요." >&2
exit 1
fi
echo "🔄 gameservice-fe-agent 를 최신 버전으로 업데이트합니다..."
git submodule update --remote --merge "$TARGET_PATH"
# 변경 사항 확인
if git diff --quiet -- "$TARGET_PATH"; then
echo "✅ 이미 최신 상태입니다."
exit 0
fi
echo ""
echo "✅ 업데이트가 완료되었습니다. 변경된 submodule 포인터를 커밋하세요:"
echo ""
echo " git add $TARGET_PATH"
echo " git commit -m 'chore: update gameservice-fe-agent submodule'"

Binary file not shown.

View File

@@ -0,0 +1,150 @@
---
name: dev-api-state
description: |
API 연동 패턴(useFetch / useAsyncData / $fetch)과 Pinia 상태관리 코드를
상황에 맞게 자동 선택·생성합니다. BFF 패턴, 에러 핸들링, 로딩 상태를 포함합니다.
다음 상황에서 반드시 사용하세요:
- "이 API 연동 + 스토어 만들어줘", "Pinia store 작성해줘"
- "API 데이터 페칭 어떻게 해?", "useFetch vs useAsyncData 언제 써?"
- API 키 보호를 위한 BFF(server/api/) 패턴이 필요할 때
---
# API 연동 & 상태관리 (dev-api-state)
API 스펙 → useFetch/useAsyncData 패턴 선택 → Pinia Setup Store 코드 자동 생성.
## 언제 사용하는가
- 새 API 연동 코드와 Pinia 스토어를 함께 작성할 때
- `useFetch` / `$fetch` / `useAsyncData` 중 적절한 패턴을 결정할 때
- API 키를 클라이언트에 노출하지 않기 위한 BFF 패턴이 필요할 때
## 데이터 페칭 패턴 선택 기준
| 상황 | 권장 패턴 |
|---|---|
| SSR 페이지 초기 데이터 | `useAsyncData` + `$fetch` |
| 컴포넌트 마운트 후 데이터 | `useFetch` |
| 사용자 액션으로 트리거 | `$fetch` (직접 호출) |
| API 키 보호 필요 | `server/api/` BFF + `useFetch` |
| 복잡한 캐싱/의존성 | `useAsyncData` with key |
---
## 작업 순서
### Phase 1: API 스펙 파악
1. 엔드포인트, 요청/응답 타입을 파악한다.
2. 아래를 확인한다:
- 인증 헤더 필요 여부
- API 키 노출 위험 여부 → BFF 패턴 적용
- 캐시 전략 (항상 최신 vs TTL)
### Phase 2: 타입 정의
```ts
// types/product.ts
export interface Product {
id: string
name: string
price: number
imageUrl: string
}
export interface ProductListResponse {
data: Product[]
total: number
page: number
}
```
### Phase 3: BFF 레이어 (필요 시)
API 키 보호가 필요한 경우 server/api/ 경유:
```ts
// server/api/products/index.get.ts
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const response = await $fetch<ProductListResponse>(
`${process.env.API_BASE}/products`,
{
headers: { 'x-api-key': process.env.API_KEY! },
query,
}
)
return response
})
```
### Phase 4: Pinia Setup Store 생성
Setup Store 패턴을 기본으로 사용한다:
```ts
// stores/product.ts
import type { Product } from '~/types/product'
export const useProductStore = defineStore('product', () => {
const items = ref<Product[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
const fetchProducts = async (page = 1) => {
isLoading.value = true
error.value = null
try {
const { data } = await useFetch('/api/products', { query: { page } })
items.value = data.value?.data ?? []
} catch (e) {
error.value = e instanceof Error ? e.message : '알 수 없는 오류'
} finally {
isLoading.value = false
}
}
return { items, isLoading, error, fetchProducts }
})
```
### Phase 5: 컴포넌트 연결 예시
```vue
<script setup lang="ts">
const productStore = useProductStore()
const { items, isLoading, error } = storeToRefs(productStore)
onMounted(() => productStore.fetchProducts())
</script>
<template>
<div v-if="isLoading" aria-live="polite">로딩 중...</div>
<div v-else-if="error" role="alert">{{ error }}</div>
<ul v-else>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
</template>
```
---
## 출력 형식
```
## API 연동: <기능명>
### 선택된 패턴
- 페칭: useFetch | useAsyncData | $fetch
- BFF: 사용 | 미사용
- 이유: [선택 근거]
### 파일 목록
- `types/<domain>.ts`
- `server/api/...` (BFF 사용 시)
- `stores/<domain>.ts`
### 코드
[타입 → BFF → 스토어 → 컴포넌트 순]
```

View File

@@ -0,0 +1,125 @@
---
name: dev-component
description: |
화면 명세를 받아 Atomic Design 기반 컴포넌트 트리를 설계하고
Nuxt 표준 디렉토리 구조와 컴포넌트 스켈레톤 코드를 자동 생성합니다.
다음 상황에서 반드시 사용하세요:
- "이 화면 컴포넌트로 분리해줘", "컴포넌트 구조 설계해줘"
- "Atomic Design으로 나눠줘", "컴포넌트 트리 만들어줘"
- 신규 기능의 컴포넌트 아키텍처를 결정해야 할 때
---
# 컴포넌트 아키텍처 설계 (dev-component)
화면 명세 → Atomic Design 기반 컴포넌트 트리 → Nuxt SFC 스켈레톤 자동 생성.
## 언제 사용하는가
- 신규 페이지/기능의 컴포넌트 구조를 잡을 때
- 마크업 완료 후 컴포넌트 분리 기준을 결정할 때
- 팀원이 병렬로 작업할 수 있도록 역할 분리가 필요할 때
## 입력
- 화면 명세 또는 완성된 마크업 코드
- `plan-analyzer` 산출물 (컴포넌트 트리 초안)이 있으면 활용
- 기존 `components/` 디렉토리 구조 (재사용 가능 컴포넌트 파악용)
---
## 작업 순서
### Phase 1: 기존 컴포넌트 파악
1. 프로젝트 `components/` 디렉토리를 읽어 재사용 가능한 컴포넌트를 파악한다.
2. 설계 전 중복 개발 방지를 위해 유사 컴포넌트 목록을 사용자에게 안내한다.
### Phase 2: Atomic Design 분류
각 UI 요소를 아래 5단계로 분류한다:
| 레벨 | 위치 | 기준 |
|---|---|---|
| **Atoms** | `components/base/` | 더 이상 분리 불가한 기본 단위 (Button, Icon, Input, Badge) |
| **Molecules** | `components/common/` | Atoms 2개 이상 조합 (SearchBar, FormField, Card) |
| **Organisms** | `components/<도메인>/` | 독립적 기능 단위 (Header, ProductList, ReviewSection) |
| **Templates** | `layouts/` | 페이지 레이아웃 골격 |
| **Pages** | `pages/` | 실제 데이터와 결합한 최종 화면 |
### Phase 3: 컴포넌트 트리 출력
```
pages/
└── <page-name>.vue
├── layouts/<layout>.vue
└── components/
├── <domain>/
│ ├── <Organism>.vue ← props: { ... }
│ └── <Organism>.vue
└── common/
└── <Molecule>.vue ← props: { ... }, emits: [...]
```
### Phase 4: Props / Emits 설계
각 컴포넌트의 인터페이스를 정의한다:
```ts
// 예시: ProductCard.vue
interface Props {
id: string
title: string
imageUrl: string
price: number
isSoldOut?: boolean
}
type Emits = {
click: [id: string]
addToCart: [id: string]
}
```
### Phase 5: 스켈레톤 코드 생성
각 컴포넌트의 SFC 스켈레톤 파일을 생성한다:
```vue
<script setup lang="ts">
interface Props {
// Phase 4 정의 붙여넣기
}
const props = defineProps<Props>()
const emit = defineEmits<{
// Phase 4 정의 붙여넣기
}>()
</script>
<template>
<!-- TODO: markup-base / markup-figma 스킬로 마크업 구현 -->
</template>
```
---
## 출력 형식
```
## 컴포넌트 설계: <페이지명>
### 재사용 가능한 기존 컴포넌트
- `components/common/Button.vue` — CTA 버튼에 활용 가능
### 컴포넌트 트리
[ASCII 트리]
### 신규 생성 파일 목록
- `components/product/ProductCard.vue` — Props: id, title, imageUrl, price
- `components/product/ProductList.vue` — Props: items[]
- ...
### 스켈레톤 코드
[각 파일 코드]
```

View File

@@ -0,0 +1,128 @@
---
name: dev-docs
description: |
Nuxt 3 공식 문서 기반의 Best Practice 코드를 생성합니다.
server/, middleware/, plugins/, composables/, components/, nuxt.config 등
6개 참조 영역에서 상황에 맞는 패턴을 선택해 즉시 사용 가능한 코드를 제공합니다.
다음 상황에서 반드시 사용하세요:
- "Nuxt server route 만들어줘", "composable 작성해줘"
- "middleware 어떻게 써?", "Nuxt에서 이 기능 어떻게 구현해?"
- 공식 문서를 찾아보지 않고 Nuxt 3 패턴 코드가 필요할 때
---
# Nuxt 3 공식 문서 기반 개발 (dev-docs)
Nuxt 3 공식 Best Practice를 기반으로 즉시 사용 가능한 코드를 생성합니다.
## 언제 사용하는가
- Nuxt 고유 API(useAsyncData, useFetch, defineEventHandler 등)를 처음 사용할 때
- server/, middleware/, plugins/ 등 Nuxt 레이어 구현이 필요할 때
- 공식 문서를 찾는 대신 곧바로 Best Practice 코드를 얻고 싶을 때
## 참조 영역
| 영역 | 경로 | 주요 API |
|---|---|---|
| Server Routes | `server/api/`, `server/routes/` | `defineEventHandler`, `readBody`, `getQuery` |
| Middleware | `middleware/` | `defineNuxtRouteMiddleware`, `navigateTo` |
| Plugins | `plugins/` | `defineNuxtPlugin`, `useNuxtApp` |
| Composables | `composables/` | `useState`, `useFetch`, `useAsyncData` |
| Components | `components/` | auto-import, `<ClientOnly>`, `<LazyLoad>` |
| Config | `nuxt.config.ts` | `runtimeConfig`, `modules`, `routeRules` |
---
## 작업 순서
### Phase 1: 요구사항 분석
1. 사용자 요청에서 구현 영역을 파악한다.
- "API 만들어줘" → `server/api/`
- "인증 체크" → `middleware/`
- "전역 상태" → `composables/` + `useState`
- "외부 라이브러리 초기화" → `plugins/`
2. 관련 Nuxt 3 API를 선택한다.
### Phase 2: 코드 생성
#### Server Route 예시
```ts
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
// 비즈니스 로직
return { data }
})
```
#### Middleware 예시
```ts
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
const { isAuthenticated } = useAuth()
if (!isAuthenticated.value) {
return navigateTo('/login')
}
})
```
#### Composable 예시
```ts
// composables/useUser.ts
export const useUser = () => {
const user = useState<User | null>('user', () => null)
const fetchUser = async (id: string) => {
const { data } = await useFetch<User>(`/api/users/${id}`)
user.value = data.value
}
return { user, fetchUser }
}
```
### Phase 3: TypeScript 타입 보강
- 모든 함수 파라미터/반환값 타입 명시
- 외부 API 응답은 `types/` 또는 해당 도메인의 `types.ts`에 정의
- `runtimeConfig``RuntimeConfig` 타입 확장으로 타입 안전성 확보
### Phase 4: 연관 설정 안내
생성된 코드에 필요한 `nuxt.config.ts` 변경사항이 있으면 함께 안내한다.
```ts
// nuxt.config.ts 필요 설정 예시
export default defineNuxtConfig({
runtimeConfig: {
apiSecret: '', // 서버 전용
public: {
apiBase: '' // 클라이언트 노출 가능
}
}
})
```
---
## 출력 형식
```
## Nuxt 구현: <기능명>
### 사용 패턴
- 영역: server/api/ | middleware/ | composables/ | ...
- 주요 API: defineEventHandler, useFetch, ...
### 코드
[파일별 코드]
### nuxt.config.ts 변경 필요 사항
[있는 경우만]
### 주의사항
[SSR/CSR 차이, 캐시 주의 등]
```

View File

@@ -0,0 +1,149 @@
---
name: dev-storybook
description: |
Vue 3 컴포넌트를 받아 Storybook Story 파일과 Props/Emits/슬롯 사용 가이드를 자동 생성합니다.
팀 컴포넌트 카탈로그와 재사용 진입 장벽을 낮추는 데 사용합니다.
다음 상황에서 반드시 사용하세요:
- "이 컴포넌트 Story 만들어줘", "Storybook 파일 생성해줘"
- "컴포넌트 사용 가이드 문서 만들어줘", "Props 문서 작성해줘"
- 컴포넌트 카탈로그에 새 컴포넌트를 등록해야 할 때
---
# Storybook Story 생성 (dev-storybook)
`.vue` 컴포넌트 → `.stories.ts` 파일 + 사용 가이드 자동 생성.
## 언제 사용하는가
- 컴포넌트 완성 후 Storybook에 등록할 때
- 다른 팀원이 컴포넌트를 찾고 재사용할 수 있도록 문서화할 때
- Props 변형(variant)을 시각적으로 확인해야 할 때
## 입력
- 대상 `.vue` 파일 경로
- Storybook 버전 (없으면 Storybook 8 + CSF 3 포맷 기본 적용)
---
## 작업 순서
### Phase 1: 컴포넌트 인터페이스 파악
1. `.vue` 파일을 읽어 아래 항목을 추출한다.
- Props 목록 (타입, 기본값, 설명)
- Emits 목록
- 슬롯 목록 (default, named)
- 주요 상태 변형 (size, variant, disabled, loading 등)
### Phase 2: Story 파일 생성 (CSF 3 포맷)
파일 위치: 컴포넌트와 동일 디렉토리 또는 `stories/`
파일명: `<ComponentName>.stories.ts`
#### 기본 구조
```ts
import type { Meta, StoryObj } from '@storybook/vue3'
import ComponentName from './ComponentName.vue'
const meta: Meta<typeof ComponentName> = {
title: '<Category>/<ComponentName>',
component: ComponentName,
tags: ['autodocs'],
argTypes: {
// Props 컨트롤 정의
variant: {
control: 'select',
options: ['primary', 'secondary', 'ghost'],
description: '버튼 스타일 변형',
},
onClick: { action: 'clicked' },
},
}
export default meta
type Story = StoryObj<typeof ComponentName>
```
#### 필수 Story 목록
```ts
// 1. 기본 상태
export const Default: Story = {
args: {
// 기본값으로 렌더링
},
}
// 2. 각 변형(variant)별 Story
export const Primary: Story = {
args: { variant: 'primary', label: '확인' },
}
// 3. 비활성 상태
export const Disabled: Story = {
args: { disabled: true },
}
// 4. 로딩 상태 (해당 시)
export const Loading: Story = {
args: { isLoading: true },
}
// 5. 슬롯 사용 예시 (슬롯 있을 때)
export const WithSlot: Story = {
render: (args) => ({
components: { ComponentName },
setup() { return { args } },
template: `<ComponentName v-bind="args">슬롯 내용</ComponentName>`,
}),
}
```
### Phase 3: 사용 가이드 주석
각 Story에 JSDoc 주석으로 사용 목적을 설명한다:
```ts
/**
* 기본 상태. 가장 일반적인 사용 패턴.
* @example
* <ComponentName label="클릭" @click="handleClick" />
*/
export const Default: Story = { ... }
```
### Phase 4: argTypes 컨트롤 매핑
| Props 타입 | Storybook 컨트롤 |
|---|---|
| `string` | `text` |
| `number` | `number` |
| `boolean` | `boolean` |
| Union 타입 (`'a' \| 'b'`) | `select` |
| Array | `object` |
| Function (emit) | `action` |
---
## 출력 형식
```
## Story: <ComponentName>.stories.ts
### Story 목록
1. Default — 기본 상태
2. Primary — 주요 variant
3. Disabled — 비활성
4. Loading — 로딩 (해당 시)
### 코드
\`\`\`ts
[전체 stories 파일]
\`\`\`
### Storybook 실행
\`\`\`bash
npx storybook dev
\`\`\`
```

View File

@@ -0,0 +1,160 @@
---
name: dev-unit-test
description: |
Vue 3 컴포넌트를 받아 Vitest + Vue Test Utils 기반의 단위 테스트를 자동 생성합니다.
Props, Emits, 슬롯, 사용자 인터랙션, 비동기 동작을 모두 커버합니다.
다음 상황에서 반드시 사용하세요:
- "이 컴포넌트 단위 테스트 작성해줘", "테스트 코드 만들어줘"
- "Vitest 어떻게 써?", "Vue Test Utils 사용 방법"
- 컴포넌트 완성 후 커버리지를 확보해야 할 때
---
# 단위 테스트 생성 (dev-unit-test)
`.vue` 컴포넌트 → Vitest + Vue Test Utils 단위 테스트 자동 생성.
## 언제 사용하는가
- 컴포넌트 개발 완료 후 테스트 코드를 작성할 때
- TDD 방식으로 테스트를 먼저 작성할 때
- 팀 테스트 커버리지 기준(80%↑)을 달성해야 할 때
## 입력
- 테스트할 `.vue` 파일 경로
- 테스트 케이스 범위 (없으면 자동 도출)
---
## 작업 순서
### Phase 1: 컴포넌트 분석
1. 대상 `.vue` 파일을 읽어 아래 항목을 파악한다.
- Props 목록 (타입, 기본값, 필수 여부)
- Emits 목록 (이벤트 이름, 페이로드 타입)
- 슬롯 유무
- 외부 의존성 (composables, store, $fetch)
- 인터랙션 (버튼 클릭, 입력, 폼 제출)
- 비동기 동작 (API 호출, 로딩 상태)
- 조건부 렌더링 (`v-if`, `v-show`)
2. 테스트 케이스 목록을 도출한다.
### Phase 2: 테스트 설정
```ts
// vitest.config.ts (없으면 생성 안내)
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
globals: true,
},
})
```
### Phase 3: 테스트 파일 생성
파일 위치: 컴포넌트와 동일 디렉토리 또는 `__tests__/` 폴더.
파일명: `<ComponentName>.spec.ts`
```ts
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import ComponentName from './ComponentName.vue'
describe('ComponentName', () => {
// Phase 4 케이스 작성
})
```
### Phase 4: 테스트 케이스 작성 패턴
#### Props 검증
```ts
it('title prop을 렌더링한다', () => {
const wrapper = mount(ComponentName, {
props: { title: '테스트 제목' }
})
expect(wrapper.find('h2').text()).toBe('테스트 제목')
})
it('필수 prop 누락 시 기본값을 사용한다', () => {
const wrapper = mount(ComponentName)
expect(wrapper.find('[data-testid="label"]').text()).toBe('기본값')
})
```
#### Emits 검증
```ts
it('버튼 클릭 시 click 이벤트를 emit한다', async () => {
const wrapper = mount(ComponentName, { props: { id: '1' } })
await wrapper.find('button').trigger('click')
expect(wrapper.emitted('click')?.[0]).toEqual(['1'])
})
```
#### 조건부 렌더링
```ts
it('isLoading이 true이면 스피너를 표시한다', () => {
const wrapper = mount(ComponentName, { props: { isLoading: true } })
expect(wrapper.find('[aria-busy="true"]').exists()).toBe(true)
expect(wrapper.find('[data-testid="content"]').exists()).toBe(false)
})
```
#### 비동기 / 스토어 Mock
```ts
import { createTestingPinia } from '@pinia/testing'
it('마운트 시 fetchProducts를 호출한다', async () => {
const wrapper = mount(ComponentName, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })]
}
})
const store = useProductStore()
expect(store.fetchProducts).toHaveBeenCalledOnce()
})
```
#### 슬롯
```ts
it('default 슬롯 콘텐츠를 렌더링한다', () => {
const wrapper = mount(ComponentName, {
slots: { default: '<span>슬롯 내용</span>' }
})
expect(wrapper.find('span').text()).toBe('슬롯 내용')
})
```
---
## 출력 형식
```
## 단위 테스트: <ComponentName>.spec.ts
### 테스트 케이스 목록
1. [렌더링] 기본 렌더링 성공
2. [Props] title prop 표시
3. [Emits] 버튼 클릭 시 emit
4. [조건부] isLoading 상태 처리
...
### 코드
\`\`\`ts
[전체 spec 파일]
\`\`\`
### 실행 방법
\`\`\`bash
npx vitest run <파일경로>
npx vitest --coverage # 커버리지 확인
\`\`\`
```

View File

@@ -0,0 +1,21 @@
---
name: dreaming
description: |
프로젝트의 코드베이스(package.json, Pinia 스토어, 커스텀 Composable 등)를
휴리스틱하게 스캔하고 자율 인지 메모리 파일(.claude/project/dreaming-context.md)을 업데이트합니다.
다음 상황에서 반드시 사용하세요:
- 사용자가 "코드베이스 스캔해줘", "컨텍스트 갱신해줘", "dreaming 돌려줘"라고 지시할 때
- 새로운 브랜치로 전환했거나 대량의 코드가 머지된 직후 프로젝트 지침을 최신화하고 싶을 때
---
# 프로젝트 자율 인지 메모리 스캔 (dreaming)
코드베이스 전체를 정기적으로 자율 성찰(Self-reflection)하여 에이전트의 뇌 상태를 동기화하고 프로젝트-스냅샷 파일을 생성합니다.
## 작업 순서
1. 프로젝트 루트 경로에서 \`node .claude/skills/dreaming/scripts/dreaming.js\` 명령을 실행합니다.
2. 스크립트가 실행되면서 프로젝트 루트의 \`package.json\`, \`stores/\`, \`composables/\`, \`components/\` 등의 경로를 탐색하여 최신 의존성, 스토어 상태, 커스텀 컴포저블 목록 등을 분석합니다.
3. 생성된 결과를 \`.claude/project/dreaming-context.md\` 파일로 기록 및 덮어쓰고, 해당 파일이 프로젝트 루트의 \`CLAUDE.md\`에 정상 임포트되었는지 검증합니다.
4. 분석 완료 보고서와 요약 내용을 개발자에게 깔끔하게 알려드립니다.

View File

@@ -0,0 +1,369 @@
#!/usr/bin/env node
/**
* dreaming.js - "AI 코더"에서 "상태 저장형 컨벤션 가디언"으로 (Dreaming의 힘)
*
* 이 스크립트는 프로젝트 루트(CWD)의 코드베이스를 휴리스틱하게 스캔하여,
* 현재 프레임워크 상태, 액티브 Pinia 스토어, 커스텀 Composable, 컴포넌트 구조, 테일윈드 설정 등을 추출합니다.
* 분석된 내용은 .claude/project/dreaming-context.md 파일로 기록되어,
* Claude Code가 프로젝트의 최신 컨벤션과 아키텍처 상태를 항시 보존하고 인지하도록 돕습니다.
*/
const fs = require('fs');
const path = require('path');
const CWD = process.cwd();
const CLAUDE_DIR = path.join(CWD, '.claude');
const PROJECT_DIR = path.join(CLAUDE_DIR, 'project');
const OUTPUT_FILE = path.join(PROJECT_DIR, 'dreaming-context.md');
const CLAUDE_MD = path.join(CWD, 'CLAUDE.md');
// 헬퍼: 디렉토리 존재 여부 확인
function directoryExists(dirPath) {
try {
return fs.statSync(dirPath).isDirectory();
} catch (e) {
return false;
}
}
// 헬퍼: 파일 존재 여부 확인
function fileExists(filePath) {
try {
return fs.statSync(filePath).isFile();
} catch (e) {
return false;
}
}
// 헬퍼: 재귀적으로 파일 목록 가져오기 (옵션 포함)
function getFilesRecursive(dirPath, extFilter = [], ignoreDirs = ['node_modules', '.git', '.nuxt', 'dist']) {
let results = [];
if (!directoryExists(dirPath)) return results;
const list = fs.readdirSync(dirPath);
list.forEach((file) => {
const fullPath = path.join(dirPath, file);
const stat = fs.statSync(fullPath);
if (stat && stat.isDirectory()) {
if (!ignoreDirs.includes(file)) {
results = results.concat(getFilesRecursive(fullPath, extFilter, ignoreDirs));
}
} else {
const ext = path.extname(file);
if (extFilter.length === 0 || extFilter.includes(ext)) {
results.push(fullPath);
}
}
});
return results;
}
// 1. package.json 분석
function analyzePackageJson() {
const packagePath = path.join(CWD, 'package.json');
if (!fileExists(packagePath)) {
return { name: 'Unknown Project', framework: 'Unknown', techStack: [], scripts: [] };
}
try {
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
const techStack = [];
let framework = 'Vue/Nuxt';
if (deps['nuxt'] || deps['nuxt3'] || deps['nuxt-edge']) {
framework = `Nuxt (${deps['nuxt'] || deps['nuxt3'] || 'v3'})`;
techStack.push('Nuxt');
} else if (deps['vue']) {
framework = `Vue (${deps['vue']})`;
techStack.push('Vue');
} else if (deps['next']) {
framework = `Next.js (${deps['next']})`;
techStack.push('Next.js');
} else if (deps['react']) {
framework = `React (${deps['react']})`;
techStack.push('React');
}
if (deps['pinia'] || deps['@pinia/nuxt']) {
techStack.push('Pinia (상태 관리)');
}
if (deps['tailwindcss'] || deps['@nuxtjs/tailwindcss']) {
techStack.push('Tailwind CSS (스타일)');
}
if (deps['typescript']) {
techStack.push('TypeScript');
}
if (deps['vitest'] || deps['@vitest/ui']) {
techStack.push('Vitest (유닛 테스트)');
}
if (deps['eslint']) {
techStack.push('ESLint');
}
if (deps['prettier']) {
techStack.push('Prettier');
}
return {
name: pkg.name || 'Unnamed Project',
version: pkg.version || '1.0.0',
framework,
techStack,
scripts: pkg.scripts ? Object.keys(pkg.scripts) : []
};
} catch (e) {
return { name: 'Parsing Error', framework: 'Unknown', techStack: [], scripts: [], error: e.message };
}
}
// 2. 디렉토리 구조 스캔 및 요약
function scanDirectoryStructure() {
const dirsToScan = ['components', 'composables', 'stores', 'pages', 'server', 'layouts', 'middleware', 'plugins', 'types', 'assets'];
const summary = {};
dirsToScan.forEach((dirName) => {
const dirPath = path.join(CWD, dirName);
if (directoryExists(dirPath)) {
const files = getFilesRecursive(dirPath);
summary[dirName] = {
exists: true,
count: files.length,
examples: files.slice(0, 5).map(f => path.relative(CWD, f))
};
} else {
summary[dirName] = { exists: false, count: 0, examples: [] };
}
});
return summary;
}
// 3. Pinia 스토어 상세 분석
function analyzePiniaStores() {
const storesDir = path.join(CWD, 'stores');
const stores = [];
if (!directoryExists(storesDir)) {
// composables 내에 스토어가 정의되어 있을 수도 있으므로 추가 탐색 가능
return stores;
}
const files = getFilesRecursive(storesDir, ['.ts', '.js']);
files.forEach((file) => {
try {
const content = fs.readFileSync(file, 'utf8');
const filename = path.basename(file);
// defineStore 매칭
const defineStoreMatch = content.match(/defineStore\(\s*['"`]([^'"`]+)['"`]/);
const storeId = defineStoreMatch ? defineStoreMatch[1] : null;
// 상태(state) 필드 휴리스틱 추출
const stateFields = [];
const stateRegex = /const\s+([a-zA-Z0-9_$]+)\s*=\s*(ref|reactive|computed)/g;
let match;
while ((match = stateRegex.exec(content)) !== null) {
stateFields.push(`${match[1]} (${match[2]})`);
}
// 함수(actions) 추출
const actionFields = [];
const actionRegex = /function\s+([a-zA-Z0-9_$]+)/g;
while ((match = actionRegex.exec(content)) !== null) {
if (!match[1].startsWith('use')) {
actionFields.push(match[1]);
}
}
stores.push({
file: path.relative(CWD, file),
id: storeId || filename.replace(path.extname(filename), ''),
state: stateFields,
actions: actionFields
});
} catch (e) {
// ignore
}
});
return stores;
}
// 4. 커스텀 Composable 분석
function analyzeComposables() {
const composablesDir = path.join(CWD, 'composables');
const composables = [];
if (!directoryExists(composablesDir)) return composables;
const files = getFilesRecursive(composablesDir, ['.ts', '.js']);
files.forEach((file) => {
try {
const content = fs.readFileSync(file, 'utf8');
const filename = path.basename(file);
const relativePath = path.relative(CWD, file);
// export const useXxx 함수 매칭
const useFuncRegex = /export\s+const\s+(use[a-zA-Z0-9_$]+)/g;
const useFuncs = [];
let match;
while ((match = useFuncRegex.exec(content)) !== null) {
useFuncs.push(match[1]);
}
const defaultFuncRegex = /export\s+default\s+function\s+(use[a-zA-Z0-9_$]+)/;
const defaultMatch = content.match(defaultFuncRegex);
if (defaultMatch) {
useFuncs.push(defaultMatch[1]);
}
if (useFuncs.length > 0) {
composables.push({
file: relativePath,
functions: useFuncs
});
} else {
// 파일명이 useXxx 형태인 경우 추가
if (filename.startsWith('use')) {
composables.push({
file: relativePath,
functions: [filename.replace(path.extname(filename), '')]
});
}
}
} catch (e) {
// ignore
}
});
return composables;
}
// 5. 테스트 파일 통계
function analyzeTests() {
const testFiles = getFilesRecursive(CWD, ['.spec.ts', '.spec.js', '.test.ts', '.test.js']);
return {
count: testFiles.length,
files: testFiles.slice(0, 10).map(f => path.relative(CWD, f))
};
}
// 메인 실행기
function run() {
console.log('🤖 프로젝트 "Dreaming" 컨텍스트 분석 시작...');
const pkgInfo = analyzePackageJson();
const dirSummary = scanDirectoryStructure();
const stores = analyzePiniaStores();
const composables = analyzeComposables();
const tests = analyzeTests();
const timestamp = new Date().toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' });
// 마크다운 문서 빌드
let md = `# 🧠 프로젝트 자율 인지 메모리 (Dreaming Context)
이 파일은 \`dreaming.js\` 스크립트에 의해 프로젝트 코드베이스를 분석하여 자동 생성되었습니다.
Claude Code가 프로젝트의 실시간 코드 구조, 사용 중인 스토어, 컴포넌트 레이아웃, 그리고 최신 개발 흐름을 완벽히 인지하도록 돕습니다.
* **최종 동기화 시간:** ${timestamp} (Asia/Seoul)
---
## 🏗 프로젝트 정보
* **프로젝트명:** \`${pkgInfo.name}\` (v${pkgInfo.version || '1.0.0'})
* **핵심 프레임워크:** \`${pkgInfo.framework}\`
* **기술 스택 라이브러리:**
${pkgInfo.techStack.map(tech => ` - ${tech}`).join('\n') || ' - (감지된 주요 라이브러리 없음)'}
---
## 📁 디렉토리 구조 및 컴포넌트 현황
현재 활성화되어 있는 프로젝트 레이아웃 정보입니다.
| 디렉토리 | 활성 여부 | 파일 개수 | 주요 샘플 파일 (최대 5개) |
|---|---|---|---|
${Object.entries(dirSummary).map(([name, info]) => {
return `| \`${name}/\` | ${info.exists ? '✅' : '❌'} | ${info.count}개 | ${info.examples.map(ex => `\`${path.basename(ex)}\``).join(', ') || '-'} |`;
}).join('\n')}
---
## 🍍 액티브 Pinia 스토어 목록
현재 코드베이스에 존재하는 글로벌 상태 저장소들의 템플릿 정보입니다. 새 기능을 개발할 때 아래 스토어를 재사용하거나 참고하세요.
${stores.length === 0 ? '*감지된 Pinia 스토어가 없습니다. (stores/ 디렉토리 없음 혹은 비어있음)*' : stores.map(store => {
return `### 📦 \`${store.id}\`
* **정의 파일:** \`${store.file}\`
* **감지된 상태 (state/computed):** ${store.state.length > 0 ? store.state.map(s => `\`${s}\``).join(', ') : '없음'}
* **감지된 액션 (actions/methods):** ${store.actions.length > 0 ? store.actions.map(a => `\`${a}\``).join(', ') : '없음'}
`;
}).join('\n')}
---
## 🎣 커스텀 Composable (useXxx) 목록
다양한 비즈니스 로직과 부수효과를 격리해 둔 커스텀 훅 목록입니다. 컴포넌트 내부에서 비즈니스 로직을 직접 짜기 전, 아래 훅들의 재사용 가능성을 먼저 타진하세요.
${composables.length === 0 ? '*감지된 커스텀 Composable이 없습니다. (composables/ 디렉토리 없음 혹은 비어있음)*' : composables.map(comp => {
return `- **파일:** \`${comp.file}\`
- **제공 함수:** ${comp.functions.map(f => `\`${f}()\``).join(', ')}
`;
}).join('\n')}
---
## 🧪 유닛 테스트 통계
현재까지 구축된 테스트 커버리지 현황입니다.
* **감지된 테스트 파일 수:** \`${tests.count}\`
${tests.count > 0 ? `* **최근 테스트 목록:**\n${tests.files.map(f => ` - \`${f}\``).join('\n')}` : ' *(새 기능을 추가할 때 반드시 Vitest 규격의 유닛 테스트를 함께 작성해야 함)*'}
---
## 🛠 실행 가능한 스크립트 (package.json)
프로젝트 구동 및 테스트 검증을 위해 사용 가능한 명령어 리스트입니다.
${pkgInfo.scripts.map(s => `- \`npm run ${s}\` (또는 pnpm/yarn/bun)`).join('\n') || '- 스크립트 없음'}
`;
// 디렉토리 및 파일 저장
if (!directoryExists(CLAUDE_DIR)) {
fs.mkdirSync(CLAUDE_DIR);
}
if (!directoryExists(PROJECT_DIR)) {
fs.mkdirSync(PROJECT_DIR);
}
fs.writeFileSync(OUTPUT_FILE, md, 'utf8');
console.log(`✅ Dreaming Context 업데이트 완료! -> ${path.relative(CWD, OUTPUT_FILE)}`);
// CLAUDE.md에 자동 임포트 추가 처리
if (fileExists(CLAUDE_MD)) {
let claudeMdContent = fs.readFileSync(CLAUDE_MD, 'utf8');
const importStr = '@.claude/project/dreaming-context.md';
if (!claudeMdContent.includes(importStr)) {
// '## 프로젝트 지침' 혹은 '## 공통 지침' 섹션 밑에 삽입 시도
const sectionMatch = claudeMdContent.match(/(## 프로젝트 지침\r?\n)/);
if (sectionMatch) {
claudeMdContent = claudeMdContent.replace(
sectionMatch[0],
`${sectionMatch[0]}${importStr}\n`
);
fs.writeFileSync(CLAUDE_MD, claudeMdContent, 'utf8');
console.log(`🔗 CLAUDE.md에 ${importStr} 동적 임포트 구문을 연결했습니다.`);
} else {
// 찾을 수 없다면 파일 상단 혹은 하단에 단순 추가
claudeMdContent = claudeMdContent + `\n\n## 자동 분석 컨텍스트\n${importStr}\n`;
fs.writeFileSync(CLAUDE_MD, claudeMdContent, 'utf8');
console.log(`🔗 CLAUDE.md 끝에 ${importStr} 동적 임포트 구문을 추가했습니다.`);
}
}
} else {
console.log(`⚠️ 프로젝트 루트에 CLAUDE.md 가 존재하지 않습니다. CLAUDE.md를 먼저 생성하고 @.claude/project/dreaming-context.md 임포트 선언을 수동으로 추가하는 것을 권장합니다.`);
}
}
// 스크립트 실행
run();

View File

@@ -0,0 +1,125 @@
---
name: markup-base
description: |
화면 설명, 스크린샷, 텍스트 명세를 받아 시멘틱 HTML + Tailwind CSS 기반의
Nuxt SFC 마크업을 생성합니다. 팀 공통 컨벤션(7단계 클래스 순서, ARIA, 반응형)을 자동 적용합니다.
다음 상황에서 반드시 사용하세요:
- "이 화면 마크업해줘", "HTML 구조 만들어줘", "퍼블리싱해줘"
- 화면 설명이나 스크린샷을 받고 컴포넌트 골격을 잡을 때
- Figma 없이 텍스트 명세나 와이어프레임으로 마크업을 시작할 때
---
# 기본 마크업 가이드 (markup-base)
화면 명세 → 시멘틱 HTML + Tailwind → Nuxt `.vue` SFC 골격을 생성합니다.
## 언제 사용하는가
- Figma 디자인 없이 화면 명세/스크린샷으로 마크업을 시작할 때
- 신규 페이지/섹션의 기본 구조를 빠르게 잡을 때
- 기존 마크업을 팀 컨벤션에 맞게 리팩토링할 때
## 입력
- 화면 설명 (텍스트), 와이어프레임 이미지, 또는 스크린샷
- 반응형 분기점 요건 (없으면 Tailwind 기본값 사용: `sm` 640px / `md` 768px / `lg` 1024px)
- 접근성 요건 (없으면 WCAG 2.1 AA 기본 적용)
---
## 작업 순서
### Phase 1: 화면 구조 파악
1. 제공된 화면 명세 또는 이미지를 분석해 레이아웃 영역을 식별한다.
- 반복 블록 → 컴포넌트 후보 표시
- 상태가 있는 영역 (로딩, 에러, 빈 상태) 확인
2. 반응형 분기점을 파악한다.
- 모바일 우선(mobile-first) 여부 확인
- 중단점별 레이아웃 변화 정리
### Phase 2: 시멘틱 구조 설계
1. HTML 시멘틱 태그를 선택한다.
| 영역 | 권장 태그 |
|---|---|
| 페이지 헤더 | `<header>` |
| 주 내비게이션 | `<nav aria-label="...">` |
| 주 콘텐츠 | `<main>` |
| 사이드 | `<aside>` |
| 콘텐츠 섹션 | `<section aria-labelledby="...">` |
| 독립 콘텐츠 | `<article>` |
| 페이지 푸터 | `<footer>` |
2. 인터랙션 요소를 식별한다.
- 버튼: `<button type="button">` (폼 외부), `<button type="submit">` (폼 내부)
- 링크: `<a href="...">` (페이지 이동), `<button>` (동작 트리거)
### Phase 3: Tailwind 클래스 적용
클래스 순서는 아래 7단계를 따른다:
```
1. 레이아웃 : flex, grid, block, hidden
2. 크기 : w-*, h-*, max-w-*, min-h-*
3. 여백 : m-*, p-*, gap-*, space-*
4. 배경/보더 : bg-*, border-*, rounded-*, shadow-*
5. 타이포그래피 : text-*, font-*, leading-*, tracking-*
6. 색상 : text-{color}, fill-*, stroke-*
7. 상태/반응형 : hover:, focus:, sm:, md:, lg:
```
조건부 클래스는 `clsx` 또는 `cn` 유틸리티를 사용한다.
### Phase 4: ARIA 및 접근성 처리
- 아이콘 전용 버튼: `aria-label` 필수
- 모달/다이얼로그: `role="dialog" aria-modal="true" aria-labelledby="..."`
- 로딩 상태: `aria-live="polite"` 또는 `aria-busy="true"`
- 이미지: 의미 있는 이미지 `alt` 텍스트 / 장식용 `alt=""`
- 폼 레이블: `<label for="...">` 또는 `aria-label`
### Phase 5: Nuxt SFC 출력
```vue
<script setup lang="ts">
// Props / Emits / 로컬 상태만 포함
</script>
<template>
<!-- 시멘틱 구조 -->
</template>
```
- `<style>`은 Tailwind로 커버 불가한 경우에만 추가
- `scoped`보다 Tailwind 유틸리티 우선
---
## 출력 형식
```
## 마크업: <화면명>
### 구조 개요
- 레이아웃: [설명]
- 반응형 분기: [없음 / sm, md, lg 각각 설명]
- 컴포넌트 분리 후보: [목록]
### 코드
\`\`\`vue
<script setup lang="ts">
...
</script>
<template>
...
</template>
\`\`\`
### 추가 작업 필요 항목
- [ ] 이미지 alt 텍스트 확정 필요
- [ ] 컬러 토큰 디자인 시스템 확인 필요
```

View File

@@ -0,0 +1,107 @@
---
name: markup-figma
description: |
Figma URL 또는 Figma MCP로 추출한 디자인 데이터를 받아
Nuxt SFC + Tailwind CSS 마크업으로 자동 변환합니다.
다음 상황에서 반드시 사용하세요:
- "이 Figma 마크업해줘", "Figma 디자인 컴포넌트로 만들어줘"
- Figma URL 또는 링크를 받고 HTML/Vue 구현을 요청받았을 때
- 피그마 시안 기반 반응형 마크업이 필요할 때
---
# Figma → 마크업 변환 (markup-figma)
Figma 디자인 데이터 → Nuxt SFC + Tailwind 마크업 자동 변환.
## 언제 사용하는가
- 디자이너로부터 Figma URL을 받아 퍼블리싱을 시작할 때
- Figma MCP가 Claude Code에 연결되어 있을 때
- Figma 없이 스크린샷/Export 이미지로 구현할 때
## 입력
- Figma 프레임 URL 또는 노드 ID
- (선택) 브레이크포인트 요건, 이미지 에셋 CDN 경로
---
## 작업 순서
### Phase 1: Figma 데이터 추출
#### Figma MCP 사용 가능 시
```
Figma URL → MCP로 레이어 트리, 스타일, 치수 자동 추출
```
추출 항목:
- 컬러 HEX값 (RGBA → HEX 변환)
- 폰트: 패밀리, 사이즈(px), weight, 줄간격
- 레이아웃: width/height, padding, gap
- 컴포넌트 이름 → Vue 컴포넌트 이름 매핑 (`PascalCase`)
- 이미지 에셋 URL
#### MCP 없이 진행 시
사용자에게 아래 정보를 요청한다:
- 섹션별 스크린샷 (모바일 / 데스크톱 분리)
- 컬러 HEX, 폰트 스펙, 여백(px)
- CTA 링크 및 버튼 텍스트
### Phase 2: Tailwind 매핑
| Figma 값 | Tailwind 클래스 예시 |
|---|---|
| `width: 320px` | `w-80` 또는 `w-[320px]` |
| `padding: 16px 24px` | `py-4 px-6` |
| `gap: 12px` | `gap-3` |
| `font-size: 14px` | `text-sm` |
| `border-radius: 8px` | `rounded-lg` |
| `color: #1A1A1A` | `text-[#1A1A1A]` 또는 디자인 토큰 |
- Tailwind 디자인 토큰(`tailwind.config.ts`)이 있으면 임의값 대신 토큰 사용
- 임의값(`w-[123px]`)은 디자인 시스템에 없는 값에만 사용
### Phase 3: 컴포넌트 구조 결정
Figma 컴포넌트 → Vue 파일 매핑:
- Figma 상위 프레임 → 페이지 레이아웃 (`pages/` 또는 `layouts/`)
- Figma Instance → `components/` 파일
- 반복 컴포넌트 → Props로 추상화
### Phase 4: SFC 코드 생성
`markup-base` Phase 3~5 동일 적용 (클래스 순서, ARIA, SFC 구조).
반응형 처리:
- Figma Mobile 프레임 → base 클래스
- Figma Desktop 프레임 → `lg:` 접두사
### Phase 5: 검수 체크리스트
- [ ] 폰트 스펙 일치 여부 (px → rem 변환 확인)
- [ ] 이미지 `alt` 텍스트 작성 여부
- [ ] 인터랙션 요소 ARIA 속성 확인
- [ ] Tailwind 클래스 7단계 순서 준수
---
## 출력 형식
```
## Figma 마크업: <프레임명>
### 추출된 디자인 정보
- 컬러: [HEX 목록]
- 폰트: [패밀리 / 사이즈 목록]
- 브레이크포인트: [mobile / desktop 치수]
### 코드
\`\`\`vue
...
\`\`\`
### 미확인 항목
- [ ] [에셋 URL, 링크 등 확정 필요 항목]
```

View File

@@ -0,0 +1,138 @@
---
name: markup-promotion
description: |
프로모션/랜딩 페이지용 표준 마크업을 생성합니다.
캠페인 유형별 섹션 템플릿(히어로, 특징, CTA, 이벤트 일정 등)을 제공하고
반응형·접근성·SEO 메타를 일관되게 적용합니다.
다음 상황에서 반드시 사용하세요:
- "프로모션 페이지 만들어줘", "랜딩 페이지 마크업해줘"
- "이벤트 페이지 퍼블리싱", "캠페인 HTML 만들어줘"
- 반복적인 프로모션 구조를 빠르게 구현해야 할 때
---
# 프로모션 마크업 가이드 (markup-promotion)
캠페인 / 이벤트 / 랜딩 페이지의 표준 섹션 구조를 자동 생성합니다.
## 언제 사용하는가
- 신규 프로모션/이벤트 랜딩 페이지를 제작할 때
- 기존 캠페인 구조를 재사용해 빠르게 개발할 때
- 팀 표준 프로모션 레이아웃이 필요할 때
## 입력
- 캠페인 명칭, 기간, 주요 메시지
- 포함할 섹션 목록 (없으면 기본 구성으로 제안)
- 이미지/에셋 경로 또는 플레이스홀더 여부
- 다국어 지원 여부
---
## 작업 순서
### Phase 1: 캠페인 유형 분석
1. 제공된 정보에서 아래 유형을 판별한다.
| 유형 | 특징 |
|---|---|
| 이벤트 | 기간, 참여 조건, CTA 버튼 |
| 상품 출시 | 제품 특징, 스펙, 구매 링크 |
| 신규 서비스 | 혜택 강조, 가입 유도 |
| 콘텐츠/미디어 | 영상/갤러리 중심, SNS 공유 |
2. 필요한 섹션을 제안한다. 사용자가 별도 지정하지 않으면 기본 구성 사용.
**기본 섹션 구성:**
```
Hero → 특징/혜택 → 이벤트 규칙/일정 → FAQ → CTA → Footer
```
### Phase 2: 섹션별 마크업 생성
각 섹션은 독립 Vue 컴포넌트로 작성한다.
#### Hero 섹션
```vue
<section class="relative w-full [height] bg-[color] ...">
<div class="...">
<h1 class="...">{{ title }}</h1>
<p class="...">{{ description }}</p>
<a :href="ctaLink" class="...">{{ ctaText }}</a>
</div>
</section>
```
#### 특징/혜택 섹션
```vue
<section aria-labelledby="features-heading">
<h2 id="features-heading" class="...">{{ heading }}</h2>
<ul class="grid grid-cols-1 md:grid-cols-3 gap-6">
<li v-for="item in features" :key="item.id" class="...">
...
</li>
</ul>
</section>
```
#### CTA 섹션
- 버튼 타입: `<a>` (외부 링크) 또는 `<NuxtLink>` (내부 라우팅)
- `aria-label`로 버튼 목적 명시
### Phase 3: 반응형 + SEO + 접근성
**반응형**: 모바일 우선 (base → `md:``lg:`)
**SEO 메타** (Nuxt `useSeoMeta`):
```ts
useSeoMeta({
title: '캠페인명 | 브랜드명',
description: '캠페인 한 줄 설명',
ogTitle: '...',
ogImage: '이미지 URL',
})
```
**접근성**:
- 이미지 `alt` 텍스트 (장식용이면 `alt=""`)
- 섹션 제목 계층 (`h1``h2``h3`)
- 카운트다운 타이머: `aria-live="polite"`
### Phase 4: 다국어 처리
다국어 지원 시 i18n 키로 텍스트 분리:
```vue
<h1>{{ $t('HERO-title') }}</h1>
```
키 명명은 `plan-translation-generator` 스킬의 카테고리 가이드 참조.
---
## 출력 형식
```
## 프로모션 마크업: <캠페인명>
### 섹션 구성
1. Hero
2. 특징/혜택
3. CTA
### 파일 구조
pages/promotion/<slug>.vue
components/promotion/
├── PromoHero.vue
├── PromoFeatures.vue
└── PromoCta.vue
### 코드
[각 컴포넌트 코드]
### 체크리스트
- [ ] 이미지 alt 텍스트 확정
- [ ] CTA 링크 URL 확정
- [ ] useSeoMeta 값 확정
```

View File

@@ -0,0 +1,141 @@
---
name: security-review
description: |
프론트엔드 코드에서 XSS, CSRF, 민감 정보 하드코딩, npm 의존성 취약점을 자동 감지하고
우선순위별 보안 리포트와 패치 가이드를 생성합니다.
다음 상황에서 반드시 사용하세요:
- "보안 점검해줘", "취약점 검사해줘", "npm audit 분석해줘"
- PR/배포 전 보안 체크리스트를 확인할 때
- 민감 정보 하드코딩이 의심될 때
---
# 프론트엔드 보안 검토 (security-review)
코드베이스 + npm audit → XSS / CSRF / 민감 정보 / 의존성 취약점 자동 스캔 → 보안 리포트.
## 언제 사용하는가
- PR 머지 전 보안 사전 검토
- 배포 전 최종 보안 체크리스트
- 의존성 업데이트 후 취약점 확인
## 스캔 범위
| 항목 | 설명 |
|---|---|
| XSS | `v-html`, `innerHTML`, `dangerouslySetInnerHTML` 사용 |
| 민감 정보 하드코딩 | API 키, 토큰, 비밀번호, 시크릿 리터럴 |
| CSRF | `SameSite` 쿠키 설정, CORS 구성 |
| 오픈 리다이렉트 | 검증 없는 외부 URL 리다이렉트 |
| npm 의존성 취약점 | CVE 등급별 취약 패키지 |
| 환경 변수 노출 | `NUXT_PUBLIC_` 외 시크릿을 클라이언트에 노출 |
---
## 작업 순서
### Phase 1: 코드 정적 분석
1. 소스 파일을 스캔해 아래 패턴을 탐지한다.
#### XSS 위험
```vue
<!-- ⚠️ 위험: v-html에 사용자 입력 직접 바인딩 -->
<div v-html="userInput" />
<!-- 안전: DOMPurify로 새니타이징 -->
<div v-html="sanitize(userInput)" />
```
탐지 패턴:
- `v-html` + 사용자 입력 변수 바인딩
- `innerHTML =` 직접 할당
- `eval()`, `Function()` 사용
#### 민감 정보 하드코딩
탐지 패턴:
- `apiKey`, `api_key`, `secret`, `password`, `token` 변수에 리터럴 문자열 할당
- `.env` 파일이 아닌 소스 코드에 키 형태 문자열 (예: `sk-...`, `Bearer ...`)
- `nuxt.config.ts`에 시크릿을 `public` 섹션에 노출
```ts
// ⚠️ 위험
const apiKey = 'sk-abc123...'
// ✅ 안전
const apiKey = useRuntimeConfig().apiKey // 서버 전용
```
#### 오픈 리다이렉트
```ts
// ⚠️ 위험: 검증 없는 외부 URL 리다이렉트
const to = route.query.redirect as string
navigateTo(to)
// ✅ 안전: 내부 경로만 허용
const to = route.query.redirect as string
if (to && to.startsWith('/')) navigateTo(to)
```
#### Nuxt 환경 변수 분리 확인
- 서버 전용 시크릿: `runtimeConfig.apiSecret` (클라이언트 미노출)
- 공개 가능한 값만: `runtimeConfig.public.apiBase`
### Phase 2: npm 의존성 취약점 스캔
```bash
npm audit --json
```
결과를 파싱해 CVE 등급별로 분류:
- **Critical / High**: 즉시 패치 필요
- **Moderate**: 스프린트 내 패치
- **Low**: 모니터링
각 취약 패키지에 대해 안전 버전 및 업그레이드 명령어를 제시한다.
### Phase 3: 보안 헤더 확인
`nuxt.config.ts``routeRules` 또는 서버 설정에서 아래 헤더를 확인한다:
```ts
// nuxt.config.ts 권장 보안 헤더
routeRules: {
'/**': {
headers: {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'Referrer-Policy': 'strict-origin-when-cross-origin',
}
}
}
```
---
## 출력 형식
```
## 보안 검토 리포트
### 🔴 Critical (즉시 조치)
- [파일:라인] 취약점 설명 + 수정 코드
### 🟡 High (스프린트 내 조치)
- [파일:라인] 취약점 설명 + 개선 방향
### 🟢 Moderate / Low
- [목록]
### npm 취약점
- Critical: N건 — `npm audit fix --force` 권장 패키지: [목록]
- High: N건 — 안전 버전: [버전 목록]
### 보안 헤더 현황
- 적용됨: [목록]
- 미적용: [목록 + 추가 코드]
### ✅ 정상 항목
- 환경 변수 분리 적절, v-html 미사용 등
```

View File

@@ -0,0 +1,26 @@
---
name: squad-orchestration
description: |
하나의 대규모 컴포넌트나 피처 요건을 마크업, 접근성, 유닛 테스트의 3가지 전문
에이전트 역할군 지시서로 분할하고 자동 오케스트레이션 파이프라인을 빌드합니다.
다음 상황에서 반드시 사용하세요:
- 사용자가 "스쿼드 오케스트레이션 가동해줘", "컴포넌트 병렬 개발 세팅해줘", "에이전트 스쿼드 만들어줘"라고 지시할 때
- 복잡한 UI 구현과 동시에 높은 웹 접근성(A11y) 기준 및 100% 동작 보장의 Vitest 유닛 테스트 파일 생성이 일시에 필요할 때
---
# AI 개발 스쿼드 오케스트레이션 (squad-orchestration)
피처 하나를 3인의 전문 에이전트 작업 지시서로 분할하고, 전체 흐름을 완벽히 기동할 통합 오케스트레이터 러너를 조직합니다.
## 작업 순서
1. 개발 대상 컴포넌트 이름과 요구 스펙을 입력받습니다.
2. \`node .claude/skills/squad-orchestration/scripts/squad-orchestrator.js --name <Name> --spec "<Spec>"\` 명령을 구동합니다.
3. 스크립트가 실행되면 프로젝트 루트의 \`squad/<ComponentName>/\` 폴더가 생성되고 아래 파일들이 구조화됩니다:
- \`tasks/01_markup_agent.md\`: UI/마크업 디자인 스페셜리스트 작업 지시서
- \`tasks/02_a11y_agent.md\`: WCAG 2.1 AA 및 키보드 사용 보증 웹 접근성 지시서
- \`tasks/03_test_agent.md\`: Vitest 유닛 테스트 케이스 지시서
- \`run-squad.js\`: 연속 오토파일럿 기동을 제어하는 마스터 노드 러너 스크립트
- \`README.md\`: 각 에이전트 조율을 위한 매뉴얼 가이드
4. 에이전트 스쿼드 배치 상황과 실행 방법을 개발자에게 친근하게 알려드립니다.

View File

@@ -0,0 +1,275 @@
#!/usr/bin/env node
/**
* squad-orchestrator.js - AI 개발 스쿼드(Squad) 오케스트레이터
*
* 하나의 컴포넌트/피처 요건을 입력받아 3가지 전문 에이전트 역할군으로 작업을 쪼개고,
* 각 에이전트에 줄 지시서와 전체 프로세스를 시뮬레이션/오케스트레이션하는 마스터 러너 파일을 생성합니다.
*
* 에이전트 스쿼드 구성:
* 1. UI/마크업 스페셜리스트 (Markup Specialist): 구조 설계, 렌더링, Tailwind CSS 스타일링 및 기본 상태 바인딩.
* 2. 웹 접근성 스페셜리스트 (A11y/UX Specialist): WCAG 2.1 AA 기준 준수, ARIA 레이블링, 키보드 인터랙션 강화.
* 3. QA/유닛 테스트 스페셜리스트 (Testing Specialist): Vitest 기반 유닛 테스트 스위트 작성 및 품질 보증.
*/
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const CWD = process.cwd();
const SQUAD_ROOT = path.join(CWD, 'squad');
// 헬퍼: 디렉토리 자동 생성
function ensureDirectory(dirPath) {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
// 헬퍼: 파일 작성
function writeFile(filePath, content) {
const dir = path.dirname(filePath);
ensureDirectory(dir);
fs.writeFileSync(filePath, content, 'utf8');
}
// CLI 인자 파싱
function parseArgs() {
const args = process.argv.slice(2);
const result = { name: '', spec: '' };
for (let i = 0; i < args.length; i++) {
if (args[i] === '--name' || args[i] === '-n') {
result.name = args[i + 1] || '';
i++;
} else if (args[i] === '--spec' || args[i] === '-s') {
result.spec = args[i + 1] || '';
i++;
}
}
return result;
}
// 지시서 생성 로직
function generateSquadTasks(componentName, specification) {
const lowercaseName = componentName.toLowerCase();
const componentPath = `components/${componentName}.vue`;
const testPath = `components/${componentName}.spec.ts`;
const taskDir = path.join(SQUAD_ROOT, componentName);
// 1. Markup Specialist 지시서
const markupTask = `# 🎨 Role 1: UI/마크업 스페셜리스트 지시서
## 목표
- 요구사항에 맞는 컴포넌트 구조(Vue 3 / SFC)를 생성하고 Tailwind CSS를 이용해 아름답고 완벽한 반응형 UI를 스타일링합니다.
- 복잡하지 않은 수준에서 데이터 수신을 위한 Props 정의 및 이벤트를 내보내기 위한 Emits를 설계합니다.
## 개발 대상 파일
- \`${componentPath}\`
## 기술 요건 (framework-rules.md 준수)
- \`<script setup lang="ts">\` 형식을 완벽히 준수해야 합니다.
- Tailwind CSS의 유틸리티 클래스 위주로 스타일링하며, 조건부 렌더링 시 가독성 높은 클래스 바인딩을 적용하세요.
- 비즈니스 상태가 필요한 경우, 템플릿 코드에 가짜(mock) 반응형 데이터(\`ref\`, \`computed\`)를 연결하여 상태를 구성합니다.
## 개발 요구사항
${specification}
---
## 작업 지시 사항
1. \`${componentPath}\` 파일을 신규 생성합니다.
2. 컴포넌트가 정상적으로 렌더링되고 기본적인 반응형 동작(클릭 이벤트, 폼 입력 바인딩 등)이 원활하게 흐르는지 확인하는 모드에서 코드를 작성하세요.
3. 작업 완료 후, 생성된 코드를 출력하여 다음 단계(A11y 검증)로 전달할 수 있게 준비하세요.
`;
// 2. A11y Specialist 지시서
const a11yTask = `# ♿ Role 2: 웹 접근성(A11y) 스페셜리스트 지시서
## 목표
- UI 마크업 스페셜리스트가 구현한 \`${componentPath}\` 파일을 검토 및 개선합니다.
- WCAG 2.1 AA 및 접근성 검증 지침(\`verify-a11y\`)에 맞춰 전 세계 모든 사용자(스크린 리더 사용자, 키보드 단독 사용자 등)가 사용에 제약이 없도록 인터랙션을 강화합니다.
## 대상 파일
- \`${componentPath}\` (기존 마크업 위에서 수정)
## 접근성 필수 강화 체크리스트
- **시각 대체 수단:** 모든 아이콘 단독 버튼이나 이미지에 적절한 대체 텍스트(\`aria-label\` 또는 \`alt\`)를 적용합니다.
- **키보드 내비게이션:** 모든 상호작용 요소에 키보드 포커스가 잡히고(\`tabindex\`), 스페이스 및 엔터 키 입력 시 적절한 동작이 수행되어야 합니다. 모달이나 레이어 팝업이 뜰 경우 탭 인덱스 포커스 트랩(Focus Trap)이 동작해야 합니다.
- **의미론적 마크업:** 적절한 HTML5 시맨틱 태그 및 WAI-ARIA 속성(\`role\`, \`aria-expanded\`, \`aria-haspopup\`, \`aria-controls\` 등)을 사용합니다.
- **포커스 링 가시성:** 모든 인터랙티브 요소는 포커스를 받았을 때 아웃라인(\`:focus-visible\`)이 명확하게 나타나야 합니다.
---
## 작업 지시 사항
1. UI 마크업 스페셜리스트가 작성한 \`${componentPath}\` 파일을 상세 분석합니다.
2. 접근성 상의 미비점(예: 키보드 미지원 드롭다운, 라벨링이 누락된 버튼 등)을 발견하면 원본 코드의 핵심 구조를 깨지 않는 선에서 **Surgical Changes(최소한의 정밀 수정)**로 접근성 강화 코드를 삽입하세요.
3. 주석을 통해 어떤 부분이 접근성 보장을 위해 개선되었는지 기록하세요.
`;
// 3. QA/Testing Specialist 지시서
const testingTask = `# 🧪 Role 3: QA/유닛 테스트 스페셜리스트 지시서
## 목표
- 완성된 \`${componentPath}\`에 대해 완벽한 유닛 테스트 케이스를 생성합니다.
- 컴포넌트의 비즈니스 반응형 흐름, 엣지 케이스, 이벤트 방출, 컴포넌트 마운트 상태, 그리고 접근성 레이블들의 존재성까지 종합 검증합니다.
## 대상 파일
- \`${testPath}\` (신규 생성)
## 테스트 시나리오 설계 요건
- **마운트 상태 검증:** 기본 props를 전달했을 때 컴포넌트가 깨짐 없이 정상 마운트되고 지정된 요소들이 화면에 나오는지 확인.
- **인터랙션/상태 변경 테스트:** 사용자가 클릭, 입력 등을 수행할 때 로컬 상태가 정확히 변하고, 부모 컴포넌트로 적절한 \`emit\`이 전달되는지 검증.
- **경계 조건(Edge Cases):** Props 값이 비어있거나, 최대 길이 초과 등 예외 상황에서 에러 메시지가 렌더링되거나 방어 동작이 작동하는지 검증.
- **접근성(A11y) 속성 검증:** 주요 클릭 타깃의 \`aria-label\` 유효성, 상태 전이에 따른 \`aria-expanded\`의 동적 변경 값 검증.
---
## 작업 지시 사항
1. Vitest 및 \`@vue/test-utils\` 조합으로 \`${testPath}\` 파일을 작성합니다.
2. 테스트를 실행하여 모든 케이스가 초록색(PASS)을 반환하는지 수동 또는 자동으로 검증하세요.
3. 테스트 코드는 가독성이 뛰어나야 하며, 복잡한 상태 관리가 사용된다면 Mocking을 적극적으로 활용하세요.
`;
// 4. Master Run Orchestrator Script (run-squad.js)
const masterRunScript = `const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const COMPONENT_NAME = "${componentName}";
const COMPONENT_PATH = "${componentPath}";
const TEST_PATH = "${testPath}";
console.log(\`🚀 [Squad Run] \${COMPONENT_NAME} 스쿼드 개발 자동화 파이프라인 가동!\\n\`);
function runCommand(command) {
try {
console.log(\`👉 실행 중: \${command}\`);
execSync(command, { stdio: 'inherit' });
return true;
} catch (error) {
console.error(\`❌ 에러 발생: \${command}\`);
return false;
}
}
// Phase 1: Markup Specialist 실행
console.log('----------------------------------------------------');
console.log('🎨 Phase 1: 마크업 & UI 구현 (Role 1)');
console.log('----------------------------------------------------');
const markupPrompt = \`squad/\${COMPONENT_NAME}/tasks/01_markup_agent.md 지시서에 명시된 목표와 프레임워크 규칙에 맞추어 \${COMPONENT_PATH} 파일을 먼저 구현해 주세요. 작업을 마친 후에는 코드만 마크다운 파일 등에 별도로 복사해 두는 것이 아니라, 실제 파일로 저장한 다음 상태를 알려주세요.\`;
runCommand(\`claude -p "\${markupPrompt}"\`);
// Phase 2: A11y Specialist 실행
console.log('\\n----------------------------------------------------');
console.log('♿ Phase 2: WCAG 2.1 AA 웹 접근성 개선 (Role 2)');
console.log('----------------------------------------------------');
const a11yPrompt = \`squad/\${COMPONENT_NAME}/tasks/02_a11y_agent.md 지시서를 기반으로, 앞서 생성된 \${COMPONENT_PATH} 파일의 코드를 검토하여 접근성을 강화해 주세요. Surgical Changes 원칙에 따라 핵심 마크업은 보존하고 ARIA 속성, 대체 텍스트, 키보드 핸들링만 지능적으로 주입 및 수정한 뒤 저장해 주세요.\`;
runCommand(\`claude -p "\${a11yPrompt}"\`);
// Phase 3: Testing Specialist 실행
console.log('\\n----------------------------------------------------');
console.log('🧪 Phase 3: QA 및 유닛 테스트 케이스 구축 (Role 3)');
console.log('----------------------------------------------------');
const testingPrompt = \`squad/\${COMPONENT_NAME}/tasks/03_test_agent.md 지시서에 맞춰 \${TEST_PATH} 유닛 테스트 파일을 생성하고 Vitest를 기반으로 작성해 주세요.\`;
runCommand(\`claude -p "\${testingPrompt}"\`);
// Phase 4: 최종 테스트 검증 및 코드 정리
console.log('\\n----------------------------------------------------');
console.log('🏁 Phase 4: 전체 스쿼드 통합 테스트 및 코드 정리');
console.log('----------------------------------------------------');
if (fs.existsSync(TEST_PATH)) {
console.log('🔬 Vitest 테스트 실행으로 품질 최종 점검...');
runCommand('npx vitest run ' + TEST_PATH);
} else {
console.log('⚠️ 테스트 파일이 생성되지 않았습니다.');
}
console.log('\\n✨ 모든 에이전트의 스쿼드 협업을 통해 초고품질 컴포넌트가 빌드되었습니다!');
console.log(\`📍 컴포넌트 위치: \${COMPONENT_PATH}\`);
console.log(\`📍 테스트 파일: \${TEST_PATH}\`);
`;
// 5. Squad Readme
const squadReadme = `# 🚀 AI 개발 스쿼드 (Squad) 오케스트레이션 가이드: ${componentName}
이 폴더는 고품질의 프론트엔드 컴포넌트 개발을 위해 **3가지 전문화된 AI 역할군**을 정의하고 작업을 위임하는 오케스트레이션 패키지입니다.
## 📁 파일 구조
- \`tasks/01_markup_agent.md\`: UI 레이아웃, 마크업 및 기본 구조 설계 지시서
- \`tasks/02_a11y_agent.md\`: WCAG 2.1 AA 및 키보드 접근성 개선 지시서
- \`tasks/03_test_agent.md\`: Vitest 기반 유닛 테스트 코드 작성 지시서
- \`run-squad.js\`: 로컬의 Claude Code를 활용해 세 단계를 연속/자동으로 가동시키는 마스터 오케스트레이터 스크립트
## 🛠 실행 방법
### 방법 A: 전체 자동 가동 (Claude Code CLI 이용)
프로젝트 루트에서 다음 노드 명령어를 실행하면 세 가지 에이전트 작업이 순차적으로 트리거되고 최종 유닛 테스트 검증까지 완벽히 오토파일럿으로 수행됩니다:
\`\`\`bash
node squad/${componentName}/run-squad.js
\`\`\`
### 방법 B: 수동 협업 가동 (각 전문 에이전트 수동 프롬프트)
만약 Claude Code 세션을 단계별로 수동 제어하고 싶다면, 아래 순서대로 각 지시서 파일을 Claude에게 먹여서 진행할 수도 있습니다:
1. **Step 1:** \`squad/${componentName}/tasks/01_markup_agent.md\` 지시 내용을 Claude에게 전달 후 UI 마크업 파일 생성.
2. **Step 2:** \`squad/${componentName}/tasks/02_a11y_agent.md\` 지시 내용을 전달하여 기존 UI 마크업 파일에 접근성 코드 주입.
3. **Step 3:** \`squad/${componentName}/tasks/03_test_agent.md\` 지시 내용을 전달하여 Vitest 테스트 세트 확보.
4. **Step 4:** 터미널에서 \`npx vitest run components/${componentName}.spec.ts\` 실행 및 검증 완료!
`;
// 파일들 쓰기
writeFile(path.join(taskDir, 'tasks/01_markup_agent.md'), markupTask);
writeFile(path.join(taskDir, 'tasks/02_a11y_agent.md'), a11yTask);
writeFile(path.join(taskDir, 'tasks/03_test_agent.md'), testingTask);
writeFile(path.join(taskDir, 'run-squad.js'), masterRunScript);
writeFile(path.join(taskDir, 'README.md'), squadReadme);
return taskDir;
}
// 메인 비화 및 입력 대기 제어
function run() {
const args = parseArgs();
if (args.name && args.spec) {
console.log(`🤖 전달받은 CLI 아규먼트로 스쿼드를 즉시 세팅합니다...`);
const path = generateSquadTasks(args.name, args.spec);
console.log(`\n🎉 에이전트 스쿼드가 성공적으로 정렬되었습니다!`);
console.log(`📍 위치: ${path}`);
console.log(`💬 'node ${path}/run-squad.js' 명령으로 전체 프로세스를 가동하거나 가이드 문서를 읽어보세요.`);
process.exit(0);
}
// 대화형 질문으로 수행
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log('👥 [Squad Orchestrator] 새로운 고품질 피처 개발을 위해 스쿼드를 조직합니다.');
rl.question('💬 개발할 컴포넌트/피처 이름을 작성해 주세요 (예: GameScoreBoard): ', (name) => {
if (!name.trim()) {
console.log('❌ 컴포넌트 이름은 필수입니다. 중단되었습니다.');
rl.close();
return;
}
rl.question('💬 구현할 세부 요건/기능 스펙을 적어주세요:\n> ', (spec) => {
if (!spec.trim()) {
spec = '기본적인 반응형 디자인과 상태 데이터 흐름을 갖춘 고품질 컴포넌트 설계';
}
const path = generateSquadTasks(name.trim(), spec.trim());
console.log(`\n🎉 에이전트 스쿼드가 성공적으로 정렬되었습니다!`);
console.log(`📍 위치: ${path}`);
console.log(`💬 'node ${path}/run-squad.js' 명령으로 전체 프로세스를 가동하거나 가이드 문서를 읽어보세요.`);
rl.close();
});
});
}
run();

View File

@@ -0,0 +1,130 @@
---
name: verify-a11y
description: |
Vue 3 / HTML 코드를 WCAG 2.1 AA 기준으로 자동 감사하고
ARIA 레이블 누락, 키보드 포커스 순서, 색상 대비 비율 등의 문제와
코드 레벨 개선 방안을 제시합니다.
다음 상황에서 반드시 사용하세요:
- "접근성 검증해줘", "WCAG 체크해줘", "a11y 확인해줘"
- ARIA 속성이 빠진 것 같을 때
- QA 전 접근성 관련 반려를 사전에 방지하고 싶을 때
---
# 접근성 검증 (verify-a11y)
Vue 3 / HTML → WCAG 2.1 AA 자동 감사 → 코드 레벨 개선 방안 리포트.
## 언제 사용하는가
- 마크업 완료 후 접근성 기준 충족 여부를 확인할 때
- 스크린 리더 사용자를 위한 ARIA 적용이 필요할 때
- 키보드 전용 사용자 지원이 필요할 때
## 검사 기준: WCAG 2.1 AA
| 원칙 | 핵심 항목 |
|---|---|
| **인식 가능** | 이미지 대체 텍스트, 색상 대비 비율, 캡션 |
| **운용 가능** | 키보드 접근성, 포커스 순서, 충분한 시간 |
| **이해 가능** | 레이블, 오류 식별, 일관된 네비게이션 |
| **견고성** | 유효한 HTML, ARIA 역할 |
---
## 작업 순서
### Phase 1: 코드 수집
1. 검증 대상 파일을 읽는다. (`.vue`, `.html`, 템플릿 코드)
2. 검증 범위가 넓으면 화면 단위로 분리해 진행한다.
### Phase 2: 체크리스트 점검
#### 이미지 & 미디어
- [ ] 의미 있는 `<img>`: `alt` 텍스트 존재
- [ ] 장식용 `<img>`: `alt=""`
- [ ] `<video>`: 자막(captions) 또는 스크립트 제공
#### 색상 대비
- [ ] 일반 텍스트: 대비 비율 4.5:1 이상
- [ ] 대형 텍스트 (18px+ 또는 14px+ bold): 3:1 이상
- [ ] 아이콘/그래픽 UI: 3:1 이상
#### 키보드 접근성
- [ ] 모든 인터랙션 요소가 키보드로 접근 가능
- [ ] 포커스 순서가 시각적 레이아웃과 일치
- [ ] 포커스 링(outline) 가시성 확보 (`:focus-visible`)
- [ ] 모달/드롭다운 열릴 때 포커스 이동, 닫힐 때 원래 요소로 복귀
#### ARIA 적용
- [ ] `<button>` 아이콘 전용: `aria-label` 또는 `aria-labelledby`
- [ ] 모달: `role="dialog"`, `aria-modal="true"`, `aria-labelledby`
- [ ] 폼 에러: `aria-describedby` → 에러 메시지 연결
- [ ] 로딩 영역: `aria-live="polite"` 또는 `aria-busy="true"`
- [ ] 탭 UI: `role="tablist"`, `role="tab"`, `role="tabpanel"`, `aria-selected`
- [ ] 아코디언: `aria-expanded`, `aria-controls`
- [ ] 드롭다운 메뉴: `aria-haspopup`, `aria-expanded`
#### 폼
- [ ] 모든 입력 필드에 연결된 `<label>` 존재
- [ ] 필수 필드: `aria-required="true"` 또는 `required`
- [ ] 에러 메시지: `role="alert"` 또는 `aria-live="assertive"`
#### 제목 계층
- [ ] `h1``h2``h3` 순서 준수 (건너뛰기 없음)
- [ ] 페이지에 `h1` 1개만 존재
#### 랜드마크
- [ ] `<header>`, `<nav>`, `<main>`, `<footer>` 적절히 사용
- [ ] 여러 `<nav>`가 있으면 `aria-label`로 구분
### Phase 3: 개선 방안 코드 제시
```vue
<!-- Before: 아이콘 버튼 접근성 없음 -->
<button @click="close">
<IconClose />
</button>
<!-- After: aria-label 추가 -->
<button @click="close" aria-label="닫기">
<IconClose aria-hidden="true" />
</button>
```
```vue
<!-- Before: 에러 접근성 없음 -->
<input type="email" />
<p class="text-red-500">올바른 이메일을 입력하세요</p>
<!-- After -->
<input
type="email"
:aria-describedby="hasError ? 'email-error' : undefined"
:aria-invalid="hasError"
/>
<p v-if="hasError" id="email-error" role="alert">
올바른 이메일을 입력하세요
</p>
```
---
## 출력 형식
```
## 접근성 검증 리포트: <파일명>
### 🚨 Critical — WCAG 위반 (N건)
- [라인 NN] 문제 설명 + 수정 코드
### ⚠️ Warning — 개선 권장 (N건)
- [라인 NN] 문제 설명 + 수정 방향
### 💡 Info — 선택 개선 (N건)
- [라인 NN] 개선 사항
### ✅ 적절히 처리된 항목
- ARIA 레이블 적용됨, 색상 대비 충족 등
```

View File

@@ -0,0 +1,141 @@
---
name: verify-perf
description: |
Nuxt 프로젝트의 성능 병목을 분석하고 Core Web Vitals 기준으로
코드 스플리팅, 이미지 최적화, SSR/ISR 전략 등 구체적인 개선 방안을 제시합니다.
다음 상황에서 반드시 사용하세요:
- "성능 최적화해줘", "Lighthouse 점수 올려줘"
- "LCP가 느린데 어떻게 해?", "번들 사이즈 줄여줘"
- Core Web Vitals 기준 미달 시 개선 방향이 필요할 때
---
# 성능 최적화 분석 (verify-perf)
Nuxt 프로젝트 → 성능 병목 자동 분석 → Core Web Vitals 개선 가이드 생성.
## 언제 사용하는가
- 배포 전 성능 기준(LCP < 2.5s / CLS < 0.1 / INP < 200ms) 충족 여부 확인
- Lighthouse 점수 80+ 달성을 위한 구체적 액션이 필요할
- 번들 사이즈 증가, 이미지 로딩 지연 특정 성능 문제가 있을
## Core Web Vitals 목표
| 지표 | 목표 | 기준 |
|---|---|---|
| LCP (Largest Contentful Paint) | < 2.5s | 가장 콘텐츠 렌더링 |
| CLS (Cumulative Layout Shift) | < 0.1 | 레이아웃 안정성 |
| INP (Interaction to Next Paint) | < 200ms | 입력 반응성 |
| FCP (First Contentful Paint) | < 1.8s | 콘텐츠 노출 |
---
## 작업 순서
### Phase 1: 코드 분석
1. 아래 파일을 읽어 성능 관련 설정을 파악한다.
- `nuxt.config.ts`: 이미지 모듈, SSR 설정, 번들러 옵션
- `pages/` `components/`: 이미지 태그, 폰트 로딩, 라이브러리 import
- `package.json`: 번들 크기 영향이 패키지 확인
2. 일반적인 병목 패턴을 체크한다.
**번들 크기**
- `import * from '...'` (tree-shaking 비활성화)
- 전체 import 라이브러리 (lodash, moment )
- 대형 아이콘 라이브러리 전체 import
**이미지**
- `<img>` 직접 사용 (Nuxt `<NuxtImg>` 미사용)
- `width`, `height` 미지정 (CLS 발생)
- 레이지 로딩 미적용
**렌더링 전략**
- 정적 콘텐츠에 `useFetch` 남용
- 불필요한 CSR 컴포넌트
### Phase 2: 개선 방안 생성
#### 이미지 최적화
```vue
<!-- Before -->
<img src="/hero.jpg" alt="히어로" />
<!-- After: Nuxt Image 모듈 활용 -->
<NuxtImg
src="/hero.jpg"
alt="히어로"
width="1200"
height="600"
loading="lazy"
format="webp"
/>
```
#### 번들 최적화
```ts
// nuxt.config.ts
export default defineNuxtConfig({
vite: {
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'pinia'],
}
}
}
}
}
})
```
#### SSR / SSG / ISR 전략
```ts
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }, // 정적 홈
'/products/**': { swr: 3600 }, // ISR: 1시간 캐시
'/dashboard/**': { ssr: false }, // CSR: 인증 필요 페이지
}
})
```
#### 컴포넌트 지연 로딩
```vue
<!-- 무거운 컴포넌트 lazy 로딩 -->
<LazyHeavyChart v-if="isVisible" />
```
### Phase 3: 우선순위 정렬
발견된 개선사항을 impact/effort 매트릭스로 정렬한다:
- 🔴 High impact, Low effort 즉시 적용
- 🟡 High impact, High effort 스프린트 계획
- 🟢 Low impact, Low effort 여유 있을
- Low impact, High effort 보류
---
## 출력 형식
```
## 성능 분석 리포트
### 현황 추정
- 예상 LCP: ~ (분석된 병목 기준)
- 주요 이슈: [번들 사이즈 | 이미지 | 렌더링 전략]
### 🔴 즉시 적용 (High impact, Low effort)
1. [문제] → [해결책 + 코드]
### 🟡 스프린트 계획 (High impact, High effort)
1. [문제] → [해결 방향]
### 예상 개선 효과
- LCP: Xs → Xs
- 번들: XkB → XkB (XX% 감소)
```

View File

@@ -0,0 +1,90 @@
---
name: verify-requirement
description: |
plan-analyzer가 생성한 요구사항 명세와 실제 구현 코드를 자동 비교하여
누락 기능, 스펙 불일치, 미구현 항목을 사전에 감지합니다.
다음 상황에서 반드시 사용하세요:
- "요구사항 대비 누락 기능 체크해줘", "기획서랑 구현 맞는지 확인해줘"
- QA 전 스펙 불일치를 미리 잡고 싶을 때
- 개발 완료 후 기획 의도와 일치하는지 검증할 때
---
# 요구사항 검증 (verify-requirement)
plan-analyzer 명세 ↔ 실제 구현 코드 자동 비교 → 누락/불일치 리포트 생성.
## 언제 사용하는가
- 개발 완료 후 QA 이전에 스펙 누락 여부를 확인할 때
- 기획자와 개발자 간 스펙 해석 차이를 사전에 해소할 때
- 릴리스 전 체크리스트를 자동화하고 싶을 때
## 입력
- `plan-analyzer` 산출물 (요구사항 명세 MD 파일)
- 검증 대상 디렉토리 또는 파일 목록
- (선택) 검증 범위: 화면 목록, API 엔드포인트, 컴포넌트 트리
---
## 작업 순서
### Phase 1: 요구사항 명세 파싱
1. plan-analyzer 산출물에서 아래 항목을 추출한다.
- 화면(페이지) 목록과 라우팅 경로
- 각 화면의 핵심 기능 목록
- API 엔드포인트 목록
- 컴포넌트 트리
2. 명세가 없는 경우 사용자에게 파일 경로를 요청한다.
### Phase 2: 구현 현황 파악
1. `pages/` 디렉토리를 스캔해 실제 라우팅 구현 현황을 파악한다.
2. `components/` 디렉토리에서 구현된 컴포넌트 목록을 수집한다.
3. `server/api/` 에서 구현된 엔드포인트를 파악한다.
4. 각 화면 파일에서 아래를 확인한다.
- 기획서에 명시된 기능 구현 여부
- 폼/버튼/모달 등 인터랙션 요소 구현 여부
- 에러/로딩/빈 상태 처리 여부
### Phase 3: 비교 및 분류
발견된 차이를 3단계로 분류한다:
| 등급 | 기준 | 예시 |
|---|---|---|
| 🚨 Critical | 핵심 기능 미구현 | 결제 버튼 없음, 필수 API 미연동 |
| ⚠️ Warning | 일부 기능 누락 또는 스펙 불일치 | 에러 상태 처리 없음, 페이지네이션 미구현 |
| 💡 Info | 선택 기능 누락 또는 UX 개선 사항 | 로딩 스피너 없음, 빈 상태 메시지 없음 |
### Phase 4: 개선 방안 제시
각 Critical / Warning 항목에 대해 구체적인 구현 방향을 제안한다.
---
## 출력 형식
```
## 요구사항 검증 리포트
### 검증 범위
- 화면: N개 / 구현: N개 (일치율 NN%)
- API: N개 / 구현: N개 (일치율 NN%)
- 컴포넌트: N개 / 구현: N개 (일치율 NN%)
### 🚨 Critical (N건)
- [화면명] 기능 설명 — 구체적 해결 방안
### ⚠️ Warning (N건)
- [화면명] 기능 설명 — 구체적 해결 방안
### 💡 Info (N건)
- [화면명] 개선 사항
### ✅ 일치 항목
- [목록]
```

View File

@@ -0,0 +1,143 @@
---
name: verify-seo-geo
description: |
Nuxt 프로젝트를 SEO · AEO · GEO 3계층으로 자동 감사하고
useSeoMeta, useSchemaOrg 기반 메타/구조화 데이터 코드를 자동 생성합니다.
AI 검색(ChatGPT, Perplexity, Google AI Overview) 대응까지 포함합니다.
다음 상황에서 반드시 사용하세요:
- "SEO 검증해줘", "메타 태그 확인해줘", "AEO/GEO 대응해줘"
- Schema.org 구조화 데이터가 필요할 때
- AI 검색 노출을 높이고 싶을 때
---
# SEO · GEO · AEO 검증 (verify-seo-geo)
Nuxt 프로젝트 → SEO / AEO / GEO 3계층 자동 감사 → 메타 + Schema 코드 생성.
## 언제 사용하는가
- 배포 전 SEO 메타 누락 여부를 확인할 때
- AI 검색 엔진(ChatGPT, Perplexity 등)에 콘텐츠 인용률을 높이고 싶을 때
- Schema.org 구조화 데이터를 처음 적용할 때
## 3계층 정의
| 계층 | 목표 | 핵심 기술 |
|---|---|---|
| **SEO** | 전통 검색 엔진 노출 | 메타 태그, Open Graph, sitemap |
| **AEO** (Answer Engine Optimization) | 검색 결과 직접 답변(Featured Snippet) | FAQ Schema, HowTo Schema |
| **GEO** (Generative Engine Optimization) | AI 검색 인용 | 구조화 데이터, 명확한 문서 구조 |
---
## 작업 순서
### Phase 1: 현황 감사
1. 대상 페이지 파일을 읽어 아래 항목을 체크한다.
#### SEO 기본 체크리스트
- [ ] `useSeoMeta()` 사용 여부
- [ ] `title`, `description` 설정 여부 (title 50~60자, description 150~160자)
- [ ] `ogTitle`, `ogDescription`, `ogImage` 설정 여부
- [ ] `canonical` URL 설정 여부
- [ ] `<NuxtImg>` 사용 + `alt` 텍스트 여부
- [ ] H1 태그 존재 및 키워드 포함 여부
- [ ] 페이지 로딩 속도 (verify-perf 연계)
#### AEO / GEO 체크리스트
- [ ] FAQ, HowTo, Article 등 Schema.org 적용 여부
- [ ] 구조화 데이터 JSON-LD 유효성
- [ ] 콘텐츠 답변 가능성 (질문 → 명확한 답변 구조)
### Phase 2: SEO 코드 생성
```ts
// pages/product/[id].vue
useSeoMeta({
title: `${product.name} | 브랜드명`,
description: product.description.slice(0, 155),
ogTitle: `${product.name} | 브랜드명`,
ogDescription: product.description.slice(0, 155),
ogImage: product.imageUrl,
ogType: 'product',
twitterCard: 'summary_large_image',
})
useHead({
link: [{ rel: 'canonical', href: `https://example.com/product/${product.id}` }],
})
```
### Phase 3: Schema.org 구조화 데이터
#### FAQ Schema (AEO)
```ts
useSchemaOrg([
defineQuestion({
name: '자주 묻는 질문 1',
acceptedAnswer: { text: '답변 내용' },
}),
])
```
#### HowTo Schema
```ts
useSchemaOrg([
defineHowTo({
name: '사용 방법',
step: [
{ name: '1단계', text: '설명' },
{ name: '2단계', text: '설명' },
],
}),
])
```
#### Article Schema (GEO)
```ts
useSchemaOrg([
defineArticle({
headline: article.title,
description: article.summary,
datePublished: article.publishedAt,
dateModified: article.updatedAt,
author: [{ name: article.author }],
}),
])
```
### Phase 4: GEO 콘텐츠 구조 개선 제안
AI 검색 인용을 높이기 위한 콘텐츠 구조 권고:
- 명확한 소제목(H2/H3)으로 콘텐츠 분절
- 핵심 개념은 첫 단락에 요약
- 테이블/리스트로 스캔 가능한 정보 구조화
- 정의·비교·절차 형식의 콘텐츠 우선 작성
---
## 출력 형식
```
## SEO/GEO/AEO 검증 리포트: <페이지명>
### SEO 현황
- title: 있음 / 없음 / 글자수 초과
- description: 있음 / 없음
- OG 태그: NN/6 항목 적용
- Schema.org: 없음 / FAQ / Article / ...
### 🚨 Critical
- [누락된 필수 메타 항목]
### ⚠️ Warning
- [개선 권장 항목]
### 생성된 코드
[useSeoMeta + useSchemaOrg 코드]
### GEO 개선 제안
[콘텐츠 구조 개선 방향]
```

View File

@@ -0,0 +1,109 @@
---
name: work-code-reviewer
description: |
현재 브랜치 변경 코드 또는 특정 파일을 받아 팀 공통 지침 기준으로 종합 리뷰를 수행합니다.
컨벤션, 로직, 보안, 성능, 접근성을 통합 검토하고 우선순위별 리포트를 생성합니다.
다음 상황에서 반드시 사용하세요:
- "변경된 코드 리뷰해줘", "이 파일 코드 리뷰해줘"
- PR 올리기 전 셀프 리뷰가 필요할 때
- 특정 파일의 전반적인 품질을 점검할 때
---
# 코드 리뷰어 (work-code-reviewer)
변경 코드 / 파일 → 컨벤션 · 로직 · 보안 · 성능 통합 리뷰 → 우선순위별 리포트.
## 언제 사용하는가
- MR 올리기 전 셀프 리뷰 / 사전 점검
- 특정 파일의 전반적인 코드 품질 검토
- 리뷰 요청 전 자가 진단
## 입력
- `git diff` 출력, 파일 경로 목록, 또는 코드 스니펫
- (선택) 리뷰 초점 (컨벤션 / 로직 / 보안 / 전체)
---
## 작업 순서
### Phase 1: 검토 범위 파악
1. 대상 파일 또는 diff를 읽는다.
2. 파일 유형별로 적용할 검토 항목을 결정한다.
- `.vue` → 컨벤션 + 컴포넌트 구조 + 접근성
- `.ts` → 타입 안전성 + 로직
- `server/` → 보안 + 에러 핸들링
- `stores/` → 상태 관리 패턴
- `composables/` → 부수효과 최소화
### Phase 2: 통합 체크리스트
#### 코드 품질
- [ ] 함수/파일 단일 책임 원칙
- [ ] 중복 코드 없음 (재사용 가능 composable/유틸 추출 여부)
- [ ] 매직 넘버/문자열 → 상수 또는 i18n 키
- [ ] 불필요한 `console.log` 제거
#### 타입 안전성
- [ ] `any` 타입 미사용
- [ ] 외부 API 응답 타입 정의
- [ ] 함수 파라미터/반환 타입 명시
#### 에러 핸들링
- [ ] API 호출 try-catch 또는 onError 처리
- [ ] 에러 상태 UI 존재 여부
- [ ] 엣지 케이스 (null, undefined, 빈 배열) 처리
#### 성능
- [ ] 불필요한 watch / watchEffect 없음
- [ ] computed 캐싱 활용
- [ ] 과도한 reactive 래핑 없음
#### 보안
- [ ] `v-html` 입력 새니타이징
- [ ] 민감 정보 하드코딩 없음
- [ ] 클라이언트 노출 불필요한 데이터 없음
#### Vue 컨벤션 (`.vue` 파일)
`verify-component-review` 스킬의 체크리스트 전체 적용.
### Phase 3: 리뷰 코멘트 작성
`work-mr-reviewer`의 코멘트 형식과 동일:
- 🚨 Critical / ⚠️ Warning / 💡 Nit 3단계 분류
- 파일명:라인번호 명시
- 수정 코드 예시 포함
### Phase 4: 요약
개선 전/후 예상 품질 변화를 간단히 서술한다.
---
## 출력 형식
```
## 코드 리뷰: <파일명 또는 기능명>
### 검토 요약
- 파일: N개
- 주요 이슈: [컨벤션 | 로직 | 보안 | 성능]
### 🚨 Critical (N건)
[파일:라인] 문제 + 수정 코드
### ⚠️ Warning (N건)
[파일:라인] 문제 + 수정 방향
### 💡 Nit (N건)
[목록]
### ✅ 잘된 점
[1~3개]
### 개선 후 기대 효과
[한 줄 요약]
```

View File

@@ -0,0 +1,111 @@
---
name: work-mr-reviewer
description: |
GitLab MR URL 또는 diff를 받아 팀 공통 지침(gameservice-fe-agent) 기준으로
코드 리뷰 코멘트 초안을 자동 생성합니다. 리뷰어가 비즈니스 로직에 집중할 수 있도록
컨벤션/스타일 지적은 AI가 사전 처리합니다.
다음 상황에서 반드시 사용하세요:
- "이 MR 리뷰해줘", "MR 코멘트 작성해줘"
- "이 PR 어떤지 봐줘: <URL>"
- 리뷰어 역할로 MR을 검토하기 전 사전 검토가 필요할 때
---
# GitLab MR 리뷰어 (work-mr-reviewer)
MR URL 또는 diff → 팀 공통 지침 기준 리뷰 코멘트 초안 자동 생성.
## 언제 사용하는가
- MR 리뷰 요청을 받았을 때 사전 분석이 필요할 때
- 리뷰 코멘트 작성 시간을 줄이고 싶을 때
- 컨벤션 위반 사항을 일괄 감지하고 싶을 때
## 입력
- GitLab MR URL 또는 `git diff` 출력
- (선택) 리뷰 우선순위 (컨벤션 중심 / 로직 중심 / 전체)
---
## 작업 순서
### Phase 1: 변경사항 파악
1. MR URL이 제공된 경우 diff를 가져온다.
2. 변경된 파일 목록과 변경 규모를 파악한다.
3. 변경 유형을 분류한다: 신규 기능 / 버그픽스 / 리팩토링 / 설정 변경
### Phase 2: 자동 컨벤션 검토
`verify-component-review` 스킬의 체크리스트를 변경된 Vue 파일에 적용한다:
- `<script setup lang="ts">` 사용 여부
- `defineProps<T>()` / `defineEmits<T>()` 제네릭 형태
- `any` 타입 사용 여부
- Tailwind 클래스 7단계 순서
- 네이밍 컨벤션 (camelCase / PascalCase / UPPER_SNAKE_CASE)
- import 순서
### Phase 3: 로직 / 구조 검토
- 컴포넌트 분리 기준 적절성 (200줄 초과 여부)
- 비즈니스 로직의 composable 분리 여부
- 에러 핸들링 / 로딩 상태 처리 여부
- 불필요한 API 호출 또는 상태 중복 여부
- 보안 취약점 (XSS, 민감 정보 등)
### Phase 4: 코멘트 작성
리뷰 코멘트는 아래 형식을 따른다:
```
[등급] 파일명:라인번호
문제: [간단한 설명]
이유: [왜 문제인지 — 팀 규칙 또는 Best Practice 근거]
제안:
\`\`\`
[수정 코드 예시]
\`\`\`
```
등급:
- 🚨 **Blocker** — 반드시 수정 후 머지 (보안, 런타임 에러, 명백한 버그)
- ⚠️ **Major** — 수정 권장 (컨벤션 위반, 성능 문제)
- 💡 **Minor** — 선택적 개선 (가독성, 스타일)
- 💬 **Question** — 로직 확인 필요 (질문 형태)
### Phase 5: 요약
- 전체 변경사항에 대한 한 줄 요약
- Blocker 건수 명시
- 칭찬할 만한 좋은 패턴 1~2개 언급
---
## 출력 형식
```
## MR 리뷰: <MR 제목>
### 변경 요약
- 변경 파일: N개
- 유형: 신규 기능 / 버그픽스 / ...
### 🚨 Blocker (N건)
...
### ⚠️ Major (N건)
...
### 💡 Minor (N건)
...
### 💬 Questions
...
### ✅ 잘된 점
- [좋은 패턴 1~2개]
> 이 리뷰는 AI 초안입니다. 최종 리뷰어의 판단으로 조정해 주세요.
```

116
.omc/project-memory.json Normal file
View File

@@ -0,0 +1,116 @@
{
"version": "1.0.0",
"lastScanned": 1778898050574,
"projectRoot": "/Users/gil/Downloads/gameservice-fe-agent 2",
"techStack": {
"languages": [],
"frameworks": [],
"packageManager": null,
"runtime": null
},
"build": {
"buildCommand": null,
"testCommand": null,
"lintCommand": null,
"devCommand": null,
"scripts": {}
},
"conventions": {
"namingStyle": null,
"importStyle": null,
"testPattern": null,
"fileOrganization": null
},
"structure": {
"isMonorepo": false,
"workspaces": [],
"mainDirectories": [
"docs",
"scripts"
],
"gitBranches": {
"defaultBranch": "main",
"branchingStrategy": null
}
},
"customNotes": [],
"directoryMap": {
"docs": {
"path": "docs",
"purpose": "Documentation",
"fileCount": 5,
"lastAccessed": 1778898050563,
"keyFiles": [
"WDG00.04.02.06.07.01 AI 활용 - CBO-플랫폼서비스개발담당.md",
"fe-ai-reference-flow 2.html",
"fe-ai-reference-flow.html",
"fe-ai-rules.html",
"fe-ai-workflow.html"
]
},
"html": {
"path": "html",
"purpose": null,
"fileCount": 5,
"lastAccessed": 1778898050564,
"keyFiles": [
"fe-agent-structure.html",
"fe-ai-reference-flow.html",
"fe-ai-rules.html",
"fe-ai-workflow-ppt.html",
"fe-ai-workflow.html"
]
},
"rules": {
"path": "rules",
"purpose": null,
"fileCount": 4,
"lastAccessed": 1778898050564,
"keyFiles": [
"claude-workflow.md",
"coding-conventions.md",
"commit-pr.md",
"framework-rules.md"
]
},
"scripts": {
"path": "scripts",
"purpose": "Build/utility scripts",
"fileCount": 4,
"lastAccessed": 1778898050564,
"keyFiles": [
"init-project.sh",
"install.sh",
"link-skills.sh",
"update.sh"
]
},
"skills": {
"path": "skills",
"purpose": null,
"fileCount": 2,
"lastAccessed": 1778898050565,
"keyFiles": [
"README.md"
]
},
"templates": {
"path": "templates",
"purpose": null,
"fileCount": 1,
"lastAccessed": 1778898050565,
"keyFiles": [
"CLAUDE.md.tpl"
]
}
},
"hotPaths": [
{
"path": "session-report-20260516-1137.html",
"accessCount": 4,
"lastAccessed": 1778899125860,
"type": "file"
}
],
"userDirectives": []
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "082bc0a1-f53c-41d5-8f6c-90b000014186",
"ended_at": "2026-05-16T02:21:24.479Z",
"reason": "other",
"agents_spawned": 0,
"agents_completed": 0,
"modes_used": []
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "2f3f79c0-ebbd-4d15-a4d8-d9b3a59f53e5",
"ended_at": "2026-05-21T10:55:51.309Z",
"reason": "other",
"agents_spawned": 0,
"agents_completed": 0,
"modes_used": []
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "7943f1a4-77eb-4ed3-8390-0307b53dde90",
"ended_at": "2026-05-16T02:20:47.812Z",
"reason": "prompt_input_exit",
"agents_spawned": 0,
"agents_completed": 0,
"modes_used": []
}

View File

@@ -0,0 +1,6 @@
{
"timestamp": "2026-05-16T02:22:38.580Z",
"backgroundTasks": [],
"sessionStartTimestamp": "2026-05-16T02:21:29.466Z",
"sessionId": "2f3f79c0-ebbd-4d15-a4d8-d9b3a59f53e5"
}

View File

@@ -0,0 +1,6 @@
{
"timestamp": "2026-05-16T02:13:41.290Z",
"backgroundTasks": [],
"sessionStartTimestamp": "2026-05-16T02:08:47.379Z",
"sessionId": "7943f1a4-77eb-4ed3-8390-0307b53dde90"
}

View File

@@ -0,0 +1,6 @@
{
"timestamp": "2026-05-16T02:13:41.290Z",
"backgroundTasks": [],
"sessionStartTimestamp": "2026-05-16T01:40:11.643Z",
"sessionId": "8c56053b-8d23-4314-926d-5895c6c27fd9"
}

View File

@@ -20,6 +20,8 @@
@rules/claude-workflow.md
@skills/squad-orchestration/docs/automation-guide.md
## 우선순위
1. 프로젝트 `CLAUDE.md`에 명시된 **프로젝트 지침**이 최우선입니다.
@@ -37,3 +39,42 @@
- 공통 지침은 이 레파지토리(`gameservice-fe-agent`)에서만 수정합니다.
- 각 프로젝트는 `scripts/update.sh`(또는 `git submodule update --remote`)로 최신 버전을 받아갑니다.
- 수정 제안은 PR로 받습니다. 자세한 내용은 루트 `README.md` 참고.
## 주요 명령어 (이 레포에서 작업 시)
```bash
# 프로젝트에 이 레포 설치 (대상 프로젝트에서 실행)
bash scripts/install.sh <repo-url> [branch]
# 공통 skill 을 .claude/skills/ 로 심볼릭 링크
bash .claude/common/scripts/link-skills.sh
# 최신 버전으로 submodule 업데이트 (대상 프로젝트에서 실행)
bash .claude/common/scripts/update.sh
```
## 새 스킬 추가 절차
1. `skills/<skill-name>/SKILL.md` 파일 생성 (YAML frontmatter + 지시문)
2. 필요 시 `skills/<skill-name>/` 하위에 보조 파일 추가
3. PR로 팀 리뷰 후 머지
4. 각 프로젝트에서 `update.sh` + `link-skills.sh` 실행
`SKILL.md` 최소 형식:
```markdown
---
name: <skill-name>
description: <Claude가 skill을 언제 써야 하는지 줄로>
---
# <스킬 제목>
```
## Behavioral Guidelines
> 불필요한 코드 작성과 섣부른 구현을 방지하기 위한 지침. 단순한 작업은 판단하여 적용.
- **코딩 전 먼저 생각**: 가정하지 말고, 불확실하면 질문. 여러 해석이 가능하면 제시.
- **단순함 우선**: 요청된 것만 구현. 단일 사용 코드에 추상화 금지. 추측성 기능 추가 금지.
- **최소 변경**: 요청에 직접 연결되지 않는 코드는 건드리지 않음. 인접 코드 "개선" 금지.
- **성공 기준 정의**: 복잡한 작업은 검증 가능한 단계로 분리하여 진행.

View File

@@ -1,85 +0,0 @@
---
title: "WDG00.04.02.06.07.01 AI 활용 - CBO-플랫폼서비스개발담당"
source: "https://wiki.smilegate.net/pages/viewpage.action?pageId=694388022"
author:
- "[[김형길 (Gil)/SGP 커뮤니티 Product]]"
published:
created: 2026-05-04
description:
tags:
- "clippings"
---
## AI 활용 방안
---
퍼블리싱 FE 전체 워크프로세스에서 Claude Code Skills 기반 AI 도입 전(As-Is)과 도입 후(To-Be)를 단계별로 정리합니다.
---
## 단계별 Skill
| 단계 | Skill | 프롬프트 예시 | 산출물 |
| --- | --- | --- | --- |
| 프로젝트 Init | `project-init` | "프로젝트 초기화해줘" / `/init` | `.claude/project/*.md` |
| 기획 | `plan-analyzer` | "이 기획서 분석해줘: `<pptx 경로>`" | 요구사항 명세 MD |
| | `plan-translation-generator` | "번역코드 만들어줘: `<xlsx 경로>`" | 번역 키 채워진 xlsx |
| 마크업 | `markup-base` | "이 화면 시멘틱 HTML로 마크업해줘" | `.vue` 컴포넌트 골격 |
| | `markup-figma` | "이 Figma 마크업해줘: `<figma url>`" | `.vue` 컴포넌트 |
| | `markup-promotion` | "프로모션 페이지 마크업해줘" | 랜딩 페이지 `.vue` |
| | `markup-edm` | "EDM 메일 만들어줘: `<figma url>`" | 이메일 HTML |
| 개발 | `dev-component` | "이 화면 컴포넌트로 분리 설계해줘" | 컴포넌트 트리 + SFC 스켈레톤 |
| | `dev-docs` | "Nuxt server route로 API 만들어줘" | Nuxt 3 Best Practice 코드 |
| | `dev-api-state` | "이 API 연동 + Pinia 스토어 만들어줘" | 스토어 + 페칭 코드 |
| | `dev-unit-test` | "이 컴포넌트 단위 테스트 작성해줘" | `.spec.ts` |
| | `dev-storybook` | "이 컴포넌트 Story 만들어줘" | `.stories.ts` + 사용 가이드 |
| 코드 리뷰 | `verify-component-review` | "이 컴포넌트 리뷰해줘" | 우선순위별 리뷰 리포트 |
| 검증 | `verify-requirement` | "요구사항 대비 누락 기능 체크해줘" | 누락/불일치 리포트 |
| | `verify-perf` | "성능 최적화 분석해줘" | Core Web Vitals 개선 가이드 |
| | `verify-a11y` | "접근성(WCAG) 검증해줘" | a11y 리포트 + 수정 코드 |
| | `verify-seo-geo` | "SEO/AEO/GEO 검증해줘" | 메타 + Schema 코드 |
| | `security-review` | "보안 점검해줘" | 보안 리포트 + 패치 가이드 |
| 기타 업무 스킬 | `work-log` | "오늘 업무일지 작성해줘" | Confluence 업데이트 |
| | `work-mr-reviewer` | "이 MR 리뷰해줘: `<MR URL>`" | 리뷰 코멘트 초안 |
| | `work-code-reviewer` | "변경 코드 리뷰해줘" | 우선순위별 리뷰 리포트 |
## 📋 단계별 As-Is / To-Be
| | | | | | |
| --- | --- | --- | --- | --- | --- |
| **기획** | Phase 1 | - 기획서를 개발자가 수동으로 읽고 페이지 구조·컴포넌트를 직접 설계 - 기획 의도 해석 오류로 인한 재작업 발생 - 플로우차트 작성에 별도 시간 소요 - 개발자마다 요구사항 해석 기준 상이 | - **requirement-analyzer** Skill이 기획서를 자동 파싱 - Nuxt pages/ 라우팅 구조, 컴포넌트 트리, API 엔드포인트 목록 자동 생성 - Mermaid 플로우차트로 화면 전환 흐름 자동 시각화 - 팀 전체가 동일한 요구사항 명세 기반으로 개발 착수 | `requirement-analyzer` | |
| **다국어** | Phase 1-1 | - 번역 요청 후 결과를 다국어 번역코드 키값을 엑셀파일에서 수동 입력 - 누락된 번역 키나 중복 키값 발생 시 런타임에서야 발견 | - AI가 번역 코드 전체에서 번역 대상 문자열에서 자동 추출 생성 - 번역 키 누락 및 중복을 사전 감지 및 경고 | `translation-keys` | |
| **마크업 (기본)** | Phase 2 | - 개발자 역량에 따라 시멘틱 태그 사용 수준 불일치 - Tailwind 클래스 순서 및 패턴 제각각 - 접근성(ARIA) 검토를 별도 단계로 진행 - 반응형 브레이크포인트 구현 기준 없음 | - **markup** Skill이 시멘틱 HTML 구조 자동 생성 - Tailwind 클래스 7단계 순서 컨벤션 자동 적용 - ARIA 레이블 및 접근성 속성 자동 삽입 - 팀 전체 마크업 품질 균일화 | `markup` | |
| **PSD → Figma** | Phase 2-0 | - PSD 파일을 디자이너가 Figma에 수동 재작업 - 레이어 구조 재설계로 많은 시간 소요 - 변환 과정에서 디자인 요소 누락·변형 발생 | - **Codia AI** / **PSD Importer for Figma** 활용으로 자동 컨버팅 - PSD 레이어 구조를 유지하며 Figma 포맷으로 변환 - 디자이너 수작업 시간 대폭 단축 | Codia AI / PSD Importer for Figma | |
| **Figma → HTML** | Phase 2-1 | - 피그마 시안을 보며 개발자가 수동으로 HTML 작성 - 반응형 브레이크포인트를 수동으로 추출·적용 - 피그마 컴포넌트와 HTML 구조 간 매핑 기준 없음 | - **markup (Figma)** Skill + Figma MCP로 레이어 자동 파싱 - 피그마 컴포넌트를 Nuxt SFC 구조로 자동 매핑 - 반응형 브레이크포인트 자동 추출 및 Tailwind 클래스 적용 | `markup` + Figma MCP | |
| **프로모션 마크업** | Phase 2-2 | - 프로모션/랜딩페이지마다 마크업 패턴 상이 - 반응형 대응 일관성 없음 - 프로모션별 재작업 반복으로 생산성 저하 | - **markup-promotion** Skill로 캠페인 표준 마크업 패턴 자동 생성 - 시멘틱 구조, 반응형 일관 적용 - 프로모션 유형별 템플릿 재사용으로 개발 속도 향상 | `markup-promotion` | |
| **EDM 마크업** | Phase 2-3 | - 이메일 클라이언트별(Outlook 등) 호환성 수동 대응 - 테이블 기반 레이아웃 반복 작성으로 시간 소요 - 인라인 스타일 누락으로 렌더링 오류 발생 | - **markup-edm** Skill로 이메일 클라이언트 호환 table 기반 마크업 자동 생성 - 인라인 스타일 자동 적용으로 렌더링 오류 사전 방지 - 뉴스레터·EDM 표준 구조 일관 유지 | `markup-edm` | |
| **FE 개발 (컴포넌트)** | Phase 3 | - 개발자마다 컴포넌트 분리 기준 상이 - Nuxt 디렉토리 구조(pages/, components/, composables/) 불일치 - 컴포넌트 재사용률 낮음 (30% 수준) - 신규 인력 온보딩 기간 필요 | - **dev-component** Skill이 Atomic Design 기반 컴포넌트 트리 자동 설계 - 표준 디렉토리 구조 자동 제안으로 일관성 확보 - 컴포넌트 재사용률 70%↑ 목표 - 온보딩 기간 단축 | `dev-component` | |
| **Nuxt 공식 문서 기반 개발** | Phase 3-1 | - Nuxt 공식문서를 직접 검색하며 Best Practice 파악 - server/, middleware/, plugins/ 활용 기준 불명확 - composable 작성 시 공식 패턴 확인에 시간 소요 | - **dev-docs** Skill이 6개 참조 문서(server, middleware, plugins, composables, components, config)를 컨텍스트에 따라 선택 로드 - 즉시 Nuxt 3 Best Practice 코드 생성 - 공식문서 탐색 없이 정확한 API 활용 | `dev-docs` | |
| **단위 테스트** | Phase 3-2 | - 컴포넌트 개발 완료 후 테스트 작성 생략 또는 뒤늦게 작성 - Vue Test Utils / Vitest 설정을 매번 조사 - 테스트 케이스 구성 기준 없어 커버리지 20% 미만 - 테스트 코드 패턴이 개발자마다 상이 | - **dev-unit-test** Skill이 컴포넌트 분석 후 Vitest + Vue Test Utils 기반 단위 테스트 자동 생성 - Props / Emits / 슬롯 / 인터랙션 케이스 자동 커버 - 테스트 패턴 팀 표준화로 코드 리뷰 부담 감소 - 커버리지 80%↑ 목표 | `dev-unit-test` | |
| **컴포넌트 문서화** | Phase 3-3 | - 컴포넌트 사용법 문서화 없어 팀원이 소스 코드 직접 확인 - Storybook 미운영으로 컴포넌트 목록 파악 어려움 - Props / Emits 스펙 구두 전달 의존 - 신규 인력이 재사용 가능한 컴포넌트를 중복 개발 | - **dev-storybook** Skill로 컴포넌트 Props / Emits / 슬롯 기반 사용 가이드 자동 생성 - Storybook Story 파일 자동 생성 - 팀 내 컴포넌트 재사용 진입 장벽 감소 - 컴포넌트 카탈로그 자동 갱신 | `dev-storybook` | |
| **API 연동 & 상태관리** | Phase 4 | - useFetch / $fetch / useAsyncData 혼용으로 일관성 없음 - Pinia 스토어 설계 기준 없어 구조 제각각 - 에러 핸들링 패턴 통일 없음 - BFF 패턴 미활용으로 API 키 노출 위험 | - **dev-api-state** Skill로 상황별 데이터 페칭 패턴 자동 선택·생성 - Setup Store 기반 Pinia 스토어 표준 코드 자동 생성 - 에러 핸들링·로딩 상태 처리 패턴 통일 - server/api/ BFF 패턴으로 API 키 보호 | `dev-api-state` | |
| **코드 리뷰** | Phase 4-1 | - PR 리뷰어가 팀 컨벤션을 기억하며 수동 확인 - 리뷰어 역량 차이로 컨벤션 누락 발생 - 반복적인 스타일 지적으로 리뷰 사이클 증가 - 리뷰어 부재 시 병목 발생 - 비즈니스 로직보다 코드 스타일 지적에 리뷰 시간 소비 | - **verify-component-review** Skill이 PR 머지 전 팀 공통 지침 기준으로 자동 사전 검토 - Composition API / 타입 / Tailwind 컨벤션 위반 자동 감지 - 반복적 스타일 지적 제거로 리뷰어가 비즈니스 로직 집중 - 코드 리뷰 반려율 50%↓ 목표 | `verify-component-review` | |
| **접근성 검증** | Phase 5-0 | - 접근성 검토 별도 단계 없음 - ARIA 속성 누락 QA 단계에서야 사후 발견 - 키보드 네비게이션 테스트 수동 진행 - WCAG 기준 인지 부족으로 누락 항목 반복 발생 - 스크린 리더 대응 미검증 | - **verify-a11y** Skill로 WCAG 2.1 AA 기준 자동 검증 - ARIA 레이블 누락 / 키보드 포커스 순서 / 색상 대비 비율 자동 감지 - 개선 가이드 코드 레벨 자동 제시 - 접근성 관련 QA 반려 건수 80%↓ 목표 | `verify-a11y` | |
| **요구사항 검증** | Phase 5-1 | - 개발 완료 후 QA 단계에서 수동 검증 - 기획 대비 누락 기능을 늦게 발견하여 재작업 발생 - 기획자·개발자 간 스펙 해석 차이 사후 발견 | - **requirement-optimizer** Skill이 Phase 1 요구사항 명세와 실제 구현 결과를 자동 비교 - 기능 누락, 스펙 불일치, 미구현 항목 사전 감지 - 개선 방안 자동 제시로 QA 전 품질 확보 | `requirement-optimizer` | |
| **성능 최적화** | Phase 5-2 | - Lighthouse 수동 실행 후 결과 해석에 시간 소요 - Core Web Vitals 개선 방법 별도 조사 필요 - Lighthouse 선택적 확인 - 번들 최적화·이미지 최적화 개발자 개인 역량 의존 | - **perf-optimizer** Skill로 성능 병목 자동 분석 - 코드 스플리팅, 이미지 최적화, SSR/ISR 전략 자동 제안 - LCP < 2.5s / CLS < 0.1 / INP < 200ms 달성 가이드 - Lighthouse 점수 80+ 목표 | `perf-optimizer` | |
| **SEO · GEO · AEO 검증** | Phase 6 | - SEO 메타태그 누락률 40% 수준 - Schema.org 구조화 데이터 미적용 - AI 검색(ChatGPT, Perplexity, Google AI Overview) 대응 전무 - 검색 가시성 개선 활동 미체계화 | - **nuxt-seo-geo** Skill로 SEO · AEO · GEO 3계층 자동 감사 - useSeoMeta() / useSchemaOrg() 기반 메타·구조화 데이터 자동 생성 - FAQ·HowTo Schema 적용으로 AI 검색 직접 답변 노출 - SEO 메타 누락률 5%↓ · AI 검색 인용률 측정 체계 구축 | `nuxt-seo-geo` | |
| **보안 검토** | Phase 6-1 | - 보안 취약점 점검 별도 프로세스 없음 - XSS / CSRF 프론트엔드 보안 항목 개인 역량 의존 - npm audit 결과 미모니터링 - 민감 정보 하드코딩 발생 위험 - 서드파티 라이브러리 취약점 미파악 | - **security-review** Skill로 XSS / CSRF / 의존성 취약점 자동 감지 - 민감 정보 하드코딩 코드베이스 전체 스캔 - npm audit 결과 해석 패치 우선순위 가이드 자동 생성 - 배포 보안 체크리스트 자동 검증 | `security-review` | |
---
## 📊 성과 지표 (KPI)
| | | | |
| --- | --- | --- | --- |
| 코드 리뷰 반려율 | 35% | **15%↓** | Phase 2, 3, 4, 4-1 마크업·컴포넌트·API 패턴 표준화 + verify-component-review 사전 자동 검토 |
| 컴포넌트 재사용률 | 30% | **70%↑** | Phase 3 Atomic Design 기반 컴포넌트 아키텍처 |
| 신규 인력 온보딩 기간 | 3주 | **1주** | Phase 1, 3 요구사항 자동 분석 + Skill 기반 가이드 |
| 단위 테스트 커버리지 | 20% 미만 | **80%↑** | Phase 3-2 dev-unit-test 자동 생성 |
| Lighthouse 성능 점수 | 65~75 | **90+** | Phase 5-2 Core Web Vitals 최적화 |
| 접근성(WCAG) 관련 QA 반려 건수 | 미측정 | **80%↓** | Phase 5-0 verify-a11y 자동 감지 |
| SEO 메타 누락률 | 40% | **5%↓** | Phase 6 SEO·GEO·AEO 통합 검증 |
| AI 검색 인용률 | 미측정 | **측정 체계 구축** | Phase 6 GEO 최적화 (ChatGPT, Perplexity, AI Overview) |
| 프로모션·EDM 마크업 작업 시간 | 건당 수동 작업 | **50%↓ (자동화)** | Phase 2-2, 2-3 프로모션·EDM Skill 적용 |
| 기획-개발 스펙 불일치 건수 | QA 단계 사후 발견 | **개발 중 사전 감지** | Phase 5-1 requirement-optimizer 자동 비교 검증 |
| 보안 취약점 사전 감지율 | 0% (배포 발견) | **배포 전 100% 스캔** | Phase 6-1 security-review 자동 검증 |

View File

@@ -1,794 +0,0 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>프론트엔드 작업 단계별 AI 참조 흐름도</title>
<style>
:root {
--navy: #17172b;
--blue: #0b63ce;
--blue-soft: #eaf4ff;
--purple: #5937b7;
--purple-soft: #f1ebff;
--green: #15975f;
--green-soft: #e8f8ef;
--orange: #d97706;
--orange-soft: #fff4dd;
--yellow: #f2b43f;
--yellow-soft: #fff8e6;
--red: #d94841;
--red-soft: #fff0f0;
--bg: #f5f7fb;
--card: #ffffff;
--line: #dce4ef;
--text: #1f2937;
--muted: #667085;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
color: var(--text);
background: var(--bg);
font-family:
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
"Noto Sans KR",
sans-serif;
line-height: 1.55;
}
.hero {
padding: 34px 44px 36px;
color: #fff;
background: var(--navy);
border-bottom: 4px solid #1677ff;
}
.hero .eyebrow {
margin: 0 0 8px;
color: #8d96b8;
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.hero h1 {
margin: 0 0 10px;
font-size: 27px;
font-weight: 900;
letter-spacing: -0.04em;
}
.hero p {
margin: 0;
color: #c2c8da;
font-size: 13px;
}
.container {
width: min(1180px, calc(100% - 56px));
margin: 28px auto 48px;
}
.card {
margin-bottom: 22px;
padding: 24px;
background: var(--card);
border: 1px solid var(--line);
border-radius: 8px;
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.03);
}
.section-title {
display: flex;
align-items: center;
gap: 8px;
margin: 0 0 18px;
color: #315a9e;
font-size: 15px;
font-weight: 900;
letter-spacing: -0.02em;
}
.section-title::before {
content: "";
width: 4px;
height: 18px;
background: var(--blue);
border-radius: 999px;
}
.notice {
padding: 12px 16px;
margin-bottom: 16px;
color: #17436f;
font-size: 13px;
font-weight: 700;
background: var(--blue-soft);
border-left: 4px solid var(--blue);
border-radius: 4px;
}
.case-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 14px;
}
.case-box {
min-height: 126px;
padding: 18px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
}
.case-box.case1 {
border-color: #a8caff;
background: #f7fbff;
}
.case-box.case2 {
border-color: #c8b5ff;
background: #fbf8ff;
}
.case-label {
display: inline-block;
margin-bottom: 10px;
padding: 4px 11px;
color: #fff;
font-size: 12px;
font-weight: 900;
border-radius: 999px;
}
.case1 .case-label {
background: var(--blue);
}
.case2 .case-label {
background: var(--purple);
}
.case-box h3 {
margin: 0 0 8px;
font-size: 15px;
font-weight: 900;
}
.case-box p {
margin: 0;
color: #475467;
font-size: 13px;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
border: 1px solid var(--line);
}
th,
td {
padding: 12px 14px;
text-align: left;
vertical-align: top;
border-right: 1px solid var(--line);
border-bottom: 1px solid var(--line);
}
th {
color: #344054;
font-weight: 900;
background: #eef4ff;
}
td:last-child,
th:last-child {
border-right: 0;
}
.path {
color: #c0392b;
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 12px;
white-space: nowrap;
}
.load-badge {
display: inline-block;
padding: 3px 8px;
color: #915f00;
background: var(--yellow-soft);
border: 1px solid #e8bd54;
border-radius: 999px;
font-size: 11px;
font-weight: 900;
white-space: nowrap;
}
.load-badge.blue {
color: #0b4fa6;
background: var(--blue-soft);
border-color: #9ec3ff;
}
.quote {
padding: 12px 16px;
margin-bottom: 18px;
color: #0c55b0;
font-size: 14px;
font-weight: 700;
font-style: italic;
background: var(--blue-soft);
border-left: 4px solid var(--blue);
border-radius: 4px;
}
.quote.purple {
color: #4d2e91;
background: var(--purple-soft);
border-left-color: var(--purple);
}
.tree {
position: relative;
padding-left: 24px;
}
.tree::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 6px;
width: 2px;
background: #e4e7ec;
}
.tree-node {
position: relative;
margin: 0 0 16px;
padding: 14px 16px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
}
.tree-node::before {
content: "";
position: absolute;
top: 24px;
left: -18px;
width: 18px;
height: 2px;
background: #e4e7ec;
}
.tree-node:last-child {
margin-bottom: 0;
}
.node-head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
font-weight: 900;
}
.round {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
color: #fff;
font-size: 12px;
font-weight: 900;
background: var(--blue);
border-radius: 999px;
flex-shrink: 0;
}
.round.green {
background: var(--green);
}
.round.orange {
background: var(--orange);
}
.round.purple {
background: var(--purple);
}
.chip-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 8px 0;
}
.chip {
display: inline-block;
padding: 4px 9px;
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 11px;
border: 1px solid;
border-radius: 999px;
background: #fff;
}
.chip.yellow {
color: #8a5a00;
border-color: #e6b64e;
background: var(--yellow-soft);
}
.chip.green {
color: #087443;
border-color: #8bd3aa;
background: var(--green-soft);
}
.chip.blue {
color: #0b4fa6;
border-color: #9ec3ff;
background: var(--blue-soft);
}
.chip.purple {
color: #4d2e91;
border-color: #c6b6ff;
background: var(--purple-soft);
}
.chip.red {
color: #a33a34;
border-color: #e4a39f;
background: var(--red-soft);
}
.work-box {
margin-top: 10px;
padding: 12px 14px;
border-radius: 6px;
font-size: 12px;
}
.work-box.blue {
background: var(--blue-soft);
border-left: 4px solid var(--blue);
}
.work-box.green {
background: var(--green-soft);
border-left: 4px solid var(--green);
}
.work-box.yellow {
background: var(--yellow-soft);
border-left: 4px solid var(--yellow);
}
.work-box.red {
background: var(--red-soft);
border-left: 4px solid var(--red);
}
.work-box ul {
margin: 4px 0 0;
padding-left: 18px;
}
.case2-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 14px;
}
.stage-card {
padding: 16px;
border: 2px solid;
border-radius: 8px;
background: #fff;
}
.stage-card.purple {
border-color: var(--purple);
}
.stage-card.green {
border-color: var(--green);
}
.stage-card.orange {
border-color: var(--orange);
}
.stage-card.red {
border-color: var(--red);
}
.stage-card .stage-title {
display: flex;
align-items: center;
gap: 8px;
margin: 0 0 12px;
font-size: 15px;
font-weight: 900;
}
.example {
padding: 10px 12px;
margin-bottom: 12px;
font-size: 12px;
font-style: italic;
border-radius: 4px;
background: #f8f5ff;
color: #4d2e91;
}
.legend {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
padding: 12px 14px;
background: #fff;
border: 1px solid var(--line);
border-radius: 8px;
font-size: 12px;
}
.legend strong {
margin-right: 4px;
}
@media (max-width: 900px) {
.container {
width: calc(100% - 28px);
}
.hero {
padding: 28px 24px;
}
.case-grid,
.case2-grid {
grid-template-columns: 1fr;
}
table,
thead,
tbody,
tr,
th,
td {
display: block;
width: 100%;
}
thead {
display: none;
}
tr {
border-bottom: 1px solid var(--line);
}
td {
border-right: 0;
}
}
</style>
</head>
<body>
<header class="hero">
<p class="eyebrow">Frontend AI Guidelines — gameservice-fe-agent</p>
<h1>프론트엔드 작업 단계별 AI 참조 흐름도</h1>
<p>
프론트엔드 개발 요청 시 공통 지침(rules/)을 기반으로 Claude AI에게 업무를 위임할 때
어떤 파일을 언제 참조하는지 정리합니다.
</p>
</header>
<main class="container">
<!-- 작업 사례 -->
<section class="card">
<h2 class="section-title">실제 작업 사례 기준</h2>
<div class="notice">
예시: <strong>게임 카드 컴포넌트에 신규 출시 뱃지 추가 (Vue 3 + Nuxt)</strong>
</div>
<div class="case-grid">
<article class="case-box case1">
<span class="case-label">CASE 1</span>
<h3>자연어 업무 요청</h3>
<p>
AI가 claude-workflow.md에 따라 탐색 → 계획 → 구현 → 검증 순서로
전체 흐름을 주도하며 진행합니다. 모호한 부분은 질문합니다.
</p>
</article>
<article class="case-box case2">
<span class="case-label">CASE 2</span>
<h3>@rules 직접 지정 · 단계 분리</h3>
<p>
개발자가 각 작업 단계마다 필요한 rules/ 파일을 직접 지정하고
탐색 · 계획 · 구현 · 검증을 나누어 요청합니다.
</p>
</article>
</div>
</section>
<!-- 공통 자동 로드 -->
<section class="card">
<h2 class="section-title">공통 자동 로드 구조 — 모든 대화에서 항상 적용</h2>
<table>
<thead>
<tr>
<th style="width: 120px;">구분</th>
<th style="width: 280px;">파일</th>
<th>주요 내용</th>
<th style="width: 120px;">로드 시점</th>
</tr>
</thead>
<tbody>
<tr>
<td>공통 지침 진입점</td>
<td><span class="path">CLAUDE.md</span></td>
<td>rules/* 전체를 @import. 공통 지침 우선순위 및 skill 사용 안내</td>
<td><span class="load-badge blue">세션 1회</span></td>
</tr>
<tr>
<td>코딩 컨벤션</td>
<td><span class="path">rules/coding-conventions.md</span></td>
<td>포맷팅(스페이스 2칸·싱글 쿼터·세미콜론), 네이밍(camelCase·PascalCase), any 금지, import 순서</td>
<td><span class="load-badge">매 대화 턴</span></td>
</tr>
<tr>
<td>프레임워크 규칙</td>
<td><span class="path">rules/framework-rules.md</span></td>
<td>Vue 3 &lt;script setup lang="ts"&gt;, Pinia, useFetch, Tailwind 유틸리티 우선, clsx 조건부 클래스</td>
<td><span class="load-badge">매 대화 턴</span></td>
</tr>
<tr>
<td>커밋 / PR 규칙</td>
<td><span class="path">rules/commit-pr.md</span></td>
<td>Conventional Commits 형식, subject 50자·명령형, Squash merge, CI + 1인 승인 필수</td>
<td><span class="load-badge">매 대화 턴</span></td>
</tr>
<tr>
<td>작업 방식 지침</td>
<td><span class="path">rules/claude-workflow.md</span></td>
<td>탐색→계획→구현→검증 순서, 최소 변경 원칙, 모호 시 질문, 임의 기능 추가 금지</td>
<td><span class="load-badge">매 대화 턴</span></td>
</tr>
</tbody>
</table>
</section>
<!-- CASE 1 Tree -->
<section class="card">
<h2 class="section-title">CASE 1 자연어 업무 요청 — 자동 참조 흐름 (Tree)</h2>
<div class="quote">"게임 카드 컴포넌트에 신규 출시 뱃지 추가해줘."</div>
<div class="tree">
<article class="tree-node">
<div class="node-head"><span class="round">0</span>자동 로드</div>
<p>모든 대화 시작 시 CLAUDE.md와 rules/* 4개 파일을 자동으로 참조합니다.</p>
<div class="chip-row">
<span class="chip blue">CLAUDE.md</span>
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">framework-rules.md</span>
<span class="chip yellow">commit-pr.md</span>
<span class="chip yellow">claude-workflow.md</span>
</div>
</article>
<article class="tree-node">
<div class="node-head"><span class="round">1</span>요구사항 분석 — 모호한 부분 질문</div>
<p>claude-workflow.md 원칙에 따라 추측 대신 사용자에게 확인합니다.</p>
<div class="chip-row">
<span class="chip yellow">claude-workflow.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box yellow">
<strong>예상 질문</strong>
<ul>
<li>뱃지는 어떤 조건(출시일 기준 N일 이내 등)일 때 표시하나요?</li>
<li>뱃지 디자인은 기존 디자인 시스템 컴포넌트를 사용하나요?</li>
<li>GameCard 컴포넌트는 여러 페이지에서 공유 중인가요?</li>
</ul>
</div>
</article>
<article class="tree-node">
<div class="node-head"><span class="round">2</span>탐색 — 관련 파일 파악 · 유사 패턴 확인</div>
<p>GameCard 컴포넌트와 주변 컨벤션을 먼저 읽고 유사 뱃지 구현 패턴을 찾습니다.</p>
<div class="chip-row">
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box blue">
<strong>탐색 항목</strong>
<ul>
<li>GameCard.vue 현재 구조 파악</li>
<li>기존 뱃지 컴포넌트(Badge.vue 등) 존재 여부 확인</li>
<li>props 타입 정의 위치 확인 (types/ 또는 도메인 types.ts)</li>
</ul>
</div>
</article>
<article class="tree-node">
<div class="node-head"><span class="round">3</span>계획 — 할 일 목록 작성 · 승인</div>
<p>변경 파일 목록과 구현 순서를 공유하고, 아키텍처 영향이 있으면 승인을 받습니다.</p>
<div class="chip-row">
<span class="chip yellow">claude-workflow.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box blue">
<strong>계획 산출물</strong>
<ul>
<li>GameCard.vue — isNew prop 추가, 뱃지 렌더링 조건 분기</li>
<li>types/game.ts — isNew: boolean 필드 추가</li>
<li>기존 뱃지 컴포넌트 재사용 or 인라인 처리 결정</li>
</ul>
</div>
</article>
<article class="tree-node">
<div class="node-head"><span class="round green">4</span>구현 — 코드 작성</div>
<p>계획에 따라 Vue 컴포넌트와 타입을 수정합니다. 요청 범위를 벗어나는 리팩토링은 하지 않습니다.</p>
<div class="chip-row">
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box green">
<strong>구현 항목</strong>
<ul>
<li>defineProps&lt;T&gt;()에 isNew?: boolean 추가</li>
<li>Tailwind 유틸리티 클래스로 뱃지 스타일 적용</li>
<li>조건부 클래스에 clsx 사용, 200줄 초과 시 분리 검토</li>
</ul>
</div>
</article>
<article class="tree-node">
<div class="node-head"><span class="round orange">5</span>검증 — 린트 · 빌드 · 커밋 / PR 작성</div>
<p>타입체크와 빌드를 확인한 뒤 Conventional Commits 형식으로 커밋과 PR을 작성합니다.</p>
<div class="chip-row">
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">commit-pr.md</span>
<span class="chip yellow">claude-workflow.md</span>
</div>
<div class="work-box red">
<strong>최종 산출물</strong>
<ul>
<li>lint / tsc / build 통과 확인</li>
<li>커밋: <code>feat(game-card): add new-release badge</code></li>
<li>PR 본문: 변경 사항·배경·테스트·체크리스트 작성</li>
</ul>
</div>
</article>
</div>
</section>
<!-- CASE 2 -->
<section class="card">
<h2 class="section-title">CASE 2 @rules 직접 지정 · 단계별 업무 요청 — 직접 참조 흐름 (Tree)</h2>
<div class="quote purple">
개발자가 각 작업 단계마다 필요한 rules/ 파일 또는 skill을 직접 지정해 요청합니다.
</div>
<div class="case2-grid">
<article class="stage-card purple">
<h3 class="stage-title"><span class="round purple">1</span>탐색 단계</h3>
<div class="example">"GameCard 컴포넌트 구조 파악해줘. @rules/framework-rules.md 참고해서"</div>
<div class="chip-row">
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box green">
<strong>산출 내용</strong>
<ul>
<li>컴포넌트 파일 구조 및 props 정리</li>
<li>재사용 가능한 유사 패턴 목록</li>
<li>영향받는 파일 범위 파악</li>
</ul>
</div>
</article>
<article class="stage-card purple">
<h3 class="stage-title"><span class="round purple">2</span>계획 단계</h3>
<div class="example">"뱃지 추가 구현 계획 잡아줘. @rules/claude-workflow.md 참고해서"</div>
<div class="chip-row">
<span class="chip yellow">claude-workflow.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box green">
<strong>산출 내용</strong>
<ul>
<li>수정 파일 목록 및 변경 순서</li>
<li>아키텍처 영향 범위 정리</li>
<li>사용자 승인 필요 항목 명시</li>
</ul>
</div>
</article>
<article class="stage-card green">
<h3 class="stage-title"><span class="round green">3</span>구현 단계</h3>
<div class="example">"계획대로 구현해줘. @rules/coding-conventions.md @rules/framework-rules.md 참고해서"</div>
<div class="chip-row">
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box green">
<strong>산출 내용</strong>
<ul>
<li>Vue 컴포넌트 코드 수정</li>
<li>TypeScript 타입 추가 · 수정</li>
<li>Tailwind 클래스 적용 및 clsx 처리</li>
</ul>
</div>
</article>
<article class="stage-card orange">
<h3 class="stage-title"><span class="round orange">4</span>검증 단계</h3>
<div class="example">"변경 코드 검증하고 커밋·PR 작성해줘. @rules/commit-pr.md 참고해서"</div>
<div class="chip-row">
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">commit-pr.md</span>
</div>
<div class="work-box red">
<strong>산출 내용</strong>
<ul>
<li>lint / tsc / build 통과 확인</li>
<li>Conventional Commits 커밋 메시지 작성</li>
<li>PR 템플릿(변경사항·배경·테스트·체크리스트) 작성</li>
</ul>
</div>
</article>
</div>
<div style="margin-top: 16px; padding: 12px 14px; background: var(--purple-soft); border-left: 4px solid var(--purple); border-radius: 4px; font-size: 13px; color: #4d2e91;">
<strong>Skill 활용 팁</strong> — 반복 업무는 스킬 명령으로 더 빠르게 처리할 수 있습니다.<br>
<span style="font-family: monospace; font-size: 12px;">
/conventional-commit &nbsp;·&nbsp; /verify-component-review &nbsp;·&nbsp; /plan-analyzer &nbsp;·&nbsp; /markup-edm
</span>
</div>
</section>
<!-- 범례 -->
<section class="card">
<h2 class="section-title">범례</h2>
<div class="legend">
<strong>공통 자동</strong>
<span class="chip blue">CLAUDE.md — 세션 1회 자동 로드</span>
<span class="chip yellow">rules/* — 매 대화 턴 자동 로드 (4개 파일)</span>
<span class="chip green">단계별 주요 참조 파일</span>
<span class="chip purple">@rules — 직접 지정 (CASE 2)</span>
<span class="chip red">검증 · 커밋 / PR 단계</span>
</div>
</section>
</main>
</body>
</html>

View File

@@ -1,794 +0,0 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>프론트엔드 작업 단계별 AI 참조 흐름도</title>
<style>
:root {
--navy: #17172b;
--blue: #0b63ce;
--blue-soft: #eaf4ff;
--purple: #5937b7;
--purple-soft: #f1ebff;
--green: #15975f;
--green-soft: #e8f8ef;
--orange: #d97706;
--orange-soft: #fff4dd;
--yellow: #f2b43f;
--yellow-soft: #fff8e6;
--red: #d94841;
--red-soft: #fff0f0;
--bg: #f5f7fb;
--card: #ffffff;
--line: #dce4ef;
--text: #1f2937;
--muted: #667085;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
color: var(--text);
background: var(--bg);
font-family:
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
"Noto Sans KR",
sans-serif;
line-height: 1.55;
}
.hero {
padding: 34px 44px 36px;
color: #fff;
background: var(--navy);
border-bottom: 4px solid #1677ff;
}
.hero .eyebrow {
margin: 0 0 8px;
color: #8d96b8;
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.hero h1 {
margin: 0 0 10px;
font-size: 27px;
font-weight: 900;
letter-spacing: -0.04em;
}
.hero p {
margin: 0;
color: #c2c8da;
font-size: 13px;
}
.container {
width: min(1180px, calc(100% - 56px));
margin: 28px auto 48px;
}
.card {
margin-bottom: 22px;
padding: 24px;
background: var(--card);
border: 1px solid var(--line);
border-radius: 8px;
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.03);
}
.section-title {
display: flex;
align-items: center;
gap: 8px;
margin: 0 0 18px;
color: #315a9e;
font-size: 15px;
font-weight: 900;
letter-spacing: -0.02em;
}
.section-title::before {
content: "";
width: 4px;
height: 18px;
background: var(--blue);
border-radius: 999px;
}
.notice {
padding: 12px 16px;
margin-bottom: 16px;
color: #17436f;
font-size: 13px;
font-weight: 700;
background: var(--blue-soft);
border-left: 4px solid var(--blue);
border-radius: 4px;
}
.case-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 14px;
}
.case-box {
min-height: 126px;
padding: 18px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
}
.case-box.case1 {
border-color: #a8caff;
background: #f7fbff;
}
.case-box.case2 {
border-color: #c8b5ff;
background: #fbf8ff;
}
.case-label {
display: inline-block;
margin-bottom: 10px;
padding: 4px 11px;
color: #fff;
font-size: 12px;
font-weight: 900;
border-radius: 999px;
}
.case1 .case-label {
background: var(--blue);
}
.case2 .case-label {
background: var(--purple);
}
.case-box h3 {
margin: 0 0 8px;
font-size: 15px;
font-weight: 900;
}
.case-box p {
margin: 0;
color: #475467;
font-size: 13px;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
border: 1px solid var(--line);
}
th,
td {
padding: 12px 14px;
text-align: left;
vertical-align: top;
border-right: 1px solid var(--line);
border-bottom: 1px solid var(--line);
}
th {
color: #344054;
font-weight: 900;
background: #eef4ff;
}
td:last-child,
th:last-child {
border-right: 0;
}
.path {
color: #c0392b;
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 12px;
white-space: nowrap;
}
.load-badge {
display: inline-block;
padding: 3px 8px;
color: #915f00;
background: var(--yellow-soft);
border: 1px solid #e8bd54;
border-radius: 999px;
font-size: 11px;
font-weight: 900;
white-space: nowrap;
}
.load-badge.blue {
color: #0b4fa6;
background: var(--blue-soft);
border-color: #9ec3ff;
}
.quote {
padding: 12px 16px;
margin-bottom: 18px;
color: #0c55b0;
font-size: 14px;
font-weight: 700;
font-style: italic;
background: var(--blue-soft);
border-left: 4px solid var(--blue);
border-radius: 4px;
}
.quote.purple {
color: #4d2e91;
background: var(--purple-soft);
border-left-color: var(--purple);
}
.tree {
position: relative;
padding-left: 24px;
}
.tree::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 6px;
width: 2px;
background: #e4e7ec;
}
.tree-node {
position: relative;
margin: 0 0 16px;
padding: 14px 16px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
}
.tree-node::before {
content: "";
position: absolute;
top: 24px;
left: -18px;
width: 18px;
height: 2px;
background: #e4e7ec;
}
.tree-node:last-child {
margin-bottom: 0;
}
.node-head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
font-weight: 900;
}
.round {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
color: #fff;
font-size: 12px;
font-weight: 900;
background: var(--blue);
border-radius: 999px;
flex-shrink: 0;
}
.round.green {
background: var(--green);
}
.round.orange {
background: var(--orange);
}
.round.purple {
background: var(--purple);
}
.chip-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 8px 0;
}
.chip {
display: inline-block;
padding: 4px 9px;
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 11px;
border: 1px solid;
border-radius: 999px;
background: #fff;
}
.chip.yellow {
color: #8a5a00;
border-color: #e6b64e;
background: var(--yellow-soft);
}
.chip.green {
color: #087443;
border-color: #8bd3aa;
background: var(--green-soft);
}
.chip.blue {
color: #0b4fa6;
border-color: #9ec3ff;
background: var(--blue-soft);
}
.chip.purple {
color: #4d2e91;
border-color: #c6b6ff;
background: var(--purple-soft);
}
.chip.red {
color: #a33a34;
border-color: #e4a39f;
background: var(--red-soft);
}
.work-box {
margin-top: 10px;
padding: 12px 14px;
border-radius: 6px;
font-size: 12px;
}
.work-box.blue {
background: var(--blue-soft);
border-left: 4px solid var(--blue);
}
.work-box.green {
background: var(--green-soft);
border-left: 4px solid var(--green);
}
.work-box.yellow {
background: var(--yellow-soft);
border-left: 4px solid var(--yellow);
}
.work-box.red {
background: var(--red-soft);
border-left: 4px solid var(--red);
}
.work-box ul {
margin: 4px 0 0;
padding-left: 18px;
}
.case2-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 14px;
}
.stage-card {
padding: 16px;
border: 2px solid;
border-radius: 8px;
background: #fff;
}
.stage-card.purple {
border-color: var(--purple);
}
.stage-card.green {
border-color: var(--green);
}
.stage-card.orange {
border-color: var(--orange);
}
.stage-card.red {
border-color: var(--red);
}
.stage-card .stage-title {
display: flex;
align-items: center;
gap: 8px;
margin: 0 0 12px;
font-size: 15px;
font-weight: 900;
}
.example {
padding: 10px 12px;
margin-bottom: 12px;
font-size: 12px;
font-style: italic;
border-radius: 4px;
background: #f8f5ff;
color: #4d2e91;
}
.legend {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
padding: 12px 14px;
background: #fff;
border: 1px solid var(--line);
border-radius: 8px;
font-size: 12px;
}
.legend strong {
margin-right: 4px;
}
@media (max-width: 900px) {
.container {
width: calc(100% - 28px);
}
.hero {
padding: 28px 24px;
}
.case-grid,
.case2-grid {
grid-template-columns: 1fr;
}
table,
thead,
tbody,
tr,
th,
td {
display: block;
width: 100%;
}
thead {
display: none;
}
tr {
border-bottom: 1px solid var(--line);
}
td {
border-right: 0;
}
}
</style>
</head>
<body>
<header class="hero">
<p class="eyebrow">Frontend AI Guidelines — gameservice-fe-agent</p>
<h1>프론트엔드 작업 단계별 AI 참조 흐름도</h1>
<p>
프론트엔드 개발 요청 시 공통 지침(rules/)을 기반으로 Claude AI에게 업무를 위임할 때
어떤 파일을 언제 참조하는지 정리합니다.
</p>
</header>
<main class="container">
<!-- 작업 사례 -->
<section class="card">
<h2 class="section-title">실제 작업 사례 기준</h2>
<div class="notice">
예시: <strong>게임 카드 컴포넌트에 신규 출시 뱃지 추가 (Vue 3 + Nuxt)</strong>
</div>
<div class="case-grid">
<article class="case-box case1">
<span class="case-label">CASE 1</span>
<h3>자연어 업무 요청</h3>
<p>
AI가 claude-workflow.md에 따라 탐색 → 계획 → 구현 → 검증 순서로
전체 흐름을 주도하며 진행합니다. 모호한 부분은 질문합니다.
</p>
</article>
<article class="case-box case2">
<span class="case-label">CASE 2</span>
<h3>@rules 직접 지정 · 단계 분리</h3>
<p>
개발자가 각 작업 단계마다 필요한 rules/ 파일을 직접 지정하고
탐색 · 계획 · 구현 · 검증을 나누어 요청합니다.
</p>
</article>
</div>
</section>
<!-- 공통 자동 로드 -->
<section class="card">
<h2 class="section-title">공통 자동 로드 구조 — 모든 대화에서 항상 적용</h2>
<table>
<thead>
<tr>
<th style="width: 120px;">구분</th>
<th style="width: 280px;">파일</th>
<th>주요 내용</th>
<th style="width: 120px;">로드 시점</th>
</tr>
</thead>
<tbody>
<tr>
<td>공통 지침 진입점</td>
<td><span class="path">CLAUDE.md</span></td>
<td>rules/* 전체를 @import. 공통 지침 우선순위 및 skill 사용 안내</td>
<td><span class="load-badge blue">세션 1회</span></td>
</tr>
<tr>
<td>코딩 컨벤션</td>
<td><span class="path">rules/coding-conventions.md</span></td>
<td>포맷팅(스페이스 2칸·싱글 쿼터·세미콜론), 네이밍(camelCase·PascalCase), any 금지, import 순서</td>
<td><span class="load-badge">매 대화 턴</span></td>
</tr>
<tr>
<td>프레임워크 규칙</td>
<td><span class="path">rules/framework-rules.md</span></td>
<td>Vue 3 &lt;script setup lang="ts"&gt;, Pinia, useFetch, Tailwind 유틸리티 우선, clsx 조건부 클래스</td>
<td><span class="load-badge">매 대화 턴</span></td>
</tr>
<tr>
<td>커밋 / PR 규칙</td>
<td><span class="path">rules/commit-pr.md</span></td>
<td>Conventional Commits 형식, subject 50자·명령형, Squash merge, CI + 1인 승인 필수</td>
<td><span class="load-badge">매 대화 턴</span></td>
</tr>
<tr>
<td>작업 방식 지침</td>
<td><span class="path">rules/claude-workflow.md</span></td>
<td>탐색→계획→구현→검증 순서, 최소 변경 원칙, 모호 시 질문, 임의 기능 추가 금지</td>
<td><span class="load-badge">매 대화 턴</span></td>
</tr>
</tbody>
</table>
</section>
<!-- CASE 1 Tree -->
<section class="card">
<h2 class="section-title">CASE 1 자연어 업무 요청 — 자동 참조 흐름 (Tree)</h2>
<div class="quote">"게임 카드 컴포넌트에 신규 출시 뱃지 추가해줘."</div>
<div class="tree">
<article class="tree-node">
<div class="node-head"><span class="round">0</span>자동 로드</div>
<p>모든 대화 시작 시 CLAUDE.md와 rules/* 4개 파일을 자동으로 참조합니다.</p>
<div class="chip-row">
<span class="chip blue">CLAUDE.md</span>
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">framework-rules.md</span>
<span class="chip yellow">commit-pr.md</span>
<span class="chip yellow">claude-workflow.md</span>
</div>
</article>
<article class="tree-node">
<div class="node-head"><span class="round">1</span>요구사항 분석 — 모호한 부분 질문</div>
<p>claude-workflow.md 원칙에 따라 추측 대신 사용자에게 확인합니다.</p>
<div class="chip-row">
<span class="chip yellow">claude-workflow.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box yellow">
<strong>예상 질문</strong>
<ul>
<li>뱃지는 어떤 조건(출시일 기준 N일 이내 등)일 때 표시하나요?</li>
<li>뱃지 디자인은 기존 디자인 시스템 컴포넌트를 사용하나요?</li>
<li>GameCard 컴포넌트는 여러 페이지에서 공유 중인가요?</li>
</ul>
</div>
</article>
<article class="tree-node">
<div class="node-head"><span class="round">2</span>탐색 — 관련 파일 파악 · 유사 패턴 확인</div>
<p>GameCard 컴포넌트와 주변 컨벤션을 먼저 읽고 유사 뱃지 구현 패턴을 찾습니다.</p>
<div class="chip-row">
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box blue">
<strong>탐색 항목</strong>
<ul>
<li>GameCard.vue 현재 구조 파악</li>
<li>기존 뱃지 컴포넌트(Badge.vue 등) 존재 여부 확인</li>
<li>props 타입 정의 위치 확인 (types/ 또는 도메인 types.ts)</li>
</ul>
</div>
</article>
<article class="tree-node">
<div class="node-head"><span class="round">3</span>계획 — 할 일 목록 작성 · 승인</div>
<p>변경 파일 목록과 구현 순서를 공유하고, 아키텍처 영향이 있으면 승인을 받습니다.</p>
<div class="chip-row">
<span class="chip yellow">claude-workflow.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box blue">
<strong>계획 산출물</strong>
<ul>
<li>GameCard.vue — isNew prop 추가, 뱃지 렌더링 조건 분기</li>
<li>types/game.ts — isNew: boolean 필드 추가</li>
<li>기존 뱃지 컴포넌트 재사용 or 인라인 처리 결정</li>
</ul>
</div>
</article>
<article class="tree-node">
<div class="node-head"><span class="round green">4</span>구현 — 코드 작성</div>
<p>계획에 따라 Vue 컴포넌트와 타입을 수정합니다. 요청 범위를 벗어나는 리팩토링은 하지 않습니다.</p>
<div class="chip-row">
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box green">
<strong>구현 항목</strong>
<ul>
<li>defineProps&lt;T&gt;()에 isNew?: boolean 추가</li>
<li>Tailwind 유틸리티 클래스로 뱃지 스타일 적용</li>
<li>조건부 클래스에 clsx 사용, 200줄 초과 시 분리 검토</li>
</ul>
</div>
</article>
<article class="tree-node">
<div class="node-head"><span class="round orange">5</span>검증 — 린트 · 빌드 · 커밋 / PR 작성</div>
<p>타입체크와 빌드를 확인한 뒤 Conventional Commits 형식으로 커밋과 PR을 작성합니다.</p>
<div class="chip-row">
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">commit-pr.md</span>
<span class="chip yellow">claude-workflow.md</span>
</div>
<div class="work-box red">
<strong>최종 산출물</strong>
<ul>
<li>lint / tsc / build 통과 확인</li>
<li>커밋: <code>feat(game-card): add new-release badge</code></li>
<li>PR 본문: 변경 사항·배경·테스트·체크리스트 작성</li>
</ul>
</div>
</article>
</div>
</section>
<!-- CASE 2 -->
<section class="card">
<h2 class="section-title">CASE 2 @rules 직접 지정 · 단계별 업무 요청 — 직접 참조 흐름 (Tree)</h2>
<div class="quote purple">
개발자가 각 작업 단계마다 필요한 rules/ 파일 또는 skill을 직접 지정해 요청합니다.
</div>
<div class="case2-grid">
<article class="stage-card purple">
<h3 class="stage-title"><span class="round purple">1</span>탐색 단계</h3>
<div class="example">"GameCard 컴포넌트 구조 파악해줘. @rules/framework-rules.md 참고해서"</div>
<div class="chip-row">
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box green">
<strong>산출 내용</strong>
<ul>
<li>컴포넌트 파일 구조 및 props 정리</li>
<li>재사용 가능한 유사 패턴 목록</li>
<li>영향받는 파일 범위 파악</li>
</ul>
</div>
</article>
<article class="stage-card purple">
<h3 class="stage-title"><span class="round purple">2</span>계획 단계</h3>
<div class="example">"뱃지 추가 구현 계획 잡아줘. @rules/claude-workflow.md 참고해서"</div>
<div class="chip-row">
<span class="chip yellow">claude-workflow.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box green">
<strong>산출 내용</strong>
<ul>
<li>수정 파일 목록 및 변경 순서</li>
<li>아키텍처 영향 범위 정리</li>
<li>사용자 승인 필요 항목 명시</li>
</ul>
</div>
</article>
<article class="stage-card green">
<h3 class="stage-title"><span class="round green">3</span>구현 단계</h3>
<div class="example">"계획대로 구현해줘. @rules/coding-conventions.md @rules/framework-rules.md 참고해서"</div>
<div class="chip-row">
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">framework-rules.md</span>
</div>
<div class="work-box green">
<strong>산출 내용</strong>
<ul>
<li>Vue 컴포넌트 코드 수정</li>
<li>TypeScript 타입 추가 · 수정</li>
<li>Tailwind 클래스 적용 및 clsx 처리</li>
</ul>
</div>
</article>
<article class="stage-card orange">
<h3 class="stage-title"><span class="round orange">4</span>검증 단계</h3>
<div class="example">"변경 코드 검증하고 커밋·PR 작성해줘. @rules/commit-pr.md 참고해서"</div>
<div class="chip-row">
<span class="chip yellow">coding-conventions.md</span>
<span class="chip yellow">commit-pr.md</span>
</div>
<div class="work-box red">
<strong>산출 내용</strong>
<ul>
<li>lint / tsc / build 통과 확인</li>
<li>Conventional Commits 커밋 메시지 작성</li>
<li>PR 템플릿(변경사항·배경·테스트·체크리스트) 작성</li>
</ul>
</div>
</article>
</div>
<div style="margin-top: 16px; padding: 12px 14px; background: var(--purple-soft); border-left: 4px solid var(--purple); border-radius: 4px; font-size: 13px; color: #4d2e91;">
<strong>Skill 활용 팁</strong> — 반복 업무는 스킬 명령으로 더 빠르게 처리할 수 있습니다.<br>
<span style="font-family: monospace; font-size: 12px;">
/conventional-commit &nbsp;·&nbsp; /verify-component-review &nbsp;·&nbsp; /plan-analyzer &nbsp;·&nbsp; /markup-edm
</span>
</div>
</section>
<!-- 범례 -->
<section class="card">
<h2 class="section-title">범례</h2>
<div class="legend">
<strong>공통 자동</strong>
<span class="chip blue">CLAUDE.md — 세션 1회 자동 로드</span>
<span class="chip yellow">rules/* — 매 대화 턴 자동 로드 (4개 파일)</span>
<span class="chip green">단계별 주요 참조 파일</span>
<span class="chip purple">@rules — 직접 지정 (CASE 2)</span>
<span class="chip red">검증 · 커밋 / PR 단계</span>
</div>
</section>
</main>
</body>
</html>

View File

@@ -1,612 +0,0 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>프론트엔드 AI 활용 지침 체계</title>
<style>
:root {
--navy: #18172b;
--blue: #0b5ed7;
--blue-dark: #0756c9;
--purple: #46369a;
--green: #15803d;
--orange: #d97706;
--bg: #f5f7fb;
--card: #ffffff;
--line: #dce4f0;
--text: #1f2937;
--muted: #667085;
--soft-blue: #eef5ff;
--soft-yellow: #fff9e8;
--soft-green: #eefaf2;
--soft-purple: #f3f0ff;
--soft-gray: #f8fafc;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family:
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
"Noto Sans KR",
sans-serif;
color: var(--text);
background: var(--bg);
line-height: 1.55;
}
.hero {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28px 44px;
color: #fff;
background: var(--navy);
border-bottom: 3px solid #1677ff;
}
.hero h1 {
margin: 0;
font-size: 25px;
font-weight: 800;
letter-spacing: -0.04em;
}
.hero p {
margin: 0;
color: #9da8ca;
font-size: 12px;
}
.container {
width: min(1050px, calc(100% - 48px));
margin: 32px auto 48px;
}
.card {
margin-bottom: 20px;
padding: 24px;
background: var(--card);
border: 1px solid var(--line);
border-radius: 8px;
}
.section-title {
margin: 0 0 18px;
color: #8a9cbc;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.02em;
}
.purpose-title {
margin: 0 0 10px;
color: var(--blue);
font-size: 17px;
font-weight: 800;
line-height: 1.45;
}
.purpose-desc {
margin: 0;
font-size: 14px;
}
/* rules 4개 카드 */
.rules-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.rule-box {
padding: 18px;
border-radius: 6px;
border: 1px solid;
}
.rule-box.blue { background: var(--soft-blue); border-color: #9ec3ff; }
.rule-box.yellow { background: var(--soft-yellow); border-color: #ecc65d; }
.rule-box.green { background: var(--soft-green); border-color: #8fd1a4; }
.rule-box.purple { background: var(--soft-purple); border-color: #c4b5fd; }
.box-title {
margin: 0 0 8px;
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 12px;
font-weight: 800;
}
.blue .box-title { color: #003d91; }
.yellow .box-title { color: #8a5a00; }
.green .box-title { color: #065f2b; }
.purple .box-title { color: #3b2780; }
.box-label {
display: inline-block;
margin-bottom: 10px;
padding: 3px 8px;
font-size: 11px;
font-weight: 800;
border-radius: 4px;
}
.blue .box-label { color: #004da8; background: #bdd7ff; }
.yellow .box-label { color: #8a5a00; background: #ffe49c; }
.green .box-label { color: #065f2b; background: #bce9cd; }
.purple .box-label { color: #3b2780; background: #ddd6fe; }
.box-subject {
margin: 0 0 10px;
font-size: 13px;
font-weight: 700;
}
.dash-list {
margin: 0;
padding: 0;
list-style: none;
color: #4b5563;
font-size: 12px;
line-height: 1.8;
}
.dash-list li::before {
content: "· ";
}
/* 파일별 상세 내용 */
.detail-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.detail-box {
padding: 18px 20px;
background: var(--soft-gray);
border: 1px solid var(--line);
border-radius: 6px;
}
.detail-title {
display: flex;
align-items: center;
gap: 10px;
margin: 0 0 14px;
font-size: 14px;
font-weight: 800;
}
.detail-title code {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 12px;
color: var(--blue-dark);
background: var(--soft-blue);
padding: 2px 7px;
border-radius: 4px;
}
.kv-list {
margin: 0;
padding: 0;
list-style: none;
font-size: 13px;
}
.kv-list li {
display: flex;
gap: 8px;
padding: 5px 0;
border-bottom: 1px solid var(--line);
}
.kv-list li:last-child {
border-bottom: 0;
}
.kv-key {
flex-shrink: 0;
width: 90px;
color: #6b7280;
font-size: 12px;
}
.kv-val {
color: var(--text);
}
/* 커밋 타입 뱃지 */
.commit-types {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 10px;
}
.ct {
padding: 3px 9px;
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 11px;
font-weight: 800;
border-radius: 4px;
background: #1e293b;
color: #e2e8f0;
}
/* 작업 흐름 */
.flow {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
align-items: stretch;
}
.flow-item {
position: relative;
padding: 18px 16px;
background: var(--soft-gray);
border: 1px solid var(--line);
border-radius: 6px;
}
.flow-item:not(:last-child)::after {
content: "→";
position: absolute;
top: 50%;
right: -14px;
color: #98a2b3;
font-size: 18px;
transform: translateY(-50%);
}
.flow-num {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
margin-bottom: 10px;
color: #fff;
font-size: 12px;
font-weight: 800;
border-radius: 50%;
background: var(--blue-dark);
}
.flow-num.green { background: var(--green); }
.flow-num.orange { background: var(--orange); }
.flow-num.purple { background: var(--purple); }
.flow-title {
margin: 0 0 4px;
font-size: 14px;
font-weight: 800;
}
.flow-desc {
margin: 0 0 10px;
color: #4b5563;
font-size: 12px;
}
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.tag {
display: inline-block;
padding: 2px 7px;
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 11px;
border-radius: 4px;
border: 1px solid;
background: #fff;
}
.tag.blue { color: #064aa2; border-color: #9ec3ff; background: #eef5ff; }
.tag.yellow { color: #8a5a00; border-color: #ecc65d; background: #fff9e8; }
.tag.green { color: #166534; border-color: #8fd1a4; background: #eefaf2; }
.tag.purple { color: #3b2780; border-color: #c4b5fd; background: #f5f3ff; }
/* 금지 규칙 */
.no-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.no-item {
padding: 12px 14px;
background: #fff5f5;
border: 1px solid #fca5a5;
border-radius: 6px;
font-size: 13px;
color: #991b1b;
}
.no-item strong {
display: block;
margin-bottom: 4px;
font-size: 12px;
color: #7f1d1d;
}
@media (max-width: 900px) {
.hero {
display: block;
padding: 24px;
}
.hero p {
margin-top: 8px;
}
.container {
width: calc(100% - 28px);
margin-top: 20px;
}
.rules-grid,
.detail-grid,
.flow,
.no-list {
grid-template-columns: 1fr;
}
.flow-item:not(:last-child)::after {
top: auto;
right: 50%;
bottom: -18px;
transform: translateX(50%) rotate(90deg);
}
}
</style>
</head>
<body>
<header class="hero">
<div>
<h1>프론트엔드 AI 활용 지침 체계</h1>
<p>gameservice-fe-agent — rules/ 4개 파일 요약</p>
</div>
</header>
<main class="container">
<!-- 목적 -->
<section class="card">
<h2 class="section-title">문서 목적</h2>
<p class="purpose-title">
Claude AI가 프론트엔드 개발 업무를 지원할 때,<br />
어떤 지침을 언제 자동으로 참조하는지 정의합니다.
</p>
<p class="purpose-desc">
<code>rules/</code> 폴더의 4개 파일은 <code>CLAUDE.md</code>에서 <code>@import</code>로 불러오며,
모든 작업에 자동으로 적용됩니다. Vue 3 / Nuxt / TypeScript / Tailwind CSS 기반 프로젝트에서
일관성 있는 코드 작성과 협업을 지원합니다.
</p>
</section>
<!-- rules 4개 개요 -->
<section class="card">
<h2 class="section-title">rules/ 4개 파일 개요</h2>
<div class="rules-grid">
<article class="rule-box blue">
<h3 class="box-title">coding-conventions.md</h3>
<span class="box-label">코딩 컨벤션</span>
<p class="box-subject">포맷팅 · 네이밍 · 타입</p>
<ul class="dash-list">
<li>스페이스 2칸, 싱글 쿼터, 세미콜론 필수</li>
<li>camelCase / PascalCase / UPPER_SNAKE</li>
<li>any 사용 금지, 함수 반환 타입 명시</li>
<li>import 순서 (외부→절대→상대)</li>
</ul>
</article>
<article class="rule-box yellow">
<h3 class="box-title">framework-rules.md</h3>
<span class="box-label">프레임워크 규칙</span>
<p class="box-subject">Vue 3 / Nuxt / Tailwind</p>
<ul class="dash-list">
<li>&lt;script setup lang="ts"&gt; 기본</li>
<li>Pinia (공유 상태), useFetch (서버 상태)</li>
<li>Tailwind 유틸리티 우선, clsx 조건부 클래스</li>
<li>라이브러리 도입 시 PR에 이유 기록</li>
</ul>
</article>
<article class="rule-box green">
<h3 class="box-title">commit-pr.md</h3>
<span class="box-label">커밋 / PR 규칙</span>
<p class="box-subject">Conventional Commits + PR 템플릿</p>
<ul class="dash-list">
<li>type(scope): subject 형식</li>
<li>subject 50자 이내, 명령형 현재 시제</li>
<li>Squash and merge 기본 전략</li>
<li>CI(Lint/Test/Build) + 1인 이상 승인</li>
</ul>
</article>
<article class="rule-box purple">
<h3 class="box-title">claude-workflow.md</h3>
<span class="box-label">Claude 작업 방식</span>
<p class="box-subject">탐색 → 계획 → 구현 → 검증</p>
<ul class="dash-list">
<li>기존 코드 존중, 최소 변경 원칙</li>
<li>모호한 요구사항은 추측 말고 질문</li>
<li>임의 기능 추가·대량 리팩토링 금지</li>
<li>결론 먼저, 변경 파일 요약</li>
</ul>
</article>
</div>
</section>
<!-- 파일별 상세 -->
<section class="card">
<h2 class="section-title">파일별 핵심 규칙 상세</h2>
<div class="detail-grid">
<div class="detail-box">
<h3 class="detail-title"><code>coding-conventions.md</code> 포맷팅 &amp; 네이밍</h3>
<ul class="kv-list">
<li><span class="kv-key">들여쓰기</span><span class="kv-val">스페이스 2칸 (탭 금지)</span></li>
<li><span class="kv-key">문자열</span><span class="kv-val">싱글 쿼터 <code>'</code>, JSX 속성은 더블 쿼터 <code>"</code></span></li>
<li><span class="kv-key">세미콜론</span><span class="kv-val">항상 작성 (생략 금지)</span></li>
<li><span class="kv-key">최대 길이</span><span class="kv-val">한 줄 100자, 초과 시 줄바꿈</span></li>
<li><span class="kv-key">변수/함수</span><span class="kv-val">camelCase — userProfile, fetchUserData</span></li>
<li><span class="kv-key">상수</span><span class="kv-val">UPPER_SNAKE_CASE — MAX_RETRY_COUNT</span></li>
<li><span class="kv-key">컴포넌트</span><span class="kv-val">PascalCase.vue — UserCard.vue</span></li>
<li><span class="kv-key">Composable</span><span class="kv-val">use 접두사 camelCase — useAuth.ts</span></li>
<li><span class="kv-key">불리언</span><span class="kv-val">is / has / can / should 접두사</span></li>
<li><span class="kv-key">any 사용</span><span class="kv-val">금지 — 불가피 시 주석 + unknown 우선 검토</span></li>
</ul>
</div>
<div class="detail-box">
<h3 class="detail-title"><code>framework-rules.md</code> Vue 3 / Nuxt / Tailwind</h3>
<ul class="kv-list">
<li><span class="kv-key">컴포넌트</span><span class="kv-val">&lt;script setup lang="ts"&gt; 필수, Options API 금지</span></li>
<li><span class="kv-key">컴포넌트 크기</span><span class="kv-val">200줄 초과 시 분리 검토</span></li>
<li><span class="kv-key">Props</span><span class="kv-val">defineProps&lt;T&gt;() 제네릭으로 타입 명시</span></li>
<li><span class="kv-key">Emits</span><span class="kv-val">defineEmits&lt;{ ... }&gt;() 제네릭으로 선언</span></li>
<li><span class="kv-key">ref vs reactive</span><span class="kv-val">원시값 · 단일 객체는 ref 우선</span></li>
<li><span class="kv-key">공유 상태</span><span class="kv-val">Pinia 사용</span></li>
<li><span class="kv-key">서버 상태</span><span class="kv-val">useFetch / useAsyncData (직접 fetch 지양)</span></li>
<li><span class="kv-key">라우팅</span><span class="kv-val">Nuxt 파일 기반 라우팅, [param].vue 동적 라우트</span></li>
<li><span class="kv-key">Tailwind</span><span class="kv-val">유틸리티 우선, 조건부는 clsx / cn</span></li>
<li><span class="kv-key">라이브러리</span><span class="kv-val">도입 시 PR에 이유·번들 영향·대안 기록</span></li>
</ul>
</div>
<div class="detail-box">
<h3 class="detail-title"><code>commit-pr.md</code> Conventional Commits</h3>
<ul class="kv-list">
<li><span class="kv-key">형식</span><span class="kv-val">type(scope): subject</span></li>
<li><span class="kv-key">subject</span><span class="kv-val">50자 이내, 명령형 현재 시제, 마침표 없음</span></li>
<li><span class="kv-key">body</span><span class="kv-val">"왜"를 설명, 72자 줄바꿈</span></li>
<li><span class="kv-key">머지 전략</span><span class="kv-val">Squash and merge 기본</span></li>
<li><span class="kv-key">승인</span><span class="kv-val">최소 1인 이상 + CI 전부 통과</span></li>
<li><span class="kv-key">Draft PR</span><span class="kv-val">중간 피드백 필요 시 Draft 먼저 오픈</span></li>
</ul>
<div class="commit-types">
<span class="ct">feat</span>
<span class="ct">fix</span>
<span class="ct">refactor</span>
<span class="ct">style</span>
<span class="ct">docs</span>
<span class="ct">test</span>
<span class="ct">chore</span>
<span class="ct">perf</span>
<span class="ct">ci</span>
</div>
</div>
<div class="detail-box">
<h3 class="detail-title"><code>claude-workflow.md</code> 작업 원칙</h3>
<ul class="kv-list">
<li><span class="kv-key">작업 순서</span><span class="kv-val">탐색 → 계획 → 구현 → 검증</span></li>
<li><span class="kv-key">기본 원칙</span><span class="kv-val">기존 코드 존중 · 최소 변경 · 근거 있는 수정</span></li>
<li><span class="kv-key">모호한 요구</span><span class="kv-val">추측 금지 → 사용자에게 확인</span></li>
<li><span class="kv-key">커뮤니케이션</span><span class="kv-val">결론 먼저, 변경 파일 요약</span></li>
<li><span class="kv-key">검증</span><span class="kv-val">린트 / 타입체크 / 빌드 통과 확인</span></li>
</ul>
</div>
</div>
</section>
<!-- 금지 규칙 -->
<section class="card">
<h2 class="section-title">claude-workflow.md — 해서는 안 되는 것</h2>
<div class="no-list">
<div class="no-item">
<strong>임의 기능 추가 금지</strong>
사용자가 요청하지 않은 기능을 추가하지 않습니다.
</div>
<div class="no-item">
<strong>대량 리팩토링 금지</strong>
요청 범위를 벗어나는 코드 변경은 하지 않습니다.
</div>
<div class="no-item">
<strong>주석·문서 임의 삭제 금지</strong>
불필요해 보여도 삭제 전 사용자에게 확인합니다.
</div>
<div class="no-item">
<strong>비밀정보 하드코딩 금지</strong>
환경변수, 키, 토큰을 코드에 직접 작성하지 않습니다.
</div>
<div class="no-item">
<strong>의존성 버전 임의 변경 금지</strong>
요청 없이 package.json 버전을 수정하지 않습니다.
</div>
<div class="no-item">
<strong>강제 푸시·히스토리 재작성 금지</strong>
push --force, reset --hard는 명시적 요청 없이 실행하지 않습니다.
</div>
</div>
</section>
<!-- 작업 흐름 -->
<section class="card">
<h2 class="section-title">Claude 작업 단계별 참조 흐름</h2>
<div class="flow">
<article class="flow-item">
<div class="flow-num">1</div>
<h3 class="flow-title">탐색 (Explore)</h3>
<p class="flow-desc">관련 파일 파악, 유사 패턴 확인</p>
<div class="tag-list">
<span class="tag blue">CLAUDE.md</span>
<span class="tag yellow">coding-conventions</span>
</div>
</article>
<article class="flow-item">
<div class="flow-num purple">2</div>
<h3 class="flow-title">계획 (Plan)</h3>
<p class="flow-desc">할 일 목록 공유, 아키텍처 변경 시 승인</p>
<div class="tag-list">
<span class="tag yellow">coding-conventions</span>
<span class="tag yellow">framework-rules</span>
<span class="tag purple">claude-workflow</span>
</div>
</article>
<article class="flow-item">
<div class="flow-num orange">3</div>
<h3 class="flow-title">구현 (Implement)</h3>
<p class="flow-desc">코드 작성, 공통·프로젝트 지침 준수</p>
<div class="tag-list">
<span class="tag yellow">coding-conventions</span>
<span class="tag yellow">framework-rules</span>
</div>
</article>
<article class="flow-item">
<div class="flow-num green">4</div>
<h3 class="flow-title">검증 (Verify)</h3>
<p class="flow-desc">린트·타입체크·빌드, 커밋·PR 작성</p>
<div class="tag-list">
<span class="tag yellow">coding-conventions</span>
<span class="tag green">commit-pr</span>
<span class="tag purple">claude-workflow</span>
</div>
</article>
</div>
</section>
</main>
</body>
</html>

View File

@@ -1,800 +0,0 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>프론트엔드 AI 공통 지침 - Work Flow</title>
<style>
:root {
--bg: #f6f5f1;
--panel: #ffffff;
--line: #d7d7d7;
--text: #222;
--muted: #777;
--navy: #25384a;
--blue: #1f7fbe;
--blue-bg: #e8f5fb;
--yellow: #e6a400;
--yellow-bg: #fff9dc;
--green: #17a968;
--green-bg: #e6f7ee;
--red: #d94132;
--red-bg: #fde9e9;
--orange: #e86f16;
--purple: #8d42b2;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
color: var(--text);
background: var(--bg);
font-family:
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
"Noto Sans KR",
"Apple SD Gothic Neo",
sans-serif;
line-height: 1.45;
}
.page {
min-width: 1480px;
padding: 8px 10px 18px;
}
.layout {
display: grid;
grid-template-columns: 255px 1fr;
gap: 22px;
align-items: stretch;
}
/* Sidebar */
.sidebar {
overflow: hidden;
background: #fff;
border: 1px solid #d0d0d0;
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}
.sidebar-head {
padding: 14px 20px;
color: #fff;
font-size: 15px;
font-weight: 800;
background: var(--navy);
}
.side-body {
padding: 14px 14px 18px;
}
.side-group {
margin-bottom: 14px;
padding: 10px;
border-radius: 7px;
border: 2px solid;
}
.side-group.yellow {
border-color: #f2ad23;
background: #fffdf4;
}
.side-group.blue {
border-color: #1f7fbe;
background: #f0f8ff;
}
.side-title,
.side-file {
display: block;
margin-bottom: 7px;
padding: 4px 8px;
text-align: center;
border-radius: 3px;
font-size: 11px;
font-weight: 800;
font-family: "SFMono-Regular", Consolas, monospace;
}
.yellow .side-title,
.yellow .side-file {
color: #7b5a00;
border: 1px solid #f2ad23;
background: #fff3bf;
}
.blue .side-title,
.blue .side-file {
color: #0f4d77;
border: 1px solid #1f7fbe;
background: #d6ecf8;
}
.side-caption {
margin: 8px 0 0;
color: #8a8a8a;
text-align: center;
font-size: 11px;
font-style: italic;
}
.side-desc {
margin: 6px 0 0;
color: #666;
font-size: 11px;
line-height: 1.5;
padding: 0 2px;
}
/* Main */
.main {
padding-top: 0;
}
.title-wrap {
padding: 10px 0 14px;
text-align: center;
}
h1 {
margin: 0;
font-size: 24px;
font-weight: 900;
letter-spacing: -0.03em;
}
.subtitle {
margin: 10px 0 0;
color: #767676;
font-size: 12px;
}
.workflow-panel {
position: relative;
min-height: 780px;
padding: 236px 42px 36px;
background: var(--panel);
border: 1px solid #cfcfcf;
border-radius: 8px;
}
.flow-row {
display: grid;
grid-template-columns: 280px 60px 280px 60px 280px 60px 280px 60px 280px 60px 280px;
align-items: center;
}
.arrow {
color: #444;
font-size: 48px;
line-height: 1;
text-align: center;
}
.node {
height: 135px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 4px solid;
border-radius: 16px;
text-align: center;
background: #fff;
}
.node .title {
margin-bottom: 8px;
font-size: 24px;
font-weight: 900;
letter-spacing: -0.04em;
}
.node .desc {
color: #555;
font-size: 17px;
}
.node.blue {
color: #0f4d77;
border-color: var(--blue);
background: var(--blue-bg);
}
.node.yellow {
color: #8a6500;
border-color: var(--yellow);
background: var(--yellow-bg);
}
.node.green {
color: #09643d;
border-color: var(--green);
background: var(--green-bg);
}
.node.red {
color: #9f2820;
border-color: var(--red);
background: var(--red-bg);
}
.node-num {
display: inline-flex;
justify-content: center;
align-items: center;
width: 23px;
height: 23px;
margin-right: 6px;
border: 2px solid currentColor;
border-radius: 50%;
font-size: 14px;
font-weight: 900;
}
/* absolute guide lines */
.rules-bar {
position: absolute;
left: 518px;
right: 43px;
top: 392px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
color: #9a6500;
font-size: 17px;
font-weight: 900;
background: #fffef7;
border: 2px dashed #f0a000;
border-radius: 10px;
}
.v-line {
position: absolute;
width: 0;
border-left: 2px dashed #b7b7b7;
}
.v-line.yellow {
border-color: #d49a00;
}
.v-line.green {
border-color: #15975f;
}
.v-line.red {
border-color: #c0392b;
}
.v1 {
left: 645px;
top: 360px;
height: 33px;
}
.v2 {
left: 989px;
top: 355px;
height: 77px;
}
.v3 {
left: 1344px;
top: 356px;
height: 36px;
}
.v4 {
left: 1696px;
top: 356px;
height: 36px;
}
.v5 {
left: 2041px;
top: 356px;
height: 36px;
}
.doc-row {
position: absolute;
left: 520px;
right: 42px;
top: 448px;
display: grid;
grid-template-columns: 280px 60px 280px 60px 280px 60px 280px 60px 280px;
align-items: start;
}
.doc-col {
display: flex;
flex-direction: column;
gap: 10px;
}
.doc-chip {
min-height: 36px;
padding: 6px 12px;
text-align: center;
border: 2px solid;
border-radius: 8px;
font-size: 15px;
font-family: "SFMono-Regular", Consolas, monospace;
background: #fff;
}
.doc-chip.yellow {
color: #8a6500;
border-color: var(--yellow);
background: var(--yellow-bg);
font-family: inherit;
font-weight: 800;
font-size: 14px;
}
.doc-chip.green {
color: #09643d;
border-color: var(--green);
background: var(--green-bg);
}
.doc-chip.red {
color: #9f2820;
border-color: var(--red);
background: var(--red-bg);
}
.doc-chip.blue {
color: #0f4d77;
border-color: var(--blue);
background: var(--blue-bg);
}
/* feedback loop */
.loop-label {
position: absolute;
z-index: 3;
padding: 7px 22px;
font-size: 20px;
font-weight: 900;
border: 2px solid;
border-radius: 10px;
background: #fff;
}
.label-purple {
top: 30px;
left: 1180px;
color: var(--purple);
border-color: var(--purple);
background: #fff5ff;
}
.label-orange {
top: 109px;
left: 990px;
color: var(--orange);
border-color: var(--orange);
background: #fff8f2;
}
.label-red {
top: 86px;
left: 1383px;
color: var(--red);
border-color: var(--red);
background: #fff5f5;
}
.loop-line {
position: absolute;
pointer-events: none;
}
.loop-purple {
top: 54px;
left: 944px;
width: 705px;
height: 150px;
border: 4px dashed var(--purple);
border-bottom: 0;
border-radius: 18px 18px 0 0;
}
.loop-purple:before,
.loop-purple:after {
content: "";
position: absolute;
bottom: -18px;
width: 0;
height: 0;
border-left: 13px solid transparent;
border-right: 13px solid transparent;
border-top: 30px solid var(--purple);
}
.loop-purple:before {
left: -16px;
}
.loop-purple:after {
right: -16px;
}
.loop-orange {
top: 137px;
left: 944px;
width: 350px;
height: 72px;
border-top: 4px dashed var(--orange);
border-left: 4px dashed var(--orange);
border-right: 4px dashed var(--orange);
border-radius: 12px 12px 0 0;
}
.loop-orange:before,
.loop-orange:after {
content: "";
position: absolute;
bottom: -18px;
width: 0;
height: 0;
border-left: 13px solid transparent;
border-right: 13px solid transparent;
border-top: 30px solid var(--orange);
}
.loop-orange:before {
left: -16px;
}
.loop-orange:after {
right: -16px;
}
.loop-red {
top: 137px;
left: 1292px;
width: 355px;
height: 72px;
border-top: 4px dashed #ff4b3d;
border-left: 4px dashed #ff4b3d;
border-right: 4px dashed #ff4b3d;
border-radius: 12px 12px 0 0;
}
.loop-red:before {
content: "";
position: absolute;
bottom: -18px;
left: -16px;
width: 0;
height: 0;
border-left: 13px solid transparent;
border-right: 13px solid transparent;
border-top: 30px solid #ff4b3d;
}
.feedback-box {
position: absolute;
left: 42px;
right: 42px;
bottom: 28px;
padding: 16px 36px 28px;
border: 2px solid #d1d1d1;
border-radius: 14px;
background: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.feedback-box h2 {
margin: 0 0 22px;
text-align: center;
font-size: 23px;
font-weight: 900;
}
.legend-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 36px;
}
.legend-item {
display: flex;
align-items: center;
gap: 14px;
font-size: 20px;
}
.line-sample {
position: relative;
flex-shrink: 0;
width: 85px;
border-top: 4px dashed;
}
.line-sample:after {
content: "";
position: absolute;
right: -1px;
top: -10px;
width: 0;
height: 0;
border-left: 18px solid currentColor;
border-top: 9px solid transparent;
border-bottom: 9px solid transparent;
}
.line-sample.red {
color: #ff4b3d;
border-color: #ff4b3d;
}
.line-sample.orange {
color: var(--orange);
border-color: var(--orange);
}
.line-sample.purple {
color: var(--purple);
border-color: var(--purple);
}
.bottom-legend {
margin-top: 14px;
padding: 12px 18px;
background: #fff;
border: 1px solid #d4d4d4;
border-radius: 7px;
font-size: 12px;
}
.bottom-legend strong {
margin-right: 12px;
}
.mini-box {
display: inline-block;
width: 25px;
height: 12px;
margin: 0 5px 0 12px;
border: 2px solid;
border-radius: 2px;
vertical-align: -2px;
}
.mini-box.yellow {
border-color: var(--yellow);
background: var(--yellow-bg);
}
.mini-box.green {
border-color: var(--green);
background: var(--green-bg);
}
.mini-box.red {
border-color: var(--red);
background: var(--red-bg);
}
.mini-box.blue {
border-color: var(--blue);
background: var(--blue-bg);
}
.mini-arrow {
display: inline-block;
margin: 0 10px 0 20px;
font-size: 22px;
line-height: 0;
vertical-align: -3px;
}
.mini-arrow.red {
color: #ff4b3d;
}
.note {
margin-top: 14px;
padding: 11px 18px;
color: #777;
text-align: center;
background: #f1f1ee;
border: 1px solid #d6d6d2;
border-radius: 7px;
font-size: 12px;
}
@media (max-width: 1600px) {
.page {
transform-origin: top left;
}
}
</style>
</head>
<body>
<div class="page">
<div class="layout">
<aside class="sidebar">
<div class="sidebar-head">📁 gameservice-fe-agent 구조</div>
<div class="side-body">
<div class="side-group yellow">
<span class="side-file">CLAUDE.md</span>
<p class="side-caption">세션 시작 시 1회 자동 로드<br />rules/* 전체를 @import</p>
</div>
<div class="side-group yellow">
<span class="side-title">rules/ (매 대화 턴 자동)</span>
<span class="side-file">coding-conventions.md</span>
<span class="side-file">framework-rules.md</span>
<span class="side-file">commit-pr.md</span>
<span class="side-file">claude-workflow.md</span>
<p class="side-caption">포맷팅·네이밍·Vue·커밋·작업방식</p>
</div>
<div class="side-group blue">
<span class="side-title">skills/ (명령으로 호출)</span>
<span class="side-file">conventional-commit</span>
<span class="side-file">markup-edm</span>
<span class="side-file">plan-analyzer</span>
<span class="side-file">plan-translation-generator</span>
<span class="side-file">project-init</span>
<span class="side-file">verify-component-review</span>
<span class="side-file">work-log</span>
<p class="side-caption">link-skills.sh 로 .claude/skills/ 링크</p>
</div>
</div>
</aside>
<main class="main">
<header class="title-wrap">
<h1>프론트엔드 AI 공통 지침 - Work Flow</h1>
<p class="subtitle">gameservice-fe-agent rules/ 구조와 각 작업 단계별 참조 파일의 흐름 (피드백 루프 포함)</p>
</header>
<section class="workflow-panel">
<div class="loop-line loop-purple"></div>
<div class="loop-line loop-orange"></div>
<div class="loop-line loop-red"></div>
<div class="loop-label label-purple">설계 변경 필요</div>
<div class="loop-label label-orange">계획 재수립 필요</div>
<div class="loop-label label-red">버그 발견 · 수정</div>
<div class="flow-row">
<article class="node blue">
<div class="title">요청자 / 개발자</div>
<div class="desc">업무 요청</div>
</article>
<div class="arrow"></div>
<article class="node yellow">
<div class="title"><span class="node-num">1</span>자동 로드</div>
<div class="desc">CLAUDE.md + rules/* × 4</div>
</article>
<div class="arrow"></div>
<article class="node green">
<div class="title"><span class="node-num">2</span>탐색</div>
<div class="desc">파일 파악 · 패턴 확인</div>
</article>
<div class="arrow"></div>
<article class="node green">
<div class="title"><span class="node-num">3</span>계획</div>
<div class="desc">할 일 목록 · 승인</div>
</article>
<div class="arrow"></div>
<article class="node green">
<div class="title"><span class="node-num">4</span>구현</div>
<div class="desc">코드 작성 · 지침 준수</div>
</article>
<div class="arrow"></div>
<article class="node red">
<div class="title"><span class="node-num">5</span>검증</div>
<div class="desc">린트 · 빌드 · 커밋/PR</div>
</article>
</div>
<div class="v-line yellow v1"></div>
<div class="v-line green v2"></div>
<div class="v-line green v3"></div>
<div class="v-line green v4"></div>
<div class="v-line red v5"></div>
<div class="rules-bar">rules/* × 4 — 매 대화 턴 자동 참조 (① ~ ⑤ 전 단계 공통)</div>
<div class="doc-row">
<div class="doc-col">
<div class="doc-chip yellow">CLAUDE.md (세션 시작 1회)</div>
</div>
<div></div>
<div class="doc-col">
<div class="doc-chip green">coding-conventions.md</div>
<div class="doc-chip green">framework-rules.md</div>
</div>
<div></div>
<div class="doc-col">
<div class="doc-chip green">claude-workflow.md</div>
<div class="doc-chip green">framework-rules.md</div>
</div>
<div></div>
<div class="doc-col">
<div class="doc-chip green">coding-conventions.md</div>
<div class="doc-chip green">framework-rules.md</div>
</div>
<div></div>
<div class="doc-col">
<div class="doc-chip red">coding-conventions.md</div>
<div class="doc-chip red">commit-pr.md</div>
</div>
</div>
<div class="feedback-box">
<h2>피드백 루프 (역방향 흐름)</h2>
<div class="legend-grid">
<div class="legend-item">
<span class="line-sample red"></span>
<span>④ 구현 → ⑤ 검증 → ④ 구현 : 버그 발견 시 수정 후 재검증</span>
</div>
<div class="legend-item">
<span class="line-sample orange"></span>
<span>③ 계획 → ④ 구현 중 재설계 : 계획 재수립 후 다시 구현</span>
</div>
<div class="legend-item">
<span class="line-sample purple"></span>
<span>⑤ 검증 → ② 탐색 : 검증 결과로 구조 변경이 필요한 경우</span>
</div>
</div>
</div>
</section>
<div class="bottom-legend">
<strong>범례</strong>
<span class="mini-box yellow"></span> 세션 시작 1회 자동 로드 (CLAUDE.md)
<span class="mini-box yellow"></span> 매 대화 턴 자동 참조 (rules/ × 4)
<span class="mini-box green"></span> 탐색·계획·구현 단계 참조
<span class="mini-box red"></span> 검증·커밋/PR 단계 참조
<span class="mini-box blue"></span> skills/ (명령으로 호출)
<span class="mini-arrow"></span> 정방향 진행
<span class="mini-arrow red">--▶</span> 피드백 루프 (역방향)
</div>
<div class="note">
피드백 루프는 구현·검증 중 발견된 이슈에 따라 이전 단계로 돌아가 재작업 후 다시 진행합니다.
모호한 요구사항은 추측하지 않고 사용자에게 확인 후 계획 단계부터 다시 시작합니다.
</div>
</main>
</div>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

21
skills/dreaming/SKILL.md Normal file
View File

@@ -0,0 +1,21 @@
---
name: dreaming
description: |
프로젝트의 코드베이스(package.json, Pinia 스토어, 커스텀 Composable 등)를
휴리스틱하게 스캔하고 자율 인지 메모리 파일(.claude/project/dreaming-context.md)을 업데이트합니다.
다음 상황에서 반드시 사용하세요:
- 사용자가 "코드베이스 스캔해줘", "컨텍스트 갱신해줘", "dreaming 돌려줘"라고 지시할 때
- 새로운 브랜치로 전환했거나 대량의 코드가 머지된 직후 프로젝트 지침을 최신화하고 싶을 때
---
# 프로젝트 자율 인지 메모리 스캔 (dreaming)
코드베이스 전체를 정기적으로 자율 성찰(Self-reflection)하여 에이전트의 뇌 상태를 동기화하고 프로젝트-스냅샷 파일을 생성합니다.
## 작업 순서
1. 프로젝트 루트 경로에서 \`node .claude/skills/dreaming/scripts/dreaming.js\` 명령을 실행합니다.
2. 스크립트가 실행되면서 프로젝트 루트의 \`package.json\`, \`stores/\`, \`composables/\`, \`components/\` 등의 경로를 탐색하여 최신 의존성, 스토어 상태, 커스텀 컴포저블 목록 등을 분석합니다.
3. 생성된 결과를 \`.claude/project/dreaming-context.md\` 파일로 기록 및 덮어쓰고, 해당 파일이 프로젝트 루트의 \`CLAUDE.md\`에 정상 임포트되었는지 검증합니다.
4. 분석 완료 보고서와 요약 내용을 개발자에게 깔끔하게 알려드립니다.

View File

@@ -0,0 +1,369 @@
#!/usr/bin/env node
/**
* dreaming.js - "AI 코더"에서 "상태 저장형 컨벤션 가디언"으로 (Dreaming의 힘)
*
* 이 스크립트는 프로젝트 루트(CWD)의 코드베이스를 휴리스틱하게 스캔하여,
* 현재 프레임워크 상태, 액티브 Pinia 스토어, 커스텀 Composable, 컴포넌트 구조, 테일윈드 설정 등을 추출합니다.
* 분석된 내용은 .claude/project/dreaming-context.md 파일로 기록되어,
* Claude Code가 프로젝트의 최신 컨벤션과 아키텍처 상태를 항시 보존하고 인지하도록 돕습니다.
*/
const fs = require('fs');
const path = require('path');
const CWD = process.cwd();
const CLAUDE_DIR = path.join(CWD, '.claude');
const PROJECT_DIR = path.join(CLAUDE_DIR, 'project');
const OUTPUT_FILE = path.join(PROJECT_DIR, 'dreaming-context.md');
const CLAUDE_MD = path.join(CWD, 'CLAUDE.md');
// 헬퍼: 디렉토리 존재 여부 확인
function directoryExists(dirPath) {
try {
return fs.statSync(dirPath).isDirectory();
} catch (e) {
return false;
}
}
// 헬퍼: 파일 존재 여부 확인
function fileExists(filePath) {
try {
return fs.statSync(filePath).isFile();
} catch (e) {
return false;
}
}
// 헬퍼: 재귀적으로 파일 목록 가져오기 (옵션 포함)
function getFilesRecursive(dirPath, extFilter = [], ignoreDirs = ['node_modules', '.git', '.nuxt', 'dist']) {
let results = [];
if (!directoryExists(dirPath)) return results;
const list = fs.readdirSync(dirPath);
list.forEach((file) => {
const fullPath = path.join(dirPath, file);
const stat = fs.statSync(fullPath);
if (stat && stat.isDirectory()) {
if (!ignoreDirs.includes(file)) {
results = results.concat(getFilesRecursive(fullPath, extFilter, ignoreDirs));
}
} else {
const ext = path.extname(file);
if (extFilter.length === 0 || extFilter.includes(ext)) {
results.push(fullPath);
}
}
});
return results;
}
// 1. package.json 분석
function analyzePackageJson() {
const packagePath = path.join(CWD, 'package.json');
if (!fileExists(packagePath)) {
return { name: 'Unknown Project', framework: 'Unknown', techStack: [], scripts: [] };
}
try {
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
const techStack = [];
let framework = 'Vue/Nuxt';
if (deps['nuxt'] || deps['nuxt3'] || deps['nuxt-edge']) {
framework = `Nuxt (${deps['nuxt'] || deps['nuxt3'] || 'v3'})`;
techStack.push('Nuxt');
} else if (deps['vue']) {
framework = `Vue (${deps['vue']})`;
techStack.push('Vue');
} else if (deps['next']) {
framework = `Next.js (${deps['next']})`;
techStack.push('Next.js');
} else if (deps['react']) {
framework = `React (${deps['react']})`;
techStack.push('React');
}
if (deps['pinia'] || deps['@pinia/nuxt']) {
techStack.push('Pinia (상태 관리)');
}
if (deps['tailwindcss'] || deps['@nuxtjs/tailwindcss']) {
techStack.push('Tailwind CSS (스타일)');
}
if (deps['typescript']) {
techStack.push('TypeScript');
}
if (deps['vitest'] || deps['@vitest/ui']) {
techStack.push('Vitest (유닛 테스트)');
}
if (deps['eslint']) {
techStack.push('ESLint');
}
if (deps['prettier']) {
techStack.push('Prettier');
}
return {
name: pkg.name || 'Unnamed Project',
version: pkg.version || '1.0.0',
framework,
techStack,
scripts: pkg.scripts ? Object.keys(pkg.scripts) : []
};
} catch (e) {
return { name: 'Parsing Error', framework: 'Unknown', techStack: [], scripts: [], error: e.message };
}
}
// 2. 디렉토리 구조 스캔 및 요약
function scanDirectoryStructure() {
const dirsToScan = ['components', 'composables', 'stores', 'pages', 'server', 'layouts', 'middleware', 'plugins', 'types', 'assets'];
const summary = {};
dirsToScan.forEach((dirName) => {
const dirPath = path.join(CWD, dirName);
if (directoryExists(dirPath)) {
const files = getFilesRecursive(dirPath);
summary[dirName] = {
exists: true,
count: files.length,
examples: files.slice(0, 5).map(f => path.relative(CWD, f))
};
} else {
summary[dirName] = { exists: false, count: 0, examples: [] };
}
});
return summary;
}
// 3. Pinia 스토어 상세 분석
function analyzePiniaStores() {
const storesDir = path.join(CWD, 'stores');
const stores = [];
if (!directoryExists(storesDir)) {
// composables 내에 스토어가 정의되어 있을 수도 있으므로 추가 탐색 가능
return stores;
}
const files = getFilesRecursive(storesDir, ['.ts', '.js']);
files.forEach((file) => {
try {
const content = fs.readFileSync(file, 'utf8');
const filename = path.basename(file);
// defineStore 매칭
const defineStoreMatch = content.match(/defineStore\(\s*['"`]([^'"`]+)['"`]/);
const storeId = defineStoreMatch ? defineStoreMatch[1] : null;
// 상태(state) 필드 휴리스틱 추출
const stateFields = [];
const stateRegex = /const\s+([a-zA-Z0-9_$]+)\s*=\s*(ref|reactive|computed)/g;
let match;
while ((match = stateRegex.exec(content)) !== null) {
stateFields.push(`${match[1]} (${match[2]})`);
}
// 함수(actions) 추출
const actionFields = [];
const actionRegex = /function\s+([a-zA-Z0-9_$]+)/g;
while ((match = actionRegex.exec(content)) !== null) {
if (!match[1].startsWith('use')) {
actionFields.push(match[1]);
}
}
stores.push({
file: path.relative(CWD, file),
id: storeId || filename.replace(path.extname(filename), ''),
state: stateFields,
actions: actionFields
});
} catch (e) {
// ignore
}
});
return stores;
}
// 4. 커스텀 Composable 분석
function analyzeComposables() {
const composablesDir = path.join(CWD, 'composables');
const composables = [];
if (!directoryExists(composablesDir)) return composables;
const files = getFilesRecursive(composablesDir, ['.ts', '.js']);
files.forEach((file) => {
try {
const content = fs.readFileSync(file, 'utf8');
const filename = path.basename(file);
const relativePath = path.relative(CWD, file);
// export const useXxx 함수 매칭
const useFuncRegex = /export\s+const\s+(use[a-zA-Z0-9_$]+)/g;
const useFuncs = [];
let match;
while ((match = useFuncRegex.exec(content)) !== null) {
useFuncs.push(match[1]);
}
const defaultFuncRegex = /export\s+default\s+function\s+(use[a-zA-Z0-9_$]+)/;
const defaultMatch = content.match(defaultFuncRegex);
if (defaultMatch) {
useFuncs.push(defaultMatch[1]);
}
if (useFuncs.length > 0) {
composables.push({
file: relativePath,
functions: useFuncs
});
} else {
// 파일명이 useXxx 형태인 경우 추가
if (filename.startsWith('use')) {
composables.push({
file: relativePath,
functions: [filename.replace(path.extname(filename), '')]
});
}
}
} catch (e) {
// ignore
}
});
return composables;
}
// 5. 테스트 파일 통계
function analyzeTests() {
const testFiles = getFilesRecursive(CWD, ['.spec.ts', '.spec.js', '.test.ts', '.test.js']);
return {
count: testFiles.length,
files: testFiles.slice(0, 10).map(f => path.relative(CWD, f))
};
}
// 메인 실행기
function run() {
console.log('🤖 프로젝트 "Dreaming" 컨텍스트 분석 시작...');
const pkgInfo = analyzePackageJson();
const dirSummary = scanDirectoryStructure();
const stores = analyzePiniaStores();
const composables = analyzeComposables();
const tests = analyzeTests();
const timestamp = new Date().toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' });
// 마크다운 문서 빌드
let md = `# 🧠 프로젝트 자율 인지 메모리 (Dreaming Context)
이 파일은 \`dreaming.js\` 스크립트에 의해 프로젝트 코드베이스를 분석하여 자동 생성되었습니다.
Claude Code가 프로젝트의 실시간 코드 구조, 사용 중인 스토어, 컴포넌트 레이아웃, 그리고 최신 개발 흐름을 완벽히 인지하도록 돕습니다.
* **최종 동기화 시간:** ${timestamp} (Asia/Seoul)
---
## 🏗 프로젝트 정보
* **프로젝트명:** \`${pkgInfo.name}\` (v${pkgInfo.version || '1.0.0'})
* **핵심 프레임워크:** \`${pkgInfo.framework}\`
* **기술 스택 라이브러리:**
${pkgInfo.techStack.map(tech => ` - ${tech}`).join('\n') || ' - (감지된 주요 라이브러리 없음)'}
---
## 📁 디렉토리 구조 및 컴포넌트 현황
현재 활성화되어 있는 프로젝트 레이아웃 정보입니다.
| 디렉토리 | 활성 여부 | 파일 개수 | 주요 샘플 파일 (최대 5개) |
|---|---|---|---|
${Object.entries(dirSummary).map(([name, info]) => {
return `| \`${name}/\` | ${info.exists ? '✅' : '❌'} | ${info.count}개 | ${info.examples.map(ex => `\`${path.basename(ex)}\``).join(', ') || '-'} |`;
}).join('\n')}
---
## 🍍 액티브 Pinia 스토어 목록
현재 코드베이스에 존재하는 글로벌 상태 저장소들의 템플릿 정보입니다. 새 기능을 개발할 때 아래 스토어를 재사용하거나 참고하세요.
${stores.length === 0 ? '*감지된 Pinia 스토어가 없습니다. (stores/ 디렉토리 없음 혹은 비어있음)*' : stores.map(store => {
return `### 📦 \`${store.id}\`
* **정의 파일:** \`${store.file}\`
* **감지된 상태 (state/computed):** ${store.state.length > 0 ? store.state.map(s => `\`${s}\``).join(', ') : '없음'}
* **감지된 액션 (actions/methods):** ${store.actions.length > 0 ? store.actions.map(a => `\`${a}\``).join(', ') : '없음'}
`;
}).join('\n')}
---
## 🎣 커스텀 Composable (useXxx) 목록
다양한 비즈니스 로직과 부수효과를 격리해 둔 커스텀 훅 목록입니다. 컴포넌트 내부에서 비즈니스 로직을 직접 짜기 전, 아래 훅들의 재사용 가능성을 먼저 타진하세요.
${composables.length === 0 ? '*감지된 커스텀 Composable이 없습니다. (composables/ 디렉토리 없음 혹은 비어있음)*' : composables.map(comp => {
return `- **파일:** \`${comp.file}\`
- **제공 함수:** ${comp.functions.map(f => `\`${f}()\``).join(', ')}
`;
}).join('\n')}
---
## 🧪 유닛 테스트 통계
현재까지 구축된 테스트 커버리지 현황입니다.
* **감지된 테스트 파일 수:** \`${tests.count}\`
${tests.count > 0 ? `* **최근 테스트 목록:**\n${tests.files.map(f => ` - \`${f}\``).join('\n')}` : ' *(새 기능을 추가할 때 반드시 Vitest 규격의 유닛 테스트를 함께 작성해야 함)*'}
---
## 🛠 실행 가능한 스크립트 (package.json)
프로젝트 구동 및 테스트 검증을 위해 사용 가능한 명령어 리스트입니다.
${pkgInfo.scripts.map(s => `- \`npm run ${s}\` (또는 pnpm/yarn/bun)`).join('\n') || '- 스크립트 없음'}
`;
// 디렉토리 및 파일 저장
if (!directoryExists(CLAUDE_DIR)) {
fs.mkdirSync(CLAUDE_DIR);
}
if (!directoryExists(PROJECT_DIR)) {
fs.mkdirSync(PROJECT_DIR);
}
fs.writeFileSync(OUTPUT_FILE, md, 'utf8');
console.log(`✅ Dreaming Context 업데이트 완료! -> ${path.relative(CWD, OUTPUT_FILE)}`);
// CLAUDE.md에 자동 임포트 추가 처리
if (fileExists(CLAUDE_MD)) {
let claudeMdContent = fs.readFileSync(CLAUDE_MD, 'utf8');
const importStr = '@.claude/project/dreaming-context.md';
if (!claudeMdContent.includes(importStr)) {
// '## 프로젝트 지침' 혹은 '## 공통 지침' 섹션 밑에 삽입 시도
const sectionMatch = claudeMdContent.match(/(## 프로젝트 지침\r?\n)/);
if (sectionMatch) {
claudeMdContent = claudeMdContent.replace(
sectionMatch[0],
`${sectionMatch[0]}${importStr}\n`
);
fs.writeFileSync(CLAUDE_MD, claudeMdContent, 'utf8');
console.log(`🔗 CLAUDE.md에 ${importStr} 동적 임포트 구문을 연결했습니다.`);
} else {
// 찾을 수 없다면 파일 상단 혹은 하단에 단순 추가
claudeMdContent = claudeMdContent + `\n\n## 자동 분석 컨텍스트\n${importStr}\n`;
fs.writeFileSync(CLAUDE_MD, claudeMdContent, 'utf8');
console.log(`🔗 CLAUDE.md 끝에 ${importStr} 동적 임포트 구문을 추가했습니다.`);
}
}
} else {
console.log(`⚠️ 프로젝트 루트에 CLAUDE.md 가 존재하지 않습니다. CLAUDE.md를 먼저 생성하고 @.claude/project/dreaming-context.md 임포트 선언을 수동으로 추가하는 것을 권장합니다.`);
}
}
// 스크립트 실행
run();

View File

@@ -0,0 +1,26 @@
---
name: squad-orchestration
description: |
하나의 대규모 컴포넌트나 피처 요건을 마크업, 접근성, 유닛 테스트의 3가지 전문
에이전트 역할군 지시서로 분할하고 자동 오케스트레이션 파이프라인을 빌드합니다.
다음 상황에서 반드시 사용하세요:
- 사용자가 "스쿼드 오케스트레이션 가동해줘", "컴포넌트 병렬 개발 세팅해줘", "에이전트 스쿼드 만들어줘"라고 지시할 때
- 복잡한 UI 구현과 동시에 높은 웹 접근성(A11y) 기준 및 100% 동작 보장의 Vitest 유닛 테스트 파일 생성이 일시에 필요할 때
---
# AI 개발 스쿼드 오케스트레이션 (squad-orchestration)
피처 하나를 3인의 전문 에이전트 작업 지시서로 분할하고, 전체 흐름을 완벽히 기동할 통합 오케스트레이터 러너를 조직합니다.
## 작업 순서
1. 개발 대상 컴포넌트 이름과 요구 스펙을 입력받습니다.
2. \`node .claude/skills/squad-orchestration/scripts/squad-orchestrator.js --name <Name> --spec "<Spec>"\` 명령을 구동합니다.
3. 스크립트가 실행되면 프로젝트 루트의 \`squad/<ComponentName>/\` 폴더가 생성되고 아래 파일들이 구조화됩니다:
- \`tasks/01_markup_agent.md\`: UI/마크업 디자인 스페셜리스트 작업 지시서
- \`tasks/02_a11y_agent.md\`: WCAG 2.1 AA 및 키보드 사용 보증 웹 접근성 지시서
- \`tasks/03_test_agent.md\`: Vitest 유닛 테스트 케이스 지시서
- \`run-squad.js\`: 연속 오토파일럿 기동을 제어하는 마스터 노드 러너 스크립트
- \`README.md\`: 각 에이전트 조율을 위한 매뉴얼 가이드
4. 에이전트 스쿼드 배치 상황과 실행 방법을 개발자에게 친근하게 알려드립니다.

View File

@@ -0,0 +1,107 @@
# 🤖 프론트엔드 에이전트 자동화 시스템 가이드
이 문서는 `gameservice-fe-agent` 패키지에 탑재된 두 가지 핵심 자동화 프로세스—**프로젝트 자율 인지(Dreaming) 시스템** 및 **AI 개발 스쿼드(Squad) 오케스트레이션**의 개념과 실행 방법을 상세히 가이드합니다.
이 도구들은 일회성(Stateless) AI 코드 작성의 한계를 넘어, **지속성 있는 컨벤션 수호자(Convention Guardian)**로 동작하고 복잡한 컴포넌트를 **병렬 전문 역할 분담 스쿼드**를 통해 해결하는 것을 목적으로 합니다.
---
## 🧠 1. "AI 코더"에서 "상태 저장형 컨벤션 가디언"으로 (Dreaming)
### 💡 도입 배경 및 개념
일반적인 LLM 코딩 에이전트는 프롬프트를 보낼 때마다 컨텍스트가 초기화되는 **단발성(Stateless)** 모델로 동작합니다. 이 때문에 기존 프로젝트의 폴더 구조, 이미 만들어진 커스텀 Composable(useXxx), 활성화된 Pinia 스토어 목록, 패키지 버전 등을 매번 인지하지 못해 불필요한 코드를 중복 구현하거나 기존 컨벤션을 깨는 실수를 저지릅니다.
**Dreaming 자동화 시스템**은 에이전트가 "코드베이스 전체를 정기적으로 자율 성찰(Self-reflection)"하도록 만드는 로컬 구현체입니다. `dreaming.js`를 구동하면 프로젝트의 상태를 휴리스틱하게 분석하여 `.claude/project/dreaming-context.md` 파일에 기록하고, `CLAUDE.md` 수입 선언을 통해 에이전트가 시작부터 이 최신 상태를 인지하게 만듭니다.
### 🛠 실행 및 연동 방법
#### ① 실행 명령어
프로젝트 루트 경로에서 아래 스크립트를 구동합니다:
```bash
node .claude/skills/dreaming/scripts/dreaming.js
```
#### ② 자동 수행되는 태스크
1. `package.json` 파싱: 프레임워크 버전, 핵심 기술 스택(Nuxt, Pinia, Tailwind, Vitest 등), 실행 가능한 스크립트 명령어 수집.
2. 디렉토리 구조 스캔: 활성화된 디렉토리와 소속 파일 개수, 구조적 예시 목록 도출.
3. **Pinia 스토어 자율 추출:** `stores/` 내부의 파일들을 분석하여 각 스토어의 ID, 반응형 상태(state/computed), 비즈니스 함수(actions) 목록을 정밀 인지.
4. **커스텀 Composable 자율 추출:** `composables/` 내부의 `useXxx` 스타일 컴포저블을 검출해 노출 함수 리스트 확보.
5. **유닛 테스트 통계 파싱:** 구축된 테스트 파일 목록 및 통계 추출.
6. **마크다운 출력 및 자동 임포트:** 수집된 정보를 마크다운 리포트로 자동 빌드해 `.claude/project/dreaming-context.md` 파일에 덮어쓰고, 루트의 `CLAUDE.md``@.claude/project/dreaming-context.md` 임포트 구문이 없으면 이를 자동으로 연결합니다.
### 📌 시니어 FE 관점의 기대 효과
* **중복 코드 생성 전면 억제:** 에이전트가 이미 존재하는 Pinia 스토어나 커스텀 훅을 즉시 찾아내기 때문에, 동일한 API 바인딩이나 헬퍼 함수를 이중으로 작성하지 않습니다.
* **프로젝트 맞춤형 가상 주니어화:** 며칠 쉬고 오거나 세션이 만료되더라도, 에이전트가 단 1초 만에 프로젝트의 최신 스냅샷을 뇌리에 새긴 상태(Stateful)로 지능적인 보조를 시작합니다.
---
## 👥 2. "AI 한 명과 대화"가 아닌 "AI 개발 스쿼드(Squad) 오케스트레이션"
### 💡 도입 배경 및 개념
아무리 성능이 뛰어난 모델이라도 템플릿 마크업 작성, 복잡한 Tailwind 스타일링, WCAG 2.1 AA 접근성 마크업, 그리고 Vitest 단위 테스트 작성을 한 번에 지시하면 문맥 누락이나 결함(Bug)이 발생하기 마련입니다.
**스쿼드 오케스트레이션**은 하나의 피처 요청을 **3인의 가상 전문 개발 에이전트**로 쪼개어 단계별/병렬 협업 파이프라인으로 수행하는 시스템입니다. 시니어 프론트엔드 엔지니어(Gil)는 코드 작성에 시간을 소모하는 대신, 이 전문 에이전트들을 조율하고 합산 결과물의 비즈니스 사양과 렌더링을 최종 승인하는 **오케스트레이터(Orchestrator)**의 최고 존엄 지위를 갖게 됩니다.
```
┌──────────────────────┐
│ Gil (Orchestrator) │
└──────────┬───────────┘
│ (스쿼드 생성 지시)
┌────────────────────────────────────────────────────────┐
│ squad-orchestrator.js │
└────────────┬───────────────┬───────────────┬───────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐┌──────────────┐┌──────────────┐
│ Role 1 ││ Role 2 ││ Role 3 │
│ UI/마크업 ││ 웹 접근성 ││ QA/단위테스트│
│ 스페셜리스트 ││ 스페셜리스트 ││ 스페셜리스트 │
└───────┬──────┘└───────┬──────┘└───────┬──────┘
│ │ │
▼ ▼ ▼
[구조 & 뼈대 빌드] [ARIA & 키보드 주입] [Vitest 케이스 PASS]
```
### 🛠 실행 및 연동 방법
#### ① 스쿼드 조직하기 (인터랙션/CLI 겸용)
새로운 피처나 컴포넌트를 설계할 때 아래 스크립트를 구동합니다:
* **대화형 모드로 조직하기:**
```bash
node .claude/skills/squad-orchestration/scripts/squad-orchestrator.js
```
명령어 실행 후 터미널의 질문에 따라 컴포넌트 이름과 요구 스펙을 입력하면 스쿼드가 즉시 조직됩니다.
* **CLI 인자로 한 번에 조직하기:**
```bash
node .claude/skills/squad-orchestration/scripts/squad-orchestrator.js --name "UserScoreCard" --spec "유저 프로필과 전적, 랭킹을 보여주고 점수에 따라 테두리 색상이 바뀌는 컴포넌트 구현"
```
#### ② 스쿼드 파일 구성 및 역할
명령이 끝나면 루트에 `squad/<ComponentName>/` 폴더가 자동 생성됩니다.
* **`tasks/01_markup_agent.md` (Role 1):** Vue 3 템플릿, 데이터 상태 설계, Tailwind 반응형 레이아웃 구성 집중 지시서.
* **`tasks/02_a11y_agent.md` (Role 2):** WCAG 2.1 AA 기준 준수, ARIA 역할(role), 스크린 리더용 라벨링, 키보드 인터랙션 집중 보완 및 Surgical 정밀 수정 지시서.
* **`tasks/03_test_agent.md` (Role 3):** 생성된 컴포넌트 사양 검증을 위한 `*.spec.ts` 단위 테스트 코드 및 Mock 데이터 구축 지시서.
* **`run-squad.js`:** Claude Code CLI를 연속 구동하여 세 단계의 코딩 에이전트를 차례로 자동 실행하고, 마지막에 Vitest 테스트 엔진을 가동하여 검증을 완수해 내는 마스터 러너 스크립트.
#### ③ 스쿼드 자동 파이프라인 가동
스쿼드가 세팅되면, 해당 컴포넌트 폴더 내의 마스터 스크립트를 즉시 가동하여 오토파일럿 개발을 시작할 수 있습니다:
```bash
node squad/<ComponentName>/run-squad.js
```
### 📌 시니어 FE 관점의 기대 효과
* **결점 제로(Defect-Free) 컴포넌트 완성:** 각 역할군이 하나의 관점(UI 구조 -> 접근성 -> 테스트 품질)에 완벽히 몰입하여 단계별로 코드를 가꾸고 다듬기 때문에, 품질적으로 완벽무결한 컴포넌트가 조립됩니다.
* **테스트 주도 개발(TDD)의 정수 체득:** 최종 테스트가 완료될 때까지 에이전트 루프가 가동되므로, 코드를 올리기도 전에 모든 단위 동작과 가시적인 비즈니스 엣지 케이스들의 통과를 보장받은 채 개발이 마무리됩니다.
---
## 🚀 실무 도입 시 베스트 프랙티스
1. **지속성 확보를 위한 git ignore 추가 권장:**
`squad/` 폴더 내의 작업 지시서나 가이드 파일들은 개발 도중의 중간 생성물(Task sheets)에 해당하므로, 프로젝트의 메인 git 히스토리를 깔끔하게 유지하기 위해 `.gitignore`에 `squad/` 경로를 추가하는 것을 추천합니다.
2. **개발 전 dreaming 구동 루틴화:**
새로운 브랜치를 따거나 대규모 PR을 머지받았을 때는 에이전트에게 일을 시키기 전 `node .claude/skills/dreaming/scripts/dreaming.js`를 한 번 실행해 주는 것이 좋습니다. 에이전트의 뇌 스냅샷을 1초 만에 최신화해 줍니다.

View File

@@ -0,0 +1,275 @@
#!/usr/bin/env node
/**
* squad-orchestrator.js - AI 개발 스쿼드(Squad) 오케스트레이터
*
* 하나의 컴포넌트/피처 요건을 입력받아 3가지 전문 에이전트 역할군으로 작업을 쪼개고,
* 각 에이전트에 줄 지시서와 전체 프로세스를 시뮬레이션/오케스트레이션하는 마스터 러너 파일을 생성합니다.
*
* 에이전트 스쿼드 구성:
* 1. UI/마크업 스페셜리스트 (Markup Specialist): 구조 설계, 렌더링, Tailwind CSS 스타일링 및 기본 상태 바인딩.
* 2. 웹 접근성 스페셜리스트 (A11y/UX Specialist): WCAG 2.1 AA 기준 준수, ARIA 레이블링, 키보드 인터랙션 강화.
* 3. QA/유닛 테스트 스페셜리스트 (Testing Specialist): Vitest 기반 유닛 테스트 스위트 작성 및 품질 보증.
*/
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const CWD = process.cwd();
const SQUAD_ROOT = path.join(CWD, 'squad');
// 헬퍼: 디렉토리 자동 생성
function ensureDirectory(dirPath) {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
// 헬퍼: 파일 작성
function writeFile(filePath, content) {
const dir = path.dirname(filePath);
ensureDirectory(dir);
fs.writeFileSync(filePath, content, 'utf8');
}
// CLI 인자 파싱
function parseArgs() {
const args = process.argv.slice(2);
const result = { name: '', spec: '' };
for (let i = 0; i < args.length; i++) {
if (args[i] === '--name' || args[i] === '-n') {
result.name = args[i + 1] || '';
i++;
} else if (args[i] === '--spec' || args[i] === '-s') {
result.spec = args[i + 1] || '';
i++;
}
}
return result;
}
// 지시서 생성 로직
function generateSquadTasks(componentName, specification) {
const lowercaseName = componentName.toLowerCase();
const componentPath = `components/${componentName}.vue`;
const testPath = `components/${componentName}.spec.ts`;
const taskDir = path.join(SQUAD_ROOT, componentName);
// 1. Markup Specialist 지시서
const markupTask = `# 🎨 Role 1: UI/마크업 스페셜리스트 지시서
## 목표
- 요구사항에 맞는 컴포넌트 구조(Vue 3 / SFC)를 생성하고 Tailwind CSS를 이용해 아름답고 완벽한 반응형 UI를 스타일링합니다.
- 복잡하지 않은 수준에서 데이터 수신을 위한 Props 정의 및 이벤트를 내보내기 위한 Emits를 설계합니다.
## 개발 대상 파일
- \`${componentPath}\`
## 기술 요건 (framework-rules.md 준수)
- \`<script setup lang="ts">\` 형식을 완벽히 준수해야 합니다.
- Tailwind CSS의 유틸리티 클래스 위주로 스타일링하며, 조건부 렌더링 시 가독성 높은 클래스 바인딩을 적용하세요.
- 비즈니스 상태가 필요한 경우, 템플릿 코드에 가짜(mock) 반응형 데이터(\`ref\`, \`computed\`)를 연결하여 상태를 구성합니다.
## 개발 요구사항
${specification}
---
## 작업 지시 사항
1. \`${componentPath}\` 파일을 신규 생성합니다.
2. 컴포넌트가 정상적으로 렌더링되고 기본적인 반응형 동작(클릭 이벤트, 폼 입력 바인딩 등)이 원활하게 흐르는지 확인하는 모드에서 코드를 작성하세요.
3. 작업 완료 후, 생성된 코드를 출력하여 다음 단계(A11y 검증)로 전달할 수 있게 준비하세요.
`;
// 2. A11y Specialist 지시서
const a11yTask = `# ♿ Role 2: 웹 접근성(A11y) 스페셜리스트 지시서
## 목표
- UI 마크업 스페셜리스트가 구현한 \`${componentPath}\` 파일을 검토 및 개선합니다.
- WCAG 2.1 AA 및 접근성 검증 지침(\`verify-a11y\`)에 맞춰 전 세계 모든 사용자(스크린 리더 사용자, 키보드 단독 사용자 등)가 사용에 제약이 없도록 인터랙션을 강화합니다.
## 대상 파일
- \`${componentPath}\` (기존 마크업 위에서 수정)
## 접근성 필수 강화 체크리스트
- **시각 대체 수단:** 모든 아이콘 단독 버튼이나 이미지에 적절한 대체 텍스트(\`aria-label\` 또는 \`alt\`)를 적용합니다.
- **키보드 내비게이션:** 모든 상호작용 요소에 키보드 포커스가 잡히고(\`tabindex\`), 스페이스 및 엔터 키 입력 시 적절한 동작이 수행되어야 합니다. 모달이나 레이어 팝업이 뜰 경우 탭 인덱스 포커스 트랩(Focus Trap)이 동작해야 합니다.
- **의미론적 마크업:** 적절한 HTML5 시맨틱 태그 및 WAI-ARIA 속성(\`role\`, \`aria-expanded\`, \`aria-haspopup\`, \`aria-controls\` 등)을 사용합니다.
- **포커스 링 가시성:** 모든 인터랙티브 요소는 포커스를 받았을 때 아웃라인(\`:focus-visible\`)이 명확하게 나타나야 합니다.
---
## 작업 지시 사항
1. UI 마크업 스페셜리스트가 작성한 \`${componentPath}\` 파일을 상세 분석합니다.
2. 접근성 상의 미비점(예: 키보드 미지원 드롭다운, 라벨링이 누락된 버튼 등)을 발견하면 원본 코드의 핵심 구조를 깨지 않는 선에서 **Surgical Changes(최소한의 정밀 수정)**로 접근성 강화 코드를 삽입하세요.
3. 주석을 통해 어떤 부분이 접근성 보장을 위해 개선되었는지 기록하세요.
`;
// 3. QA/Testing Specialist 지시서
const testingTask = `# 🧪 Role 3: QA/유닛 테스트 스페셜리스트 지시서
## 목표
- 완성된 \`${componentPath}\`에 대해 완벽한 유닛 테스트 케이스를 생성합니다.
- 컴포넌트의 비즈니스 반응형 흐름, 엣지 케이스, 이벤트 방출, 컴포넌트 마운트 상태, 그리고 접근성 레이블들의 존재성까지 종합 검증합니다.
## 대상 파일
- \`${testPath}\` (신규 생성)
## 테스트 시나리오 설계 요건
- **마운트 상태 검증:** 기본 props를 전달했을 때 컴포넌트가 깨짐 없이 정상 마운트되고 지정된 요소들이 화면에 나오는지 확인.
- **인터랙션/상태 변경 테스트:** 사용자가 클릭, 입력 등을 수행할 때 로컬 상태가 정확히 변하고, 부모 컴포넌트로 적절한 \`emit\`이 전달되는지 검증.
- **경계 조건(Edge Cases):** Props 값이 비어있거나, 최대 길이 초과 등 예외 상황에서 에러 메시지가 렌더링되거나 방어 동작이 작동하는지 검증.
- **접근성(A11y) 속성 검증:** 주요 클릭 타깃의 \`aria-label\` 유효성, 상태 전이에 따른 \`aria-expanded\`의 동적 변경 값 검증.
---
## 작업 지시 사항
1. Vitest 및 \`@vue/test-utils\` 조합으로 \`${testPath}\` 파일을 작성합니다.
2. 테스트를 실행하여 모든 케이스가 초록색(PASS)을 반환하는지 수동 또는 자동으로 검증하세요.
3. 테스트 코드는 가독성이 뛰어나야 하며, 복잡한 상태 관리가 사용된다면 Mocking을 적극적으로 활용하세요.
`;
// 4. Master Run Orchestrator Script (run-squad.js)
const masterRunScript = `const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const COMPONENT_NAME = "${componentName}";
const COMPONENT_PATH = "${componentPath}";
const TEST_PATH = "${testPath}";
console.log(\`🚀 [Squad Run] \${COMPONENT_NAME} 스쿼드 개발 자동화 파이프라인 가동!\\n\`);
function runCommand(command) {
try {
console.log(\`👉 실행 중: \${command}\`);
execSync(command, { stdio: 'inherit' });
return true;
} catch (error) {
console.error(\`❌ 에러 발생: \${command}\`);
return false;
}
}
// Phase 1: Markup Specialist 실행
console.log('----------------------------------------------------');
console.log('🎨 Phase 1: 마크업 & UI 구현 (Role 1)');
console.log('----------------------------------------------------');
const markupPrompt = \`squad/\${COMPONENT_NAME}/tasks/01_markup_agent.md 지시서에 명시된 목표와 프레임워크 규칙에 맞추어 \${COMPONENT_PATH} 파일을 먼저 구현해 주세요. 작업을 마친 후에는 코드만 마크다운 파일 등에 별도로 복사해 두는 것이 아니라, 실제 파일로 저장한 다음 상태를 알려주세요.\`;
runCommand(\`claude -p "\${markupPrompt}"\`);
// Phase 2: A11y Specialist 실행
console.log('\\n----------------------------------------------------');
console.log('♿ Phase 2: WCAG 2.1 AA 웹 접근성 개선 (Role 2)');
console.log('----------------------------------------------------');
const a11yPrompt = \`squad/\${COMPONENT_NAME}/tasks/02_a11y_agent.md 지시서를 기반으로, 앞서 생성된 \${COMPONENT_PATH} 파일의 코드를 검토하여 접근성을 강화해 주세요. Surgical Changes 원칙에 따라 핵심 마크업은 보존하고 ARIA 속성, 대체 텍스트, 키보드 핸들링만 지능적으로 주입 및 수정한 뒤 저장해 주세요.\`;
runCommand(\`claude -p "\${a11yPrompt}"\`);
// Phase 3: Testing Specialist 실행
console.log('\\n----------------------------------------------------');
console.log('🧪 Phase 3: QA 및 유닛 테스트 케이스 구축 (Role 3)');
console.log('----------------------------------------------------');
const testingPrompt = \`squad/\${COMPONENT_NAME}/tasks/03_test_agent.md 지시서에 맞춰 \${TEST_PATH} 유닛 테스트 파일을 생성하고 Vitest를 기반으로 작성해 주세요.\`;
runCommand(\`claude -p "\${testingPrompt}"\`);
// Phase 4: 최종 테스트 검증 및 코드 정리
console.log('\\n----------------------------------------------------');
console.log('🏁 Phase 4: 전체 스쿼드 통합 테스트 및 코드 정리');
console.log('----------------------------------------------------');
if (fs.existsSync(TEST_PATH)) {
console.log('🔬 Vitest 테스트 실행으로 품질 최종 점검...');
runCommand('npx vitest run ' + TEST_PATH);
} else {
console.log('⚠️ 테스트 파일이 생성되지 않았습니다.');
}
console.log('\\n✨ 모든 에이전트의 스쿼드 협업을 통해 초고품질 컴포넌트가 빌드되었습니다!');
console.log(\`📍 컴포넌트 위치: \${COMPONENT_PATH}\`);
console.log(\`📍 테스트 파일: \${TEST_PATH}\`);
`;
// 5. Squad Readme
const squadReadme = `# 🚀 AI 개발 스쿼드 (Squad) 오케스트레이션 가이드: ${componentName}
이 폴더는 고품질의 프론트엔드 컴포넌트 개발을 위해 **3가지 전문화된 AI 역할군**을 정의하고 작업을 위임하는 오케스트레이션 패키지입니다.
## 📁 파일 구조
- \`tasks/01_markup_agent.md\`: UI 레이아웃, 마크업 및 기본 구조 설계 지시서
- \`tasks/02_a11y_agent.md\`: WCAG 2.1 AA 및 키보드 접근성 개선 지시서
- \`tasks/03_test_agent.md\`: Vitest 기반 유닛 테스트 코드 작성 지시서
- \`run-squad.js\`: 로컬의 Claude Code를 활용해 세 단계를 연속/자동으로 가동시키는 마스터 오케스트레이터 스크립트
## 🛠 실행 방법
### 방법 A: 전체 자동 가동 (Claude Code CLI 이용)
프로젝트 루트에서 다음 노드 명령어를 실행하면 세 가지 에이전트 작업이 순차적으로 트리거되고 최종 유닛 테스트 검증까지 완벽히 오토파일럿으로 수행됩니다:
\`\`\`bash
node squad/${componentName}/run-squad.js
\`\`\`
### 방법 B: 수동 협업 가동 (각 전문 에이전트 수동 프롬프트)
만약 Claude Code 세션을 단계별로 수동 제어하고 싶다면, 아래 순서대로 각 지시서 파일을 Claude에게 먹여서 진행할 수도 있습니다:
1. **Step 1:** \`squad/${componentName}/tasks/01_markup_agent.md\` 지시 내용을 Claude에게 전달 후 UI 마크업 파일 생성.
2. **Step 2:** \`squad/${componentName}/tasks/02_a11y_agent.md\` 지시 내용을 전달하여 기존 UI 마크업 파일에 접근성 코드 주입.
3. **Step 3:** \`squad/${componentName}/tasks/03_test_agent.md\` 지시 내용을 전달하여 Vitest 테스트 세트 확보.
4. **Step 4:** 터미널에서 \`npx vitest run components/${componentName}.spec.ts\` 실행 및 검증 완료!
`;
// 파일들 쓰기
writeFile(path.join(taskDir, 'tasks/01_markup_agent.md'), markupTask);
writeFile(path.join(taskDir, 'tasks/02_a11y_agent.md'), a11yTask);
writeFile(path.join(taskDir, 'tasks/03_test_agent.md'), testingTask);
writeFile(path.join(taskDir, 'run-squad.js'), masterRunScript);
writeFile(path.join(taskDir, 'README.md'), squadReadme);
return taskDir;
}
// 메인 비화 및 입력 대기 제어
function run() {
const args = parseArgs();
if (args.name && args.spec) {
console.log(`🤖 전달받은 CLI 아규먼트로 스쿼드를 즉시 세팅합니다...`);
const path = generateSquadTasks(args.name, args.spec);
console.log(`\n🎉 에이전트 스쿼드가 성공적으로 정렬되었습니다!`);
console.log(`📍 위치: ${path}`);
console.log(`💬 'node ${path}/run-squad.js' 명령으로 전체 프로세스를 가동하거나 가이드 문서를 읽어보세요.`);
process.exit(0);
}
// 대화형 질문으로 수행
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log('👥 [Squad Orchestrator] 새로운 고품질 피처 개발을 위해 스쿼드를 조직합니다.');
rl.question('💬 개발할 컴포넌트/피처 이름을 작성해 주세요 (예: GameScoreBoard): ', (name) => {
if (!name.trim()) {
console.log('❌ 컴포넌트 이름은 필수입니다. 중단되었습니다.');
rl.close();
return;
}
rl.question('💬 구현할 세부 요건/기능 스펙을 적어주세요:\n> ', (spec) => {
if (!spec.trim()) {
spec = '기본적인 반응형 디자인과 상태 데이터 흐름을 갖춘 고품질 컴포넌트 설계';
}
const path = generateSquadTasks(name.trim(), spec.trim());
console.log(`\n🎉 에이전트 스쿼드가 성공적으로 정렬되었습니다!`);
console.log(`📍 위치: ${path}`);
console.log(`💬 'node ${path}/run-squad.js' 명령으로 전체 프로세스를 가동하거나 가이드 문서를 읽어보세요.`);
rl.close();
});
});
}
run();