@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.
@@ -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