MDC 템플릿 라이브러리: 재사용 가능한 규칙 템플릿 구축 가이드




효율적인 개발을 위해서는 검증된 패턴과 템플릿을 재사용하는 것이 중요합니다. 이 가이드에서는 프로젝트 타입별 스타터 규칙, 업종별 규칙 템플릿, 주요 코딩 스타일 가이드를 MDC로 구현하는 방법을 상세히 다룹니다.

1. 프로젝트 타입별 스타터 규칙

Create React App (CRA) 템플릿

---
description: Create React App project starter rules
version: "2.1.0"
projectType: "create-react-app"
framework: "react"
buildTool: "webpack"
tags: ["react", "cra", "frontend", "starter"]
globs: ["src/**/*.{ts,tsx,js,jsx}", "public/**/*", "package.json"]
alwaysApply: false
---

# Create React App 프로젝트 규칙

## ?? 프로젝트 구조 및 설정

### 1. 디렉터리 구조
src/
├── components/          # 재사용 가능한 컴포넌트
│   ├── ui/             # 기본 UI 컴포넌트
│   └── feature/        # 기능별 컴포넌트
├── hooks/              # 커스텀 훅
├── services/           # API 호출 로직
├── utils/              # 유틸리티 함수
├── types/              # TypeScript 타입 정의
├── styles/             # 전역 스타일
└── assets/             # 정적 자원

### 2. 컴포넌트 작성 규칙
// ? 권장: 함수형 컴포넌트 + TypeScript
import React, { useState, useCallback, useMemo } from 'react';
import './Button.css';

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  loading?: boolean;
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
  children: React.ReactNode;
  className?: string;
  'data-testid'?: string;
}

export const Button: React.FC<ButtonProps> = ({
  variant = 'primary',
  size = 'md',
  disabled = false,
  loading = false,
  onClick,
  children,
  className = '',
  'data-testid': testId,
  ...restProps
}) => {
  const handleClick = useCallback((event: React.MouseEvent<HTMLButtonNode>) => {
    if (disabled || loading) return;
    onClick?.(event);
  }, [disabled, loading, onClick]);

  const buttonClass = useMemo(() => {
    return [
      'btn',
      `btn--${variant}`,
      `btn--${size}`,
      loading && 'btn--loading',
      disabled && 'btn--disabled',
      className
    ].filter(Boolean).join(' ');
  }, [variant, size, loading, disabled, className]);

  return (
    <button
      type="button"
      className={buttonClass}
      onClick={handleClick}
      disabled={disabled || loading}
      data-testid={testId}
      {...restProps}
    >
      {loading && <span className="btn__spinner" />}
      {children}
    </button>
  );
};

### 3. 상태 관리 패턴
// Context API를 활용한 전역 상태
import React, { createContext, useContext, useReducer } from 'react';

interface AppState {
  user: User | null;
  theme: 'light' | 'dark';
  loading: boolean;
}

type AppAction = 
  | { type: 'SET_USER'; payload: User }
  | { type: 'SET_THEME'; payload: 'light' | 'dark' }
  | { type: 'SET_LOADING'; payload: boolean };

const AppContext = createContext<{
  state: AppState;
  dispatch: React.Dispatch<AppAction>;
} | null>(null);

export const useAppContext = () => {
  const context = useContext(AppContext);
  if (!context) {
    throw new Error('useAppContext must be used within AppProvider');
  }
  return context;
};

### 4. 테스트 가이드라인
// ? 권장: React Testing Library 사용
import { render, screen, userEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button Component', () => {
  it('renders with correct text', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
  });

  it('calls onClick when clicked', async () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click me</Button>);
    
    await userEvent.click(screen.getByRole('button'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('does not call onClick when disabled', async () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick} disabled>Click me</Button>);
    
    await userEvent.click(screen.getByRole('button'));
    expect(handleClick).not.toHaveBeenCalled();
  });
});

## ?? 체크리스트
- [ ] TypeScript 설정 완료
- [ ] ESLint + Prettier 구성
- [ ] 테스트 환경 설정
- [ ] 컴포넌트 스토리북 구성
- [ ] CI/CD 파이프라인 설정

@cra-component-template.tsx
@cra-hook-template.ts
@cra-test-template.spec.tsx

Vite 프로젝트 템플릿

---
description: Vite project starter rules and optimizations
version: "1.8.0"
projectType: "vite"
framework: "react"
buildTool: "vite"
tags: ["vite", "react", "fast-build", "hmr"]
globs: ["src/**/*.{ts,tsx,js,jsx}", "vite.config.{ts,js}", "index.html"]
alwaysApply: false
---

# Vite 프로젝트 최적화 규칙

## ? Vite 특화 최적화

### 1. Vite 설정 최적화
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';

export default defineConfig({
  plugins: [
    react({
      // Fast Refresh 최적화
      fastRefresh: true,
      // JSX 런타임 자동 설정
      jsxRuntime: 'automatic'
    })
  ],
  
  // 개발 서버 최적화
  server: {
    port: 3000,
    open: true,
    hmr: {
      overlay: true
    }
  },
  
  // 빌드 최적화
  build: {
    // 청크 분할 전략
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          ui: ['@mui/material', '@emotion/react']
        }
      }
    },
    // 소스맵 설정
    sourcemap: true,
    // 압축 설정
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    }
  },
  
  // 경로 별칭 설정
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      '@components': resolve(__dirname, 'src/components'),
      '@utils': resolve(__dirname, 'src/utils'),
      '@types': resolve(__dirname, 'src/types')
    }
  },
  
  // 환경 변수 설정
  define: {
    __APP_VERSION__: JSON.stringify(process.env.npm_package_version)
  }
});

