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
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"url": "https://github.com/TMHSDigital"
},
"description": "Blender Python skills and rules for AI agents: correct bpy for Blender 5.2 LTS and 4.5 LTS (Slotted Actions, temp_override, Extensions, bmesh, Geometry Nodes, glTF/FBX export), backed by smoke-tested examples",
"homepage": "https://github.com/TMHSDigital/Blender-Developer-Tools",
"homepage": "https://tmhsdigital.github.io/Blender-Developer-Tools/",
"repository": "https://github.com/TMHSDigital/Blender-Developer-Tools",
"license": "CC-BY-NC-ND-4.0 AND MIT",
"skills": [
Expand Down
4 changes: 2 additions & 2 deletions .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
"name": "TMHSDigital"
},
"metadata": {
"description": "Blender Python skills and rules for Cursor: correct bpy for Blender 5.2 LTS and 4.5 LTS."
"description": "Blender Python skills and rules for AI agents in Cursor and Claude Code: correct bpy for Blender 5.2 LTS and 4.5 LTS."
},
"plugins": [
{
"name": "blender-developer-tools",
"source": "./",
"description": "Cursor and Claude Code skills, rules, snippets, and templates for Blender Python add-on and scripting development"
"description": "Blender Python skills and rules for AI agents: correct bpy for Blender 5.2 LTS and 4.5 LTS (Slotted Actions, temp_override, Extensions, bmesh, Geometry Nodes, glTF/FBX export), backed by smoke-tested examples"
}
]
}
3 changes: 2 additions & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"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",
"description": "Blender Python skills and rules for AI agents: correct bpy for Blender 5.2 LTS and 4.5 LTS (Slotted Actions, temp_override, Extensions, bmesh, Geometry Nodes, glTF/FBX export), backed by smoke-tested examples",
"version": "0.147.1",
"author": {
"name": "TMHSDigital"
},
"homepage": "https://tmhsdigital.github.io/Blender-Developer-Tools/",
"repository": "https://github.com/TMHSDigital/Blender-Developer-Tools",
"license": "CC-BY-NC-ND-4.0 AND MIT",
"logo": "assets/favicon.svg",
"keywords": [
"cursor-plugin",
"claude-code",
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ contact_links:
about: Report a security issue privately via GitHub Security Advisory. Do not open a public issue for vulnerabilities.
- name: Roadmap / candidate pool
url: https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/ROADMAP.md
about: Check ROADMAP.md before requesting new skills, snippets, or examples. Shipped items are struck through there.
about: Check ROADMAP.md before requesting new skills, snippets, or examples. Shipped items are removed from it; see CHANGELOG.md for what shipped.
79 changes: 58 additions & 21 deletions .github/scripts/release_notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@
python .github/scripts/release_notes.py --since v0.142.3 --version 0.143.0 \
--notes "$RUNNER_TEMP/notes.md" --changelog CHANGELOG.md

Groups conventional-commit subjects (Features, Fixes, Other) with links to
each commit, skips the release bot's own "chore: bump version" commits, writes
the list to --notes (the GitHub release body) and, with --changelog, replaces
the version's "See release notes ... for details." placeholder that
release-doc-sync writes with the same list plus the link. Without this, every
CHANGELOG entry and release body said nothing about what shipped.
Splits the commits in two (#455):

- **For agents and users**: commits that touched what the plugin ships
(skills/, rules/, snippets/, templates/, claude/), grouped Features, Fixes,
Other. This is what a plugin user gets from the update, so it leads.
- **Maintenance**: everything else (CI, tests, the site and gallery,
showcase pieces, examples, docs), in a collapsed <details> block.

A release with no plugin change says so in one line above the collapsed
block. Skips the release bot's own "chore: bump version" commits, writes the
body to --notes (the GitHub release body) and, with --changelog, replaces the
version's "See release notes ... for details." placeholder that
release-doc-sync writes with the same body plus the link.
"""
from __future__ import annotations

Expand All @@ -24,33 +31,63 @@
("Fixes", re.compile(r"^fix(\(.+?\))?!?:", re.I)),
)
SKIP = re.compile(r"^chore: bump version to ", re.I)
# What the plugin ships (scripts/build_plugin_dist.py DIRS): a change here is
# a change a plugin user receives.
USER_PATHS = ("skills/", "rules/", "snippets/", "templates/", "claude/")
NO_USER_CHANGES = ("No changes to the plugin content (skills, rules, snippets, templates) "
"in this release.")


def commits(since: str | None) -> list[tuple[str, str]]:
def commits(since: str | None) -> list[tuple[str, str, list[str]]]:
"""(sha, subject, changed paths) per commit, newest first."""
rng = f"{since}..HEAD" if since else "HEAD"
out = subprocess.run(
["git", "log", rng, "--no-merges", "--format=%H%x09%s"],
["git", "log", rng, "--no-merges", "--name-only", "--format=%x00%H%x09%s"],
capture_output=True, text=True, check=True,
).stdout
rows = []
for line in out.splitlines():
sha, _, subject = line.partition("\t")
for block in out.split("\0")[1:]:
head, _, files = block.partition("\n")
sha, _, subject = head.partition("\t")
if subject and not SKIP.match(subject):
rows.append((sha, subject.strip()))
rows.append((sha, subject.strip(), [f for f in files.splitlines() if f.strip()]))
return rows


def render(rows: list[tuple[str, str]]) -> str:
buckets: dict[str, list[str]] = {name: [] for name, _ in GROUPS}
buckets["Other"] = []
for sha, subject in rows:
name = next((n for n, rx in GROUPS if rx.match(subject)), "Other")
buckets[name].append(f"- {subject} ([`{sha[:7]}`]({REPO_URL}/commit/{sha}))")
def is_user_facing(files: list[str]) -> bool:
return any(f.startswith(USER_PATHS) for f in files)


def _line(sha: str, subject: str) -> str:
return f"- {subject} ([`{sha[:7]}`]({REPO_URL}/commit/{sha}))"


def render(rows: list[tuple[str, str, list[str]]]) -> str:
if not rows:
return "No changes recorded."
user = [r for r in rows if is_user_facing(r[2])]
maint = [r for r in rows if not is_user_facing(r[2])]

parts = []
for name, lines in buckets.items():
if lines:
parts.append(f"### {name}\n\n" + "\n".join(lines))
return "\n\n".join(parts) if parts else "No changes recorded."
if user:
buckets: dict[str, list[str]] = {name: [] for name, _ in GROUPS}
buckets["Other"] = []
for sha, subject, _ in user:
name = next((n for n, rx in GROUPS if rx.match(subject)), "Other")
buckets[name].append(_line(sha, subject))
groups = [f"**{name}**\n\n" + "\n".join(lines) for name, lines in buckets.items() if lines]
parts.append("### For agents and users\n\n" + "\n\n".join(groups))
else:
parts.append(NO_USER_CHANGES)
if maint:
parts.append(
f"<details>\n<summary>Maintenance ({len(maint)} "
f"commit{'s' if len(maint) != 1 else ''}): CI, tests, site, examples, showcase, docs"
"</summary>\n\n"
+ "\n".join(_line(sha, subject) for sha, subject, _ in maint)
+ "\n\n</details>"
)
return "\n\n".join(parts)


def patch_changelog(path: str, version: str, body: str) -> bool:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ on:
paths:
- "skills/**"
- "rules/**"
- "snippets/**" # the landing's snippet list and What's new strip
- "site.json"
- ".cursor-plugin/plugin.json"
- "assets/**"
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ jobs:
print(f'Manifest verified at v{version}: {counts}')
PYEOF

- name: Check both plugin manifests share description, homepage, license, keywords and logo
run: python3 tests/check_manifest_meta.py

validate-claude-packaging:
name: Validate Claude Code packaging
runs-on: ubuntu-latest
Expand Down
26 changes: 22 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ classifies it as a `cursor-plugin`. This is content the AI loads when the user
asks Blender questions or works on Blender add-ons in Cursor or Claude Code.

The content base is 18 skills, 9 rules, 3 templates, 29 snippets, 66
examples, and 76 showcase pieces (counts are CI-enforced against README.md)
and the manifest). The full inventory tables and per-item purposes live in
examples, and 76 showcase pieces (counts are CI-enforced across README.md, the agent docs, ROADMAP.md,
`site.json` and the site templates). The full inventory tables and per-item purposes live in
`CLAUDE.md`. Example anatomy and authoring rules: copy `examples/bmesh-gear/`;
showcase conventions: `showcase/README.md`. The render look is specified
in `docs/VISUAL-STYLE.md`; the canonical run prompt is
Expand All @@ -35,8 +35,8 @@ Blender-Developer-Tools/
skills/<skill-name>/SKILL.md # 18 skill files
rules/<rule-name>.mdc # 9 rule files
templates/<template-name>/ # 3 starter templates
snippets/<snippet-name>.py # 27 standalone Python snippets
examples/<name>/ # 64 runnable smoke-gated examples (+ gallery.json)
snippets/<snippet-name>.py # 29 standalone Python snippets
examples/<name>/ # 66 runnable smoke-gated examples (+ gallery.json)
examples/gallery_framing.py # shared Layer 1 framing measurement (render path only)
showcase/<name>/ # budget-conformance props (sibling of examples/)
showcase/gallery.json # this tree's gallery index; merged into docs/gallery/
Expand Down Expand Up @@ -177,6 +177,10 @@ way, and a one-paragraph rationale. 30 to 80 lines is the right size.
and rule exists and every one on disk is listed, `marketplace.json` lists
this plugin, and the manifest `version` must equal `VERSION`. The release pipeline owns
the manifest `version` line (see `release.yml` below) — never hand-edit it.
`tests/check_manifest_meta.py` (same job) keeps the Cursor and Claude Code
manifests in step: one description, the site as `homepage`, one license
string (also the gallery footer's), the same keywords, and a Cursor `logo`
that the plugin-dist build ships.
- `blender-smoke.yml` executes every shipped example (check-only, no render)
plus snippet/template smoke tests inside REAL headless Blender, on
5.2 LTS and 4.5 LTS for every PR. 5.1 is weekly cron, the opt-in
Expand Down Expand Up @@ -204,6 +208,20 @@ way, and a one-paragraph rationale. 30 to 80 lines is the right size.
`.cursor-plugin/plugin.json`, `.claude-plugin/plugin.json` and
`.claude-plugin/marketplace.json`, and force-pushes the slim plugin build
to the `plugin-dist` branch.
- **Release notes and the commit-type policy.** `.github/scripts/release_notes.py`
writes the GitHub release body and the CHANGELOG entry in two parts: **For
agents and users** lists the commits that touched what the plugin ships
(`skills/`, `rules/`, `snippets/`, `templates/`, `claude/`), grouped
Features / Fixes / Other, and leads; everything else (CI, tests, the site
and gallery, examples, showcase, docs) goes in a collapsed **Maintenance**
`<details>` block. A release with no plugin change says so in one line above
that block. Every release emails watchers, so pick the commit type by who
receives the change: use `feat:`/`fix:` for changes to plugin content or
to what a user runs; use `ci:`, `test:`, `docs:`, `style:` or `chore:` (not
`fix(ci)`, `fix(tests)` or `fix(site)`) for maintenance, so it does not cut
a release on its own. Pages still deploys from those commits (`pages.yml`
runs on its own path filter), and the next release lists them under
Maintenance. `tests/test_release_notes.py` covers the split.
- `label-sync.yml` creates any missing label and applies path-based labels
to same-repo PRs.
- `pages.yml` builds the landing page from the **locally vendored** template
Expand Down
Loading
Loading