Skip to content

Commit ae0d859

Browse files
TMHSDigitalclaude
andauthored
feat(distribution): Claude Code plugin, rules bridge, Pages trim, count and cap gates (#310)
* feat(distribution): Claude Code plugin, rules bridge, Pages trim, count and cap gates - #288: remove the personal context-mode block from CLAUDE.md; CI fails if it returns - #289: add .claude-plugin/plugin.json + marketplace.json (CI-validated, version synced by release.yml), generate claude/blender-rules.md from rules/*.mdc (CI drift check), rewrite README and site.json install steps, drop placeholder author email - #295: fix stale counts, document the real 5-to-75 snippet cap and enforce it; CI scans AGENTS.md and CONTRIBUTING.md for stale aggregate counts - #307: pin Jinja2/MarkupSafe exactly, pip cache, dependabot pip entry, warn when the GitHub description drifts from the README counts - #212: Pages artifact leaves out contact/asset sheets and internal docs Closes #288, closes #289, closes #295, closes #307, closes #212 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fix(distribution): sync .claude-plugin versions to 0.131.2 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent f87c7ea commit ae0d859

15 files changed

Lines changed: 315 additions & 80 deletions

File tree

‎.claude-plugin/marketplace.json‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "blender-developer-tools",
3+
"owner": {
4+
"name": "TMHSDigital",
5+
"url": "https://github.com/TMHSDigital"
6+
},
7+
"plugins": [
8+
{
9+
"name": "blender-developer-tools",
10+
"source": "./",
11+
"description": "Cursor and Claude Code skills, rules, snippets, and templates for Blender Python add-on and scripting development",
12+
"version": "0.131.2"
13+
}
14+
]
15+
}

‎.claude-plugin/plugin.json‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "blender-developer-tools",
3+
"description": "Cursor and Claude Code skills, rules, snippets, and templates for Blender Python add-on and scripting development",
4+
"version": "0.131.2",
5+
"author": {
6+
"name": "TMHSDigital",
7+
"url": "https://github.com/TMHSDigital"
8+
},
9+
"homepage": "https://github.com/TMHSDigital/Blender-Developer-Tools",
10+
"repository": "https://github.com/TMHSDigital/Blender-Developer-Tools",
11+
"license": "CC-BY-NC-ND-4.0",
12+
"keywords": [
13+
"claude-code",
14+
"blender",
15+
"bpy",
16+
"blender-addon",
17+
"blender-python",
18+
"developer-tools"
19+
]
20+
}

