@eltonssouza/development-utility-kit 0.10.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 (131) hide show
  1. package/.claude/agents/README.md +24 -0
  2. package/.claude/agents/analyst.md +198 -0
  3. package/.claude/agents/backend-developer.md +126 -0
  4. package/.claude/agents/brain-keeper.md +229 -0
  5. package/.claude/agents/code-reviewer.md +181 -0
  6. package/.claude/agents/database-engineer.md +94 -0
  7. package/.claude/agents/devops-engineer.md +141 -0
  8. package/.claude/agents/frontend-developer.md +97 -0
  9. package/.claude/agents/gate-keeper.md +118 -0
  10. package/.claude/agents/migrator.md +291 -0
  11. package/.claude/agents/mobile-developer.md +80 -0
  12. package/.claude/agents/n8n-specialist.md +94 -0
  13. package/.claude/agents/product-owner.md +115 -0
  14. package/.claude/agents/qa-engineer.md +232 -0
  15. package/.claude/agents/release-engineer.md +204 -0
  16. package/.claude/agents/scaffold.md +87 -0
  17. package/.claude/agents/security-engineer.md +199 -0
  18. package/.claude/agents/sprint-runner.md +46 -0
  19. package/.claude/agents/stack-resolver.md +104 -0
  20. package/.claude/agents/tech-lead.md +182 -0
  21. package/.claude/agents/update-template.md +54 -0
  22. package/.claude/agents/ux-designer.md +118 -0
  23. package/.claude/hooks/flow-guard.js +261 -0
  24. package/.claude/hooks/flow-state.js +197 -0
  25. package/.claude/local/CLAUDE.md +71 -0
  26. package/.claude/settings.json +55 -0
  27. package/.claude/skills/README.md +331 -0
  28. package/.claude/skills/active-project/SKILL.md +131 -0
  29. package/.claude/skills/api-integration-test/SKILL.md +84 -0
  30. package/.claude/skills/auto-test-guard/SKILL.md +239 -0
  31. package/.claude/skills/auto-test-guard/resources/backend-tests.md +20 -0
  32. package/.claude/skills/auto-test-guard/resources/e2e-tests.md +24 -0
  33. package/.claude/skills/auto-test-guard/resources/execution-report.md +49 -0
  34. package/.claude/skills/auto-test-guard/resources/frontend-tests.md +18 -0
  35. package/.claude/skills/auto-test-guard/resources/initial-setup.md +108 -0
  36. package/.claude/skills/auto-test-guard/resources/run-suite.md +48 -0
  37. package/.claude/skills/auto-test-guard/resources/senior-gate.md +19 -0
  38. package/.claude/skills/brain-keeper/SKILL.md +62 -0
  39. package/.claude/skills/brain-keeper/obsidian/app.json +9 -0
  40. package/.claude/skills/brain-keeper/obsidian/appearance.json +4 -0
  41. package/.claude/skills/brain-keeper/obsidian/core-plugins.json +20 -0
  42. package/.claude/skills/brain-keeper/obsidian/daily-notes.json +5 -0
  43. package/.claude/skills/brain-keeper/obsidian/graph.json +32 -0
  44. package/.claude/skills/brain-keeper/obsidian/snippets/folder-colors.css +90 -0
  45. package/.claude/skills/brain-keeper/obsidian/templates.json +5 -0
  46. package/.claude/skills/brain-keeper/templates/README.md +51 -0
  47. package/.claude/skills/brain-keeper/templates/adr.md +40 -0
  48. package/.claude/skills/brain-keeper/templates/bug.md +35 -0
  49. package/.claude/skills/brain-keeper/templates/daily.md +38 -0
  50. package/.claude/skills/brain-keeper/templates/feature.md +62 -0
  51. package/.claude/skills/brain-keeper/templates/meeting.md +34 -0
  52. package/.claude/skills/brain-keeper/templates/tech-debt.md +21 -0
  53. package/.claude/skills/caveman/SKILL.md +189 -0
  54. package/.claude/skills/create-stack-pack/SKILL.md +281 -0
  55. package/.claude/skills/grill-me/SKILL.md +80 -0
  56. package/.claude/skills/pair-debug/SKILL.md +288 -0
  57. package/.claude/skills/prd-ready-check/SKILL.md +86 -0
  58. package/.claude/skills/project-manager/SKILL.md +334 -0
  59. package/.claude/skills/quality-standards/SKILL.md +203 -0
  60. package/.claude/skills/quick-feature/SKILL.md +266 -0
  61. package/.claude/skills/run-sprint/SKILL.md +41 -0
  62. package/.claude/skills/scaffold/SKILL.md +60 -0
  63. package/.claude/skills/stack-discovery/SKILL.md +161 -0
  64. package/.claude/skills/test-coverage-auditor/SKILL.md +87 -0
  65. package/.claude/skills/to-issues/SKILL.md +163 -0
  66. package/.claude/skills/to-prd/SKILL.md +130 -0
  67. package/.claude/skills/update-template/SKILL.md +256 -0
  68. package/.claude/stacks/CODEOWNERS +30 -0
  69. package/.claude/stacks/README.md +97 -0
  70. package/.claude/stacks/_template.md +116 -0
  71. package/.claude/stacks/dotnet/aspire-9.md +528 -0
  72. package/.claude/stacks/go/gin-1.10.md +570 -0
  73. package/.claude/stacks/java/spring-boot-3.md +376 -0
  74. package/.claude/stacks/java/spring-boot-4.md +438 -0
  75. package/.claude/stacks/node/express-5.md +538 -0
  76. package/.claude/stacks/python/django-5.md +483 -0
  77. package/.claude/stacks/python/fastapi-0.115.md +522 -0
  78. package/.claude/stacks/typescript/angular-18.md +420 -0
  79. package/.claude/stacks/typescript/angular-19.md +397 -0
  80. package/.claude/stacks/typescript/angular-21.md +494 -0
  81. package/CLAUDE.md +472 -0
  82. package/README.md +412 -0
  83. package/bin/cli.js +848 -0
  84. package/bin/lib/adr.js +146 -0
  85. package/bin/lib/backup.js +62 -0
  86. package/bin/lib/detect-stack.js +476 -0
  87. package/bin/lib/doctor.js +527 -0
  88. package/bin/lib/help.js +328 -0
  89. package/bin/lib/identity.js +108 -0
  90. package/bin/lib/lint-allowlist.json +15 -0
  91. package/bin/lib/lint.js +798 -0
  92. package/bin/lib/local-dir.js +68 -0
  93. package/bin/lib/manifest.js +236 -0
  94. package/bin/lib/sync-all.js +394 -0
  95. package/bin/lib/version-check.js +398 -0
  96. package/dashboard/db.js +321 -0
  97. package/dashboard/package.json +22 -0
  98. package/dashboard/public/app.js +853 -0
  99. package/dashboard/public/content/docs/agents-reference.en.md +911 -0
  100. package/dashboard/public/content/docs/architecture-overview.en.md +252 -0
  101. package/dashboard/public/content/docs/autonomy-matrix.en.md +186 -0
  102. package/dashboard/public/content/docs/cli-reference.en.md +538 -0
  103. package/dashboard/public/content/docs/git-flow.en.md +525 -0
  104. package/dashboard/public/content/docs/honcho-memory.en.md +394 -0
  105. package/dashboard/public/content/docs/hooks-reference.en.md +404 -0
  106. package/dashboard/public/content/docs/pipeline.en.md +414 -0
  107. package/dashboard/public/content/docs/plugins.en.md +289 -0
  108. package/dashboard/public/content/docs/quality-gate.en.md +315 -0
  109. package/dashboard/public/content/docs/skills-reference.en.md +484 -0
  110. package/dashboard/public/content/docs/stack-rules.en.md +362 -0
  111. package/dashboard/public/content/docs/troubleshooting.en.md +565 -0
  112. package/dashboard/public/content/manifest.json +114 -0
  113. package/dashboard/public/content/manual/backend.en.md +1053 -0
  114. package/dashboard/public/content/manual/existing-project.en.md +848 -0
  115. package/dashboard/public/content/manual/frontend.en.md +1008 -0
  116. package/dashboard/public/content/manual/fullstack.en.md +1459 -0
  117. package/dashboard/public/content/manual/mobile.en.md +837 -0
  118. package/dashboard/public/content/manual/quickstart.en.md +169 -0
  119. package/dashboard/public/index.html +217 -0
  120. package/dashboard/public/style.css +857 -0
  121. package/dashboard/public/vendor/marked.min.js +69 -0
  122. package/dashboard/rtk.js +143 -0
  123. package/dashboard/server-app.js +421 -0
  124. package/dashboard/server.js +104 -0
  125. package/dashboard/test/sprint1.test.js +406 -0
  126. package/dashboard/test/sprint2.test.js +571 -0
  127. package/dashboard/test/sprint3.test.js +560 -0
  128. package/package.json +33 -0
  129. package/scripts/hooks/subagent-telemetry.sh +14 -0
  130. package/scripts/hooks/telemetry-writer.js +250 -0
  131. package/scripts/latest-versions.json +56 -0
