This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
What I Built
miglens reads a SQL migration and tells you what is risky about it before you run it: first with a deterministic rule scan, then in plain English from a local open-weight model.
miglens migrations/0042_orders_cleanup.sql
cat migration.sql | miglens -model llama3.2:1b
The dangerous line in a migration is rarely a syntax error. It is the DROP COLUMN that deletes data a still-running service needs, the ADD COLUMN ... NOT NULL that fails on a table with two million rows, the CREATE INDEX that quietly blocks writes for twenty minutes. Linters check style. miglens checks blast radius.
Fitting this week's theme, a migration is where a codebase keeps its ghosts: dropped columns that old code still reaches for, dead tables nobody owns, constraints added in a hurry that haunt every deploy after. miglens finds those ghosts while they are still just lines in a file, before they start haunting production.
Two layers, in order:
- Rule scan (always runs, fully offline). Fifteen rules with severity, line number, a plain explanation, and a safer way to do the same thing. Dropping a table or column is CRITICAL, whole-table writes without a WHERE and NOT NULL without a DEFAULT are HIGH, a non-concurrent index build is MEDIUM, and so on.
- AI summary (optional, local). The findings plus the migration go to a model running in Ollama on my own machine, which answers in a fixed shape: what could break, who it affects, a safer alternative, and a rollout order.
If Ollama is not running, miglens prints the full scan, a short note on how to enable the summary, and exits 0. The scan is the source of truth. The model only explains it.
Demo
examples/risky.sql packs five classic mistakes into one small migration. Real output from a local run with llama3.2:1b:
$ ./miglens -model llama3.2:1b examples/risky.sql
== examples/risky.sql ==
5 findings: 1 critical, 3 high, 1 medium
line 4 CRITICAL drop-column
> ALTER TABLE orders DROP COLUMN legacy_code;
Dropping a column permanently deletes that data, and any running code that still reads or writes the column starts failing immediately.
Safer: Ship code that ignores the column first (expand/contract), then drop the column in a later migration.
line 7 HIGH add-column-not-null
> ADD COLUMN discount_code text NOT NULL;
Adding a NOT NULL column without a DEFAULT fails on tables that already have rows, and on newer Postgres versions it still takes a lock while the default is applied.
Safer: Add the column nullable (or with a DEFAULT), backfill existing rows, then add the NOT NULL constraint.
line 9 MEDIUM create-index-not-concurrent
> CREATE INDEX idx_orders_customer_id ON orders (customer_id);
CREATE INDEX without CONCURRENTLY blocks writes to the table until the index is built, which on a large table means downtime.
Safer: Use CREATE INDEX CONCURRENTLY (outside a transaction) so writes keep flowing during the build.
And the local model on the same findings:
--- AI summary (local model) ---
The worst realistic outcome could be that the database is permanently
locked and unusable for a certain period, causing disruptions to users,
services, and jobs that rely on the orders table.
Honest note, same as last time: the 1B model is loose. It gets the shape of the answer but not the precision, and the summary gets sharper with llama3.2:3b. The tool does not care which one you run. That is the point of building on a local, swappable model. The deterministic scan underneath is exact either way: right lines, right severities, every time.
Code
miglens is one Go module with zero dependencies, standard library only:
-
cmd/miglens: flags and env config (-model,-host,-no-ai,OLLAMA_HOST,OLLAMA_MODEL), file or stdin input -
internal/rules: the pattern engine: walks the SQL statement by statement, comments stripped with line numbers preserved, one finding per rule per statement -
internal/report: renders the scan: counts by severity, then each finding with its line, explanation, and safer alternative -
internal/prompt: builds the fixed-shape summary request, worded so the model reports impact instead of inventing root causes -
internal/ollama: a thin client for Ollama's/api/generate; unreachable server or missing model is a note, never a failed run
53 unit tests cover the rule engine (including exact line and severity assertions on the shipped risky example, and zero findings on the safe one), the report renderer, the prompt builder, and the client against a stub Ollama server. go build ./... and go test ./... are green.
How I Built It
The layering came first, before any code. An AI summary of a migration is only worth reading if something exact sits underneath it, because a small local model will happily sound confident about the wrong line. So the deterministic scan owns the facts, and the model owns the explanation. If the model is down, vague, or not pulled yet, you lose adjectives, not correctness.
The unglamorous problems:
- SQL is not line-shaped. Statements span lines, comments sit inside them, and a finding that points at the wrong line is worse than no finding. The engine strips comments while preserving line numbers, then reports the exact line where a pattern matched inside a multi-line statement.
-
Rules interact.
CREATE INDEX CONCURRENTLYcannot run inside a transaction, so flagging "no transaction" on a file that uses CONCURRENTLY would be a false alarm. The missing-transaction rule stays quiet when CONCURRENTLY is present. -
Failure should teach. A missing model or a stopped Ollama prints exactly how to fix it (
ollama serve,ollama pull llama3.2:3b) and the run still ends with the full scan in hand.
Why Does Open Innovation Matter?
A production schema is a map of the business: table names, column names, the shape of the data, which parts are being changed and why. Sending a migration to a hosted AI API sends that map to someone else's computer, under someone else's retention policy, usually minutes before a deploy, when nobody is stopping to think about data policy.
A local open-weight model removes the question entirely. The migration stays on the machine that wrote it. It costs nothing per call, works with the network cable unplugged, and the model can be swapped the day a better open one drops. And because the deterministic layer needs no model at all, the tool still earns its place in a CI pipeline on a box with no GPU and no Ollama.
The Spirit of It
Reviewing a migration by hand means holding the whole table in your head: how big it is, what locks each statement takes, which deploy order makes it safe. miglens does that first pass in seconds and hands me five findings with line numbers instead of forty lines of raw SQL. It does not make the migration safe. It makes sure I am not the last thing standing between a DROP COLUMN and production.
Top comments (0)