docs(ko-KR): add Korean translation for examples

Translate 6 CLAUDE.md examples (project, user, SaaS Next.js, Django API,
Go microservice, Rust API) and copy statusline.json config.
This commit is contained in:
hahmee
2026-03-10 17:09:23 +09:00
parent 3144b96faa
commit 526a9070e6
7 changed files with 1254 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
# 프로젝트 CLAUDE.md 예제
프로젝트 수준의 CLAUDE.md 파일 예제입니다. 프로젝트 루트에 배치하세요.
## 프로젝트 개요
[프로젝트에 대한 간단한 설명 - 기능, 기술 스택]
## 핵심 규칙
### 1. 코드 구성
- 큰 파일 소수보다 작은 파일 다수를 선호
- 높은 응집도, 낮은 결합도
- 일반적으로 200-400줄, 파일당 최대 800줄
- 타입별이 아닌 기능/도메인별로 구성
### 2. 코드 스타일
- 코드, 주석, 문서에 이모지 사용 금지
- 항상 불변성 유지 - 객체나 배열을 직접 변경하지 않음
- 프로덕션 코드에 console.log 사용 금지
- try/catch를 사용한 적절한 에러 처리
- Zod 또는 유사 라이브러리를 사용한 입력 유효성 검사
### 3. 테스트
- TDD: 테스트를 먼저 작성
- 최소 80% 커버리지
- 유틸리티에 대한 단위 테스트
- API에 대한 통합 테스트
- 핵심 흐름에 대한 E2E 테스트
### 4. 보안
- 하드코딩된 시크릿 금지
- 민감한 데이터는 환경 변수 사용
- 모든 사용자 입력 유효성 검사
- 매개변수화된 쿼리만 사용
- CSRF 보호 활성화
## 파일 구조
```
src/
|-- app/ # Next.js app router
|-- components/ # 재사용 가능한 UI 컴포넌트
|-- hooks/ # 커스텀 React hooks
|-- lib/ # 유틸리티 라이브러리
|-- types/ # TypeScript 타입 정의
```
## 주요 패턴
### API 응답 형식
```typescript
interface ApiResponse<T> {
success: boolean
data?: T
error?: string
}
```
### 에러 처리
```typescript
try {
const result = await operation()
return { success: true, data: result }
} catch (error) {
console.error('Operation failed:', error)
return { success: false, error: 'User-friendly message' }
}
```
## 환경 변수
```bash
# 필수
DATABASE_URL=
API_KEY=
# 선택
DEBUG=false
```
## 사용 가능한 명령어
- `/tdd` - 테스트 주도 개발 워크플로우
- `/plan` - 구현 계획 생성
- `/code-review` - 코드 품질 리뷰
- `/build-fix` - 빌드 에러 수정
## Git 워크플로우
- Conventional commits: `feat:`, `fix:`, `refactor:`, `docs:`, `test:`
- main 브랜치에 직접 커밋 금지
- PR은 리뷰 필수
- 병합 전 모든 테스트 통과 필수

View File

