@su-record/vibe 2.2.4 → 2.3.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.
Files changed (38) hide show
  1. package/.claude/settings.json +0 -117
  2. package/.claude/settings.local.json +2 -1
  3. package/.claude/vibe/rules/languages/dart-flutter.md +509 -0
  4. package/.claude/vibe/rules/languages/go.md +396 -0
  5. package/.claude/vibe/rules/languages/java-spring.md +586 -0
  6. package/.claude/vibe/rules/languages/kotlin-android.md +491 -0
  7. package/.claude/vibe/rules/languages/python-django.md +371 -0
  8. package/.claude/vibe/rules/languages/python-fastapi.md +386 -0
  9. package/.claude/vibe/rules/languages/rust.md +425 -0
  10. package/.claude/vibe/rules/languages/swift-ios.md +516 -0
  11. package/.claude/vibe/rules/languages/typescript-nextjs.md +441 -0
  12. package/.claude/vibe/rules/languages/typescript-node.md +375 -0
  13. package/.claude/vibe/rules/languages/typescript-nuxt.md +521 -0
  14. package/.claude/vibe/rules/languages/typescript-react-native.md +446 -0
  15. package/.claude/vibe/rules/languages/typescript-react.md +525 -0
  16. package/.claude/vibe/rules/languages/typescript-vue.md +353 -0
  17. package/README.md +96 -96
  18. package/commands/vibe.analyze.md +14 -73
  19. package/commands/vibe.reason.md +49 -172
  20. package/commands/vibe.review.md +72 -260
  21. package/commands/vibe.utils.md +101 -0
  22. package/commands/vibe.verify.md +4 -1
  23. package/dist/cli/index.d.ts.map +1 -1
  24. package/dist/cli/index.js +44 -14
  25. package/dist/cli/index.js.map +1 -1
  26. package/{templates/hooks-template.json → hooks/hooks.json} +6 -6
  27. package/package.json +2 -2
  28. package/commands/vibe.continue.md +0 -88
  29. package/commands/vibe.setup.md +0 -97
  30. /package/{templates → .claude/vibe/templates}/constitution-template.md +0 -0
  31. /package/{templates → .claude/vibe/templates}/contract-backend-template.md +0 -0
  32. /package/{templates → .claude/vibe/templates}/contract-frontend-template.md +0 -0
  33. /package/{templates → .claude/vibe/templates}/feature-template.md +0 -0
  34. /package/{templates → .claude/vibe/templates}/spec-template.md +0 -0
  35. /package/{commands/vibe.compound.md → agents/compounder.md} +0 -0
  36. /package/{commands/vibe.diagram.md → agents/diagrammer.md} +0 -0
  37. /package/{commands/vibe.e2e.md → agents/e2e-tester.md} +0 -0
  38. /package/{commands/vibe.ui.md → agents/ui-previewer.md} +0 -0
