start-vibing-stacks 2.5.0 → 2.6.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/dist/detector.js +5 -2
- package/dist/scanner.js +91 -0
- package/package.json +1 -1
- package/stacks/python/skills/python-patterns/SKILL.md +21 -5
- package/stacks/python/skills/scripting-automation/SKILL.md +255 -0
- package/stacks/python/stack.json +74 -21
- package/templates/CLAUDE-python.md +315 -0
package/dist/detector.js
CHANGED
|
@@ -114,10 +114,8 @@ export function detectNodeFramework(projectDir) {
|
|
|
114
114
|
return null;
|
|
115
115
|
}
|
|
116
116
|
export function detectPythonFramework(projectDir) {
|
|
117
|
-
// Check manage.py (Django)
|
|
118
117
|
if (existsSync(join(projectDir, 'manage.py')))
|
|
119
118
|
return 'django';
|
|
120
|
-
// Check for FastAPI in requirements or pyproject
|
|
121
119
|
for (const reqFile of ['requirements.txt', 'pyproject.toml', 'Pipfile']) {
|
|
122
120
|
const filePath = join(projectDir, reqFile);
|
|
123
121
|
if (existsSync(filePath)) {
|
|
@@ -128,5 +126,10 @@ export function detectPythonFramework(projectDir) {
|
|
|
128
126
|
return 'flask';
|
|
129
127
|
}
|
|
130
128
|
}
|
|
129
|
+
// If Python project detected but no web framework, suggest scripts
|
|
130
|
+
if (existsSync(join(projectDir, 'main.py')) &&
|
|
131
|
+
!existsSync(join(projectDir, 'app', 'main.py'))) {
|
|
132
|
+
return 'scripts';
|
|
133
|
+
}
|
|
131
134
|
return null;
|
|
132
135
|
}
|
package/dist/scanner.js
CHANGED
|
@@ -322,6 +322,69 @@ function scanEslintConfig(projectDir) {
|
|
|
322
322
|
],
|
|
323
323
|
};
|
|
324
324
|
}
|
|
325
|
+
// ─── Python Scanners ───────────────────────────────────────────────────────
|
|
326
|
+
const PYTHON_PACKAGES = {
|
|
327
|
+
'fastapi': { category: 'framework', name: 'FastAPI framework' },
|
|
328
|
+
'django': { category: 'framework', name: 'Django framework' },
|
|
329
|
+
'flask': { category: 'framework', name: 'Flask framework' },
|
|
330
|
+
'uvicorn': { category: 'server', name: 'Uvicorn ASGI server' },
|
|
331
|
+
'gunicorn': { category: 'server', name: 'Gunicorn WSGI server' },
|
|
332
|
+
'sqlalchemy': { category: 'database', name: 'SQLAlchemy ORM' },
|
|
333
|
+
'alembic': { category: 'database', name: 'Alembic migrations' },
|
|
334
|
+
'asyncpg': { category: 'database', name: 'asyncpg (PostgreSQL async)' },
|
|
335
|
+
'psycopg': { category: 'database', name: 'psycopg (PostgreSQL)' },
|
|
336
|
+
'mariadb': { category: 'database', name: 'MariaDB connector' },
|
|
337
|
+
'pymongo': { category: 'database', name: 'PyMongo (MongoDB)' },
|
|
338
|
+
'beanie': { category: 'database', name: 'Beanie ODM (MongoDB)' },
|
|
339
|
+
'motor': { category: 'database', name: 'Motor (MongoDB async)' },
|
|
340
|
+
'pydantic': { category: 'validation', name: 'Pydantic v2 validation' },
|
|
341
|
+
'pydantic-settings': { category: 'config', name: 'Pydantic Settings' },
|
|
342
|
+
'httpx': { category: 'http', name: 'httpx HTTP client' },
|
|
343
|
+
'tenacity': { category: 'reliability', name: 'tenacity retry logic' },
|
|
344
|
+
'celery': { category: 'queue', name: 'Celery task queue' },
|
|
345
|
+
'arq': { category: 'queue', name: 'ARQ async task queue' },
|
|
346
|
+
'redis': { category: 'cache', name: 'Redis client' },
|
|
347
|
+
'pytest': { category: 'testing', name: 'pytest testing' },
|
|
348
|
+
'pytest-asyncio': { category: 'testing', name: 'pytest async support' },
|
|
349
|
+
'mypy': { category: 'quality', name: 'mypy type checker' },
|
|
350
|
+
'ruff': { category: 'quality', name: 'ruff linter + formatter' },
|
|
351
|
+
'rich': { category: 'ui', name: 'rich CLI output' },
|
|
352
|
+
'typer': { category: 'cli', name: 'Typer CLI framework' },
|
|
353
|
+
'click': { category: 'cli', name: 'Click CLI framework' },
|
|
354
|
+
'stripe': { category: 'billing', name: 'Stripe payments' },
|
|
355
|
+
'google-ads': { category: 'ads', name: 'Google Ads API' },
|
|
356
|
+
'facebook-business': { category: 'ads', name: 'Facebook/Meta Ads API' },
|
|
357
|
+
'python-wordpress-xmlrpc': { category: 'cms', name: 'WordPress XML-RPC client' },
|
|
358
|
+
};
|
|
359
|
+
function scanPyprojectToml(projectDir) {
|
|
360
|
+
const content = readFileIfExists(join(projectDir, 'pyproject.toml')) ||
|
|
361
|
+
readFileIfExists(join(projectDir, 'requirements.txt'));
|
|
362
|
+
if (!content)
|
|
363
|
+
return null;
|
|
364
|
+
const patterns = [];
|
|
365
|
+
const lowerContent = content.toLowerCase();
|
|
366
|
+
for (const [pkg, meta] of Object.entries(PYTHON_PACKAGES)) {
|
|
367
|
+
if (lowerContent.includes(pkg.toLowerCase())) {
|
|
368
|
+
patterns.push({
|
|
369
|
+
category: meta.category,
|
|
370
|
+
name: meta.name,
|
|
371
|
+
confidence: 95,
|
|
372
|
+
detail: `Found: ${pkg}`,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const pythonVersionMatch = content.match(/requires-python\s*=\s*"([^"]+)"/i);
|
|
377
|
+
if (pythonVersionMatch) {
|
|
378
|
+
patterns.push({
|
|
379
|
+
category: 'runtime',
|
|
380
|
+
name: `Python version constraint: ${pythonVersionMatch[1]}`,
|
|
381
|
+
confidence: 100,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
if (patterns.length === 0)
|
|
385
|
+
return null;
|
|
386
|
+
return { source: 'pyproject.toml', patterns };
|
|
387
|
+
}
|
|
325
388
|
// ─── Shared Scanners ───────────────────────────────────────────────────────
|
|
326
389
|
function scanProjectFiles(projectDir) {
|
|
327
390
|
const patterns = [];
|
|
@@ -399,6 +462,33 @@ function scanProjectFiles(projectDir) {
|
|
|
399
462
|
else if (existsSync(join(projectDir, 'yarn.lock'))) {
|
|
400
463
|
patterns.push({ category: 'runtime', name: 'Yarn package manager in use', confidence: 100 });
|
|
401
464
|
}
|
|
465
|
+
// ── Python: Project configs ──
|
|
466
|
+
if (existsSync(join(projectDir, 'pyproject.toml'))) {
|
|
467
|
+
patterns.push({ category: 'runtime', name: 'pyproject.toml present (modern Python)', confidence: 100 });
|
|
468
|
+
}
|
|
469
|
+
if (existsSync(join(projectDir, 'uv.lock'))) {
|
|
470
|
+
patterns.push({ category: 'runtime', name: 'uv package manager in use', confidence: 100 });
|
|
471
|
+
}
|
|
472
|
+
else if (existsSync(join(projectDir, 'Pipfile.lock'))) {
|
|
473
|
+
patterns.push({ category: 'runtime', name: 'Pipenv package manager in use', confidence: 100 });
|
|
474
|
+
}
|
|
475
|
+
if (existsSync(join(projectDir, 'mypy.ini')) || existsSync(join(projectDir, '.mypy.ini'))) {
|
|
476
|
+
patterns.push({ category: 'quality', name: 'mypy config present', confidence: 100 });
|
|
477
|
+
}
|
|
478
|
+
if (existsSync(join(projectDir, 'ruff.toml')) || existsSync(join(projectDir, '.ruff.toml'))) {
|
|
479
|
+
patterns.push({ category: 'quality', name: 'ruff config present', confidence: 100 });
|
|
480
|
+
}
|
|
481
|
+
if (existsSync(join(projectDir, 'alembic.ini'))) {
|
|
482
|
+
patterns.push({ category: 'database', name: 'Alembic migrations present', confidence: 100 });
|
|
483
|
+
}
|
|
484
|
+
if (existsSync(join(projectDir, 'pytest.ini')) || existsSync(join(projectDir, 'pyproject.toml'))) {
|
|
485
|
+
if (existsSync(join(projectDir, 'tests'))) {
|
|
486
|
+
patterns.push({ category: 'testing', name: 'pytest test directory present', confidence: 90 });
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (existsSync(join(projectDir, '.pre-commit-config.yaml'))) {
|
|
490
|
+
patterns.push({ category: 'quality', name: 'pre-commit hooks configured', confidence: 100 });
|
|
491
|
+
}
|
|
402
492
|
// ── Deploy targets ──
|
|
403
493
|
if (existsSync(join(projectDir, 'vercel.json'))) {
|
|
404
494
|
patterns.push({ category: 'deploy', name: 'Vercel deployment config', confidence: 100 });
|
|
@@ -485,6 +575,7 @@ export function scanProjectStandards(projectDir) {
|
|
|
485
575
|
scanPackageJson,
|
|
486
576
|
scanTsConfig,
|
|
487
577
|
scanEslintConfig,
|
|
578
|
+
scanPyprojectToml,
|
|
488
579
|
scanProjectFiles,
|
|
489
580
|
];
|
|
490
581
|
const results = [];
|
package/package.json
CHANGED
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
What are you building?
|
|
9
|
-
├── API / Microservices
|
|
10
|
-
├── Full-stack / CMS / Admin
|
|
11
|
-
├──
|
|
12
|
-
├── AI/ML API serving
|
|
13
|
-
|
|
9
|
+
├── API / Microservices → FastAPI (async, Pydantic, fast)
|
|
10
|
+
├── Full-stack / CMS / Admin → Django (batteries-included)
|
|
11
|
+
├── Lightweight web app → Flask (minimal)
|
|
12
|
+
├── AI/ML API serving → FastAPI (Pydantic, uvicorn)
|
|
13
|
+
├── Local scripts / automation → Scripts (httpx, argparse, rich)
|
|
14
|
+
├── WordPress / Ads / ETL → Scripts (no framework needed)
|
|
15
|
+
└── Background workers → Celery + any framework
|
|
14
16
|
```
|
|
15
17
|
|
|
16
18
|
## Async vs Sync
|
|
@@ -74,6 +76,20 @@ myproject/
|
|
|
74
76
|
└── tests/
|
|
75
77
|
```
|
|
76
78
|
|
|
79
|
+
### Local Scripts / Automation
|
|
80
|
+
```
|
|
81
|
+
project/
|
|
82
|
+
├── main.py # CLI entry (argparse + match/case)
|
|
83
|
+
├── scripts/ # One module per task
|
|
84
|
+
├── lib/
|
|
85
|
+
│ ├── config.py # Pydantic Settings (.env)
|
|
86
|
+
│ ├── http_client.py # httpx + tenacity retry
|
|
87
|
+
│ └── logger.py # rich logging
|
|
88
|
+
├── data/ # Input/output files
|
|
89
|
+
├── logs/
|
|
90
|
+
└── tests/
|
|
91
|
+
```
|
|
92
|
+
|
|
77
93
|
## Error Handling
|
|
78
94
|
|
|
79
95
|
```python
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# Local Scripts & Automation — Python 3.12+
|
|
2
|
+
|
|
3
|
+
**ALWAYS invoke when building local scripts, CLI tools, or automation tasks.**
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
- WordPress API automation (create/update posts, manage media)
|
|
8
|
+
- Ad campaign management (Google Ads, Facebook Ads, TikTok Ads API)
|
|
9
|
+
- Data pipelines (CSV/Excel processing, database sync)
|
|
10
|
+
- Web scraping and data extraction
|
|
11
|
+
- File system operations and batch processing
|
|
12
|
+
- Scheduled tasks and cron-like automation
|
|
13
|
+
- API integrations without a web framework
|
|
14
|
+
|
|
15
|
+
## Project Structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
project/
|
|
19
|
+
├── pyproject.toml # Dependencies + project metadata
|
|
20
|
+
├── .env # API keys, credentials (NEVER commit)
|
|
21
|
+
├── .env.example # Template without real values
|
|
22
|
+
├── scripts/
|
|
23
|
+
│ ├── __init__.py
|
|
24
|
+
│ ├── wordpress.py # WordPress automation
|
|
25
|
+
│ ├── ads_manager.py # Ad campaigns
|
|
26
|
+
│ └── data_sync.py # Database sync
|
|
27
|
+
├── lib/
|
|
28
|
+
│ ├── __init__.py
|
|
29
|
+
│ ├── http_client.py # Reusable httpx client
|
|
30
|
+
│ ├── config.py # Pydantic Settings
|
|
31
|
+
│ ├── logger.py # Structured logging
|
|
32
|
+
│ └── retry.py # Retry with backoff
|
|
33
|
+
├── data/ # Input/output data files
|
|
34
|
+
├── logs/ # Log files
|
|
35
|
+
├── tests/
|
|
36
|
+
│ └── test_scripts.py
|
|
37
|
+
└── main.py # Entry point / CLI
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Configuration (Pydantic Settings)
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from pydantic_settings import BaseSettings
|
|
44
|
+
|
|
45
|
+
class Settings(BaseSettings):
|
|
46
|
+
WP_URL: str
|
|
47
|
+
WP_USER: str
|
|
48
|
+
WP_APP_PASSWORD: str
|
|
49
|
+
|
|
50
|
+
GOOGLE_ADS_DEVELOPER_TOKEN: str = ""
|
|
51
|
+
FACEBOOK_ACCESS_TOKEN: str = ""
|
|
52
|
+
TIKTOK_ACCESS_TOKEN: str = ""
|
|
53
|
+
|
|
54
|
+
DB_HOST: str = "localhost"
|
|
55
|
+
DB_PORT: int = 3306
|
|
56
|
+
DB_NAME: str
|
|
57
|
+
DB_USER: str
|
|
58
|
+
DB_PASSWORD: str
|
|
59
|
+
|
|
60
|
+
LOG_LEVEL: str = "INFO"
|
|
61
|
+
DRY_RUN: bool = False
|
|
62
|
+
|
|
63
|
+
model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
|
|
64
|
+
|
|
65
|
+
settings = Settings()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## HTTP Client (reusable)
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import httpx
|
|
72
|
+
from tenacity import retry, stop_after_attempt, wait_exponential
|
|
73
|
+
|
|
74
|
+
class ApiClient:
|
|
75
|
+
def __init__(self, base_url: str, auth: tuple[str, str] | None = None):
|
|
76
|
+
self.client = httpx.Client(
|
|
77
|
+
base_url=base_url,
|
|
78
|
+
auth=auth,
|
|
79
|
+
timeout=30.0,
|
|
80
|
+
headers={"User-Agent": "AutomationScript/1.0"},
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@retry(stop=stop_after_attempt(3), wait=wait_exponential(min=1, max=10))
|
|
84
|
+
def get(self, path: str, **kwargs) -> dict:
|
|
85
|
+
r = self.client.get(path, **kwargs)
|
|
86
|
+
r.raise_for_status()
|
|
87
|
+
return r.json()
|
|
88
|
+
|
|
89
|
+
@retry(stop=stop_after_attempt(3), wait=wait_exponential(min=1, max=10))
|
|
90
|
+
def post(self, path: str, **kwargs) -> dict:
|
|
91
|
+
r = self.client.post(path, **kwargs)
|
|
92
|
+
r.raise_for_status()
|
|
93
|
+
return r.json()
|
|
94
|
+
|
|
95
|
+
def close(self):
|
|
96
|
+
self.client.close()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## WordPress REST API Pattern
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from lib.http_client import ApiClient
|
|
103
|
+
from lib.config import settings
|
|
104
|
+
|
|
105
|
+
wp = ApiClient(
|
|
106
|
+
base_url=f"{settings.WP_URL}/wp-json/wp/v2",
|
|
107
|
+
auth=(settings.WP_USER, settings.WP_APP_PASSWORD),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def create_post(title: str, content: str, status: str = "draft") -> dict:
|
|
111
|
+
return wp.post("/posts", json={
|
|
112
|
+
"title": title,
|
|
113
|
+
"content": content,
|
|
114
|
+
"status": status,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
def update_post(post_id: int, **fields) -> dict:
|
|
118
|
+
return wp.post(f"/posts/{post_id}", json=fields)
|
|
119
|
+
|
|
120
|
+
def bulk_update_posts(posts: list[dict]) -> list[dict]:
|
|
121
|
+
results = []
|
|
122
|
+
for post in posts:
|
|
123
|
+
pid = post.pop("id")
|
|
124
|
+
result = update_post(pid, **post)
|
|
125
|
+
results.append(result)
|
|
126
|
+
logger.info(f"Updated post {pid}: {result['title']['rendered']}")
|
|
127
|
+
return results
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Database Access (direct, no ORM)
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
import mariadb
|
|
134
|
+
from contextlib import contextmanager
|
|
135
|
+
from lib.config import settings
|
|
136
|
+
|
|
137
|
+
@contextmanager
|
|
138
|
+
def get_connection():
|
|
139
|
+
conn = mariadb.connect(
|
|
140
|
+
host=settings.DB_HOST,
|
|
141
|
+
port=settings.DB_PORT,
|
|
142
|
+
user=settings.DB_USER,
|
|
143
|
+
password=settings.DB_PASSWORD,
|
|
144
|
+
database=settings.DB_NAME,
|
|
145
|
+
)
|
|
146
|
+
try:
|
|
147
|
+
yield conn
|
|
148
|
+
finally:
|
|
149
|
+
conn.close()
|
|
150
|
+
|
|
151
|
+
def fetch_all(query: str, params: tuple = ()) -> list[dict]:
|
|
152
|
+
with get_connection() as conn:
|
|
153
|
+
cursor = conn.cursor(dictionary=True)
|
|
154
|
+
cursor.execute(query, params)
|
|
155
|
+
return cursor.fetchall()
|
|
156
|
+
|
|
157
|
+
def execute(query: str, params: tuple = ()) -> int:
|
|
158
|
+
with get_connection() as conn:
|
|
159
|
+
cursor = conn.cursor()
|
|
160
|
+
cursor.execute(query, params)
|
|
161
|
+
conn.commit()
|
|
162
|
+
return cursor.rowcount
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## CLI Entry Point
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
import argparse
|
|
169
|
+
import logging
|
|
170
|
+
from lib.config import settings
|
|
171
|
+
|
|
172
|
+
logging.basicConfig(
|
|
173
|
+
level=getattr(logging, settings.LOG_LEVEL),
|
|
174
|
+
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
175
|
+
handlers=[
|
|
176
|
+
logging.StreamHandler(),
|
|
177
|
+
logging.FileHandler("logs/script.log"),
|
|
178
|
+
],
|
|
179
|
+
)
|
|
180
|
+
logger = logging.getLogger(__name__)
|
|
181
|
+
|
|
182
|
+
def main():
|
|
183
|
+
parser = argparse.ArgumentParser(description="Automation Scripts")
|
|
184
|
+
sub = parser.add_subparsers(dest="command")
|
|
185
|
+
|
|
186
|
+
sub.add_parser("wp-sync", help="Sync WordPress posts")
|
|
187
|
+
sub.add_parser("ads-report", help="Generate ads performance report")
|
|
188
|
+
sub.add_parser("db-migrate", help="Run data migration")
|
|
189
|
+
|
|
190
|
+
args = parser.parse_args()
|
|
191
|
+
|
|
192
|
+
if settings.DRY_RUN:
|
|
193
|
+
logger.warning("DRY RUN mode — no changes will be saved")
|
|
194
|
+
|
|
195
|
+
match args.command:
|
|
196
|
+
case "wp-sync":
|
|
197
|
+
from scripts.wordpress import sync_posts
|
|
198
|
+
sync_posts()
|
|
199
|
+
case "ads-report":
|
|
200
|
+
from scripts.ads_manager import generate_report
|
|
201
|
+
generate_report()
|
|
202
|
+
case "db-migrate":
|
|
203
|
+
from scripts.data_sync import run_migration
|
|
204
|
+
run_migration()
|
|
205
|
+
case _:
|
|
206
|
+
parser.print_help()
|
|
207
|
+
|
|
208
|
+
if __name__ == "__main__":
|
|
209
|
+
main()
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Essential Libraries
|
|
213
|
+
|
|
214
|
+
```toml
|
|
215
|
+
# pyproject.toml
|
|
216
|
+
[project]
|
|
217
|
+
dependencies = [
|
|
218
|
+
"httpx>=0.27",
|
|
219
|
+
"pydantic-settings>=2.0",
|
|
220
|
+
"tenacity>=8.0",
|
|
221
|
+
"python-dotenv>=1.0",
|
|
222
|
+
"mariadb>=1.1",
|
|
223
|
+
"rich>=13.0",
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
[project.optional-dependencies]
|
|
227
|
+
dev = ["pytest>=8.0", "mypy>=1.8", "ruff>=0.3"]
|
|
228
|
+
ads = ["google-ads>=24.0", "facebook-business>=19.0"]
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Logging (structured)
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from rich.console import Console
|
|
235
|
+
from rich.logging import RichHandler
|
|
236
|
+
import logging
|
|
237
|
+
|
|
238
|
+
console = Console()
|
|
239
|
+
logging.basicConfig(
|
|
240
|
+
level="INFO",
|
|
241
|
+
format="%(message)s",
|
|
242
|
+
handlers=[RichHandler(console=console, rich_tracebacks=True)],
|
|
243
|
+
)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## FORBIDDEN
|
|
247
|
+
|
|
248
|
+
1. **Hardcoded credentials** — use `.env` + Pydantic Settings
|
|
249
|
+
2. **No error handling on API calls** — always try/except + retry
|
|
250
|
+
3. **No logging** — every script must log actions and errors
|
|
251
|
+
4. **`requests` library** — use `httpx` (modern, sync+async)
|
|
252
|
+
5. **Print statements for output** — use `logging` or `rich`
|
|
253
|
+
6. **No `--dry-run` flag** — destructive scripts must support dry run
|
|
254
|
+
7. **SQL without parameterization** — always use `?` placeholders
|
|
255
|
+
8. **No `.env.example`** — always provide template for credentials
|
package/stacks/python/stack.json
CHANGED
|
@@ -2,44 +2,97 @@
|
|
|
2
2
|
"id": "python",
|
|
3
3
|
"name": "Python 3.12+",
|
|
4
4
|
"icon": "🐍",
|
|
5
|
-
"runtime": "
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
"runtime": "Python 3.12+",
|
|
6
|
+
"minVersion": "3.12.0",
|
|
7
|
+
"packageManager": "uv|pip",
|
|
8
|
+
"extensions": [".py", ".pyi"],
|
|
9
|
+
"testExtensions": ["test_*.py", "*_test.py"],
|
|
10
|
+
"detectFiles": ["pyproject.toml", "requirements.txt", "Pipfile", "setup.py", "manage.py"],
|
|
11
|
+
"commands": {
|
|
12
|
+
"test": "pytest --tb=short",
|
|
13
|
+
"lint": "ruff check .",
|
|
14
|
+
"format": "ruff format .",
|
|
15
|
+
"serve": "uvicorn app.main:app --reload",
|
|
16
|
+
"typecheck": "mypy ."
|
|
17
|
+
},
|
|
18
|
+
"qualityGates": [
|
|
19
|
+
{ "name": "mypy", "command": "mypy .", "required": true, "order": 1 },
|
|
20
|
+
{ "name": "ruff", "command": "ruff check .", "required": true, "order": 2 },
|
|
21
|
+
{ "name": "pytest", "command": "pytest --tb=short", "required": true, "order": 3 }
|
|
10
22
|
],
|
|
11
23
|
"frameworks": [
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
24
|
+
{
|
|
25
|
+
"id": "fastapi",
|
|
26
|
+
"name": "FastAPI + Uvicorn",
|
|
27
|
+
"icon": "⚡",
|
|
28
|
+
"detectFiles": ["app/main.py"],
|
|
29
|
+
"default": true,
|
|
30
|
+
"skills": ["fastapi-patterns", "async-patterns"]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "django",
|
|
34
|
+
"name": "Django 5+",
|
|
35
|
+
"icon": "🎸",
|
|
36
|
+
"detectFiles": ["manage.py"],
|
|
37
|
+
"skills": ["django-patterns"]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "flask",
|
|
41
|
+
"name": "Flask",
|
|
42
|
+
"icon": "🧪",
|
|
43
|
+
"detectFiles": ["app.py", "wsgi.py"],
|
|
44
|
+
"skills": []
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "scripts",
|
|
48
|
+
"name": "Local Scripts / Automation",
|
|
49
|
+
"icon": "🤖",
|
|
50
|
+
"skills": ["scripting-automation"]
|
|
51
|
+
}
|
|
16
52
|
],
|
|
17
53
|
"databases": [
|
|
54
|
+
{ "id": "mysql", "name": "MySQL / MariaDB", "icon": "🐬", "default": true },
|
|
18
55
|
{ "id": "postgresql", "name": "PostgreSQL", "icon": "🐘" },
|
|
19
|
-
{ "id": "
|
|
56
|
+
{ "id": "sqlite", "name": "SQLite (local)", "icon": "📦" },
|
|
20
57
|
{ "id": "mongodb", "name": "MongoDB", "icon": "🍃" },
|
|
21
|
-
{ "id": "
|
|
58
|
+
{ "id": "none", "name": "No database", "icon": "⬜" }
|
|
22
59
|
],
|
|
23
60
|
"frontendOptions": [
|
|
24
|
-
{ "id": "react", "name": "React (SPA)", "icon": "⚛️" },
|
|
25
|
-
{ "id": "htmx", "name": "HTMX + Jinja2", "icon": "🔄" },
|
|
26
|
-
{ "id": "none", "name": "API only
|
|
61
|
+
{ "id": "react", "name": "React (SPA)", "icon": "⚛️", "frameworks": ["fastapi", "flask", "django"] },
|
|
62
|
+
{ "id": "htmx", "name": "HTMX + Jinja2", "icon": "🔄", "frameworks": ["fastapi", "flask", "django"] },
|
|
63
|
+
{ "id": "none", "name": "API only / CLI only", "icon": "🔌", "default": true }
|
|
27
64
|
],
|
|
28
65
|
"deployTargets": [
|
|
29
66
|
{ "id": "github", "name": "GitHub (git push)", "icon": "🐙" }
|
|
30
67
|
],
|
|
31
68
|
"skills": [
|
|
32
69
|
"python-patterns",
|
|
33
|
-
"fastapi-patterns",
|
|
34
|
-
"django-patterns",
|
|
35
70
|
"pydantic-validation",
|
|
36
71
|
"pytest-testing",
|
|
37
|
-
"async-patterns",
|
|
38
72
|
"python-performance"
|
|
39
73
|
],
|
|
40
|
-
"
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
74
|
+
"requirements": [
|
|
75
|
+
{
|
|
76
|
+
"name": "Python",
|
|
77
|
+
"command": "python3",
|
|
78
|
+
"versionFlag": "--version",
|
|
79
|
+
"minVersion": "3.12.0",
|
|
80
|
+
"installCommand": {
|
|
81
|
+
"macos": "brew install python@3.12",
|
|
82
|
+
"linux": "sudo apt install -y python3.12 python3.12-venv"
|
|
83
|
+
},
|
|
84
|
+
"versionRegex": "Python\\s+(\\d+\\.\\d+\\.\\d+)"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "pip",
|
|
88
|
+
"command": "pip3",
|
|
89
|
+
"versionFlag": "--version",
|
|
90
|
+
"minVersion": "23.0.0",
|
|
91
|
+
"installCommand": {
|
|
92
|
+
"macos": "python3 -m ensurepip --upgrade",
|
|
93
|
+
"linux": "python3 -m ensurepip --upgrade"
|
|
94
|
+
},
|
|
95
|
+
"versionRegex": "pip\\s+(\\d+\\.\\d+\\.?\\d*)"
|
|
96
|
+
}
|
|
44
97
|
]
|
|
45
98
|
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
> **CHARACTER LIMIT**: Max 40,000 chars. Validate with `wc -m CLAUDE.md` before commit.
|
|
4
|
+
|
|
5
|
+
## Last Change
|
|
6
|
+
|
|
7
|
+
**Branch:** main
|
|
8
|
+
**Date:** {{DATE}}
|
|
9
|
+
**Summary:** Initial project setup with start-vibing-stacks (Python)
|
|
10
|
+
|
|
11
|
+
## 30 Seconds Overview
|
|
12
|
+
|
|
13
|
+
{{PROJECT_NAME}} is a Python 3.12+ project using {{FRAMEWORK}}.
|
|
14
|
+
|
|
15
|
+
## Stack
|
|
16
|
+
|
|
17
|
+
| Component | Technology |
|
|
18
|
+
|-----------|------------|
|
|
19
|
+
| Language | Python >= 3.12 |
|
|
20
|
+
| Framework | {{FRAMEWORK}} |
|
|
21
|
+
| Database | {{DATABASE}} |
|
|
22
|
+
| Type Checking | mypy (strict) |
|
|
23
|
+
| Linting | ruff |
|
|
24
|
+
| Testing | pytest + pytest-asyncio |
|
|
25
|
+
| Validation | Pydantic v2 |
|
|
26
|
+
| HTTP Client | httpx |
|
|
27
|
+
| Package Manager | uv / pip |
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
### FastAPI / Flask Projects
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
project/
|
|
35
|
+
├── CLAUDE.md # This file (40k char max)
|
|
36
|
+
├── pyproject.toml # Dependencies + project config
|
|
37
|
+
├── .env # Secrets (NEVER commit)
|
|
38
|
+
├── .env.example # Template without values
|
|
39
|
+
├── .claude/
|
|
40
|
+
│ ├── agents/ # 6 active subagents
|
|
41
|
+
│ ├── skills/ # Skill systems (auto-injected)
|
|
42
|
+
│ ├── hooks/ # Validation hooks
|
|
43
|
+
│ ├── config/ # Project configuration
|
|
44
|
+
│ └── commands/ # Slash commands
|
|
45
|
+
├── app/
|
|
46
|
+
│ ├── main.py # App entrypoint + startup
|
|
47
|
+
│ ├── api/v1/
|
|
48
|
+
│ │ ├── routes/ # Endpoint modules
|
|
49
|
+
│ │ └── deps.py # Dependencies (auth, db)
|
|
50
|
+
│ ├── models/ # SQLAlchemy / Beanie models
|
|
51
|
+
│ ├── schemas/ # Pydantic schemas
|
|
52
|
+
│ ├── services/ # Business logic layer
|
|
53
|
+
│ ├── core/
|
|
54
|
+
│ │ ├── config.py # Pydantic Settings (env)
|
|
55
|
+
│ │ └── security.py # Auth, hashing
|
|
56
|
+
│ └── utils/ # Helpers
|
|
57
|
+
├── scripts/ # CLI scripts / automation
|
|
58
|
+
├── tests/
|
|
59
|
+
│ ├── conftest.py # Shared fixtures
|
|
60
|
+
│ ├── unit/
|
|
61
|
+
│ └── integration/
|
|
62
|
+
└── alembic/ # DB migrations (if SQL)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Local Scripts / Automation Projects
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
project/
|
|
69
|
+
├── CLAUDE.md
|
|
70
|
+
├── pyproject.toml
|
|
71
|
+
├── .env
|
|
72
|
+
├── .env.example
|
|
73
|
+
├── .claude/
|
|
74
|
+
├── main.py # CLI entry point (argparse)
|
|
75
|
+
├── scripts/
|
|
76
|
+
│ ├── __init__.py
|
|
77
|
+
│ ├── wordpress.py # WordPress API automation
|
|
78
|
+
│ ├── ads_manager.py # Google/Facebook/TikTok Ads
|
|
79
|
+
│ └── data_sync.py # Database sync / ETL
|
|
80
|
+
├── lib/
|
|
81
|
+
│ ├── __init__.py
|
|
82
|
+
│ ├── config.py # Pydantic Settings
|
|
83
|
+
│ ├── http_client.py # Reusable httpx client
|
|
84
|
+
│ ├── logger.py # Structured logging (rich)
|
|
85
|
+
│ └── retry.py # tenacity retry logic
|
|
86
|
+
├── data/ # Input/output data files
|
|
87
|
+
├── logs/ # Log files
|
|
88
|
+
└── tests/
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Django Projects
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
project/
|
|
95
|
+
├── CLAUDE.md
|
|
96
|
+
├── pyproject.toml
|
|
97
|
+
├── manage.py
|
|
98
|
+
├── config/ # Settings, URLs, ASGI
|
|
99
|
+
├── apps/
|
|
100
|
+
│ ├── users/ # Per-app: models, views, serializers, tests
|
|
101
|
+
│ └── products/
|
|
102
|
+
└── tests/
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Critical Rules
|
|
106
|
+
|
|
107
|
+
### Python 3.12+ (MANDATORY)
|
|
108
|
+
|
|
109
|
+
- **Type hints on ALL public functions** — parameters, returns, class attributes
|
|
110
|
+
- **Pydantic v2 for all data boundaries** — API schemas, config, external data
|
|
111
|
+
- **`match/case`** for complex branching (3.10+)
|
|
112
|
+
- **`type` keyword** for simple type aliases (3.12+)
|
|
113
|
+
- **f-strings** everywhere, never `%` or `.format()`
|
|
114
|
+
- **Structural pattern matching** over nested if/elif chains
|
|
115
|
+
|
|
116
|
+
### Code Organization
|
|
117
|
+
|
|
118
|
+
- **Thin routes, fat services** — business logic in `services/`, not in routes/views
|
|
119
|
+
- **Repository pattern** for database access — isolate queries from business logic
|
|
120
|
+
- **Dependency injection** — FastAPI `Depends()`, Django class-based views
|
|
121
|
+
- **One concern per module** — a file should do one thing well
|
|
122
|
+
|
|
123
|
+
### Environment Variables & Secrets (MANDATORY)
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
# CORRECT: Pydantic Settings loads from .env
|
|
127
|
+
from pydantic_settings import BaseSettings
|
|
128
|
+
|
|
129
|
+
class Settings(BaseSettings):
|
|
130
|
+
DATABASE_URL: str
|
|
131
|
+
SECRET_KEY: str
|
|
132
|
+
API_TOKEN: str
|
|
133
|
+
model_config = {"env_file": ".env"}
|
|
134
|
+
|
|
135
|
+
settings = Settings()
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
| Rule | Reason |
|
|
139
|
+
|------|--------|
|
|
140
|
+
| All secrets in `.env` | Never hardcode credentials |
|
|
141
|
+
| `.env` in `.gitignore` | Never commit secrets |
|
|
142
|
+
| `.env.example` always present | Document required variables |
|
|
143
|
+
| Pydantic Settings for loading | Typed, validated, auto-parsed |
|
|
144
|
+
| `--dry-run` flag for destructive scripts | Prevent accidental data loss |
|
|
145
|
+
|
|
146
|
+
### Async vs Sync
|
|
147
|
+
|
|
148
|
+
| Workload | Pattern |
|
|
149
|
+
|----------|---------|
|
|
150
|
+
| I/O-bound (HTTP, DB, files) | `async def` + `httpx` / `asyncpg` |
|
|
151
|
+
| CPU-bound (parsing, computation) | `def` + `multiprocessing` / `concurrent.futures` |
|
|
152
|
+
| Local scripts (simple) | Sync is fine unless hitting APIs in bulk |
|
|
153
|
+
| Bulk API calls | `async def` + `asyncio.gather` + semaphore |
|
|
154
|
+
|
|
155
|
+
### Database Safety
|
|
156
|
+
|
|
157
|
+
- **Parameterized queries ALWAYS** — `cursor.execute(query, params)`
|
|
158
|
+
- **Connection context managers** — auto-close on exit
|
|
159
|
+
- **Migrations** — Alembic (FastAPI) or Django `makemigrations`
|
|
160
|
+
- **Pool connections** in production — `pool_size` + `max_overflow`
|
|
161
|
+
|
|
162
|
+
## Quality Gates
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
mypy . # Type checking (MUST pass)
|
|
166
|
+
ruff check . # Linting (MUST pass)
|
|
167
|
+
ruff format . --check # Format check
|
|
168
|
+
pytest --tb=short # Tests (MUST pass)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## FORBIDDEN
|
|
172
|
+
|
|
173
|
+
### Security (CRITICAL)
|
|
174
|
+
|
|
175
|
+
| Action | Reason |
|
|
176
|
+
|--------|--------|
|
|
177
|
+
| Hardcoded API keys/passwords | Use `.env` + Pydantic Settings |
|
|
178
|
+
| Commit `.env` files | Secrets leak to repository |
|
|
179
|
+
| SQL without parameterization | SQL injection |
|
|
180
|
+
| Deserializing untrusted binary data | Remote code execution risk |
|
|
181
|
+
| `yaml.load()` without SafeLoader | Code injection |
|
|
182
|
+
| No input validation on external data | Use Pydantic models |
|
|
183
|
+
| Storing plaintext passwords | Use `passlib` / `bcrypt` |
|
|
184
|
+
|
|
185
|
+
### Code Quality
|
|
186
|
+
|
|
187
|
+
| Action | Reason |
|
|
188
|
+
|--------|--------|
|
|
189
|
+
| `import *` | Explicit imports only |
|
|
190
|
+
| `requests` library | Use `httpx` (modern, sync+async) |
|
|
191
|
+
| `print()` for logging | Use `logging` module or `rich` |
|
|
192
|
+
| No type hints on public APIs | mypy must pass |
|
|
193
|
+
| Business logic in routes/views | Use services layer |
|
|
194
|
+
| Bare `except Exception` | Catch specific exceptions |
|
|
195
|
+
| `time.sleep()` in async code | Use `await asyncio.sleep()` |
|
|
196
|
+
| Sync HTTP in async context | Blocks the event loop |
|
|
197
|
+
| Global mutable state | Use dependency injection |
|
|
198
|
+
| Files > 400 lines | Split into modules |
|
|
199
|
+
|
|
200
|
+
### Workflow
|
|
201
|
+
|
|
202
|
+
| Action | Reason |
|
|
203
|
+
|--------|--------|
|
|
204
|
+
| Skip tests | Quality gate blocks commit |
|
|
205
|
+
| Skip type checking | mypy catches runtime errors |
|
|
206
|
+
| No `.env.example` | Others can't configure project |
|
|
207
|
+
| No `--dry-run` on destructive scripts | Risk of accidental data loss |
|
|
208
|
+
| Commit directly to main | Use feature branches |
|
|
209
|
+
|
|
210
|
+
## CLAUDE.md Update Rules
|
|
211
|
+
|
|
212
|
+
### When to Update
|
|
213
|
+
|
|
214
|
+
| Change Type | What to Update |
|
|
215
|
+
|-------------|----------------|
|
|
216
|
+
| Any file change | Last Change section |
|
|
217
|
+
| New feature | 30s Overview, Architecture if needed |
|
|
218
|
+
| New pattern | Add to relevant section |
|
|
219
|
+
| Gotcha discovered | Add to FORBIDDEN or NRY |
|
|
220
|
+
| New dependency | Update Stack table |
|
|
221
|
+
|
|
222
|
+
### Last Change Format (MANDATORY)
|
|
223
|
+
|
|
224
|
+
```markdown
|
|
225
|
+
## Last Change
|
|
226
|
+
|
|
227
|
+
**Branch:** feature/example
|
|
228
|
+
**Date:** YYYY-MM-DD
|
|
229
|
+
**Summary:** 1-2 sentences describing WHAT and WHY.
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Agent System
|
|
233
|
+
|
|
234
|
+
| Agent | Purpose |
|
|
235
|
+
|-------|---------|
|
|
236
|
+
| research-web | Researches best practices (MANDATORY for new features) |
|
|
237
|
+
| documenter | Maps files to domains, tracks what exists |
|
|
238
|
+
| domain-updater | Records problems, solutions, learnings |
|
|
239
|
+
| commit-manager | Manages commits and merges |
|
|
240
|
+
| tester | Creates tests with pytest |
|
|
241
|
+
| claude-md-compactor | Compacts CLAUDE.md when over 40k chars |
|
|
242
|
+
|
|
243
|
+
### Skills
|
|
244
|
+
|
|
245
|
+
| Category | Skills |
|
|
246
|
+
|----------|--------|
|
|
247
|
+
| **Core** | python-patterns, pydantic-validation, pytest-testing, python-performance |
|
|
248
|
+
| **Framework** | fastapi-patterns, django-patterns, async-patterns, scripting-automation |
|
|
249
|
+
| **UI** | ui-ux-pro-max (auto-installed for frontend projects) |
|
|
250
|
+
|
|
251
|
+
## Domain Documentation
|
|
252
|
+
|
|
253
|
+
### Location
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
.claude/skills/codebase-knowledge/domains/
|
|
257
|
+
├── api.md
|
|
258
|
+
├── database.md
|
|
259
|
+
├── scripts.md
|
|
260
|
+
└── ...
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Documentation Agents
|
|
264
|
+
|
|
265
|
+
| Agent | Role | When |
|
|
266
|
+
|-------|------|------|
|
|
267
|
+
| **documenter** | Maps files to domains | AFTER implementation |
|
|
268
|
+
| **domain-updater** | Records problems + solutions | BEFORE commit |
|
|
269
|
+
|
|
270
|
+
## Workflow
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
1. TODO LIST → Create detailed task list
|
|
274
|
+
2. RESEARCH → Run research agent for new features
|
|
275
|
+
3. BRANCH → Create feature/ | fix/ | refactor/ | test/
|
|
276
|
+
4. IMPLEMENT → Follow skills + type everything
|
|
277
|
+
5. QUALITY → mypy → ruff → pytest (all must pass)
|
|
278
|
+
6. DOCUMENT → Update domains + CLAUDE.md
|
|
279
|
+
7. COMMIT → Conventional commit format
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Commit Format
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
[type]: [description]
|
|
286
|
+
|
|
287
|
+
- Detail 1
|
|
288
|
+
- Detail 2
|
|
289
|
+
|
|
290
|
+
Generated with Claude Code
|
|
291
|
+
Co-Authored-By: Claude <noreply@anthropic.com>
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Types: `feat`, `fix`, `refactor`, `docs`, `chore`, `test`
|
|
295
|
+
|
|
296
|
+
## NRY (Never Repeat Yourself)
|
|
297
|
+
|
|
298
|
+
- Always check `.claude/skills/` before implementing patterns from scratch
|
|
299
|
+
- Use `tenacity` for retry logic, not hand-rolled loops
|
|
300
|
+
- Use `pydantic_settings` for env config, not `os.getenv()` manually
|
|
301
|
+
- Use `httpx` for HTTP, never `urllib` or `requests`
|
|
302
|
+
- Use `rich` for CLI output, not bare `print()`
|
|
303
|
+
|
|
304
|
+
## Configuration
|
|
305
|
+
|
|
306
|
+
Project-specific settings in `.claude/config/`:
|
|
307
|
+
|
|
308
|
+
- `active-project.json` — Stack, framework, database, skills
|
|
309
|
+
- `security-rules.json` — Security audit rules
|
|
310
|
+
- `standards-review.json` — Imported project standards (if adapted)
|
|
311
|
+
|
|
312
|
+
## Setup by start-vibing
|
|
313
|
+
|
|
314
|
+
This project was set up with `npx start-vibing-stacks`.
|
|
315
|
+
For updates: `npx start-vibing-stacks --force`
|