fix: make rule scoping match the documented scope - #197
Merged
Merged
Conversation
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 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The call
The frontmatter was wrong, not the documentation.
All nine rules carried
alwaysApply: trueandglobs. In Cursor,alwaysApply: trueloads the rule regardless of globs, so CLAUDE.md's per-rule Scope column described behavior that never happened: every rule entered every context and the globs were decorative. SetalwaysApply: falseon all nine so each loads from its globs.Verified before flipping, not after
Setting
alwaysApply: falsemakes each rule depend entirely on its globs, so a narrow glob means the rule silently stops firing — worse than over-applying. For each rule I enumerated every tracked file in the tree carrying that rule's trigger API and checked it against the glob..py**/*.py*.py**/*.py**/__init__.py,**/blender_manifest.toml*.py**/*.py*.py**/*.py*.py**/*.py*.py**/*.py*.py**/*.py*.py**/*.pyNo glob needed correcting.
**/*.pymatches all 133 tracked Python files. Every trigger-bearing file that fell outside a glob turned out to be arules/*.mdcquoting its own anti-pattern — rule text, not code to guard.**/__init__.py+**/blender_manifest.tomlmatch exactly the one add-on root in the tree (templates/extension-addon-template/), and the onlybl_info-bearing file in the repository is that package's__init__.py, inside it.The one Scope cell that was wrong
prefer-data-over-ops-in-loopswas documented as "Always on" rather than*.py. Its anti-pattern —bpy.ops.*inside iteration — cannot occur outside Python, so*.pyis its real scope. Corrected the CLAUDE.md cell rather than leaving the rule global. Both outcomes were available; this one is true.Known boundary, stated rather than left silent
A single-file legacy add-on (
my_addon.pycarryingbl_info, no package) is not matched bytarget-extensions-platform-format. Widening it to**/*.pywas rejected: that fires the rule on every Blender script, including snippets and examples that are not add-ons — exactly 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__.pyandblender_manifest.toml. That case is served by thebl-info-migrationskill, which is invoked by name rather than by glob.Also fixed: the templates that taught the broken pattern
Leaving these would have reintroduced the mismatch on the next rule.
CONTRIBUTING.md§ Adding a Rule showedalwaysApply: trueaboveglobs.AGENTS.md§ Rules showedalwaysApply: trueand omittedglobsentirely.Both now show
alwaysApply: falsewith globs, and state why the two keys do not compose:alwaysApply: truemakesglobsdecorative, so the documented scope stops describing behavior.CLAUDE.md§ Rules now says the Scope column is the rule's actual load condition rather than a description, since it is load-bearing again.CI note
validate.yml's "Validate rule frontmatter" step checks only fordescription:andstandards-version:. It does not inspectalwaysApply, so nothing in CI was asserting the old value and nothing asserts the new one. The mismatch this PR closes was invisible to the gates — which is how it survived nine rules.Closes #189
🤖 Generated with Claude Code