start-vibing-stacks 2.5.1 → 2.7.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 (82) hide show
  1. package/dist/detector.js +5 -2
  2. package/dist/index.js +16 -2
  3. package/dist/migrate.d.ts +27 -0
  4. package/dist/migrate.js +217 -0
  5. package/dist/scanner.js +91 -0
  6. package/dist/setup.js +10 -0
  7. package/package.json +1 -1
  8. package/stacks/_shared/agents/claude-md-compactor.md +1 -0
  9. package/stacks/_shared/agents/commit-manager.md +1 -0
  10. package/stacks/_shared/agents/documenter.md +1 -0
  11. package/stacks/_shared/agents/domain-updater.md +1 -0
  12. package/stacks/_shared/agents/research-web.md +1 -0
  13. package/stacks/_shared/agents/security-auditor.md +168 -0
  14. package/stacks/_shared/agents/tester.md +1 -0
  15. package/stacks/_shared/hooks/final-check.ts +205 -0
  16. package/stacks/_shared/hooks/stop-validator.ts +77 -1
  17. package/stacks/_shared/skills/accessibility-wcag22/SKILL.md +284 -0
  18. package/stacks/_shared/skills/ci-pipelines/SKILL.md +166 -0
  19. package/stacks/_shared/skills/codebase-knowledge/SKILL.md +5 -0
  20. package/stacks/_shared/skills/database-migrations/SKILL.md +256 -0
  21. package/stacks/_shared/skills/debugging-patterns/SKILL.md +5 -0
  22. package/stacks/_shared/skills/docker-patterns/SKILL.md +5 -0
  23. package/stacks/_shared/skills/docs-tracker/SKILL.md +5 -0
  24. package/stacks/_shared/skills/error-handling/SKILL.md +335 -0
  25. package/stacks/_shared/skills/final-check/SKILL.md +74 -37
  26. package/stacks/_shared/skills/git-workflow/SKILL.md +5 -0
  27. package/stacks/_shared/skills/hook-development/SKILL.md +5 -0
  28. package/stacks/_shared/skills/observability/SKILL.md +351 -0
  29. package/stacks/_shared/skills/performance-patterns/SKILL.md +5 -0
  30. package/stacks/_shared/skills/playwright-automation/SKILL.md +5 -0
  31. package/stacks/_shared/skills/quality-gate/SKILL.md +5 -0
  32. package/stacks/_shared/skills/research-cache/SKILL.md +5 -0
  33. package/stacks/_shared/skills/secrets-management/SKILL.md +245 -0
  34. package/stacks/_shared/skills/security-baseline/SKILL.md +202 -0
  35. package/stacks/_shared/skills/test-coverage/SKILL.md +5 -0
  36. package/stacks/_shared/skills/ui-ux-audit/SKILL.md +5 -0
  37. package/stacks/frontend/react/skills/preline-ui/SKILL.md +5 -0
  38. package/stacks/frontend/react/skills/react-patterns/SKILL.md +5 -0
  39. package/stacks/frontend/react/skills/react-standards/SKILL.md +5 -0
  40. package/stacks/frontend/react/skills/react-ui-patterns/SKILL.md +5 -0
  41. package/stacks/frontend/react/skills/shadcn-ui/SKILL.md +5 -0
  42. package/stacks/frontend/react/skills/tailwind-patterns/SKILL.md +5 -0
  43. package/stacks/frontend/react/skills/zod-validation/SKILL.md +5 -0
  44. package/stacks/frontend/react-inertia/skills/inertia-react/SKILL.md +5 -0
  45. package/stacks/frontend/react-inertia/skills/react-standards/SKILL.md +5 -0
  46. package/stacks/nodejs/skills/api-security-node/SKILL.md +275 -0
  47. package/stacks/nodejs/skills/bun-runtime/SKILL.md +5 -0
  48. package/stacks/nodejs/skills/mongoose-patterns/SKILL.md +5 -0
  49. package/stacks/nodejs/skills/nextjs-app-router/SKILL.md +5 -0
  50. package/stacks/nodejs/skills/trpc-api/SKILL.md +5 -0
  51. package/stacks/nodejs/skills/typescript-strict/SKILL.md +5 -0
  52. package/stacks/nodejs/stack.json +2 -1
  53. package/stacks/nodejs/workflows/ci.yml +90 -0
  54. package/stacks/nodejs/workflows/security.yml +45 -0
  55. package/stacks/php/skills/api-design/SKILL.md +5 -0
  56. package/stacks/php/skills/api-security/SKILL.md +5 -0
  57. package/stacks/php/skills/composer-workflow/SKILL.md +5 -0
  58. package/stacks/php/skills/external-api-patterns/SKILL.md +5 -0
  59. package/stacks/php/skills/inertia-react/SKILL.md +5 -0
  60. package/stacks/php/skills/laravel-inertia-i18n/SKILL.md +5 -0
  61. package/stacks/php/skills/laravel-octane/SKILL.md +5 -0
  62. package/stacks/php/skills/laravel-patterns/SKILL.md +5 -0
  63. package/stacks/php/skills/mariadb-octane/SKILL.md +5 -0
  64. package/stacks/php/skills/php-patterns/SKILL.md +5 -0
  65. package/stacks/php/skills/phpstan-analysis/SKILL.md +5 -0
  66. package/stacks/php/skills/phpunit-testing/SKILL.md +5 -0
  67. package/stacks/php/skills/security-scan-php/SKILL.md +5 -0
  68. package/stacks/php/workflows/ci.yml +106 -0
  69. package/stacks/php/workflows/security.yml +36 -0
  70. package/stacks/python/skills/api-security-python/SKILL.md +312 -0
  71. package/stacks/python/skills/async-patterns/SKILL.md +5 -0
  72. package/stacks/python/skills/django-patterns/SKILL.md +5 -0
  73. package/stacks/python/skills/fastapi-patterns/SKILL.md +5 -0
  74. package/stacks/python/skills/pydantic-validation/SKILL.md +5 -0
  75. package/stacks/python/skills/pytest-testing/SKILL.md +5 -0
  76. package/stacks/python/skills/python-patterns/SKILL.md +26 -5
  77. package/stacks/python/skills/python-performance/SKILL.md +5 -0
  78. package/stacks/python/skills/scripting-automation/SKILL.md +260 -0
  79. package/stacks/python/stack.json +70 -35
  80. package/stacks/python/workflows/ci.yml +76 -0
  81. package/stacks/python/workflows/security.yml +56 -0
  82. package/templates/CLAUDE-python.md +315 -0
