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