diff --git a/AGENTS.md b/AGENTS.md index ca076250..abd6ad9f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -139,11 +139,18 @@ Rules are `.mdc` files in `rules/`. Frontmatter: ```yaml --- description: -alwaysApply: true +alwaysApply: false +globs: + - "**/*.py" standards-version: 1.10.0 --- ``` +`alwaysApply: false` plus `globs` is the scoping contract: the rule loads only +when a matching file is in context. Setting `alwaysApply: true` makes `globs` +decorative and applies the rule everywhere, so the documented scope stops +describing behavior. Choose globs that cover every file the rule guards. + Rules encode anti-patterns. Each rule should show the wrong way, the right way, and a one-paragraph rationale. 30 to 80 lines is the right size. diff --git a/CLAUDE.md b/CLAUDE.md index c61a9bc8..7cabee24 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,9 +52,13 @@ VERSION - Source of truth for the repo version ## Rules (9) +Every rule sets `alwaysApply: false`, so the Scope column below is the rule's +actual load condition, not a description: a rule enters context only when a file +matching its `globs` does. Changing a glob changes when the rule fires. + | Rule | Scope | What it flags | | --- | --- | --- | -| prefer-data-over-ops-in-loops | Always on | `bpy.ops.*` calls inside iteration over many objects | +| prefer-data-over-ops-in-loops | `*.py` | `bpy.ops.*` calls inside iteration over many objects | | always-free-bmesh | `*.py` | `bmesh.new()` without paired `bm.free()` in a `try`/`finally` block | | target-extensions-platform-format | Add-on roots | Legacy `bl_info` only add-ons missing `blender_manifest.toml` | | type-annotate-props-and-defend-context | `*.py` | `bpy.props` defined as assignments, unguarded `context.active_object` | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c76ad9e4..4a21af97 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,13 +77,21 @@ showcase/ ```yaml --- description: One-line summary for humans and tooling. - alwaysApply: true + alwaysApply: false globs: - "**/*.py" standards-version: --- ``` + `alwaysApply: false` is deliberate and is what makes `globs` load-bearing: + the rule enters context only when a file it matches does. `alwaysApply: + true` would apply the rule in every context and make `globs` decorative, so + the two must never both be set as if they compose. Pick globs that cover + every file the rule is meant to guard before setting this — a glob that is + too narrow means the rule silently stops firing, which is worse than + over-applying. Record the scope in the `CLAUDE.md` rules table to match. + 3. Write 30 to 80 lines: the anti-pattern, a code example showing it wrong, a code example showing it right, and a short "Why it matters" section. ## Adding a Snippet diff --git a/rules/always-free-bmesh.mdc b/rules/always-free-bmesh.mdc index 610a532a..05858faa 100644 --- a/rules/always-free-bmesh.mdc +++ b/rules/always-free-bmesh.mdc @@ -1,6 +1,6 @@ --- description: Flag bmesh.new() calls without a paired bm.free() in a try/finally block. BMesh allocates C-side memory that Python's garbage collector cannot reclaim; missing free() leaks and eventually crashes Blender. -alwaysApply: true +alwaysApply: false globs: - "**/*.py" standards-version: 1.10.0 diff --git a/rules/no-unapplied-modifiers-on-export.mdc b/rules/no-unapplied-modifiers-on-export.mdc index db41f590..878dc8a1 100644 --- a/rules/no-unapplied-modifiers-on-export.mdc +++ b/rules/no-unapplied-modifiers-on-export.mdc @@ -1,6 +1,6 @@ --- description: Flag an export call on objects that still carry unapplied modifiers when the export arguments do not request evaluated geometry. The engine then receives the authored cage, not the modifier result. -alwaysApply: true +alwaysApply: false globs: - "**/*.py" standards-version: 1.10.0 diff --git a/rules/prefer-data-over-ops-in-loops.mdc b/rules/prefer-data-over-ops-in-loops.mdc index f6e68909..483c68f9 100644 --- a/rules/prefer-data-over-ops-in-loops.mdc +++ b/rules/prefer-data-over-ops-in-loops.mdc @@ -1,6 +1,6 @@ --- description: Flag bpy.ops.* calls inside iteration over many objects, meshes, or frames. Each bpy.ops call triggers a full depsgraph evaluation and UI redraw; loops slow down by orders of magnitude. Use bpy.data.* and bmesh instead. -alwaysApply: true +alwaysApply: false globs: - "**/*.py" standards-version: 1.10.0 diff --git a/rules/prefer-temp-override-over-context-copy.mdc b/rules/prefer-temp-override-over-context-copy.mdc index d71c28dc..329cc644 100644 --- a/rules/prefer-temp-override-over-context-copy.mdc +++ b/rules/prefer-temp-override-over-context-copy.mdc @@ -1,6 +1,6 @@ --- description: Flag uses of `bpy.context.copy()` to override context for an operator call. The copy-and-pass pattern was deprecated in Blender 4.x and the override semantics were removed in 5.x. Use `bpy.context.temp_override(**overrides)` as a context manager instead. -alwaysApply: true +alwaysApply: false globs: - "**/*.py" standards-version: 1.10.0 diff --git a/rules/target-extensions-platform-format.mdc b/rules/target-extensions-platform-format.mdc index 46058ee2..eb7ebf2e 100644 --- a/rules/target-extensions-platform-format.mdc +++ b/rules/target-extensions-platform-format.mdc @@ -1,6 +1,6 @@ --- description: Flag new Blender add-ons that ship only a legacy bl_info dict without a blender_manifest.toml. New add-ons must use the Extensions Platform format. bl_info may appear alongside as a fallback for backward compatibility, but the manifest is the source of truth. -alwaysApply: true +alwaysApply: false globs: - "**/__init__.py" - "**/blender_manifest.toml" diff --git a/rules/type-annotate-props-and-defend-context.mdc b/rules/type-annotate-props-and-defend-context.mdc index f7fdf932..5f0ff1d8 100644 --- a/rules/type-annotate-props-and-defend-context.mdc +++ b/rules/type-annotate-props-and-defend-context.mdc @@ -1,6 +1,6 @@ --- description: Flag two related anti-patterns. (1) bpy.props defined as class-level assignments instead of type annotations (deprecated since 2.8). (2) Code that touches bpy.context.active_object without guarding for None. -alwaysApply: true +alwaysApply: false globs: - "**/*.py" standards-version: 1.10.0 diff --git a/rules/use-correct-axis-rna-per-exporter.mdc b/rules/use-correct-axis-rna-per-exporter.mdc index 3082e3dc..304fd891 100644 --- a/rules/use-correct-axis-rna-per-exporter.mdc +++ b/rules/use-correct-axis-rna-per-exporter.mdc @@ -1,6 +1,6 @@ --- description: Flag export_scene.gltf calls that pass FBX axis_forward or axis_up, and export_scene.fbx calls that pass glTF export_yup. The two exporters do not share axis RNA. -alwaysApply: true +alwaysApply: false globs: - "**/*.py" standards-version: 1.10.0 diff --git a/rules/use-foreach-set-for-bulk-data.mdc b/rules/use-foreach-set-for-bulk-data.mdc index 7704742c..d38a63a9 100644 --- a/rules/use-foreach-set-for-bulk-data.mdc +++ b/rules/use-foreach-set-for-bulk-data.mdc @@ -1,6 +1,6 @@ --- description: Flag Python loops that set vertex coordinates, normals, UVs, or other bulk per-element data one element at a time. For meshes of more than a few thousand elements, this is 100x to 1000x slower than `mesh.vertices.foreach_set("co", flat_array)`, which writes through to C-level storage in one pass. -alwaysApply: true +alwaysApply: false globs: - "**/*.py" standards-version: 1.10.0 diff --git a/rules/validate-imported-mesh-scale.mdc b/rules/validate-imported-mesh-scale.mdc index ba6d4428..3086b01b 100644 --- a/rules/validate-imported-mesh-scale.mdc +++ b/rules/validate-imported-mesh-scale.mdc @@ -1,6 +1,6 @@ --- description: Flag a glTF or FBX import followed by mesh operations with no transform_apply and no unit-scale check. Generated files often arrive with non-identity object scale or a non-meter scene scale; mesh edits then bake the wrong size. -alwaysApply: true +alwaysApply: false globs: - "**/*.py" standards-version: 1.10.0