@@ -0,0 +1,312 @@
1
+ ---
2
+ name: api-security-python
3
+ version: 1.0.0
4
+ description: Production-grade API hardening for Python (FastAPI, Django, Flask). Rate limit, CORS, JWT, secure cookies, CSRF, OAuth2.
5
+ ---
6
+
7
+ # API Security — Python
8
+
9
+ **ALWAYS invoke when building API endpoints, auth flows, or admin actions.**
10
+
11
+ > Pair this with `security-baseline` for OWASP Top 10. This skill is stack-specific hardening.
12
+
13
+ ## Layered Defense
14
+
15
+ ```
16
+ Edge (CDN/WAF) → Rate Limit → CORS → Headers → Auth → Authz → Validate → Logic → Encode → Audit
17
+ ```
18
+
19
+ ---
20
+
21
+ ## 1. Security Headers
22
+
23
+ ### FastAPI — middleware
24
+ ```python
25
+ from fastapi import FastAPI
26
+ from starlette.middleware.base import BaseHTTPMiddleware
27
+
28
+ class SecurityHeaders(BaseHTTPMiddleware):
29
+ async def dispatch(self, request, call_next):
30
+ response = await call_next(request)
31
+ response.headers["Strict-Transport-Security"] = "max-age=63072000; includeSubDomains; preload"
32
+ response.headers["X-Content-Type-Options"] = "nosniff"
33
+ response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
34
+ response.headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=()"
35
+ response.headers["X-Frame-Options"] = "DENY"
36
+ response.headers["Content-Security-Policy"] = (
37
+ "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; "
38
+ "img-src 'self' data: https:; frame-ancestors 'none'"
39
+ )
40
+ return response
41
+
42
+ app = FastAPI()
43
+ app.add_middleware(SecurityHeaders)
44
+ ```
45
+
46
+ ### Django — `settings.py`
47
+ ```python
48
+ SECURE_HSTS_SECONDS = 63072000
49
+ SECURE_HSTS_INCLUDE_SUBDOMAINS = True
50
+ SECURE_HSTS_PRELOAD = True
51
+ SECURE_CONTENT_TYPE_NOSNIFF = True
52
+ SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
53
+ SECURE_SSL_REDIRECT = True
54
+ SESSION_COOKIE_SECURE = True
55
+ SESSION_COOKIE_HTTPONLY = True
56
+ SESSION_COOKIE_SAMESITE = "Lax"
57
+ CSRF_COOKIE_SECURE = True
58
+ CSRF_COOKIE_HTTPONLY = True
59
+ X_FRAME_OPTIONS = "DENY"
60
+ ```
61
+
62
+ ---
63
+
64
+ ## 2. CORS — Strict Allowlist
65
+
66
+ ```python
67
+ from fastapi.middleware.cors import CORSMiddleware
68
+ import os
69
+
70
+ ALLOW = [o.strip() for o in os.getenv("CORS_ORIGINS", "").split(",") if o]
71
+
72
+ app.add_middleware(
73
+ CORSMiddleware,
74
+ allow_origins=ALLOW, # explicit list, NEVER ["*"] with credentials
75
+ allow_credentials=True,
76
+ allow_methods=["GET", "POST", "PATCH", "DELETE"],
77
+ allow_headers=["Authorization", "Content-Type", "X-CSRF-Token"],
78
+ max_age=600,
79
+ )
80
+ ```
81
+
82
+ **Never** `allow_origins=["*"]` with `allow_credentials=True` — browsers reject and auth silently breaks.
83
+
84
+ ---
85
+
86
+ ## 3. Rate Limiting
87
+
88
+ ### FastAPI — `slowapi`
89
+ ```python
90
+ from slowapi import Limiter, _rate_limit_exceeded_handler
91
+ from slowapi.util import get_remote_address
92
+ from slowapi.errors import RateLimitExceeded
93
+
94
+ limiter = Limiter(key_func=get_remote_address, storage_uri="redis://localhost:6379")
95
+ app.state.limiter = limiter
96
+ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
97
+
98
+ @app.post("/auth/login")
99
+ @limiter.limit("5/15minutes")
100
+ async def login(request: Request, body: LoginIn):
101
+ ...
102
+ ```
103
+
104
+ ### Django — `django-ratelimit`
105
+ ```python
106
+ from django_ratelimit.decorators import ratelimit
107
+
108
+ @ratelimit(key='ip', rate='5/15m', block=True)
109
+ def login_view(request):
110
+ ...
111
+ ```
112
+
113
+ **Limits to set:** auth (5/15min), password reset (3/hour), signup (3/hour/IP), generic write (60/min/user).
114
+
115
+ ---
116
+
117
+ ## 4. Cookies
118
+
119
+ ### FastAPI
120
+ ```python
121
+ from fastapi import Response
122
+
123
+ response.set_cookie(
124
+ key="session",
125
+ value=token,
126
+ httponly=True, # JS cannot read
127
+ secure=True, # HTTPS only
128
+ samesite="lax", # 'strict' if no cross-site flows
129
+ max_age=60 * 60 * 24 * 7, # 7d
130
+ path="/",
131
+ domain=os.getenv("COOKIE_DOMAIN"),
132
+ )
133
+ ```
134
+
135
+ ---
136
+
137
+ ## 5. JWT / OAuth2 — FastAPI
138
+
139
+ ```python
140
+ from datetime import datetime, timedelta, timezone
141
+ from jose import jwt, JWTError
142
+ import os, uuid
143
+
144
+ SECRET = os.environ["JWT_SECRET"]
145
+ ALG = "HS256"
146
+
147
+ def issue_access_token(user_id: str, role: str) -> str:
148
+ now = datetime.now(timezone.utc)
149
+ return jwt.encode(
150
+ {
151
+ "sub": user_id,
152
+ "role": role,
153
+ "iat": now,
154
+ "exp": now + timedelta(minutes=15),
155
+ "jti": str(uuid.uuid4()),
156
+ },
157
+ SECRET,
158
+ algorithm=ALG,
159
+ )
160
+
161
+ async def current_user(token: str = Depends(oauth2_scheme)) -> User:
162
+ try:
163
+ payload = jwt.decode(token, SECRET, algorithms=[ALG]) # pin algorithm
164
+ except JWTError:
165
+ raise HTTPException(401, "Invalid token")
166
+ user = await User.get_or_none(id=payload["sub"])
167
+ if not user:
168
+ raise HTTPException(401, "User not found")
169
+ return user
170
+ ```
171
+
172
+ Rules:
173
+ - Access tokens: ≤ 15 min. Refresh tokens: rotate on use, store hash in DB, revocable.
174
+ - Pin `algorithms=[ALG]`. Never accept `alg: none`.
175
+ - Include `jti` for revocation lists.
176
+
177
+ ---
178
+
179
+ ## 6. CSRF
180
+
181
+ ### Django
182
+ Built-in: `django.middleware.csrf.CsrfViewMiddleware`. Always enabled — never disable globally.
183
+ For DRF + SessionAuth, use `@ensure_csrf_cookie` on the view that bootstraps the SPA.
184
+
185
+ ### FastAPI — double-submit cookie
186
+ ```python
187
+ import secrets
188
+ from fastapi import Request, HTTPException
189
+
190
+ CSRF_COOKIE = "csrf-token"
191
+ CSRF_HEADER = "x-csrf-token"
192
+ UNSAFE = {"POST", "PUT", "PATCH", "DELETE"}
193
+
194
+ @app.middleware("http")
195
+ async def csrf(request: Request, call_next):
196
+ if request.method in UNSAFE:
197
+ cookie = request.cookies.get(CSRF_COOKIE)
198
+ header = request.headers.get(CSRF_HEADER)
199
+ if not cookie or cookie != header:
200
+ raise HTTPException(403, "CSRF")
201
+ response = await call_next(request)
202
+ if not request.cookies.get(CSRF_COOKIE):
203
+ response.set_cookie(CSRF_COOKIE, secrets.token_urlsafe(32),
204
+ secure=True, samesite="lax", path="/")
205
+ return response
206
+ ```
207
+
208
+ ---
209
+
210
+ ## 7. Input Validation Boundary (Pydantic)
211
+
212
+ ```python
213
+ from pydantic import BaseModel, EmailStr, Field, ConfigDict
214
+
215
+ class CreateUser(BaseModel):
216
+ model_config = ConfigDict(extra="forbid") # rejects unknown keys → blocks mass assignment
217
+ email: EmailStr = Field(max_length=254)
218
+ age: int = Field(ge=13, le=120)
219
+
220
+ @app.post("/users")
221
+ async def create(body: CreateUser): # FastAPI validates automatically; 422 on failure
222
+ ...
223
+ ```
224
+
225
+ ---
226
+
227
+ ## 8. File Upload
228
+
229
+ ```python
230
+ import magic # python-magic — reads magic bytes
231
+ ALLOWED = {"image/jpeg", "image/png", "image/webp"}
232
+
233
+ async def upload(file: UploadFile = File(...)):
234
+ if file.size and file.size > 10 * 1024 * 1024:
235
+ raise HTTPException(413, "Too large")
236
+ head = await file.read(2048)
237
+ mime = magic.from_buffer(head, mime=True)
238
+ if mime not in ALLOWED:
239
+ raise HTTPException(415, "Unsupported type")
240
+ await file.seek(0)
241
+ # Save with UUID name, outside webroot
242
+ ```
243
+
244
+ ---
245
+
246
+ ## 9. Password Hashing
247
+
248
+ ```python
249
+ from argon2 import PasswordHasher
250
+ ph = PasswordHasher(memory_cost=19_456, time_cost=2, parallelism=1)
251
+
252
+ hashed = ph.hash(password)
253
+ try:
254
+ ph.verify(hashed, attempt)
255
+ except argon2.exceptions.VerifyMismatchError:
256
+ raise HTTPException(401, "Invalid credentials")
257
+
258
+ if ph.check_needs_rehash(hashed): # transparent upgrade
259
+ user.password = ph.hash(attempt)
260
+ ```
261
+
262
+ Argon2id is the modern default. Avoid bare `hashlib`. For Django, the framework handles this — don't reinvent it.
263
+
264
+ ---
265
+
266
+ ## 10. SQL Injection — Use ORM Bindings
267
+
268
+ ```python
269
+ # WRONG — string interpolation
270
+ cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
271
+
272
+ # CORRECT — parameterized
273
+ cursor.execute("SELECT * FROM users WHERE id = %s", [user_id])
274
+
275
+ # CORRECT — ORM
276
+ user = await User.get(id=user_id) # Tortoise / SQLAlchemy / Django ORM
277
+ ```
278
+
279
+ `SQLAlchemy.text()` with `:param` bindings is also safe. Raw f-strings are not.
280
+
281
+ ---
282
+
283
+ ## Endpoint Checklist
284
+
285
+ - [ ] `Depends(current_user)` for protected routes
286
+ - [ ] User ID from `current_user`, **never** from body
287
+ - [ ] Pydantic model with `extra="forbid"`
288
+ - [ ] Authz check on the resource (object-level)
289
+ - [ ] Rate limit applied to auth + writes
290
+ - [ ] No PII in logs
291
+ - [ ] Errors: `HTTPException` with generic detail; full info to logs only
292
+
293
+ ## FORBIDDEN Patterns
294
+
295
+ | Anti-pattern | Reason |
296
+ |---|---|
297
+ | `allow_origins=["*"]` with credentials | Browsers reject; auth breaks |
298
+ | Calling `jwt.decode` without `algorithms=` | `alg: none` and confusion attacks |
299
+ | Storing JWT in `localStorage` | XSS exfiltration |
300
+ | Disabling Django `CsrfViewMiddleware` globally | CSRF wide open |
301
+ | f-string interpolation in SQL | SQL injection |
302
+ | Deserializing untrusted bytes (pickle, marshal, shelve) | RCE via gadget chains — use JSON |
303
+ | `eval` / `exec` on user input | RCE |
304
+ | Logging request body or headers raw | Leaks passwords, cookies, tokens |
305
+ | Shell-mode subprocess with user input | Command injection — use list args, no shell |
306
+
307
+ ## See Also
308
+
309
+ - `security-baseline` — OWASP Top 10
310
+ - `secrets-management` — env vars, rotation
311
+ - `pydantic-validation` — schema patterns
312
+ - `observability` — structured logs without PII
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: async-patterns
3
+ version: 1.0.0
4
+ ---
5
+
1
6
  # Async Python Patterns — Concurrency & Performance
