cc-codeconductor 0.4.3 → 0.5.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/README.md +1 -1
- package/dist/index.js +2642 -226
- package/package.json +1 -1
- package/presets/agy/skills/evaluation/SKILL.md +6 -0
- package/presets/agy/skills/openspec/SKILL.md +32 -0
- package/presets/agy/workflows/cc-openspec.md +62 -0
- package/presets/agy/workflows/cc-scorecard.md +17 -0
- package/presets/claude/commands/cc/openspec.md +144 -0
- package/presets/claude/commands/cc/review.md +13 -2
- package/presets/claude/commands/cc/scorecard.md +65 -0
- package/presets/claude/skills/evaluation/SKILL.md +42 -0
- package/presets/claude/skills/openspec/SKILL.md +54 -0
- package/presets/codex/AGENTS.md +57 -0
- package/presets/cursor/.cursorignore +15 -0
- package/presets/cursor/AGENTS.md +504 -0
- package/presets/cursor/agents/architect.md +211 -0
- package/presets/cursor/agents/complexity-auditor.md +76 -0
- package/presets/cursor/agents/contract-builder.md +75 -0
- package/presets/cursor/agents/docs.md +180 -0
- package/presets/cursor/agents/goal-planner.md +71 -0
- package/presets/cursor/agents/implementer.md +161 -0
- package/presets/cursor/agents/orchestrator.md +377 -0
- package/presets/cursor/agents/repo-explorer.md +100 -0
- package/presets/cursor/agents/reviewer.md +237 -0
- package/presets/cursor/agents/security-reviewer.md +113 -0
- package/presets/cursor/agents/task-coach.md +145 -0
- package/presets/cursor/agents/tester.md +241 -0
- package/presets/cursor/commands/cc/api-contract.md +58 -0
- package/presets/cursor/commands/cc/db-migration.md +58 -0
- package/presets/cursor/commands/cc/feature.md +115 -0
- package/presets/cursor/commands/cc/fix.md +121 -0
- package/presets/cursor/commands/cc/openspec.md +144 -0
- package/presets/cursor/commands/cc/pagespeed.md +103 -0
- package/presets/cursor/commands/cc/refactor.md +148 -0
- package/presets/cursor/commands/cc/review.md +137 -0
- package/presets/cursor/commands/cc/scorecard.md +65 -0
- package/presets/cursor/commands/cc/tdd-cycle.md +226 -0
- package/presets/cursor/commands/cc/test-plan.md +138 -0
- package/presets/cursor/rules/behavioral-discipline.mdc +14 -0
- package/presets/cursor/rules/context-budget.mdc +12 -0
- package/presets/cursor/rules/orchestration.mdc +12 -0
- package/presets/cursor/rules/yagni-stdlib.mdc +11 -0
- package/presets/cursor/skills/android/SKILL.md +122 -0
- package/presets/cursor/skills/api-versioning/SKILL.md +394 -0
- package/presets/cursor/skills/astro/SKILL.md +322 -0
- package/presets/cursor/skills/auth-token-inspector/SKILL.md +33 -0
- package/presets/cursor/skills/code-review/SKILL.md +208 -0
- package/presets/cursor/skills/conductor-setup/SKILL.md +127 -0
- package/presets/cursor/skills/django-orm/SKILL.md +463 -0
- package/presets/cursor/skills/django-testing/SKILL.md +417 -0
- package/presets/cursor/skills/django-uv/SKILL.md +409 -0
- package/presets/cursor/skills/drizzle-schema-architect/SKILL.md +54 -0
- package/presets/cursor/skills/evaluation/SKILL.md +8 -0
- package/presets/cursor/skills/fastapi-pydantic-strict/SKILL.md +46 -0
- package/presets/cursor/skills/find-skills/SKILL.md +144 -0
- package/presets/cursor/skills/jpa-nplusone-detector/SKILL.md +49 -0
- package/presets/cursor/skills/jpa-postgres/SKILL.md +626 -0
- package/presets/cursor/skills/laravel-specialist/SKILL.md +267 -0
- package/presets/cursor/skills/laravel-specialist/references/eloquent.md +351 -0
- package/presets/cursor/skills/laravel-specialist/references/livewire.md +512 -0
- package/presets/cursor/skills/laravel-specialist/references/queues.md +423 -0
- package/presets/cursor/skills/laravel-specialist/references/routing.md +362 -0
- package/presets/cursor/skills/laravel-specialist/references/testing.md +522 -0
- package/presets/cursor/skills/livewire-alpine-bridge/SKILL.md +39 -0
- package/presets/cursor/skills/multi-agent-orchestration/README.md +144 -0
- package/presets/cursor/skills/multi-agent-orchestration/SKILL.md +579 -0
- package/presets/cursor/skills/multi-agent-orchestration/examples/framework_implementations.py +362 -0
- package/presets/cursor/skills/multi-agent-orchestration/examples/orchestration_patterns.py +411 -0
- package/presets/cursor/skills/multi-agent-orchestration/scripts/agent_communication.py +334 -0
- package/presets/cursor/skills/multi-agent-orchestration/scripts/benchmarking.py +341 -0
- package/presets/cursor/skills/multi-agent-orchestration/scripts/workflow_management.py +334 -0
- package/presets/cursor/skills/nextjs-typescript/SKILL.md +394 -0
- package/presets/cursor/skills/openspec/SKILL.md +52 -0
- package/presets/cursor/skills/pagespeed-insights/SKILL.md +445 -0
- package/presets/cursor/skills/pagespeed-insights/reference.md +50 -0
- package/presets/cursor/skills/pagespeed-perf/SKILL.md +281 -0
- package/presets/cursor/skills/php-pro/SKILL.md +210 -0
- package/presets/cursor/skills/php-pro/references/async-patterns.md +412 -0
- package/presets/cursor/skills/php-pro/references/laravel-patterns.md +377 -0
- package/presets/cursor/skills/php-pro/references/modern-php-features.md +323 -0
- package/presets/cursor/skills/php-pro/references/symfony-patterns.md +466 -0
- package/presets/cursor/skills/php-pro/references/testing-quality.md +466 -0
- package/presets/cursor/skills/python/SKILL.md +613 -0
- package/presets/cursor/skills/python-django-stack/SKILL.md +500 -0
- package/presets/cursor/skills/python-fastapi-stack/SKILL.md +464 -0
- package/presets/cursor/skills/security/SKILL.md +384 -0
- package/presets/cursor/skills/seo-analytics-injector/SKILL.md +44 -0
- package/presets/cursor/skills/spring-auth-auditor/SKILL.md +33 -0
- package/presets/cursor/skills/spring-boot-feature/SKILL.md +566 -0
- package/presets/cursor/skills/spring-boot-kotlin/SKILL.md +408 -0
- package/presets/cursor/skills/spring-boot-testing-strategy/SKILL.md +479 -0
- package/presets/cursor/skills/sqlalchemy/SKILL.md +473 -0
- package/presets/cursor/skills/tailwind-responsive-auditor/SKILL.md +32 -0
- package/presets/cursor/skills/tdd-mutation-tester/SKILL.md +28 -0
- package/presets/cursor/skills/testing-tdd/SKILL.md +592 -0
- package/presets/cursor/skills/workflow-orchestration-patterns/SKILL.md +98 -0
- package/presets/cursor/skills/workflow-orchestration-patterns/references/details.md +223 -0
- package/presets/opencode/agents/architect.md +1 -2
- package/presets/opencode/agents/complexity-auditor.md +1 -0
- package/presets/opencode/agents/contract-builder.md +93 -0
- package/presets/opencode/agents/docs.md +1 -2
- package/presets/opencode/agents/goal-planner.md +82 -0
- package/presets/opencode/agents/implementer.md +9 -2
- package/presets/opencode/agents/orchestrator.md +50 -12
- package/presets/opencode/agents/repo-explorer.md +0 -1
- package/presets/opencode/agents/reviewer.md +23 -2
- package/presets/opencode/agents/security-reviewer.md +129 -0
- package/presets/opencode/agents/task-coach.md +1 -2
- package/presets/opencode/agents/tester.md +1 -2
- package/presets/opencode/commands/cc-openspec.md +61 -0
- package/presets/opencode/commands/cc-scorecard.md +16 -0
- package/presets/opencode/prompts/v0.5.0/architect.md +222 -0
- package/presets/opencode/prompts/v0.5.0/complexity-auditor.md +91 -0
- package/presets/opencode/prompts/v0.5.0/contract-builder.md +84 -0
- package/presets/opencode/prompts/v0.5.0/docs.md +190 -0
- package/presets/opencode/prompts/v0.5.0/goal-planner.md +80 -0
- package/presets/opencode/prompts/v0.5.0/implementer.md +171 -0
- package/presets/opencode/prompts/v0.5.0/orchestrator.md +388 -0
- package/presets/opencode/prompts/v0.5.0/repo-explorer.md +111 -0
- package/presets/opencode/prompts/v0.5.0/reviewer.md +248 -0
- package/presets/opencode/prompts/v0.5.0/security-reviewer.md +123 -0
- package/presets/opencode/prompts/v0.5.0/task-coach.md +156 -0
- package/presets/opencode/prompts/v0.5.0/tester.md +252 -0
- package/presets/opencode/skills/evaluation/SKILL.md +6 -0
- package/presets/opencode/skills/openspec/SKILL.md +50 -0
- package/presets/templates/BACKLOG.md +33 -0
- package/presets/templates/execution-profile.yml +6 -0
- package/presets/templates/model-comparison.md +11 -0
- package/presets/templates/regression-checklist.yml +10 -0
- package/src/presets/manifests/agy.yml +2 -2
- package/src/presets/manifests/claude.yml +2 -2
- package/src/presets/manifests/codex.yml +2 -2
- package/src/presets/manifests/cursor.yml +19 -3
- package/src/presets/manifests/gemini.yml +2 -2
- package/src/presets/manifests/opencode.yml +2 -2
- package/src/presets/models/agy.yml +21 -0
- package/src/presets/models/claude.yml +18 -0
- package/src/presets/models/codex.yml +18 -0
- package/src/presets/models/cursor.yml +39 -9
- package/src/presets/models/gemini.yml +18 -0
- package/src/presets/models/opencode.yml +18 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sqlalchemy
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
name: SQLAlchemy
|
|
5
|
+
description: >
|
|
6
|
+
SQLAlchemy 2.x patterns for async FastAPI projects: models, sessions, queries,
|
|
7
|
+
bulk operations, transactions, and Alembic migrations.
|
|
8
|
+
Trigger: When writing models, queries, bulk operations, or DB-touching service code.
|
|
9
|
+
|
|
10
|
+
user-invokable: true
|
|
11
|
+
license: MIT
|
|
12
|
+
metadata:
|
|
13
|
+
author: lgzarturo
|
|
14
|
+
category: database
|
|
15
|
+
|
|
16
|
+
compatibility:
|
|
17
|
+
tools: [claude, codex, gemini, agy, opencode]
|
|
18
|
+
stacks:
|
|
19
|
+
languages: [python]
|
|
20
|
+
frameworks: [sqlalchemy, alembic, asyncpg, fastapi]
|
|
21
|
+
|
|
22
|
+
risk:
|
|
23
|
+
level: medium
|
|
24
|
+
can_execute_shell: true
|
|
25
|
+
can_modify_files: true
|
|
26
|
+
requires_network: false
|
|
27
|
+
|
|
28
|
+
inputs:
|
|
29
|
+
- model files (models/*.py)
|
|
30
|
+
- service files with DB queries (services/*.py)
|
|
31
|
+
- migration files (alembic/versions/*.py)
|
|
32
|
+
- database setup (db.py)
|
|
33
|
+
|
|
34
|
+
outputs:
|
|
35
|
+
- DeclarativeBase model definitions (SQLAlchemy 2.x style)
|
|
36
|
+
- AsyncSession setup and get_db dependency
|
|
37
|
+
- select() query patterns with scalars, scalar_one, scalar_one_or_none
|
|
38
|
+
- bulk insert/update via execute()
|
|
39
|
+
- transaction-safe async service methods
|
|
40
|
+
- Alembic async env.py configuration
|
|
41
|
+
- anti-pattern corrections (lazy loading, N+1, commit in service)
|
|
42
|
+
|
|
43
|
+
quality:
|
|
44
|
+
reviewed_by: codeconductor-core
|
|
45
|
+
version: 0.1.0
|
|
46
|
+
paths:
|
|
47
|
+
- "**/*.py"
|
|
48
|
+
---
|
|
49
|
+
## When to Use
|
|
50
|
+
|
|
51
|
+
- Defining new SQLAlchemy models
|
|
52
|
+
- Writing query logic in services
|
|
53
|
+
- Implementing bulk operations
|
|
54
|
+
- Setting up or modifying Alembic migrations
|
|
55
|
+
- Debugging N+1 queries or session issues
|
|
56
|
+
|
|
57
|
+
## Engine and Session Setup
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
# db.py
|
|
61
|
+
from sqlalchemy.ext.asyncio import (
|
|
62
|
+
AsyncSession,
|
|
63
|
+
async_sessionmaker,
|
|
64
|
+
create_async_engine,
|
|
65
|
+
)
|
|
66
|
+
from src.config import settings
|
|
67
|
+
|
|
68
|
+
engine = create_async_engine(
|
|
69
|
+
settings.database_url, # postgresql+asyncpg://user:pass@host/db
|
|
70
|
+
echo=settings.debug,
|
|
71
|
+
pool_size=10,
|
|
72
|
+
max_overflow=20,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
async_session_factory = async_sessionmaker(
|
|
76
|
+
engine,
|
|
77
|
+
class_=AsyncSession,
|
|
78
|
+
expire_on_commit=False, # required for async — objects usable after commit
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`expire_on_commit=False` is mandatory in async SQLAlchemy. Without it, accessing
|
|
83
|
+
attributes after commit triggers lazy load → `MissingGreenlet` error.
|
|
84
|
+
|
|
85
|
+
## Model Base
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
# models/base.py
|
|
89
|
+
from datetime import datetime
|
|
90
|
+
from sqlalchemy import func
|
|
91
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class Base(DeclarativeBase):
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TimestampMixin:
|
|
99
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
100
|
+
server_default=func.now(), nullable=False
|
|
101
|
+
)
|
|
102
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
103
|
+
server_default=func.now(), onupdate=func.now(), nullable=False
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
All models inherit from `Base`. All persistent entities include
|
|
108
|
+
`TimestampMixin`.
|
|
109
|
+
|
|
110
|
+
## Model Definition (SQLAlchemy 2.x style)
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# models/product.py
|
|
114
|
+
from decimal import Decimal
|
|
115
|
+
from sqlalchemy import String, Numeric, ForeignKey, Index
|
|
116
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
117
|
+
from src.models.base import Base, TimestampMixin
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class Product(Base, TimestampMixin):
|
|
121
|
+
__tablename__ = "products"
|
|
122
|
+
__table_args__ = (
|
|
123
|
+
Index("ix_products_is_active_stock", "is_active", "stock"),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
127
|
+
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
128
|
+
slug: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
|
|
129
|
+
price: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
|
|
130
|
+
stock: Mapped[int] = mapped_column(default=0, nullable=False)
|
|
131
|
+
is_active: Mapped[bool] = mapped_column(default=True, nullable=False)
|
|
132
|
+
|
|
133
|
+
category_id: Mapped[int | None] = mapped_column(ForeignKey("categories.id"))
|
|
134
|
+
category: Mapped["Category | None"] = relationship(
|
|
135
|
+
back_populates="products", lazy="raise"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def __repr__(self) -> str:
|
|
139
|
+
return f"<Product id={self.id} name={self.name!r}>"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Rules**:
|
|
143
|
+
|
|
144
|
+
- Use `Mapped[T]` and `mapped_column()` — never the old `Column()` style
|
|
145
|
+
- Always set `lazy="raise"` on relationships — prevents accidental lazy loads
|
|
146
|
+
- Always define `__tablename__` explicitly
|
|
147
|
+
- Add `Index` in `__table_args__` for composite or frequently filtered columns
|
|
148
|
+
|
|
149
|
+
## Relationships
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
# One-to-many: Category → Products
|
|
153
|
+
class Category(Base, TimestampMixin):
|
|
154
|
+
__tablename__ = "categories"
|
|
155
|
+
|
|
156
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
157
|
+
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
158
|
+
|
|
159
|
+
products: Mapped[list["Product"]] = relationship(
|
|
160
|
+
back_populates="category", lazy="raise"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# Many-to-many via association table
|
|
165
|
+
from sqlalchemy import Table, Column
|
|
166
|
+
|
|
167
|
+
product_tags = Table(
|
|
168
|
+
"product_tags",
|
|
169
|
+
Base.metadata,
|
|
170
|
+
Column("product_id", ForeignKey("products.id"), primary_key=True),
|
|
171
|
+
Column("tag_id", ForeignKey("tags.id"), primary_key=True),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class Product(Base, TimestampMixin):
|
|
176
|
+
...
|
|
177
|
+
tags: Mapped[list["Tag"]] = relationship(
|
|
178
|
+
secondary=product_tags, back_populates="products", lazy="raise"
|
|
179
|
+
)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Always use `back_populates` (explicit) — never `backref` (implicit, hard to
|
|
183
|
+
trace). `lazy="raise"` catches N+1 at runtime instead of silently degrading
|
|
184
|
+
performance.
|
|
185
|
+
|
|
186
|
+
## Queries
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from sqlalchemy import select, func
|
|
190
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
191
|
+
from sqlalchemy.orm import selectinload
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# Get by PK — fastest for single object
|
|
195
|
+
product = await db.get(Product, product_id)
|
|
196
|
+
|
|
197
|
+
# Select with filter
|
|
198
|
+
result = await db.execute(select(Product).where(Product.is_active == True))
|
|
199
|
+
products = result.scalars().all()
|
|
200
|
+
|
|
201
|
+
# Single result — raises if 0 or >1
|
|
202
|
+
result = await db.execute(select(Product).where(Product.slug == slug))
|
|
203
|
+
product = result.scalar_one()
|
|
204
|
+
|
|
205
|
+
# Optional single result — returns None if not found
|
|
206
|
+
result = await db.execute(select(Product).where(Product.id == product_id))
|
|
207
|
+
product = result.scalar_one_or_none()
|
|
208
|
+
|
|
209
|
+
# With eager load (avoids lazy load with lazy="raise")
|
|
210
|
+
result = await db.execute(
|
|
211
|
+
select(Product)
|
|
212
|
+
.where(Product.is_active == True)
|
|
213
|
+
.options(selectinload(Product.category))
|
|
214
|
+
.order_by(Product.name)
|
|
215
|
+
)
|
|
216
|
+
products = result.scalars().all()
|
|
217
|
+
|
|
218
|
+
# Count
|
|
219
|
+
result = await db.execute(select(func.count()).select_from(Product))
|
|
220
|
+
total = result.scalar_one()
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Eager loading options**:
|
|
224
|
+
|
|
225
|
+
| Method | Use |
|
|
226
|
+
| -------------- | --------------------------------------------- |
|
|
227
|
+
| `selectinload` | One-to-many, many-to-many — separate IN query |
|
|
228
|
+
| `joinedload` | Many-to-one (FK) — JOIN in same query |
|
|
229
|
+
| `subqueryload` | Large collections — subquery approach |
|
|
230
|
+
|
|
231
|
+
Default to `selectinload` for collections, `joinedload` for FK parents.
|
|
232
|
+
|
|
233
|
+
## Write Operations
|
|
234
|
+
|
|
235
|
+
### Create
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
# Single
|
|
239
|
+
product = Product(name="Laptop", price=Decimal("999.99"), stock=10)
|
|
240
|
+
db.add(product)
|
|
241
|
+
await db.flush() # assigns id without committing
|
|
242
|
+
await db.refresh(product) # reload after flush if needed
|
|
243
|
+
return product
|
|
244
|
+
|
|
245
|
+
# Bulk insert
|
|
246
|
+
products = [Product(name=n, price=p) for n, p in data]
|
|
247
|
+
db.add_all(products)
|
|
248
|
+
await db.flush()
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Update
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from sqlalchemy import update
|
|
255
|
+
|
|
256
|
+
# Object-based (for single row with loaded object)
|
|
257
|
+
product.stock -= quantity
|
|
258
|
+
product.updated_at = datetime.utcnow()
|
|
259
|
+
await db.flush()
|
|
260
|
+
|
|
261
|
+
# Bulk update via execute (no objects loaded — efficient)
|
|
262
|
+
await db.execute(
|
|
263
|
+
update(Product)
|
|
264
|
+
.where(Product.category_id == category_id)
|
|
265
|
+
.values(is_active=False)
|
|
266
|
+
)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Delete
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from sqlalchemy import delete
|
|
273
|
+
|
|
274
|
+
# Single (object already loaded)
|
|
275
|
+
await db.delete(product)
|
|
276
|
+
|
|
277
|
+
# Bulk delete
|
|
278
|
+
await db.execute(delete(Product).where(Product.stock == 0))
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Rule**: Never call `session.commit()` in services — commit belongs in
|
|
282
|
+
`get_db`. Use `await db.flush()` to flush to DB within a transaction without
|
|
283
|
+
committing.
|
|
284
|
+
|
|
285
|
+
## Transactions
|
|
286
|
+
|
|
287
|
+
The `get_db` dependency owns the transaction. For nested operations requiring an
|
|
288
|
+
explicit savepoint:
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
async def transfer_stock(
|
|
295
|
+
db: AsyncSession, from_id: int, to_id: int, quantity: int
|
|
296
|
+
) -> None:
|
|
297
|
+
async with db.begin_nested(): # SAVEPOINT
|
|
298
|
+
source = await db.get(Product, from_id)
|
|
299
|
+
target = await db.get(Product, to_id)
|
|
300
|
+
|
|
301
|
+
if source.stock < quantity:
|
|
302
|
+
raise ValueError("insufficient stock")
|
|
303
|
+
|
|
304
|
+
source.stock -= quantity
|
|
305
|
+
target.stock += quantity
|
|
306
|
+
await db.flush()
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Use `begin_nested()` for savepoints within an existing transaction. Never call
|
|
310
|
+
`db.begin()` inside a service — the session is already in a transaction.
|
|
311
|
+
|
|
312
|
+
## Alembic Setup (Async)
|
|
313
|
+
|
|
314
|
+
```python
|
|
315
|
+
# alembic/env.py
|
|
316
|
+
import asyncio
|
|
317
|
+
from logging.config import fileConfig
|
|
318
|
+
from sqlalchemy import pool
|
|
319
|
+
from sqlalchemy.ext.asyncio import async_engine_from_config
|
|
320
|
+
from alembic import context
|
|
321
|
+
from src.models.base import Base
|
|
322
|
+
from src.config import settings
|
|
323
|
+
|
|
324
|
+
config = context.config
|
|
325
|
+
config.set_main_option("sqlalchemy.url", settings.database_url)
|
|
326
|
+
|
|
327
|
+
if config.config_file_name is not None:
|
|
328
|
+
fileConfig(config.config_file_name)
|
|
329
|
+
|
|
330
|
+
target_metadata = Base.metadata
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def run_migrations_offline() -> None:
|
|
334
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
335
|
+
context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
|
|
336
|
+
with context.begin_transaction():
|
|
337
|
+
context.run_migrations()
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def do_run_migrations(connection):
|
|
341
|
+
context.configure(connection=connection, target_metadata=target_metadata)
|
|
342
|
+
with context.begin_transaction():
|
|
343
|
+
context.run_migrations()
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
async def run_async_migrations() -> None:
|
|
347
|
+
connectable = async_engine_from_config(
|
|
348
|
+
config.get_section(config.config_ini_section, {}),
|
|
349
|
+
prefix="sqlalchemy.",
|
|
350
|
+
poolclass=pool.NullPool,
|
|
351
|
+
)
|
|
352
|
+
async with connectable.connect() as connection:
|
|
353
|
+
await connection.run_sync(do_run_migrations)
|
|
354
|
+
await connectable.dispose()
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def run_migrations_online() -> None:
|
|
358
|
+
asyncio.run(run_async_migrations())
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
if context.is_offline_mode():
|
|
362
|
+
run_migrations_offline()
|
|
363
|
+
else:
|
|
364
|
+
run_migrations_online()
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Alembic Commands
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# Generate migration from model changes
|
|
371
|
+
alembic revision --autogenerate -m "add product slug"
|
|
372
|
+
|
|
373
|
+
# Apply all pending migrations
|
|
374
|
+
alembic upgrade head
|
|
375
|
+
|
|
376
|
+
# Rollback one migration
|
|
377
|
+
alembic downgrade -1
|
|
378
|
+
|
|
379
|
+
# Show current revision
|
|
380
|
+
alembic current
|
|
381
|
+
|
|
382
|
+
# Show migration history
|
|
383
|
+
alembic history --verbose
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**Always review autogenerated migrations** before applying. Alembic misses:
|
|
387
|
+
|
|
388
|
+
- `server_default` changes on existing columns
|
|
389
|
+
- Index changes inside `__table_args__`
|
|
390
|
+
- Column type changes on some dialects
|
|
391
|
+
|
|
392
|
+
## Anti-Patterns
|
|
393
|
+
|
|
394
|
+
### Lazy load with lazy="raise" (will crash)
|
|
395
|
+
|
|
396
|
+
```python
|
|
397
|
+
# WRONG — lazy="raise" means this raises MissingGreenlet or InvalidRequestError
|
|
398
|
+
products = result.scalars().all()
|
|
399
|
+
for p in products:
|
|
400
|
+
print(p.category.name) # ERROR
|
|
401
|
+
|
|
402
|
+
# CORRECT — eager load upfront
|
|
403
|
+
result = await db.execute(
|
|
404
|
+
select(Product).options(selectinload(Product.category))
|
|
405
|
+
)
|
|
406
|
+
products = result.scalars().all()
|
|
407
|
+
for p in products:
|
|
408
|
+
print(p.category.name) # OK
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Commit in service
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
# WRONG — service owns the transaction
|
|
415
|
+
class ProductService:
|
|
416
|
+
@staticmethod
|
|
417
|
+
async def create(db: AsyncSession, payload: ProductCreate) -> Product:
|
|
418
|
+
product = Product(**payload.model_dump())
|
|
419
|
+
db.add(product)
|
|
420
|
+
await db.commit() # WRONG — get_db should commit
|
|
421
|
+
return product
|
|
422
|
+
|
|
423
|
+
# CORRECT — flush only, commit in get_db
|
|
424
|
+
class ProductService:
|
|
425
|
+
@staticmethod
|
|
426
|
+
async def create(db: AsyncSession, payload: ProductCreate) -> Product:
|
|
427
|
+
product = Product(**payload.model_dump())
|
|
428
|
+
db.add(product)
|
|
429
|
+
await db.flush()
|
|
430
|
+
await db.refresh(product)
|
|
431
|
+
return product
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### expire_on_commit=True (default) in async
|
|
435
|
+
|
|
436
|
+
```python
|
|
437
|
+
# WRONG — accessing attributes after commit triggers lazy load → MissingGreenlet
|
|
438
|
+
async_session_factory = async_sessionmaker(engine, class_=AsyncSession)
|
|
439
|
+
# expire_on_commit defaults to True
|
|
440
|
+
|
|
441
|
+
# After commit:
|
|
442
|
+
await db.commit()
|
|
443
|
+
print(product.name) # MissingGreenlet error
|
|
444
|
+
|
|
445
|
+
# CORRECT
|
|
446
|
+
async_session_factory = async_sessionmaker(
|
|
447
|
+
engine, class_=AsyncSession, expire_on_commit=False
|
|
448
|
+
)
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### N+1 queries
|
|
452
|
+
|
|
453
|
+
```python
|
|
454
|
+
# WRONG — 1 query for products + N queries for categories
|
|
455
|
+
products = (await db.execute(select(Product))).scalars().all()
|
|
456
|
+
for p in products:
|
|
457
|
+
print(p.category.name) # N queries
|
|
458
|
+
|
|
459
|
+
# CORRECT — 2 queries total
|
|
460
|
+
products = (
|
|
461
|
+
await db.execute(select(Product).options(selectinload(Product.category)))
|
|
462
|
+
).scalars().all()
|
|
463
|
+
for p in products:
|
|
464
|
+
print(p.category.name) # no extra queries
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Resources
|
|
468
|
+
|
|
469
|
+
- **SQLAlchemy 2.x docs**: <https://docs.sqlalchemy.org/en/20/>
|
|
470
|
+
- **Async SQLAlchemy guide**:
|
|
471
|
+
<https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html>
|
|
472
|
+
- **Alembic docs**: <https://alembic.sqlalchemy.org/en/latest/>
|
|
473
|
+
- **asyncpg driver**: <https://magicstack.github.io/asyncpg/>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: tailwind-responsive-auditor
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
name: Tailwind Responsive Auditor
|
|
5
|
+
description: >
|
|
6
|
+
Audits Tailwind CSS usage ensuring mobile-first responsive utilities and clean classes.
|
|
7
|
+
user-invokable: true
|
|
8
|
+
license: MIT
|
|
9
|
+
metadata:
|
|
10
|
+
author: lgzarturo
|
|
11
|
+
category: frontend
|
|
12
|
+
compatibility:
|
|
13
|
+
tools: [claude, codex, gemini, agy, opencode]
|
|
14
|
+
stacks:
|
|
15
|
+
languages: [typescript, javascript, html]
|
|
16
|
+
frameworks: [astro, nextjs, react, tailwindcss]
|
|
17
|
+
paths:
|
|
18
|
+
- "**/*.{tsx,jsx,vue,css}"
|
|
19
|
+
---
|
|
20
|
+
# Tailwind Responsive Auditor
|
|
21
|
+
|
|
22
|
+
## Core Principles
|
|
23
|
+
|
|
24
|
+
1. **Mobile-First Design**: Always apply styles for mobile first (without breakpoints), then override them for larger screens (`sm:`, `md:`, `lg:`, `xl:`).
|
|
25
|
+
2. **Class Deduplication**: Avoid redundant classes (e.g. `w-full w-auto` or `text-red-500 text-blue-500`).
|
|
26
|
+
3. **No Arbitrary Values**: Avoid using arbitrary values like `w-[327px]` or `bg-[#f0f0f0]` unless absolutely necessary. Rely on the configured Tailwind theme.
|
|
27
|
+
|
|
28
|
+
## Audit Checklist
|
|
29
|
+
|
|
30
|
+
- Do not use desktop-first design (e.g., max-width utilities should be avoided unless specified).
|
|
31
|
+
- Ensure that elements have responsive margins, padding, and layout flex/grid direction where needed.
|
|
32
|
+
- Alert on duplicate or conflicting utility classes in component files.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: tdd-mutation-tester
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
name: TDD Mutation Tester
|
|
5
|
+
description: >
|
|
6
|
+
Performs simple mutation testing to verify test coverage and assertions.
|
|
7
|
+
user-invokable: true
|
|
8
|
+
license: MIT
|
|
9
|
+
metadata:
|
|
10
|
+
author: lgzarturo
|
|
11
|
+
category: testing
|
|
12
|
+
compatibility:
|
|
13
|
+
tools: [claude, codex, gemini, agy, opencode]
|
|
14
|
+
stacks:
|
|
15
|
+
languages: [typescript, javascript, python, kotlin, java, php]
|
|
16
|
+
---
|
|
17
|
+
# TDD Mutation Tester
|
|
18
|
+
|
|
19
|
+
## Core Principles
|
|
20
|
+
|
|
21
|
+
1. **Verify Assertion Quality**: Coverage percentage can be misleading. Tests must fail when business logic changes.
|
|
22
|
+
2. **Mutation Process**:
|
|
23
|
+
- Locate the core logic implemented by the `implementer`.
|
|
24
|
+
- Temporarily mutate a logical operator (e.g. swap `>` for `<`, `==` for `!=`, `+` for `-`, or invert a boolean).
|
|
25
|
+
- Run the test suite.
|
|
26
|
+
- **Expected Result**: At least one test MUST fail.
|
|
27
|
+
- **Clean Up**: Revert the mutated symbol immediately after running the test.
|
|
28
|
+
3. **Scorecard**: If the test suite passes even with mutated business logic, report a "0 in Real Coverage" on the scorecard and return the task for better test assertions.
|