Audit date: 2026-06-20 · Repo @ v0.5.0 · Auditor: Principal Eng review
Standing log. Notes-derived 5.1→5.2 (and later) deltas are incomplete by default.
The Blender python_api release notes are a starting set, not a closed
inventory. A sweep that only copies listed removals will miss behavior
changes that keep the same RNA names. Record those here when CI or
authoring finds them, even when upstream notes omit the break. Do not
treat a clean notes-diff as proof that two series are interchangeable.
4.5 LTS / 5.1: COLOR strips have no intrinsic size. transform.scale_*
is a fraction of the output frame. Creating at factory resolution then
rendering a tiny buffer is fine.
5.2+: new_effect(..., type='COLOR') bakes readonly width / height
from scene.render.resolution_* at creation. Scale and offset are in that
media space. Factory 1920×1080 + later 96×54 render → 0.36-scaled cells
larger than the output; the long-runner covers the frame.
A lone COLOR strip does honor transform.scale_* on 5.2. The break is
media size vs a later output size, not "transform is ignored."
Set scene resolution before building strips.
- Taught:
skills/vse-python/SKILL.md - Witness:
examples/vse-cut-list/--check-pixels(on 5.2 assertsA.width, A.height == (96, 54)). No separate example — that script already fails if the bake drifts.
Default wm.usd_export export_subdivision='BEST_MATCH' writes the cage
plus subdivisionScheme = catmullClark. VIEWPORT and RENDER USDA files
are then identical, so evaluation_mode looks like a no-op. TESSELLATE
makes the mode observable. Catmull-Clark on a cube: verts =
2 + 6 × 4^n (n=1 → 26, n=2 → 98). Same on 4.5 LTS and 5.2 LTS; not a
version split, an exporter-default trap. Not listed in the python_api notes
as a behavior change because the RNA names did not move.
- Taught:
skills/depsgraph-and-evaluated-data/SKILL.md - Witness:
examples/usd-export-evaluation-mode/
Creating Repeat / For Each Element input and output nodes and linking
sockets is not enough. pair_with_output creates the Geometry items;
unpaired Repeat Input has only Iterations, unpaired For Each evaluates
empty. For Each's main Geometry output is the input-mesh passthrough;
generated meshes live on Generation_0. Tree-structure checks are
vacuous. Count-only is also insufficient: joining N+1 cubes at the origin
hits 8×(1+N) verts with one X-center. Same on 4.5 LTS and 5.x (zones
are 4.3+). Not a version split.
- Taught:
skills/geometry-nodes-python/SKILL.md - Witness:
examples/gn-zone-iterate/
5.x Combine / Separate Bundle RNA is NodeCombineBundle /
NodeSeparateBundle. GeometryNodeCombineBundle is undefined on 5.2
and is the 4.5 experimental id. 4.5 LTS evaluates empty unless
preferences.experimental.use_bundle_and_closure_nodes is on; with the
flag the same closed form lands. Official contract is 5.0+. Item names
on bundle_items are load-bearing. Vert count alone is not a witness.
- Taught:
skills/geometry-nodes-python/SKILL.md - Witness:
examples/gn-bundle-roundtrip/
mod[identifier] = value raises TypeError on 5.2 (id properties not supported for this type). Replacement: getattr(mod.properties.inputs, ident).value. This one was listed in the 5.2 python_api notes.
Taught in skills/geometry-nodes-python/SKILL.md, witnessed by
examples/gn-modifier-inputs/. Keep both classes of finding in this
log: notes-listed removals that still need a taught split, and
behavior changes the notes never mentioned.
The audit brief assumed a runnable add-on (operators, UI panels, a socket/file-watcher
execution model). That is not what this repo is. This is a content / knowledge-pack
repository — an AI "skill pack" of SKILL.md workflows, .mdc anti-pattern rules, copy-paste
snippets/, starter templates/, and self-verifying examples/ consumed by Cursor and Claude
Code. The only executable code is (a) the templates/examples/snippets, which are teaching
artifacts, and (b) the CI tooling (tests/smoke/, scripts/build_gallery.py).
So "core operator logic" and "UI panels" exist only as demonstration code in
templates/extension-addon-template/__init__.py and the example scripts. The architectural
gaps that matter are about content correctness, validation coverage, and distribution, not
runtime robustness of a live tool.
| Layer | What it is | Notable detail |
|---|---|---|
skills/ (12) |
Markdown workflows w/ YAML frontmatter | name must match dir; standards-version gated in CI |
rules/ (6) |
.mdc anti-pattern rules |
alwaysApply / scope globs for Cursor |
snippets/ (17) |
Standalone .py patterns |
py_compile-checked only |
templates/ (2) |
extension add-on + headless batch | real, registerable add-on code |
examples/ (4) |
self-verifying demo scripts | run inside real Blender in CI, exit non-zero on failure |
tests/smoke/ |
headless Blender harness | already exists — runs on 5.1 + 4.5 matrix |
scripts/build_gallery.py |
stdlib HTML generator | builds docs/gallery/ Pages site |
.github/workflows/ |
8 workflows | validate, blender-smoke, drift-check, release, pages, label-sync, stale, dependabot |
.cursor-plugin/plugin.json |
distribution manifest | stale: v0.2.3, missing arrays |
Architectural style: static content pack + automated content-integrity CI. Distribution is
"copy skills/+rules/ into the workspace" or reference the checkout directly. No MCP server
(explicitly noted in CLAUDE.md/AGENTS.md).
Genuine strengths (the brief assumed these were missing — they are not):
- A real headless Blender testing harness already exists.
tests/smoke/run_smoke.pyasserts content, not just "no exception" (e.g. EEVEE engine-id polarity, slotted-actions boundary, driverid_typefix, SDFGridToMeshlink validity, render-non-black). It deliberately copies content rather than importing it, to catch drift in shipped files. blender-smoke.ymlruns that harness on the current stable (5.2 LTS) and fallback LTS (4.5), plus 5.1 on the weekly cron, plus runs the shipped templates and examples end-to-end (glTF magic-bytes check, exit-code-2 no-mesh path).- Conventional-commit-driven release automation with doc-sync; count-integrity gate in CI.
.cursor-plugin/plugin.json is frozen at version: 0.2.3, enumerates only skills (12) and
rules (6), and has no snippets, templates, or examples arrays. Nothing in
validate.yml checks the manifest against the filesystem, so it rotted silently while the repo
reached v0.5.0. Consumers reading the manifest see a v0.2.0-era subset.
validate.yml enforces structure, frontmatter, name=dir, py_compile, and README counts. It does
not: (a) lint/run snippets beyond byte-compilation (no Blender import, no ruff); (b) verify
every snippet/skill/template is referenced from README/ROADMAP/manifest (orphan detection);
(c) check internal/external doc links; (d) validate gallery.json against the on-disk examples.
The smoke harness covers a hand-picked subset, so a new snippet can ship un-exercised.
The harness exercises ~6 headline behaviors and 4 examples. 17 snippets + 12 skills are largely un-run. There is no manifest of "what is covered vs. not," so coverage erosion is invisible — a classic silent-truncation smell. No per-snippet smoke and no coverage report.
The brief's strongest forward-looking point. Today content is human/AI-read-only. There is no
Model Context Protocol server exposing skills/rules/snippets as retrievable resources or tools
(e.g. get_skill, search_patterns, lint_blender_py). This is the natural next-gen evolution
and the cleanest way to make the pack first-class in MCP-aware clients beyond Cursor/Claude Code.
Rules flag bpy.ops-in-loops and per-element loops, and snippets show foreach_set/get. But
there is no skill on profiling / scaling to dense meshes (e.g. numpy buffer interop,
foreach_get into preallocated numpy arrays, attribute-domain bulk ops, depsgraph cost). For a
tool whose whole pitch is "do bulk work the fast way," this is a content gap.
5.2 LTS is current stable. The smoke matrix is 5.2 + 4.5 on PRs, with 5.1 on the weekly cron. A 5.1→5.2 API removal (NodesModifier dict assignment) still required a human reading of the python_api notes — the LTS "no API change" guarantee does not cover the 5.1 to 5.2 minor step.
- Fix + gate the Cursor manifest (G1). Regenerate
plugin.jsonfrom the filesystem and add a CI check so it can't drift again. - Orphan + link audit job (G2). Fail CI if any snippet/skill/template/example is unreferenced by README/ROADMAP/manifest, or if a markdown link 404s.
gallery.json↔ disk validator (G2). Assert every example dir + hero image referenced exists and vice-versa.- Coverage manifest for smoke (G3). Emit a printed "covered / not covered" table so gaps are visible in the CI log.
- Per-snippet smoke harness (G3): a generic runner that imports/execs each snippet in headless
Blender behind a small
# smoke: skipopt-out, closing the un-run-content gap. ruff+ import-time lint of all Python content with a Blender-aware config.- New skill:
performance-and-dense-data(G5) — numpyforeach_get/foreach_setbuffers, attribute domains, when bmesh beats data API, profiling withtime/cProfileheadless. - New skills from the committed pool:
modal-operators,mathutils-patterns,usd-pipelines. - Automated LTS-series watcher (G6): scheduled job that diffs
download.blender.orgseries against the matrix and opens a sweep issue.
- MCP server (G4) exposing the pack as MCP resources + tools:
list_skills,get_skill,search_patterns, and alint_blender_pythontool wrapping the.mdcrules as executable checks. Ships as an optional adjacent package so the content repo stays a pure pack. - Rules-as-linter: compile the 6
.mdcanti-patterns into an actual AST linter (libcst/ast) that runs in CI and is reusable by the MCPlinttool and by consumers. - 5.2 LTS targeting is the current primary; keep
blender_version_minat 4.5.0 unless a 5.2-only API is required. NodesModifier input writes must version-gate dict vsmod.properties.inputs. - Fleet Pages examples support (already in ROADMAP) — lift
gallery.jsoninto the shared template, retire the local generator.
Labels: bug, ci, distribution
Context / Problem Statement
.cursor-plugin/plugin.json is pinned at "version": "0.2.3" while the repo is at v0.5.0
(VERSION). It lists only the skills (12) and rules (6) arrays and omits snippets,
templates, and examples entirely. No job in .github/workflows/validate.yml checks the
manifest against the filesystem, so it has drifted silently. Consumers that read the manifest see a
v0.2.0-era view of the pack.
Proposed Solution / Implementation Steps
- Decide the manifest schema for
snippets/templates/examples(mirror the existing relative-path-array style used forskills/rules). If the upstreamcursor-pluginschema only supports skills+rules, document that and instead just fixversion+ add the gate. - Update
.cursor-plugin/plugin.json: bumpversionto matchVERSION, and add the missing arrays enumerating files undersnippets/,templates/,examples/. - Add a
validate-manifestjob tovalidate.yml(apython3heredoc like the existingvalidate-countsjob) that:- loads
plugin.json, - asserts every path in each array exists on disk,
- asserts every
skills/*/SKILL.mdandrules/*.mdcon disk is present in the manifest, - asserts
plugin.jsonversionequalsVERSION(or is intentionally decoupled — pick one and enforce it).
- loads
- If
versionshould track releases, add it to therelease-doc-syncowned-lines list so the release pipeline rewrites it; otherwise document why it is independent inAGENTS.md.
Definition of Done
-
plugin.jsonenumerates all current skills, rules, snippets, templates, examples. -
plugin.jsonversionreconciled withVERSION(synced or documented as independent). - New CI job fails on any manifest↔filesystem mismatch (proven by a deliberately broken commit).
-
AGENTS.md"CI/CD workflows" section documents the new job.
Labels: ci, content-integrity
Context / Problem Statement
validate.yml checks structure, frontmatter, name=dir, py_compile, and README aggregate counts,
but nothing detects a snippet/skill/template/example that ships without being referenced by
README.md, ROADMAP.md, or the manifest, nor does anything catch broken markdown links. New
content can land orphaned (discoverable only by directory listing), and doc links can rot.
Proposed Solution / Implementation Steps
- Add an
orphan-checkjob tovalidate.yml: enumerate files undersnippets/,skills/,templates/,examples/; for each, grepREADME.md+ROADMAP.mdfor its name; fail listing any unreferenced item. - Add a lightweight link check: collect markdown links from
*.md, validate that relative targets exist on disk; optionally check externalhttps://links with a--allow-failsoft mode to avoid flakiness (or gate external checks behind the weeklyschedule, not PRs). - Keep it stdlib-only (consistent with
build_gallery.py) so no new CI deps are needed.
Definition of Done
- CI fails when a snippet/skill/template/example is unreferenced in README/ROADMAP.
- CI fails on a broken relative markdown link.
- External link checks run on the weekly schedule, not on every PR (no PR flakiness).
- A deliberately orphaned test file proves the gate fires, then is removed.
Labels: testing, enhancement
Context / Problem Statement
tests/smoke/run_smoke.py is excellent but exercises only ~6 hand-picked headline behaviors and
blender-smoke.yml runs 4 examples. The 17 snippets and 12 skills are largely un-run in real
Blender, and there is no record of what is covered. A new snippet can ship having only passed
py_compile, and coverage can erode invisibly (silent-truncation risk).
Proposed Solution / Implementation Steps
- Add
tests/smoke/run_snippets.py: iteratesnippets/*.py,execeach inside areset()factory-empty scene in headless Blender, treating any raised exception as failure (mirror the try/except wrapper already inrun_smoke.py). - Support a
# smoke: skip <reason>first-line pragma for snippets that need scene state they can't self-provide; print the skipped list so skips are visible, never silent. - Emit a coverage table at the end:
N snippets run, M skipped (reasons), K skills with a smoke check. Print it to the CI log. - Wire a new step into
blender-smoke.ymlafter the existing smoke driver, on the same 5.1/4.5 matrix,xvfb-run-wrapped.
Definition of Done
- Every snippet is either executed in headless Blender or explicitly
# smoke: skip-ged with a reason. - CI log prints a run/skip/coverage summary table.
- A snippet that raises in Blender fails the job (proven once).
- Runs green on both 5.1 and 4.5 matrix legs.
Labels: content, skill
Context / Problem Statement
The pack's core thesis is "do bulk Blender work the fast way," and rules
(prefer-data-over-ops-in-loops, use-foreach-set-for-bulk-data) plus the foreach-* snippets
point that direction. But there is no skill that teaches scaling to dense meshes: numpy
buffer interop with foreach_get/foreach_set, attribute-domain bulk operations, the
bmesh-vs-data-API decision under load, and how to profile headless. AI agents currently get the
"don't do it slow" rules without a positive "here is the fast, measured pattern" skill.
Proposed Solution / Implementation Steps
- Create
skills/performance-and-dense-data/SKILL.mdwith the standard YAML frontmatter (name: performance-and-dense-data, one-linedescription,standards-version: 1.10.0). - Cover: preallocated
numpyarrays withforeach_get/foreach_set(np.empty(n*3)), reshaping conventions, attribute API bulk read/write across domains, when bmesh wins, and a headlesscProfile/time.perf_countermeasurement pattern. Show 5.1 and 4.5 paths where they diverge. - Add 1–2 supporting snippets (e.g.
numpy-foreach-vertices.py) and a self-verifying assertion in the smoke harness (roundtrip equality + a coarse timing sanity check). - Update
README.mdaggregate counts,ROADMAP.mdcandidate pool,AGENTS.md, andplugin.json; commit withfeat:.
Definition of Done
- New skill passes frontmatter + name=dir CI checks.
- Shows numpy buffer interop and at least one profiling pattern, with 4.5/5.1 branches where relevant.
- Supporting snippet(s) added and smoke-exercised.
- README counts, ROADMAP, AGENTS, and manifest updated (counts gate green).
Labels: enhancement, mcp, next-gen
Context / Problem Statement
CLAUDE.md and AGENTS.md both note "there is no MCP server." Today the content is consumable only by
clients that natively read skills/+rules/ (Cursor, Claude Code). An MCP server would make the
pack first-class in any MCP-aware client and turn the 6 .mdc anti-pattern rules into an
executable lint tool — a far stronger guarantee than prose rules.
Proposed Solution / Implementation Steps
- Add an optional adjacent package (e.g.
mcp-server/) so the content repo stays a pure pack; the server readsskills/,rules/,snippets/from the repo at runtime. - Expose MCP resources:
list_skills,get_skill(name),list_snippets,get_snippet(name),search_patterns(query). - Expose an MCP tool
lint_blender_python(source)implementing the rules as AST checks:bpy.ops.*inside loops,bmesh.new()without pairedfree(), prop assignment vs annotation,bpy.context.copy()override, per-element bulk loops. Reuse this checker in CI (see the rules-as-linter long-term item). - Document install/usage in a
mcp-server/README.md; keep it out of the count/validate gates (it's tooling, not pack content) but add its own minimal test job.
Definition of Done
- MCP server lists and serves skills/rules/snippets as resources.
-
lint_blender_pythonflags all 6 anti-patterns on a fixture file and passes clean code. - Server runs against the live repo checkout with documented setup.
- CI runs the server's own tests without touching the content-count gates.