Files
craft-anything/docs/file-formats.md
Afloat fed67edd38 Pinned builds keep runtime models in obj folders (R2.10) (#439)
Frozen and light pins keep a project's runtime models in assets/obj; only obj folders beside a .NET project file are left out as build output. Fixes #274.
2026-11-03 00:16:57 +07:00

4.4 KiB

File formats

Every file Craft Anything saves carries a version number. Reading a file runs the same three steps: parse it into a raw document (a JSON value or a TOML table), run the migration steps from the file's version up to the current one, then turn the document into Rust types. The step runner is crates/engine/src/versioned.rs.

File Owner Current version Older files Newer files
<project>/craft.json engine::workspace (Manifest) MANIFEST_VERSION = 1 (version; missing = 0) brought forward, saved as current opened read-only: saving is refused
config.toml (settings::config_dir()) engine::settings (Settings) SETTINGS_VERSION = 2 (version; missing = 0; 2 moved [engines.ghidra] enabled to deep_mode) brought forward loaded, never saved over
<name>.cany/project.toml engine::project (ProjectMeta) FORMAT_VERSION = 1 (format_version, required) brought forward refused, with "update Craft Anything"
<project>/transcript.json ai::context (the API builder's conversation) TRANSCRIPT_VERSION = 1 ({version, history}) read as before not resumed; a fresh build replaces it
<project>/app/mechanics.json engine::mechanics (written by the builder, read by MechanicsCheck) MECHANICS_VERSION = 1 (version; missing = 1) — the check fails and says so
<project>/checks/round-N/self-test.json engine::self_test (written by the built game's --self-test, read by SelfTestCheck) 1 (version; missing = 1) — the check fails and says so
<project>/usage.json engine::workspace (ProjectUsage; written only by the rebuild run) USAGE_VERSION = 1 (version; missing = 0) read as before, saved as current read as far as its fields are known, never written over
<project>/questions.json engine::questions (AskedQuestion, written by rebuild_question_ask) QUESTIONS_VERSION = 1 ({version, items}) read as before read as far as its fields are known
<project>/pinned/<n>/pin.json engine::pins (Pin) PIN_VERSION = 2 (version; missing = 1; 2 records the selected program for a copied direct launch) brought forward read as far as its fields are known
<folder>/standin.json engine::tools::standin_tools (analysis::standin::StandIn; written only by web_standin_build) STANDIN_VERSION = 1 (version, required) brought forward refused, with "update Craft Anything", and never written over
<project>/feed.json ai::rebuild::feed (written by the run) FEED_VERSION = 1 ({version, items}; a bare list = 0) read as before, saved as current never written over; the conversation is rebuilt from the project for display

Other JSON in a project (assets.json, inbox/, checks/, evidence records) has no version yet; add one the first time its shape changes.

Newer files

A file from a newer Craft Anything is never downgraded or overwritten, because the fields we don't know would be lost. craft.json and config.toml are still read as far as their fields are known, so the person can see their project or keep their settings; every save returns a ToolError that says to update Craft Anything. A newer craft.json whose fields can't be read at all is refused the same way, and a newer config.toml that can't be read gives defaults without touching the file. A newer .cany is refused outright: its evidence layout may have changed in ways we can't see.

A file that can't be parsed is not "newer": config.toml is set aside as config.toml.broken and defaults are used; an unreadable craft.json gives a plain-English error when opened and is left out of the project list.

Adding a migration step

  1. Bump the version constant (MANIFEST_VERSION, SETTINGS_VERSION or FORMAT_VERSION).
  2. Append one function to the matching steps list (MANIFEST_STEPS, SETTINGS_STEPS, FORMAT_STEPS). It takes the raw document from version n to n + 1: rename, move or fill keys. Return Err with a plain-English reason when it can't. A compile-time assertion fails if the list and the constant disagree.
  3. Add a test that reads a literal sample of the old file (keep the existing samples; old files must keep loading forever).
  4. For craft.json, mechanics.json and self-test.json, run cargo xtask schemas and commit docs/schemas/. cargo xtask schemas --check (and an engine test) fail when the published schema is stale.