gtmesh upgrade
Re-applies the engine-owned scaffold files into your project, refreshing them to the current version without overwriting anything you’ve edited.
gtmesh upgrade [--dry-run] [--with-defaults]What it does
A scaffolded mesh repo contains two kinds of files: engine-owned files (the plumbing init laid down and the engine maintains) and project-owned files (your config, reference data, per-type schemas, and templates). After you bump the gtmesh CLI itself, gtmesh upgrade brings the engine-owned files in your project up to the new version.
Every skill under .claude/skills/ is engine-owned, so upgrade keeps them current. A skill is a parameterized capability: you retune it by editing what it reads — foundation/voice.md, foundation/editorial.md, foundation/brand.md, templates/<page-type>.md, reference/editorial-rules.yaml, and the relevant gtmesh.config.yaml blocks — rather than by editing the skill. If you have edited one anyway, upgrade keeps your version and writes the new one beside it as SKILL.default.md to merge.
It’s careful about your work:
- Engine-owned files are refreshed only if you haven’t touched them. If you have edited one, the new version lands beside it as
<file>.default.<ext>for you to merge by hand — your edit is never clobbered. - Project-owned files (config, reference data, your per-type schemas, templates) are never overwritten. The one thing
upgradedoes togtmesh.config.yamlis run its format migrations — structural edits that keep every value and comment you wrote. - It’s idempotent — running it again when you’re already current is a no-op.
Use --dry-run to see the plan first. Use --with-defaults to also drop the new project-file defaults next to yours (as *.default) so you can hand-merge any changed defaults.
How you learn an upgrade is due
You don’t have to remember to check. The mesh tells you, on what actually differs rather than on a version number — a release often ships no scaffold change at all, so a version comparison would nag you for a release that changed nothing of yours.
gtmesh doctorreports the whole picture asengine-files-pending: the files a plaingtmesh upgradewould take, and separately the ones that need a hand-merge (files you edited, and project-owned files whose engine default moved).gtmesh statuscloses with a one-line footer counting the files a plaingtmesh upgradewould take. A file you’ve edited never reaches this footer — it resolves through a merge, not an upgrade, so nagging you about it on every command would be noise.validate,sealandapplywarn about exactly one file:schemas/common.schema.yaml, the shared base every per-type page schema$refs. When it’s pending, those commands validated against a stale contract — the one case where a missed upgrade changes an answer rather than just holding back an improvement.
A mesh that’s current prints none of this.
It restores its own baseline
upgrade compares your files against .gtmesh/manifest.json — the baseline init recorded, which is how it knows an engine file is untouched versus edited. That file is engine state, not one of your project files, so if it’s missing (an older project that predates it, or one where it was deleted) upgrade recreates it from the bundled scaffold and carries on, printing:
recreated .gtmesh/manifest.json (was missing — upgrade baseline restored)apply does the same, and --dry-run heals too — the baseline is what the dry plan is computed from. Nothing of yours is touched by the heal; only the manifest is written. A directory with no gtmesh.config.yaml isn’t a gtmesh project, and there upgrade still stops rather than inventing a baseline.
It runs the schema migrations
Some engine upgrades change the shape of your committed state (a hash formula, a new field). upgrade reconciles that with a versioned, sequential migration chain — the same idea as a database schema migration.
Your mesh records the schema it’s on in graph/meta.json (a small, committed, machine-owned file: { "schema_version": N }). A mesh with no graph/meta.json is version 0. When you upgrade, the engine runs each migration step from your mesh’s version up to the current one, in order, then stamps the new version.
Two properties matter:
- It catches up from any age. A mesh several versions behind runs the whole chain (
v0 → vN) in a singleupgrade— you never have to step through intermediate releases. - Each step fires exactly once. The version stamp gates every step, so re-running
upgradewhen you’re already current is a no-op — a migration never runs twice against the same mesh.
graph/meta.json is written by upgrade and committed — commit it along with the other changes so your teammates (and clones) know which schema the mesh is on. --dry-run runs no migrations.
It migrates your config file
gtmesh.config.yaml has its own chain, on the same pattern. Engine releases occasionally change the shape of the config — a key gets a clearer name, a setting the engine stopped reading gets dropped. upgrade makes that change for you rather than leaving you to find it in a changelog.
The version lives in the config itself, as a top-level config_version key:
# gtmesh.config.yaml — the ENGINE-FACING structural law for this mesh.
# …your header comment…
config_version: 1
project: my-meshA config with no config_version is version 0 — every mesh scaffolded before this existed — and the first upgrade brings it current and writes the stamp in as the first key, under your own header comment. Three things are worth knowing:
- Your comments survive. The config is a file you read and annotate, so migrations edit it through the YAML tree rather than re-serializing it: every comment — the header block, the notes above a key, the inline trailers — and the key order come out the other side. Values are only ever touched by the specific step that moves them. YAML whitespace inside untouched blocks may be re-columned, so expect a slightly noisy diff the first time.
- Nothing behaves differently. Config migrations are structural. Run
gtmesh planbefore and after and you get the same plan, down to itsconfig_hash— the stamp itself is deliberately excluded from that fingerprint, so a migration never reads as “config changed since the last apply.” - It runs exactly once. The stamp gates every step, so a config already at the current version isn’t even rewritten — a second
upgradeleaves it byte-identical, andupgradesays nothing about it.
When a migration does run, upgrade names the steps it applied:
migrated gtmesh.config.yaml v0 → v1 (ran: config-version-stamp); comments preserved — review the diff before committingReview that diff like any other change and commit gtmesh.config.yaml with the rest of the upgrade. --dry-run runs no config migration either.
upgrade refreshes the files inside an existing project. Bumping the gtmesh CLI binary itself is a separate step — see the full workflow in Installation.
Arguments & flags
| Option | Description | Default |
|---|---|---|
--dry-run | Report the plan without writing anything | off |
--with-defaults | Also write changed project-file defaults as *.default for hand-merging | off |
The global options (--project, --json, --quiet) also apply.
Reads & writes
- Consumes: the bundled scaffold assets for the installed CLI version, compared against your project’s files.
- Produces: refreshed engine-owned files;
<file>.default.<ext>beside any engine file you’ve edited; and, with--with-defaults,*.defaultcopies of changed project-file defaults. It also runs any pending schema migrations (updating your committed state and stampinggraph/meta.json) and any pending config-format migrations (editinggtmesh.config.yamlin place and stamping itsconfig_version). With--dry-run, produces only a plan — read-only. - Commits: review the refreshed files and merge any
.defaultsidecars; commit them along withgraph/meta.json,gtmesh.config.yaml, and any migrated state.
Examples
gtmesh upgrade --dry-run # preview what would change
gtmesh upgrade # refresh engine-owned files in place
gtmesh upgrade --with-defaults # also surface changed project-file defaults to hand-mergeRelated
- Installation — the full upgrade workflow, including bumping the CLI itself.
- Reference — the version-exact flag list.