red64-cli 0.1.0 → 0.2.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 (103) hide show
  1. package/dist/cli/parseArgs.d.ts.map +1 -1
  2. package/dist/cli/parseArgs.js +5 -0
  3. package/dist/cli/parseArgs.js.map +1 -1
  4. package/dist/components/init/CompleteStep.d.ts.map +1 -1
  5. package/dist/components/init/CompleteStep.js +2 -2
  6. package/dist/components/init/CompleteStep.js.map +1 -1
  7. package/dist/components/init/TestCheckStep.d.ts +16 -0
  8. package/dist/components/init/TestCheckStep.d.ts.map +1 -0
  9. package/dist/components/init/TestCheckStep.js +120 -0
  10. package/dist/components/init/TestCheckStep.js.map +1 -0
  11. package/dist/components/init/index.d.ts +1 -0
  12. package/dist/components/init/index.d.ts.map +1 -1
  13. package/dist/components/init/index.js +1 -0
  14. package/dist/components/init/index.js.map +1 -1
  15. package/dist/components/init/types.d.ts +9 -0
  16. package/dist/components/init/types.d.ts.map +1 -1
  17. package/dist/components/screens/InitScreen.d.ts.map +1 -1
  18. package/dist/components/screens/InitScreen.js +69 -6
  19. package/dist/components/screens/InitScreen.js.map +1 -1
  20. package/dist/components/screens/StartScreen.d.ts.map +1 -1
  21. package/dist/components/screens/StartScreen.js +89 -3
  22. package/dist/components/screens/StartScreen.js.map +1 -1
  23. package/dist/services/ConfigService.d.ts +1 -0
  24. package/dist/services/ConfigService.d.ts.map +1 -1
  25. package/dist/services/ConfigService.js.map +1 -1
  26. package/dist/services/ProjectDetector.d.ts +28 -0
  27. package/dist/services/ProjectDetector.d.ts.map +1 -0
  28. package/dist/services/ProjectDetector.js +236 -0
  29. package/dist/services/ProjectDetector.js.map +1 -0
  30. package/dist/services/TestRunner.d.ts +46 -0
  31. package/dist/services/TestRunner.d.ts.map +1 -0
  32. package/dist/services/TestRunner.js +85 -0
  33. package/dist/services/TestRunner.js.map +1 -0
  34. package/dist/services/index.d.ts +2 -0
  35. package/dist/services/index.d.ts.map +1 -1
  36. package/dist/services/index.js +2 -0
  37. package/dist/services/index.js.map +1 -1
  38. package/dist/types/index.d.ts +1 -0
  39. package/dist/types/index.d.ts.map +1 -1
  40. package/dist/types/index.js.map +1 -1
  41. package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
  42. package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
  43. package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
  44. package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
  45. package/framework/stacks/generic/feedback.md +80 -0
  46. package/framework/stacks/nextjs/accessibility.md +437 -0
  47. package/framework/stacks/nextjs/api.md +431 -0
  48. package/framework/stacks/nextjs/coding-style.md +282 -0
  49. package/framework/stacks/nextjs/commenting.md +226 -0
  50. package/framework/stacks/nextjs/components.md +411 -0
  51. package/framework/stacks/nextjs/conventions.md +333 -0
  52. package/framework/stacks/nextjs/css.md +310 -0
  53. package/framework/stacks/nextjs/error-handling.md +442 -0
  54. package/framework/stacks/nextjs/feedback.md +124 -0
  55. package/framework/stacks/nextjs/migrations.md +332 -0
  56. package/framework/stacks/nextjs/models.md +362 -0
  57. package/framework/stacks/nextjs/queries.md +410 -0
  58. package/framework/stacks/nextjs/responsive.md +338 -0
  59. package/framework/stacks/nextjs/tech-stack.md +177 -0
  60. package/framework/stacks/nextjs/test-writing.md +475 -0
  61. package/framework/stacks/nextjs/validation.md +467 -0
  62. package/framework/stacks/python/api.md +468 -0
  63. package/framework/stacks/python/authentication.md +342 -0
  64. package/framework/stacks/python/code-quality.md +283 -0
  65. package/framework/stacks/python/code-refactoring.md +315 -0
  66. package/framework/stacks/python/coding-style.md +462 -0
  67. package/framework/stacks/python/conventions.md +399 -0
  68. package/framework/stacks/python/error-handling.md +512 -0
  69. package/framework/stacks/python/feedback.md +92 -0
  70. package/framework/stacks/python/implement-ai-llm.md +468 -0
  71. package/framework/stacks/python/migrations.md +388 -0
  72. package/framework/stacks/python/models.md +399 -0
  73. package/framework/stacks/python/python.md +232 -0
  74. package/framework/stacks/python/queries.md +451 -0
  75. package/framework/stacks/python/structure.md +245 -58
  76. package/framework/stacks/python/tech.md +92 -35
  77. package/framework/stacks/python/testing.md +380 -0
  78. package/framework/stacks/python/validation.md +471 -0
  79. package/framework/stacks/rails/authentication.md +176 -0
  80. package/framework/stacks/rails/code-quality.md +287 -0
  81. package/framework/stacks/rails/code-refactoring.md +299 -0
  82. package/framework/stacks/rails/feedback.md +130 -0
  83. package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
  84. package/framework/stacks/rails/rails.md +301 -0
  85. package/framework/stacks/rails/rails8-best-practices.md +498 -0
  86. package/framework/stacks/rails/rails8-css.md +573 -0
  87. package/framework/stacks/rails/structure.md +140 -0
  88. package/framework/stacks/rails/tech.md +108 -0
  89. package/framework/stacks/react/code-quality.md +521 -0
  90. package/framework/stacks/react/components.md +625 -0
  91. package/framework/stacks/react/data-fetching.md +586 -0
  92. package/framework/stacks/react/feedback.md +110 -0
  93. package/framework/stacks/react/forms.md +694 -0
  94. package/framework/stacks/react/performance.md +640 -0
  95. package/framework/stacks/react/product.md +22 -9
  96. package/framework/stacks/react/state-management.md +472 -0
  97. package/framework/stacks/react/structure.md +351 -44
  98. package/framework/stacks/react/tech.md +219 -30
  99. package/framework/stacks/react/testing.md +690 -0
  100. package/package.json +1 -1
  101. package/framework/stacks/node/product.md +0 -27
  102. package/framework/stacks/node/structure.md +0 -82
  103. package/framework/stacks/node/tech.md +0 -63