### 2. 동적 임포트 최적화
// ? 권장: 라우트 레벨 코드 스플리팅
import React, { Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { LoadingSpinner } from '@components/ui/LoadingSpinner';

// 지연 로딩 컴포넌트
const Home = React.lazy(() => import('@/pages/Home'));
const About = React.lazy(() => import('@/pages/About'));
const Dashboard = React.lazy(() => import('@/pages/Dashboard'));

export const App: React.FC = () => {
  return (
    <Router>
      <div className="app">
        <Suspense fallback={<LoadingSpinner />}>
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
            <Route path="/dashboard" element={<Dashboard />} />
          </Routes>
        </Suspense>
      </div>
    </Router>
  );
};

// 조건부 임포트
const ImportAnalytics = () => {
  if (process.env.NODE_ENV === 'production') {
    return import('@/utils/analytics');
  }
  return Promise.resolve(null);
};

### 3. 환경별 설정
// src/config/index.ts
interface Config {
  apiUrl: string;
  enableAnalytics: boolean;
  logLevel: 'debug' | 'info' | 'warn' | 'error';
}

const config: Config = {
  apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:3001',
  enableAnalytics: import.meta.env.VITE_ENABLE_ANALYTICS === 'true',
  logLevel: (import.meta.env.VITE_LOG_LEVEL as Config['logLevel']) || 'info'
};

export default config;

// 타입 안전한 환경 변수
interface ImportMetaEnv {
  readonly VITE_API_URL: string;
  readonly VITE_ENABLE_ANALYTICS: string;
  readonly VITE_LOG_LEVEL: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

### 4. HMR 최적화
// ? HMR 상태 보존
import { useState, useEffect } from 'react';

export const useHMRState = <T>(key: string, initialValue: T): [T, (value: T) => void] => {
  const [state, setState] = useState<T>(() => {
    // HMR 중 상태 복원
    if (import.meta.hot?.data && key in import.meta.hot.data) {
      return import.meta.hot.data[key];
    }
    return initialValue;
  });

  useEffect(() => {
    // HMR 중 상태 보존
    if (import.meta.hot) {
      import.meta.hot.data[key] = state;
    }
  }, [key, state]);

  return [state, setState];
};

// HMR 바운더리
if (import.meta.hot) {
  import.meta.hot.accept();
}

@vite-config-template.ts
@vite-component-template.tsx
@vite-env-template.d.ts

Next.js 프로젝트 템플릿

---
description: Next.js 13+ App Router project template
version: "3.2.0"
projectType: "nextjs"
framework: "nextjs"
router: "app-router"
tags: ["nextjs", "app-router", "ssr", "typescript"]
globs: ["app/**/*.{ts,tsx}", "pages/**/*.{ts,tsx}", "next.config.{js,ts}"]
alwaysApply: false
---

# Next.js App Router 프로젝트 템플릿

## ??? App Router 구조 및 패턴

### 1. 앱 디렉터리 구조
app/
├── layout.tsx              # 루트 레이아웃
├── page.tsx               # 홈 페이지
├── loading.tsx            # 로딩 UI
├── error.tsx              # 에러 UI
├── not-found.tsx          # 404 페이지
├── globals.css            # 전역 스타일
├── products/
│   ├── layout.tsx         # 제품 섹션 레이아웃
│   ├── page.tsx          # 제품 목록 페이지
│   ├── [id]/
│   │   ├── page.tsx      # 제품 상세 페이지
│   │   └── loading.tsx   # 상세 페이지 로딩
│   └── loading.tsx       # 제품 목록 로딩
└── api/
    └── products/
        └── route.ts       # API 라우트

### 2. 페이지 컴포넌트 패턴
// app/products/[id]/page.tsx
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { ProductDetail } from '@/components/ProductDetail';
import { getProduct } from '@/lib/api';

interface ProductPageProps {
  params: { id: string };
  searchParams: { [key: string]: string | string[] | undefined };
}

// 정적 메타데이터 생성
export async function generateMetadata({ 
  params 
}: ProductPageProps): Promise<Metadata> {
  const product = await getProduct(params.id);
  
  if (!product) {
    return {
      title: 'Product Not Found'
    };
  }

  return {
    title: product.name,
    description: product.description,
    openGraph: {
      title: product.name,
      description: product.description,
      images: [
        {
          url: product.imageUrl,
          width: 1200,
          height: 630,
          alt: product.name
        }
      ]
    },
    twitter: {
      card: 'summary_large_image',
      title: product.name,
      description: product.description,
      images: [product.imageUrl]
    }
  };
}

// 정적 파라미터 생성 (선택사항)
export async function generateStaticParams() {
  const products = await getProducts();
  
  return products.map((product) => ({
    id: product.id
  }));
}

export default async function ProductPage({ 
  params, 
  searchParams 
}: ProductPageProps) {
  // 서버 컴포넌트에서 데이터 페칭
  const product = await getProduct(params.id);
  
  if (!product) {
    notFound();
  }

  return (
    <div className="container mx-auto px-4 py-8">
      <ProductDetail 
        product={product} 
        variant={searchParams.variant as string}
      />
    </div>
  );
}

### 3. 레이아웃 컴포넌트
// app/layout.tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { Providers } from '@/components/Providers';
import { Header } from '@/components/Header';
import { Footer } from '@/components/Footer';
import './globals.css';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
  title: {
    template: '%s | My App',
    default: 'My App'
  },
  description: 'A modern Next.js application',
  keywords: ['Next.js', 'React', 'TypeScript'],
  authors: [{ name: 'Your Name' }],
  creator: 'Your Name',
  publisher: 'Your Company',
  formatDetection: {
    email: false,
    address: false,
    telephone: false
  }
};

export default function RootLayout({
  children
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ko" suppressHydrationWarning>
      <body className={inter.className}>
        <Providers>
          <div className="min-h-screen flex flex-col">
            <Header />
            <main className="flex-1">
              {children}
            </main>
            <Footer />
          </div>
        </Providers>
      </body>
    </html>
  );
}

### 4. API 라우트 패턴
// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { getProducts, createProduct } from '@/lib/db';

// 요청 검증 스키마
const CreateProductSchema = z.object({
  name: z.string().min(1).max(100),
  price: z.number().positive(),
  description: z.string().max(1000)
});

export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const page = parseInt(searchParams.get('page') || '1');
    const limit = parseInt(searchParams.get('limit') || '10');
    
    const products = await getProducts({ page, limit });
    
    return NextResponse.json({
      success: true,
      data: products.items,
      pagination: {
        page,
        limit,
        total: products.total,
        totalPages: Math.ceil(products.total / limit)
      }
    });
  } catch (error) {
    console.error('Products GET error:', error);
    return NextResponse.json(
      { success: false, error: 'Failed to fetch products' },
      { status: 500 }
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const validatedData = CreateProductSchema.parse(body);
    
    const product = await createProduct(validatedData);
    
    return NextResponse.json(
      { success: true, data: product },
      { status: 201 }
    );
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        { success: false, error: 'Validation failed', details: error.errors },
        { status: 400 }
      );
    }
    
    console.error('Products POST error:', error);
    return NextResponse.json(
      { success: false, error: 'Failed to create product' },
      { status: 500 }
    );
  }
}

### 5. 데이터 페칭 패턴
// lib/api.ts
import { unstable_cache } from 'next/cache';

// 캐시된 데이터 페칭
export const getProduct = unstable_cache(
  async (id: string) => {
    const response = await fetch(`${process.env.API_URL}/products/${id}`, {
      next: { revalidate: 3600 } // 1시간 캐시
    });
    
    if (!response.ok) {
      throw new Error('Failed to fetch product');
    }
    
    return response.json();
  },
  ['product'],
  {
    tags: ['products'],
    revalidate: 3600
  }
);

// 스트리밍 데이터
export async function getProductsStream() {
  const response = await fetch(`${process.env.API_URL}/products`, {
    next: { revalidate: 0 } // 실시간 데이터
  });
  
  return response.body;
}

@nextjs-page-template.tsx
@nextjs-layout-template.tsx
@nextjs-api-template.ts
@nextjs-config-template.js

2. 업종별 규칙 템플릿

핀테크 프로젝트 템플릿

---
description: Fintech project security and compliance rules
version: "2.3.0"
industry: "fintech"
compliance: ["PCI-DSS", "SOX", "GDPR"]
securityLevel: "high"
tags: ["fintech", "security", "compliance", "payments"]
globs: ["src/**/*.{ts,tsx,js,jsx}", "**/*payment*", "**/*transaction*", "**/*auth*"]
alwaysApply: true
---

# 핀테크 프로젝트 보안 및 규정 준수 규칙

## ?? 보안 필수 요구사항

### 1. 데이터 보호 및 암호화
// ? 민감한 데이터 처리 패턴
import CryptoJS from 'crypto-js';

interface SecureData {
  encryptedValue: string;
  iv: string;
  tag: string;
}

class DataEncryption {
  private static readonly SECRET_KEY = process.env.ENCRYPTION_KEY;
  
  static encrypt(data: string): SecureData {
    if (!this.SECRET_KEY) {
      throw new Error('암호화 키가 설정되지 않았습니다');
    }
    
    const iv = CryptoJS.lib.WordArray.random(16);
    const encrypted = CryptoJS.AES.encrypt(data, this.SECRET_KEY, {
      iv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    });
    
    return {
      encryptedValue: encrypted.toString(),
      iv: iv.toString(),
      tag: encrypted.key.toString()
    };
  }
  
  static decrypt(secureData: SecureData): string {
    if (!this.SECRET_KEY) {
      throw new Error('복호화 키가 설정되지 않았습니다');
    }
    
    const decrypted = CryptoJS.AES.decrypt(
      secureData.encryptedValue,
      this.SECRET_KEY,
      {
        iv: CryptoJS.enc.Hex.parse(secureData.iv),
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
      }
    );
    
    return decrypted.toString(CryptoJS.enc.Utf8);
  }
}

// ? 금지: 민감한 데이터 로깅
// console.log('User SSN:', user.ssn); // 절대 금지!

// ? 권장: 마스킹된 데이터 로깅
const maskSensitiveData = (data: string): string => {
  if (data.length <= 4) return '****';
  return data.slice(0, 2) + '*'.repeat(data.length - 4) + data.slice(-2);
};

console.log('Card Number:', maskSensitiveData(cardNumber));

### 2. 결제 처리 보안
// 결제 트랜잭션 검증
interface PaymentTransaction {
  id: string;
  amount: number;
  currency: string;
  merchantId: string;
  timestamp: Date;
  signature: string;
}

class PaymentValidator {
  static validateTransaction(transaction: PaymentTransaction): boolean {
    // 금액 검증
    if (transaction.amount <= 0 || transaction.amount > 1000000) {
      throw new Error('유효하지 않은 거래 금액');
    }
    
    // 통화 검증
    const validCurrencies = ['USD', 'EUR', 'KRW', 'JPY'];
    if (!validCurrencies.includes(transaction.currency)) {
      throw new Error('지원하지 않는 통화');
    }
    
    // 서명 검증
    const expectedSignature = this.generateSignature(transaction);
    if (transaction.signature !== expectedSignature) {
      throw new Error('거래 서명이 유효하지 않음');
    }
    
    // 시간 검증 (5분 이내)
    const currentTime = new Date();
    const timeDiff = currentTime.getTime() - transaction.timestamp.getTime();
    if (timeDiff > 5 * 60 * 1000) {
      throw new Error('거래 시간이 만료됨');
    }
    
    return true;
  }
  
  private static generateSignature(transaction: Omit<PaymentTransaction, 'signature'>): string {
    const payload = `${transaction.id}${transaction.amount}${transaction.currency}${transaction.merchantId}${transaction.timestamp.toISOString()}`;
    return CryptoJS.HmacSHA256(payload, process.env.PAYMENT_SECRET!).toString();
  }
}