@@ -0,0 +1,483 @@
1
+ ---
2
+ stack: python/django-5
3
+ versions_covered: "5.0.x — 5.2.x"
4
+ last_validated: 2026-05-28
5
+ validated_against: "reference pack — Python 3.13 + Django 5.2 LTS + DRF 3.15"
6
+ status: active
7
+ pack_owner: "@elton"
8
+ security_review: 2026-05-28
9
+ next_review_due: 2027-05-28
10
+ ---
11
+
12
+ # Python 3.13 + Django 5.x
13
+
14
+ Canonical knowledge pack for greenfield projects on Python 3.12+/3.13 + Django 5.x (5.0, 5.1, 5.2 LTS). Django 5 raised the floor to Python 3.10+ and brought significant async view maturity, generated model fields, declarative DRF serializers, and Python 3.13 free-threaded compatibility on Django 5.2. For projects still on Django 4.2 LTS, use `django-4.md` (not yet published — open issue if you need it).
15
+
16
+ ## 1. When to use this pack
17
+
18
+ - Project declares `Primary stack: Python 3.12+ + Django 5.x` in `## Project Identity`.
19
+ - `pyproject.toml` (or `requirements.txt`) declares `django>=5.0,<6.0` and `python_requires = ">=3.12"`.
20
+ - Greenfield: **prefer this pack** unless your hosting platform pins Python 3.10/3.11.
21
+ - API-heavy projects: pair Django with Django REST Framework (DRF) for the API layer; `## Code patterns` below assumes DRF.
22
+ - For projects that are HTTP-API-only (no admin, no templates), consider FastAPI (`python/fastapi-*.md`) — Django shines when the admin + ORM + auth + forms ecosystem is valuable.
23
+
24
+ ## 2. Stack baseline (what this pack assumes)
25
+
26
+ | Component | Version range | Notes |
27
+ |---|---|---|
28
+ | Python | 3.12 (min) / 3.13 (recommended) | 3.13 brings free-threaded build; Django 5.2 verified on 3.13 |
29
+ | Django | 5.0.x — 5.2.x | 5.2 is LTS (April 2025 → April 2028); 5.0/5.1 are non-LTS |
30
+ | Django REST Framework | 3.15.x | Declarative serializer mode; `@extend_schema` for OpenAPI via drf-spectacular |
31
+ | ORM / migrations | Django ORM (native) + django-migrations | Never raw SQL outside `RunSQL` migrations; never auto-makemigrations in CI |
32
+ | Build / packaging | `uv` (recommended) or `pip` + `pyproject.toml` | `uv` is 10–100× faster; `uv lock` produces deterministic lockfiles |
33
+ | Async runtime | ASGI via `uvicorn` workers + `gunicorn` master | WSGI via `gunicorn` if no async views needed |
34
+ | Tests | pytest 8.x + pytest-django 4.x + factory-boy 3.x + Faker | NEVER SQLite for tests if prod is Postgres — use Postgres in CI via service container |
35
+ | Mutation | mutmut 3.x (or cosmic-ray) | Target ≥70% on `domain/` + `application/` |
36
+ | Coverage | coverage.py 7.x with `branch = True` | Target ≥85% lines, ≥80% branches |
37
+ | Static analysis | `ruff` (replaces flake8/black/isort) + `mypy` + `django-stubs` | `ruff check --fix .` + `ruff format .` + `mypy --strict` on `domain/` and `application/` |
38
+ | Security scan | `pip-audit` + `bandit` | 0 CVE with CVSS ≥7.0; `bandit -ll` for medium+ |
39
+ | Observability | OpenTelemetry SDK + `opentelemetry-instrumentation-django` | W3C Trace Context; auto-instrumented requests, DB, cache |
40
+ | Background jobs | Celery 5.x + Redis 7 | Or Django-Q2 for simpler needs |
41
+ | Static files | `whitenoise` | Behind gunicorn; no separate Nginx for static needed |
42
+ | Env config | `django-environ` or `pydantic-settings` | `.env` file gitignored; `env.example` committed |
43
+
44
+ ## 3. Project structure (DDD-flavored Django)
45
+
46
+ ```
47
+ backend/
48
+ ├── pyproject.toml # uv-managed; PEP 621 metadata
49
+ ├── uv.lock # committed
50
+ ├── manage.py
51
+ ├── env.example # committed; .env gitignored
52
+ ├── config/ # Django project (settings)
53
+ │ ├── __init__.py
54
+ │ ├── asgi.py
55
+ │ ├── wsgi.py
56
+ │ ├── urls.py
57
+ │ └── settings/
58
+ │ ├── __init__.py
59
+ │ ├── base.py
60
+ │ ├── dev.py
61
+ │ ├── prod.py
62
+ │ └── test.py
63
+ ├── domain/ # pure Python; no Django imports
64
+ │ ├── products/
65
+ │ │ ├── entities.py # dataclasses, value objects
66
+ │ │ ├── repository.py # ABC port; impl lives in infrastructure
67
+ │ │ └── services.py # pure domain services
68
+ │ └── shared/
69
+ ├── application/ # use cases (1 public callable each)
70
+ │ ├── products/
71
+ │ │ ├── create_product.py
72
+ │ │ ├── list_products.py
73
+ │ │ └── dto.py # request/response dataclasses
74
+ │ └── shared/
75
+ ├── infrastructure/ # Django ORM adapters, external clients
76
+ │ ├── products/
77
+ │ │ ├── models.py # @models.Model — JPA-equivalent layer
78
+ │ │ ├── repository_impl.py # adapts ORM to the domain port
79
+ │ │ └── migrations/
80
+ │ └── shared/
81
+ ├── api/ # DRF views + serializers + urls
82
+ │ ├── products/
83
+ │ │ ├── serializers.py
84
+ │ │ ├── views.py # APIView / ViewSet
85
+ │ │ ├── urls.py
86
+ │ │ └── permissions.py
87
+ │ └── shared/
88
+ │ └── exception_handler.py # DRF custom exception handler -> RFC 9457
89
+ └── tests/
90
+ ├── unit/ # domain + application
91
+ ├── integration/ # ORM + DRF with TestContainers Postgres
92
+ └── e2e/ # Playwright (optional, if there is a UI)
93
+ ```
94
+
95
+ **Rule**: `domain/` and `application/` contain **zero Django imports**. They are pure Python testable without a database, without `pytest-django`, in milliseconds. `infrastructure/` is the only layer that imports `django.db.models`. `api/` is the only layer that imports `rest_framework`.
96
+
97
+ ## 4. Code patterns
98
+
99
+ ### Domain entity as `@dataclass(frozen=True)` (no Django dependency)
100
+
101
+ ```python
102
+ # domain/products/entities.py
103
+ from dataclasses import dataclass
104
+ from decimal import Decimal
105
+ from uuid import UUID
106
+ from datetime import datetime
107
+
108
+ @dataclass(frozen=True)
109
+ class Product:
110
+ id: UUID
111
+ name: str
112
+ price: Decimal
113
+ stock: int
114
+ created_at: datetime
115
+
116
+ def reserve(self, qty: int) -> "Product":
117
+ if qty <= 0:
118
+ raise ValueError("qty must be positive")
119
+ if qty > self.stock:
120
+ raise ValueError("insufficient stock")
121
+ return Product(self.id, self.name, self.price, self.stock - qty, self.created_at)
122
+ ```
123
+
124
+ **Rule**: domain entities are immutable (`frozen=True`), live in `domain/`, and have **zero Django imports**. Business invariants are methods on the entity, not service-layer code. Tests for this file run without `pytest-django`.
125
+
126
+ ### Infrastructure ORM model (separate file, separate type)
127
+
128
+ ```python
129
+ # infrastructure/products/models.py
130
+ from django.db import models
131
+ import uuid
132
+
133
+ class ProductORM(models.Model):
134
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
135
+ name = models.CharField(max_length=120, db_index=True)
136
+ price = models.DecimalField(max_digits=12, decimal_places=2)
137
+ stock = models.PositiveIntegerField()
138
+ created_at = models.DateTimeField(auto_now_add=True)
139
+
140
+ class Meta:
141
+ db_table = "products"
142
+ indexes = [models.Index(fields=["created_at"])]
143
+ ```
144
+
145
+ **Rule**: ORM models live in `infrastructure/`, never `domain/`. Naming: `<Entity>ORM` to mark the boundary. Translation between domain entity and ORM model happens in `repository_impl.py`. UUID primary key by default — sequential integers are an anti-pattern for distributed systems.
146
+
147
+ ### Repository implementation (DDD-style adapter)
148
+
149
+ ```python
150
+ # infrastructure/products/repository_impl.py
151
+ from typing import Optional
152
+ from uuid import UUID
153
+ from domain.products.entities import Product
154
+ from domain.products.repository import ProductRepository
155
+ from infrastructure.products.models import ProductORM
156
+
157
+ class DjangoProductRepository(ProductRepository):
158
+ def get(self, id: UUID) -> Optional[Product]:
159
+ row = ProductORM.objects.filter(id=id).first()
160
+ return self._to_domain(row) if row else None
161
+
162
+ def save(self, product: Product) -> None:
163
+ ProductORM.objects.update_or_create(
164
+ id=product.id,
165
+ defaults={
166
+ "name": product.name,
167
+ "price": product.price,
168
+ "stock": product.stock,
169
+ },
170
+ )
171
+
172
+ @staticmethod
173
+ def _to_domain(row: ProductORM) -> Product:
174
+ return Product(
175
+ id=row.id, name=row.name, price=row.price,
176
+ stock=row.stock, created_at=row.created_at,
177
+ )
178
+ ```
179
+
180
+ **Rule**: domain port (`ProductRepository`) is an ABC defined in `domain/`. Implementation imports Django. Application use cases depend on the ABC, not the impl — wired by DI in `apps.py` or via simple module-level singleton.
181
+
182
+ ### DRF serializer + view (declarative mode, Django 5.x)
183
+
184
+ ```python
185
+ # api/products/serializers.py
186
+ from rest_framework import serializers
187
+ from decimal import Decimal
188
+
189
+ class CreateProductRequest(serializers.Serializer):
190
+ name = serializers.CharField(max_length=120)
191
+ price = serializers.DecimalField(max_digits=12, decimal_places=2, min_value=Decimal("0.01"))
192
+ stock = serializers.IntegerField(min_value=0)
193
+
194
+ class ProductResponse(serializers.Serializer):
195
+ id = serializers.UUIDField(read_only=True)
196
+ name = serializers.CharField(read_only=True)
197
+ price = serializers.DecimalField(max_digits=12, decimal_places=2, read_only=True)
198
+ stock = serializers.IntegerField(read_only=True)
199
+ created_at = serializers.DateTimeField(read_only=True)
200
+ ```
201
+
202
+ ```python
203
+ # api/products/views.py
204
+ from rest_framework.views import APIView
205
+ from rest_framework.response import Response
206
+ from rest_framework import status
207
+ from application.products.create_product import CreateProductUseCase
208
+ from .serializers import CreateProductRequest, ProductResponse
209
+
210
+ class ProductCreateView(APIView):
211
+ def __init__(self, use_case: CreateProductUseCase, **kwargs):
212
+ super().__init__(**kwargs)
213
+ self.use_case = use_case
214
+
215
+ def post(self, request):
216
+ req = CreateProductRequest(data=request.data)
217
+ req.is_valid(raise_exception=True)
218
+ product = self.use_case.execute(**req.validated_data)
219
+ return Response(ProductResponse(product).data, status=status.HTTP_201_CREATED)
220
+ ```
221
+
222
+ **Rule**: serializers are request/response DTOs, never `ModelSerializer` from ORM directly (it leaks the data model into the API contract). Views are thin — they parse, validate, call the use case, serialize the response. Business logic lives in `application/`.
223
+
224
+ ### Async view (Django 5.x, when a real reason exists)
225
+
226
+ ```python
227
+ # api/products/async_views.py
228
+ from adrf.views import APIView # async DRF
229
+ from rest_framework.response import Response
230
+ import httpx
231
+
232
+ class ProductPriceCheckView(APIView):
233
+ async def get(self, request, product_id):
234
+ async with httpx.AsyncClient(timeout=2.0) as client:
235
+ r = await client.get(f"https://price-api.example.com/{product_id}")
236
+ return Response({"upstream_price": r.json()["price"]})
237
+ ```
238
+
239
+ **Rule**: async views only when the view does I/O that benefits from concurrency (external HTTP, large concurrent reads). Sync views are simpler and have less footgun (no `sync_to_async` mismatch on ORM). When in doubt, sync.
240
+
241
+ ## 5. Testing
242
+
243
+ ### Unit (pytest, no Django bootstrap)
244
+
245
+ ```python
246
+ # tests/unit/domain/products/test_product.py
247
+ import pytest
248
+ from decimal import Decimal
249
+ from uuid import uuid4
250
+ from datetime import datetime
251
+ from domain.products.entities import Product
252
+
253
+ def test_reserve_decreases_stock():
254
+ p = Product(uuid4(), "x", Decimal("9.90"), stock=10, created_at=datetime.now())
255
+ p2 = p.reserve(3)
256
+ assert p2.stock == 7
257
+
258
+ def test_reserve_refuses_zero():
259
+ p = Product(uuid4(), "x", Decimal("9.90"), stock=10, created_at=datetime.now())
260
+ with pytest.raises(ValueError):
261
+ p.reserve(0)
262
+ ```
263
+
264
+ Runs in milliseconds. No `pytest-django`, no DB.
265
+
266
+ ### Integration (pytest-django + Testcontainers Postgres)
267
+
268
+ ```python
269
+ # conftest.py
270
+ import pytest
271
+ from testcontainers.postgres import PostgresContainer
272
+
273
+ @pytest.fixture(scope="session")
274
+ def postgres_container():
275
+ with PostgresContainer("postgres:16-alpine") as pg:
276
+ yield pg
277
+
278
+ @pytest.fixture(scope="session")
279
+ def django_db_setup(django_db_setup, postgres_container):
280
+ pass # connection URL injected via env in pytest.ini
281
+ ```
282
+
283
+ ```python
284
+ # tests/integration/infrastructure/products/test_repository.py
285
+ import pytest
286
+ from decimal import Decimal
287
+ from uuid import uuid4
288
+ from datetime import datetime, timezone
289
+ from domain.products.entities import Product
290
+ from infrastructure.products.repository_impl import DjangoProductRepository
291
+
292
+ @pytest.mark.django_db
293
+ def test_save_and_get():
294
+ repo = DjangoProductRepository()
295
+ p = Product(uuid4(), "widget", Decimal("9.90"), 100, datetime.now(timezone.utc))
296
+ repo.save(p)
297
+ fetched = repo.get(p.id)
298
+ assert fetched == p
299
+ ```
300
+
301
+ **Rule**: never SQLite for integration tests if production is Postgres. Use Testcontainers Postgres 16. Migrations run automatically via `pytest-django`.
302
+
303
+ ### Mutation (mutmut)
304
+
305
+ ```bash
306
+ mutmut run --paths-to-mutate=domain,application
307
+ mutmut results
308
+ # Target: killed/total >= 70% on domain/ and application/
309
+ ```
310
+
311
+ ### E2E (Playwright, optional)
312
+
313
+ ```python
314
+ # tests/e2e/test_create_product_flow.py
315
+ from playwright.sync_api import Page, expect
316
+
317
+ def test_user_creates_product(page: Page, live_server):
318
+ page.goto(f"{live_server.url}/admin/login/")
319
+ # ... login + navigation + assertions
320
+ expect(page.get_by_text("widget")).to_be_visible()
321
+ ```
322
+
323
+ ## 6. Build & run commands
324
+
325
+ ```bash
326
+ # Setup (uv recommended)
327
+ uv venv
328
+ uv pip sync requirements.lock # or: uv pip install -e ".[dev]"
329
+
330
+ # Run dev
331
+ python manage.py migrate
332
+ python manage.py runserver 0.0.0.0:8000
333
+
334
+ # Run prod (ASGI)
335
+ uvicorn config.asgi:application --workers 4 --host 0.0.0.0 --port 8000
336
+
337
+ # Lint + format (ruff replaces flake8 + black + isort)
338
+ ruff check --fix .
339
+ ruff format .
340
+
341
+ # Type check
342
+ mypy --strict domain/ application/
343
+
344
+ # Tests
345
+ pytest # all
346
+ pytest tests/unit/ # fast loop
347
+ pytest --cov=domain --cov=application --cov-branch --cov-fail-under=85
348
+ pytest -m "not slow" # skip Testcontainers in tight loop
349
+
350
+ # Mutation
351
+ mutmut run --paths-to-mutate=domain,application
352
+
353
+ # Security scan
354
+ pip-audit
355
+ bandit -ll -r domain/ application/ infrastructure/ api/
356
+
357
+ # Migrations (always review before commit)
358
+ python manage.py makemigrations
359
+ python manage.py showmigrations
360
+ python manage.py migrate
361
+
362
+ # Production-only checks before deploy
363
+ python manage.py check --deploy
364
+ ```
365
+
366
+ ## 7. Security (per ADR-007 + ADR-027 — MANDATORY section)
367
+
368
+ ### 7.1 Authentication & Authorization
369
+
370
+ - **Sessions for admin/web**: Django's built-in session auth. Cookie flags: `SESSION_COOKIE_SECURE=True`, `SESSION_COOKIE_HTTPONLY=True`, `SESSION_COOKIE_SAMESITE="Lax"` (or `Strict`).
371
+ - **API auth**: DRF + `djangorestframework-simplejwt` for stateless JWT, OR `dj-rest-auth` + session for cookie-based SPAs. Choose one — do not mix without ADR.
372
+ - **Password hashing**: Django uses Argon2 (preferred) or PBKDF2-SHA256. Add `argon2-cffi` and set `PASSWORD_HASHERS` with Argon2 first. Bcrypt with cost 12 acceptable.
373
+ - **MFA**: `django-otp` + `django-two-factor-auth` for TOTP. Always enabled for staff users.
374
+ - **Authorization**: `permissions.py` per app, using DRF `BasePermission` subclasses. Object-level via `get_object()` + check. IDOR mitigation: every "owned resource" read/write validates `request.user.id == obj.owner_id`.
375
+
376
+ ### 7.2 CORS
377
+
378
+ ```python
379
+ # settings/prod.py
380
+ INSTALLED_APPS += ["corsheaders"]
381
+ MIDDLEWARE.insert(0, "corsheaders.middleware.CorsMiddleware")
382
+ CORS_ALLOWED_ORIGINS = ["https://app.example.com"] # NEVER ["*"] in prod
383
+ CORS_ALLOW_CREDENTIALS = True
384
+ ```
385
+
386
+ ### 7.3 Validation & input sanitization
387
+
388
+ - **SQL injection**: ORM parameterizes by default. NEVER use `raw()` or `extra()` with f-strings/concatenation. If raw SQL is needed, use `cursor.execute(sql, params)` with `%s` placeholders.
389
+ - **XSS**: Django templates auto-escape. DRF serializers do not auto-escape — sanitize via `bleach` if rendering user HTML in any frontend.
390
+ - **Path traversal**: never join user input directly into `FileField` `upload_to`. Use `secrets.token_urlsafe()` for filenames.
391
+ - **Mass assignment**: DRF serializers explicit; never `Serializer(Meta).fields = "__all__"` on writable serializers — list fields explicitly.
392
+
393
+ ### 7.4 Secrets management
394
+
395
+ - `.env` file (loaded by `django-environ`) gitignored; `env.example` committed with placeholder values.
396
+ - `SECRET_KEY`: env var; rotate via `python manage.py rotate_secret_key` ritual (manual; Django does not auto-rotate). On rotation, invalidate sessions.
397
+ - AWS Secrets Manager / GCP Secret Manager / HashiCorp Vault preferred over `.env` for prod.
398
+
399
+ ### 7.5 Rate limiting
400
+
401
+ ```python
402
+ # pip install django-ratelimit
403
+ from django_ratelimit.decorators import ratelimit
404
+
405
+ @ratelimit(key="ip", rate="5/m", method="POST", block=True)
406
+ def login_view(request):
407
+ ...
408
+ ```
409
+
410
+ - `5/m` on `/api/auth/login`, `/api/auth/password-reset`, `/api/auth/signup`.
411
+ - `100/m` on regular GET endpoints by user.
412
+ - For DRF: `rest_framework.throttling.AnonRateThrottle` + `UserRateThrottle` with `DEFAULT_THROTTLE_RATES`.
413
+ - Brute-force lockout: `django-axes` for admin and login views — lock account after 5 failed attempts within 10 minutes.
414
+
415
+ ### 7.6 OWASP Top 10 mapping
416
+
417
+ | OWASP | Mitigation in Django 5.x |
418
+ |---|---|
419
+ | A01 Broken Access Control | `LoginRequiredMiddleware` (Django 5.0+); per-view DRF permissions; object-level `get_object_or_404(Model, owner=request.user, id=...)` pattern |
420
+ | A02 Cryptographic Failures | Argon2 password hasher; `DJANGO_SETTINGS_MODULE=config.settings.prod` enforces TLS; `SECURE_HSTS_SECONDS=31536000`, `SECURE_HSTS_INCLUDE_SUBDOMAINS=True`, `SECURE_HSTS_PRELOAD=True` |
421
+ | A03 Injection | ORM parameterization; never `raw()`/`extra()` with f-strings; DRF input validation via serializers |
422
+ | A04 Insecure Design | DDD layering enforced (`domain/` no Django); use cases as single-responsibility classes; ADRs document deviations |
423
+ | A05 Security Misconfiguration | `python manage.py check --deploy` in CI; `DEBUG=False` in prod via env split; `ALLOWED_HOSTS` set to actual domains; `X_FRAME_OPTIONS="DENY"` |
424
+ | A06 Vulnerable Components | `pip-audit` in CI; `dependabot` or `renovate` for PR-based bumps; `bandit` for code-level patterns |
425
+ | A07 Auth Failures | `django-axes` brute-force lock; Argon2 hashing; `django-two-factor-auth` for staff; session rotation on password change |
426
+ | A08 Data Integrity | Verify upstream packages via `uv pip install --require-hashes`; SBOM via `cyclonedx-py`; signed releases for internal libs |
427
+ | A09 Logging Failures | Structured JSON logs via `python-json-logger`; correlation ID via `django-guid` middleware; **NEVER** log `request.body` raw (PII) |
428
+ | A10 SSRF | Allowlist outbound HTTP via `httpx` client with explicit allowed hosts; validate URLs server-side before fetching; never trust user-supplied URLs as fetch targets |
429
+
430
+ ### 7.7 LGPD / GDPR / compliance specifics
431
+
432
+ - **PII tagging**: a `pii=True` model field convention enforced by `django-stubs` + custom lint (or naming convention `<field>__pii`).
433
+ - **Soft delete**: `is_deleted` boolean + `deleted_at` timestamp; never hard delete user-owned data without an explicit erasure request workflow.
434
+ - **Data subject access**: `python manage.py export_user_data <user_id>` custom command exports all PII as JSON for GDPR Article 15.
435
+ - **Erasure (Article 17)**: `python manage.py erase_user_data <user_id>` redacts PII fields while preserving foreign keys (e.g. orders kept with `customer=None`).
436
+ - **Encryption at rest**: PostgreSQL TDE or column-level via `django-cryptography` for sensitive fields (CPF, ID documents).
437
+
438
+ ## 8. Anti-patterns (block in code-review)
439
+
440
+ | ❌ Bad | ✅ Good | Why |
441
+ |---|---|---|
442
+ | `Product.objects.filter(...).first()` inside DRF view | Repository in `infrastructure/`, called from `application/` use case | View should be thin; logic + persistence belong in layers |
443
+ | `ModelSerializer` exposing all ORM fields | Request/response `Serializer` with explicit fields | API contract leaks the data model otherwise |
444
+ | `auto_now_add=True` on `created_at` + `auto_now=True` on `updated_at` mixed with manual sets | Pick one — automatic OR explicit, never both | Race conditions in batch operations |
445
+ | `SECRET_KEY` in `settings/base.py` | `env("SECRET_KEY")` via django-environ | Secret in code = secret in git history |
446
+ | `DEBUG=True` reachable in prod | `DJANGO_SETTINGS_MODULE=config.settings.prod` + `python manage.py check --deploy` gate | DEBUG exposes stack traces with code and DB queries |
447
+ | Migrations auto-applied in CI without review | `makemigrations` runs locally, dev reviews, commits, then CI runs `migrate` | Schema changes need human eyes |
448
+ | SQLite for tests when prod is Postgres | Testcontainers Postgres 16 in CI | Subtle SQL differences cause prod-only bugs |
449
+ | `.objects.all()` without `select_related` / `prefetch_related` in templates/serializers | Explicit `.select_related("category").prefetch_related("tags")` | N+1 queries kill performance under load |
450
+ | Catching `Exception:` in views | Catch specific domain exceptions; let DRF exception handler do the rest | Hides bugs, breaks 500 telemetry |
451
+ | Sync view with `requests.get(...)` blocking external call | Async view + `httpx.AsyncClient` OR move external call to Celery task | Blocks worker thread; cascade failures under load |
452
+ | `User = get_user_model()` at module level + circular import | Lazy import via `django.contrib.auth.get_user_model()` inside the function | Solves the AppRegistryNotReady bootstrap issue |
453
+
454
+ ## 9. Migration hints — Django 4.2 → 5.x
455
+
456
+ Breaking changes worth flagging when `migrator` agent runs Django 4 → 5:
457
+
458
+ - **Python 3.10 minimum** (Django 5.0); recommend 3.12 or 3.13.
459
+ - **`USE_L10N` removed** — was deprecated since 4.0. Localization is always on; format strings via `formats.py`.
460
+ - **`pytz` removed** — use `zoneinfo` from stdlib. Replace `pytz.timezone("America/Sao_Paulo")` with `zoneinfo.ZoneInfo("America/Sao_Paulo")`.
461
+ - **Form rendering**: default template-based form rendering (Django 4.0+) is now the only mode. If you have legacy `as_p()` overrides, review.
462
+ - **Async ORM**: `aget`, `aupdate`, `acreate` are first-class in 5.x; if you wrote `sync_to_async(Model.objects.get)` workarounds, swap them for native async calls.
463
+ - **DRF on Django 5**: requires DRF 3.15+. Older DRF versions have async-related deprecations.
464
+ - **`USE_TZ = True` enforced**: timezone-aware datetimes everywhere. If your code does `datetime.now()` without tzinfo, Django 5 issues warnings and may raise in 6.0.
465
+ - **`LoginRequiredMiddleware`** (5.0+): when enabled, every view is login-required by default; mark public views with `@login_not_required`. Simpler than per-view decorators for protected apps.
466
+ - **Generated model fields** (5.0+): `GeneratedField` allows DB-computed columns; consider migrating computed properties into the DB layer where appropriate.
467
+
468
+ Hand off to `migrator` agent with: target version, current Django/Python versions from `pyproject.toml`, list of installed third-party Django apps (some need bumps).
469
+
470
+ ## 10. References
471
+
472
+ - [Django 5.0 release notes](https://docs.djangoproject.com/en/5.0/releases/5.0/)
473
+ - [Django 5.2 LTS release notes](https://docs.djangoproject.com/en/5.2/releases/5.2/)
474
+ - [Django Security Documentation](https://docs.djangoproject.com/en/stable/topics/security/)
475
+ - [DRF official tutorial](https://www.django-rest-framework.org/)
476
+ - [OWASP Django Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Django_REST_Framework_Cheat_Sheet.html)
477
+ - [pytest-django docs](https://pytest-django.readthedocs.io/)
478
+ - [django-axes (brute-force lockout)](https://github.com/jazzband/django-axes)
479
+ - [django-environ (env var config)](https://github.com/joke2k/django-environ)
480
+ - ADR-007 (Senior+ gate thresholds — coverage ≥85%, mutation ≥70%)
481
+ - ADR-026 (Generic agents + stack packs architecture)
482
+ - ADR-027 (Pack governance — frontmatter + security mandatory + CODEOWNERS + annual review)
483
+ - ADR-029 (Canonical pack format — this document follows it)