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