claude-code-pilot 2.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.
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/bin/install.js +431 -0
- package/docs/agent-guides/architecture.md +107 -0
- package/ecc/agents/architect.md +211 -0
- package/ecc/agents/code-reviewer.md +237 -0
- package/ecc/agents/doc-updater.md +107 -0
- package/ecc/agents/e2e-runner.md +107 -0
- package/ecc/agents/security-reviewer.md +108 -0
- package/ecc/agents/tdd-guide.md +91 -0
- package/ecc/commands/checkpoint.md +74 -0
- package/ecc/commands/evolve.md +178 -0
- package/ecc/commands/learn.md +70 -0
- package/ecc/commands/model-route.md +26 -0
- package/ecc/commands/quality-gate.md +29 -0
- package/ecc/commands/resume-session.md +155 -0
- package/ecc/commands/save-session.md +275 -0
- package/ecc/commands/sessions.md +305 -0
- package/ecc/commands/verify.md +59 -0
- package/ecc/contexts/dev.md +20 -0
- package/ecc/contexts/research.md +26 -0
- package/ecc/contexts/review.md +22 -0
- package/ecc/examples/CLAUDE.md +100 -0
- package/ecc/examples/django-api-CLAUDE.md +308 -0
- package/ecc/examples/go-microservice-CLAUDE.md +267 -0
- package/ecc/examples/rust-api-CLAUDE.md +285 -0
- package/ecc/examples/saas-nextjs-CLAUDE.md +166 -0
- package/ecc/examples/user-CLAUDE.md +109 -0
- package/ecc/rules/common/agents.md +49 -0
- package/ecc/rules/common/coding-style.md +48 -0
- package/ecc/rules/common/development-workflow.md +37 -0
- package/ecc/rules/common/git-workflow.md +24 -0
- package/ecc/rules/common/hooks.md +30 -0
- package/ecc/rules/common/patterns.md +31 -0
- package/ecc/rules/common/performance.md +55 -0
- package/ecc/rules/common/security.md +29 -0
- package/ecc/rules/common/testing.md +29 -0
- package/ecc/rules/golang/coding-style.md +32 -0
- package/ecc/rules/golang/hooks.md +17 -0
- package/ecc/rules/golang/patterns.md +45 -0
- package/ecc/rules/golang/security.md +34 -0
- package/ecc/rules/golang/testing.md +31 -0
- package/ecc/rules/kotlin/coding-style.md +86 -0
- package/ecc/rules/kotlin/patterns.md +146 -0
- package/ecc/rules/kotlin/security.md +82 -0
- package/ecc/rules/kotlin/testing.md +128 -0
- package/ecc/rules/perl/coding-style.md +46 -0
- package/ecc/rules/perl/hooks.md +22 -0
- package/ecc/rules/perl/patterns.md +76 -0
- package/ecc/rules/perl/security.md +69 -0
- package/ecc/rules/perl/testing.md +54 -0
- package/ecc/rules/php/coding-style.md +35 -0
- package/ecc/rules/php/hooks.md +24 -0
- package/ecc/rules/php/patterns.md +32 -0
- package/ecc/rules/php/security.md +33 -0
- package/ecc/rules/php/testing.md +34 -0
- package/ecc/rules/python/coding-style.md +42 -0
- package/ecc/rules/python/hooks.md +19 -0
- package/ecc/rules/python/patterns.md +39 -0
- package/ecc/rules/python/security.md +30 -0
- package/ecc/rules/python/testing.md +38 -0
- package/ecc/rules/swift/coding-style.md +47 -0
- package/ecc/rules/swift/hooks.md +20 -0
- package/ecc/rules/swift/patterns.md +66 -0
- package/ecc/rules/swift/security.md +33 -0
- package/ecc/rules/swift/testing.md +45 -0
- package/ecc/rules/typescript/coding-style.md +199 -0
- package/ecc/rules/typescript/hooks.md +22 -0
- package/ecc/rules/typescript/patterns.md +52 -0
- package/ecc/rules/typescript/security.md +28 -0
- package/ecc/rules/typescript/testing.md +18 -0
- package/ecc/scripts/hooks/check-hook-enabled.js +12 -0
- package/ecc/scripts/hooks/evaluate-session.js +100 -0
- package/ecc/scripts/hooks/pre-compact.js +48 -0
- package/ecc/scripts/hooks/run-with-flags-shell.sh +32 -0
- package/ecc/scripts/hooks/run-with-flags.js +120 -0
- package/ecc/scripts/hooks/session-end-marker.js +15 -0
- package/ecc/scripts/hooks/session-end.js +258 -0
- package/ecc/scripts/hooks/session-start.js +97 -0
- package/ecc/scripts/hooks/suggest-compact.js +80 -0
- package/ecc/scripts/lib/hook-flags.js +74 -0
- package/ecc/scripts/lib/package-manager.d.ts +119 -0
- package/ecc/scripts/lib/package-manager.js +431 -0
- package/ecc/scripts/lib/project-detect.js +428 -0
- package/ecc/scripts/lib/resolve-formatter.js +185 -0
- package/ecc/scripts/lib/session-aliases.d.ts +136 -0
- package/ecc/scripts/lib/session-aliases.js +481 -0
- package/ecc/scripts/lib/session-manager.d.ts +131 -0
- package/ecc/scripts/lib/session-manager.js +444 -0
- package/ecc/scripts/lib/shell-split.js +86 -0
- package/ecc/scripts/lib/utils.d.ts +183 -0
- package/ecc/scripts/lib/utils.js +543 -0
- package/ecc/skills/continuous-learning-v2/SKILL.md +365 -0
- package/ecc/skills/continuous-learning-v2/agents/observer-loop.sh +144 -0
- package/ecc/skills/continuous-learning-v2/agents/observer.md +198 -0
- package/ecc/skills/continuous-learning-v2/agents/start-observer.sh +194 -0
- package/ecc/skills/continuous-learning-v2/config.json +8 -0
- package/ecc/skills/continuous-learning-v2/hooks/observe.sh +246 -0
- package/ecc/skills/continuous-learning-v2/scripts/detect-project.sh +218 -0
- package/ecc/skills/continuous-learning-v2/scripts/instinct-cli.py +1148 -0
- package/ecc/skills/continuous-learning-v2/scripts/test_parse_instinct.py +984 -0
- package/ecc/skills/strategic-compact/SKILL.md +103 -0
- package/ecc/skills/strategic-compact/suggest-compact.sh +54 -0
- package/ecc/skills/verification-loop-SKILL.md +126 -0
- package/gsd/LICENSE +21 -0
- package/gsd/agents/gsd-codebase-mapper.md +772 -0
- package/gsd/agents/gsd-debugger.md +1257 -0
- package/gsd/agents/gsd-executor.md +489 -0
- package/gsd/agents/gsd-integration-checker.md +445 -0
- package/gsd/agents/gsd-nyquist-auditor.md +178 -0
- package/gsd/agents/gsd-phase-researcher.md +555 -0
- package/gsd/agents/gsd-plan-checker.md +708 -0
- package/gsd/agents/gsd-planner.md +1309 -0
- package/gsd/agents/gsd-project-researcher.md +631 -0
- package/gsd/agents/gsd-research-synthesizer.md +249 -0
- package/gsd/agents/gsd-roadmapper.md +652 -0
- package/gsd/agents/gsd-verifier.md +581 -0
- package/gsd/commands-gsd/add-phase.md +43 -0
- package/gsd/commands-gsd/add-tests.md +41 -0
- package/gsd/commands-gsd/add-todo.md +47 -0
- package/gsd/commands-gsd/audit-milestone.md +36 -0
- package/gsd/commands-gsd/check-todos.md +45 -0
- package/gsd/commands-gsd/cleanup.md +18 -0
- package/gsd/commands-gsd/complete-milestone.md +136 -0
- package/gsd/commands-gsd/debug.md +168 -0
- package/gsd/commands-gsd/discuss-phase.md +90 -0
- package/gsd/commands-gsd/execute-phase.md +41 -0
- package/gsd/commands-gsd/health.md +22 -0
- package/gsd/commands-gsd/help.md +22 -0
- package/gsd/commands-gsd/insert-phase.md +32 -0
- package/gsd/commands-gsd/join-discord.md +18 -0
- package/gsd/commands-gsd/list-phase-assumptions.md +46 -0
- package/gsd/commands-gsd/map-codebase.md +71 -0
- package/gsd/commands-gsd/new-milestone.md +44 -0
- package/gsd/commands-gsd/new-project.md +42 -0
- package/gsd/commands-gsd/pause-work.md +38 -0
- package/gsd/commands-gsd/plan-milestone-gaps.md +34 -0
- package/gsd/commands-gsd/plan-phase.md +45 -0
- package/gsd/commands-gsd/progress.md +24 -0
- package/gsd/commands-gsd/quick.md +45 -0
- package/gsd/commands-gsd/reapply-patches.md +123 -0
- package/gsd/commands-gsd/remove-phase.md +31 -0
- package/gsd/commands-gsd/research-phase.md +190 -0
- package/gsd/commands-gsd/resume-work.md +40 -0
- package/gsd/commands-gsd/set-profile.md +34 -0
- package/gsd/commands-gsd/settings.md +36 -0
- package/gsd/commands-gsd/update.md +37 -0
- package/gsd/commands-gsd/validate-phase.md +35 -0
- package/gsd/commands-gsd/verify-work.md +38 -0
- package/gsd/get-shit-done/bin/gsd-tools.cjs +592 -0
- package/gsd/get-shit-done/bin/lib/commands.cjs +548 -0
- package/gsd/get-shit-done/bin/lib/config.cjs +169 -0
- package/gsd/get-shit-done/bin/lib/core.cjs +492 -0
- package/gsd/get-shit-done/bin/lib/frontmatter.cjs +299 -0
- package/gsd/get-shit-done/bin/lib/init.cjs +710 -0
- package/gsd/get-shit-done/bin/lib/milestone.cjs +241 -0
- package/gsd/get-shit-done/bin/lib/phase.cjs +901 -0
- package/gsd/get-shit-done/bin/lib/roadmap.cjs +298 -0
- package/gsd/get-shit-done/bin/lib/state.cjs +721 -0
- package/gsd/get-shit-done/bin/lib/template.cjs +222 -0
- package/gsd/get-shit-done/bin/lib/verify.cjs +820 -0
- package/gsd/get-shit-done/references/checkpoints.md +776 -0
- package/gsd/get-shit-done/references/continuation-format.md +249 -0
- package/gsd/get-shit-done/references/decimal-phase-calculation.md +65 -0
- package/gsd/get-shit-done/references/git-integration.md +248 -0
- package/gsd/get-shit-done/references/git-planning-commit.md +38 -0
- package/gsd/get-shit-done/references/model-profile-resolution.md +34 -0
- package/gsd/get-shit-done/references/model-profiles.md +93 -0
- package/gsd/get-shit-done/references/phase-argument-parsing.md +61 -0
- package/gsd/get-shit-done/references/planning-config.md +200 -0
- package/gsd/get-shit-done/references/questioning.md +162 -0
- package/gsd/get-shit-done/references/tdd.md +263 -0
- package/gsd/get-shit-done/references/ui-brand.md +160 -0
- package/gsd/get-shit-done/references/verification-patterns.md +612 -0
- package/gsd/get-shit-done/templates/DEBUG.md +164 -0
- package/gsd/get-shit-done/templates/UAT.md +247 -0
- package/gsd/get-shit-done/templates/VALIDATION.md +76 -0
- package/gsd/get-shit-done/templates/codebase/architecture.md +255 -0
- package/gsd/get-shit-done/templates/codebase/concerns.md +310 -0
- package/gsd/get-shit-done/templates/codebase/conventions.md +307 -0
- package/gsd/get-shit-done/templates/codebase/integrations.md +280 -0
- package/gsd/get-shit-done/templates/codebase/stack.md +186 -0
- package/gsd/get-shit-done/templates/codebase/structure.md +285 -0
- package/gsd/get-shit-done/templates/codebase/testing.md +480 -0
- package/gsd/get-shit-done/templates/config.json +37 -0
- package/gsd/get-shit-done/templates/context.md +297 -0
- package/gsd/get-shit-done/templates/continue-here.md +78 -0
- package/gsd/get-shit-done/templates/debug-subagent-prompt.md +91 -0
- package/gsd/get-shit-done/templates/discovery.md +146 -0
- package/gsd/get-shit-done/templates/milestone-archive.md +123 -0
- package/gsd/get-shit-done/templates/milestone.md +115 -0
- package/gsd/get-shit-done/templates/phase-prompt.md +569 -0
- package/gsd/get-shit-done/templates/planner-subagent-prompt.md +117 -0
- package/gsd/get-shit-done/templates/project.md +184 -0
- package/gsd/get-shit-done/templates/requirements.md +231 -0
- package/gsd/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
- package/gsd/get-shit-done/templates/research-project/FEATURES.md +147 -0
- package/gsd/get-shit-done/templates/research-project/PITFALLS.md +200 -0
- package/gsd/get-shit-done/templates/research-project/STACK.md +120 -0
- package/gsd/get-shit-done/templates/research-project/SUMMARY.md +170 -0
- package/gsd/get-shit-done/templates/research.md +552 -0
- package/gsd/get-shit-done/templates/retrospective.md +54 -0
- package/gsd/get-shit-done/templates/roadmap.md +202 -0
- package/gsd/get-shit-done/templates/state.md +176 -0
- package/gsd/get-shit-done/templates/summary-complex.md +59 -0
- package/gsd/get-shit-done/templates/summary-minimal.md +41 -0
- package/gsd/get-shit-done/templates/summary-standard.md +48 -0
- package/gsd/get-shit-done/templates/summary.md +248 -0
- package/gsd/get-shit-done/templates/user-setup.md +311 -0
- package/gsd/get-shit-done/templates/verification-report.md +322 -0
- package/gsd/get-shit-done/workflows/add-phase.md +112 -0
- package/gsd/get-shit-done/workflows/add-tests.md +351 -0
- package/gsd/get-shit-done/workflows/add-todo.md +158 -0
- package/gsd/get-shit-done/workflows/audit-milestone.md +332 -0
- package/gsd/get-shit-done/workflows/check-todos.md +177 -0
- package/gsd/get-shit-done/workflows/cleanup.md +152 -0
- package/gsd/get-shit-done/workflows/complete-milestone.md +764 -0
- package/gsd/get-shit-done/workflows/diagnose-issues.md +219 -0
- package/gsd/get-shit-done/workflows/discovery-phase.md +289 -0
- package/gsd/get-shit-done/workflows/discuss-phase.md +676 -0
- package/gsd/get-shit-done/workflows/execute-phase.md +459 -0
- package/gsd/get-shit-done/workflows/execute-plan.md +449 -0
- package/gsd/get-shit-done/workflows/health.md +159 -0
- package/gsd/get-shit-done/workflows/help.md +489 -0
- package/gsd/get-shit-done/workflows/insert-phase.md +130 -0
- package/gsd/get-shit-done/workflows/list-phase-assumptions.md +178 -0
- package/gsd/get-shit-done/workflows/map-codebase.md +316 -0
- package/gsd/get-shit-done/workflows/new-milestone.md +384 -0
- package/gsd/get-shit-done/workflows/new-project.md +1111 -0
- package/gsd/get-shit-done/workflows/pause-work.md +122 -0
- package/gsd/get-shit-done/workflows/plan-milestone-gaps.md +274 -0
- package/gsd/get-shit-done/workflows/plan-phase.md +560 -0
- package/gsd/get-shit-done/workflows/progress.md +382 -0
- package/gsd/get-shit-done/workflows/quick.md +601 -0
- package/gsd/get-shit-done/workflows/remove-phase.md +155 -0
- package/gsd/get-shit-done/workflows/research-phase.md +74 -0
- package/gsd/get-shit-done/workflows/resume-project.md +307 -0
- package/gsd/get-shit-done/workflows/set-profile.md +81 -0
- package/gsd/get-shit-done/workflows/settings.md +214 -0
- package/gsd/get-shit-done/workflows/transition.md +544 -0
- package/gsd/get-shit-done/workflows/update.md +240 -0
- package/gsd/get-shit-done/workflows/validate-phase.md +167 -0
- package/gsd/get-shit-done/workflows/verify-phase.md +243 -0
- package/gsd/get-shit-done/workflows/verify-work.md +583 -0
- package/gsd/hooks/gsd-check-update.js +81 -0
- package/gsd/hooks/gsd-context-monitor.js +141 -0
- package/gsd/hooks/gsd-statusline.js +115 -0
- package/kit/CLAUDE.md +43 -0
- package/kit/commands/kit/update.md +46 -0
- package/kit/commands/setup-refresh.md +50 -0
- package/kit/commands/setup.md +579 -0
- package/kit/commands/tool-guide.md +44 -0
- package/kit/hooks/kit-check-update.js +54 -0
- package/kit/mcp.json +10 -0
- package/kit/rules/code-style.md +24 -0
- package/manifest.json +30 -0
- package/package.json +36 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# Django REST API — Project CLAUDE.md
|
|
2
|
+
|
|
3
|
+
> Real-world example for a Django REST Framework API with PostgreSQL and Celery.
|
|
4
|
+
> Copy this to your project root and customize for your service.
|
|
5
|
+
|
|
6
|
+
## Project Overview
|
|
7
|
+
|
|
8
|
+
**Stack:** Python 3.12+, Django 5.x, Django REST Framework, PostgreSQL, Celery + Redis, pytest, Docker Compose
|
|
9
|
+
|
|
10
|
+
**Architecture:** Domain-driven design with apps per business domain. DRF for API layer, Celery for async tasks, pytest for testing. All endpoints return JSON — no template rendering.
|
|
11
|
+
|
|
12
|
+
## Critical Rules
|
|
13
|
+
|
|
14
|
+
### Python Conventions
|
|
15
|
+
|
|
16
|
+
- Type hints on all function signatures — use `from __future__ import annotations`
|
|
17
|
+
- No `print()` statements — use `logging.getLogger(__name__)`
|
|
18
|
+
- f-strings for string formatting, never `%` or `.format()`
|
|
19
|
+
- Use `pathlib.Path` not `os.path` for file operations
|
|
20
|
+
- Imports sorted with isort: stdlib, third-party, local (enforced by ruff)
|
|
21
|
+
|
|
22
|
+
### Database
|
|
23
|
+
|
|
24
|
+
- All queries use Django ORM — raw SQL only with `.raw()` and parameterized queries
|
|
25
|
+
- Migrations committed to git — never use `--fake` in production
|
|
26
|
+
- Use `select_related()` and `prefetch_related()` to prevent N+1 queries
|
|
27
|
+
- All models must have `created_at` and `updated_at` auto-fields
|
|
28
|
+
- Indexes on any field used in `filter()`, `order_by()`, or `WHERE` clauses
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
# BAD: N+1 query
|
|
32
|
+
orders = Order.objects.all()
|
|
33
|
+
for order in orders:
|
|
34
|
+
print(order.customer.name) # hits DB for each order
|
|
35
|
+
|
|
36
|
+
# GOOD: Single query with join
|
|
37
|
+
orders = Order.objects.select_related("customer").all()
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Authentication
|
|
41
|
+
|
|
42
|
+
- JWT via `djangorestframework-simplejwt` — access token (15 min) + refresh token (7 days)
|
|
43
|
+
- Permission classes on every view — never rely on default
|
|
44
|
+
- Use `IsAuthenticated` as base, add custom permissions for object-level access
|
|
45
|
+
- Token blacklisting enabled for logout
|
|
46
|
+
|
|
47
|
+
### Serializers
|
|
48
|
+
|
|
49
|
+
- Use `ModelSerializer` for simple CRUD, `Serializer` for complex validation
|
|
50
|
+
- Separate read and write serializers when input/output shapes differ
|
|
51
|
+
- Validate at serializer level, not in views — views should be thin
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
class CreateOrderSerializer(serializers.Serializer):
|
|
55
|
+
product_id = serializers.UUIDField()
|
|
56
|
+
quantity = serializers.IntegerField(min_value=1, max_value=100)
|
|
57
|
+
|
|
58
|
+
def validate_product_id(self, value):
|
|
59
|
+
if not Product.objects.filter(id=value, active=True).exists():
|
|
60
|
+
raise serializers.ValidationError("Product not found or inactive")
|
|
61
|
+
return value
|
|
62
|
+
|
|
63
|
+
class OrderDetailSerializer(serializers.ModelSerializer):
|
|
64
|
+
customer = CustomerSerializer(read_only=True)
|
|
65
|
+
product = ProductSerializer(read_only=True)
|
|
66
|
+
|
|
67
|
+
class Meta:
|
|
68
|
+
model = Order
|
|
69
|
+
fields = ["id", "customer", "product", "quantity", "total", "status", "created_at"]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Error Handling
|
|
73
|
+
|
|
74
|
+
- Use DRF exception handler for consistent error responses
|
|
75
|
+
- Custom exceptions for business logic in `core/exceptions.py`
|
|
76
|
+
- Never expose internal error details to clients
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
# core/exceptions.py
|
|
80
|
+
from rest_framework.exceptions import APIException
|
|
81
|
+
|
|
82
|
+
class InsufficientStockError(APIException):
|
|
83
|
+
status_code = 409
|
|
84
|
+
default_detail = "Insufficient stock for this order"
|
|
85
|
+
default_code = "insufficient_stock"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Code Style
|
|
89
|
+
|
|
90
|
+
- No emojis in code or comments
|
|
91
|
+
- Max line length: 120 characters (enforced by ruff)
|
|
92
|
+
- Classes: PascalCase, functions/variables: snake_case, constants: UPPER_SNAKE_CASE
|
|
93
|
+
- Views are thin — business logic lives in service functions or model methods
|
|
94
|
+
|
|
95
|
+
## File Structure
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
config/
|
|
99
|
+
settings/
|
|
100
|
+
base.py # Shared settings
|
|
101
|
+
local.py # Dev overrides (DEBUG=True)
|
|
102
|
+
production.py # Production settings
|
|
103
|
+
urls.py # Root URL config
|
|
104
|
+
celery.py # Celery app configuration
|
|
105
|
+
apps/
|
|
106
|
+
accounts/ # User auth, registration, profile
|
|
107
|
+
models.py
|
|
108
|
+
serializers.py
|
|
109
|
+
views.py
|
|
110
|
+
services.py # Business logic
|
|
111
|
+
tests/
|
|
112
|
+
test_views.py
|
|
113
|
+
test_services.py
|
|
114
|
+
factories.py # Factory Boy factories
|
|
115
|
+
orders/ # Order management
|
|
116
|
+
models.py
|
|
117
|
+
serializers.py
|
|
118
|
+
views.py
|
|
119
|
+
services.py
|
|
120
|
+
tasks.py # Celery tasks
|
|
121
|
+
tests/
|
|
122
|
+
products/ # Product catalog
|
|
123
|
+
models.py
|
|
124
|
+
serializers.py
|
|
125
|
+
views.py
|
|
126
|
+
tests/
|
|
127
|
+
core/
|
|
128
|
+
exceptions.py # Custom API exceptions
|
|
129
|
+
permissions.py # Shared permission classes
|
|
130
|
+
pagination.py # Custom pagination
|
|
131
|
+
middleware.py # Request logging, timing
|
|
132
|
+
tests/
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Key Patterns
|
|
136
|
+
|
|
137
|
+
### Service Layer
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
# apps/orders/services.py
|
|
141
|
+
from django.db import transaction
|
|
142
|
+
|
|
143
|
+
def create_order(*, customer, product_id: uuid.UUID, quantity: int) -> Order:
|
|
144
|
+
"""Create an order with stock validation and payment hold."""
|
|
145
|
+
product = Product.objects.select_for_update().get(id=product_id)
|
|
146
|
+
|
|
147
|
+
if product.stock < quantity:
|
|
148
|
+
raise InsufficientStockError()
|
|
149
|
+
|
|
150
|
+
with transaction.atomic():
|
|
151
|
+
order = Order.objects.create(
|
|
152
|
+
customer=customer,
|
|
153
|
+
product=product,
|
|
154
|
+
quantity=quantity,
|
|
155
|
+
total=product.price * quantity,
|
|
156
|
+
)
|
|
157
|
+
product.stock -= quantity
|
|
158
|
+
product.save(update_fields=["stock", "updated_at"])
|
|
159
|
+
|
|
160
|
+
# Async: send confirmation email
|
|
161
|
+
send_order_confirmation.delay(order.id)
|
|
162
|
+
return order
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### View Pattern
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# apps/orders/views.py
|
|
169
|
+
class OrderViewSet(viewsets.ModelViewSet):
|
|
170
|
+
permission_classes = [IsAuthenticated]
|
|
171
|
+
pagination_class = StandardPagination
|
|
172
|
+
|
|
173
|
+
def get_serializer_class(self):
|
|
174
|
+
if self.action == "create":
|
|
175
|
+
return CreateOrderSerializer
|
|
176
|
+
return OrderDetailSerializer
|
|
177
|
+
|
|
178
|
+
def get_queryset(self):
|
|
179
|
+
return (
|
|
180
|
+
Order.objects
|
|
181
|
+
.filter(customer=self.request.user)
|
|
182
|
+
.select_related("product", "customer")
|
|
183
|
+
.order_by("-created_at")
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def perform_create(self, serializer):
|
|
187
|
+
order = create_order(
|
|
188
|
+
customer=self.request.user,
|
|
189
|
+
product_id=serializer.validated_data["product_id"],
|
|
190
|
+
quantity=serializer.validated_data["quantity"],
|
|
191
|
+
)
|
|
192
|
+
serializer.instance = order
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Test Pattern (pytest + Factory Boy)
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
# apps/orders/tests/factories.py
|
|
199
|
+
import factory
|
|
200
|
+
from apps.accounts.tests.factories import UserFactory
|
|
201
|
+
from apps.products.tests.factories import ProductFactory
|
|
202
|
+
|
|
203
|
+
class OrderFactory(factory.django.DjangoModelFactory):
|
|
204
|
+
class Meta:
|
|
205
|
+
model = "orders.Order"
|
|
206
|
+
|
|
207
|
+
customer = factory.SubFactory(UserFactory)
|
|
208
|
+
product = factory.SubFactory(ProductFactory, stock=100)
|
|
209
|
+
quantity = 1
|
|
210
|
+
total = factory.LazyAttribute(lambda o: o.product.price * o.quantity)
|
|
211
|
+
|
|
212
|
+
# apps/orders/tests/test_views.py
|
|
213
|
+
import pytest
|
|
214
|
+
from rest_framework.test import APIClient
|
|
215
|
+
|
|
216
|
+
@pytest.mark.django_db
|
|
217
|
+
class TestCreateOrder:
|
|
218
|
+
def setup_method(self):
|
|
219
|
+
self.client = APIClient()
|
|
220
|
+
self.user = UserFactory()
|
|
221
|
+
self.client.force_authenticate(self.user)
|
|
222
|
+
|
|
223
|
+
def test_create_order_success(self):
|
|
224
|
+
product = ProductFactory(price=29_99, stock=10)
|
|
225
|
+
response = self.client.post("/api/orders/", {
|
|
226
|
+
"product_id": str(product.id),
|
|
227
|
+
"quantity": 2,
|
|
228
|
+
})
|
|
229
|
+
assert response.status_code == 201
|
|
230
|
+
assert response.data["total"] == 59_98
|
|
231
|
+
|
|
232
|
+
def test_create_order_insufficient_stock(self):
|
|
233
|
+
product = ProductFactory(stock=0)
|
|
234
|
+
response = self.client.post("/api/orders/", {
|
|
235
|
+
"product_id": str(product.id),
|
|
236
|
+
"quantity": 1,
|
|
237
|
+
})
|
|
238
|
+
assert response.status_code == 409
|
|
239
|
+
|
|
240
|
+
def test_create_order_unauthenticated(self):
|
|
241
|
+
self.client.force_authenticate(None)
|
|
242
|
+
response = self.client.post("/api/orders/", {})
|
|
243
|
+
assert response.status_code == 401
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Environment Variables
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
# Django
|
|
250
|
+
SECRET_KEY=
|
|
251
|
+
DEBUG=False
|
|
252
|
+
ALLOWED_HOSTS=api.example.com
|
|
253
|
+
|
|
254
|
+
# Database
|
|
255
|
+
DATABASE_URL=postgres://user:pass@localhost:5432/myapp
|
|
256
|
+
|
|
257
|
+
# Redis (Celery broker + cache)
|
|
258
|
+
REDIS_URL=redis://localhost:6379/0
|
|
259
|
+
|
|
260
|
+
# JWT
|
|
261
|
+
JWT_ACCESS_TOKEN_LIFETIME=15 # minutes
|
|
262
|
+
JWT_REFRESH_TOKEN_LIFETIME=10080 # minutes (7 days)
|
|
263
|
+
|
|
264
|
+
# Email
|
|
265
|
+
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
|
|
266
|
+
EMAIL_HOST=smtp.example.com
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Testing Strategy
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
# Run all tests
|
|
273
|
+
pytest --cov=apps --cov-report=term-missing
|
|
274
|
+
|
|
275
|
+
# Run specific app tests
|
|
276
|
+
pytest apps/orders/tests/ -v
|
|
277
|
+
|
|
278
|
+
# Run with parallel execution
|
|
279
|
+
pytest -n auto
|
|
280
|
+
|
|
281
|
+
# Only failing tests from last run
|
|
282
|
+
pytest --lf
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## ECC Workflow
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
# Planning
|
|
289
|
+
/plan "Add order refund system with Stripe integration"
|
|
290
|
+
|
|
291
|
+
# Development with TDD
|
|
292
|
+
/tdd # pytest-based TDD workflow
|
|
293
|
+
|
|
294
|
+
# Review
|
|
295
|
+
/python-review # Python-specific code review
|
|
296
|
+
/security-scan # Django security audit
|
|
297
|
+
/code-review # General quality check
|
|
298
|
+
|
|
299
|
+
# Verification
|
|
300
|
+
/verify # Build, lint, test, security scan
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Git Workflow
|
|
304
|
+
|
|
305
|
+
- `feat:` new features, `fix:` bug fixes, `refactor:` code changes
|
|
306
|
+
- Feature branches from `main`, PRs required
|
|
307
|
+
- CI: ruff (lint + format), mypy (types), pytest (tests), safety (dep check)
|
|
308
|
+
- Deploy: Docker image, managed via Kubernetes or Railway
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Go Microservice — Project CLAUDE.md
|
|
2
|
+
|
|
3
|
+
> Real-world example for a Go microservice with PostgreSQL, gRPC, and Docker.
|
|
4
|
+
> Copy this to your project root and customize for your service.
|
|
5
|
+
|
|
6
|
+
## Project Overview
|
|
7
|
+
|
|
8
|
+
**Stack:** Go 1.22+, PostgreSQL, gRPC + REST (grpc-gateway), Docker, sqlc (type-safe SQL), Wire (dependency injection)
|
|
9
|
+
|
|
10
|
+
**Architecture:** Clean architecture with domain, repository, service, and handler layers. gRPC as primary transport with REST gateway for external clients.
|
|
11
|
+
|
|
12
|
+
## Critical Rules
|
|
13
|
+
|
|
14
|
+
### Go Conventions
|
|
15
|
+
|
|
16
|
+
- Follow Effective Go and the Go Code Review Comments guide
|
|
17
|
+
- Use `errors.New` / `fmt.Errorf` with `%w` for wrapping — never string matching on errors
|
|
18
|
+
- No `init()` functions — explicit initialization in `main()` or constructors
|
|
19
|
+
- No global mutable state — pass dependencies via constructors
|
|
20
|
+
- Context must be the first parameter and propagated through all layers
|
|
21
|
+
|
|
22
|
+
### Database
|
|
23
|
+
|
|
24
|
+
- All queries in `queries/` as plain SQL — sqlc generates type-safe Go code
|
|
25
|
+
- Migrations in `migrations/` using golang-migrate — never alter the database directly
|
|
26
|
+
- Use transactions for multi-step operations via `pgx.Tx`
|
|
27
|
+
- All queries must use parameterized placeholders (`$1`, `$2`) — never string formatting
|
|
28
|
+
|
|
29
|
+
### Error Handling
|
|
30
|
+
|
|
31
|
+
- Return errors, don't panic — panics are only for truly unrecoverable situations
|
|
32
|
+
- Wrap errors with context: `fmt.Errorf("creating user: %w", err)`
|
|
33
|
+
- Define sentinel errors in `domain/errors.go` for business logic
|
|
34
|
+
- Map domain errors to gRPC status codes in the handler layer
|
|
35
|
+
|
|
36
|
+
```go
|
|
37
|
+
// Domain layer — sentinel errors
|
|
38
|
+
var (
|
|
39
|
+
ErrUserNotFound = errors.New("user not found")
|
|
40
|
+
ErrEmailTaken = errors.New("email already registered")
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
// Handler layer — map to gRPC status
|
|
44
|
+
func toGRPCError(err error) error {
|
|
45
|
+
switch {
|
|
46
|
+
case errors.Is(err, domain.ErrUserNotFound):
|
|
47
|
+
return status.Error(codes.NotFound, err.Error())
|
|
48
|
+
case errors.Is(err, domain.ErrEmailTaken):
|
|
49
|
+
return status.Error(codes.AlreadyExists, err.Error())
|
|
50
|
+
default:
|
|
51
|
+
return status.Error(codes.Internal, "internal error")
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Code Style
|
|
57
|
+
|
|
58
|
+
- No emojis in code or comments
|
|
59
|
+
- Exported types and functions must have doc comments
|
|
60
|
+
- Keep functions under 50 lines — extract helpers
|
|
61
|
+
- Use table-driven tests for all logic with multiple cases
|
|
62
|
+
- Prefer `struct{}` for signal channels, not `bool`
|
|
63
|
+
|
|
64
|
+
## File Structure
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
cmd/
|
|
68
|
+
server/
|
|
69
|
+
main.go # Entrypoint, Wire injection, graceful shutdown
|
|
70
|
+
internal/
|
|
71
|
+
domain/ # Business types and interfaces
|
|
72
|
+
user.go # User entity and repository interface
|
|
73
|
+
errors.go # Sentinel errors
|
|
74
|
+
service/ # Business logic
|
|
75
|
+
user_service.go
|
|
76
|
+
user_service_test.go
|
|
77
|
+
repository/ # Data access (sqlc-generated + custom)
|
|
78
|
+
postgres/
|
|
79
|
+
user_repo.go
|
|
80
|
+
user_repo_test.go # Integration tests with testcontainers
|
|
81
|
+
handler/ # gRPC + REST handlers
|
|
82
|
+
grpc/
|
|
83
|
+
user_handler.go
|
|
84
|
+
rest/
|
|
85
|
+
user_handler.go
|
|
86
|
+
config/ # Configuration loading
|
|
87
|
+
config.go
|
|
88
|
+
proto/ # Protobuf definitions
|
|
89
|
+
user/v1/
|
|
90
|
+
user.proto
|
|
91
|
+
queries/ # SQL queries for sqlc
|
|
92
|
+
user.sql
|
|
93
|
+
migrations/ # Database migrations
|
|
94
|
+
001_create_users.up.sql
|
|
95
|
+
001_create_users.down.sql
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Key Patterns
|
|
99
|
+
|
|
100
|
+
### Repository Interface
|
|
101
|
+
|
|
102
|
+
```go
|
|
103
|
+
type UserRepository interface {
|
|
104
|
+
Create(ctx context.Context, user *User) error
|
|
105
|
+
FindByID(ctx context.Context, id uuid.UUID) (*User, error)
|
|
106
|
+
FindByEmail(ctx context.Context, email string) (*User, error)
|
|
107
|
+
Update(ctx context.Context, user *User) error
|
|
108
|
+
Delete(ctx context.Context, id uuid.UUID) error
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Service with Dependency Injection
|
|
113
|
+
|
|
114
|
+
```go
|
|
115
|
+
type UserService struct {
|
|
116
|
+
repo domain.UserRepository
|
|
117
|
+
hasher PasswordHasher
|
|
118
|
+
logger *slog.Logger
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
func NewUserService(repo domain.UserRepository, hasher PasswordHasher, logger *slog.Logger) *UserService {
|
|
122
|
+
return &UserService{repo: repo, hasher: hasher, logger: logger}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
func (s *UserService) Create(ctx context.Context, req CreateUserRequest) (*domain.User, error) {
|
|
126
|
+
existing, err := s.repo.FindByEmail(ctx, req.Email)
|
|
127
|
+
if err != nil && !errors.Is(err, domain.ErrUserNotFound) {
|
|
128
|
+
return nil, fmt.Errorf("checking email: %w", err)
|
|
129
|
+
}
|
|
130
|
+
if existing != nil {
|
|
131
|
+
return nil, domain.ErrEmailTaken
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
hashed, err := s.hasher.Hash(req.Password)
|
|
135
|
+
if err != nil {
|
|
136
|
+
return nil, fmt.Errorf("hashing password: %w", err)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
user := &domain.User{
|
|
140
|
+
ID: uuid.New(),
|
|
141
|
+
Name: req.Name,
|
|
142
|
+
Email: req.Email,
|
|
143
|
+
Password: hashed,
|
|
144
|
+
}
|
|
145
|
+
if err := s.repo.Create(ctx, user); err != nil {
|
|
146
|
+
return nil, fmt.Errorf("creating user: %w", err)
|
|
147
|
+
}
|
|
148
|
+
return user, nil
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Table-Driven Tests
|
|
153
|
+
|
|
154
|
+
```go
|
|
155
|
+
func TestUserService_Create(t *testing.T) {
|
|
156
|
+
tests := []struct {
|
|
157
|
+
name string
|
|
158
|
+
req CreateUserRequest
|
|
159
|
+
setup func(*MockUserRepo)
|
|
160
|
+
wantErr error
|
|
161
|
+
}{
|
|
162
|
+
{
|
|
163
|
+
name: "valid user",
|
|
164
|
+
req: CreateUserRequest{Name: "Alice", Email: "alice@example.com", Password: "secure123"},
|
|
165
|
+
setup: func(m *MockUserRepo) {
|
|
166
|
+
m.On("FindByEmail", mock.Anything, "alice@example.com").Return(nil, domain.ErrUserNotFound)
|
|
167
|
+
m.On("Create", mock.Anything, mock.Anything).Return(nil)
|
|
168
|
+
},
|
|
169
|
+
wantErr: nil,
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: "duplicate email",
|
|
173
|
+
req: CreateUserRequest{Name: "Alice", Email: "taken@example.com", Password: "secure123"},
|
|
174
|
+
setup: func(m *MockUserRepo) {
|
|
175
|
+
m.On("FindByEmail", mock.Anything, "taken@example.com").Return(&domain.User{}, nil)
|
|
176
|
+
},
|
|
177
|
+
wantErr: domain.ErrEmailTaken,
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for _, tt := range tests {
|
|
182
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
183
|
+
repo := new(MockUserRepo)
|
|
184
|
+
tt.setup(repo)
|
|
185
|
+
svc := NewUserService(repo, &bcryptHasher{}, slog.Default())
|
|
186
|
+
|
|
187
|
+
_, err := svc.Create(context.Background(), tt.req)
|
|
188
|
+
|
|
189
|
+
if tt.wantErr != nil {
|
|
190
|
+
assert.ErrorIs(t, err, tt.wantErr)
|
|
191
|
+
} else {
|
|
192
|
+
assert.NoError(t, err)
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Environment Variables
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Database
|
|
203
|
+
DATABASE_URL=postgres://user:pass@localhost:5432/myservice?sslmode=disable
|
|
204
|
+
|
|
205
|
+
# gRPC
|
|
206
|
+
GRPC_PORT=50051
|
|
207
|
+
REST_PORT=8080
|
|
208
|
+
|
|
209
|
+
# Auth
|
|
210
|
+
JWT_SECRET= # Load from vault in production
|
|
211
|
+
TOKEN_EXPIRY=24h
|
|
212
|
+
|
|
213
|
+
# Observability
|
|
214
|
+
LOG_LEVEL=info # debug, info, warn, error
|
|
215
|
+
OTEL_ENDPOINT= # OpenTelemetry collector
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Testing Strategy
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
/go-test # TDD workflow for Go
|
|
222
|
+
/go-review # Go-specific code review
|
|
223
|
+
/go-build # Fix build errors
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Test Commands
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Unit tests (fast, no external deps)
|
|
230
|
+
go test ./internal/... -short -count=1
|
|
231
|
+
|
|
232
|
+
# Integration tests (requires Docker for testcontainers)
|
|
233
|
+
go test ./internal/repository/... -count=1 -timeout 120s
|
|
234
|
+
|
|
235
|
+
# All tests with coverage
|
|
236
|
+
go test ./... -coverprofile=coverage.out -count=1
|
|
237
|
+
go tool cover -func=coverage.out # summary
|
|
238
|
+
go tool cover -html=coverage.out # browser
|
|
239
|
+
|
|
240
|
+
# Race detector
|
|
241
|
+
go test ./... -race -count=1
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## ECC Workflow
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# Planning
|
|
248
|
+
/plan "Add rate limiting to user endpoints"
|
|
249
|
+
|
|
250
|
+
# Development
|
|
251
|
+
/go-test # TDD with Go-specific patterns
|
|
252
|
+
|
|
253
|
+
# Review
|
|
254
|
+
/go-review # Go idioms, error handling, concurrency
|
|
255
|
+
/security-scan # Secrets and vulnerabilities
|
|
256
|
+
|
|
257
|
+
# Before merge
|
|
258
|
+
go vet ./...
|
|
259
|
+
staticcheck ./...
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Git Workflow
|
|
263
|
+
|
|
264
|
+
- `feat:` new features, `fix:` bug fixes, `refactor:` code changes
|
|
265
|
+
- Feature branches from `main`, PRs required
|
|
266
|
+
- CI: `go vet`, `staticcheck`, `go test -race`, `golangci-lint`
|
|
267
|
+
- Deploy: Docker image built in CI, deployed to Kubernetes
|