### 3. 사용자 활동 감사 로그
// 감사 로그 시스템
interface AuditLog {
  userId: string;
  action: string;
  resource: string;
  timestamp: Date;
  ipAddress: string;
  userAgent: string;
  result: 'success' | 'failure';
  details?: Record<string, any>;
}

class AuditLogger {
  static async logUserAction(
    userId: string,
    action: string,
    resource: string,
    request: Request,
    result: 'success' | 'failure',
    details?: Record<string, any>
  ): Promise<void> {
    const auditLog: AuditLog = {
      userId,
      action,
      resource,
      timestamp: new Date(),
      ipAddress: this.getClientIP(request),
      userAgent: request.headers.get('user-agent') || 'unknown',
      result,
      details: this.sanitizeDetails(details)
    };
    
    // 보안 민감한 액션은 즉시 알림
    const criticalActions = ['login_failed', 'payment_failed', 'account_locked'];
    if (criticalActions.includes(action) && result === 'failure') {
      await this.sendSecurityAlert(auditLog);
    }
    
    // 감사 로그 저장 (암호화된 저장소)
    await this.saveAuditLog(auditLog);
  }
  
  private static sanitizeDetails(details?: Record<string, any>): Record<string, any> {
    if (!details) return {};
    
    const sensitiveFields = ['password', 'ssn', 'creditCard', 'bankAccount'];
    const sanitized = { ...details };
    
    sensitiveFields.forEach(field => {
      if (sanitized[field]) {
        sanitized[field] = '[REDACTED]';
      }
    });
    
    return sanitized;
  }
}

### 4. 컴플라이언스 체크리스트
// 자동 컴플라이언스 검사
class ComplianceChecker {
  static async runPCIDSSChecks(): Promise<ComplianceResult> {
    const checks = [
      this.checkDataEncryption,
      this.checkAccessControls,
      this.checkNetworkSecurity,
      this.checkVulnerabilityManagement,
      this.checkSecurityTesting,
      this.checkSecurityPolicies
    ];
    
    const results: ComplianceCheckResult[] = [];
    
    for (const check of checks) {
      try {
        const result = await check();
        results.push(result);
      } catch (error) {
        results.push({
          requirement: check.name,
          status: 'failed',
          error: error.message
        });
      }
    }
    
    return {
      standard: 'PCI-DSS',
      timestamp: new Date(),
      results,
      overallStatus: results.every(r => r.status === 'passed') ? 'compliant' : 'non-compliant'
    };
  }
  
  private static async checkDataEncryption(): Promise<ComplianceCheckResult> {
    // 데이터 암호화 검증 로직
    const encryptionStatus = await this.verifyEncryptionImplementation();
    
    return {
      requirement: 'Data Encryption (PCI-DSS 3.4)',
      status: encryptionStatus.isCompliant ? 'passed' : 'failed',
      details: encryptionStatus.details
    };
  }
}

## ?? 핀테크 보안 체크리스트
- [ ] 모든 민감한 데이터 암호화
- [ ] 접근 제어 및 권한 관리 구현
- [ ] 감사 로그 시스템 구축
- [ ] 결제 트랜잭션 검증 로직
- [ ] 보안 취약점 스캔 자동화
- [ ] 컴플라이언스 자동 검사
- [ ] 보안 사고 대응 계획 수립

@fintech-security-utils.ts
@payment-validation.ts
@audit-logger.ts
@compliance-checker.ts

이커머스 프로젝트 템플릿

---
description: E-commerce project patterns and best practices
version: "2.0.0"
industry: "ecommerce"
features: ["cart", "checkout", "inventory", "orders"]
tags: ["ecommerce", "shopping", "inventory", "payments"]
globs: ["src/**/*.{ts,tsx}", "**/*product*", "**/*cart*", "**/*order*"]
alwaysApply: false
---

# 이커머스 프로젝트 개발 표준

## ?? 핵심 기능 구현 패턴

### 1. 제품 관리 시스템
// 제품 데이터 모델
interface Product {
  id: string;
  name: string;
  description: string;
  price: number;
  compareAtPrice?: number;
  sku: string;
  inventory: {
    quantity: number;
    trackQuantity: boolean;
    allowBackorder: boolean;
  };
  images: ProductImage[];
  variants?: ProductVariant[];
  categories: string[];
  tags: string[];
  seo: {
    title?: string;
    description?: string;
    slug: string;
  };
  status: 'active' | 'draft' | 'archived';
  createdAt: Date;
  updatedAt: Date;
}

// 제품 서비스
class ProductService {
  static async getProduct(id: string): Promise<Product | null> {
    try {
      const product = await this.fetchFromAPI(`/products/${id}`);
      return this.transformProduct(product);
    } catch (error) {
      console.error('Failed to fetch product:', error);
      return null;
    }
  }
  
  static async searchProducts(params: ProductSearchParams): Promise<ProductSearchResult> {
    const query = this.buildSearchQuery(params);
    const response = await this.fetchFromAPI('/products/search', { method: 'POST', body: query });
    
    return {
      products: response.products.map(this.transformProduct),
      pagination: response.pagination,
      filters: response.filters,
      facets: response.facets
    };
  }
  
  static calculateDiscount(product: Product): DiscountInfo {
    if (!product.compareAtPrice || product.compareAtPrice <= product.price) {
      return { hasDiscount: false, percentage: 0, amount: 0 };
    }
    
    const amount = product.compareAtPrice - product.price;
    const percentage = Math.round((amount / product.compareAtPrice) * 100);
    
    return {
      hasDiscount: true,
      percentage,
      amount,
      savings: amount
    };
  }
}

### 2. 장바구니 상태 관리
// 장바구니 상태 관리 (Zustand 사용)
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface CartItem {
  productId: string;
  variantId?: string;
  quantity: number;
  price: number;
  product: Pick<Product, 'id' | 'name' | 'images' | 'sku'>;
}

interface CartState {
  items: CartItem[];
  isLoading: boolean;
  error: string | null;
  
  // Actions
  addItem: (item: Omit<CartItem, 'quantity'>, quantity?: number) => void;
  updateQuantity: (productId: string, variantId: string | undefined, quantity: number) => void;
  removeItem: (productId: string, variantId?: string) => void;
  clearCart: () => void;
  
  // Computed values
  getTotalItems: () => number;
  getTotalPrice: () => number;
  getItemCount: (productId: string, variantId?: string) => number;
}

export const useCartStore = create<CartState>()(
  persist(
    (set, get) => ({
      items: [],
      isLoading: false,
      error: null,
      
      addItem: (newItem, quantity = 1) => {
        set(state => {
          const existingIndex = state.items.findIndex(
            item => item.productId === newItem.productId && item.variantId === newItem.variantId
          );
          
          if (existingIndex >= 0) {
            // 기존 아이템 수량 업데이트
            const updatedItems = [...state.items];
            updatedItems[existingIndex].quantity += quantity;
            return { items: updatedItems };
          } else {
            // 새 아이템 추가
            return {
              items: [...state.items, { ...newItem, quantity }]
            };
          }
        });
      },
      
      updateQuantity: (productId, variantId, quantity) => {
        set(state => ({
          items: state.items.map(item =>
            item.productId === productId && item.variantId === variantId
              ? { ...item, quantity: Math.max(0, quantity) }
              : item
          ).filter(item => item.quantity > 0)
        }));
      },
      
      removeItem: (productId, variantId) => {
        set(state => ({
          items: state.items.filter(
            item => !(item.productId === productId && item.variantId === variantId)
          )
        }));
      },
      
      clearCart: () => set({ items: [] }),
      
      getTotalItems: () => {
        return get().items.reduce((total, item) => total + item.quantity, 0);
      },
      
      getTotalPrice: () => {
        return get().items.reduce((total, item) => total + (item.price * item.quantity), 0);
      },
      
      getItemCount: (productId, variantId) => {
        const item = get().items.find(
          item => item.productId === productId && item.variantId === variantId
        );
        return item?.quantity || 0;
      }
    }),
    {
      name: 'shopping-cart',
      partialize: (state) => ({ items: state.items })
    }
  )
);

### 3. 주문 처리 워크플로우
// 주문 처리 상태 머신
type OrderStatus = 
  | 'pending'
  | 'confirmed'
  | 'processing'
  | 'shipped'
  | 'delivered'
  | 'cancelled'
  | 'refunded';

interface Order {
  id: string;
  customerId: string;
  items: OrderItem[];
  shippingAddress: Address;
  billingAddress: Address;
  paymentMethod: PaymentMethod;
  status: OrderStatus;
  totals: OrderTotals;
  timestamps: {
    createdAt: Date;
    confirmedAt?: Date;
    shippedAt?: Date;
    deliveredAt?: Date;
  };
  tracking?: TrackingInfo;
}

