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,456 @@
1
+ ---
2
+ id: python-fastapi-stack
3
+ version: 1.0.0
4
+ name: Python FastAPI Stack
5
+ description: >
6
+ FastAPI conventions for REST APIs: routers, Pydantic v2 schemas, dependency
7
+ injection, error handling, pagination, and project structure.
8
+ Trigger: When writing any router, endpoint, schema, or dependency in a FastAPI project.
9
+
10
+ compatibility:
11
+ tools: [claude, codex, opencode]
12
+ stacks:
13
+ languages: [python]
14
+ frameworks: [fastapi, pydantic, uvicorn, httpx]
15
+
16
+ risk:
17
+ level: medium
18
+ can_execute_shell: true
19
+ can_modify_files: true
20
+ requires_network: false
21
+
22
+ inputs:
23
+ - router files (routers/*.py)
24
+ - schema files (schemas/*.py)
25
+ - dependency files (dependencies/*.py)
26
+ - main application file (main.py)
27
+ - existing endpoint or service code
28
+
29
+ outputs:
30
+ - APIRouter implementations with prefix and tags
31
+ - Pydantic v2 request/response schemas
32
+ - Dependency injection functions (Depends)
33
+ - HTTP exception handlers
34
+ - paginated list endpoints
35
+ - lifespan-based startup/shutdown
36
+ - settings via pydantic-settings
37
+
38
+ quality:
39
+ reviewed_by: codeconductor-core
40
+ version: 0.1.0
41
+ ---
42
+ ## When to Use
43
+
44
+ - Writing any new endpoint, router, or schema
45
+ - Designing the structure of a new FastAPI feature
46
+ - Adding error handling or validation
47
+ - Implementing pagination or filtering
48
+ - Configuring dependencies (DB session, auth, settings)
49
+
50
+ ## Project Structure
51
+
52
+ ```
53
+ src/
54
+ ├── main.py # App factory, lifespan, router inclusion
55
+ ├── config.py # Settings via pydantic-settings
56
+ ├── dependencies.py # Shared Depends() functions (db, auth)
57
+ ├── routers/
58
+ │ ├── __init__.py
59
+ │ ├── products.py
60
+ │ └── orders.py
61
+ ├── schemas/
62
+ │ ├── __init__.py
63
+ │ ├── product.py # ProductCreate, ProductRead, ProductUpdate
64
+ │ └── order.py
65
+ ├── models/ # SQLAlchemy models (see sqlalchemy skill)
66
+ │ ├── __init__.py
67
+ │ └── product.py
68
+ ├── services/ # Business logic — never in routers
69
+ │ └── product.py
70
+ └── tests/
71
+ ├── conftest.py
72
+ └── test_products.py
73
+ ```
74
+
75
+ **Rule**: Routers delegate to services. Services contain all business logic.
76
+ Never write business logic directly in an endpoint function.
77
+
78
+ ## Application Factory
79
+
80
+ ```python
81
+ # main.py
82
+ from contextlib import asynccontextmanager
83
+ from fastapi import FastAPI
84
+ from src.routers import products, orders
85
+ from src.config import settings
86
+
87
+
88
+ @asynccontextmanager
89
+ async def lifespan(app: FastAPI):
90
+ # startup
91
+ yield
92
+ # shutdown
93
+
94
+
95
+ def create_app() -> FastAPI:
96
+ app = FastAPI(
97
+ title=settings.app_name,
98
+ version=settings.app_version,
99
+ lifespan=lifespan,
100
+ )
101
+ app.include_router(products.router)
102
+ app.include_router(orders.router)
103
+ return app
104
+
105
+
106
+ app = create_app()
107
+ ```
108
+
109
+ Use `lifespan` — never `@app.on_event("startup")` (deprecated).
110
+
111
+ ## Settings
112
+
113
+ ```python
114
+ # config.py
115
+ from pydantic_settings import BaseSettings, SettingsConfigDict
116
+
117
+
118
+ class Settings(BaseSettings):
119
+ model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
120
+
121
+ app_name: str = "My API"
122
+ app_version: str = "0.1.0"
123
+ database_url: str
124
+ secret_key: str
125
+ debug: bool = False
126
+
127
+
128
+ settings = Settings()
129
+ ```
130
+
131
+ Never hardcode secrets. Never import `os.environ` directly in app code — always
132
+ go through `settings`.
133
+
134
+ ## Routers
135
+
136
+ ```python
137
+ # routers/products.py
138
+ from fastapi import APIRouter, Depends, HTTPException, status
139
+ from sqlalchemy.ext.asyncio import AsyncSession
140
+ from src.dependencies import get_db
141
+ from src.schemas.product import ProductCreate, ProductRead, ProductListResponse
142
+ from src.services.product import ProductService
143
+
144
+ router = APIRouter(prefix="/products", tags=["products"])
145
+
146
+
147
+ @router.get("/", response_model=ProductListResponse)
148
+ async def list_products(
149
+ offset: int = 0,
150
+ limit: int = 20,
151
+ db: AsyncSession = Depends(get_db),
152
+ ):
153
+ return await ProductService.list(db, offset=offset, limit=limit)
154
+
155
+
156
+ @router.get("/{product_id}", response_model=ProductRead)
157
+ async def get_product(product_id: int, db: AsyncSession = Depends(get_db)):
158
+ product = await ProductService.get_or_404(db, product_id)
159
+ return product
160
+
161
+
162
+ @router.post("/", response_model=ProductRead, status_code=status.HTTP_201_CREATED)
163
+ async def create_product(payload: ProductCreate, db: AsyncSession = Depends(get_db)):
164
+ return await ProductService.create(db, payload)
165
+
166
+
167
+ @router.patch("/{product_id}", response_model=ProductRead)
168
+ async def update_product(
169
+ product_id: int,
170
+ payload: ProductUpdate,
171
+ db: AsyncSession = Depends(get_db),
172
+ ):
173
+ return await ProductService.update(db, product_id, payload)
174
+
175
+
176
+ @router.delete("/{product_id}", status_code=status.HTTP_204_NO_CONTENT)
177
+ async def delete_product(product_id: int, db: AsyncSession = Depends(get_db)):
178
+ await ProductService.delete(db, product_id)
179
+ ```
180
+
181
+ **HTTP method rules**:
182
+
183
+ - `GET` — read, never mutates
184
+ - `POST` — create → `201`
185
+ - `PATCH` — partial update → `200`
186
+ - `PUT` — full replace → `200`
187
+ - `DELETE` — remove → `204` (no body)
188
+
189
+ ## Schemas (Pydantic v2)
190
+
191
+ ```python
192
+ # schemas/product.py
193
+ from datetime import datetime
194
+ from pydantic import BaseModel, Field, model_validator
195
+
196
+
197
+ class ProductBase(BaseModel):
198
+ name: str = Field(..., min_length=1, max_length=255)
199
+ price: float = Field(..., gt=0)
200
+ stock: int = Field(default=0, ge=0)
201
+ is_active: bool = True
202
+
203
+
204
+ class ProductCreate(ProductBase):
205
+ pass
206
+
207
+
208
+ class ProductUpdate(BaseModel):
209
+ name: str | None = Field(default=None, min_length=1, max_length=255)
210
+ price: float | None = Field(default=None, gt=0)
211
+ stock: int | None = Field(default=None, ge=0)
212
+ is_active: bool | None = None
213
+
214
+ @model_validator(mode="after")
215
+ def at_least_one_field(self) -> "ProductUpdate":
216
+ if all(v is None for v in self.model_dump().values()):
217
+ raise ValueError("at least one field must be provided")
218
+ return self
219
+
220
+
221
+ class ProductRead(ProductBase):
222
+ model_config = {"from_attributes": True}
223
+
224
+ id: int
225
+ created_at: datetime
226
+ updated_at: datetime
227
+
228
+
229
+ class ProductListResponse(BaseModel):
230
+ results: list[ProductRead]
231
+ total: int
232
+ has_more: bool
233
+ ```
234
+
235
+ **Schema split rule**:
236
+
237
+ - `Base` — shared fields
238
+ - `Create` — input for POST (no id, no timestamps)
239
+ - `Update` — all fields optional (PATCH semantics)
240
+ - `Read` — output with `from_attributes = True` (ORM → schema)
241
+
242
+ Never return ORM model objects directly from endpoints — always go through a
243
+ `Read` schema.
244
+
245
+ ## Dependency Injection
246
+
247
+ ```python
248
+ # dependencies.py
249
+ from collections.abc import AsyncGenerator
250
+ from sqlalchemy.ext.asyncio import AsyncSession
251
+ from src.db import async_session_factory
252
+
253
+
254
+ async def get_db() -> AsyncGenerator[AsyncSession, None]:
255
+ async with async_session_factory() as session:
256
+ try:
257
+ yield session
258
+ await session.commit()
259
+ except Exception:
260
+ await session.rollback()
261
+ raise
262
+ ```
263
+
264
+ **Rule**: commit in the dependency, not in the service. Services are
265
+ commit-agnostic. The dependency owns the transaction boundary.
266
+
267
+ ## Error Handling
268
+
269
+ ```python
270
+ # Raise HTTPException in services for expected errors
271
+ from fastapi import HTTPException, status
272
+
273
+
274
+ class ProductService:
275
+ @staticmethod
276
+ async def get_or_404(db: AsyncSession, product_id: int) -> Product:
277
+ product = await db.get(Product, product_id)
278
+ if product is None:
279
+ raise HTTPException(
280
+ status_code=status.HTTP_404_NOT_FOUND,
281
+ detail=f"Product {product_id} not found",
282
+ )
283
+ return product
284
+ ```
285
+
286
+ ```python
287
+ # main.py — global exception handlers for unexpected errors
288
+ from fastapi import Request
289
+ from fastapi.responses import JSONResponse
290
+
291
+
292
+ @app.exception_handler(Exception)
293
+ async def unhandled_exception_handler(request: Request, exc: Exception) -> JSONResponse:
294
+ return JSONResponse(
295
+ status_code=500,
296
+ content={"detail": "Internal server error"},
297
+ )
298
+ ```
299
+
300
+ **Status code table**:
301
+
302
+ | Code | When |
303
+ | ---- | ------------------------------------------ |
304
+ | 200 | Successful GET, PATCH, PUT |
305
+ | 201 | Successful POST (resource created) |
306
+ | 204 | Successful DELETE (no body) |
307
+ | 400 | Validation error, bad input |
308
+ | 401 | Not authenticated |
309
+ | 403 | Authenticated but not authorized |
310
+ | 404 | Resource not found |
311
+ | 409 | Conflict (duplicate, constraint violation) |
312
+ | 422 | Pydantic validation error (automatic) |
313
+ | 500 | Unhandled server error |
314
+
315
+ Never return `200` for a creation — use `201`. Never return a body for `204`.
316
+
317
+ ## Pagination
318
+
319
+ Offset/limit — no page numbers. Default 20, max 100:
320
+
321
+ ```python
322
+ # schemas
323
+ class ProductListResponse(BaseModel):
324
+ results: list[ProductRead]
325
+ total: int
326
+ has_more: bool
327
+
328
+
329
+ # service
330
+ class ProductService:
331
+ @staticmethod
332
+ async def list(
333
+ db: AsyncSession,
334
+ offset: int = 0,
335
+ limit: int = 20,
336
+ ) -> ProductListResponse:
337
+ limit = min(limit, 100)
338
+
339
+ count_result = await db.execute(select(func.count()).select_from(Product))
340
+ total = count_result.scalar_one()
341
+
342
+ result = await db.execute(
343
+ select(Product).offset(offset).limit(limit).order_by(Product.id)
344
+ )
345
+ items = result.scalars().all()
346
+
347
+ return ProductListResponse(
348
+ results=items,
349
+ total=total,
350
+ has_more=(offset + limit) < total,
351
+ )
352
+ ```
353
+
354
+ ## Async vs Sync Endpoints
355
+
356
+ | Use async | Use sync |
357
+ | --------------------------------------- | ------------------------- |
358
+ | DB queries (AsyncSession) | CPU-bound transformations |
359
+ | External HTTP calls (httpx.AsyncClient) | Pure in-memory logic |
360
+ | Any `await` inside the handler | No I/O at all |
361
+
362
+ ```python
363
+ # async — has I/O
364
+ @router.get("/{id}")
365
+ async def get_product(id: int, db: AsyncSession = Depends(get_db)):
366
+ return await ProductService.get_or_404(db, id)
367
+
368
+ # sync — pure computation, no I/O
369
+ @router.get("/health")
370
+ def health_check():
371
+ return {"status": "ok"}
372
+ ```
373
+
374
+ Never mix `async def` with synchronous blocking calls (e.g., `requests.get`).
375
+ Use `httpx.AsyncClient` for external HTTP.
376
+
377
+ ## Naming Conventions
378
+
379
+ | Concern | Convention | Example |
380
+ | -------------------- | -------------------------- | ------------------------------- |
381
+ | Router files | `snake_case.py` | `product_variants.py` |
382
+ | Schema classes | `PascalCase` + suffix | `ProductCreate`, `ProductRead` |
383
+ | Endpoint functions | `verb_resource` | `list_products`, `create_order` |
384
+ | Service methods | `@staticmethod`, verb_noun | `get_or_404`, `create`, `list` |
385
+ | Dependency functions | `get_*` | `get_db`, `get_current_user` |
386
+ | Router prefix | lowercase, plural, kebab | `/product-variants` |
387
+ | Tags | lowercase, plural | `["product-variants"]` |
388
+
389
+ ## Service Layer
390
+
391
+ Business logic lives in `services/`. Services are classes with `@staticmethod`
392
+ only:
393
+
394
+ ```python
395
+ # services/product.py
396
+ from sqlalchemy import select, func
397
+ from sqlalchemy.ext.asyncio import AsyncSession
398
+ from fastapi import HTTPException, status
399
+ from src.models.product import Product
400
+ from src.schemas.product import ProductCreate, ProductUpdate, ProductListResponse
401
+
402
+
403
+ class ProductService:
404
+ @staticmethod
405
+ async def get_or_404(db: AsyncSession, product_id: int) -> Product:
406
+ product = await db.get(Product, product_id)
407
+ if product is None:
408
+ raise HTTPException(status_code=404, detail=f"Product {product_id} not found")
409
+ return product
410
+
411
+ @staticmethod
412
+ async def create(db: AsyncSession, payload: ProductCreate) -> Product:
413
+ product = Product(**payload.model_dump())
414
+ db.add(product)
415
+ await db.flush() # get the ID without committing
416
+ await db.refresh(product)
417
+ return product
418
+
419
+ @staticmethod
420
+ async def update(db: AsyncSession, product_id: int, payload: ProductUpdate) -> Product:
421
+ product = await ProductService.get_or_404(db, product_id)
422
+ for field, value in payload.model_dump(exclude_unset=True).items():
423
+ setattr(product, field, value)
424
+ await db.flush()
425
+ await db.refresh(product)
426
+ return product
427
+
428
+ @staticmethod
429
+ async def delete(db: AsyncSession, product_id: int) -> None:
430
+ product = await ProductService.get_or_404(db, product_id)
431
+ await db.delete(product)
432
+ ```
433
+
434
+ **Rules**:
435
+
436
+ - `db.flush()` to get the ID without committing — commit is in `get_db`
437
+ - `db.refresh(product)` to reload relationships after flush
438
+ - `payload.model_dump(exclude_unset=True)` for PATCH — only update provided
439
+ fields
440
+
441
+ ## Commands
442
+
443
+ ```bash
444
+ uvicorn src.main:app --reload # dev server
445
+ uvicorn src.main:app --host 0.0.0.0 # production-like
446
+ pytest tests/ -v # test suite
447
+ ruff check . # lint
448
+ ruff format . # format
449
+ ```
450
+
451
+ ## Resources
452
+
453
+ - **FastAPI docs**: <https://fastapi.tiangolo.com/>
454
+ - **Pydantic v2 docs**: <https://docs.pydantic.dev/latest/>
455
+ - **pydantic-settings**:
456
+ <https://docs.pydantic.dev/latest/concepts/pydantic_settings/>