Skip to content

Commit ebe5c4e

Browse files
TMHSDigitalclaude
andauthored
fix: make rule scoping match the documented scope (#197)
All nine rules carried `alwaysApply: true` alongside `globs`. In Cursor, `alwaysApply: true` loads the rule regardless of globs, so the per-rule Scope column in CLAUDE.md described behavior that never happened: every rule entered every context, and the globs were decorative. The frontmatter was wrong, not the documentation. Set `alwaysApply: false` on all nine so each one loads from its globs. Glob coverage verified before flipping, not after. For each rule, every tracked file in the tree carrying the rule's trigger API was enumerated and checked against the glob: | Rule | Scope | Glob | Trigger-bearing files | Glob fixed | | --- | --- | --- | --- | --- | | prefer-data-over-ops-in-loops | `*.py` (was "Always on") | `**/*.py` | 103 .py | no | | always-free-bmesh | `*.py` | `**/*.py` | 86 .py | no | | target-extensions-platform-format | Add-on roots | `**/__init__.py`, `**/blender_manifest.toml` | 1 .py | no | | type-annotate-props-and-defend-context | `*.py` | `**/*.py` | 24 .py | no | | prefer-temp-override-over-context-copy | `*.py` | `**/*.py` | 11 .py | no | | use-foreach-set-for-bulk-data | `*.py` | `**/*.py` | 15 .py | no | | validate-imported-mesh-scale | `*.py` | `**/*.py` | 4 .py | no | | no-unapplied-modifiers-on-export | `*.py` | `**/*.py` | 41 .py | no | | use-correct-axis-rna-per-exporter | `*.py` | `**/*.py` | 41 .py | no | No glob needed correcting. Every trigger-bearing file outside the globs was a `rules/*.mdc` quoting its own anti-pattern, which is rule text, not code to guard. `**/*.py` matches all 133 tracked Python files; `**/__init__.py` + `**/blender_manifest.toml` match exactly the one add-on root in the tree, and the only `bl_info`-bearing file (`templates/extension-addon-template/__init__.py`) is inside it. `prefer-data-over-ops-in-loops` was the one rule documented as "Always on". Its anti-pattern — `bpy.ops.*` inside iteration — cannot occur outside Python, so `*.py` is its real scope and the CLAUDE.md cell was corrected rather than the frontmatter left as-is. Known boundary, stated rather than left silent: a single-file legacy add-on (`my_addon.py` carrying `bl_info`, no package) is not matched by `target-extensions-platform-format`. Widening it to `**/*.py` was rejected — that fires the rule on every Blender script including snippets and examples that are not add-ons, which is the over-application the Scope column exists to prevent. Cursor globs match paths, not content, and the only path-identifiable add-on-root markers are `__init__.py` and `blender_manifest.toml`. That case is served by the `bl-info-migration` skill, invoked by name. Also update the authoring templates that taught the broken pattern: CONTRIBUTING.md and AGENTS.md both showed `alwaysApply: true`, and AGENTS.md omitted `globs` entirely. Both now show the scoping contract and say why the two keys do not compose. Closes #189 Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent c6a7951 commit ebe5c4e

12 files changed

Lines changed: 31 additions & 12 deletions

‎AGENTS.md‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,18 @@ Rules are `.mdc` files in `rules/`. Frontmatter:
139139
```yaml
140140
---
141141
description: <one-line>
142-
alwaysApply: true
142+
alwaysApply: false
143+
globs:
144+
- "**/*.py"
143145
standards-version: 1.10.0
144146
---
145147
```
146148

149+
`alwaysApply: false` plus `globs` is the scoping contract: the rule loads only
150+
when a matching file is in context. Setting `alwaysApply: true` makes `globs`
151+
decorative and applies the rule everywhere, so the documented scope stops
152+
describing behavior. Choose globs that cover every file the rule guards.
153+
147154
Rules encode anti-patterns. Each rule should show the wrong way, the right
148155
way, and a one-paragraph rationale. 30 to 80 lines is the right size.
149156

‎CLAUDE.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,13 @@ VERSION - Source of truth for the repo version
5252

5353
## Rules (9)
5454

55+
Every rule sets `alwaysApply: false`, so the Scope column below is the rule's
56+
actual load condition, not a description: a rule enters context only when a file
57+
matching its `globs` does. Changing a glob changes when the rule fires.
58+
5559
| Rule | Scope | What it flags |
5660
| --- | --- | --- |
57-
| prefer-data-over-ops-in-loops | Always on | `bpy.ops.*` calls inside iteration over many objects |
61+
| prefer-data-over-ops-in-loops | `*.py` | `bpy.ops.*` calls inside iteration over many objects |
5862
| always-free-bmesh | `*.py` | `bmesh.new()` without paired `bm.free()` in a `try`/`finally` block |
5963
| target-extensions-platform-format | Add-on roots | Legacy `bl_info` only add-ons missing `blender_manifest.toml` |
6064
| type-annotate-props-and-defend-context | `*.py` | `bpy.props` defined as assignments, unguarded `context.active_object` |

‎CONTRIBUTING.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,21 @@ showcase/
7777
```yaml
7878
---
7979
description: One-line summary for humans and tooling.
80-
alwaysApply: true
80+
alwaysApply: false
8181
globs:
8282
- "**/*.py"
8383
standards-version: <current meta-repo STANDARDS_VERSION>
8484
---
8585
```
8686

87+
`alwaysApply: false` is deliberate and is what makes `globs` load-bearing:
88+
the rule enters context only when a file it matches does. `alwaysApply:
89+
true` would apply the rule in every context and make `globs` decorative, so
90+
the two must never both be set as if they compose. Pick globs that cover
91+
every file the rule is meant to guard before setting this — a glob that is
92+
too narrow means the rule silently stops firing, which is worse than
93+
over-applying. Record the scope in the `CLAUDE.md` rules table to match.
94+
8795
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.
8896

8997
## Adding a Snippet

‎rules/always-free-bmesh.mdc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
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.
3-
alwaysApply: true
3+
alwaysApply: false
44
globs:
55
- "**/*.py"
66
standards-version: 1.10.0

‎rules/no-unapplied-modifiers-on-export.mdc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
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.
3-
alwaysApply: true
3+
alwaysApply: false
44
globs:
55
- "**/*.py"
66
standards-version: 1.10.0

‎rules/prefer-data-over-ops-in-loops.mdc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
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.
3-
alwaysApply: true
3+
alwaysApply: false
44
globs:
55
- "**/*.py"
66
standards-version: 1.10.0

‎rules/prefer-temp-override-over-context-copy.mdc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
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.
3-
alwaysApply: true
3+
alwaysApply: false
44
globs:
55
- "**/*.py"
66
standards-version: 1.10.0

‎rules/target-extensions-platform-format.mdc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
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.
3-
alwaysApply: true
3+
alwaysApply: false
44
globs:
55
- "**/__init__.py"
66
- "**/blender_manifest.toml"

‎rules/type-annotate-props-and-defend-context.mdc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
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.
3-
alwaysApply: true
3+
alwaysApply: false
44
globs:
55
- "**/*.py"
66
standards-version: 1.10.0

‎rules/use-correct-axis-rna-per-exporter.mdc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
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.
3-
alwaysApply: true
3+
alwaysApply: false
44
globs:
55
- "**/*.py"
66
standards-version: 1.10.0

0 commit comments

Comments
 (0)