Communitygithub.com

exmergo/dex

Dex est la boîte à outils d'ingénierie analytique native pour agents. Pointez-la vers votre entrepôt de données et votre projet dbt. Elle apprend le paysage, rédige vos transformations et vous dit exactement quoi corriger lorsque le schéma dérive. Conçue pour les ingénieurs analytiques et les ingénieurs de données qui souhaitent tirer le meilleur parti de leur agent de codage.

Qu'est-ce que dex ?

Dex est une compétence d'agent spécialisée dans l'ingénierie analytique, conçue pour interagir avec des entrepôts de données (BigQuery, Snowflake, Databricks, Redshift) et des bases DuckDB locales. Elle offre une suite de commandes en lecture seule (sous-commande `explore`) permettant d'analyser l'inventaire, de profiler les colonnes, d'inférer les relations, de générer des diagrammes ER, d'exécuter des requêtes SQL ad hoc, de clusteriser des données et d'interagir avec la couche sémantique dbt. L'agent pilote l'exécution via un wrapper Python (`uv run`), et les résultats sont renvoyés dans une enveloppe JSON structurée. La compétence gère automatiquement les connexions, les autorisations et les coûts (estimation et confirmation pour les connecteurs facturés). Elle est particulièrement utile pour les ingénieurs analytiques et de données qui souhaitent automatiser l'exploration, la documentation et la maintenance de leurs entrepôts de données, en gardant le contrôle et en respectant la sécurité des données (PII). Dex fonctionne sans état, en mettant en cache les profils et les relations dans un dossier `.dex/`, et peut être utilisé de manière itérative, en effectuant des mappages supplémentaires coûtant peu de ressources.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add exmergo/dex

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Explorer

Donnez du sens à un entrepôt de données ou à une base DuckDB locale comme le ferait un ingénieur d'analyse : classez ce qui est important, forez sélectivement et persistez une carte provisoire. C'est la compétence phare, entièrement en lecture seule. Elle intègre le profilage et l'inférence de relations en tant que capacités ; ce ne sont pas des compétences séparées.

Comment l'utiliser

Exécutez le moteur via le wrapper. Il imprime une seule enveloppe JSON nettoyée et rien d'autre ; lisez l'enveloppe et décidez de la prochaine étape.

uv run "${CLAUDE_SKILL_DIR}/scripts/run.py" <sous-commande> [drapeaux]

