@su-record/vibe 0.1.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.
- package/LICENSE +21 -0
- package/README.md +448 -0
- package/agents/backend-python-expert.md +453 -0
- package/agents/database-postgres-expert.md +538 -0
- package/agents/frontend-flutter-expert.md +487 -0
- package/agents/frontend-react-expert.md +424 -0
- package/agents/quality-reviewer.md +542 -0
- package/agents/specification-agent.md +505 -0
- package/bin/sutory +332 -0
- package/bin/vibe +338 -0
- package/mcp/dist/__tests__/complexity.test.js +126 -0
- package/mcp/dist/__tests__/memory.test.js +120 -0
- package/mcp/dist/__tests__/python-dart-complexity.test.js +146 -0
- package/mcp/dist/index.js +230 -0
- package/mcp/dist/lib/ContextCompressor.js +305 -0
- package/mcp/dist/lib/MemoryManager.js +334 -0
- package/mcp/dist/lib/ProjectCache.js +126 -0
- package/mcp/dist/lib/PythonParser.js +241 -0
- package/mcp/dist/tools/browser/browserPool.js +76 -0
- package/mcp/dist/tools/browser/browserUtils.js +135 -0
- package/mcp/dist/tools/browser/inspectNetworkRequests.js +140 -0
- package/mcp/dist/tools/browser/monitorConsoleLogs.js +97 -0
- package/mcp/dist/tools/convention/analyzeComplexity.js +248 -0
- package/mcp/dist/tools/convention/applyQualityRules.js +102 -0
- package/mcp/dist/tools/convention/checkCouplingCohesion.js +233 -0
- package/mcp/dist/tools/convention/complexityMetrics.js +133 -0
- package/mcp/dist/tools/convention/dartComplexity.js +117 -0
- package/mcp/dist/tools/convention/getCodingGuide.js +64 -0
- package/mcp/dist/tools/convention/languageDetector.js +50 -0
- package/mcp/dist/tools/convention/pythonComplexity.js +109 -0
- package/mcp/dist/tools/convention/suggestImprovements.js +257 -0
- package/mcp/dist/tools/convention/validateCodeQuality.js +177 -0
- package/mcp/dist/tools/memory/autoSaveContext.js +79 -0
- package/mcp/dist/tools/memory/database.js +123 -0
- package/mcp/dist/tools/memory/deleteMemory.js +39 -0
- package/mcp/dist/tools/memory/listMemories.js +38 -0
- package/mcp/dist/tools/memory/memoryConfig.js +27 -0
- package/mcp/dist/tools/memory/memorySQLite.js +138 -0
- package/mcp/dist/tools/memory/memoryUtils.js +34 -0
- package/mcp/dist/tools/memory/migrate.js +113 -0
- package/mcp/dist/tools/memory/prioritizeMemory.js +109 -0
- package/mcp/dist/tools/memory/recallMemory.js +40 -0
- package/mcp/dist/tools/memory/restoreSessionContext.js +69 -0
- package/mcp/dist/tools/memory/saveMemory.js +34 -0
- package/mcp/dist/tools/memory/searchMemories.js +37 -0
- package/mcp/dist/tools/memory/startSession.js +100 -0
- package/mcp/dist/tools/memory/updateMemory.js +46 -0
- package/mcp/dist/tools/planning/analyzeRequirements.js +166 -0
- package/mcp/dist/tools/planning/createUserStories.js +119 -0
- package/mcp/dist/tools/planning/featureRoadmap.js +202 -0
- package/mcp/dist/tools/planning/generatePrd.js +156 -0
- package/mcp/dist/tools/prompt/analyzePrompt.js +145 -0
- package/mcp/dist/tools/prompt/enhancePrompt.js +105 -0
- package/mcp/dist/tools/semantic/findReferences.js +195 -0
- package/mcp/dist/tools/semantic/findSymbol.js +200 -0
- package/mcp/dist/tools/thinking/analyzeProblem.js +50 -0
- package/mcp/dist/tools/thinking/breakDownProblem.js +140 -0
- package/mcp/dist/tools/thinking/createThinkingChain.js +39 -0
- package/mcp/dist/tools/thinking/formatAsPlan.js +73 -0
- package/mcp/dist/tools/thinking/stepByStepAnalysis.js +58 -0
- package/mcp/dist/tools/thinking/thinkAloudProcess.js +75 -0
- package/mcp/dist/tools/time/getCurrentTime.js +61 -0
- package/mcp/dist/tools/ui/previewUiAscii.js +232 -0
- package/mcp/dist/types/tool.js +2 -0
- package/mcp/package.json +53 -0
- package/package.json +49 -0
- package/scripts/install-mcp.js +48 -0
- package/scripts/install.sh +70 -0
- package/skills/core/communication-guide.md +104 -0
- package/skills/core/development-philosophy.md +53 -0
- package/skills/core/quick-start.md +121 -0
- package/skills/languages/dart-flutter.md +509 -0
- package/skills/languages/python-fastapi.md +386 -0
- package/skills/languages/typescript-nextjs.md +441 -0
- package/skills/languages/typescript-react-native.md +446 -0
- package/skills/languages/typescript-react.md +525 -0
- package/skills/quality/checklist.md +276 -0
- package/skills/quality/testing-strategy.md +437 -0
- package/skills/standards/anti-patterns.md +369 -0
- package/skills/standards/code-structure.md +291 -0
- package/skills/standards/complexity-metrics.md +312 -0
- package/skills/standards/naming-conventions.md +198 -0
- package/skills/tools/mcp-hi-ai-guide.md +665 -0
- package/skills/tools/mcp-workflow.md +51 -0
- package/templates/constitution-template.md +193 -0
- package/templates/plan-template.md +237 -0
- package/templates/spec-template.md +142 -0
- package/templates/tasks-template.md +132 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# 🐍 Python + FastAPI 품질 규칙
|
|
2
|
+
|
|
3
|
+
## 핵심 원칙 (core에서 상속)
|
|
4
|
+
|
|
5
|
+
```markdown
|
|
6
|
+
✅ 단일 책임 (SRP)
|
|
7
|
+
✅ 중복 제거 (DRY)
|
|
8
|
+
✅ 재사용성
|
|
9
|
+
✅ 낮은 복잡도
|
|
10
|
+
✅ 함수 ≤ 30줄 (권장), ≤ 50줄 (허용)
|
|
11
|
+
✅ 중첩 ≤ 3단계
|
|
12
|
+
✅ Cyclomatic complexity ≤ 10
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Python 특화 규칙
|
|
16
|
+
|
|
17
|
+
### 1. 타입 힌트 100% 필수
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
# ❌ 타입 힌트 없음
|
|
21
|
+
def get_user(user_id):
|
|
22
|
+
return db.get(user_id)
|
|
23
|
+
|
|
24
|
+
# ✅ 완전한 타입 힌트
|
|
25
|
+
async def get_user(user_id: str, db: AsyncSession) -> User | None:
|
|
26
|
+
result = await db.execute(select(User).where(User.id == user_id))
|
|
27
|
+
return result.scalar_one_or_none()
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. Pydantic으로 Contract 정의
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from pydantic import BaseModel, Field, EmailStr, field_validator
|
|
34
|
+
|
|
35
|
+
class CreateUserRequest(BaseModel):
|
|
36
|
+
"""사용자 생성 요청 스키마"""
|
|
37
|
+
email: EmailStr
|
|
38
|
+
username: str = Field(min_length=3, max_length=50)
|
|
39
|
+
password: str = Field(min_length=8)
|
|
40
|
+
age: int = Field(ge=0, le=150)
|
|
41
|
+
|
|
42
|
+
@field_validator("username")
|
|
43
|
+
def validate_username(cls, v: str) -> str:
|
|
44
|
+
if not v.isalnum():
|
|
45
|
+
raise ValueError("Username must be alphanumeric")
|
|
46
|
+
return v.lower()
|
|
47
|
+
|
|
48
|
+
class Config:
|
|
49
|
+
json_schema_extra = {
|
|
50
|
+
"example": {
|
|
51
|
+
"email": "user@example.com",
|
|
52
|
+
"username": "johndoe",
|
|
53
|
+
"password": "securepass123",
|
|
54
|
+
"age": 25,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class UserResponse(BaseModel):
|
|
59
|
+
"""사용자 응답 스키마"""
|
|
60
|
+
id: str
|
|
61
|
+
email: str
|
|
62
|
+
username: str
|
|
63
|
+
created_at: datetime
|
|
64
|
+
|
|
65
|
+
class Config:
|
|
66
|
+
from_attributes = True # SQLAlchemy 호환
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. async/await 패턴
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
# ✅ 비동기 I/O (데이터베이스, API 호출)
|
|
73
|
+
async def get_user_with_posts(
|
|
74
|
+
user_id: str,
|
|
75
|
+
db: AsyncSession
|
|
76
|
+
) -> tuple[User, list[Post]]:
|
|
77
|
+
# 병렬 실행
|
|
78
|
+
user_task = db.execute(select(User).where(User.id == user_id))
|
|
79
|
+
posts_task = db.execute(select(Post).where(Post.user_id == user_id))
|
|
80
|
+
|
|
81
|
+
user_result, posts_result = await asyncio.gather(user_task, posts_task)
|
|
82
|
+
|
|
83
|
+
user = user_result.scalar_one_or_none()
|
|
84
|
+
posts = list(posts_result.scalars().all())
|
|
85
|
+
|
|
86
|
+
return user, posts
|
|
87
|
+
|
|
88
|
+
# ❌ 동기 함수 (블로킹)
|
|
89
|
+
def get_user(user_id: str):
|
|
90
|
+
return requests.get(f"/users/{user_id}") # 블로킹!
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 4. Early Return 선호
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# ❌ 중첩된 if문
|
|
97
|
+
async def process_order(order_id: str, db: AsyncSession):
|
|
98
|
+
order = await get_order(order_id, db)
|
|
99
|
+
if order:
|
|
100
|
+
if order.is_valid:
|
|
101
|
+
if order.items:
|
|
102
|
+
if order.user.is_active:
|
|
103
|
+
return await process_items(order.items)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
# ✅ Early return
|
|
107
|
+
async def process_order(order_id: str, db: AsyncSession) -> ProcessResult | None:
|
|
108
|
+
order = await get_order(order_id, db)
|
|
109
|
+
if not order:
|
|
110
|
+
return None
|
|
111
|
+
if not order.is_valid:
|
|
112
|
+
return None
|
|
113
|
+
if not order.items:
|
|
114
|
+
return None
|
|
115
|
+
if not order.user.is_active:
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
return await process_items(order.items)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 5. Repository 패턴 (데이터 액세스 분리)
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
# ✅ Repository 레이어
|
|
125
|
+
class UserRepository:
|
|
126
|
+
"""데이터 액세스만 담당"""
|
|
127
|
+
|
|
128
|
+
def __init__(self, db: AsyncSession):
|
|
129
|
+
self.db = db
|
|
130
|
+
|
|
131
|
+
async def get_by_id(self, user_id: str) -> User | None:
|
|
132
|
+
result = await self.db.execute(
|
|
133
|
+
select(User).where(User.id == user_id)
|
|
134
|
+
)
|
|
135
|
+
return result.scalar_one_or_none()
|
|
136
|
+
|
|
137
|
+
async def create(self, user: User) -> User:
|
|
138
|
+
self.db.add(user)
|
|
139
|
+
await self.db.commit()
|
|
140
|
+
await self.db.refresh(user)
|
|
141
|
+
return user
|
|
142
|
+
|
|
143
|
+
async def get_by_email(self, email: str) -> User | None:
|
|
144
|
+
result = await self.db.execute(
|
|
145
|
+
select(User).where(User.email == email)
|
|
146
|
+
)
|
|
147
|
+
return result.scalar_one_or_none()
|
|
148
|
+
|
|
149
|
+
# ✅ Service 레이어 (비즈니스 로직)
|
|
150
|
+
class UserService:
|
|
151
|
+
"""비즈니스 로직만 담당"""
|
|
152
|
+
|
|
153
|
+
def __init__(self, repository: UserRepository):
|
|
154
|
+
self.repository = repository
|
|
155
|
+
|
|
156
|
+
async def create_user(
|
|
157
|
+
self, request: CreateUserRequest
|
|
158
|
+
) -> UserResponse:
|
|
159
|
+
# 비즈니스 규칙: 이메일 중복 체크
|
|
160
|
+
existing = await self.repository.get_by_email(request.email)
|
|
161
|
+
if existing:
|
|
162
|
+
raise HTTPException(409, detail="Email already exists")
|
|
163
|
+
|
|
164
|
+
# 비즈니스 규칙: 비밀번호 해싱
|
|
165
|
+
hashed_password = hash_password(request.password)
|
|
166
|
+
|
|
167
|
+
# 생성
|
|
168
|
+
user = User(
|
|
169
|
+
email=request.email,
|
|
170
|
+
username=request.username,
|
|
171
|
+
password_hash=hashed_password,
|
|
172
|
+
)
|
|
173
|
+
user = await self.repository.create(user)
|
|
174
|
+
|
|
175
|
+
return UserResponse.model_validate(user)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 6. 의존성 주입 (FastAPI Depends)
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
# app/core/deps.py
|
|
182
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
183
|
+
|
|
184
|
+
async def get_db() -> AsyncSession:
|
|
185
|
+
"""데이터베이스 세션 의존성"""
|
|
186
|
+
async with async_session_maker() as session:
|
|
187
|
+
yield session
|
|
188
|
+
|
|
189
|
+
async def get_current_user(
|
|
190
|
+
token: str = Depends(oauth2_scheme),
|
|
191
|
+
db: AsyncSession = Depends(get_db)
|
|
192
|
+
) -> User:
|
|
193
|
+
"""현재 사용자 의존성"""
|
|
194
|
+
payload = decode_jwt(token)
|
|
195
|
+
user = await get_user_by_id(payload["sub"], db)
|
|
196
|
+
if not user:
|
|
197
|
+
raise HTTPException(401, detail="Invalid credentials")
|
|
198
|
+
return user
|
|
199
|
+
|
|
200
|
+
# app/api/v1/users.py
|
|
201
|
+
@router.get("/me", response_model=UserResponse)
|
|
202
|
+
async def get_current_user_profile(
|
|
203
|
+
current_user: User = Depends(get_current_user)
|
|
204
|
+
):
|
|
205
|
+
"""현재 사용자 프로필 조회"""
|
|
206
|
+
return UserResponse.model_validate(current_user)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 7. 에러 처리 표준
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from fastapi import HTTPException
|
|
213
|
+
|
|
214
|
+
# ✅ 명확한 에러 메시지
|
|
215
|
+
async def get_user(user_id: str, db: AsyncSession) -> User:
|
|
216
|
+
user = await db.get(User, user_id)
|
|
217
|
+
if not user:
|
|
218
|
+
raise HTTPException(
|
|
219
|
+
status_code=404,
|
|
220
|
+
detail=f"User {user_id} not found"
|
|
221
|
+
)
|
|
222
|
+
return user
|
|
223
|
+
|
|
224
|
+
# ✅ 커스텀 예외
|
|
225
|
+
class UserNotFoundError(Exception):
|
|
226
|
+
def __init__(self, user_id: str):
|
|
227
|
+
self.user_id = user_id
|
|
228
|
+
super().__init__(f"User {user_id} not found")
|
|
229
|
+
|
|
230
|
+
# 전역 예외 핸들러
|
|
231
|
+
@app.exception_handler(UserNotFoundError)
|
|
232
|
+
async def user_not_found_handler(request: Request, exc: UserNotFoundError):
|
|
233
|
+
return JSONResponse(
|
|
234
|
+
status_code=404,
|
|
235
|
+
content={"detail": str(exc)}
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### 8. SQLAlchemy 2.0 스타일
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
from sqlalchemy import select, func
|
|
243
|
+
from sqlalchemy.orm import selectinload
|
|
244
|
+
|
|
245
|
+
# ✅ 2.0 스타일 (async + select)
|
|
246
|
+
async def get_users_with_posts(db: AsyncSession) -> list[User]:
|
|
247
|
+
result = await db.execute(
|
|
248
|
+
select(User)
|
|
249
|
+
.options(selectinload(User.posts)) # Eager loading
|
|
250
|
+
.where(User.is_active == True)
|
|
251
|
+
.order_by(User.created_at.desc())
|
|
252
|
+
.limit(20)
|
|
253
|
+
)
|
|
254
|
+
return list(result.scalars().all())
|
|
255
|
+
|
|
256
|
+
# ❌ 1.x 스타일 (레거시)
|
|
257
|
+
def get_users():
|
|
258
|
+
return session.query(User).filter_by(is_active=True).all()
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 9. Python 관용구 활용
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
# ✅ List comprehension
|
|
265
|
+
active_users = [u for u in users if u.is_active]
|
|
266
|
+
|
|
267
|
+
# ✅ Dictionary comprehension
|
|
268
|
+
user_dict = {u.id: u.name for u in users}
|
|
269
|
+
|
|
270
|
+
# ✅ Generator expression (메모리 효율)
|
|
271
|
+
total = sum(u.age for u in users)
|
|
272
|
+
|
|
273
|
+
# ✅ Context manager
|
|
274
|
+
async with db.begin():
|
|
275
|
+
user = User(...)
|
|
276
|
+
db.add(user)
|
|
277
|
+
# 자동 commit/rollback
|
|
278
|
+
|
|
279
|
+
# ✅ Dataclass (간단한 데이터 구조)
|
|
280
|
+
from dataclasses import dataclass
|
|
281
|
+
|
|
282
|
+
@dataclass(frozen=True) # Immutable
|
|
283
|
+
class Point:
|
|
284
|
+
x: float
|
|
285
|
+
y: float
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 10. 로깅 표준
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
import structlog
|
|
292
|
+
|
|
293
|
+
logger = structlog.get_logger()
|
|
294
|
+
|
|
295
|
+
# ✅ 구조화된 로깅
|
|
296
|
+
async def create_user(request: CreateUserRequest):
|
|
297
|
+
logger.info(
|
|
298
|
+
"user_creation_started",
|
|
299
|
+
email=request.email,
|
|
300
|
+
username=request.username
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
user = await user_service.create(request)
|
|
305
|
+
logger.info(
|
|
306
|
+
"user_creation_succeeded",
|
|
307
|
+
user_id=user.id,
|
|
308
|
+
email=user.email
|
|
309
|
+
)
|
|
310
|
+
return user
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.error(
|
|
313
|
+
"user_creation_failed",
|
|
314
|
+
email=request.email,
|
|
315
|
+
error=str(e),
|
|
316
|
+
exc_info=True
|
|
317
|
+
)
|
|
318
|
+
raise
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## 안티패턴
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
# ❌ any 타입
|
|
325
|
+
def process_data(data: any): # 타입 안전성 상실
|
|
326
|
+
return data
|
|
327
|
+
|
|
328
|
+
# ❌ 블로킹 I/O in async 함수
|
|
329
|
+
async def bad_example():
|
|
330
|
+
data = requests.get("https://api.example.com") # 블로킹!
|
|
331
|
+
return data
|
|
332
|
+
|
|
333
|
+
# ❌ 예외 무시
|
|
334
|
+
try:
|
|
335
|
+
risky_operation()
|
|
336
|
+
except:
|
|
337
|
+
pass # 위험!
|
|
338
|
+
|
|
339
|
+
# ❌ Mutable default argument
|
|
340
|
+
def append_to_list(item, my_list=[]): # 버그!
|
|
341
|
+
my_list.append(item)
|
|
342
|
+
return my_list
|
|
343
|
+
|
|
344
|
+
# ✅ 올바른 방법
|
|
345
|
+
def append_to_list(item, my_list: list | None = None):
|
|
346
|
+
if my_list is None:
|
|
347
|
+
my_list = []
|
|
348
|
+
my_list.append(item)
|
|
349
|
+
return my_list
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## 코드 품질 도구
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# 포맷팅
|
|
356
|
+
black .
|
|
357
|
+
isort .
|
|
358
|
+
|
|
359
|
+
# 린팅
|
|
360
|
+
flake8 .
|
|
361
|
+
ruff check .
|
|
362
|
+
|
|
363
|
+
# 타입 체크
|
|
364
|
+
mypy app/ --strict
|
|
365
|
+
|
|
366
|
+
# 테스트
|
|
367
|
+
pytest tests/ -v --cov=app
|
|
368
|
+
|
|
369
|
+
# 보안 체크
|
|
370
|
+
bandit -r app/
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## 체크리스트
|
|
374
|
+
|
|
375
|
+
Python/FastAPI 코드 작성 시:
|
|
376
|
+
|
|
377
|
+
- [ ] 타입 힌트 100% (함수 시그니처, 변수)
|
|
378
|
+
- [ ] Pydantic 스키마로 Contract 정의
|
|
379
|
+
- [ ] async/await 사용 (I/O 작업)
|
|
380
|
+
- [ ] Early return 패턴
|
|
381
|
+
- [ ] Repository + Service 레이어 분리
|
|
382
|
+
- [ ] 의존성 주입 (Depends)
|
|
383
|
+
- [ ] 명확한 에러 메시지
|
|
384
|
+
- [ ] 구조화된 로깅
|
|
385
|
+
- [ ] 함수 ≤ 30줄 (SRP 준수)
|
|
386
|
+
- [ ] 복잡도 ≤ 10
|