Skip to content

Repository files navigation

smt — Schema Migration Tool

SMT extracts the schema of a source database and builds matching target-dialect DDL for review. Applying that DDL to a target database is explicit. Incremental schema changes (ALTER TABLE / CREATE INDEX / ...) are generated by diffing the current source schema against a stored snapshot. Schema DDL is generated deterministically for every supported target, with optional AI review available as an extra inspection layer.

SMT is the schema-only counterpart to DMT (the data migration tool): same pluggable driver model, same TUI scaffolding, same encrypted profile storage. It does not move rows.

Release Status

SMT v1.4.0 is published as a stable GitHub Release. Its annotated tag points to release commit 3f1e459189e0c90fee0c5d7a42069798064d2994, whose required CI and acceptance gates passed. Linux, macOS, and Windows artifacts are published with verified SHA-256 checksums. See docs/release-checklist.md for the published status, validation gates, and asset list, and CHANGELOG.md for the release notes.

Supported databases

PostgreSQL, SQL Server, MySQL — both as source and target. New engines are added by dropping a package under internal/driver/foo/ that implements driver.Driver/Reader/Writer and registers itself in init().

For applications that need deterministic schema DDL without using the SMT CLI, the public github.com/johndauphine/smt/schema package exposes deterministic create and schema-evolution DDL. The create surface covers schemas/databases, tables, columns, indexes, and standalone primary-key, foreign-key, named-unique, and check constraints. The evolution surface covers schema/table/index/constraint drops, column add/drop/type/nullability/default changes, and table truncation as ordered batches. PostgreSQL, SQL Server, MySQL, SQLite, and ClickHouse advertise their exact supported subsets through capability APIs; unsupported requests fail explicitly.

Quick start

make build
./smt init                          # guided wizard → writes config.yaml
# (or: cp config.yaml.example config.yaml and edit by hand)
./smt health-check
./smt create                        # write target DDL to schema.sql
./smt create --apply                # execute DDL against the configured target
./smt drift                         # report source-derived vs live-target drift
./smt snapshot                      # capture source schema as a baseline
# ...time passes, source schema evolves...
./smt sync                          # diff source vs live target, render ALTERs to migration.sql
./smt sync --against snapshot       # diff source vs last snapshot instead (offline planning)
./smt sync --apply                  # execute the ALTERs against the target

Run ./smt with no arguments to launch the TUI. See ./smt --help for command help and docs/cli.md for the v1.4 CLI surface contract.

Commands

command what it does
smt init build a config.yaml with a guided wizard (--non-interactive + per-field flags for scripting; --print to stdout)
smt create extract source schema and emit CREATE TABLE / CREATE INDEX / etc to schema.sql
smt create --apply also execute the generated DDL against the configured target
smt drift report read-only drift between the source-derived schema and the live target
smt snapshot save the current source schema as a baseline for future diffing
smt snapshot list list stored snapshots (id, source, schema, table count, captured-at); --limit N
smt sync diff source against the live target schema; generate target-dialect SQL; emit to migration.sql for review
smt sync --against snapshot diff source against the latest stored snapshot instead — planning is fully offline (no target connection)
smt sync --apply also execute the generated SQL against the target
smt sync --apply --allow-data-loss permit column/table drops
smt sync --apply --save-snapshot save the new schema as the next baseline after success
smt health-check ping both databases, count source tables
smt history list past schema runs
smt profile {save,list,delete,export} encrypted profile storage
smt init-secrets create the secrets file template

Deterministic DDL

SMT uses deterministic schema generation by default:

schema_generation:
  mode: deterministic
  unknown_type_policy: fail

ai_review:
  enabled: false

PostgreSQL, SQL Server, and MySQL target smt create and smt sync do not require LM Studio, Ollama, OpenAI, Anthropic, Google, or any other AI provider. SMT introspects source schema metadata, maps source types to target types through deterministic rules, renders target DDL, and writes SQL artifacts under migration.data_dir. Use --apply to execute generated SQL against a live target.

Optional AI Review

AI review is optional. When enabled, the AI reviews deterministic DDL before apply and reports issues. It does not silently rewrite executable SQL.

ai_review:
  enabled: true
  mode: warn   # warn | fail

Configure the provider in ~/.secrets/smt-config.yaml (run smt init-secrets for the template):

