@choblue/claude-code-toolkit 1.2.5 → 1.2.7
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 +80 -18
- package/.claude/hooks/prompt-hook.sh +4 -4
- package/.claude/prompts/feature.md +11 -0
- package/.claude/prompts/fix.md +11 -0
- package/.claude/prompts/review.md +7 -0
- package/.claude/skills/Coding/SKILL.md +5 -4
- package/.claude/skills/Curation/SKILL.md +36 -0
- package/.claude/skills/FailureRecovery/SKILL.md +47 -0
- package/.claude/skills/{Coding/backend.md → NestJS/SKILL.md} +7 -2
- package/.claude/skills/NextJS/SKILL.md +13 -303
- package/.claude/skills/NextJS/references/data-fetching.md +96 -0
- package/.claude/skills/NextJS/references/middleware-actions.md +74 -0
- package/.claude/skills/NextJS/references/optimization.md +127 -0
- package/.claude/skills/Planning/SKILL.md +30 -7
- package/.claude/skills/React/SKILL.md +25 -287
- package/.claude/skills/React/references/a11y-ux.md +134 -0
- package/.claude/skills/React/references/rendering-patterns.md +62 -0
- package/.claude/skills/React/references/state-hooks.md +73 -0
- package/.claude/skills/ReactHookForm/SKILL.md +8 -196
- package/.claude/skills/ReactHookForm/references/advanced-patterns.md +193 -0
- package/.claude/skills/TDD/SKILL.md +2 -2
- package/.claude/skills/TailwindCSS/SKILL.md +14 -240
- package/.claude/skills/TailwindCSS/references/patterns-components.md +93 -0
- package/.claude/skills/TailwindCSS/references/responsive-dark.md +102 -0
- package/.claude/skills/TailwindCSS/references/transitions.md +33 -0
- package/README.md +25 -12
- package/package.json +1 -1
- package/.claude/skills/Coding/frontend.md +0 -11
- /package/.claude/skills/TDD/{backend.md → references/backend.md} +0 -0
- /package/.claude/skills/TDD/{frontend.md → references/frontend.md} +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# 데이터 페칭 & Route Handlers
|
|
2
|
+
|
|
3
|
+
## 데이터 페칭
|
|
4
|
+
|
|
5
|
+
### Server Component에서 직접 fetch
|
|
6
|
+
- Server Component에서 `async/await`로 직접 데이터를 가져온다
|
|
7
|
+
- `useEffect`로 데이터를 가져오지 않는다
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// Good - Server Component에서 직접 페칭
|
|
11
|
+
export default async function UserPage({ params }: { params: { id: string } }) {
|
|
12
|
+
const user = await getUser(params.id);
|
|
13
|
+
return <UserProfile user={user} />;
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Revalidation 전략
|
|
18
|
+
- **Time-based**: `next: { revalidate: 60 }` - 일정 시간마다 갱신
|
|
19
|
+
- **On-demand**: `revalidateTag()`, `revalidatePath()` - 특정 이벤트 시 갱신
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Time-based revalidation
|
|
23
|
+
const data = await fetch('https://api.example.com/users', {
|
|
24
|
+
next: { revalidate: 3600 }, // 1시간마다 갱신
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Tag-based revalidation
|
|
28
|
+
const data = await fetch('https://api.example.com/users', {
|
|
29
|
+
next: { tags: ['users'] },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Server Action에서 revalidation 트리거
|
|
33
|
+
'use server';
|
|
34
|
+
import { revalidateTag } from 'next/cache';
|
|
35
|
+
|
|
36
|
+
export async function createUser(formData: FormData) {
|
|
37
|
+
await saveUser(formData);
|
|
38
|
+
revalidateTag('users');
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Route Handlers
|
|
45
|
+
|
|
46
|
+
### 위치 및 구조
|
|
47
|
+
- `app/api/` 디렉토리 하위에 `route.ts` 파일로 정의한다
|
|
48
|
+
- HTTP 메서드를 named export로 정의한다
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// app/api/users/route.ts
|
|
52
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
53
|
+
|
|
54
|
+
export async function GET(request: NextRequest) {
|
|
55
|
+
const users = await getUsers();
|
|
56
|
+
return NextResponse.json(users);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function POST(request: NextRequest) {
|
|
60
|
+
const body = await request.json();
|
|
61
|
+
const user = await createUser(body);
|
|
62
|
+
return NextResponse.json(user, { status: 201 });
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 동적 라우트
|
|
67
|
+
```typescript
|
|
68
|
+
// app/api/users/[id]/route.ts
|
|
69
|
+
export async function GET(
|
|
70
|
+
request: NextRequest,
|
|
71
|
+
{ params }: { params: { id: string } },
|
|
72
|
+
) {
|
|
73
|
+
const user = await getUser(params.id);
|
|
74
|
+
if (!user) {
|
|
75
|
+
return NextResponse.json({ error: 'Not Found' }, { status: 404 });
|
|
76
|
+
}
|
|
77
|
+
return NextResponse.json(user);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function PUT(
|
|
81
|
+
request: NextRequest,
|
|
82
|
+
{ params }: { params: { id: string } },
|
|
83
|
+
) {
|
|
84
|
+
const body = await request.json();
|
|
85
|
+
const user = await updateUser(params.id, body);
|
|
86
|
+
return NextResponse.json(user);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function DELETE(
|
|
90
|
+
request: NextRequest,
|
|
91
|
+
{ params }: { params: { id: string } },
|
|
92
|
+
) {
|
|
93
|
+
await deleteUser(params.id);
|
|
94
|
+
return new NextResponse(null, { status: 204 });
|
|
95
|
+
}
|
|
96
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Middleware & Server Actions
|
|
2
|
+
|
|
3
|
+
## Middleware
|
|
4
|
+
|
|
5
|
+
### 용도
|
|
6
|
+
- 인증/인가 체크
|
|
7
|
+
- 리다이렉트 처리
|
|
8
|
+
- 요청/응답 헤더 수정
|
|
9
|
+
- 국제화(i18n) 라우팅
|
|
10
|
+
|
|
11
|
+
### 작성 패턴
|
|
12
|
+
```typescript
|
|
13
|
+
// middleware.ts (프로젝트 루트)
|
|
14
|
+
import { NextResponse } from 'next/server';
|
|
15
|
+
import type { NextRequest } from 'next/server';
|
|
16
|
+
|
|
17
|
+
export function middleware(request: NextRequest) {
|
|
18
|
+
const token = request.cookies.get('token')?.value;
|
|
19
|
+
|
|
20
|
+
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
|
|
21
|
+
return NextResponse.redirect(new URL('/login', request.url));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return NextResponse.next();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const config = {
|
|
28
|
+
matcher: ['/dashboard/:path*', '/admin/:path*'],
|
|
29
|
+
};
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Server Actions
|
|
35
|
+
|
|
36
|
+
### 정의 및 사용
|
|
37
|
+
- `'use server'` 지시문으로 서버 액션을 정의한다
|
|
38
|
+
- form의 `action` 속성이나 이벤트 핸들러에서 호출한다
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// actions/user.ts
|
|
42
|
+
'use server';
|
|
43
|
+
|
|
44
|
+
import { revalidatePath } from 'next/cache';
|
|
45
|
+
|
|
46
|
+
export async function createUser(formData: FormData) {
|
|
47
|
+
const name = formData.get('name') as string;
|
|
48
|
+
const email = formData.get('email') as string;
|
|
49
|
+
|
|
50
|
+
await db.user.create({ data: { name, email } });
|
|
51
|
+
revalidatePath('/users');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function deleteUser(userId: string) {
|
|
55
|
+
await db.user.delete({ where: { id: userId } });
|
|
56
|
+
revalidatePath('/users');
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Form에서 사용
|
|
61
|
+
```typescript
|
|
62
|
+
// components/CreateUserForm.tsx
|
|
63
|
+
import { createUser } from '@/actions/user';
|
|
64
|
+
|
|
65
|
+
export function CreateUserForm() {
|
|
66
|
+
return (
|
|
67
|
+
<form action={createUser}>
|
|
68
|
+
<input name="name" type="text" required />
|
|
69
|
+
<input name="email" type="email" required />
|
|
70
|
+
<button type="submit">생성</button>
|
|
71
|
+
</form>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Metadata, 디렉토리 구조 패턴 & Image/Font 최적화
|
|
2
|
+
|
|
3
|
+
## Metadata
|
|
4
|
+
|
|
5
|
+
### 정적 Metadata
|
|
6
|
+
```typescript
|
|
7
|
+
// app/about/page.tsx
|
|
8
|
+
import type { Metadata } from 'next';
|
|
9
|
+
|
|
10
|
+
export const metadata: Metadata = {
|
|
11
|
+
title: '소개 - 서비스명',
|
|
12
|
+
description: '서비스 소개 페이지입니다.',
|
|
13
|
+
};
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### 동적 Metadata
|
|
17
|
+
```typescript
|
|
18
|
+
// app/users/[id]/page.tsx
|
|
19
|
+
import type { Metadata } from 'next';
|
|
20
|
+
|
|
21
|
+
export async function generateMetadata({
|
|
22
|
+
params,
|
|
23
|
+
}: {
|
|
24
|
+
params: { id: string };
|
|
25
|
+
}): Promise<Metadata> {
|
|
26
|
+
const user = await getUser(params.id);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
title: `${user.name} - 프로필`,
|
|
30
|
+
description: `${user.name}의 프로필 페이지입니다.`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 디렉토리 구조 패턴
|
|
38
|
+
|
|
39
|
+
### Route Groups - `(group)`
|
|
40
|
+
- URL에 영향을 주지 않고 라우트를 그룹화한다
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
app/
|
|
44
|
+
(marketing)/
|
|
45
|
+
about/page.tsx # /about
|
|
46
|
+
blog/page.tsx # /blog
|
|
47
|
+
(dashboard)/
|
|
48
|
+
layout.tsx # dashboard 전용 레이아웃
|
|
49
|
+
settings/page.tsx # /settings
|
|
50
|
+
profile/page.tsx # /profile
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Private Folders - `_folder`
|
|
54
|
+
- 라우팅에서 제외되는 내부 폴더
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
app/
|
|
58
|
+
_components/ # 라우팅에 포함되지 않음
|
|
59
|
+
Header.tsx
|
|
60
|
+
Footer.tsx
|
|
61
|
+
_lib/ # 내부 유틸리티
|
|
62
|
+
utils.ts
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Parallel Routes - `@slot`
|
|
66
|
+
- 동일 레이아웃에서 여러 페이지를 동시에 렌더링한다
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
app/
|
|
70
|
+
layout.tsx # children + @analytics + @team 동시 렌더링
|
|
71
|
+
@analytics/
|
|
72
|
+
page.tsx
|
|
73
|
+
@team/
|
|
74
|
+
page.tsx
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Intercepting Routes - `(.)`, `(..)`, `(...)`
|
|
78
|
+
- 현재 레이아웃 내에서 다른 라우트를 가로채서 표시한다
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
app/
|
|
82
|
+
feed/
|
|
83
|
+
page.tsx
|
|
84
|
+
(..)photo/[id]/ # /photo/:id를 모달로 가로챔
|
|
85
|
+
page.tsx
|
|
86
|
+
photo/[id]/
|
|
87
|
+
page.tsx # 직접 접근 시 전체 페이지
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Image/Font 최적화
|
|
93
|
+
|
|
94
|
+
### next/image
|
|
95
|
+
- 모든 이미지는 `next/image`를 사용한다
|
|
96
|
+
- `width`, `height`를 명시하거나 `fill` 속성을 사용한다
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import Image from 'next/image';
|
|
100
|
+
|
|
101
|
+
// 크기 지정
|
|
102
|
+
<Image src="/hero.png" alt="히어로 이미지" width={800} height={400} />
|
|
103
|
+
|
|
104
|
+
// fill 모드 (부모 기준 채움)
|
|
105
|
+
<div className="relative h-64 w-full">
|
|
106
|
+
<Image src="/banner.png" alt="배너" fill className="object-cover" />
|
|
107
|
+
</div>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### next/font
|
|
111
|
+
- Google Fonts는 `next/font/google`을 사용한다
|
|
112
|
+
- 커스텀 폰트는 `next/font/local`을 사용한다
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// app/layout.tsx
|
|
116
|
+
import { Inter } from 'next/font/google';
|
|
117
|
+
|
|
118
|
+
const inter = Inter({ subsets: ['latin'] });
|
|
119
|
+
|
|
120
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
121
|
+
return (
|
|
122
|
+
<html lang="ko" className={inter.className}>
|
|
123
|
+
<body>{children}</body>
|
|
124
|
+
</html>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
@@ -32,6 +32,22 @@ L 티어는 채팅이 아닌 **파일 기반**으로 설계를 진행한다. 세
|
|
|
32
32
|
- [기술적 제약, 호환성 이슈 등]
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
### Step 1.5: Task Header 출력
|
|
36
|
+
research 후, plan 작성 전에 Task Header를 출력한다:
|
|
37
|
+
```
|
|
38
|
+
📋 [작업명]
|
|
39
|
+
⚡ L — [판단 근거 한 줄]
|
|
40
|
+
📚 [사용할 스킬 목록]
|
|
41
|
+
🔄 [에이전트 흐름]
|
|
42
|
+
📁 [예상 파일 수와 대상]
|
|
43
|
+
📌 Plan:
|
|
44
|
+
○ Step 1: [작업 단위]
|
|
45
|
+
○ Step 2: [작업 단위]
|
|
46
|
+
...
|
|
47
|
+
```
|
|
48
|
+
각 Step 진행에 따라 상태를 갱신한다:
|
|
49
|
+
- `○` 대기 → `▶ ⏳` 진행 중 → `✔` 완료
|
|
50
|
+
|
|
35
51
|
### Step 2: plan.md 작성
|
|
36
52
|
research 기반으로 구현 계획을 작성한다.
|
|
37
53
|
```markdown
|
|
@@ -57,17 +73,24 @@ research 기반으로 구현 계획을 작성한다.
|
|
|
57
73
|
3. **사용자가 승인할 때까지 구현하지 않는다**
|
|
58
74
|
4. 승인 후 implementer 에이전트에 plan.md를 전달하여 구현한다
|
|
59
75
|
|
|
60
|
-
## 3. M 티어 Planning: 채팅 기반
|
|
76
|
+
## 3. M 티어 Planning: Task Header + 채팅 기반
|
|
61
77
|
|
|
62
|
-
M 티어는
|
|
78
|
+
M 티어는 Task Header를 출력한 후 바로 구현을 진행한다:
|
|
63
79
|
```
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
📋 [작업명]
|
|
81
|
+
⚡ M — [판단 근거 한 줄]
|
|
82
|
+
📚 [사용할 스킬 목록]
|
|
83
|
+
🔄 [에이전트 흐름]
|
|
84
|
+
📁 [예상 파일 수와 대상]
|
|
69
85
|
```
|
|
70
86
|
|
|
87
|
+
### Task Header 작성 규칙
|
|
88
|
+
- **📋**: 사용자 요청을 한 줄로 요약
|
|
89
|
+
- **⚡**: 티어 + 판단 근거 (예: `M — 단일 API 엔드포인트 추가`)
|
|
90
|
+
- **📚**: 이 작업에서 참조할 스킬 (예: `Coding, TypeORM`)
|
|
91
|
+
- **🔄**: 에이전트 실행 순서 (예: `code-writer-be → git-manager`)
|
|
92
|
+
- **📁**: 변경 파일 수와 대상 (예: `3 files (service, controller, dto)`)
|
|
93
|
+
|
|
71
94
|
## 4. 작업 분해 원칙
|
|
72
95
|
|
|
73
96
|
**"한 번에 하나, 완전하게"** — 전체를 한꺼번에 80%로 끝내지 말고, 단위별로 100% 완성한다.
|