diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 5bbc44bc..a2b76bbe 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -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", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index fb77231f..95f1fabb 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -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" diff --git a/AGENTS.md b/AGENTS.md index 03a9b322..2c8b05df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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) diff --git a/README.md b/README.md index 0779f8d2..7b3851a4 100644 --- a/README.md +++ b/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 | diff --git a/README.zh-CN.md b/README.zh-CN.md index da802c5e..894bf852 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -160,7 +160,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/" /plugin list ecc@ecc ``` -**完成!** 你现在可以使用 58 个代理、220 个技能和 74 个命令。 +**完成!** 你现在可以使用 60 个代理、221 个技能和 74 个命令。 ### multi-* 命令需要额外配置 diff --git a/agents/django-build-resolver.md b/agents/django-build-resolver.md new file mode 100644 index 00000000..5691f89c --- /dev/null +++ b/agents/django-build-resolver.md @@ -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 + +# Reset migrations for an app (dev only!) +python manage.py migrate zero +python manage.py makemigrations +python manage.py migrate + +# 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 " 2>&1 + +# Find where an import is used +grep -r "from import" . --include="*.py" + +# Check installed app paths +python -c "import ; print(.__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`. diff --git a/agents/django-reviewer.md b/agents/django-reviewer.md new file mode 100644 index 00000000..e44b4fe4 --- /dev/null +++ b/agents/django-reviewer.md @@ -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?" diff --git a/docs/stale-pr-salvage-ledger.md b/docs/stale-pr-salvage-ledger.md index 592953bc..58c3b757 100644 --- a/docs/stale-pr-salvage-ledger.md +++ b/docs/stale-pr-salvage-ledger.md @@ -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 | diff --git a/docs/zh-CN/AGENTS.md b/docs/zh-CN/AGENTS.md index a4fac6e9..1259a0ca 100644 --- a/docs/zh-CN/AGENTS.md +++ b/docs/zh-CN/AGENTS.md @@ -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/ — 始终遵循的指导方针(通用 + 每种语言) diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md index 2376852a..863dc638 100644 --- a/docs/zh-CN/README.md +++ b/docs/zh-CN/README.md @@ -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 条指令 | diff --git a/skills/django-celery/SKILL.md b/skills/django-celery/SKILL.md new file mode 100644 index 00000000..05c47126 --- /dev/null +++ b/skills/django-celery/SKILL.md @@ -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 diff --git a/tests/docs/stale-pr-salvage-ledger.test.js b/tests/docs/stale-pr-salvage-ledger.test.js index a1a8f199..49050351 100644 --- a/tests/docs/stale-pr-salvage-ledger.test.js +++ b/tests/docs/stale-pr-salvage-ledger.test.js @@ -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}`); -