2
7
 
3
8
  **ALWAYS invoke when writing async/await code or concurrent operations.**
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: django-patterns
3
+ version: 1.0.0
4
+ ---
5
+
1
6
  # Django Patterns — Full-Stack Python (Django 5+)
2
7
 
3
8
  **ALWAYS invoke when writing Django models, views, or serializers.**
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: fastapi-patterns
3
+ version: 1.0.0
4
+ ---
5
+
1
6
  # FastAPI Patterns — High-Performance Async APIs
2
7
 
3
8
  **ALWAYS invoke when writing FastAPI routes, dependencies, or middleware.**
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: pydantic-validation
3
+ version: 1.0.0
4
+ ---
5
+
1
6
  # Pydantic Validation — Runtime Type Safety for Python
2
7
 
3
8
  **ALWAYS use Pydantic for API schemas, config, and data boundaries.**
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: pytest-testing
3
+ version: 1.0.0
4
+ ---
5
+
1
6
  # Pytest Testing — Python Testing Patterns
2
7
 
3
8
  **ALWAYS invoke AFTER implementing any feature.**
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: python-patterns
3
+ version: 1.0.0
4
+ ---
5
+
1
6
  # Python Patterns — Architecture & Decision-Making
2
7
 
3
8
  **ALWAYS invoke when making Python architecture decisions.**
