@su-record/vibe 2.3.0 → 2.3.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.
Files changed (98) hide show
  1. package/.claude/settings.json +35 -35
  2. package/.claude/settings.local.json +24 -25
  3. package/.claude/vibe/constitution.md +184 -184
  4. package/.claude/vibe/rules/core/communication-guide.md +104 -104
  5. package/.claude/vibe/rules/core/development-philosophy.md +52 -52
  6. package/.claude/vibe/rules/core/quick-start.md +120 -120
  7. package/.claude/vibe/rules/languages/dart-flutter.md +509 -509
  8. package/.claude/vibe/rules/languages/go.md +396 -396
  9. package/.claude/vibe/rules/languages/java-spring.md +586 -586
  10. package/.claude/vibe/rules/languages/kotlin-android.md +491 -491
  11. package/.claude/vibe/rules/languages/python-django.md +371 -371
  12. package/.claude/vibe/rules/languages/python-fastapi.md +386 -386
  13. package/.claude/vibe/rules/languages/rust.md +425 -425
  14. package/.claude/vibe/rules/languages/swift-ios.md +516 -516
  15. package/.claude/vibe/rules/languages/typescript-nextjs.md +441 -441
  16. package/.claude/vibe/rules/languages/typescript-node.md +375 -375
  17. package/.claude/vibe/rules/languages/typescript-nuxt.md +521 -521
  18. package/.claude/vibe/rules/languages/typescript-react-native.md +446 -446
  19. package/.claude/vibe/rules/languages/typescript-react.md +525 -525
  20. package/.claude/vibe/rules/languages/typescript-vue.md +353 -353
  21. package/.claude/vibe/rules/quality/bdd-contract-testing.md +388 -388
  22. package/.claude/vibe/rules/quality/checklist.md +276 -276
  23. package/.claude/vibe/rules/quality/testing-strategy.md +437 -437
  24. package/.claude/vibe/rules/standards/anti-patterns.md +369 -369
  25. package/.claude/vibe/rules/standards/code-structure.md +291 -291
  26. package/.claude/vibe/rules/standards/complexity-metrics.md +312 -312
  27. package/.claude/vibe/rules/standards/naming-conventions.md +198 -198
  28. package/.claude/vibe/setup.sh +31 -31
  29. package/.claude/vibe/templates/constitution-template.md +184 -184
  30. package/.claude/vibe/templates/contract-backend-template.md +517 -517
  31. package/.claude/vibe/templates/contract-frontend-template.md +594 -594
  32. package/.claude/vibe/templates/feature-template.md +96 -96
  33. package/.claude/vibe/templates/spec-template.md +199 -199
  34. package/CLAUDE.md +345 -323
  35. package/LICENSE +21 -21
  36. package/README.md +744 -724
  37. package/agents/compounder.md +261 -261
  38. package/agents/diagrammer.md +178 -178
  39. package/agents/e2e-tester.md +266 -266
  40. package/agents/explorer.md +48 -48
  41. package/agents/implementer.md +53 -53
  42. package/agents/research/best-practices-agent.md +139 -139
  43. package/agents/research/codebase-patterns-agent.md +147 -147
  44. package/agents/research/framework-docs-agent.md +181 -181
  45. package/agents/research/security-advisory-agent.md +167 -167
  46. package/agents/review/architecture-reviewer.md +107 -107
  47. package/agents/review/complexity-reviewer.md +116 -116
  48. package/agents/review/data-integrity-reviewer.md +88 -88
  49. package/agents/review/git-history-reviewer.md +103 -103
  50. package/agents/review/performance-reviewer.md +86 -86
  51. package/agents/review/python-reviewer.md +152 -152
  52. package/agents/review/rails-reviewer.md +139 -139
  53. package/agents/review/react-reviewer.md +144 -144
  54. package/agents/review/security-reviewer.md +80 -80
  55. package/agents/review/simplicity-reviewer.md +140 -140
  56. package/agents/review/test-coverage-reviewer.md +116 -116
  57. package/agents/review/typescript-reviewer.md +127 -127
  58. package/agents/searcher.md +54 -54
  59. package/agents/simplifier.md +119 -119
  60. package/agents/tester.md +49 -49
  61. package/agents/ui-previewer.md +137 -137
  62. package/commands/vibe.analyze.md +245 -180
  63. package/commands/vibe.reason.md +223 -183
  64. package/commands/vibe.review.md +200 -136
  65. package/commands/vibe.run.md +838 -836
  66. package/commands/vibe.spec.md +419 -383
  67. package/commands/vibe.utils.md +101 -101
  68. package/commands/vibe.verify.md +282 -241
  69. package/dist/cli/index.js +385 -385
  70. package/dist/lib/MemoryManager.d.ts.map +1 -1
  71. package/dist/lib/MemoryManager.js +119 -114
  72. package/dist/lib/MemoryManager.js.map +1 -1
  73. package/dist/lib/PythonParser.js +108 -108
  74. package/dist/lib/gemini-mcp.js +15 -15
  75. package/dist/lib/gemini-oauth.js +35 -35
  76. package/dist/lib/gpt-mcp.js +17 -17
  77. package/dist/lib/gpt-oauth.js +44 -44
  78. package/dist/tools/analytics/getUsageAnalytics.js +12 -12
  79. package/dist/tools/index.d.ts +50 -0
  80. package/dist/tools/index.d.ts.map +1 -0
  81. package/dist/tools/index.js +61 -0
  82. package/dist/tools/index.js.map +1 -0
  83. package/dist/tools/memory/createMemoryTimeline.js +10 -10
  84. package/dist/tools/memory/getMemoryGraph.js +12 -12
  85. package/dist/tools/memory/getSessionContext.js +9 -9
  86. package/dist/tools/memory/linkMemories.js +14 -14
  87. package/dist/tools/memory/listMemories.js +4 -4
  88. package/dist/tools/memory/recallMemory.js +4 -4
  89. package/dist/tools/memory/saveMemory.js +4 -4
  90. package/dist/tools/memory/searchMemoriesAdvanced.js +22 -22
  91. package/dist/tools/planning/generatePrd.js +46 -46
  92. package/dist/tools/prompt/enhancePromptGemini.js +160 -160
  93. package/dist/tools/reasoning/applyReasoningFramework.js +56 -56
  94. package/dist/tools/semantic/analyzeDependencyGraph.js +12 -12
  95. package/hooks/hooks.json +121 -103
  96. package/package.json +73 -69
  97. package/skills/git-worktree.md +178 -178
  98. package/skills/priority-todos.md +236 -236
