이 문서는 AI 에이전트가 이 프로젝트에서 코드를 작성하고 기여할 때 따라야 하는 규칙과 가이드라인을 정의합니다.
모든 코드 파일은 450줄 내외로 작성되어야 합니다.
- 최대 줄 수: 450줄
- 권장 줄 수: 300-400줄
- 초과 시 조치: 파일이 450줄을 초과하면 기능별로 분리하여 모듈화
- 예외: 자동 생성 파일(예: OpenAPI 생성 산출물)은 예외로 둘 수 있음
// ❌ 나쁜 예: 하나의 파일에 모든 기능 (600줄)
// src/products.ts (600줄)
// ✅ 좋은 예: 기능별로 분리
// src/products/search.ts (200줄)
// src/products/filter.ts (150줄)
// src/products/formatter.ts (100줄)
// src/products/index.ts (50줄)- 명확성: 코드는 명확하고 이해하기 쉽게 작성
- 재사용성: 중복 코드를 최소화하고 공통 로직은 함수로 추출
- 타입 안정성: TypeScript의 타입 시스템을 적극 활용
- 에러 핸들링: 모든 비동기 작업과 외부 API 호출에 적절한 에러 처리 구현
// ✅ 좋은 예
async function fetchData(url: string): Promise<ApiResponse> {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP 에러: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('데이터 가져오기 실패:', error);
throw error;
}
}
// ❌ 나쁜 예
async function fetchData(url) {
const response = await fetch(url);
return await response.json();
}- 주기적인 커밋: 논리적인 작업 단위마다 커밋
- 작은 단위: 한 번에 하나의 기능이나 수정사항만 포함
- 완성된 코드: 빌드 실패나 런타임 에러가 없는 상태에서만 커밋
접두사는 영어, 메시지는 한국어를 사용합니다.
<타입>: <제목>
<본문> (선택사항)
<푸터> (선택사항)
feat:- 새로운 기능 추가fix:- 버그 수정docs:- 문서 수정style:- 코드 포맷팅, 세미콜론 누락 등 (로직 변경 없음)refactor:- 코드 리팩토링 (기능 변경 없음)test:- 테스트 코드 추가/수정chore:- 빌드 설정, 패키지 매니저 설정 등perf:- 성능 개선ci:- CI/CD 설정 변경revert:- 커밋 되돌리기
# ✅ 좋은 예
feat: 제품 검색 필터링 기능 추가
fix: 매장 찾기 시 거리 계산 오류 수정
docs: API 사용 예시 문서 업데이트
refactor: 재고 확인 로직을 별도 모듈로 분리
test: 가격 정보 조회 API 테스트 추가
chore: TypeScript 버전 5.7.2로 업데이트
# ❌ 나쁜 예
feat: add feature (영어로만 작성)
update (타입 접두사 누락)
fix: bug fix (구체적이지 않음)
여러 기능 추가 (타입 접두사 누락)- 명확하고 구체적으로: 무엇을 변경했는지 명확히 기술
- 50자 이내: 제목은 간결하게 작성
- 명령형: "~했음" 대신 "~함" 또는 "~추가" 형태 사용
- 마침표 없음: 제목 끝에 마침표를 붙이지 않음
feat: 제품 카테고리별 필터링 기능 추가
사용자가 제품을 카테고리별로 필터링할 수 있는 기능을 추가했습니다.
지원 카테고리:
- 주방/생활
- 문구
- 완구/취미
- 화장품/미용
관련 이슈: #123커밋하기 전에 다음 사항을 반드시 확인하세요:
- 코드가 빌드되는가?
- TypeScript 타입 에러가 없는가?
- 개인정보나 민감한 정보가 포함되어 있지 않은가?
- API 키, 비밀번호, 토큰 등이 포함되어 있지 않은가?
- 테스트가 통과하는가? (테스트가 있는 경우)
-
npm run test:coverage가 100% (Statements/Branches/Functions/Lines)를 만족하는가? - 주석이 한국어로 작성되어 있는가?
- 파일이 450줄을 초과하지 않는가?
다음 항목들은 절대 Git 저장소에 커밋되어서는 안 됩니다:
// ❌ 절대 금지
const API_KEY = 'sk-1234567890abcdef';
const PASSWORD = 'mypassword123';
const DATABASE_URL = 'postgresql://user:pass@localhost/db';// ✅ 올바른 방법: 환경 변수 사용
const API_KEY = process.env.API_KEY;
const PASSWORD = process.env.PASSWORD;
const DATABASE_URL = process.env.DATABASE_URL;- 실제 이메일 주소
- 전화번호
- 주소
- 신용카드 정보
- 주민등록번호
- 기타 식별 가능한 개인정보
# ❌ 커밋 금지
.env
.env.local
.env.production
.env.development
secrets.json
credentials.json
service-account-key.json
# ✅ .gitignore에 추가되어 있는지 확인- SSH private keys (
id_rsa,id_ed25519등) - SSL/TLS 인증서의 private key
- JWT secret keys
- OAuth client secrets
민감한 정보는 반드시 환경 변수로 관리하세요.
# .env.example (커밋 가능)
API_KEY=your_api_key_here
DATABASE_URL=your_database_url_here
SECRET_KEY=your_secret_key_here# .env (커밋 금지, .gitignore에 포함)
API_KEY=sk-real-api-key-1234567890
DATABASE_URL=postgresql://user:pass@localhost/db
SECRET_KEY=super-secret-key-xyzCloudflare Workers 환경 변수는 다음과 같이 관리:
# 로컬 개발용 (.dev.vars 파일 - .gitignore에 포함)
API_KEY=test-key-for-local-dev
# 프로덕션 배포용 (wrangler를 통해 설정)
npx wrangler secret put API_KEY커밋 전 다음 사항을 확인하세요:
-
.env파일이.gitignore에 포함되어 있는가? - 하드코딩된 API 키나 비밀번호가 없는가?
-
console.log()에 민감한 정보가 출력되지 않는가? - 주석에 실제 계정 정보가 포함되어 있지 않는가?
- 테스트 데이터가 실제 개인정보를 포함하지 않는가?
이 프로젝트는 확장 가능한 플러그인 아키텍처로 설계되었습니다. 다이소 외에 편의점, 백화점, 영화관 등 다양한 서비스를 쉽게 추가할 수 있습니다.
- ServiceProvider 인터페이스: 모든 서비스가 구현해야 하는 표준 계약
- ServiceRegistry: 서비스를 동적으로 등록하고 MCP 서버에 연결
- 도구 이름 네임스페이스: 서비스별 접두사 사용 (예:
daiso_,cu_,cgv_)
daiso-mcp/
├── src/
│ ├── index.ts # MCP 서버 진입점 (450줄 이하)
│ ├── core/ # 핵심 모듈 (확장 기반)
│ │ ├── types.ts # 공통 타입 정의
│ │ ├── interfaces.ts # ServiceProvider 인터페이스
│ │ └── registry.ts # ServiceRegistry 클래스
│ ├── services/ # 서비스 프로바이더
│ │ └── daiso/ # 다이소 서비스
│ │ ├── index.ts # 서비스 팩토리
│ │ ├── types.ts # 다이소 전용 타입
│ │ ├── api.ts # API 엔드포인트 중앙화
│ │ └── tools/ # 도구 구현
│ │ ├── searchProducts.ts
│ │ ├── findStores.ts
│ │ ├── checkInventory.ts
│ │ └── getPriceInfo.ts
│ └── utils/ # 유틸리티 함수
├── docs/ # 네트워크 파싱/리서치 문서
├── examples/ # 사용 예시
├── tests/ # 테스트 파일
└── scripts/ # 빌드/배포 스크립트
### 네트워크 파싱 기록
`docs/` 폴더에는 API 엔드포인트 분석을 위한 네트워크 파싱 기록이 저장됩니다.
새로운 서비스를 추가할 때 네트워크 분석 결과를 이 폴더에 `{서비스명}-` 접두사를 붙여 저장하세요.
docs/ ├── daiso-network-analysis-result.md # 다이소 API 분석 결과 ├── daiso-playwright-network-analysis.md # Playwright 네트워크 분석 ├── daiso-replay-session-test.html # 세션 테스트 HTML └── daiso-test-replay.js # 테스트 리플레이 스크립트
### 문서 기록
`docs/` 폴더에는 네트워크 파싱 기록과 API 리서치 문서를 함께 저장합니다.
새로운 서비스를 추가할 때 관련 문서를 이 폴더에 `{서비스명}-` 접두사로 저장하세요.
docs/ ├── daiso-network-analysis-result.md # 다이소 API 분석 결과 ├── daiso-playwright-network-analysis.md # Playwright 네트워크 분석 ├── daiso-replay-session-test.html # 세션 테스트 HTML └── daiso-test-replay.js # 테스트 리플레이 스크립트
### 새 서비스 추가 방법
새로운 서비스(예: CU 편의점)를 추가하려면:
#### 1. 서비스 디렉토리 생성
src/services/cu/ ├── index.ts # CuService 클래스 및 팩토리 ├── types.ts # CU 전용 타입 정의 ├── api.ts # CU API 엔드포인트 └── tools/ # CU 도구 구현 ├── searchProducts.ts └── findStores.ts
#### 2. ServiceProvider 구현
```typescript
// src/services/cu/index.ts
import type { ServiceProvider } from '../../core/interfaces.js';
import type { ToolRegistration } from '../../core/types.js';
class CuService implements ServiceProvider {
readonly metadata = {
id: 'cu',
name: 'CU 편의점',
version: '1.0.0',
description: 'CU 편의점 제품 검색 및 매장 찾기',
};
getTools(): ToolRegistration[] {
return [
// cu_search_products, cu_find_stores 등
];
}
}
export function createCuService(): ServiceProvider {
return new CuService();
}
// src/index.ts
import { createDaisoService } from './services/daiso/index.js';
import { createCuService } from './services/cu/index.js';
registry.registerAll([createDaisoService, createCuService]);서비스별 도구는 접두사로 구분합니다:
| 서비스 | 접두사 | 예시 |
|---|---|---|
| 다이소 | daiso_ |
daiso_search_products |
| CU | cu_ |
cu_search_products |
| CGV | cgv_ |
cgv_search_movies |
- camelCase: 함수, 변수명
- PascalCase: 클래스, 인터페이스, 타입명
- kebab-case: 파일명 (선택사항)
- SCREAMING_SNAKE_CASE: 상수
// 함수, 변수
const userName = 'John';
function getUserData() {}
// 클래스, 인터페이스, 타입
class UserService {}
interface UserData {}
type UserId = string;
// 상수
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = 'https://api.example.com';모든 주석은 한국어로 작성합니다.
/**
* 제품 검색 도구
*
* 다이소 제품을 검색하고 필터링하는 기능을 제공합니다.
* 검색 조건: 키워드, 카테고리, 가격 범위
*
* @module tools/searchProducts
*//**
* 제품을 검색하고 필터링된 결과를 반환합니다.
*
* @param query - 검색 키워드
* @param category - 제품 카테고리 (선택사항)
* @param maxPrice - 최대 가격 (선택사항)
* @returns 검색 결과 객체
* @throws {Error} API 호출 실패 시
*/
async function searchProducts(
query: string,
category?: string,
maxPrice?: number,
): Promise<SearchResult> {
// 구현
}// 사용자 입력 검증
if (!query || query.trim().length === 0) {
throw new Error('검색어를 입력해주세요');
}
// TODO: 캐싱 기능 추가 필요
const results = await fetchFromAPI(query);
// FIXME: 대소문자 구분 없이 검색하도록 수정 필요
const filtered = results.filter((item) => item.name.includes(query));// 인터페이스 정의
interface Product {
id: string;
name: string;
category: string;
price: number;
description?: string; // 선택적 속성
inStock: boolean;
}
// 타입 별칭
type ProductId = string;
type ProductCategory = '주방/생활' | '문구' | '완구/취미' | '화장품/미용';
// 유니온 타입
type SearchFilter = {
query: string;
category?: ProductCategory;
maxPrice?: number;
};- 들여쓰기: 2 spaces
- 따옴표: single quotes (
') 우선 - 세미콜론: 사용
- 줄 길이: 최대 100자 권장
// ✅ 좋은 예
const message = '안녕하세요';
const user = {
name: '홍길동',
age: 30,
};
// ❌ 나쁜 예
const message = '안녕하세요';
const user = { name: '홍길동', age: 30 };프로젝트의 메인 문서로 다음 내용을 포함:
- 프로젝트 개요
- 설치 방법
- 사용 방법
- API 문서
- 배포 가이드
각 도구의 입력/출력 스키마를 명확히 문서화:
/**
* 제품 검색 API
*
* 요청 예시:
* ```json
* {
* "name": "search_products",
* "arguments": {
* "query": "수납박스",
* "category": "주방/생활",
* "maxPrice": 5000
* }
* }
* ```
*
* 응답 예시:
* ```json
* {
* "content": [{
* "type": "text",
* "text": "{ ... }"
* }]
* }
* ```
*/주요 변경사항을 기록:
# Changelog
## [1.1.0] - 2025-03-01
### Added
- 제품 카테고리별 필터링 기능 추가
- 가격 히스토리 조회 기능 추가
### Fixed
- 매장 찾기 시 거리 계산 오류 수정
### Changed
- API 응답 형식 개선- 모든 도구는 테스트를 작성해야 합니다
- 엣지 케이스를 고려한 테스트 작성
- 에러 처리 로직도 테스트
- 코드 커버리지 100% 유지: 커밋 전
npm run test:coverage를 실행해 100%를 유지해야 합니다
src/tools/searchProducts.ts
tests/tools/searchProducts.test.ts
import { searchProducts } from '../src/tools/searchProducts';
describe('제품 검색', () => {
test('키워드로 제품을 검색할 수 있다', async () => {
const result = await searchProducts({ query: '수납박스' });
expect(result.content).toBeDefined();
expect(result.content[0].type).toBe('text');
});
test('존재하지 않는 제품은 빈 결과를 반환한다', async () => {
const result = await searchProducts({ query: '존재하지않는제품xyz' });
const data = JSON.parse(result.content[0].text);
expect(data.count).toBe(0);
});
test('빈 검색어는 에러를 발생시킨다', async () => {
await expect(searchProducts({ query: '' })).rejects.toThrow();
});
});- 어떤 기능을 구현할지 명확히 이해했는가?
- 파일이 450줄을 초과하지 않도록 설계했는가?
- 적절한 파일 구조와 모듈 분리를 계획했는가?
- TypeScript 타입을 명확히 정의했는가?
- 주석을 한국어로 작성했는가?
- 에러 핸들링을 적절히 구현했는가?
- 보안에 취약한 코드가 없는가?
- 빌드가 성공하는가?
- 타입 에러가 없는가?
- 개인정보나 민감한 정보가 포함되지 않았는가?
- 커밋 메시지가 컨벤션을 따르는가?
- 파일이 450줄 이하인가?
-
npm run test:coverage결과가 100% (Statements/Branches/Functions/Lines)인가?
# 형식 확인
<타입>: <한국어 제목>
# 예시
feat: 제품 검색 필터링 기능 추가
fix: 매장 거리 계산 오류 수정
docs: API 사용 가이드 업데이트이 규칙을 따라 일관되고 안전하며 유지보수 가능한 코드를 작성해주세요.