@@ -0,0 +1,308 @@
# Django REST API — 프로젝트 CLAUDE.md
> PostgreSQL과 Celery를 사용하는 Django REST Framework API의 실전 예시입니다.
> 프로젝트 루트에 복사하여 서비스에 맞게 커스터마이즈하세요.
## 프로젝트 개요
**기술 스택:** Python 3.12+, Django 5.x, Django REST Framework, PostgreSQL, Celery + Redis, pytest, Docker Compose
**아키텍처:** 비즈니스 도메인별 앱으로 구성된 도메인 주도 설계. API 레이어에 DRF, 비동기 작업에 Celery, 테스트에 pytest 사용. 모든 엔드포인트는 JSON을 반환하며 템플릿 렌더링은 없음.
## 필수 규칙
### Python 규칙
- 모든 함수 시그니처에 type hints 사용 — `from __future__ import annotations` 사용
- `print()` 문 사용 금지 — `logging.getLogger(__name__)` 사용
- 문자열 포매팅은 f-strings 사용, `%``.format()`은 사용 금지
- 파일 작업에 `os.path` 대신 `pathlib.Path` 사용
- isort로 import 정렬: stdlib, third-party, local 순서 (ruff에 의해 강제)
### 데이터베이스
- 모든 쿼리는 Django ORM 사용 — raw SQL은 `.raw()`와 parameterized 쿼리로만 사용
- 마이그레이션은 git에 커밋 — 프로덕션에서 `--fake` 사용 금지
- N+1 쿼리 방지를 위해 `select_related()``prefetch_related()` 사용
- 모든 모델에 `created_at``updated_at` 자동 필드 필수
- `filter()`, `order_by()`, 또는 `WHERE` 절에 사용되는 모든 필드에 인덱스 추가
```python
# 나쁜 예: N+1 쿼리
orders = Order.objects.all()
for order in orders:
print(order.customer.name) # 각 주문마다 DB를 조회함
# 좋은 예: join을 사용한 단일 쿼리
orders = Order.objects.select_related("customer").all()
```
### 인증
- `djangorestframework-simplejwt`를 통한 JWT — access token (15분) + refresh token (7일)
- 모든 뷰에 permission 클래스 지정 — 기본값에 의존하지 않기
- `IsAuthenticated`를 기본으로, 객체 수준 접근에는 커스텀 permission 추가
- 로그아웃을 위한 token blacklisting 활성화
### Serializers
- 간단한 CRUD에는 `ModelSerializer`, 복잡한 유효성 검증에는 `Serializer` 사용
- 입력/출력 형태가 다를 때는 읽기와 쓰기 serializer를 분리
- 유효성 검증은 serializer 레벨에서 — 뷰는 얇게 유지
```python
class CreateOrderSerializer(serializers.Serializer):
product_id = serializers.UUIDField()
quantity = serializers.IntegerField(min_value=1, max_value=100)
def validate_product_id(self, value):
if not Product.objects.filter(id=value, active=True).exists():
raise serializers.ValidationError("Product not found or inactive")
return value
class OrderDetailSerializer(serializers.ModelSerializer):
customer = CustomerSerializer(read_only=True)
product = ProductSerializer(read_only=True)
class Meta:
model = Order
fields = ["id", "customer", "product", "quantity", "total", "status", "created_at"]
```
### 오류 처리
- 일관된 오류 응답을 위해 DRF exception handler 사용
- 비즈니스 로직용 커스텀 예외는 `core/exceptions.py`에 정의
- 클라이언트에 내부 오류 세부 정보를 노출하지 않기
```python
# core/exceptions.py
from rest_framework.exceptions import APIException
class InsufficientStockError(APIException):
status_code = 409
default_detail = "Insufficient stock for this order"
default_code = "insufficient_stock"
```
### 코드 스타일
- 코드나 주석에 이모지 사용 금지
- 최대 줄 길이: 120자 (ruff에 의해 강제)
- 클래스: PascalCase, 함수/변수: snake_case, 상수: UPPER_SNAKE_CASE
- 뷰는 얇게 유지 — 비즈니스 로직은 서비스 함수나 모델 메서드에 배치
## 파일 구조
```
config/
settings/
base.py # 공유 설정
local.py # 개발 환경 오버라이드 (DEBUG=True)
production.py # 프로덕션 설정
urls.py # 루트 URL 설정
celery.py # Celery 앱 설정
apps/
accounts/ # 사용자 인증, 회원가입, 프로필
models.py
serializers.py
views.py
services.py # 비즈니스 로직
tests/
test_views.py
test_services.py
factories.py # Factory Boy 팩토리
orders/ # 주문 관리
models.py
serializers.py
views.py
services.py
tasks.py # Celery 작업
tests/
products/ # 상품 카탈로그
models.py
serializers.py
views.py
tests/
core/
exceptions.py # 커스텀 API 예외
permissions.py # 공유 permission 클래스
pagination.py # 커스텀 페이지네이션
middleware.py # 요청 로깅, 타이밍
tests/
```
## 주요 패턴
### Service 레이어
```python
# apps/orders/services.py
from django.db import transaction
def create_order(*, customer, product_id: uuid.UUID, quantity: int) -> Order:
"""재고 검증과 결제 보류를 포함한 주문 생성."""
product = Product.objects.select_for_update().get(id=product_id)
if product.stock < quantity:
raise InsufficientStockError()
with transaction.atomic():
order = Order.objects.create(
customer=customer,
product=product,
quantity=quantity,
total=product.price * quantity,
)
product.stock -= quantity
product.save(update_fields=["stock", "updated_at"])
# 비동기: 주문 확인 이메일 발송
send_order_confirmation.delay(order.id)
return order
```
### View 패턴
```python
# apps/orders/views.py
class OrderViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
pagination_class = StandardPagination
def get_serializer_class(self):
if self.action == "create":
return CreateOrderSerializer
return OrderDetailSerializer
def get_queryset(self):
return (
Order.objects
.filter(customer=self.request.user)
.select_related("product", "customer")
.order_by("-created_at")
)
def perform_create(self, serializer):
order = create_order(
customer=self.request.user,
product_id=serializer.validated_data["product_id"],
quantity=serializer.validated_data["quantity"],
)
serializer.instance = order
```
### 테스트 패턴 (pytest + Factory Boy)
```python
# apps/orders/tests/factories.py
import factory
from apps.accounts.tests.factories import UserFactory
from apps.products.tests.factories import ProductFactory
class OrderFactory(factory.django.DjangoModelFactory):
class Meta:
model = "orders.Order"
customer = factory.SubFactory(UserFactory)
product = factory.SubFactory(ProductFactory, stock=100)
quantity = 1
total = factory.LazyAttribute(lambda o: o.product.price * o.quantity)
# apps/orders/tests/test_views.py
import pytest
from rest_framework.test import APIClient
@pytest.mark.django_db
class TestCreateOrder:
def setup_method(self):
self.client = APIClient()
self.user = UserFactory()
self.client.force_authenticate(self.user)
def test_create_order_success(self):
product = ProductFactory(price=29_99, stock=10)
response = self.client.post("/api/orders/", {
"product_id": str(product.id),
"quantity": 2,
})
assert response.status_code == 201
assert response.data["total"] == 59_98
def test_create_order_insufficient_stock(self):
product = ProductFactory(stock=0)
response = self.client.post("/api/orders/", {
"product_id": str(product.id),
"quantity": 1,
})
assert response.status_code == 409
def test_create_order_unauthenticated(self):
self.client.force_authenticate(None)
response = self.client.post("/api/orders/", {})
assert response.status_code == 401
```
## 환경 변수
```bash
# Django
SECRET_KEY=
DEBUG=False
ALLOWED_HOSTS=api.example.com
# 데이터베이스
DATABASE_URL=postgres://user:pass@localhost:5432/myapp
# Redis (Celery broker + 캐시)
REDIS_URL=redis://localhost:6379/0
# JWT
JWT_ACCESS_TOKEN_LIFETIME=15 # 분
JWT_REFRESH_TOKEN_LIFETIME=10080 # 분 (7일)
# 이메일
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
EMAIL_HOST=smtp.example.com
```
## 테스트 전략
```bash
# 전체 테스트 실행
pytest --cov=apps --cov-report=term-missing
# 특정 앱 테스트 실행
pytest apps/orders/tests/ -v
# 병렬 실행
pytest -n auto
# 마지막 실행에서 실패한 테스트만 실행
pytest --lf
```
## ECC 워크플로우
```bash
# 계획 수립
/plan "Add order refund system with Stripe integration"
# TDD로 개발
/tdd # pytest 기반 TDD 워크플로우
# 리뷰
/python-review # Python 전용 코드 리뷰
/security-scan # Django 보안 감사
/code-review # 일반 품질 검사
# 검증
/verify # 빌드, 린트, 테스트, 보안 스캔
```
## Git 워크플로우
- `feat:` 새 기능, `fix:` 버그 수정, `refactor:` 코드 변경
- `main`에서 feature 브랜치 생성, PR 필수
- CI: ruff (린트 + 포맷), mypy (타입), pytest (테스트), safety (의존성 검사)
- 배포: Docker 이미지, Kubernetes 또는 Railway로 관리

