@mrciphersmith/keryx 0.3.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/cli.js +872 -316
  2. package/docs/README.md +2 -0
  3. package/package.json +1 -1
  4. package/src/gdskills/bundled/install-manifest.json +319 -76
  5. package/src/gdskills/bundled/stacks/django/agent-refs.json +3 -0
  6. package/src/gdskills/bundled/stacks/django/governance/eval.json +1763 -0
  7. package/src/gdskills/bundled/stacks/django/governance/scout.json +40 -0
  8. package/src/gdskills/bundled/stacks/django/pack.json +43 -0
  9. package/src/gdskills/bundled/stacks/django/rules/coding-style.mdc +80 -0
  10. package/src/gdskills/bundled/stacks/django/rules/patterns.mdc +92 -0
  11. package/src/gdskills/bundled/stacks/django/rules/security.mdc +92 -0
  12. package/src/gdskills/bundled/stacks/django/rules/testing.mdc +89 -0
  13. package/src/gdskills/bundled/stacks/django/skills/django-build-fix/SKILL.md +149 -0
  14. package/src/gdskills/bundled/stacks/django/skills/django-build-fix/evals.json +49 -0
  15. package/src/gdskills/bundled/stacks/django/skills/django-code-review/SKILL.md +137 -0
  16. package/src/gdskills/bundled/stacks/django/skills/django-code-review/evals.json +48 -0
  17. package/src/gdskills/bundled/stacks/django/skills/django-implementation/SKILL.md +147 -0
  18. package/src/gdskills/bundled/stacks/django/skills/django-implementation/evals.json +75 -0
  19. package/src/gdskills/bundled/stacks/django/skills/django-migrate/SKILL.md +166 -0
  20. package/src/gdskills/bundled/stacks/django/skills/django-migrate/evals.json +49 -0
  21. package/src/gdskills/bundled/stacks/django/skills/django-testing/SKILL.md +130 -0
  22. package/src/gdskills/bundled/stacks/django/skills/django-testing/evals.json +48 -0
  23. package/src/gdskills/bundled/stacks/fastapi/agent-refs.json +3 -0
  24. package/src/gdskills/bundled/stacks/fastapi/governance/eval.json +1777 -0
  25. package/src/gdskills/bundled/stacks/fastapi/governance/scout.json +34 -0
  26. package/src/gdskills/bundled/stacks/fastapi/pack.json +43 -0
  27. package/src/gdskills/bundled/stacks/fastapi/rules/coding-style.mdc +68 -0
  28. package/src/gdskills/bundled/stacks/fastapi/rules/patterns.mdc +108 -0
  29. package/src/gdskills/bundled/stacks/fastapi/rules/security.mdc +99 -0
  30. package/src/gdskills/bundled/stacks/fastapi/rules/testing.mdc +85 -0
  31. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/SKILL.md +157 -0
  32. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/evals.json +76 -0
  33. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/SKILL.md +150 -0
  34. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/evals.json +74 -0
  35. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/SKILL.md +158 -0
  36. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/evals.json +75 -0
  37. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/SKILL.md +146 -0
  38. package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/evals.json +74 -0
  39. package/src/gdskills/bundled/stacks/java-kotlin-spring/agent-refs.json +3 -0
  40. package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/eval.json +2194 -0
  41. package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/scout.json +39 -0
  42. package/src/gdskills/bundled/stacks/java-kotlin-spring/pack.json +40 -0
  43. package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/coding-style.mdc +67 -0
  44. package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/patterns.mdc +65 -0
  45. package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/security.mdc +69 -0
  46. package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/testing.mdc +80 -0
  47. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/SKILL.md +144 -0
  48. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/evals.json +74 -0
  49. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/SKILL.md +129 -0
  50. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/evals.json +74 -0
  51. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/SKILL.md +147 -0
  52. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/evals.json +75 -0
  53. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/SKILL.md +139 -0
  54. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/evals.json +74 -0
  55. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/SKILL.md +128 -0
  56. package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/evals.json +73 -0
  57. package/src/gdskills/bundled/stacks/python/agent-refs.json +2 -1
  58. package/src/gdskills/bundled/stacks/python/pack.json +1 -1
  59. package/src/gdskills/bundled/stacks/rust/agent-refs.json +3 -0
  60. package/src/gdskills/bundled/stacks/rust/governance/eval.json +1823 -0
  61. package/src/gdskills/bundled/stacks/rust/governance/scout.json +32 -0
  62. package/src/gdskills/bundled/stacks/rust/pack.json +42 -0
  63. package/src/gdskills/bundled/stacks/rust/rules/coding-style.mdc +93 -0
  64. package/src/gdskills/bundled/stacks/rust/rules/patterns.mdc +85 -0
  65. package/src/gdskills/bundled/stacks/rust/rules/security.mdc +85 -0
  66. package/src/gdskills/bundled/stacks/rust/rules/testing.mdc +82 -0
  67. package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/SKILL.md +141 -0
  68. package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/evals.json +78 -0
  69. package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/SKILL.md +127 -0
  70. package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/evals.json +72 -0
  71. package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/SKILL.md +133 -0
  72. package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/evals.json +79 -0
  73. package/src/gdskills/bundled/stacks/rust/skills/rust-testing/SKILL.md +130 -0
  74. package/src/gdskills/bundled/stacks/rust/skills/rust-testing/evals.json +75 -0
  75. package/src/gdskills/bundled/agents/python-build-fixer.md +0 -52
  76. package/src/gdskills/bundled/agents/python-code-auditor.md +0 -49
