From 526a9070e64903b49bbf3e6c6da021cef7e2b63a Mon Sep 17 00:00:00 2001 From: hahmee Date: Tue, 10 Mar 2026 17:09:23 +0900 Subject: [PATCH] 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. --- docs/ko-KR/examples/CLAUDE.md | 100 ++++++ docs/ko-KR/examples/django-api-CLAUDE.md | 308 ++++++++++++++++++ docs/ko-KR/examples/go-microservice-CLAUDE.md | 267 +++++++++++++++ docs/ko-KR/examples/rust-api-CLAUDE.md | 285 ++++++++++++++++ docs/ko-KR/examples/saas-nextjs-CLAUDE.md | 166 ++++++++++ docs/ko-KR/examples/statusline.json | 19 ++ docs/ko-KR/examples/user-CLAUDE.md | 109 +++++++ 7 files changed, 1254 insertions(+) create mode 100644 docs/ko-KR/examples/CLAUDE.md create mode 100644 docs/ko-KR/examples/django-api-CLAUDE.md create mode 100644 docs/ko-KR/examples/go-microservice-CLAUDE.md create mode 100644 docs/ko-KR/examples/rust-api-CLAUDE.md create mode 100644 docs/ko-KR/examples/saas-nextjs-CLAUDE.md create mode 100644 docs/ko-KR/examples/statusline.json create mode 100644 docs/ko-KR/examples/user-CLAUDE.md diff --git a/docs/ko-KR/examples/CLAUDE.md b/docs/ko-KR/examples/CLAUDE.md new file mode 100644 index 00000000..5f89b82d --- /dev/null +++ b/docs/ko-KR/examples/CLAUDE.md @@ -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 { + 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은 리뷰 필수 +- 병합 전 모든 테스트 통과 필수 diff --git a/docs/ko-KR/examples/django-api-CLAUDE.md b/docs/ko-KR/examples/django-api-CLAUDE.md new file mode 100644 index 00000000..18cd261c --- /dev/null +++ b/docs/ko-KR/examples/django-api-CLAUDE.md @@ -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로 관리 diff --git a/docs/ko-KR/examples/go-microservice-CLAUDE.md b/docs/ko-KR/examples/go-microservice-CLAUDE.md new file mode 100644 index 00000000..1966008f --- /dev/null +++ b/docs/ko-KR/examples/go-microservice-CLAUDE.md @@ -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에 배포 diff --git a/docs/ko-KR/examples/rust-api-CLAUDE.md b/docs/ko-KR/examples/rust-api-CLAUDE.md new file mode 100644 index 00000000..0f05c8b2 --- /dev/null +++ b/docs/ko-KR/examples/rust-api-CLAUDE.md @@ -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` 사용 — 요청마다 커넥션을 생성하지 않기 +- 모든 쿼리에 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, + Json(payload): Json, +) -> Result<(StatusCode, Json), 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 { + 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, 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 { + 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 멀티스테이지 빌드 diff --git a/docs/ko-KR/examples/saas-nextjs-CLAUDE.md b/docs/ko-KR/examples/saas-nextjs-CLAUDE.md new file mode 100644 index 00000000..c9d501e3 --- /dev/null +++ b/docs/ko-KR/examples/saas-nextjs-CLAUDE.md @@ -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 = + | { 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` 병합 시 프로덕션 배포 diff --git a/docs/ko-KR/examples/statusline.json b/docs/ko-KR/examples/statusline.json new file mode 100644 index 00000000..4fa84e81 --- /dev/null +++ b/docs/ko-KR/examples/statusline.json @@ -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" + } +} diff --git a/docs/ko-KR/examples/user-CLAUDE.md b/docs/ko-KR/examples/user-CLAUDE.md new file mode 100644 index 00000000..976ee967 --- /dev/null +++ b/docs/ko-KR/examples/user-CLAUDE.md @@ -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 우선 설계, 병렬 실행, 실행 전 계획, 코드 전 테스트, 항상 보안 우선.