@choblue/claude-code-toolkit 1.0.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/.gitkeep +0 -0
- package/.claude/CLAUDE.md +100 -0
- package/.claude/agents/code-reviewer.md +87 -0
- package/.claude/agents/code-writer/backend.md +95 -0
- package/.claude/agents/code-writer/common.md +79 -0
- package/.claude/agents/code-writer/frontend.md +91 -0
- package/.claude/agents/explore.md +54 -0
- package/.claude/agents/git-manager.md +102 -0
- package/.claude/agents/tdd/backend.md +207 -0
- package/.claude/agents/tdd/common.md +137 -0
- package/.claude/agents/tdd/frontend.md +250 -0
- package/.claude/hooks/quality-gate.sh +17 -0
- package/.claude/settings.json +15 -0
- package/.claude/skills/Coding/SKILL.md +108 -0
- package/.claude/skills/Coding/backend.md +97 -0
- package/.claude/skills/Coding/frontend.md +11 -0
- package/.claude/skills/Git/SKILL.md +93 -0
- package/.claude/skills/NextJS/SKILL.md +424 -0
- package/.claude/skills/React/SKILL.md +261 -0
- package/.claude/skills/ReactHookForm/SKILL.md +317 -0
- package/.claude/skills/TDD/SKILL.md +161 -0
- package/.claude/skills/TDD/backend.md +356 -0
- package/.claude/skills/TDD/frontend.md +392 -0
- package/.claude/skills/TailwindCSS/SKILL.md +368 -0
- package/.claude/skills/TanStackQuery/SKILL.md +242 -0
- package/.claude/skills/TypeORM/SKILL.md +621 -0
- package/.claude/skills/TypeScript/SKILL.md +528 -0
- package/.claude/skills/Zustand/SKILL.md +285 -0
- package/README.md +157 -0
- package/bin/cli.js +18 -0
- package/install.sh +255 -0
- package/package.json +27 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# Zustand Skill - 클라이언트 상태 관리 규칙
|
|
2
|
+
|
|
3
|
+
Zustand를 사용한 클라이언트 상태 관리 규칙을 정의한다.
|
|
4
|
+
서버 상태 관리는 `../TanStackQuery/SKILL.md`를 참고한다.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. 기본 패턴
|
|
9
|
+
|
|
10
|
+
### Store 생성
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { create } from 'zustand';
|
|
14
|
+
|
|
15
|
+
interface CounterStore {
|
|
16
|
+
count: number;
|
|
17
|
+
increment: () => void;
|
|
18
|
+
decrement: () => void;
|
|
19
|
+
reset: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const useCounterStore = create<CounterStore>((set) => ({
|
|
23
|
+
count: 0,
|
|
24
|
+
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
25
|
+
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
26
|
+
reset: () => set({ count: 0 }),
|
|
27
|
+
}));
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Selector로 구독
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// Good - 필요한 값만 구독 (리렌더링 최소화)
|
|
34
|
+
const count = useCounterStore((state) => state.count);
|
|
35
|
+
const increment = useCounterStore((state) => state.increment);
|
|
36
|
+
|
|
37
|
+
// Bad - 전체 store 구독 (불필요한 리렌더링 발생)
|
|
38
|
+
const store = useCounterStore();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 2. Store 설계 원칙
|
|
44
|
+
|
|
45
|
+
### 도메인별 분리
|
|
46
|
+
- 하나의 store는 하나의 관심사만 담당한다
|
|
47
|
+
- 관련 없는 상태를 하나의 store에 넣지 않는다
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Good - 도메인별 분리
|
|
51
|
+
const useAuthStore = create<AuthStore>(() => ({ /* 인증 관련 */ }));
|
|
52
|
+
const useUIStore = create<UIStore>(() => ({ /* UI 상태 관련 */ }));
|
|
53
|
+
const useCartStore = create<CartStore>(() => ({ /* 장바구니 관련 */ }));
|
|
54
|
+
|
|
55
|
+
// Bad - 모든 것을 하나의 store에
|
|
56
|
+
const useAppStore = create<AppStore>(() => ({
|
|
57
|
+
user: null,
|
|
58
|
+
isModalOpen: false,
|
|
59
|
+
cartItems: [],
|
|
60
|
+
theme: 'light',
|
|
61
|
+
// ... 모든 상태가 뒤섞임
|
|
62
|
+
}));
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 서버 상태와 클라이언트 상태 분리
|
|
66
|
+
|
|
67
|
+
| 상태 유형 | 관리 도구 | 예시 |
|
|
68
|
+
|-----------|-----------|------|
|
|
69
|
+
| 서버 상태 | TanStack Query | 사용자 목록, 게시글, API 응답 |
|
|
70
|
+
| 클라이언트 상태 | Zustand | 모달 열림/닫힘, 테마, 사이드바 상태 |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 3. Selector 패턴
|
|
75
|
+
|
|
76
|
+
### 개별 Selector
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Good - 각 값을 개별 selector로 구독
|
|
80
|
+
function UserInfo() {
|
|
81
|
+
const userName = useAuthStore((state) => state.userName);
|
|
82
|
+
const avatarUrl = useAuthStore((state) => state.avatarUrl);
|
|
83
|
+
return <div>{userName}</div>;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Shallow 비교
|
|
88
|
+
|
|
89
|
+
여러 값을 한 번에 가져올 때 `useShallow`로 불필요한 리렌더링을 방지한다.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { useShallow } from 'zustand/react/shallow';
|
|
93
|
+
|
|
94
|
+
// Good - 객체 반환 시 useShallow 사용
|
|
95
|
+
const { userName, avatarUrl } = useAuthStore(
|
|
96
|
+
useShallow((state) => ({
|
|
97
|
+
userName: state.userName,
|
|
98
|
+
avatarUrl: state.avatarUrl,
|
|
99
|
+
}))
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 4. Actions 패턴
|
|
106
|
+
|
|
107
|
+
### set과 get 사용
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
interface TodoStore {
|
|
111
|
+
todos: Todo[];
|
|
112
|
+
addTodo: (text: string) => void;
|
|
113
|
+
removeTodo: (id: string) => void;
|
|
114
|
+
toggleTodo: (id: string) => void;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const useTodoStore = create<TodoStore>((set, get) => ({
|
|
118
|
+
todos: [],
|
|
119
|
+
|
|
120
|
+
addTodo: (text) =>
|
|
121
|
+
set((state) => ({
|
|
122
|
+
todos: [...state.todos, { id: crypto.randomUUID(), text, done: false }],
|
|
123
|
+
})),
|
|
124
|
+
|
|
125
|
+
removeTodo: (id) =>
|
|
126
|
+
set((state) => ({
|
|
127
|
+
todos: state.todos.filter((todo) => todo.id !== id),
|
|
128
|
+
})),
|
|
129
|
+
|
|
130
|
+
// get()으로 현재 상태를 읽어야 할 때
|
|
131
|
+
toggleTodo: (id) => {
|
|
132
|
+
const todo = get().todos.find((t) => t.id === id);
|
|
133
|
+
if (!todo) return;
|
|
134
|
+
set((state) => ({
|
|
135
|
+
todos: state.todos.map((t) =>
|
|
136
|
+
t.id === id ? { ...t, done: !t.done } : t
|
|
137
|
+
),
|
|
138
|
+
}));
|
|
139
|
+
},
|
|
140
|
+
}));
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Action은 Store 안에 정의한다
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// Good - action이 store 안에 있음
|
|
147
|
+
export const useCartStore = create<CartStore>((set) => ({
|
|
148
|
+
items: [],
|
|
149
|
+
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
|
|
150
|
+
}));
|
|
151
|
+
|
|
152
|
+
// Bad - action이 store 밖에 있음
|
|
153
|
+
export const useCartStore = create<CartStore>(() => ({
|
|
154
|
+
items: [],
|
|
155
|
+
}));
|
|
156
|
+
|
|
157
|
+
export function addItem(item: CartItem) {
|
|
158
|
+
useCartStore.setState((state) => ({ items: [...state.items, item] }));
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 5. Middleware
|
|
165
|
+
|
|
166
|
+
### persist - localStorage 저장
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { persist } from 'zustand/middleware';
|
|
170
|
+
|
|
171
|
+
export const useSettingsStore = create<SettingsStore>()(
|
|
172
|
+
persist(
|
|
173
|
+
(set) => ({
|
|
174
|
+
theme: 'light' as const,
|
|
175
|
+
language: 'ko' as const,
|
|
176
|
+
setTheme: (theme) => set({ theme }),
|
|
177
|
+
setLanguage: (language) => set({ language }),
|
|
178
|
+
}),
|
|
179
|
+
{
|
|
180
|
+
name: 'settings-storage', // localStorage key
|
|
181
|
+
partialize: (state) => ({
|
|
182
|
+
theme: state.theme,
|
|
183
|
+
language: state.language,
|
|
184
|
+
}), // 저장할 상태만 선택
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### devtools - 개발 도구 연동
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { devtools } from 'zustand/middleware';
|
|
194
|
+
|
|
195
|
+
export const useAuthStore = create<AuthStore>()(
|
|
196
|
+
devtools(
|
|
197
|
+
(set) => ({
|
|
198
|
+
user: null,
|
|
199
|
+
login: (user) => set({ user }, false, 'auth/login'),
|
|
200
|
+
logout: () => set({ user: null }, false, 'auth/logout'),
|
|
201
|
+
}),
|
|
202
|
+
{ name: 'AuthStore' }
|
|
203
|
+
)
|
|
204
|
+
);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### immer - 불변 업데이트 간소화
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { immer } from 'zustand/middleware/immer';
|
|
211
|
+
|
|
212
|
+
export const useTodoStore = create<TodoStore>()(
|
|
213
|
+
immer((set) => ({
|
|
214
|
+
todos: [],
|
|
215
|
+
toggleTodo: (id) =>
|
|
216
|
+
set((state) => {
|
|
217
|
+
const todo = state.todos.find((t) => t.id === id);
|
|
218
|
+
if (todo) todo.done = !todo.done; // 직접 변경 가능
|
|
219
|
+
}),
|
|
220
|
+
}))
|
|
221
|
+
);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 6. Slice 패턴
|
|
227
|
+
|
|
228
|
+
큰 store를 slice로 분리하여 합치는 패턴이다.
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// authSlice.ts
|
|
232
|
+
interface AuthSlice {
|
|
233
|
+
user: User | null;
|
|
234
|
+
login: (user: User) => void;
|
|
235
|
+
logout: () => void;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const createAuthSlice: StateCreator<StoreState, [], [], AuthSlice> = (set) => ({
|
|
239
|
+
user: null,
|
|
240
|
+
login: (user) => set({ user }),
|
|
241
|
+
logout: () => set({ user: null }),
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// uiSlice.ts
|
|
245
|
+
interface UISlice {
|
|
246
|
+
isSidebarOpen: boolean;
|
|
247
|
+
toggleSidebar: () => void;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const createUISlice: StateCreator<StoreState, [], [], UISlice> = (set) => ({
|
|
251
|
+
isSidebarOpen: true,
|
|
252
|
+
toggleSidebar: () => set((state) => ({ isSidebarOpen: !state.isSidebarOpen })),
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// store.ts - Slice 합치기
|
|
256
|
+
type StoreState = AuthSlice & UISlice;
|
|
257
|
+
|
|
258
|
+
export const useAppStore = create<StoreState>()((...args) => ({
|
|
259
|
+
...createAuthSlice(...args),
|
|
260
|
+
...createUISlice(...args),
|
|
261
|
+
}));
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## 7. 네이밍 컨벤션
|
|
267
|
+
|
|
268
|
+
| 대상 | 규칙 | 예시 |
|
|
269
|
+
|------|------|------|
|
|
270
|
+
| Store 훅 | `use` + 도메인 + `Store` | `useAuthStore`, `useCartStore` |
|
|
271
|
+
| Store 파일 | 도메인 + `Store.ts` | `authStore.ts`, `cartStore.ts` |
|
|
272
|
+
| Slice 파일 | 도메인 + `Slice.ts` | `authSlice.ts`, `uiSlice.ts` |
|
|
273
|
+
| Action | 동사 + 명사 (camelCase) | `addItem`, `setTheme`, `toggleSidebar` |
|
|
274
|
+
| Store 디렉토리 | `stores/` | `src/stores/authStore.ts` |
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 8. 금지 사항
|
|
279
|
+
|
|
280
|
+
- Store에 서버 데이터 캐싱 금지 - 서버 상태는 TanStack Query를 사용한다
|
|
281
|
+
- 거대한 단일 store 금지 - 도메인별로 분리한다
|
|
282
|
+
- Selector 없이 전체 store 구독 금지 - 필요한 값만 개별 selector로 가져온다
|
|
283
|
+
- Store 밖에서 action 정의 금지 - action은 store 안에 정의한다
|
|
284
|
+
- `any` 타입 사용 금지 - store에 명시적 타입을 정의한다
|
|
285
|
+
- 컴포넌트 안에서 `useStore.setState()` 직접 호출 금지 - store에 정의된 action을 사용한다
|
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# my-claude-code-toolkit
|
|
2
|
+
|
|
3
|
+
매번 새 프로젝트마다 Claude Code 세팅을 처음부터 만드는 비효율을 해결한다.
|
|
4
|
+
`~/.claude/` 글로벌 설정으로 모든 프로젝트에 자동 적용되며, 이 GitHub 레포로 버전 관리한다.
|
|
5
|
+
|
|
6
|
+
## 기술 스택
|
|
7
|
+
|
|
8
|
+
- Frontend: React (TypeScript)
|
|
9
|
+
- Backend: NestJS (TypeScript)
|
|
10
|
+
- 방식: 서브에이전트 위임 (Context 절약)
|
|
11
|
+
|
|
12
|
+
## 구조
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
.claude/
|
|
16
|
+
├── CLAUDE.md ← 핵심 워크플로우/위임 규칙
|
|
17
|
+
├── settings.json ← hooks 설정
|
|
18
|
+
├── agents/
|
|
19
|
+
│ ├── explore.md ← 코드베이스 탐색 (haiku)
|
|
20
|
+
│ ├── code-writer/
|
|
21
|
+
│ │ ├── common.md ← 공통 구현 규칙
|
|
22
|
+
│ │ ├── backend.md ← NestJS 백엔드 규칙
|
|
23
|
+
│ │ └── frontend.md ← React 프론트엔드 규칙
|
|
24
|
+
│ ├── code-reviewer.md ← 코드 품질 리뷰 (opus)
|
|
25
|
+
│ ├── tdd/
|
|
26
|
+
│ │ ├── common.md ← TDD 공통 규칙
|
|
27
|
+
│ │ ├── backend.md ← NestJS 테스트 규칙
|
|
28
|
+
│ │ └── frontend.md ← React 테스트 규칙
|
|
29
|
+
│ └── git-manager.md ← Git 작업 (sonnet)
|
|
30
|
+
├── skills/
|
|
31
|
+
│ ├── Coding/
|
|
32
|
+
│ │ ├── SKILL.md ← 공통 코딩 원칙
|
|
33
|
+
│ │ └── backend.md ← NestJS 코딩 규칙
|
|
34
|
+
│ ├── React/
|
|
35
|
+
│ │ └── SKILL.md ← React 컴포넌트, 훅, 상태 관리
|
|
36
|
+
│ ├── NextJS/
|
|
37
|
+
│ │ └── SKILL.md ← Next.js App Router, SSR
|
|
38
|
+
│ ├── TailwindCSS/
|
|
39
|
+
│ │ └── SKILL.md ← Tailwind CSS 유틸리티 패턴
|
|
40
|
+
│ ├── TanStackQuery/
|
|
41
|
+
│ │ └── SKILL.md ← TanStack Query 서버 상태
|
|
42
|
+
│ ├── Zustand/
|
|
43
|
+
│ │ └── SKILL.md ← Zustand 클라이언트 상태
|
|
44
|
+
│ ├── ReactHookForm/
|
|
45
|
+
│ │ └── SKILL.md ← React Hook Form + Zod
|
|
46
|
+
│ ├── TypeScript/
|
|
47
|
+
│ │ └── SKILL.md ← TypeScript 고급 패턴
|
|
48
|
+
│ ├── TypeORM/
|
|
49
|
+
│ │ └── SKILL.md ← TypeORM Entity, Repository
|
|
50
|
+
│ ├── TDD/
|
|
51
|
+
│ │ ├── SKILL.md ← TDD 핵심 원칙
|
|
52
|
+
│ │ ├── frontend.md ← React 테스트 규칙
|
|
53
|
+
│ │ └── backend.md ← NestJS 테스트 규칙
|
|
54
|
+
│ └── Git/
|
|
55
|
+
│ └── SKILL.md ← 커밋/PR/브랜치 규칙
|
|
56
|
+
└── hooks/
|
|
57
|
+
└── quality-gate.sh ← 품질 체크 프로토콜
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 설치
|
|
61
|
+
|
|
62
|
+
### npx로 설치 (권장)
|
|
63
|
+
|
|
64
|
+
clone 없이 바로 설치할 수 있다.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# 전체 설치 (프로젝트 로컬)
|
|
68
|
+
npx @choblue/claude-code-toolkit
|
|
69
|
+
|
|
70
|
+
# FE만 설치 (공통 + React, Next.js, TailwindCSS 등)
|
|
71
|
+
npx @choblue/claude-code-toolkit --fe
|
|
72
|
+
|
|
73
|
+
# BE만 설치 (공통 + NestJS, TypeORM 등)
|
|
74
|
+
npx @choblue/claude-code-toolkit --be
|
|
75
|
+
|
|
76
|
+
# 글로벌 설치
|
|
77
|
+
npx @choblue/claude-code-toolkit --global
|
|
78
|
+
|
|
79
|
+
# 글로벌 + FE만
|
|
80
|
+
npx @choblue/claude-code-toolkit --global --fe
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 소스에서 설치
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# 레포 클론
|
|
87
|
+
git clone https://github.com/choblue/my-claude-code-toolkit.git
|
|
88
|
+
cd my-claude-code-toolkit
|
|
89
|
+
|
|
90
|
+
# 전체 설치 (프로젝트 로컬)
|
|
91
|
+
./install.sh
|
|
92
|
+
|
|
93
|
+
# FE만 설치
|
|
94
|
+
./install.sh --fe
|
|
95
|
+
|
|
96
|
+
# BE만 설치
|
|
97
|
+
./install.sh --be
|
|
98
|
+
|
|
99
|
+
# 글로벌 설치
|
|
100
|
+
./install.sh --global
|
|
101
|
+
|
|
102
|
+
# 글로벌 + FE만
|
|
103
|
+
./install.sh --global --fe
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| 옵션 | 설명 |
|
|
107
|
+
|------|------|
|
|
108
|
+
| (없음) | 전체 설치 (FE + BE), 프로젝트 로컬 |
|
|
109
|
+
| `--fe` | 공통 + FE 스킬만 설치 |
|
|
110
|
+
| `--be` | 공통 + BE 스킬만 설치 |
|
|
111
|
+
| `--fe --be` | 전체 설치 (기본값과 동일) |
|
|
112
|
+
| `--global` | `~/.claude/`에 글로벌 설치 |
|
|
113
|
+
|
|
114
|
+
기존 `.claude/` 파일이 있으면 자동으로 백업 후 덮어쓴다.
|
|
115
|
+
|
|
116
|
+
## 작동 방식
|
|
117
|
+
|
|
118
|
+
### 워크플로우 (Planning → Test → Implementation → Review)
|
|
119
|
+
|
|
120
|
+
1. **Planning**: 사용자 요구사항 분석 → `explore` 에이전트로 코드 탐색 → 작업 계획 제시
|
|
121
|
+
2. **Test (Red)**: `tdd` 에이전트로 실패하는 테스트 작성 → Red 상태 확인
|
|
122
|
+
3. **Implementation (Green + Refactor)**: `code-writer` 에이전트에 구현 위임 → 테스트 통과 확인
|
|
123
|
+
4. **Review**: `code-reviewer`로 코드 + 테스트 리뷰 → `git-manager`로 커밋/PR 생성
|
|
124
|
+
|
|
125
|
+
### 서브에이전트 위임
|
|
126
|
+
|
|
127
|
+
Main Agent는 직접 코드를 작성하거나 탐색하지 않고, 전문 서브에이전트에 위임한다.
|
|
128
|
+
이를 통해 Context Window를 절약하고 각 작업에 최적화된 프롬프트를 사용한다.
|
|
129
|
+
|
|
130
|
+
| 에이전트 | 모델 | 역할 |
|
|
131
|
+
|---------|------|------|
|
|
132
|
+
| explore | haiku | 빠른 코드베이스 탐색 |
|
|
133
|
+
| code-writer | opus | FE/BE 코드 구현 |
|
|
134
|
+
| code-reviewer | opus | 코드 품질 리뷰 |
|
|
135
|
+
| tdd | opus | TDD 테스트 작성/실행 |
|
|
136
|
+
| git-manager | sonnet | 커밋, 브랜치, PR |
|
|
137
|
+
|
|
138
|
+
### Quality Gate Hook
|
|
139
|
+
|
|
140
|
+
매 프롬프트마다 `quality-gate.sh`가 실행되어 적절한 에이전트/스킬 활용을 상기시킨다.
|
|
141
|
+
|
|
142
|
+
## 프로젝트별 커스터마이징
|
|
143
|
+
|
|
144
|
+
프로젝트 루트에 `CLAUDE.md`를 추가하면 글로벌 규칙보다 우선 적용된다.
|
|
145
|
+
프로젝트별 규칙은 글로벌 규칙을 확장하되, 충돌 시 프로젝트 규칙을 따른다.
|
|
146
|
+
|
|
147
|
+
## 업데이트
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# npx는 항상 최신 버전을 실행한다
|
|
151
|
+
npx @choblue/claude-code-toolkit
|
|
152
|
+
|
|
153
|
+
# 소스에서 설치한 경우
|
|
154
|
+
cd my-claude-code-toolkit
|
|
155
|
+
git pull
|
|
156
|
+
./install.sh
|
|
157
|
+
```
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
7
|
+
const installScript = path.join(packageRoot, "install.sh");
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2).join(" ");
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
execSync(`bash "${installScript}" ${args}`, {
|
|
13
|
+
stdio: "inherit",
|
|
14
|
+
env: { ...process.env, PACKAGE_ROOT: packageRoot },
|
|
15
|
+
});
|
|
16
|
+
} catch (err) {
|
|
17
|
+
process.exit(err.status || 1);
|
|
18
|
+
}
|
package/install.sh
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# install.sh - my-claude-code-toolkit을 .claude/에 설치한다
|
|
3
|
+
# Usage: ./install.sh [--global] [--fe] [--be]
|
|
4
|
+
# 기본값: 현재 디렉토리의 .claude/에 전체 설치 (프로젝트 로컬)
|
|
5
|
+
# --global: ~/.claude/에 설치 (글로벌)
|
|
6
|
+
# --fe: 공통 + 프론트엔드 스킬만 설치
|
|
7
|
+
# --be: 공통 + 백엔드 스킬만 설치
|
|
8
|
+
# --fe --be: 전체 설치 (= 기본값)
|
|
9
|
+
|
|
10
|
+
set -e
|
|
11
|
+
|
|
12
|
+
# 도움말 출력
|
|
13
|
+
usage() {
|
|
14
|
+
echo "Usage: $0 [--global] [--fe] [--be]"
|
|
15
|
+
echo ""
|
|
16
|
+
echo "스택 선택:"
|
|
17
|
+
echo " (기본값) 전체 설치 (공통 + FE + BE)"
|
|
18
|
+
echo " --fe 공통 + 프론트엔드만 설치"
|
|
19
|
+
echo " --be 공통 + 백엔드만 설치"
|
|
20
|
+
echo " --fe --be 전체 설치 (= 기본값)"
|
|
21
|
+
echo ""
|
|
22
|
+
echo "설치 위치:"
|
|
23
|
+
echo " (기본값) 현재 디렉토리의 .claude/에 설치 (프로젝트 로컬)"
|
|
24
|
+
echo " --global ~/.claude/에 설치 (글로벌)"
|
|
25
|
+
echo ""
|
|
26
|
+
echo "예시:"
|
|
27
|
+
echo " $0 # 전체 설치 (로컬)"
|
|
28
|
+
echo " $0 --fe # 공통 + FE만 (로컬)"
|
|
29
|
+
echo " $0 --be # 공통 + BE만 (로컬)"
|
|
30
|
+
echo " $0 --global --fe # 공통 + FE만 (글로벌)"
|
|
31
|
+
exit 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# 인자 파싱
|
|
35
|
+
INSTALL_MODE="local"
|
|
36
|
+
INSTALL_FE=false
|
|
37
|
+
INSTALL_BE=false
|
|
38
|
+
|
|
39
|
+
for arg in "$@"; do
|
|
40
|
+
case "$arg" in
|
|
41
|
+
--global)
|
|
42
|
+
INSTALL_MODE="global"
|
|
43
|
+
;;
|
|
44
|
+
--fe)
|
|
45
|
+
INSTALL_FE=true
|
|
46
|
+
;;
|
|
47
|
+
--be)
|
|
48
|
+
INSTALL_BE=true
|
|
49
|
+
;;
|
|
50
|
+
--help|-h)
|
|
51
|
+
usage
|
|
52
|
+
;;
|
|
53
|
+
*)
|
|
54
|
+
echo "Error: 알 수 없는 옵션 '$arg'"
|
|
55
|
+
usage
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
# --fe도 --be도 지정하지 않으면 둘 다 true (전체 설치)
|
|
61
|
+
if [ "$INSTALL_FE" = false ] && [ "$INSTALL_BE" = false ]; then
|
|
62
|
+
INSTALL_FE=true
|
|
63
|
+
INSTALL_BE=true
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# 스택 라벨 결정
|
|
67
|
+
if [ "$INSTALL_FE" = true ] && [ "$INSTALL_BE" = true ]; then
|
|
68
|
+
STACK_LABEL="FE + BE (전체)"
|
|
69
|
+
elif [ "$INSTALL_FE" = true ]; then
|
|
70
|
+
STACK_LABEL="FE만"
|
|
71
|
+
else
|
|
72
|
+
STACK_LABEL="BE만"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
if [ -n "$PACKAGE_ROOT" ]; then
|
|
76
|
+
SOURCE_DIR="$PACKAGE_ROOT/.claude"
|
|
77
|
+
else
|
|
78
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
79
|
+
SOURCE_DIR="$SCRIPT_DIR/.claude"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
if [ "$INSTALL_MODE" = "global" ]; then
|
|
83
|
+
TARGET_DIR="$HOME/.claude"
|
|
84
|
+
BACKUP_DIR="$HOME/.claude-backup-$(date +%Y%m%d_%H%M%S)"
|
|
85
|
+
MODE_LABEL="글로벌 설치 (~/.claude/)"
|
|
86
|
+
else
|
|
87
|
+
TARGET_DIR="$(pwd)/.claude"
|
|
88
|
+
BACKUP_DIR="$(pwd)/.claude-backup-$(date +%Y%m%d_%H%M%S)"
|
|
89
|
+
MODE_LABEL="로컬 설치 ($(pwd)/.claude/)"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
echo "=== my-claude-code-toolkit 설치 ==="
|
|
93
|
+
echo ""
|
|
94
|
+
echo "모드: $MODE_LABEL"
|
|
95
|
+
echo "스택: $STACK_LABEL"
|
|
96
|
+
echo "Source: $SOURCE_DIR"
|
|
97
|
+
echo "Target: $TARGET_DIR"
|
|
98
|
+
echo ""
|
|
99
|
+
|
|
100
|
+
# Source 디렉토리 존재 확인
|
|
101
|
+
if [ ! -d "$SOURCE_DIR" ]; then
|
|
102
|
+
echo "Error: $SOURCE_DIR 디렉토리를 찾을 수 없습니다."
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# 기존 파일 백업
|
|
107
|
+
if [ -d "$TARGET_DIR" ]; then
|
|
108
|
+
# 백업할 파일이 있는지 확인 (agents, skills, hooks, CLAUDE.md, settings.json)
|
|
109
|
+
HAS_EXISTING=false
|
|
110
|
+
for item in CLAUDE.md settings.json agents skills hooks; do
|
|
111
|
+
if [ -e "$TARGET_DIR/$item" ]; then
|
|
112
|
+
HAS_EXISTING=true
|
|
113
|
+
break
|
|
114
|
+
fi
|
|
115
|
+
done
|
|
116
|
+
|
|
117
|
+
if [ "$HAS_EXISTING" = true ]; then
|
|
118
|
+
echo "기존 설정 파일이 발견되었습니다. 백업합니다..."
|
|
119
|
+
mkdir -p "$BACKUP_DIR"
|
|
120
|
+
for item in CLAUDE.md settings.json agents skills hooks; do
|
|
121
|
+
if [ -e "$TARGET_DIR/$item" ]; then
|
|
122
|
+
cp -r "$TARGET_DIR/$item" "$BACKUP_DIR/"
|
|
123
|
+
echo " 백업: $item → $BACKUP_DIR/$item"
|
|
124
|
+
fi
|
|
125
|
+
done
|
|
126
|
+
echo ""
|
|
127
|
+
echo "백업 완료: $BACKUP_DIR"
|
|
128
|
+
echo ""
|
|
129
|
+
fi
|
|
130
|
+
else
|
|
131
|
+
mkdir -p "$TARGET_DIR"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
# === 헬퍼 함수 ===
|
|
135
|
+
|
|
136
|
+
# 개별 파일 복사 (상대경로 기준)
|
|
137
|
+
copy_file() {
|
|
138
|
+
local rel_path="$1"
|
|
139
|
+
local dir
|
|
140
|
+
dir="$(dirname "$rel_path")"
|
|
141
|
+
mkdir -p "$TARGET_DIR/$dir"
|
|
142
|
+
cp "$SOURCE_DIR/$rel_path" "$TARGET_DIR/$rel_path"
|
|
143
|
+
echo " 복사: $rel_path"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# 디렉토리 전체 복사 (상대경로 기준)
|
|
147
|
+
copy_dir() {
|
|
148
|
+
local rel_path="$1"
|
|
149
|
+
mkdir -p "$TARGET_DIR/$rel_path"
|
|
150
|
+
cp -r "$SOURCE_DIR/$rel_path/"* "$TARGET_DIR/$rel_path/"
|
|
151
|
+
echo " 복사: $rel_path/"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# === 복사 함수 ===
|
|
155
|
+
|
|
156
|
+
# 공통 (항상 설치)
|
|
157
|
+
copy_common() {
|
|
158
|
+
echo "[공통]"
|
|
159
|
+
|
|
160
|
+
# 루트 설정 파일
|
|
161
|
+
copy_file "CLAUDE.md"
|
|
162
|
+
copy_file "settings.json"
|
|
163
|
+
|
|
164
|
+
# 공통 에이전트
|
|
165
|
+
copy_file "agents/explore.md"
|
|
166
|
+
copy_file "agents/code-reviewer.md"
|
|
167
|
+
copy_file "agents/git-manager.md"
|
|
168
|
+
copy_file "agents/code-writer/common.md"
|
|
169
|
+
copy_file "agents/tdd/common.md"
|
|
170
|
+
|
|
171
|
+
# 공통 스킬
|
|
172
|
+
copy_file "skills/Coding/SKILL.md"
|
|
173
|
+
copy_dir "skills/TypeScript"
|
|
174
|
+
copy_dir "skills/Git"
|
|
175
|
+
copy_file "skills/TDD/SKILL.md"
|
|
176
|
+
|
|
177
|
+
# hooks
|
|
178
|
+
copy_dir "hooks"
|
|
179
|
+
chmod +x "$TARGET_DIR/hooks/"*.sh
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
# FE (프론트엔드)
|
|
183
|
+
copy_fe() {
|
|
184
|
+
echo "[FE]"
|
|
185
|
+
|
|
186
|
+
# FE 에이전트
|
|
187
|
+
copy_file "agents/code-writer/frontend.md"
|
|
188
|
+
copy_file "agents/tdd/frontend.md"
|
|
189
|
+
|
|
190
|
+
# FE 스킬 (디렉토리 전체)
|
|
191
|
+
copy_dir "skills/React"
|
|
192
|
+
copy_dir "skills/NextJS"
|
|
193
|
+
copy_dir "skills/TailwindCSS"
|
|
194
|
+
copy_dir "skills/TanStackQuery"
|
|
195
|
+
copy_dir "skills/Zustand"
|
|
196
|
+
copy_dir "skills/ReactHookForm"
|
|
197
|
+
|
|
198
|
+
# FE 개별 스킬 파일
|
|
199
|
+
copy_file "skills/TDD/frontend.md"
|
|
200
|
+
copy_file "skills/Coding/frontend.md"
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# BE (백엔드)
|
|
204
|
+
copy_be() {
|
|
205
|
+
echo "[BE]"
|
|
206
|
+
|
|
207
|
+
# BE 에이전트
|
|
208
|
+
copy_file "agents/code-writer/backend.md"
|
|
209
|
+
copy_file "agents/tdd/backend.md"
|
|
210
|
+
|
|
211
|
+
# BE 스킬 (디렉토리 전체)
|
|
212
|
+
copy_dir "skills/TypeORM"
|
|
213
|
+
|
|
214
|
+
# BE 개별 스킬 파일
|
|
215
|
+
copy_file "skills/Coding/backend.md"
|
|
216
|
+
copy_file "skills/TDD/backend.md"
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# === 실행 ===
|
|
220
|
+
|
|
221
|
+
echo "파일을 복사합니다..."
|
|
222
|
+
echo ""
|
|
223
|
+
|
|
224
|
+
# 공통은 항상 설치
|
|
225
|
+
copy_common
|
|
226
|
+
echo ""
|
|
227
|
+
|
|
228
|
+
# 선택된 스택 설치
|
|
229
|
+
if [ "$INSTALL_FE" = true ]; then
|
|
230
|
+
copy_fe
|
|
231
|
+
echo ""
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
if [ "$INSTALL_BE" = true ]; then
|
|
235
|
+
copy_be
|
|
236
|
+
echo ""
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
echo "=== 설치 완료 ($MODE_LABEL) ==="
|
|
240
|
+
echo ""
|
|
241
|
+
echo "스택: $STACK_LABEL"
|
|
242
|
+
echo ""
|
|
243
|
+
echo "설치된 파일:"
|
|
244
|
+
echo " $TARGET_DIR/CLAUDE.md"
|
|
245
|
+
echo " $TARGET_DIR/settings.json"
|
|
246
|
+
echo " $TARGET_DIR/agents/"
|
|
247
|
+
echo " $TARGET_DIR/skills/"
|
|
248
|
+
echo " $TARGET_DIR/hooks/"
|
|
249
|
+
echo ""
|
|
250
|
+
if [ "$INSTALL_MODE" = "global" ]; then
|
|
251
|
+
echo "이제 어떤 프로젝트에서든 Claude Code를 실행하면 자동으로 적용됩니다."
|
|
252
|
+
else
|
|
253
|
+
echo "현재 프로젝트($(pwd))에서 Claude Code를 실행하면 자동으로 적용됩니다."
|
|
254
|
+
echo "다른 프로젝트에도 적용하려면 --global 옵션을 사용하세요."
|
|
255
|
+
fi
|