mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-13 18:00:35 +08:00
feat: salvage Django Celery workflow (#1812)
Source: maintainer-owned salvage of useful Django reviewer/build-resolver/Celery work from stale PR #1310 by mrigank2seven. - add django-reviewer and django-build-resolver agents - add django-celery skill with timezone-aware scheduling example - update catalog counts to 60 agents / 221 skills and record the May 12 salvage gap pass Co-authored-by: MRIGANK GUPTA <mrigank2seven@users.noreply.github.com>
This commit is contained in:
parent
2c8cda03e7
commit
f239379ebf
@ -11,7 +11,7 @@
|
||||
{
|
||||
"name": "ecc",
|
||||
"source": "./",
|
||||
"description": "The most comprehensive Claude Code plugin — 58 agents, 220 skills, 74 legacy command shims, selective install profiles, and production-ready hooks for TDD, security scanning, code review, and continuous learning",
|
||||
"description": "The most comprehensive Claude Code plugin — 60 agents, 221 skills, 74 legacy command shims, selective install profiles, and production-ready hooks for TDD, security scanning, code review, and continuous learning",
|
||||
"version": "2.0.0-rc.1",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ecc",
|
||||
"version": "2.0.0-rc.1",
|
||||
"description": "Battle-tested Claude Code plugin for engineering teams — 58 agents, 220 skills, 74 legacy command shims, production-ready hooks, and selective install workflows evolved through continuous real-world use",
|
||||
"description": "Battle-tested Claude Code plugin for engineering teams — 60 agents, 221 skills, 74 legacy command shims, production-ready hooks, and selective install workflows evolved through continuous real-world use",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
"url": "https://x.com/affaanmustafa"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Everything Claude Code (ECC) — Agent Instructions
|
||||
|
||||
This is a **production-ready AI coding plugin** providing 58 specialized agents, 220 skills, 74 commands, and automated hook workflows for software development.
|
||||
This is a **production-ready AI coding plugin** providing 60 specialized agents, 221 skills, 74 commands, and automated hook workflows for software development.
|
||||
|
||||
**Version:** 2.0.0-rc.1
|
||||
|
||||
@ -35,6 +35,8 @@ This is a **production-ready AI coding plugin** providing 58 specialized agents,
|
||||
| kotlin-build-resolver | Kotlin/Gradle build errors | Kotlin build failures |
|
||||
| database-reviewer | PostgreSQL/Supabase specialist | Schema design, query optimization |
|
||||
| python-reviewer | Python code review | Python projects |
|
||||
| django-reviewer | Django code review | Django apps, DRF APIs, ORM, migrations |
|
||||
| django-build-resolver | Django build, migration, and setup errors | Django startup, dependency, migration, collectstatic failures |
|
||||
| java-reviewer | Java and Spring Boot code review | Java/Spring Boot projects |
|
||||
| java-build-resolver | Java/Maven/Gradle build errors | Java build failures |
|
||||
| loop-operator | Autonomous loop execution | Run loops safely, monitor stalls, intervene |
|
||||
@ -147,8 +149,8 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
agents/ — 58 specialized subagents
|
||||
skills/ — 220 workflow skills and domain knowledge
|
||||
agents/ — 60 specialized subagents
|
||||
skills/ — 221 workflow skills and domain knowledge
|
||||
commands/ — 74 slash commands
|
||||
hooks/ — Trigger-based automations
|
||||
rules/ — Always-follow guidelines (common + per-language)
|
||||
|
||||
12
README.md
12
README.md
@ -358,7 +358,7 @@ If you stacked methods, clean up in this order:
|
||||
/plugin list ecc@ecc
|
||||
```
|
||||
|
||||
**That's it!** You now have access to 58 agents, 220 skills, and 74 legacy command shims.
|
||||
**That's it!** You now have access to 60 agents, 221 skills, and 74 legacy command shims.
|
||||
|
||||
### Dashboard GUI
|
||||
|
||||
@ -456,7 +456,7 @@ everything-claude-code/
|
||||
| |-- plugin.json # Plugin metadata and component paths
|
||||
| |-- marketplace.json # Marketplace catalog for /plugin marketplace add
|
||||
|
|
||||
|-- agents/ # 58 specialized subagents for delegation
|
||||
|-- agents/ # 60 specialized subagents for delegation
|
||||
| |-- planner.md # Feature implementation planning
|
||||
| |-- architect.md # System design decisions
|
||||
| |-- tdd-guide.md # Test-driven development
|
||||
@ -1360,9 +1360,9 @@ The configuration is automatically detected from `.opencode/opencode.json`.
|
||||
|
||||
| Feature | Claude Code | OpenCode | Status |
|
||||
|---------|-------------|----------|--------|
|
||||
| Agents | PASS: 58 agents | PASS: 12 agents | **Claude Code leads** |
|
||||
| Agents | PASS: 60 agents | PASS: 12 agents | **Claude Code leads** |
|
||||
| Commands | PASS: 74 commands | PASS: 35 commands | **Claude Code leads** |
|
||||
| Skills | PASS: 220 skills | PASS: 37 skills | **Claude Code leads** |
|
||||
| Skills | PASS: 221 skills | PASS: 37 skills | **Claude Code leads** |
|
||||
| Hooks | PASS: 8 event types | PASS: 11 events | **OpenCode has more!** |
|
||||
| Rules | PASS: 29 rules | PASS: 13 instructions | **Claude Code leads** |
|
||||
| MCP Servers | PASS: 14 servers | PASS: Full | **Full parity** |
|
||||
@ -1465,9 +1465,9 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e
|
||||
|
||||
| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|
||||
|---------|------------|------------|-----------|----------|
|
||||
| **Agents** | 58 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |
|
||||
| **Agents** | 60 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |
|
||||
| **Commands** | 74 | Shared | Instruction-based | 35 |
|
||||
| **Skills** | 220 | Shared | 10 (native format) | 37 |
|
||||
| **Skills** | 221 | Shared | 10 (native format) | 37 |
|
||||
| **Hook Events** | 8 types | 15 types | None yet | 11 types |
|
||||
| **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks |
|
||||
| **Rules** | 34 (common + lang) | 34 (YAML frontmatter) | Instruction-based | 13 instructions |
|
||||
|
||||
@ -160,7 +160,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
||||
/plugin list ecc@ecc
|
||||
```
|
||||
|
||||
**完成!** 你现在可以使用 58 个代理、220 个技能和 74 个命令。
|
||||
**完成!** 你现在可以使用 60 个代理、221 个技能和 74 个命令。
|
||||
|
||||
### multi-* 命令需要额外配置
|
||||
|
||||
|
||||
243
agents/django-build-resolver.md
Normal file
243
agents/django-build-resolver.md
Normal file
@ -0,0 +1,243 @@
|
||||
---
|
||||
name: django-build-resolver
|
||||
description: Django/Python build, migration, and dependency error resolution specialist. Fixes pip/Poetry errors, migration conflicts, import errors, Django configuration issues, and collectstatic failures with minimal changes. Use when Django setup or startup fails.
|
||||
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
# Django Build Error Resolver
|
||||
|
||||
You are an expert Django/Python error resolution specialist. Your mission is to fix build errors, migration conflicts, import failures, dependency issues, and Django startup errors with **minimal, surgical changes**.
|
||||
|
||||
You DO NOT refactor or rewrite code — you fix the error only.
|
||||
|
||||
## Core Responsibilities
|
||||
|
||||
1. Resolve pip, Poetry, and virtualenv dependency errors
|
||||
2. Fix Django migration conflicts and state inconsistencies
|
||||
3. Diagnose and repair Django configuration/settings errors
|
||||
4. Resolve Python import errors and module not found issues
|
||||
5. Fix `collectstatic`, `runserver`, and management command failures
|
||||
6. Repair database connection and `DATABASES` misconfiguration
|
||||
|
||||
## Diagnostic Commands
|
||||
|
||||
Run these in order to locate the error:
|
||||
|
||||
```bash
|
||||
# Check Python and Django versions
|
||||
python --version
|
||||
python -m django --version
|
||||
|
||||
# Verify virtual environment is active
|
||||
which python
|
||||
pip list | grep -E "Django|djangorestframework|celery|psycopg"
|
||||
|
||||
# Check for missing dependencies
|
||||
pip check
|
||||
|
||||
# Validate Django configuration
|
||||
python manage.py check --deploy 2>&1 || python manage.py check 2>&1
|
||||
|
||||
# List pending migrations
|
||||
python manage.py showmigrations 2>&1
|
||||
|
||||
# Detect migration conflicts
|
||||
python manage.py migrate --check 2>&1
|
||||
|
||||
# Static files
|
||||
python manage.py collectstatic --dry-run --noinput 2>&1
|
||||
```
|
||||
|
||||
## Resolution Workflow
|
||||
|
||||
```text
|
||||
1. Reproduce the error -> Capture exact message
|
||||
2. Identify error category -> See table below
|
||||
3. Read affected file/config -> Understand context
|
||||
4. Apply minimal fix -> Only what's needed
|
||||
5. python manage.py check -> Validate Django config
|
||||
6. Run test suite -> Ensure nothing broke
|
||||
```
|
||||
|
||||
## Common Fix Patterns
|
||||
|
||||
### Dependency / pip Errors
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `ModuleNotFoundError: No module named 'X'` | Missing package | `pip install X` or add to `requirements.txt` |
|
||||
| `ImportError: cannot import name 'X' from 'Y'` | Version mismatch | Pin compatible version in requirements |
|
||||
| `ERROR: pip's dependency resolver...` | Conflicting deps | Upgrade pip: `pip install --upgrade pip`, then `pip install -r requirements.txt` |
|
||||
| `Poetry: No solution found` | Conflicting constraints | Relax version pin in `pyproject.toml` |
|
||||
| `pkg_resources.DistributionNotFound` | Installed outside venv | Reinstall inside venv |
|
||||
|
||||
```bash
|
||||
# Force reinstall all dependencies
|
||||
pip install --force-reinstall -r requirements.txt
|
||||
|
||||
# Poetry: clear cache and resolve
|
||||
poetry cache clear --all pypi
|
||||
poetry install
|
||||
|
||||
# Create fresh virtualenv if corrupt
|
||||
deactivate
|
||||
python -m venv .venv && source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Migration Errors
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `django.db.migrations.exceptions.MigrationSchemaMissing` | DB tables not created | `python manage.py migrate` |
|
||||
| `InconsistentMigrationHistory` | Applied out of order | Squash or fake migrations |
|
||||
| `Migration X dependencies reference nonexistent parent Y` | Missing migration file | Recreate with `makemigrations` |
|
||||
| `Table already exists` | Migration applied outside Django | `migrate --fake-initial` |
|
||||
| `Multiple leaf nodes in the migration graph` | Conflicting migration branches | Merge: `python manage.py makemigrations --merge` |
|
||||
| `django.db.utils.OperationalError: no such column` | Unapplied migration | `python manage.py migrate` |
|
||||
|
||||
```bash
|
||||
# Fix conflicting migrations
|
||||
python manage.py makemigrations --merge --no-input
|
||||
|
||||
# Fake migrations already applied at DB level
|
||||
python manage.py migrate --fake <app> <migration_number>
|
||||
|
||||
# Reset migrations for an app (dev only!)
|
||||
python manage.py migrate <app> zero
|
||||
python manage.py makemigrations <app>
|
||||
python manage.py migrate <app>
|
||||
|
||||
# Show migration plan
|
||||
python manage.py migrate --plan
|
||||
```
|
||||
|
||||
### Django Configuration Errors
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `django.core.exceptions.ImproperlyConfigured` | Missing setting or wrong value | Check `settings.py` for the named setting |
|
||||
| `DJANGO_SETTINGS_MODULE not set` | Env var missing | `export DJANGO_SETTINGS_MODULE=config.settings.development` |
|
||||
| `SECRET_KEY must not be empty` | Missing env var | Set `DJANGO_SECRET_KEY` in `.env` |
|
||||
| `Invalid HTTP_HOST header` | `ALLOWED_HOSTS` misconfigured | Add hostname to `ALLOWED_HOSTS` |
|
||||
| `Apps aren't loaded yet` | Importing models before `django.setup()` | Call `django.setup()` or move imports inside functions |
|
||||
| `RuntimeError: Model class ... doesn't declare an explicit app_label` | App not in `INSTALLED_APPS` | Add the app to `INSTALLED_APPS` |
|
||||
|
||||
```bash
|
||||
# Verify settings module resolves
|
||||
python -c "import django; django.setup(); print('OK')"
|
||||
|
||||
# Check environment variable
|
||||
echo $DJANGO_SETTINGS_MODULE
|
||||
|
||||
# Find missing settings
|
||||
python manage.py diffsettings 2>&1
|
||||
```
|
||||
|
||||
### Import Errors
|
||||
|
||||
```bash
|
||||
# Diagnose circular imports
|
||||
python -c "import <module>" 2>&1
|
||||
|
||||
# Find where an import is used
|
||||
grep -r "from <module> import" . --include="*.py"
|
||||
|
||||
# Check installed app paths
|
||||
python -c "import <app>; print(<app>.__file__)"
|
||||
```
|
||||
|
||||
**Circular import fix:** Move imports inside functions or use `apps.get_model()`:
|
||||
|
||||
```python
|
||||
# Bad - top-level causes circular import
|
||||
from apps.users.models import User
|
||||
|
||||
# Good - import inside function
|
||||
def get_user(pk):
|
||||
from apps.users.models import User
|
||||
return User.objects.get(pk=pk)
|
||||
|
||||
# Good - use apps registry
|
||||
from django.apps import apps
|
||||
User = apps.get_model('users', 'User')
|
||||
```
|
||||
|
||||
### Database Connection Errors
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `django.db.utils.OperationalError: could not connect to server` | DB not running or wrong host | Start DB or fix `DATABASES['HOST']` |
|
||||
| `django.db.utils.OperationalError: FATAL: role X does not exist` | Wrong DB user | Fix `DATABASES['USER']` |
|
||||
| `django.db.utils.ProgrammingError: relation X does not exist` | Missing migration | `python manage.py migrate` |
|
||||
| `psycopg2 not installed` | Missing driver | `pip install psycopg2-binary` |
|
||||
|
||||
```bash
|
||||
# Test database connection
|
||||
python manage.py dbshell
|
||||
|
||||
# Check DATABASES setting
|
||||
python -c "from django.conf import settings; print(settings.DATABASES)"
|
||||
```
|
||||
|
||||
### collectstatic / Static Files Errors
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `staticfiles.E001: The STATICFILES_DIRS...` | Dir in both `STATICFILES_DIRS` and `STATIC_ROOT` | Remove from `STATICFILES_DIRS` |
|
||||
| `FileNotFoundError` during collectstatic | Missing static file referenced in template | Remove or create the referenced file |
|
||||
| `AttributeError: 'str' object has no attribute 'path'` | `STORAGES` not configured for Django 4.2+ | Update `STORAGES` dict in settings |
|
||||
|
||||
```bash
|
||||
# Dry run to find issues
|
||||
python manage.py collectstatic --dry-run --noinput 2>&1
|
||||
|
||||
# Clear and recollect
|
||||
python manage.py collectstatic --clear --noinput
|
||||
```
|
||||
|
||||
### runserver Failures
|
||||
|
||||
```bash
|
||||
# Port already in use
|
||||
lsof -ti:8000 | xargs kill -9
|
||||
python manage.py runserver
|
||||
|
||||
# Use alternate port
|
||||
python manage.py runserver 8080
|
||||
|
||||
# Verbose startup for hidden errors
|
||||
python manage.py runserver --verbosity=2 2>&1
|
||||
```
|
||||
|
||||
## Key Principles
|
||||
|
||||
- **Surgical fixes only** — don't refactor, just fix the error
|
||||
- **Never** delete migration files — fake them instead
|
||||
- **Always** run `python manage.py check` after fixing
|
||||
- Fix root cause over suppressing symptoms
|
||||
- Use `--fake` sparingly and only when DB state is known
|
||||
- Prefer `pip install --upgrade` over manual `requirements.txt` edits when resolving conflicts
|
||||
|
||||
## Stop Conditions
|
||||
|
||||
Stop and report if:
|
||||
- Migration conflict requires destructive DB changes (data loss risk)
|
||||
- Same error persists after 3 fix attempts
|
||||
- Fix requires changes to production data or irreversible DB operations
|
||||
- Missing external service (Redis, PostgreSQL) that needs user setup
|
||||
|
||||
## Output Format
|
||||
|
||||
```text
|
||||
[FIXED] apps/users/migrations/0003_auto.py
|
||||
Error: InconsistentMigrationHistory — 0002_add_email applied before 0001_initial
|
||||
Fix: python manage.py migrate users 0001 --fake, then re-applied
|
||||
Remaining errors: 0
|
||||
```
|
||||
|
||||
Final: `Django Status: OK/FAILED | Errors Fixed: N | Files Modified: list`
|
||||
|
||||
For Django architecture and ORM patterns, see `skill: django-patterns`.
|
||||
For Django security settings, see `skill: django-security`.
|
||||
160
agents/django-reviewer.md
Normal file
160
agents/django-reviewer.md
Normal file
@ -0,0 +1,160 @@
|
||||
---
|
||||
name: django-reviewer
|
||||
description: Expert Django code reviewer specializing in ORM correctness, DRF patterns, migration safety, security misconfigurations, and production-grade Django practices. Use for all Django code changes. MUST BE USED for Django projects.
|
||||
tools: ["Read", "Grep", "Glob", "Bash"]
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are a senior Django code reviewer ensuring production-grade quality, security, and performance.
|
||||
|
||||
**Note**: This agent focuses on Django-specific concerns. Ensure `python-reviewer` has been invoked for general Python quality checks before or after this review.
|
||||
|
||||
When invoked:
|
||||
1. Run `git diff -- '*.py'` to see recent Python file changes
|
||||
2. Run `python manage.py check` if a Django project is present
|
||||
3. Run `ruff check .` and `mypy .` if available
|
||||
4. Focus on modified `.py` files and any related migrations
|
||||
5. Assume CI checks have passed (orchestration gated); if CI status needs verification, run `gh pr checks` to confirm green before proceeding
|
||||
|
||||
## Review Priorities
|
||||
|
||||
### CRITICAL — Security
|
||||
|
||||
- **SQL Injection**: Raw SQL with f-strings or `%` formatting — use `%s` parameters or ORM
|
||||
- **`mark_safe` on user input**: Never without explicit `escape()` first
|
||||
- **CSRF exemption without reason**: `@csrf_exempt` on non-webhook views
|
||||
- **`DEBUG = True` in production settings**: Leaks full stack traces
|
||||
- **Hardcoded `SECRET_KEY`**: Must come from environment variable
|
||||
- **Missing `permission_classes` on DRF views**: Defaults to global — verify intent
|
||||
- **`eval()`/`exec()` on user input**: Immediate block
|
||||
- **File upload without extension/size validation**: Path traversal risk
|
||||
|
||||
### CRITICAL — ORM Correctness
|
||||
|
||||
- **N+1 queries in loops**: Accessing related objects without `select_related`/`prefetch_related`
|
||||
```python
|
||||
# Bad
|
||||
for order in Order.objects.all():
|
||||
print(order.user.email) # N+1
|
||||
|
||||
# Good
|
||||
for order in Order.objects.select_related('user').all():
|
||||
print(order.user.email)
|
||||
```
|
||||
- **Missing `atomic()` for multi-step writes**: Use `transaction.atomic()` for any sequence of DB writes
|
||||
- **`bulk_create` without `update_conflicts`**: Silent data loss on duplicate keys
|
||||
- **`get()` without `DoesNotExist` handling**: Unhandled exception risk
|
||||
- **Queryset used after `delete()`**: Stale queryset reference
|
||||
|
||||
### CRITICAL — Migration Safety
|
||||
|
||||
- **Model change without migration**: Run `python manage.py makemigrations --check`
|
||||
- **Backward-incompatible column drop**: Must be done in two deployments (nullable first)
|
||||
- **`RunPython` without `reverse_code`**: Migration cannot be reversed
|
||||
- **`atomic = False` without justification**: Leaves DB in partial state on failure
|
||||
|
||||
### HIGH — DRF Patterns
|
||||
|
||||
- **Serializer without explicit `fields`**: `fields = '__all__'` exposes all columns including sensitive ones
|
||||
- **No pagination on list endpoints**: Unbounded queries can return millions of rows
|
||||
- **Missing `read_only_fields`**: Auto-generated fields (id, created_at) editable by API
|
||||
- **`perform_create` not used**: Injecting user context should happen in `perform_create`, not `validate`
|
||||
- **No throttling on auth endpoints**: Login/registration open to brute force
|
||||
- **Nested writable serializers without `update()`**: Default update silently ignores nested data
|
||||
|
||||
### HIGH — Performance
|
||||
|
||||
- **Queryset evaluated in template context**: Use `.values()` or pass list; avoid lazy evaluation in templates
|
||||
- **Missing `db_index` on FK/filter fields**: Full table scan on filtered queries
|
||||
- **Synchronous external API call in view**: Blocks the request thread — offload to Celery
|
||||
- **`len(queryset)` instead of `.count()`**: Forces full fetch
|
||||
- **`exists()` not used for existence checks**: `if queryset:` fetches objects unnecessarily
|
||||
|
||||
```python
|
||||
# Bad
|
||||
if Product.objects.filter(sku=sku):
|
||||
...
|
||||
|
||||
# Good
|
||||
if Product.objects.filter(sku=sku).exists():
|
||||
...
|
||||
```
|
||||
|
||||
### HIGH — Code Quality
|
||||
|
||||
- **Business logic in views or serializers**: Move to `services.py`
|
||||
- **Signal logic that belongs in a service**: Signals make flow hard to trace — use explicitly
|
||||
- **Mutable default in model field**: `default=[]` or `default={}` — use `default=list`
|
||||
- **`save()` called without `update_fields`**: Overwrites all columns — risk of clobbering concurrent writes
|
||||
|
||||
```python
|
||||
# Bad
|
||||
user.last_active = now()
|
||||
user.save()
|
||||
|
||||
# Good
|
||||
user.last_active = now()
|
||||
user.save(update_fields=['last_active'])
|
||||
```
|
||||
|
||||
### MEDIUM — Best Practices
|
||||
|
||||
- **`str(queryset)` or slicing for debug**: Use Django shell, not production code
|
||||
- **Accessing `request.user` in serializer `validate()`**: Pass via context, not direct access
|
||||
- **`print()` instead of `logger`**: Use `logging.getLogger(__name__)`
|
||||
- **Missing `related_name`**: Reverse accessors like `user_set` are confusing
|
||||
- **`blank=True` without `null=True` on non-string fields**: DB stores empty string for non-string types
|
||||
- **Hardcoded URLs**: Use `reverse()` or `reverse_lazy()`
|
||||
- **Missing `__str__` on models**: Django admin and logging are broken without it
|
||||
- **App not using `AppConfig.ready()`**: Signal receivers not connected properly
|
||||
|
||||
### MEDIUM — Testing Gaps
|
||||
|
||||
- **No test for permission boundary**: Verify unauthorized access returns 403/401
|
||||
- **`force_authenticate` instead of proper token**: Tests skip auth logic entirely
|
||||
- **Missing `@pytest.mark.django_db`**: Tests silently hit no DB
|
||||
- **Factory not used**: Raw `Model.objects.create()` in tests is fragile
|
||||
|
||||
## Diagnostic Commands
|
||||
|
||||
```bash
|
||||
python manage.py check # Django system check
|
||||
python manage.py makemigrations --check # Detect missing migrations
|
||||
ruff check . # Fast linter
|
||||
mypy . --ignore-missing-imports # Type checking
|
||||
bandit -r . -ll # Security scan (medium+)
|
||||
pytest --cov=apps --cov-report=term-missing -q # Tests + coverage
|
||||
```
|
||||
|
||||
## Review Output Format
|
||||
|
||||
```text
|
||||
[SEVERITY] Issue title
|
||||
File: apps/orders/views.py:42
|
||||
Issue: Description of the problem
|
||||
Fix: What to change and why
|
||||
```
|
||||
|
||||
## Approval Criteria
|
||||
|
||||
- **Approve**: No CRITICAL or HIGH issues
|
||||
- **Warning**: MEDIUM issues only (can merge with caution)
|
||||
- **Block**: CRITICAL or HIGH issues found
|
||||
|
||||
## Framework-Specific Checks
|
||||
|
||||
- **Migrations**: Every model change must have a migration. Two-phase for column removal.
|
||||
- **DRF**: All public endpoints need explicit `permission_classes`. Pagination on all list views.
|
||||
- **Celery**: Tasks must be idempotent. Use `bind=True` + `self.retry()` for transient failures.
|
||||
- **Django Admin**: Never expose sensitive fields. Use `readonly_fields` for auto-generated data.
|
||||
- **Signals**: Prefer explicit service calls. If signals are used, register in `AppConfig.ready()`.
|
||||
|
||||
## Reference
|
||||
|
||||
For Django architecture patterns and ORM examples, see `skill: django-patterns`.
|
||||
For security configuration checklists, see `skill: django-security`.
|
||||
For testing patterns and fixtures, see `skill: django-tdd`.
|
||||
|
||||
---
|
||||
|
||||
Review with the mindset: "Would this code safely serve 10,000 concurrent users without data loss, security breach, or a 3am pager alert?"
|
||||
@ -20,6 +20,7 @@ on fresh branches, and credit the source PR.
|
||||
| Source PR | Original contribution | Salvage result |
|
||||
| --- | --- | --- |
|
||||
| #1309 | Trading/community project material | Salvaged in #1761 as a neutral community-project README listing. |
|
||||
| #1310 | Django reviewer, build resolver, and Celery async task guidance | Salvaged in the May 12 Django/Celery maintainer pass with current catalog counts and minor example cleanup. |
|
||||
| #1322 | Vietnamese README translation | Salvaged in #1764 as `docs/vi-VN/README.md` plus selector updates. |
|
||||
| #1326 | Angular developer skill and rules | Salvaged in #1763 with current skill, rules, install wiring, and catalog updates. |
|
||||
| #1328 | Continuous-learning Windows UTF-8 stdout fix | Salvaged in #1761. |
|
||||
@ -50,6 +51,22 @@ on fresh branches, and credit the source PR.
|
||||
| #1727 | MySQL patterns skill | Salvaged in #1733. |
|
||||
| #1757 | Machine-learning engineering workflow | Salvaged in #1758 and tuned in #1759. |
|
||||
|
||||
## 2026-05-12 Gap Pass
|
||||
|
||||
The initial stale-closure ledger covered the P0 cleanup cohort and the biggest
|
||||
salvage branches. A follow-up gap pass over PRs closed on 2026-05-11 found
|
||||
additional useful items that were already present on `main` or still worth
|
||||
porting.
|
||||
|
||||
| Source PR | Disposition |
|
||||
| --- | --- |
|
||||
| #1310 | Ported through the Django/Celery maintainer branch after confirming `agents/django-reviewer.md`, `agents/django-build-resolver.md`, and `skills/django-celery/SKILL.md` were still missing. |
|
||||
| #1360 | Already present as `skills/security-bounty-hunter/`. |
|
||||
| #1415 | Already present as `skills/vite-patterns/`. |
|
||||
| #1438 | Already present as `skills/ui-to-vue/`. |
|
||||
| #1508 | Already present as `skills/fastapi-patterns/` and `agents/fastapi-reviewer.md`. |
|
||||
| #1693 | Already present as `skills/redis-patterns/`. |
|
||||
|
||||
## Already Present Or Superseded
|
||||
|
||||
| Source PR | Disposition |
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Everything Claude Code (ECC) — 智能体指令
|
||||
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 58 个专业代理、220 项技能、74 条命令以及自动化钩子工作流,用于软件开发。
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 60 个专业代理、221 项技能、74 条命令以及自动化钩子工作流,用于软件开发。
|
||||
|
||||
**版本:** 2.0.0-rc.1
|
||||
|
||||
@ -146,8 +146,8 @@
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
agents/ — 58 个专业子代理
|
||||
skills/ — 220 个工作流技能和领域知识
|
||||
agents/ — 60 个专业子代理
|
||||
skills/ — 221 个工作流技能和领域知识
|
||||
commands/ — 74 个斜杠命令
|
||||
hooks/ — 基于触发的自动化
|
||||
rules/ — 始终遵循的指导方针(通用 + 每种语言)
|
||||
|
||||
@ -224,7 +224,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
||||
/plugin list ecc@ecc
|
||||
```
|
||||
|
||||
**搞定!** 你现在可以使用 58 个智能体、220 项技能和 74 个命令了。
|
||||
**搞定!** 你现在可以使用 60 个智能体、221 项技能和 74 个命令了。
|
||||
|
||||
***
|
||||
|
||||
@ -1136,9 +1136,9 @@ opencode
|
||||
|
||||
| 功能特性 | Claude Code | OpenCode | 状态 |
|
||||
|---------|-------------|----------|--------|
|
||||
| 智能体 | PASS: 58 个 | PASS: 12 个 | **Claude Code 领先** |
|
||||
| 智能体 | PASS: 60 个 | PASS: 12 个 | **Claude Code 领先** |
|
||||
| 命令 | PASS: 74 个 | PASS: 35 个 | **Claude Code 领先** |
|
||||
| 技能 | PASS: 220 项 | PASS: 37 项 | **Claude Code 领先** |
|
||||
| 技能 | PASS: 221 项 | PASS: 37 项 | **Claude Code 领先** |
|
||||
| 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** |
|
||||
| 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** |
|
||||
| MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** |
|
||||
@ -1244,9 +1244,9 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
|
||||
|
||||
| 功能特性 | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|
||||
|---------|------------|------------|-----------|----------|
|
||||
| **智能体** | 58 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
|
||||
| **智能体** | 60 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
|
||||
| **命令** | 74 | 共享 | 基于指令 | 35 |
|
||||
| **技能** | 220 | 共享 | 10 (原生格式) | 37 |
|
||||
| **技能** | 221 | 共享 | 10 (原生格式) | 37 |
|
||||
| **钩子事件** | 8 种类型 | 15 种类型 | 暂无 | 11 种类型 |
|
||||
| **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | N/A | 插件钩子 |
|
||||
| **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 |
|
||||
|
||||
457
skills/django-celery/SKILL.md
Normal file
457
skills/django-celery/SKILL.md
Normal file
@ -0,0 +1,457 @@
|
||||
---
|
||||
name: django-celery
|
||||
description: Django + Celery async task patterns — configuration, task design, beat scheduling, retries, canvas workflows, monitoring, and testing. Use when adding background jobs, scheduled tasks, or async processing to a Django app.
|
||||
origin: ECC
|
||||
---
|
||||
|
||||
# Django + Celery Async Task Patterns
|
||||
|
||||
Production-grade patterns for background task processing in Django using Celery with Redis or RabbitMQ.
|
||||
|
||||
## When to Activate
|
||||
|
||||
- Adding background jobs or async processing to a Django app
|
||||
- Implementing periodic/scheduled tasks
|
||||
- Offloading slow operations (email, PDF generation, API calls) from request cycle
|
||||
- Setting up Celery Beat for cron-like scheduling
|
||||
- Debugging task failures, retries, or queue backlogs
|
||||
- Writing tests for Celery tasks
|
||||
|
||||
## Project Setup
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
pip install celery[redis] django-celery-results django-celery-beat
|
||||
```
|
||||
|
||||
### `celery.py` — App Entrypoint
|
||||
|
||||
```python
|
||||
# config/celery.py
|
||||
import os
|
||||
from celery import Celery
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.development')
|
||||
|
||||
app = Celery('myproject')
|
||||
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||
app.autodiscover_tasks() # Discovers tasks.py in each INSTALLED_APP
|
||||
|
||||
@app.task(bind=True, ignore_result=True)
|
||||
def debug_task(self):
|
||||
print(f'Request: {self.request!r}')
|
||||
```
|
||||
|
||||
```python
|
||||
# config/__init__.py
|
||||
from .celery import app as celery_app
|
||||
|
||||
__all__ = ('celery_app',)
|
||||
```
|
||||
|
||||
### Django Settings
|
||||
|
||||
```python
|
||||
# config/settings/base.py
|
||||
|
||||
# Broker (Redis recommended for production)
|
||||
CELERY_BROKER_URL = env('CELERY_BROKER_URL', default='redis://localhost:6379/0')
|
||||
CELERY_RESULT_BACKEND = env('CELERY_RESULT_BACKEND', default='django-db')
|
||||
|
||||
# Serialization
|
||||
CELERY_ACCEPT_CONTENT = ['json']
|
||||
CELERY_TASK_SERIALIZER = 'json'
|
||||
CELERY_RESULT_SERIALIZER = 'json'
|
||||
|
||||
# Task behavior
|
||||
CELERY_TASK_TRACK_STARTED = True
|
||||
CELERY_TASK_TIME_LIMIT = 30 * 60 # Hard limit: 30 min
|
||||
CELERY_TASK_SOFT_TIME_LIMIT = 25 * 60 # Soft limit: sends SoftTimeLimitExceeded
|
||||
CELERY_WORKER_PREFETCH_MULTIPLIER = 1 # Prevent worker hoarding long tasks
|
||||
CELERY_TASK_ACKS_LATE = True # Re-queue on worker crash
|
||||
|
||||
# Result persistence
|
||||
CELERY_RESULT_EXPIRES = 60 * 60 * 24 # Keep results 24 hours
|
||||
|
||||
# Beat scheduler (for periodic tasks)
|
||||
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
|
||||
|
||||
# Installed apps
|
||||
INSTALLED_APPS += [
|
||||
'django_celery_results',
|
||||
'django_celery_beat',
|
||||
]
|
||||
```
|
||||
|
||||
### Running Workers
|
||||
|
||||
```bash
|
||||
# Start worker (development)
|
||||
celery -A config worker --loglevel=info
|
||||
|
||||
# Start beat scheduler (periodic tasks)
|
||||
celery -A config beat --loglevel=info --scheduler django_celery_beat.schedulers:DatabaseScheduler
|
||||
|
||||
# Combined worker + beat (dev only, never production)
|
||||
celery -A config worker --beat --loglevel=info
|
||||
|
||||
# Production: multiple workers with concurrency
|
||||
celery -A config worker --loglevel=warning --concurrency=4 -Q default,high_priority
|
||||
```
|
||||
|
||||
## Task Design Patterns
|
||||
|
||||
### Basic Task
|
||||
|
||||
```python
|
||||
# apps/notifications/tasks.py
|
||||
from celery import shared_task
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@shared_task(name='notifications.send_welcome_email')
|
||||
def send_welcome_email(user_id: int) -> None:
|
||||
"""Send welcome email to newly registered user."""
|
||||
from apps.users.models import User
|
||||
from apps.notifications.services import EmailService
|
||||
|
||||
try:
|
||||
user = User.objects.get(pk=user_id)
|
||||
except User.DoesNotExist:
|
||||
logger.warning('send_welcome_email: user %s not found', user_id)
|
||||
return # Idempotent — do not raise, task already impossible to complete
|
||||
|
||||
EmailService.send_welcome(user)
|
||||
logger.info('Welcome email sent to user %s', user_id)
|
||||
```
|
||||
|
||||
### Retryable Task
|
||||
|
||||
```python
|
||||
@shared_task(
|
||||
bind=True,
|
||||
name='integrations.sync_to_crm',
|
||||
max_retries=5,
|
||||
default_retry_delay=60, # seconds before first retry
|
||||
autoretry_for=(ConnectionError, TimeoutError),
|
||||
retry_backoff=True, # exponential backoff
|
||||
retry_backoff_max=600, # cap at 10 minutes
|
||||
retry_jitter=True, # randomise to avoid thundering herd
|
||||
)
|
||||
def sync_contact_to_crm(self, contact_id: int) -> dict:
|
||||
"""Sync contact to external CRM with retry on transient failures."""
|
||||
from apps.crm.services import CRMClient
|
||||
|
||||
try:
|
||||
result = CRMClient().sync(contact_id)
|
||||
return result
|
||||
except CRMClient.RateLimitError as exc:
|
||||
# Specific retry delay from response header
|
||||
raise self.retry(exc=exc, countdown=int(exc.retry_after))
|
||||
```
|
||||
|
||||
### Idempotent Task Pattern
|
||||
|
||||
Design tasks so they can safely run multiple times with the same inputs:
|
||||
|
||||
```python
|
||||
@shared_task(name='orders.mark_shipped')
|
||||
def mark_order_shipped(order_id: int, tracking_number: str) -> None:
|
||||
"""Mark order as shipped — safe to run multiple times."""
|
||||
from apps.orders.models import Order
|
||||
|
||||
updated = Order.objects.filter(
|
||||
pk=order_id,
|
||||
status=Order.Status.PROCESSING, # Guard: only update if not already shipped
|
||||
).update(
|
||||
status=Order.Status.SHIPPED,
|
||||
tracking_number=tracking_number,
|
||||
)
|
||||
|
||||
if not updated:
|
||||
logger.info('mark_order_shipped: order %s already shipped or not found', order_id)
|
||||
```
|
||||
|
||||
### Task with Soft Time Limit
|
||||
|
||||
```python
|
||||
from celery.exceptions import SoftTimeLimitExceeded
|
||||
|
||||
@shared_task(
|
||||
bind=True,
|
||||
name='reports.generate_pdf',
|
||||
soft_time_limit=120,
|
||||
time_limit=150,
|
||||
)
|
||||
def generate_pdf_report(self, report_id: int) -> str:
|
||||
"""Generate PDF report with graceful timeout handling."""
|
||||
from apps.reports.services import PDFGenerator
|
||||
|
||||
try:
|
||||
path = PDFGenerator.build(report_id)
|
||||
return path
|
||||
except SoftTimeLimitExceeded:
|
||||
# Clean up partial files before hard kill
|
||||
PDFGenerator.cleanup(report_id)
|
||||
raise
|
||||
```
|
||||
|
||||
## Calling Tasks
|
||||
|
||||
```python
|
||||
from datetime import timedelta
|
||||
from django.utils import timezone
|
||||
|
||||
# Fire and forget (async)
|
||||
send_welcome_email.delay(user.pk)
|
||||
|
||||
# Schedule in the future
|
||||
send_reminder.apply_async(args=[user.pk], countdown=3600) # 1 hour from now
|
||||
send_reminder.apply_async(args=[user.pk], eta=timezone.now() + timedelta(days=1))
|
||||
|
||||
# Apply with queue routing
|
||||
sync_contact_to_crm.apply_async(args=[contact.pk], queue='high_priority')
|
||||
|
||||
# Run synchronously (tests / debugging only)
|
||||
result = generate_pdf_report.apply(args=[report.pk])
|
||||
```
|
||||
|
||||
## Beat Scheduling (Periodic Tasks)
|
||||
|
||||
### Code-Defined Schedule
|
||||
|
||||
```python
|
||||
# config/settings/base.py
|
||||
from celery.schedules import crontab
|
||||
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
'cleanup-expired-sessions': {
|
||||
'task': 'users.cleanup_expired_sessions',
|
||||
'schedule': crontab(hour=2, minute=0), # 2am daily
|
||||
},
|
||||
'sync-inventory': {
|
||||
'task': 'products.sync_inventory',
|
||||
'schedule': 60.0, # every 60 seconds
|
||||
},
|
||||
'weekly-digest': {
|
||||
'task': 'notifications.send_weekly_digest',
|
||||
'schedule': crontab(day_of_week='monday', hour=8, minute=0),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Database-Defined Schedule (via django-celery-beat)
|
||||
|
||||
```python
|
||||
# Manage periodic tasks from Django admin or code
|
||||
from django_celery_beat.models import PeriodicTask, CrontabSchedule
|
||||
import json
|
||||
|
||||
schedule, _ = CrontabSchedule.objects.get_or_create(
|
||||
hour='*/6', minute='0',
|
||||
timezone='UTC',
|
||||
)
|
||||
|
||||
PeriodicTask.objects.update_or_create(
|
||||
name='Sync inventory every 6 hours',
|
||||
defaults={
|
||||
'crontab': schedule,
|
||||
'task': 'products.sync_inventory',
|
||||
'args': json.dumps([]),
|
||||
'enabled': True,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Canvas: Chaining and Grouping Tasks
|
||||
|
||||
```python
|
||||
from celery import chain, group, chord
|
||||
|
||||
# Chain: run tasks sequentially, passing results
|
||||
pipeline = chain(
|
||||
fetch_data.s(source_id),
|
||||
transform_data.s(), # receives fetch_data result as first arg
|
||||
load_to_warehouse.s(),
|
||||
)
|
||||
pipeline.delay()
|
||||
|
||||
# Group: run tasks in parallel
|
||||
parallel = group(
|
||||
send_welcome_email.s(user_id)
|
||||
for user_id in new_user_ids
|
||||
)
|
||||
parallel.delay()
|
||||
|
||||
# Chord: parallel tasks + callback when all complete
|
||||
result = chord(
|
||||
group(process_chunk.s(chunk) for chunk in data_chunks),
|
||||
aggregate_results.s(), # called with list of chunk results
|
||||
)
|
||||
result.delay()
|
||||
```
|
||||
|
||||
## Error Handling and Dead Letter Queue
|
||||
|
||||
```python
|
||||
# apps/core/tasks.py
|
||||
from celery.signals import task_failure
|
||||
|
||||
@task_failure.connect
|
||||
def on_task_failure(sender, task_id, exception, args, kwargs, traceback, einfo, **kw):
|
||||
"""Log all task failures to Sentry / alerting."""
|
||||
import sentry_sdk
|
||||
with sentry_sdk.new_scope() as scope:
|
||||
scope.set_context('celery', {
|
||||
'task': sender.name,
|
||||
'task_id': task_id,
|
||||
'args': args,
|
||||
'kwargs': kwargs,
|
||||
})
|
||||
sentry_sdk.capture_exception(exception)
|
||||
```
|
||||
|
||||
```python
|
||||
# Route failed tasks to dead-letter queue after max retries
|
||||
@shared_task(
|
||||
bind=True,
|
||||
max_retries=3,
|
||||
name='payments.charge_card',
|
||||
)
|
||||
def charge_card(self, order_id: int) -> None:
|
||||
from apps.payments.models import Order, FailedCharge
|
||||
|
||||
try:
|
||||
_do_charge(order_id)
|
||||
except Exception as exc:
|
||||
if self.request.retries >= self.max_retries:
|
||||
# Persist to dead-letter table for manual review
|
||||
FailedCharge.objects.create(
|
||||
order_id=order_id,
|
||||
error=str(exc),
|
||||
task_id=self.request.id,
|
||||
)
|
||||
return # Don't raise — task is permanently failed
|
||||
raise self.retry(exc=exc)
|
||||
```
|
||||
|
||||
## Testing Celery Tasks
|
||||
|
||||
### Unit Testing (No Broker)
|
||||
|
||||
```python
|
||||
# tests/test_tasks.py
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from apps.notifications.tasks import send_welcome_email
|
||||
|
||||
class TestSendWelcomeEmail:
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sends_email_to_existing_user(self, user):
|
||||
with patch('apps.notifications.services.EmailService') as mock_email:
|
||||
send_welcome_email(user.pk)
|
||||
mock_email.send_welcome.assert_called_once_with(user)
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_skips_missing_user_gracefully(self):
|
||||
"""Should not raise when user is deleted between enqueue and execute."""
|
||||
send_welcome_email(99999) # Non-existent user — must not raise
|
||||
```
|
||||
|
||||
### Integration Testing with CELERY_TASK_ALWAYS_EAGER
|
||||
|
||||
```python
|
||||
# config/settings/test.py
|
||||
CELERY_TASK_ALWAYS_EAGER = True # Run tasks synchronously in tests
|
||||
CELERY_TASK_EAGER_PROPAGATES = True # Re-raise exceptions from tasks
|
||||
|
||||
# tests/test_integration.py
|
||||
@pytest.mark.django_db
|
||||
def test_registration_triggers_welcome_email(client):
|
||||
with patch('apps.notifications.services.EmailService') as mock_email:
|
||||
response = client.post('/api/users/', {
|
||||
'email': 'new@example.com',
|
||||
'password': 'strongpass123',
|
||||
})
|
||||
|
||||
assert response.status_code == 201
|
||||
mock_email.send_welcome.assert_called_once()
|
||||
```
|
||||
|
||||
### Testing Retries
|
||||
|
||||
```python
|
||||
@pytest.mark.django_db
|
||||
def test_task_retries_on_connection_error():
|
||||
with patch('apps.crm.services.CRMClient.sync') as mock_sync:
|
||||
mock_sync.side_effect = ConnectionError('timeout')
|
||||
|
||||
with pytest.raises(ConnectionError):
|
||||
sync_contact_to_crm.apply(args=[1], throw=True)
|
||||
|
||||
assert mock_sync.call_count == 1 # First attempt only when eager
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
```bash
|
||||
# Inspect active workers and queues
|
||||
celery -A config inspect active
|
||||
celery -A config inspect stats
|
||||
celery -A config inspect reserved
|
||||
|
||||
# Check queue lengths (Redis)
|
||||
redis-cli llen celery
|
||||
|
||||
# Flower: web-based real-time monitor
|
||||
pip install flower
|
||||
celery -A config flower --port=5555
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
```python
|
||||
# BAD: Passing model instances — they may be stale by execution time
|
||||
send_welcome_email.delay(user) # Never pass ORM objects
|
||||
send_welcome_email.delay(user.pk) # Always pass PKs
|
||||
|
||||
# BAD: Calling tasks synchronously in production views
|
||||
result = generate_report.apply() # Blocks the request thread
|
||||
|
||||
# BAD: Non-idempotent task without guards
|
||||
@shared_task
|
||||
def charge_and_fulfill(order_id):
|
||||
order.charge() # May charge twice if task retries!
|
||||
order.fulfill()
|
||||
|
||||
# GOOD: Idempotent with status guard
|
||||
@shared_task
|
||||
def charge_and_fulfill(order_id):
|
||||
order = Order.objects.select_for_update().get(pk=order_id)
|
||||
if order.status != Order.Status.PENDING:
|
||||
return # Already processed
|
||||
order.charge()
|
||||
order.fulfill()
|
||||
```
|
||||
|
||||
## Production Checklist
|
||||
|
||||
| Check | Setting |
|
||||
|-------|---------|
|
||||
| Worker restarts on crash | `supervisord` or `systemd` unit |
|
||||
| `CELERY_TASK_ACKS_LATE = True` | Re-queue tasks on worker crash |
|
||||
| `CELERY_WORKER_PREFETCH_MULTIPLIER = 1` | Fair distribution of long tasks |
|
||||
| Separate queues per priority | `-Q default,high_priority,low_priority` |
|
||||
| `CELERY_TASK_SOFT_TIME_LIMIT` set | Graceful timeout before hard kill |
|
||||
| Sentry integration | Capture all `task_failure` signals |
|
||||
| Flower or other monitor | Visibility into queue depths |
|
||||
| Beat runs on single node only | Prevents duplicate scheduled task execution |
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `django-patterns` — ORM, service layer, and project structure
|
||||
- `django-tdd` — Testing Django models, views, and services
|
||||
- `python-testing` — pytest configuration and fixtures
|
||||
@ -48,6 +48,7 @@ test('stale PR salvage ledger preserves representative source attribution', () =
|
||||
'#1309',
|
||||
'#1322',
|
||||
'#1326',
|
||||
'#1310',
|
||||
'#1413',
|
||||
'#1493',
|
||||
'#1528/#1529/#1547',
|
||||
@ -88,10 +89,20 @@ test('legacy inventory and roadmap link to the durable salvage ledger', () => {
|
||||
assert.ok(roadmap.includes('#1687 translator/manual'));
|
||||
});
|
||||
|
||||
test('stale PR salvage ledger records the May 12 gap pass', () => {
|
||||
const source = read('docs/stale-pr-salvage-ledger.md');
|
||||
|
||||
for (const pr of ['#1310', '#1360', '#1415', '#1438', '#1508', '#1693']) {
|
||||
assert.ok(source.includes(pr), `Missing May 12 gap-pass PR ${pr}`);
|
||||
}
|
||||
|
||||
assert.ok(source.includes('Django/Celery maintainer branch'));
|
||||
assert.ok(source.includes('Already present as `skills/redis-patterns/`'));
|
||||
});
|
||||
|
||||
if (failed > 0) {
|
||||
console.log(`\nFailed: ${failed}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\nPassed: ${passed}`);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user