sqlplan-compare — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited sqlplan-compare (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Identify what changed between two execution plans for the same query — one known-good (baseline) and one regressed (new). Produce a side-by-side diff that explains why the query is slower and what to fix. Applies 20 regression checks (C1–C20).
Accept any of:
.sqlplan file paths: baseline.sqlplan and new.sqlplan.sqlplan XML pasted inline, labeled Baseline and New| Metric | Where to Find | Signal |
|---|---|---|
| StatementSubTreeCost | StmtSimple/@StatementSubTreeCost | > 2× increase = regression |
| DegreeOfParallelism | QueryPlan/@DegreeOfParallelism | DOP drop = serial plan forced |
| GrantedMemory (KB) | MemoryGrantInfo/@GrantedMemory | > 2× increase = cardinality inflation |
| MaxUsedMemory (KB) | MemoryGrantInfo/@MaxUsedMemory | Used > Granted = spill |
| CardinalityEstimationModelVersion | QueryPlan/@CardinalityEstimationModelVersion | Version drop = compat level change |
| CompileCPU (ms) | StmtSimple/@CompileCPU | > 2× increase = optimizer struggling |
| MissingIndexGroup count | <MissingIndexes> children | New suggestions = indexes dropped |
Compare these for each plan:
<Warnings> elements in the new plan not present in baselineSpillToTempDb entriesPlanAffectingConvert entriesNoJoinPredicate flagsactualRows on the probe side > 10,000GrantedMemory > baseline GrantedMemory × 2GrantedMemory < baseline GrantedMemory / 2 AND MaxUsedMemory > GrantedMemory in new planDegreeOfParallelism > 1 AND new plan DegreeOfParallelism = 1NonParallelPlanReason if presentSpillToTempDb present in new plan but not in baselinecostPercentMissingIndexGroup in the new plan is not present in the baseline AND Impact > 50costPercent ≥ 10%CardinalityEstimationModelVersion in new plan < baselineAdaptiveThresholdRows attribute on an Adaptive Join operator differs between plans — SQL 2017+executionMode="Batch"; new plan has only executionMode="Row" — SQL 2017+ (Columnstore), SQL 2019+ (Rowstore)DISABLE_BATCH_MODE_ON_ROWSTORE hint added; compat level dropped below 150; scalar UDF or incompatible operator introducedPlanAffectingConvert element present in new plan but absent from baselineactualRows / estimateRows ratio across all operators (with actualRows > 100) increased by > 10× between plans — requires actual execution plansCompileCPU in new plan > baseline CompileCPU × 3PlanGuideName attribute present in new plan but absent from baselineEager Index Spool operator present in new plan but absent from baselineParameterSensitivePredicate dispatcher node present in new plan but absent from baseline — SQL 2022+ onlyserver.db.schema.table) or a linked server operator absent from baselineIf the SQL Server version is known — from the ServerVersion attribute in the plan XML or stated by the user — read VERSION_COMPATIBILITY.md (~/.claude/skills/VERSION_COMPATIBILITY.md if installed, or skills/VERSION_COMPATIBILITY.md from the repo). If unavailable, skip silently. For checks whose minimum version exceeds the instance version: verbose mode → log as SKIP (version: requires SQL 20XX+, instance is SQL 20YY); standard report → omit entirely. Do not suppress NOT ASSESSED rows from missing input — only suppress version-inapplicable checks.
## Execution Plan Comparison
### Summary Table
| Metric | Baseline | New Plan | Change |
|--------|----------|----------|--------|
| Statement Cost | X | Y | +Z% |
| DOP | X | Y | ↓ or ↑ |
| Memory Grant (MB) | X | Y | +Z% |
| Join Types (Hash/NL/Merge) | X/Y/Z | A/B/C | — |
| Spills | None | 2 (Sort, Hash) | ⚠ New |
| Missing Indexes | N | M | +K |
### Regression Findings
**[R1 — C2, NodeId 8→12] Finding Name**
- **Was:** [baseline operator/value]
- **Now:** [new plan operator/value]
- **Why:** [root cause — what changed and why it caused this shift]
- **Fix:** [concrete action with code if applicable]
The bracket suffix (`— C2`, `— C5`) is the check ID from the C1–C10 checks above that fired. Include the NodeId from both plans for each changed operator (e.g., `NodeId 8→12`). If NodeIds are absent, use operator name + table name instead.
Findings reference each other where one is the root cause of another (e.g., "consequence of R1").
Do not use Critical/Warning severity tiers — regression findings are ranked by fix priority, not severity.
### Root Cause Summary
[One paragraph synthesising all findings into a single root cause statement.
Example: "A single change caused the entire regression: the @status parameter type changed from
VARCHAR to NVARCHAR, introducing an implicit conversion on the Status column. This made the
index non-sargable → cardinality collapsed → Hash Match replaced Nested Loops → 2 GB memory
grant → 3.2-second wait before execution begins."]
-- Recommended fix (step-by-step code block) -- Step 1: ... -- Step 2: ...
### Confirmed Stable (Unchanged)
[List key operators, DOP, CE version, compile time that are the same in both plans.
This gives confidence the comparison is valid.]
---
*Analyzed by: [state the AI model and version you are running as, e.g. "Claude Sonnet 4.6", "DeepSeek R1", "GPT-4o"] · [current date and time in the user's local timezone, or UTC if timezone is unknown, e.g. "2026-05-16 20:15 NZST"]*`--brief` — Omit the Passed Checks table and attribution footer. Output the Summary, Findings, and Prioritized Fix Sequence sections only. Use when a quick scan of what fired is all that's needed.
`--critical-only` — Suppress Warning and Info findings. Show only Critical findings. The Passed Checks table is also omitted. Use when triaging an incident and only actionable blockers matter.
Both flags can be combined: --brief --critical-only produces the Summary section plus Critical findings only.
When neither flag is present, produce the full report as documented above.
When the user's request includes --verbose, --trace, or the word verbose:
1. Append a `## Check Evaluation Log` section after the Passed Checks table.
Include one row for every check in this skill's ruleset, in check-ID order:
| Check | Evidence | Threshold | Result |
|---|---|---|---|
| [ID — Name] | [key attribute(s) and value found, or "absent"] | [threshold or condition] | PASS / FIRE → [severity] / NOT ASSESSED |
Result conventions:
PASS — attribute present, threshold not met**FIRE → Critical/Warning/Info** — threshold met; bold to distinguish from passesNOT ASSESSED — required attribute absent from input2. Save both files to the current working directory using the Write tool:
output/<skill-name>/<YYYY-MM-DD-HHmmss>-<input-prefix>/analysis.md ← full report output/<skill-name>/<YYYY-MM-DD-HHmmss>-<input-prefix>/trace.md ← Check Evaluation Log
Derive <input-prefix>:
horrible.sqlplan → horrible)runSanitize: alphanumeric + hyphens/underscores only, max 32 chars.
File headers: analysis.md → # Analysis — <skill-name> / # Input: <first 80 chars> / # Generated: <UTC timestamp> trace.md → # Check Evaluation Log — <skill-name> / # Input: <first 80 chars> / # Generated: <UTC timestamp>
Create directories as needed. When --verbose is not present, write nothing to disk.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.