@carllee1983/dbcli 1.39.1 → 1.41.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.
@@ -18,7 +18,16 @@ the CLI package has not been installed globally.
18
18
 
19
19
  1. `dbcli blacklist list` — confirm sensitive-data boundaries.
20
20
  2. `dbcli schema <object> --format json` — confirm real column/field names. **Never guess.**
21
- 3. All writes: `--dry-run` (SQL/Mongo) → run → `query` read-back to confirm.
21
+ 3. All writes: `--dry-run` (SQL/Mongo) → run → `query` read-back to confirm. Redis
22
+ `query` has **no `--dry-run`** (see **Redis**); Elasticsearch is **read-only**.
23
+
24
+ **`update` / `delete` `--where` is equality-only (SQL).** It accepts **only** `col=val` or
25
+ `col1=v1 AND col2=v2`. A comparison / pattern operator (`>`, `>=`, `<`, `!=`, `LIKE`, `IN`)
26
+ is a **parse error**; worse, `OR` is **silently swallowed into the value** — `a=1 OR b=2`
27
+ parses as `a = "1 OR b=2"` and matches the wrong rows (or none). For a range or compound
28
+ condition, first `query` / `export` the target rows' primary keys, then run one
29
+ `update` / `delete --where "id=<pk>"` per key — or escalate to a human. (MongoDB `--where`
30
+ takes a full JSON filter and is exempt.)
22
31
 
23
32
  > `report` and `guide` already embed an `inspect` snapshot — you do **not** need to run
24
33
  > `dbcli inspect` first. Run `dbcli inspect --for-agent` manually only when you want the
@@ -36,7 +45,7 @@ the CLI package has not been installed globally.
36
45
 
37
46
  Slow-query diagnosis has three canonical paths (pick by what you already know):
38
47
 
39
- - 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>"`
40
49
  - Known hot table → `skill tasks plan analyze-table-perf --param table=<table>`
41
50
  - Whole-environment scan → `report --section perf` → `guide slow-query`
42
51
 
@@ -45,7 +54,7 @@ afterwards add only the `@diag/*` it does not cover (`missing-indexes`, `locks`,
45
54
  `table-sizes`). Once you have a specific slow statement, `explain --analyze "<SQL>"` shows its plan.
46
55
 
47
56
  **On failure:** pass `--recovery` to `query` / `q` / `insert` / `update` / `delete` /
48
- `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
49
58
  it to `.dbcli/last-recovery.json`; then `dbcli recover` inspects it and `dbcli recover --apply`
50
59
  runs the saved plan under risk gating. Multi-turn `--next`, connection branching, and the
51
60
  post-apply verify probe are documented in reference.md §Recovery Cookbook.
@@ -72,12 +81,16 @@ The plan is an ordered list of dbcli commands with rationale and risk labels. Ex
72
81
  one at a time — task plans do **not** override blacklist, schema, dry-run, or confirmation
73
82
  requirements.
74
83
 
75
- Builtin packs: `diagnose-slow-query` (targets a specific SQL), `analyze-table-perf` (targets
76
- a specific table; `dbcli inspect` auto-suggests it for the hottest table in recent audit
77
- activity), `audit-permissions`, `safe-backfill`, `schema-drift-review`, `connection-health`.
78
- Review/verify packs: `pr-database-review`, `migration-review`, `safe-backfill-verify`,
79
- `slow-endpoint-investigation`. All are read-only `plan-only` — pick the pack matching the
80
- 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.
81
94
 
82
95
  Tasks live under `assets/tasks/` (builtin), `.dbcli-shared/tasks/` (shared), and
83
96
  `.dbcli/tasks/` (local override).
@@ -92,9 +105,9 @@ in **How to use dbcli** still applies.
92
105
  | DB-backed feature | `blacklist list` → `schema <object>` → `queries suggest <intent>` |
93
106
  | DB report / dashboard request | `blacklist list` → `queries search <keywords>` / `queries suggest <intent>` → `queries show @<name>` → `q @<name> --ui` or `--format html` |
94
107
  | Application data bug | `audit tail --for-agent --n 10` → `blacklist list` → `schema <object>` → narrow query |
95
- | 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. |
96
109
  | PR database review | Review changed persistence paths, then propose concrete `schema` / `plan` / `dry-run` / `report` / `guide` commands per material claim. |
97
- | 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. |
98
111
  | Safe data backfill | `blacklist list` → `schema <object>` → count/scope query → `update … --dry-run` → read-back or snippet `--verify`. |
99
112
  | Environment validation | `status --format json` → `doctor --format json` → `inspect --for-agent --no-connect`. |
100
113
 
@@ -112,9 +125,13 @@ dbcli q @<name> --param k=v --format html > report.html
112
125
  dbcli export "<SQL>" --format html --output report.html
113
126
  dbcli audit tail --for-agent --n 10
114
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
115
131
  dbcli report --section perf --format json
116
132
  dbcli skill tasks plan analyze-table-perf --param table=<table> --format json
117
133
  dbcli guide missing-index-for "<query>" --format json
134
+ dbcli lint "<SQL>" --format json
118
135
  dbcli update <object> --where "<bounded predicate>" --set '<json>' --dry-run --format json
119
136
  dbcli inspect --for-agent --no-connect --format json
120
137
  ```