Sous-commandes, dans l'ordre habituel :

  1. connect test --path <fichier.duckdb> confirme une connexion en lecture seule et rapporte les capacités.
  2. explore inventory --rank renvoie un résumé classé des objets (compteurs et tailles, jamais de lignes).
  3. explore profile <objets> (séparés par des espaces ou des virgules) renvoie les profils de colonnes, les indicateurs PII enregistrés sous forme (colonne, catégorie, confiance) et jamais de valeurs d'exemple, plus les clés candidates, le grain probable et les avertissements sur la qualité des données (par exemple, un identifiant non unique qui générera des duplications lors des jointures). Un indicateur générique *_name voit sa confiance affinée par des preuves de forme de valeur issues du même scan, dans les deux sens : des valeurs en forme de personne le corroborent, un vocabulaire de référence fermé ou des étiquettes longues le déclassent en dessous du seuil de blocage du pare-feu, et l'absence de preuve ne change rien (l'indicateur lui-même n'est jamais supprimé). Les comptes distincts sont approximatifs pour des raisons d'échelle, mais toute colonne qui semble unique dans le bruit d'approximation est promue vers un COUNT(DISTINCT) exact (distinct_count_exact: true), de sorte que les verdicts d'unicité et de grain reposent sur une preuve ; un préfixe ~ dans un avertissement marque un compte qui est encore approximatif. Un objet demandé dont le profil mis en cache est encore frais (même connecteur, schéma inchangé, dans profile_freshness_hours, par défaut 24) est servi depuis le cache (cache_hit_count) au lieu d'être scanné à nouveau, donc profiler une table que map vient d'écrire ne coûte rien à dépenser ; passez --refresh pour forcer un nouveau scan lorsque la source a changé d'une manière que la vérification de métadonnées gratuite ne peut pas voir.
  4. explore relationships renvoie les jointures inférées et déclarées avec des confiances, plus des notes expliquant ce que l'inférence a examiné (une liste vide est donc significative). Ajoutez --verify pour mesurer chaque jointure inférée avec une sonde de chevauchement agrégée (fraction orpheline, confiance ajustée).
  5. explore map écrit ou met à jour le cache .dex/ et imprime un résumé (--verify fonctionne aussi ici). Au-delà de 50 objets, il ne profile que les 25 premiers par rang et le mentionne dans les notes (avec skipped_count) ; passez --full pour tout profiler. Lors d'un remappage, les objets ignorés lors de cette exécution conservent leurs profils antérieurs (carried_forward_count), chacun estampillé avec son propre profiled_at afin que l'obsolescence soit visible au lieu que les détails des colonnes disparaissent silencieusement. Un objet sélectionné dont le profil mis en cache est encore frais (même connecteur, schéma inchangé, profilé dans profile_freshness_hours, par défaut 24) est réutilisé sans nouveau scan (cache_hit_count), donc les réexécutions ne coûtent rien à dépenser ; passez --refresh pour forcer un reprofilage complet lorsque la source a changé d'une manière que la vérification de métadonnées gratuite ne peut pas voir (par exemple, les lignes ont changé mais le schéma n'a pas changé). explore relationships et la commande autonome explore profile réutilisent les profils frais de la même manière.
  6. explore diagram [--full] rend la carte mise en cache sous forme de diagramme ER Mermaid dans data.mermaid. Gratuit et sans connexion (il lit le cache, jamais l'entrepôt), il est donc sûr de le réexécuter tout en façonnant l'image. Reproduisez la chaîne textuellement dans un bloc ```mermaid afin que l'humain puisse la voir, et écrivez-la dans un fichier .mmd ou un fichier Markdown lorsqu'il en veut un sur le disque : le moteur n'écrit délibérément aucun fichier. Ne redessinez ni ne "réarrangez" jamais le diagramme à la main. Les glyphes sont des affirmations que le moteur a dérivées de preuves, et une cardinalité plausible que vous avez fournie est exactement la sur-affirmation que cette commande vise à empêcher : les jointures déclarées sont en trait plein, les inférées en pointillé, et une inférence non vérifiée ne dit jamais "exactement un". Lisez les notes avant de le présenter, car elles indiquent tout objet ou colonne qui a été omis ; --full élargit par défaut (objets profilés et joints avec leurs colonnes de grain, clé, jointure et PII) à tout ce qui est éligible.
  7. explore query "<SELECT ...>" ["<SELECT ...>" ...] répond aux questions ad hoc que les commandes fixes ne couvrent pas : vous écrivez le SQL, le pare-feu de requêtes du moteur le refuse ou le limite. Passez une déclaration par argument, ou --sql-file <chemin> pour une liste plus longue, et posez toute une chaîne de questions en un seul appel plutôt qu'un appel chacune ; chaque déclaration est jugée et répondue individuellement, donc un refus sur l'une ne vous coûte pas les autres, et data.results contient une entrée par déclaration. Une table que vous n'avez pas profilée, y compris un modèle que vous venez de construire, est profilée pour vous, puis la déclaration s'exécute, donc sonder quelque chose de nouveau est un seul appel plutôt que trois ; l'enveloppe dit ce qu'elle a profilé, et sur un connecteur facturé, ce profil est inclus dans la même confirmation que les déclarations. Les résultats sont renvoyés ligne par ligne et plafonnés ; un refus nomme la colonne incriminée et la correction, donc une seule réécriture suffit. Lisez ${CLAUDE_SKILL_DIR}/references/probe-playbook.md avant d'écrire une sonde : il mappe les questions courantes aux formes de sonde efficaces.
  8. explore cluster <objet> [--features a,b,c] [-k N] exécute k-means sur un échantillon limité des colonnes numériques de l'objet et renvoie la structure des segments : tailles et fractions par cluster, centroïdes (chaque coordonnée est la moyenne de cette caractéristique pour un cluster, une agrégation), le score de silhouette, et, lorsque -k est omis, le k qu'il a choisi plus le balayage de silhouette sur lequel il a choisi. Nécessite le cache .dex/ (exécutez map/profile d'abord) afin que les caractéristiques puissent être automatiquement sélectionnées parmi les colonnes numériques profilées, non PII, non clés ; passez --features pour les choisir vous-même (nommer une colonne PII, ou une clé, l'active délibérément, et seule sa moyenne est jamais rapportée). Une clé n'est jamais une caractéristique : sa moyenne est dénuée de sens, et une table de faits est principalement composée de clés plus une poignée de mesures, donc les clusteriser sur elles ne fait que partitionner des plages de substituts. Les clés sont les colonnes uniques, les colonnes qui joignent (à partir des jointures que map a inférées), et les colonnes nommées comme telles ; préférez map à un simple profile ici, car sans jointures inférées, une clé étrangère n'est détectée que si son nom le révèle. Les notes nomment chaque colonne exclue, alors vérifiez-les avant de faire confiance à un résultat. Deux choses que la silhouette seule ne vous dira pas, que les notes feront toutes deux. Un cluster contenant moins de 1% de l'échantillon est une poche aberrante, pas un segment, et il pousse le score vers le haut précisément parce qu'il est si éloigné : signalez-le comme détection d'aberrations, ou réexécutez avec -k pour diviser la masse. Et sur les connecteurs qui ne peuvent pas ensemencer un échantillon, le tirage change à chaque exécution, donc deux exécutions peuvent ne pas être d'accord sur k ; le sample_repeatable de l'enveloppe indique dans quel cas vous vous trouvez, et comparer des exécutions sur différents tirages est dénué de sens. Seules les agrégations traversent la frontière : les lignes d'échantillon sont clusterisées en cours de traitement et n'entrent jamais dans le contexte. Sur un connecteur facturé, il nécessite la même poignée de main de coût que les commandes de scan ci-dessous (seules les colonnes de caractéristiques sont scannées, et une clause d'échantillonnage adaptée au dialecte lit une fraction), alors présentez l'estimation et obtenez d'abord un budget. Nécessite l'extension [cluster] (scikit-learn) ; le wrapper l'installe automatiquement pour cette sous-commande.
  9. explore semantic list et explore semantic query atteignent la couche sémantique dbt (métriques, dimensions, entités). list est la découverte : quelles métriques existent et par quelles dimensions chacune peut être regroupée. query prend une métrique positionnelle après le mode explicite (avec --metric conservé pour compatibilité) et un --group-by <entité__dim> (plus --where, --grain et --limit optionnels) et renvoie les valeurs d'une métrique sous forme de résultat colonnaire plafonné. Les drapeaux de nom prennent une liste séparée par des virgules ou un drapeau répété (--group-by a,b équivaut à --group-by a --group-by b) ; --where n'est jamais divisé. Deux backends répondent à ces commandes, choisis par semantic.backend dans .dex/config.yml et surchargeables avec --local / --api. --local rend le SQL avec MetricFlow et l'exécute via le propre connecteur et la poignée de main de coût de dex, donc le coût est présenté avant la dépense (nécessite un projet dbt et, pour query, l'extension [semantic] ; list est une vue de lecture de manifeste qui n'a besoin d'aucune des deux). --api envoie la requête à un déploiement dbt Cloud hébergé (nécessite seulement un hôte, un identifiant d'environnement et un DBT_SL_TOKEN, plus l'extension [semantic-api], aucun projet local). Le backend hébergé est le seul endroit où le garde-fou des coûts ne peut pas s'appliquer : dbt Cloud s'exécute côté serveur, donc le résultat porte un avertissement explicite indiquant que la dépense y est régie, et non par dex, et aucun --confirm n'est demandé. Dans les deux cas, une dimension groupée ou filtrée en forme de PII (par exemple user__email) est refusée avant l'exécution de la requête. Ceci interroge la couche ; la créer est le travail de transform.

Règles d'engagement pour query : préférez les commandes fixes lorsqu'elles répondent à la question ; une sonde répond à une question ; regroupez les mesures connexes en une seule requête plutôt que d'en émettre plusieurs ; les agrégations sur des colonnes marquées PII doivent être des mesures (COUNT, APPROX_COUNT_DISTINCT, AVG(LENGTH(...))), jamais porteuses de valeurs (MIN, ANY_VALUE, STRING_AGG). La clause FROM peut déchiqueter les colonnes JSON et tableaux dans l'idiome natif du connecteur, ce qui est la bonne façon d'explorer des données sans schéma (par exemple "quelles clés apparaissent dans chaque ligne de cette colonne JSON") : BigQuery t, UNNEST(JSON_KEYS(doc)) AS k, Snowflake t, LATERAL FLATTEN(input => doc) f, Databricks t LATERAL VIEW EXPLODE(json_object_keys(doc)) x AS k, Postgres t, jsonb_object_keys(doc) AS k, Redshift t, UNPIVOT t.doc AS v AT k, DuckDB t, UNNEST(json_keys(doc)) AS u(k). La valeur déchiquetée doit provenir d'une colonne d'une table de la requête (seule, ou via une fonction JSON/tableau) ; déchiqueter une sous-requête, une autre table, un littéral ou un générateur est refusé, et les sorties du déchiquetage héritent des indicateurs PII de la colonne source. Une colonne dont l'indicateur a été déclassé en dessous du seuil de blocage de 0,5 se projette normalement, avec un avertissement dans l'enveloppe la nommant ; traitez l'avertissement comme une information pour l'utilisateur, pas une erreur à corriger. Si l'utilisateur dit qu'une colonne refusée n'est pas une donnée personnelle, recommandez une entrée pii_overrides dans .dex/config.yml (colonne entièrement qualifiée, raison optionnelle) : cela débloque l'interrogation immédiatement, survit aux re-profils, et est révisable dans git. Ne modifiez jamais .dex/cache.json à la main pour effacer un indicateur. Ne retombez jamais sur du Python brut ou une CLI de base de données pour exécuter du SQL ; le chemin du pare-feu est le seul autorisé.

Cibles cloud et bases de données (BigQuery, Snowflake, Databricks, Postgres, Redshift)

Un entrepôt ou une base de données distante remplace --path par une configuration de connecteur. Commencez par connect test --connector <nom> (ou définissez connector: plus le bloc correspondant dans .dex/config.yml : bigquery: avec project et une liste autorisée de datasets, snowflake: avec le warehouse épinglé et une liste autorisée de databases, databricks: avec le SQL warehouse épinglé et une liste autorisée de catalogs, postgres: avec une liste autorisée de schemas, redshift: avec le workgroup Serverless et une liste autorisée de schemas). Les identifiants sont découverts, jamais demandés : si l'enveloppe signale des identifiants manquants ou expirés, relayez la correction qu'elle nomme (pour BigQuery gcloud auth application-default login ; pour Snowflake une entrée connections.toml ou les variables d'environnement SNOWFLAKE_* ; pour Databricks databricks auth login ou les variables d'environnement DATABRICKS_* ; pour Postgres DATABASE_URL, les variables d'environnement PG*, ou une entrée pg_service.conf ; pour Redshift la chaîne d'identifiants AWS (aws configure, les variables d'environnement AWS_*) ou les variables d'environnement REDSHIFT_*) et ne demandez jamais à l'utilisateur de coller une clé, un jeton ou un mot de passe.

Sur un connecteur facturé, les commandes de scan (profile, map, relationships, query) exécutent une poignée de main en deux étapes. Le premier appel renvoie needs_confirmation avec une estimation dans cost.estimate (et une ventilation par table le cas échéant) : un chiffre exact en octets d'exécution à sec sur BigQuery, une estimation heuristique étiquetée estimate_quality: "heuristic" en secondes d'entrepôt sur Snowflake (crédits à côté), un plancher étiqueté estimate_quality: "low" en secondes d'entrepôt sur Databricks (DBUs à côté ; il s'affine à l'intérieur du budget confirmé), une estimation heuristique en secondes de calcul sur Redshift (heures RPU à côté ; les estimations Serverless incluent le minimum de 60 secondes de réveil une fois), et en secondes de base de données sur Postgres (pas de dollars ; la quantité surveillée est la charge sur la base de données opérationnelle). Présentez l'estimation à l'utilisateur en unités humaines, obtenez un budget explicite de sa part, et réémettez la même commande avec --confirm et --budget <magnitude> dans l'unité du paradigme. N'inventez jamais un budget que l'utilisateur n'a pas accepté, et ne réessayez jamais avec un budget augmenté en cas de refus pour dépassement de plafond sans demander. Les métadonnées sont gratuites (connect test, inventory s'exécutent immédiatement), et les enveloppes OK rapportent la dépense réelle sous data.spend.

Sur BigQuery, une estimation de profilage contient un plancher de 10 Mo par table pour chaque requête d'escalade qu'un profil peut encore émettre après son scan agrégé, donc sur un entrepôt contenant de nombreuses petites tables, la majeure partie du nombre peut être une réserve pour un travail qui ne se produit jamais. La poignée de main et le refus pour dépassement de plafond rapportent tous deux cette division (reserved_bytes et reserved_queries, et dans le texte). Transmettez-la lorsque vous présentez l'estimation : qu'un nombre soit un scan ou une réserve change si l'augmentation du budget achète du travail ou de la marge.

Lorsqu'une estimation est plus grande que le travail ne le mérite, réduisez la portée plutôt que d'augmenter le budget. --scope (répétable) limite une commande à une partie de la liste autorisée de sources configurée, dans le propre vocabulaire du connecteur : un dataset sur BigQuery, un schema ou database.schema sur Snowflake, un catalog.schema sur Databricks, un schéma sur Postgres ou Redshift. C'est gratuit à résoudre, cela ne peut que na…

[la suite a été tronquée pour la traduction]

Skills associés