Skip to content

Commit 89ec30a

Browse files
TMHSDigitalclaude
andauthored
fix: make install copy, counts, manifests and release notes tell one story (#475)
Visitors and plugin users saw contradictory numbers (27 vs 29 snippets, 66 vs 56 examples with no explanation), a hero command that registers a marketplace but installs nothing, install copy that still sent plugin users to clone main, a Cursor listing with the old generic pitch and no logo, and release notes where a new skill drowned in CI fixes. - check_counts now scans ROADMAP, site.json, the site templates, the manifests and the plugin-dist README, allows two words between number and noun, checks the README layer table, the snippet line-range phrase and ROADMAP's last shipped theme row (#448) - hero shows and copies both Claude Code commands (#449) - README/landing install copy no longer tells plugin users to clone main or copy rules by hand (#450) - ROADMAP: "Next for agents" section, missing theme rows, self-contained candidate text; ISSUE_TEMPLATE wording (#451) - What's new lists skills, rules, snippets and check-only examples with a kind badge (#452) - landing and gallery explain the 10 check-only examples (#453) - both manifests share description, homepage, license and keywords; the Cursor manifest gets a logo shipped in plugin-dist; gallery footer reads the manifest license; tests/check_manifest_meta.py guards it (#454) - release_notes.py leads with plugin-content changes and collapses maintenance; policy documented in AGENTS.md (#455) - SECURITY.md: plugin-dist installs cannot pin to a tag (#392, doc part) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 3ea3289 commit 89ec30a

153 files changed

Lines changed: 840 additions & 347 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.claude-plugin/plugin.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"url": "https://github.com/TMHSDigital"
77
},
88
"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",
9-
"homepage": "https://github.com/TMHSDigital/Blender-Developer-Tools",
9+
"homepage": "https://tmhsdigital.github.io/Blender-Developer-Tools/",
1010
"repository": "https://github.com/TMHSDigital/Blender-Developer-Tools",
1111
"license": "CC-BY-NC-ND-4.0 AND MIT",
1212
"skills": [

‎.cursor-plugin/marketplace.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
"name": "TMHSDigital"
55
},
66
"metadata": {
7-
"description": "Blender Python skills and rules for Cursor: correct bpy for Blender 5.2 LTS and 4.5 LTS."
7+
"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."
88
},
99
"plugins": [
1010
{
1111
"name": "blender-developer-tools",
1212
"source": "./",
13-
"description": "Cursor and Claude Code skills, rules, snippets, and templates for Blender Python add-on and scripting development"
13+
"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"
1414
}
1515
]
1616
}

