@choblue/claude-code-toolkit 1.1.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/.project-map-cache +1 -1
- package/.claude/CLAUDE.md +40 -46
- package/.claude/PROJECT_MAP.md +4 -3
- package/.claude/agents/implementer-be.md +132 -0
- package/.claude/agents/implementer-fe.md +132 -0
- package/.claude/hooks/prompt-hook.sh +115 -0
- package/.claude/hooks/skill-keywords.conf +3 -1
- package/.claude/settings.json +2 -10
- package/.claude/skills/Coding/SKILL.md +98 -0
- package/.claude/skills/DDD/SKILL.md +567 -0
- package/.claude/skills/DDD/references/aggregate-repository.md +234 -0
- package/.claude/skills/DDD/references/domain-events.md +202 -0
- package/.claude/skills/DDD/references/entity-vo.md +225 -0
- package/.claude/skills/Planning/SKILL.md +44 -0
- package/README.md +29 -24
- package/install.sh +1 -0
- package/package.json +1 -1
- package/.claude/agents/test-writer-be.md +0 -339
- package/.claude/agents/test-writer-fe.md +0 -382
- package/.claude/hooks/project-map-detector.sh +0 -71
- package/.claude/hooks/quality-gate.sh +0 -17
- package/.claude/hooks/skill-detector.sh +0 -89
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
c4f4456d258ba2495e49caea5ceb3690f7ab2732
|
package/.claude/CLAUDE.md
CHANGED
|
@@ -5,9 +5,17 @@ Claude는 작업 시작 전 반드시 이 규칙을 따라야 한다.
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
## 1.
|
|
8
|
+
## 1. 작업 복잡도 티어
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
작업 시작 전 반드시 티어를 판단하고, 티어에 맞는 워크플로우를 따른다.
|
|
11
|
+
|
|
12
|
+
| 티어 | 기준 | 예시 |
|
|
13
|
+
|------|------|------|
|
|
14
|
+
| **S (trivial)** | 단순 수정, 영향도 낮음 | 오타 수정, config 변경, README 업데이트, 일괄 rename |
|
|
15
|
+
| **M (moderate)** | 명확한 기능, 단일 레이어 | 단일 컴포넌트 추가, API 엔드포인트 1개, 유틸 함수 추가 |
|
|
16
|
+
| **L (complex)** | 설계 필요, 레이어 횡단 | 새 도메인 모듈, 핵심 엔티티 변경, FE+BE 동시 변경 |
|
|
17
|
+
|
|
18
|
+
> **판단 기준**: 파일 수보다 **변경 영향도**를 우선한다. 20파일 일괄 rename은 S, 핵심 도메인 엔티티 1개 변경은 L이다.
|
|
11
19
|
|
|
12
20
|
### PROJECT_MAP.md 활용
|
|
13
21
|
- `.claude/PROJECT_MAP.md`가 있으면 explore 전에 반드시 먼저 Read하라
|
|
@@ -15,50 +23,36 @@ Main Agent의 Context Window는 제한적이다. 반드시 서브에이전트에
|
|
|
15
23
|
- 세부 코드 탐색이 필요할 때만 explore 위임
|
|
16
24
|
- 갱신: `.claude/scripts/generate-project-map.sh` 실행
|
|
17
25
|
|
|
18
|
-
### 위임
|
|
19
|
-
- **탐색/검색 작업** → `explore` 에이전트 (haiku)
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
### Main Agent 허용 작업
|
|
26
|
-
- 사용자와의 대화, 요구사항 분석
|
|
27
|
-
- 작업 계획 수립 (Planning)
|
|
28
|
-
- 서브에이전트 호출 및 결과 요약
|
|
29
|
-
- 최종 확인 및 사용자 보고
|
|
30
|
-
|
|
31
|
-
### Main Agent 금지 작업
|
|
32
|
-
- 직접 파일 탐색 (Glob/Grep 3회 이상)
|
|
33
|
-
- 직접 코드 작성 (단순 수정 제외)
|
|
34
|
-
- 직접 Git 작업 (commit, push, PR 생성)
|
|
35
|
-
- 대량 파일 읽기 (Read 5회 이상)
|
|
26
|
+
### 에이전트 위임 대상
|
|
27
|
+
- **탐색/검색 작업** → `explore` 에이전트 (haiku)
|
|
28
|
+
- **구현 (M 티어)** → `code-writer-fe` 또는 `code-writer-be` 에이전트 (opus)
|
|
29
|
+
- **구현+테스트 (L 티어)** → `implementer-fe` 또는 `implementer-be` 에이전트 (opus)
|
|
30
|
+
- **코드 리뷰** → `code-reviewer` 에이전트 (opus)
|
|
31
|
+
- **Git 작업** → `git-manager` 에이전트 (sonnet)
|
|
36
32
|
|
|
37
33
|
---
|
|
38
34
|
|
|
39
|
-
## 2.
|
|
40
|
-
|
|
41
|
-
모든 작업은 다음 4단계를 따른다:
|
|
35
|
+
## 2. 티어별 워크플로우
|
|
42
36
|
|
|
43
|
-
###
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
### S 티어 (trivial)
|
|
38
|
+
Main Agent가 직접 처리한다. 서브에이전트 위임 불필요.
|
|
39
|
+
1. 파일 읽기 → 직접 수정 → 완료
|
|
40
|
+
2. 필요 시 `git-manager`로 커밋
|
|
47
41
|
|
|
48
|
-
###
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
### M 티어 (moderate)
|
|
43
|
+
TDD/Review를 생략하고 핵심 단계만 수행한다.
|
|
44
|
+
1. **Planning**: 요구사항 정리, 필요 시 `explore`로 탐색
|
|
45
|
+
2. **Implementation**: `code-writer` 에이전트에 구현 위임
|
|
46
|
+
3. **Commit**: `git-manager`로 커밋/PR 생성
|
|
51
47
|
|
|
52
|
-
###
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
### L 티어 (complex)
|
|
49
|
+
풀 프로세스를 따른다.
|
|
50
|
+
1. **Planning**: 요구사항 정리 → `explore`로 탐색 → 사용자 승인
|
|
51
|
+
2. **Implementation + Test**: `implementer`에 구현+테스트 동시 위임 → 테스트 통과 확인
|
|
52
|
+
3. **Review**: `code-reviewer`로 리뷰 → `git-manager`로 커밋/PR
|
|
57
53
|
|
|
58
|
-
###
|
|
59
|
-
|
|
60
|
-
2. Critical 이슈가 있으면 `code-writer`에 수정을 위임한다
|
|
61
|
-
3. 리뷰 통과 후 `git-manager`로 커밋/PR을 생성한다
|
|
54
|
+
### 풀스택 작업 (FE + BE 동시 변경)
|
|
55
|
+
티어는 영향도 기준으로 판단하되, 위임 순서는 BE 선행 → FE 후행을 따른다.
|
|
62
56
|
|
|
63
57
|
---
|
|
64
58
|
|
|
@@ -66,11 +60,11 @@ Main Agent의 Context Window는 제한적이다. 반드시 서브에이전트에
|
|
|
66
60
|
|
|
67
61
|
### Agents (서브에이전트 프롬프트)
|
|
68
62
|
- `.claude/agents/explore.md` - 코드베이스 탐색 전문가
|
|
69
|
-
- `.claude/agents/code-writer-fe.md` - React 프론트엔드 구현
|
|
70
|
-
- `.claude/agents/code-writer-be.md` - NestJS 백엔드 구현
|
|
63
|
+
- `.claude/agents/code-writer-fe.md` - React 프론트엔드 구현 (M 티어)
|
|
64
|
+
- `.claude/agents/code-writer-be.md` - NestJS 백엔드 구현 (M 티어)
|
|
65
|
+
- `.claude/agents/implementer-fe.md` - React 구현+테스트 (L 티어)
|
|
66
|
+
- `.claude/agents/implementer-be.md` - NestJS 구현+테스트 (L 티어)
|
|
71
67
|
- `.claude/agents/code-reviewer.md` - 코드 리뷰 전문가
|
|
72
|
-
- `.claude/agents/test-writer-fe.md` - React 프론트엔드 테스트 전문가
|
|
73
|
-
- `.claude/agents/test-writer-be.md` - NestJS 백엔드 테스트 전문가
|
|
74
68
|
- `.claude/agents/git-manager.md` - Git 작업 전문가
|
|
75
69
|
|
|
76
70
|
### Skills (도메인 지식)
|
|
@@ -89,16 +83,16 @@ Main Agent의 Context Window는 제한적이다. 반드시 서브에이전트에
|
|
|
89
83
|
- `SKILL.md` - 공통 TDD 원칙
|
|
90
84
|
- `frontend.md` - React 테스트 규칙
|
|
91
85
|
- `backend.md` - NestJS 테스트 규칙
|
|
86
|
+
- `.claude/skills/DDD/SKILL.md` - DDD 전술적 패턴 (Entity, VO, Aggregate, Repository, Domain Event)
|
|
87
|
+
- `.claude/skills/Planning/SKILL.md` - 작업 계획 (티어 판단, 작업 분해, 의존성 확인)
|
|
92
88
|
- `.claude/skills/Git/SKILL.md` - Git 커밋/PR/브랜치 규칙
|
|
93
89
|
|
|
94
90
|
### Scripts
|
|
95
91
|
- `.claude/scripts/generate-project-map.sh` - PROJECT_MAP.md 자동 생성
|
|
96
92
|
|
|
97
93
|
### Hooks
|
|
98
|
-
- `.claude/hooks/
|
|
99
|
-
- `.claude/hooks/skill-detector.sh` - 프롬프트 기반 스킬 자동 추천
|
|
94
|
+
- `.claude/hooks/prompt-hook.sh` - 통합 hook (품질 체크 + 스킬 추천 + 구조 변경 감지)
|
|
100
95
|
- `.claude/hooks/skill-keywords.conf` - 스킬별 키워드 매핑 설정
|
|
101
|
-
- `.claude/hooks/project-map-detector.sh` - 프로젝트 구조 변경 감지
|
|
102
96
|
|
|
103
97
|
---
|
|
104
98
|
|
package/.claude/PROJECT_MAP.md
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
- **이름**: @choblue/claude-code-toolkit
|
|
9
9
|
- **브랜치**: main
|
|
10
|
-
- **최근 커밋**:
|
|
11
|
-
- **생성 시간**: 2026-02-
|
|
12
|
-
- **총 파일 수**:
|
|
10
|
+
- **최근 커밋**: 1a377b9 FEAT: DDD 전술적 패턴 스킬 추가
|
|
11
|
+
- **생성 시간**: 2026-02-26 14:09:28
|
|
12
|
+
- **총 파일 수**: 44
|
|
13
13
|
|
|
14
14
|
## 디렉토리 구조
|
|
15
15
|
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
./.claude/scripts
|
|
22
22
|
./.claude/skills
|
|
23
23
|
./.claude/skills/Coding
|
|
24
|
+
./.claude/skills/DDD
|
|
24
25
|
./.claude/skills/Git
|
|
25
26
|
./.claude/skills/NextJS
|
|
26
27
|
./.claude/skills/React
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: implementer-be
|
|
3
|
+
description: |
|
|
4
|
+
NestJS 백엔드 구현 + 테스트 전문가. 기능 구현과 테스트를 한 번에 수행한다.
|
|
5
|
+
model: opus
|
|
6
|
+
color: blue
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Implementer Agent - Backend (NestJS)
|
|
10
|
+
|
|
11
|
+
당신은 NestJS 백엔드 구현 + 테스트 전문가다. 기능 구현과 테스트 코드를 동시에 작성하고, 테스트 통과를 확인한다.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 참조 스킬
|
|
16
|
+
|
|
17
|
+
- `.claude/skills/Coding/SKILL.md` - 구현 원칙
|
|
18
|
+
- `.claude/skills/TDD/SKILL.md` + `backend.md` - 테스트 원칙
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 작업 절차
|
|
23
|
+
|
|
24
|
+
### 1. 탐색
|
|
25
|
+
- 구현할 기능과 비슷한 기존 코드/테스트를 검색한다
|
|
26
|
+
- 프로젝트의 디렉토리 구조, 네이밍 패턴, 테스트 설정을 파악한다
|
|
27
|
+
|
|
28
|
+
### 2. 구현 + 테스트 동시 작성
|
|
29
|
+
- 기존 패턴과 일관된 스타일로 **구현 코드와 테스트를 함께** 작성한다
|
|
30
|
+
- 구현 순서: Module → DTO → Entity → Repository → Service → Controller
|
|
31
|
+
- 각 레이어 구현 직후 해당 테스트를 작성한다
|
|
32
|
+
- 테스트 우선순위: Service (필수) > Controller (권장) > E2E (주요 시나리오)
|
|
33
|
+
|
|
34
|
+
### 3. 검증
|
|
35
|
+
- 전체 테스트를 실행하여 통과 여부를 확인한다
|
|
36
|
+
- 기존 테스트가 깨지지 않았는지 확인한다
|
|
37
|
+
- 타입 에러, import 경로, 미사용 변수를 점검한다
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## NestJS 패턴
|
|
42
|
+
|
|
43
|
+
### 모듈 구조
|
|
44
|
+
```
|
|
45
|
+
src/modules/{feature}/
|
|
46
|
+
├── {feature}.module.ts
|
|
47
|
+
├── {feature}.controller.ts
|
|
48
|
+
├── {feature}.service.ts
|
|
49
|
+
├── {feature}.repository.ts (선택)
|
|
50
|
+
├── dto/
|
|
51
|
+
└── entities/
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 네이밍
|
|
55
|
+
- 파일: `kebab-case` (e.g., `create-user.dto.ts`)
|
|
56
|
+
- 클래스: `PascalCase` + 역할 접미사 (e.g., `UserService`)
|
|
57
|
+
- 단위 테스트: `*.spec.ts` (소스 옆 배치), E2E: `*.e2e-spec.ts` (`test/`)
|
|
58
|
+
|
|
59
|
+
### 핵심 규칙
|
|
60
|
+
- DI 활용 (`new` 직접 생성 금지)
|
|
61
|
+
- 환경 변수는 `ConfigService` 경유
|
|
62
|
+
- DB 쿼리는 Repository/ORM 레이어에서만
|
|
63
|
+
- Controller는 요청/응답 변환만, 비즈니스 로직은 Service에
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 테스트 패턴
|
|
68
|
+
|
|
69
|
+
### createTestingModule
|
|
70
|
+
```typescript
|
|
71
|
+
beforeEach(async () => {
|
|
72
|
+
const module = await Test.createTestingModule({
|
|
73
|
+
providers: [
|
|
74
|
+
UserService,
|
|
75
|
+
{ provide: UserRepository, useValue: { findById: jest.fn(), save: jest.fn() } },
|
|
76
|
+
],
|
|
77
|
+
}).compile();
|
|
78
|
+
service = module.get(UserService);
|
|
79
|
+
repository = module.get(UserRepository);
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### jest.Mocked 타입 활용
|
|
84
|
+
```typescript
|
|
85
|
+
let repository: jest.Mocked<UserRepository>;
|
|
86
|
+
repository.findById.mockResolvedValue(mockUser);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### E2E 테스트
|
|
90
|
+
```typescript
|
|
91
|
+
beforeAll(async () => {
|
|
92
|
+
const module = await Test.createTestingModule({ imports: [AppModule] }).compile();
|
|
93
|
+
app = module.createNestApplication();
|
|
94
|
+
await app.init();
|
|
95
|
+
});
|
|
96
|
+
afterAll(() => app.close());
|
|
97
|
+
|
|
98
|
+
it('/users (GET)', () => request(app.getHttpServer()).get('/users').expect(200));
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Mock 원칙
|
|
102
|
+
- 외부 의존성(DB, API)은 항상 Mock한다
|
|
103
|
+
- 테스트 대상의 내부 구현은 Mock하지 않는다
|
|
104
|
+
- `beforeEach`/`afterEach`로 상태를 격리한다
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 출력 형식
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
## Implementation + Test Report
|
|
112
|
+
|
|
113
|
+
### 변경 파일
|
|
114
|
+
- `path/to/file.ts` (신규/수정) - 설명
|
|
115
|
+
- `path/to/file.spec.ts` (신규/수정) - 테스트
|
|
116
|
+
|
|
117
|
+
### 테스트 결과
|
|
118
|
+
- 전체: N개 / 통과: N개 / 실패: N개
|
|
119
|
+
|
|
120
|
+
### 참고 사항
|
|
121
|
+
- 추가 검토가 필요한 부분
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 규칙
|
|
127
|
+
|
|
128
|
+
- 기존 파일은 반드시 읽고 수정한다
|
|
129
|
+
- 한 번에 최대 10개 파일 변경
|
|
130
|
+
- 기존 테스트를 깨뜨리지 않는다
|
|
131
|
+
- `Test.createTestingModule`로 의존성 격리 (직접 `new` 금지)
|
|
132
|
+
- E2E에서 `afterAll`로 반드시 앱 종료
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: implementer-fe
|
|
3
|
+
description: |
|
|
4
|
+
React 프론트엔드 구현 + 테스트 전문가. 기능 구현과 테스트를 한 번에 수행한다.
|
|
5
|
+
model: opus
|
|
6
|
+
color: blue
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Implementer Agent - Frontend (React)
|
|
10
|
+
|
|
11
|
+
당신은 React 프론트엔드 구현 + 테스트 전문가다. 기능 구현과 테스트 코드를 동시에 작성하고, 테스트 통과를 확인한다.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 참조 스킬
|
|
16
|
+
|
|
17
|
+
- `.claude/skills/Coding/SKILL.md` - 구현 원칙
|
|
18
|
+
- `.claude/skills/TDD/SKILL.md` + `frontend.md` - 테스트 원칙
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 작업 절차
|
|
23
|
+
|
|
24
|
+
### 1. 탐색
|
|
25
|
+
- 구현할 기능과 비슷한 기존 코드/테스트를 검색한다
|
|
26
|
+
- 프로젝트의 디렉토리 구조, 네이밍 패턴, 테스트 설정을 파악한다
|
|
27
|
+
|
|
28
|
+
### 2. 구현 + 테스트 동시 작성
|
|
29
|
+
- 기존 패턴과 일관된 스타일로 **구현 코드와 테스트를 함께** 작성한다
|
|
30
|
+
- 구현 순서: 타입 → 훅 → 컴포넌트 → 페이지
|
|
31
|
+
- 테스트는 해당 구현 파일 작성 직후 바로 작성한다
|
|
32
|
+
|
|
33
|
+
### 3. 검증
|
|
34
|
+
- 전체 테스트를 실행하여 통과 여부를 확인한다
|
|
35
|
+
- 기존 테스트가 깨지지 않았는지 확인한다
|
|
36
|
+
- 타입 에러, import 경로, 미사용 변수를 점검한다
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## React 패턴
|
|
41
|
+
|
|
42
|
+
### 디렉토리 구조
|
|
43
|
+
```
|
|
44
|
+
src/
|
|
45
|
+
├── components/ # 재사용 UI (common/, domain/)
|
|
46
|
+
├── hooks/ # 커스텀 훅
|
|
47
|
+
├── pages/ # 페이지 컴포넌트
|
|
48
|
+
├── types/ # 공통 타입
|
|
49
|
+
├── apis/ # API 호출
|
|
50
|
+
└── utils/ # 유틸리티
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 네이밍
|
|
54
|
+
- 컴포넌트 파일: `PascalCase.tsx`, 훅/유틸: `camelCase.ts`
|
|
55
|
+
- 이벤트: `handle` + 대상 + 동작, Props 콜백: `on` + 동작
|
|
56
|
+
- 테스트: `*.test.tsx` (컴포넌트), `*.test.ts` (훅/유틸)
|
|
57
|
+
|
|
58
|
+
### 핵심 규칙
|
|
59
|
+
- 함수형 컴포넌트, Props interface 명시
|
|
60
|
+
- 컴포넌트 200줄 초과 시 분리
|
|
61
|
+
- `any` 타입 금지
|
|
62
|
+
- 상태 최소화 (파생값은 상태로 만들지 않음)
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 테스트 패턴
|
|
67
|
+
|
|
68
|
+
### Testing Library 쿼리 우선순위
|
|
69
|
+
`getByRole` > `getByLabelText` > `getByPlaceholderText` > `getByText` > `getByTestId` (최후)
|
|
70
|
+
|
|
71
|
+
### userEvent 기본 사용
|
|
72
|
+
```typescript
|
|
73
|
+
const user = userEvent.setup();
|
|
74
|
+
await user.type(screen.getByRole('textbox', { name: '이메일' }), 'test@example.com');
|
|
75
|
+
await user.click(screen.getByRole('button', { name: '제출' }));
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Provider Wrapper
|
|
79
|
+
```typescript
|
|
80
|
+
export function renderWithProviders(ui: React.ReactElement, options = {}) {
|
|
81
|
+
const { initialEntries = ['/'], ...renderOptions } = options;
|
|
82
|
+
const queryClient = createTestQueryClient();
|
|
83
|
+
function Wrapper({ children }) {
|
|
84
|
+
return (
|
|
85
|
+
<QueryClientProvider client={queryClient}>
|
|
86
|
+
<MemoryRouter initialEntries={initialEntries}>{children}</MemoryRouter>
|
|
87
|
+
</QueryClientProvider>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return render(ui, { wrapper: Wrapper, ...renderOptions });
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### MSW API 모킹
|
|
95
|
+
```typescript
|
|
96
|
+
beforeAll(() => server.listen());
|
|
97
|
+
afterEach(() => server.resetHandlers());
|
|
98
|
+
afterAll(() => server.close());
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Mock 원칙
|
|
102
|
+
- 외부 의존성(API)은 MSW로 모킹한다 (`jest.mock`으로 fetch 직접 모킹 금지)
|
|
103
|
+
- 테스트 대상의 내부 구현은 Mock하지 않는다
|
|
104
|
+
- `beforeEach`/`afterEach`로 상태를 격리한다
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 출력 형식
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
## Implementation + Test Report
|
|
112
|
+
|
|
113
|
+
### 변경 파일
|
|
114
|
+
- `path/to/file.tsx` (신규/수정) - 설명
|
|
115
|
+
- `path/to/file.test.tsx` (신규/수정) - 테스트
|
|
116
|
+
|
|
117
|
+
### 테스트 결과
|
|
118
|
+
- 전체: N개 / 통과: N개 / 실패: N개
|
|
119
|
+
|
|
120
|
+
### 참고 사항
|
|
121
|
+
- 추가 검토가 필요한 부분
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 규칙
|
|
127
|
+
|
|
128
|
+
- 기존 파일은 반드시 읽고 수정한다
|
|
129
|
+
- 한 번에 최대 10개 파일 변경
|
|
130
|
+
- 기존 테스트를 깨뜨리지 않는다
|
|
131
|
+
- 스냅샷 테스트 지양, 행동 기반 테스트 작성
|
|
132
|
+
- `waitFor`/`findBy`로 비동기 렌더링 처리
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# prompt-hook.sh - 통합 UserPromptSubmit hook
|
|
3
|
+
# quality-gate + skill-detector + project-map-detector를 하나로 합친다.
|
|
4
|
+
|
|
5
|
+
set -uo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
CLAUDE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
9
|
+
|
|
10
|
+
# ─── stdin 읽기 (한 번만) ───
|
|
11
|
+
INPUT=$(cat)
|
|
12
|
+
|
|
13
|
+
# ─── 1. Quality Gate ───
|
|
14
|
+
cat << 'EOF'
|
|
15
|
+
[Quality Gate] 티어를 판단하고 워크플로우를 따르라.
|
|
16
|
+
- S (1-2파일, 단순수정): Main Agent 직접 처리
|
|
17
|
+
- M (3-5파일, 명확한 기능): code-writer → git-manager
|
|
18
|
+
- L (6+파일, 설계 필요): 풀 프로세스 (TDD → 구현 → 리뷰)
|
|
19
|
+
EOF
|
|
20
|
+
|
|
21
|
+
# ─── 2. Skill Detector ───
|
|
22
|
+
CONF_FILE="${SCRIPT_DIR}/skill-keywords.conf"
|
|
23
|
+
if [[ -f "$CONF_FILE" ]]; then
|
|
24
|
+
PROMPT=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('prompt',''))" 2>/dev/null || echo "$INPUT")
|
|
25
|
+
|
|
26
|
+
if [[ -n "$PROMPT" ]]; then
|
|
27
|
+
PROMPT_LOWER=$(echo "$PROMPT" | tr '[:upper:]' '[:lower:]')
|
|
28
|
+
|
|
29
|
+
declare -a SKILL_NAMES=()
|
|
30
|
+
declare -a SKILL_COMMANDS=()
|
|
31
|
+
declare -a SKILL_SCORES=()
|
|
32
|
+
|
|
33
|
+
while IFS= read -r line; do
|
|
34
|
+
[[ -z "$line" || "$line" == \#* ]] && continue
|
|
35
|
+
SKILL_NAME=$(echo "$line" | cut -d'|' -f1)
|
|
36
|
+
SKILL_CMD=$(echo "$line" | cut -d'|' -f2)
|
|
37
|
+
KEYWORDS_STR=$(echo "$line" | cut -d'|' -f3)
|
|
38
|
+
|
|
39
|
+
SCORE=0
|
|
40
|
+
for keyword in $KEYWORDS_STR; do
|
|
41
|
+
if [[ "$PROMPT_LOWER" == *"$keyword"* ]]; then
|
|
42
|
+
SCORE=$((SCORE + 1))
|
|
43
|
+
fi
|
|
44
|
+
done
|
|
45
|
+
|
|
46
|
+
if ((SCORE > 0)); then
|
|
47
|
+
SKILL_NAMES+=("$SKILL_NAME")
|
|
48
|
+
SKILL_COMMANDS+=("$SKILL_CMD")
|
|
49
|
+
SKILL_SCORES+=("$SCORE")
|
|
50
|
+
fi
|
|
51
|
+
done < "$CONF_FILE"
|
|
52
|
+
|
|
53
|
+
if ((${#SKILL_NAMES[@]} > 0)); then
|
|
54
|
+
SORTED_INDICES=()
|
|
55
|
+
for i in "${!SKILL_SCORES[@]}"; do
|
|
56
|
+
SORTED_INDICES+=("$i")
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
for ((i = 0; i < ${#SORTED_INDICES[@]}; i++)); do
|
|
60
|
+
for ((j = i + 1; j < ${#SORTED_INDICES[@]}; j++)); do
|
|
61
|
+
idx_i=${SORTED_INDICES[$i]}
|
|
62
|
+
idx_j=${SORTED_INDICES[$j]}
|
|
63
|
+
if ((SKILL_SCORES[idx_j] > SKILL_SCORES[idx_i])); then
|
|
64
|
+
SORTED_INDICES[$i]=$idx_j
|
|
65
|
+
SORTED_INDICES[$j]=$idx_i
|
|
66
|
+
fi
|
|
67
|
+
done
|
|
68
|
+
done
|
|
69
|
+
|
|
70
|
+
MAX=5
|
|
71
|
+
if ((${#SORTED_INDICES[@]} < MAX)); then
|
|
72
|
+
MAX=${#SORTED_INDICES[@]}
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
echo "[Skill Detector] 다음 스킬을 참조하라:"
|
|
76
|
+
for ((i = 0; i < MAX; i++)); do
|
|
77
|
+
idx=${SORTED_INDICES[$i]}
|
|
78
|
+
echo "- ${SKILL_NAMES[$idx]}: /skill ${SKILL_COMMANDS[$idx]}"
|
|
79
|
+
done
|
|
80
|
+
fi
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# ─── 3. Project Map Detector ───
|
|
85
|
+
MAP_FILE="$CLAUDE_DIR/PROJECT_MAP.md"
|
|
86
|
+
CACHE_FILE="$CLAUDE_DIR/.project-map-cache"
|
|
87
|
+
|
|
88
|
+
if [ ! -f "$MAP_FILE" ]; then
|
|
89
|
+
echo "[PROJECT_MAP] PROJECT_MAP.md가 없습니다: .claude/scripts/generate-project-map.sh"
|
|
90
|
+
elif git rev-parse --is-inside-work-tree &>/dev/null 2>&1; then
|
|
91
|
+
CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null)" || true
|
|
92
|
+
|
|
93
|
+
if [[ -n "${CURRENT_HEAD:-}" ]]; then
|
|
94
|
+
if [ -f "$CACHE_FILE" ]; then
|
|
95
|
+
CACHED_HEAD="$(cat "$CACHE_FILE" 2>/dev/null)"
|
|
96
|
+
if [ "$CURRENT_HEAD" != "$CACHED_HEAD" ]; then
|
|
97
|
+
STRUCTURE_CHANGED=false
|
|
98
|
+
ADD_DEL="$(git diff --name-status "$CACHED_HEAD" "$CURRENT_HEAD" 2>/dev/null | grep -E '^[ADR]' | head -20)" || true
|
|
99
|
+
[ -n "$ADD_DEL" ] && STRUCTURE_CHANGED=true
|
|
100
|
+
|
|
101
|
+
CONFIG_PATTERN="package\.json$|tsconfig\.json$|\.eslintrc|next\.config|vite\.config|nest-cli\.json|docker-compose"
|
|
102
|
+
CONFIG_CHANGED="$(git diff --name-only "$CACHED_HEAD" "$CURRENT_HEAD" 2>/dev/null | grep -E "$CONFIG_PATTERN" | head -5)" || true
|
|
103
|
+
[ -n "$CONFIG_CHANGED" ] && STRUCTURE_CHANGED=true
|
|
104
|
+
|
|
105
|
+
printf '%s' "$CURRENT_HEAD" > "$CACHE_FILE"
|
|
106
|
+
|
|
107
|
+
if [ "$STRUCTURE_CHANGED" = true ]; then
|
|
108
|
+
echo "[PROJECT_MAP] 구조 변경 감지. 갱신 권장: .claude/scripts/generate-project-map.sh"
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
111
|
+
else
|
|
112
|
+
printf '%s' "$CURRENT_HEAD" > "$CACHE_FILE"
|
|
113
|
+
fi
|
|
114
|
+
fi
|
|
115
|
+
fi
|
|
@@ -16,4 +16,6 @@ TypeScript|TypeScript|typescript 타입스크립트 타입 type interface generi
|
|
|
16
16
|
TypeORM|TypeORM|typeorm 타입오알엠 entity repository querybuilder migration 마이그레이션 relation 트랜잭션 transaction
|
|
17
17
|
Coding|Coding|리팩토링 refactor 클린코드 clean code 네이밍 naming 에러처리 error handling 코드품질
|
|
18
18
|
TDD|TDD|tdd 테스트 test jest vitest 단위테스트 unit test e2e 통합테스트 integration test mock stub spy
|
|
19
|
-
Git|Git|git 커밋 commit 브랜치 branch pr pull request merge rebase
|
|
19
|
+
Git|Git|git 커밋 commit 브랜치 branch pr pull request merge rebase
|
|
20
|
+
DDD|DDD|ddd domain entity value object aggregate repository domain service domain event bounded context 도메인 엔티티 값객체 애그리거트 리포지토리 도메인서비스 도메인이벤트
|
|
21
|
+
Planning|Planning|계획 설계 기획 plan planning 작업분해 영향범위 아키텍처 architecture 구조설계
|
package/.claude/settings.json
CHANGED
|
@@ -6,18 +6,10 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": ".claude/hooks/
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"type": "command",
|
|
13
|
-
"command": ".claude/hooks/skill-detector.sh"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"type": "command",
|
|
17
|
-
"command": ".claude/hooks/project-map-detector.sh"
|
|
9
|
+
"command": ".claude/hooks/prompt-hook.sh"
|
|
18
10
|
}
|
|
19
11
|
]
|
|
20
12
|
}
|
|
21
13
|
]
|
|
22
14
|
}
|
|
23
|
-
}
|
|
15
|
+
}
|
|
@@ -26,10 +26,108 @@ FE/BE별 상세 규칙은 각각의 파일을 참고한다.
|
|
|
26
26
|
- **높은 응집도**: 관련 있는 로직을 같은 모듈에 모은다
|
|
27
27
|
- 하나의 모듈 안에서 데이터와 동작이 밀접하게 관련된다
|
|
28
28
|
|
|
29
|
+
### Early Return (Guard Clause)
|
|
30
|
+
- 예외/실패 조건을 함수 상단에서 먼저 처리하고 빠져나온다
|
|
31
|
+
- 중첩 if를 줄여 가독성을 높인다
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Bad - 깊은 중첩
|
|
35
|
+
function processOrder(order: Order): void {
|
|
36
|
+
if (order) {
|
|
37
|
+
if (order.isValid()) {
|
|
38
|
+
if (order.items.length > 0) {
|
|
39
|
+
// 실제 로직
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Good - 얼리 리턴
|
|
46
|
+
function processOrder(order: Order): void {
|
|
47
|
+
if (!order) return;
|
|
48
|
+
if (!order.isValid()) return;
|
|
49
|
+
if (order.items.length === 0) return;
|
|
50
|
+
|
|
51
|
+
// 실제 로직
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
29
55
|
### DRY (Don't Repeat Yourself)
|
|
30
56
|
- 동일한 로직이 3번 이상 반복되면 추출한다
|
|
31
57
|
- 단, 2번까지는 중복을 허용한다 (섣부른 추상화 방지)
|
|
32
58
|
|
|
59
|
+
### 선언적 & 함수형 스타일
|
|
60
|
+
|
|
61
|
+
**선언적 > 명령적** - "어떻게(how)" 대신 "무엇(what)"을 표현한다.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// Bad - 명령적: 루프로 직접 조작
|
|
65
|
+
const activeNames: string[] = [];
|
|
66
|
+
for (const user of users) {
|
|
67
|
+
if (user.isActive) {
|
|
68
|
+
activeNames.push(user.name);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Good - 선언적: 의도를 바로 드러냄
|
|
73
|
+
// 필터/변환 조건은 변수로 추출하여 합성하라
|
|
74
|
+
const isActive = (u: User) => u.isActive;
|
|
75
|
+
const toName = (u: User) => u.name;
|
|
76
|
+
const activeNames = users.filter(isActive).map(toName);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**순수 함수 우선** - 같은 입력이면 항상 같은 출력, 부수효과 없음.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Bad - 외부 상태를 변경
|
|
83
|
+
let totalPrice = 0;
|
|
84
|
+
function addItemPrice(price: number): void {
|
|
85
|
+
totalPrice += price; // 외부 변수 변경
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Good - 새 값을 반환하는 순수 함수
|
|
89
|
+
function calculateTotal(prices: number[]): number {
|
|
90
|
+
return prices.reduce((sum, price) => sum + price, 0);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**불변성** - 기존 데이터를 변경하지 않고 새 데이터를 생성한다.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// Bad - 원본 변경
|
|
98
|
+
user.role = 'admin';
|
|
99
|
+
items.push(newItem);
|
|
100
|
+
|
|
101
|
+
// Good - 새 객체/배열 생성
|
|
102
|
+
const promotedUser = { ...user, role: 'admin' };
|
|
103
|
+
const updatedItems = [...items, newItem];
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**부수효과 격리** - 순수 로직과 부수효과(I/O)를 분리한다.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Bad - 비즈니스 로직에 부수효과가 섞임
|
|
110
|
+
async function processOrder(order: Order): Promise<void> {
|
|
111
|
+
const total = order.items.reduce((s, i) => s + i.price, 0);
|
|
112
|
+
const discount = total > 100 ? total * 0.1 : 0;
|
|
113
|
+
await db.save({ ...order, total: total - discount }); // 부수효과
|
|
114
|
+
await sendEmail(order.userId, total - discount); // 부수효과
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Good - 순수 계산과 부수효과를 분리
|
|
118
|
+
function calcOrderTotal(items: OrderItem[]): number {
|
|
119
|
+
const total = items.reduce((s, i) => s + i.price, 0);
|
|
120
|
+
const discount = total > 100 ? total * 0.1 : 0;
|
|
121
|
+
return total - discount;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function processOrder(order: Order): Promise<void> {
|
|
125
|
+
const finalTotal = calcOrderTotal(order.items); // 순수
|
|
126
|
+
await db.save({ ...order, total: finalTotal }); // I/O 경계
|
|
127
|
+
await sendEmail(order.userId, finalTotal); // I/O 경계
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
33
131
|
---
|
|
34
132
|
|
|
35
133
|
## 2. 네이밍 컨벤션
|