@su-record/vibe 0.4.4 → 0.4.6

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.
@@ -1,121 +0,0 @@
1
- # ⚡ Quick Start - 즉시 적용 가능한 원칙
2
-
3
- ## 🎯 5가지 핵심 원칙
4
-
5
- ```
6
- ✅ 🇰🇷 한국어로 응답 (최우선)
7
- ✅ 📉 코드가 적을수록 부채도 적다
8
- ✅ 🚫 DRY - Don't Repeat Yourself
9
- ✅ 🎯 단일 책임 원칙 (SRP)
10
- ✅ 🙏 YAGNI - You Aren't Gonna Need It
11
- ```
12
-
13
- ## ⚠️ 언어 규칙 (절대 준수)
14
-
15
- **모든 답변은 한국어로 작성합니다.**
16
-
17
- ### 한국어 사용 원칙
18
-
19
- 1. **설명, 주석, 대화**: 100% 한국어
20
- 2. **코드**: 영어 (프로그래밍 언어 표준)
21
- 3. **기술 용어**: 한국어 우선, 필요시 영어 병기
22
- - ✅ "의존성 주입 (Dependency Injection)"
23
- - ✅ "상태 관리 (State Management)"
24
- - ❌ "Dependency Injection"
25
- 4. **에러 메시지**: 한국어로 설명
26
- 5. **문서 제목/헤더**: 한국어
27
-
28
- ### 예시
29
-
30
- ```python
31
- # ✅ 좋은 예
32
- # 사용자 인증 미들웨어
33
- async def authenticate_user(token: str) -> User:
34
- """
35
- JWT 토큰을 검증하고 사용자를 반환합니다.
36
-
37
- Args:
38
- token: JWT 인증 토큰
39
-
40
- Returns:
41
- 인증된 사용자 객체
42
-
43
- Raises:
44
- HTTPException: 토큰이 유효하지 않은 경우
45
- """
46
- # 토큰 검증
47
- payload = decode_jwt(token)
48
-
49
- # 사용자 조회
50
- user = await get_user(payload["sub"])
51
- if not user:
52
- raise HTTPException(401, detail="인증 실패")
53
-
54
- return user
55
-
56
- # ❌ 나쁜 예
57
- # User authentication middleware
58
- async def authenticate_user(token: str) -> User:
59
- """
60
- Verify JWT token and return user.
61
- """
62
- # Verify token
63
- payload = decode_jwt(token)
64
-
65
- # Get user
66
- user = await get_user(payload["sub"])
67
- if not user:
68
- raise HTTPException(401, detail="Authentication failed")
69
-
70
- return user
71
- ```
72
-
73
- ### AI 응답 예시
74
-
75
- ```markdown
76
- # ❌ 영어 응답
77
- I'll help you create a new API endpoint. First, let's define the schema...
78
-
79
- # ✅ 한국어 응답
80
- 새로운 API 엔드포인트를 만들어드리겠습니다. 먼저 스키마를 정의하겠습니다...
81
- ```
82
-
83
- ## 📦 체크포인트
84
-
85
- ### 새 패키지 추가 전
86
-
87
- - [ ] 기존 패키지로 해결 가능한가?
88
- - [ ] 정말 필요한가?
89
- - [ ] 번들 크기 영향은?
90
-
91
- ### 파일 생성 시
92
-
93
- - [ ] 사용 위치 확인
94
- - [ ] import 즉시 추가
95
- - [ ] 순환 의존성 체크
96
-
97
- ## 🥇 최우선 원칙: 수술적 정밀도
98
-
99
- > **⚠️ 이것은 모든 작업에 앞서는 TORY의 첫 번째 원칙입니다.**
100
- >
101
- > **요청받지 않은 코드는 절대 수정/삭제하지 않습니다.**
102
-
103
- - **엄격한 범위 준수**: 사용자가 명시적으로 요청한 파일과 코드 블록만 수정
104
- - **기존 코드 보존**: 작동하는 코드를 임의로 리팩토링하거나 제거하지 않음
105
- - **스타일 존중**: 기존 네이밍, 포맷팅, 주석 스타일 유지
106
-
107
- ## 🚀 작업 전 필수 체크리스트
108
-
109
- ```
110
- [x] 최우선 원칙 준수: 요청 범위 외 절대 수정 금지
111
- [ ] 기존 코드 존중: 기존 스타일과 구조 유지
112
- [ ] 문서 규칙 준수: 네이밍, 구조 등 모든 가이드라인 준수
113
- ```
114
-
115
- ## 🎯 황금률
116
-
117
- - **한국어 우선**: 모든 커뮤니케이션은 명확한 한국어로
118
- - **단순함의 미학**: 코드가 적을수록 좋은 코드
119
- - **DRY 원칙**: 반복하지 말고 재사용
120
- - **단일 책임**: 하나의 함수는 하나의 목적만
121
- - **실용주의**: 완벽보다 실용, YAGNI 정신
@@ -1,509 +0,0 @@
1
- # 🎯 Dart + Flutter 품질 규칙
2
-
3
- ## 핵심 원칙 (core에서 상속)
4
-
5
- ```markdown
6
- ✅ 단일 책임 (SRP)
7
- ✅ 중복 제거 (DRY)
8
- ✅ 재사용성
9
- ✅ 낮은 복잡도
10
- ✅ 함수 ≤ 30줄, build() ≤ 50줄
11
- ✅ 중첩 ≤ 3단계
12
- ✅ Cyclomatic complexity ≤ 10
13
- ```
14
-
15
- ## Dart/Flutter 특화 규칙
16
-
17
- ### 1. Immutability 우선 (@immutable)
18
-
19
- ```dart
20
- // ❌ Mutable 클래스
21
- class User {
22
- String name;
23
- int age;
24
-
25
- User({required this.name, required this.age});
26
- }
27
-
28
- // ✅ Immutable 클래스 + copyWith
29
- @immutable
30
- class User {
31
- const User({
32
- required this.name,
33
- required this.age,
34
- });
35
-
36
- final String name;
37
- final int age;
38
-
39
- User copyWith({
40
- String? name,
41
- int? age,
42
- }) {
43
- return User(
44
- name: name ?? this.name,
45
- age: age ?? this.age,
46
- );
47
- }
48
-
49
- @override
50
- bool operator ==(Object other) =>
51
- identical(this, other) ||
52
- other is User && name == other.name && age == other.age;
53
-
54
- @override
55
- int get hashCode => name.hashCode ^ age.hashCode;
56
- }
57
- ```
58
-
59
- ### 2. StatelessWidget 선호
60
-
61
- ```dart
62
- // ✅ StatelessWidget (순수 위젯)
63
- class UserAvatar extends StatelessWidget {
64
- const UserAvatar({
65
- super.key,
66
- required this.imageUrl,
67
- this.size = 40.0,
68
- this.onTap,
69
- });
70
-
71
- final String imageUrl;
72
- final double size;
73
- final VoidCallback? onTap;
74
-
75
- @override
76
- Widget build(BuildContext context) {
77
- return GestureDetector(
78
- onTap: onTap,
79
- child: CircleAvatar(
80
- radius: size / 2,
81
- backgroundImage: NetworkImage(imageUrl),
82
- ),
83
- );
84
- }
85
- }
86
-
87
- // ❌ StatefulWidget 남용 (상태가 없는데 사용)
88
- class UserAvatar extends StatefulWidget {
89
- // 상태 관리 불필요
90
- }
91
- ```
92
-
93
- ### 3. Provider 패턴 (상태 관리)
94
-
95
- ```dart
96
- // ✅ Immutable State + ChangeNotifier
97
- @immutable
98
- class FeedState {
99
- const FeedState({
100
- this.feeds = const [],
101
- this.isLoading = false,
102
- this.error,
103
- });
104
-
105
- final List<Feed> feeds;
106
- final bool isLoading;
107
- final String? error;
108
-
109
- FeedState copyWith({
110
- List<Feed>? feeds,
111
- bool? isLoading,
112
- String? error,
113
- }) {
114
- return FeedState(
115
- feeds: feeds ?? this.feeds,
116
- isLoading: isLoading ?? this.isLoading,
117
- error: error ?? this.error,
118
- );
119
- }
120
- }
121
-
122
- class FeedProvider extends ChangeNotifier {
123
- FeedState _state = const FeedState();
124
- FeedState get state => _state;
125
-
126
- final FeedService _feedService;
127
-
128
- FeedProvider(this._feedService);
129
-
130
- Future<void> loadFeeds() async {
131
- _state = _state.copyWith(isLoading: true, error: null);
132
- notifyListeners();
133
-
134
- try {
135
- final feeds = await _feedService.getFeeds();
136
- _state = _state.copyWith(feeds: feeds, isLoading: false);
137
- } catch (e) {
138
- _state = _state.copyWith(error: e.toString(), isLoading: false);
139
- }
140
- notifyListeners();
141
- }
142
- }
143
-
144
- // 사용
145
- class FeedScreen extends StatelessWidget {
146
- @override
147
- Widget build(BuildContext context) {
148
- final feedState = context.watch<FeedProvider>().state;
149
-
150
- if (feedState.isLoading) return const CircularProgressIndicator();
151
- if (feedState.error != null) return ErrorWidget(feedState.error!);
152
-
153
- return FeedList(feeds: feedState.feeds);
154
- }
155
- }
156
- ```
157
-
158
- ### 4. Null Safety 명확히
159
-
160
- ```dart
161
- // ✅ Null safety 활용
162
- class User {
163
- User({
164
- required this.id, // Non-nullable (필수)
165
- required this.name,
166
- this.bio, // Nullable (선택)
167
- });
168
-
169
- final String id;
170
- final String name;
171
- final String? bio; // ? 명시
172
-
173
- String getBioOrDefault() {
174
- return bio ?? 'No bio'; // ?? 연산자
175
- }
176
-
177
- void printBio() {
178
- bio?.length; // ?. 안전 호출
179
- }
180
- }
181
-
182
- // ✅ Late 변수 (초기화 지연)
183
- class MyWidget extends StatefulWidget {
184
- @override
185
- State<MyWidget> createState() => _MyWidgetState();
186
- }
187
-
188
- class _MyWidgetState extends State<MyWidget> {
189
- late AnimationController _controller; // initState에서 초기화
190
-
191
- @override
192
- void initState() {
193
- super.initState();
194
- _controller = AnimationController(vsync: this);
195
- }
196
-
197
- @override
198
- void dispose() {
199
- _controller.dispose();
200
- super.dispose();
201
- }
202
- }
203
- ```
204
-
205
- ### 5. 위젯 분리 (Extract Widget)
206
-
207
- ```dart
208
- // ❌ 긴 build 메서드 (80줄)
209
- class UserProfile extends StatelessWidget {
210
- @override
211
- Widget build(BuildContext context) {
212
- return Column(
213
- children: [
214
- // 30줄: 헤더
215
- Container(...),
216
- // 25줄: 통계
217
- Row(...),
218
- // 25줄: 피드 리스트
219
- ListView(...),
220
- ],
221
- );
222
- }
223
- }
224
-
225
- // ✅ 서브 위젯으로 분리
226
- class UserProfile extends StatelessWidget {
227
- @override
228
- Widget build(BuildContext context) {
229
- return Column(
230
- children: [
231
- const ProfileHeader(),
232
- const ProfileStats(),
233
- const ProfileFeedList(),
234
- ],
235
- );
236
- }
237
- }
238
-
239
- class ProfileHeader extends StatelessWidget {
240
- const ProfileHeader({super.key});
241
-
242
- @override
243
- Widget build(BuildContext context) {
244
- // 헤더만
245
- }
246
- }
247
-
248
- class ProfileStats extends StatelessWidget {
249
- const ProfileStats({super.key});
250
-
251
- @override
252
- Widget build(BuildContext context) {
253
- // 통계만
254
- }
255
- }
256
- ```
257
-
258
- ### 6. 순수 함수 (Static Methods)
259
-
260
- ```dart
261
- // ✅ 순수 함수 (상태 없음)
262
- class DateUtils {
263
- // Private constructor (인스턴스 생성 방지)
264
- DateUtils._();
265
-
266
- static String formatRelativeTime(DateTime dateTime) {
267
- final now = DateTime.now();
268
- final difference = now.difference(dateTime);
269
-
270
- if (difference.inDays > 0) return '${difference.inDays}일 전';
271
- if (difference.inHours > 0) return '${difference.inHours}시간 전';
272
- return '${difference.inMinutes}분 전';
273
- }
274
-
275
- static bool isToday(DateTime dateTime) {
276
- final now = DateTime.now();
277
- return dateTime.year == now.year &&
278
- dateTime.month == now.month &&
279
- dateTime.day == now.day;
280
- }
281
- }
282
-
283
- // 사용
284
- final formatted = DateUtils.formatRelativeTime(feed.createdAt);
285
- ```
286
-
287
- ### 7. 에러 처리 (Result/Either 패턴)
288
-
289
- ```dart
290
- // ✅ Result 타입으로 에러 처리
291
- sealed class Result<T> {
292
- const Result();
293
- }
294
-
295
- class Success<T> extends Result<T> {
296
- const Success(this.value);
297
- final T value;
298
- }
299
-
300
- class Failure<T> extends Result<T> {
301
- const Failure(this.error);
302
- final String error;
303
- }
304
-
305
- // 사용
306
- Future<Result<User>> login(String email, String password) async {
307
- try {
308
- final user = await _authService.login(email, password);
309
- return Success(user);
310
- } catch (e) {
311
- return Failure(e.toString());
312
- }
313
- }
314
-
315
- // 호출부 (Pattern matching)
316
- final result = await login(email, password);
317
- switch (result) {
318
- case Success(:final value):
319
- Navigator.pushReplacement(context, HomePage(user: value));
320
- case Failure(:final error):
321
- showErrorDialog(context, error);
322
- }
323
- ```
324
-
325
- ### 8. Extension Methods
326
-
327
- ```dart
328
- // ✅ Extension으로 기능 확장
329
- extension StringExtension on String {
330
- String capitalize() {
331
- if (isEmpty) return this;
332
- return '${this[0].toUpperCase()}${substring(1)}';
333
- }
334
-
335
- bool get isEmail {
336
- final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
337
- return emailRegex.hasMatch(this);
338
- }
339
- }
340
-
341
- extension ListExtension<T> on List<T> {
342
- List<T> distinctBy<K>(K Function(T) keySelector) {
343
- final seen = <K>{};
344
- return where((item) => seen.add(keySelector(item))).toList();
345
- }
346
- }
347
-
348
- // 사용
349
- final name = 'john'.capitalize(); // 'John'
350
- final isValid = 'test@example.com'.isEmail; // true
351
- ```
352
-
353
- ### 9. const Constructor 활용
354
-
355
- ```dart
356
- // ✅ const constructor (컴파일 타임 상수)
357
- class AppColors {
358
- const AppColors._();
359
-
360
- static const primary = Color(0xFF6200EE);
361
- static const secondary = Color(0xFF03DAC6);
362
- static const error = Color(0xFFB00020);
363
- }
364
-
365
- class Spacing {
366
- const Spacing._();
367
-
368
- static const xs = 4.0;
369
- static const sm = 8.0;
370
- static const md = 16.0;
371
- static const lg = 24.0;
372
- static const xl = 32.0;
373
- }
374
-
375
- // ✅ const 위젯 (재사용 시 성능 향상)
376
- class LoadingIndicator extends StatelessWidget {
377
- const LoadingIndicator({super.key});
378
-
379
- @override
380
- Widget build(BuildContext context) {
381
- return const Center(
382
- child: CircularProgressIndicator(),
383
- );
384
- }
385
- }
386
-
387
- // 사용
388
- const LoadingIndicator() // const로 생성
389
- ```
390
-
391
- ### 10. 비동기 처리 (Future/Stream)
392
-
393
- ```dart
394
- // ✅ Future (단일 비동기 작업)
395
- Future<List<Feed>> fetchFeeds() async {
396
- final response = await dio.get('/api/feeds');
397
- return (response.data as List)
398
- .map((json) => Feed.fromJson(json))
399
- .toList();
400
- }
401
-
402
- // ✅ Stream (연속 비동기 이벤트)
403
- Stream<List<Feed>> watchFeeds() {
404
- return Stream.periodic(
405
- const Duration(seconds: 30),
406
- (_) => fetchFeeds(),
407
- ).asyncMap((future) => future);
408
- }
409
-
410
- // ✅ StreamBuilder 사용
411
- class FeedStream extends StatelessWidget {
412
- @override
413
- Widget build(BuildContext context) {
414
- return StreamBuilder<List<Feed>>(
415
- stream: watchFeeds(),
416
- builder: (context, snapshot) {
417
- if (snapshot.connectionState == ConnectionState.waiting) {
418
- return const LoadingIndicator();
419
- }
420
- if (snapshot.hasError) {
421
- return ErrorWidget(snapshot.error.toString());
422
- }
423
- if (!snapshot.hasData) {
424
- return const EmptyState();
425
- }
426
-
427
- return FeedList(feeds: snapshot.data!);
428
- },
429
- );
430
- }
431
- }
432
- ```
433
-
434
- ## 안티패턴
435
-
436
- ```dart
437
- // ❌ Mutable state
438
- class BadCounter extends StatefulWidget {
439
- int count = 0; // 위험! StatefulWidget은 재생성될 수 있음
440
-
441
- @override
442
- State<BadCounter> createState() => _BadCounterState();
443
- }
444
-
445
- // ❌ BuildContext를 async gap 너머에서 사용
446
- Future<void> badNavigate() async {
447
- await Future.delayed(Duration(seconds: 1));
448
- Navigator.push(context, ...); // ❌ context가 무효화됐을 수 있음
449
- }
450
-
451
- // ✅ mounted 체크
452
- Future<void> goodNavigate() async {
453
- await Future.delayed(Duration(seconds: 1));
454
- if (!mounted) return;
455
- Navigator.push(context, ...);
456
- }
457
-
458
- // ❌ setState에서 긴 작업
459
- setState(() {
460
- // 10줄의 복잡한 계산 ❌
461
- });
462
-
463
- // ✅ 계산 후 setState
464
- final newValue = expensiveCalculation();
465
- setState(() {
466
- _value = newValue; // 간단한 할당만
467
- });
468
-
469
- // ❌ GlobalKey 남용
470
- final GlobalKey<FormState> _formKey = GlobalKey();
471
-
472
- // ✅ Controller 사용
473
- final TextEditingController _controller = TextEditingController();
474
- ```
475
-
476
- ## 코드 품질 도구
477
-
478
- ```bash
479
- # 분석
480
- flutter analyze
481
-
482
- # 포맷팅
483
- dart format .
484
-
485
- # 테스트
486
- flutter test
487
- flutter test --coverage
488
-
489
- # 빌드
490
- flutter build apk --release
491
- flutter build ios --release
492
- flutter build web --release
493
- ```
494
-
495
- ## 체크리스트
496
-
497
- Dart/Flutter 코드 작성 시:
498
-
499
- - [ ] @immutable + copyWith 패턴
500
- - [ ] StatelessWidget 우선 사용
501
- - [ ] Provider로 상태 관리 분리
502
- - [ ] Null safety (?, ??, ?., !)
503
- - [ ] build() ≤ 50줄 (위젯 분리)
504
- - [ ] 순수 함수 (static methods)
505
- - [ ] Result 타입으로 에러 처리
506
- - [ ] Extension methods 활용
507
- - [ ] const constructor 사용
508
- - [ ] Future/Stream 적절히 선택
509
- - [ ] 복잡도 ≤ 10