📝 docs: 여러 문서 및 파일 삭제
This commit is contained in:
59
.claude/project/dreaming-context.md
Normal file
59
.claude/project/dreaming-context.md
Normal 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)
|
||||
프로젝트 구동 및 테스트 검증을 위해 사용 가능한 명령어 리스트입니다.
|
||||
- 스크립트 없음
|
||||
107
.claude/rules/automation-guide.md
Normal file
107
.claude/rules/automation-guide.md
Normal 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초 만에 최신화해 줍니다.
|
||||
62
.claude/rules/claude-workflow.md
Normal file
62
.claude/rules/claude-workflow.md
Normal 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를 확인할 수 있도록 단계별로 진행합니다.
|
||||
|
||||
## 질문이 필요한 상황
|
||||
|
||||
다음의 경우 반드시 사용자에게 확인을 요청합니다.
|
||||
|
||||
- 요구사항의 일부가 불명확할 때
|
||||
- 여러 구현 방식이 있고 각각 장단점이 뚜렷할 때
|
||||
- 공통 지침과 프로젝트 지침이 충돌할 때
|
||||
- 파괴적 작업(파일 삭제, 데이터 마이그레이션, 스키마 변경 등)이 필요할 때
|
||||
- 외부 서비스 호출이나 결제 관련 작업일 때
|
||||
50
.claude/rules/coding-conventions.md
Normal file
50
.claude/rules/coding-conventions.md
Normal 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는 각 그룹 내에서 별도 블록으로 분리
|
||||
|
||||
그룹 사이에는 빈 줄을 한 줄 둡니다.
|
||||
83
.claude/rules/commit-pr.md
Normal file
83
.claude/rules/commit-pr.md
Normal 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 실패가 있어도 괜찮습니다.
|
||||
47
.claude/rules/framework-rules.md
Normal file
47
.claude/rules/framework-rules.md
Normal 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
96
.claude/scripts/init-project.sh
Executable 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
209
.claude/scripts/install.sh
Executable 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
139
.claude/scripts/link-skills.sh
Executable 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
36
.claude/scripts/update.sh
Executable 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'"
|
||||
BIN
.claude/skills/.DS_Store
vendored
BIN
.claude/skills/.DS_Store
vendored
Binary file not shown.
150
.claude/skills/dev-api-state/SKILL.md
Normal file
150
.claude/skills/dev-api-state/SKILL.md
Normal 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 → 스토어 → 컴포넌트 순]
|
||||
```
|
||||
125
.claude/skills/dev-component/SKILL.md
Normal file
125
.claude/skills/dev-component/SKILL.md
Normal 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[]
|
||||
- ...
|
||||
|
||||
### 스켈레톤 코드
|
||||
[각 파일 코드]
|
||||
```
|
||||
128
.claude/skills/dev-docs/SKILL.md
Normal file
128
.claude/skills/dev-docs/SKILL.md
Normal 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 차이, 캐시 주의 등]
|
||||
```
|
||||
149
.claude/skills/dev-storybook/SKILL.md
Normal file
149
.claude/skills/dev-storybook/SKILL.md
Normal 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
|
||||
\`\`\`
|
||||
```
|
||||
160
.claude/skills/dev-unit-test/SKILL.md
Normal file
160
.claude/skills/dev-unit-test/SKILL.md
Normal 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 # 커버리지 확인
|
||||
\`\`\`
|
||||
```
|
||||
21
.claude/skills/dreaming/SKILL.md
Normal file
21
.claude/skills/dreaming/SKILL.md
Normal 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. 분석 완료 보고서와 요약 내용을 개발자에게 깔끔하게 알려드립니다.
|
||||
369
.claude/skills/dreaming/scripts/dreaming.js
Executable file
369
.claude/skills/dreaming/scripts/dreaming.js
Executable 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();
|
||||
125
.claude/skills/markup-base/SKILL.md
Normal file
125
.claude/skills/markup-base/SKILL.md
Normal 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 텍스트 확정 필요
|
||||
- [ ] 컬러 토큰 디자인 시스템 확인 필요
|
||||
```
|
||||
107
.claude/skills/markup-figma/SKILL.md
Normal file
107
.claude/skills/markup-figma/SKILL.md
Normal 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, 링크 등 확정 필요 항목]
|
||||
```
|
||||
138
.claude/skills/markup-promotion/SKILL.md
Normal file
138
.claude/skills/markup-promotion/SKILL.md
Normal 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 값 확정
|
||||
```
|
||||
141
.claude/skills/security-review/SKILL.md
Normal file
141
.claude/skills/security-review/SKILL.md
Normal 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 미사용 등
|
||||
```
|
||||
26
.claude/skills/squad-orchestration/SKILL.md
Normal file
26
.claude/skills/squad-orchestration/SKILL.md
Normal 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. 에이전트 스쿼드 배치 상황과 실행 방법을 개발자에게 친근하게 알려드립니다.
|
||||
275
.claude/skills/squad-orchestration/scripts/squad-orchestrator.js
Executable file
275
.claude/skills/squad-orchestration/scripts/squad-orchestrator.js
Executable 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();
|
||||
130
.claude/skills/verify-a11y/SKILL.md
Normal file
130
.claude/skills/verify-a11y/SKILL.md
Normal 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 레이블 적용됨, 색상 대비 충족 등
|
||||
```
|
||||
141
.claude/skills/verify-perf/SKILL.md
Normal file
141
.claude/skills/verify-perf/SKILL.md
Normal 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% 감소)
|
||||
```
|
||||
90
.claude/skills/verify-requirement/SKILL.md
Normal file
90
.claude/skills/verify-requirement/SKILL.md
Normal 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건)
|
||||
- [화면명] 개선 사항
|
||||
|
||||
### ✅ 일치 항목
|
||||
- [목록]
|
||||
```
|
||||
143
.claude/skills/verify-seo-geo/SKILL.md
Normal file
143
.claude/skills/verify-seo-geo/SKILL.md
Normal 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 개선 제안
|
||||
[콘텐츠 구조 개선 방향]
|
||||
```
|
||||
109
.claude/skills/work-code-reviewer/SKILL.md
Normal file
109
.claude/skills/work-code-reviewer/SKILL.md
Normal 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개]
|
||||
|
||||
### 개선 후 기대 효과
|
||||
[한 줄 요약]
|
||||
```
|
||||
111
.claude/skills/work-mr-reviewer/SKILL.md
Normal file
111
.claude/skills/work-mr-reviewer/SKILL.md
Normal 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
116
.omc/project-memory.json
Normal 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": []
|
||||
}
|
||||
8
.omc/sessions/082bc0a1-f53c-41d5-8f6c-90b000014186.json
Normal file
8
.omc/sessions/082bc0a1-f53c-41d5-8f6c-90b000014186.json
Normal 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": []
|
||||
}
|
||||
8
.omc/sessions/2f3f79c0-ebbd-4d15-a4d8-d9b3a59f53e5.json
Normal file
8
.omc/sessions/2f3f79c0-ebbd-4d15-a4d8-d9b3a59f53e5.json
Normal 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": []
|
||||
}
|
||||
8
.omc/sessions/7943f1a4-77eb-4ed3-8390-0307b53dde90.json
Normal file
8
.omc/sessions/7943f1a4-77eb-4ed3-8390-0307b53dde90.json
Normal 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": []
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
41
CLAUDE.md
41
CLAUDE.md
@@ -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
|
||||
|
||||
> 불필요한 코드 작성과 섣부른 구현을 방지하기 위한 지침. 단순한 작업은 판단하여 적용.
|
||||
|
||||
- **코딩 전 먼저 생각**: 가정하지 말고, 불확실하면 질문. 여러 해석이 가능하면 제시.
|
||||
- **단순함 우선**: 요청된 것만 구현. 단일 사용 코드에 추상화 금지. 추측성 기능 추가 금지.
|
||||
- **최소 변경**: 요청에 직접 연결되지 않는 코드는 건드리지 않음. 인접 코드 "개선" 금지.
|
||||
- **성공 기준 정의**: 복잡한 작업은 검증 가능한 단계로 분리하여 진행.
|
||||
|
||||
@@ -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 자동 검증 |
|
||||
@@ -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 <script setup lang="ts">, 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<T>()에 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 · /verify-component-review · /plan-analyzer · /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>
|
||||
@@ -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 <script setup lang="ts">, 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<T>()에 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 · /verify-component-review · /plan-analyzer · /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>
|
||||
@@ -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><script setup lang="ts"> 기본</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> 포맷팅 & 네이밍</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"><script setup lang="ts"> 필수, 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<T>() 제네릭으로 타입 명시</span></li>
|
||||
<li><span class="kv-key">Emits</span><span class="kv-val">defineEmits<{ ... }>() 제네릭으로 선언</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>
|
||||
@@ -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>
|
||||
575
session-report-20260516-1041.html
Normal file
575
session-report-20260516-1041.html
Normal file
File diff suppressed because one or more lines are too long
1978
session-report-20260516-1137.html
Normal file
1978
session-report-20260516-1137.html
Normal file
File diff suppressed because it is too large
Load Diff
21
skills/dreaming/SKILL.md
Normal file
21
skills/dreaming/SKILL.md
Normal 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. 분석 완료 보고서와 요약 내용을 개발자에게 깔끔하게 알려드립니다.
|
||||
369
skills/dreaming/scripts/dreaming.js
Executable file
369
skills/dreaming/scripts/dreaming.js
Executable 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();
|
||||
26
skills/squad-orchestration/SKILL.md
Normal file
26
skills/squad-orchestration/SKILL.md
Normal 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. 에이전트 스쿼드 배치 상황과 실행 방법을 개발자에게 친근하게 알려드립니다.
|
||||
107
skills/squad-orchestration/docs/automation-guide.md
Normal file
107
skills/squad-orchestration/docs/automation-guide.md
Normal 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초 만에 최신화해 줍니다.
|
||||
275
skills/squad-orchestration/scripts/squad-orchestrator.js
Executable file
275
skills/squad-orchestration/scripts/squad-orchestrator.js
Executable 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();
|
||||
Reference in New Issue
Block a user