create-claude-pipeline 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +359 -0
- package/package.json +32 -0
- package/template/.claude/agents/be-developer.md +218 -0
- package/template/.claude/agents/designer.md +192 -0
- package/template/.claude/agents/fe-developer.md +175 -0
- package/template/.claude/agents/infra-developer.md +270 -0
- package/template/.claude/agents/planner.md +126 -0
- package/template/.claude/agents/pm.md +130 -0
- package/template/.claude/agents/qa-engineer.md +270 -0
- package/template/.claude/agents/security-reviewer.md +281 -0
- package/template/.claude/settings.json +5 -0
- package/template/.claude/skills/analyze-requirements/SKILL.md +166 -0
- package/template/.claude/skills/api-integration/SKILL.md +354 -0
- package/template/.claude/skills/assemble-context/SKILL.md +192 -0
- package/template/.claude/skills/db-migration/SKILL.md +228 -0
- package/template/.claude/skills/explore-be-codebase/SKILL.md +260 -0
- package/template/.claude/skills/explore-codebase/SKILL.md +190 -0
- package/template/.claude/skills/explore-design-system/SKILL.md +150 -0
- package/template/.claude/skills/explore-fe-codebase/SKILL.md +209 -0
- package/template/.claude/skills/explore-implementation/SKILL.md +147 -0
- package/template/.claude/skills/explore-infra/SKILL.md +242 -0
- package/template/.claude/skills/implement-api/SKILL.md +477 -0
- package/template/.claude/skills/implement-components/SKILL.md +217 -0
- package/template/.claude/skills/review-auth/SKILL.md +175 -0
- package/template/.claude/skills/scan-vulnerabilities/SKILL.md +200 -0
- package/template/.claude/skills/write-cicd/SKILL.md +293 -0
- package/template/.claude/skills/write-design-spec/SKILL.md +363 -0
- package/template/.claude/skills/write-dockerfile/SKILL.md +269 -0
- package/template/.claude/skills/write-plan-doc/SKILL.md +164 -0
- package/template/.claude/skills/write-plan-doc/assets/plan_template.html +251 -0
- package/template/.claude/skills/write-qa-report/SKILL.md +151 -0
- package/template/.claude/skills/write-security-report/SKILL.md +185 -0
- package/template/.claude/skills/write-test-cases/SKILL.md +234 -0
- package/template/.claude-pipeline/dashboard/.env.example +1 -0
- package/template/.claude-pipeline/dashboard/.eslintrc.json +3 -0
- package/template/.claude-pipeline/dashboard/README.md +36 -0
- package/template/.claude-pipeline/dashboard/next.config.mjs +6 -0
- package/template/.claude-pipeline/dashboard/package-lock.json +8148 -0
- package/template/.claude-pipeline/dashboard/package.json +36 -0
- package/template/.claude-pipeline/dashboard/postcss.config.mjs +8 -0
- package/template/.claude-pipeline/dashboard/server.ts +24 -0
- package/template/.claude-pipeline/dashboard/src/app/api/pipelines/[id]/checkpoint/route.ts +23 -0
- package/template/.claude-pipeline/dashboard/src/app/api/pipelines/[id]/outputs/[...filepath]/route.ts +18 -0
- package/template/.claude-pipeline/dashboard/src/app/api/pipelines/[id]/route.ts +10 -0
- package/template/.claude-pipeline/dashboard/src/app/api/pipelines/route.ts +64 -0
- package/template/.claude-pipeline/dashboard/src/app/favicon.ico +0 -0
- package/template/.claude-pipeline/dashboard/src/app/fonts/GeistMonoVF.woff +0 -0
- package/template/.claude-pipeline/dashboard/src/app/fonts/GeistVF.woff +0 -0
- package/template/.claude-pipeline/dashboard/src/app/globals.css +52 -0
- package/template/.claude-pipeline/dashboard/src/app/layout.tsx +33 -0
- package/template/.claude-pipeline/dashboard/src/app/page.tsx +49 -0
- package/template/.claude-pipeline/dashboard/src/app/pipeline/[id]/page.tsx +84 -0
- package/template/.claude-pipeline/dashboard/src/components/agent-card.tsx +40 -0
- package/template/.claude-pipeline/dashboard/src/components/agent-logs.tsx +65 -0
- package/template/.claude-pipeline/dashboard/src/components/artifact-viewer.tsx +130 -0
- package/template/.claude-pipeline/dashboard/src/components/checkpoint-banner.tsx +59 -0
- package/template/.claude-pipeline/dashboard/src/components/new-pipeline-modal.tsx +63 -0
- package/template/.claude-pipeline/dashboard/src/components/output-list.tsx +57 -0
- package/template/.claude-pipeline/dashboard/src/components/phase-dots.tsx +37 -0
- package/template/.claude-pipeline/dashboard/src/components/pipeline-card.tsx +53 -0
- package/template/.claude-pipeline/dashboard/src/components/resizable-panels.tsx +91 -0
- package/template/.claude-pipeline/dashboard/src/hooks/use-pipeline-detail.ts +65 -0
- package/template/.claude-pipeline/dashboard/src/hooks/use-pipelines.ts +60 -0
- package/template/.claude-pipeline/dashboard/src/hooks/use-websocket.ts +58 -0
- package/template/.claude-pipeline/dashboard/src/lib/agents.ts +30 -0
- package/template/.claude-pipeline/dashboard/src/lib/checkpoint.ts +37 -0
- package/template/.claude-pipeline/dashboard/src/lib/pipelines.ts +91 -0
- package/template/.claude-pipeline/dashboard/src/lib/watcher.ts +90 -0
- package/template/.claude-pipeline/dashboard/src/lib/ws-server.ts +123 -0
- package/template/.claude-pipeline/dashboard/src/types/pipeline.ts +61 -0
- package/template/.claude-pipeline/dashboard/tailwind.config.ts +31 -0
- package/template/.claude-pipeline/dashboard/tsconfig.json +26 -0
- package/template/CLAUDE.md +301 -0
- package/template/references/context-structure.md +34 -0
- package/template/references/pm-context-assembly.md +34 -0
- package/template/references/task-context-template.md +65 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: write-cicd
|
|
3
|
+
description: "인프라 엔지니어가 GitHub Actions CI/CD 파이프라인을 작성할 때 참조하는 skill. CI(PR 검증)와 CD(자동 배포)를 분리하여 작성하며, 캐싱, 헬스체크, 롤백, 시크릿 관리 패턴을 제공한다. Infra Agent가 CI/CD 파이프라인을 새로 만들거나, 기존 워크플로우를 수정하거나, 배포 자동화를 설정할 때 반드시 사용한다. 'CI/CD 작성', 'GitHub Actions 설정', '배포 파이프라인', '자동 배포', 'PR 검증 워크플로우', 'Docker 배포', 'GHCR 푸시', '롤백 설정', 'ci.yml 작성', 'cd.yml 작성' 등의 상황에서 트리거된다."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Write CI/CD
|
|
7
|
+
|
|
8
|
+
인프라 엔지니어가 GitHub Actions CI/CD 파이프라인을 작성할 때 따르는 패턴이다.
|
|
9
|
+
|
|
10
|
+
CI와 CD를 별도 워크플로우 파일로 분리한다. CI는 "코드가 안전한가"를 검증하고, CD는 "검증된 코드를 배포"한다. 이 분리가 중요한 이유는, CI는 모든 PR에서 빈번하게 실행되므로 가볍고 빨라야 하고, CD는 main 머지 후에만 실행되므로 Docker 빌드 같은 무거운 작업을 포함할 수 있기 때문이다.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 파일 구조
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
.github/
|
|
18
|
+
└── workflows/
|
|
19
|
+
├── ci.yml # PR 검증 — lint, typecheck, test, build
|
|
20
|
+
└── cd.yml # 자동 배포 — Docker build, push, deploy, rollback
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
기존 프로젝트에 이미 워크플로우 파일이 있으면 기존 구조를 따른다.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## CI 워크플로우 (ci.yml)
|
|
28
|
+
|
|
29
|
+
PR이 생성되거나 업데이트될 때 실행된다. 모든 검증이 통과해야 머지할 수 있다.
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
name: CI
|
|
33
|
+
|
|
34
|
+
on:
|
|
35
|
+
pull_request:
|
|
36
|
+
branches: [main]
|
|
37
|
+
|
|
38
|
+
concurrency:
|
|
39
|
+
group: ci-${{ github.ref }}
|
|
40
|
+
cancel-in-progress: true
|
|
41
|
+
|
|
42
|
+
jobs:
|
|
43
|
+
ci:
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
steps:
|
|
46
|
+
# 1. 코드 체크아웃
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
|
|
49
|
+
# 2. Node.js 설정 + 의존성 캐싱
|
|
50
|
+
- uses: actions/setup-node@v4
|
|
51
|
+
with:
|
|
52
|
+
node-version: 20
|
|
53
|
+
cache: 'npm'
|
|
54
|
+
|
|
55
|
+
# 3. 의존성 설치
|
|
56
|
+
- run: npm ci
|
|
57
|
+
|
|
58
|
+
# 4. 린트 검사
|
|
59
|
+
- run: npm run lint
|
|
60
|
+
|
|
61
|
+
# 5. 타입 체크
|
|
62
|
+
- run: npm run typecheck
|
|
63
|
+
|
|
64
|
+
# 6. 테스트 실행
|
|
65
|
+
- run: npm run test
|
|
66
|
+
|
|
67
|
+
# 7. 빌드 확인
|
|
68
|
+
- run: npm run build
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### CI 설계 원칙
|
|
72
|
+
|
|
73
|
+
**concurrency 설정**: 같은 PR에서 새 커밋이 푸시되면 이전 CI를 취소한다. 불필요한 리소스 낭비를 방지한다.
|
|
74
|
+
|
|
75
|
+
**`npm ci` vs `npm install`**: `npm ci`는 `package-lock.json`을 그대로 사용하므로, 팀원 간 의존성 차이 없이 동일한 환경을 재현한다. CI에서는 항상 `npm ci`를 사용한다.
|
|
76
|
+
|
|
77
|
+
**캐싱**: `actions/setup-node`의 `cache: 'npm'`이 `~/.npm` 디렉토리를 자동 캐싱한다. 매번 모든 패키지를 다운로드하지 않아 CI 시간이 단축된다.
|
|
78
|
+
|
|
79
|
+
**단계 순서**: 빠르게 실패하는 검사를 먼저 실행한다. lint(초 단위) → typecheck(초 단위) → test(분 단위) → build(분 단위) 순서로, 빠른 검사에서 실패하면 느린 검사를 실행하지 않는다.
|
|
80
|
+
|
|
81
|
+
**Branch protection**: CI를 GitHub의 Branch Protection Rule에 등록하여, CI 통과 없이는 머지할 수 없게 한다. 이것은 수동 설정이므로 워크플로우 파일로는 할 수 없고, 레포지토리 설정에서 직접 해야 한다.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## CD 워크플로우 (cd.yml)
|
|
86
|
+
|
|
87
|
+
main 브랜치에 머지되면 자동으로 배포를 진행한다. 스테이징은 자동, 프로덕션은 수동 승인 후 배포된다.
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
name: CD
|
|
91
|
+
|
|
92
|
+
on:
|
|
93
|
+
push:
|
|
94
|
+
branches: [main]
|
|
95
|
+
|
|
96
|
+
env:
|
|
97
|
+
REGISTRY: ghcr.io
|
|
98
|
+
IMAGE_NAME: ${{ github.repository }}
|
|
99
|
+
|
|
100
|
+
jobs:
|
|
101
|
+
# ─── Docker 이미지 빌드 & 푸시 ───
|
|
102
|
+
build-and-push:
|
|
103
|
+
runs-on: ubuntu-latest
|
|
104
|
+
permissions:
|
|
105
|
+
contents: read
|
|
106
|
+
packages: write
|
|
107
|
+
outputs:
|
|
108
|
+
image-tag: ${{ steps.meta.outputs.tags }}
|
|
109
|
+
image-digest: ${{ steps.build.outputs.digest }}
|
|
110
|
+
steps:
|
|
111
|
+
- uses: actions/checkout@v4
|
|
112
|
+
|
|
113
|
+
- uses: docker/setup-buildx-action@v3
|
|
114
|
+
|
|
115
|
+
- uses: docker/login-action@v3
|
|
116
|
+
with:
|
|
117
|
+
registry: ${{ env.REGISTRY }}
|
|
118
|
+
username: ${{ github.actor }}
|
|
119
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
120
|
+
|
|
121
|
+
- id: meta
|
|
122
|
+
uses: docker/metadata-action@v5
|
|
123
|
+
with:
|
|
124
|
+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
125
|
+
tags: |
|
|
126
|
+
type=sha,prefix=
|
|
127
|
+
type=raw,value=latest
|
|
128
|
+
|
|
129
|
+
- id: build
|
|
130
|
+
uses: docker/build-push-action@v5
|
|
131
|
+
with:
|
|
132
|
+
context: .
|
|
133
|
+
push: true
|
|
134
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
135
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
136
|
+
cache-from: type=gha
|
|
137
|
+
cache-to: type=gha,mode=max
|
|
138
|
+
|
|
139
|
+
# ─── 스테이징 배포 (자동) ───
|
|
140
|
+
deploy-staging:
|
|
141
|
+
needs: build-and-push
|
|
142
|
+
runs-on: ubuntu-latest
|
|
143
|
+
environment: staging
|
|
144
|
+
steps:
|
|
145
|
+
- uses: actions/checkout@v4
|
|
146
|
+
|
|
147
|
+
- name: Deploy to staging
|
|
148
|
+
run: |
|
|
149
|
+
# 배포 플랫폼에 맞게 수정한다
|
|
150
|
+
# 예: Docker Compose, Kubernetes, AWS ECS, Railway 등
|
|
151
|
+
echo "Deploying ${{ needs.build-and-push.outputs.image-tag }} to staging"
|
|
152
|
+
# ssh ${{ secrets.STAGING_HOST }} "docker pull ... && docker compose up -d"
|
|
153
|
+
env:
|
|
154
|
+
DEPLOY_HOST: ${{ secrets.STAGING_HOST }}
|
|
155
|
+
DEPLOY_KEY: ${{ secrets.STAGING_SSH_KEY }}
|
|
156
|
+
|
|
157
|
+
- name: Health check (staging)
|
|
158
|
+
run: |
|
|
159
|
+
for i in $(seq 1 30); do
|
|
160
|
+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${{ secrets.STAGING_URL }}/health" || echo "000")
|
|
161
|
+
if [ "$STATUS" = "200" ]; then
|
|
162
|
+
echo "Staging health check passed"
|
|
163
|
+
exit 0
|
|
164
|
+
fi
|
|
165
|
+
echo "Attempt $i/30: status=$STATUS, retrying in 10s..."
|
|
166
|
+
sleep 10
|
|
167
|
+
done
|
|
168
|
+
echo "Staging health check failed after 30 attempts"
|
|
169
|
+
exit 1
|
|
170
|
+
|
|
171
|
+
# ─── 프로덕션 배포 (수동 승인) ───
|
|
172
|
+
deploy-production:
|
|
173
|
+
needs: [build-and-push, deploy-staging]
|
|
174
|
+
runs-on: ubuntu-latest
|
|
175
|
+
environment: production # GitHub Environment로 수동 승인 게이트
|
|
176
|
+
steps:
|
|
177
|
+
- uses: actions/checkout@v4
|
|
178
|
+
|
|
179
|
+
- name: Save current version for rollback
|
|
180
|
+
id: current
|
|
181
|
+
run: |
|
|
182
|
+
# 현재 배포된 버전을 기록해둔다 (롤백에 필요)
|
|
183
|
+
CURRENT_TAG=$(curl -s "${{ secrets.PRODUCTION_URL }}/health" | jq -r '.version // "unknown"')
|
|
184
|
+
echo "current-tag=$CURRENT_TAG" >> "$GITHUB_OUTPUT"
|
|
185
|
+
echo "Current production version: $CURRENT_TAG"
|
|
186
|
+
|
|
187
|
+
- name: Deploy to production
|
|
188
|
+
id: deploy
|
|
189
|
+
run: |
|
|
190
|
+
echo "Deploying ${{ needs.build-and-push.outputs.image-tag }} to production"
|
|
191
|
+
# ssh ${{ secrets.PRODUCTION_HOST }} "docker pull ... && docker compose up -d"
|
|
192
|
+
env:
|
|
193
|
+
DEPLOY_HOST: ${{ secrets.PRODUCTION_HOST }}
|
|
194
|
+
DEPLOY_KEY: ${{ secrets.PRODUCTION_SSH_KEY }}
|
|
195
|
+
|
|
196
|
+
- name: Health check (production)
|
|
197
|
+
id: healthcheck
|
|
198
|
+
run: |
|
|
199
|
+
for i in $(seq 1 30); do
|
|
200
|
+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${{ secrets.PRODUCTION_URL }}/health" || echo "000")
|
|
201
|
+
if [ "$STATUS" = "200" ]; then
|
|
202
|
+
echo "Production health check passed"
|
|
203
|
+
exit 0
|
|
204
|
+
fi
|
|
205
|
+
echo "Attempt $i/30: status=$STATUS, retrying in 10s..."
|
|
206
|
+
sleep 10
|
|
207
|
+
done
|
|
208
|
+
echo "Production health check failed"
|
|
209
|
+
exit 1
|
|
210
|
+
|
|
211
|
+
- name: Rollback on failure
|
|
212
|
+
if: failure() && steps.deploy.outcome == 'success'
|
|
213
|
+
run: |
|
|
214
|
+
echo "Rolling back to ${{ steps.current.outputs.current-tag }}"
|
|
215
|
+
# ssh ${{ secrets.PRODUCTION_HOST }} "docker pull <previous-image> && docker compose up -d"
|
|
216
|
+
# 롤백 후 헬스체크
|
|
217
|
+
sleep 15
|
|
218
|
+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${{ secrets.PRODUCTION_URL }}/health" || echo "000")
|
|
219
|
+
if [ "$STATUS" = "200" ]; then
|
|
220
|
+
echo "Rollback successful"
|
|
221
|
+
else
|
|
222
|
+
echo "CRITICAL: Rollback also failed. Manual intervention required."
|
|
223
|
+
exit 1
|
|
224
|
+
fi
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### CD 설계 원칙
|
|
228
|
+
|
|
229
|
+
**이미지 태깅**: commit SHA를 태그로 사용한다. `latest`는 편의를 위해 추가하지만, 배포에는 항상 SHA 태그를 사용해야 어떤 커밋이 배포되었는지 추적할 수 있다.
|
|
230
|
+
|
|
231
|
+
**빌드 캐시**: `cache-from/cache-to: type=gha`로 GitHub Actions의 캐시를 활용한다. Docker 레이어 캐시를 통해 변경이 없는 레이어는 다시 빌드하지 않아 빌드 시간이 크게 단축된다.
|
|
232
|
+
|
|
233
|
+
**스테이징 → 프로덕션 순서**: 스테이징에서 먼저 검증한 후 프로덕션에 배포한다. `environment: production` 설정으로 GitHub에서 수동 승인을 요구하므로, 스테이징 검증 결과를 확인한 후 승인할 수 있다.
|
|
234
|
+
|
|
235
|
+
**헬스체크**: 배포 후 `/health` 엔드포인트를 최대 5분(10초 × 30회) 동안 폴링한다. 서비스가 기동하는 데 시간이 걸리기 때문에 즉시 확인하면 실패할 수 있다.
|
|
236
|
+
|
|
237
|
+
**자동 롤백**: 프로덕션 배포 후 헬스체크가 실패하면 이전 버전으로 자동 롤백한다. 배포 전에 현재 버전을 기록해두었다가 롤백에 사용한다. 롤백마저 실패하면 수동 개입이 필요함을 명시한다.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 시크릿 관리
|
|
242
|
+
|
|
243
|
+
모든 민감 정보는 GitHub Secrets에 저장하고, 코드에 직접 작성하지 않는다.
|
|
244
|
+
|
|
245
|
+
### 필요한 시크릿 목록
|
|
246
|
+
|
|
247
|
+
| 시크릿 이름 | 용도 | 설정 위치 |
|
|
248
|
+
|------------|------|----------|
|
|
249
|
+
| `GITHUB_TOKEN` | GHCR 로그인 (자동 제공) | 자동 |
|
|
250
|
+
| `STAGING_HOST` | 스테이징 서버 주소 | Repository Secrets |
|
|
251
|
+
| `STAGING_SSH_KEY` | 스테이징 SSH 키 | Repository Secrets |
|
|
252
|
+
| `STAGING_URL` | 스테이징 URL (헬스체크용) | Repository Secrets |
|
|
253
|
+
| `PRODUCTION_HOST` | 프로덕션 서버 주소 | Environment Secrets (production) |
|
|
254
|
+
| `PRODUCTION_SSH_KEY` | 프로덕션 SSH 키 | Environment Secrets (production) |
|
|
255
|
+
| `PRODUCTION_URL` | 프로덕션 URL (헬스체크용) | Environment Secrets (production) |
|
|
256
|
+
|
|
257
|
+
**Repository Secrets vs Environment Secrets**: 프로덕션 관련 시크릿은 Environment Secrets에 저장한다. 이렇게 하면 `environment: production` 승인을 통과해야만 해당 시크릿에 접근할 수 있어서, 일반 워크플로우에서 프로덕션 크레덴셜이 노출되는 것을 방지한다.
|
|
258
|
+
|
|
259
|
+
### 시크릿 사용 규칙
|
|
260
|
+
|
|
261
|
+
- 워크플로우에서 `${{ secrets.NAME }}`으로 참조한다
|
|
262
|
+
- 시크릿 값은 로그에 자동 마스킹된다
|
|
263
|
+
- 새로운 시크릿이 필요하면 README나 주석에 어떤 시크릿을 설정해야 하는지 기록한다
|
|
264
|
+
- `.env` 파일은 `.gitignore`에 포함되어 있는지 확인한다
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 배포 플랫폼별 커스터마이징
|
|
269
|
+
|
|
270
|
+
CD 워크플로우의 배포 스텝은 플랫폼에 따라 달라진다. 위 템플릿의 배포 커맨드를 프로젝트에 맞게 교체한다.
|
|
271
|
+
|
|
272
|
+
| 플랫폼 | 배포 방식 |
|
|
273
|
+
|--------|----------|
|
|
274
|
+
| Docker Compose (VPS) | `ssh + docker compose pull && docker compose up -d` |
|
|
275
|
+
| Kubernetes | `kubectl set image deployment/app app=<image>` |
|
|
276
|
+
| AWS ECS | `aws ecs update-service --force-new-deployment` |
|
|
277
|
+
| Railway / Fly.io | 플랫폼 CLI 사용 (`railway up`, `fly deploy`) |
|
|
278
|
+
| Vercel / Netlify | 별도 CD 불필요 (Git 연동으로 자동 배포) |
|
|
279
|
+
|
|
280
|
+
Vercel/Netlify 같은 PaaS를 사용하는 프로젝트는 CD 워크플로우가 불필요할 수 있다. 이 경우 CI 워크플로우만 작성한다.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 구현 순서 체크리스트
|
|
285
|
+
|
|
286
|
+
1. [ ] 기존 `.github/workflows/` 확인 (이미 있으면 기존 패턴 유지)
|
|
287
|
+
2. [ ] `ci.yml` 작성 — PR 트리거, lint/typecheck/test/build
|
|
288
|
+
3. [ ] `cd.yml` 작성 — main 머지 트리거, Docker build/push/deploy
|
|
289
|
+
4. [ ] Dockerfile 확인 (없으면 생성)
|
|
290
|
+
5. [ ] `/health` 엔드포인트 확인 (없으면 BE에게 요청)
|
|
291
|
+
6. [ ] GitHub Secrets 목록 정리 및 README에 기록
|
|
292
|
+
7. [ ] GitHub Environment 설정 (staging, production) 안내
|
|
293
|
+
8. [ ] Branch Protection Rule 설정 안내 (CI 통과 필수)
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: write-design-spec
|
|
3
|
+
description: "디자이너 Agent가 FE Agent에게 전달할 디자인 명세(context/02_design_spec.md)를 작성할 때 포맷 기준으로 사용하는 skill. 디자인 토큰, 공통 컴포넌트(Props+상태), 화면별 레이아웃(ASCII+반응형), 인터랙션, 접근성 가이드 5개 섹션을 FE가 바로 구현할 수 있는 수준으로 작성한다. '디자인 명세 작성', '02_design_spec 생성', '화면 설계', '컴포넌트 정의', 'FE에게 전달할 디자인' 등의 상황에서 반드시 사용한다."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Write Design Spec
|
|
7
|
+
|
|
8
|
+
디자이너 Agent가 `context/02_design_spec.md`를 작성할 때 따르는 포맷 기준이다.
|
|
9
|
+
|
|
10
|
+
이 명세의 독자는 FE 개발자다. FE가 이 문서 하나만 읽고 디자인 의도대로 구현할 수 있어야 한다. 그래서 모든 값은 구체적인 숫자로, 모든 상태는 빠짐없이, 모든 레이아웃은 시각적으로 표현한다.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Step 1: 입력 확인
|
|
15
|
+
|
|
16
|
+
아래 파일을 읽는다:
|
|
17
|
+
- `context/00_requirements.md` — 프로젝트 현황, 기술 스택
|
|
18
|
+
- `context/01_plan.md` — 화면 목록(S-xx), 기능 명세(F-xx)
|
|
19
|
+
|
|
20
|
+
`01_plan.md`의 화면 목록이 이 명세의 범위를 결정한다. 기획안에 없는 화면을 임의로 추가하지 않는다.
|
|
21
|
+
|
|
22
|
+
`explore-design-system` skill을 통해 기존 디자인 시스템 현황을 파악했다면 그 결과도 참고한다. 기존 토큰/컴포넌트가 있으면 재사용하고, 없는 것만 새로 정의한다.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Step 2: 5개 섹션으로 명세 작성
|
|
27
|
+
|
|
28
|
+
### 섹션 1: 디자인 토큰
|
|
29
|
+
|
|
30
|
+
서비스 전체에서 일관되게 사용할 기본 값을 정의한다. 기존 디자인 시스템이 있으면 그것을 기반으로 확장하고, 없으면 새로 정의한다.
|
|
31
|
+
|
|
32
|
+
```markdown
|
|
33
|
+
## 1. 디자인 토큰
|
|
34
|
+
|
|
35
|
+
### 색상 (Color)
|
|
36
|
+
|
|
37
|
+
| 토큰명 | 값 | 용도 |
|
|
38
|
+
|--------|------|------|
|
|
39
|
+
| --color-primary | #3182ce | 주요 액션, 링크 |
|
|
40
|
+
| --color-primary-hover | #2b6cb0 | Primary hover 상태 |
|
|
41
|
+
| --color-secondary | #718096 | 보조 요소 |
|
|
42
|
+
| --color-bg | #ffffff | 페이지 배경 |
|
|
43
|
+
| --color-surface | #f7fafc | 카드/섹션 배경 |
|
|
44
|
+
| --color-border | #e2e8f0 | 테두리 |
|
|
45
|
+
| --color-error | #e53e3e | 에러 메시지, 유효성 |
|
|
46
|
+
| --color-success | #38a169 | 성공 상태 |
|
|
47
|
+
| --color-warning | #dd6b20 | 경고 |
|
|
48
|
+
| --color-text-primary | #1a202c | 본문 텍스트 |
|
|
49
|
+
| --color-text-secondary | #718096 | 보조 텍스트 |
|
|
50
|
+
| --color-text-disabled | #a0aec0 | 비활성 텍스트 |
|
|
51
|
+
|
|
52
|
+
### 타이포그래피 (Typography)
|
|
53
|
+
|
|
54
|
+
| 토큰명 | Size | Weight | Line Height | 용도 |
|
|
55
|
+
|--------|------|--------|-------------|------|
|
|
56
|
+
| H1 | 28px | 700 | 1.3 | 페이지 제목 |
|
|
57
|
+
| H2 | 22px | 600 | 1.35 | 섹션 제목 |
|
|
58
|
+
| H3 | 18px | 600 | 1.4 | 서브 섹션 |
|
|
59
|
+
| Body1 | 16px | 400 | 1.6 | 본문 |
|
|
60
|
+
| Body2 | 14px | 400 | 1.5 | 보조 본문 |
|
|
61
|
+
| Caption | 12px | 400 | 1.4 | 캡션, 힌트 |
|
|
62
|
+
|
|
63
|
+
Font Family: `'Pretendard', -apple-system, sans-serif`
|
|
64
|
+
|
|
65
|
+
### 간격 (Spacing)
|
|
66
|
+
|
|
67
|
+
| 토큰명 | 값 |
|
|
68
|
+
|--------|------|
|
|
69
|
+
| --space-xs | 4px |
|
|
70
|
+
| --space-sm | 8px |
|
|
71
|
+
| --space-md | 16px |
|
|
72
|
+
| --space-lg | 24px |
|
|
73
|
+
| --space-xl | 32px |
|
|
74
|
+
| --space-2xl | 48px |
|
|
75
|
+
|
|
76
|
+
### 반경 (Border Radius)
|
|
77
|
+
|
|
78
|
+
| 토큰명 | 값 |
|
|
79
|
+
|--------|------|
|
|
80
|
+
| --radius-sm | 4px |
|
|
81
|
+
| --radius-md | 8px |
|
|
82
|
+
| --radius-lg | 12px |
|
|
83
|
+
| --radius-full | 9999px |
|
|
84
|
+
|
|
85
|
+
### 그림자 (Shadow)
|
|
86
|
+
|
|
87
|
+
| 토큰명 | 값 |
|
|
88
|
+
|--------|------|
|
|
89
|
+
| --shadow-sm | 0 1px 2px rgba(0,0,0,0.05) |
|
|
90
|
+
| --shadow-md | 0 4px 6px rgba(0,0,0,0.07) |
|
|
91
|
+
| --shadow-lg | 0 10px 15px rgba(0,0,0,0.1) |
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
위 값들은 예시다. 프로젝트에 맞는 실제 값을 사용한다. 핵심은 모든 값에 **토큰명 + 구체적 수치 + 용도**를 명시하는 것이다.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### 섹션 2: 공통 컴포넌트
|
|
99
|
+
|
|
100
|
+
재사용되는 UI 컴포넌트를 정의한다. 각 컴포넌트는 아래 4가지를 포함한다:
|
|
101
|
+
|
|
102
|
+
1. **TypeScript Props 타입**
|
|
103
|
+
2. **상태별 스타일** (default / hover / focus / active / disabled / loading / error)
|
|
104
|
+
3. **크기 변형** (sm / md / lg)
|
|
105
|
+
4. **사용 예시**
|
|
106
|
+
|
|
107
|
+
```markdown
|
|
108
|
+
## 2. 공통 컴포넌트
|
|
109
|
+
|
|
110
|
+
### Button
|
|
111
|
+
|
|
112
|
+
\`\`\`typescript
|
|
113
|
+
interface ButtonProps {
|
|
114
|
+
variant: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
|
115
|
+
size: 'sm' | 'md' | 'lg';
|
|
116
|
+
disabled?: boolean;
|
|
117
|
+
loading?: boolean;
|
|
118
|
+
leftIcon?: ReactNode;
|
|
119
|
+
rightIcon?: ReactNode;
|
|
120
|
+
fullWidth?: boolean;
|
|
121
|
+
children: ReactNode;
|
|
122
|
+
onClick?: () => void;
|
|
123
|
+
}
|
|
124
|
+
\`\`\`
|
|
125
|
+
|
|
126
|
+
**크기별 스타일:**
|
|
127
|
+
|
|
128
|
+
| Size | Height | Padding (H) | Font Size | Border Radius |
|
|
129
|
+
|------|--------|-------------|-----------|---------------|
|
|
130
|
+
| sm | 32px | 12px | 13px | --radius-sm |
|
|
131
|
+
| md | 40px | 16px | 14px | --radius-md |
|
|
132
|
+
| lg | 48px | 20px | 16px | --radius-md |
|
|
133
|
+
|
|
134
|
+
**variant=primary 상태별 스타일:**
|
|
135
|
+
|
|
136
|
+
| 상태 | Background | Text | Border | 기타 |
|
|
137
|
+
|------|-----------|------|--------|------|
|
|
138
|
+
| Default | --color-primary | #ffffff | none | cursor: pointer |
|
|
139
|
+
| Hover | --color-primary-hover | #ffffff | none | — |
|
|
140
|
+
| Focus | --color-primary | #ffffff | 2px solid #63b3ed | outline-offset: 2px |
|
|
141
|
+
| Active | #2c5282 | #ffffff | none | — |
|
|
142
|
+
| Disabled | #cbd5e0 | #a0aec0 | none | cursor: not-allowed, opacity: 0.6 |
|
|
143
|
+
| Loading | --color-primary | — | none | spinner 표시, 텍스트 숨김 |
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**반드시 정의할 컴포넌트 목록:**
|
|
147
|
+
- Button
|
|
148
|
+
- Input (text, password, search)
|
|
149
|
+
- Select / Dropdown
|
|
150
|
+
- Checkbox, Radio
|
|
151
|
+
- Modal / Dialog
|
|
152
|
+
- Toast / Alert
|
|
153
|
+
- Loading Spinner
|
|
154
|
+
- Empty State (데이터 없음)
|
|
155
|
+
- Error State (에러 발생)
|
|
156
|
+
- Skeleton (로딩 플레이스홀더)
|
|
157
|
+
|
|
158
|
+
기획안의 기능에 따라 추가 컴포넌트를 정의한다. 예: Table, Card, Badge, Avatar, Tabs 등.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### 섹션 3: 화면별 레이아웃
|
|
163
|
+
|
|
164
|
+
기획안의 화면 목록(S-xx)을 기준으로 각 화면의 레이아웃을 정의한다.
|
|
165
|
+
|
|
166
|
+
각 화면은 아래 구조로 작성한다:
|
|
167
|
+
|
|
168
|
+
```markdown
|
|
169
|
+
## 3. 화면별 레이아웃
|
|
170
|
+
|
|
171
|
+
### S-01: 로그인
|
|
172
|
+
|
|
173
|
+
**경로:** `/login`
|
|
174
|
+
**관련 기능:** F-01, F-02
|
|
175
|
+
|
|
176
|
+
**Desktop (1280px+)**
|
|
177
|
+
\`\`\`
|
|
178
|
+
┌─────────────────────────────────────┐
|
|
179
|
+
│ Header (64px) │
|
|
180
|
+
├─────────────────────────────────────┤
|
|
181
|
+
│ │
|
|
182
|
+
│ ┌─────────────────────┐ │
|
|
183
|
+
│ │ Logo │ │
|
|
184
|
+
│ │ 48px × 48px │ │
|
|
185
|
+
│ ├─────────────────────┤ │
|
|
186
|
+
│ │ Email Input │ 400px │
|
|
187
|
+
│ │ Password Input │ width │
|
|
188
|
+
│ │ [로그인 버튼] │ center │
|
|
189
|
+
│ │ ─────────────── │ │
|
|
190
|
+
│ │ 소셜 로그인 영역 │ │
|
|
191
|
+
│ └─────────────────────┘ │
|
|
192
|
+
│ │
|
|
193
|
+
└─────────────────────────────────────┘
|
|
194
|
+
\`\`\`
|
|
195
|
+
|
|
196
|
+
**Tablet (768px)**
|
|
197
|
+
\`\`\`
|
|
198
|
+
┌───────────────────────┐
|
|
199
|
+
│ Header (56px) │
|
|
200
|
+
├───────────────────────┤
|
|
201
|
+
│ Login Form (360px) │
|
|
202
|
+
│ center aligned │
|
|
203
|
+
└───────────────────────┘
|
|
204
|
+
\`\`\`
|
|
205
|
+
|
|
206
|
+
**Mobile (375px)**
|
|
207
|
+
\`\`\`
|
|
208
|
+
┌─────────────────┐
|
|
209
|
+
│ Header (48px) │
|
|
210
|
+
├─────────────────┤
|
|
211
|
+
│ Logo │
|
|
212
|
+
│ Email Input │ padding:
|
|
213
|
+
│ Password Input │ 16px
|
|
214
|
+
│ [로그인 버튼] │ full-width
|
|
215
|
+
│ 소셜 로그인 │
|
|
216
|
+
└─────────────────┘
|
|
217
|
+
\`\`\`
|
|
218
|
+
|
|
219
|
+
**컴포넌트 배치:**
|
|
220
|
+
|
|
221
|
+
| 영역 | 컴포넌트 | Props |
|
|
222
|
+
|------|---------|-------|
|
|
223
|
+
| 이메일 | Input | type="email", size="lg", placeholder="이메일" |
|
|
224
|
+
| 비밀번호 | Input | type="password", size="lg", placeholder="비밀번호" |
|
|
225
|
+
| 로그인 | Button | variant="primary", size="lg", fullWidth |
|
|
226
|
+
|
|
227
|
+
**화면 상태:**
|
|
228
|
+
|
|
229
|
+
| 상태 | 설명 |
|
|
230
|
+
|------|------|
|
|
231
|
+
| Default | 빈 폼, 로그인 버튼 비활성 |
|
|
232
|
+
| Filled | 두 필드 입력 완료, 로그인 버튼 활성 |
|
|
233
|
+
| Loading | 로그인 버튼 loading 상태, 입력 disabled |
|
|
234
|
+
| Error | Input error 상태 + 에러 메시지 표시 |
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**반응형 기준점:**
|
|
238
|
+
- Desktop: 1280px 이상
|
|
239
|
+
- Tablet: 768px ~ 1279px
|
|
240
|
+
- Mobile: 375px ~ 767px
|
|
241
|
+
|
|
242
|
+
모든 화면에 3가지 해상도 레이아웃을 정의한다. 레이아웃이 변하지 않는 경우 "Desktop과 동일"로 표기해도 된다.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
### 섹션 4: 인터랙션 & 애니메이션
|
|
247
|
+
|
|
248
|
+
화면 전환, 컴포넌트 동작의 시각적 행동을 정의한다.
|
|
249
|
+
|
|
250
|
+
```markdown
|
|
251
|
+
## 4. 인터랙션 & 애니메이션
|
|
252
|
+
|
|
253
|
+
### 전역 설정
|
|
254
|
+
|
|
255
|
+
| 항목 | 값 |
|
|
256
|
+
|------|------|
|
|
257
|
+
| 기본 transition | 150ms ease-in-out |
|
|
258
|
+
| 페이지 전환 | fade 200ms |
|
|
259
|
+
| easing 함수 | cubic-bezier(0.4, 0, 0.2, 1) |
|
|
260
|
+
|
|
261
|
+
### 컴포넌트별 애니메이션
|
|
262
|
+
|
|
263
|
+
| 컴포넌트 | 트리거 | 애니메이션 | Duration |
|
|
264
|
+
|---------|--------|-----------|----------|
|
|
265
|
+
| Modal | 열기 | fade-in + scale(0.95→1) | 200ms |
|
|
266
|
+
| Modal | 닫기 | fade-out + scale(1→0.95) | 150ms |
|
|
267
|
+
| Toast | 표시 | slide-down from top | 300ms |
|
|
268
|
+
| Toast | 사라짐 | fade-out | 200ms, 3초 후 자동 |
|
|
269
|
+
| Dropdown | 열기 | fade-in + slide-down(4px) | 150ms |
|
|
270
|
+
| Skeleton | 대기 | pulse (opacity 0.4↔1) | 1.5s infinite |
|
|
271
|
+
|
|
272
|
+
### 스크롤 동작
|
|
273
|
+
|
|
274
|
+
| 화면 | 방식 | 비고 |
|
|
275
|
+
|------|------|------|
|
|
276
|
+
| 목록형 | 무한 스크롤 / 페이지네이션 | (기획안 기준) |
|
|
277
|
+
| 상세 | 일반 스크롤 | — |
|
|
278
|
+
|
|
279
|
+
### 로딩 전략
|
|
280
|
+
|
|
281
|
+
| 상황 | 처리 |
|
|
282
|
+
|------|------|
|
|
283
|
+
| 페이지 초기 로딩 | Skeleton 표시 |
|
|
284
|
+
| 버튼 액션 후 | 버튼 loading 상태 |
|
|
285
|
+
| 목록 추가 로딩 | 하단 spinner |
|
|
286
|
+
| 이미지 로딩 | blur placeholder → fade-in |
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
### 섹션 5: 접근성 가이드
|
|
292
|
+
|
|
293
|
+
```markdown
|
|
294
|
+
## 5. 접근성 가이드
|
|
295
|
+
|
|
296
|
+
### 색상 대비
|
|
297
|
+
|
|
298
|
+
| 조합 | 대비 비율 | WCAG AA |
|
|
299
|
+
|------|----------|---------|
|
|
300
|
+
| text-primary / bg | 최소 4.5:1 | 통과 |
|
|
301
|
+
| text-secondary / bg | 최소 3:1 | 통과 |
|
|
302
|
+
| primary button / white text | 최소 4.5:1 | 통과 |
|
|
303
|
+
|
|
304
|
+
### 포커스 스타일
|
|
305
|
+
|
|
306
|
+
모든 인터랙티브 요소에 visible focus indicator를 제공한다:
|
|
307
|
+
- `outline: 2px solid --color-primary`
|
|
308
|
+
- `outline-offset: 2px`
|
|
309
|
+
- `:focus-visible`만 적용 (마우스 클릭 시 미표시)
|
|
310
|
+
|
|
311
|
+
### 키보드 네비게이션
|
|
312
|
+
|
|
313
|
+
| 컴포넌트 | 키 | 동작 |
|
|
314
|
+
|---------|------|------|
|
|
315
|
+
| Modal | Escape | 닫기 |
|
|
316
|
+
| Modal | Tab | 내부 포커스 트랩 |
|
|
317
|
+
| Dropdown | ArrowDown/Up | 항목 이동 |
|
|
318
|
+
| Dropdown | Enter | 선택 |
|
|
319
|
+
| Dropdown | Escape | 닫기 |
|
|
320
|
+
|
|
321
|
+
### 스크린리더
|
|
322
|
+
|
|
323
|
+
| 컴포넌트 | aria 속성 |
|
|
324
|
+
|---------|-----------|
|
|
325
|
+
| Modal | role="dialog", aria-modal="true", aria-labelledby |
|
|
326
|
+
| Toast | role="alert", aria-live="polite" |
|
|
327
|
+
| Loading | aria-busy="true", aria-label="로딩 중" |
|
|
328
|
+
| Icon Button | aria-label (텍스트 없는 버튼) |
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Step 3: 저장 및 보고
|
|
334
|
+
|
|
335
|
+
완성된 명세를 `context/02_design_spec.md`에 저장한다.
|
|
336
|
+
|
|
337
|
+
저장 후 보고:
|
|
338
|
+
|
|
339
|
+
```
|
|
340
|
+
[디자인 명세 완성]
|
|
341
|
+
- 저장 위치: context/02_design_spec.md
|
|
342
|
+
- 디자인 토큰: 색상 N개, 타이포 N개, 간격 N개
|
|
343
|
+
- 공통 컴포넌트: N개 (각 상태/크기 정의 완료)
|
|
344
|
+
- 화면 레이아웃: N개 (반응형 3단계)
|
|
345
|
+
- 인터랙션: N개 정의
|
|
346
|
+
- 접근성: WCAG AA 기준 적용
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## 품질 체크리스트
|
|
352
|
+
|
|
353
|
+
저장 전 점검한다:
|
|
354
|
+
|
|
355
|
+
- [ ] 모든 색상이 hex 코드인가? (`#3182ce` O, `blue` X)
|
|
356
|
+
- [ ] 모든 크기에 단위가 있는가? (`16px` O, `16` X)
|
|
357
|
+
- [ ] 모든 duration에 단위가 있는가? (`200ms` O, `빠르게` X)
|
|
358
|
+
- [ ] 모호한 표현이 없는가? (`적당히`, `자연스럽게`, `예쁘게` X)
|
|
359
|
+
- [ ] 모든 컴포넌트에 Props 타입이 TypeScript로 정의되었는가?
|
|
360
|
+
- [ ] 모든 컴포넌트에 최소 default/hover/focus/disabled 상태가 있는가?
|
|
361
|
+
- [ ] 모든 화면에 Desktop/Tablet/Mobile 레이아웃이 있는가?
|
|
362
|
+
- [ ] 기획안의 화면 목록(S-xx)을 모두 커버하는가?
|
|
363
|
+
- [ ] 기획안에 없는 화면을 임의 추가하지 않았는가?
|