Skip to content

fix: make rule scoping match the documented scope - #197

Merged
TMHSDigital merged 1 commit into
mainfrom
fix/rule-scoping
Sep 22, 2026
Merged

TMHSDigital merged 1 commit into
mainfrom
fix/rule-scoping

Conversation

@TMHSDigital

Copy link
Copy Markdown
Owner

The call

The frontmatter was wrong, not the documentation.

All nine rules carried alwaysApply: true and globs. In Cursor, alwaysApply: true loads 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. Set alwaysApply: false on all nine so each loads from its globs.

Verified before flipping, not after

Setting alwaysApply: false makes 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.

Rule Claimed scope Glob Trigger-bearing .py Matched by glob Glob fixed
prefer-data-over-ops-in-loops "Always on" **/*.py 103 all no — Scope cell corrected instead
always-free-bmesh *.py **/*.py 86 all no
target-extensions-platform-format Add-on roots **/__init__.py, **/blender_manifest.toml 1 all no
type-annotate-props-and-defend-context *.py **/*.py 24 all no
prefer-temp-override-over-context-copy *.py **/*.py 11 all no
use-foreach-set-for-bulk-data *.py **/*.py 15 all no
validate-imported-mesh-scale *.py **/*.py 4 all no
no-unapplied-modifiers-on-export *.py **/*.py 41 all no
use-correct-axis-rna-per-exporter *.py **/*.py 41 all no

No glob needed correcting. **/*.py matches all 133 tracked Python files. Every trigger-bearing file that fell outside a glob turned out to be a rules/*.mdc quoting its own anti-pattern — rule text, not code to guard. **/__init__.py + **/blender_manifest.toml match exactly the one add-on root in the tree (templates/extension-addon-template/), and the only bl_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-loops was documented as "Always on" rather than *.py. Its anti-pattern — bpy.ops.* inside iteration — cannot occur outside Python, so *.py is 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.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 — 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__.py and blender_manifest.toml. That case is served by the bl-info-migration skill, 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 showed alwaysApply: true above globs.
  • AGENTS.md § Rules showed alwaysApply: true and omitted globs entirely.

Both now show alwaysApply: false with globs, and state why the two keys do not compose: alwaysApply: true makes globs decorative, 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 for description: and standards-version:. It does not inspect alwaysApply, 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

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>
@github-actions github-actions Bot added rules documentation Improvements or additions to documentation labels Sep 22, 2026
@TMHSDigital
TMHSDigital merged commit ebe5c4e into main Sep 22, 2026
11 checks passed
@TMHSDigital
TMHSDigital deleted the fix/rule-scoping branch September 22, 2026 01:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation rules

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: rule scope column disagrees with alwaysApply on every rule

1 participant