Borrowing it
Nothing to install: this file belongs to cturkieh/france-data-mcp. Take a copy, put it at the same path in your own repository, and replace the rules that are about this project with yours.
curl -O https://raw.githubusercontent.com/cturkieh/france-data-mcp/main/.claude/skills/db-gotchas/SKILL.mdgit clone --depth 1 https://github.com/cturkieh/france-data-mcpWrote this? Show the measurements
A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.
[](https://agentmods.dev/skills/cturkieh/france-data-mcp/db-gotchas)<a href="https://agentmods.dev/skills/cturkieh/france-data-mcp/db-gotchas"><img src="https://agentmods.dev/badge/skills/cturkieh/france-data-mcp/db-gotchas/github.svg" alt="Measured on agentmods" height="20"></a>Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.
<a href="https://agentmods.dev/skills/cturkieh/france-data-mcp/db-gotchas"><img src="https://agentmods.dev/badge/skills/cturkieh/france-data-mcp/db-gotchas.svg" alt="Reviewed on agentmods" width="80" height="20"></a>What it costs to keep this loaded
Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5.1 | $0.00098 | $0.05915 |
| Opus 5 | $0.00049 | $0.02958 |
| Sonnet 5 | $0.00020 | $0.01183 |
| Haiku 4.5 | $0.00010 | $0.00592 |
Grade A, and why
db-gotchas scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 5d ago.
A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.
Nothing flagged
None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.
How it starts
The opening of the file, as written. The whole thing — 30 lines — stays where its author put it; the contents beside it link to each section on GitHub.
Top gotchas DB (post-mortems prouvés prod)
Déplacé verbatim depuis
CLAUDE.mdle 2026-09-06 (budget). Source de vérité pour ce périmètre ;CLAUDE.mdne garde que les règles de tête.
- PostgREST sérialise
NUMERIC/BIGINTen STRING JSON (préservation de précision), JAMAIS en number — toute valeur de ce type lue d'une RPC arrive enstringcôté supabase-js MALGRÉ un type TSnumber | null. Trois pièges prouvés (rpps-db.ts:322,iris-db.ts:161,iris-profil.ts) : (1)acc += row.numericFieldconcatène (0 + "100"→"0100") au lieu d'additionner → agrégat faux servi ensuccess; (2) un champ exposé brut viole son contratnumber | null(string servie au LLM, arithmétique aval cassée) ; (3) une valeur source corrompue ("N/A", secret stat"s") →Number()=NaN. RÈGLE : coercer AU BOUNDARY DB toute valeur NUMERIC/BIGINT avant arithmétique OU exposition —Number(v)+Number.isFinite; pour une Σ, helpern(v)(null→0 neutre) ; pour un champ exposé où null est légitime, helpernumOrNull(v, field)(null→null, NaN→null +console.warngrep-able — dégradation source jamais muette, cf. règle error handling). Le piège est SILENCIEUX en test si les mocks injectent desnumber(la prod, elle, donne des string) → tout test d'agrégation/exposition d'un NUMERIC RPC DOIT avoir un cas enstring(cf.iris-profil.test.ts"réalisme PostgREST"). Alternative SQL-side (::float8dans la RPC → PostgREST renvoie un number) écartée : romprait la précision NUMERIC et la cohérence avec la doctrine de coercition TS du projet. ST_AsGeoJSON(geom)::jsonbobligatoire en sortie RPC (sinon hex EWKB).- Colonne calculée
geog GEOGRAPHY ... STORED+ index GIST (cast runtime tue le plan). - PostgREST proxy timeout 60s ≠ Postgres
statement_timeout→ batch UPDATEs par 10K. LANGUAGE plpgsql + EXECUTE format(... %L::CHAR(3) ...)pour RPC filtrant une colonneCHAR(n)indexée par un paramTEXT. JamaisWHERE col_char = p_text: Postgres caste la COLONNE indexée en text ((col)::text = $1) → index inutilisable → fallback seq/mauvais index (post-mortem V0.10.1, 254 ms→5,5 ms / 265 786→90 buffers). Interpoler le param en literal typé via%L::CHAR(n), garder les autres params enUSING $n.- Matview
FROM <table swappée par ingest_atomic_swap>+ post-swap REFRESH-only = bombe OID (post-mortem 2026-05-18, prouvé prod). Une matview suit l'OID de sa table source, pas son nom.ingest_atomic_swapfait une rotation par RENAME (<t>→<t>_previous→<t>_previous_OLD→DROP CASCADE). Un post-swap qui se contente deREFRESH MATERIALIZED VIEW(RPCingest_refresh_matview) ⇒ 1er cron réussi : la matview reste collée à l'ancienne table (désync SILENCIEUSE, statussuccess, données périmées servies) ; 2e cron :DROP <t>_previous_OLD CASCADEla DÉTRUIT → tools42P01avalés enpartial. Fix RPPS :ingest_rebuild_rpps_matviewsRECONSTRUIT post-swap (DROP MV <m>+CREATE MV <m>_rebuild AS <SELECT canonique VERBATIM> FROM <t>résolu PAR NOM + index +RENAMEatomique, 1 transaction PL/pgSQL ; échec transitoire→partialsans throw car rollback préserve l'ancienne, structurel→throwfailed+exit).scripts/ingest/rpps-matview-rebuild.test.tsgarde l'invariant + la parité DDL anti-drift. Ameli avait le MÊME défaut — CORRIGÉ 2026-05-19 paringest_rebuild_ameli_matviews(migration20260519T200000, réplication 1:1 du patron RPPS ci-dessus ;refreshAmeliMatviews→rebuildAmeliMatviews, garde-fouameli-matview-rebuild.test.ts). Le DROP sans CASCADE deameli_nomenclature_statsest prouvé prod malgré les 2 RPCLANGUAGE sqlqui la référencent (fonctions sql à corps$$= pas de dépendance catalogue bloquante ; CASCADE proscrit, droperait les RPC). Dette P1 close :shortCircuitIfSameChecksumpeut désormais être optimisé sans risque. Plus AUCUN script ingest n'utiliseingest_refresh_matview(RPPS + Ameli en rebuild) — la whitelistingest_refresh_matviewreste pour toute FUTURE matview refresh-only légitime (non FROM table swappée) ;staging-parity.test.tsgarde la protection whitelist générique, les invariants rebuild sont gardés par{rpps,ameli}-matview-rebuild.test.ts. - Parité index prod↔
ingest_create_*_staging: tout index sur la table prod DOIT être mirroré dans la staging-create (sinon perdu SILENCIEUSEMENT au swap → re-régression 57014).staging-parity.test.tsgarde-fou (set prod vivant = creates − drops,DROP INDEXhonoré PAR NOM, regex de drop ancrée sur;sinon une prose de commentaire « drop index X » dé-tracke un index vivant = faux négatif). Recréeringest_create_*_staging= recopie VERBATIM de la dernière def (PostgreSQL n'a pas d'héritage de corps de fonction) ; patcher « prod − N lignes » réintroduit silencieusement un objet retiré par une migration ultérieure. - RPC d'ingestion longue via PostgREST = budget
statement_timeout8s hérité si la fonction n'a pas son propreSET; + bulk COPY sansANALYZE= plan dégradé (post-mortem 2026-05-18, RÉFUTE l'hypothèse « index BAN » ci-dessous, prouvé prod run #26046475566 +pg_roles+EXPLAIN ANALYZE). Supabase :service_rolen'a AUCUNstatement_timeout(rolconfigNULL) → un appel supabase-js clé SERVICE_ROLE → PostgREST hérite dustatement_timeoutdeauthenticator= 8 s (anon3s /authenticated8s /postgrescap 2min). Toute RPC d'ingestion longue DOIT porter unSET statement_timeoutAU NIVEAU FONCTION (best practice Supabase ; valeur < 60 s = cap passerelle PostgREST, sinon timeout passerelle opaque au lieu d'un 57014 propre). De plus,ingest_apply_*_finess_enrichment_batchrequêterpps_stagingjuste après un bulk COPY (~2,24 M lignes, table fraîchementCREATE) : sansANALYZEle planner n'a aucune statistique → plan dégradé sur le 1er batch → >8 s → 57014 déterministe envalidate, avant le swap (données intactes, cron cassé « tout seul »). Fix RPPS C :SET statement_timeout='55s'sur l'enrichment ET suringest_analyze_rpps_staging, cette dernière (ANALYZE rpps_staging) appelée post-COPY/pré-enrichment parrpps.ts(échec →IngestErrorLOUD). Garde-fouscripts/ingest/enrichment-statement-timeout.test.ts. Vérifier le budget réel :SELECT rolname, rolconfig FROM pg_roles. - Index fonctionnel Unicode-lourd dans
ingest_create_*_staging= AGGRAVANT du run (INSERT ralenti), PAS la cause du 57014 enrichment (correctif d'un post-mortem erroné — la prod a réfuté l'inférence). Un index fonctionnel Unicode (rpps_address_key_for_index) avec prédicat partiel sur des colonnes que l'UPDATE de masse modifie alourdit la maintenance d'index pendant le COPY/UPDATE (run ~57 min) — mais le vrai déclencheur du 57014 était le budget 8 s hérité + l'absence d'ANALYZE(gotcha ci-dessus), pas cet index : son retrait seul (fix A) n'a PAS corrigé le timeout (run #26046475566). Garder néanmoins la règle d'hygiène : un index BAN/Unicode lourd doit vivre dans un step DÉDIÉ post-enrichment, JAMAIS dansingest_create_*_staging(évite de re-rallonger le run). Leçon transverse : prouver une cause-racine par la prod avant de coder le fix ; une inférence passée en /review P1+P2 reste une inférence. ban_join: pose BAN cache→staging = jumeaufiness_joinMAIS piloté CURSEUR KEYSET (p_after), JAMAIS sentinelle ; plus AUCUN build d'index lourd ni géocodage API dans le cron (refonte 2026-05-19, prouvée prod ; design+post-mortem consolidédocs/plans/ban-join.md; SUPERSEDE la refonte 2026-05-18 ci-dessous). Le cachegeocoded_addressesétant rempli (hors cron, parban-backfill.mjs), il devient « une table à joindre » comme FINESS :ingest_apply_rpps_ban_join_batch(p_after, p_limit)(migration20260519T180000,SET statement_timeout='55s',RETURNS TABLE(last_id, applied)) fait unUPDATE rpps_staging ⟕ geocoded_addresses ON g.address_key = rpps_address_key_for_index(...), lot bornéWHERE id > p_after ORDER BY id LIMIT p_limit. Pourquoi keyset et NON sentinelle (prouvé prod, EXPLAIN ANALYZE transaction ROLLBACK) : la sentinelle façon FINESS re-scanne le préfixe déjà traité → quadratique → 57014 en fin de parcours (proxyOFFSET 1.2M> 120 s, RÉFUTÉ) ; le keyset démarre où le lot précédent s'est arrêté → ~4,8 s/lot CONSTANT début↔fin (mesuré à vide 2026-05 ; en conditions réelles de cron ~16 s/lot ≈ 33-35 min pour ~1,31 M éligibles, prouvé runs 07→09/2026 — c'est le poste dominant du cron, d'oùtimeout-minutes: 120; un run tué par le budget =cancelled≠failure, alerte dédiée dansingest-rpps.yml). Jointuregeocoded_addresses_pkey= nested-loop indexé optimal 0,18 ms/ligne → aucun index fonctionnel lourd surrpps_stagingrequis (≠ l'ancieningest_build_rpps_staging_ban_indexes, cause structurelle du blocage :CREATE INDEXmulti-min via PostgREST = impossible, cap passerelle Supabase 60 s en dur). Séquence load-bearingscripts/ingest/rpps.ts: analyze (5a) → enrichment FINESS (5b) →rpps_count_ban_eligible_rows+runKeysetRpc(ingest_apply_rpps_ban_join_batch)(5c, fail-loud + sentinelle cohérence « 0 posé/cache non vide → throw ») → 5c-bis repli FINESS keysetingest_apply_rpps_finess_centroid_fallback_batch(best-effort, même commune, migration20260905T140000; retire ~5 900 clés (14 %) du périmètre BAN, dont ~3 700 rejetées jamais re-tentées —ban_eligible_distinctchute d'autant, ce n'est pas un progrès BAN) → swap → rebuildMatviews. Helper génériquerunKeysetRpc(shared.ts, garde de non-progression +withTimeoutanti-hang). Expressionrpps_address_key_for_index(...)+ prédicatgeom_source='commune_centroid' OR (geom IS NULL AND adresse IS NOT NULL)byte-identiques sur 6 sites (count / skip-scan ×2 / index staging ×2 / ban_join), gardés parban-eligibility-predicate-parity(6 sites) +ban-eligibility-index-expr-parity(ban_join via WRAPPER, jamais le jumeau nu) +enrichment-statement-timeout(ban_join ≤55 s).runBanGeocodeStepSUPPRIMÉ (et ses ~8 constantes/imports BAN). Dette tracée :ingest_build_rpps_staging_ban_indexesconservée en base mais PLUS câblée par le cron ;ban-backfill.mjs(inchangé, hors scope) dépend encore des index BAN présents surrpps— à résoudre dans la future feature « automatisation backfill » (post-swap bloquant = dead-end connu).- Acceptation BAN = par PRÉCISION (
result_type), JAMAIS un gate binaire de score (fix 2026-05-19, prouvé prod ; détaildocs/plans/ban-join.md+ mémoireban-acceptance-precision-tier).result_type(housenumber/street/locality/municipality) EST la garantie de précision géographique ;result_scoren'est qu'une confiance fuzzy-match que les abréviations RPPS (R/BD/AV) + accents font chuter ALORS QUE le point est correct (prouvé : 500 rejetées re-géocodées, 81 % ont coords, ~90 % deshousenumber0,5–0,7 = bon bâtiment). L'ancien gatescore≥0,7 ET type∈{housenumber,street}+ban-backfill.mjsmettantlat=NULLsi non accepté jetait ~40k médecins au centroïde commune (~3 km) malgré un point rue/bâtiment BAN valide. Règle produit : rue/lieu-dit > centroïde. Fix :BAN_ACCEPT_SCORE0,7→0,5 (scripts/ban-backfill.mjs) +type∈{housenumber,street,locality}(municipalityexclu = aucun gain) danssrc/core/ban-bulk-client.ts; gardeban-bulk-client.test.ts. Le commentaire historique « JAMAIS 0.5 » (audit-P2) valait pour l'accept binaire-précis — la sémantique est désormais « upgrade vs centroïde », documentée inline (NE PAS reverter à 0,7 sans relire la preuve prod). Recovery = cache-only (geocoded_addresseshors swap, idempotent) ;ban_joindu cron mensuel pose. Dette : re-géocodage récurrent encore manuel (cron ne géocode plus depuis suppressionrunBanGeocodeStep). - [OBSOLÈTE — superseded par
ban_joinci-dessus, gardé pour le post-mortem] Ré-armement BAN viaingest_build_rpps_staging_ban_indexes()post-enrichment/pre-swap (refonte 2026-05-18) : la prémisse « un step RPC d'index dédié suffit » a été réfutée par la prod (run #26087010166 :CREATE INDEXmulti-minutes via supabase-js →upstream request timeout, cap passerelle Supabase 60 s structurel). Leçon transverse conservée : indexer APRÈS chargement de masse reste juste (doc PostgreSQL « Populating a Database ») — mais via canal direct, JAMAIS via une RPC PostgREST synchrone dans le cron. - Valider un code/identifiant contre une matview FILTRÉE = faux positif sur les codes valides exclus par le filtre (post-mortem dette #1).
rpps_savoir_faire_statsfiltreWHERE profession_code IS NOT NULL⇒ unsavoir_faire_coden'apparaissant que sur des lignes profession NULL en serait absent →RangeErrorsur un code POURTANT valide (nouvelle panne silencieuse côté caller, pire que la dette). Toujours valider une nomenclature contre la matview NON filtrée qui est la source réelle du count que la validation protège (rpps_count_stats), pas une matview dérivée à finalité différente. (date - date)retourne jours total ; pasEXTRACT(DAY FROM interval)(fragile, retourne le champ "day").- Coords = centroïde commune + recherche rayon = piège O(lignes/commune) (post-mortem V0.10.2). Une table dont les coords sont des centroïdes commune (RPPS) empile des dizaines de milliers de lignes au point identique en commune dense →
ST_DWithin/KNN<->par-ligne sur le cluster co-localisé = 15 s, l'index GiST n'élague rien (tous les points identiques passent le&&bbox). Fix : matview de centroïdes communaux distincts (1 ligne/commune) → résoudre les communes dans le rayon (GiST sur la petite matview) puisCROSS JOIN LATERAL (... WHERE code_insee = c.code_insee ... LIMIT n)en early-stop via l'index B-treecode_insee. Jamais de KNNgeog <-> pointglobal sur une table à coords centroïde dense. ORDER BY ST_Distance(geog, point) LIMIT Nne déclenche PAS le KNN GiST — utiliserORDER BY geog <-> point(post-mortem V0.13.3, prouvé prod EXPLAIN ANALYZE Neuilly 2 km). PostGIS GiST supporte le KNN surgeographyvia l'opérateur<->qui trie en streaming par bounding box + early-stop natif sur LIMIT.ORDER BY ST_Distance(...)au contraire force le planner à ramener TOUTES les lignes du bbox&&, recalculer la distance exacte par-ligne, puis top-N heapsort — coût O(N×K) où N = densité dans le bbox. Le piège V0.10.2 ci-dessus visait les centroïdes commune (cluster O(lignes/commune)) ; ici c'est le même piège inversé surban_addresspost-V0.13 (cache BAN à 1,14 M lignes, cluster Paris ouest dense 5–10 000 candidats à 2 km). Mesures prodrpps_in_radius(48.88, 2.27, 2000, ..., precise_only=true): pré-fix 2 594 ms / 10 628 buffers → post-fix 56 ms / 1 393 buffers (×46 sur le temps, ×7,6 sur les buffers). Règle :distance_metersreste calculé viaST_Distancedans le SELECT (distance géodésique exacte, l'opérateur<->retourne une distance bbox approximative — bonne pour le tri, mauvaise comme valeur publique). Vérification rapide qu'un plan utilise bien le KNN : chercherOrder By: (geog <-> ...)dansIndex Scan using <gist_index>, pas unSortséparé.- La branche
precisederpps_in_radiusexige un GiST PARTIELWHERE geom_source IN ('finess_join','ban_address')(rpps_geog_precise_gist) ; un GiST GLOBAL surrpps(geog)la re-régresse en 57014, etingest_create_rpps_stagingdoit créer ce PARTIEL (jamais le global) sinon le swap reverte (post-mortem 2026-05-19, prouvé prod). Extension du piège V0.10.2 ci-dessus à la CTEprecise:ST_DWithin(r.geog, v_point)filtrégeom_source IN ('finess_join','ban_address'). Avec un GiST GLOBAL présent, le planner prendIndex Scan rpps_geog_gist(geog && _st_expand) et relèguegeom_sourceen Filter post-index → le bbox ramène tout le cluster co-localisécommune_centroid(prouvé Paris 1 km : 77 381 lignes dont 76 940 jetées en Filter pour 225 résultats) → 57014.20260516T050000DROP le global + CREATE le partiel surrpps. Maisingest_create_rpps_staging(def20260518T140000, désamorçage cron) recopiait verbatim la def main créant le GiST globalrpps_staging_geog_gist: au 1er swap le RENAME revertaitrpps_geog_precise_gist→rpps_geog_gistglobal = re-régression SILENCIEUSE (découplage des 2 firefights BAN-rearm vs désamorçage cron). Fix durabilité20260519T160000: staging-create créerpps_staging_geog_precise_gist(partiel, prédicat byte-identique RPC↔20260516T050000↔guard), le swap le renomme enrpps_geog_precise_gist. Garde-foustaging-parity.test.ts(« tout GiST rpps_staging(geog) porte le prédicat partiel ») : forme POSITIVE sur CHAQUE GiST(geog)(≠ regex négative qui rataitIF NOT EXISTS/public./WITH/coexistence = faux VERT silencieux) + parité consommateur croisée vsrpps_in_radius+ lecteur STRICT tag-awarelatestFunctionBody(..., {stripComments:true})du module (ferme le faux VERT « prédicat en commentaire inline » et « def future en$tag$→ corps mort capturé »). Le guardindexColumnListshistorique est AVEUGLE ici (global et partiel normalisent à la même liste de colonnesgeog, la clause WHERE étant hors du 1er groupe de parenthèses) — d'où l'assertion dédiée. Leçon transverse : 2 firefights concurrents peuvent découpler une fonction de son index compagnon ; tout index spatial surrppsDOIT être mirroré PARTIEL-à-PARTIEL dans staging-create, pas seulement « par liste de colonnes ». - Cache paresseux rempli au serve-time → RLS doctrine
anon lit / service_role écrit, JAMAIS d'écriture exposée à anon (V0.26 immobilier DVF, prouvé prod42501 new row violates RLS). Toute table de cache remplie à la volée par l'endpoint public (dvf_mutations,dvf_commune_cache) : RLS ON + policySELECT TO anon USING(true)pour le chemin de lecture (RPC SECURITY INVOKER tourne sous anon) +GRANT INSERT/UPDATE TO service_rolepour les écritures viagetUntypedServiceClient()(src/storage/supabase.ts), jamais de policy d'écriture anon (le rôle public polluerait le cache de prix). Registre interne (dvf_commune_cache) = 0 policy anon, lu+écrit en service. Miroir exact degeocoded_addresses(20260516T060000). 1ʳᵉ utilisation serve-time de service_role (avant : ingestion only) →SUPABASE_SERVICE_ROLE_KEYDOIT être dans l'env runtime Vercel (Production + Preview) sinonrequireEnvthrow au request-time (brique morte, échec LOUD). Le filet d'intégration env-gated DOIT tourner contre une VRAIE base RLS (clés ANON et SERVICE) au checkpoint deploy, sinon le lockout passe la revue en silence. Cf. [[immobilier-dvf-rls-lazy-cache]]. - Upsert d'un CSV externe à PK composite → dédoublonner par PK AVANT
ON CONFLICT(V0.26 DVF, SQLSTATE21000prouvé prod 50129 : 3107 lignes → 2073 clés). Le CSV geo-dvf partage la PK(id_mutation, code_commune, date_mutation, type_local)sur plusieurs lots d'une même vente →INSERT … ON CONFLICTrejette TOUT le lot (« cannot affect row a second time »).upsertMutationsdédup enMapkeep-last sur l'ENSEMBLE avant le batching (couvre la collision inter-batch) et retourne le nb réellement écrit (row_countcache honnête). Clé de dédup ET cibleonConflictdérivées d'une source uniqueDVF_PK_COLS(as const satisfies readonly DvfStringKey[]— interdit une colonne PK non-string) ; toute divergence clé↔conflit ré-arme le 21000 en silence. Tests mockés/synthétiques distincts ne voient PAS ce bug → tester l'écriture DB contre ≥1 commune réelle.
What this file has done since we first saw it
Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.
- 5d ago First seen · 30 lines · 98 tokens per session scan A 72f704dcba4f
db-gotchas is a skill published in the GitHub repository cturkieh/france-data-mcp (3 stars, last pushed 4d ago), licensed MIT. It adds 98 tokens to every session and 5,915 once invoked, about $0.0005 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-09-06.
Other skills, from other repositories
triage-issues
Triage GitHub issues in the googleapis/mcp-toolbox repo: propose the correct labels (type / priority / product / status), check for duplicates, verify a bug has enough info to act on, and draft a triage comment. Use whenever a maintainer asks you to triage, label, categorize, prioritize, or "look at" an issue (or a…
postgresql-indexing
PostgreSQL indexing best practices for Prowler: index design, partial indexes, partitioned table indexing, EXPLAIN ANALYZE validation, concurrent operations, monitoring, and maintenance. Trigger: When creating or modifying PostgreSQL indexes, analyzing query performance with EXPLAIN, debugging slow queries, reviewing…
graphjin-eval
Create, extend, run, baseline, and diagnose GraphJin agent evaluations through the graphjin eval CLI.
axiom-audit-grdb-performance
Use when the user mentions GRDB performance review, slow GRDB queries, app-group database setup audit, a ValueObservation that stopped updating, or pre-release GRDB scan.
django-perf-review
Django performance code review. Use when asked to "review Django performance", "find N+1 queries", "optimize Django", "check queryset performance", "database performance", "Django ORM issues", or audit Django code for performance problems.
mma-investigator
Expert system for investigating MMA (Multi-Metric Allocator) behavior on CockroachDB clusters. Helps oncall engineers diagnose load imbalances, understand rebalancing decisions, and identify why MMA did or didn't act.