@@ -1,353 +1,353 @@
1
- # 🟢 TypeScript + Vue/Nuxt 품질 규칙
2
-
3
- ## 핵심 원칙 (core에서 상속)
4
-
5
- ```markdown
6
- ✅ 단일 책임 (SRP)
7
- ✅ 중복 제거 (DRY)
8
- ✅ 재사용성
9
- ✅ 낮은 복잡도
10
- ✅ 함수 ≤ 30줄, Template ≤ 100줄
11
- ✅ 중첩 ≤ 3단계
12
- ✅ Cyclomatic complexity ≤ 10
13
- ```
14
-
15
- ## Vue 3 + TypeScript 특화 규칙
16
-
17
- ### 1. Composition API 사용 (Options API 지양)
18
-
19
- ```typescript
20
- // ❌ Options API (레거시)
21
- export default {
22
- data() {
23
- return { count: 0 };
24
- },
25
- methods: {
26
- increment() {
27
- this.count++;
28
- }
29
- }
30
- };
31
-
32
- // ✅ Composition API + script setup
33
- <script setup lang="ts">
34
- import { ref, computed, onMounted } from 'vue';
35
-
36
- const count = ref(0);
37
- const doubled = computed(() => count.value * 2);
38
-
39
- function increment() {
40
- count.value++;
41
- }
42
-
43
- onMounted(() => {
44
- console.log('컴포넌트 마운트됨');
45
- });
46
- </script>
47
- ```
48
-
49
- ### 2. 타입 안전한 Props/Emits
50
-
51
- ```typescript
52
- // ✅ Props 타입 정의
53
- interface Props {
54
- userId: string;
55
- title?: string;
56
- items: Item[];
57
- }
58
-
59
- const props = withDefaults(defineProps<Props>(), {
60
- title: '기본 제목',
61
- });
62
-
63
- // ✅ Emits 타입 정의
64
- interface Emits {
65
- (e: 'update', value: string): void;
66
- (e: 'delete', id: number): void;
67
- (e: 'select', item: Item): void;
68
- }
69
-
70
- const emit = defineEmits<Emits>();
71
-
72
- // 사용
73
- emit('update', '새 값');
74
- emit('delete', 123);
75
- ```
76
-
77
- ### 3. Composables로 로직 분리
78
-
79
- ```typescript
80
- // ✅ composables/useUser.ts
81
- import { ref, computed } from 'vue';
82
- import type { User } from '@/types';
83
-
84
- export function useUser(userId: string) {
85
- const user = ref<User | null>(null);
86
- const isLoading = ref(false);
87
- const error = ref<string | null>(null);
88
-
89
- const fullName = computed(() =>
90
- user.value ? `${user.value.firstName} ${user.value.lastName}` : ''
91
- );
92
-
93
- async function fetchUser() {
94
- isLoading.value = true;
95
- error.value = null;
96
- try {
97
- const response = await api.getUser(userId);
98
- user.value = response.data;
99
- } catch (e) {
100
- error.value = '사용자를 불러오지 못했습니다';
101
- } finally {
102
- isLoading.value = false;
103
- }
104
- }
105
-
106
- return {
107
- user,
108
- isLoading,
109
- error,
110
- fullName,
111
- fetchUser,
112
- };
113
- }
114
-
115
- // 컴포넌트에서 사용
116
- <script setup lang="ts">
117
- const { user, isLoading, fetchUser } = useUser(props.userId);
118
-
119
- onMounted(fetchUser);
120
- </script>
121
- ```
122
-
123
- ### 4. Pinia 상태 관리
124
-
125
- ```typescript
126
- // ✅ stores/user.ts
127
- import { defineStore } from 'pinia';
128
- import type { User } from '@/types';
129
-
130
- interface UserState {
131
- currentUser: User | null;
132
- users: User[];
133
- isLoading: boolean;
134
- }
135
-
136
- export const useUserStore = defineStore('user', {
137
- state: (): UserState => ({
138
- currentUser: null,
139
- users: [],
140
- isLoading: false,
141
- }),
142
-
143
- getters: {
144
- isLoggedIn: (state) => !!state.currentUser,
145
- userCount: (state) => state.users.length,
146
- },
147
-
148
- actions: {
149
- async login(email: string, password: string) {
150
- this.isLoading = true;
151
- try {
152
- const user = await authApi.login(email, password);
153
- this.currentUser = user;
154
- } finally {
155
- this.isLoading = false;
156
- }
157
- },
158
-
159
- logout() {
160
- this.currentUser = null;
161
- },
162
- },
163
- });
164
-
165
- // Setup Store 스타일 (권장)
166
- export const useUserStore = defineStore('user', () => {
167
- const currentUser = ref<User | null>(null);
168
- const isLoggedIn = computed(() => !!currentUser.value);
169
-
170
- async function login(email: string, password: string) {
171
- currentUser.value = await authApi.login(email, password);
172
- }
173
-
174
- return { currentUser, isLoggedIn, login };
175
- });
176
- ```
177
-
178
- ### 5. Nuxt 3 특화 규칙
179
-
180
- ```typescript
181
- // ✅ Server API Routes (server/api/)
182
- // server/api/users/[id].get.ts
183
- export default defineEventHandler(async (event) => {
184
- const id = getRouterParam(event, 'id');
185
-
186
- if (!id) {
187
- throw createError({
188
- statusCode: 400,
189
- message: 'ID가 필요합니다',
190
- });
191
- }
192
-
193
- const user = await prisma.user.findUnique({ where: { id } });
194
-
195
- if (!user) {
196
- throw createError({
197
- statusCode: 404,
198
- message: '사용자를 찾을 수 없습니다',
199
- });
200
- }
201
-
202
- return user;
203
- });
204
-
205
- // ✅ useFetch / useAsyncData
206
- <script setup lang="ts">
207
- // SSR 지원 데이터 페칭
208
- const { data: user, pending, error } = await useFetch<User>(
209
- `/api/users/${props.userId}`
210
- );
211
-
212
- // 캐싱 키 지정
213
- const { data: posts } = await useAsyncData(
214
- `user-${props.userId}-posts`,
215
- () => $fetch(`/api/users/${props.userId}/posts`)
216
- );
217
- </script>
218
-
219
- // ✅ Middleware
220
- // middleware/auth.ts
221
- export default defineNuxtRouteMiddleware((to, from) => {
222
- const { isLoggedIn } = useUserStore();
223
-
224
- if (!isLoggedIn && to.path !== '/login') {
225
- return navigateTo('/login');
226
- }
227
- });
228
- ```
229
-
230
- ### 6. 컴포넌트 구조
231
-
232
- ```vue
233
- <!-- ✅ 권장 컴포넌트 구조 -->
234
- <script setup lang="ts">
235
- // 1. 타입 import
236
- import type { User, Item } from '@/types';
237
-
238
- // 2. 컴포넌트 import
239
- import UserAvatar from '@/components/UserAvatar.vue';
240
-
241
- // 3. Props/Emits
242
- interface Props {
243
- user: User;
244
- editable?: boolean;
245
- }
246
-
247
- const props = withDefaults(defineProps<Props>(), {
248
- editable: false,
249
- });
250
-
251
- const emit = defineEmits<{
252
- (e: 'update', user: User): void;
253
- }>();
254
-
255
- // 4. Composables
256
- const { isLoading, save } = useUserForm();
257
-
258
- // 5. Reactive state
259
- const formData = ref({ ...props.user });
260
- const isEditing = ref(false);
261
-
262
- // 6. Computed
263
- const canSave = computed(() =>
264
- formData.value.name.length > 0 && !isLoading.value
265
- );
266
-
267
- // 7. Methods
268
- async function handleSave() {
269
- await save(formData.value);
270
- emit('update', formData.value);
271
- }
272
-
273
- // 8. Lifecycle
274
- onMounted(() => {
275
- console.log('컴포넌트 준비됨');
276
- });
277
- </script>
278
-
279
- <template>
280
- <div class="user-card">
281
- <UserAvatar :src="user.avatar" />
282
- <h2>{{ user.name }}</h2>
283
- <button
284
- v-if="editable"
285
- :disabled="!canSave"
286
- @click="handleSave"
287
- >
288
- 저장
289
- </button>
290
- </div>
291
- </template>
292
-
293
- <style scoped>
294
- .user-card {
295
- padding: 1rem;
296
- border-radius: 8px;
297
- }
298
- </style>
299
- ```
300
-
301
- ## 안티패턴
302
-
303
- ```typescript
304
- // ❌ v-if와 v-for 함께 사용
305
- <li v-for="user in users" v-if="user.isActive">
306
-
307
- // ✅ computed로 필터링
308
- const activeUsers = computed(() => users.value.filter(u => u.isActive));
309
- <li v-for="user in activeUsers">
310
-
311
- // ❌ Props 직접 수정
312
- props.user.name = '새 이름';
313
-
314
- // ✅ emit으로 부모에게 알림
315
- emit('update', { ...props.user, name: '새 이름' });
316
-
317
- // ❌ $refs 남용
318
- this.$refs.input.focus();
319
-
320
- // ✅ template ref + expose
321
- const inputRef = ref<HTMLInputElement>();
322
- defineExpose({ focus: () => inputRef.value?.focus() });
323
- ```
324
-
325
- ## 파일 구조 (Nuxt 3)
326
-
327
- ```
328
- project/
329
- ├── components/
330
- │ ├── ui/ # 기본 UI 컴포넌트
331
- │ ├── features/ # 기능별 컴포넌트
332
- │ └── layouts/ # 레이아웃 컴포넌트
333
- ├── composables/ # Composition 함수
334
- ├── stores/ # Pinia 스토어
335
- ├── server/
336
- │ ├── api/ # API 라우트
337
- │ ├── middleware/ # 서버 미들웨어
338
- │ └── utils/ # 서버 유틸리티
339
- ├── pages/ # 파일 기반 라우팅
340
- ├── middleware/ # 클라이언트 미들웨어
341
- ├── types/ # TypeScript 타입
342
- └── utils/ # 유틸리티 함수
343
- ```
344
-
345
- ## 체크리스트
346
-
347
- - [ ] Composition API + `<script setup>` 사용
348
- - [ ] Props/Emits 타입 정의
349
- - [ ] Composables로 로직 분리
350
- - [ ] Pinia Setup Store 스타일 사용
351
- - [ ] `any` 타입 사용 금지
352
- - [ ] v-if/v-for 분리
353
- - [ ] scoped 스타일 사용
1
+ # 🟢 TypeScript + Vue/Nuxt 품질 규칙
2
+
3
+ ## 핵심 원칙 (core에서 상속)
4
+
5
+ ```markdown
6
+ ✅ 단일 책임 (SRP)
7
+ ✅ 중복 제거 (DRY)
8
+ ✅ 재사용성
9
+ ✅ 낮은 복잡도
10
+ ✅ 함수 ≤ 30줄, Template ≤ 100줄
11
+ ✅ 중첩 ≤ 3단계
12
+ ✅ Cyclomatic complexity ≤ 10
13
+ ```
14
+
15
+ ## Vue 3 + TypeScript 특화 규칙
16
+
17
+ ### 1. Composition API 사용 (Options API 지양)
18
+
19
+ ```typescript
20
+ // ❌ Options API (레거시)
21
+ export default {
22
+ data() {
23
+ return { count: 0 };
24
+ },
25
+ methods: {
26
+ increment() {
27
+ this.count++;
28
+ }
29
+ }
30
+ };
31
+
32
+ // ✅ Composition API + script setup
33
+ <script setup lang="ts">
34
+ import { ref, computed, onMounted } from 'vue';
35
+
36
+ const count = ref(0);
37
+ const doubled = computed(() => count.value * 2);
38
+
39
+ function increment() {
40
+ count.value++;
41
+ }
42
+
43
+ onMounted(() => {
44
+ console.log('컴포넌트 마운트됨');
45
+ });
46
+ </script>
47
+ ```
48
+
49
+ ### 2. 타입 안전한 Props/Emits
50
+
51
+ ```typescript
52
+ // ✅ Props 타입 정의
53
+ interface Props {
54
+ userId: string;
55
+ title?: string;
56
+ items: Item[];
57
+ }
58
+
59
+ const props = withDefaults(defineProps<Props>(), {
60
+ title: '기본 제목',
61
+ });
62
+
63
+ // ✅ Emits 타입 정의
64
+ interface Emits {
65
+ (e: 'update', value: string): void;
66
+ (e: 'delete', id: number): void;
67
+ (e: 'select', item: Item): void;
68
+ }
69
+
70
+ const emit = defineEmits<Emits>();
71
+
72
+ // 사용
73
+ emit('update', '새 값');
74
+ emit('delete', 123);
75
+ ```
76
+
77
+ ### 3. Composables로 로직 분리
78
+
79
+ ```typescript
80
+ // ✅ composables/useUser.ts
81
+ import { ref, computed } from 'vue';
82
+ import type { User } from '@/types';
83
+
84
+ export function useUser(userId: string) {
85
+ const user = ref<User | null>(null);
86
+ const isLoading = ref(false);
87
+ const error = ref<string | null>(null);
88
+
89
+ const fullName = computed(() =>
90
+ user.value ? `${user.value.firstName} ${user.value.lastName}` : ''
91
+ );
92
+
93
+ async function fetchUser() {
94
+ isLoading.value = true;
95
+ error.value = null;
96
+ try {
97
+ const response = await api.getUser(userId);
98
+ user.value = response.data;
99
+ } catch (e) {
100
+ error.value = '사용자를 불러오지 못했습니다';
101
+ } finally {
102
+ isLoading.value = false;
103
+ }
104
+ }
105
+
106
+ return {
107
+ user,
108
+ isLoading,
109
+ error,
110
+ fullName,
111
+ fetchUser,
112
+ };
113
+ }
114
+
115
+ // 컴포넌트에서 사용
116
+ <script setup lang="ts">
117
+ const { user, isLoading, fetchUser } = useUser(props.userId);
118
+
119
+ onMounted(fetchUser);
120
+ </script>
121
+ ```
122
+
123
+ ### 4. Pinia 상태 관리
124
+
125
+ ```typescript
126
+ // ✅ stores/user.ts
127
+ import { defineStore } from 'pinia';
128
+ import type { User } from '@/types';
129
+
130
+ interface UserState {
131
+ currentUser: User | null;
132
+ users: User[];
133
+ isLoading: boolean;
134
+ }
135
+
136
+ export const useUserStore = defineStore('user', {
137
+ state: (): UserState => ({
138
+ currentUser: null,
139
+ users: [],
140
+ isLoading: false,
141
+ }),
142
+
143
+ getters: {
144
+ isLoggedIn: (state) => !!state.currentUser,
145
+ userCount: (state) => state.users.length,
146
+ },
147
+
148
+ actions: {
149
+ async login(email: string, password: string) {
150
+ this.isLoading = true;
151
+ try {
152
+ const user = await authApi.login(email, password);
153
+ this.currentUser = user;
154
+ } finally {
155
+ this.isLoading = false;
156
+ }
157
+ },
158
+
159
+ logout() {
160
+ this.currentUser = null;
161
+ },
162
+ },
163
+ });
164
+
165
+ // Setup Store 스타일 (권장)
166
+ export const useUserStore = defineStore('user', () => {
167
+ const currentUser = ref<User | null>(null);
168
+ const isLoggedIn = computed(() => !!currentUser.value);
169
+
170
+ async function login(email: string, password: string) {
171
+ currentUser.value = await authApi.login(email, password);
172
+ }
173
+
174
+ return { currentUser, isLoggedIn, login };
175
+ });
176
+ ```
177
+
178
+ ### 5. Nuxt 3 특화 규칙
179
+
180
+ ```typescript
181
+ // ✅ Server API Routes (server/api/)
182
+ // server/api/users/[id].get.ts
183
+ export default defineEventHandler(async (event) => {
184
+ const id = getRouterParam(event, 'id');
185
+
186
+ if (!id) {
187
+ throw createError({
188
+ statusCode: 400,
189
+ message: 'ID가 필요합니다',
190
+ });
191
+ }
192
+
193
+ const user = await prisma.user.findUnique({ where: { id } });
194
+
195
+ if (!user) {
196
+ throw createError({
197
+ statusCode: 404,
198
+ message: '사용자를 찾을 수 없습니다',
199
+ });
200
+ }
201
+
202
+ return user;
203
+ });
204
+
205
+ // ✅ useFetch / useAsyncData
206
+ <script setup lang="ts">
207
+ // SSR 지원 데이터 페칭
208
+ const { data: user, pending, error } = await useFetch<User>(
209
+ `/api/users/${props.userId}`
210
+ );
211
+
212
+ // 캐싱 키 지정
213
+ const { data: posts } = await useAsyncData(
214
+ `user-${props.userId}-posts`,
215
+ () => $fetch(`/api/users/${props.userId}/posts`)
216
+ );
217
+ </script>
218
+
219
+ // ✅ Middleware
220
+ // middleware/auth.ts
221
+ export default defineNuxtRouteMiddleware((to, from) => {
222
+ const { isLoggedIn } = useUserStore();
223
+
224
+ if (!isLoggedIn && to.path !== '/login') {
225
+ return navigateTo('/login');
226
+ }
227
+ });
228
+ ```
229
+
230
+ ### 6. 컴포넌트 구조
231
+
232
+ ```vue
233
+ <!-- ✅ 권장 컴포넌트 구조 -->
234
+ <script setup lang="ts">
235
+ // 1. 타입 import
236
+ import type { User, Item } from '@/types';
237
+
238
+ // 2. 컴포넌트 import
239
+ import UserAvatar from '@/components/UserAvatar.vue';
240
+
241
+ // 3. Props/Emits
242
+ interface Props {
243
+ user: User;
244
+ editable?: boolean;
245
+ }
246
+
247
+ const props = withDefaults(defineProps<Props>(), {
248
+ editable: false,
249
+ });
250
+
251
+ const emit = defineEmits<{
252
+ (e: 'update', user: User): void;
253
+ }>();
254
+
255
+ // 4. Composables
256
+ const { isLoading, save } = useUserForm();
257
+
258
+ // 5. Reactive state
259
+ const formData = ref({ ...props.user });
260
+ const isEditing = ref(false);
261
+
262
+ // 6. Computed
263
+ const canSave = computed(() =>
264
+ formData.value.name.length > 0 && !isLoading.value
265
+ );
266
+
267
+ // 7. Methods
268
+ async function handleSave() {
269
+ await save(formData.value);
270
+ emit('update', formData.value);
271
+ }
272
+
273
+ // 8. Lifecycle
274
+ onMounted(() => {
275
+ console.log('컴포넌트 준비됨');
276
+ });
277
+ </script>
278
+
279
+ <template>
280
+ <div class="user-card">
281
+ <UserAvatar :src="user.avatar" />
282
+ <h2>{{ user.name }}</h2>
283
+ <button
284
+ v-if="editable"
285
+ :disabled="!canSave"
286
+ @click="handleSave"
287
+ >
288
+ 저장
289
+ </button>
290
+ </div>
291
+ </template>
292
+
293
+ <style scoped>
294
+ .user-card {
295
+ padding: 1rem;
296
+ border-radius: 8px;
297
+ }
298
+ </style>
299
+ ```
300
+
301
+ ## 안티패턴
302
+
303
+ ```typescript
304
+ // ❌ v-if와 v-for 함께 사용
305
+ <li v-for="user in users" v-if="user.isActive">
306
+
307
+ // ✅ computed로 필터링
308
+ const activeUsers = computed(() => users.value.filter(u => u.isActive));
309
+ <li v-for="user in activeUsers">
310
+
311
+ // ❌ Props 직접 수정
312
+ props.user.name = '새 이름';
313
+
314
+ // ✅ emit으로 부모에게 알림
315
+ emit('update', { ...props.user, name: '새 이름' });
316
+
317
+ // ❌ $refs 남용
318
+ this.$refs.input.focus();
319
+
320
+ // ✅ template ref + expose
321
+ const inputRef = ref<HTMLInputElement>();
322
+ defineExpose({ focus: () => inputRef.value?.focus() });
323
+ ```
324
+
325
+ ## 파일 구조 (Nuxt 3)
326
+
327
+ ```
328
+ project/
329
+ ├── components/
330
+ │ ├── ui/ # 기본 UI 컴포넌트
331
+ │ ├── features/ # 기능별 컴포넌트
332
+ │ └── layouts/ # 레이아웃 컴포넌트
333
+ ├── composables/ # Composition 함수
334
+ ├── stores/ # Pinia 스토어
335
+ ├── server/
336
+ │ ├── api/ # API 라우트
337
+ │ ├── middleware/ # 서버 미들웨어
338
+ │ └── utils/ # 서버 유틸리티
339
+ ├── pages/ # 파일 기반 라우팅
340
+ ├── middleware/ # 클라이언트 미들웨어
341
+ ├── types/ # TypeScript 타입
342
+ └── utils/ # 유틸리티 함수
343
+ ```
344
+
345
+ ## 체크리스트
346
+
347
+ - [ ] Composition API + `<script setup>` 사용
348
+ - [ ] Props/Emits 타입 정의
349
+ - [ ] Composables로 로직 분리
350
+ - [ ] Pinia Setup Store 스타일 사용
351
+ - [ ] `any` 타입 사용 금지
352
+ - [ ] v-if/v-for 분리
353
+ - [ ] scoped 스타일 사용