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