cc-codeconductor 0.2.1

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 (93) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +319 -0
  3. package/dist/index.js +12741 -0
  4. package/package.json +51 -0
  5. package/policy.yml +124 -0
  6. package/presets/claude/CLAUDE.md +598 -0
  7. package/presets/claude/commands/cc/feature.md +115 -0
  8. package/presets/claude/commands/cc/fix.md +121 -0
  9. package/presets/claude/commands/cc/refactor.md +148 -0
  10. package/presets/claude/commands/cc/review.md +126 -0
  11. package/presets/claude/commands/cc/tdd-cycle.md +226 -0
  12. package/presets/claude/commands/cc/test-plan.md +138 -0
  13. package/presets/claude/settings.json +37 -0
  14. package/presets/claude/skills/api-versioning/SKILL.md +389 -0
  15. package/presets/claude/skills/django-orm/SKILL.md +455 -0
  16. package/presets/claude/skills/django-testing/SKILL.md +409 -0
  17. package/presets/claude/skills/jpa-postgres/SKILL.md +618 -0
  18. package/presets/claude/skills/python/SKILL.md +606 -0
  19. package/presets/claude/skills/python-django-stack/SKILL.md +492 -0
  20. package/presets/claude/skills/python-fastapi-stack/SKILL.md +457 -0
  21. package/presets/claude/skills/spring-boot-feature/SKILL.md +558 -0
  22. package/presets/claude/skills/spring-boot-kotlin/SKILL.md +400 -0
  23. package/presets/claude/skills/sqlalchemy/SKILL.md +466 -0
  24. package/presets/claude/skills/testing-strategy/SKILL.md +479 -0
  25. package/presets/codex/AGENTS.md +883 -0
  26. package/presets/codex/README.md +102 -0
  27. package/presets/codex/skills/api-versioning/SKILL.md +389 -0
  28. package/presets/codex/skills/django-orm/SKILL.md +455 -0
  29. package/presets/codex/skills/django-testing/SKILL.md +409 -0
  30. package/presets/codex/skills/jpa-postgres/SKILL.md +618 -0
  31. package/presets/codex/skills/python/SKILL.md +606 -0
  32. package/presets/codex/skills/python-django-stack/SKILL.md +492 -0
  33. package/presets/codex/skills/python-fastapi-stack/SKILL.md +457 -0
  34. package/presets/codex/skills/spring-boot-feature/SKILL.md +558 -0
  35. package/presets/codex/skills/spring-boot-kotlin/SKILL.md +400 -0
  36. package/presets/codex/skills/sqlalchemy/SKILL.md +466 -0
  37. package/presets/codex/skills/testing-strategy/SKILL.md +479 -0
  38. package/presets/opencode/README.md +191 -0
  39. package/presets/opencode/agents/architect.md +133 -0
  40. package/presets/opencode/agents/docs.md +113 -0
  41. package/presets/opencode/agents/implementer.md +116 -0
  42. package/presets/opencode/agents/orchestrator.md +248 -0
  43. package/presets/opencode/agents/repo-explorer.md +119 -0
  44. package/presets/opencode/agents/reviewer.md +130 -0
  45. package/presets/opencode/agents/task-coach.md +103 -0
  46. package/presets/opencode/agents/tester.md +269 -0
  47. package/presets/opencode/commands/cc-feature.md +114 -0
  48. package/presets/opencode/commands/cc-fix.md +123 -0
  49. package/presets/opencode/commands/cc-refactor.md +148 -0
  50. package/presets/opencode/commands/cc-review.md +141 -0
  51. package/presets/opencode/commands/cc-tdd-cycle.md +225 -0
  52. package/presets/opencode/commands/cc-test-plan.md +144 -0
  53. package/presets/opencode/opencode.jsonc +89 -0
  54. package/presets/opencode/prompts/v0.1.0/architect.md +213 -0
  55. package/presets/opencode/prompts/v0.1.0/docs.md +181 -0
  56. package/presets/opencode/prompts/v0.1.0/implementer.md +154 -0
  57. package/presets/opencode/prompts/v0.1.0/orchestrator.md +169 -0
  58. package/presets/opencode/prompts/v0.1.0/repo-explorer.md +102 -0
  59. package/presets/opencode/prompts/v0.1.0/reviewer.md +183 -0
  60. package/presets/opencode/prompts/v0.1.0/task-coach.md +142 -0
  61. package/presets/opencode/prompts/v0.1.0/tester.md +160 -0
  62. package/presets/opencode/prompts/v0.2.0/architect.md +219 -0
  63. package/presets/opencode/prompts/v0.2.0/docs.md +187 -0
  64. package/presets/opencode/prompts/v0.2.0/implementer.md +160 -0
  65. package/presets/opencode/prompts/v0.2.0/orchestrator.md +238 -0
  66. package/presets/opencode/prompts/v0.2.0/repo-explorer.md +108 -0
  67. package/presets/opencode/prompts/v0.2.0/reviewer.md +190 -0
  68. package/presets/opencode/prompts/v0.2.0/task-coach.md +153 -0
  69. package/presets/opencode/prompts/v0.2.0/tester.md +249 -0
  70. package/presets/opencode/skills/api-versioning/SKILL.md +388 -0
  71. package/presets/opencode/skills/astro/SKILL.md +313 -0
  72. package/presets/opencode/skills/code-review/SKILL.md +202 -0
  73. package/presets/opencode/skills/django-orm/SKILL.md +455 -0
  74. package/presets/opencode/skills/django-testing/SKILL.md +408 -0
  75. package/presets/opencode/skills/django-uv/SKILL.md +400 -0
  76. package/presets/opencode/skills/jpa-postgres/SKILL.md +617 -0
  77. package/presets/opencode/skills/nextjs-typescript/SKILL.md +385 -0
  78. package/presets/opencode/skills/python/SKILL.md +605 -0
  79. package/presets/opencode/skills/python-django-stack/SKILL.md +491 -0
  80. package/presets/opencode/skills/python-fastapi-stack/SKILL.md +456 -0
  81. package/presets/opencode/skills/security/SKILL.md +376 -0
  82. package/presets/opencode/skills/spring-boot-feature/SKILL.md +557 -0
  83. package/presets/opencode/skills/spring-boot-kotlin/SKILL.md +399 -0
  84. package/presets/opencode/skills/spring-boot-testing-strategy/SKILL.md +470 -0
  85. package/presets/opencode/skills/sqlalchemy/SKILL.md +465 -0
  86. package/presets/opencode/skills/testing-tdd/SKILL.md +586 -0
  87. package/src/presets/council/council.yml +59 -0
  88. package/src/presets/manifests/claude.yml +23 -0
  89. package/src/presets/manifests/codex.yml +12 -0
  90. package/src/presets/manifests/opencode.yml +18 -0
  91. package/src/presets/models/claude.yml +37 -0
  92. package/src/presets/models/codex.yml +38 -0
  93. package/src/presets/models/opencode.yml +37 -0