View File

@@ -0,0 +1,267 @@
# Go Microservice — 프로젝트 CLAUDE.md
> PostgreSQL, gRPC, Docker를 사용하는 Go 마이크로서비스의 실전 예시입니다.
> 프로젝트 루트에 복사하여 서비스에 맞게 커스터마이즈하세요.
## 프로젝트 개요
**기술 스택:** Go 1.22+, PostgreSQL, gRPC + REST (grpc-gateway), Docker, sqlc (타입 안전 SQL), Wire (의존성 주입)
**아키텍처:** domain, repository, service, handler 레이어로 구성된 클린 아키텍처. gRPC를 기본 전송 프로토콜로 사용하고, 외부 클라이언트를 위한 REST gateway 제공.
## 필수 규칙
### Go 규칙
- Effective Go와 Go Code Review Comments 가이드를 따를 것
- 오류 래핑에 `errors.New` / `fmt.Errorf``%w` 사용 — 오류를 문자열 매칭하지 않기
- `init()` 함수 사용 금지 — `main()`이나 생성자에서 명시적으로 초기화
- 전역 가변 상태 금지 — 생성자를 통해 의존성 전달
- Context는 반드시 첫 번째 매개변수이며 모든 레이어를 통해 전파
### 데이터베이스
- 모든 쿼리는 `queries/`에 순수 SQL로 작성 — sqlc가 타입 안전한 Go 코드를 생성
- 마이그레이션은 `migrations/`에 golang-migrate 사용 — 데이터베이스를 직접 변경하지 않기
- 다중 단계 작업에는 `pgx.Tx`를 통한 트랜잭션 사용
- 모든 쿼리에 parameterized placeholder (`$1`, `$2`) 사용 — 문자열 포매팅 사용 금지
### 오류 처리
- 오류를 반환하고, panic하지 않기 — panic은 진정으로 복구 불가능한 상황에만 사용
- 컨텍스트와 함께 오류 래핑: `fmt.Errorf("creating user: %w", err)`
- 비즈니스 로직을 위한 sentinel 오류는 `domain/errors.go`에 정의
- handler 레이어에서 도메인 오류를 gRPC status 코드로 매핑
```go
// 도메인 레이어 — sentinel 오류
var (
ErrUserNotFound = errors.New("user not found")
ErrEmailTaken = errors.New("email already registered")
)
// Handler 레이어 — gRPC status로 매핑
func toGRPCError(err error) error {
switch {
case errors.Is(err, domain.ErrUserNotFound):
return status.Error(codes.NotFound, err.Error())
case errors.Is(err, domain.ErrEmailTaken):
return status.Error(codes.AlreadyExists, err.Error())
default:
return status.Error(codes.Internal, "internal error")
}
}
```
### 코드 스타일
- 코드나 주석에 이모지 사용 금지
- 외부로 공개되는 타입과 함수에는 반드시 doc 주석 작성
- 함수는 50줄 이하로 유지 — 헬퍼 함수로 분리
- 여러 케이스가 있는 모든 로직에 table-driven 테스트 사용
- signal 채널에는 `bool`이 아닌 `struct{}` 사용
## 파일 구조
```
cmd/
server/
main.go # 진입점, Wire 주입, 우아한 종료
internal/
domain/ # 비즈니스 타입과 인터페이스
user.go # User 엔티티와 repository 인터페이스
errors.go # Sentinel 오류
service/ # 비즈니스 로직
user_service.go
user_service_test.go
repository/ # 데이터 접근 (sqlc 생성 + 커스텀)
postgres/
user_repo.go
user_repo_test.go # testcontainers를 사용한 통합 테스트
handler/ # gRPC + REST 핸들러
grpc/
user_handler.go
rest/
user_handler.go
config/ # 설정 로딩
config.go
proto/ # Protobuf 정의
user/v1/
user.proto
queries/ # sqlc용 SQL 쿼리
user.sql
migrations/ # 데이터베이스 마이그레이션
001_create_users.up.sql
001_create_users.down.sql
```
## 주요 패턴
### Repository 인터페이스
```go
type UserRepository interface {
Create(ctx context.Context, user *User) error
FindByID(ctx context.Context, id uuid.UUID) (*User, error)
FindByEmail(ctx context.Context, email string) (*User, error)
Update(ctx context.Context, user *User) error
Delete(ctx context.Context, id uuid.UUID) error
}
```
### 의존성 주입을 사용한 Service
```go
type UserService struct {
repo domain.UserRepository
hasher PasswordHasher
logger *slog.Logger
}
func NewUserService(repo domain.UserRepository, hasher PasswordHasher, logger *slog.Logger) *UserService {
return &UserService{repo: repo, hasher: hasher, logger: logger}
}
func (s *UserService) Create(ctx context.Context, req CreateUserRequest) (*domain.User, error) {
existing, err := s.repo.FindByEmail(ctx, req.Email)
if err != nil && !errors.Is(err, domain.ErrUserNotFound) {
return nil, fmt.Errorf("checking email: %w", err)
}
if existing != nil {
return nil, domain.ErrEmailTaken
}
hashed, err := s.hasher.Hash(req.Password)
if err != nil {
return nil, fmt.Errorf("hashing password: %w", err)
}
user := &domain.User{
ID: uuid.New(),
Name: req.Name,
Email: req.Email,
Password: hashed,
}
if err := s.repo.Create(ctx, user); err != nil {
return nil, fmt.Errorf("creating user: %w", err)
}
return user, nil
}
```
### Table-Driven 테스트
```go
func TestUserService_Create(t *testing.T) {
tests := []struct {
name string
req CreateUserRequest
setup func(*MockUserRepo)
wantErr error
}{
{
name: "valid user",
req: CreateUserRequest{Name: "Alice", Email: "alice@example.com", Password: "secure123"},
setup: func(m *MockUserRepo) {
m.On("FindByEmail", mock.Anything, "alice@example.com").Return(nil, domain.ErrUserNotFound)
m.On("Create", mock.Anything, mock.Anything).Return(nil)
},
wantErr: nil,
},
{
name: "duplicate email",
req: CreateUserRequest{Name: "Alice", Email: "taken@example.com", Password: "secure123"},
setup: func(m *MockUserRepo) {
m.On("FindByEmail", mock.Anything, "taken@example.com").Return(&domain.User{}, nil)
},
wantErr: domain.ErrEmailTaken,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := new(MockUserRepo)
tt.setup(repo)
svc := NewUserService(repo, &bcryptHasher{}, slog.Default())
_, err := svc.Create(context.Background(), tt.req)
if tt.wantErr != nil {
assert.ErrorIs(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
}
})
}
}
```
## 환경 변수
```bash
# 데이터베이스
DATABASE_URL=postgres://user:pass@localhost:5432/myservice?sslmode=disable
# gRPC
GRPC_PORT=50051
REST_PORT=8080
# 인증
JWT_SECRET= # 프로덕션에서는 vault에서 로드
TOKEN_EXPIRY=24h
# 관측 가능성
LOG_LEVEL=info # debug, info, warn, error
OTEL_ENDPOINT= # OpenTelemetry 콜렉터
```
## 테스트 전략
```bash
/go-test # Go용 TDD 워크플로우
/go-review # Go 전용 코드 리뷰
/go-build # 빌드 오류 수정
```
### 테스트 명령어
```bash
# 단위 테스트 (빠름, 외부 의존성 없음)
go test ./internal/... -short -count=1
# 통합 테스트 (testcontainers를 위해 Docker 필요)
go test ./internal/repository/... -count=1 -timeout 120s
# 전체 테스트와 커버리지
go test ./... -coverprofile=coverage.out -count=1
go tool cover -func=coverage.out # 요약
go tool cover -html=coverage.out # 브라우저
# Race detector
go test ./... -race -count=1
```
## ECC 워크플로우
```bash
# 계획 수립
/plan "Add rate limiting to user endpoints"
# 개발
/go-test # Go 전용 패턴으로 TDD
# 리뷰
/go-review # Go 관용구, 오류 처리, 동시성
/security-scan # 시크릿 및 취약점 점검
# 머지 전 확인
go vet ./...
staticcheck ./...
```
## Git 워크플로우
- `feat:` 새 기능, `fix:` 버그 수정, `refactor:` 코드 변경
- `main`에서 feature 브랜치 생성, PR 필수
- CI: `go vet`, `staticcheck`, `go test -race`, `golangci-lint`
- 배포: CI에서 Docker 이미지 빌드, Kubernetes에 배포

