mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-05 00:33:27 +08:00
fix: harden unicode safety checks
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌐 Language / 语言 / 語言 / 언어**
|
||||
**Language / 语言 / 語言 / 언어**
|
||||
|
||||
[**English**](../../README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](README.md)
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 🚀 빠른 시작
|
||||
## 빠른 시작
|
||||
|
||||
2분 안에 설정 완료:
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
|
||||
### 2단계: 룰 설치 (필수)
|
||||
|
||||
> ⚠️ **중요:** Claude Code 플러그인은 `rules`를 자동으로 배포할 수 없습니다. 수동으로 설치해야 합니다:
|
||||
> WARNING: **중요:** Claude Code 플러그인은 `rules`를 자동으로 배포할 수 없습니다. 수동으로 설치해야 합니다:
|
||||
|
||||
```bash
|
||||
# 먼저 저장소 클론
|
||||
@@ -150,11 +150,11 @@ cd everything-claude-code
|
||||
/plugin list everything-claude-code@everything-claude-code
|
||||
```
|
||||
|
||||
✨ **끝!** 이제 16개 에이전트, 65개 스킬, 40개 커맨드를 사용할 수 있습니다.
|
||||
**끝!** 이제 16개 에이전트, 65개 스킬, 40개 커맨드를 사용할 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
## 🌐 크로스 플랫폼 지원
|
||||
## 크로스 플랫폼 지원
|
||||
|
||||
이 플러그인은 **Windows, macOS, Linux**를 완벽하게 지원하며, 주요 IDE(Cursor, OpenCode, Antigravity) 및 CLI 하네스와 긴밀하게 통합됩니다. 모든 훅과 스크립트는 최대 호환성을 위해 Node.js로 작성되었습니다.
|
||||
|
||||
@@ -201,7 +201,7 @@ export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,post:edit:typecheck"
|
||||
|
||||
---
|
||||
|
||||
## 📦 구성 요소
|
||||
## 구성 요소
|
||||
|
||||
이 저장소는 **Claude Code 플러그인**입니다 - 직접 설치하거나 컴포넌트를 수동으로 복사할 수 있습니다.
|
||||
|
||||
@@ -263,7 +263,7 @@ everything-claude-code/
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 에코시스템 도구
|
||||
## 에코시스템 도구
|
||||
|
||||
### Skill Creator
|
||||
|
||||
@@ -314,7 +314,7 @@ Claude Code에서 `/security-scan`을 사용하거나, [GitHub Action](https://g
|
||||
|
||||
[GitHub](https://github.com/affaan-m/agentshield) | [npm](https://www.npmjs.com/package/ecc-agentshield)
|
||||
|
||||
### 🧠 지속적 학습 v2
|
||||
### 지속적 학습 v2
|
||||
|
||||
직관(Instinct) 기반 학습 시스템이 여러분의 패턴을 자동으로 학습합니다:
|
||||
|
||||
@@ -329,7 +329,7 @@ Claude Code에서 `/security-scan`을 사용하거나, [GitHub Action](https://g
|
||||
|
||||
---
|
||||
|
||||
## 📋 요구 사항
|
||||
## 요구 사항
|
||||
|
||||
### Claude Code CLI 버전
|
||||
|
||||
@@ -344,13 +344,13 @@ claude --version
|
||||
|
||||
### 중요: 훅 자동 로딩 동작
|
||||
|
||||
> ⚠️ **기여자 참고:** `.claude-plugin/plugin.json`에 `"hooks"` 필드를 추가하지 **마세요**. 회귀 테스트로 이를 강제합니다.
|
||||
> WARNING: **기여자 참고:** `.claude-plugin/plugin.json`에 `"hooks"` 필드를 추가하지 **마세요**. 회귀 테스트로 이를 강제합니다.
|
||||
|
||||
Claude Code v2.1+는 설치된 플러그인의 `hooks/hooks.json`을 **자동으로 로드**합니다. 명시적으로 선언하면 중복 감지 오류가 발생합니다.
|
||||
|
||||
---
|
||||
|
||||
## 📥 설치
|
||||
## 설치
|
||||
|
||||
### 옵션 1: 플러그인으로 설치 (권장)
|
||||
|
||||
@@ -397,7 +397,7 @@ Claude Code v2.1+는 설치된 플러그인의 `hooks/hooks.json`을 **자동으
|
||||
|
||||
---
|
||||
|
||||
### 🔧 옵션 2: 수동 설치
|
||||
### 옵션 2: 수동 설치
|
||||
|
||||
설치할 항목을 직접 선택하고 싶다면:
|
||||
|
||||
@@ -422,7 +422,7 @@ cp -r everything-claude-code/skills/search-first ~/.claude/skills/
|
||||
|
||||
---
|
||||
|
||||
## 🎯 핵심 개념
|
||||
## 핵심 개념
|
||||
|
||||
### 에이전트
|
||||
|
||||
@@ -483,7 +483,7 @@ rules/
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ 어떤 에이전트를 사용해야 할까?
|
||||
## 어떤 에이전트를 사용해야 할까?
|
||||
|
||||
어디서 시작해야 할지 모르겠다면 이 참고표를 보세요:
|
||||
|
||||
@@ -529,7 +529,7 @@ rules/
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQ
|
||||
## FAQ
|
||||
|
||||
<details>
|
||||
<summary><b>설치된 에이전트/커맨드 확인은 어떻게 하나요?</b></summary>
|
||||
@@ -602,7 +602,7 @@ cp -r everything-claude-code/rules/common/* ~/.claude/rules/
|
||||
|
||||
---
|
||||
|
||||
## 🧪 테스트 실행
|
||||
## 테스트 실행
|
||||
|
||||
```bash
|
||||
# 모든 테스트 실행
|
||||
@@ -616,7 +616,7 @@ node tests/hooks/hooks.test.js
|
||||
|
||||
---
|
||||
|
||||
## 🤝 기여하기
|
||||
## 기여하기
|
||||
|
||||
**기여를 환영합니다.**
|
||||
|
||||
@@ -687,7 +687,7 @@ Claude Code 사용 비용이 부담된다면 토큰 소비를 관리해야 합
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 중요 참고 사항
|
||||
## WARNING: 중요 참고 사항
|
||||
|
||||
### 커스터마이징
|
||||
|
||||
@@ -699,7 +699,7 @@ Claude Code 사용 비용이 부담된다면 토큰 소비를 관리해야 합
|
||||
|
||||
---
|
||||
|
||||
## 💜 스폰서
|
||||
## 스폰서
|
||||
|
||||
이 프로젝트는 무료 오픈소스입니다. 스폰서의 지원으로 유지보수와 성장이 이루어집니다.
|
||||
|
||||
@@ -707,13 +707,13 @@ Claude Code 사용 비용이 부담된다면 토큰 소비를 관리해야 합
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Star 히스토리
|
||||
## Star 히스토리
|
||||
|
||||
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 링크
|
||||
## 링크
|
||||
|
||||
- **요약 가이드 (여기서 시작):** [The Shorthand Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2012378465664745795)
|
||||
- **상세 가이드 (고급):** [The Longform Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2014040193557471352)
|
||||
@@ -722,7 +722,7 @@ Claude Code 사용 비용이 부담된다면 토큰 소비를 관리해야 합
|
||||
|
||||
---
|
||||
|
||||
## 📄 라이선스
|
||||
## 라이선스
|
||||
|
||||
MIT - 자유롭게 사용하고, 필요에 따라 수정하고, 가능하다면 기여해 주세요.
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ Running 3 tests using 3 workers
|
||||
╔══════════════════════════════════════════════════════════════╗
|
||||
║ E2E 테스트 결과 ║
|
||||
╠══════════════════════════════════════════════════════════════╣
|
||||
║ 상태: ✅ 모든 테스트 통과 ║
|
||||
║ 상태: PASS: 모든 테스트 통과 ║
|
||||
║ 전체: 3개 테스트 ║
|
||||
║ 통과: 3 (100%) ║
|
||||
║ 실패: 0 ║
|
||||
@@ -191,15 +191,15 @@ Running 3 tests using 3 workers
|
||||
╚══════════════════════════════════════════════════════════════╝
|
||||
|
||||
아티팩트:
|
||||
📸 스크린샷: 2개 파일
|
||||
📹 비디오: 0개 파일 (실패 시에만)
|
||||
🔍 트레이스: 0개 파일 (실패 시에만)
|
||||
📊 HTML 보고서: playwright-report/index.html
|
||||
스크린샷: 2개 파일
|
||||
비디오: 0개 파일 (실패 시에만)
|
||||
트레이스: 0개 파일 (실패 시에만)
|
||||
HTML 보고서: playwright-report/index.html
|
||||
|
||||
보고서 확인: npx playwright show-report
|
||||
```
|
||||
|
||||
✅ CI/CD 통합 준비가 완료된 E2E 테스트 모음!
|
||||
PASS: CI/CD 통합 준비가 완료된 E2E 테스트 모음!
|
||||
````
|
||||
|
||||
## 테스트 아티팩트
|
||||
@@ -235,7 +235,7 @@ open artifacts/search-results.png
|
||||
테스트가 간헐적으로 실패하는 경우:
|
||||
|
||||
```
|
||||
⚠️ 불안정한 테스트 감지됨: tests/e2e/markets/trade.spec.ts
|
||||
WARNING: 불안정한 테스트 감지됨: tests/e2e/markets/trade.spec.ts
|
||||
|
||||
테스트가 10회 중 7회 통과 (70% 통과율)
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ ok project/internal/handler 0.023s
|
||||
| 수정된 파일 | 2 |
|
||||
| 남은 이슈 | 0 |
|
||||
|
||||
Build 상태: ✅ 성공
|
||||
Build 상태: PASS: 성공
|
||||
````
|
||||
|
||||
## 자주 발생하는 에러
|
||||
|
||||
@@ -124,16 +124,16 @@ return fmt.Errorf("get user %s: %w", userID, err)
|
||||
- HIGH: 1
|
||||
- MEDIUM: 0
|
||||
|
||||
권장: ❌ CRITICAL 이슈가 수정될 때까지 merge 차단
|
||||
권장: FAIL: CRITICAL 이슈가 수정될 때까지 merge 차단
|
||||
````
|
||||
|
||||
## 승인 기준
|
||||
|
||||
| 상태 | 조건 |
|
||||
|------|------|
|
||||
| ✅ 승인 | CRITICAL 또는 HIGH 이슈 없음 |
|
||||
| ⚠️ 경고 | MEDIUM 이슈만 있음 (주의하여 merge) |
|
||||
| ❌ 차단 | CRITICAL 또는 HIGH 이슈 발견 |
|
||||
| PASS: 승인 | CRITICAL 또는 HIGH 이슈 없음 |
|
||||
| WARNING: 경고 | MEDIUM 이슈만 있음 (주의하여 merge) |
|
||||
| FAIL: 차단 | CRITICAL 또는 HIGH 이슈 발견 |
|
||||
|
||||
## 다른 커맨드와의 연동
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ Dead Code Cleanup
|
||||
건너뜀: 항목 2개 (테스트 실패)
|
||||
절감: 약 450줄 제거
|
||||
──────────────────────────────
|
||||
모든 테스트 통과 ✅
|
||||
모든 테스트 통과 PASS:
|
||||
```
|
||||
|
||||
## 규칙
|
||||
|
||||
@@ -133,7 +133,7 @@ FAIL lib/liquidity.test.ts
|
||||
1 test failed, 0 passed
|
||||
```
|
||||
|
||||
✅ 예상대로 테스트 실패. 구현 준비 완료.
|
||||
PASS: 예상대로 테스트 실패. 구현 준비 완료.
|
||||
|
||||
## 4단계: 최소한의 코드 구현 (GREEN)
|
||||
|
||||
@@ -179,7 +179,7 @@ PASS lib/liquidity.test.ts
|
||||
3 tests passed
|
||||
```
|
||||
|
||||
✅ 모든 테스트 통과!
|
||||
PASS: 모든 테스트 통과!
|
||||
|
||||
## 6단계: 리팩토링 (IMPROVE)
|
||||
|
||||
@@ -236,7 +236,7 @@ PASS lib/liquidity.test.ts
|
||||
3 tests passed
|
||||
```
|
||||
|
||||
✅ 리팩토링 완료, 테스트 여전히 통과!
|
||||
PASS: 리팩토링 완료, 테스트 여전히 통과!
|
||||
|
||||
## 8단계: 커버리지 확인
|
||||
|
||||
@@ -247,10 +247,10 @@ File | % Stmts | % Branch | % Funcs | % Lines
|
||||
---------------|---------|----------|---------|--------
|
||||
liquidity.ts | 100 | 100 | 100 | 100
|
||||
|
||||
Coverage: 100% ✅ (목표: 80%)
|
||||
Coverage: 100% PASS: (목표: 80%)
|
||||
```
|
||||
|
||||
✅ TDD 세션 완료!
|
||||
PASS: TDD 세션 완료!
|
||||
````
|
||||
|
||||
## TDD 모범 사례
|
||||
|
||||
@@ -62,7 +62,7 @@ description: 테스트 커버리지를 분석하고, 80% 이상을 목표로 누
|
||||
src/services/auth.ts 45% 88%
|
||||
src/utils/validation.ts 32% 82%
|
||||
──────────────────────────────
|
||||
전체: 67% 84% ✅
|
||||
전체: 67% 84% PASS:
|
||||
```
|
||||
|
||||
## 집중 영역
|
||||
|
||||
@@ -23,7 +23,7 @@ origin: ECC
|
||||
### RESTful API 구조
|
||||
|
||||
```typescript
|
||||
// ✅ Resource-based URLs
|
||||
// PASS: Resource-based URLs
|
||||
GET /api/markets # List resources
|
||||
GET /api/markets/:id # Get single resource
|
||||
POST /api/markets # Create resource
|
||||
@@ -31,7 +31,7 @@ PUT /api/markets/:id # Replace resource
|
||||
PATCH /api/markets/:id # Update resource
|
||||
DELETE /api/markets/:id # Delete resource
|
||||
|
||||
// ✅ Query parameters for filtering, sorting, pagination
|
||||
// PASS: Query parameters for filtering, sorting, pagination
|
||||
GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
||||
```
|
||||
|
||||
@@ -132,7 +132,7 @@ export default withAuth(async (req, res) => {
|
||||
### 쿼리 최적화
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Select only needed columns
|
||||
// PASS: GOOD: Select only needed columns
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('id, name, status, volume')
|
||||
@@ -140,7 +140,7 @@ const { data } = await supabase
|
||||
.order('volume', { ascending: false })
|
||||
.limit(10)
|
||||
|
||||
// ❌ BAD: Select everything
|
||||
// FAIL: BAD: Select everything
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('*')
|
||||
@@ -149,13 +149,13 @@ const { data } = await supabase
|
||||
### N+1 쿼리 방지
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: N+1 query problem
|
||||
// FAIL: BAD: N+1 query problem
|
||||
const markets = await getMarkets()
|
||||
for (const market of markets) {
|
||||
market.creator = await getUser(market.creator_id) // N queries
|
||||
}
|
||||
|
||||
// ✅ GOOD: Batch fetch
|
||||
// PASS: GOOD: Batch fetch
|
||||
const markets = await getMarkets()
|
||||
const creatorIds = markets.map(m => m.creator_id)
|
||||
const creators = await getUsers(creatorIds) // 1 query
|
||||
|
||||
@@ -96,7 +96,7 @@ ORDER BY hour DESC;
|
||||
### 효율적인 필터링
|
||||
|
||||
```sql
|
||||
-- ✅ 좋음: 인덱스된 컬럼을 먼저 사용
|
||||
-- PASS: 좋음: 인덱스된 컬럼을 먼저 사용
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE date >= '2025-01-01'
|
||||
@@ -105,7 +105,7 @@ WHERE date >= '2025-01-01'
|
||||
ORDER BY date DESC
|
||||
LIMIT 100;
|
||||
|
||||
-- ❌ 나쁨: 비인덱스 컬럼을 먼저 필터링
|
||||
-- FAIL: 나쁨: 비인덱스 컬럼을 먼저 필터링
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE volume > 1000
|
||||
@@ -116,7 +116,7 @@ WHERE volume > 1000
|
||||
### 집계
|
||||
|
||||
```sql
|
||||
-- ✅ 좋음: ClickHouse 전용 집계 함수를 사용
|
||||
-- PASS: 좋음: ClickHouse 전용 집계 함수를 사용
|
||||
SELECT
|
||||
toStartOfDay(created_at) AS day,
|
||||
market_id,
|
||||
@@ -129,7 +129,7 @@ WHERE created_at >= today() - INTERVAL 7 DAY
|
||||
GROUP BY day, market_id
|
||||
ORDER BY day DESC, total_volume DESC;
|
||||
|
||||
-- ✅ 백분위수에는 quantile 사용 (percentile보다 효율적)
|
||||
-- PASS: 백분위수에는 quantile 사용 (percentile보다 효율적)
|
||||
SELECT
|
||||
quantile(0.50)(trade_size) AS median,
|
||||
quantile(0.95)(trade_size) AS p95,
|
||||
@@ -172,7 +172,7 @@ const clickhouse = new ClickHouse({
|
||||
}
|
||||
})
|
||||
|
||||
// ✅ 배치 삽입 (효율적)
|
||||
// PASS: 배치 삽입 (효율적)
|
||||
async function bulkInsertTrades(trades: Trade[]) {
|
||||
const rows = trades.map(trade => ({
|
||||
id: trade.id,
|
||||
@@ -185,7 +185,7 @@ async function bulkInsertTrades(trades: Trade[]) {
|
||||
await clickhouse.insert('trades', rows)
|
||||
}
|
||||
|
||||
// ❌ 개별 삽입 (느림)
|
||||
// FAIL: 개별 삽입 (느림)
|
||||
async function insertTrade(trade: Trade) {
|
||||
// 루프 안에서 이렇게 하지 마세요!
|
||||
await clickhouse.query(`
|
||||
|
||||
@@ -48,12 +48,12 @@ origin: ECC
|
||||
### 변수 네이밍
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Descriptive names
|
||||
// PASS: GOOD: Descriptive names
|
||||
const marketSearchQuery = 'election'
|
||||
const isUserAuthenticated = true
|
||||
const totalRevenue = 1000
|
||||
|
||||
// ❌ BAD: Unclear names
|
||||
// FAIL: BAD: Unclear names
|
||||
const q = 'election'
|
||||
const flag = true
|
||||
const x = 1000
|
||||
@@ -62,12 +62,12 @@ const x = 1000
|
||||
### 함수 네이밍
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Verb-noun pattern
|
||||
// PASS: GOOD: Verb-noun pattern
|
||||
async function fetchMarketData(marketId: string) { }
|
||||
function calculateSimilarity(a: number[], b: number[]) { }
|
||||
function isValidEmail(email: string): boolean { }
|
||||
|
||||
// ❌ BAD: Unclear or noun-only
|
||||
// FAIL: BAD: Unclear or noun-only
|
||||
async function market(id: string) { }
|
||||
function similarity(a, b) { }
|
||||
function email(e) { }
|
||||
@@ -76,7 +76,7 @@ function email(e) { }
|
||||
### 불변성 패턴 (필수)
|
||||
|
||||
```typescript
|
||||
// ✅ ALWAYS use spread operator
|
||||
// PASS: ALWAYS use spread operator
|
||||
const updatedUser = {
|
||||
...user,
|
||||
name: 'New Name'
|
||||
@@ -84,7 +84,7 @@ const updatedUser = {
|
||||
|
||||
const updatedArray = [...items, newItem]
|
||||
|
||||
// ❌ NEVER mutate directly
|
||||
// FAIL: NEVER mutate directly
|
||||
user.name = 'New Name' // BAD
|
||||
items.push(newItem) // BAD
|
||||
```
|
||||
@@ -92,7 +92,7 @@ items.push(newItem) // BAD
|
||||
### 에러 처리
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Comprehensive error handling
|
||||
// PASS: GOOD: Comprehensive error handling
|
||||
async function fetchData(url: string) {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
@@ -108,7 +108,7 @@ async function fetchData(url: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ BAD: No error handling
|
||||
// FAIL: BAD: No error handling
|
||||
async function fetchData(url) {
|
||||
const response = await fetch(url)
|
||||
return response.json()
|
||||
@@ -118,14 +118,14 @@ async function fetchData(url) {
|
||||
### Async/Await 모범 사례
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Parallel execution when possible
|
||||
// PASS: GOOD: Parallel execution when possible
|
||||
const [users, markets, stats] = await Promise.all([
|
||||
fetchUsers(),
|
||||
fetchMarkets(),
|
||||
fetchStats()
|
||||
])
|
||||
|
||||
// ❌ BAD: Sequential when unnecessary
|
||||
// FAIL: BAD: Sequential when unnecessary
|
||||
const users = await fetchUsers()
|
||||
const markets = await fetchMarkets()
|
||||
const stats = await fetchStats()
|
||||
@@ -134,7 +134,7 @@ const stats = await fetchStats()
|
||||
### 타입 안전성
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Proper types
|
||||
// PASS: GOOD: Proper types
|
||||
interface Market {
|
||||
id: string
|
||||
name: string
|
||||
@@ -146,7 +146,7 @@ function getMarket(id: string): Promise<Market> {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// ❌ BAD: Using 'any'
|
||||
// FAIL: BAD: Using 'any'
|
||||
function getMarket(id: any): Promise<any> {
|
||||
// Implementation
|
||||
}
|
||||
@@ -157,7 +157,7 @@ function getMarket(id: any): Promise<any> {
|
||||
### 컴포넌트 구조
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Functional component with types
|
||||
// PASS: GOOD: Functional component with types
|
||||
interface ButtonProps {
|
||||
children: React.ReactNode
|
||||
onClick: () => void
|
||||
@@ -182,7 +182,7 @@ export function Button({
|
||||
)
|
||||
}
|
||||
|
||||
// ❌ BAD: No types, unclear structure
|
||||
// FAIL: BAD: No types, unclear structure
|
||||
export function Button(props) {
|
||||
return <button onClick={props.onClick}>{props.children}</button>
|
||||
}
|
||||
@@ -191,7 +191,7 @@ export function Button(props) {
|
||||
### 커스텀 Hook
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Reusable custom hook
|
||||
// PASS: GOOD: Reusable custom hook
|
||||
export function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
||||
|
||||
@@ -213,25 +213,25 @@ const debouncedQuery = useDebounce(searchQuery, 500)
|
||||
### 상태 관리
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Proper state updates
|
||||
// PASS: GOOD: Proper state updates
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
// Functional update for state based on previous state
|
||||
setCount(prev => prev + 1)
|
||||
|
||||
// ❌ BAD: Direct state reference
|
||||
// FAIL: BAD: Direct state reference
|
||||
setCount(count + 1) // Can be stale in async scenarios
|
||||
```
|
||||
|
||||
### 조건부 렌더링
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Clear conditional rendering
|
||||
// PASS: GOOD: Clear conditional rendering
|
||||
{isLoading && <Spinner />}
|
||||
{error && <ErrorMessage error={error} />}
|
||||
{data && <DataDisplay data={data} />}
|
||||
|
||||
// ❌ BAD: Ternary hell
|
||||
// FAIL: BAD: Ternary hell
|
||||
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
|
||||
```
|
||||
|
||||
@@ -254,7 +254,7 @@ GET /api/markets?status=active&limit=10&offset=0
|
||||
### 응답 형식
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Consistent response structure
|
||||
// PASS: GOOD: Consistent response structure
|
||||
interface ApiResponse<T> {
|
||||
success: boolean
|
||||
data?: T
|
||||
@@ -285,7 +285,7 @@ return NextResponse.json({
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
// ✅ GOOD: Schema validation
|
||||
// PASS: GOOD: Schema validation
|
||||
const CreateMarketSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
description: z.string().min(1).max(2000),
|
||||
@@ -348,14 +348,14 @@ types/market.types.ts # camelCase with .types suffix
|
||||
### 주석을 작성해야 하는 경우
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Explain WHY, not WHAT
|
||||
// PASS: GOOD: Explain WHY, not WHAT
|
||||
// Use exponential backoff to avoid overwhelming the API during outages
|
||||
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
|
||||
|
||||
// Deliberately using mutation here for performance with large arrays
|
||||
items.push(newItem)
|
||||
|
||||
// ❌ BAD: Stating the obvious
|
||||
// FAIL: BAD: Stating the obvious
|
||||
// Increment counter by 1
|
||||
count++
|
||||
|
||||
@@ -395,12 +395,12 @@ export async function searchMarkets(
|
||||
```typescript
|
||||
import { useMemo, useCallback } from 'react'
|
||||
|
||||
// ✅ GOOD: Memoize expensive computations
|
||||
// PASS: GOOD: Memoize expensive computations
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return [...markets].sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ GOOD: Memoize callbacks
|
||||
// PASS: GOOD: Memoize callbacks
|
||||
const handleSearch = useCallback((query: string) => {
|
||||
setSearchQuery(query)
|
||||
}, [])
|
||||
@@ -411,7 +411,7 @@ const handleSearch = useCallback((query: string) => {
|
||||
```typescript
|
||||
import { lazy, Suspense } from 'react'
|
||||
|
||||
// ✅ GOOD: Lazy load heavy components
|
||||
// PASS: GOOD: Lazy load heavy components
|
||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||
|
||||
export function Dashboard() {
|
||||
@@ -426,13 +426,13 @@ export function Dashboard() {
|
||||
### 데이터베이스 쿼리
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Select only needed columns
|
||||
// PASS: GOOD: Select only needed columns
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('id, name, status')
|
||||
.limit(10)
|
||||
|
||||
// ❌ BAD: Select everything
|
||||
// FAIL: BAD: Select everything
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('*')
|
||||
@@ -459,12 +459,12 @@ test('calculates similarity correctly', () => {
|
||||
### 테스트 네이밍
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Descriptive test names
|
||||
// PASS: GOOD: Descriptive test names
|
||||
test('returns empty array when no markets match query', () => { })
|
||||
test('throws error when OpenAI API key is missing', () => { })
|
||||
test('falls back to substring search when Redis unavailable', () => { })
|
||||
|
||||
// ❌ BAD: Vague test names
|
||||
// FAIL: BAD: Vague test names
|
||||
test('works', () => { })
|
||||
test('test search', () => { })
|
||||
```
|
||||
@@ -475,12 +475,12 @@ test('test search', () => { })
|
||||
|
||||
### 1. 긴 함수
|
||||
```typescript
|
||||
// ❌ BAD: Function > 50 lines
|
||||
// FAIL: BAD: Function > 50 lines
|
||||
function processMarketData() {
|
||||
// 100 lines of code
|
||||
}
|
||||
|
||||
// ✅ GOOD: Split into smaller functions
|
||||
// PASS: GOOD: Split into smaller functions
|
||||
function processMarketData() {
|
||||
const validated = validateData()
|
||||
const transformed = transformData(validated)
|
||||
@@ -490,7 +490,7 @@ function processMarketData() {
|
||||
|
||||
### 2. 깊은 중첩
|
||||
```typescript
|
||||
// ❌ BAD: 5+ levels of nesting
|
||||
// FAIL: BAD: 5+ levels of nesting
|
||||
if (user) {
|
||||
if (user.isAdmin) {
|
||||
if (market) {
|
||||
@@ -503,7 +503,7 @@ if (user) {
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ GOOD: Early returns
|
||||
// PASS: GOOD: Early returns
|
||||
if (!user) return
|
||||
if (!user.isAdmin) return
|
||||
if (!market) return
|
||||
@@ -515,11 +515,11 @@ if (!hasPermission) return
|
||||
|
||||
### 3. 매직 넘버
|
||||
```typescript
|
||||
// ❌ BAD: Unexplained numbers
|
||||
// FAIL: BAD: Unexplained numbers
|
||||
if (retryCount > 3) { }
|
||||
setTimeout(callback, 500)
|
||||
|
||||
// ✅ GOOD: Named constants
|
||||
// PASS: GOOD: Named constants
|
||||
const MAX_RETRIES = 3
|
||||
const DEBOUNCE_DELAY_MS = 500
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ React, Next.js 및 고성능 사용자 인터페이스를 위한 모던 프론
|
||||
### 상속보다 합성
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Component composition
|
||||
// PASS: GOOD: Component composition
|
||||
interface CardProps {
|
||||
children: React.ReactNode
|
||||
variant?: 'default' | 'outlined'
|
||||
@@ -304,17 +304,17 @@ export function useMarkets() {
|
||||
### 메모이제이션
|
||||
|
||||
```typescript
|
||||
// ✅ useMemo for expensive computations
|
||||
// PASS: useMemo for expensive computations
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return [...markets].sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ useCallback for functions passed to children
|
||||
// PASS: useCallback for functions passed to children
|
||||
const handleSearch = useCallback((query: string) => {
|
||||
setSearchQuery(query)
|
||||
}, [])
|
||||
|
||||
// ✅ React.memo for pure components
|
||||
// PASS: React.memo for pure components
|
||||
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||
return (
|
||||
<div className="market-card">
|
||||
@@ -330,7 +330,7 @@ export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||
```typescript
|
||||
import { lazy, Suspense } from 'react'
|
||||
|
||||
// ✅ Lazy load heavy components
|
||||
// PASS: Lazy load heavy components
|
||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
|
||||
|
||||
@@ -525,7 +525,7 @@ export class ErrorBoundary extends React.Component<
|
||||
```typescript
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
|
||||
// ✅ List animations
|
||||
// PASS: List animations
|
||||
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
@@ -544,7 +544,7 @@ export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ Modal animations
|
||||
// PASS: Modal animations
|
||||
export function Modal({ isOpen, onClose, children }: ModalProps) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
|
||||
@@ -36,12 +36,12 @@ origin: ECC
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ DISPATCH │─────▶│ EVALUATE │ │
|
||||
│ │ DISPATCH │─────│ EVALUATE │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ ▲ │ │
|
||||
│ │ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ LOOP │◀─────│ REFINE │ │
|
||||
│ │ LOOP │─────│ REFINE │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
│ Max 3 cycles, then proceed │
|
||||
|
||||
@@ -140,10 +140,10 @@ await db.query(
|
||||
|
||||
#### JWT 토큰 처리
|
||||
```typescript
|
||||
// ❌ WRONG: localStorage (vulnerable to XSS)
|
||||
// FAIL: WRONG: localStorage (vulnerable to XSS)
|
||||
localStorage.setItem('token', token)
|
||||
|
||||
// ✅ CORRECT: httpOnly cookies
|
||||
// PASS: CORRECT: httpOnly cookies
|
||||
res.setHeader('Set-Cookie',
|
||||
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
|
||||
```
|
||||
@@ -302,18 +302,18 @@ app.use('/api/search', searchLimiter)
|
||||
|
||||
#### 로깅
|
||||
```typescript
|
||||
// ❌ WRONG: Logging sensitive data
|
||||
// FAIL: WRONG: Logging sensitive data
|
||||
console.log('User login:', { email, password })
|
||||
console.log('Payment:', { cardNumber, cvv })
|
||||
|
||||
// ✅ CORRECT: Redact sensitive data
|
||||
// PASS: CORRECT: Redact sensitive data
|
||||
console.log('User login:', { email, userId })
|
||||
console.log('Payment:', { last4: card.last4, userId })
|
||||
```
|
||||
|
||||
#### 에러 메시지
|
||||
```typescript
|
||||
// ❌ WRONG: Exposing internal details
|
||||
// FAIL: WRONG: Exposing internal details
|
||||
catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message, stack: error.stack },
|
||||
@@ -321,7 +321,7 @@ catch (error) {
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ CORRECT: Generic error messages
|
||||
// PASS: CORRECT: Generic error messages
|
||||
catch (error) {
|
||||
console.error('Internal error:', error)
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#### 최소 권한 원칙
|
||||
|
||||
```yaml
|
||||
# ✅ CORRECT: Minimal permissions
|
||||
# PASS: CORRECT: Minimal permissions
|
||||
iam_role:
|
||||
permissions:
|
||||
- s3:GetObject # Only read access
|
||||
@@ -32,7 +32,7 @@ iam_role:
|
||||
resources:
|
||||
- arn:aws:s3:::my-bucket/* # Specific bucket only
|
||||
|
||||
# ❌ WRONG: Overly broad permissions
|
||||
# FAIL: WRONG: Overly broad permissions
|
||||
iam_role:
|
||||
permissions:
|
||||
- s3:* # All S3 actions
|
||||
@@ -65,14 +65,14 @@ aws iam enable-mfa-device \
|
||||
#### 클라우드 시크릿 매니저
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECT: Use cloud secrets manager
|
||||
// PASS: CORRECT: Use cloud secrets manager
|
||||
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
|
||||
|
||||
const client = new SecretsManager({ region: 'us-east-1' });
|
||||
const secret = await client.getSecretValue({ SecretId: 'prod/api-key' });
|
||||
const apiKey = JSON.parse(secret.SecretString).key;
|
||||
|
||||
// ❌ WRONG: Hardcoded or in environment variables only
|
||||
// FAIL: WRONG: Hardcoded or in environment variables only
|
||||
const apiKey = process.env.API_KEY; // Not rotated, not audited
|
||||
```
|
||||
|
||||
@@ -99,7 +99,7 @@ aws secretsmanager rotate-secret \
|
||||
#### VPC 및 방화벽 구성
|
||||
|
||||
```terraform
|
||||
# ✅ CORRECT: Restricted security group
|
||||
# PASS: CORRECT: Restricted security group
|
||||
resource "aws_security_group" "app" {
|
||||
name = "app-sg"
|
||||
|
||||
@@ -118,7 +118,7 @@ resource "aws_security_group" "app" {
|
||||
}
|
||||
}
|
||||
|
||||
# ❌ WRONG: Open to the internet
|
||||
# FAIL: WRONG: Open to the internet
|
||||
resource "aws_security_group" "bad" {
|
||||
ingress {
|
||||
from_port = 0
|
||||
@@ -142,7 +142,7 @@ resource "aws_security_group" "bad" {
|
||||
#### CloudWatch/로깅 구성
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECT: Comprehensive logging
|
||||
// PASS: CORRECT: Comprehensive logging
|
||||
import { CloudWatchLogsClient, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
|
||||
|
||||
const logSecurityEvent = async (event: SecurityEvent) => {
|
||||
@@ -177,7 +177,7 @@ const logSecurityEvent = async (event: SecurityEvent) => {
|
||||
#### 보안 파이프라인 구성
|
||||
|
||||
```yaml
|
||||
# ✅ CORRECT: Secure GitHub Actions workflow
|
||||
# PASS: CORRECT: Secure GitHub Actions workflow
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
@@ -237,7 +237,7 @@ jobs:
|
||||
#### Cloudflare 보안 구성
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECT: Cloudflare Workers with security headers
|
||||
// PASS: CORRECT: Cloudflare Workers with security headers
|
||||
export default {
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const response = await fetch(request);
|
||||
@@ -281,7 +281,7 @@ export default {
|
||||
#### 자동 백업
|
||||
|
||||
```terraform
|
||||
# ✅ CORRECT: Automated RDS backups
|
||||
# PASS: CORRECT: Automated RDS backups
|
||||
resource "aws_db_instance" "main" {
|
||||
allocated_storage = 20
|
||||
engine = "postgres"
|
||||
@@ -327,10 +327,10 @@ resource "aws_db_instance" "main" {
|
||||
### S3 버킷 노출
|
||||
|
||||
```bash
|
||||
# ❌ WRONG: Public bucket
|
||||
# FAIL: WRONG: Public bucket
|
||||
aws s3api put-bucket-acl --bucket my-bucket --acl public-read
|
||||
|
||||
# ✅ CORRECT: Private bucket with specific access
|
||||
# PASS: CORRECT: Private bucket with specific access
|
||||
aws s3api put-bucket-acl --bucket my-bucket --acl private
|
||||
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
||||
```
|
||||
@@ -338,12 +338,12 @@ aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
||||
### RDS 공개 접근
|
||||
|
||||
```terraform
|
||||
# ❌ WRONG
|
||||
# FAIL: WRONG
|
||||
resource "aws_db_instance" "bad" {
|
||||
publicly_accessible = true # NEVER do this!
|
||||
}
|
||||
|
||||
# ✅ CORRECT
|
||||
# PASS: CORRECT
|
||||
resource "aws_db_instance" "good" {
|
||||
publicly_accessible = false
|
||||
vpc_security_group_ids = [aws_security_group.db.id]
|
||||
|
||||
Reference in New Issue
Block a user