mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
docs: address Korean translation review feedback
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
**Language:** English | [繁體中文](docs/zh-TW/README.md) | [한국어](docs/ko-KR/README.md)
|
||||
**Language:** English | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md) | [한국어](docs/ko-KR/README.md)
|
||||
|
||||
# Everything Claude Code
|
||||
|
||||
|
||||
@@ -195,6 +195,15 @@ model: sonnet
|
||||
| `tools` | 필요한 것만 포함 | `Read, Write, Edit, Bash, Grep, Glob, WebFetch, Task` |
|
||||
| `model` | 복잡도 수준 | `haiku` (단순), `sonnet` (코딩), `opus` (복잡) |
|
||||
|
||||
### 예시 에이전트
|
||||
|
||||
| 에이전트 | 용도 |
|
||||
|----------|------|
|
||||
| `tdd-guide.md` | 테스트 주도 개발 |
|
||||
| `code-reviewer.md` | 코드 리뷰 |
|
||||
| `security-reviewer.md` | 보안 점검 |
|
||||
| `build-error-resolver.md` | 빌드 오류 수정 |
|
||||
|
||||
---
|
||||
|
||||
## 훅 기여하기
|
||||
@@ -216,6 +225,68 @@ hooks/hooks.json
|
||||
| `SessionStart` | 세션 시작 시 | 컨텍스트 로딩 |
|
||||
| `Stop` | 세션 종료 시 | 정리, 감사 |
|
||||
|
||||
### 훅 형식
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "tool == \"Bash\" && tool_input.command matches \"rm -rf /\"",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "echo '[Hook] BLOCKED: Dangerous command' && exit 1"
|
||||
}
|
||||
],
|
||||
"description": "위험한 rm 명령 차단"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Matcher 문법
|
||||
|
||||
```javascript
|
||||
// 특정 도구 매칭
|
||||
tool == "Bash"
|
||||
tool == "Edit"
|
||||
tool == "Write"
|
||||
|
||||
// 입력 패턴 매칭
|
||||
tool_input.command matches "npm install"
|
||||
tool_input.file_path matches "\\.tsx?$"
|
||||
|
||||
// 조건 결합
|
||||
tool == "Bash" && tool_input.command matches "git push"
|
||||
```
|
||||
|
||||
### 훅 예시
|
||||
|
||||
```json
|
||||
// tmux 밖 dev 서버 차단
|
||||
{
|
||||
"matcher": "tool == \"Bash\" && tool_input.command matches \"npm run dev\"",
|
||||
"hooks": [{"type": "command", "command": "echo '개발 서버는 tmux에서 실행하세요' && exit 1"}],
|
||||
"description": "dev 서버를 tmux에서 실행하도록 강제"
|
||||
}
|
||||
|
||||
// TypeScript 편집 후 자동 포맷
|
||||
{
|
||||
"matcher": "tool == \"Edit\" && tool_input.file_path matches \"\\.tsx?$\"",
|
||||
"hooks": [{"type": "command", "command": "npx prettier --write \"$file_path\""}],
|
||||
"description": "TypeScript 파일 편집 후 포맷"
|
||||
}
|
||||
|
||||
// git push 전 경고
|
||||
{
|
||||
"matcher": "tool == \"Bash\" && tool_input.command matches \"git push\"",
|
||||
"hooks": [{"type": "command", "command": "echo '[Hook] push 전에 변경사항을 다시 검토하세요'"}],
|
||||
"description": "push 전 검토 리마인더"
|
||||
}
|
||||
```
|
||||
|
||||
### 훅 체크리스트
|
||||
|
||||
- [ ] Matcher가 구체적 (너무 광범위하지 않게)
|
||||
@@ -236,6 +307,36 @@ hooks/hooks.json
|
||||
commands/your-command.md
|
||||
```
|
||||
|
||||
### 커맨드 템플릿
|
||||
|
||||
```markdown
|
||||
---
|
||||
description: /help에 표시되는 간단한 설명
|
||||
---
|
||||
|
||||
# 커맨드 이름
|
||||
|
||||
## 목적
|
||||
|
||||
이 커맨드가 수행하는 작업.
|
||||
|
||||
## 사용법
|
||||
|
||||
\`\`\`
|
||||
/your-command [args]
|
||||
\`\`\`
|
||||
|
||||
## 워크플로우
|
||||
|
||||
1. 첫 번째 단계
|
||||
2. 두 번째 단계
|
||||
3. 마지막 단계
|
||||
|
||||
## 출력
|
||||
|
||||
사용자가 받는 결과.
|
||||
```
|
||||
|
||||
### 커맨드 예시
|
||||
|
||||
| 커맨드 | 용도 |
|
||||
@@ -247,6 +348,34 @@ commands/your-command.md
|
||||
|
||||
---
|
||||
|
||||
## 크로스-하네스 및 번역
|
||||
|
||||
### 스킬 서브셋 (Codex 및 Cursor)
|
||||
|
||||
ECC는 다른 하네스를 위한 스킬 서브셋도 제공합니다:
|
||||
|
||||
- **Codex:** `.agents/skills/` — `agents/openai.yaml`에 나열된 스킬이 Codex에서 로드됩니다.
|
||||
- **Cursor:** `.cursor/skills/` — Cursor용 스킬 서브셋이 별도로 포함됩니다.
|
||||
|
||||
Codex 또는 Cursor에서도 제공해야 하는 **새 스킬**을 추가한다면:
|
||||
|
||||
1. 먼저 `skills/your-skill-name/` 아래에 일반적인 ECC 스킬로 추가합니다.
|
||||
2. **Codex**에서도 제공해야 하면 `.agents/skills/`에 반영하고, 필요하면 `agents/openai.yaml`에도 참조를 추가합니다.
|
||||
3. **Cursor**에서도 제공해야 하면 Cursor 레이아웃에 맞게 `.cursor/skills/` 아래에 추가합니다.
|
||||
|
||||
기존 디렉터리의 구조를 확인한 뒤 같은 패턴을 따르세요. 이 서브셋 동기화는 수동이므로 PR 설명에 반영 여부를 적어 두는 것이 좋습니다.
|
||||
|
||||
### 번역
|
||||
|
||||
번역 문서는 `docs/` 아래에 있습니다. 예: `docs/zh-CN`, `docs/zh-TW`, `docs/ja-JP`.
|
||||
|
||||
번역된 에이전트, 커맨드, 스킬을 변경한다면:
|
||||
|
||||
- 대응하는 번역 파일도 함께 업데이트하거나
|
||||
- 유지보수자/번역자가 후속 작업을 할 수 있도록 이슈를 열어 주세요.
|
||||
|
||||
---
|
||||
|
||||
## Pull Request 프로세스
|
||||
|
||||
### 1. PR 제목 형식
|
||||
|
||||
@@ -167,7 +167,7 @@ cd everything-claude-code
|
||||
3. **package.json**: `packageManager` 필드
|
||||
4. **락 파일**: package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb에서 감지
|
||||
5. **글로벌 설정**: `~/.claude/package-manager.json`
|
||||
6. **폴백**: 사용 가능한 첫 번째 패키지 매니저
|
||||
6. **폴백**: `npm`
|
||||
|
||||
패키지 매니저 설정 방법:
|
||||
|
||||
@@ -416,7 +416,7 @@ cp -r everything-claude-code/rules/typescript/* ~/.claude/rules/ # 사용하
|
||||
cp everything-claude-code/commands/*.md ~/.claude/commands/
|
||||
|
||||
# 스킬 복사
|
||||
cp -r everything-claude-code/.agents/skills/* ~/.claude/skills/
|
||||
cp -r everything-claude-code/skills/* ~/.claude/skills/
|
||||
cp -r everything-claude-code/skills/search-first ~/.claude/skills/
|
||||
```
|
||||
|
||||
@@ -498,7 +498,9 @@ rules/
|
||||
| 보안 취약점 찾기 | `/security-scan` | security-reviewer |
|
||||
| 사용하지 않는 코드 제거 | `/refactor-clean` | refactor-cleaner |
|
||||
| 문서 업데이트 | `/update-docs` | doc-updater |
|
||||
| Go 빌드 실패 수정 | `/go-build` | go-build-resolver |
|
||||
| Go 코드 리뷰 | `/go-review` | go-reviewer |
|
||||
| 데이터베이스 스키마/쿼리 리뷰 | `/code-review` + database-reviewer 에이전트 | database-reviewer |
|
||||
| Python 코드 리뷰 | `/python-review` | python-reviewer |
|
||||
|
||||
### 일반적인 워크플로우
|
||||
|
||||
@@ -38,20 +38,20 @@
|
||||
| Merge | merge | 확정 | 영문 유지 |
|
||||
| Repository | 저장소 | 확정 | |
|
||||
| Fork | Fork | 확정 | 영문 유지 |
|
||||
| Supabase | Supabase | - | 제품명 유지 |
|
||||
| Redis | Redis | - | 제품명 유지 |
|
||||
| Playwright | Playwright | - | 제품명 유지 |
|
||||
| TypeScript | TypeScript | - | 언어명 유지 |
|
||||
| JavaScript | JavaScript | - | 언어명 유지 |
|
||||
| Go/Golang | Go | - | 언어명 유지 |
|
||||
| React | React | - | 프레임워크명 유지 |
|
||||
| Next.js | Next.js | - | 프레임워크명 유지 |
|
||||
| PostgreSQL | PostgreSQL | - | 제품명 유지 |
|
||||
| Supabase | Supabase | 확정 | 제품명 유지 |
|
||||
| Redis | Redis | 확정 | 제품명 유지 |
|
||||
| Playwright | Playwright | 확정 | 제품명 유지 |
|
||||
| TypeScript | TypeScript | 확정 | 언어명 유지 |
|
||||
| JavaScript | JavaScript | 확정 | 언어명 유지 |
|
||||
| Go/Golang | Go | 확정 | 언어명 유지 |
|
||||
| React | React | 확정 | 프레임워크명 유지 |
|
||||
| Next.js | Next.js | 확정 | 프레임워크명 유지 |
|
||||
| PostgreSQL | PostgreSQL | 확정 | 제품명 유지 |
|
||||
| RLS (Row Level Security) | RLS(행 수준 보안) | 확정 | 최초 사용 시 전개 |
|
||||
| OWASP | OWASP | - | 영문 유지 |
|
||||
| XSS | XSS | - | 영문 유지 |
|
||||
| OWASP | OWASP | 확정 | 영문 유지 |
|
||||
| XSS | XSS | 확정 | 영문 유지 |
|
||||
| SQL Injection | SQL 인젝션 | 확정 | |
|
||||
| CSRF | CSRF | - | 영문 유지 |
|
||||
| CSRF | CSRF | 확정 | 영문 유지 |
|
||||
| Refactor | 리팩토링 | 확정 | |
|
||||
| Dead Code | 데드 코드 | 확정 | |
|
||||
| Lint/Linter | Lint | 확정 | 영문 유지 |
|
||||
@@ -70,11 +70,11 @@
|
||||
| Migration | 마이그레이션 | 확정 | |
|
||||
| Transaction | 트랜잭션 | 확정 | |
|
||||
| Concurrency | 동시성 | 확정 | |
|
||||
| Goroutine | Goroutine | - | Go 용어 유지 |
|
||||
| Goroutine | Goroutine | 확정 | Go 용어 유지 |
|
||||
| Channel | Channel | 확정 | Go 컨텍스트에서 유지 |
|
||||
| Mutex | Mutex | - | 영문 유지 |
|
||||
| Mutex | Mutex | 확정 | 영문 유지 |
|
||||
| Interface | 인터페이스 | 확정 | |
|
||||
| Struct | Struct | - | Go 용어 유지 |
|
||||
| Struct | Struct | 확정 | Go 용어 유지 |
|
||||
| Mock | Mock | 확정 | 테스트 용어 유지 |
|
||||
| Stub | Stub | 확정 | 테스트 용어 유지 |
|
||||
| Fixture | Fixture | 확정 | 테스트 용어 유지 |
|
||||
@@ -82,7 +82,7 @@
|
||||
| Snapshot | 스냅샷 | 확정 | |
|
||||
| Trace | 트레이스 | 확정 | |
|
||||
| Artifact | 아티팩트 | 확정 | |
|
||||
| CI/CD | CI/CD | - | 영문 유지 |
|
||||
| CI/CD | CI/CD | 확정 | 영문 유지 |
|
||||
| Pipeline | 파이프라인 | 확정 | |
|
||||
|
||||
---
|
||||
|
||||
@@ -87,6 +87,15 @@ docs/CODEMAPS/
|
||||
4. **실행 가능** — 실제로 작동하는 설정 커맨드 포함
|
||||
5. **상호 참조** — 관련 문서 링크
|
||||
|
||||
## 품질 체크리스트
|
||||
|
||||
- [ ] 실제 코드에서 코드맵 생성
|
||||
- [ ] 모든 파일 경로 존재 확인
|
||||
- [ ] 코드 예제가 컴파일 또는 실행됨
|
||||
- [ ] 링크 검증 완료
|
||||
- [ ] 최신 타임스탬프 업데이트
|
||||
- [ ] 오래된 참조 없음
|
||||
|
||||
## 업데이트 시점
|
||||
|
||||
**항상:** 새 주요 기능, API 라우트 변경, 의존성 추가/제거, 아키텍처 변경, 설정 프로세스 수정.
|
||||
|
||||
@@ -71,7 +71,7 @@ npx playwright show-report # HTML 보고서 보기
|
||||
|
||||
- **시맨틱 로케이터 사용**: `[data-testid="..."]` > CSS 셀렉터 > XPath
|
||||
- **시간이 아닌 조건 대기**: `waitForResponse()` > `waitForTimeout()`
|
||||
- **자동 대기 내장**: `page.locator().click()`은 자동 대기; `page.click()`은 아님
|
||||
- **자동 대기 내장**: `locator.click()`과 `page.click()` 모두 자동 대기를 제공하지만, 더 안정적인 `locator` 기반 API를 선호
|
||||
- **테스트 격리**: 각 테스트는 독립적; 공유 상태 없음
|
||||
- **빠른 실패**: 모든 핵심 단계에서 `expect()` 어설션 사용
|
||||
- **재시도 시 트레이스**: 실패 디버깅을 위해 `trace: 'on-first-retry'` 설정
|
||||
|
||||
@@ -21,10 +21,16 @@ model: sonnet
|
||||
기대 동작을 설명하는 실패하는 테스트 작성.
|
||||
|
||||
### 2. 테스트 실행 -- 실패 확인
|
||||
Node.js (npm):
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
언어 중립:
|
||||
- 프로젝트의 기본 테스트 명령을 실행하세요.
|
||||
- Python: `pytest`
|
||||
- Go: `go test ./...`
|
||||
|
||||
### 3. 최소한의 구현 작성 (GREEN)
|
||||
테스트를 통과하기에 충분한 코드만.
|
||||
|
||||
@@ -34,11 +40,17 @@ npm test
|
||||
중복 제거, 이름 개선, 최적화 -- 테스트는 그린 유지.
|
||||
|
||||
### 6. 커버리지 확인
|
||||
Node.js (npm):
|
||||
```bash
|
||||
npm run test:coverage
|
||||
# 필수: branches, functions, lines, statements 80% 이상
|
||||
```
|
||||
|
||||
언어 중립:
|
||||
- 프로젝트의 기본 커버리지 명령을 실행하세요.
|
||||
- Python: `pytest --cov`
|
||||
- Go: `go test ./... -cover`
|
||||
|
||||
## 필수 테스트 유형
|
||||
|
||||
| 유형 | 테스트 대상 | 시점 |
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
name: build-fix
|
||||
description: 최소한의 안전한 변경으로 build 및 타입 오류를 점진적으로 수정합니다.
|
||||
---
|
||||
|
||||
# Build 오류 수정
|
||||
|
||||
최소한의 안전한 변경으로 build 및 타입 오류를 점진적으로 수정합니다.
|
||||
@@ -14,7 +19,7 @@
|
||||
| `pom.xml` | `mvn compile` |
|
||||
| `build.gradle` | `./gradlew compileJava` |
|
||||
| `go.mod` | `go build ./...` |
|
||||
| `pyproject.toml` | `python -m py_compile` 또는 `mypy .` |
|
||||
| `pyproject.toml` | `python -m compileall .` 또는 `mypy .` |
|
||||
|
||||
## 2단계: 오류 파싱 및 그룹화
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
---
|
||||
name: checkpoint
|
||||
description: 워크플로우에서 checkpoint를 생성, 검증, 조회 또는 정리합니다.
|
||||
---
|
||||
|
||||
# Checkpoint 명령어
|
||||
|
||||
워크플로우에서 checkpoint를 생성하거나 검증합니다.
|
||||
|
||||
## 사용법
|
||||
|
||||
`/checkpoint [create|verify|list] [name]`
|
||||
`/checkpoint [create|verify|list|clear] [name]`
|
||||
|
||||
## Checkpoint 생성
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
- 800줄 초과 파일
|
||||
- 4단계 초과 중첩 깊이
|
||||
- 누락된 에러 처리
|
||||
- console.log 문
|
||||
- 디버그 로깅 문구(예: 개발용 로그/print 등)
|
||||
- TODO/FIXME 주석
|
||||
- 공개 API에 대한 JSDoc 누락
|
||||
- 활성 언어에 대한 공개 API 문서 누락(예: JSDoc/Go doc/Docstring 등)
|
||||
|
||||
**모범 사례 (MEDIUM):**
|
||||
- 변이(Mutation) 패턴 (불변 패턴을 사용하세요)
|
||||
|
||||
@@ -36,7 +36,7 @@ e2e-runner 에이전트가 수행하는 작업:
|
||||
|
||||
## 사용 예시
|
||||
|
||||
```
|
||||
````
|
||||
User: /e2e 마켓 검색 및 조회 흐름 테스트
|
||||
|
||||
Agent (e2e-runner):
|
||||
@@ -200,7 +200,7 @@ Running 3 tests using 3 workers
|
||||
```
|
||||
|
||||
✅ CI/CD 통합 준비가 완료된 E2E 테스트 모음!
|
||||
```
|
||||
````
|
||||
|
||||
## 테스트 아티팩트
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## 사용법
|
||||
|
||||
`/eval [define|check|report|list] [feature-name]`
|
||||
`/eval [define|check|report|list|clean] [feature-name]`
|
||||
|
||||
## 평가 정의
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ go mod tidy -v
|
||||
|
||||
## 예시 세션
|
||||
|
||||
```text
|
||||
````text
|
||||
User: /go-build
|
||||
|
||||
Agent:
|
||||
@@ -141,7 +141,7 @@ ok project/internal/handler 0.023s
|
||||
| 남은 이슈 | 0 |
|
||||
|
||||
Build 상태: ✅ 성공
|
||||
```
|
||||
````
|
||||
|
||||
## 자주 발생하는 에러
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ govulncheck ./...
|
||||
|
||||
## 사용 예시
|
||||
|
||||
```text
|
||||
````text
|
||||
User: /go-review
|
||||
|
||||
Agent:
|
||||
@@ -125,7 +125,7 @@ return fmt.Errorf("get user %s: %w", userID, err)
|
||||
- MEDIUM: 0
|
||||
|
||||
권장: ❌ CRITICAL 이슈가 수정될 때까지 merge 차단
|
||||
```
|
||||
````
|
||||
|
||||
## 승인 기준
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ node scripts/setup-package-manager.js --list
|
||||
3. **package.json**: `packageManager` 필드
|
||||
4. **락 파일**: package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb의 존재 여부
|
||||
5. **전역 설정**: `~/.claude/package-manager.json`
|
||||
6. **폴백**: 사용 가능한 첫 번째 패키지 매니저 (pnpm > bun > yarn > npm)
|
||||
6. **폴백**: `npm`
|
||||
|
||||
## 설정 파일
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ REPEAT: 다음 기능/시나리오
|
||||
|
||||
## 사용 예시
|
||||
|
||||
```
|
||||
````
|
||||
User: /tdd 마켓 유동성 점수를 계산하는 함수가 필요합니다
|
||||
|
||||
Agent (tdd-guide):
|
||||
@@ -251,7 +251,7 @@ Coverage: 100% ✅ (목표: 80%)
|
||||
```
|
||||
|
||||
✅ TDD 세션 완료!
|
||||
```
|
||||
````
|
||||
|
||||
## TDD 모범 사례
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
name: test-coverage
|
||||
description: 테스트 커버리지를 분석하고, 80% 이상을 목표로 누락된 테스트를 식별하고 생성합니다.
|
||||
---
|
||||
|
||||
# 테스트 커버리지
|
||||
|
||||
테스트 커버리지를 분석하고, 갭을 식별하며, 80% 이상 커버리지 달성을 위해 누락된 테스트를 생성합니다.
|
||||
|
||||
@@ -10,15 +10,16 @@
|
||||
|
||||
## 2단계: 코드맵 생성
|
||||
|
||||
`docs/CODEMAPS/` (또는 `.reports/codemaps/`)에 코드맵 생성 또는 업데이트:
|
||||
`docs/CODEMAPS/`에 코드맵 생성 또는 업데이트:
|
||||
|
||||
| 파일 | 내용 |
|
||||
|------|------|
|
||||
| `architecture.md` | 상위 시스템 다이어그램, 서비스 경계, 데이터 흐름 |
|
||||
| `INDEX.md` | 전체 코드베이스 개요와 영역별 링크 |
|
||||
| `backend.md` | API 라우트, 미들웨어 체인, 서비스 → 리포지토리 매핑 |
|
||||
| `frontend.md` | 페이지 트리, 컴포넌트 계층, 상태 관리 흐름 |
|
||||
| `data.md` | 데이터베이스 테이블, 관계, 마이그레이션 히스토리 |
|
||||
| `dependencies.md` | 외부 서비스, 서드파티 통합, 공유 라이브러리 |
|
||||
| `database.md` | 데이터베이스 스키마, 마이그레이션, 저장소 계층 |
|
||||
| `integrations.md` | 외부 서비스, 서드파티 통합, 어댑터 |
|
||||
| `workers.md` | 백그라운드 작업, 큐, 스케줄러 |
|
||||
|
||||
### 코드맵 형식
|
||||
|
||||
@@ -41,27 +42,33 @@ src/repos/user.ts (데이터베이스 접근, 80줄)
|
||||
- Stripe (결제 처리)
|
||||
```
|
||||
|
||||
## 3단계: 변경 감지
|
||||
## 3단계: 영역 분류
|
||||
|
||||
1. 이전 코드맵이 있는 경우 변경 비율 계산
|
||||
2. 변경이 30%를 초과하면 diff를 표시하고 덮어쓰기 전에 사용자 승인 요청
|
||||
3. 변경이 30% 이하이면 기존 파일에 바로 업데이트
|
||||
생성기는 파일 경로 패턴을 기반으로 영역을 자동 분류합니다:
|
||||
|
||||
1. 프론트엔드: `app/`, `pages/`, `components/`, `hooks/`, `.tsx`, `.jsx`
|
||||
2. 백엔드: `api/`, `routes/`, `controllers/`, `services/`, `.route.ts`
|
||||
3. 데이터베이스: `db/`, `migrations/`, `prisma/`, `repositories/`
|
||||
4. 통합: `integrations/`, `adapters/`, `connectors/`, `plugins/`
|
||||
5. 워커: `workers/`, `jobs/`, `queues/`, `tasks/`, `cron/`
|
||||
|
||||
## 4단계: 메타데이터 추가
|
||||
|
||||
각 코드맵에 최신 정보 헤더를 추가합니다:
|
||||
|
||||
```markdown
|
||||
<!-- Generated: 2026-02-11 | Files scanned: 142 | Token estimate: ~800 -->
|
||||
**Last Updated:** 2026-03-12
|
||||
**Total Files:** 42
|
||||
**Total Lines:** 1875
|
||||
```
|
||||
|
||||
## 5단계: 분석 보고서 저장
|
||||
## 5단계: 인덱스와 영역 문서 동기화
|
||||
|
||||
`.reports/codemap-diff.txt`에 요약을 작성합니다:
|
||||
- 마지막 스캔 이후 추가/제거/수정된 파일
|
||||
- 새로 감지된 의존성
|
||||
- 아키텍처 변경 사항 (새 라우트, 새 서비스 등)
|
||||
- 90일 이상 업데이트되지 않은 문서에 대한 오래된 항목 경고
|
||||
`INDEX.md`는 생성된 영역 문서를 링크하고 요약해야 합니다:
|
||||
- 각 영역의 파일 수와 총 라인 수
|
||||
- 감지된 엔트리 포인트
|
||||
- 저장소 트리의 간단한 ASCII 개요
|
||||
- 영역별 세부 문서 링크
|
||||
|
||||
## 팁
|
||||
|
||||
@@ -69,4 +76,4 @@ src/repos/user.ts (데이터베이스 접근, 80줄)
|
||||
- 전체 코드 블록 대신 **파일 경로와 함수 시그니처** 사용
|
||||
- 효율적인 컨텍스트 로딩을 위해 각 코드맵을 **1000 토큰 미만**으로 유지
|
||||
- 장황한 설명 대신 데이터 흐름에 ASCII 다이어그램 사용
|
||||
- 주요 기능 추가 또는 리팩토링 세션 후 실행
|
||||
- 주요 기능 추가 또는 리팩토링 세션 후 `npx tsx scripts/codemaps/generate.ts` 실행
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
name: update-docs
|
||||
description: 코드베이스를 기준으로 문서를 동기화하고 생성된 섹션을 갱신합니다.
|
||||
---
|
||||
|
||||
# 문서 업데이트
|
||||
|
||||
문서를 코드베이스와 동기화하고, 원본 소스 파일에서 생성합니다.
|
||||
|
||||
@@ -23,11 +23,15 @@
|
||||
- 통과/실패 수 보고
|
||||
- 커버리지 비율 보고
|
||||
|
||||
5. **Console.log 감사**
|
||||
5. **시크릿 스캔**
|
||||
- 소스 파일에서 API 키, 토큰, 비밀값 패턴 검색
|
||||
- 발견 위치 보고
|
||||
|
||||
6. **Console.log 감사**
|
||||
- 소스 파일에서 console.log 검색
|
||||
- 위치 보고
|
||||
|
||||
6. **Git 상태**
|
||||
7. **Git 상태**
|
||||
- 커밋되지 않은 변경사항 표시
|
||||
- 마지막 커밋 이후 수정된 파일 표시
|
||||
|
||||
|
||||
@@ -142,12 +142,12 @@ from django.db import transaction
|
||||
|
||||
def create_order(*, customer, product_id: uuid.UUID, quantity: int) -> Order:
|
||||
"""재고 검증과 결제 보류를 포함한 주문 생성."""
|
||||
product = Product.objects.select_for_update().get(id=product_id)
|
||||
|
||||
if product.stock < quantity:
|
||||
raise InsufficientStockError()
|
||||
|
||||
with transaction.atomic():
|
||||
product = Product.objects.select_for_update().get(id=product_id)
|
||||
|
||||
if product.stock < quantity:
|
||||
raise InsufficientStockError()
|
||||
|
||||
order = Order.objects.create(
|
||||
customer=customer,
|
||||
product=product,
|
||||
|
||||
@@ -55,7 +55,9 @@ pub enum AppError {
|
||||
#[error("Unauthorized")]
|
||||
Unauthorized,
|
||||
#[error(transparent)]
|
||||
Internal(#[from] anyhow::Error),
|
||||
Database(#[from] sqlx::Error),
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
@@ -64,7 +66,11 @@ impl IntoResponse for AppError {
|
||||
Self::NotFound => (StatusCode::NOT_FOUND, self.to_string()),
|
||||
Self::Validation(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
|
||||
Self::Unauthorized => (StatusCode::UNAUTHORIZED, self.to_string()),
|
||||
Self::Internal(err) => {
|
||||
Self::Database(err) => {
|
||||
tracing::error!(?err, "database error");
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Internal error".into())
|
||||
}
|
||||
Self::Io(err) => {
|
||||
tracing::error!(?err, "internal error");
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Internal error".into())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "input=$(cat); user=$(whoami); cwd=$(echo \"$input\" | jq -r '.workspace.current_dir' | sed \"s|$HOME|~|g\"); model=$(echo \"$input\" | jq -r '.model.display_name'); time=$(date +%H:%M); remaining=$(echo \"$input\" | jq -r '.context_window.remaining_percentage // empty'); transcript=$(echo \"$input\" | jq -r '.transcript_path'); todo_count=$([ -f \"$transcript\" ] && grep -c '\"type\":\"todo\"' \"$transcript\" 2>/dev/null || echo 0); cd \"$(echo \"$input\" | jq -r '.workspace.current_dir')\" 2>/dev/null; branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo ''); status=''; [ -n \"$branch\" ] && { [ -n \"$(git status --porcelain 2>/dev/null)\" ] && status='*'; }; B='\\033[38;2;30;102;245m'; G='\\033[38;2;64;160;43m'; Y='\\033[38;2;223;142;29m'; M='\\033[38;2;136;57;239m'; C='\\033[38;2;23;146;153m'; R='\\033[0m'; T='\\033[38;2;76;79;105m'; printf \"${C}${user}${R}:${B}${cwd}${R}\"; [ -n \"$branch\" ] && printf \" ${G}${branch}${Y}${status}${R}\"; [ -n \"$remaining\" ] && printf \" ${M}ctx:${remaining}%%${R}\"; printf \" ${T}${model}${R} ${Y}${time}${R}\"; [ \"$todo_count\" -gt 0 ] && printf \" ${C}todos:${todo_count}${R}\"; echo",
|
||||
"command": "input=$(cat); user=$(whoami); cwd=$(echo \"$input\" | jq -r '.workspace.current_dir' | sed \"s|$HOME|~|g\"); model=$(echo \"$input\" | jq -r '.model.display_name'); time=$(date +%H:%M); remaining=$(echo \"$input\" | jq -r '.context_window.remaining_percentage // empty'); transcript=$(echo \"$input\" | jq -r '.transcript_path'); todo_count=$([ -f \"$transcript\" ] && { grep -c '\"type\":\"todo\"' \"$transcript\" 2>/dev/null || true; } || echo 0); cd \"$(echo \"$input\" | jq -r '.workspace.current_dir')\" 2>/dev/null; branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo ''); status=''; [ -n \"$branch\" ] && { [ -n \"$(git status --porcelain 2>/dev/null)\" ] && status='*'; }; B='\\033[38;2;30;102;245m'; G='\\033[38;2;64;160;43m'; Y='\\033[38;2;223;142;29m'; M='\\033[38;2;136;57;239m'; C='\\033[38;2;23;146;153m'; R='\\033[0m'; T='\\033[38;2;76;79;105m'; printf \"${C}${user}${R}:${B}${cwd}${R}\"; [ -n \"$branch\" ] && printf \" ${G}${branch}${Y}${status}${R}\"; [ -n \"$remaining\" ] && printf \" ${M}ctx:${remaining}%%${R}\"; printf \" ${T}${model}${R} ${Y}${time}${R}\"; [ \"$todo_count\" -gt 0 ] && printf \" ${C}todos:${todo_count}${R}\"; echo",
|
||||
"description": "Custom status line showing: user:path branch* ctx:% model time todos:N"
|
||||
},
|
||||
"_comments": {
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
| security-reviewer | 보안 분석 | 커밋 전 |
|
||||
| build-error-resolver | 빌드 에러 수정 | 빌드 실패 시 |
|
||||
| e2e-runner | E2E 테스팅 | 핵심 사용자 흐름 |
|
||||
| database-reviewer | 데이터베이스 스키마/쿼리 리뷰 | 스키마 설계, 쿼리 최적화 |
|
||||
| go-reviewer | Go 코드 리뷰 | Go 코드 작성 또는 수정 후 |
|
||||
| go-build-resolver | Go 빌드 에러 수정 | `go build` 또는 `go vet` 실패 시 |
|
||||
| refactor-cleaner | 사용하지 않는 코드 정리 | 코드 유지보수 |
|
||||
| doc-updater | 문서 관리 | 문서 업데이트 |
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
타입: feat, fix, refactor, docs, test, chore, perf, ci
|
||||
|
||||
참고: 어트리뷰션은 ~/.claude/settings.json에서 전역적으로 비활성화되어 있습니다.
|
||||
참고: 어트리뷰션 비활성화 여부는 각자의 `~/.claude/settings.json` 로컬 설정에 따라 달라질 수 있습니다.
|
||||
|
||||
## Pull Request 워크플로우
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
||||
interface MarketRepository {
|
||||
findAll(filters?: MarketFilters): Promise<Market[]>
|
||||
findById(id: string): Promise<Market | null>
|
||||
findByIds(ids: string[]): Promise<Market[]>
|
||||
create(data: CreateMarketDto): Promise<Market>
|
||||
update(id: string, data: UpdateMarketDto): Promise<Market>
|
||||
delete(id: string): Promise<void>
|
||||
@@ -85,7 +86,7 @@ class MarketService {
|
||||
const markets = await this.marketRepo.findByIds(results.map(r => r.id))
|
||||
|
||||
// Sort by similarity
|
||||
return markets.sort((a, b) => {
|
||||
return [...markets].sort((a, b) => {
|
||||
const scoreA = results.find(r => r.id === a.id)?.score || 0
|
||||
const scoreB = results.find(r => r.id === b.id)?.score || 0
|
||||
return scoreA - scoreB
|
||||
@@ -320,7 +321,7 @@ async function fetchWithRetry<T>(
|
||||
fn: () => Promise<T>,
|
||||
maxRetries = 3
|
||||
): Promise<T> {
|
||||
let lastError: Error
|
||||
let lastError: Error = new Error('Retry attempts exhausted')
|
||||
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
|
||||
@@ -51,7 +51,7 @@ SETTINGS index_granularity = 8192;
|
||||
### ReplacingMergeTree (중복 제거)
|
||||
|
||||
```sql
|
||||
-- For data that may have duplicates (e.g., from multiple sources)
|
||||
-- 중복이 있을 수 있는 데이터용 (예: 여러 소스에서 수집된 경우)
|
||||
CREATE TABLE user_events (
|
||||
event_id String,
|
||||
user_id String,
|
||||
@@ -67,7 +67,7 @@ PRIMARY KEY (user_id, event_id);
|
||||
### AggregatingMergeTree (사전 집계)
|
||||
|
||||
```sql
|
||||
-- For maintaining aggregated metrics
|
||||
-- 집계 메트릭을 유지하기 위한 용도
|
||||
CREATE TABLE market_stats_hourly (
|
||||
hour DateTime,
|
||||
market_id String,
|
||||
@@ -78,7 +78,7 @@ CREATE TABLE market_stats_hourly (
|
||||
PARTITION BY toYYYYMM(hour)
|
||||
ORDER BY (hour, market_id);
|
||||
|
||||
-- Query aggregated data
|
||||
-- 집계된 데이터 조회
|
||||
SELECT
|
||||
hour,
|
||||
market_id,
|
||||
@@ -96,7 +96,7 @@ ORDER BY hour DESC;
|
||||
### 효율적인 필터링
|
||||
|
||||
```sql
|
||||
-- ✅ GOOD: Use indexed columns first
|
||||
-- ✅ 좋음: 인덱스된 컬럼을 먼저 사용
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE date >= '2025-01-01'
|
||||
@@ -105,7 +105,7 @@ WHERE date >= '2025-01-01'
|
||||
ORDER BY date DESC
|
||||
LIMIT 100;
|
||||
|
||||
-- ❌ BAD: Filter on non-indexed columns first
|
||||
-- ❌ 나쁨: 비인덱스 컬럼을 먼저 필터링
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE volume > 1000
|
||||
@@ -116,7 +116,7 @@ WHERE volume > 1000
|
||||
### 집계
|
||||
|
||||
```sql
|
||||
-- ✅ GOOD: Use ClickHouse-specific aggregation functions
|
||||
-- ✅ 좋음: 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;
|
||||
|
||||
-- ✅ Use quantile for percentiles (more efficient than percentile)
|
||||
-- ✅ 백분위수에는 quantile 사용 (percentile보다 효율적)
|
||||
SELECT
|
||||
quantile(0.50)(trade_size) AS median,
|
||||
quantile(0.95)(trade_size) AS p95,
|
||||
@@ -141,7 +141,7 @@ WHERE created_at >= now() - INTERVAL 1 HOUR;
|
||||
### 윈도우 함수
|
||||
|
||||
```sql
|
||||
-- Calculate running totals
|
||||
-- 누적 합계 계산
|
||||
SELECT
|
||||
date,
|
||||
market_id,
|
||||
@@ -172,25 +172,22 @@ const clickhouse = new ClickHouse({
|
||||
}
|
||||
})
|
||||
|
||||
// ✅ Batch insert (efficient)
|
||||
// ✅ 배치 삽입 (효율적)
|
||||
async function bulkInsertTrades(trades: Trade[]) {
|
||||
const values = trades.map(trade => `(
|
||||
'${trade.id}',
|
||||
'${trade.market_id}',
|
||||
'${trade.user_id}',
|
||||
${trade.amount},
|
||||
'${trade.timestamp.toISOString()}'
|
||||
)`).join(',')
|
||||
const rows = trades.map(trade => ({
|
||||
id: trade.id,
|
||||
market_id: trade.market_id,
|
||||
user_id: trade.user_id,
|
||||
amount: trade.amount,
|
||||
timestamp: trade.timestamp.toISOString()
|
||||
}))
|
||||
|
||||
await clickhouse.query(`
|
||||
INSERT INTO trades (id, market_id, user_id, amount, timestamp)
|
||||
VALUES ${values}
|
||||
`).toPromise()
|
||||
await clickhouse.insert('trades', rows)
|
||||
}
|
||||
|
||||
// ❌ Individual inserts (slow)
|
||||
// ❌ 개별 삽입 (느림)
|
||||
async function insertTrade(trade: Trade) {
|
||||
// Don't do this in a loop!
|
||||
// 루프 안에서 이렇게 하지 마세요!
|
||||
await clickhouse.query(`
|
||||
INSERT INTO trades VALUES ('${trade.id}', ...)
|
||||
`).toPromise()
|
||||
@@ -200,7 +197,7 @@ async function insertTrade(trade: Trade) {
|
||||
### 스트리밍 삽입
|
||||
|
||||
```typescript
|
||||
// For continuous data ingestion
|
||||
// 연속적인 데이터 수집용
|
||||
import { createWriteStream } from 'fs'
|
||||
import { pipeline } from 'stream/promises'
|
||||
|
||||
@@ -220,7 +217,7 @@ async function streamInserts() {
|
||||
### 실시간 집계
|
||||
|
||||
```sql
|
||||
-- Create materialized view for hourly stats
|
||||
-- 시간별 통계를 위한 materialized view 생성
|
||||
CREATE MATERIALIZED VIEW market_stats_hourly_mv
|
||||
TO market_stats_hourly
|
||||
AS SELECT
|
||||
@@ -232,7 +229,7 @@ AS SELECT
|
||||
FROM trades
|
||||
GROUP BY hour, market_id;
|
||||
|
||||
-- Query the materialized view
|
||||
-- materialized view 조회
|
||||
SELECT
|
||||
hour,
|
||||
market_id,
|
||||
@@ -249,7 +246,7 @@ GROUP BY hour, market_id;
|
||||
### 쿼리 성능
|
||||
|
||||
```sql
|
||||
-- Check slow queries
|
||||
-- 느린 쿼리 확인
|
||||
SELECT
|
||||
query_id,
|
||||
user,
|
||||
@@ -269,7 +266,7 @@ LIMIT 10;
|
||||
### 테이블 통계
|
||||
|
||||
```sql
|
||||
-- Check table sizes
|
||||
-- 테이블 크기 확인
|
||||
SELECT
|
||||
database,
|
||||
table,
|
||||
@@ -287,7 +284,7 @@ ORDER BY sum(bytes) DESC;
|
||||
### 시계열 분석
|
||||
|
||||
```sql
|
||||
-- Daily active users
|
||||
-- 일간 활성 사용자
|
||||
SELECT
|
||||
toDate(timestamp) AS date,
|
||||
uniq(user_id) AS daily_active_users
|
||||
@@ -296,7 +293,7 @@ WHERE timestamp >= today() - INTERVAL 30 DAY
|
||||
GROUP BY date
|
||||
ORDER BY date;
|
||||
|
||||
-- Retention analysis
|
||||
-- 리텐션 분석
|
||||
SELECT
|
||||
signup_date,
|
||||
countIf(days_since_signup = 0) AS day_0,
|
||||
@@ -319,7 +316,7 @@ ORDER BY signup_date DESC;
|
||||
### 퍼널 분석
|
||||
|
||||
```sql
|
||||
-- Conversion funnel
|
||||
-- 전환 퍼널
|
||||
SELECT
|
||||
countIf(step = 'viewed_market') AS viewed,
|
||||
countIf(step = 'clicked_trade') AS clicked,
|
||||
@@ -340,7 +337,7 @@ GROUP BY session_id;
|
||||
### 코호트 분석
|
||||
|
||||
```sql
|
||||
-- User cohorts by signup month
|
||||
-- 가입 월별 사용자 코호트
|
||||
SELECT
|
||||
toStartOfMonth(signup_date) AS cohort,
|
||||
toStartOfMonth(activity_date) AS month,
|
||||
@@ -362,12 +359,12 @@ ORDER BY cohort, months_since_signup;
|
||||
### ETL 패턴
|
||||
|
||||
```typescript
|
||||
// Extract, Transform, Load
|
||||
// 추출, 변환, 적재(ETL)
|
||||
async function etlPipeline() {
|
||||
// 1. Extract from source
|
||||
// 1. 소스에서 추출
|
||||
const rawData = await extractFromPostgres()
|
||||
|
||||
// 2. Transform
|
||||
// 2. 변환
|
||||
const transformed = rawData.map(row => ({
|
||||
date: new Date(row.created_at).toISOString().split('T')[0],
|
||||
market_id: row.market_slug,
|
||||
@@ -375,18 +372,29 @@ async function etlPipeline() {
|
||||
trades: parseInt(row.trade_count)
|
||||
}))
|
||||
|
||||
// 3. Load to ClickHouse
|
||||
// 3. ClickHouse에 적재
|
||||
await bulkInsertToClickHouse(transformed)
|
||||
}
|
||||
|
||||
// Run periodically
|
||||
setInterval(etlPipeline, 60 * 60 * 1000) // Every hour
|
||||
// 주기적으로 실행
|
||||
let etlRunning = false
|
||||
|
||||
setInterval(async () => {
|
||||
if (etlRunning) return
|
||||
|
||||
etlRunning = true
|
||||
try {
|
||||
await etlPipeline()
|
||||
} finally {
|
||||
etlRunning = false
|
||||
}
|
||||
}, 60 * 60 * 1000) // Every hour
|
||||
```
|
||||
|
||||
### 변경 데이터 캡처 (CDC)
|
||||
|
||||
```typescript
|
||||
// Listen to PostgreSQL changes and sync to ClickHouse
|
||||
// PostgreSQL 변경을 수신하고 ClickHouse와 동기화
|
||||
import { Client } from 'pg'
|
||||
|
||||
const pgClient = new Client({ connectionString: process.env.DATABASE_URL })
|
||||
|
||||
@@ -397,7 +397,7 @@ import { useMemo, useCallback } from 'react'
|
||||
|
||||
// ✅ GOOD: Memoize expensive computations
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return markets.sort((a, b) => b.volume - a.volume)
|
||||
return [...markets].sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ GOOD: Memoize callbacks
|
||||
|
||||
@@ -77,6 +77,35 @@ Claude Code 세션 종료 시 자동으로 평가하여 학습된 스킬로 저
|
||||
}
|
||||
```
|
||||
|
||||
## 예시
|
||||
|
||||
### 자동 패턴 추출 설정 예시
|
||||
|
||||
```json
|
||||
{
|
||||
"min_session_length": 10,
|
||||
"extraction_threshold": "medium",
|
||||
"auto_approve": false,
|
||||
"learned_skills_path": "~/.claude/skills/learned/"
|
||||
}
|
||||
```
|
||||
|
||||
### Stop Hook 연결 예시
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"Stop": [{
|
||||
"matcher": "*",
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "~/.claude/skills/continuous-learning/evaluate-session.sh"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Stop Hook을 사용하는 이유
|
||||
|
||||
- **경량**: 세션 종료 시 한 번만 실행
|
||||
@@ -116,4 +145,4 @@ Homunculus v2는 더 정교한 접근법을 취합니다:
|
||||
4. **도메인 태깅** - code-style, testing, git, debugging 등
|
||||
5. **진화 경로** - 관련 본능을 스킬/명령어로 클러스터링
|
||||
|
||||
자세한 사양은 `docs/continuous-learning-v2-spec.md`를 참조하세요.
|
||||
자세한 사양은 [`continuous-learning-v2-spec.md`](../../../continuous-learning-v2-spec.md)를 참조하세요.
|
||||
|
||||
@@ -154,6 +154,8 @@ const [isOpen, toggleOpen] = useToggle()
|
||||
### 비동기 데이터 페칭 Hook
|
||||
|
||||
```typescript
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
interface UseQueryOptions<T> {
|
||||
onSuccess?: (data: T) => void
|
||||
onError?: (error: Error) => void
|
||||
@@ -168,6 +170,14 @@ export function useQuery<T>(
|
||||
const [data, setData] = useState<T | null>(null)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const successRef = useRef(options?.onSuccess)
|
||||
const errorRef = useRef(options?.onError)
|
||||
const enabled = options?.enabled !== false
|
||||
|
||||
useEffect(() => {
|
||||
successRef.current = options?.onSuccess
|
||||
errorRef.current = options?.onError
|
||||
}, [options?.onSuccess, options?.onError])
|
||||
|
||||
const refetch = useCallback(async () => {
|
||||
setLoading(true)
|
||||
@@ -176,21 +186,21 @@ export function useQuery<T>(
|
||||
try {
|
||||
const result = await fetcher()
|
||||
setData(result)
|
||||
options?.onSuccess?.(result)
|
||||
successRef.current?.(result)
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
setError(error)
|
||||
options?.onError?.(error)
|
||||
errorRef.current?.(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [fetcher, options])
|
||||
}, [fetcher])
|
||||
|
||||
useEffect(() => {
|
||||
if (options?.enabled !== false) {
|
||||
if (enabled) {
|
||||
refetch()
|
||||
}
|
||||
}, [key, refetch, options?.enabled])
|
||||
}, [key, enabled, refetch])
|
||||
|
||||
return { data, error, loading, refetch }
|
||||
}
|
||||
@@ -296,7 +306,7 @@ export function useMarkets() {
|
||||
```typescript
|
||||
// ✅ useMemo for expensive computations
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return markets.sort((a, b) => b.volume - a.volume)
|
||||
return [...markets].sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ useCallback for functions passed to children
|
||||
|
||||
@@ -533,7 +533,8 @@ func ProcessRequest(data []byte) []byte {
|
||||
|
||||
buf.Write(data)
|
||||
// Process...
|
||||
return buf.Bytes()
|
||||
out := append([]byte(nil), buf.Bytes()...)
|
||||
return out
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -215,8 +215,8 @@ const securityHeaders = [
|
||||
key: 'Content-Security-Policy',
|
||||
value: `
|
||||
default-src 'self';
|
||||
script-src 'self' 'unsafe-eval' 'unsafe-inline';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
script-src 'self' 'nonce-{nonce}';
|
||||
style-src 'self' 'nonce-{nonce}';
|
||||
img-src 'self' data: https:;
|
||||
font-src 'self';
|
||||
connect-src 'self' https://api.example.com;
|
||||
@@ -225,6 +225,8 @@ const securityHeaders = [
|
||||
]
|
||||
```
|
||||
|
||||
`{nonce}`는 요청마다 새로 생성하고, 헤더와 인라인 `<script>`/`<style>` 태그에 동일하게 주입해야 합니다.
|
||||
|
||||
#### 확인 단계
|
||||
- [ ] 사용자 제공 HTML이 새니타이징됨
|
||||
- [ ] CSP 헤더가 구성됨
|
||||
@@ -339,7 +341,9 @@ catch (error) {
|
||||
|
||||
#### 지갑 검증
|
||||
```typescript
|
||||
import { verify } from '@solana/web3.js'
|
||||
import nacl from 'tweetnacl'
|
||||
import bs58 from 'bs58'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
|
||||
async function verifyWalletOwnership(
|
||||
publicKey: string,
|
||||
@@ -347,18 +351,23 @@ async function verifyWalletOwnership(
|
||||
message: string
|
||||
) {
|
||||
try {
|
||||
const isValid = verify(
|
||||
Buffer.from(message),
|
||||
Buffer.from(signature, 'base64'),
|
||||
Buffer.from(publicKey, 'base64')
|
||||
const publicKeyBytes = new PublicKey(publicKey).toBytes()
|
||||
const signatureBytes = bs58.decode(signature)
|
||||
const messageBytes = new TextEncoder().encode(message)
|
||||
|
||||
return nacl.sign.detached.verify(
|
||||
messageBytes,
|
||||
signatureBytes,
|
||||
publicKeyBytes
|
||||
)
|
||||
return isValid
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
참고: Solana 공개 키와 서명은 일반적으로 base64가 아니라 base58로 인코딩됩니다.
|
||||
|
||||
#### 트랜잭션 검증
|
||||
```typescript
|
||||
async function verifyTransaction(transaction: Transaction) {
|
||||
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
|
||||
# Scan for secrets
|
||||
- name: Secret scanning
|
||||
uses: trufflesecurity/trufflehog@main
|
||||
uses: trufflesecurity/trufflehog@6c05c4a00b91aa542267d8e32a8254774799d68d
|
||||
|
||||
# Dependency audit
|
||||
- name: Audit dependencies
|
||||
@@ -215,7 +215,7 @@ jobs:
|
||||
// package.json - Use lock files and integrity checks
|
||||
{
|
||||
"scripts": {
|
||||
"install": "npm ci", // Use ci for reproducible builds
|
||||
"deps:install": "npm ci", // Use ci for reproducible builds
|
||||
"audit": "npm audit --audit-level=moderate",
|
||||
"check": "npm outdated"
|
||||
}
|
||||
|
||||
@@ -45,12 +45,14 @@ origin: ECC
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Edit",
|
||||
"hooks": [{ "type": "command", "command": "node ~/.claude/skills/strategic-compact/suggest-compact.js" }]
|
||||
},
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [{ "type": "command", "command": "node ~/.claude/skills/strategic-compact/suggest-compact.js" }]
|
||||
"matcher": "Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"pre:edit-write:suggest-compact\" \"scripts/hooks/suggest-compact.js\" \"standard,strict\""
|
||||
}
|
||||
],
|
||||
"description": "Suggest manual compaction at logical intervals"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -191,11 +191,9 @@ test('user can search and filter markets', async ({ page }) => {
|
||||
// Search for markets
|
||||
await page.fill('input[placeholder="Search markets"]', 'election')
|
||||
|
||||
// Wait for debounce and results
|
||||
await page.waitForTimeout(600)
|
||||
|
||||
// Verify search results displayed
|
||||
// Wait for stable search results instead of sleeping
|
||||
const results = page.locator('[data-testid="market-card"]')
|
||||
await expect(results.first()).toBeVisible({ timeout: 5000 })
|
||||
await expect(results).toHaveCount(5, { timeout: 5000 })
|
||||
|
||||
// Verify results contain search term
|
||||
@@ -300,7 +298,7 @@ npm run test:coverage
|
||||
```json
|
||||
{
|
||||
"jest": {
|
||||
"coverageThresholds": {
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 80,
|
||||
"functions": 80,
|
||||
|
||||
@@ -77,7 +77,8 @@ grep -rn "console.log" --include="*.ts" --include="*.tsx" src/ 2>/dev/null | hea
|
||||
```bash
|
||||
# Show what changed
|
||||
git diff --stat
|
||||
git diff HEAD~1 --name-only
|
||||
git diff --name-only
|
||||
git diff --cached --name-only
|
||||
```
|
||||
|
||||
각 변경된 파일에서 다음을 검토합니다:
|
||||
|
||||
Reference in New Issue
Block a user