class OrderService {
  static async createOrder(orderData: CreateOrderRequest): Promise<Order> {
    // 1. 재고 확인
    await this.validateInventory(orderData.items);
    
    // 2. 가격 검증
    await this.validatePricing(orderData.items);
    
    // 3. 결제 처리
    const paymentResult = await PaymentService.processPayment({
      amount: orderData.total,
      paymentMethod: orderData.paymentMethod,
      customerId: orderData.customerId
    });
    
    if (!paymentResult.success) {
      throw new Error('결제 처리 실패');
    }
    
    // 4. 주문 생성
    const order = await this.saveOrder({
      ...orderData,
      status: 'confirmed',
      paymentId: paymentResult.transactionId
    });
    
    // 5. 재고 차감
    await InventoryService.reserveItems(orderData.items);
    
    // 6. 주문 확인 이메일 발송
    await EmailService.sendOrderConfirmation(order);
    
    // 7. 이벤트 발행
    await EventBus.publish('order.created', { orderId: order.id });
    
    return order;
  }
  
  static async updateOrderStatus(orderId: string, newStatus: OrderStatus): Promise<void> {
    const order = await this.getOrder(orderId);
    if (!order) {
      throw new Error('주문을 찾을 수 없습니다');
    }
    
    // 상태 전환 검증
    this.validateStatusTransition(order.status, newStatus);
    
    // 상태별 추가 처리
    switch (newStatus) {
      case 'shipped':
        await this.handleShipped(order);
        break;
      case 'delivered':
        await this.handleDelivered(order);
        break;
      case 'cancelled':
        await this.handleCancelled(order);
        break;
    }
    
    await this.updateOrder(orderId, { status: newStatus });
  }
  
  private static validateStatusTransition(current: OrderStatus, next: OrderStatus): void {
    const validTransitions: Record<OrderStatus, OrderStatus[]> = {
      pending: ['confirmed', 'cancelled'],
      confirmed: ['processing', 'cancelled'],
      processing: ['shipped', 'cancelled'],
      shipped: ['delivered'],
      delivered: ['refunded'],
      cancelled: [],
      refunded: []
    };
    
    if (!validTransitions[current].includes(next)) {
      throw new Error(`Invalid status transition: ${current} -> ${next}`);
    }
  }
}

### 4. 재고 관리 시스템
// 실시간 재고 관리
class InventoryService {
  private static readonly stockCache = new Map<string, number>();
  
  static async checkAvailability(productId: string, variantId?: string, quantity = 1): Promise<boolean> {
    const currentStock = await this.getCurrentStock(productId, variantId);
    return currentStock >= quantity;
  }
  
  static async reserveItems(items: OrderItem[]): Promise<void> {
    const reservations: InventoryReservation[] = [];
    
    try {
      // 모든 아이템에 대해 재고 예약 시도
      for (const item of items) {
        const reservation = await this.createReservation({
          productId: item.productId,
          variantId: item.variantId,
          quantity: item.quantity,
          expiresAt: new Date(Date.now() + 10 * 60 * 1000) // 10분 후 만료
        });
        reservations.push(reservation);
      }
    } catch (error) {
      // 실패 시 모든 예약 롤백
      await this.rollbackReservations(reservations);
      throw error;
    }
  }
  
  static async updateStock(productId: string, variantId: string | undefined, delta: number): Promise<void> {
    const key = `${productId}:${variantId || 'default'}`;
    
    // 낙관적 잠금을 사용한 재고 업데이트
    let retries = 3;
    while (retries > 0) {
      try {
        const currentStock = await this.getCurrentStock(productId, variantId);
        const newStock = currentStock + delta;
        
        if (newStock < 0) {
          throw new Error('재고가 부족합니다');
        }
        
        await this.setStock(productId, variantId, newStock, currentStock);
        
        // 캐시 업데이트
        this.stockCache.set(key, newStock);
        
        // 재고 변경 이벤트 발행
        await EventBus.publish('inventory.updated', {
          productId,
          variantId,
          oldStock: currentStock,
          newStock,
          delta
        });
        
        break;
      } catch (error) {
        if (error.message.includes('version mismatch') && retries > 1) {
          retries--;
          await new Promise(resolve => setTimeout(resolve, 100)); // 100ms 대기
        } else {
          throw error;
        }
      }
    }
  }
  
  // 재고 부족 알림
  static async checkLowStockAlerts(): Promise<void> {
    const lowStockItems = await this.getLowStockItems();
    
    for (const item of lowStockItems) {
      await NotificationService.sendLowStockAlert({
        productId: item.productId,
        variantId: item.variantId,
        currentStock: item.stock,
        threshold: item.lowStockThreshold,
        supplier: item.supplier
      });
    }
  }
}

@ecommerce-product-service.ts
@cart-store.ts
@order-service.ts
@inventory-service.ts

게임 개발 프로젝트 템플릿

---
description: Game development patterns and optimization rules
version: "1.5.0"
industry: "gaming"
platform: ["web", "mobile", "desktop"]
gameType: ["casual", "rpg", "strategy", "action"]
tags: ["game-dev", "unity", "performance", "optimization"]
globs: ["src/**/*.{ts,js,cs}", "Assets/**/*.cs", "**/*game*", "**/*player*"]
alwaysApply: false
---

# 게임 개발 최적화 및 패턴 가이드

## ?? 게임 개발 핵심 패턴

### 1. 게임 오브젝트 풀링
// 오브젝트 풀 패턴으로 성능 최적화
class ObjectPool<T> {
  private pool: T[] = [];
  private createFn: () => T;
  private resetFn: (obj: T) => void;
  private maxSize: number;

  constructor(
    createFunction: () => T,
    resetFunction: (obj: T) => void,
    initialSize = 10,
    maxSize = 100
  ) {
    this.createFn = createFunction;
    this.resetFn = resetFunction;
    this.maxSize = maxSize;
    
    // 초기 오브젝트 생성
    for (let i = 0; i < initialSize; i++) {
      this.pool.push(this.createFn());
    }
  }

  get(): T {
    if (this.pool.length > 0) {
      return this.pool.pop()!;
    }
    return this.createFn();
  }

  release(obj: T): void {
    if (this.pool.length < this.maxSize) {
      this.resetFn(obj);
      this.pool.push(obj);
    }
  }

  clear(): void {
    this.pool.length = 0;
  }
}

// 총알 오브젝트 풀 예시
interface Bullet {
  x: number;
  y: number;
  vx: number;
  vy: number;
  active: boolean;
  sprite: PIXI.Sprite;
}

const bulletPool = new ObjectPool<Bullet>(
  () => ({
    x: 0,
    y: 0,
    vx: 0,
    vy: 0,
    active: false,
    sprite: new PIXI.Sprite(bulletTexture)
  }),
  (bullet) => {
    bullet.active = false;
    bullet.sprite.visible = false;
  },
  50, // 초기 50개
  200 // 최대 200개
);

### 2. 게임 상태 관리
// 상태 머신 패턴
abstract class GameState {
  abstract enter(): void;
  abstract update(deltaTime: number): void;
  abstract exit(): void;
  abstract handleInput(input: InputEvent): void;
}

class GameStateMachine {
  private currentState: GameState | null = null;
  private states: Map<string, GameState> = new Map();

  addState(name: string, state: GameState): void {
    this.states.set(name, state);
  }

  changeState(stateName: string): void {
    const newState = this.states.get(stateName);
    if (!newState) {
      throw new Error(`State ${stateName} not found`);
    }

    if (this.currentState) {
      this.currentState.exit();
    }

    this.currentState = newState;
    this.currentState.enter();
  }

  update(deltaTime: number): void {
    if (this.currentState) {
      this.currentState.update(deltaTime);
    }
  }

  handleInput(input: InputEvent): void {
    if (this.currentState) {
      this.currentState.handleInput(input);
    }
  }
}

// 게임 상태 구현 예시
class MenuState extends GameState {
  enter(): void {
    console.log('메뉴 상태 진입');
    // UI 활성화, 배경음악 재생 등
  }

  update(deltaTime: number): void {
    // 메뉴 애니메이션 업데이트
  }

  exit(): void {
    console.log('메뉴 상태 종료');
    // UI 비활성화
  }

  handleInput(input: InputEvent): void {
    if (input.type === 'click' && input.target === 'startButton') {
      gameStateMachine.changeState('playing');
    }
  }
}

class PlayingState extends GameState {
  enter(): void {
    console.log('게임 플레이 시작');
    // 게임 로직 초기화
  }

  update(deltaTime: number): void {
    // 게임 로직 업데이트
    this.updateEntities(deltaTime);
    this.checkCollisions();
    this.updatePhysics(deltaTime);
  }