@@ -281,6 +298,7 @@ Full flags and edge cases: see [reference.md](reference.md) `init` section.
281
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`. |
282
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`. |
283
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`. |
284
302
  | `plan` | n/a | Static SQL risk analyzer (`--format text\|json`); classifies a statement without connecting to the database. |
285
303
  | `q` | query-only+ | Run a saved snippet by `@name` with `--param k=v`. Supports `--verify` to run assertions. |
286
304
  | `queries` | n/a | Manage saved snippets: `list` / `show` / `search` / `suggest` / `new` / `edit` / `check` / `delete` / `rename` / `copy` / `import` / `export`. |
@@ -289,7 +307,7 @@ Full flags and edge cases: see [reference.md](reference.md) `init` section.
289
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`. |
290
308
  | `blacklist` | n/a | `list` / `table` / `column` subcommands redact sensitive data from query results. |
291
309
  | `check` | query-only+ | SQL only (best on MySQL/MariaDB). |
292
- | `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`, `--ignore <globs>`, `--format json\|table\|markdown`. |
293
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`. |
294
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>`. |
295
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. |
@@ -309,7 +327,7 @@ Full flags and edge cases: see [reference.md](reference.md) `init` section.
309
327
 
310
328
  `--use <name>` on any subcommand (including `status` / `doctor`) targets a v2 connection
311
329
  without changing the default. `--recovery` is honoured by `query`, `q`, `insert`, `update`,
312
- `delete`, `export`, `schema`, and `inspect` (see **On failure** above).
330
+ `delete`, `export`, `schema`, `inspect`, `lint`, and `diff --against-orm` (see **On failure** above).
313
331
 
314
332
  **Write & query flag semantics** (SQL/Mongo `insert`/`update`):
315
333
 
@@ -364,12 +382,18 @@ without changing the default. `--recovery` is honoured by `query`, `q`, `insert`
364
382
  → `data-admin`. A command not in the whitelist is refused.
365
383
  - **No `--dry-run` for Redis `query`** — write safety comes from the permission gate and key
366
384
  blacklist (matching reads/writes are rejected). To preview a delete, use `delete <key> --dry-run`.
367
- - `database` is the logical DB index (default `0`). `dbcli blacklist add 'secrets:*'`
385
+ - `database` is the logical DB index (default `0`). `dbcli blacklist table add 'secrets:*'`
368
386
  registers a key glob; an optional `redis.mask` block masks values on read. Size guards
369
387
  (SCAN/HGETALL truncation, `--no-limit` to bypass) and masking details: reference.md Redis section.
370
388
 
371
389
  ## Elasticsearch
372
390
 
391
+ **dbcli is read-only against Elasticsearch — `insert` / `update` / `delete` are not supported.**
392
+
393
+ ```bash
394
+ dbcli query '{"query":{"match":{"status":"active"}}}' --collection orders
395
+ ```
396
+
373
397
  - `query` takes a DSL (JSON body) or Lucene query string; `--collection <index>` is required.
374
398
  - **Supported:** `init`, `list` (indices with doc count), `schema [index]` (flattened mapping),
375
399
  `query`, `export` (v1.22), `shell` (v1.22), `status`, `use`, `doctor`. **Not supported:**
@@ -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
@@ -249,6 +375,7 @@ dbcli q @analytics/revenue --param days=30 --format html > report.html
249
375
  - `--dry-run` — print the bound SQL + values without executing
250
376
  - `--use <name>` — pick a v2 named connection
251
377
  - `--recovery` — emit a `RecoveryEnvelope` on failure (see `recover`)
378
+ - `--verify` — run the snippet's verification assertions after execution (only if the snippet defines them)
252
379
 
253
380
  **Permission:** query-only+
254
381
 