@@ -6,11 +11,13 @@
6
11
 
7
12
  ```
8
13
  What are you building?
9
- ├── API / Microservices → FastAPI (async, Pydantic, fast)
10
- ├── Full-stack / CMS / Admin → Django (batteries-included)
11
- ├── Simple / Script → Flask (minimal)
12
- ├── AI/ML API serving → FastAPI (Pydantic, uvicorn)
13
- └── Background workers Celery + any framework
14
+ ├── API / Microservices → FastAPI (async, Pydantic, fast)
15
+ ├── Full-stack / CMS / Admin → Django (batteries-included)
16
+ ├── Lightweight web app → Flask (minimal)
17
+ ├── AI/ML API serving → FastAPI (Pydantic, uvicorn)
18
+ ├── Local scripts / automation Scripts (httpx, argparse, rich)
19
+ ├── WordPress / Ads / ETL → Scripts (no framework needed)
20
+ └── Background workers → Celery + any framework
14
21
  ```
15
22
 
16
23
  ## Async vs Sync
@@ -74,6 +81,20 @@ myproject/
74
81
  └── tests/
75
82
  ```
76
83
 
84
+ ### Local Scripts / Automation
85
+ ```
86
+ project/
87
+ ├── main.py # CLI entry (argparse + match/case)
88
+ ├── scripts/ # One module per task
89
+ ├── lib/
90
+ │ ├── config.py # Pydantic Settings (.env)
91
+ │ ├── http_client.py # httpx + tenacity retry
92
+ │ └── logger.py # rich logging
93
+ ├── data/ # Input/output files
94
+ ├── logs/
95
+ └── tests/
96
+ ```
97
+
77
98
  ## Error Handling
78
99
 
79
100
  ```python
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: python-performance
3
+ version: 1.0.0
4
+ ---
5
+
1
6
  # Python Performance — Profiling & Optimization