  exit(): void {
    console.log('게임 플레이 종료');
    // 점수 저장, 리소스 정리
  }

  handleInput(input: InputEvent): void {
    // 플레이어 입력 처리
  }

  private updateEntities(deltaTime: number): void {
    // 엔티티 업데이트 로직
  }

  private checkCollisions(): void {
    // 충돌 검사 로직
  }

  private updatePhysics(deltaTime: number): void {
    // 물리 시뮬레이션
  }
}

### 3. 엔티티 컴포넌트 시스템 (ECS)
// ECS 아키텍처 구현
interface Component {
  readonly type: string;
}

interface Transform extends Component {
  type: 'transform';
  x: number;
  y: number;
  rotation: number;
  scaleX: number;
  scaleY: number;
}

interface Velocity extends Component {
  type: 'velocity';
  vx: number;
  vy: number;
}

interface Health extends Component {
  type: 'health';
  current: number;
  maximum: number;
}

class Entity {
  private static nextId = 0;
  public readonly id: number;
  private components: Map<string, Component> = new Map();

  constructor() {
    this.id = Entity.nextId++;
  }

  addComponent<T extends Component>(component: T): void {
    this.components.set(component.type, component);
  }

  getComponent<T extends Component>(type: string): T | undefined {
    return this.components.get(type) as T;
  }

  hasComponent(type: string): boolean {
    return this.components.has(type);
  }

  removeComponent(type: string): void {
    this.components.delete(type);
  }
}

abstract class System {
  protected entities: Set<Entity> = new Set();

  abstract update(deltaTime: number): void;

  addEntity(entity: Entity): void {
    if (this.matchesComponents(entity)) {
      this.entities.add(entity);
    }
  }

  removeEntity(entity: Entity): void {
    this.entities.delete(entity);
  }

  protected abstract matchesComponents(entity: Entity): boolean;
}

// 움직임 시스템
class MovementSystem extends System {
  protected matchesComponents(entity: Entity): boolean {
    return entity.hasComponent('transform') && entity.hasComponent('velocity');
  }

  update(deltaTime: number): void {
    for (const entity of this.entities) {
      const transform = entity.getComponent<Transform>('transform')!;
      const velocity = entity.getComponent<Velocity>('velocity')!;

      transform.x += velocity.vx * deltaTime;
      transform.y += velocity.vy * deltaTime;
    }
  }
}

// 렌더링 시스템
class RenderSystem extends System {
  private renderer: PIXI.Renderer;

  constructor(renderer: PIXI.Renderer) {
    super();
    this.renderer = renderer;
  }

  protected matchesComponents(entity: Entity): boolean {
    return entity.hasComponent('transform') && entity.hasComponent('sprite');
  }

  update(deltaTime: number): void {
    for (const entity of this.entities) {
      const transform = entity.getComponent<Transform>('transform')!;
      const sprite = entity.getComponent<any>('sprite')!;

      sprite.position.set(transform.x, transform.y);
      sprite.rotation = transform.rotation;
      sprite.scale.set(transform.scaleX, transform.scaleY);
    }
  }
}

### 4. 성능 최적화 기법
// 공간 분할을 통한 충돌 검사 최적화
class QuadTree {
  private bounds: Rectangle;
  private maxObjects: number;
  private maxDepth: number;
  private depth: number;
  private objects: GameObject[] = [];
  private nodes: QuadTree[] = [];

  constructor(bounds: Rectangle, maxObjects = 10, maxDepth = 5, depth = 0) {
    this.bounds = bounds;
    this.maxObjects = maxObjects;
    this.maxDepth = maxDepth;
    this.depth = depth;
  }

  clear(): void {
    this.objects.length = 0;
    for (const node of this.nodes) {
      node.clear();
    }
    this.nodes.length = 0;
  }

  split(): void {
    const halfWidth = this.bounds.width / 2;
    const halfHeight = this.bounds.height / 2;
    const x = this.bounds.x;
    const y = this.bounds.y;

    this.nodes[0] = new QuadTree(
      new Rectangle(x + halfWidth, y, halfWidth, halfHeight),
      this.maxObjects, this.maxDepth, this.depth + 1
    );
    this.nodes[1] = new QuadTree(
      new Rectangle(x, y, halfWidth, halfHeight),
      this.maxObjects, this.maxDepth, this.depth + 1
    );
    this.nodes[2] = new QuadTree(
      new Rectangle(x, y + halfHeight, halfWidth, halfHeight),
      this.maxObjects, this.maxDepth, this.depth + 1
    );
    this.nodes[3] = new QuadTree(
      new Rectangle(x + halfWidth, y + halfHeight, halfWidth, halfHeight),
      this.maxObjects, this.maxDepth, this.depth + 1
    );
  }

  insert(object: GameObject): void {
    if (this.nodes.length > 0) {
      const index = this.getIndex(object);
      if (index !== -1) {
        this.nodes[index].insert(object);
        return;
      }
    }

    this.objects.push(object);

    if (this.objects.length > this.maxObjects && this.depth < this.maxDepth) {
      if (this.nodes.length === 0) {
        this.split();
      }

      let i = 0;
      while (i < this.objects.length) {
        const index = this.getIndex(this.objects[i]);
        if (index !== -1) {
          this.nodes[index].insert(this.objects.splice(i, 1)[0]);
        } else {
          i++;
        }
      }
    }
  }

  retrieve(object: GameObject): GameObject[] {
    const returnObjects: GameObject[] = [];
    const index = this.getIndex(object);

    if (index !== -1 && this.nodes.length > 0) {
      returnObjects.push(...this.nodes[index].retrieve(object));
    }

    returnObjects.push(...this.objects);
    return returnObjects;
  }

  private getIndex(object: GameObject): number {
    let index = -1;
    const verticalMidpoint = this.bounds.x + this.bounds.width / 2;
    const horizontalMidpoint = this.bounds.y + this.bounds.height / 2;

    const topQuadrant = object.y < horizontalMidpoint && object.y + object.height < horizontalMidpoint;
    const bottomQuadrant = object.y > horizontalMidpoint;

    if (object.x < verticalMidpoint && object.x + object.width < verticalMidpoint) {
      if (topQuadrant) {
        index = 1;
      } else if (bottomQuadrant) {
        index = 2;
      }
    } else if (object.x > verticalMidpoint) {
      if (topQuadrant) {
        index = 0;
      } else if (bottomQuadrant) {
        index = 3;
      }
    }

    return index;
  }
}

// 프레임 레이트 독립적 게임 루프
class GameLoop {
  private lastTime = 0;
  private accumulator = 0;
  private readonly fixedDeltaTime = 1000 / 60; // 60 FPS
  private readonly maxFrameTime = 250; // 최대 프레임 시간 (4 FPS)

  start(): void {
    this.lastTime = performance.now();
    this.loop();
  }

  private loop = (): void => {
    const currentTime = performance.now();
    let frameTime = currentTime - this.lastTime;
    this.lastTime = currentTime;

    // 너무 긴 프레임 시간 제한
    frameTime = Math.min(frameTime, this.maxFrameTime);

    this.accumulator += frameTime;

    // 고정 시간 간격으로 물리 업데이트
    while (this.accumulator >= this.fixedDeltaTime) {
      this.updatePhysics(this.fixedDeltaTime);
      this.accumulator -= this.fixedDeltaTime;
    }

    // 가변 시간 간격으로 렌더링
    const alpha = this.accumulator / this.fixedDeltaTime;
    this.render(alpha);

    requestAnimationFrame(this.loop);
  };

  private updatePhysics(deltaTime: number): void {
    // 물리 시뮬레이션, 게임 로직 업데이트
  }

  private render(alpha: number): void {
    // 보간을 사용한 부드러운 렌더링
  }
}

@game-object-pool.ts
@game-state-machine.ts
@ecs-system.ts
@quadtree-collision.ts

3. 코딩 스타일 가이드 MDC 구현

Google 스타일 가이드 템플릿

---
description: Google TypeScript Style Guide implementation
version: "1.3.0"
styleGuide: "google"
language: "typescript"
framework: "any"
tags: ["google-style", "typescript", "formatting", "conventions"]
globs: ["**/*.{ts,tsx}"]
alwaysApply: true
---

# Google TypeScript 스타일 가이드

## ?? 포맷팅 및 네이밍 규칙

### 1. 네이밍 컨벤션
// ? 올바른 네이밍
class UserService {                    // PascalCase for classes
  private readonly apiEndpoint: string; // camelCase for properties
  
  async getUserById(userId: string): Promise<User> {  // camelCase for methods
    const API_VERSION = 'v1';          // SCREAMING_SNAKE_CASE for constants
    
    return this.fetchUser(userId);
  }
}

interface UserPreferences {            // PascalCase for interfaces
  theme: 'light' | 'dark';
  language: string;
}

