new-export-version
Create a new game export schema version, migration module, and related tests.
SKILL.md.claude/skills/new-export-version/SKILL.md
GitHubNew Export Version
Cut a new game export format version. This creates a standalone schema, migration module, and updates all references.
Background
Each export version has its own schema file (src/magebench/game/game-export-vN.schema.json). In this repo, export migrations live in the single module src/magebench/game/game_export_migrations.py, and raw export file I/O lives in src/magebench/game/game_exports.py.
Key files:
src/magebench/game/game-export-v*.schema.json— per-version JSON Schemassrc/magebench/game/game_export_migrations.py— current/legacy version constants and normalization helperssrc/magebench/game/game_exports.py— raw export load/write helpers used by scripts and testssrc/magebench/game/export_game.py— export producer (sets version, computes new fields)tests/test_game_export_migrations.py— roundtrip migration teststests/test_export_schema.py— schema validation tests
Step 1: Determine what's changing
Ask the user what fields are being added, removed, or modified. Determine:
- The current version number N (check the
"version"line insrc/magebench/game/export_game.py) - What new fields to add and their JSON Schema types
- Whether the
up()migration needs external data or is purely derived from existing fields - Whether the
down()migration is lossless (can we reconstruct N from N+1?)
Before deciding on a version bump for a semantics-only fix, check whether the
latest typed loaders (load_game_export, load_built_game_export) are used
directly against committed exports in tests or scripts. If they only accept the
newest version, a code-only version bump can break local consumers until the
repo's game exports are migrated too. In that case, consider whether an
in-place backfill on the existing version is the safer path.
Step 2: Create the new schema file
Copy src/magebench/game/game-export-vN.schema.json to src/magebench/game/game-export-v{N+1}.schema.json:
- Change
"const": Nto"const": N+1in theversionproperty - Update
$id,title,descriptionto reference v{N+1} - Add new fields to
properties - Add new
$defsif needed
Step 3: Extend the migration module
Add vN <-> v{N+1} support to src/magebench/game/game_export_migrations.py:
"""Migration: vN -> v{N+1} (description of what changes)."""
SOURCE_VERSION = N
TARGET_VERSION = N + 1
def up(data: dict) -> dict:
"""Migrate from vN to v{N+1}."""
assert data["version"] == N, f"Expected vN, got v{data['version']}"
# Add new fields here
data["version"] = N + 1
return data
def down(data: dict) -> dict:
"""Migrate from v{N+1} to vN."""
assert data["version"] == N + 1, f"Expected v{N+1}, got v{data['version']}"
# Remove new fields here
data["version"] = N
return data
The migration must satisfy: down(up(game)) == game for all exported games.
Step 4: Update src/magebench/game/export_game.py
- Change
"version": Nto"version": N+1inbuild_export() - Add computation for new fields in
build_export() - Update
_validate_export(): changeversion == Ntoversion == N+1 - Update the comment referencing the schema filename
Step 5: Update schema references
These files reference the schema filename and need updating:
Makefile(regen-schema-typesandverify-schema-typestargets) →game-export-v{N+1}.schema.json.claude/hooks/enforce-agents-rules.py— no change needed (uses glob pattern)doc/export-schema.md— update prose reference if it mentions a specific version
Step 6: Regenerate TypeScript types
make regen-schema-types
Verify the generated types look correct in website/src/types/game-export.d.ts.
Step 7: Add tests
In tests/test_export_schema.py, add:
test_v{N+1}_schema_is_valid— validates the new schema structuretest_v{N+1}_schema_accepts_v{N+1}— minimal valid export passestest_v{N+1}_schema_rejects_vN— old version is rejected
In tests/test_game_export_migrations.py, add migration tests:
test_vN_to_v{N+1}_up_adds_fields— verify up() adds the right fieldstest_v{N+1}_to_vN_down_removes_fields— verify down() strips themtest_round_trip_preserves_vN_structure—down(up(game)) == game
When raw-export helpers are intentionally schema-light (for example load_raw_game_export() tests), make the migration functions tolerant of partial payloads. Only assert on version and transform optional sections when they are present.
Step 8: Run checks
make check
All lint, typecheck, and tests must pass before proceeding.
Step 9: Create the PR (usually code only — data migration optional)
Default to a code-only PR. Game migrations touch hundreds of JSON files and GitHub cannot render large diffs. If the issue explicitly asks for a bounded in-repo migration (for example one season only), migrate only that slice and file a follow-up issue for the remaining historical exports.
- Create the PR with the code changes (schema, migration module, export_game.py, tests, docs, TypeScript types).
- If you did not migrate all committed exports, note the remaining migration scope in the PR description and file a follow-up issue.
Step 10: Follow-up migration
This repo does not currently have a generic export migration runner. For bounded migrations, use src/magebench/game/game_exports.py helpers (load_raw_game_export() / write_raw_game_export()) or add a one-off script in the PR. Verify the migrated files with make check.