@@ -0,0 +1,465 @@
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
+ compatibility:
11
+ tools: [claude, codex, opencode]
12
+ stacks:
13
+ languages: [python]
14
+ frameworks: [sqlalchemy, alembic, asyncpg, fastapi]
15
+
16
+ risk:
17
+ level: medium
18
+ can_execute_shell: true
19
+ can_modify_files: true
20
+ requires_network: false
21
+
22
+ inputs:
23
+ - model files (models/*.py)
24
+ - service files with DB queries (services/*.py)
25
+ - migration files (alembic/versions/*.py)
26
+ - database setup (db.py)
27
+
28
+ outputs:
29
+ - DeclarativeBase model definitions (SQLAlchemy 2.x style)
30
+ - AsyncSession setup and get_db dependency
31
+ - select() query patterns with scalars, scalar_one, scalar_one_or_none
32
+ - bulk insert/update via execute()
33
+ - transaction-safe async service methods
34
+ - Alembic async env.py configuration
35
+ - anti-pattern corrections (lazy loading, N+1, commit in service)
36
+
37
+ quality:
38
+ reviewed_by: codeconductor-core
39
+ version: 0.1.0
40
+ ---
41
+ ## When to Use
42
+
43
+ - Defining new SQLAlchemy models
44
+ - Writing query logic in services
45
+ - Implementing bulk operations
46
+ - Setting up or modifying Alembic migrations
47
+ - Debugging N+1 queries or session issues
48
+
49
+ ## Engine and Session Setup
50
+
51
+ ```python
52
+ # db.py
53
+ from sqlalchemy.ext.asyncio import (
54
+ AsyncSession,
55
+ async_sessionmaker,
56
+ create_async_engine,
57
+ )
58
+ from src.config import settings
59
+
60
+ engine = create_async_engine(
61
+ settings.database_url, # postgresql+asyncpg://user:pass@host/db
62
+ echo=settings.debug,
63
+ pool_size=10,
64
+ max_overflow=20,
65
+ )
66
+
67
+ async_session_factory = async_sessionmaker(
68
+ engine,
69
+ class_=AsyncSession,
70
+ expire_on_commit=False, # required for async — objects usable after commit
71
+ )
72
+ ```
73
+
74
+ `expire_on_commit=False` is mandatory in async SQLAlchemy. Without it, accessing
75
+ attributes after commit triggers lazy load → `MissingGreenlet` error.
76
+
77
+ ## Model Base
78
+
79
+ ```python
80
+ # models/base.py
81
+ from datetime import datetime
82
+ from sqlalchemy import func
83
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
84
+
85
+
86
+ class Base(DeclarativeBase):
87
+ pass
88
+
89
+
90
+ class TimestampMixin:
91
+ created_at: Mapped[datetime] = mapped_column(
92
+ server_default=func.now(), nullable=False
93
+ )
94
+ updated_at: Mapped[datetime] = mapped_column(
95
+ server_default=func.now(), onupdate=func.now(), nullable=False
96
+ )
97
+ ```
98
+
99
+ All models inherit from `Base`. All persistent entities include
100
+ `TimestampMixin`.
101
+
102
+ ## Model Definition (SQLAlchemy 2.x style)
103
+
104
+ ```python
105
+ # models/product.py
106
+ from decimal import Decimal
107
+ from sqlalchemy import String, Numeric, ForeignKey, Index
108
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
109
+ from src.models.base import Base, TimestampMixin
110
+
111
+
112
+ class Product(Base, TimestampMixin):
113
+ __tablename__ = "products"
114
+ __table_args__ = (
115
+ Index("ix_products_is_active_stock", "is_active", "stock"),
116
+ )
117
+
118
+ id: Mapped[int] = mapped_column(primary_key=True)
119
+ name: Mapped[str] = mapped_column(String(255), nullable=False)
120
+ slug: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
121
+ price: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False)
122
+ stock: Mapped[int] = mapped_column(default=0, nullable=False)
123
+ is_active: Mapped[bool] = mapped_column(default=True, nullable=False)
124
+
125
+ category_id: Mapped[int | None] = mapped_column(ForeignKey("categories.id"))
126
+ category: Mapped["Category | None"] = relationship(
127
+ back_populates="products", lazy="raise"
128
+ )
129
+
130
+ def __repr__(self) -> str:
131
+ return f"<Product id={self.id} name={self.name!r}>"
132
+ ```
133
+
134
+ **Rules**:
135
+
136
+ - Use `Mapped[T]` and `mapped_column()` — never the old `Column()` style
137
+ - Always set `lazy="raise"` on relationships — prevents accidental lazy loads
138
+ - Always define `__tablename__` explicitly
139
+ - Add `Index` in `__table_args__` for composite or frequently filtered columns
140
+
141
+ ## Relationships
142
+
143
+ ```python
144
+ # One-to-many: Category → Products
145
+ class Category(Base, TimestampMixin):
146
+ __tablename__ = "categories"
147
+
148
+ id: Mapped[int] = mapped_column(primary_key=True)
149
+ name: Mapped[str] = mapped_column(String(100), nullable=False)
150
+
151
+ products: Mapped[list["Product"]] = relationship(
152
+ back_populates="category", lazy="raise"
153
+ )
154
+
155
+
156
+ # Many-to-many via association table
157
+ from sqlalchemy import Table, Column
158
+
159
+ product_tags = Table(
160
+ "product_tags",
161
+ Base.metadata,
162
+ Column("product_id", ForeignKey("products.id"), primary_key=True),
163
+ Column("tag_id", ForeignKey("tags.id"), primary_key=True),
164
+ )
165
+
166
+
167
+ class Product(Base, TimestampMixin):
168
+ ...
169
+ tags: Mapped[list["Tag"]] = relationship(
170
+ secondary=product_tags, back_populates="products", lazy="raise"
171
+ )
172
+ ```
173
+
174
+ Always use `back_populates` (explicit) — never `backref` (implicit, hard to
175
+ trace). `lazy="raise"` catches N+1 at runtime instead of silently degrading
176
+ performance.
177
+
178
+ ## Queries
179
+
180
+ ```python
181
+ from sqlalchemy import select, func
182
+ from sqlalchemy.ext.asyncio import AsyncSession
183
+ from sqlalchemy.orm import selectinload
184
+
185
+
186
+ # Get by PK — fastest for single object
187
+ product = await db.get(Product, product_id)
188
+
189
+ # Select with filter
190
+ result = await db.execute(select(Product).where(Product.is_active == True))
191
+ products = result.scalars().all()
192
+
193
+ # Single result — raises if 0 or >1
194
+ result = await db.execute(select(Product).where(Product.slug == slug))
195
+ product = result.scalar_one()
196
+
197
+ # Optional single result — returns None if not found
198
+ result = await db.execute(select(Product).where(Product.id == product_id))
199
+ product = result.scalar_one_or_none()
200
+
201
+ # With eager load (avoids lazy load with lazy="raise")
202
+ result = await db.execute(
203
+ select(Product)
204
+ .where(Product.is_active == True)
205
+ .options(selectinload(Product.category))
206
+ .order_by(Product.name)
207
+ )
208
+ products = result.scalars().all()
209
+
210
+ # Count
211
+ result = await db.execute(select(func.count()).select_from(Product))
212
+ total = result.scalar_one()
213
+ ```
214
+
215
+ **Eager loading options**:
216
+
217
+ | Method | Use |
218
+ | -------------- | --------------------------------------------- |
219
+ | `selectinload` | One-to-many, many-to-many — separate IN query |
220
+ | `joinedload` | Many-to-one (FK) — JOIN in same query |
221
+ | `subqueryload` | Large collections — subquery approach |
222
+
223
+ Default to `selectinload` for collections, `joinedload` for FK parents.
224
+
225
+ ## Write Operations
226
+
227
+ ### Create
228
+
229
+ ```python
230
+ # Single
231
+ product = Product(name="Laptop", price=Decimal("999.99"), stock=10)
232
+ db.add(product)
233
+ await db.flush() # assigns id without committing
234
+ await db.refresh(product) # reload after flush if needed
235
+ return product
236
+
237
+ # Bulk insert
238
+ products = [Product(name=n, price=p) for n, p in data]
239
+ db.add_all(products)
240
+ await db.flush()
241
+ ```
242
+
243
+ ### Update
244
+
245
+ ```python
246
+ from sqlalchemy import update
247
+
248
+ # Object-based (for single row with loaded object)
249
+ product.stock -= quantity
250
+ product.updated_at = datetime.utcnow()
251
+ await db.flush()
252
+
253
+ # Bulk update via execute (no objects loaded — efficient)
254
+ await db.execute(
255
+ update(Product)
256
+ .where(Product.category_id == category_id)
257
+ .values(is_active=False)
258
+ )
259
+ ```
260
+
261
+ ### Delete
262
+
263
+ ```python
264
+ from sqlalchemy import delete
265
+
266
+ # Single (object already loaded)
267
+ await db.delete(product)
268
+
269
+ # Bulk delete
270
+ await db.execute(delete(Product).where(Product.stock == 0))
271
+ ```
272
+
273
+ **Rule**: Never call `session.commit()` in services — commit belongs in
274
+ `get_db`. Use `await db.flush()` to flush to DB within a transaction without
275
+ committing.
276
+
277
+ ## Transactions
278
+
279
+ The `get_db` dependency owns the transaction. For nested operations requiring an
280
+ explicit savepoint:
281
+
282
+ ```python
283
+ from sqlalchemy.ext.asyncio import AsyncSession
284
+
285
+
286
+ async def transfer_stock(
287
+ db: AsyncSession, from_id: int, to_id: int, quantity: int
288
+ ) -> None:
289
+ async with db.begin_nested(): # SAVEPOINT
290
+ source = await db.get(Product, from_id)
291
+ target = await db.get(Product, to_id)
292
+
293
+ if source.stock < quantity:
294
+ raise ValueError("insufficient stock")
295
+
296
+ source.stock -= quantity
297
+ target.stock += quantity
298
+ await db.flush()
299
+ ```
300
+
301
+ Use `begin_nested()` for savepoints within an existing transaction. Never call
302
+ `db.begin()` inside a service — the session is already in a transaction.
303
+
304
+ ## Alembic Setup (Async)
305
+
306
+ ```python
307
+ # alembic/env.py
308
+ import asyncio
309
+ from logging.config import fileConfig
310
+ from sqlalchemy import pool
311
+ from sqlalchemy.ext.asyncio import async_engine_from_config
312
+ from alembic import context
313
+ from src.models.base import Base
314
+ from src.config import settings
315
+
316
+ config = context.config
317
+ config.set_main_option("sqlalchemy.url", settings.database_url)
318
+
319
+ if config.config_file_name is not None:
320
+ fileConfig(config.config_file_name)
321
+
322
+ target_metadata = Base.metadata
323
+
324
+
325
+ def run_migrations_offline() -> None:
326
+ url = config.get_main_option("sqlalchemy.url")
327
+ context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
328
+ with context.begin_transaction():
329
+ context.run_migrations()
330
+
331
+
332
+ def do_run_migrations(connection):
333
+ context.configure(connection=connection, target_metadata=target_metadata)
334
+ with context.begin_transaction():
335
+ context.run_migrations()
336
+
337
+
338
+ async def run_async_migrations() -> None:
339
+ connectable = async_engine_from_config(
340
+ config.get_section(config.config_ini_section, {}),
341
+ prefix="sqlalchemy.",
342
+ poolclass=pool.NullPool,
343
+ )
344
+ async with connectable.connect() as connection:
345
+ await connection.run_sync(do_run_migrations)
346
+ await connectable.dispose()
347
+
348
+
349
+ def run_migrations_online() -> None:
350
+ asyncio.run(run_async_migrations())
351
+
352
+
353
+ if context.is_offline_mode():
354
+ run_migrations_offline()
355
+ else:
356
+ run_migrations_online()
357
+ ```
358
+
359
+ ## Alembic Commands
360
+
361
+ ```bash
362
+ # Generate migration from model changes
363
+ alembic revision --autogenerate -m "add product slug"
364
+
365
+ # Apply all pending migrations
366
+ alembic upgrade head
367
+
368
+ # Rollback one migration
369
+ alembic downgrade -1
370
+
371
+ # Show current revision
372
+ alembic current
373
+
374
+ # Show migration history
375
+ alembic history --verbose
376
+ ```
377
+
378
+ **Always review autogenerated migrations** before applying. Alembic misses:
379
+
380
+ - `server_default` changes on existing columns
381
+ - Index changes inside `__table_args__`
382
+ - Column type changes on some dialects
383
+
384
+ ## Anti-Patterns
385
+
386
+ ### Lazy load with lazy="raise" (will crash)
387
+
388
+ ```python
389
+ # WRONG — lazy="raise" means this raises MissingGreenlet or InvalidRequestError
390
+ products = result.scalars().all()
391
+ for p in products:
392
+ print(p.category.name) # ERROR
393
+
394
+ # CORRECT — eager load upfront
395
+ result = await db.execute(
396
+ select(Product).options(selectinload(Product.category))
397
+ )
398
+ products = result.scalars().all()
399
+ for p in products:
400
+ print(p.category.name) # OK
401
+ ```
402
+
403
+ ### Commit in service
404
+
405
+ ```python
406
+ # WRONG — service owns the transaction
407
+ class ProductService:
408
+ @staticmethod
409
+ async def create(db: AsyncSession, payload: ProductCreate) -> Product:
410
+ product = Product(**payload.model_dump())
411
+ db.add(product)
412
+ await db.commit() # WRONG — get_db should commit
413
+ return product
414
+
415
+ # CORRECT — flush only, commit in get_db
416
+ class ProductService:
417
+ @staticmethod
418
+ async def create(db: AsyncSession, payload: ProductCreate) -> Product:
419
+ product = Product(**payload.model_dump())
420
+ db.add(product)
421
+ await db.flush()
422
+ await db.refresh(product)
423
+ return product
424
+ ```
425
+
426
+ ### expire_on_commit=True (default) in async
427
+
428
+ ```python
429
+ # WRONG — accessing attributes after commit triggers lazy load → MissingGreenlet
430
+ async_session_factory = async_sessionmaker(engine, class_=AsyncSession)
431
+ # expire_on_commit defaults to True
432
+
433
+ # After commit:
434
+ await db.commit()
435
+ print(product.name) # MissingGreenlet error
436
+
437
+ # CORRECT
438
+ async_session_factory = async_sessionmaker(
439
+ engine, class_=AsyncSession, expire_on_commit=False
440
+ )
441
+ ```
442
+
443
+ ### N+1 queries
444
+
445
+ ```python
446
+ # WRONG — 1 query for products + N queries for categories
447
+ products = (await db.execute(select(Product))).scalars().all()
448
+ for p in products:
449
+ print(p.category.name) # N queries
450
+
451
+ # CORRECT — 2 queries total
452
+ products = (
453
+ await db.execute(select(Product).options(selectinload(Product.category)))
454
+ ).scalars().all()
455
+ for p in products:
456
+ print(p.category.name) # no extra queries
457
+ ```
458
+
459
+ ## Resources
460
+
461
+ - **SQLAlchemy 2.x docs**: <https://docs.sqlalchemy.org/en/20/>
462
+ - **Async SQLAlchemy guide**:
463
+ <https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html>
464
+ - **Alembic docs**: <https://alembic.sqlalchemy.org/en/latest/>
465
+ - **asyncpg driver**: <https://magicstack.github.io/asyncpg/>