@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,396 @@
1
+ # 🐹 Go 품질 규칙
2
+
3
+ ## 핵심 원칙 (core에서 상속)
4
+
5
+ ```markdown
6
+ ✅ 단일 책임 (SRP)
7
+ ✅ 중복 제거 (DRY)
8
+ ✅ 재사용성
9
+ ✅ 낮은 복잡도
10
+ ✅ 함수 ≤ 30줄
11
+ ✅ 중첩 ≤ 3단계
12
+ ✅ Cyclomatic complexity ≤ 10
13
+ ```
14
+
15
+ ## Go 특화 규칙
16
+
17
+ ### 1. 에러 처리
18
+
19
+ ```go
20
+ // ❌ 에러 무시
21
+ data, _ := ioutil.ReadFile("config.json")
22
+
23
+ // ✅ 에러 항상 처리
24
+ data, err := ioutil.ReadFile("config.json")
25
+ if err != nil {
26
+ return fmt.Errorf("설정 파일 읽기 실패: %w", err)
27
+ }
28
+
29
+ // ✅ 커스텀 에러 타입
30
+ type NotFoundError struct {
31
+ Resource string
32
+ ID string
33
+ }
34
+
35
+ func (e *NotFoundError) Error() string {
36
+ return fmt.Sprintf("%s (ID: %s)를 찾을 수 없습니다", e.Resource, e.ID)
37
+ }
38
+
39
+ // 사용
40
+ func GetUser(id string) (*User, error) {
41
+ user, err := repo.FindByID(id)
42
+ if err != nil {
43
+ return nil, fmt.Errorf("사용자 조회 실패: %w", err)
44
+ }
45
+ if user == nil {
46
+ return nil, &NotFoundError{Resource: "사용자", ID: id}
47
+ }
48
+ return user, nil
49
+ }
50
+ ```
51
+
52
+ ### 2. 구조체와 인터페이스
53
+
54
+ ```go
55
+ // ✅ 구조체 정의
56
+ type User struct {
57
+ ID string `json:"id"`
58
+ Email string `json:"email"`
59
+ Name string `json:"name"`
60
+ CreatedAt time.Time `json:"created_at"`
61
+ UpdatedAt time.Time `json:"updated_at"`
62
+ }
63
+
64
+ // ✅ 생성자 함수
65
+ func NewUser(email, name string) *User {
66
+ now := time.Now()
67
+ return &User{
68
+ ID: uuid.New().String(),
69
+ Email: email,
70
+ Name: name,
71
+ CreatedAt: now,
72
+ UpdatedAt: now,
73
+ }
74
+ }
75
+
76
+ // ✅ 작은 인터페이스 (Go의 철학)
77
+ type Reader interface {
78
+ Read(p []byte) (n int, err error)
79
+ }
80
+
81
+ type Writer interface {
82
+ Write(p []byte) (n int, err error)
83
+ }
84
+
85
+ // ✅ 인터페이스 조합
86
+ type ReadWriter interface {
87
+ Reader
88
+ Writer
89
+ }
90
+
91
+ // ✅ Repository 인터페이스
92
+ type UserRepository interface {
93
+ FindByID(ctx context.Context, id string) (*User, error)
94
+ FindByEmail(ctx context.Context, email string) (*User, error)
95
+ Create(ctx context.Context, user *User) error
96
+ Update(ctx context.Context, user *User) error
97
+ Delete(ctx context.Context, id string) error
98
+ }
99
+ ```
100
+
101
+ ### 3. Context 사용
102
+
103
+ ```go
104
+ // ✅ Context 전파
105
+ func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
106
+ // Context를 하위 함수에 전달
107
+ user, err := s.repo.FindByID(ctx, id)
108
+ if err != nil {
109
+ return nil, err
110
+ }
111
+ return user, nil
112
+ }
113
+
114
+ // ✅ Context 타임아웃
115
+ func (h *Handler) HandleRequest(w http.ResponseWriter, r *http.Request) {
116
+ ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
117
+ defer cancel()
118
+
119
+ result, err := h.service.Process(ctx)
120
+ if err != nil {
121
+ if errors.Is(err, context.DeadlineExceeded) {
122
+ http.Error(w, "요청 시간 초과", http.StatusRequestTimeout)
123
+ return
124
+ }
125
+ http.Error(w, err.Error(), http.StatusInternalServerError)
126
+ return
127
+ }
128
+
129
+ json.NewEncoder(w).Encode(result)
130
+ }
131
+ ```
132
+
133
+ ### 4. HTTP 핸들러 (net/http, Gin, Echo)
134
+
135
+ ```go
136
+ // ✅ net/http 핸들러
137
+ func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
138
+ id := chi.URLParam(r, "id")
139
+
140
+ user, err := h.service.GetUser(r.Context(), id)
141
+ if err != nil {
142
+ var notFound *NotFoundError
143
+ if errors.As(err, &notFound) {
144
+ http.Error(w, err.Error(), http.StatusNotFound)
145
+ return
146
+ }
147
+ http.Error(w, "서버 오류", http.StatusInternalServerError)
148
+ return
149
+ }
150
+
151
+ w.Header().Set("Content-Type", "application/json")
152
+ json.NewEncoder(w).Encode(user)
153
+ }
154
+
155
+ // ✅ Gin 핸들러
156
+ func (h *UserHandler) GetUser(c *gin.Context) {
157
+ id := c.Param("id")
158
+
159
+ user, err := h.service.GetUser(c.Request.Context(), id)
160
+ if err != nil {
161
+ var notFound *NotFoundError
162
+ if errors.As(err, &notFound) {
163
+ c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
164
+ return
165
+ }
166
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "서버 오류"})
167
+ return
168
+ }
169
+
170
+ c.JSON(http.StatusOK, user)
171
+ }
172
+
173
+ // ✅ Echo 핸들러
174
+ func (h *UserHandler) GetUser(c echo.Context) error {
175
+ id := c.Param("id")
176
+
177
+ user, err := h.service.GetUser(c.Request().Context(), id)
178
+ if err != nil {
179
+ var notFound *NotFoundError
180
+ if errors.As(err, &notFound) {
181
+ return c.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
182
+ }
183
+ return c.JSON(http.StatusInternalServerError, map[string]string{"error": "서버 오류"})
184
+ }
185
+
186
+ return c.JSON(http.StatusOK, user)
187
+ }
188
+ ```
189
+
190
+ ### 5. 의존성 주입
191
+
192
+ ```go
193
+ // ✅ 구조체에 의존성 주입
194
+ type UserService struct {
195
+ repo UserRepository
196
+ cache CacheRepository
197
+ logger *slog.Logger
198
+ }
199
+
200
+ func NewUserService(
201
+ repo UserRepository,
202
+ cache CacheRepository,
203
+ logger *slog.Logger,
204
+ ) *UserService {
205
+ return &UserService{
206
+ repo: repo,
207
+ cache: cache,
208
+ logger: logger,
209
+ }
210
+ }
211
+
212
+ // ✅ 옵션 패턴
213
+ type ServerOption func(*Server)
214
+
215
+ func WithPort(port int) ServerOption {
216
+ return func(s *Server) {
217
+ s.port = port
218
+ }
219
+ }
220
+
221
+ func WithTimeout(timeout time.Duration) ServerOption {
222
+ return func(s *Server) {
223
+ s.timeout = timeout
224
+ }
225
+ }
226
+
227
+ func NewServer(opts ...ServerOption) *Server {
228
+ s := &Server{
229
+ port: 8080, // 기본값
230
+ timeout: 30 * time.Second,
231
+ }
232
+ for _, opt := range opts {
233
+ opt(s)
234
+ }
235
+ return s
236
+ }
237
+
238
+ // 사용
239
+ server := NewServer(
240
+ WithPort(3000),
241
+ WithTimeout(60*time.Second),
242
+ )
243
+ ```
244
+
245
+ ### 6. 동시성
246
+
247
+ ```go
248
+ // ✅ Goroutine + Channel
249
+ func ProcessItems(ctx context.Context, items []Item) ([]Result, error) {
250
+ results := make(chan Result, len(items))
251
+ errs := make(chan error, len(items))
252
+
253
+ var wg sync.WaitGroup
254
+ for _, item := range items {
255
+ wg.Add(1)
256
+ go func(item Item) {
257
+ defer wg.Done()
258
+ result, err := processItem(ctx, item)
259
+ if err != nil {
260
+ errs <- err
261
+ return
262
+ }
263
+ results <- result
264
+ }(item)
265
+ }
266
+
267
+ // 결과 수집
268
+ go func() {
269
+ wg.Wait()
270
+ close(results)
271
+ close(errs)
272
+ }()
273
+
274
+ var finalResults []Result
275
+ for result := range results {
276
+ finalResults = append(finalResults, result)
277
+ }
278
+
279
+ // 첫 번째 에러 반환
280
+ select {
281
+ case err := <-errs:
282
+ return nil, err
283
+ default:
284
+ return finalResults, nil
285
+ }
286
+ }
287
+
288
+ // ✅ errgroup 사용 (권장)
289
+ import "golang.org/x/sync/errgroup"
290
+
291
+ func ProcessItems(ctx context.Context, items []Item) ([]Result, error) {
292
+ g, ctx := errgroup.WithContext(ctx)
293
+ results := make([]Result, len(items))
294
+
295
+ for i, item := range items {
296
+ i, item := i, item // 클로저 캡처
297
+ g.Go(func() error {
298
+ result, err := processItem(ctx, item)
299
+ if err != nil {
300
+ return err
301
+ }
302
+ results[i] = result
303
+ return nil
304
+ })
305
+ }
306
+
307
+ if err := g.Wait(); err != nil {
308
+ return nil, err
309
+ }
310
+ return results, nil
311
+ }
312
+ ```
313
+
314
+ ### 7. 테스트
315
+
316
+ ```go
317
+ // ✅ 테이블 기반 테스트
318
+ func TestAdd(t *testing.T) {
319
+ tests := []struct {
320
+ name string
321
+ a, b int
322
+ expected int
323
+ }{
324
+ {"양수 덧셈", 2, 3, 5},
325
+ {"음수 덧셈", -1, -2, -3},
326
+ {"영과 덧셈", 0, 5, 5},
327
+ }
328
+
329
+ for _, tt := range tests {
330
+ t.Run(tt.name, func(t *testing.T) {
331
+ result := Add(tt.a, tt.b)
332
+ if result != tt.expected {
333
+ t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
334
+ }
335
+ })
336
+ }
337
+ }
338
+
339
+ // ✅ Mock 사용 (testify)
340
+ type MockUserRepository struct {
341
+ mock.Mock
342
+ }
343
+
344
+ func (m *MockUserRepository) FindByID(ctx context.Context, id string) (*User, error) {
345
+ args := m.Called(ctx, id)
346
+ if args.Get(0) == nil {
347
+ return nil, args.Error(1)
348
+ }
349
+ return args.Get(0).(*User), args.Error(1)
350
+ }
351
+
352
+ func TestUserService_GetUser(t *testing.T) {
353
+ mockRepo := new(MockUserRepository)
354
+ service := NewUserService(mockRepo, nil, slog.Default())
355
+
356
+ expectedUser := &User{ID: "123", Name: "테스트"}
357
+ mockRepo.On("FindByID", mock.Anything, "123").Return(expectedUser, nil)
358
+
359
+ user, err := service.GetUser(context.Background(), "123")
360
+
361
+ assert.NoError(t, err)
362
+ assert.Equal(t, expectedUser, user)
363
+ mockRepo.AssertExpectations(t)
364
+ }
365
+ ```
366
+
367
+ ## 파일 구조
368
+
369
+ ```
370
+ project/
371
+ ├── cmd/
372
+ │ └── server/
373
+ │ └── main.go # 엔트리포인트
374
+ ├── internal/
375
+ │ ├── domain/ # 도메인 모델
376
+ │ ├── handler/ # HTTP 핸들러
377
+ │ ├── service/ # 비즈니스 로직
378
+ │ ├── repository/ # 데이터 액세스
379
+ │ └── middleware/ # 미들웨어
380
+ ├── pkg/ # 외부 공개 패키지
381
+ ├── config/ # 설정
382
+ ├── migrations/ # DB 마이그레이션
383
+ ├── go.mod
384
+ └── go.sum
385
+ ```
386
+
387
+ ## 체크리스트
388
+
389
+ - [ ] 에러 항상 처리 (_, err 금지)
390
+ - [ ] fmt.Errorf("%w", err)로 에러 래핑
391
+ - [ ] Context 첫 번째 인자로 전달
392
+ - [ ] 작은 인터페이스 정의
393
+ - [ ] 생성자 함수 (NewXxx) 사용
394
+ - [ ] 테이블 기반 테스트
395
+ - [ ] gofmt, golint, go vet 통과
396
+ - [ ] 동시성에서 race condition 주의