type DatabaseConfig = {                // PascalCase for types
  host: string;
  port: number;
};

enum HttpStatus {                      // PascalCase for enums
  OK = 200,
  NOT_FOUND = 404,
  INTERNAL_SERVER_ERROR = 500
}

// ? 잘못된 네이밍
class userservice { }                  // 클래스명은 PascalCase
const User_Name = 'john';             // 변수명은 camelCase
function Get_User() { }               // 함수명은 camelCase

### 2. 타입 정의 규칙
// ? 명시적 타입 정의
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
  timestamp: Date;
}

// 제네릭 타입 매개변수는 의미있는 이름 사용
interface Repository<TEntity, TKey> {
  findById(id: TKey): Promise<TEntity | null>;
  save(entity: TEntity): Promise<TEntity>;
  delete(id: TKey): Promise<boolean>;
}

// 유니온 타입은 명확하게 정의
type Theme = 'light' | 'dark' | 'auto';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';

// 복잡한 타입은 단계적으로 구성
type BaseUser = {
  id: string;
  email: string;
  createdAt: Date;
};

type UserProfile = BaseUser & {
  firstName: string;
  lastName: string;
  avatar?: string;
};

type AdminUser = UserProfile & {
  permissions: string[];
  lastLoginAt?: Date;
};

### 3. 함수 정의 규칙
// ? 함수 시그니처는 명확하게
async function processUserData(
  users: User[],
  options: {
    includeInactive?: boolean;
    sortBy?: 'name' | 'createdAt';
    limit?: number;
  } = {}
): Promise<ProcessedUser[]> {
  // 구현
}

// 순수 함수 우선 사용
function calculateTax(amount: number, rate: number): number {
  if (amount < 0 || rate < 0) {
    throw new Error('Amount and rate must be non-negative');
  }
  return amount * rate;
}

// 고차 함수는 타입 안전하게
function createValidator<T>(
  schema: (value: unknown) => value is T
): (input: unknown) => T {
  return (input: unknown): T => {
    if (!schema(input)) {
      throw new Error('Validation failed');
    }
    return input;
  };
}

### 4. 클래스 구조 규칙
// ? 클래스 멤버 순서 및 접근 제한자
class UserManager {
  // 1. 정적 멤버 (public -> private)
  public static readonly DEFAULT_ROLE = 'user';
  private static instance: UserManager;
  
  // 2. 인스턴스 필드 (readonly -> public -> private)
  private readonly logger: Logger;
  public readonly config: UserConfig;
  private users: Map<string, User> = new Map();
  
  // 3. 생성자
  constructor(config: UserConfig, logger: Logger) {
    this.config = config;
    this.logger = logger;
  }
  
  // 4. 정적 메서드
  public static getInstance(config: UserConfig, logger: Logger): UserManager {
    if (!UserManager.instance) {
      UserManager.instance = new UserManager(config, logger);
    }
    return UserManager.instance;
  }
  
  // 5. 공개 메서드
  public async createUser(userData: CreateUserRequest): Promise<User> {
    this.validateUserData(userData);
    
    const user = await this.buildUser(userData);
    this.users.set(user.id, user);
    
    this.logger.info(`User created: ${user.id}`);
    return user;
  }
  
  public getUserById(id: string): User | null {
    return this.users.get(id) || null;
  }
  
  // 6. 비공개 메서드
  private validateUserData(userData: CreateUserRequest): void {
    if (!userData.email || !this.isValidEmail(userData.email)) {
      throw new Error('Valid email is required');
    }
    
    if (!userData.password || userData.password.length < 8) {
      throw new Error('Password must be at least 8 characters');
    }
  }
  
  private async buildUser(userData: CreateUserRequest): Promise<User> {
    return {
      id: this.generateUserId(),
      email: userData.email,
      role: userData.role || UserManager.DEFAULT_ROLE,
      createdAt: new Date(),
      isActive: true
    };
  }
  
  private isValidEmail(email: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
  
  private generateUserId(): string {
    return `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

### 5. 에러 처리 패턴
// ? 커스텀 에러 클래스
abstract class AppError extends Error {
  abstract readonly statusCode: number;
  abstract readonly isOperational: boolean;
  
  constructor(message: string, public readonly context?: Record<string, unknown>) {
    super(message);
    this.name = this.constructor.name;
    Error.captureStackTrace(this, this.constructor);
  }
}

class ValidationError extends AppError {
  readonly statusCode = 400;
  readonly isOperational = true;
  
  constructor(message: string, public readonly field: string) {
    super(message, { field });
  }
}

class NotFoundError extends AppError {
  readonly statusCode = 404;
  readonly isOperational = true;
  
  constructor(resource: string, id: string) {
    super(`${resource} with id ${id} not found`, { resource, id });
  }
}

// Result 패턴 사용
type Result<T, E = Error> = {
  success: true;
  data: T;
} | {
  success: false;
  error: E;
};

async function safeApiCall<T>(apiCall: () => Promise<T>): Promise<Result<T>> {
  try {
    const data = await apiCall();
    return { success: true, data };
  } catch (error) {
    return { 
      success: false, 
      error: error instanceof Error ? error : new Error('Unknown error')
    };
  }
}

### 6. 비동기 처리 규칙
// ? Promise 체이닝보다 async/await 선호
async function processUserRegistration(userData: UserRegistration): Promise<User> {
  try {
    // 순차 처리
    const validatedData = await validateUserData(userData);
    const hashedPassword = await hashPassword(validatedData.password);
    const user = await createUserInDatabase({ ...validatedData, password: hashedPassword });
    
    // 병렬 처리
    const [emailResult, profileResult] = await Promise.all([
      sendWelcomeEmail(user.email),
      createUserProfile(user.id)
    ]);
    
    if (!emailResult.success) {
      logger.warn('Failed to send welcome email', { userId: user.id });
    }
    
    return user;
  } catch (error) {
    logger.error('User registration failed', { userData: userData.email, error });
    throw error;
  }
}

// Promise.allSettled 사용으로 부분 실패 처리
async function notifyMultipleUsers(userIds: string[], message: string): Promise<NotificationResult[]> {
  const notificationPromises = userIds.map(userId => 
    sendNotification(userId, message).catch(error => ({ userId, error }))
  );
  
  const results = await Promise.allSettled(notificationPromises);
  
  return results.map((result, index) => ({
    userId: userIds[index],
    success: result.status === 'fulfilled',
    error: result.status === 'rejected' ? result.reason : undefined
  }));
}

@google-style-template.ts
@typescript-patterns.ts
@error-handling-patterns.ts

Airbnb 스타일 가이드 템플릿

---
description: Airbnb JavaScript/TypeScript Style Guide implementation
version: "2.1.0"
styleGuide: "airbnb"
language: "typescript"
framework: "react"
tags: ["airbnb-style", "react", "javascript", "typescript"]
globs: ["**/*.{ts,tsx,js,jsx}"]
alwaysApply: true
---

# Airbnb JavaScript/TypeScript 스타일 가이드

## ?? Airbnb 규칙 구현

### 1. 변수 선언 및 할당
// ? const를 기본으로, 재할당이 필요한 경우만 let 사용
const users = ['alice', 'bob', 'charlie'];
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
};

let currentUser = null; // 재할당이 필요한 경우
if (isAuthenticated) {
  currentUser = getCurrentUser();
}

// ? 구조 분해 할당 적극 활용
const { name, email, age } = user;
const [first, second, ...rest] = items;

// 함수 매개변수도 구조 분해
function createUser({ name, email, age = 18 }: CreateUserParams) {
  return { id: generateId(), name, email, age };
}

// ? 템플릿 리터럴 사용
const message = `Hello, ${user.name}! You have ${user.notifications.length} new notifications.`;

// ? 피해야 할 패턴
var userName = 'john'; // var 사용 금지
const greeting = 'Hello, ' + user.name + '!'; // 문자열 연결 대신 템플릿 리터럴

### 2. 함수 정의 패턴
// ? 화살표 함수 우선 사용 (단, 메서드는 제외)
const calculateTotal = (items: CartItem[]): number => {
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
};

// 단일 표현식인 경우 간결하게
const isEven = (num: number): boolean => num % 2 === 0;
const getFullName = (user: User): string => `${user.firstName} ${user.lastName}`;

// ? 고차 함수 활용
const createApiClient = (baseURL: string) => {
  return {
    get: <T>(endpoint: string): Promise<T> => fetch(`${baseURL}${endpoint}`).then(res => res.json()),
    post: <T>(endpoint: string, data: unknown): Promise<T> => 
      fetch(`${baseURL}${endpoint}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      }).then(res => res.json())
  };
};

