@choblue/claude-code-toolkit 1.1.0 → 1.1.2
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/agents/code-reviewer.md +8 -0
- package/.claude/agents/code-writer-be.md +8 -0
- package/.claude/agents/code-writer-fe.md +8 -0
- package/.claude/agents/explore.md +8 -0
- package/.claude/agents/git-manager.md +8 -0
- package/.claude/agents/test-writer-be.md +8 -0
- package/.claude/agents/test-writer-fe.md +8 -0
- package/.claude/settings.json +1 -1
- package/.claude/skills/Coding/SKILL.md +5 -0
- package/.claude/skills/NextJS/SKILL.md +5 -0
- package/.claude/skills/React/SKILL.md +5 -0
- package/.claude/skills/ReactHookForm/SKILL.md +5 -0
- package/.claude/skills/TDD/SKILL.md +5 -0
- package/.claude/skills/TailwindCSS/SKILL.md +5 -0
- package/.claude/skills/TanStackQuery/SKILL.md +5 -0
- package/.claude/skills/TypeORM/SKILL.md +16 -303
- package/.claude/skills/TypeORM/references/advanced-queries.md +176 -0
- package/.claude/skills/TypeORM/references/migrations.md +62 -0
- package/.claude/skills/TypeORM/references/transactions.md +76 -0
- package/.claude/skills/TypeScript/SKILL.md +17 -225
- package/.claude/skills/TypeScript/references/advanced-patterns.md +146 -0
- package/.claude/skills/TypeScript/references/generics.md +98 -0
- package/.claude/skills/TypeScript/references/type-guards.md +109 -0
- package/.claude/skills/Zustand/SKILL.md +5 -0
- package/install.sh +122 -36
- package/package.json +1 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# 타입 가드 (Type Guards)
|
|
2
|
+
|
|
3
|
+
이 문서는 TypeScript 타입 가드의 상세 패턴을 다룬다.
|
|
4
|
+
기본 규칙은 [SKILL.md](../SKILL.md)를 참고한다.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## is 키워드 (사용자 정의 타입 가드)
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
interface Admin {
|
|
12
|
+
role: 'admin';
|
|
13
|
+
permissions: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface Guest {
|
|
17
|
+
role: 'guest';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type AppUser = Admin | Guest;
|
|
21
|
+
|
|
22
|
+
// Bad - 타입 단언
|
|
23
|
+
function getPermissions(user: AppUser): string[] {
|
|
24
|
+
return (user as Admin).permissions ?? [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Good - 타입 가드
|
|
28
|
+
function isAdmin(user: AppUser): user is Admin {
|
|
29
|
+
return user.role === 'admin';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getPermissions(user: AppUser): string[] {
|
|
33
|
+
if (isAdmin(user)) {
|
|
34
|
+
return user.permissions; // Admin으로 좁혀짐
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## in 연산자
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
interface Dog {
|
|
46
|
+
bark: () => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface Cat {
|
|
50
|
+
meow: () => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type Pet = Dog | Cat;
|
|
54
|
+
|
|
55
|
+
function makeSound(pet: Pet): void {
|
|
56
|
+
if ('bark' in pet) {
|
|
57
|
+
pet.bark(); // Dog으로 좁혀짐
|
|
58
|
+
} else {
|
|
59
|
+
pet.meow(); // Cat으로 좁혀짐
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Discriminated Union (태그드 유니언)
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// 공통 판별 필드(type)를 가진 유니언
|
|
70
|
+
type Shape =
|
|
71
|
+
| { type: 'circle'; radius: number }
|
|
72
|
+
| { type: 'rectangle'; width: number; height: number }
|
|
73
|
+
| { type: 'triangle'; base: number; height: number };
|
|
74
|
+
|
|
75
|
+
function calculateArea(shape: Shape): number {
|
|
76
|
+
switch (shape.type) {
|
|
77
|
+
case 'circle':
|
|
78
|
+
return Math.PI * shape.radius ** 2;
|
|
79
|
+
case 'rectangle':
|
|
80
|
+
return shape.width * shape.height;
|
|
81
|
+
case 'triangle':
|
|
82
|
+
return (shape.base * shape.height) / 2;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Exhaustive Check (never를 이용한 완전성 검사)
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// 모든 케이스를 처리했는지 컴파일 타임에 검증한다
|
|
93
|
+
function assertNever(value: never): never {
|
|
94
|
+
throw new Error(`Unexpected value: ${value}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getShapeLabel(shape: Shape): string {
|
|
98
|
+
switch (shape.type) {
|
|
99
|
+
case 'circle':
|
|
100
|
+
return '원';
|
|
101
|
+
case 'rectangle':
|
|
102
|
+
return '직사각형';
|
|
103
|
+
case 'triangle':
|
|
104
|
+
return '삼각형';
|
|
105
|
+
default:
|
|
106
|
+
return assertNever(shape); // 새로운 Shape 추가 시 컴파일 에러 발생
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
package/install.sh
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
#!/bin/bash
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
2
|
# install.sh - my-claude-code-toolkit을 .claude/에 설치한다
|
|
3
|
-
# Usage: ./install.sh [--global] [--fe] [--be]
|
|
3
|
+
# Usage: ./install.sh [--global] [--fe] [--be] [--force]
|
|
4
4
|
# 기본값: 현재 디렉토리의 .claude/에 전체 설치 (프로젝트 로컬)
|
|
5
5
|
# --global: ~/.claude/에 설치 (글로벌)
|
|
6
6
|
# --fe: 공통 + 프론트엔드 스킬만 설치
|
|
7
7
|
# --be: 공통 + 백엔드 스킬만 설치
|
|
8
8
|
# --fe --be: 전체 설치 (= 기본값)
|
|
9
|
+
# --force: 사용자 수정 파일도 강제 덮어쓰기
|
|
9
10
|
|
|
10
11
|
set -e
|
|
11
12
|
|
|
12
13
|
# 도움말 출력
|
|
13
14
|
usage() {
|
|
14
|
-
echo "Usage: $0 [--global] [--fe] [--be]"
|
|
15
|
+
echo "Usage: $0 [--global] [--fe] [--be] [--force]"
|
|
15
16
|
echo ""
|
|
16
17
|
echo "스택 선택:"
|
|
17
18
|
echo " (기본값) 전체 설치 (공통 + FE + BE)"
|
|
@@ -23,11 +24,15 @@ usage() {
|
|
|
23
24
|
echo " (기본값) 현재 디렉토리의 .claude/에 설치 (프로젝트 로컬)"
|
|
24
25
|
echo " --global ~/.claude/에 설치 (글로벌)"
|
|
25
26
|
echo ""
|
|
27
|
+
echo "옵션:"
|
|
28
|
+
echo " --force 사용자 수정 파일도 강제 덮어쓰기"
|
|
29
|
+
echo ""
|
|
26
30
|
echo "예시:"
|
|
27
31
|
echo " $0 # 전체 설치 (로컬)"
|
|
28
32
|
echo " $0 --fe # 공통 + FE만 (로컬)"
|
|
29
33
|
echo " $0 --be # 공통 + BE만 (로컬)"
|
|
30
34
|
echo " $0 --global --fe # 공통 + FE만 (글로벌)"
|
|
35
|
+
echo " $0 --force # 수정된 파일도 강제 덮어쓰기"
|
|
31
36
|
exit 0
|
|
32
37
|
}
|
|
33
38
|
|
|
@@ -35,6 +40,7 @@ usage() {
|
|
|
35
40
|
INSTALL_MODE="local"
|
|
36
41
|
INSTALL_FE=false
|
|
37
42
|
INSTALL_BE=false
|
|
43
|
+
FORCE_OVERWRITE=false
|
|
38
44
|
|
|
39
45
|
for arg in "$@"; do
|
|
40
46
|
case "$arg" in
|
|
@@ -47,6 +53,9 @@ for arg in "$@"; do
|
|
|
47
53
|
--be)
|
|
48
54
|
INSTALL_BE=true
|
|
49
55
|
;;
|
|
56
|
+
--force)
|
|
57
|
+
FORCE_OVERWRITE=true
|
|
58
|
+
;;
|
|
50
59
|
--help|-h)
|
|
51
60
|
usage
|
|
52
61
|
;;
|
|
@@ -81,11 +90,9 @@ fi
|
|
|
81
90
|
|
|
82
91
|
if [ "$INSTALL_MODE" = "global" ]; then
|
|
83
92
|
TARGET_DIR="$HOME/.claude"
|
|
84
|
-
BACKUP_DIR="$HOME/.claude-backup-$(date +%Y%m%d_%H%M%S)"
|
|
85
93
|
MODE_LABEL="글로벌 설치 (~/.claude/)"
|
|
86
94
|
else
|
|
87
95
|
TARGET_DIR="$(pwd)/.claude"
|
|
88
|
-
BACKUP_DIR="$(pwd)/.claude-backup-$(date +%Y%m%d_%H%M%S)"
|
|
89
96
|
MODE_LABEL="로컬 설치 ($(pwd)/.claude/)"
|
|
90
97
|
fi
|
|
91
98
|
|
|
@@ -103,52 +110,112 @@ if [ ! -d "$SOURCE_DIR" ]; then
|
|
|
103
110
|
exit 1
|
|
104
111
|
fi
|
|
105
112
|
|
|
106
|
-
#
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
# TARGET_DIR 생성
|
|
114
|
+
mkdir -p "$TARGET_DIR"
|
|
115
|
+
|
|
116
|
+
# === 해시 함수 ===
|
|
117
|
+
file_hash() {
|
|
118
|
+
if command -v shasum &>/dev/null; then
|
|
119
|
+
shasum -a 256 "$1" | cut -d' ' -f1
|
|
120
|
+
elif command -v sha256sum &>/dev/null; then
|
|
121
|
+
sha256sum "$1" | cut -d' ' -f1
|
|
122
|
+
else
|
|
123
|
+
# fallback: md5
|
|
124
|
+
md5 -q "$1" 2>/dev/null || md5sum "$1" | cut -d' ' -f1
|
|
125
|
+
fi
|
|
126
|
+
}
|
|
116
127
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
+
# === 매니페스트 관리 ===
|
|
129
|
+
MANIFEST_FILE="$TARGET_DIR/.toolkit-manifest"
|
|
130
|
+
NEW_MANIFEST=""
|
|
131
|
+
|
|
132
|
+
# 이전 매니페스트 로드 (grep 기반으로 bash 3.x 호환)
|
|
133
|
+
# OLD_MANIFEST_CONTENT: 한 줄에 "<sha256hash> <relative_path>" 형식
|
|
134
|
+
OLD_MANIFEST_CONTENT=""
|
|
135
|
+
if [ -f "$MANIFEST_FILE" ]; then
|
|
136
|
+
OLD_MANIFEST_CONTENT="$(cat "$MANIFEST_FILE")"
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# 이전 매니페스트에서 특정 파일의 해시를 조회
|
|
140
|
+
old_manifest_hash() {
|
|
141
|
+
local path="$1"
|
|
142
|
+
if [ -z "$OLD_MANIFEST_CONTENT" ]; then
|
|
128
143
|
echo ""
|
|
144
|
+
return
|
|
129
145
|
fi
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
fi
|
|
146
|
+
echo "$OLD_MANIFEST_CONTENT" | grep " ${path}$" | head -1 | cut -d' ' -f1
|
|
147
|
+
}
|
|
133
148
|
|
|
134
149
|
# === 헬퍼 함수 ===
|
|
135
150
|
|
|
136
|
-
# 개별 파일 복사 (상대경로
|
|
151
|
+
# 개별 파일 복사 (상대경로 기준, 매니페스트 체크섬 비교)
|
|
137
152
|
copy_file() {
|
|
138
153
|
local rel_path="$1"
|
|
139
154
|
local dir
|
|
140
155
|
dir="$(dirname "$rel_path")"
|
|
141
156
|
mkdir -p "$TARGET_DIR/$dir"
|
|
142
|
-
|
|
143
|
-
|
|
157
|
+
|
|
158
|
+
local target_file="$TARGET_DIR/$rel_path"
|
|
159
|
+
local source_file="$SOURCE_DIR/$rel_path"
|
|
160
|
+
|
|
161
|
+
if [ -f "$target_file" ]; then
|
|
162
|
+
local old_hash
|
|
163
|
+
old_hash="$(old_manifest_hash "$rel_path")"
|
|
164
|
+
|
|
165
|
+
if [ -n "$old_hash" ]; then
|
|
166
|
+
# 매니페스트에 기록이 있음 → 사용자 수정 여부 확인
|
|
167
|
+
local current_hash
|
|
168
|
+
current_hash="$(file_hash "$target_file")"
|
|
169
|
+
|
|
170
|
+
if [ "$current_hash" = "$old_hash" ]; then
|
|
171
|
+
# 사용자가 수정하지 않음 → 새 버전으로 덮어씀
|
|
172
|
+
cp "$source_file" "$target_file"
|
|
173
|
+
echo " 복사: $rel_path"
|
|
174
|
+
else
|
|
175
|
+
# 사용자가 수정함
|
|
176
|
+
if [ "$FORCE_OVERWRITE" = true ]; then
|
|
177
|
+
cp "$target_file" "$target_file.bak"
|
|
178
|
+
cp "$source_file" "$target_file"
|
|
179
|
+
echo " ⚠️ $rel_path - 사용자 수정 감지, 강제 덮어씀"
|
|
180
|
+
else
|
|
181
|
+
echo " ⚠️ $rel_path - 사용자 수정 감지, 건너뜀 (강제: --force)"
|
|
182
|
+
# 건너뛴 파일도 매니페스트에는 현재 해시로 기록 (추적 유지)
|
|
183
|
+
NEW_MANIFEST="${NEW_MANIFEST}${current_hash} ${rel_path}"$'\n'
|
|
184
|
+
return
|
|
185
|
+
fi
|
|
186
|
+
fi
|
|
187
|
+
else
|
|
188
|
+
# 매니페스트에 기록 없음 (첫 설치 or 매니페스트 없음) → 그냥 복사
|
|
189
|
+
cp "$source_file" "$target_file"
|
|
190
|
+
echo " 복사: $rel_path"
|
|
191
|
+
fi
|
|
192
|
+
else
|
|
193
|
+
# 대상 파일이 존재하지 않음 → 그냥 복사
|
|
194
|
+
cp "$source_file" "$target_file"
|
|
195
|
+
echo " 복사: $rel_path"
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# 복사 성공한 파일의 해시를 새 매니페스트에 추가
|
|
199
|
+
local new_hash
|
|
200
|
+
new_hash="$(file_hash "$target_file")"
|
|
201
|
+
NEW_MANIFEST="${NEW_MANIFEST}${new_hash} ${rel_path}"$'\n'
|
|
144
202
|
}
|
|
145
203
|
|
|
146
|
-
# 디렉토리 전체 복사 (
|
|
204
|
+
# 디렉토리 전체 복사 (개별 파일 단위로 처리하여 매니페스트에 기록)
|
|
147
205
|
copy_dir() {
|
|
148
206
|
local rel_path="$1"
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
207
|
+
local file
|
|
208
|
+
for file in "$SOURCE_DIR/$rel_path/"*; do
|
|
209
|
+
if [ -f "$file" ]; then
|
|
210
|
+
local filename
|
|
211
|
+
filename="$(basename "$file")"
|
|
212
|
+
copy_file "$rel_path/$filename"
|
|
213
|
+
elif [ -d "$file" ]; then
|
|
214
|
+
local dirname
|
|
215
|
+
dirname="$(basename "$file")"
|
|
216
|
+
copy_dir "$rel_path/$dirname"
|
|
217
|
+
fi
|
|
218
|
+
done
|
|
152
219
|
}
|
|
153
220
|
|
|
154
221
|
# === 복사 함수 ===
|
|
@@ -234,6 +301,25 @@ if [ "$INSTALL_BE" = true ]; then
|
|
|
234
301
|
echo ""
|
|
235
302
|
fi
|
|
236
303
|
|
|
304
|
+
# === 오래된 파일 정리 ===
|
|
305
|
+
# 이전 매니페스트에는 있지만 새 매니페스트에는 없는 파일 삭제
|
|
306
|
+
if [ -n "$OLD_MANIFEST_CONTENT" ]; then
|
|
307
|
+
while IFS=' ' read -r old_hash old_path; do
|
|
308
|
+
[ -z "$old_path" ] && continue
|
|
309
|
+
# 새 매니페스트에 해당 경로가 없으면 삭제
|
|
310
|
+
if ! echo "$NEW_MANIFEST" | grep -q " ${old_path}$"; then
|
|
311
|
+
if [ -f "$TARGET_DIR/$old_path" ]; then
|
|
312
|
+
rm "$TARGET_DIR/$old_path"
|
|
313
|
+
echo " 🗑️ $old_path - 더 이상 사용하지 않는 파일 삭제"
|
|
314
|
+
fi
|
|
315
|
+
fi
|
|
316
|
+
done <<< "$OLD_MANIFEST_CONTENT"
|
|
317
|
+
echo ""
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
# === 새 매니페스트 작성 ===
|
|
321
|
+
printf '%s' "$NEW_MANIFEST" > "$MANIFEST_FILE"
|
|
322
|
+
|
|
237
323
|
echo "=== 설치 완료 ($MODE_LABEL) ==="
|
|
238
324
|
echo ""
|
|
239
325
|
echo "스택: $STACK_LABEL"
|