oh-my-ag 1.2.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/.agent/skills/_shared/api-contracts/README.md +56 -0
- package/.agent/skills/_shared/api-contracts/template.md +88 -0
- package/.agent/skills/_shared/clarification-protocol.md +217 -0
- package/.agent/skills/_shared/common-checklist.md +31 -0
- package/.agent/skills/_shared/context-budget.md +118 -0
- package/.agent/skills/_shared/context-loading.md +105 -0
- package/.agent/skills/_shared/difficulty-guide.md +55 -0
- package/.agent/skills/_shared/lessons-learned.md +113 -0
- package/.agent/skills/_shared/memory-protocol.md +79 -0
- package/.agent/skills/_shared/reasoning-templates.md +161 -0
- package/.agent/skills/_shared/skill-routing.md +80 -0
- package/.agent/skills/_shared/verify.sh +252 -0
- package/.agent/skills/backend-agent/SKILL.md +47 -0
- package/.agent/skills/backend-agent/resources/api-template.py +326 -0
- package/.agent/skills/backend-agent/resources/checklist.md +36 -0
- package/.agent/skills/backend-agent/resources/error-playbook.md +98 -0
- package/.agent/skills/backend-agent/resources/examples.md +85 -0
- package/.agent/skills/backend-agent/resources/execution-protocol.md +45 -0
- package/.agent/skills/backend-agent/resources/snippets.md +197 -0
- package/.agent/skills/backend-agent/resources/tech-stack.md +39 -0
- package/.agent/skills/commit/SKILL.md +121 -0
- package/.agent/skills/commit/config/commit-config.yaml +55 -0
- package/.agent/skills/commit/resources/conventional-commits.md +166 -0
- package/.agent/skills/debug-agent/SKILL.md +51 -0
- package/.agent/skills/debug-agent/resources/bug-report-template.md +332 -0
- package/.agent/skills/debug-agent/resources/checklist.md +30 -0
- package/.agent/skills/debug-agent/resources/common-patterns.md +734 -0
- package/.agent/skills/debug-agent/resources/debugging-checklist.md +362 -0
- package/.agent/skills/debug-agent/resources/error-playbook.md +94 -0
- package/.agent/skills/debug-agent/resources/examples.md +87 -0
- package/.agent/skills/debug-agent/resources/execution-protocol.md +51 -0
- package/.agent/skills/frontend-agent/SKILL.md +48 -0
- package/.agent/skills/frontend-agent/resources/checklist.md +38 -0
- package/.agent/skills/frontend-agent/resources/component-template.tsx +92 -0
- package/.agent/skills/frontend-agent/resources/error-playbook.md +108 -0
- package/.agent/skills/frontend-agent/resources/examples.md +77 -0
- package/.agent/skills/frontend-agent/resources/execution-protocol.md +49 -0
- package/.agent/skills/frontend-agent/resources/snippets.md +205 -0
- package/.agent/skills/frontend-agent/resources/tailwind-rules.md +343 -0
- package/.agent/skills/frontend-agent/resources/tech-stack.md +36 -0
- package/.agent/skills/mobile-agent/SKILL.md +46 -0
- package/.agent/skills/mobile-agent/resources/checklist.md +35 -0
- package/.agent/skills/mobile-agent/resources/error-playbook.md +106 -0
- package/.agent/skills/mobile-agent/resources/examples.md +79 -0
- package/.agent/skills/mobile-agent/resources/execution-protocol.md +49 -0
- package/.agent/skills/mobile-agent/resources/screen-template.dart +298 -0
- package/.agent/skills/mobile-agent/resources/snippets.md +235 -0
- package/.agent/skills/mobile-agent/resources/tech-stack.md +45 -0
- package/.agent/skills/orchestrator/SKILL.md +99 -0
- package/.agent/skills/orchestrator/config/cli-config.yaml +78 -0
- package/.agent/skills/orchestrator/resources/memory-schema.md +212 -0
- package/.agent/skills/orchestrator/resources/subagent-prompt-template.md +153 -0
- package/.agent/skills/orchestrator/scripts/parallel-run.sh +330 -0
- package/.agent/skills/orchestrator/scripts/spawn-agent.sh +263 -0
- package/.agent/skills/orchestrator/templates/backend-task.md +18 -0
- package/.agent/skills/orchestrator/templates/debug-task.md +16 -0
- package/.agent/skills/orchestrator/templates/frontend-task.md +17 -0
- package/.agent/skills/orchestrator/templates/mobile-task.md +17 -0
- package/.agent/skills/orchestrator/templates/qa-task.md +16 -0
- package/.agent/skills/orchestrator/templates/tasks-example.yaml +15 -0
- package/.agent/skills/pm-agent/SKILL.md +47 -0
- package/.agent/skills/pm-agent/resources/error-playbook.md +75 -0
- package/.agent/skills/pm-agent/resources/examples.md +121 -0
- package/.agent/skills/pm-agent/resources/execution-protocol.md +46 -0
- package/.agent/skills/pm-agent/resources/task-template.json +57 -0
- package/.agent/skills/qa-agent/SKILL.md +43 -0
- package/.agent/skills/qa-agent/resources/checklist.md +294 -0
- package/.agent/skills/qa-agent/resources/error-playbook.md +95 -0
- package/.agent/skills/qa-agent/resources/examples.md +100 -0
- package/.agent/skills/qa-agent/resources/execution-protocol.md +50 -0
- package/.agent/skills/qa-agent/resources/self-check.md +27 -0
- package/.agent/skills/workflow-guide/SKILL.md +57 -0
- package/.agent/skills/workflow-guide/resources/examples.md +68 -0
- package/README.ko.md +459 -0
- package/README.md +563 -0
- package/bin/cli.js +205 -0
- package/package.json +75 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: backend-agent
|
|
3
|
+
description: Backend specialist for APIs, databases, authentication, and server-side logic using FastAPI, Node.js, or other frameworks
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Backend Agent - API & Server Specialist
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
- Building REST APIs or GraphQL endpoints
|
|
10
|
+
- Database design and migrations
|
|
11
|
+
- Authentication and authorization
|
|
12
|
+
- Server-side business logic
|
|
13
|
+
- Background jobs and queues
|
|
14
|
+
|
|
15
|
+
## When NOT to use
|
|
16
|
+
- Frontend UI -> use Frontend Agent
|
|
17
|
+
- Mobile-specific code -> use Mobile Agent
|
|
18
|
+
|
|
19
|
+
## Core Rules
|
|
20
|
+
1. Clean architecture: router -> service -> repository -> models
|
|
21
|
+
2. No business logic in route handlers
|
|
22
|
+
3. All inputs validated with Pydantic/Zod
|
|
23
|
+
4. Parameterized queries only (never string interpolation)
|
|
24
|
+
5. JWT + bcrypt for auth; rate limit auth endpoints
|
|
25
|
+
6. Async/await consistently; type hints on all signatures
|
|
26
|
+
|
|
27
|
+
## How to Execute
|
|
28
|
+
Follow `resources/execution-protocol.md` step by step.
|
|
29
|
+
See `resources/examples.md` for input/output examples.
|
|
30
|
+
Before submitting, run `resources/checklist.md`.
|
|
31
|
+
|
|
32
|
+
## Serena Memory (CLI Mode)
|
|
33
|
+
See `../_shared/serena-memory-protocol.md`.
|
|
34
|
+
|
|
35
|
+
## References
|
|
36
|
+
- Execution steps: `resources/execution-protocol.md`
|
|
37
|
+
- Code examples: `resources/examples.md`
|
|
38
|
+
- Code snippets: `resources/snippets.md`
|
|
39
|
+
- Checklist: `resources/checklist.md`
|
|
40
|
+
- Error recovery: `resources/error-playbook.md`
|
|
41
|
+
- Tech stack: `resources/tech-stack.md`
|
|
42
|
+
- API template: `resources/api-template.py`
|
|
43
|
+
- Context loading: `../_shared/context-loading.md`
|
|
44
|
+
- Reasoning templates: `../_shared/reasoning-templates.md`
|
|
45
|
+
- Clarification: `../_shared/clarification-protocol.md`
|
|
46
|
+
- Context budget: `../_shared/context-budget.md`
|
|
47
|
+
- Lessons learned: `../_shared/lessons-learned.md`
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API Endpoint Template for Backend Agent
|
|
3
|
+
|
|
4
|
+
This template demonstrates best practices for FastAPI endpoints.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
|
8
|
+
from sqlalchemy.orm import Session
|
|
9
|
+
from typing import Annotated, List
|
|
10
|
+
from uuid import UUID
|
|
11
|
+
|
|
12
|
+
from app.database import get_db
|
|
13
|
+
from app.auth import get_current_user
|
|
14
|
+
from app.models import User, Resource
|
|
15
|
+
from app.schemas import ResourceCreate, ResourceUpdate, ResourceResponse
|
|
16
|
+
from app.services import ResourceService
|
|
17
|
+
|
|
18
|
+
# Type aliases for cleaner code
|
|
19
|
+
DatabaseDep = Annotated[Session, Depends(get_db)]
|
|
20
|
+
UserDep = Annotated[User, Depends(get_current_user)]
|
|
21
|
+
|
|
22
|
+
# Router setup
|
|
23
|
+
router = APIRouter(
|
|
24
|
+
prefix="/api/resources",
|
|
25
|
+
tags=["resources"],
|
|
26
|
+
responses={404: {"description": "Not found"}},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# List endpoint with pagination and filtering
|
|
31
|
+
@router.get(
|
|
32
|
+
"/",
|
|
33
|
+
response_model=List[ResourceResponse],
|
|
34
|
+
summary="List resources",
|
|
35
|
+
description="Retrieve a paginated list of resources with optional filtering"
|
|
36
|
+
)
|
|
37
|
+
async def list_resources(
|
|
38
|
+
db: DatabaseDep,
|
|
39
|
+
current_user: UserDep,
|
|
40
|
+
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
|
41
|
+
limit: int = Query(100, ge=1, le=1000, description="Max records to return"),
|
|
42
|
+
search: str | None = Query(None, description="Search query"),
|
|
43
|
+
status: str | None = Query(None, description="Filter by status"),
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
List resources with pagination.
|
|
47
|
+
|
|
48
|
+
- **skip**: Offset for pagination
|
|
49
|
+
- **limit**: Maximum number of records
|
|
50
|
+
- **search**: Optional search term
|
|
51
|
+
- **status**: Optional status filter
|
|
52
|
+
"""
|
|
53
|
+
service = ResourceService(db)
|
|
54
|
+
resources = service.list_resources(
|
|
55
|
+
user_id=current_user.id,
|
|
56
|
+
skip=skip,
|
|
57
|
+
limit=limit,
|
|
58
|
+
search=search,
|
|
59
|
+
status=status,
|
|
60
|
+
)
|
|
61
|
+
return resources
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Get single resource
|
|
65
|
+
@router.get(
|
|
66
|
+
"/{resource_id}",
|
|
67
|
+
response_model=ResourceResponse,
|
|
68
|
+
summary="Get resource",
|
|
69
|
+
responses={
|
|
70
|
+
200: {"description": "Resource found"},
|
|
71
|
+
404: {"description": "Resource not found"},
|
|
72
|
+
403: {"description": "Access denied"}
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
async def get_resource(
|
|
76
|
+
resource_id: UUID,
|
|
77
|
+
db: DatabaseDep,
|
|
78
|
+
current_user: UserDep,
|
|
79
|
+
):
|
|
80
|
+
"""
|
|
81
|
+
Get a specific resource by ID.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
404: Resource not found
|
|
85
|
+
403: User doesn't own this resource
|
|
86
|
+
"""
|
|
87
|
+
service = ResourceService(db)
|
|
88
|
+
resource = service.get_resource(resource_id)
|
|
89
|
+
|
|
90
|
+
if not resource:
|
|
91
|
+
raise HTTPException(
|
|
92
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
93
|
+
detail=f"Resource {resource_id} not found"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Authorization check
|
|
97
|
+
if resource.user_id != current_user.id:
|
|
98
|
+
raise HTTPException(
|
|
99
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
100
|
+
detail="Access denied"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return resource
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# Create resource
|
|
107
|
+
@router.post(
|
|
108
|
+
"/",
|
|
109
|
+
response_model=ResourceResponse,
|
|
110
|
+
status_code=status.HTTP_201_CREATED,
|
|
111
|
+
summary="Create resource",
|
|
112
|
+
)
|
|
113
|
+
async def create_resource(
|
|
114
|
+
resource_data: ResourceCreate,
|
|
115
|
+
db: DatabaseDep,
|
|
116
|
+
current_user: UserDep,
|
|
117
|
+
):
|
|
118
|
+
"""
|
|
119
|
+
Create a new resource.
|
|
120
|
+
|
|
121
|
+
- **name**: Resource name (required)
|
|
122
|
+
- **description**: Optional description
|
|
123
|
+
- **status**: Initial status (default: active)
|
|
124
|
+
"""
|
|
125
|
+
service = ResourceService(db)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
resource = service.create_resource(
|
|
129
|
+
user_id=current_user.id,
|
|
130
|
+
data=resource_data
|
|
131
|
+
)
|
|
132
|
+
return resource
|
|
133
|
+
except ValueError as e:
|
|
134
|
+
raise HTTPException(
|
|
135
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
136
|
+
detail=str(e)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Update resource
|
|
141
|
+
@router.patch(
|
|
142
|
+
"/{resource_id}",
|
|
143
|
+
response_model=ResourceResponse,
|
|
144
|
+
summary="Update resource",
|
|
145
|
+
)
|
|
146
|
+
async def update_resource(
|
|
147
|
+
resource_id: UUID,
|
|
148
|
+
resource_data: ResourceUpdate,
|
|
149
|
+
db: DatabaseDep,
|
|
150
|
+
current_user: UserDep,
|
|
151
|
+
):
|
|
152
|
+
"""
|
|
153
|
+
Update an existing resource (partial update).
|
|
154
|
+
|
|
155
|
+
Only provided fields will be updated.
|
|
156
|
+
"""
|
|
157
|
+
service = ResourceService(db)
|
|
158
|
+
resource = service.get_resource(resource_id)
|
|
159
|
+
|
|
160
|
+
if not resource:
|
|
161
|
+
raise HTTPException(
|
|
162
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
163
|
+
detail=f"Resource {resource_id} not found"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
if resource.user_id != current_user.id:
|
|
167
|
+
raise HTTPException(
|
|
168
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
169
|
+
detail="Access denied"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
updated_resource = service.update_resource(resource, resource_data)
|
|
174
|
+
return updated_resource
|
|
175
|
+
except ValueError as e:
|
|
176
|
+
raise HTTPException(
|
|
177
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
178
|
+
detail=str(e)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# Delete resource
|
|
183
|
+
@router.delete(
|
|
184
|
+
"/{resource_id}",
|
|
185
|
+
status_code=status.HTTP_204_NO_CONTENT,
|
|
186
|
+
summary="Delete resource",
|
|
187
|
+
)
|
|
188
|
+
async def delete_resource(
|
|
189
|
+
resource_id: UUID,
|
|
190
|
+
db: DatabaseDep,
|
|
191
|
+
current_user: UserDep,
|
|
192
|
+
hard: bool = Query(False, description="Perform hard delete instead of soft delete"),
|
|
193
|
+
):
|
|
194
|
+
"""
|
|
195
|
+
Delete a resource.
|
|
196
|
+
|
|
197
|
+
- **hard=false**: Soft delete (default) - sets deleted_at timestamp
|
|
198
|
+
- **hard=true**: Hard delete - permanently removes from database
|
|
199
|
+
"""
|
|
200
|
+
service = ResourceService(db)
|
|
201
|
+
resource = service.get_resource(resource_id)
|
|
202
|
+
|
|
203
|
+
if not resource:
|
|
204
|
+
raise HTTPException(
|
|
205
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
206
|
+
detail=f"Resource {resource_id} not found"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if resource.user_id != current_user.id:
|
|
210
|
+
raise HTTPException(
|
|
211
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
212
|
+
detail="Access denied"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
service.delete_resource(resource, hard=hard)
|
|
216
|
+
# 204 No Content - no response body
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# Bulk operations example
|
|
220
|
+
@router.post(
|
|
221
|
+
"/bulk",
|
|
222
|
+
response_model=List[ResourceResponse],
|
|
223
|
+
status_code=status.HTTP_201_CREATED,
|
|
224
|
+
summary="Bulk create resources",
|
|
225
|
+
)
|
|
226
|
+
async def bulk_create_resources(
|
|
227
|
+
resources_data: List[ResourceCreate],
|
|
228
|
+
db: DatabaseDep,
|
|
229
|
+
current_user: UserDep,
|
|
230
|
+
):
|
|
231
|
+
"""
|
|
232
|
+
Create multiple resources in one request.
|
|
233
|
+
|
|
234
|
+
Useful for batch imports.
|
|
235
|
+
"""
|
|
236
|
+
if len(resources_data) > 100:
|
|
237
|
+
raise HTTPException(
|
|
238
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
239
|
+
detail="Maximum 100 resources per bulk operation"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
service = ResourceService(db)
|
|
243
|
+
created_resources = []
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
for data in resources_data:
|
|
247
|
+
resource = service.create_resource(
|
|
248
|
+
user_id=current_user.id,
|
|
249
|
+
data=data
|
|
250
|
+
)
|
|
251
|
+
created_resources.append(resource)
|
|
252
|
+
|
|
253
|
+
return created_resources
|
|
254
|
+
except ValueError as e:
|
|
255
|
+
db.rollback() # Rollback on error
|
|
256
|
+
raise HTTPException(
|
|
257
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
258
|
+
detail=str(e)
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# Service class template (separate file: app/services/resource_service.py)
|
|
263
|
+
"""
|
|
264
|
+
from sqlalchemy.orm import Session
|
|
265
|
+
from app.models import Resource
|
|
266
|
+
from app.schemas import ResourceCreate, ResourceUpdate
|
|
267
|
+
from datetime import datetime
|
|
268
|
+
from uuid import UUID
|
|
269
|
+
|
|
270
|
+
class ResourceService:
|
|
271
|
+
def __init__(self, db: Session):
|
|
272
|
+
self.db = db
|
|
273
|
+
|
|
274
|
+
def list_resources(
|
|
275
|
+
self,
|
|
276
|
+
user_id: UUID,
|
|
277
|
+
skip: int = 0,
|
|
278
|
+
limit: int = 100,
|
|
279
|
+
search: str | None = None,
|
|
280
|
+
status: str | None = None,
|
|
281
|
+
):
|
|
282
|
+
query = self.db.query(Resource).filter(
|
|
283
|
+
Resource.user_id == user_id,
|
|
284
|
+
Resource.deleted_at.is_(None)
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if search:
|
|
288
|
+
query = query.filter(Resource.name.ilike(f"%{search}%"))
|
|
289
|
+
|
|
290
|
+
if status:
|
|
291
|
+
query = query.filter(Resource.status == status)
|
|
292
|
+
|
|
293
|
+
return query.offset(skip).limit(limit).all()
|
|
294
|
+
|
|
295
|
+
def get_resource(self, resource_id: UUID) -> Resource | None:
|
|
296
|
+
return self.db.query(Resource).filter(
|
|
297
|
+
Resource.id == resource_id,
|
|
298
|
+
Resource.deleted_at.is_(None)
|
|
299
|
+
).first()
|
|
300
|
+
|
|
301
|
+
def create_resource(self, user_id: UUID, data: ResourceCreate) -> Resource:
|
|
302
|
+
resource = Resource(
|
|
303
|
+
**data.model_dump(),
|
|
304
|
+
user_id=user_id
|
|
305
|
+
)
|
|
306
|
+
self.db.add(resource)
|
|
307
|
+
self.db.commit()
|
|
308
|
+
self.db.refresh(resource)
|
|
309
|
+
return resource
|
|
310
|
+
|
|
311
|
+
def update_resource(self, resource: Resource, data: ResourceUpdate) -> Resource:
|
|
312
|
+
for field, value in data.model_dump(exclude_unset=True).items():
|
|
313
|
+
setattr(resource, field, value)
|
|
314
|
+
|
|
315
|
+
self.db.commit()
|
|
316
|
+
self.db.refresh(resource)
|
|
317
|
+
return resource
|
|
318
|
+
|
|
319
|
+
def delete_resource(self, resource: Resource, hard: bool = False):
|
|
320
|
+
if hard:
|
|
321
|
+
self.db.delete(resource)
|
|
322
|
+
else:
|
|
323
|
+
resource.deleted_at = datetime.utcnow()
|
|
324
|
+
|
|
325
|
+
self.db.commit()
|
|
326
|
+
"""
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Backend Agent - Self-Verification Checklist
|
|
2
|
+
|
|
3
|
+
Run through every item before submitting your work.
|
|
4
|
+
|
|
5
|
+
## API Design
|
|
6
|
+
- [ ] RESTful conventions followed (proper HTTP methods, status codes)
|
|
7
|
+
- [ ] OpenAPI documentation complete (all endpoints documented)
|
|
8
|
+
- [ ] Request/response schemas defined with Pydantic
|
|
9
|
+
- [ ] Pagination for list endpoints returning > 20 items
|
|
10
|
+
- [ ] Consistent error response format
|
|
11
|
+
|
|
12
|
+
## Database
|
|
13
|
+
- [ ] Migrations created (Alembic) and tested
|
|
14
|
+
- [ ] Indexes on foreign keys and frequently queried columns
|
|
15
|
+
- [ ] No N+1 queries (use joinedload/selectinload)
|
|
16
|
+
- [ ] Transactions used for multi-step operations
|
|
17
|
+
|
|
18
|
+
## Security
|
|
19
|
+
- [ ] JWT authentication on protected endpoints
|
|
20
|
+
- [ ] Password hashing with bcrypt (cost 10-12)
|
|
21
|
+
- [ ] Rate limiting on auth endpoints
|
|
22
|
+
- [ ] Input validation with Pydantic (no raw user input in queries)
|
|
23
|
+
- [ ] SQL injection protected (ORM or parameterized queries)
|
|
24
|
+
- [ ] No secrets in code or logs
|
|
25
|
+
|
|
26
|
+
## Testing
|
|
27
|
+
- [ ] Unit tests for service layer logic
|
|
28
|
+
- [ ] Integration tests for all endpoints (happy + error paths)
|
|
29
|
+
- [ ] Auth scenarios tested (missing token, expired, wrong role)
|
|
30
|
+
- [ ] Test coverage > 80%
|
|
31
|
+
|
|
32
|
+
## Code Quality
|
|
33
|
+
- [ ] Clean architecture layers: router -> service -> repository
|
|
34
|
+
- [ ] No business logic in route handlers
|
|
35
|
+
- [ ] Async/await used consistently
|
|
36
|
+
- [ ] Type hints on all function signatures
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Backend Agent - Error Recovery Playbook
|
|
2
|
+
|
|
3
|
+
When you encounter a failure, find the matching scenario and follow the recovery steps.
|
|
4
|
+
Do NOT stop or ask for help until you have exhausted the playbook.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Import / Module Not Found
|
|
9
|
+
|
|
10
|
+
**Symptoms**: `ModuleNotFoundError`, `ImportError`, `No module named X`
|
|
11
|
+
|
|
12
|
+
1. Check the import path — typo? wrong package name?
|
|
13
|
+
2. Verify the dependency exists in `pyproject.toml` or `requirements.txt`
|
|
14
|
+
3. If missing: note it in your result as "requires `pip install X`" — do NOT install yourself
|
|
15
|
+
4. If it's a local module: check the directory structure with `get_symbols_overview`
|
|
16
|
+
5. If the path changed: use `search_for_pattern("class ClassName")` to find the new location
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Test Failure
|
|
21
|
+
|
|
22
|
+
**Symptoms**: `pytest` returns FAILED, assertion errors
|
|
23
|
+
|
|
24
|
+
1. Read the full error output — which test, which assertion, expected vs actual
|
|
25
|
+
2. `find_symbol("test_function_name")` to read the test code
|
|
26
|
+
3. Determine: is the test wrong or is the implementation wrong?
|
|
27
|
+
- Test expects old behavior → update test
|
|
28
|
+
- Implementation has a bug → fix implementation
|
|
29
|
+
4. Re-run the specific test: `pytest path/to/test.py::test_name -v`
|
|
30
|
+
5. After fix, run full test suite to check for regressions
|
|
31
|
+
6. **3회 실패 시**: 다른 접근 방식 시도. 현재 시도를 progress에 기록하고 대안 구현
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Database Migration Error
|
|
36
|
+
|
|
37
|
+
**Symptoms**: `alembic upgrade head` fails, `IntegrityError`, duplicate column
|
|
38
|
+
|
|
39
|
+
1. Read the error — is it a conflict with existing migration?
|
|
40
|
+
2. Check current DB state: `alembic current`
|
|
41
|
+
3. If migration conflicts: `alembic downgrade -1` then fix migration script
|
|
42
|
+
4. If schema mismatch: compare model with actual DB schema
|
|
43
|
+
5. **절대 하지 말 것**: `alembic stamp head` (데이터 손실 위험)
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Authentication / JWT Error
|
|
48
|
+
|
|
49
|
+
**Symptoms**: 401/403 responses, `InvalidTokenError`, `ExpiredSignatureError`
|
|
50
|
+
|
|
51
|
+
1. Check: is the secret key consistent between encode and decode?
|
|
52
|
+
2. Check: is the algorithm specified (`HS256` vs `RS256`)?
|
|
53
|
+
3. Check: is the token being sent in the correct header format? (`Bearer {token}`)
|
|
54
|
+
4. Check: is token expiry set correctly? (access: 15min, refresh: 7day)
|
|
55
|
+
5. Test with a manually created token to isolate the issue
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## N+1 Query / Slow Response
|
|
60
|
+
|
|
61
|
+
**Symptoms**: API response > 500ms, many similar SQL queries in logs
|
|
62
|
+
|
|
63
|
+
1. Enable SQL logging: `echo=True` on engine
|
|
64
|
+
2. Count queries for a single request
|
|
65
|
+
3. If N+1: add `joinedload()` or `selectinload()` to the query
|
|
66
|
+
4. If slow single query: check indexes with `EXPLAIN ANALYZE`
|
|
67
|
+
5. If still slow: consider caching with Redis
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Rate Limit / Quota Error (Gemini API)
|
|
72
|
+
|
|
73
|
+
**Symptoms**: `429`, `RESOURCE_EXHAUSTED`, `rate limit exceeded`
|
|
74
|
+
|
|
75
|
+
1. **즉시 멈춤** — 추가 API 호출 하지 말 것
|
|
76
|
+
2. 현재까지 작업을 `progress-{agent-id}.md`에 저장
|
|
77
|
+
3. `result-{agent-id}.md`에 Status: `quota_exceeded` 기록
|
|
78
|
+
4. 남은 작업 목록을 명시하여 orchestrator가 나중에 재시도할 수 있게 함
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Serena Memory 접근 불가
|
|
83
|
+
|
|
84
|
+
**Symptoms**: `write_memory` / `read_memory` 실패, timeout
|
|
85
|
+
|
|
86
|
+
1. 1회 재시도 (일시적 오류일 수 있음)
|
|
87
|
+
2. 2회 연속 실패 시: 로컬 파일로 대체
|
|
88
|
+
- progress → `/tmp/progress-{agent-id}.md`로 작성
|
|
89
|
+
- result → `/tmp/result-{agent-id}.md`로 작성
|
|
90
|
+
3. result에 `memory_fallback: true` 플래그 추가
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 일반 원칙
|
|
95
|
+
|
|
96
|
+
- **3회 실패**: 같은 접근 3번 실패하면 반드시 다른 방법 시도
|
|
97
|
+
- **막힘**: 5턴 이상 진전 없으면 현재 상태 저장하고 result에 `Status: blocked` 기록
|
|
98
|
+
- **범위 초과**: 다른 에이전트 영역의 문제 발견 시, result에 기록만 하고 직접 수정하지 말 것
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Backend Agent - Examples
|
|
2
|
+
|
|
3
|
+
## Example 1: CRUD API Endpoint
|
|
4
|
+
|
|
5
|
+
**Input**: "Create a TODO CRUD API with user ownership"
|
|
6
|
+
|
|
7
|
+
**Output**:
|
|
8
|
+
```
|
|
9
|
+
## Task: TODO CRUD API
|
|
10
|
+
|
|
11
|
+
### Endpoints Implemented
|
|
12
|
+
- POST /api/todos (create)
|
|
13
|
+
- GET /api/todos (list, user-scoped)
|
|
14
|
+
- GET /api/todos/{id} (detail)
|
|
15
|
+
- PATCH /api/todos/{id} (update)
|
|
16
|
+
- DELETE /api/todos/{id} (delete)
|
|
17
|
+
|
|
18
|
+
### Database Schema
|
|
19
|
+
- todos table: id (UUID), title (VARCHAR), completed (BOOL), user_id (FK), created_at
|
|
20
|
+
- Index on user_id
|
|
21
|
+
|
|
22
|
+
### Security
|
|
23
|
+
- [x] All endpoints require JWT auth
|
|
24
|
+
- [x] User can only access own todos (user_id filter)
|
|
25
|
+
- [x] Input validated with Pydantic
|
|
26
|
+
|
|
27
|
+
### Files Created
|
|
28
|
+
- app/models/todo.py
|
|
29
|
+
- app/schemas/todo.py
|
|
30
|
+
- app/services/todo_service.py
|
|
31
|
+
- app/api/todos.py
|
|
32
|
+
- tests/test_todos.py
|
|
33
|
+
- alembic/versions/xxx_add_todos.py
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Example 2: Authentication System
|
|
37
|
+
|
|
38
|
+
**Input**: "Add JWT authentication with register and login"
|
|
39
|
+
|
|
40
|
+
**Output**:
|
|
41
|
+
```
|
|
42
|
+
## Task: JWT Authentication
|
|
43
|
+
|
|
44
|
+
### Endpoints Implemented
|
|
45
|
+
- POST /api/auth/register (email + password)
|
|
46
|
+
- POST /api/auth/login (returns access + refresh tokens)
|
|
47
|
+
- POST /api/auth/refresh (refresh token rotation)
|
|
48
|
+
|
|
49
|
+
### Security
|
|
50
|
+
- [x] Password hashed with bcrypt (cost 12)
|
|
51
|
+
- [x] Access token: 15min expiry
|
|
52
|
+
- [x] Refresh token: 7 day expiry, single-use rotation
|
|
53
|
+
- [x] Rate limiting: 5 attempts/min on login
|
|
54
|
+
- [x] Email validation with Pydantic EmailStr
|
|
55
|
+
|
|
56
|
+
### Files Created
|
|
57
|
+
- app/models/user.py
|
|
58
|
+
- app/schemas/auth.py
|
|
59
|
+
- app/services/auth_service.py
|
|
60
|
+
- app/api/auth.py
|
|
61
|
+
- app/core/security.py (JWT helpers, password hashing)
|
|
62
|
+
- tests/test_auth.py
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Example 3: Fixing N+1 Query
|
|
66
|
+
|
|
67
|
+
**Input**: "The /api/posts endpoint is slow, taking 2s for 50 posts"
|
|
68
|
+
|
|
69
|
+
**Output**:
|
|
70
|
+
```
|
|
71
|
+
## Task: Fix N+1 Query in Posts API
|
|
72
|
+
|
|
73
|
+
### Root Cause
|
|
74
|
+
Each post triggers a separate query for its author (N+1 problem).
|
|
75
|
+
|
|
76
|
+
### Fix
|
|
77
|
+
Added joinedload(Post.author) to the query.
|
|
78
|
+
|
|
79
|
+
### Before: 51 queries (1 + 50)
|
|
80
|
+
### After: 1 query with JOIN
|
|
81
|
+
|
|
82
|
+
### Files Modified
|
|
83
|
+
- app/api/posts.py (added eager loading)
|
|
84
|
+
- tests/test_posts.py (added performance assertion)
|
|
85
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Backend Agent - Execution Protocol
|
|
2
|
+
|
|
3
|
+
## Step 0: Prepare
|
|
4
|
+
1. **Assess difficulty** — see `../_shared/difficulty-guide.md`
|
|
5
|
+
- **Simple**: Skip to Step 3 | **Medium**: All 4 steps | **Complex**: All steps + checkpoints
|
|
6
|
+
2. **Check lessons** — read your domain section in `../_shared/lessons-learned.md`
|
|
7
|
+
3. **Clarify requirements** — follow `../_shared/clarification-protocol.md`
|
|
8
|
+
- Check **Uncertainty Triggers**: 비즈니스 로직, 보안/인증, 기존 코드 충돌?
|
|
9
|
+
- Determine level: LOW → proceed | MEDIUM → present options | HIGH → ask immediately
|
|
10
|
+
4. **Budget context** — follow `../_shared/context-budget.md` (read symbols, not whole files)
|
|
11
|
+
|
|
12
|
+
**⚠️ Intelligent Escalation**: When uncertain, escalate early. Don't blindly proceed.
|
|
13
|
+
|
|
14
|
+
Follow these steps in order (adjust depth by difficulty).
|
|
15
|
+
|
|
16
|
+
## Step 1: Analyze
|
|
17
|
+
- Read the task requirements carefully
|
|
18
|
+
- Identify which endpoints, models, and services are needed
|
|
19
|
+
- Check existing code with Serena: `get_symbols_overview("app/api")`, `find_symbol("existing_function")`
|
|
20
|
+
- List assumptions; ask if unclear
|
|
21
|
+
|
|
22
|
+
## Step 2: Plan
|
|
23
|
+
- Decide on file structure: models, schemas, routes, services
|
|
24
|
+
- Define API contracts (method, path, request/response types)
|
|
25
|
+
- Plan database schema changes (tables, columns, indexes, migrations)
|
|
26
|
+
- Identify security requirements (auth, validation, rate limiting)
|
|
27
|
+
|
|
28
|
+
## Step 3: Implement
|
|
29
|
+
- Create/modify files in this order:
|
|
30
|
+
1. Database models + migrations
|
|
31
|
+
2. Pydantic schemas (request/response)
|
|
32
|
+
3. Service layer (business logic)
|
|
33
|
+
4. API routes (thin, delegate to services)
|
|
34
|
+
5. Tests (unit + integration)
|
|
35
|
+
- Use `resources/api-template.py` as reference
|
|
36
|
+
- Follow clean architecture: router -> service -> repository -> models
|
|
37
|
+
|
|
38
|
+
## Step 4: Verify
|
|
39
|
+
- Run `resources/checklist.md` items
|
|
40
|
+
- Run `../_shared/common-checklist.md` items
|
|
41
|
+
- Ensure all tests pass
|
|
42
|
+
- Confirm OpenAPI docs are complete
|
|
43
|
+
|
|
44
|
+
## On Error
|
|
45
|
+
See `resources/error-playbook.md` for recovery steps.
|