Migrating from v0.4.x to v0.5.0
v0.5.0 moves gest's storage from flat TOML/Markdown files inside a .gest/ directory to a single SQLite database (via libsql). If you have an existing v0.4.x project, the gest migrate command imports all of it into the new database in one shot.
This guide covers:
- How to run the migration
- How data is mapped from the old layout to the new one
- What to verify afterward
- What changes are visible to you as a user
- Why the storage model changed
- How to roll back if something goes wrong
Before you migrate
Find your v0.4.x data. In v0.4.x, local-mode projects stored data in
.gest/at the project root, and global-mode projects stored data under$XDG_DATA_HOME/gest/<sha256(cwd)[..8]>/.gest migratewill auto-discover either layout, but it helps to know which one you have so you can back it up.While you are still on v0.4.x, the fastest way to locate the resolved project directory is to ask v0.4 directly:
shgest config get storage.project_dirThis key only exists on the v0.4.x binary — it was removed in v0.5.0, so run it before you upgrade.
Back it up. Copy your legacy project directory somewhere safe. Using the path from the previous step:
shcp -r "$(gest config get storage.project_dir)" ~/gest-v0.4-backupOr, if you already know the layout:
sh# For local-mode v0.4 projects cp -r .gest ~/gest-v0.4-backup # For global-mode v0.4 projects (example hash; run `ls ~/.local/share/gest/` # to find yours) cp -r ~/.local/share/gest/<old-hash> ~/gest-v0.4-backupUpgrade gest. Pull v0.5.0:
shgest self-updategest --versionshould now report 0.5.0 or higher.Run the migration. From the root of your project:
shgest migrate --from v0.4You do not need to run
gest initfirst —gest migrateautomatically creates (or reuses) the v0.5.0 project row for the current working directory as part of the import.
Run the migration
gest migrate --from v0.4 does the heavy lifting. The command:
Finds your legacy
.gest/directory by checking the current directory, then walking up ancestors, then finally checking the global hash location ($XDG_DATA_HOME/gest/<sha256(cwd)[..8]>/) that v0.4.x used.Opens (or creates) a v0.5.0 project row in the new SQLite database keyed on the current working directory.
Reads every
.tomlfile undertasks/andtasks/resolved/and inserts each as a task row. Old IDs are preserved.Reads every
.mdfile underartifacts/andartifacts/archive/and inserts each as an artifact row. If the old file hadarchived_atin its frontmatter, the new row is also archived. Thekindfield is converted to a tag.Reads every
.tomlfile underiterations/anditerations/resolved/and inserts each as an iteration row. Tasks referenced by the iteration are re-linked using the preserved IDs.Attaches every tag on every entity.
Recreates notes (attributed to
find_or_create-ed authors) and relationships (blocked-by, blocks, child-of, parent-of, relates-to).Prints a summary line:
textmigrated 42 tasks, 17 artifacts, 5 iterations, 23 notes, 61 relationships
Explicit source path
If the auto-discovery does not find your data (for example, you moved the directory, or you are importing from a different checkout), pass --path:
gest migrate --from v0.4 --path /some/backup/.gestSkipped files
If the migrator cannot parse a specific file, it logs a warning and increments a skipped counter. You will see a summary line like:
skipped 3 files (run with RUST_LOG=warn for details)Re-run with RUST_LOG=warn (or gest -vv migrate --from v0.4) to see exactly which files failed and why. Common causes are hand-edited files that drifted from the v0.4.x format or partial writes from an interrupted v0.4.x command.
How data maps from v0.4 to v0.5
| v0.4.x location | v0.5.0 destination |
|---|---|
tasks/<id>.toml | tasks table (status preserved) |
tasks/resolved/<id>.toml | tasks table (status done / cancelled) |
artifacts/<id>.md | artifacts table + sync mirror file |
artifacts/archive/<id>.md | artifacts table with archived_at set |
iterations/<id>.toml | iterations table (status active) |
iterations/resolved/<id>.toml | iterations table (completed/cancelled) |
Task assigned_to: "alice" | authors row + tasks.assigned_to FK |
Task notes/events | notes + events tables |
Task links (blocks, blocked-by…) | relationships table (with inverses) |
Task/Iteration metadata (TOML table) | JSON column on the row |
Artifact metadata (YAML frontmatter) | JSON column on the row |
Artifact kind: "spec" | spec tag on the artifact |
Artifact tags: [a, b, c] | tags + entity_tags rows |
IDs are preserved across the migration — every k-z 32-character identifier from v0.4.x will still resolve after the migration, so any external references (commit messages, PR bodies, issue notes) keep working.
After the migration
Verify counts. Compare the migration summary against what you expect:
shgest task list --all --json | jq 'length' gest artifact list --all --json | jq 'length' gest iteration list --all --json | jq 'length'These should match (or exceed, if the migrator already found additional state) the
tasks/,artifacts/,iterations/file counts in your old.gest/directory.Spot-check a few entities. Pick a task, artifact, and iteration you know well and
gest <entity> show <id>them. Verify that title, description, tags, links, and notes are all present.Search your project. Run a familiar search query:
shgest search "is:task tag:spec"If you relied on
type:filters before, update your scripts and personal shell history to usetag:instead.Enable
.gest/sync (optional). If you usedgest init --localon v0.4.x and want the sync mirror to keep working, confirm thatstorage.syncis stilltrue(it defaults to true) and run any command — the sync layer will regenerate.gest/from the new database on first export.Remove old subdirectory overrides. If your old
gest.tomlhas any of these keys, delete them — they are no longer honored:toml[storage] project_dir = "..." # remove state_dir = "..." # remove artifact_dir = "..." # remove task_dir = "..." # remove iteration_dir = "..." # removeRemove the legacy
.gest/directory (only once you are happy). The migrator does not delete the source — it leaves everything in place so you can diff and verify. When you are confident the migration is correct:shrm -rf .gest # for local-mode v0.4 data rm -rf ~/.local/share/gest/<old-hash> # for global-mode v0.4 dataFor local-mode projects, the sync layer will recreate
.gest/from the new SQLite database on the next command invocation — so do this only after verifying the database has everything.
What changes for you
A few things look different in v0.5.0:
gest.dbis the source of truth. Entity data lives in SQLite at<data_dir>/gest.db. For local-mode projects,.gest/becomes a sync mirror that is rewritten from the database on every mutation.- Artifact
kindis gone. Categorization is tag-driven now. The migrator converts your existingkind: "spec"into aspectag automatically. type:search filter is removed. Usetag:spec(or whatever tag) instead.- Artifact
createtakes a positional title.--titleand--typeflags no longer exist.-tis now--tag. - Several env vars are gone.
GEST_PROJECT_DIR,GEST_STATE_DIR,GEST_ARTIFACT_DIR,GEST_TASK_DIR,GEST_ITERATION_DIR, andGEST_LOG_LEVEL(single underscore) have been removed or renamed. See the Configuration reference for the new names. - Undo history lives in the database. There is no longer a separate state directory. Undo follows whichever database the command ran against.
See the v0.5.0 changelog entry for the full list.
Why the change
The real driver was worktree coordination, not storage performance.
In v0.4.x, both storage modes assumed the current working directory identified the project:
- Local mode wrote to
.gest/inside the worktree, so two worktrees of the same repo had two completely separate stores. An agent working infeature-a/could not see tasks created inmain/. - Global mode keyed the global store by
sha256(cwd)[..8], so every worktree hashed to a different project row even though they were the same project. Same problem, different shape.
Neither mode let parallel agents — the whole point of gest — share a live view of the same project's tasks, artifacts, and iterations.
v0.5.0 fixes this by making project identity explicit: a project is a row in a single global database, and additional checkouts opt in by attaching themselves as workspaces. From a second worktree:
cd ../myapp-feature-a
gest project # in the original checkout, copy the project id
gest project attach <project-id>After attaching, both checkouts resolve to the same project row and share the same tasks, artifacts, and iterations. No cwd hashing, no per-worktree state drift.
Once project identity moved out of the filesystem, SQLite became the obvious backend — it gives us atomic multi-entity writes (so a task + its tags + its relationships land together or not at all), real query support for dependency graphs and tag filtering, and safe concurrent access for multiple agents hitting the same store. The flat-file layout could not offer any of those, but they were enablers, not motivations.
For local-mode projects, a sync layer still mirrors the database to .gest/ as YAML and Markdown files — grouped into singular per-entity subdirectories (task/, artifact/, iteration/, etc.) — so the data stays inspectable and git-committable. The database is still the source of truth.
See discussion #28 for the original RFC and ADR-0013 for the implementation decision.
Rolling back
If the migration produced unexpected results and you want to go back:
- Don't delete the v0.4.x
.gest/directory. The migrator never touches the source, so as long as you skipped the cleanup step above your old data is intact. - Reinstall v0.4.x.
cargo install gest --version 0.4.4for the last v0.4 release. - Delete the v0.5.0 database. Remove
<data_dir>/gest.db. On Linux that is typically~/.local/share/gest/gest.db; rungest config showto check. The v0.4.x binary will ignore the file either way because it does not know about SQLite storage, but removing it keeps your data dir clean. - Restore your backup. The
~/gest-v0.4-backupcopy from step 2 of the pre-migration checklist is your safety net — copy it back to its original location.
Please also open an issue with what went wrong. The migrator is new and edge cases in v0.4.x-generated files are easier to fix when we see them.
See also
- gest migrate — CLI reference for the command
- Configuration — new config sections and env vars
- ADR-0013 — the decision to move to SQLite-first storage