View File

@@ -0,0 +1,285 @@
# Rust API Service — 프로젝트 CLAUDE.md
> Axum, PostgreSQL, Docker를 사용하는 Rust API 서비스의 실전 예시입니다.
> 프로젝트 루트에 복사하여 서비스에 맞게 커스터마이즈하세요.
## 프로젝트 개요
**기술 스택:** Rust 1.78+, Axum (웹 프레임워크), SQLx (비동기 데이터베이스), PostgreSQL, Tokio (비동기 런타임), Docker
**아키텍처:** handler -> service -> repository로 분리된 레이어드 아키텍처. HTTP에 Axum, 컴파일 타임에 타입이 검증되는 SQL에 SQLx, 횡단 관심사에 Tower 미들웨어 사용.
## 필수 규칙
### Rust 규칙
- 라이브러리 오류에 `thiserror`, 바이너리 크레이트나 테스트에서만 `anyhow` 사용
- 프로덕션 코드에서 `.unwrap()`이나 `.expect()` 사용 금지 — `?`로 오류 전파
- 함수 매개변수에 `String`보다 `&str` 선호; 소유권 이전 시 `String` 반환
- `#![deny(clippy::all, clippy::pedantic)]`과 함께 `clippy` 사용 — 모든 경고 수정
- 모든 공개 타입에 `Debug` derive; `Clone`, `PartialEq`는 필요할 때만 derive
- `// SAFETY:` 주석으로 정당화하지 않는 한 `unsafe` 블록 사용 금지
### 데이터베이스
- 모든 쿼리에 SQLx `query!` 또는 `query_as!` 매크로 사용 — 스키마에 대해 컴파일 타임에 검증
- 마이그레이션은 `migrations/``sqlx migrate` 사용 — 데이터베이스를 직접 변경하지 않기
- 공유 상태로 `sqlx::Pool<Postgres>` 사용 — 요청마다 커넥션을 생성하지 않기
- 모든 쿼리에 parameterized placeholder (`$1`, `$2`) 사용 — 문자열 포매팅 사용 금지
```rust
// 나쁜 예: 문자열 보간 (SQL injection 위험)
let q = format!("SELECT * FROM users WHERE id = '{}'", id);
// 좋은 예: parameterized 쿼리, 컴파일 타임에 검증
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
.fetch_optional(&pool)
.await?;
```
### 오류 처리
- 모듈별로 `thiserror`를 사용한 도메인 오류 enum 정의
- `IntoResponse`를 통해 오류를 HTTP 응답으로 매핑 — 내부 세부 정보를 노출하지 않기
- 구조화된 로깅에 `tracing` 사용 — `println!`이나 `eprintln!` 사용 금지
```rust
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AppError {
#[error("Resource not found")]
NotFound,
#[error("Validation failed: {0}")]
Validation(String),
#[error("Unauthorized")]
Unauthorized,
#[error(transparent)]
Internal(#[from] anyhow::Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match &self {
Self::NotFound => (StatusCode::NOT_FOUND, self.to_string()),
Self::Validation(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
Self::Unauthorized => (StatusCode::UNAUTHORIZED, self.to_string()),
Self::Internal(err) => {
tracing::error!(?err, "internal error");
(StatusCode::INTERNAL_SERVER_ERROR, "Internal error".into())
}
};
(status, Json(json!({ "error": message }))).into_response()
}
}
```
### 테스트
- 각 소스 파일 내의 `#[cfg(test)]` 모듈에서 단위 테스트
- `tests/` 디렉토리에서 실제 PostgreSQL을 사용한 통합 테스트 (Testcontainers 또는 Docker)
- 자동 마이그레이션과 롤백이 포함된 데이터베이스 테스트에 `#[sqlx::test]` 사용
- 외부 서비스 모킹에 `mockall` 또는 `wiremock` 사용
### 코드 스타일
- 최대 줄 길이: 100자 (rustfmt에 의해 강제)
- import 그룹화: `std`, 외부 크레이트, `crate`/`super` — 빈 줄로 구분
- 모듈: 모듈당 파일 하나, `mod.rs`는 re-export용으로만 사용
- 타입: PascalCase, 함수/변수: snake_case, 상수: UPPER_SNAKE_CASE
## 파일 구조
```
src/
main.rs # 진입점, 서버 설정, 우아한 종료
lib.rs # 통합 테스트를 위한 re-export
config.rs # envy 또는 figment을 사용한 환경 설정
router.rs # 모든 라우트가 포함된 Axum 라우터
middleware/
auth.rs # JWT 추출 및 검증
logging.rs # 요청/응답 트레이싱
handlers/
mod.rs # 라우트 핸들러 (얇게 — 서비스에 위임)
users.rs
orders.rs
services/
mod.rs # 비즈니스 로직
users.rs
orders.rs
repositories/
mod.rs # 데이터베이스 접근 (SQLx 쿼리)
users.rs
orders.rs
domain/
mod.rs # 도메인 타입, 오류 enum
user.rs
order.rs
migrations/
001_create_users.sql
002_create_orders.sql
tests/
common/mod.rs # 공유 테스트 헬퍼, 테스트 서버 설정
api_users.rs # 사용자 엔드포인트 통합 테스트
api_orders.rs # 주문 엔드포인트 통합 테스트
```
## 주요 패턴
### Handler (얇은 레이어)
```rust
async fn create_user(
State(ctx): State<AppState>,
Json(payload): Json<CreateUserRequest>,
) -> Result<(StatusCode, Json<UserResponse>), AppError> {
let user = ctx.user_service.create(payload).await?;
Ok((StatusCode::CREATED, Json(UserResponse::from(user))))
}
```
### Service (비즈니스 로직)
```rust
impl UserService {
pub async fn create(&self, req: CreateUserRequest) -> Result<User, AppError> {
if self.repo.find_by_email(&req.email).await?.is_some() {
return Err(AppError::Validation("Email already registered".into()));
}
let password_hash = hash_password(&req.password)?;
let user = self.repo.insert(&req.email, &req.name, &password_hash).await?;
Ok(user)
}
}
```
### Repository (데이터 접근)
```rust
impl UserRepository {
pub async fn find_by_email(&self, email: &str) -> Result<Option<User>, sqlx::Error> {
sqlx::query_as!(User, "SELECT * FROM users WHERE email = $1", email)
.fetch_optional(&self.pool)
.await
}
pub async fn insert(
&self,
email: &str,
name: &str,
password_hash: &str,
) -> Result<User, sqlx::Error> {
sqlx::query_as!(
User,
r#"INSERT INTO users (email, name, password_hash)
VALUES ($1, $2, $3) RETURNING *"#,
email, name, password_hash,
)
.fetch_one(&self.pool)
.await
}
}
```
### 통합 테스트
```rust
#[tokio::test]
async fn test_create_user() {
let app = spawn_test_app().await;
let response = app
.client
.post(&format!("{}/api/v1/users", app.address))
.json(&json!({
"email": "alice@example.com",
"name": "Alice",
"password": "securepassword123"
}))
.send()
.await
.expect("Failed to send request");
assert_eq!(response.status(), StatusCode::CREATED);
let body: serde_json::Value = response.json().await.unwrap();
assert_eq!(body["email"], "alice@example.com");
}
#[tokio::test]
async fn test_create_user_duplicate_email() {
let app = spawn_test_app().await;
// 첫 번째 사용자 생성
create_test_user(&app, "alice@example.com").await;
// 중복 시도
let response = create_user_request(&app, "alice@example.com").await;
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
```
## 환경 변수
```bash
# 서버
HOST=0.0.0.0
PORT=8080
RUST_LOG=info,tower_http=debug
# 데이터베이스
DATABASE_URL=postgres://user:pass@localhost:5432/myapp
# 인증
JWT_SECRET=your-secret-key-min-32-chars
JWT_EXPIRY_HOURS=24
# 선택 사항
CORS_ALLOWED_ORIGINS=http://localhost:3000
```
## 테스트 전략
```bash
# 전체 테스트 실행
cargo test
# 출력과 함께 실행
cargo test -- --nocapture
# 특정 테스트 모듈 실행
cargo test api_users
# 커버리지 확인 (cargo-llvm-cov 필요)
cargo llvm-cov --html
open target/llvm-cov/html/index.html
# 린트
cargo clippy -- -D warnings
# 포맷 검사
cargo fmt -- --check
```
## ECC 워크플로우
```bash
# 계획 수립
/plan "Add order fulfillment with Stripe payment"
# TDD로 개발
/tdd # cargo test 기반 TDD 워크플로우
# 리뷰
/code-review # Rust 전용 코드 리뷰
/security-scan # 의존성 감사 + unsafe 스캔
# 검증
/verify # 빌드, clippy, 테스트, 보안 스캔
```
## Git 워크플로우
- `feat:` 새 기능, `fix:` 버그 수정, `refactor:` 코드 변경
- `main`에서 feature 브랜치 생성, PR 필수
- CI: `cargo fmt --check`, `cargo clippy`, `cargo test`, `cargo audit`
- 배포: `scratch` 또는 `distroless` 베이스를 사용한 Docker 멀티스테이지 빌드