@@ -0,0 +1,342 @@
1
+ # Authentication Patterns
2
+
3
+ Modern Python authentication using JWT, OAuth2, and session-based approaches.
4
+
5
+ ---
6
+
7
+ ## Philosophy
8
+
9
+ - **Stateless API auth**: JWT tokens for API consumers
10
+ - **OAuth2 flows**: Standards-based third-party auth
11
+ - **Secure defaults**: bcrypt hashing, httpOnly cookies, short-lived tokens
12
+ - **Separation of concerns**: Auth middleware, not scattered checks
13
+
14
+ ---
15
+
16
+ ## JWT Authentication (API)
17
+
18
+ ### Token Generation
19
+
20
+ ```python
21
+ # app/utils/auth.py
22
+ from datetime import datetime, timedelta, timezone
23
+ from jose import JWTError, jwt
24
+ from app.config import settings
25
+
26
+ ALGORITHM = "HS256"
27
+
28
+ def create_access_token(
29
+ data: dict,
30
+ expires_delta: timedelta | None = None,
31
+ ) -> str:
32
+ to_encode = data.copy()
33
+ expire = datetime.now(timezone.utc) + (
34
+ expires_delta or timedelta(minutes=settings.access_token_expire_minutes)
35
+ )
36
+ to_encode["exp"] = expire
37
+ return jwt.encode(to_encode, settings.secret_key, algorithm=ALGORITHM)
38
+
39
+ def decode_access_token(token: str) -> dict:
40
+ """Decode and validate JWT token.
41
+
42
+ Raises:
43
+ JWTError: If token is invalid or expired.
44
+ """
45
+ return jwt.decode(token, settings.secret_key, algorithms=[ALGORITHM])
46
+ ```
47
+
48
+ ### Refresh Token Pattern
49
+
50
+ ```python
51
+ def create_token_pair(user_id: int) -> dict:
52
+ access = create_access_token(
53
+ {"sub": str(user_id), "type": "access"},
54
+ expires_delta=timedelta(minutes=15),
55
+ )
56
+ refresh = create_access_token(
57
+ {"sub": str(user_id), "type": "refresh"},
58
+ expires_delta=timedelta(days=7),
59
+ )
60
+ return {"access_token": access, "refresh_token": refresh, "token_type": "bearer"}
61
+ ```
62
+
63
+ ### FastAPI Dependency
64
+
65
+ ```python
66
+ from fastapi import Depends, HTTPException, status
67
+ from fastapi.security import OAuth2PasswordBearer
68
+
69
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
70
+
71
+ async def get_current_user(
72
+ token: str = Depends(oauth2_scheme),
73
+ db: AsyncSession = Depends(get_db),
74
+ ) -> User:
75
+ try:
76
+ payload = decode_access_token(token)
77
+ user_id = int(payload["sub"])
78
+ except (JWTError, KeyError, ValueError):
79
+ raise HTTPException(
80
+ status_code=status.HTTP_401_UNAUTHORIZED,
81
+ detail="Invalid or expired token",
82
+ headers={"WWW-Authenticate": "Bearer"},
83
+ )
84
+
85
+ user = await db.get(User, user_id)
86
+ if user is None or not user.is_active:
87
+ raise HTTPException(status_code=401, detail="User not found or inactive")
88
+ return user
89
+
90
+ # Usage in route
91
+ @router.get("/me")
92
+ async def get_profile(current_user: User = Depends(get_current_user)):
93
+ return UserResponse.model_validate(current_user)
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Password Management
99
+
100
+ ### Hashing with passlib + bcrypt
101
+
102
+ ```python
103
+ # app/utils/hashing.py
104
+ from passlib.context import CryptContext
105
+
106
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
107
+
108
+ def hash_password(password: str) -> str:
109
+ return pwd_context.hash(password)
110
+
111
+ def verify_password(plain: str, hashed: str) -> bool:
112
+ return pwd_context.verify(plain, hashed)
113
+ ```
114
+
115
+ ### Login Endpoint
116
+
117
+ ```python
118
+ @router.post("/auth/login")
119
+ async def login(
120
+ form_data: OAuth2PasswordRequestForm = Depends(),
121
+ db: AsyncSession = Depends(get_db),
122
+ ):
123
+ user = await user_repo.get_by_email(db, form_data.username)
124
+ if not user or not verify_password(form_data.password, user.hashed_password):
125
+ raise HTTPException(
126
+ status_code=401,
127
+ detail="Incorrect email or password",
128
+ )
129
+ return create_token_pair(user.id)
130
+ ```
131
+
132
+ ---
133
+
134
+ ## OAuth2 with authlib
135
+
136
+ ### Provider Setup
137
+
138
+ ```python
139
+ # app/auth/oauth.py
140
+ from authlib.integrations.starlette_client import OAuth
141
+
142
+ oauth = OAuth()
143
+
144
+ oauth.register(
145
+ name="google",
146
+ client_id=settings.google_client_id,
147
+ client_secret=settings.google_client_secret,
148
+ server_metadata_url="https://accounts.google.com/.well-known/openid-configuration",
149
+ client_kwargs={"scope": "openid email profile"},
150
+ )
151
+
152
+ oauth.register(
153
+ name="github",
154
+ client_id=settings.github_client_id,
155
+ client_secret=settings.github_client_secret,
156
+ authorize_url="https://github.com/login/oauth/authorize",
157
+ access_token_url="https://github.com/login/oauth/access_token",
158
+ api_base_url="https://api.github.com/",
159
+ client_kwargs={"scope": "user:email"},
160
+ )
161
+ ```
162
+
163
+ ### OAuth Flow Endpoints
164
+
165
+ ```python
166
+ from starlette.requests import Request
167
+
168
+ @router.get("/auth/{provider}/login")
169
+ async def oauth_login(provider: str, request: Request):
170
+ client = getattr(oauth, provider, None)
171
+ if not client:
172
+ raise HTTPException(404, f"Unknown provider: {provider}")
173
+ redirect_uri = request.url_for("oauth_callback", provider=provider)
174
+ return await client.authorize_redirect(request, redirect_uri)
175
+
176
+ @router.get("/auth/{provider}/callback")
177
+ async def oauth_callback(
178
+ provider: str,
179
+ request: Request,
180
+ db: AsyncSession = Depends(get_db),
181
+ ):
182
+ client = getattr(oauth, provider)
183
+ token = await client.authorize_access_token(request)
184
+ user_info = token.get("userinfo") or await client.userinfo(token=token)
185
+
186
+ # Find or create user
187
+ user = await find_or_create_oauth_user(
188
+ db=db,
189
+ provider=provider,
190
+ provider_id=str(user_info["sub"]),
191
+ email=user_info["email"],
192
+ name=user_info.get("name", ""),
193
+ )
194
+
195
+ return create_token_pair(user.id)
196
+ ```
197
+
198
+ ---
199
+
200
+ ## Session-Based Authentication
201
+
202
+ ### Cookie Sessions (for web apps)
203
+
204
+ ```python
205
+ from starlette.middleware.sessions import SessionMiddleware
206
+
207
+ app.add_middleware(
208
+ SessionMiddleware,
209
+ secret_key=settings.secret_key,
210
+ max_age=86400, # 24 hours
211
+ https_only=True, # Production only
212
+ same_site="lax",
213
+ )
214
+
215
+ # Login: store user in session
216
+ @router.post("/login")
217
+ async def login(request: Request, form: LoginForm = Depends()):
218
+ user = await authenticate_user(form.email, form.password)
219
+ if not user:
220
+ raise HTTPException(401)
221
+ request.session["user_id"] = user.id
222
+ return RedirectResponse("/dashboard", status_code=303)
223
+
224
+ # Middleware: load user from session
225
+ async def get_session_user(
226
+ request: Request,
227
+ db: AsyncSession = Depends(get_db),
228
+ ) -> User | None:
229
+ user_id = request.session.get("user_id")
230
+ if not user_id:
231
+ return None
232
+ return await db.get(User, user_id)
233
+ ```
234
+
235
+ ---
236
+
237
+ ## Authorization
238
+
239
+ ### Role-Based Access Control
240
+
241
+ ```python
242
+ from enum import StrEnum
243
+ from functools import wraps
244
+
245
+ class Role(StrEnum):
246
+ ADMIN = "admin"
247
+ MEMBER = "member"
248
+ VIEWER = "viewer"
249
+
250
+ def require_role(*roles: Role):
251
+ """Dependency that checks user role."""
252
+ async def checker(current_user: User = Depends(get_current_user)) -> User:
253
+ if current_user.role not in roles:
254
+ raise HTTPException(status_code=403, detail="Insufficient permissions")
255
+ return current_user
256
+ return checker
257
+
258
+ # Usage
259
+ @router.delete("/users/{user_id}")
260
+ async def delete_user(
261
+ user_id: int,
262
+ admin: User = Depends(require_role(Role.ADMIN)),
263
+ ):
264
+ ...
265
+ ```
266
+
267
+ ### Resource Ownership
268
+
269
+ ```python
270
+ # Pattern: Always scope queries to current user
271
+ async def get_user_content(
272
+ content_id: int,
273
+ current_user: User = Depends(get_current_user),
274
+ db: AsyncSession = Depends(get_db),
275
+ ) -> Content:
276
+ content = await db.get(Content, content_id)
277
+ if not content or content.user_id != current_user.id:
278
+ raise HTTPException(status_code=404)
279
+ return content
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Rate Limiting
285
+
286
+ ```python
287
+ # Using slowapi
288
+ from slowapi import Limiter
289
+ from slowapi.util import get_remote_address
290
+
291
+ limiter = Limiter(key_func=get_remote_address)
292
+
293
+ @router.post("/auth/login")
294
+ @limiter.limit("5/minute")
295
+ async def login(request: Request, ...):
296
+ ...
297
+
298
+ @router.post("/auth/reset-password")
299
+ @limiter.limit("3/minute")
300
+ async def reset_password(request: Request, ...):
301
+ ...
302
+ ```
303
+
304
+ ---
305
+
306
+ ## Security Checklist
307
+
308
+ - [x] Passwords hashed with bcrypt (passlib)
309
+ - [x] JWT tokens with short expiry (15 min access, 7 day refresh)
310
+ - [x] httpOnly cookies for session auth
311
+ - [x] Rate limiting on auth endpoints
312
+ - [x] CORS configured for allowed origins
313
+ - [ ] Account lockout after failed attempts (implement as needed)
314
+ - [ ] Email verification flow (implement as needed)
315
+ - [ ] MFA / TOTP support (implement as needed)
316
+
317
+ ---
318
+
319
+ ## Testing Auth
320
+
321
+ ```python
322
+ # Fixture: authenticated client
323
+ @pytest.fixture
324
+ async def auth_client(client: httpx.AsyncClient, sample_user: User):
325
+ token = create_access_token({"sub": str(sample_user.id)})
326
+ client.headers["Authorization"] = f"Bearer {token}"
327
+ return client
328
+
329
+ # Test protected endpoint
330
+ async def test_profile_requires_auth(client: httpx.AsyncClient):
331
+ response = await client.get("/api/v1/me")
332
+ assert response.status_code == 401
333
+
334
+ async def test_profile_with_auth(auth_client: httpx.AsyncClient):
335
+ response = await auth_client.get("/api/v1/me")
336
+ assert response.status_code == 200
337
+ assert "email" in response.json()
338
+ ```
339
+
340
+ ---
341
+
342
+ _Document patterns and extension points, not implementation details._
@@ -0,0 +1,283 @@
1
+ # Code Quality Standards
2
+
3
+ Project memory for code quality conventions, linting, type checking, and testing standards in Python.
4
+
5
+ ---
6
+
7
+ ## Linting and Formatting
8
+
9
+ ### Ruff (Single Tool for Everything)
10
+
11
+ Ruff replaces black, isort, flake8, pyupgrade, and more. Configuration lives in `pyproject.toml`:
12
+
13
+ ```toml
14
+ [tool.ruff]
15
+ target-version = "py312"
16
+ src = ["src"]
17
+ line-length = 99
18
+
19
+ [tool.ruff.lint]
20
+ select = [
21
+ "E", # pycodestyle errors
22
+ "F", # pyflakes
23
+ "I", # isort
24
+ "N", # pep8-naming
25
+ "UP", # pyupgrade (modern syntax)
26
+ "B", # flake8-bugbear
27
+ "A", # flake8-builtins
28
+ "SIM", # flake8-simplify
29
+ "TCH", # flake8-type-checking (move imports behind TYPE_CHECKING)
30
+ "RUF", # ruff-specific rules
31
+ "ASYNC",# flake8-async
32
+ ]
33
+ ignore = ["E501"] # line length handled by formatter
34
+
35
+ [tool.ruff.lint.isort]
36
+ known-first-party = ["app"]
37
+ ```
38
+
39
+ ```bash
40
+ # Run checks
41
+ uv run ruff check . # Lint
42
+ uv run ruff check . --fix # Auto-fix
43
+ uv run ruff format . # Format
44
+ uv run ruff format . --check # Verify formatting
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Type Checking
50
+
51
+ ### mypy (Strict Mode)
52
+
53
+ ```toml
54
+ [tool.mypy]
55
+ python_version = "3.12"
56
+ strict = true
57
+ warn_return_any = true
58
+ warn_unused_configs = true
59
+ plugins = ["pydantic.mypy"]
60
+
61
+ [[tool.mypy.overrides]]
62
+ module = ["tests.*"]
63
+ disallow_untyped_defs = false
64
+
65
+ [[tool.mypy.overrides]]
66
+ module = ["celery.*", "arq.*"]
67
+ ignore_missing_imports = true
68
+ ```
69
+
70
+ ### Type Annotation Patterns
71
+
72
+ ```python
73
+ # Pattern: Annotate all public function signatures
74
+ async def get_user(user_id: int, db: AsyncSession) -> User | None:
75
+ ...
76
+
77
+ # Pattern: Use TYPE_CHECKING for import-only types
78
+ from __future__ import annotations
79
+ from typing import TYPE_CHECKING
80
+
81
+ if TYPE_CHECKING:
82
+ from app.models.user import User
83
+
84
+ # Pattern: TypeVar for generic functions
85
+ from typing import TypeVar
86
+ T = TypeVar("T")
87
+
88
+ async def get_or_404(model: type[T], id: int, db: AsyncSession) -> T:
89
+ result = await db.get(model, id)
90
+ if result is None:
91
+ raise NotFoundError(model.__name__, str(id))
92
+ return result
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Pre-commit Hooks
98
+
99
+ ### Configuration (`.pre-commit-config.yaml`)
100
+
101
+ ```yaml
102
+ repos:
103
+ - repo: https://github.com/astral-sh/ruff-pre-commit
104
+ rev: v0.8.0
105
+ hooks:
106
+ - id: ruff
107
+ args: [--fix]
108
+ - id: ruff-format
109
+
110
+ - repo: https://github.com/pre-commit/mirrors-mypy
111
+ rev: v1.13.0
112
+ hooks:
113
+ - id: mypy
114
+ additional_dependencies:
115
+ - pydantic>=2.0
116
+ - types-redis
117
+
118
+ - repo: https://github.com/pre-commit/pre-commit-hooks
119
+ rev: v5.0.0
120
+ hooks:
121
+ - id: check-yaml
122
+ - id: check-toml
123
+ - id: check-added-large-files
124
+ - id: no-commit-to-branch
125
+ args: [--branch, main]
126
+ - id: detect-private-key
127
+ ```
128
+
129
+ ```bash
130
+ # Setup
131
+ uv run pre-commit install
132
+ uv run pre-commit run --all-files # Verify
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Testing Framework
138
+
139
+ ### pytest Configuration
140
+
141
+ ```toml
142
+ [tool.pytest.ini_options]
143
+ testpaths = ["tests"]
144
+ asyncio_mode = "auto"
145
+ addopts = [
146
+ "-v",
147
+ "--strict-markers",
148
+ "--tb=short",
149
+ ]
150
+ markers = [
151
+ "slow: marks tests as slow (deselect with '-m \"not slow\"')",
152
+ "integration: marks integration tests",
153
+ ]
154
+ filterwarnings = [
155
+ "error",
156
+ "ignore::DeprecationWarning:sqlalchemy.*",
157
+ ]
158
+ ```
159
+
160
+ ### Coverage Configuration
161
+
162
+ ```toml
163
+ [tool.coverage.run]
164
+ source = ["src/app"]
165
+ omit = ["*/migrations/*", "*/tests/*"]
166
+
167
+ [tool.coverage.report]
168
+ fail_under = 80
169
+ show_missing = true
170
+ exclude_lines = [
171
+ "pragma: no cover",
172
+ "if TYPE_CHECKING:",
173
+ "raise NotImplementedError",
174
+ "@overload",
175
+ ]
176
+ ```
177
+
178
+ ```bash
179
+ uv run pytest --cov=src/app --cov-report=term-missing
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Test Data Generation
185
+
186
+ ### Factory Boy + Faker
187
+
188
+ ```python
189
+ # tests/factories/user_factory.py
190
+ import factory
191
+ from faker import Faker
192
+ from app.models.user import User
193
+
194
+ fake = Faker()
195
+
196
+ class UserFactory(factory.Factory):
197
+ class Meta:
198
+ model = User
199
+
200
+ id = factory.Sequence(lambda n: n + 1)
201
+ email = factory.LazyFunction(fake.email)
202
+ name = factory.LazyFunction(fake.name)
203
+ hashed_password = "hashed_test_password"
204
+ is_active = True
205
+
206
+ # Usage
207
+ user = UserFactory()
208
+ admin = UserFactory(name="Admin", is_active=True)
209
+ users = UserFactory.build_batch(5)
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Security Scanning
215
+
216
+ ### bandit (Static Analysis)
217
+
218
+ ```toml
219
+ # pyproject.toml
220
+ [tool.bandit]
221
+ exclude_dirs = ["tests"]
222
+ skips = ["B101"] # Allow assert in non-test code if needed
223
+ ```
224
+
225
+ ```bash
226
+ uv run bandit -r src/ -c pyproject.toml
227
+ ```
228
+
229
+ ### Dependency Auditing
230
+
231
+ ```bash
232
+ uv run pip-audit # Check for known vulnerabilities
233
+ ```
234
+
235
+ ---
236
+
237
+ ## Documentation Standards
238
+
239
+ ### Docstrings (Google Style)
240
+
241
+ ```python
242
+ class UserService:
243
+ """Service for user-related business logic.
244
+
245
+ Handles user creation, authentication, and profile management.
246
+ Depends on UserRepo for data access.
247
+ """
248
+
249
+ async def create_user(self, data: CreateUserRequest) -> User:
250
+ """Create a new user account.
251
+
252
+ Args:
253
+ data: Validated user creation request.
254
+
255
+ Returns:
256
+ The created User instance.
257
+
258
+ Raises:
259
+ ValidationError: If email already exists.
260
+ """
261
+ ```
262
+
263
+ ---
264
+
265
+ ## Quality Commands Summary
266
+
267
+ ```bash
268
+ # Full quality check (CI pipeline)
269
+ uv run ruff check . # Lint
270
+ uv run ruff format . --check # Format check
271
+ uv run mypy src/ # Type check
272
+ uv run bandit -r src/ # Security
273
+ uv run pytest --cov=src/app # Tests + coverage
274
+
275
+ # Development workflow
276
+ uv run ruff check . --fix && uv run ruff format . # Quick fix
277
+ uv run pytest tests/unit/ -x # Fast feedback
278
+ uv run mypy src/app/services/ # Focused type check
279
+ ```
280
+
281
+ ---
282
+
283
+ _Focus on patterns over exhaustive rules. Code should be typed, formatted, and tested._