Use this to keep a dbt project correct as the warehouse and the business change. It detects drift on four axes and proposes the fix: schema drift (source columns and tables added, dropped, retyped, or renamed), volume drift (a row count that collapsed, a table that emptied, a load that half-failed), grain drift (a key that lost uniqueness, a changed row-per-entity cardinality, an increased join fanout), and semantic drift (a metric, measure, dimension, or entity definition that no longer matc...
Install to Claude Code
npx -y skills add exmergo/dex --skill maintain --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Maintain?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/exmergo-maintain)More formats (shields.io, HTML) on the badges page.
---
name: maintain
description: 'Use this to keep a dbt project correct as the warehouse and the business change. It detects drift on four axes and proposes the fix: schema drift (source columns and tables added, dropped, retyped, or renamed), volume drift (a row count that collapsed, a table that emptied, a load that half-failed), grain drift (a key that lost uniqueness, a changed row-per-entity cardinality, an increased join fanout), and semantic drift (a metric, measure, dimension, or entity definition that no longer matches, new categorical values, dangling semantic references). Reach for this when something that used to work has started failing or producing different numbers and the cause is more likely upstream than in the code you just wrote: a test that began failing with no code change, a dashboard whose numbers moved, a model that is suddenly empty or duplicated. Trigger it for requests like "what changed in the warehouse", "did anything drift", "is my dbt project still in sync", "my primary key has duplicates now", "the row count dropped", "did the load run", "the data stopped flowing", "the revenue metric definition changed", "reconcile my models with the source schema", or "which models are stale". It reads the .dex/ snapshot and proposes reviewable diffs; it never overwrites hand-written work. To author new models or metrics from scratch, use transform. To learn an unfamiliar warehouse for the first time, use explore.'
---
# Maintain
Keep the dbt project correct as the world underneath it moves. Maintenance is the
recurring half of the loop: warehouses drift, loads half-fail, models go stale,
keys stop being unique, and business definitions change. This skill compares a
known-good baseline against current reality, classifies what drifted, and proposes
the reconciling edit. It is manual and on-demand here; continuous drift detection
and automated PRs are the commercial product.
## The model: baseline, detect, reconcile
Drift is measured against a **baseline** (the `.dex/snapshot.json` fingerprint of
the warehouse map and the project's per-layer definitions). Detection is
read-only; only reconcile proposes edits.
**Snapshot discipline matters.** A snapshot is only as trustworthy as the moment
it froze. Take one right after a known-good build (`maintain snapshot`), and
**commit `.dex/snapshot.json` like a lockfile** so the whole team diffs against
the same reference. Snapshot a state that is already drifted and `check` will
mask the very drift you care about. When you accept a change as the new normal
(re-run `explore map` first, then `maintain snapshot`); `check` warns when the
baseline looks stale.
**On a warehouse past the rank cutoff, use `explore map --full` before
snapshotting.** Past 50 objects `explore map` profiles the top 25 by rank and
enters the rest as metadata alone, and the baseline can only compare columns for
objects it has columns for. Snapshotting a partial map is still valid, and the
envelope reports `column_detail_count` against `dataset_count` plus a warning
naming what it could not cover, so the gap is visible rather than silently
mistaken for a clean bill.
## How to drive it
```bash
uv run "${CLAUDE_SKILL_DIR}/scripts/run.py" <subcommand> [flags]
```
dex runs its engine through `uv`, which is a prerequisite and is not installed by
Claude Code. If the shell reports `uv: command not found`, stop and tell the user
to install it (`curl -LsSf https://astral.sh/uv/install.sh | sh`, or
`brew install uv`, or `pipx install uv`), then re-run. Never fall back to diffing
the warehouse against the project by hand instead: the drift axes and the baseline
comparison live in the engine, so any other path is guesswork.
- `maintain snapshot` captures or refreshes the baseline. Run it after a clean
explore or transform session so later runs have a known-good reference. It pins
the current `.dex/cache.json` (so the grain baseline is the exact-distinct
verdicts `explore map` already computed) plus per-layer fingerprints of the dbt
project. Without a cache it captures a metadata-only baseline and says so. It
also warns when the cache it pinned is thin (objects without column detail) or
older than the profile freshness window, because either makes an "accept
current state" only partly true.
- `maintain check` is the everyday entry point: it sweeps every axis and returns
a report ranked by blast radius. Read-only.
- `maintain schema [<objects>]` detects **structural drift**: source columns and
tables added, dropped, retyped, or renamed; nullability changes; declared
sources the warehouse no longer honors.
- `maintain volume [<objects>]` detects **freshness drift**: row counts that
collapsed, spiked, or went to zero. This is the "is the data still flowing
correctly?" axis, distinct from "did the shape change?".
- `maintain grain [<objects>]` detects **grain drift**: a key that now has
duplicates, a changed row-per-entity cardinality, or an increased join fanout.
It also re-verifies the grains your project *declares* (a model-level
`unique_combination_of_columns`), which measurement on its own can miss. Uses
aggregates, never raw rows.
Two findings come out of the uniqueness checks and the difference is the
baseline. `key_lost_uniqueness` is a key that was proven unique and is not any
more: something changed in the data. `declared_grain_not_unique` is a declared
combination that does not hold, and nothing changed at all: the project asserts
a grain the data never had, so the fix is to the declaration (widen it, dedup
upstream, or drop the claim) rather than to the data.
- `maintain semantic [<objects>]` detects **definition drift**: metric, measure,
dimension, or entity definitions that changed against the baseline; semantic
references that no longer resolve to a model or column; and categorical
dimensions whose set of values widened or narrowed underneath their metrics.
- `maintain reconcile [<class>]` proposes the dbt edits that bring the project
back in sync, as reviewable diffs. Optionally scope it to one class (`schema`,
`volume`, `grain`, or `semantic`).
The usual flow: `check` to triage, a focused detector to understand one axis in
depth, then `reconcile` to get the proposed fix.
## Per-axis cost: what is free and what scans
Detection is read-only, but read-only is not the same as free on a metered
connector (BigQuery, Snowflake, Databricks, Postgres, Redshift, ClickHouse).
The axes split:
- **Schema, volume, and the reference/definition half of semantic are free**
everywhere: they read metadata and the snapshot, and run immediately.
- **Grain and the dimension-cardinality half of semantic scan the warehouse**, so
on a metered connector they run the two-step handshake. The first call returns
`needs_confirmation` with an estimate in `cost.estimate` (and a per-table
breakdown). Surface it to the user in human units, get an explicit budget, and
re-issue the same command with `--confirm --budget <magnitude>` in the
paradigm's unit (bytes on BigQuery, warehouse-seconds on Snowflake and
Databricks, compute-seconds on Redshift, database-seconds on Postgres and
ClickHouse).
Never invent a budget the user did not agree to, and never retry with a
raised budget on an over-ceiling refusal without asking.
- **`check` is two-phase on a metered connector**: the free axes complete
immediately and their findings ride along in the `needs_confirmation` envelope,
with one combined estimate for the scanning axes. Confirm to complete the sweep.
On DuckDB everything is free and local, so nothing prompts.
## Reconcile proposals are mechanical or advisory
Reconcile tags every proposal by `kind`, because the fix differs sharply by axis:
- **`mechanical`**: schema drift on a dex-scaffolded staging model re-scaffolds
the model from the drifted source. High-confidence, but still a reviewable diff:
read it for hand-written logic the scaffold cannot know about.
- **`advisory`**: grain, volume, and semantic drift are decisions, not auto-fixes
(dex cannot dedup your warehouse or decide whether a new `'refunded'` status
belongs in a metric). The proposal is the decision surfaced, at most backed by a
test edit that makes the break visible in builds. It declines that test where
the test would be wrong: if your model declares a composite grain covering the
column, no column-level `unique` is proposed on it, and the warning names the
combination so you can tell "re-baseline, this is still the grain" from
"something relied on that column alone".
When reconcile produces edits it stores them as a plan and prints a `plan_id`.
Apply them with `transform apply <plan-id>` (the one apply door): a human edit made
since detection surfaces as a conflict, never a silent overwrite.
## Guardrails (enforced in the engine, not here)
- Read-only against data. Schema, volume, and semantic references are computed from
metadata and the snapshot; grain and dimension-cardinality use aggregates only.
Raw rows and dimension values never cross the envelope.
- Propose, don't impose. Reconciliation is always a reviewable diff, applied
through `transform apply`. Human dbt edits are authoritative; on conflict the
engine surfaces the divergence and asks rather than overwriting.
- The dbt project is the source of truth; the `.dex/` snapshot is a non-canonical
fingerprint used only to detect change.
Scanned 8/30/2026
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!