@@ -474,9 +601,10 @@ Insert data into a table.
474
601
  dbcli insert users --data '{"name":"Alice","email":"alice@example.com"}'
475
602
  dbcli insert users --data '{"name":"Alice"}' --dry-run
476
603
  dbcli insert users --data '{"name":"Alice"}' --force
604
+ dbcli insert users --data '{"name":"Alice"}' --plan --format json # risk analysis only; no DB connection
477
605
  ```
478
606
 
479
- **Options:** `--data <json>`, `--dry-run`, `--force`
607
+ **Options:** `--data <json>`, `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output), `--recovery`
480
608
  **Permission:** read-write+
481
609
 
482
610
  ### update
@@ -486,11 +614,19 @@ Update existing data.
486
614
  ```bash
487
615
  dbcli update users --where "id=1" --set '{"name":"Bob"}'
488
616
  dbcli update users --where "id=1" --set '{"name":"Bob"}' --dry-run
617
+ dbcli update users --where "id=1" --set '{"name":"Bob"}' --plan --format json # risk analysis only; no DB connection
489
618
  ```
490
619
 
491
- **Options:** `--where <condition>` (required), `--set <json>` (required), `--dry-run`, `--force`
620
+ **Options:** `--where <condition>` (required), `--set <json>` (required), `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output), `--recovery`
492
621
  **Permission:** read-write+
493
622
 
623
+ > **`--where` grammar (SQL `update` / `delete`)** — equality only: `col=val` or
624
+ > `col1=v1 AND col2=v2`. Comparison / pattern operators (`>`, `>=`, `<`, `!=`, `LIKE`, `IN`)
625
+ > raise a parse error, and `OR` is **silently folded into the value** (`a=1 OR b=2` parses as
626
+ > `a = "1 OR b=2"`, matching nothing intended). For ranges or compound predicates, select the
627
+ > target primary keys first, then issue one `update` / `delete --where "id=<pk>"` per key.
628
+ > (MongoDB `--where` accepts a full JSON filter and is exempt.)
629
+
494
630
  ### delete
495
631
 
496
632
  Delete data from a table.
@@ -499,9 +635,10 @@ Delete data from a table.
499
635
  dbcli delete users --where "id=1"
500
636
  dbcli delete users --where "id=1" --dry-run
501
637
  dbcli delete users --where "id=1" --force
638
+ dbcli delete users --where "id=1" --plan --format json # risk analysis only; no DB connection
502
639
  ```
503
640
 
504
- **Options:** `--where <condition>` (required), `--dry-run`, `--force`
641
+ **Options:** `--where <condition>` (required), `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output), `--recovery`
505
642
  **Permission:** data-admin+
506
643
 
507
644
  ### export
@@ -521,7 +658,7 @@ dbcli export orders --format csv --output orders.csv # index name as query
521
658
  dbcli export orders --no-limit --format jsonl # scroll the whole index in batches
522
659
  ```
523
660
 
524
- **Options:** `--format <json|jsonl|csv|html>` (required), `--output <path>`, `--force`, `--recovery`, `--index <name>` (Elasticsearch), `--no-limit` (Elasticsearch full-index scroll)
661
+ **Options:** `--format <json|jsonl|csv|html>` (required), `--output <path>`, `--force`, `--recovery`, `--collection <name>` (MongoDB collection) / `--index <name>` (Elasticsearch index; alias for `--collection`), `--limit <number>` (overrides auto-limit), `--no-limit` (Elasticsearch full-index scroll)
525
662
  **Permission:** query-only+ — SQL, MongoDB, and **(v1.22)** Elasticsearch.
526
663
 