‎.cursor-plugin/plugin.json‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
"description": "Cursor and Claude Code skills, rules, snippets, and templates for Blender Python add-on and scripting development",
55
"version": "0.131.2",
66
"author": {
7-
"name": "TMHSDigital",
8-
"email": "contact@users.noreply.github.com"
7+
"name": "TMHSDigital"
98
},
109
"license": "CC-BY-NC-ND-4.0",
1110
"keywords": [
1211
"cursor-plugin",
12+
"claude-code",
1313
"developer-tools",
14-
"blender"
14+
"blender",
15+
"bpy",
16+
"blender-addon",
17+
"blender-python"
1518
],
1619
"skills": [
1720
"skills/addon-scaffolding/SKILL.md",

‎.github/dependabot.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@ updates:
55
schedule:
66
interval: "daily"
77
target-branch: "main"
8+
- package-ecosystem: "pip"
9+
directory: "/scripts/site"
10+
schedule:
11+
interval: "weekly"
12+
target-branch: "main"

‎.github/workflows/pages.yml‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ jobs:
7676
- uses: actions/setup-python@v7
7777
with:
7878
python-version: "3.12"
79+
cache: pip
80+
cache-dependency-path: scripts/site/requirements.txt
7981

8082
- run: pip install -r scripts/site/requirements.txt
8183

@@ -92,9 +94,18 @@ jobs:
9294

9395
- uses: actions/configure-pages@v6
9496

97+
- name: Stage the public site (leave out internal docs and unlinked sheets)
98+
# Everything stays in the repo; only the Pages artifact shrinks. No
99+
# published page links to any of these (tests/check_site_links.py).
100+
run: |
101+
mkdir -p _site
102+
cp -r docs/. _site/
103+
rm -rf _site/gallery/contact-sheets _site/gallery/asset-sheets _site/gallery/DESIGN_NOTES.md
104+
rm -f _site/*.md
105+
95106
- uses: actions/upload-pages-artifact@v5
96107
with:
97-
path: docs
108+
path: _site
98109

99110
- uses: actions/deploy-pages@v5
100111
id: deployment

‎.github/workflows/release.yml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,17 @@ jobs:
142142
flags=re.M,
143143
)
144144
open(path, 'w').write(text)
145+
146+
# Claude Code manifests: same version, rewritten via JSON.
147+
import json
148+
for path in ('.claude-plugin/plugin.json', '.claude-plugin/marketplace.json'):
149+
data = json.load(open(path, encoding='utf-8'))
150+
if 'version' in data:
151+
data['version'] = os.environ['NEW_VERSION']
152+
for entry in data.get('plugins', []):
153+
entry['version'] = os.environ['NEW_VERSION']
154+
with open(path, 'w', encoding='utf-8', newline='\n') as fh:
155+
fh.write(json.dumps(data, indent=2) + '\n')
145156
PYEOF
146157
147158
- name: Commit version bump

‎.github/workflows/validate.yml‎

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ jobs:
197197
- uses: actions/setup-python@v7
198198
with:
199199
python-version: "3.12"
200+
cache: pip
201+
cache-dependency-path: scripts/site/requirements.txt
200202

201203
- run: pip install -r scripts/site/requirements.txt
202204

@@ -275,13 +277,71 @@ jobs:
275277
print(f'Manifest verified at v{version}: {counts}')
276278
PYEOF
277279
280+
validate-claude-packaging:
281+
name: Validate Claude Code packaging
282+
runs-on: ubuntu-latest
283+
steps:
284+
- uses: actions/checkout@v7
285+
286+
- name: Check .claude-plugin manifests match VERSION and the skills on disk
287+
run: |
288+
python3 << 'PYEOF'
289+
import glob
290+
import json
291+
import os
292+
import sys
293+
294+
errors = []
295+
version = open('VERSION').read().strip()
296+
plugin = json.load(open('.claude-plugin/plugin.json', encoding='utf-8'))
297+
market = json.load(open('.claude-plugin/marketplace.json', encoding='utf-8'))
298+
299+
if plugin.get('version') != version:
300+
errors.append(f"claude plugin.json version {plugin.get('version')!r} != VERSION {version!r}")
301+
if plugin.get('name') != 'blender-developer-tools':
302+
errors.append('claude plugin.json name must be blender-developer-tools')
303+
entries = market.get('plugins', [])
304+
if len(entries) != 1 or entries[0].get('name') != plugin.get('name'):
305+
errors.append('marketplace.json must list exactly the one plugin from plugin.json')
306+
elif entries[0].get('version') != version:
307+
errors.append(f"marketplace.json plugin version {entries[0].get('version')!r} != VERSION {version!r}")
308+
elif not os.path.isdir(os.path.join(entries[0].get('source', ''), 'skills')):
309+
errors.append('marketplace.json source must point at a directory containing skills/')
310+
311+
# Claude Code discovers skills/<name>/SKILL.md; each needs name + description.
312+
for path in sorted(glob.glob('skills/*/SKILL.md')):
313+
parts = open(path, encoding='utf-8').read().replace('\r\n', '\n').split('---')
314+
front = parts[1] if len(parts) > 2 else ''
315+
for key in ('name:', 'description:'):
316+
if key not in front:
317+
errors.append(f'{path}: frontmatter missing {key}')
318+
319+
if errors:
320+
for e in errors:
321+
print(f'::error::{e}', file=sys.stderr)
322+
sys.exit(1)
323+
print('Claude Code packaging verified')
324+
PYEOF
325+
326+
- name: Check CLAUDE.md carries no personal plugin routing block
327+
run: |
328+
if grep -nE 'context-mode|MANDATORY routing rules|ctx_(execute|batch_execute|search|fetch_and_index)' CLAUDE.md; then
329+
echo "::error::CLAUDE.md contains a personal plugin routing block (see #288)"
330+
exit 1
331+
fi
332+
333+
- name: Check claude/blender-rules.md is regenerated from rules/*.mdc
334+
run: python3 scripts/build_claude_rules.py --check
335+
278336
validate-counts:
279337
name: Validate content counts
280338
runs-on: ubuntu-latest
281339
steps:
282340
- uses: actions/checkout@v7
283341

284342
- name: Check content counts match README
343+
env:
344+
GH_TOKEN: ${{ github.token }}
285345
run: |
286346
python3 << 'PYEOF'
287347
import os
@@ -338,6 +398,36 @@ jobs:
338398
f'README showcase count mismatch (expected "{showcase_needle}" substring)'
339399
)
340400
401+
# Documented snippet length cap (README, CLAUDE.md, CONTRIBUTING.md).
402+
for f in sorted(os.listdir('snippets')):
403+
if f.endswith('.py'):
404+
n = len(open(os.path.join('snippets', f), encoding='utf-8').read().splitlines())
405+
if not 5 <= n <= 75:
406+
errors.append(f'snippets/{f} is {n} lines (documented range is 5 to 75)')
407+
408+
# Secondary docs must not carry stale aggregate counts.
409+
import re
410+
for doc in ('AGENTS.md', 'CONTRIBUTING.md'):
411+
text = open(doc, encoding='utf-8').read()
412+
for kind, actual in (('examples', example_count), ('showcase pieces', showcase_count)):
413+
for m in re.finditer(r'(\d+) ' + kind, text):
414+
if int(m.group(1)) != actual:
415+
errors.append(f'{doc} says "{m.group(0)}" but the repo has {actual}')
416+
417+
# The GitHub About text drifts silently otherwise. Warn only: it is
418+
# repo metadata edited by hand and must not block content PRs.
419+
import subprocess
420+
try:
421+
about = subprocess.run(
422+
['gh', 'repo', 'view', '--json', 'description', '--jq', '.description'],
423+
capture_output=True, text=True, timeout=60, check=True,
424+
).stdout
425+
for needle in (f'{example_count} examples', f'{showcase_count} showcase'):
426+
if needle not in about:
427+
print(f'::warning::GitHub repo description is stale (missing "{needle}"): {about.strip()}')
428+
except (subprocess.SubprocessError, OSError) as exc:
429+
print(f'::warning::could not read the GitHub repo description: {exc}')
430+
341431
if errors:
342432
for e in errors:
343433
print(f'::error::{e}', file=sys.stderr)

‎CLAUDE.md‎

Lines changed: 1 addition & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ matching its `globs` does. Changing a glob changes when the rule fires.
9797

9898
## Snippets (27)
9999

100-
Small standalone `.py` files at `snippets/<name>.py`, each 5 to 50 lines.
100+
Small standalone `.py` files at `snippets/<name>.py`, each 5 to 75 lines.
101101

102102
v0.1.0: canonical object creation and deletion, depsgraph evaluated mesh, bmesh load-edit-free, temp_override context, foreach_set vertex bulk write, register_classes_factory, PointerProperty binding, cross-version property delete, and the `action_ensure_channelbag_for_slot` slotted-actions bridge.
103103

@@ -239,66 +239,3 @@ When adding content to a future version:
239239
3. Update ROADMAP.md candidate pool entries.
240240
4. Use `feat:` for new content, `fix:` for corrections.
241241
5. Push. The release pipeline handles VERSION, tags, CHANGELOG, the `**Version:**` line here, and the `**Current:**` line in ROADMAP.md.
242-
243-
# context-mode — MANDATORY routing rules
244-
245-
You have context-mode MCP tools available. These rules are NOT optional — they protect your context window from flooding. A single unrouted command can dump 56 KB into context and waste the entire session.
246-
247-
## BLOCKED commands — do NOT attempt these
248-
249-
### curl / wget — BLOCKED
250-
Any Bash command containing `curl` or `wget` is intercepted and replaced with an error message. Do NOT retry.
251-
Instead use:
252-
- `ctx_fetch_and_index(url, source)` to fetch and index web pages
253-
- `ctx_execute(language: "javascript", code: "const r = await fetch(...)")` to run HTTP calls in sandbox
254-
255-
### Inline HTTP — BLOCKED
256-
Any Bash command containing `fetch('http`, `requests.get(`, `requests.post(`, `http.get(`, or `http.request(` is intercepted and replaced with an error message. Do NOT retry with Bash.
257-
Instead use:
258-
- `ctx_execute(language, code)` to run HTTP calls in sandbox — only stdout enters context
259-
260-
### WebFetch — BLOCKED
261-
WebFetch calls are denied entirely. The URL is extracted and you are told to use `ctx_fetch_and_index` instead.
262-
Instead use:
263-
- `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` to query the indexed content
264-
265-
## REDIRECTED tools — use sandbox equivalents
266-
267-
### Bash (>20 lines output)
268-
Bash is ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`, and other short-output commands.
269-
For everything else, use:
270-
- `ctx_batch_execute(commands, queries)` — run multiple commands + search in ONE call
271-
- `ctx_execute(language: "shell", code: "...")` — run in sandbox, only stdout enters context
272-
273-
### Read (for analysis)
274-
If you are reading a file to **Edit** it → Read is correct (Edit needs content in context).
275-
If you are reading to **analyze, explore, or summarize** → use `ctx_execute_file(path, language, code)` instead. Only your printed summary enters context. The raw file content stays in the sandbox.
276-
277-
### Grep (large results)
278-
Grep results can flood context. Use `ctx_execute(language: "shell", code: "grep ...")` to run searches in sandbox. Only your printed summary enters context.
279-
280-
## Tool selection hierarchy
281-
282-
1. **GATHER**: `ctx_batch_execute(commands, queries)` — Primary tool. Runs all commands, auto-indexes output, returns search results. ONE call replaces 30+ individual calls.
283-
2. **FOLLOW-UP**: `ctx_search(queries: ["q1", "q2", ...])` — Query indexed content. Pass ALL questions as array in ONE call.
284-
3. **PROCESSING**: `ctx_execute(language, code)` | `ctx_execute_file(path, language, code)` — Sandbox execution. Only stdout enters context.
285-
4. **WEB**: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` — Fetch, chunk, index, query. Raw HTML never enters context.
286-
5. **INDEX**: `ctx_index(content, source)` — Store content in FTS5 knowledge base for later search.
287-
288-
## Subagent routing
289-
290-
When spawning subagents (Agent/Task tool), the routing block is automatically injected into their prompt. Bash-type subagents are upgraded to general-purpose so they have access to MCP tools. You do NOT need to manually instruct subagents about context-mode.
291-
292-
## Output constraints
293-
294-
- Keep responses under 500 words.
295-
- Write artifacts (code, configs, PRDs) to FILES — never return them as inline text. Return only: file path + 1-line description.
296-
- When indexing content, use descriptive source labels so others can `ctx_search(source: "label")` later.
297-
298-
## ctx commands
299-
300-
| Command | Action |
301-
|---------|--------|
302-
| `ctx stats` | Call the `ctx_stats` MCP tool and display the full output verbatim |
303-
| `ctx doctor` | Call the `ctx_doctor` MCP tool, run the returned shell command, display as checklist |
304-
| `ctx upgrade` | Call the `ctx_upgrade` MCP tool, run the returned shell command, display as checklist |

‎CONTRIBUTING.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ showcase/
4848

4949
- **`skills/`** - one directory per skill, each containing `SKILL.md` with YAML frontmatter (`name`, `description`, `standards-version`).
5050
- **`rules/`** - Cursor-style rules as `.mdc` files with YAML frontmatter (`description`, `alwaysApply`, `globs`, `standards-version`).
51-
- **`snippets/`** - small standalone `.py` files (5 to 50 lines) demonstrating a single canonical pattern.
51+
- **`snippets/`** - small standalone `.py` files (5 to 75 lines) demonstrating a single canonical pattern.
5252
- **`templates/`** - copy-paste starting points; one directory per template.
5353
- **`showcase/`** - budget-conformance props, sibling of `examples/`. Not API
5454
contracts. Conventions: [`showcase/README.md`](showcase/README.md).
@@ -97,7 +97,7 @@ showcase/
9797
## Adding a Snippet
9898

9999
1. Add a `.py` file under `snippets/`, e.g. `snippets/depsgraph-evaluated-mesh.py`.
100-
2. Keep it 5 to 50 lines, fully working code, with a header comment naming the snippet and citing the relevant Blender doc URL or research section.
100+
2. Keep it 5 to 75 lines, fully working code, with a header comment naming the snippet and citing the relevant Blender doc URL or research section.
101101
3. Snippets are validated for Python syntax in CI.
102102

103103
## Adding a Template
@@ -307,7 +307,7 @@ The drift-check workflow enforces these on every push and PR.
307307

308308
## Aggregate Counts
309309

310-
`README.md` declares aggregate counts (e.g. "16 skills, 9 rules, 3 templates, 27 snippets, 59 examples, and 26 showcase pieces"). The `validate-counts` job in `.github/workflows/validate.yml` enforces these substrings against the filesystem on every push and PR. Showcase pieces are counted separately from examples. When you add or remove content, update the README counts in the same commit.
310+
`README.md` declares aggregate counts (e.g. "16 skills, 9 rules, 3 templates, 27 snippets, 64 examples, and 76 showcase pieces"). The `validate-counts` job in `.github/workflows/validate.yml` enforces these substrings against the filesystem on every push and PR. Showcase pieces are counted separately from examples. When you add or remove content, update the README counts in the same commit.
311311

312312
## Pull Request Process
313313

‎README.md‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,15 @@ The content is consumed by AI coding agents reading these files directly from a
5656
git clone https://github.com/TMHSDigital/Blender-Developer-Tools.git
5757
```
5858

59-
- **Cursor** — point Cursor at the checkout (or symlink `rules/` into your project). The `.mdc` rules apply automatically by glob scope; skills are referenced by name in chat.
60-
- **Claude Code** — copy `skills/` and `rules/` into your project workspace, or keep this repo as a checkout that Claude Code references directly.
59+
- **Cursor** — open your project with this checkout available and copy (or symlink) `rules/*.mdc` into your project's `.cursor/rules/`. The rules apply automatically by glob scope; skills are referenced by name in chat.
60+
- **Claude Code** — install as a plugin, then run `/skills` to see all 16 skills:
61+
62+
```text
63+
/plugin marketplace add TMHSDigital/Blender-Developer-Tools
64+
/plugin install blender-developer-tools@blender-developer-tools
65+
```
66+
67+
Claude Code does not read Cursor `.mdc` rules. For the rules, add one line to your project's `CLAUDE.md` that imports the generated summary from a checkout: `@/path/to/Blender-Developer-Tools/claude/blender-rules.md`. Without the plugin, copy `skills/*` into your project's `.claude/skills/` instead.
6168
- **Get Blender** — download **5.2 LTS** (primary target) or **4.5 LTS** (supported fallback) from [blender.org/download/lts](https://www.blender.org/download/lts/); current stable lives at [blender.org/download](https://www.blender.org/download/). The `blender` command below is that binary — on macOS it is inside the app bundle at `/Applications/Blender.app/Contents/MacOS/Blender`.
6269
- **Run an example** — every example is a self-checking headless script (exit non-zero on failure, no GPU needed for the check):
6370

@@ -1451,7 +1458,7 @@ the duplicates, then glTF ships 24 tris / 48 positions / 8 unique.
14511458
skills/<name>/SKILL.md - 16 skill files, YAML frontmatter, one canonical pattern each
14521459
rules/<name>.mdc - 9 rule files, anti-pattern + correction
14531460
templates/<name>/ - 3 template directories (extension-addon-template, headless-batch-script-template, ai-asset-pipeline-template)
1454-
snippets/<name>.py - 27 standalone Python snippets, 5 to 50 lines each
1461+
snippets/<name>.py - 27 standalone Python snippets, 5 to 75 lines each
14551462
```
14561463

14571464
## Using rules in Cursor
@@ -1462,13 +1469,13 @@ The `.mdc` files in `rules/` apply automatically when Cursor opens a Blender Pyt
14621469
- `always-free-bmesh`: flags `bmesh.new()` without paired `bm.free()` in `try`/`finally`
14631470
- `target-extensions-platform-format`: flags add-ons missing `blender_manifest.toml`
14641471
- `type-annotate-props-and-defend-context`: flags `bpy.props` assignment form and unguarded `context.active_object`
1465-
- `prefer-temp-override-over-context-copy`: flags `bpy.context.copy()` passed to operators (deprecated 4.x, removed 5.x)
1472+
- `prefer-temp-override-over-context-copy`: flags `bpy.context.copy()` passed to operators (deprecated 3.2, removed 4.0)
14661473
- `use-foreach-set-for-bulk-data`: flags Python loops over `mesh.vertices` setting `co`, normals, or other per-element bulk data
14671474
- `validate-imported-mesh-scale`: flags glTF/FBX import then mesh work with no `transform_apply` and no unit-scale check
14681475
- `no-unapplied-modifiers-on-export`: flags export of objects with live modifiers when the export does not request evaluated geometry
14691476
- `use-correct-axis-rna-per-exporter`: flags `export_scene.gltf` calls that pass FBX `axis_forward` / `axis_up`, and `export_scene.fbx` calls that pass glTF `export_yup`
14701477

1471-
Symlink or clone this repo, then point Cursor at it as a skills/rules source.
1478+
Cursor: copy `rules/*.mdc` into `.cursor/rules/`. Claude Code: install the plugin (see Install) and import `claude/blender-rules.md` from your `CLAUDE.md`.
14721479

14731480
## Using the templates
14741481

0 commit comments

Comments
 (0)