Skip to content

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

  1. 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 migrate will 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:

    sh
    gest config get storage.project_dir

    This key only exists on the v0.4.x binary — it was removed in v0.5.0, so run it before you upgrade.

  2. Back it up. Copy your legacy project directory somewhere safe. Using the path from the previous step:

    sh
    cp -r "$(gest config get storage.project_dir)" ~/gest-v0.4-backup

    Or, 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-backup
  3. Upgrade gest. Pull v0.5.0:

    sh
    gest self-update

    gest --version should now report 0.5.0 or higher.

  4. Run the migration. From the root of your project:

    sh
    gest migrate --from v0.4

    You do not need to run gest init first — gest migrate automatically 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:

  1. 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.

  2. Opens (or creates) a v0.5.0 project row in the new SQLite database keyed on the current working directory.

  3. Reads every .toml file under tasks/ and tasks/resolved/ and inserts each as a task row. Old IDs are preserved.

  4. Reads every .md file under artifacts/ and artifacts/archive/ and inserts each as an artifact row. If the old file had archived_at in its frontmatter, the new row is also archived. The kind field is converted to a tag.

  5. Reads every .toml file under iterations/ and iterations/resolved/ and inserts each as an iteration row. Tasks referenced by the iteration are re-linked using the preserved IDs.

  6. Attaches every tag on every entity.

  7. Recreates notes (attributed to find_or_create-ed authors) and relationships (blocked-by, blocks, child-of, parent-of, relates-to).

  8. Prints a summary line:

    text
    migrated 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:

sh
gest migrate --from v0.4 --path /some/backup/.gest

Skipped 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:

text
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 locationv0.5.0 destination
tasks/<id>.tomltasks table (status preserved)
tasks/resolved/<id>.tomltasks table (status done / cancelled)
artifacts/<id>.mdartifacts table + sync mirror file
artifacts/archive/<id>.mdartifacts table with archived_at set
iterations/<id>.tomliterations table (status active)
iterations/resolved/<id>.tomliterations table (completed/cancelled)
Task assigned_to: "alice"authors row + tasks.assigned_to FK
Task notes/eventsnotes + 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

  1. Verify counts. Compare the migration summary against what you expect:

    sh
    gest 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.

  2. 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.

  3. Search your project. Run a familiar search query:

    sh
    gest search "is:task tag:spec"

    If you relied on type: filters before, update your scripts and personal shell history to use tag: instead.

  4. Enable .gest/ sync (optional). If you used gest init --local on v0.4.x and want the sync mirror to keep working, confirm that storage.sync is still true (it defaults to true) and run any command — the sync layer will regenerate .gest/ from the new database on first export.

  5. Remove old subdirectory overrides. If your old gest.toml has 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 = "..."  # remove
  6. Remove 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:

    sh
    rm -rf .gest                                 # for local-mode v0.4 data
    rm -rf ~/.local/share/gest/<old-hash>        # for global-mode v0.4 data

    For 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.db is 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 kind is gone. Categorization is tag-driven now. The migrator converts your existing kind: "spec" into a spec tag automatically.
  • type: search filter is removed. Use tag:spec (or whatever tag) instead.
  • Artifact create takes a positional title. --title and --type flags no longer exist. -t is now --tag.
  • Several env vars are gone. GEST_PROJECT_DIR, GEST_STATE_DIR, GEST_ARTIFACT_DIR, GEST_TASK_DIR, GEST_ITERATION_DIR, and GEST_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 in feature-a/ could not see tasks created in main/.
  • 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:

sh
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:

  1. 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.
  2. Reinstall v0.4.x. cargo install gest --version 0.4.4 for the last v0.4 release.
  3. Delete the v0.5.0 database. Remove <data_dir>/gest.db. On Linux that is typically ~/.local/share/gest/gest.db; run gest config show to 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.
  4. Restore your backup. The ~/gest-v0.4-backup copy 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

Released under the MIT License.