527
664
  The `html` format emits the same self-contained dashboard as `query --ui` (see [Interactive HTML dashboard](#interactive-html-dashboard)). Because `export` runs raw SQL (no snippet metadata), the HTML report is always rendered as a sortable / filterable table — no KPIs or charts. Use `dbcli q @<name> --format html` (or `--ui`) for the charted view.
@@ -572,6 +709,147 @@ dbcli diff --against before.json --format json
572
709
  **Options:** `--snapshot <path>`, `--against <path>`, `--format <json|table>`
573
710
  **Permission:** query-only+
574
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
+ # DDL accepts repeatable or comma-separated paths and real filesystem globs
726
+ dbcli diff --against-orm "migrations/*.sql" --format markdown
727
+ dbcli diff --against-orm migrations/base.sql,migrations/accounts.sql \
728
+ --against-orm migrations/orders.sql --orm-format ddl --format json
729
+
730
+ # Ignore patterns are comma-separated and match qualified table identity
731
+ dbcli diff --against-orm prisma/schema.prisma --ignore 'public.audit_*,public.Legacy'
732
+ ```
733
+
734
+ | Option | Behavior |
735
+ | :--- | :--- |
736
+ | `--against-orm <paths>` | Repeatable or comma-separated input. DDL inputs 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 and normalized JSON accept exactly one file, and globs are rejected for those formats. |
737
+ | `--orm-format prisma\|ddl\|json` | Override extension/content detection. Without it, dbcli detects Prisma, DDL, or normalized JSON from the path and content. |
738
+ | `--ignore <globs>` | Comma-separated, case-sensitive table globs. Patterns match the qualified display identity (for example `public.Users`). `_prisma_migrations` is always unmanaged. |
739
+ | `--format json\|table\|markdown` | Select machine JSON, human table, or Markdown output. Markdown is available only in ORM drift mode. |
740
+ | `--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. |
741
+
742
+ The command supports PostgreSQL, MySQL, and MariaDB configurations. Only
743
+ error-level **scored drift** determines the report's drift exit code: one or more
744
+ scored errors exits `1`; warnings, infos, `unmanaged`, or `unparsed` entries alone
745
+ exit `0`. Command/configuration failures independently exit code `1`. The four
746
+ drift categories and tolerance rules are:
747
+
748
+ | Category | Severity and comparison rule |
749
+ | :--- | :--- |
750
+ | `missing_in_db` | `error` — a table, column, or index exists in the ORM definition but not in the cached DB schema. |
751
+ | `missing_in_orm` | `warn` — a table, column, or index exists in the cached DB schema but not in the ORM definition. |
752
+ | `mismatch` | `error` when the type family or nullability differs; `info` for same-family type spelling, default, or primary-key differences. |
753
+ | `unmanaged` | `info`, excluded from error/warn scoring — the table matched the built-in or user `--ignore` patterns. |
754
+
755
+ Type-family tolerance deliberately treats engine spellings such as `text` and
756
+ `varchar(191)` as the same family: the spelling difference is still visible as
757
+ `info`, while an integer/text family difference is an `error`. Indexes compare
758
+ by structural index signatures — ordered, case-folded column names plus
759
+ uniqueness — rather than by engine-specific index names. Duplicate signatures
760
+ are emitted once. Drift entries sort deterministically by table, object, category,
761
+ and detail using Unicode code-point order, never locale-dependent collation.
762
+
763
+ **Schema and table identity.** Storage preserves exact, case-sensitive schema
764
+ and table names from the database catalog. Exact, case-sensitive `(schema, table)`
765
+ tuples are the comparison key, so PostgreSQL `users` and `"Users"` can coexist.
766
+ DDL resolution rules: unquoted SQL identifiers fold to lowercase; quoted identifiers match exactly.
767
+ For example, unquoted `Users` resolves to `users`, and quoted
768
+ `"Users"` resolves only to `Users`. Quote state comes from the parsed identifier representation;
769
+ dbcli never infers it from display text, catalog spelling, or a
770
+ Prisma mapping. Qualified components resolve independently, and unqualified ORM
771
+ identities use the cached DB default schema when one is known. Qualified display
772
+ names and `--ignore` matching remain case-sensitive. Duplicate exact or
773
+ duplicate resolved table identities fail closed instead of overwriting one
774
+ another.
775
+
776
+ **Prisma subset.** The parser supports `model` blocks; scalar `String`, `Int`,
777
+ `BigInt`, `Float`, `Decimal`, `Boolean`, `DateTime`, `Json`, and `Bytes` fields;
778
+ `?`; relation-side `[]`; `@id`, `@unique`, `@default(...)`, `@map("...")`,
779
+ `@@map("...")`, `@@index([...])`, `@@unique([...])`; relations with
780
+ `fields` / `references`; and the validated native mappings `@db.Text`,
781
+ `@db.VarChar(n)`, `@db.Uuid`, `@db.Timestamptz([precision])`, `@db.Date`,
782
+ `@db.SmallInt`, and `@db.JsonB`. Views, composite types, enums used as scalar
783
+ columns, multi-schema datasource configuration, malformed declarations, unknown
784
+ attributes, and unsupported native mappings are never guessed.
785
+
786
+ Prisma and DDL constructs outside the supported subset are retained in
787
+ `unparsed` with a `blocked:` reason. These entries are separate from scored drift:
788
+ inspect and resolve them before treating an otherwise clean summary as complete.
789
+ Multi-file DDL is consumed as one deterministic shared ordered statement context,
790
+ so later `CREATE INDEX` statements can reference tables declared in earlier
791
+ files. PostgreSQL `PARTITION BY` and MySQL/MariaDB table engine, charset, and
792
+ other `CREATE TABLE` table options are unsupported: the construct produces a
793
+ `blocked:` `unparsed` entry and does not emit a managed ORM table.
794
+ The normalized JSON escape hatch is Zod-validated and uses an array of tables
795
+ with explicit exact `identity` objects; optional parsed identifiers must include
796
+ their `quoted` flags, and every normalized JSON `unparsed.reason` must start with
797
+ `blocked:`.
798
+
799
+ ```json
800
+ {
801
+ "ormSource": "prisma",
802
+ "entries": [
803
+ {
804
+ "category": "missing_in_db",
805
+ "severity": "error",
806
+ "table": "public.users",
807
+ "object": "email",
808
+ "detail": "column 'email' (text) is defined in prisma but absent in the database",
809
+ "proposedCommands": [
810
+ "# escalate: schema-qualified table 'public.users' is not losslessly representable by dbcli migrate — run: dbcli skill tasks plan migration-review"
811
+ ]
812
+ }
813
+ ],
814
+ "unparsed": [],
815
+ "summary": { "errors": 1, "warns": 0, "infos": 0, "unmanaged": 0 }
816
+ }
817
+ ```
818
+
819
+ Missing unqualified columns and indexes may receive shell-safe, dry-run-by-default
820
+ `dbcli migrate add-column` or `add-index` proposal strings. Simple arguments stay
821
+ unquoted; unsafe shell characters are POSIX single-quoted. Table creation,
822
+ removal, mismatch, and DB-only drift escalate to `migration-review`. A
823
+ schema-qualified target, or index columns that the current `migrate --columns`
824
+ CLI cannot represent losslessly, also escalates instead of emitting a corrupt
825
+ command. Any table, column, or type positional beginning with `-` also escalates
826
+ so Commander cannot reinterpret it as an option. A leading-dash option value is
827
+ rendered with option-safe attached syntax, for example `--default=-1` or
828
+ `--columns=--config,email`. Proposals are text only and never add `--execute`.
829
+
830
+ For a guided, cache-refreshing review, use the built-in `orm-drift-review` pack:
831
+
832
+ ```bash
833
+ dbcli skill tasks plan orm-drift-review \
834
+ --param orm_path=prisma/schema.prisma \
835
+ --format json
836
+ ```
837
+
838
+ The plan is `blacklist list` → `schema --format json` →
839
+ `diff --against-orm ... --format json`. Run any proposed `migrate` command in its
840
+ default dry-run mode, capture the emitted DDL, confirm its exact target, and pass
841
+ both values to the separate migration review:
842
+
843
+ ```sh
844
+ dbcli skill tasks plan migration-review \
845
+ --param "table=${exact_table}" \
846
+ --param "ddl=${captured_ddl}"
847
+ ```
848
+
849
+ Both parameters are required. Keep each expansion as one quoted shell argument;
850
+ never use `eval`, and consider `--execute` only after the plan and captured DDL
851
+ have been reviewed.
852
+
575
853
  ### snapshot
576
854
 
577
855
  Capture a **result fingerprint** of a query (not schema): `rowCount` plus per-column
@@ -892,6 +1170,7 @@ Boundaries:
892
1170
  | `--from <path>` | Read the envelope from this file instead of `.dbcli/last-recovery.json`. Accepts raw `RecoveryEnvelope` or `SavedRecoveryEnvelope`. | — |
893
1171
  | `--allow-write <tier>` | Open the risk gate. Values: `readonly-cmd` (local-side writes) \| `write-cmd` (database writes). | `none` |
894
1172
  | `--no-verify` | Skip the verify step appended after a successful `--apply`. | off (verify runs by default) |
1173
+ | `--write-verification-artifact` | After a successful `--apply`, persist a secret-free `VerificationArtifact` JSON under `.dbcli/verification/`. | off |
895
1174
  | `--format <format>` | `markdown` \| `json`. | `markdown` for inspect, `json` for `--apply` |
896
1175
 
897
1176
  #### Plan source resolution
@@ -1650,6 +1929,7 @@ dbcli skill --install codex # install to ~/.codex/skills/dbcli/
1650
1929
  **Options:**
1651
1930
  - `--install <platform>` — `claude` | `gemini` | `antigravity` | `copilot` | `cursor` | `codex` | `windsurf`. Writes `SKILL.md` plus `reference.md` next to it so the agent gets progressive disclosure.
1652
1931
  - `--output <path>` — write `SKILL.md` to a file instead of stdout. Does not install `reference.md`.
1932
+ - `--lang <en|zh-TW>` — source language for the emitted SKILL content (default `en`). It selects `assets/SKILL.md` vs `assets/SKILL.zh-TW.md`; the install/output filename stays `SKILL.md` regardless.
1653
1933
 
1654
1934
  **Notes:**
1655
1935
  - Both files come straight from `assets/SKILL.md` + `assets/reference.md` inside the dbcli package — no runtime rendering. Keep these in sync when shipping a release.
@@ -1662,6 +1942,21 @@ dbcli skill --install codex # install to ~/.codex/skills/dbcli/
1662
1942
 
1663
1943
  **Permission:** n/a.
1664
1944
 
1945
+ ### skill context
1946
+
1947
+ Emit an AI-friendly snapshot of the connected database's schema and saved-query snippets (blacklist-filtered) so an agent can be primed with the current context.
1948
+
1949
+ ```bash
1950
+ dbcli skill context # XML (default)
1951
+ dbcli skill context --format json
1952
+ dbcli skill context --format markdown
1953
+ ```
1954
+
1955
+ **Options:**
1956
+ - `--format <xml|json|markdown>` — output format (default: `xml`)
1957
+
1958
+ **Permission:** query-only+ — read-only; blacklisted objects are never emitted.
1959
+
1665
1960
  ### skill tasks (Agent Task Packs)
1666
1961
 
1667
1962
  ```bash
@@ -1685,8 +1980,12 @@ a read-only (`plan-only`) pack taking a required `table` parameter that walks
1685
1980
  in recent audit activity. Additional read-only packs ship for common agent
1686
1981
  workflows: `audit-permissions` (permission/blacklist audit), `safe-backfill`
1687
1982
  (plan a write with blacklist+schema+risk checks), `schema-drift-review` (cached
1688
- vs live schema diff), and `connection-health` (reachability/config/capacity
1689
- triage). Run `dbcli skill tasks list` for the full set.
1983
+ vs live schema diff), `orm-drift-review` (ORM definition vs cached DB schema),
1984
+ and `connection-health` (reachability/config/capacity
1985
+ triage). **MongoDB packs:** `mongo-safe-backfill` (dry-run–previewed backfill)
1986
+ and `mongo-schema-drift-review` (sampled dot-path drift, with a `sample_size` knob
1987
+ to damp sampling noise); filter them with `dbcli skill tasks list --engine mongodb`.
1988
+ Run `dbcli skill tasks list` for the full set.
1690
1989
 
1691
1990
  ```bash
1692
1991
  dbcli skill tasks plan analyze-table-perf --param table=betting_logs --format json
@@ -2201,7 +2500,7 @@ Rewrites emit a `REDIS_SIZE_REWRITE` warning; truncations emit `REDIS_SIZE_TRUNC
2201
2500
  Blacklist rules are enforced as **Redis-native key globs** (`*`, `?`, `[abc]`, `[a-z]`):
2202
2501
 
2203
2502
  ```bash
2204
- dbcli blacklist add 'secrets:*' # register a key-glob rule
2503
+ dbcli blacklist table add 'secrets:*' # register a key-glob rule
2205
2504
  dbcli query "GET secrets:api_key" # → BlacklistRejection (exit non-zero)
2206
2505
  dbcli query "MGET safe:k secrets:api" # → rejected (any matching key fails the whole command)
2207
2506
  dbcli query "KEYS secrets:*" # → rejected (pattern overlaps a rule)
package/CHANGELOG.md CHANGED
@@ -5,11 +5,74 @@ All notable changes to dbcli are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.41.0] - 2026-07-19 - ORM Drift 比對與無損 Schema Identity
9
+
10
+ ### Added
11
+
12
+ - **`dbcli diff --against-orm` ORM drift 比對。** 可將 Prisma schema、DDL/migration SQL 或 normalized JSON 與既有 SQL schema cache 比對;支援多檔 DDL、filesystem glob、格式自動偵測、大小寫敏感的 `--ignore` pattern,以及 JSON、table、Markdown 輸出。比對只讀本地 cache,不連線、不更新 cache,也不執行提案。
13
+ - **結構化 drift 分類與安全提案。** 報告區分 `missing_in_db`、`missing_in_orm`、`mismatch`、`unmanaged` 與 `unparsed`;只有計分後的 error 會使 drift exit code 為 `1`。可無損表達的缺漏欄位/index 會產生 shell-safe、預設 dry-run 的 `migrate` 提案,其餘情況升級至 `migration-review`。
14
+ - **`orm-drift-review` agent task pack。** 工作流依序執行 blacklist 檢查、schema cache 更新與 ORM drift JSON 比對,並要求將 dry-run DDL 與精確目標交給獨立 migration review。
15
+
16
+ ### Changed
17
+
18
+ - **Schema identity 改為精確保存。** PostgreSQL schema/table 名稱不再正規化為小寫;quoted 與 unquoted identifier 依 SQL 規則解析,qualified name、ignore pattern、foreign key 與 drift output 都保留大小寫與 schema identity。
19
+ - **ORM drift 文件完整同步。** 英文/繁體中文的 Markdown 與 HTML 使用者文件、skill assets、各平台 plugin 副本及 reference 已補上格式、exit code、安全邊界與操作流程。
20
+ - **跨平台發版 metadata 對齊。** npm package、Codex/Claude/Cursor plugin、packaged Codex plugin 與 Gemini extension 統一為 `1.41.0`。
21
+
22
+ ### Fixed
23
+
24
+ - **Lossy ORM drift proposal 改為 fail closed。** Schema-qualified target、dash-leading positional、無法無損表達的 index column、identity collision 與不支援語法不再輸出可能損壞的指令,而是阻擋或升級人工審查。
25
+ - **DDL/Prisma adapter identity 與語意硬化。** 多檔 DDL 共用 deterministic context,foreign key pairing、default schema resolution、table option/partition 阻擋、重複 index 去重與 Unicode code-point 穩定排序皆保留來源語意。
26
+
27
+ ## [1.40.0] - 2026-07-19 - SQL Lint、安全強化與 Agent 工作流擴充
28
+
29
+ ### Added
30
+
31
+ - **新增唯讀 `dbcli lint` 靜態 SQL 顧問。** 支援 inline SQL、saved query、SQL 檔案與 glob/混合批次輸入,提供 text、JSON、Markdown 輸出、最低嚴重度篩選、`--no-schema` 與 `--recovery`;指令不連線、不執行 SQL,也不會自動套用 rewrite。
32
+ - **九條結構與 schema-aware lint 規則。** 涵蓋 `SELECT *`、未錨定 `LIKE`、深度 `OFFSET`、non-sargable predicate、`OR`/subquery 改寫機會、重複 `DISTINCT` + `GROUP BY`、implicit cast,以及 `NOT IN` 右側 NULL 風險;finding 可附 confidence 標籤的草稿與 shell-safe 驗證指令。
33
+ - **MongoDB agent task packs。** 新增 `mongo-safe-backfill` 與 `mongo-schema-drift-review`,補上 MongoDB 安全回填與 schema drift 檢視工作流。
34
+
35
+ ### Changed
36
+
37
+ - **Slow-query guide 納入 lint。** `guide slow-query` 現在會先安排本機靜態分析,再銜接 explain 與診斷 snippets,brief plan 也保留執行 metadata。
38
+ - **Agent 與使用者文件完整同步。** `lint` 已寫入 skill assets、platform plugin 副本及英文/繁體中文 Markdown 與 HTML 文件;GitHub Pages 產品介紹頁同步完成雙語、可及性與行動裝置導覽重構。
39
+ - **跨平台發版 metadata 對齊。** npm package、Codex/Claude/Cursor plugin、packaged Codex plugin 與 Gemini extension 統一為 `1.40.0`。
40
+
41
+ ### Fixed
42
+
43
+ - **Lint 採 fail-closed 安全邊界。** 解析失敗、schema binding 不明、identifier 大小寫碰撞、CTE/derived/qualified relation 與不安全 rewrite proof 會阻擋對應建議,不再借用不可靠的 cache facts。
44
+ - **`NOT IN` NULL 分析補齊 scope 與 provenance。** 遞迴處理巢狀 SELECT、CTE、derived statement、JOIN `ON`、`WHERE`、`HAVING`、outer-join null extension、nullable 投影與 CASE/cast/aggregate,並保留正確 traversal order。
45
+ - **Lint audit/recovery 遮蔽與驗證指令硬化。** positional、global、bulk 與 `--` 後的 SQL 都會遮蔽;只有結構上已證明唯讀的 SQL 才建議 `explain --analyze`,session assignment 與 function-bearing statement 會保守退回 plain explain。
46
+
47
+ ## [1.39.2] - 2026-07-03 - Windows 跨平台、skill 安裝安全與 plugin 版本對齊
48
+
49
+ > npm `1.39.1` 已於 2026-06-30 發布;本批修復在其後累積於同一版號下(npm 版本不可覆蓋),故獨立為 1.39.2 以便日後發布。
50
+
51
+ ### Fixed
52
+
53
+ - **Windows 跨平台修復(Windows CI 首次全綠)。** filesystem 操作與 path 檢查改為跨平台實作、修正 `emit` 子行程 import 與殘留的 path assertion,並以 portable `node:fs` 取代僅限 unix 的 coreutils spawns。此前 Windows job 從未通過(fail-fast 總是先取消它)。
54
+ - **Skill 安裝安全強化。** 修正 output / install 旗標衝突、強化安裝安全檢查與 task 過濾條件。
55
+ - **zh-TW skill 安裝不再被誤判為永遠過期。**
56
+ - **Skill 參考修正。** 移除文件中不存在的 `blacklist add`、補回缺漏的 reference flags。
57
+
58
+ ### Changed
59
+
60
+ - **文件補齊。** 明示 `--where` 僅支援等值比較、補上 Redis / Elasticsearch 寫入模型說明、記錄 home-storage 綁定並重新同步 md/html parity、對齊 config-location-policy 與實作綁定模型。
61
+ - **Plugin manifest 版本對齊。** `.claude-plugin` / `.cursor-plugin` / `.codex-plugin` 及 `plugins/dbcli-agent` 的 `plugin.json` 版本更新為 1.39.2(先前漂移在 1.37.1 / 1.31.0,未跟上主版本;`plugin:sync` / `plugin:check` 只同步 skill 內容不同步版本)。
62
+
63
+ ### Internal
64
+
65
+ - CI 加入 doc / skill drift guards 並修正 release-gate 說明;新增 `reference.md` 指令覆蓋契約測試;移除失效的 `validate-skill.sh`(testing doc 改指向 `bun test`);稽核冗餘測試改用 collision-proof token sentinel;zsh 不存在時跳過 rc-eval 測試;每檔還原 leaked spies 以修正順序相依的 CI 失敗;prettier 對齊 `q` / audit `logger` 測試。
66
+
8
67
  ## [1.39.1] - 2026-06-30 - Skill report dashboard routing
9
68
 
69
+ ### Fixed
70
+
71
+ - **Dashboard 請求不再落入通用 query 路由。** 先前 dashboard / report 意圖的請求會 fall through 到一般 query 路徑;現已正確導向 dashboard 專用流程。
72
+
10
73
  ### Changed
11
74
 
12
- - **Skill 路由補上 DB report / dashboard / HTML UI 意圖。** `assets/SKILL.md` / `assets/SKILL.zh-TW.md` 的 metadata、任務路由表、開發者速查與 HTML dashboard 範例現在明確導向 `queries search|suggest` → `queries show` → `q @<name> --ui` / `--format html`,並保留 raw SQL `export --format html` 的檔案輸出路徑。已透過 `plugin:sync` 同步到所有受管理平台副本。純文件 / skill 變更,無 CLI 行為更動。
75
+ - **Skill 路由補上 DB report / dashboard / HTML UI 意圖。** `assets/SKILL.md` / `assets/SKILL.zh-TW.md` 的 metadata、任務路由表、開發者速查與 HTML dashboard 範例現在明確導向 `queries search|suggest` → `queries show` → `q @<name> --ui` / `--format html`,並保留 raw SQL `export --format html` 的檔案輸出路徑。已透過 `plugin:sync` 同步到所有受管理平台副本。純文件 / skill 變更。
13
76
 
14
77
  ## [1.39.0] - 2026-06-24 - Dashboard chart type 解析時邊界驗證
15
78
 
package/README.zh-TW.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  統一的資料庫 CLI 工具,讓 AI 代理(Claude Code、Gemini、Copilot、Cursor)能安全地查詢、探索與操作資料庫。
6
6
 
7
- **核心價值:** AI 代理可透過單一、具權限控管的 CLI 工具,在敏感資料保護下安全且智慧地存取專案資料庫。
7
+ **核心價值:** AI 代理可透過單一、具權限控管的 CLI 工具,在敏感資料保護下安全存取專案資料庫。
8
8
 
9
9
  > **安全性更新:** `dbcli init` 現在只會在 `./.dbcli/config.json` 寫入一個很小的專案綁定 stub。完整的連線設定會存放在 `~/.config/dbcli/projects/<project-id>/config.json`,因此敏感設定預設不會留在專案工作區內。
10
10