// ? 순수 함수 선호
const addTax = (price: number, taxRate: number): number => price * (1 + taxRate);
const formatCurrency = (amount: number, currency = 'USD'): string => 
  new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount);

### 3. 객체 및 배열 조작
// ? 스프레드 연산자 활용
const originalUser = { name: 'John', age: 30 };
const updatedUser = { ...originalUser, age: 31, city: 'New York' };

const numbers = [1, 2, 3];
const moreNumbers = [...numbers, 4, 5, 6];

// ? 객체 메서드 단축 구문
const userService = {
  users: [] as User[],
  
  // 메서드 단축 구문
  addUser(user: User) {
    this.users.push(user);
  },
  
  getUserById(id: string) {
    return this.users.find(user => user.id === id);
  },
  
  // 계산된 속성명
  [Symbol.iterator]() {
    return this.users[Symbol.iterator]();
  }
};

// ? 배열 메서드 체이닝
const processUsers = (users: User[]): ProcessedUser[] => {
  return users
    .filter(user => user.isActive)
    .map(user => ({
      ...user,
      fullName: `${user.firstName} ${user.lastName}`,
      displayName: user.displayName || user.firstName
    }))
    .sort((a, b) => a.fullName.localeCompare(b.fullName));
};

### 4. React 컴포넌트 패턴
// ? 함수형 컴포넌트 + TypeScript
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  loading?: boolean;
  children: React.ReactNode;
}

const Button: React.FC<ButtonProps> = ({ 
  variant = 'primary',
  size = 'md',
  loading = false,
  children,
  className = '',
  disabled,
  ...restProps 
}) => {
  // ? early return 패턴
  if (loading) {
    return (
      <button className={`btn btn--${variant} btn--loading`} disabled>
        <Spinner size="sm" />
        Loading...
      </button>
    );
  }

  // ? 조건부 클래스명 처리
  const buttonClass = [
    'btn',
    `btn--${variant}`,
    `btn--${size}`,
    className,
  ].filter(Boolean).join(' ');

  return (
    <button 
      className={buttonClass}
      disabled={disabled || loading}
      {...restProps}
    >
      {children}
    </button>
  );
};

// ? 커스텀 훅 패턴
const useLocalStorage = <T>(key: string, initialValue: T) => {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.warn(`Error reading localStorage key "${key}":`, error);
      return initialValue;
    }
  });

  const setValue = useCallback((value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.warn(`Error setting localStorage key "${key}":`, error);
    }
  }, [key, storedValue]);

  const removeValue = useCallback(() => {
    try {
      window.localStorage.removeItem(key);
      setStoredValue(initialValue);
    } catch (error) {
      console.warn(`Error removing localStorage key "${key}":`, error);
    }
  }, [key, initialValue]);

  return [storedValue, setValue, removeValue] as const;
};

### 5. 모듈 시스템 및 Import
// ? Named import 우선 사용
import { useState, useEffect, useCallback } from 'react';
import { debounce, throttle } from 'lodash';
import { ApiClient } from '@/services/api';

// ? 타입 import 분리
import type { User, UserPreferences } from '@/types/user';
import type { ComponentProps } from 'react';

// ? 절대 경로 사용 (상대경로 최소화)
import { Button } from '@/components/ui/Button';
import { validateEmail } from '@/utils/validation';
import { UserService } from '@/services/UserService';

// ? Export 패턴
// Named export 우선
export const API_ENDPOINTS = {
  USERS: '/api/users',
  POSTS: '/api/posts'
} as const;

export class UserRepository {
  // 구현
}

export const createUser = (userData: CreateUserData): Promise<User> => {
  // 구현
};

// Default export는 컴포넌트나 주요 클래스에만 사용
export default UserRepository;

### 6. 조건문 및 반복문
// ? 삼항 연산자 적절히 사용
const statusColor = user.isActive ? 'green' : 'red';
const displayName = user.nickname || user.firstName || 'Anonymous';

// ? 단락 평가 활용
const handleClick = () => {
  isLoading && showSpinner();
  hasPermission && executeAction();
};

// ? Array 메서드 우선 사용
const activeUsers = users.filter(user => user.isActive);
const userNames = users.map(user => user.name);
const hasAdminUser = users.some(user => user.role === 'admin');

// ? 조건문 정리
const canEditPost = (user: User, post: Post): boolean => {
  return user.id === post.authorId || 
         user.role === 'admin' || 
         user.permissions.includes('edit_all_posts');
};

// 복잡한 조건은 함수로 추출
if (canEditPost(currentUser, selectedPost)) {
  enableEditMode();
}

@airbnb-style-template.ts
@react-patterns-airbnb.tsx
@functional-programming-patterns.ts

4. 템플릿 사용 가이드

템플릿 선택 가이드

---
description: Template selection and customization guide
version: "1.0.0"
category: "meta"
---

# MDC 템플릿 선택 및 커스터마이징 가이드

## ?? 프로젝트별 템플릿 선택

### 새 프로젝트 시작 시
# 프로젝트 타입 확인 체크리스트
echo "프로젝트 타입을 확인하세요:"
echo "1. 프레임워크: React/Vue/Angular/Vanilla"
echo "2. 빌드 도구: Vite/Webpack/Parcel/Rollup" 
echo "3. 언어: TypeScript/JavaScript"
echo "4. 스타일: Tailwind/CSS Modules/Styled Components"
echo "5. 상태 관리: Redux/Zustand/Context API"

# 템플릿 다운로드 스크립트
#!/bin/bash
PROJECT_TYPE=$1
FRAMEWORK=$2