@@ -0,0 +1,40 @@
1
+ [
2
+ {
3
+ "query": "Use when a Django project fails manage.py check, makemigrations/migrate, the test suite, or lint/type-check -- resolves migration conflicts and inconsistent migration history, ImproperlyConfigured settings errors, app-loading/circular-import errors, mypy/django-stubs type errors, and ruff failures with the smallest root-cause fix. Not for adding a feature (see django-implementation) or fixing test content (see django-testing).",
4
+ "decision": "fork",
5
+ "topMatch": "python/python-build-fix",
6
+ "recordedAt": "2026-09-25T18:06:19.335Z",
7
+ "skillName": "django-build-fix",
8
+ "justification": "top match python/python-build-fix (0.3-0.55 range) is the same build-fix category but for generic Python tooling, not Django's own manage.py/migration/ImproperlyConfigured failure modes; a fork scoped to Django's own build/check/migration errors is warranted since the shared category is deliberate but the specific failure surface (makemigrations conflicts, app-loading, django-stubs) is not covered by python-build-fix."
9
+ },
10
+ {
11
+ "query": "Use when reviewing Django changes for correctness and injection-class risks -- checks N+1 queries missing select_related/prefetch_related, mark_safe/|safe applied to user-traceable data, raw()/extra() built by string interpolation, @csrf_exempt on a session-authenticated view, missing/hand-edited migrations, and settings.py misconfiguration (DEBUG, SECRET_KEY, ALLOWED_HOSTS). Read-only: reports findings, does not edit code.",
12
+ "decision": "create",
13
+ "topMatch": "fastapi/fastapi-code-review",
14
+ "recordedAt": "2026-09-25T18:06:19.640Z",
15
+ "skillName": "django-code-review",
16
+ "justification": "top match python/python-code-review is the same review category but for generic Python risks (mutable defaults, bare except, resource leaks), not Django's own injection-class sinks (N+1 via select_related, mark_safe/|safe XSS, raw()/extra() SQL, csrf_exempt, migration hygiene); a Django-scoped fork covers concerns python-code-review has no visibility into."
17
+ },
18
+ {
19
+ "query": "Use when implementing or extending a feature in a Django (5.x) project -- covers models and migrations, class-based/function-based views, forms and serializers, querysets with select_related/prefetch_related to avoid N+1, and the framework's CSRF/escaping defaults. Not for plain Python with no Django import (see python-implementation) or a Django version-major upgrade (see django-migrate).",
20
+ "decision": "fork",
21
+ "topMatch": "django/django-code-review",
22
+ "recordedAt": "2026-09-25T18:06:19.952Z",
23
+ "skillName": "django-implementation",
24
+ "justification": "top match django/django-testing is this same pack's test-authoring skill, sharing only generic Django-project vocabulary (models, views, pytest-django) from its own test-authoring scope, not implementation/typing/queryset-design guidance; decision is fork since both are legitimately Django-scoped but cover disjoint workflows (writing production code vs. writing tests)."
25
+ },
26
+ {
27
+ "query": "Use when generating, reviewing, or safely applying Django schema/data migrations -- makemigrations for a model change, RunPython data migrations with forward/reverse functions, merge migrations for divergent branches, and staged rollout for a migration that must ship alongside a multi-phase deploy (e.g. a column removal). Not for a Django-major-version framework upgrade (there is no separate django-upgrade skill in this pack; treat a version jump as django-implementation plus this skill's migration discipline) or for fixing a broken/conflicting migration that's blocking the build (see django-build-fix).",
28
+ "decision": "create",
29
+ "topMatch": "java-kotlin-spring/java-kotlin-spring-migrate",
30
+ "recordedAt": "2026-09-25T18:06:20.259Z",
31
+ "skillName": "django-migrate"
32
+ },
33
+ {
34
+ "query": "Use when writing Django TestCase/SimpleTestCase classes, simulating browser requests against a view with Django's own request-simulation helper, wiring pytest-django fixtures (db, django_user_model), building factory_boy model factories, or asserting on permission/form/queryset behavior in a Django app's own test suite. Django-specific: ORM assertions, migration-aware test databases, and DRF serializer/view tests, scoped to code that actually imports Django.",
35
+ "decision": "create",
36
+ "topMatch": "fastapi/fastapi-testing",
37
+ "recordedAt": "2026-09-25T18:06:20.569Z",
38
+ "skillName": "django-testing"
39
+ }
40
+ ]
@@ -0,0 +1,43 @@
1
+ {
2
+ "id": "django",
3
+ "family": "framework",
4
+ "extends": "python",
5
+ "modules": ["django-rules", "django-skills"],
6
+ "detectionMarkers": ["django"],
7
+ "provenance": {
8
+ "origin": "authored",
9
+ "sourceRef": "flow 335, Wave 4 batch 3"
10
+ },
11
+ "stability": "experimental",
12
+ "skills": {
13
+ "implement": ["django-implementation"],
14
+ "test": ["django-testing"],
15
+ "review": ["django-code-review"],
16
+ "build-fix": ["django-build-fix"],
17
+ "migrate": ["django-migrate"]
18
+ },
19
+ "agentProfile": {
20
+ "displayName": "Django",
21
+ "auditFocus": [
22
+ "a loop that issues one query per row where `select_related`/`prefetch_related` would collapse it (N+1)",
23
+ "`mark_safe`/the `|safe` template filter applied to anything that traces back to user input",
24
+ "`.raw()`/`.extra()` built with f-strings/`%`/`.format()` instead of the `params` argument",
25
+ "`@csrf_exempt` or `CsrfViewMiddleware` disabled on a view that actually needs CSRF protection",
26
+ "a model field or migration change with no corresponding migration file, or a hand-edited migration that drifts from `makemigrations` output",
27
+ "`SECRET_KEY`/`DEBUG=True`/permissive `ALLOWED_HOSTS` reaching a production settings module"
28
+ ],
29
+ "buildCommands": [
30
+ "python manage.py check",
31
+ "python manage.py makemigrations --check --dry-run",
32
+ "ruff check . / ruff format --check .",
33
+ "mypy . (with django-stubs) or the project's configured type checker",
34
+ "python manage.py test # or: pytest (pytest-django) if the project uses it"
35
+ ],
36
+ "fixGuardrails": [
37
+ "never add `mark_safe`/`|safe` to route around an escaping error instead of fixing the underlying markup",
38
+ "never disable CSRF (`@csrf_exempt`, removing `CsrfViewMiddleware`) to make a failing request succeed",
39
+ "never hand-edit a generated migration file to dodge a `makemigrations` conflict without resolving the actual model drift",
40
+ "never widen a narrowed queryset/model field type or drop a validator just to make a check pass"
41
+ ]
42
+ }
43
+ }
@@ -0,0 +1,80 @@
1
+ ---
2
+ extends: common
3
+ paths: ["**/models.py", "**/models/**/*.py", "**/views.py", "**/views/**/*.py", "**/forms.py", "**/serializers.py", "**/urls.py", "**/admin.py", "**/*.py"]
4
+ metadata:
5
+ origin: authored
6
+ ---
7
+
8
+ # Django coding style
9
+
10
+ Narrows `python`'s coding style to Django-project conventions. Applies to
11
+ `*.py` files inside a Django project; non-Django Python in the same
12
+ repository (a standalone script, a shared library package with no Django
13
+ import) still comes from `python`'s own coding-style rule, not this file.
14
+
15
+ ## App and project layout
16
+
17
+ - One Django "app" per cohesive domain concern (`billing/`, `accounts/`),
18
+ each with its own `models.py`/`views.py`/`urls.py`/`migrations/` — do not
19
+ grow a single app into an unrelated dumping ground once it outgrows one
20
+ clear responsibility.
21
+ - Name an app's `urls.py` route names with the app as a namespace prefix
22
+ (`billing:invoice-detail`) and reverse them with `reverse()`/`{% url %}`,
23
+ never a hand-built path string, so a route can move without breaking every
24
+ caller.
25
+ - Keep `settings.py` split by environment (`settings/base.py` +
26
+ `dev.py`/`prod.py`, or `django-environ`/`django-configurations`) when the
27
+ project already uses that layout; match the existing convention rather
28
+ than introducing a second settings-splitting scheme.
29
+
30
+ ## Models
31
+
32
+ - Give every model field an explicit `verbose_name`/`help_text` when the
33
+ field name alone would not be self-explanatory to someone using the admin
34
+ or a generated form.
35
+ - Use `related_name` on a `ForeignKey`/`ManyToManyField`/`OneToOneField`
36
+ whenever the default (`<model>_set`) would collide or read unclearly at
37
+ the call site; pick a name that reads naturally from the related side
38
+ (`author.books`, not `author.book_set`).
39
+ - Put field-level defaults and choices near the field (`choices=Status.choices`
40
+ with a `TextChoices`/`IntegerChoices` enum), not a bare tuple of literal
41
+ strings repeated at each call site.
42
+ - Override `save()`/add a `clean()` only for invariants that must hold for
43
+ every write path (including the admin and shell); a validation rule that
44
+ only applies to one form belongs on that `Form`, not the model.
45
+
46
+ ## Views and URLs
47
+
48
+ - Prefer a class-based view (`ListView`, `DetailView`, `CreateView`, or a
49
+ DRF `APIView`/`ViewSet` when the project uses Django REST Framework) for
50
+ the framework's standard CRUD shapes; reach for a function-based view when
51
+ the logic does not map cleanly onto a generic view's hooks — current
52
+ Django guidance treats CBVs and FBVs as equally supported, not one as
53
+ deprecated.
54
+ - Keep business logic out of the view body: a view coordinates
55
+ request/response and delegates the actual work to a model method, a form's
56
+ `clean`/`save`, or a service function — a view that inlines several
57
+ unrelated queries and branches is a sign the logic belongs elsewhere.
58
+ - Name URL patterns and match the project's existing `path()`/`re_path()`
59
+ convention; prefer `path()` with converters (`<int:pk>`) over `re_path()`
60
+ unless the match genuinely needs a regex.
61
+
62
+ ## Forms and serializers
63
+
64
+ - Validate with a `Form`/`ModelForm` (or DRF `Serializer`/`ModelSerializer`)
65
+ `clean_<field>`/`clean()`/`validate_<field>`/`validate()` method, not
66
+ ad-hoc validation scattered in the view after the fact.
67
+ - Use `ModelForm`/`ModelSerializer` with an explicit `fields = [...]` list
68
+ (never `fields = "__all__"` on a form or serializer that accepts
69
+ user-submitted data) so a new model field is not silently exposed for
70
+ mass-assignment until it is deliberately added.
71
+
72
+ ## Imports and structure
73
+
74
+ - Follow `python`'s stdlib/third-party/local import grouping; within
75
+ "third-party", Django's own imports (`django.db`, `django.http`, ...)
76
+ commonly sort before other third-party packages alphabetically — match
77
+ what the project's `ruff`/`isort` config already produces rather than
78
+ hand-ordering.
79
+ - Import from `django.conf.settings`, never re-read environment variables
80
+ directly in application code that already has a settings value for it.
@@ -0,0 +1,92 @@
1
+ ---
2
+ extends: common
3
+ paths: ["**/models.py", "**/models/**/*.py", "**/views.py", "**/views/**/*.py", "**/managers.py", "**/migrations/**/*.py", "**/*.py"]
4
+ metadata:
5
+ origin: authored
6
+ ---
7
+
8
+ # Django patterns
9
+
10
+ Narrows `python`'s design guidance to idiomatic Django (5.x) ORM, view, and
11
+ migration patterns and the anti-patterns they replace. Applies to Django
12
+ project files; a non-Django module in the same repo still follows `python`'s
13
+ own `rules/patterns.mdc`.
14
+
15
+ ## Query optimization (the N+1 problem)
16
+
17
+ - When a view or serializer will access a related object for every row in a
18
+ queryset (`obj.author.name` in a loop, or a DRF nested serializer field),
19
+ add `select_related("author")` for a `ForeignKey`/`OneToOneField` and
20
+ `prefetch_related("tags")` for a `ManyToManyField`/reverse `ForeignKey` —
21
+ `select_related` joins in one query; `prefetch_related` issues one extra
22
+ query per relation and joins in Python. Combine both when a
23
+ `prefetch_related` path itself needs a `select_related` (`Prefetch("best_pizza__toppings")`
24
+ after `select_related("best_pizza")`).
25
+ - Use `only()`/`defer()` to trim columns from a wide model only when a
26
+ profiled query shows the extra columns matter; reaching for it by default
27
+ makes the queryset fragile to a later field access that silently triggers
28
+ a new query.
29
+ - Prefer `QuerySet.exists()` over `bool(qs)`/`len(qs) > 0` for an
30
+ existence check, and `.count()` over `len(list(qs))`, so the database does
31
+ the work instead of materializing every row into Python.
32
+ - Use `bulk_create`/`bulk_update`/`update()` for a batch write instead of a
33
+ Python loop calling `.save()` per instance — a per-instance loop is an N+1
34
+ on the write side, and skips `bulk_create`'s single round trip (note it
35
+ bypasses `save()`/signals unless explicitly configured to call them).
36
+
37
+ ## Migrations
38
+
39
+ - Run `python manage.py makemigrations` to generate a migration from a model
40
+ change; never hand-write a migration's `operations` list to match a model
41
+ edit by guesswork — let Django diff the models, then review the generated
42
+ file.
43
+ - Give a migration that needs to transform existing row data a `RunPython`
44
+ operation with both a forward and a reverse function (or
45
+ `migrations.RunPython.noop` when reversal is genuinely not meaningful),
46
+ not a schema-only migration that silently leaves old data inconsistent
47
+ with a new constraint.
48
+ - Keep a destructive migration (dropping a column/table another release
49
+ still reads) behind a deploy sequence that ships the code change first,
50
+ then the schema change, when the project deploys code and migrations
51
+ separately — a same-release drop-and-read is a two-phase problem, not a
52
+ one-step migration.
53
+ - Never edit a migration file that has already been applied in another
54
+ environment (shared branch, staging, production) to "fix" it in place;
55
+ add a new migration instead — editing history desyncs any environment
56
+ that already recorded the old migration as applied.
57
+
58
+ ## Models as the single source of truth
59
+
60
+ - Put a field-level invariant (a `CheckConstraint`, a `unique_together`/
61
+ `UniqueConstraint`, a `validators=[...]` list) on the model field or
62
+ `Meta.constraints`, not only in a form's `clean()` — validation the
63
+ database itself does not enforce can still be violated by a direct
64
+ `.save()`, the admin, a shell, or another service writing to the same
65
+ table.
66
+ - Use a model `Manager`/`QuerySet` subclass to name a repeated filter
67
+ (`Order.objects.pending()`) instead of repeating the same `.filter(...)`
68
+ chain at every call site.
69
+
70
+ ## Views
71
+
72
+ - A view that only reads and renders/serializes belongs on a generic
73
+ class-based view (`ListView`/`DetailView`/DRF `ReadOnlyModelViewSet`); a
74
+ view with custom branching logic per HTTP method is a legitimate reason to
75
+ drop to a function-based view or override a specific CBV method
76
+ (`get_queryset`, `form_valid`) rather than reimplementing the whole
77
+ request/response cycle.
78
+ - Return the framework's own response helpers (`HttpResponseRedirect`/
79
+ `redirect()`, `JsonResponse`, DRF's `Response`) instead of building a raw
80
+ `HttpResponse` with a hand-set `Content-Type` and manually serialized body.
81
+
82
+ ## Anti-patterns to flag, not introduce
83
+
84
+ - A `for` loop over a queryset that touches a related object per iteration
85
+ with no `select_related`/`prefetch_related` upstream — this is Django's
86
+ most common performance bug and is invisible in a small dev dataset.
87
+ - `fields = "__all__"` on a `ModelForm`/`ModelSerializer` that accepts
88
+ user input, silently exposing every future field for mass-assignment.
89
+ - A signal handler (`post_save`, etc.) that performs a slow or
90
+ externally-visible side effect (sending an email, calling a third-party
91
+ API) synchronously inside the request/response cycle instead of queuing it
92
+ through the project's async task runner (Celery or equivalent).
@@ -0,0 +1,92 @@
1
+ ---
2
+ extends: common
3
+ paths: ["**/views.py", "**/views/**/*.py", "**/forms.py", "**/serializers.py", "**/templatetags/**/*.py", "**/settings*.py", "**/settings/**/*.py"]
4
+ metadata:
5
+ origin: authored
6
+ ---
7
+
8
+ # Django security
9
+
10
+ Narrows `python`'s security guidance to Django's own injection-class sinks:
11
+ views, forms/serializers, template-tag modules, and settings — the Python
12
+ files where Django's built-in CSRF/XSS/SQL-injection protections are either
13
+ relied on or bypassed (template-rendered `.html` files are not `.py`, so
14
+ they are out of this rule's `paths:` scope by construction, even though the
15
+ XSS guidance below still applies to what those views/templatetags render
16
+ into them). Applies only to the listed `.py` files; a non-request-facing
17
+ module (a standalone script, a batch job with no HTTP surface) still
18
+ follows `python`'s own `rules/security.mdc`.
19
+
20
+ ## CSRF
21
+
22
+ - Leave `django.middleware.csrf.CsrfViewMiddleware` enabled and let
23
+ `{% csrf_token %}`/the framework's CSRF cookie handling do its job for
24
+ every state-changing view (`POST`/`PUT`/`PATCH`/`DELETE`).
25
+ - Never add `@csrf_exempt` to make a failing request succeed without first
26
+ establishing that the endpoint has another, equivalent protection (a
27
+ signed webhook signature, a non-browser API authenticated by token, never
28
+ session/cookie auth) — CSRF exemption on a session-authenticated endpoint
29
+ reopens the exact attack the middleware exists to block.
30
+ - A DRF `APIView`/`ViewSet` using `SessionAuthentication` still needs CSRF
31
+ protection on unsafe methods (DRF enforces this by default for session
32
+ auth); do not disable it to work around a client that isn't sending the
33
+ token — fix the client to send it.
34
+
35
+ ## Cross-site scripting (XSS)
36
+
37
+ - Rely on Django's automatic template auto-escaping for any value that
38
+ reaches an HTML template — do not disable it project-wide.
39
+ - Never apply `mark_safe()`/the `|safe` template filter/`format_html()`'s
40
+ raw-string argument to a value that traces back to user input (a form
41
+ field, a query parameter, an uploaded file's name, anything stored from a
42
+ prior user submission) — `mark_safe`/`|safe` tell Django to skip escaping
43
+ entirely, and reintroduce stored or reflected XSS on exactly the value the
44
+ auto-escaper was protecting. `mark_safe`/`|safe` are for markup the
45
+ application itself constructs and fully controls (a trusted CMS field
46
+ rendered through a sanitizer, static admin-authored HTML) — never for
47
+ data supplied by a request.
48
+ - Build dynamically-assembled HTML with `format_html()`/`format_html_join()`
49
+ (which escape each interpolated argument) instead of string
50
+ concatenation or an f-string passed to `mark_safe()`.
51
+
52
+ ## SQL injection
53
+
54
+ - Django's ORM parameterizes query values automatically
55
+ (`.filter(name=user_input)`); this protection does not extend to
56
+ `.raw()` or `.extra()` — pass user-derived values through the `params`
57
+ argument (`Model.objects.raw("... WHERE id = %s", [id])`), never by
58
+ interpolating them into the SQL string with an f-string/`%`/`.format()`.
59
+ - Treat `.extra()` as legacy; prefer expressing the same query with the ORM
60
+ (`annotate`, `F()`, `Q()`, `Func`) or a parameterized `.raw()` call — the
61
+ Django security team explicitly does not treat unsanitized input reaching
62
+ `.extra()`'s `select`/`where` clauses as a framework bug, because
63
+ `.extra()` was never given the ORM's own escaping.
64
+
65
+ ## Settings and secrets
66
+
67
+ - `SECRET_KEY` must be at least 50 characters, unique per environment, never
68
+ the `django-insecure-` placeholder `startproject` generates, and never
69
+ committed to source — read it from the environment/secret store.
70
+ - `DEBUG` must be `False` in any settings module used in production;
71
+ `DEBUG = True` leaks stack traces, local variable values, and settings
72
+ contents to any visitor who triggers an unhandled exception.
73
+ - `ALLOWED_HOSTS` must be a real, non-empty list of the application's actual
74
+ hostnames in production, never `["*"]` — an empty or wildcard value
75
+ defeats the Host-header validation Django's deploy checks exist to
76
+ enforce.
77
+ - Run `python manage.py check --deploy` before a production release; it
78
+ flags exactly these three (and related HSTS/secure-cookie) settings
79
+ automatically — don't hand-audit `settings.py` for them instead.
80
+
81
+ ## Red flags
82
+
83
+ - "I'll add `|safe` here, this content only ever comes from an admin form" —
84
+ if that admin form field can ever hold end-user-influenced text (a
85
+ support agent pasting a customer's message, a bio field editable by the
86
+ account owner), the trust boundary already includes untrusted input.
87
+ - "`.extra()` with an f-string is fine, this value is just an internal ID"
88
+ — an "internal" ID that ever originates from a URL param, form field, or
89
+ header is user-controlled regardless of how it's described; use `params`.
90
+ - "`@csrf_exempt` unblocks this POST and the tests pass now" — passing
91
+ tests after exempting CSRF proves the exemption works, not that it's
92
+ safe; verify the endpoint has an equivalent protection first.
@@ -0,0 +1,89 @@
1
+ ---
2
+ extends: common
3
+ paths: ["**/tests.py", "**/tests/**/*.py", "**/test_*.py", "**/*_test.py", "**/conftest.py"]
4
+ metadata:
5
+ origin: authored
6
+ ---
7
+
8
+ # Django testing
9
+
10
+ Narrows `python`'s `pytest` testing guidance (`python/rules/testing.mdc`) to
11
+ Django's own test tooling: the Django `TestCase`/test `Client`, fixtures and
12
+ factories for model data, and `pytest-django` where a project has adopted
13
+ it. Applies to Django test files; a non-Django test module in the same repo
14
+ still follows `python`'s own testing rule.
15
+
16
+ ## Choosing the test base
17
+
18
+ - Use `django.test.TestCase` for anything that touches the ORM — it wraps
19
+ each test in a transaction that rolls back afterward, so tests stay
20
+ isolated without manually cleaning up rows.
21
+ - Use `django.test.SimpleTestCase` (or mark it `databases = []`
22
+ under `pytest-django`) for a test that touches no database at all — it
23
+ skips the per-test transaction wrapper and is faster for pure logic.
24
+ - Where the project has adopted `pytest-django`, use its `@pytest.mark.django_db`
25
+ fixture-based style (plain `def test_x(db, client): ...` functions,
26
+ `django_user_model`, `rf` for `RequestFactory`) rather than mixing it with
27
+ `unittest`-style `TestCase` subclasses in the same suite — match whichever
28
+ the project already uses; do not introduce the other style alongside it.
29
+
30
+ ## The test client
31
+
32
+ - Drive request/response behavior through `self.client`
33
+ (`TestCase`)/the `client` fixture (`pytest-django`), not by calling a view
34
+ function directly with a hand-built request object — the client exercises
35
+ URL resolution, middleware, and template rendering the same way a real
36
+ request does.
37
+ - Assert on `response.status_code`, `response.context`, and
38
+ `response.templates` (or the parsed JSON body for an API) rather than
39
+ inspecting internal view state that the client's response object doesn't
40
+ expose.
41
+ - Log a test user in through `self.client.force_login(user)` (fast, skips
42
+ the auth backend) or `self.client.login(...)` (exercises the real auth
43
+ backend) — pick `force_login` for tests where the auth mechanism itself
44
+ isn't what's under test.
45
+
46
+ ## Model data: fixtures and factories
47
+
48
+ - Prefer a factory (`factory_boy`'s `DjangoModelFactory`, if the project
49
+ already depends on it) over a JSON/YAML fixture file for test data —
50
+ factories build valid model instances from code, so a new required field
51
+ doesn't silently break every fixture file that predates it.
52
+ - Keep a `conftest.py`/factory module for related test data in the
53
+ narrowest app-level location that covers the tests needing it, matching
54
+ `python`'s own "narrowest `conftest.py`" guidance.
55
+ - Build only the fields a test actually asserts on or that a model
56
+ constraint requires; a factory that sets every field to a fixed literal
57
+ makes tests brittle to unrelated schema changes.
58
+
59
+ ## Forms, views, and permissions
60
+
61
+ - Test a `Form`/DRF `Serializer` by constructing it directly with test data
62
+ and asserting `is_valid()`/`.errors`, separately from a full
63
+ client-driven view test — this isolates a validation bug from a
64
+ view-wiring bug.
65
+ - For a permission-gated view, assert both the authorized case (200/302 to
66
+ the expected page) and the unauthorized case (302 to login, or 403) —
67
+ a view test that only exercises the happy path never proves the
68
+ permission check actually runs.
69
+
70
+ ## Migrations in tests
71
+
72
+ - Do not disable migrations for the whole suite (`MIGRATION_MODULES` set to
73
+ a no-op) purely for speed unless the project has already made that
74
+ tradeoff deliberately — a suite that skips migrations can pass while a
75
+ real deploy's migration path is broken.
76
+ - A test asserting data-migration behavior (a `RunPython` step) belongs in
77
+ its own test using `django.test.migrations`' testing utilities or the
78
+ project's configured migration-test helper, not folded into an unrelated
79
+ model test.
80
+
81
+ ## Determinism
82
+
83
+ - Freeze time (`freezegun`, or `pytest-django`'s equivalent) instead of
84
+ asserting against `timezone.now()` computed twice in the test and the
85
+ code under test — a real few-millisecond drift between the two calls is a
86
+ known source of flaky Django tests.
87
+ - Never depend on a specific queryset row order unless the code under test
88
+ applies an explicit `order_by()` — Django does not guarantee row order
89
+ without one, even for a small SQLite test database.
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: django-build-fix
3
+ description: "Use when a Django project fails manage.py check, makemigrations/migrate, the test suite, or lint/type-check -- resolves migration conflicts and inconsistent migration history, ImproperlyConfigured settings errors, app-loading/circular-import errors, mypy/django-stubs type errors, and ruff failures with the smallest root-cause fix. Not for adding a feature (see django-implementation) or fixing test content (see django-testing)."
4
+ triggers:
5
+ - "manage.py check is failing"
6
+ - "fix this django migration conflict"
7
+ - "django ImproperlyConfigured error"
8
+ - "django app isn't loading, circular import"
9
+ - "mypy is failing on this django model"
10
+ - "makemigrations wants to make a conflicting migration"
11
+ - "django test suite won't even start"
12
+ metadata:
13
+ origin: authored
14
+ category: build-fix
15
+ version: "1.0.0"
16
+ compatible_harnesses: "claude,codex,cursor,zed,opencode"
17
+ license: "MIT"
18
+ ---
19
+
20
+ # Django build-fix
21
+
22
+ Resolve a broken Django `check`/migration/settings/import/type-check/lint
23
+ failure with the smallest change that fixes the actual cause. Scoped to
24
+ making the Django toolchain green again — for adding a feature use
25
+ `django-implementation`, for writing/fixing test *content* (not a collection
26
+ or startup error) use `django-testing`, for reviewing without fixing use
27
+ `django-code-review`.
28
+
29
+ ## Workflow
30
+
31
+ ### Step 1: Reproduce and classify the failure
32
+
33
+ Run the project's own configured commands (discover the run prefix from
34
+ `pyproject.toml`/lockfile presence):
35
+
36
+ ```bash
37
+ python manage.py check
38
+ python manage.py makemigrations --check --dry-run
39
+ ruff check .
40
+ ruff format --check .
41
+ mypy . # with django-stubs, if configured
42
+ python manage.py test # or: pytest, if pytest-django is configured
43
+ ```
44
+
45
+ Read the *first* error in each tool's output. Classify:
46
+
47
+ - **`ImproperlyConfigured`** — a required setting missing/misconfigured
48
+ (`SECRET_KEY`, `DATABASES`, `AUTH_USER_MODEL` pointing at a model that
49
+ doesn't exist), or an app used before `AppConfig.ready()`/app registry is
50
+ populated.
51
+ - **Migration conflict** — two migrations in the same app both claim the
52
+ same `dependencies` leaf (usually from two branches adding migrations in
53
+ parallel), or `makemigrations --check` reports an unmigrated model change.
54
+ - **App-loading / circular import** — a model or app imports another app's
55
+ model at module import time before Django's app registry is ready, or two
56
+ apps import each other's models directly instead of via a string reference
57
+ (`"otherapp.Model"`) in a `ForeignKey`.
58
+ - **Type errors** — `mypy`/`django-stubs` reports a real mismatch, often
59
+ around manager/queryset generics or a model field's inferred type.
60
+ - **Lint failures** — `ruff check` reports a rule violation.
61
+ - **Test suite won't start** — an import error in a test file, `conftest.py`,
62
+ or a fixture referencing a model/setting that doesn't exist.
63
+
64
+ ### Step 2: Find the root cause
65
+
66
+ - **`ImproperlyConfigured`**: read the exact message — it names the missing/
67
+ bad setting. Check the correct settings module is active
68
+ (`DJANGO_SETTINGS_MODULE`) before assuming the setting itself is wrong.
69
+ - **Migration conflict**: run `python manage.py makemigrations --merge` to
70
+ generate a merge migration when two branches added divergent migrations
71
+ from the same parent — review the generated merge, don't hand-edit
72
+ `dependencies` to force a resolution. For an unmigrated model change,
73
+ run `makemigrations` for the actual diff instead of skipping the check.
74
+ - **App-loading/circular import**: use a string reference
75
+ (`models.ForeignKey("otherapp.Model", ...)`) for a cross-app relation
76
+ instead of importing the model class directly; move an import that only
77
+ needs to run inside a function body (e.g. inside a signal handler) out of
78
+ module level if it's the actual cycle.
79
+ - **Type errors**: read the exact mismatch; check `django-stubs` is
80
+ installed and configured (`mypy_django_plugin` in `mypy.ini`/
81
+ `pyproject.toml`) before assuming the type itself is wrong — a missing
82
+ plugin config produces spurious errors on manager/queryset types.
83
+ - **Lint failures**: apply `ruff check --fix .` for mechanical fixes; fix
84
+ the code for a substantive rule, don't suppress it.
85
+ - **Test suite won't start**: apply the same import-error diagnosis above to
86
+ the failing test/`conftest.py`'s own imports.
87
+
88
+ ### Step 3: Apply the smallest fix
89
+
90
+ - Fix the actual cause identified in Step 2 — the missing setting, the
91
+ merge migration, the string-reference relation, the real type mismatch.
92
+ - Touch only what the failure requires; do not refactor unrelated code
93
+ while fixing a build failure.
94
+ - When a migration conflict needs a merge, generate it with
95
+ `makemigrations --merge` and review the result; never hand-edit
96
+ `dependencies`/`operations` to force a resolution.
97
+
98
+ ### Step 4: Verify
99
+
100
+ Re-run every command from Step 1 in order; all must exit 0. Also run
101
+ `python manage.py test`/`pytest` even when the original failure was only a
102
+ lint/type error — a fix can introduce a runtime regression static tooling
103
+ won't see.
104
+
105
+ ### Step 5: Report
106
+
107
+ ```
108
+ Fixed: ImproperlyConfigured: AUTH_USER_MODEL refers to model 'accounts.Member'
109
+ that has not been installed
110
+ Root cause: settings.py set AUTH_USER_MODEL = "accounts.Member" but
111
+ "accounts" was missing from INSTALLED_APPS.
112
+ Fix: added "accounts" to INSTALLED_APPS
113
+ Verified: manage.py check, makemigrations --check, ruff, mypy, test all green
114
+ ```
115
+
116
+ ## Rules
117
+
118
+ - NEVER hand-edit a migration's `dependencies`/`operations` to force past a
119
+ conflict — generate a merge migration with `makemigrations --merge` and
120
+ review it.
121
+ - NEVER add `# type: ignore`/`# noqa` as a blanket suppression to make a
122
+ real error disappear without fixing or explicitly justifying it inline.
123
+ - NEVER wrap a real settings/import error in a broad `try/except` to hide
124
+ it — fix the actual misconfiguration or import cycle.
125
+ - Fix the root cause with the smallest change; do not refactor beyond what
126
+ the failure requires.
127
+
128
+ ## Red Flags
129
+
130
+ | Rationalization | Why it is wrong |
131
+ |---|---|
132
+ | "I'll hand-edit the migration's `dependencies` to skip the conflict" | Desyncs the migration graph from what actually ran in other environments; generate a proper merge migration instead |
133
+ | "I'll import the model directly instead of using a string reference to dodge the circular import" | Papers over the app-loading order problem instead of fixing it; use `\"otherapp.Model\"` string references for cross-app relations |
134
+ | "mypy is noisy on Django models, I'll just add `# type: ignore` everywhere" | Usually means `django-stubs`'s mypy plugin isn't configured; fix the plugin config instead of suppressing every resulting error |
135
+ | "`makemigrations --check` fails, I'll just skip the check in CI" | Hides real model/migration drift instead of fixing it; generate the missing migration |
136
+
137
+ ## Verification
138
+
139
+ Do not report the fix done until all of the following hold:
140
+
141
+ - The originally failing command now exits 0.
142
+ - `python manage.py check`, `makemigrations --check --dry-run`, `ruff
143
+ check .`, `ruff format --check .`, `mypy .`, and the test suite (the
144
+ project's own configured equivalents) all exit 0.
145
+ - No new `# type: ignore`/`# noqa` was added without an inline reason.
146
+ - No migration file's `dependencies`/`operations` was hand-edited; a
147
+ conflict was resolved with a generated merge migration.
148
+ - `git status` shows only the files whose actual cause was diagnosed in
149
+ Step 2 — no unrelated refactor.
@@ -0,0 +1,49 @@
1
+ {
2
+ "triggers": {
3
+ "positive": [
4
+ "manage.py check is failing with ImproperlyConfigured, can you fix it",
5
+ "After merging release/2.1 into main, `makemigrations` reports two leaf nodes in the payments app's migration history for our Django project -- what's the right way to reconcile that before I merge?",
6
+ "Django app won't load, there's a circular import between two apps",
7
+ "Type-checking chokes on `Invoice.objects` with an incompatible-type error even though django-stubs is installed -- any idea why?",
8
+ "makemigrations wants to create a migration that conflicts with an existing one",
9
+ "Pytest blows up before collecting a single test in our Django app -- traceback points to a bad import inside conftest.py. What's going on?",
10
+ "ruff check is failing on this Django project"
11
+ ],
12
+ "negative": [
13
+ "Add a new feature to this Django view, the build is currently green",
14
+ "Write more pytest-django tests for this already-passing view",
15
+ "Review this Django migration for correctness, don't fix anything",
16
+ "Fix this ModuleNotFoundError in our plain Python script with no Django",
17
+ "Fix this FastAPI app that fails to start with a dependency resolution error",
18
+ "Fix this failing Jest test in our React frontend",
19
+ "npm run build is failing in our Node.js service"
20
+ ]
21
+ },
22
+ "scenarios": [
23
+ {
24
+ "id": "migration-conflict-merge-not-hand-edit",
25
+ "prompt": "Running `python manage.py makemigrations` fails with: 'Conflicting migrations detected; multiple leaf nodes in the migration graph: (0004_add_phone, 0004_add_address in accounts).' Fix it.",
26
+ "strictness": "high",
27
+ "expected_behavior": [
28
+ {
29
+ "grader": "judge",
30
+ "rubric": "A correct answer resolves a migration conflict with two leaf migrations by generating a merge migration (`python manage.py makemigrations --merge`) and reviewing the result, rather than hand-editing either migration's `dependencies` to force one order.",
31
+ "pass_criteria": [
32
+ "runs (or explicitly instructs running) `python manage.py makemigrations --merge` to generate a merge migration resolving the two leaf nodes",
33
+ "states that the generated merge migration should be reviewed before applying/committing it"
34
+ ],
35
+ "fail_criteria": [
36
+ "resolves the conflict by hand-editing one migration's `dependencies` list to point at the other, instead of generating a merge migration. Mentioning that hand-editing dependencies is unsafe only as a warning, while still recommending --merge, is not this failure."
37
+ ]
38
+ }
39
+ ],
40
+ "calibration": {
41
+ "known_right": "Run:\n```bash\npython manage.py makemigrations --merge\n```\nThis detects the two leaf migrations (`0004_add_phone` and `0004_add_address`, both children of the same parent) and generates a new migration, e.g. `0005_merge_20260925_1200.py`, whose `dependencies` list both leaves so the graph has a single path again. Review the generated file before committing -- confirm it doesn't silently drop any operation -- since `--merge` only needs a real conflict to resolve when both branches' changes are independent (as here, phone vs. address fields); if they touched the same field, the review step is where that would surface.",
42
+ "known_wrong": "Open `accounts/migrations/0004_add_address.py` and change its `dependencies` from `[(\"accounts\", \"0003_initial\")]` to `[(\"accounts\", \"0004_add_phone\")]` so it comes after `0004_add_phone` instead of being a sibling leaf. That resolves the 'multiple leaf nodes' error directly by hand-editing the dependency graph.",
43
+ "vague": "You need to merge the two migration branches together so there's only one path through the migration graph again.",
44
+ "subtle_wrong": "The cleanest fix here is to delete `0004_add_address.py` and regenerate it after `0004_add_phone` has been applied, so its `dependencies` naturally point at `0004_add_phone` instead of the shared parent -- that avoids needing a separate merge migration file at all and keeps the migration history linear."
45
+ },
46
+ "anti_patterns": ["hand-edit"]
47
+ }
48
+ ]
49
+ }