FFI Regeneration Impact Audit
SkillDev toolsAudit the impact of regenerated MuJoCo FFI bindings (the ffi-regenerate Cargo feature) on the wrapper layer. Diffs src/mujoco_c.rs, reads the MuJoCo changelog entries for the version range, deep-verifies every changed item against the C/C++ source, and writes a self-contained HTML report suggesting required wrapper additions/removals/updates. Strictly read-only -- it suggests changes but never edits code. Use after regenerating the FFI bindings or bumping the MuJoCo version.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the FFI Regeneration Impact Audit skill
What this skill tells your AI
The instructions your AI receives, as published by davidhozic/mujoco-rs in .claude/skills/ffi-audit/SKILL.md and read by ahel’s review.
Obey all files in .claude/rules/ at all times (including the subagent policy when
authoring agent prompts). Re-read them after any context compaction.
Determine what must be fixed, added, removed, or updated in the mujoco-rs wrapper layer after
src/mujoco_c.rs has been regenerated against a new MuJoCo version. Drive the analysis with the
Workflow tool (parallel subagents).
Ground rules (non-negotiable scope)
- Strictly read-only. No source files, tests, docs, or changelog entries are modified. The ONLY output is the HTML report. Fixes are suggested, never applied.
- Never trigger the
ffi-regeneratefeature. The audit analyzes an already-regeneratedsrc/mujoco_c.rs. If the bindings have not changed relative to the chosen baseline, stop and tell the user there is nothing to audit. - Changed items only. Scope is the FFI delta plus the matching MuJoCo changelog entries.
Re-verifying unchanged wrappers is
/verify's and/safety's job -- do not duplicate it. - C/C++ evidence is mandatory. Every suggestion must be backed by reading the actual C/C++
implementation (
mujoco/src/**,mujoco/include/**) of the changed item -- deeply enough to understand the new logic (how the field is written/read, which functions consume it, whether values are used as unguarded indices, return-code conventions, dimension comments). Citing a changelog bullet or a function name without reading the implementation is not sufficient; such suggestions must be dropped. Citefile:linefor every C-side claim. - Quote both sides. Each suggestion quotes the old and new C definition (or C behaviour) AND the current Rust code it affects.
Impact catalog -- what FFI regeneration can break
The inventory and mapping phases must classify every change into (at least) these categories:
- Removed / renamed struct fields -- compile breaks in
info_method!,info_with_view!,array_slice_dyn!,getter_setter!,view_creator!,c_str_as_str_method!invocations and hand-written methods. Suggest removal/rename plus a migration-guide entry. - Added struct fields -- missing accessors. Suggest the appropriate macro, the stride from
the header dimension comment,
-1-address ->Optionmapping, and the mutability classification ((unsafe)/(allow_mut = false)/ default) by tracing how C uses the field's VALUES (unguarded array index?) permacro-system.md. - Dimension / stride changes -- the field still exists, so this compiles and is silently
wrong (OOB reads or garbage). Check BOTH the
info_method!stride and the matchinginfo_with_view!type, plusarray_slice_dyn!length expressions and cast types. - Field type changes (e.g.
mjtByte->mjtBool) -- accessor element types,bytemuck/cast_mut_info!/maybe_force_cast!targets, andbuild.rsrepr patches (e.g. themjtSameFrame->repr(u8)regex). - Semantic-only changes -- no binding diff at all; the meaning of an existing field or
function changed (e.g. the 3.9.0
margin/gapredesign). Only discoverable via the MuJoCo changelog and the C source. Affects doc comments, helper logic, viewer behaviour, examples. - Enum changes -- added/removed variants, changed discriminants, repr changes: enum-dispatch
match arms, casts of C integers into fieldless
#[repr]enums (invalid-discriminant UB), meta-variant guards (mjNOBJECT-style), range checks. - Function changes -- added (suggest a wrapper: panicking +
try_convention; safety-guard tier percoding-conventions.md: type encoding > O(1) assert >unsafe fn; verify the return-code convention from the C source -- conventions vary per function); removed (remove wrapper + migration entry); signature changed; behaviour changed (C now validates an argument itself -> a duplicated Rust guard should go; C stopped validating -> a guard is now missing). - New / removed types -- new wrapper type needed? Decide Drop (find the matching
mj_delete*/mju_free*in C),Send/Syncsoundness, and Clone derivation: thebuild.rsCloneCallbackomitsCloneon pointer-containing types by NAME PREFIX (mjs*,mjSpec_) -- a new pointer-containing type with a different prefix silently derives an unsoundClone. - Constant changes (
mjN*,mjMAX*, buffer sizes) -- fixed-size arrays and buffers in wrappers sized from those constants. - Count / address mapping tables -- new
nXcount fields or address arrays may need rows inmj_model_nx_to_mapping!/mj_model_nx_to_nitem!insrc/util.rs; changed address/count relationships breakmj_view_indices!users. build.rspost-processing -- the bindgen derive lists (Clone/Copy/PartialEq/ ThreeWayMerge) and fix-up regexes reference type names; renamed or new types can fall through. New fields inmjOption/mjStatistic/mjVisual*enter the derived ThreeWayMerge used by the viewer's state sync.- Docs and tests -- required
changelog.rst/migration.rstentries (percoding-conventions.mdrubric ordering), guide pages, examples, and correctness tests for every suggested accessor. Report these as part of each finding's "docs/tests impact".
Methodology -- multi-agent (drive with the Workflow tool)
Phase 0 -- Inline recon (main conversation, before calling Workflow)
- Pick the baseline ref. If
git diff HEAD -- src/mujoco_c.rsis non-empty, the baseline isHEAD(uncommitted regeneration). Otherwise inspectgit log --oneline -- src/mujoco_c.rsand the previous release tag (tags have novprefix); if ambiguous, ask the user. - Record versions. OLD/NEW
mjVERSION_HEADERfrom both sides of the diff and the+mj-X.Y.Zsuffix inCargo.toml(note if the suffix was not bumped yet -- checklist item). - Dump the diffs into
target/ffi-audit/(gitignored):git diff <BASE> -- src/mujoco_c.rs > target/ffi-audit/ffi.diff. Themujoco/submodule carries upstream tags (e.g.3.8.1,3.9.0):git -C mujoco diff <old-tag> <new-tag> -- include/ src/ > target/ffi-audit/c.diffand note the new version's section(s) inmujoco/doc/changelog.rst. If the old tag is absent locally, do NOT fetch (network is disallowed) -- fall back toffi.diff+ changelog only. - Compile probe (cheap removed/renamed signal):
cargo check --features "viewer-ui renderer-winit-fallback" 2>&1 | tail -n 120with the build env below. Capture all errors. - Call
Workflowwithargs: { base, old_version, new_version, diff paths, mujoco_lib, compile_errors, prior_findings }.
Phase 1 -- INVENTORY (parallel scouts, structured output)
- Diff scouts (one per category group: structs / enums / functions / constants+types, or
chunked by hunks for large diffs) parse
ffi.diffinto entries:{ id, kind, c_item, owner_struct, old, new }wherekindis one offield-added | field-removed | field-type-changed | dim-changed | enum-changed | fn-added | fn-removed | fn-sig-changed | const-changed | type-added | type-removed | semantic. - Changelog scout: read ALL sections of
mujoco/doc/changelog.rstbetween the old and new versions (multiple sections when releases are skipped). Emit an entry for every bullet that touches the public C API or semantics -- especiallysemanticentries that produce no binding diff. Cross-link bullets to diff entries.
Phase 2 -- MERGE / DEDUP (barrier -- needs all scouts)
Merge and dedupe both inventories; drop pure bindgen noise (formatting churn, comment-only
hunks) but log() what was dropped. Every diff hunk and every changelog bullet must map to at
least one inventory entry or be explicitly classified as no-impact.
Phase 3 -- DEEP C ANALYSIS + IMPACT MAPPING (pipeline, per entry)
For each entry, one analyst agent:
- Reads the C/C++ implementation deeply (
mujoco/src/**,mujoco/include/**) until the new logic of the changed item is understood; quotesfile:lineevidence. - Greps
src/(andbuild.rs) for every usage the change touches: macro tables insrc/util.rs, wrapper files, viewer/renderer,build.rscallbacks/regexes. - Emits a suggestion:
{ entry, c_evidence, rust_locations, action (add|remove|update|none), suggestion, snippet?, severity, docs_tests_impact }. Fields the crate deliberately does not wrap (e.g. arena-internalefc_*style fields) getaction: nonebut are still reported as optional additions.
Phase 4 -- ADVERSARIAL VERIFICATION (pipeline, per suggestion)
Per the subagent policy, argue both sides. For each suggestion spawn, independently:
- a hostile refuter prompted to prove the suggestion wrong or unnecessary (C already validates; the item is not wrapped and need not be; the dimension did not actually change; default to "refuted" when uncertain), and
- an independent re-deriver that reconstructs the impact from scratch without the analyst's notes.
Keep only suggestions surviving both. The C/C++ citation rule from Phase 3 applies here too.
Phase 5 -- COMPLETENESS CRITIC (loop until dry)
A final agent checks: is every ffi.diff hunk, every changelog bullet in the version range,
and every Phase 0 compile error accounted for by a finding or an explicit no-impact entry?
Each gap spawns another Phase 3 round. Loop until a round produces no new entries.
Severities
| Severity | Meaning |
|---|---|
| Critical | Silent UB/OOB or unsoundness if left unfixed (dim/type change, index field, invalid enum discriminant) |
| High | Compile break, or wrong values returned through the safe API |
| Medium | New functionality not yet wrapped (fields, functions, types) |
| Low | Docs, comments, or tests only |
| Info | Verified no-impact |
Build env
export MUJOCO_DYNAMIC_LINK_DIR="$(realpath mujoco-X.Y.Z/lib)"
export LD_LIBRARY_PATH="$MUJOCO_DYNAMIC_LINK_DIR"
Substitute X.Y.Z from the +mj-X.Y.Z suffix in Cargo.toml. For cargo check
(type-checking only, no link) only MUJOCO_DYNAMIC_LINK_DIR is strictly required, so the probe
works even before the new MuJoCo binary release is downloaded.
Deliverable -- a single self-contained HTML report
The main loop writes/overwrites mujoco-rs-ffi-audit-report.html at the repo root from the
workflow's structured findings.
Scope: the current chat session. The report accumulates findings across ALL /ffi-audit
runs within this chat. Do NOT read the on-disk HTML file to repopulate findings -- it belongs
to a prior chat. On re-runs within the same chat, pass the prior findings (with statuses) as
args so the new run merges with them; on the first run start empty.
The file must be self-contained (inline <style>), ASCII-only, and contain:
- A header with the audited version range (
old -> new), the baseline ref, and a short method note describing the phases and the impact catalog. - An overview table:
ID | Severity (pill) | Kind (badge) | C item | One-line suggestion | Rust location(s) | Status. If nothing was found, the table says "No wrapper impact found for this FFI delta." - One finding card per finding: ID, severity, kind badge, the C item with its changelog
reference, C/C++ evidence (quoted,
file:line), affected Rust locations, a concise (< 300 words) suggested change (description; a short code snippet is allowed -- NOT applied), and a docs/tests impact line (changelog/migration/test entries needed). - A "Verified no-impact" section: diff/changelog items checked and classified as requiring no wrapper change, each with a one-line reason.
- A "Version bump checklist" section: the status of each item from the
important-context.md"MuJoCo version bump checklist" (version suffix,mujoco-X.Y.Z/directory,build.rs, docs) as observed during recon.
Styling: use the same Claude aesthetic as mujoco-rs-memory-safety-audit.html -- ivory canvas
background, coral accent, warm near-black ink, Georgia serif headings, rounded pill badges, white
finding cards on the canvas, and a hairline-border overview table. Severity pills
(sev-critical/high/medium/low/info), status badges (st-open / st-already-handled /
st-no-action), kind badges in distinct hues per category (added=green, removed=red,
type/sig-changed=orange, dim-changed=purple, enum-changed=violet, semantic=grey).
Statuses:
| Status | When to use |
|---|---|
| Open | Suggested change not yet applied |
| Already handled | The wrapper was already updated for this change before the audit |
| No action | Verified to require no wrapper change |
After writing the file, present the overview table to the user as a plain-text summary. No interactive questions; do NOT modify any source files.
Signals
- GitHub stars
- 78
- Forks
- 10
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
ffi-audit- Source
- github.com/davidhozic/mujoco-rs