case "$PROJECT_TYPE" in
  "web-app")
    if [ "$FRAMEWORK" = "react" ]; then
      echo "React 웹 앱 템플릿 적용 중..."
      cp templates/react-webapp/* .cursor/rules/
    elif [ "$FRAMEWORK" = "vue" ]; then
      echo "Vue 웹 앱 템플릿 적용 중..."
      cp templates/vue-webapp/* .cursor/rules/
    fi
    ;;
  "api")
    echo "API 서버 템플릿 적용 중..."
    cp templates/api-server/* .cursor/rules/
    ;;
  "fullstack")
    echo "풀스택 템플릿 적용 중..."
    cp templates/fullstack/* .cursor/rules/
    ;;
esac

### 업종별 특화 규칙 적용
// 업종별 규칙 매트릭스
const industryRuleMatrix = {
  fintech: {
    required: ['security-baseline', 'data-encryption', 'audit-logging'],
    recommended: ['pci-compliance', 'fraud-detection', 'payment-validation'],
    optional: ['kyc-verification', 'aml-checks']
  },
  
  ecommerce: {
    required: ['product-management', 'cart-handling', 'order-processing'],
    recommended: ['inventory-tracking', 'payment-integration', 'seo-optimization'],
    optional: ['recommendation-engine', 'analytics-tracking']
  },
  
  healthcare: {
    required: ['hipaa-compliance', 'data-privacy', 'audit-trails'],
    recommended: ['patient-data-security', 'medical-validation'],
    optional: ['telemedicine-integration', 'hl7-standards']
  },
  
  gaming: {
    required: ['performance-optimization', 'object-pooling', 'game-loop'],
    recommended: ['asset-management', 'physics-integration', 'multiplayer-sync'],
    optional: ['analytics-events', 'monetization-tracking']
  }
};

// 자동 규칙 추천 시스템
function recommendRules(projectType: string, industry: string): RuleRecommendation {
  const baseRules = getBaseRules(projectType);
  const industryRules = industryRuleMatrix[industry] || {};
  
  return {
    essential: [...baseRules.core, ...industryRules.required],
    recommended: [...baseRules.recommended, ...industryRules.recommended],
    optional: [...baseRules.optional, ...industryRules.optional],
    conflicts: detectRuleConflicts([...baseRules.core, ...industryRules.required])
  };
}

### 템플릿 커스터마이징

## 템플릿 수정 가이드

### 1. 기본 템플릿 복사
# 기본 템플릿을 프로젝트에 복사
cp -r templates/react-typescript .cursor/rules/base/

# 프로젝트별 수정사항 적용
echo "프로젝트명: MyApp" >> .cursor/rules/base/project-info.mdc
echo "팀명: Frontend Team" >> .cursor/rules/base/project-info.mdc

### 2. 규칙 우선순위 조정
# .cursor/rules/priority-config.yml
ruleOverrides:
  - ruleId: "typescript-strict"
    priority: 10
    reason: "타입 안전성이 최우선"
  
  - ruleId: "performance-optimization"  
    priority: 8
    reason: "사용자 경험 중요"
    
  - ruleId: "code-formatting"
    priority: 5
    reason: "팀 협업을 위한 기본"

### 3. 팀별 커스터마이징
// 팀별 규칙 설정 생성기
interface TeamConfig {
  teamName: string;
  preferences: {
    codeStyle: 'google' | 'airbnb' | 'standard';
    framework: string;
    testingLibrary: string;
    stateManagement: string;
  };
  restrictions: string[];
  customRules: string[];
}

function generateTeamRules(config: TeamConfig): void {
  const teamRulesDir = `.cursor/rules/teams/${config.teamName}`;
  
  // 팀별 디렉터리 생성
  ensureDirectory(teamRulesDir);
  
  // 기본 규칙 적용
  applyBaseRules(teamRulesDir, config.preferences);
  
  // 제한사항 적용
  applyRestrictions(teamRulesDir, config.restrictions);
  
  // 커스텀 규칙 추가
  addCustomRules(teamRulesDir, config.customRules);
  
  console.log(`${config.teamName} 팀 규칙이 생성되었습니다.`);
}

5. 템플릿 배포 및 공유

템플릿 저장소 구축

---
description: Template repository structure and distribution
version: "1.2.0"
category: "distribution"
---

# MDC 템플릿 저장소 관리

## ?? 저장소 구조

mdc-templates/
├── README.md
├── CONTRIBUTING.md
├── templates/
│   ├── project-types/
│   │   ├── react-app/
│   │   ├── vue-app/
│   │   ├── nextjs-app/
│   │   ├── node-api/
│   │   └── fullstack/
│   ├── industries/
│   │   ├── fintech/
│   │   ├── ecommerce/
│   │   ├── healthcare/
│   │   └── gaming/
│   ├── style-guides/
│   │   ├── google/
│   │   ├── airbnb/
│   │   └── standard/
│   └── frameworks/
│       ├── react/
│       ├── vue/
│       ├── angular/
│       └── svelte/
├── scripts/
│   ├── install-template.sh
│   ├── update-template.sh
│   └── validate-template.sh
└── docs/
    ├── getting-started.md
    ├── customization.md
    └── api-reference.md

### 템플릿 설치 스크립트
#!/bin/bash
# scripts/install-template.sh

set -e

TEMPLATE_NAME=""
PROJECT_DIR="."
TEMPLATE_REPO="https://github.com/your-org/mdc-templates"

# 옵션 파싱
while [[ $# -gt 0 ]]; do
  case $1 in
    -t|--template)
      TEMPLATE_NAME="$2"
      shift 2
      ;;
    -d|--directory)
      PROJECT_DIR="$2"
      shift 2
      ;;
    -h|--help)
      echo "사용법: $0 -t TEMPLATE_NAME [-d PROJECT_DIR]"
      echo "예시: $0 -t react-typescript -d ./my-project"
      exit 0
      ;;
    *)
      echo "알 수 없는 옵션: $1"
      exit 1
      ;;
  esac
done

if [ -z "$TEMPLATE_NAME" ]; then
  echo "템플릿 이름을 지정해주세요. -h 옵션으로 도움말을 확인하세요."
  exit 1
fi

echo "?? MDC 템플릿 설치 시작..."
echo "템플릿: $TEMPLATE_NAME"
echo "디렉터리: $PROJECT_DIR"

# 임시 디렉터리에 템플릿 저장소 클론
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT

git clone --depth 1 "$TEMPLATE_REPO" "$TEMP_DIR"

# 템플릿 존재 확인
TEMPLATE_PATH="$TEMP_DIR/templates/$TEMPLATE_NAME"
if [ ! -d "$TEMPLATE_PATH" ]; then
  echo "? 템플릿 '$TEMPLATE_NAME'을 찾을 수 없습니다."
  echo "사용 가능한 템플릿:"
  ls "$TEMP_DIR/templates/" | sed 's/^/  - /'
  exit 1
fi

# .cursor/rules 디렉터리 생성
mkdir -p "$PROJECT_DIR/.cursor/rules"

# 템플릿 파일 복사
echo "?? 템플릿 파일 복사 중..."
cp -r "$TEMPLATE_PATH"/* "$PROJECT_DIR/.cursor/rules/"

# 템플릿 변수 치환
echo "?? 템플릿 변수 치환 중..."
if [ -f "$PROJECT_DIR/package.json" ]; then
  PROJECT_NAME=$(node -p "require('$PROJECT_DIR/package.json').name" 2>/dev/null || echo "my-project")
else
  PROJECT_NAME=$(basename "$PROJECT_DIR")
fi

find "$PROJECT_DIR/.cursor/rules" -name "*.mdc" -type f -exec sed -i.bak \
  -e "s/{{PROJECT_NAME}}/$PROJECT_NAME/g" \
  -e "s/{{TEMPLATE_NAME}}/$TEMPLATE_NAME/g" \
  -e "s/{{INSTALL_DATE}}/$(date -u +%Y-%m-%dT%H:%M:%SZ)/g" \
  {} \;

# 백업 파일 정리
find "$PROJECT_DIR/.cursor/rules" -name "*.bak" -delete

echo "? 템플릿 설치 완료!"
echo "?? 다음 파일들이 생성되었습니다:"
find "$PROJECT_DIR/.cursor/rules" -name "*.mdc" | sed 's/^/  - /'

echo ""
echo "?? 다음 단계:"
echo "1. .cursor/rules/ 디렉터리의 템플릿을 프로젝트에 맞게 수정하세요"
echo "2. Cursor에서 프로젝트를 열어 규칙이 적용되는지 확인하세요"
echo "3. 팀원들과 규칙을 공유하고 피드백을 받으세요"

### 템플릿 업데이트 시스템
// 템플릿 버전 관리 시스템
interface TemplateMetadata {
  name: string;
  version: string;
  description: string;
  author: string;
  tags: string[];
  dependencies: string[];
  compatibleWith: string[];
  lastUpdated: Date;
  downloadCount: number;
}

class TemplateManager {
  private templates: Map<string, TemplateMetadata> = new Map();

  async installTemplate(templateName: string, targetDir: string): Promise<void> {
    const template = await this.fetchTemplate(templateName);
    
    // 의존성 확인
    await this.checkDependencies(template.dependencies);
    
    // 호환성 확인
    this.validateCompatibility(template.compatibleWith);
    
    // 템플릿 설치
    await this.extractTemplate(template, targetDir);
    
    // 설치 후 검증
    await this.validateInstallation(templateName, targetDir);
    
    console.log(`? 템플릿 '${templateName}' 설치 완료`);
  }

  async updateTemplate(templateName: string): Promise<void> {
    const currentVersion = await this.getCurrentVersion(templateName);
    const latestVersion = await this.getLatestVersion(templateName);
    
    if (currentVersion === latestVersion) {
      console.log(`템플릿 '${templateName}'은 이미 최신 버전입니다.`);
      return;
    }
    
    console.log(`템플릿 업데이트: ${currentVersion} → ${latestVersion}`);
    
    // 백업 생성
    await this.createBackup(templateName);
    
    try {
      await this.installTemplate(templateName, './');
      console.log(`? 템플릿 '${templateName}' 업데이트 완료`);
    } catch (error) {
      console.error('템플릿 업데이트 실패, 백업에서 복원 중...');
      await this.restoreFromBackup(templateName);
      throw error;
    }
  }

  async listAvailableTemplates(): Promise<TemplateMetadata[]> {
    const response = await fetch('https://api.mdc-templates.com/templates');
    return response.json();
  }

  async searchTemplates(query: string, filters: {
    framework?: string;
    industry?: string;
    styleGuide?: string;
  } = {}): Promise<TemplateMetadata[]> {
    const allTemplates = await this.listAvailableTemplates();
    
    return allTemplates.filter(template => {
      const matchesQuery = template.name.includes(query) || 
                          template.description.includes(query) ||
                          template.tags.some(tag => tag.includes(query));
      
      const matchesFramework = !filters.framework || 
                              template.compatibleWith.includes(filters.framework);
      
      const matchesIndustry = !filters.industry || 
                             template.tags.includes(filters.industry);
      
      const matchesStyleGuide = !filters.styleGuide || 
                               template.tags.includes(filters.styleGuide);
      
      return matchesQuery && matchesFramework && matchesIndustry && matchesStyleGuide;
    });
  }
}

// CLI 인터페이스
const templateManager = new TemplateManager();

// 사용 예시
// mdc install react-typescript
// mdc update react-typescript  
// mdc search "fintech" --framework=react
// mdc list --industry=ecommerce

@template-installer.sh
@template-manager.ts
@template-metadata.json

이 종합적인 MDC 템플릿 라이브러리 가이드를 통해 다양한 프로젝트 타입, 업종, 코딩 스타일에 맞는 검증된 규칙 템플릿을 효율적으로 활용할 수 있습니다. 템플릿을 기반으로 프로젝트를 시작하면 개발 초기 설정 시간을 크게 단축하고 일관된 코드 품질을 유지할 수 있습니다.




Leave a Comment