‎.cursor-plugin/plugin.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
{
22
"name": "blender-developer-tools",
33
"displayName": "Blender Developer Tools",
4-
"description": "Cursor and Claude Code skills, rules, snippets, and templates for Blender Python add-on and scripting development",
4+
"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",
55
"version": "0.147.1",
66
"author": {
77
"name": "TMHSDigital"
88
},
99
"homepage": "https://tmhsdigital.github.io/Blender-Developer-Tools/",
1010
"repository": "https://github.com/TMHSDigital/Blender-Developer-Tools",
1111
"license": "CC-BY-NC-ND-4.0 AND MIT",
12+
"logo": "assets/favicon.svg",
1213
"keywords": [
1314
"cursor-plugin",
1415
"claude-code",

‎.github/ISSUE_TEMPLATE/config.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ contact_links:
55
about: Report a security issue privately via GitHub Security Advisory. Do not open a public issue for vulnerabilities.
66
- name: Roadmap / candidate pool
77
url: https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/ROADMAP.md
8-
about: Check ROADMAP.md before requesting new skills, snippets, or examples. Shipped items are struck through there.
8+
about: Check ROADMAP.md before requesting new skills, snippets, or examples. Shipped items are removed from it; see CHANGELOG.md for what shipped.

‎.github/scripts/release_notes.py‎

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@
44
python .github/scripts/release_notes.py --since v0.142.3 --version 0.143.0 \
55
--notes "$RUNNER_TEMP/notes.md" --changelog CHANGELOG.md
66
7-
Groups conventional-commit subjects (Features, Fixes, Other) with links to
8-
each commit, skips the release bot's own "chore: bump version" commits, writes
9-
the list to --notes (the GitHub release body) and, with --changelog, replaces
10-
the version's "See release notes ... for details." placeholder that
11-
release-doc-sync writes with the same list plus the link. Without this, every
12-
CHANGELOG entry and release body said nothing about what shipped.
7+
Splits the commits in two (#455):
8+
9+
- **For agents and users**: commits that touched what the plugin ships
10+
(skills/, rules/, snippets/, templates/, claude/), grouped Features, Fixes,
11+
Other. This is what a plugin user gets from the update, so it leads.
12+
- **Maintenance**: everything else (CI, tests, the site and gallery,
13+
showcase pieces, examples, docs), in a collapsed <details> block.
14+
15+
A release with no plugin change says so in one line above the collapsed
16+
block. Skips the release bot's own "chore: bump version" commits, writes the
17+
body to --notes (the GitHub release body) and, with --changelog, replaces the
18+
version's "See release notes ... for details." placeholder that
19+
release-doc-sync writes with the same body plus the link.
1320
"""
1421
from __future__ import annotations
1522

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

2840

29-
def commits(since: str | None) -> list[tuple[str, str]]:
41+
def commits(since: str | None) -> list[tuple[str, str, list[str]]]:
42+
"""(sha, subject, changed paths) per commit, newest first."""
3043
rng = f"{since}..HEAD" if since else "HEAD"
3144
out = subprocess.run(
32-
["git", "log", rng, "--no-merges", "--format=%H%x09%s"],
45+
["git", "log", rng, "--no-merges", "--name-only", "--format=%x00%H%x09%s"],
3346
capture_output=True, text=True, check=True,
3447
).stdout
3548
rows = []
36-
for line in out.splitlines():
37-
sha, _, subject = line.partition("\t")
49+
for block in out.split("\0")[1:]:
50+
head, _, files = block.partition("\n")
51+
sha, _, subject = head.partition("\t")
3852
if subject and not SKIP.match(subject):
39-
rows.append((sha, subject.strip()))
53+
rows.append((sha, subject.strip(), [f for f in files.splitlines() if f.strip()]))
4054
return rows
4155

4256

43-
def render(rows: list[tuple[str, str]]) -> str:
44-
buckets: dict[str, list[str]] = {name: [] for name, _ in GROUPS}
45-
buckets["Other"] = []
46-
for sha, subject in rows:
47-
name = next((n for n, rx in GROUPS if rx.match(subject)), "Other")
48-
buckets[name].append(f"- {subject} ([`{sha[:7]}`]({REPO_URL}/commit/{sha}))")
57+
def is_user_facing(files: list[str]) -> bool:
58+
return any(f.startswith(USER_PATHS) for f in files)
59+
60+
61+
def _line(sha: str, subject: str) -> str:
62+
return f"- {subject} ([`{sha[:7]}`]({REPO_URL}/commit/{sha}))"
63+
64+
65+
def render(rows: list[tuple[str, str, list[str]]]) -> str:
66+
if not rows:
67+
return "No changes recorded."
68+
user = [r for r in rows if is_user_facing(r[2])]
69+
maint = [r for r in rows if not is_user_facing(r[2])]
70+
4971
parts = []
50-
for name, lines in buckets.items():
51-
if lines:
52-
parts.append(f"### {name}\n\n" + "\n".join(lines))
53-
return "\n\n".join(parts) if parts else "No changes recorded."
72+
if user:
73+
buckets: dict[str, list[str]] = {name: [] for name, _ in GROUPS}
74+
buckets["Other"] = []
75+
for sha, subject, _ in user:
76+
name = next((n for n, rx in GROUPS if rx.match(subject)), "Other")
77+
buckets[name].append(_line(sha, subject))
78+
groups = [f"**{name}**\n\n" + "\n".join(lines) for name, lines in buckets.items() if lines]
79+
parts.append("### For agents and users\n\n" + "\n\n".join(groups))
80+
else:
81+
parts.append(NO_USER_CHANGES)
82+
if maint:
83+
parts.append(
84+
f"<details>\n<summary>Maintenance ({len(maint)} "
85+
f"commit{'s' if len(maint) != 1 else ''}): CI, tests, site, examples, showcase, docs"
86+
"</summary>\n\n"
87+
+ "\n".join(_line(sha, subject) for sha, subject, _ in maint)
88+
+ "\n\n</details>"
89+
)
90+
return "\n\n".join(parts)
5491

5592

5693
def patch_changelog(path: str, version: str, body: str) -> bool:

‎.github/workflows/pages.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ on:
1313
paths:
1414
- "skills/**"
1515
- "rules/**"
16+
- "snippets/**" # the landing's snippet list and What's new strip
1617
- "site.json"
1718
- ".cursor-plugin/plugin.json"
1819
- "assets/**"

‎.github/workflows/validate.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ jobs:
239239
print(f'Manifest verified at v{version}: {counts}')
240240
PYEOF
241241
242+
- name: Check both plugin manifests share description, homepage, license, keywords and logo
243+
run: python3 tests/check_manifest_meta.py
244+
242245
validate-claude-packaging:
243246
name: Validate Claude Code packaging
244247
runs-on: ubuntu-latest

‎AGENTS.md‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ classifies it as a `cursor-plugin`. This is content the AI loads when the user
2121
asks Blender questions or works on Blender add-ons in Cursor or Claude Code.
2222

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

0 commit comments

Comments
 (0)