/transform
Use this to author and change a dbt project or a semantic layer: bootstrap a project in a repo that has none (`transform init`), write or refactor model SQL from staging to marts, add tests and docs in schema.yml, manage dependencies, and define or update the semantic layer,
$ npx -y skills add exmergo/dex --skill transform --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/transform
Context preview
The summary Claude sees to decide when to auto-load this skill.
Use this to author and change a dbt project or a semantic layer: bootstrap a project in a repo that has none (`transform init`), write or refactor model SQL from staging to marts, add tests and docs in schema.yml, manage dependencies, and define or update the semantic layer,
SKILL.md
transform.SKILL.mdname: transform
description: 'Use this to author and change a dbt project or a semantic layer: bootstrap a project in a repo that has none (`transform init`), write or refactor model SQL from staging to marts, add tests and docs in schema.yml, manage dependencies, and define or update the semantic layer, whether that is dbt semantic models (MetricFlow: entities, dimensions, measures, metrics) or native Apache Ossie documents in a repo with no dbt project at all. Reach for this rather than editing model files by hand whenever the change spans more than one file or has to stay consistent with the rest of the project: it validates the edit against the real schema before writing, returns the change as a reviewable diff with a plan id, and catches the class of error that only surfaces at `dbt run`, such as wrong column names, broken refs, or a materialization that fights the project config. On a large project that check is worth more than the round trip costs. It applies to bug-fix tickets too: "this model returns wrong numbers, fix it" is a transform task. Trigger it for requests like "set up a dbt project in this repo", "build a staging model for this table", "refactor this model", "add tests to this model", "create a mart for X", "define a revenue metric", "add a dimension to this entity", "add a metric to my Ossie semantic model", or "update semantics/commerce.ossie.yaml". Any warehouse build is dev-target only, gated, and cost-surfaced first. If you do not yet know the source tables'' columns or grain, use explore first, then come back. To reconcile a project that has drifted out of sync with the warehouse, use maintain.'
Transform
Author and refactor the dbt project: both the SQL transformations (staging to marts, tests, docs) and the semantic layer on top (entities, dimensions, measures, metrics). Both are the same job, writing reviewable diffs to the dbt project, which is the source of truth. This is the building half of the loop. It writes only to the repo, as reviewable diffs, and runs against a dev target only.
How to drive it
uv run --no-project --script "${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 editing the dbt project by hand instead: the validation, the diffs, and the dev-target gating live in the engine, so any other path is unguarded.
The first command in a fresh environment installs the engine, so it can take tens of seconds where later ones take well under a second. `--warm` pays that install up front and exits without running anything:
uv run --no-project --script "${CLAUDE_SKILL_DIR}/scripts/run.py" --warmOffer it once at setup. It is not something to run before an ordinary command.
You author the dbt file content; the engine validates it, computes the diffs, and stores the proposal as a plan. Hand content over with `--edits-file <path>` (or `-` to read stdin), a JSON payload:
{"edits": [
{"path": "models/staging/stg_orders.sql", "kind": "model_sql", "content": "..."},
{"path": "models/staging/stg_orders.yml", "kind": "schema_yml", "content": "..."},
{"path": "snapshots/snap_orders.sql", "kind": "snapshot_sql", "content": "..."},
{"path": "seeds/country_vat.csv", "kind": "seed_csv", "content": "..."},
{"path": "tests/assert_totals_reconcile.sql", "kind": "test_sql", "content": "..."},
{"path": "analyses/email_skew.sql", "kind": "analysis_sql", "content": "..."},
{"path": "models/marts/dim_orders.sql", "kind": "model_sql", "op": "delete"}
]}`kind` is `model_sql`, `schema_yml`, `semantic_yml` (optional on `semantic define|update|plan`, which imply it), `macro_sql` (a macro file under the project's macro paths), `snapshot_sql` (a snapshot under the snapshot paths), `seed_csv` (a seed's CSV under the seed paths), `test_sql` (a singular test or a generic test definition under the test paths), `analysis_sql` (SQL dbt compiles but never runs, under the analysis paths), `packages_yml`, `project_yml` (the project-root `dbt_project.yml`), or `profiles_yml` (the project-root `profiles.yml`). Model SQL must be a single read-only SELECT once its jinja is stripped; semantic YAML is validated against MetricFlow's schemas, cross-reference-checked, and (when dbt is available) parsed by dbt itself before the plan is accepted; a macro file must hold only macro definitions and jinja comments. A snapshot must hold exactly one `{% snapshot %}` block whose `config()` names a `unique_key` and a `strategy` of `timestamp` (with `updated_at`) or `check` (with `check_cols`), and whose body is a single read-only SELECT. A seed must parse as CSV with a named, duplicate-free header and one field per column on every row, and stays under 5,000 data rows and 1 MiB (past that it is data rather than a lookup: load it into the warehouse and `source()` it). A `test_sql` file is read to decide which of the two shapes sharing the test paths it is: one holding `{% test %}` blocks is a generic test definition and must hold only those and jinja comments, balanced; anything else is a singular test and must be a single read-only SELECT. A singular test that names no `ref()` or `source()` is warned about, not refused, because it runs against nothing and passes unconditionally. An analysis must be a single read-only SELECT too, even though dbt only compiles it. `project_yml` must keep a `name`; `profiles_yml` must reference every secret via `{{ env_var('NAME') }}` (a literal credential is refused so none reaches the diff). Config kinds, snapshots and seeds are all parsed by dbt at plan time.
Each kind is confined to its own family of paths, and filing one in the wrong family is refused naming both fixes. `schema_yml` is the ex
Read more
name: transform description: 'Use this to author and change a dbt project or a semantic layer: bootstrap a project in a repo that has none (`transform init`), write or refactor model SQL from staging to marts, add tests and docs in schema.yml, manage dependencies, and define or update the semantic layer, whether that is dbt semantic models (MetricFlow: entities, dimensions, measures, metrics) or native Apache Ossie documents in a repo with no dbt project at all. Reach for this rather than editing model files by hand whenever the change spans more than one file or has to stay consistent with the rest of the project: it validates the edit against the real schema before writing, returns the change as a reviewable diff with a plan id, and catches the class of error that only surfaces at `dbt run`, such as wrong column names, broken refs, or a materialization that fights the project config. On a large project that check is worth more than the round trip costs. It applies to bug-fix tickets too: "this model returns wrong numbers, fix it" is a transform task. Trigger it for requests like "set up a dbt project in this repo", "build a staging model for this table", "refactor this model", "add tests to this model", "create a mart for X", "define a revenue metric", "add a dimension to this entity", "add a metric to my Ossie semantic model", or "update semantics/commerce.ossie.yaml". Any warehouse build is dev-target only, gated, and cost-surfaced first. If you do not yet know the source tables'' columns or grain, use explore first, then come back. To reconcile a project that has drifted out of sync with the warehouse, use maintain.'
Transform
Author and refactor the dbt project: both the SQL transformations (staging to marts, tests, docs) and the semantic layer on top (entities, dimensions, measures, metrics). Both are the same job, writing reviewable diffs to the dbt project, which is the source of truth. This is the building half of the loop. It writes only to the repo, as reviewable diffs, and runs against a dev target only.
How to drive it
uv run --no-project --script "${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 editing the dbt project by hand instead: the validation, the diffs, and the dev-target gating live in the engine, so any other path is unguarded.
The first command in a fresh environment installs the engine, so it can take tens of seconds where later ones take well under a second. `--warm` pays that install up front and exits without running anything:
uv run --no-project --script "${CLAUDE_SKILL_DIR}/scripts/run.py" --warmOffer it once at setup. It is not something to run before an ordinary command.
You author the dbt file content; the engine validates it, computes the diffs, and stores the proposal as a plan. Hand content over with `--edits-file <path>` (or `-` to read stdin), a JSON payload:
{"edits": [
{"path": "models/staging/stg_orders.sql", "kind": "model_sql", "content": "..."},
{"path": "models/staging/stg_orders.yml", "kind": "schema_yml", "content": "..."},
{"path": "snapshots/snap_orders.sql", "kind": "snapshot_sql", "content": "..."},
{"path": "seeds/country_vat.csv", "kind": "seed_csv", "content": "..."},
{"path": "tests/assert_totals_reconcile.sql", "kind": "test_sql", "content": "..."},
{"path": "analyses/email_skew.sql", "kind": "analysis_sql", "content": "..."},
{"path": "models/marts/dim_orders.sql", "kind": "model_sql", "op": "delete"}
]}`kind` is `model_sql`, `schema_yml`, `semantic_yml` (optional on `semantic define|update|plan`, which imply it), `macro_sql` (a macro file under the project's macro paths), `snapshot_sql` (a snapshot under the snapshot paths), `seed_csv` (a seed's CSV under the seed paths), `test_sql` (a singular test or a generic test definition under the test paths), `analysis_sql` (SQL dbt compiles but never runs, under the analysis paths), `packages_yml`, `project_yml` (the project-root `dbt_project.yml`), or `profiles_yml` (the project-root `profiles.yml`). Model SQL must be a single read-only SELECT once its jinja is stripped; semantic YAML is validated against MetricFlow's schemas, cross-reference-checked, and (when dbt is available) parsed by dbt itself before the plan is accepted; a macro file must hold only macro definitions and jinja comments. A snapshot must hold exactly one `{% snapshot %}` block whose `config()` names a `unique_key` and a `strategy` of `timestamp` (with `updated_at`) or `check` (with `check_cols`), and whose body is a single read-only SELECT. A seed must parse as CSV with a named, duplicate-free header and one field per column on every row, and stays under 5,000 data rows and 1 MiB (past that it is data rather than a lookup: load it into the warehouse and `source()` it). A `test_sql` file is read to decide which of the two shapes sharing the test paths it is: one holding `{% test %}` blocks is a generic test definition and must hold only those and jinja comments, balanced; anything else is a singular test and must be a single read-only SELECT. A singular test that names no `ref()` or `source()` is warned about, not refused, because it runs against nothing and passes unconditionally. An analysis must be a single read-only SELECT too, even though dbt only compiles it. `project_yml` must keep a `name`; `profiles_yml` must reference every secret via `{{ env_var('NAME') }}` (a literal credential is refused so none reaches the diff). Config kinds, snapshots and seeds are all parsed by dbt at plan time.
Each kind is confined to its own family of paths, and filing one in the wrong family is refused naming both fixes. `schema_yml` is the ex
Dex is the agent-native analytics engineering toolkit. Point it at your warehouse and your dbt project. It learns the landscape, authors your transformations, and tells you exactly what to fix when the schema drifts. Built for analytics engineers and data engineers who want more out of their coding agent.
Repo: exmergo/dex