2
7
 
3
8
  **ALWAYS invoke when optimizing slow Python code.**
@@ -0,0 +1,260 @@
1
+ ---
2
+ name: scripting-automation
3
+ version: 1.0.0
4
+ ---
5
+
6
+ # Local Scripts & Automation — Python 3.12+
7
+
8
+ **ALWAYS invoke when building local scripts, CLI tools, or automation tasks.**
9
+
10
+ ## When to Use
11
+
12
+ - WordPress API automation (create/update posts, manage media)
13
+ - Ad campaign management (Google Ads, Facebook Ads, TikTok Ads API)
14
+ - Data pipelines (CSV/Excel processing, database sync)
15
+ - Web scraping and data extraction
16
+ - File system operations and batch processing
17
+ - Scheduled tasks and cron-like automation
18
+ - API integrations without a web framework
19
+
20
+ ## Project Structure
21
+
22
+ ```
23
+ project/
24
+ ├── pyproject.toml # Dependencies + project metadata
25
+ ├── .env # API keys, credentials (NEVER commit)
26
+ ├── .env.example # Template without real values
27
+ ├── scripts/
28
+ │ ├── __init__.py
29
+ │ ├── wordpress.py # WordPress automation
30
+ │ ├── ads_manager.py # Ad campaigns
31
+ │ └── data_sync.py # Database sync
32
+ ├── lib/
33
+ │ ├── __init__.py
34
+ │ ├── http_client.py # Reusable httpx client
35
+ │ ├── config.py # Pydantic Settings
36
+ │ ├── logger.py # Structured logging
37
+ │ └── retry.py # Retry with backoff
38
+ ├── data/ # Input/output data files
39
+ ├── logs/ # Log files
40
+ ├── tests/
41
+ │ └── test_scripts.py
42
+ └── main.py # Entry point / CLI
43
+ ```
44
+
45
+ ## Configuration (Pydantic Settings)
46
+
47
+ ```python
48
+ from pydantic_settings import BaseSettings
49
+
50
+ class Settings(BaseSettings):
51
+ WP_URL: str
52
+ WP_USER: str
53
+ WP_APP_PASSWORD: str
54
+
55
+ GOOGLE_ADS_DEVELOPER_TOKEN: str = ""
56
+ FACEBOOK_ACCESS_TOKEN: str = ""
57
+ TIKTOK_ACCESS_TOKEN: str = ""
58
+
59
+ DB_HOST: str = "localhost"
60
+ DB_PORT: int = 3306
61
+ DB_NAME: str
62
+ DB_USER: str
63
+ DB_PASSWORD: str
64
+
65
+ LOG_LEVEL: str = "INFO"
66
+ DRY_RUN: bool = False
67
+
68
+ model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
69
+
70
+ settings = Settings()
71
+ ```
72
+
73
+ ## HTTP Client (reusable)
74
+
75
+ ```python
76
+ import httpx
77
+ from tenacity import retry, stop_after_attempt, wait_exponential
78
+
79
+ class ApiClient:
80
+ def __init__(self, base_url: str, auth: tuple[str, str] | None = None):
81
+ self.client = httpx.Client(
82
+ base_url=base_url,
83
+ auth=auth,
84
+ timeout=30.0,
85
+ headers={"User-Agent": "AutomationScript/1.0"},
86
+ )
87
+
88
+ @retry(stop=stop_after_attempt(3), wait=wait_exponential(min=1, max=10))
89
+ def get(self, path: str, **kwargs) -> dict:
90
+ r = self.client.get(path, **kwargs)
91
+ r.raise_for_status()
92
+ return r.json()
93
+
94
+ @retry(stop=stop_after_attempt(3), wait=wait_exponential(min=1, max=10))
95
+ def post(self, path: str, **kwargs) -> dict:
96
+ r = self.client.post(path, **kwargs)
97
+ r.raise_for_status()
98
+ return r.json()
99
+
100
+ def close(self):
101
+ self.client.close()
102
+ ```
103
+
104
+ ## WordPress REST API Pattern
105
+
106
+ ```python
107
+ from lib.http_client import ApiClient
108
+ from lib.config import settings
109
+
110
+ wp = ApiClient(
111
+ base_url=f"{settings.WP_URL}/wp-json/wp/v2",
112
+ auth=(settings.WP_USER, settings.WP_APP_PASSWORD),
113
+ )
114
+
115
+ def create_post(title: str, content: str, status: str = "draft") -> dict:
116
+ return wp.post("/posts", json={
117
+ "title": title,
118
+ "content": content,
119
+ "status": status,
120
+ })
121
+
122
+ def update_post(post_id: int, **fields) -> dict:
123
+ return wp.post(f"/posts/{post_id}", json=fields)
124
+
125
+ def bulk_update_posts(posts: list[dict]) -> list[dict]:
126
+ results = []
127
+ for post in posts:
128
+ pid = post.pop("id")
129
+ result = update_post(pid, **post)
130
+ results.append(result)
131
+ logger.info(f"Updated post {pid}: {result['title']['rendered']}")
132
+ return results
133
+ ```
134
+
135
+ ## Database Access (direct, no ORM)
136
+
137
+ ```python
138
+ import mariadb
139
+ from contextlib import contextmanager
140
+ from lib.config import settings
141
+
142
+ @contextmanager
143
+ def get_connection():
144
+ conn = mariadb.connect(
145
+ host=settings.DB_HOST,
146
+ port=settings.DB_PORT,
147
+ user=settings.DB_USER,
148
+ password=settings.DB_PASSWORD,
149
+ database=settings.DB_NAME,
150
+ )
151
+ try:
152
+ yield conn
153
+ finally:
154
+ conn.close()
155
+
156
+ def fetch_all(query: str, params: tuple = ()) -> list[dict]:
157
+ with get_connection() as conn:
158
+ cursor = conn.cursor(dictionary=True)
159
+ cursor.execute(query, params)
160
+ return cursor.fetchall()
161
+
162
+ def execute(query: str, params: tuple = ()) -> int:
163
+ with get_connection() as conn:
164
+ cursor = conn.cursor()
165
+ cursor.execute(query, params)
166
+ conn.commit()
167
+ return cursor.rowcount
168
+ ```
169
+
170
+ ## CLI Entry Point
171
+
172
+ ```python
173
+ import argparse
174
+ import logging
175
+ from lib.config import settings
176
+
177
+ logging.basicConfig(
178
+ level=getattr(logging, settings.LOG_LEVEL),
179
+ format="%(asctime)s [%(levelname)s] %(message)s",
180
+ handlers=[
181
+ logging.StreamHandler(),
182
+ logging.FileHandler("logs/script.log"),
183
+ ],
184
+ )
185
+ logger = logging.getLogger(__name__)
186
+
187
+ def main():
188
+ parser = argparse.ArgumentParser(description="Automation Scripts")
189
+ sub = parser.add_subparsers(dest="command")
190
+
191
+ sub.add_parser("wp-sync", help="Sync WordPress posts")
192
+ sub.add_parser("ads-report", help="Generate ads performance report")
193
+ sub.add_parser("db-migrate", help="Run data migration")
194
+
195
+ args = parser.parse_args()
196
+
197
+ if settings.DRY_RUN:
198
+ logger.warning("DRY RUN mode — no changes will be saved")
199
+
200
+ match args.command:
201
+ case "wp-sync":
202
+ from scripts.wordpress import sync_posts
203
+ sync_posts()
204
+ case "ads-report":
205
+ from scripts.ads_manager import generate_report
206
+ generate_report()
207
+ case "db-migrate":
208
+ from scripts.data_sync import run_migration
209
+ run_migration()
210
+ case _:
211
+ parser.print_help()
212
+
213
+ if __name__ == "__main__":
214
+ main()
215
+ ```
216
+
217
+ ## Essential Libraries
218
+
219
+ ```toml
220
+ # pyproject.toml
221
+ [project]
222
+ dependencies = [
223
+ "httpx>=0.27",
224
+ "pydantic-settings>=2.0",
225
+ "tenacity>=8.0",
226
+ "python-dotenv>=1.0",
227
+ "mariadb>=1.1",
228
+ "rich>=13.0",
229
+ ]
230
+
231
+ [project.optional-dependencies]
232
+ dev = ["pytest>=8.0", "mypy>=1.8", "ruff>=0.3"]
233
+ ads = ["google-ads>=24.0", "facebook-business>=19.0"]
234
+ ```
235
+
236
+ ## Logging (structured)
237
+
238
+ ```python
239
+ from rich.console import Console
240
+ from rich.logging import RichHandler
241
+ import logging
242
+
243
+ console = Console()
244
+ logging.basicConfig(
245
+ level="INFO",
246
+ format="%(message)s",
247
+ handlers=[RichHandler(console=console, rich_tracebacks=True)],
248
+ )
249
+ ```
250
+
251
+ ## FORBIDDEN
252
+
253
+ 1. **Hardcoded credentials** — use `.env` + Pydantic Settings
254
+ 2. **No error handling on API calls** — always try/except + retry
255
+ 3. **No logging** — every script must log actions and errors
256
+ 4. **`requests` library** — use `httpx` (modern, sync+async)
257
+ 5. **Print statements for output** — use `logging` or `rich`
258
+ 6. **No `--dry-run` flag** — destructive scripts must support dry run
259
+ 7. **SQL without parameterization** — always use `?` placeholders
260
+ 8. **No `.env.example`** — always provide template for credentials