@carllee1983/dbcli 1.39.2 → 1.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dbcli-agent",
3
- "version": "1.39.2",
3
+ "version": "1.42.0",
4
4
  "description": "Database CLI skill and command reference for AI agents.",
5
5
  "author": {
6
6
  "name": "Carl Lee",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dbcli-agent",
3
- "version": "1.39.2",
3
+ "version": "1.42.0",
4
4
  "description": "Database CLI skill and command reference for AI agents",
5
5
  "author": {
6
6
  "name": "Carl Lee",
@@ -45,7 +45,7 @@ takes a full JSON filter and is exempt.)
45
45
 
46
46
  Slow-query diagnosis has three canonical paths (pick by what you already know):
47
47
 
48
- - Known slow SQL → `skill tasks plan diagnose-slow-query --param query="<SQL>"` → `guide missing-index-for "<SQL>"`
48
+ - Known slow SQL → `skill tasks plan diagnose-slow-query --param query="<SQL>"` → `lint "<SQL>"` → `guide missing-index-for "<SQL>"`
49
49
  - Known hot table → `skill tasks plan analyze-table-perf --param table=<table>`
50
50
  - Whole-environment scan → `report --section perf` → `guide slow-query`
51
51
 
@@ -54,7 +54,7 @@ afterwards add only the `@diag/*` it does not cover (`missing-indexes`, `locks`,
54
54
  `table-sizes`). Once you have a specific slow statement, `explain --analyze "<SQL>"` shows its plan.
55
55
 
56
56
  **On failure:** pass `--recovery` to `query` / `q` / `insert` / `update` / `delete` /
57
- `export` / `schema` / `inspect`. The command emits a `RecoveryEnvelope` to stdout and saves
57
+ `export` / `schema` / `inspect` / `lint` / `diff --against-orm`. The command emits a `RecoveryEnvelope` to stdout and saves
58
58
  it to `.dbcli/last-recovery.json`; then `dbcli recover` inspects it and `dbcli recover --apply`
59
59
  runs the saved plan under risk gating. Multi-turn `--next`, connection branching, and the
60
60
  post-apply verify probe are documented in reference.md §Recovery Cookbook.
@@ -81,12 +81,16 @@ The plan is an ordered list of dbcli commands with rationale and risk labels. Ex
81
81
  one at a time — task plans do **not** override blacklist, schema, dry-run, or confirmation
82
82
  requirements.
83
83
 
84
- Builtin packs: `diagnose-slow-query` (targets a specific SQL), `analyze-table-perf` (targets
85
- a specific table; `dbcli inspect` auto-suggests it for the hottest table in recent audit
86
- activity), `audit-permissions`, `safe-backfill`, `schema-drift-review`, `connection-health`.
87
- Review/verify packs: `pr-database-review`, `migration-review`, `safe-backfill-verify`,
88
- `slow-endpoint-investigation`. All are read-only `plan-only` — pick the pack matching the
89
- situation, and run any index/DDL proposal through `migration-review` before writing.
84
+ Builtin packs (SQL — postgres/mysql): `diagnose-slow-query` (targets a specific SQL),
85
+ `analyze-table-perf` (targets a specific table; `dbcli inspect` auto-suggests it for the
86
+ hottest table in recent audit activity), `audit-permissions`, `safe-backfill`,
87
+ `schema-drift-review`, `orm-drift-review` (ORM definition vs cached DB schema),
88
+ `connection-health`. Review/verify packs: `pr-database-review`,
89
+ `migration-review`, `safe-backfill-verify`, `slow-endpoint-investigation`. MongoDB packs:
90
+ `mongo-safe-backfill` (dry-run–previewed backfill), `mongo-schema-drift-review` (sampled
91
+ dot-path drift). All are read-only `plan-only` — pick the pack matching the situation, and
92
+ run any index/DDL proposal through `migration-review` before writing. Redis/Elasticsearch
93
+ have no packs yet — lead with `guide` / `report` there.
90
94
 
91
95
  Tasks live under `assets/tasks/` (builtin), `.dbcli-shared/tasks/` (shared), and
92
96
  `.dbcli/tasks/` (local override).
@@ -101,9 +105,9 @@ in **How to use dbcli** still applies.
101
105
  | DB-backed feature | `blacklist list` → `schema <object>` → `queries suggest <intent>` |
102
106
  | DB report / dashboard request | `blacklist list` → `queries search <keywords>` / `queries suggest <intent>` → `queries show @<name>` → `q @<name> --ui` or `--format html` |
103
107
  | Application data bug | `audit tail --for-agent --n 10` → `blacklist list` → `schema <object>` → narrow query |
104
- | ORM or migration work | `schema --format json` → `diff --snapshot <name>` → `migrate add-index`/`add-column` (preview SQL) → `diff --against <snapshot>` |
108
+ | ORM or migration work | `schema --format json` → `diff --against-orm <orm-schema>` → review error-level drift → proposals via `migrate` (dry-run) → `migration-review` task pack → `diff --against <snapshot>` after applying. |
105
109
  | PR database review | Review changed persistence paths, then propose concrete `schema` / `plan` / `dry-run` / `report` / `guide` commands per material claim. |
106
- | Slow endpoint or query | `report --section perf` → task pack `analyze-table-perf` → `guide missing-index-for "<query>"`; use `proxy analyze` when logs exist. |
110
+ | Slow endpoint or query | `report --section perf` → task pack `analyze-table-perf` → `lint "<query>"` → `guide missing-index-for "<query>"`; use `proxy analyze` when logs exist. |
107
111
  | Safe data backfill | `blacklist list` → `schema <object>` → count/scope query → `update … --dry-run` → read-back or snippet `--verify`. |
108
112
  | Environment validation | `status --format json` → `doctor --format json` → `inspect --for-agent --no-connect`. |
109
113
 
@@ -121,9 +125,13 @@ dbcli q @<name> --param k=v --format html > report.html
121
125
  dbcli export "<SQL>" --format html --output report.html
122
126
  dbcli audit tail --for-agent --n 10
123
127
  dbcli diff --snapshot <name>
128
+ dbcli diff --against-orm prisma/schema.prisma --format json
129
+ dbcli diff --against-orm "migrations/*.sql" --format markdown
130
+ dbcli skill tasks plan orm-drift-review --param orm_path=prisma/schema.prisma --format json
124
131
  dbcli report --section perf --format json
125
132
  dbcli skill tasks plan analyze-table-perf --param table=<table> --format json
126
133
  dbcli guide missing-index-for "<query>" --format json
134
+ dbcli lint "<SQL>" --format json
127
135
  dbcli update <object> --where "<bounded predicate>" --set '<json>' --dry-run --format json
128
136
  dbcli inspect --for-agent --no-connect --format json
129
137
  ```
@@ -290,6 +298,7 @@ Full flags and edge cases: see [reference.md](reference.md) `init` section.
290
298
  | `schema` | query-only+ | SQL: per-table or full scan into `.dbcli/schemas/`. MongoDB: sampled. ES: flattened mapping. Redis: per-key only (type/TTL/size). Supports `--recovery`. |
291
299
  | `query` | query-only+ | SQL, Mongo JSON (`--collection`), Redis command, or ES DSL/Lucene (`--collection`). `--format table\|json\|csv\|html`, `--ui` to open the interactive dashboard in a browser. Supports `--recovery`. |
292
300
  | `explain` | query-only+ | **(v1.23)** Read-only query plan with annotations. SQL only. Single query, `@saved-query`, `@file.sql`, or `--bulk @glob/*`. `--analyze` (EXPLAIN ANALYZE / MariaDB ANALYZE SELECT), `--format markdown\|json\|table`. |
301
+ | `lint` | n/a | Static SQL anti-pattern advisor (no DB connection). 9 rules incl. schema-aware implicit-cast / NOT IN-nullable checks via the layered `.dbcli/schemas/` cache; global `--use <conn>` selects a named cache. Findings carry rewrite drafts + guarded `explain` verify commands (`--analyze` only for proven read-only SQL) — report-only, never executes. `--format text\|json\|markdown`, `--min-severity`, `--no-schema`, `--bulk`. Supports `--recovery`. |
293
302
  | `plan` | n/a | Static SQL risk analyzer (`--format text\|json`); classifies a statement without connecting to the database. |
294
303
  | `q` | query-only+ | Run a saved snippet by `@name` with `--param k=v`. Supports `--verify` to run assertions. |
295
304
  | `queries` | n/a | Manage saved snippets: `list` / `show` / `search` / `suggest` / `new` / `edit` / `check` / `delete` / `rename` / `copy` / `import` / `export`. |
@@ -298,7 +307,7 @@ Full flags and edge cases: see [reference.md](reference.md) `init` section.
298
307
  | `export` | query-only+ | SQL, MongoDB, or **(v1.22)** Elasticsearch (DSL `--index` or whole-index scroll). Query → `--format json\|jsonl\|csv\|html` file or stdout. `html` emits a standalone interactive dashboard. Supports `--recovery`. |
299
308
  | `blacklist` | n/a | `list` / `table` / `column` subcommands redact sensitive data from query results. |
300
309
  | `check` | query-only+ | SQL only (best on MySQL/MariaDB). |
301
- | `diff` | query-only+ | SQL only. Save/compare schema snapshots. |
310
+ | `diff` | query-only+ | SQL only. Save/compare schema snapshots. **(P1b)** `--against-orm <path>` compares a Prisma schema / DDL file / normalized JSON against the local schema cache (no DB connection): categorized drift (`missing_in_db` = error, `missing_in_orm` = warn, `mismatch` per tolerance table, `unmanaged`) with dry-run `migrate` proposals; exit 1 on error-level drift. `--orm-format prisma\|ddl\|json\|drizzle\|typeorm\|sequelize`, `--ignore <globs>`, `--format json\|table\|markdown`. Drizzle: point at `drizzle/meta/<NNNN>_snapshot.json` (run `drizzle-kit generate` first; `.ts` sources are rejected with a hint). TypeORM/Sequelize: feed tool-generated DDL (`schema:log` / a schema-only dump); source files are rejected with the exact generation command to run. |
302
311
  | `snapshot` | query-only+ | **(v1.25)** SQL only. Capture a result fingerprint (`rowCount` + per-column null/distinct/min/max/sum + order-independent checksum). `--out` (default `.dbcli/snapshots/snap-<ts>.json`), `--rows`, `--stdout`, `--format`, `--no-limit`. Baseline for `assert --against`. |
303
312
  | `assert` | query-only+ | **(v1.25)** SQL only. Verify an invariant; exit 1 on failure unless `--no-fail`. `--expect "rows>0\|value==X\|col:c not null\|unique\|between a and b\|>= n"`, `--vs <query> --compare rows\|value` (reconcile), `--against <snapshot> --tolerance <pct>`. |
304
313
  | `verification` | n/a | Inspect and manage local verification artifacts. `list` / `show <id-or-path>` / `summary` are read-only; `prune` is dry-run by default and deletes only with `--execute --force`. Reads `<cwd>/.dbcli/verification/`; no DB connection, no audit writes. |
@@ -318,7 +327,7 @@ Full flags and edge cases: see [reference.md](reference.md) `init` section.
318
327
 
319
328
  `--use <name>` on any subcommand (including `status` / `doctor`) targets a v2 connection
320
329
  without changing the default. `--recovery` is honoured by `query`, `q`, `insert`, `update`,
321
- `delete`, `export`, `schema`, and `inspect` (see **On failure** above).
330
+ `delete`, `export`, `schema`, `inspect`, `lint`, and `diff --against-orm` (see **On failure** above).
322
331
 
323
332
  **Write & query flag semantics** (SQL/Mongo `insert`/`update`):
324
333
 
@@ -105,6 +105,7 @@ dbcli schema --use prod # Scan prod DB; saves to .dbcli/schemas/prod
105
105
 
106
106
  **Schema storage (v1.4+):** Schema is persisted as layered files under `.dbcli/schemas/`. With v2 multi-connection config each connection gets its own subdirectory (`.dbcli/schemas/<connection>/`). Run `dbcli schema --use <connection>` once per connection before querying it — otherwise `schema <table>` may return data from the wrong connection's cache.
107
107
 
108
+ > **PostgreSQL:** Introspection uses the exact `public` catalog identity throughout. Full catalog/schema/table joins prevent a reused constraint name from contaminating another table; enum lookup includes its namespace; composite primary-key order comes from the exact table OID and index ordinality; and row estimates are scoped to the exact `public` relation. Row-count SQL qualifies and quotes both `"public"` and the exact table identifier, escaping embedded quotes so mixed-case or punctuation-bearing names remain distinct and safe.
108
109
  > **Redis:** `schema <key>` is required (no full scan). The output exposes `type`, `ttl`, `size`, and a small `sample` (e.g. first 5 hash keys). `--reset` / `--refresh` are rejected — Redis caches no schema.
109
110
  > **Elasticsearch:** `schema [index]` flattens the `_mapping` properties (nested `a.b.c`) and emits each `.fields` multi-field as a separate column (e.g. `text` + `text.keyword`). Full scan iterates all non-system indices and stores per-connection caches alongside SQL engines.
110
111
  > **MongoDB:** schema is sampled via `$sample` (default 100, max 1000). `--sample-method natural` switches to `find().limit()`; `random` (default) falls back to natural order on driver error. Output columns surface nested dot-paths with `presence` (0..1) and `redacted: true` flags for blacklist-matched paths. The persisted cache records `sampleMethod` and `sampleSize`; `dbcli doctor` reports them via a `sampled: method=…, size=…` line.
@@ -192,9 +193,134 @@ dbcli explain --bulk @analytics/* # glob over saved queries
192
193
  | `nested-loop-large` | yellow | PG `Nested Loop` with planner rows > 10,000 |
193
194
 
194
195
  > Notes:
195
- > - `--analyze` executes the statement do not use against destructive SQL.
196
+ > - `--analyze` executes the statement, so dbcli accepts it only for SQL that is
197
+ > structurally proven to be a read-only, function-free `SELECT` (including
198
+ > SELECT-only CTEs). Explicit function and table-function calls are unproven
199
+ > because user-defined and built-in functions may have side effects. DML, DDL,
200
+ > data-modifying CTEs, session assignments, function-bearing SQL, and
201
+ > unrecognized SQL are rejected before the adapter is invoked; use plain
202
+ > `dbcli explain` for those statements.
196
203
  > - Auto-`LIMIT` is **not** applied to EXPLAIN statements (since v1.23 P1).
197
204
 
205
+ ### lint
206
+
207
+ Static, report-only SQL anti-pattern analysis for PostgreSQL, MySQL, and
208
+ MariaDB. `lint` never opens a database connection, never runs the SQL, and
209
+ never applies a rewrite. Schema-aware findings use only the layered schema
210
+ cache under `.dbcli/schemas/`.
211
+
212
+ ```text
213
+ dbcli lint [queries...]
214
+ dbcli lint --bulk <input>
215
+ dbcli --use <conn> lint [queries...]
216
+ ```
217
+
218
+ An input may be inline SQL, a saved query such as `@analytics/live-summary`, a
219
+ SQL file such as `@queries.sql`, or a saved-query/filesystem glob such as
220
+ `@analytics/*` or `@queries/**/*.sql`. `--bulk` accepts a comma-separated mix
221
+ of those `@file`, `@glob`, and `@saved-query` inputs; quote a filesystem glob
222
+ in a shell so the `@` reference reaches dbcli unchanged.
223
+
224
+ ```bash
225
+ dbcli lint "SELECT * FROM users WHERE email LIKE '%@example.com'" --format json
226
+ dbcli lint --bulk '@queries/**/*.sql' --format markdown
227
+ dbcli --use staging lint @analytics/live-summary --min-severity warn
228
+ ```
229
+
230
+ | Option | Default | Meaning |
231
+ |---|---|---|
232
+ | `--format <text\|json\|markdown>` | `text` | Render one report per resolved input. |
233
+ | `--min-severity <info\|warn\|error>` | `info` | Omit findings below the selected severity. |
234
+ | `--no-schema` | off | Skip schema-only checks without reading schema-cache paths; static `NOT IN` NULL checks still run. |
235
+ | `--bulk <input>` | none | Resolve a comma-separated list of `@file`, `@glob`, or `@saved-query` inputs. |
236
+ | `--recovery` | off | On command failure, emit and save a linked `RecoveryEnvelope`. |
237
+ | global `--use <conn>` | configured default | Select a v2 named connection and its isolated cache; place it before `lint`: `dbcli --use <conn> lint …`. |
238
+
239
+ **Rules:**
240
+
241
+ | Rule | Severity | What it reports |
242
+ |---|---|---|
243
+ | `select-star` | warn | A top-level `SELECT *`; when one table and its cached columns are unambiguous, the finding may include a column-list rewrite draft. |
244
+ | `unanchored-like` | warn | A `LIKE` / `ILIKE` pattern beginning with `%`, which a conventional B-tree index cannot anchor. |
245
+ | `missing-limit-offset` | info | Deep pagination with `OFFSET >= 1000`; prefer keyset pagination. |
246
+ | `non-sargable-where` | warn | A function or arithmetic expression applied to the column side of a predicate. |
247
+ | `or-to-union` | info | A top-level `OR` across different columns that can complicate index selection; any UNION alternative must preserve identity and multiplicity. |
248
+ | `subquery-to-join` | info | `IN (SELECT …)` where an equivalent `EXISTS`, or a JOIN with proven uniqueness/deduplication, may plan better. |
249
+ | `distinct-groupby-abuse` | warn | Redundant `DISTINCT` when simple projected columns exactly cover the `GROUP BY` columns. |
250
+ | `implicit-cast` | warn | A schema-verified column/literal type mismatch that can disable index use; safe, unambiguous numeric drafts may be included. |
251
+ | `not-in-nullable` | warn | A right-hand `NOT IN` value that can be NULL: explicit `NULL`, outer-join null extension, a nullable subquery projection, or a known nullable CASE/cast/aggregate expression. A nullable left-hand column is not this rule. |
252
+
253
+ `implicit-cast` and the schema-enriched portion of `not-in-nullable` read the
254
+ selected cache through the schema loader abstraction. Static `not-in-nullable`
255
+ checks still run without it. All schema caches live beneath `.dbcli/schemas/`. A v2
256
+ configuration always uses `.dbcli/schemas/<resolved-connection>/`, including
257
+ the configured default. The root `.dbcli/schemas/` directory is only the
258
+ v1/legacy unnamed cache. Global `dbcli --use <conn> lint …` selects another
259
+ named v2 slot. The command never refreshes the cache and never falls back to
260
+ schema embedded in config.
261
+
262
+ Skipped rules are returned with machine-readable `blocked:` reasons:
263
+
264
+ - Invalid SQL blocks all nine rules with `blocked: parse failed` and includes
265
+ `parseError`.
266
+ - `--no-schema` blocks `implicit-cast` and the schema-dependent portion of
267
+ `not-in-nullable` with `blocked: --no-schema`; static RHS hazards still run.
268
+ - A missing layered cache records
269
+ `blocked: schema cache unavailable (run dbcli schema)` for those unavailable
270
+ schema checks while retaining static RHS findings.
271
+
272
+ Every finding includes its rule, severity, source span, message, and
273
+ `schemaVerified` state. Some findings also carry a confidence-labelled rewrite
274
+ draft and a shell-safe verification command. It uses
275
+ `dbcli explain --analyze` only when the statement is structurally proven read-only;
276
+ function-bearing and session-assignment statements are unproven, so lint
277
+ falls back to plain `dbcli explain`. These are suggestions only: `lint` neither
278
+ executes the verification command nor changes the query.
279
+
280
+ When schema identifiers collide after case folding, schema-aware findings and
281
+ rewrites are withheld. The SQL parser does not preserve reliable quote
282
+ provenance, so an exact-looking mixed-case AST identifier cannot disambiguate
283
+ that collision. CTE, derived, schema-qualified, and database-qualified
284
+ relations also never borrow facts from the unqualified cache.
285
+
286
+ For `not-in-nullable`, remove or filter right-hand NULL values. In a subquery,
287
+ filter the projected value with `IS NOT NULL`; `NOT EXISTS` may be a better
288
+ semantic form when appropriate. dbcli does not automatically rewrite this case
289
+ unless correlation, type classification, qualified-column resolution, and the
290
+ rewrite target are all unambiguous. A direct or `AND`-conjoined `IS NOT NULL`
291
+ filter on the exact projected expression suppresses the finding; aggregates
292
+ apply the same proof in `HAVING`. Filters under `OR` or ambiguous expression
293
+ matches do not. The rule recursively checks projection, JOIN `ON`, `WHERE`, and
294
+ `HAVING` expressions, using each nested SELECT/CTE/derived statement's own
295
+ scope. Qualified outer-join null extension remains detectable without a cache,
296
+ but a join's synthetic NULL row is not applied inside that join's own `ON`;
297
+ declared nullability and completed earlier joins still apply there.
298
+
299
+ Trimmed JSON example:
300
+
301
+ ```json
302
+ [
303
+ {
304
+ "sql": "SELECT * FROM users",
305
+ "dialect": "postgresql",
306
+ "findings": [
307
+ {
308
+ "rule": "select-star",
309
+ "severity": "warn",
310
+ "message": "SELECT * fetches every column; list the columns you need.",
311
+ "span": { "start": 0, "end": 8 },
312
+ "schemaVerified": false
313
+ }
314
+ ],
315
+ "skippedRules": [],
316
+ "relatedCommands": [
317
+ "dbcli guide missing-index-for \"SELECT * FROM users\"",
318
+ "dbcli explain --analyze \"SELECT * FROM users\""
319
+ ]
320
+ }
321
+ ]
322
+ ```
323
+
198
324
  ### plan
199
325
 
200
326
  Static SQL risk analyzer. Classifies a statement into the same permission tiers
@@ -583,6 +709,202 @@ dbcli diff --against before.json --format json
583
709
  **Options:** `--snapshot <path>`, `--against <path>`, `--format <json|table>`
584
710
  **Permission:** query-only+
585
711
 
712
+ #### `diff --against-orm`
713
+
714
+ Compare an ORM definition with the local SQL schema cache. This mode reads
715
+ `config.schema`; it does not open a database connection, refresh the cache, or
716
+ execute a proposal. An empty cache fails with
717
+ `Schema cache is empty. Run 'dbcli schema' first.` Snapshot mode remains a
718
+ separate `--snapshot` / `--against` workflow.
719
+
720
+ ```bash
721
+ # Prisma and normalized JSON accept exactly one file
722
+ dbcli diff --against-orm prisma/schema.prisma --format json
723
+ dbcli diff --against-orm schema.normalized.json --orm-format json --format table
724
+
725
+ # Drizzle requires a PostgreSQL drizzle-kit v7 snapshot (generate it first)
726
+ drizzle-kit generate
727
+ dbcli diff --against-orm drizzle/meta/0001_snapshot.json --orm-format drizzle --format table
728
+
729
+ # DDL accepts repeatable or comma-separated paths and real filesystem globs
730
+ dbcli diff --against-orm "migrations/*.sql" --format markdown
731
+ dbcli diff --against-orm migrations/base.sql,migrations/accounts.sql \
732
+ --against-orm migrations/orders.sql --orm-format ddl --format json
733
+
734
+ # Ignore patterns are comma-separated and match qualified table identity
735
+ dbcli diff --against-orm prisma/schema.prisma --ignore 'public.audit_*,public.Legacy'
736
+ ```
737
+
738
+ ##### TypeORM
739
+
740
+ TypeORM entities are not parsed directly. `schema:log` prints the SQL that
741
+ `schema:sync` would execute without applying it; `-d` is the required data-source
742
+ path. Generate that DDL, then select the `typeorm` alias so the report is tagged
743
+ `ormSource: typeorm`:
744
+
745
+ ```bash
746
+ bunx typeorm schema:log -d <path/to/datasource> > schema.sql
747
+ dbcli diff --against-orm schema.sql --orm-format typeorm --format table
748
+ ```
749
+
750
+ With `--orm-format typeorm`, `typeorm_metadata` and `migrations` are
751
+ default-ignored and appear as `unmanaged` rather than scored drift. Passing a
752
+ TypeORM `.ts`, `.js`, `.mjs`, or `.cjs` source file is rejected with the
753
+ `schema:log` command to run. See the
754
+ [TypeORM CLI documentation](https://typeorm.io/docs/using-cli) and
755
+ [`SchemaLogCommand`](https://github.com/typeorm/typeorm/blob/master/src/commands/SchemaLogCommand.ts).
756
+
757
+ ##### Sequelize
758
+
759
+ Sequelize CLI does not provide a universal `db:migrate --dry-run`. Point the
760
+ project's existing Sequelize configuration at an empty scratch database, apply
761
+ the migrations there, and dump definitions without row data:
762
+
763
+ ```bash
764
+ # Configure Sequelize for an empty scratch database first
765
+ bunx sequelize-cli db:migrate
766
+
767
+ # PostgreSQL scratch database
768
+ pg_dump --schema-only <scratch-database> > schema.sql
769
+
770
+ # MySQL scratch database
771
+ mysqldump --no-data <database> > schema.sql
772
+
773
+ dbcli diff --against-orm schema.sql --orm-format sequelize --format json
774
+ ```
775
+
776
+ With `--orm-format sequelize`, `SequelizeMeta` is default-ignored and appears as
777
+ `unmanaged` rather than scored drift. Passing a Sequelize `.ts`, `.js`, `.mjs`,
778
+ or `.cjs` model file is rejected with the scratch-database and schema-only dump
779
+ recipe. See the
780
+ [Sequelize CLI migration command](https://github.com/sequelize/cli/blob/main/src/commands/migrate.js),
781
+ [PostgreSQL `pg_dump`](https://www.postgresql.org/docs/current/app-pgdump.html),
782
+ and [MySQL `mysqldump`](https://dev.mysql.com/doc/refman/8.4/en/mysqldump-definition-data-dumps.html)
783
+ references.
784
+
785
+ | Option | Behavior |
786
+ | :--- | :--- |
787
+ | `--against-orm <paths>` | Repeatable or comma-separated input. DDL-family inputs (raw DDL, TypeORM, and Sequelize) support real filesystem globs; matches are deduplicated and put in deterministic path order, then parsed as one shared ordered context so an index in a later file can attach to a table declared in an earlier file. Prisma, normalized JSON, and Drizzle accept exactly one file, and globs are rejected for those formats. |
788
+ | Drizzle input | Run `drizzle-kit generate`, then pass the PostgreSQL drizzle-kit v7 snapshot at `drizzle/meta/<NNNN>_snapshot.json`. TypeScript ORM schema sources (`.ts` or `.TS`) are rejected with that snapshot-generation hint; dbcli does not parse them directly. |
789
+ | TypeORM / Sequelize input | Generate DDL with the ORM/database tooling, then pass the SQL file with the matching `typeorm` or `sequelize` alias. Entity/model source files are rejected rather than parsed. |
790
+ | `--orm-format prisma\|ddl\|json\|drizzle\|typeorm\|sequelize` | Override extension/content detection. The `typeorm` and `sequelize` aliases use the DDL adapter while preserving the source tag and ORM-specific default ignores. Without an override, dbcli detects Prisma, raw DDL, normalized JSON, or a Drizzle snapshot from the path and content. |
791
+ | `--ignore <globs>` | Comma-separated, case-sensitive table globs. Patterns match the qualified display identity (for example `public.Users`). `_prisma_migrations` is always unmanaged; the TypeORM alias additionally ignores `typeorm_metadata` and `migrations`, and the Sequelize alias additionally ignores `SequelizeMeta`. |
792
+ | `--format json\|table\|markdown` | Select machine JSON, human table, or Markdown output. Markdown is available only in ORM drift mode. |
793
+ | `--recovery` | On an I/O, configuration, empty-cache, invalid-format, or unsupported-engine failure, emit and save a structured recovery envelope. Invalid Prisma/DDL constructs normally become `unparsed` entries instead of throwing. |
794
+
795
+ The command supports PostgreSQL, MySQL, and MariaDB configurations. Only
796
+ error-level **scored drift** determines the report's drift exit code: one or more
797
+ scored errors exits `1`; warnings, infos, `unmanaged`, or `unparsed` entries alone
798
+ exit `0`. Command/configuration failures independently exit code `1`. The four
799
+ drift categories and tolerance rules are:
800
+
801
+ | Category | Severity and comparison rule |
802
+ | :--- | :--- |
803
+ | `missing_in_db` | `error` — a table, column, or index exists in the ORM definition but not in the cached DB schema. |
804
+ | `missing_in_orm` | `warn` — a table, column, or index exists in the cached DB schema but not in the ORM definition. |
805
+ | `mismatch` | `error` when the type family or nullability differs; `info` for same-family type spelling, default, or primary-key differences. |
806
+ | `unmanaged` | `info`, excluded from error/warn scoring — the table matched the built-in or user `--ignore` patterns. |
807
+
808
+ Type-family tolerance deliberately treats engine spellings such as `text` and
809
+ `varchar(191)` as the same family: the spelling difference is still visible as
810
+ `info`, while an integer/text family difference is an `error`. Indexes compare
811
+ by structural index signatures — ordered, case-folded column names plus
812
+ uniqueness — rather than by engine-specific index names. Duplicate signatures
813
+ are emitted once. Drift entries sort deterministically by table, object, category,
814
+ and detail using Unicode code-point order, never locale-dependent collation.
815
+
816
+ **Schema and table identity.** Storage preserves exact, case-sensitive schema
817
+ and table names from the database catalog. Exact, case-sensitive `(schema, table)`
818
+ tuples are the comparison key, so PostgreSQL `users` and `"Users"` can coexist.
819
+ DDL resolution rules: unquoted SQL identifiers fold to lowercase; quoted identifiers match exactly.
820
+ For example, unquoted `Users` resolves to `users`, and quoted
821
+ `"Users"` resolves only to `Users`. Quote state comes from the parsed identifier representation;
822
+ dbcli never infers it from display text, catalog spelling, or a
823
+ Prisma mapping. Qualified components resolve independently, and unqualified ORM
824
+ identities use the cached DB default schema when one is known. Qualified display
825
+ names and `--ignore` matching remain case-sensitive. Duplicate exact or
826
+ duplicate resolved table identities fail closed instead of overwriting one
827
+ another.
828
+
829
+ **Prisma subset.** The parser supports `model` blocks; scalar `String`, `Int`,
830
+ `BigInt`, `Float`, `Decimal`, `Boolean`, `DateTime`, `Json`, and `Bytes` fields;
831
+ `?`; relation-side `[]`; `@id`, `@unique`, `@default(...)`, `@map("...")`,
832
+ `@@map("...")`, `@@index([...])`, `@@unique([...])`; relations with
833
+ `fields` / `references`; and the validated native mappings `@db.Text`,
834
+ `@db.VarChar(n)`, `@db.Uuid`, `@db.Timestamptz([precision])`, `@db.Date`,
835
+ `@db.SmallInt`, and `@db.JsonB`. Views, composite types, enums used as scalar
836
+ columns, multi-schema datasource configuration, malformed declarations, unknown
837
+ attributes, and unsupported native mappings are never guessed.
838
+
839
+ Prisma, DDL, and Drizzle constructs outside the supported subset are retained in
840
+ `unparsed` with a `blocked:` reason. These entries are separate from scored drift:
841
+ inspect and resolve them before treating an otherwise clean summary as complete.
842
+ Drizzle enums and other unsupported snapshot constructs therefore appear as blocked
843
+ `unparsed` entries rather than managed tables or columns.
844
+ Multi-file DDL is consumed as one deterministic shared ordered statement context,
845
+ so later `CREATE INDEX` statements can reference tables declared in earlier
846
+ files. PostgreSQL `PARTITION BY` and MySQL/MariaDB table engine, charset, and
847
+ other `CREATE TABLE` table options are unsupported: the construct produces a
848
+ `blocked:` `unparsed` entry and does not emit a managed ORM table.
849
+ The normalized JSON escape hatch is Zod-validated and uses an array of tables
850
+ with explicit exact `identity` objects; optional parsed identifiers must include
851
+ their `quoted` flags, and every normalized JSON `unparsed.reason` must start with
852
+ `blocked:`.
853
+
854
+ ```json
855
+ {
856
+ "ormSource": "prisma",
857
+ "entries": [
858
+ {
859
+ "category": "missing_in_db",
860
+ "severity": "error",
861
+ "table": "public.users",
862
+ "object": "email",
863
+ "detail": "column 'email' (text) is defined in prisma but absent in the database",
864
+ "proposedCommands": [
865
+ "# escalate: schema-qualified table 'public.users' is not losslessly representable by dbcli migrate — run: dbcli skill tasks plan migration-review"
866
+ ]
867
+ }
868
+ ],
869
+ "unparsed": [],
870
+ "summary": { "errors": 1, "warns": 0, "infos": 0, "unmanaged": 0 }
871
+ }
872
+ ```
873
+
874
+ Missing unqualified columns and indexes may receive shell-safe, dry-run-by-default
875
+ `dbcli migrate add-column` or `add-index` proposal strings. Simple arguments stay
876
+ unquoted; unsafe shell characters are POSIX single-quoted. Table creation,
877
+ removal, mismatch, and DB-only drift escalate to `migration-review`. A
878
+ schema-qualified target, or index columns that the current `migrate --columns`
879
+ CLI cannot represent losslessly, also escalates instead of emitting a corrupt
880
+ command. Any table, column, or type positional beginning with `-` also escalates
881
+ so Commander cannot reinterpret it as an option. A leading-dash option value is
882
+ rendered with option-safe attached syntax, for example `--default=-1` or
883
+ `--columns=--config,email`. Proposals are text only and never add `--execute`.
884
+
885
+ For a guided, cache-refreshing review, use the built-in `orm-drift-review` pack:
886
+
887
+ ```bash
888
+ dbcli skill tasks plan orm-drift-review \
889
+ --param orm_path=prisma/schema.prisma \
890
+ --format json
891
+ ```
892
+
893
+ The plan is `blacklist list` → `schema --format json` →
894
+ `diff --against-orm ... --format json`. Run any proposed `migrate` command in its
895
+ default dry-run mode, capture the emitted DDL, confirm its exact target, and pass
896
+ both values to the separate migration review:
897
+
898
+ ```sh
899
+ dbcli skill tasks plan migration-review \
900
+ --param "table=${exact_table}" \
901
+ --param "ddl=${captured_ddl}"
902
+ ```
903
+
904
+ Both parameters are required. Keep each expansion as one quoted shell argument;
905
+ never use `eval`, and consider `--execute` only after the plan and captured DDL
906
+ have been reviewed.
907
+
586
908
  ### snapshot
587
909
 
588
910
  Capture a **result fingerprint** of a query (not schema): `rowCount` plus per-column
@@ -1713,8 +2035,12 @@ a read-only (`plan-only`) pack taking a required `table` parameter that walks
1713
2035
  in recent audit activity. Additional read-only packs ship for common agent
1714
2036
  workflows: `audit-permissions` (permission/blacklist audit), `safe-backfill`
1715
2037
  (plan a write with blacklist+schema+risk checks), `schema-drift-review` (cached
1716
- vs live schema diff), and `connection-health` (reachability/config/capacity
1717
- triage). Run `dbcli skill tasks list` for the full set.
2038
+ vs live schema diff), `orm-drift-review` (ORM definition vs cached DB schema),
2039
+ and `connection-health` (reachability/config/capacity
2040
+ triage). **MongoDB packs:** `mongo-safe-backfill` (dry-run–previewed backfill)
2041
+ and `mongo-schema-drift-review` (sampled dot-path drift, with a `sample_size` knob
2042
+ to damp sampling noise); filter them with `dbcli skill tasks list --engine mongodb`.
2043
+ Run `dbcli skill tasks list` for the full set.
1718
2044
 
1719
2045
  ```bash
1720
2046
  dbcli skill tasks plan analyze-table-perf --param table=betting_logs --format json
@@ -2,7 +2,7 @@
2
2
  "name": "dbcli-agent",
3
3
  "displayName": "dbcli Agent",
4
4
  "description": "Database CLI skill and command reference for AI agents.",
5
- "version": "1.39.2",
5
+ "version": "1.42.0",
6
6
  "author": {
7
7
  "name": "Carl Lee",
8
8
  "url": "https://github.com/CarlLee1983"
@@ -10,6 +10,7 @@
10
10
  "homepage": "https://github.com/CarlLee1983/dbcli",
11
11
  "repository": "https://github.com/CarlLee1983/dbcli",
12
12
  "license": "MIT",
13
+ "logo": "docs/assets/dbcli-intro/cursor-marketplace.png",
13
14
  "keywords": [
14
15
  "database",
15
16
  "cli",
@@ -45,7 +45,7 @@ takes a full JSON filter and is exempt.)
45
45
 
46
46
  Slow-query diagnosis has three canonical paths (pick by what you already know):
47
47
 
48
- - Known slow SQL → `skill tasks plan diagnose-slow-query --param query="<SQL>"` → `guide missing-index-for "<SQL>"`
48
+ - Known slow SQL → `skill tasks plan diagnose-slow-query --param query="<SQL>"` → `lint "<SQL>"` → `guide missing-index-for "<SQL>"`
49
49
  - Known hot table → `skill tasks plan analyze-table-perf --param table=<table>`
50
50
  - Whole-environment scan → `report --section perf` → `guide slow-query`
51
51
 
@@ -54,7 +54,7 @@ afterwards add only the `@diag/*` it does not cover (`missing-indexes`, `locks`,
54
54
  `table-sizes`). Once you have a specific slow statement, `explain --analyze "<SQL>"` shows its plan.
55
55
 
56
56
  **On failure:** pass `--recovery` to `query` / `q` / `insert` / `update` / `delete` /
57
- `export` / `schema` / `inspect`. The command emits a `RecoveryEnvelope` to stdout and saves
57
+ `export` / `schema` / `inspect` / `lint` / `diff --against-orm`. The command emits a `RecoveryEnvelope` to stdout and saves
58
58
  it to `.dbcli/last-recovery.json`; then `dbcli recover` inspects it and `dbcli recover --apply`
59
59
  runs the saved plan under risk gating. Multi-turn `--next`, connection branching, and the
60
60
  post-apply verify probe are documented in reference.md §Recovery Cookbook.
@@ -81,12 +81,16 @@ The plan is an ordered list of dbcli commands with rationale and risk labels. Ex
81
81
  one at a time — task plans do **not** override blacklist, schema, dry-run, or confirmation
82
82
  requirements.
83
83
 
84
- Builtin packs: `diagnose-slow-query` (targets a specific SQL), `analyze-table-perf` (targets
85
- a specific table; `dbcli inspect` auto-suggests it for the hottest table in recent audit
86
- activity), `audit-permissions`, `safe-backfill`, `schema-drift-review`, `connection-health`.
87
- Review/verify packs: `pr-database-review`, `migration-review`, `safe-backfill-verify`,
88
- `slow-endpoint-investigation`. All are read-only `plan-only` — pick the pack matching the
89
- situation, and run any index/DDL proposal through `migration-review` before writing.
84
+ Builtin packs (SQL — postgres/mysql): `diagnose-slow-query` (targets a specific SQL),
85
+ `analyze-table-perf` (targets a specific table; `dbcli inspect` auto-suggests it for the
86
+ hottest table in recent audit activity), `audit-permissions`, `safe-backfill`,
87
+ `schema-drift-review`, `orm-drift-review` (ORM definition vs cached DB schema),
88
+ `connection-health`. Review/verify packs: `pr-database-review`,
89
+ `migration-review`, `safe-backfill-verify`, `slow-endpoint-investigation`. MongoDB packs:
90
+ `mongo-safe-backfill` (dry-run–previewed backfill), `mongo-schema-drift-review` (sampled
91
+ dot-path drift). All are read-only `plan-only` — pick the pack matching the situation, and
92
+ run any index/DDL proposal through `migration-review` before writing. Redis/Elasticsearch
93
+ have no packs yet — lead with `guide` / `report` there.
90
94
 
91
95
  Tasks live under `assets/tasks/` (builtin), `.dbcli-shared/tasks/` (shared), and
92
96
  `.dbcli/tasks/` (local override).
@@ -101,9 +105,9 @@ in **How to use dbcli** still applies.
101
105
  | DB-backed feature | `blacklist list` → `schema <object>` → `queries suggest <intent>` |
102
106
  | DB report / dashboard request | `blacklist list` → `queries search <keywords>` / `queries suggest <intent>` → `queries show @<name>` → `q @<name> --ui` or `--format html` |
103
107
  | Application data bug | `audit tail --for-agent --n 10` → `blacklist list` → `schema <object>` → narrow query |
104
- | ORM or migration work | `schema --format json` → `diff --snapshot <name>` → `migrate add-index`/`add-column` (preview SQL) → `diff --against <snapshot>` |
108
+ | ORM or migration work | `schema --format json` → `diff --against-orm <orm-schema>` → review error-level drift → proposals via `migrate` (dry-run) → `migration-review` task pack → `diff --against <snapshot>` after applying. |
105
109
  | PR database review | Review changed persistence paths, then propose concrete `schema` / `plan` / `dry-run` / `report` / `guide` commands per material claim. |
106
- | Slow endpoint or query | `report --section perf` → task pack `analyze-table-perf` → `guide missing-index-for "<query>"`; use `proxy analyze` when logs exist. |
110
+ | Slow endpoint or query | `report --section perf` → task pack `analyze-table-perf` → `lint "<query>"` → `guide missing-index-for "<query>"`; use `proxy analyze` when logs exist. |
107
111
  | Safe data backfill | `blacklist list` → `schema <object>` → count/scope query → `update … --dry-run` → read-back or snippet `--verify`. |
108
112
  | Environment validation | `status --format json` → `doctor --format json` → `inspect --for-agent --no-connect`. |
109
113
 
@@ -121,9 +125,13 @@ dbcli q @<name> --param k=v --format html > report.html
121
125
  dbcli export "<SQL>" --format html --output report.html
122
126
  dbcli audit tail --for-agent --n 10
123
127
  dbcli diff --snapshot <name>
128
+ dbcli diff --against-orm prisma/schema.prisma --format json
129
+ dbcli diff --against-orm "migrations/*.sql" --format markdown
130
+ dbcli skill tasks plan orm-drift-review --param orm_path=prisma/schema.prisma --format json
124
131
  dbcli report --section perf --format json
125
132
  dbcli skill tasks plan analyze-table-perf --param table=<table> --format json
126
133
  dbcli guide missing-index-for "<query>" --format json
134
+ dbcli lint "<SQL>" --format json
127
135
  dbcli update <object> --where "<bounded predicate>" --set '<json>' --dry-run --format json
128
136
  dbcli inspect --for-agent --no-connect --format json
129
137
  ```
@@ -290,6 +298,7 @@ Full flags and edge cases: see [reference.md](reference.md) `init` section.
290
298
  | `schema` | query-only+ | SQL: per-table or full scan into `.dbcli/schemas/`. MongoDB: sampled. ES: flattened mapping. Redis: per-key only (type/TTL/size). Supports `--recovery`. |
291
299
  | `query` | query-only+ | SQL, Mongo JSON (`--collection`), Redis command, or ES DSL/Lucene (`--collection`). `--format table\|json\|csv\|html`, `--ui` to open the interactive dashboard in a browser. Supports `--recovery`. |
292
300
  | `explain` | query-only+ | **(v1.23)** Read-only query plan with annotations. SQL only. Single query, `@saved-query`, `@file.sql`, or `--bulk @glob/*`. `--analyze` (EXPLAIN ANALYZE / MariaDB ANALYZE SELECT), `--format markdown\|json\|table`. |
301
+ | `lint` | n/a | Static SQL anti-pattern advisor (no DB connection). 9 rules incl. schema-aware implicit-cast / NOT IN-nullable checks via the layered `.dbcli/schemas/` cache; global `--use <conn>` selects a named cache. Findings carry rewrite drafts + guarded `explain` verify commands (`--analyze` only for proven read-only SQL) — report-only, never executes. `--format text\|json\|markdown`, `--min-severity`, `--no-schema`, `--bulk`. Supports `--recovery`. |
293
302
  | `plan` | n/a | Static SQL risk analyzer (`--format text\|json`); classifies a statement without connecting to the database. |
294
303
  | `q` | query-only+ | Run a saved snippet by `@name` with `--param k=v`. Supports `--verify` to run assertions. |
295
304
  | `queries` | n/a | Manage saved snippets: `list` / `show` / `search` / `suggest` / `new` / `edit` / `check` / `delete` / `rename` / `copy` / `import` / `export`. |
@@ -298,7 +307,7 @@ Full flags and edge cases: see [reference.md](reference.md) `init` section.
298
307
  | `export` | query-only+ | SQL, MongoDB, or **(v1.22)** Elasticsearch (DSL `--index` or whole-index scroll). Query → `--format json\|jsonl\|csv\|html` file or stdout. `html` emits a standalone interactive dashboard. Supports `--recovery`. |
299
308
  | `blacklist` | n/a | `list` / `table` / `column` subcommands redact sensitive data from query results. |
300
309
  | `check` | query-only+ | SQL only (best on MySQL/MariaDB). |
301
- | `diff` | query-only+ | SQL only. Save/compare schema snapshots. |
310
+ | `diff` | query-only+ | SQL only. Save/compare schema snapshots. **(P1b)** `--against-orm <path>` compares a Prisma schema / DDL file / normalized JSON against the local schema cache (no DB connection): categorized drift (`missing_in_db` = error, `missing_in_orm` = warn, `mismatch` per tolerance table, `unmanaged`) with dry-run `migrate` proposals; exit 1 on error-level drift. `--orm-format prisma\|ddl\|json\|drizzle\|typeorm\|sequelize`, `--ignore <globs>`, `--format json\|table\|markdown`. Drizzle: point at `drizzle/meta/<NNNN>_snapshot.json` (run `drizzle-kit generate` first; `.ts` sources are rejected with a hint). TypeORM/Sequelize: feed tool-generated DDL (`schema:log` / a schema-only dump); source files are rejected with the exact generation command to run. |
302
311
  | `snapshot` | query-only+ | **(v1.25)** SQL only. Capture a result fingerprint (`rowCount` + per-column null/distinct/min/max/sum + order-independent checksum). `--out` (default `.dbcli/snapshots/snap-<ts>.json`), `--rows`, `--stdout`, `--format`, `--no-limit`. Baseline for `assert --against`. |
303
312
  | `assert` | query-only+ | **(v1.25)** SQL only. Verify an invariant; exit 1 on failure unless `--no-fail`. `--expect "rows>0\|value==X\|col:c not null\|unique\|between a and b\|>= n"`, `--vs <query> --compare rows\|value` (reconcile), `--against <snapshot> --tolerance <pct>`. |
304
313
  | `verification` | n/a | Inspect and manage local verification artifacts. `list` / `show <id-or-path>` / `summary` are read-only; `prune` is dry-run by default and deletes only with `--execute --force`. Reads `<cwd>/.dbcli/verification/`; no DB connection, no audit writes. |
@@ -318,7 +327,7 @@ Full flags and edge cases: see [reference.md](reference.md) `init` section.
318
327
 
319
328
  `--use <name>` on any subcommand (including `status` / `doctor`) targets a v2 connection
320
329
  without changing the default. `--recovery` is honoured by `query`, `q`, `insert`, `update`,
321
- `delete`, `export`, `schema`, and `inspect` (see **On failure** above).
330
+ `delete`, `export`, `schema`, `inspect`, `lint`, and `diff --against-orm` (see **On failure** above).
322
331
 
323
332
  **Write & query flag semantics** (SQL/Mongo `insert`/`update`):
324
333