@@ -0,0 +1,371 @@
1
+ # 🐍 Python + Django 품질 규칙
2
+
3
+ ## 핵심 원칙 (core에서 상속)
4
+
5
+ ```markdown
6
+ ✅ 단일 책임 (SRP)
7
+ ✅ 중복 제거 (DRY)
8
+ ✅ 재사용성
9
+ ✅ 낮은 복잡도
10
+ ✅ 함수 ≤ 30줄
11
+ ✅ 중첩 ≤ 3단계
12
+ ✅ Cyclomatic complexity ≤ 10
13
+ ```
14
+
15
+ ## Django 특화 규칙
16
+
17
+ ### 1. Model 설계
18
+
19
+ ```python
20
+ # ✅ models.py
21
+ from django.db import models
22
+ from django.contrib.auth.models import AbstractUser
23
+ from django.utils import timezone
24
+
25
+
26
+ class BaseModel(models.Model):
27
+ """공통 필드를 가진 추상 모델"""
28
+ created_at = models.DateTimeField(auto_now_add=True)
29
+ updated_at = models.DateTimeField(auto_now=True)
30
+
31
+ class Meta:
32
+ abstract = True
33
+
34
+
35
+ class User(AbstractUser):
36
+ """커스텀 사용자 모델"""
37
+ email = models.EmailField(unique=True)
38
+ phone = models.CharField(max_length=20, blank=True)
39
+ profile_image = models.ImageField(upload_to='profiles/', blank=True)
40
+
41
+ USERNAME_FIELD = 'email'
42
+ REQUIRED_FIELDS = ['username']
43
+
44
+ class Meta:
45
+ db_table = 'users'
46
+ verbose_name = '사용자'
47
+ verbose_name_plural = '사용자들'
48
+
49
+ def __str__(self):
50
+ return self.email
51
+
52
+
53
+ class Post(BaseModel):
54
+ """게시글 모델"""
55
+ author = models.ForeignKey(
56
+ User,
57
+ on_delete=models.CASCADE,
58
+ related_name='posts',
59
+ verbose_name='작성자'
60
+ )
61
+ title = models.CharField(max_length=200, verbose_name='제목')
62
+ content = models.TextField(verbose_name='내용')
63
+ is_published = models.BooleanField(default=False, verbose_name='게시 여부')
64
+ published_at = models.DateTimeField(null=True, blank=True)
65
+
66
+ class Meta:
67
+ db_table = 'posts'
68
+ ordering = ['-created_at']
69
+ verbose_name = '게시글'
70
+ verbose_name_plural = '게시글들'
71
+
72
+ def __str__(self):
73
+ return self.title
74
+
75
+ def publish(self):
76
+ """게시글 발행"""
77
+ self.is_published = True
78
+ self.published_at = timezone.now()
79
+ self.save(update_fields=['is_published', 'published_at'])
80
+ ```
81
+
82
+ ### 2. View (Class-Based Views 권장)
83
+
84
+ ```python
85
+ # ✅ views.py
86
+ from django.views.generic import ListView, DetailView, CreateView, UpdateView
87
+ from django.contrib.auth.mixins import LoginRequiredMixin
88
+ from django.urls import reverse_lazy
89
+ from .models import Post
90
+ from .forms import PostForm
91
+
92
+
93
+ class PostListView(ListView):
94
+ """게시글 목록 뷰"""
95
+ model = Post
96
+ template_name = 'posts/list.html'
97
+ context_object_name = 'posts'
98
+ paginate_by = 10
99
+
100
+ def get_queryset(self):
101
+ queryset = super().get_queryset()
102
+ return queryset.filter(is_published=True).select_related('author')
103
+
104
+
105
+ class PostDetailView(DetailView):
106
+ """게시글 상세 뷰"""
107
+ model = Post
108
+ template_name = 'posts/detail.html'
109
+ context_object_name = 'post'
110
+
111
+ def get_queryset(self):
112
+ return super().get_queryset().select_related('author')
113
+
114
+
115
+ class PostCreateView(LoginRequiredMixin, CreateView):
116
+ """게시글 생성 뷰"""
117
+ model = Post
118
+ form_class = PostForm
119
+ template_name = 'posts/form.html'
120
+ success_url = reverse_lazy('posts:list')
121
+
122
+ def form_valid(self, form):
123
+ form.instance.author = self.request.user
124
+ return super().form_valid(form)
125
+ ```
126
+
127
+ ### 3. Django REST Framework
128
+
129
+ ```python
130
+ # ✅ serializers.py
131
+ from rest_framework import serializers
132
+ from .models import Post, User
133
+
134
+
135
+ class UserSerializer(serializers.ModelSerializer):
136
+ """사용자 시리얼라이저"""
137
+ class Meta:
138
+ model = User
139
+ fields = ['id', 'email', 'username', 'profile_image']
140
+ read_only_fields = ['id']
141
+
142
+
143
+ class PostSerializer(serializers.ModelSerializer):
144
+ """게시글 시리얼라이저"""
145
+ author = UserSerializer(read_only=True)
146
+ author_id = serializers.PrimaryKeyRelatedField(
147
+ queryset=User.objects.all(),
148
+ source='author',
149
+ write_only=True
150
+ )
151
+
152
+ class Meta:
153
+ model = Post
154
+ fields = [
155
+ 'id', 'title', 'content', 'author', 'author_id',
156
+ 'is_published', 'created_at', 'updated_at'
157
+ ]
158
+ read_only_fields = ['id', 'created_at', 'updated_at']
159
+
160
+ def validate_title(self, value):
161
+ if len(value) < 5:
162
+ raise serializers.ValidationError('제목은 5자 이상이어야 합니다')
163
+ return value
164
+
165
+
166
+ # ✅ views.py (DRF)
167
+ from rest_framework import viewsets, permissions, status
168
+ from rest_framework.decorators import action
169
+ from rest_framework.response import Response
170
+ from django_filters.rest_framework import DjangoFilterBackend
171
+
172
+
173
+ class PostViewSet(viewsets.ModelViewSet):
174
+ """게시글 ViewSet"""
175
+ queryset = Post.objects.all()
176
+ serializer_class = PostSerializer
177
+ permission_classes = [permissions.IsAuthenticatedOrReadOnly]
178
+ filter_backends = [DjangoFilterBackend]
179
+ filterset_fields = ['is_published', 'author']
180
+
181
+ def get_queryset(self):
182
+ queryset = super().get_queryset()
183
+ return queryset.select_related('author')
184
+
185
+ def perform_create(self, serializer):
186
+ serializer.save(author=self.request.user)
187
+
188
+ @action(detail=True, methods=['post'])
189
+ def publish(self, request, pk=None):
190
+ """게시글 발행 액션"""
191
+ post = self.get_object()
192
+
193
+ if post.author != request.user:
194
+ return Response(
195
+ {'error': '작성자만 발행할 수 있습니다'},
196
+ status=status.HTTP_403_FORBIDDEN
197
+ )
198
+
199
+ post.publish()
200
+ return Response({'status': '발행되었습니다'})
201
+ ```
202
+
203
+ ### 4. Service 레이어 (Fat Model 방지)
204
+
205
+ ```python
206
+ # ✅ services/post_service.py
207
+ from django.db import transaction
208
+ from django.core.exceptions import PermissionDenied
209
+ from ..models import Post, User
210
+
211
+
212
+ class PostService:
213
+ """게시글 관련 비즈니스 로직"""
214
+
215
+ @staticmethod
216
+ def create_post(author: User, title: str, content: str) -> Post:
217
+ """게시글 생성"""
218
+ post = Post.objects.create(
219
+ author=author,
220
+ title=title,
221
+ content=content
222
+ )
223
+ return post
224
+
225
+ @staticmethod
226
+ def publish_post(post: Post, user: User) -> Post:
227
+ """게시글 발행"""
228
+ if post.author != user:
229
+ raise PermissionDenied('작성자만 발행할 수 있습니다')
230
+
231
+ post.publish()
232
+ return post
233
+
234
+ @staticmethod
235
+ @transaction.atomic
236
+ def bulk_publish(post_ids: list[int], user: User) -> int:
237
+ """여러 게시글 일괄 발행"""
238
+ posts = Post.objects.filter(
239
+ id__in=post_ids,
240
+ author=user,
241
+ is_published=False
242
+ )
243
+
244
+ count = posts.update(
245
+ is_published=True,
246
+ published_at=timezone.now()
247
+ )
248
+ return count
249
+ ```
250
+
251
+ ### 5. Form 및 Validation
252
+
253
+ ```python
254
+ # ✅ forms.py
255
+ from django import forms
256
+ from django.core.exceptions import ValidationError
257
+ from .models import Post
258
+
259
+
260
+ class PostForm(forms.ModelForm):
261
+ """게시글 폼"""
262
+ class Meta:
263
+ model = Post
264
+ fields = ['title', 'content', 'is_published']
265
+ widgets = {
266
+ 'title': forms.TextInput(attrs={
267
+ 'class': 'form-control',
268
+ 'placeholder': '제목을 입력하세요'
269
+ }),
270
+ 'content': forms.Textarea(attrs={
271
+ 'class': 'form-control',
272
+ 'rows': 10
273
+ }),
274
+ }
275
+
276
+ def clean_title(self):
277
+ title = self.cleaned_data.get('title')
278
+ if len(title) < 5:
279
+ raise ValidationError('제목은 5자 이상이어야 합니다')
280
+ return title
281
+
282
+ def clean(self):
283
+ cleaned_data = super().clean()
284
+ title = cleaned_data.get('title')
285
+ content = cleaned_data.get('content')
286
+
287
+ if title and content and title in content:
288
+ raise ValidationError('본문에 제목이 포함되면 안 됩니다')
289
+
290
+ return cleaned_data
291
+ ```
292
+
293
+ ### 6. Custom Manager와 QuerySet
294
+
295
+ ```python
296
+ # ✅ managers.py
297
+ from django.db import models
298
+
299
+
300
+ class PostQuerySet(models.QuerySet):
301
+ """게시글 QuerySet"""
302
+
303
+ def published(self):
304
+ return self.filter(is_published=True)
305
+
306
+ def by_author(self, user):
307
+ return self.filter(author=user)
308
+
309
+ def recent(self, days=7):
310
+ from django.utils import timezone
311
+ from datetime import timedelta
312
+ cutoff = timezone.now() - timedelta(days=days)
313
+ return self.filter(created_at__gte=cutoff)
314
+
315
+
316
+ class PostManager(models.Manager):
317
+ """게시글 Manager"""
318
+
319
+ def get_queryset(self):
320
+ return PostQuerySet(self.model, using=self._db)
321
+
322
+ def published(self):
323
+ return self.get_queryset().published()
324
+
325
+ def by_author(self, user):
326
+ return self.get_queryset().by_author(user)
327
+
328
+
329
+ # 모델에서 사용
330
+ class Post(BaseModel):
331
+ # ... fields ...
332
+ objects = PostManager()
333
+ ```
334
+
335
+ ## 파일 구조
336
+
337
+ ```
338
+ app_name/
339
+ ├── migrations/ # DB 마이그레이션
340
+ ├── management/
341
+ │ └── commands/ # 커스텀 명령어
342
+ ├── services/ # 비즈니스 로직
343
+ ├── api/
344
+ │ ├── serializers.py # DRF 시리얼라이저
345
+ │ ├── views.py # DRF 뷰
346
+ │ └── urls.py # API 라우팅
347
+ ├── templates/ # HTML 템플릿
348
+ ├── static/ # 정적 파일
349
+ ├── tests/
350
+ │ ├── test_models.py
351
+ │ ├── test_views.py
352
+ │ └── test_services.py
353
+ ├── models.py # 모델 (또는 models/ 디렉토리)
354
+ ├── views.py # 뷰
355
+ ├── forms.py # 폼
356
+ ├── managers.py # 커스텀 매니저
357
+ ├── admin.py # Admin 설정
358
+ ├── urls.py # URL 라우팅
359
+ └── apps.py # 앱 설정
360
+ ```
361
+
362
+ ## 체크리스트
363
+
364
+ - [ ] Model에 `__str__`, `Meta` 정의
365
+ - [ ] CBV 사용 (권장)
366
+ - [ ] Service 레이어로 비즈니스 로직 분리
367
+ - [ ] select_related/prefetch_related로 N+1 방지
368
+ - [ ] DRF Serializer로 입출력 검증
369
+ - [ ] Custom Manager/QuerySet 활용
370
+ - [ ] Type hints 사용 (Python 3.10+)
371
+ - [ ] 한글 verbose_name 설정