View File

@@ -0,0 +1,166 @@
# SaaS 애플리케이션 — 프로젝트 CLAUDE.md
> Next.js + Supabase + Stripe SaaS 애플리케이션을 위한 실제 사용 예제입니다.
> 프로젝트 루트에 복사한 후 기술 스택에 맞게 커스터마이즈하세요.
## 프로젝트 개요
**기술 스택:** Next.js 15 (App Router), TypeScript, Supabase (인증 + DB), Stripe (결제), Tailwind CSS, Playwright (E2E)
**아키텍처:** 기본적으로 Server Components 사용. Client Components는 상호작용이 필요한 경우에만 사용. API route는 webhook용, Server Action은 mutation용.
## 핵심 규칙
### 데이터베이스
- 모든 쿼리는 RLS가 활성화된 Supabase client 사용 — RLS를 절대 우회하지 않음
- 마이그레이션은 `supabase/migrations/`에 저장 — 데이터베이스를 직접 수정하지 않음
- `select('*')` 대신 명시적 컬럼 목록이 포함된 `select()` 사용
- 모든 사용자 대상 쿼리에는 무제한 결과를 방지하기 위해 `.limit()` 포함 필수
### 인증
- Server Components에서는 `@supabase/ssr``createServerClient()` 사용
- Client Components에서는 `@supabase/ssr``createBrowserClient()` 사용
- 보호된 라우트는 `getUser()`로 확인 — 인증에 `getSession()`만 단독으로 신뢰하지 않음
- `middleware.ts`의 Middleware가 매 요청마다 인증 토큰 갱신
### 결제
- Stripe webhook 핸들러는 `app/api/webhooks/stripe/route.ts`에 위치
- 클라이언트 측 가격 데이터를 절대 신뢰하지 않음 — 항상 서버 측에서 Stripe로부터 조회
- 구독 상태는 webhook에 의해 동기화되는 `subscription_status` 컬럼으로 확인
- 무료 플랜 사용자: 프로젝트 3개, 일일 API 호출 100회
### 코드 스타일
- 코드나 주석에 이모지 사용 금지
- 불변 패턴만 사용 — spread 연산자 사용, 직접 변경 금지
- Server Components: `'use client'` 디렉티브 없음, `useState`/`useEffect` 없음
- Client Components: 파일 상단에 `'use client'` 작성, 최소한으로 유지 — 로직은 hooks로 분리
- 모든 입력 유효성 검사에 Zod 스키마 사용 선호 (API route, 폼, 환경 변수)
## 파일 구조
```
src/
app/
(auth)/ # 인증 페이지 (로그인, 회원가입, 비밀번호 찾기)
(dashboard)/ # 보호된 대시보드 페이지
api/
webhooks/ # Stripe, Supabase webhooks
layout.tsx # Provider가 포함된 루트 레이아웃
components/
ui/ # Shadcn/ui 컴포넌트
forms/ # 유효성 검사가 포함된 폼 컴포넌트
dashboard/ # 대시보드 전용 컴포넌트
hooks/ # 커스텀 React hooks
lib/
supabase/ # Supabase client 팩토리
stripe/ # Stripe client 및 헬퍼
utils.ts # 범용 유틸리티
types/ # 공유 TypeScript 타입
supabase/
migrations/ # 데이터베이스 마이그레이션
seed.sql # 개발용 시드 데이터
```
## 주요 패턴
### API 응답 형식
```typescript
type ApiResponse<T> =
| { success: true; data: T }
| { success: false; error: string; code?: string }
```
### Server Action 패턴
```typescript
'use server'
import { z } from 'zod'
import { createServerClient } from '@/lib/supabase/server'
const schema = z.object({
name: z.string().min(1).max(100),
})
export async function createProject(formData: FormData) {
const parsed = schema.safeParse({ name: formData.get('name') })
if (!parsed.success) {
return { success: false, error: parsed.error.flatten() }
}
const supabase = await createServerClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) return { success: false, error: 'Unauthorized' }
const { data, error } = await supabase
.from('projects')
.insert({ name: parsed.data.name, user_id: user.id })
.select('id, name, created_at')
.single()
if (error) return { success: false, error: 'Failed to create project' }
return { success: true, data }
}
```
## 환경 변수
```bash
# Supabase
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY= # 서버 전용, 클라이언트에 절대 노출 금지
# Stripe
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
# 앱
NEXT_PUBLIC_APP_URL=http://localhost:3000
```
## 테스트 전략
```bash
/tdd # 새 기능에 대한 단위 + 통합 테스트
/e2e # 인증 흐름, 결제, 대시보드에 대한 Playwright 테스트
/test-coverage # 80% 이상 커버리지 확인
```
### 핵심 E2E 흐름
1. 회원가입 → 이메일 인증 → 첫 프로젝트 생성
2. 로그인 → 대시보드 → CRUD 작업
3. 플랜 업그레이드 → Stripe checkout → 구독 활성화
4. Webhook: 구독 취소 → 무료 플랜으로 다운그레이드
## ECC 워크플로우
```bash
# 기능 계획 수립
/plan "Add team invitations with email notifications"
# TDD로 개발
/tdd
# 커밋 전
/code-review
/security-scan
# 릴리스 전
/e2e
/test-coverage
```
## Git 워크플로우
- `feat:` 새 기능, `fix:` 버그 수정, `refactor:` 코드 변경
- `main`에서 기능 브랜치 생성, PR 필수
- CI 실행 항목: lint, 타입 체크, 단위 테스트, E2E 테스트
- 배포: PR 시 Vercel 미리보기, `main` 병합 시 프로덕션 배포

