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.
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.
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.
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 targetRun ./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.
| 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 |
SMT uses deterministic schema generation by default:
schema_generation:
mode: deterministic
unknown_type_policy: fail
ai_review:
enabled: falsePostgreSQL, 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.
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 | failConfigure 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.
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.
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 withsmt 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:
smt snapshotextracts the current source schema (tables + columns + indexes + FKs + check constraints) and stores the JSON in the SQLite state DB at~/.smt/migrate.db.smt synccomputes a structural diff (added / removed / changed tables and per-table column/index/FK/check deltas) against the chosen baseline.- SMT renders a target-dialect plan locally using deterministic rules.
- By default the SQL is written to
migration.sqlfor review. With--applythe statements are executed against the target in order.data-loss-riskstatements (column drops, table drops) are refused unless--allow-data-lossis 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.
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: { ... } # optionalPasswords 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.
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.
MIT.