Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "blender-developer-tools",
"displayName": "Blender Developer Tools",
"description": "Cursor and Claude Code skills, rules, snippets, and templates for Blender Python add-on and scripting development",
"version": "0.2.3",
"version": "0.5.0",
"author": {
"name": "TMHSDigital",
"email": "contact@users.noreply.github.com"
Expand Down Expand Up @@ -34,5 +34,34 @@
"rules/type-annotate-props-and-defend-context.mdc",
"rules/prefer-temp-override-over-context-copy.mdc",
"rules/use-foreach-set-for-bulk-data.mdc"
],
"snippets": [
"snippets/action-ensure-channelbag-for-slot.py",
"snippets/app-handler-registration.py",
"snippets/bmesh-load-edit-free.py",
"snippets/canonical-object-creation.py",
"snippets/canonical-object-deletion.py",
"snippets/cross-version-property-delete.py",
"snippets/depsgraph-evaluated-mesh.py",
"snippets/driver-with-custom-function.py",
"snippets/foreach-get-vertices.py",
"snippets/foreach-set-vertices.py",
"snippets/pointerproperty-binding.py",
"snippets/principled-bsdf-material.py",
"snippets/register-classes-factory.py",
"snippets/shader-node-group.py",
"snippets/temp-override-context.py",
"snippets/usd-export-evaluation-mode.py",
"snippets/version-branch-skeleton.py"
],
"templates": [
"templates/extension-addon-template",
"templates/headless-batch-script-template"
],
"examples": [
"examples/depsgraph-export",
"examples/gn-sdf-remesh",
"examples/swatch-grid",
"examples/turntable"
]
}
21 changes: 21 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,27 @@ jobs:
previous-version: ${{ steps.current.outputs.version }}
meta-repo-ref: v1.15.1

- name: Sync plugin manifest version
if: steps.check.outputs.skip == 'false' && steps.bump.outputs.release == 'true'
env:
NEW_VERSION: ${{ steps.new.outputs.version }}
run: |
python3 - << 'PYEOF'
import os
import re

path = '.cursor-plugin/plugin.json'
text = open(path).read()
text = re.sub(
r'^( "version": ")[^"]+(",)$',
rf'\g<1>{os.environ["NEW_VERSION"]}\g<2>',
text,
count=1,
flags=re.M,
)
open(path, 'w').write(text)
PYEOF

- name: Commit version bump
if: steps.check.outputs.skip == 'false' && steps.bump.outputs.release == 'true'
run: |
Expand Down
56 changes: 56 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,62 @@ jobs:
echo "Templates: $template_count"
echo "Snippets: $snippet_count"

validate-manifest:
name: Validate plugin manifest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: Check plugin.json matches filesystem and VERSION
run: |
python3 << 'PYEOF'
import glob
import json
import os
import sys

errors = []
manifest = json.load(open('.cursor-plugin/plugin.json'))

version = open('VERSION').read().strip()
if manifest.get('version') != version:
errors.append(
f"plugin.json version '{manifest.get('version')}' != VERSION '{version}'"
)

# Every manifest path must exist on disk.
for key in ('skills', 'rules', 'snippets', 'templates', 'examples'):
for path in manifest.get(key, []):
if not os.path.exists(path):
errors.append(f'{key}: manifest lists missing path {path}')

# Every content file/dir on disk must be listed in the manifest.
expected = {
'skills': sorted(glob.glob('skills/*/SKILL.md')),
'rules': sorted(glob.glob('rules/*.mdc')),
'snippets': sorted(glob.glob('snippets/*.py')),
'templates': sorted(
d for d in glob.glob('templates/*') if os.path.isdir(d)
),
'examples': sorted(
d for d in glob.glob('examples/*') if os.path.isdir(d)
),
}
for key, paths in expected.items():
listed = {p.replace('\\', '/') for p in manifest.get(key, [])}
for path in paths:
if path.replace('\\', '/') not in listed:
errors.append(f'{key}: {path} on disk but not in manifest')

if errors:
for e in errors:
print(f'::error::{e}', file=sys.stderr)
sys.exit(1)

counts = {k: len(manifest.get(k, [])) for k in expected}
print(f'Manifest verified at v{version}: {counts}')
PYEOF

validate-counts:
name: Validate content counts
runs-on: ubuntu-latest
Expand Down
10 changes: 8 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,18 @@ way, and a one-paragraph rationale. 30 to 80 lines is the right size.
asserts the README aggregate counts (12 skills, 6 rules, 2 templates, 17
snippets) match filesystem reality. The counts language in `README.md` is
load-bearing: the job greps for it.
- `validate.yml` also runs a `validate-manifest` job that checks
`.cursor-plugin/plugin.json` against reality: every listed path must exist,
every skill, rule, snippet, template, and example on disk must be listed,
and the manifest `version` must equal `VERSION`. The release pipeline owns
the manifest `version` line (see `release.yml` below) — never hand-edit it.
- `drift-check.yml` consumes `Developer-Tools-Directory/.github/actions/
drift-check@v1.15` to enforce ecosystem standards-version markers.
- `release.yml` auto-bumps the version, tags, force-updates floating tags
`v0` and `v0.1`, and runs `release-doc-sync@v1` to rewrite CHANGELOG.md,
CLAUDE.md `**Version:**`, and ROADMAP.md `**Current:**`. Triggered on
push to `main` for content-changing paths only.
CLAUDE.md `**Version:**`, and ROADMAP.md `**Current:**`. It also rewrites
the `"version"` line in `.cursor-plugin/plugin.json` so the manifest tracks
each release. Triggered on push to `main` for content-changing paths only.
- `label-sync.yml` self-heals labels via `gh label create --force` per
label, then applies them to the PR.

Expand Down
Loading