ai:
  default_provider: anthropic
  providers:
    anthropic:
      api_key: "your-anthropic-api-key"
      model: claude-sonnet-4-6
    anthropic-sonnet5:
      provider: anthropic
      api_key: "your-anthropic-api-key"
      model: claude-sonnet-5
      max_tokens: 16384
    google:
      api_key: "your-google-api-key"
      model: gemini-2.0-flash
encryption:
  master_key: ""                # openssl rand -base64 32, used for profile encryption
notifications:
  slack:
    webhook_url: ""

The same secrets file is read by both the CLI and the TUI. File mode 0600 is enforced.

Table DDL review uses an AI parser plus SMT's deterministic Go comparator. Index, foreign-key, and check-constraint review uses SMT's deterministic side-object parser/comparator over the generated DDL. Warnings in logs and manifest.json identify which method produced each finding.

Use the ai_review.enabled and ai_review.model keys for optional AI review. For Claude Sonnet 5, define a named Anthropic provider such as anthropic-sonnet5 and set ai_review.model: anthropic-sonnet5. SMT disables Sonnet 5 thinking on strict JSON parser calls and gives Anthropic JSON parser prompts a 16384 token output budget by default; max_tokens in the secrets provider can raise that further for unusually large DDL. The 0.x aliases migration.ai_verify and migration.ai_verifier_model are removed from the v1 config contract and fail with rename guidance.

Philosophy

SMT's core schema path is deterministic: introspect schemas, map types, compute structural diffs, render SQL, write artifacts, and run SQL. AI can be a strong reviewer, but it is not required and is not the author of executable DDL.

How sync works

smt sync extracts the current source schema and diffs it against a baseline selected with --against:

  • --against target (default): introspect the live target schema and diff desired-vs-existing — "how does my target differ from what the source says it should be?" Requires a target connection.
  • --against snapshot: load the latest stored snapshot (captured with smt snapshot) and diff current-vs-baseline — "what changed in my source since the last baseline?" Planning is fully offline; a target connection is only opened for --apply.

Either way:

  1. smt snapshot extracts the current source schema (tables + columns + indexes + FKs + check constraints) and stores the JSON in the SQLite state DB at ~/.smt/migrate.db.
  2. smt sync computes a structural diff (added / removed / changed tables and per-table column/index/FK/check deltas) against the chosen baseline.
  3. SMT renders a target-dialect plan locally using deterministic rules.
  4. By default the SQL is written to migration.sql for review. With --apply the statements are executed against the target in order. data-loss-risk statements (column drops, table drops) are refused unless --allow-data-loss is also passed.

The stable v1 sync support and refusal behavior is documented in docs/sync-contract.md. Apply failure and rerun behavior for create --apply and sync --apply is documented in docs/apply-recovery.md.

Configuration

config.yaml (per-migration):

source: { type, host, port, database, user, password, schema }
target: { type, schema }       # optional; defaults to postgres/public
                               # host/database/user/password only needed for --apply
schema_generation: { mode: deterministic } # optional; this is the default
ai_review: { enabled: false }              # optional; this is the default

# Optional migration overrides. When omitted, SMT generates the full schema:
# tables, indexes, foreign keys, and check constraints.
# migration:
#   include_tables: []
#   exclude_tables: ["__*", "temp_*"]
#   data_dir: ~/.smt
slack: { ... }                 # optional

Passwords support ${env:VAR}, ${file:/path}, and literal forms.

~/.secrets/smt-config.yaml (global): AI provider keys, profile encryption master key, Slack webhook. Never put these in config.yaml.

Persisted artifact compatibility for the state DB, snapshots, run manifests, and RendererVersion is documented in docs/v1-compatibility.md. Live database and optional live AI release gates are documented in docs/live-acceptance.md. Release packaging and installation steps are documented in docs/release-checklist.md.

Heritage

SMT started as a schema-feature carve-out from DMT. The driver layer, optional AI review plumbing (multi-provider HTTP for Anthropic / OpenAI / Google / Ollama / LM Studio), encrypted profile storage, and Bubble Tea TUI all carry over. The data-transfer machinery (parallel workers, chunking, write-ahead writers, runtime AI tuning) is gone.

License

MIT.

About

Schema migration tool — extracts source schemas, generates target DDL, applies AI-rendered ALTERs from schema diffs

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages