Analyzing PostgreSQL
Discovery
What discovery provides:
- Database overview from
get_database_overview():
schemas: List of database schemas
tables: Tables with names, row counts, sizes, and column information
indexes: Index information and statistics
relationships: Foreign key relationships between tables
size_info: Database and table size metrics
security: Security score (0-100), superuser count, SSL status, issues and recommendations
performance_hotspots: Tables with high sequential scans, dead tuples, bloat, or high modification rates
Discovery options (script defaults, NOT MCP server defaults):
--max-tables N: Limit tables analyzed (script default: 50, MCP server default: 500)
--sampling true: Use sampling for large tables (script default: false, MCP server default: true)
--timeout N: Timeout in seconds (script default: 60, MCP server default: 300)
--alias <name>: Target a specific postgres instance (required for multi-instance workspaces)
Why run discovery:
- Get actual table/column names (never guess - they vary between databases)
- Understand relationships before writing JOINs
- Identify large tables that need sampling
- Know schema structure before executing SQL
ALWAYS use format() for output (40-60% token savings):
import { format } from "@connections/_utils/format";
console.log(format(result)); // CORRECT
console.log(JSON.stringify(result, null, 2)); // WRONG
Two-Phase Execution (MANDATORY)
Phase 1 - Discovery Script:
const schema = await get_object_details({ schema_name: "public", object_name: "user", object_type: "table" });
console.log(format(schema));
// ⛔ STOP - End script here, read output
[CHECKPOINT: Read output, identify actual column names]
Phase 2 - Query Script (NEW execution):
// Use ONLY verified column names from Phase 1
const results = await execute_sql({ sql: `SELECT verified_col FROM ...` });
❌ FORBIDDEN: get_object_details() + execute_sql() in same script
| Bad ❌ |
Good ✅ |
Why |
| Discovery + query in one script |
Two separate scripts |
Prevents hallucinated column names |
Assuming user_id exists |
Run get_object_details() first |
Foreign key naming varies |
created_at, updated_at |
Verify columns exist |
May be named differently |
| Writing JOINs without discovery |
Discover ALL tables first |
Relationships vary |
Tools (13 total)
Schema (3): list_schemas(), list_objects(schema, type?), get_object_details(schema, name, type?)
Query (1): execute_sql(sql) - read-only in restricted mode
Performance (4):
explain_query(sql, analyze?, hypothetical_indexes?) - CONSTRAINT: analyze + hypothetical_indexes cannot be used together; hypothetical_indexes requires HypoPG extension
get_top_queries(sort_by?, limit?) - default sort_by: "resources" (multi-dimensional blend, not just time)
analyze_workload_indexes(max_size_mb?, method?) - analyzes top queries from pg_stat_statements
analyze_query_indexes(queries[], max_size_mb?, method?) - CONSTRAINT: max 10 queries per call
Health (3):
analyze_db_health(type?) - accepts comma-separated types (e.g. "index,vacuum")
get_blocking_queries() - works even without active blocking (returns deadlock analysis, contention hotspots, proactive recommendations)
analyze_vacuum_requirements() - 6-phase analysis with severity levels
Advanced (2):
get_database_overview(max_tables?, sampling_mode?, timeout?) - includes security score, performance hotspots, relationship mapping
analyze_schema_relationships() - inter-schema dependency analysis with visual representation data
Quick Patterns
Discover:
const schemas = await list_schemas();
const tables = await list_objects({ schema_name: "public", object_type: "table" });
const details = await get_object_details({ schema_name: "public", object_name: "users", object_type: "table" });
Top resource-intensive queries (recommended default):
const top = await get_top_queries({ sort_by: "resources" });
// Returns queries consuming >5% of ANY resource dimension (CPU, I/O, WAL, etc.)
Hypothetical Index (requires HypoPG, cannot use with analyze:true):
const baseline = await explain_query({ sql: "SELECT * FROM orders WHERE customer_id = $1" });
const withIndex = await explain_query({
sql: "SELECT * FROM orders WHERE customer_id = $1",
hypothetical_indexes: [{ table: "orders", columns: ["customer_id"], using: "btree" }]
});
// Use $1, $2 etc. for parameterized queries (bind variables)
Targeted health check:
const health = await analyze_db_health({ health_type: "index,vacuum,buffer" });
// Only run specific checks instead of "all" to reduce noise
Schema relationships:
const rels = await analyze_schema_relationships();
// Cross-schema FK dependencies, hub tables, isolated schemas
Workflows
Slow query (decision tree):
explain_query({ sql, analyze: false }) - get estimated plan
- If total cost < 1000: query is likely fine, check if issue is elsewhere
- If cost 1000-50000:
explain_query({ sql, analyze: true }) - get actual timings
- If cost > 50000 or seq scan on large table: test with
hypothetical_indexes (requires HypoPG)
- If index helps:
analyze_query_indexes({ queries: [sql] }) for formal recommendation
- If no index helps: escalate to query rewrite or schema change
Workload optimization (use resources sort):
get_top_queries({ sort_by: "resources" }) - find queries consuming >5% of any resource (CPU, buffer reads, dirty pages, WAL)
- For each flagged query:
explain_query() to identify bottleneck
analyze_workload_indexes({ method: "dta" }) for index recommendations across workload
Health check (targeted deep-dives):
analyze_db_health({ health_type: "all" }) - initial scan
- Based on findings, deep-dive with targeted tools:
- Index issues →
analyze_db_health({ health_type: "index" }) shows invalid/duplicate/bloated/unused indexes
- Vacuum issues →
analyze_vacuum_requirements() for 6-phase bloat analysis with maintenance commands
- Connection issues →
analyze_db_health({ health_type: "connection" }) for pool utilization
- Buffer issues →
analyze_db_health({ health_type: "buffer" }) for cache hit rates (index + table)
- Replication issues →
analyze_db_health({ health_type: "replication" }) for lag and slot health
New database assessment:
- Run discovery script (auto-calls
get_database_overview())
- Review security score and recommendations from discovery output
analyze_schema_relationships() - understand cross-schema dependencies
get_top_queries({ sort_by: "resources" }) - identify workload hotspots
analyze_db_health({ health_type: "all" }) - full health scan
analyze_workload_indexes({ method: "dta" }) - index optimization opportunities
Blocking queries: get_blocking_queries() - run immediately when investigating locks; also useful proactively (returns deadlock stats, contention hotspots, and recommendations even when no active blocking exists)
Multi-table query: Phase 1: get_object_details() for ALL tables → [CHECKPOINT] → Phase 2: query with verified columns
Incident triage (general entry point):
get_blocking_queries() — check active lock contention first (time-sensitive, always safe)
analyze_db_health({ health_type: "connection,buffer,vacuum" }) — quick health snapshot
- If slow queries, timeouts, high CPU → follow Performance incident below
- If missing data, wrong counts, usage checks → follow Data investigation below
- If unclear → run both in sequence
Performance incident:
get_blocking_queries() — active locks and deadlock stats
analyze_db_health({ health_type: "connection" }) — connection pool saturation
get_top_queries({ sort_by: "resources" }) — resource-heavy queries
- For suspect queries:
explain_query({ sql, analyze: false }) — plan without adding load
- If vacuum/bloat suspected:
analyze_vacuum_requirements() — maintenance recommendations
- Report findings with severity and recommended actions
Data investigation (usage checks, missing data, verification):
- Run discovery script if not cached — confirm table names and schema
get_object_details() for ALL relevant tables — [CHECKPOINT: verify actual column names]
execute_sql() — query using ONLY verified columns from step 2
- If investigating relationships:
analyze_schema_relationships() — FK chains, cascade effects
- If multi-replica and stale data suspected:
analyze_db_health({ health_type: "replication" }) — check lag
- Report with evidence (query results, row counts)
Parameters
explain_query:
sql (string) - supports bind variables: $1, $2 for parameterized queries
analyze (bool, default: false) - runs query for real statistics
hypothetical_indexes ([{table, columns, using?}]) - requires HypoPG extension
- CONSTRAINT:
analyze: true + hypothetical_indexes cannot be used together (returns error)
get_top_queries:
sort_by (default: "resources") - resources uses multi-dimensional blend: includes queries where ANY of 5 fractions (exec time, buffer access, buffer reads, dirty pages, WAL bytes) exceeds 5% of workload total. total_time and mean_time rank by execution time only
limit (int, default: 10) - only applies to total_time and mean_time sorts
analyze_db_health:
health_type (default: "all") - comma-separated list of checks:
index: invalid, duplicate, bloated, and unused indexes (4 sub-checks)
connection: connection count and pool utilization
vacuum: transaction ID wraparound danger
sequence: sequences approaching max value
replication: replication lag and slot health
buffer: cache hit rates for both indexes and tables (2 sub-checks)
constraint: invalid (not-validated) constraints
all: runs all above
analyze_vacuum_requirements (6 phases):
- Vacuum summary: total tables, never-vacuumed count, dead tuples aggregate
- Table bloat analysis with severity: CRITICAL (>40%), HIGH (>20%), MEDIUM (>10%), LOW (>5%), HEALTHY (<=5%)
- Autovacuum config: per-table threshold vs actual dead tuples, status (OVERDUE/APPROACHING/HEALTHY)
- Vacuum performance: counts, modifications per vacuum, time since last vacuum
- Maintenance recommendations: generates VACUUM FULL, VACUUM, or ANALYZE commands with priority
- Critical issues: transaction ID wraparound risk (XID age > 1.5B), config tuning suggestions
analyze_query_indexes:
queries (string[]) - max 10 queries per call
max_index_size_mb (int, default: 10000)
method ("dta" | "llm", default: "dta")
analyze_*_indexes: method = dta (Pareto-optimal) | llm
Safety
| Risk |
Operations |
Behavior |
| LOW |
list_*, get_object_details, explain(analyze:false), get_blocking_queries |
Always safe |
| MEDIUM |
get_database_overview, analyze_db_health, analyze_schema_relationships |
Use sampling on large DBs |
| HIGH |
explain(analyze:true) |
Check cost first with analyze:false |
| CRITICAL |
execute_sql(INSERT/UPDATE/DELETE/DDL) |
Require confirmation |
Common Errors
| Error |
Fix |
column X does not exist |
Run get_object_details() - never guess column names |
relation does not exist |
Refresh table list with list_objects() |
permission denied |
Use read-only queries |
pg_stat_statements not found |
Request DBA to enable extension |
timeout |
Reduce scope: max_tables: 100, use sampling |
HypoPG not installed |
hypothetical_indexes requires the HypoPG extension - request DBA to install, or skip hypothetical analysis |
Cannot use analyze and hypothetical indexes together |
Remove analyze: true when using hypothetical_indexes - they are mutually exclusive |
up to 10 queries to analyze |
analyze_query_indexes accepts max 10 queries - split larger batches into multiple calls |
Output Format
Present results as a structured report:
Analyzing Postgres Report
═════════════════════════
Resources discovered: [count]
Resource Status Key Metric Issues
──────────────────────────────────────────────
[name] [ok/warn] [value] [findings]
Summary: [total] resources | [ok] healthy | [warn] warnings | [crit] critical
Action Items: [list of prioritized findings]
Target ≤50 lines of output. Use tables for multi-resource comparisons.
Anti-Hallucination Rules
- NEVER assume resource names — always discover via CLI/API in Phase 1 before referencing in Phase 2.
- NEVER fabricate metric names or dimensions — verify against the service documentation or
--help output.
- NEVER mix CLI commands between service versions — confirm which version/API you are targeting.
- ALWAYS use the discovery → verify → analyze chain — every resource referenced must have been discovered first.
- ALWAYS handle empty results gracefully — an empty response is valid data, not an error to retry.
Counter-Rationalizations
| Shortcut |
Counter |
Why |
| "I'll skip discovery and check known resources" |
Always run Phase 1 discovery first |
Resource names change, new resources appear — assumed names cause errors |
| "The user only asked for a quick check" |
Follow the full discovery → analysis flow |
Quick checks miss critical issues; structured analysis catches silent failures |
| "Default configuration is probably fine" |
Audit configuration explicitly |
Defaults often leave logging, security, and optimization features disabled |
| "Metrics aren't needed for this" |
Always check relevant metrics when available |
API/CLI responses show current state; metrics reveal trends and intermittent issues |
| "I don't have access to that" |
Try the command and report the actual error |
Assumed permission failures prevent useful investigation; actual errors are informative |
1---2name: analyzing-postgres3description: PostgreSQL database analysis, performance tuning, and health monitoring. You MUST read this entire skill document before executing any PostgreSQL operations — it contains mandatory workflows, safety constraints, and two-phase execution rules that prevent common errors like hallucinated column names and unsafe queries.4---56# Analyzing PostgreSQL78## Discovery910<critical>11**If no `[cached_from_skill:analyzing-postgres:discover]` context exists, run discovery first:**12```bash13bun run ./_skills/connections/postgres/analyzing-postgres/scripts/discover.ts14bun run ./_skills/connections/postgres/analyzing-postgres/scripts/discover.ts --max-tables 100 --sampling true --timeout 12015bun run ./_skills/connections/postgres/analyzing-postgres/scripts/discover.ts --alias prod-db16```17For multi-instance setups, run discovery per-alias. If no alias is provided with multiple instances, the error will list available aliases.18Output is auto-cached.19</critical>2021**What discovery provides:**22- Database overview from `get_database_overview()`:23 - `schemas`: List of database schemas24 - `tables`: Tables with names, row counts, sizes, and column information25 - `indexes`: Index information and statistics26 - `relationships`: Foreign key relationships between tables27 - `size_info`: Database and table size metrics28 - `security`: Security score (0-100), superuser count, SSL status, issues and recommendations29 - `performance_hotspots`: Tables with high sequential scans, dead tuples, bloat, or high modification rates3031**Discovery options (script defaults, NOT MCP server defaults):**32- `--max-tables N`: Limit tables analyzed (script default: 50, MCP server default: 500)33- `--sampling true`: Use sampling for large tables (script default: false, MCP server default: true)34- `--timeout N`: Timeout in seconds (script default: 60, MCP server default: 300)35- `--alias <name>`: Target a specific postgres instance (required for multi-instance workspaces)3637**Why run discovery:**38- Get actual table/column names (never guess - they vary between databases)39- Understand relationships before writing JOINs40- Identify large tables that need sampling41- Know schema structure before executing SQL4243**ALWAYS use `format()` for output (40-60% token savings):**44```typescript45import { format } from "@connections/_utils/format";46console.log(format(result)); // CORRECT47console.log(JSON.stringify(result, null, 2)); // WRONG48```4950## Two-Phase Execution (MANDATORY)5152<critical>53**Discovery and query MUST be separate script executions.**5455**Phase 1 - Discovery Script:**56```typescript57const schema = await get_object_details({ schema_name: "public", object_name: "user", object_type: "table" });58console.log(format(schema));59// ⛔ STOP - End script here, read output60```6162**[CHECKPOINT: Read output, identify actual column names]**6364**Phase 2 - Query Script (NEW execution):**65```typescript66// Use ONLY verified column names from Phase 167const results = await execute_sql({ sql: `SELECT verified_col FROM ...` });68```6970❌ **FORBIDDEN:** `get_object_details()` + `execute_sql()` in same script71</critical>7273| Bad ❌ | Good ✅ | Why |74|--------|---------|-----|75| Discovery + query in one script | Two separate scripts | Prevents hallucinated column names |76| Assuming `user_id` exists | Run `get_object_details()` first | Foreign key naming varies |77| `created_at`, `updated_at` | Verify columns exist | May be named differently |78| Writing JOINs without discovery | Discover ALL tables first | Relationships vary |7980## Tools (13 total)8182**Schema (3):** `list_schemas()`, `list_objects(schema, type?)`, `get_object_details(schema, name, type?)`8384**Query (1):** `execute_sql(sql)` - read-only in restricted mode8586**Performance (4):**87- `explain_query(sql, analyze?, hypothetical_indexes?)` - CONSTRAINT: `analyze` + `hypothetical_indexes` cannot be used together; `hypothetical_indexes` requires HypoPG extension88- `get_top_queries(sort_by?, limit?)` - default `sort_by: "resources"` (multi-dimensional blend, not just time)89- `analyze_workload_indexes(max_size_mb?, method?)` - analyzes top queries from pg_stat_statements90- `analyze_query_indexes(queries[], max_size_mb?, method?)` - CONSTRAINT: max 10 queries per call9192**Health (3):**93- `analyze_db_health(type?)` - accepts comma-separated types (e.g. `"index,vacuum"`)94- `get_blocking_queries()` - works even without active blocking (returns deadlock analysis, contention hotspots, proactive recommendations)95- `analyze_vacuum_requirements()` - 6-phase analysis with severity levels9697**Advanced (2):**98- `get_database_overview(max_tables?, sampling_mode?, timeout?)` - includes security score, performance hotspots, relationship mapping99- `analyze_schema_relationships()` - inter-schema dependency analysis with visual representation data100101## Quick Patterns102103**Discover:**104```typescript105const schemas = await list_schemas();106const tables = await list_objects({ schema_name: "public", object_type: "table" });107const details = await get_object_details({ schema_name: "public", object_name: "users", object_type: "table" });108```109110**Top resource-intensive queries (recommended default):**111```typescript112const top = await get_top_queries({ sort_by: "resources" });113// Returns queries consuming >5% of ANY resource dimension (CPU, I/O, WAL, etc.)114```115116**Hypothetical Index (requires HypoPG, cannot use with analyze:true):**117```typescript118const baseline = await explain_query({ sql: "SELECT * FROM orders WHERE customer_id = $1" });119const withIndex = await explain_query({120 sql: "SELECT * FROM orders WHERE customer_id = $1",121 hypothetical_indexes: [{ table: "orders", columns: ["customer_id"], using: "btree" }]122});123// Use $1, $2 etc. for parameterized queries (bind variables)124```125126**Targeted health check:**127```typescript128const health = await analyze_db_health({ health_type: "index,vacuum,buffer" });129// Only run specific checks instead of "all" to reduce noise130```131132**Schema relationships:**133```typescript134const rels = await analyze_schema_relationships();135// Cross-schema FK dependencies, hub tables, isolated schemas136```137138## Workflows139140**Slow query** (decision tree):1411. `explain_query({ sql, analyze: false })` - get estimated plan1422. If total cost < 1000: query is likely fine, check if issue is elsewhere1433. If cost 1000-50000: `explain_query({ sql, analyze: true })` - get actual timings1444. If cost > 50000 or seq scan on large table: test with `hypothetical_indexes` (requires HypoPG)1455. If index helps: `analyze_query_indexes({ queries: [sql] })` for formal recommendation1466. If no index helps: escalate to query rewrite or schema change147148**Workload optimization** (use `resources` sort):1491. `get_top_queries({ sort_by: "resources" })` - find queries consuming >5% of any resource (CPU, buffer reads, dirty pages, WAL)1502. For each flagged query: `explain_query()` to identify bottleneck1513. `analyze_workload_indexes({ method: "dta" })` for index recommendations across workload152153**Health check** (targeted deep-dives):1541. `analyze_db_health({ health_type: "all" })` - initial scan1552. Based on findings, deep-dive with targeted tools:156 - Index issues → `analyze_db_health({ health_type: "index" })` shows invalid/duplicate/bloated/unused indexes157 - Vacuum issues → `analyze_vacuum_requirements()` for 6-phase bloat analysis with maintenance commands158 - Connection issues → `analyze_db_health({ health_type: "connection" })` for pool utilization159 - Buffer issues → `analyze_db_health({ health_type: "buffer" })` for cache hit rates (index + table)160 - Replication issues → `analyze_db_health({ health_type: "replication" })` for lag and slot health161162**New database assessment:**1631. Run discovery script (auto-calls `get_database_overview()`)1642. Review security score and recommendations from discovery output1653. `analyze_schema_relationships()` - understand cross-schema dependencies1664. `get_top_queries({ sort_by: "resources" })` - identify workload hotspots1675. `analyze_db_health({ health_type: "all" })` - full health scan1686. `analyze_workload_indexes({ method: "dta" })` - index optimization opportunities169170**Blocking queries**: `get_blocking_queries()` - run immediately when investigating locks; also useful proactively (returns deadlock stats, contention hotspots, and recommendations even when no active blocking exists)171172**Multi-table query**: Phase 1: `get_object_details()` for ALL tables → [CHECKPOINT] → Phase 2: query with verified columns173174**Incident triage** (general entry point):1751. `get_blocking_queries()` — check active lock contention first (time-sensitive, always safe)1762. `analyze_db_health({ health_type: "connection,buffer,vacuum" })` — quick health snapshot1773. If slow queries, timeouts, high CPU → follow **Performance incident** below1784. If missing data, wrong counts, usage checks → follow **Data investigation** below1795. If unclear → run both in sequence180181**Performance incident:**1821. `get_blocking_queries()` — active locks and deadlock stats1832. `analyze_db_health({ health_type: "connection" })` — connection pool saturation1843. `get_top_queries({ sort_by: "resources" })` — resource-heavy queries1854. For suspect queries: `explain_query({ sql, analyze: false })` — plan without adding load1865. If vacuum/bloat suspected: `analyze_vacuum_requirements()` — maintenance recommendations1876. Report findings with severity and recommended actions188189**Data investigation** (usage checks, missing data, verification):1901. Run discovery script if not cached — confirm table names and schema1912. `get_object_details()` for ALL relevant tables — [CHECKPOINT: verify actual column names]1923. `execute_sql()` — query using ONLY verified columns from step 21934. If investigating relationships: `analyze_schema_relationships()` — FK chains, cascade effects1945. If multi-replica and stale data suspected: `analyze_db_health({ health_type: "replication" })` — check lag1956. Report with evidence (query results, row counts)196197## Parameters198199**explain_query:**200- `sql` (string) - supports bind variables: `$1`, `$2` for parameterized queries201- `analyze` (bool, default: false) - runs query for real statistics202- `hypothetical_indexes` ([{table, columns, using?}]) - requires HypoPG extension203- CONSTRAINT: `analyze: true` + `hypothetical_indexes` cannot be used together (returns error)204205**get_top_queries:**206- `sort_by` (default: `"resources"`) - `resources` uses multi-dimensional blend: includes queries where ANY of 5 fractions (exec time, buffer access, buffer reads, dirty pages, WAL bytes) exceeds 5% of workload total. `total_time` and `mean_time` rank by execution time only207- `limit` (int, default: 10) - only applies to `total_time` and `mean_time` sorts208209**analyze_db_health:**210- `health_type` (default: `"all"`) - comma-separated list of checks:211 - `index`: invalid, duplicate, bloated, and unused indexes (4 sub-checks)212 - `connection`: connection count and pool utilization213 - `vacuum`: transaction ID wraparound danger214 - `sequence`: sequences approaching max value215 - `replication`: replication lag and slot health216 - `buffer`: cache hit rates for both indexes and tables (2 sub-checks)217 - `constraint`: invalid (not-validated) constraints218 - `all`: runs all above219220**analyze_vacuum_requirements** (6 phases):2211. Vacuum summary: total tables, never-vacuumed count, dead tuples aggregate2222. Table bloat analysis with severity: CRITICAL (>40%), HIGH (>20%), MEDIUM (>10%), LOW (>5%), HEALTHY (<=5%)2233. Autovacuum config: per-table threshold vs actual dead tuples, status (OVERDUE/APPROACHING/HEALTHY)2244. Vacuum performance: counts, modifications per vacuum, time since last vacuum2255. Maintenance recommendations: generates VACUUM FULL, VACUUM, or ANALYZE commands with priority2266. Critical issues: transaction ID wraparound risk (XID age > 1.5B), config tuning suggestions227228**analyze_query_indexes:**229- `queries` (string[]) - max 10 queries per call230- `max_index_size_mb` (int, default: 10000)231- `method` ("dta" | "llm", default: "dta")232233**analyze_*_indexes:** `method` = dta (Pareto-optimal) | llm234235## Safety236237| Risk | Operations | Behavior |238|------|------------|----------|239| LOW | `list_*`, `get_object_details`, `explain(analyze:false)`, `get_blocking_queries` | Always safe |240| MEDIUM | `get_database_overview`, `analyze_db_health`, `analyze_schema_relationships` | Use sampling on large DBs |241| HIGH | `explain(analyze:true)` | Check cost first with analyze:false |242| CRITICAL | `execute_sql(INSERT/UPDATE/DELETE/DDL)` | Require confirmation |243244## Common Errors245246| Error | Fix |247|-------|-----|248| `column X does not exist` | Run `get_object_details()` - never guess column names |249| `relation does not exist` | Refresh table list with `list_objects()` |250| `permission denied` | Use read-only queries |251| `pg_stat_statements not found` | Request DBA to enable extension |252| `timeout` | Reduce scope: `max_tables: 100`, use sampling |253| `HypoPG not installed` | `hypothetical_indexes` requires the HypoPG extension - request DBA to install, or skip hypothetical analysis |254| `Cannot use analyze and hypothetical indexes together` | Remove `analyze: true` when using `hypothetical_indexes` - they are mutually exclusive |255| `up to 10 queries to analyze` | `analyze_query_indexes` accepts max 10 queries - split larger batches into multiple calls |256257## Output Format258259Present results as a structured report:260```261Analyzing Postgres Report262═════════════════════════263Resources discovered: [count]264265Resource Status Key Metric Issues266──────────────────────────────────────────────267[name] [ok/warn] [value] [findings]268269Summary: [total] resources | [ok] healthy | [warn] warnings | [crit] critical270Action Items: [list of prioritized findings]271```272273Target ≤50 lines of output. Use tables for multi-resource comparisons.274275## Anti-Hallucination Rules2762771. **NEVER assume resource names** — always discover via CLI/API in Phase 1 before referencing in Phase 2.2782. **NEVER fabricate metric names or dimensions** — verify against the service documentation or `--help` output.2793. **NEVER mix CLI commands between service versions** — confirm which version/API you are targeting.2804. **ALWAYS use the discovery → verify → analyze chain** — every resource referenced must have been discovered first.2815. **ALWAYS handle empty results gracefully** — an empty response is valid data, not an error to retry.282283## Counter-Rationalizations284285| Shortcut | Counter | Why |286|----------|---------|-----|287| "I'll skip discovery and check known resources" | Always run Phase 1 discovery first | Resource names change, new resources appear — assumed names cause errors |288| "The user only asked for a quick check" | Follow the full discovery → analysis flow | Quick checks miss critical issues; structured analysis catches silent failures |289| "Default configuration is probably fine" | Audit configuration explicitly | Defaults often leave logging, security, and optimization features disabled |290| "Metrics aren't needed for this" | Always check relevant metrics when available | API/CLI responses show current state; metrics reveal trends and intermittent issues |291| "I don't have access to that" | Try the command and report the actual error | Assumed permission failures prevent useful investigation; actual errors are informative |292