View File

@@ -0,0 +1,19 @@
{
"statusLine": {
"type": "command",
"command": "input=$(cat); user=$(whoami); cwd=$(echo \"$input\" | jq -r '.workspace.current_dir' | sed \"s|$HOME|~|g\"); model=$(echo \"$input\" | jq -r '.model.display_name'); time=$(date +%H:%M); remaining=$(echo \"$input\" | jq -r '.context_window.remaining_percentage // empty'); transcript=$(echo \"$input\" | jq -r '.transcript_path'); todo_count=$([ -f \"$transcript\" ] && grep -c '\"type\":\"todo\"' \"$transcript\" 2>/dev/null || echo 0); cd \"$(echo \"$input\" | jq -r '.workspace.current_dir')\" 2>/dev/null; branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo ''); status=''; [ -n \"$branch\" ] && { [ -n \"$(git status --porcelain 2>/dev/null)\" ] && status='*'; }; B='\\033[38;2;30;102;245m'; G='\\033[38;2;64;160;43m'; Y='\\033[38;2;223;142;29m'; M='\\033[38;2;136;57;239m'; C='\\033[38;2;23;146;153m'; R='\\033[0m'; T='\\033[38;2;76;79;105m'; printf \"${C}${user}${R}:${B}${cwd}${R}\"; [ -n \"$branch\" ] && printf \" ${G}${branch}${Y}${status}${R}\"; [ -n \"$remaining\" ] && printf \" ${M}ctx:${remaining}%%${R}\"; printf \" ${T}${model}${R} ${Y}${time}${R}\"; [ \"$todo_count\" -gt 0 ] && printf \" ${C}todos:${todo_count}${R}\"; echo",
"description": "Custom status line showing: user:path branch* ctx:% model time todos:N"
},
"_comments": {
"colors": {
"B": "Blue - directory path",
"G": "Green - git branch",
"Y": "Yellow - dirty status, time",
"M": "Magenta - context remaining",
"C": "Cyan - username, todos",
"T": "Gray - model name"
},
"output_example": "affoon:~/projects/myapp main* ctx:73% sonnet-4.6 14:30 todos:3",
"usage": "Copy the statusLine object to your ~/.claude/settings.json"
}
}

View File

@@ -0,0 +1,109 @@
# 사용자 수준 CLAUDE.md 예제
사용자 수준 CLAUDE.md 파일 예제입니다. `~/.claude/CLAUDE.md`에 배치하세요.
사용자 수준 설정은 모든 프로젝트에 전역으로 적용됩니다. 다음 용도로 사용하세요:
- 개인 코딩 선호 설정
- 항상 적용하고 싶은 범용 규칙
- 모듈식 규칙 파일 링크
---
## 핵심 철학
당신은 Claude Code입니다. 저는 복잡한 작업에 특화된 agent와 skill을 사용합니다.
**핵심 원칙:**
1. **Agent 우선**: 복잡한 작업은 특화된 agent에 위임
2. **병렬 실행**: 가능할 때 Task tool을 사용하여 여러 agent를 동시에 실행
3. **실행 전 계획**: 복잡한 작업에는 Plan Mode 사용
4. **테스트 주도**: 구현 전에 테스트 작성
5. **보안 우선**: 보안에 대해 절대 타협하지 않음
---
## 모듈식 규칙
상세 가이드라인은 `~/.claude/rules/`에 있습니다:
| 규칙 파일 | 내용 |
|-----------|------|
| security.md | 보안 점검, 시크릿 관리 |
| coding-style.md | 불변성, 파일 구성, 에러 처리 |
| testing.md | TDD 워크플로우, 80% 커버리지 요구사항 |
| git-workflow.md | 커밋 형식, PR 워크플로우 |
| agents.md | Agent 오케스트레이션, 상황별 agent 선택 |
| patterns.md | API 응답, repository 패턴 |
| performance.md | 모델 선택, 컨텍스트 관리 |
| hooks.md | Hooks 시스템 |
---
## 사용 가능한 Agent
`~/.claude/agents/`에 위치합니다:
| Agent | 용도 |
|-------|------|
| planner | 기능 구현 계획 수립 |
| architect | 시스템 설계 및 아키텍처 |
| tdd-guide | 테스트 주도 개발 |
| code-reviewer | 품질/보안 코드 리뷰 |
| security-reviewer | 보안 취약점 분석 |
| build-error-resolver | 빌드 에러 해결 |
| e2e-runner | Playwright E2E 테스트 |
| refactor-cleaner | 불필요한 코드 정리 |
| doc-updater | 문서 업데이트 |
---
## 개인 선호 설정
### 개인정보 보호
- 항상 로그를 삭제하고, 시크릿(API 키/토큰/비밀번호/JWT)을 절대 붙여넣지 않음
- 공유 전 출력 내용을 검토하여 민감한 데이터 제거
### 코드 스타일
- 코드, 주석, 문서에 이모지 사용 금지
- 불변성 선호 - 객체나 배열을 직접 변경하지 않음
- 큰 파일 소수보다 작은 파일 다수를 선호
- 일반적으로 200-400줄, 파일당 최대 800줄
### Git
- Conventional commits: `feat:`, `fix:`, `refactor:`, `docs:`, `test:`
- 커밋 전 항상 로컬에서 테스트
- 작고 집중된 커밋
### 테스트
- TDD: 테스트를 먼저 작성
- 최소 80% 커버리지
- 핵심 흐름에 대해 단위 + 통합 + E2E 테스트
### 지식 축적
- 개인 디버깅 메모, 선호 설정, 임시 컨텍스트 → auto memory
- 팀/프로젝트 지식(아키텍처 결정, API 변경, 구현 런북) → 프로젝트의 기존 문서 구조를 따름
- 현재 작업에서 이미 관련 문서, 주석, 예제를 생성하는 경우 동일한 지식을 다른 곳에 중복하지 않음
- 적절한 프로젝트 문서 위치가 없는 경우 새로운 최상위 문서를 만들기 전에 먼저 질문
---
## 에디터 연동
저는 Zed을 기본 에디터로 사용합니다:
- 파일 추적을 위한 Agent Panel
- CMD+Shift+R로 명령 팔레트 사용
- Vim 모드 활성화
---
## 성공 기준
다음 조건을 충족하면 성공입니다:
- 모든 테스트 통과 (80% 이상 커버리지)
- 보안 취약점 없음
- 코드가 읽기 쉽고 유지보수 가능
- 사용자 요구사항 충족
---
**철학**: Agent 우선 설계, 병렬 실행, 실행 전 계획, 코드 전 테스트, 항상 보안 우선.