@dennisrongo/skills 0.5.0 → 0.7.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.
- package/README.md +2 -0
- package/package.json +1 -1
- package/skills/_template/SKILL.md +8 -0
- package/skills/code-review/SKILL.md +25 -4
- package/skills/codebase-explainer/SKILL.md +13 -6
- package/skills/conventional-commits/SKILL.md +10 -3
- package/skills/diagnose/SKILL.md +31 -0
- package/skills/dotnet-onion-api/SKILL.md +40 -3
- package/skills/grill-with-docs/SKILL.md +19 -3
- package/skills/handoff/SKILL.md +11 -1
- package/skills/improve-codebase-architecture/SKILL.md +11 -1
- package/skills/nextjs-app-router/SKILL.md +36 -3
- package/skills/plan-and-build/SKILL.md +11 -1
- package/skills/pr-review/SKILL.md +25 -1
- package/skills/ship-it/SKILL.md +15 -5
- package/skills/sql-review/SKILL.md +253 -0
- package/skills/task-executor/SKILL.md +15 -2
- package/skills/tauri-2-app/SKILL.md +28 -4
- package/skills/think-like-fable/SKILL.md +101 -0
- package/skills/write-a-skill/SKILL.md +29 -0
|
@@ -60,6 +60,17 @@ Versions are resolved at scaffold time, never hard-pasted into the skill. Before
|
|
|
60
60
|
3. Quote the resolved versions back to the user before generating, so they can object.
|
|
61
61
|
4. Prefer the latest **stable** Next.js (App Router GA from 13.4; resolve current).
|
|
62
62
|
5. Default to **pnpm** if `pnpm` is present, otherwise **npm**. Match `packageManager` in `package.json`.
|
|
63
|
+
6. Concrete resolution command when context7 is unavailable: `npm view <pkg> version` (e.g. `npm view next version`; `npm view next-auth dist-tags` — plain `npm view next-auth version` returns the latest *stable*, which may still be v4; never scaffold v4). Never write a version you did not just resolve this session — no versions from memory, no `latest`/`^latest`, no invented numbers.
|
|
64
|
+
7. **API-drift guard.** The version you resolved in this step decides the API shape — training-data memory is the least trustworthy source in this workflow. Highest-risk hallucination zones:
|
|
65
|
+
- NextAuth **v4 patterns are POISON here**: `authOptions` export, `getServerSession`, `NEXTAUTH_*` env vars, an options object in the `[...nextauth]` route. v5 is `const { handlers, auth, signIn, signOut } = NextAuth(config)` in `src/auth.ts`, with `AUTH_*` env vars.
|
|
66
|
+
- App Router signatures that changed across majors — e.g. in Next 15 `params`/`searchParams` are **Promises** in dynamic route segments and Route Handler contexts; check the resolved major before writing `params.id`. In `'use client'` pages read route params with `useParams()` from `next/navigation`, not the `params` prop.
|
|
67
|
+
- RTK Query / Redux Toolkit option names (`fetchBaseQuery` options, `providesTags`/`invalidatesTags` shapes) and the Prisma client API for the resolved major.
|
|
68
|
+
- Tailwind majors: v4 dropped the `tailwind.config.ts`-first setup for CSS-first config (`@import "tailwindcss"` + `@tailwindcss/postcss`); v3 uses `tailwind.config.ts` + `postcss.config.js`. Emit exactly one style — the one matching the resolved major — never a mix.
|
|
69
|
+
- Zod majors: v4 moved/renamed APIs (top-level `z.email()`, reworked error customization). A v3-only signature against a resolved v4 is a hallucination — check the installed types under `node_modules/zod`.
|
|
70
|
+
- Prisma majors: the `generator` block shape and default client output location changed across majors. After `prisma generate`, confirm the actual import path from what was generated — don't assume `@prisma/client` from memory.
|
|
71
|
+
- The shadcn CLI is `npx shadcn@latest` (`init` / `add`); the old `shadcn-ui` package name is dead.
|
|
72
|
+
|
|
73
|
+
If you are not certain a symbol exists in the resolved version, verify it (read the installed types under `node_modules/<pkg>/`, or check docs) **before** writing code that depends on it.
|
|
63
74
|
|
|
64
75
|
### Step 2 — Gather inputs (ask once, in one batch)
|
|
65
76
|
|
|
@@ -85,6 +96,7 @@ Use the **templates in [`references/templates/`](references/templates/)** as the
|
|
|
85
96
|
- Create directories before files. On Windows shell use PowerShell `New-Item -ItemType Directory -Force`.
|
|
86
97
|
- Do **not** run `create-next-app` to bootstrap — write files directly from templates so the layout matches [`references/folder-layout.md`](references/folder-layout.md). Use `npm`/`pnpm install` after `package.json` is written.
|
|
87
98
|
- Initialize shadcn/ui by writing `components.json` from [`references/templates/components-json.md`](references/templates/components-json.md) and `src/components/ui/` components incrementally as needed (button, input, form, label, dialog, toast, etc.) — don't blanket-install every Radix primitive.
|
|
99
|
+
- If a generator IS run (`shadcn` CLI, `prisma init`, or `create-next-app` at the user's insistence) and its output differs from this skill's layout: **the framework wins on file locations it mandates** (`middleware.ts` placement, `app/` conventions, `prisma/schema.prisma`), **this skill wins on everything the framework doesn't mandate** (redux layout, route groups, `_components`/`_hooks`). Reconcile deliberately and list every deviation in your report — never force the skill layout over a framework requirement, never silently abandon the skill's patterns.
|
|
88
100
|
|
|
89
101
|
### Step 4 — Database setup
|
|
90
102
|
|
|
@@ -97,6 +109,7 @@ After `package.json` and `prisma/schema.prisma` are written:
|
|
|
97
109
|
- **Never run `prisma db push` against a production-shaped database.** Use migrations.
|
|
98
110
|
- **Never run `prisma migrate reset` without explicit user confirmation** — it drops the DB.
|
|
99
111
|
5. Generate `AUTH_SECRET` for the user: `pnpm exec auth secret` (Auth.js CLI) or `openssl rand -base64 32`. Put it in `.env.local` (NOT `.env.example`).
|
|
112
|
+
6. Order is load-bearing: `prisma generate` MUST run before `pnpm typecheck` / `pnpm build` — the `@prisma/client` types don't exist until generated. If typecheck explodes with missing Prisma types, you skipped or mis-ordered this step; run generate, don't start rewriting imports.
|
|
100
113
|
|
|
101
114
|
### Step 5 — Verify and report
|
|
102
115
|
|
|
@@ -104,6 +117,8 @@ After `package.json` and `prisma/schema.prisma` are written:
|
|
|
104
117
|
- `pnpm lint`. Must exit 0.
|
|
105
118
|
- `pnpm test` (Vitest) if any tests were generated. Must exit 0.
|
|
106
119
|
- `pnpm build`. Must succeed.
|
|
120
|
+
- **A scaffold that hasn't built is not delivered.** Paste the actual proof lines in your report (Next's `✓ Compiled successfully`, Vitest's `Tests N passed`). Never report success from memory of the steps you intended, and never report partial success as success — if something is red, say exactly what is red.
|
|
121
|
+
- **CLI failure protocol** (applies to `pnpm install`, `prisma migrate`, `pnpm build`, etc.): read the full error output, change exactly one thing, retry once. If the same step fails twice, stop scaffolding and surface the verbatim error to the user — do not keep generating files on a broken base, and do not re-run the identical command hoping for a different result.
|
|
107
122
|
- Reply with a short summary: project path, versions chosen, route groups, env vars to set (especially `AUTH_SECRET` and `DATABASE_URL`), next steps (`pnpm dev`, log in with the seeded test user if Credentials was chosen).
|
|
108
123
|
|
|
109
124
|
## Project layout (canonical)
|
|
@@ -302,7 +317,7 @@ For feature `{{Feature}}` in route group `{{Group}}` (e.g. `(app)`):
|
|
|
302
317
|
- `_components/{{Feature}}Form.tsx` — `'use client'`, uses `useForm` + `zodResolver`, wraps `<UnsavedChangesWarning>`, dispatches create/update mutation on submit.
|
|
303
318
|
7. **Tests**: `tests/unit/{{feature}}.schema.test.ts` (Zod schema valid + invalid). Optional handler test that mocks `db` and `auth`.
|
|
304
319
|
|
|
305
|
-
After generating: `pnpm typecheck && pnpm test && pnpm build`.
|
|
320
|
+
After generating: `pnpm typecheck && pnpm test && pnpm build`. Apply the Step-5 proof-and-failure protocol — paste the green lines; the same step failing twice = stop and surface the verbatim error.
|
|
306
321
|
|
|
307
322
|
### Mode 3 — `add-api-slice`
|
|
308
323
|
|
|
@@ -315,10 +330,27 @@ For domain `{{domain}}` (e.g. `customers`):
|
|
|
315
330
|
5. Add the Prisma model(s) and migrate if needed.
|
|
316
331
|
6. **Do not** create a new `createApi(...)`. One base `api`, many injected slices.
|
|
317
332
|
|
|
318
|
-
After generating: `pnpm typecheck && pnpm build` must pass.
|
|
333
|
+
After generating: `pnpm typecheck && pnpm build` must pass — Step-5 proof-and-failure protocol applies.
|
|
319
334
|
|
|
320
335
|
## Verification checklist before reporting "done"
|
|
321
336
|
|
|
337
|
+
Run this as a **mechanical pass over the generated tree** — grep, don't recall. The forbidden list is easy to hold at file 1 and forgotten by file 30; do not answer any item from memory of what you intended to write. Start with this grep block — every command must return nothing:
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
grep -rn "'use server'" src/ # Server Actions
|
|
341
|
+
grep -rn "export default async function" src/app --include='page.tsx' # async pages
|
|
342
|
+
grep -rn "from '@/lib/db'" src/app --include='*.tsx' # DB access outside route.ts
|
|
343
|
+
grep -rn "fetch(" src/app src/components --include='*.tsx' # inline fetch in components
|
|
344
|
+
grep -rn "serializableCheck: false\|@ts-ignore\|as any" src/redux src/app/api
|
|
345
|
+
grep -rn "moment" src/ package.json # date-fns only
|
|
346
|
+
grep -rn "NEXTAUTH_\|getServerSession\|authOptions\|next-auth/next" src/ .env.example # NextAuth v4 leaking in
|
|
347
|
+
grep -rn "styled-components\|@emotion" src/ package.json # CSS-in-JS in a Tailwind project
|
|
348
|
+
grep -rn "localStorage\|sessionStorage" src/ # any token/session hit is a bug
|
|
349
|
+
grep -rn "dangerouslySetInnerHTML" src/ # zero on a fresh scaffold
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
A hit means fix it and re-run the grep — never rationalize it away. Then the full checklist:
|
|
353
|
+
|
|
322
354
|
- [ ] `pnpm install` succeeds.
|
|
323
355
|
- [ ] `pnpm exec prisma generate` succeeds.
|
|
324
356
|
- [ ] `pnpm exec prisma migrate dev --name init` succeeds (or the user confirmed they ran it manually).
|
|
@@ -330,7 +362,8 @@ After generating: `pnpm typecheck && pnpm build` must pass.
|
|
|
330
362
|
- [ ] Every `route.ts` file in `src/app/api/**` (except `[...nextauth]`) calls `await auth()` or `await requireSession()`.
|
|
331
363
|
- [ ] No `'use server'` directive anywhere. Grep: `grep -rn "'use server'" src/` returns nothing.
|
|
332
364
|
- [ ] No `async function .*Page` in any `page.tsx`. Pages are sync `'use client'`.
|
|
333
|
-
- [ ] No `serializableCheck: false`, `@ts-ignore`, `as any` in `src/redux/**` or `src/app/api
|
|
365
|
+
- [ ] No `serializableCheck: false`, `@ts-ignore`, `as any` in `src/redux/**` or `src/app/api/**` (covered by the grep block above).
|
|
366
|
+
- [ ] Exactly one `createApi(` in the codebase (the base `api`). Grep: `grep -rn "createApi(" src/` shows one hit.
|
|
334
367
|
- [ ] `middleware.ts` exists at project root; its `config.matcher` excludes `/api/auth` (NextAuth handles its own routes).
|
|
335
368
|
- [ ] `src/auth.ts` and `src/auth.config.ts` both exist. `auth.config.ts` has no `@/lib/db` import (Edge-safe).
|
|
336
369
|
- [ ] `.env` is **not** committed; `.env.example` is. `AUTH_SECRET` is **not** in `.env.example` (it's documented as required, with instructions to generate it).
|
|
@@ -32,6 +32,9 @@ If the feature is large, fuzzy, or still in discovery — defer to [`grill-with-
|
|
|
32
32
|
Rules:
|
|
33
33
|
|
|
34
34
|
- **One question at a time.** Wait for the answer before the next one. Use `AskUserQuestion` with 2–4 concrete options and a recommended pick first.
|
|
35
|
+
- **A question earns its slot only if different answers lead to different designs.** Before asking, name (to yourself) what changes per answer. If every answer leads to the same next step, don't ask it — generic checklist questions burn the user's patience without buying design information.
|
|
36
|
+
- ❌ "Should this endpoint be secure and handle errors gracefully?" — every answer is "yes"; nothing about the design forks.
|
|
37
|
+
- ✅ "When an order loses priority, do we keep the history of who set it and when?" — "yes" needs an audit table, "no" is two columns. The answer picks the schema.
|
|
35
38
|
- **Explore the codebase instead of asking** when the answer is already there. If a similar feature, entity, controller, or migration exists, read it before asking the user how a related thing should work.
|
|
36
39
|
- **Sharpen fuzzy language.** When the user says "account", ask: "Customer or User? Those are different things here." When they say "cancel", ask whether that means soft-delete, hard-delete, or a status transition.
|
|
37
40
|
- **Probe with concrete scenarios.** Invent edge cases that force the user to be precise. "What happens if the user submits twice while the first request is still in flight?"
|
|
@@ -114,7 +117,8 @@ Whether you drafted one plan or chose between two, the presented plan must inclu
|
|
|
114
117
|
- Whether a backfill is required and how it's gated.
|
|
115
118
|
- **Always end with:** "Migration file will be generated. No SQL, no `dotnet ef database update`, no `ExecuteSql*` calls will be run — the user runs the migration themselves."
|
|
116
119
|
6. **Pattern reuse table.** Two columns: "New code I'm about to write" and "Existing example it follows" with a path. If a row has no existing example, justify the new pattern.
|
|
117
|
-
7. **
|
|
120
|
+
7. **Validation signal per step.** Every build step names the observable signal that proves it done at plan time — a named test going green, `dotnet build` exiting 0, a `curl` returning the expected body. A step whose completion can't be observed isn't a step, it's a hope — split it or attach a signal before presenting.
|
|
121
|
+
8. **Open questions** still unresolved, if any. If there are any, loop back to Phase 1 — do not exit plan mode with open questions.
|
|
118
122
|
|
|
119
123
|
If Design It Twice ran, end the plan with: **"Considered alternative: `<approach B name>`. Rejected because `<one-line reason>`."** This lets the user redirect to B with a single sentence if they disagree.
|
|
120
124
|
|
|
@@ -126,6 +130,8 @@ Enter Plan Mode (`EnterPlanMode`) to present. Then exit with `ExitPlanMode` and
|
|
|
126
130
|
|
|
127
131
|
Build in the order that gives you the fastest feedback loop.
|
|
128
132
|
|
|
133
|
+
**The plan is a hypothesis about the codebase.** When execution contradicts it — the pattern Phase 2 found doesn't cover this case, the library API has a different shape, the file the plan modifies doesn't exist — stop. Do not improvise silently and keep typing. Re-plan the remaining steps against what you just learned, tell the user what deviated and why, and re-present via plan mode if the change is material (different files, different schema, a new dependency). A small deviation (a helper's actual name, a slightly different signature) just gets a one-line note in the Phase 6 report.
|
|
134
|
+
|
|
129
135
|
**If the feature adds or changes an API endpoint, follow TDD:**
|
|
130
136
|
|
|
131
137
|
1. **Locate the test class.** For each API change, search for an existing test class that already covers that controller / service / use-case. Common patterns:
|
|
@@ -153,6 +159,7 @@ Build in the order that gives you the fastest feedback loop.
|
|
|
153
159
|
- **Minimal comments.** Default to no comments. Only add one when the *why* is non-obvious (a workaround, a non-trivial invariant, a domain rule that isn't visible from the names). Never write block headers, never restate what the next line does, never leave `// TODO` without an issue link.
|
|
154
160
|
- **Match formatting.** Use the project's brace style, naming, file headers, `using` placement. Don't reformat unrelated code.
|
|
155
161
|
- **No dead code.** Don't commit commented-out lines or "for future use" stubs.
|
|
162
|
+
- **Build to the plan, nothing more.** Done means the approved plan's acceptance criteria. No unrequested config options, no defensive layers the plan didn't ask for, no speculative extension points. Improvements you notice mid-build are findings for the Phase 6 report, not work to do.
|
|
156
163
|
|
|
157
164
|
### Phase 5 — Migrations (generate only, never execute)
|
|
158
165
|
|
|
@@ -207,7 +214,10 @@ When appending:
|
|
|
207
214
|
## Anti-patterns
|
|
208
215
|
|
|
209
216
|
- ❌ Skipping Phase 1 and going straight to "let me read the code and start building" — the whole point of this skill is the grill.
|
|
217
|
+
- ❌ Asking grill questions whose answers all lead to the same design. Every question must fork the design.
|
|
210
218
|
- ❌ Exiting plan mode while open questions remain. Loop back instead.
|
|
219
|
+
- ❌ Improvising silently when the codebase contradicts the plan. Stop, re-plan the remainder, tell the user what deviated.
|
|
220
|
+
- ❌ Gold-plating the build: config options, abstraction layers, or "robustness" the plan never called for.
|
|
211
221
|
- ❌ Writing production code before the test it makes pass (when the feature touches an API).
|
|
212
222
|
- ❌ Creating `FooServiceTests2.cs` because `FooServiceTests.cs` already exists. Always append.
|
|
213
223
|
- ❌ Inventing a new pattern for something the codebase already has a pattern for. If you can't find the precedent, ask before forking.
|
|
@@ -54,6 +54,26 @@ Categorize each comment so the author knows what's required vs optional:
|
|
|
54
54
|
|
|
55
55
|
Lead with the *why*, not just the *what*. "This will deadlock if two callers hit it simultaneously" is more useful than "use a lock here".
|
|
56
56
|
|
|
57
|
+
Same finding, badly and well:
|
|
58
|
+
|
|
59
|
+
- ❌ `blocking: add error handling to fetchUser (api/user.ts:42)` — no failure scenario, no consequence; unverifiable reflex.
|
|
60
|
+
- ✅ `blocking: fetchUser (api/user.ts:42) rejects on 404 but ProfilePage (pages/profile.tsx:18) never catches — a deleted user's profile renders blank with an unhandled rejection. Fix: catch and render not-found.`
|
|
61
|
+
|
|
62
|
+
## Evidence rules
|
|
63
|
+
|
|
64
|
+
A claim about code you haven't opened this session is a hypothesis — verify it or label it as one.
|
|
65
|
+
|
|
66
|
+
- **Read beyond the hunk.** Before flagging anything, `Read` the full enclosing function (the whole file when small). Most false "missing null check" / "unhandled error" findings dissolve when you see the guard 10 lines above the hunk or the caller that validates. A finding based only on diff-hunk context is not reportable.
|
|
67
|
+
- **`blocking` carries a burden of proof:** one concrete failure-scenario sentence (*this input/state → this wrong outcome*). Can't write it? Demote to `suggestion`, or raise it as a `question`.
|
|
68
|
+
- **Grep before claiming absence.** "Dead code" / "unused" / "never called" / "duplicated elsewhere" require a repo-wide `Grep` for the identifier first — cite the search in the finding.
|
|
69
|
+
- **Quote verbatim.** Error messages, test output, and load-bearing identifiers go into findings exactly as they appear; paraphrase loses the token that matters.
|
|
70
|
+
- **Zero findings is a valid verdict.** A task with nothing wrong gets a clean *Approve* — never pad Blockers or Suggestions to look thorough. Thoroughness is what you checked, not what you wrote.
|
|
71
|
+
- **User framing is input, not conclusion.** "The backend part is fine, just look at the UI" does not exempt the backend — review every task fully.
|
|
72
|
+
|
|
73
|
+
## Scope discipline
|
|
74
|
+
|
|
75
|
+
A finding must belong to the task (`#NNN`) whose diff introduced it. Pre-existing code that's merely *visible* in the diff context is out of scope — unless the task made it worse (e.g. added a second caller to an already-unsafe function; that's in scope). If you notice a pre-existing issue worth mentioning, note it in one line outside the verdict ("pre-existing, not introduced by #123") — never block a task for code its commits didn't touch.
|
|
76
|
+
|
|
57
77
|
## What to look for
|
|
58
78
|
|
|
59
79
|
### Correctness
|
|
@@ -117,7 +137,7 @@ Performance + Readability usually fold into Design — only spawn a dedicated le
|
|
|
117
137
|
- The per-task diff (commits from this `#NNN` only — not the whole branch).
|
|
118
138
|
- The inferred task intent from step 4.
|
|
119
139
|
- **One** lens with its checklist verbatim from [What to look for](#what-to-look-for).
|
|
120
|
-
- Instructions: "Find issues only in your lens. Categorize each as `blocking` / `suggestion` / `question` / `nit
|
|
140
|
+
- Instructions: "Find issues only in your lens. Read the full enclosing function before flagging — hunk-only findings are not reportable. Only flag code this task's commits introduced or worsened; pre-existing issues are out of scope. Categorize each as `blocking` / `suggestion` / `question` / `nit`; a `blocking` must state a one-sentence concrete failure scenario. Cite `file:line` for every finding. Lead each with the *why*. If no findings, say 'no findings' explicitly — do not pad. Report in ≤500 words."
|
|
121
141
|
2. **Critique round.** Read all lenses side by side, then:
|
|
122
142
|
- **Challenge every `blocking`** against context from the other lenses. Demote to `suggestion` or drop if it's defused by another lens's evidence.
|
|
123
143
|
- **Surface contradictions** as `question` items the user adjudicates (e.g. Correctness flags missing null guard; Security found the caller validates).
|
|
@@ -170,6 +190,10 @@ End with a one-line roll-up: e.g. `Overall: 2 approve, 1 request changes, 1 unsc
|
|
|
170
190
|
- ❌ Reflexively requesting tests without saying what they should cover
|
|
171
191
|
- ❌ Drive-by style nits that derail the substantive discussion
|
|
172
192
|
- ❌ "I would have done it differently" without a concrete reason
|
|
193
|
+
- ❌ Flagging from the diff hunk alone — read the enclosing function first; the guard is often just outside the hunk
|
|
194
|
+
- ❌ Blocking a task for a pre-existing issue its diff didn't introduce or worsen — note it in one line, out of verdict
|
|
195
|
+
- ❌ A `blocking` with no concrete failure scenario — if you can't write "this input → this wrong outcome", it isn't blocking
|
|
196
|
+
- ❌ Padding a clean task with speculative findings — zero findings is a valid Approve
|
|
173
197
|
- ❌ Collapsing multiple tasks into one verdict — each `#NNN` is independent and should stand or fall on its own
|
|
174
198
|
- ❌ Silently ignoring commits with no task reference — surface them in the `unscoped` bucket
|
|
175
199
|
- ❌ Convening the lens council on a 20-line task. Single-pass it.
|
package/skills/ship-it/SKILL.md
CHANGED
|
@@ -20,7 +20,8 @@ Do **not** auto-trigger when the user is asking about diff-level code quality, D
|
|
|
20
20
|
|
|
21
21
|
- **Recommend, don't refactor.** This is an audit. Never edit code unprompted. After the report, ask per-blocker whether the user wants a fix drafted.
|
|
22
22
|
- **Evidence is mandatory.** Every PASS must cite `file:line`. "I checked, it looks fine" is not a PASS — that's a GAP labelled `couldn't verify`.
|
|
23
|
-
- **
|
|
23
|
+
- **Name the probe.** Every PASS also names the exact check that produced its evidence — the grep pattern + path, the file you read, or the command you ran. If you didn't run a probe for an item, it's GAP labelled `not checked` — never PASS. A result you didn't observe doesn't exist.
|
|
24
|
+
- **N/A needs a reason grounded in the scope.** The justification must derive from an observed fact about *this* scope, with the probe that established it (e.g. *"no files under `migrations/` in the diff (`git diff --stat`) → migrations N/A"*) — never a guess from the project type. Don't fabricate gaps for categories that don't apply.
|
|
24
25
|
- **Stay in your lane.** Don't re-do `code-review`'s work. If the user asks about DRY / dead code / test coverage, defer.
|
|
25
26
|
|
|
26
27
|
## Workflow
|
|
@@ -40,6 +41,8 @@ Echo the scope back in one line before starting Phase 2 (*"Auditing branch `feat
|
|
|
40
41
|
|
|
41
42
|
For each category: state the criterion in one line, search the codebase for evidence, mark PASS / GAP / N/A. Use `Grep` and `Read` aggressively; spawn an `Explore` sub-agent for any category where the search would take more than 3 queries.
|
|
42
43
|
|
|
44
|
+
Record each probe as you run it — the report cites them. A probe is a specific `Grep` pattern + path, a `Read` of a named file, or a command with its observed output. A sub-agent's result counts as a probe only if the sub-agent names its own probes and citations; "the agent said it's fine" is not evidence. Where a check depends on the user (dashboards, runbooks, rollout plans), record their answer as the probe (*"user confirmed: alert added to Grafana billing board"*) — an unanswered question stays GAP.
|
|
45
|
+
|
|
43
46
|
#### 1. Logging
|
|
44
47
|
|
|
45
48
|
**What good looks like:** Structured logs (JSON or key=value), correlation / request IDs propagated, levels used correctly (`error` ≠ `info`), no secrets / tokens / PII in log output, errors logged with stack + context, not just the message.
|
|
@@ -118,12 +121,17 @@ Group findings into four buckets. Order matters — blocking first.
|
|
|
118
121
|
- **<category>:** <one-line problem> — `<file:line>`
|
|
119
122
|
|
|
120
123
|
## N/A
|
|
121
|
-
- **<category>:** <one-line reason>
|
|
124
|
+
- **<category>:** <one-line reason grounded in the scope> (probe: `<what established it>`)
|
|
122
125
|
|
|
123
126
|
## Passing
|
|
124
|
-
- **<category>:** <one-line evidence> — `<file:line>`
|
|
127
|
+
- **<category>:** <one-line evidence> — `<file:line>` (probe: `<grep pattern / file read / command>`)
|
|
125
128
|
```
|
|
126
129
|
|
|
130
|
+
Same PASS, written badly and well:
|
|
131
|
+
|
|
132
|
+
- ❌ `**Logging:** structured logging in place — src/billing/` — no line, no probe; unverifiable, could have been written without looking.
|
|
133
|
+
- ✅ `**Logging:** new invoice paths log via structured logger with request ID — src/billing/invoice.ts:31 (probe: grep -n "logger\." src/billing/ → 6 call sites, all pass ctx.reqId)` — anyone can rerun the probe and land on the same line.
|
|
134
|
+
|
|
127
135
|
**Blocking = any one of:**
|
|
128
136
|
- Secrets in code / logs / committed config.
|
|
129
137
|
- Missing authz on a new endpoint or screen.
|
|
@@ -145,8 +153,8 @@ After the report, ask: *"Want me to draft fixes for the blocking items, or stop
|
|
|
145
153
|
|
|
146
154
|
**Claude:**
|
|
147
155
|
1. Echoes scope: *"Auditing `feat/billing-v2` against `main` — 14 files changed."*
|
|
148
|
-
2. Walks the 10 categories, citing `file:line` per finding.
|
|
149
|
-
3. Reports: 2 blocking (missing authz on `POST /api/v2/invoices
|
|
156
|
+
2. Walks the 10 categories, citing `file:line` per finding and the probe that produced it.
|
|
157
|
+
3. Reports: 2 blocking (missing authz on `POST /api/v2/invoices` — `no tenant predicate in routes/invoices.ts:24-51, read top-to-bottom`; no rollback for the `currency_code NOT NULL` migration), 4 should-fix, 1 N/A (local-first storage — *"no client-side store in scope: `grep -rl 'localStorage\|indexedDB' src/billing/` → 0 hits, server-only module"*), 3 passing with probes named.
|
|
150
158
|
4. Asks whether to draft fixes for the blockers.
|
|
151
159
|
|
|
152
160
|
### Example 2: Module-scoped audit
|
|
@@ -167,6 +175,8 @@ After the report, ask: *"Want me to draft fixes for the blocking items, or stop
|
|
|
167
175
|
## Anti-patterns
|
|
168
176
|
|
|
169
177
|
- ❌ Marking a category PASS without a `file:line` citation. "Looks fine" is a GAP labelled `couldn't verify`.
|
|
178
|
+
- ❌ PASS on vibes — a PASS with no named probe (the grep / read / command that produced the evidence) is a GAP labelled `not checked`.
|
|
179
|
+
- ❌ N/A from a guess about the project type. Derive it from the scope and name the probe: *"no schema files in this diff (`git diff --stat`) → migrations N/A"*.
|
|
170
180
|
- ❌ Inventing GAPs in N/A categories. A server-only service genuinely has no local-first storage layer — say so and move on.
|
|
171
181
|
- ❌ Reviewing diff quality (DRY, dead code, missing tests) under the ship-it banner. Defer to `code-review`.
|
|
172
182
|
- ❌ Editing code mid-audit. The report comes first, then the user picks what to fix.
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sql-review
|
|
3
|
+
description: Pre-commit SQL code review for uncommitted database changes. Detects antipatterns that cause real production incidents — `sp_send_dbmail` in CATCH blocks (masks the real error as a misleading permission denial), broken retry patterns (`@retry` declared without a surrounding `WHILE` loop), swallowing CATCH blocks (no `THROW`/`RAISERROR`/log), new tables created without a primary key or any index (silent perf-then-deadlock killer), parameter-vs-column type mismatches (8152 truncation risk), `EXEC()` string concatenation without `sp_executesql` parameters (SQL injection), `NOLOCK` in write paths, `UPDATE`/`DELETE` without `WHERE`, cursors without `READ_ONLY FORWARD_ONLY LOCAL`, hardcoded env values (emails, server names, paths), cross-DB references like `msdb.dbo.*`, missing `SET NOCOUNT ON`, missing `GRANT EXECUTE` on `CREATE PROC`, `BEGIN TRANSACTION` outside `TRY`/`CATCH`, and vestigial control-flow comments hinting at refactor leftovers (e.g. `-- end while loop` with no `WHILE`). Reports findings as `BLOCKER` / `WARN` / `INFO` with `file:line` citations and per-finding fix recommendations. **Never edits SQL** — surfaces findings for human review. Use this skill whenever the user says "/sql-review", "review my SQL", "review the SQL diff", "lint the SQL", "check my SQL changes", "SQL pre-commit check", "audit my stored proc", or "any SQL antipatterns in this diff" — even if they don't explicitly say "SQL review skill". Distinct from `code-review` (general best-practice review) — this carries SQL-specific patterns that surface DB-layer production incidents.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SQL Review
|
|
7
|
+
|
|
8
|
+
Review uncommitted SQL changes against a catalog of patterns that cause real production incidents — masked errors, unindexed new tables, silent truncation, SQL injection, deadlock-prone CATCH blocks — and surface findings as recommendations the user can act on, **without editing SQL unprompted**.
|
|
9
|
+
|
|
10
|
+
## When to use this skill
|
|
11
|
+
|
|
12
|
+
- "/sql-review" / "review my SQL" / "review the SQL diff"
|
|
13
|
+
- "lint the SQL" / "SQL pre-commit check" / "check my SQL changes"
|
|
14
|
+
- "audit my stored proc" / "any SQL antipatterns in this diff"
|
|
15
|
+
- Before committing a `.sql` change touching a stored procedure, function, or table
|
|
16
|
+
|
|
17
|
+
Do **not** auto-trigger when the user is asking for a *general* code review on a mixed diff — defer to [`code-review`](../code-review/SKILL.md) for that. This skill is specifically for SQL-heavy diffs where the antipattern catalog applies.
|
|
18
|
+
|
|
19
|
+
## Hard rule: surface, don't refactor
|
|
20
|
+
|
|
21
|
+
If you find issues, **do not start editing the SQL**. Produce the findings report first. After delivering it, ask the user per finding: *"Want me to fix #N?"* — and wait for an explicit yes. Pre-authorization ("fix them all") proceeds through the list; absent that, default to ask-per-fix.
|
|
22
|
+
|
|
23
|
+
SQL changes are particularly sensitive because they often run against production data in a single non-reversible deployment. A drive-by refactor mid-review is more dangerous here than in application code.
|
|
24
|
+
|
|
25
|
+
## Workflow
|
|
26
|
+
|
|
27
|
+
1. **Snapshot the SQL diff.** Run in parallel:
|
|
28
|
+
- `git status --short` — see what's modified, staged, untracked.
|
|
29
|
+
- `git diff -- '*.sql'` — unstaged SQL changes.
|
|
30
|
+
- `git diff --staged -- '*.sql'` — staged SQL changes.
|
|
31
|
+
- If user passed an explicit range (e.g. `main..HEAD`), use that instead: `git diff <range> -- '*.sql'`.
|
|
32
|
+
2. **Triage files into two buckets:**
|
|
33
|
+
- **New files** (status `A` or `??`) — review the entire file. The whole file is new code; pre-existing antipatterns elsewhere don't apply.
|
|
34
|
+
- **Modified files** (status `M`) — review only the changed hunks. The rest of the file may have legacy antipatterns the user isn't introducing (e.g. existing repos commonly have dozens of SPs with the `sp_send_dbmail`-in-CATCH pattern; flagging all of them on an unrelated edit creates noise).
|
|
35
|
+
3. **For each check in [What to check](#what-to-check), look for the pattern** in the relevant scope (whole file for new files, diff hunks for modified files). Use ripgrep — don't try to fully parse SQL.
|
|
36
|
+
4. **Categorize each finding** as `BLOCKER` / `WARN` / `INFO` (see [Categories](#categories)).
|
|
37
|
+
5. **Cite `file:line`** for every finding. Line numbers come from the diff or the current file content.
|
|
38
|
+
6. **Produce the report** in the [Output format](#output-format).
|
|
39
|
+
7. **Offer to fix** the suggested findings. Wait for the user's selection before editing.
|
|
40
|
+
|
|
41
|
+
## Categories
|
|
42
|
+
|
|
43
|
+
- **`BLOCKER`** — would cause a production incident: silent data loss, masked errors, SQL injection, unindexed table that will grow, broken transaction handling. Fix before commit.
|
|
44
|
+
- **`WARN`** — likely a bug or maintenance hazard: NOLOCK in write paths, hardcoded environment values, missing `SET NOCOUNT ON`, cross-DB references. Should fix but won't necessarily incident.
|
|
45
|
+
- **`INFO`** — style / convention / hygiene: vestigial comments, cursor defaults, missing `GRANT EXECUTE` where the project's pattern exists.
|
|
46
|
+
|
|
47
|
+
Lead each finding with the *why*. "This swallows the original error and surfaces a misleading permission denial" beats "add `;THROW;`".
|
|
48
|
+
|
|
49
|
+
## Pattern hit ≠ finding
|
|
50
|
+
|
|
51
|
+
The 17 checks are pattern matches. A ripgrep hit is a **candidate** — before reporting it, `Read` the whole procedure/batch it sits in; severity depends on context the pattern can't see. Concrete demotions:
|
|
52
|
+
|
|
53
|
+
- `WITH (NOLOCK)` hit, but the proc is a read-only reporting proc with no `INSERT`/`UPDATE`/`DELETE` → **WARN** at most, not BLOCKER (check #13 blocks only when the same proc writes the tables it dirty-reads).
|
|
54
|
+
- `DECLARE @retry` with no `WHILE @retry` in sight → check for a `GOTO`-label retry loop consuming it before flagging; only report when nothing reads the variable.
|
|
55
|
+
- New `CREATE TABLE` with no index, but it's a static lookup/config table seeded with a handful of rows in the same diff → **WARN** with a one-line reason, not BLOCKER (the deadlock incident needs growth under concurrent writes).
|
|
56
|
+
|
|
57
|
+
Every reported finding must:
|
|
58
|
+
1. **Quote the offending SQL verbatim** — the exact line(s) from the file, not a paraphrase. Severity turns on exact tokens.
|
|
59
|
+
2. For `BLOCKER` / `WARN`: state the concrete production incident in one sentence (*this state → this outcome*, e.g. "first deadlock under load → SP silently gives up, rows never written"). If you can't write that sentence after reading the full proc, demote one level.
|
|
60
|
+
|
|
61
|
+
Zero findings is a valid outcome — a clean diff gets a clean report. Never stretch INFO items into WARNs to fill sections; thoroughness is the 17 checks you ran, which the report shows.
|
|
62
|
+
|
|
63
|
+
## What to check
|
|
64
|
+
|
|
65
|
+
Use ripgrep for detection. The patterns below are starting points — adapt to the actual diff text.
|
|
66
|
+
|
|
67
|
+
### Error-handling antipatterns (BLOCKER class)
|
|
68
|
+
|
|
69
|
+
1. **`sp_send_dbmail` inside `CATCH`** — masks the real exception with a permission/configuration error when the executing login lacks `EXECUTE` on `msdb.dbo.sp_send_dbmail`. The real underlying error is silently lost.
|
|
70
|
+
- Pattern: `rg -n -B 20 'sp_send_dbmail' <file>` → check whether the surrounding context is a `BEGIN CATCH`.
|
|
71
|
+
- **Fix:** Replace with `;THROW;` to re-raise, or write the error info to a local log table. Never `sp_send_dbmail` from inside operational SPs.
|
|
72
|
+
|
|
73
|
+
2. **Broken retry pattern** — `DECLARE @retry INT = N;` exists, but no `WHILE @retry` loop wraps the `TRY`/`CATCH`. The retry variable is decremented in the CATCH but never read, so the SP gives up on the first deadlock instead of retrying.
|
|
74
|
+
- Pattern: `rg -n '@retry' <file>` → confirm there's a matching `WHILE.*@retry` somewhere in the same SP body.
|
|
75
|
+
- **Fix:** Add the missing `WHILE @retry > 0 BEGIN ... END` around the TRY/CATCH, or remove the vestigial variable entirely.
|
|
76
|
+
|
|
77
|
+
3. **Empty / swallowing `CATCH` blocks** — `BEGIN CATCH ... END CATCH` with no `THROW`, no `RAISERROR`, and no `INSERT INTO <error_log_table>`. Silent failure → silent data loss.
|
|
78
|
+
- Pattern: `rg -n -A 30 'BEGIN CATCH' <file>` → check each match for at least one of `THROW`, `RAISERROR`, or `INSERT INTO`.
|
|
79
|
+
- **Fix:** End the CATCH with `;THROW;` unless there's a deliberate reason to suppress (and document that reason).
|
|
80
|
+
|
|
81
|
+
4. **`BEGIN TRANSACTION` without `TRY`/`CATCH` + `XACT_STATE` handling** — an error mid-transaction leaves an open transaction on the connection.
|
|
82
|
+
- Pattern: `rg -n 'BEGIN TRAN' <file>` → confirm the same SP has `BEGIN TRY` and `XACT_STATE()` checks in CATCH.
|
|
83
|
+
- **Fix:** Wrap in `BEGIN TRY / BEGIN TRANSACTION ... COMMIT TRANSACTION / END TRY / BEGIN CATCH / IF XACT_STATE() = -1 ROLLBACK; IF XACT_STATE() = 1 COMMIT; ;THROW; / END CATCH`.
|
|
84
|
+
|
|
85
|
+
5. **Vestigial control-flow comments** — `-- end while loop` with no `WHILE` keyword, `-- retry on deadlock` with no retry loop, etc. Strong signal that a refactor left dead code behind.
|
|
86
|
+
- Pattern: `rg -n -- '-- end (while|for|repeat)' <file>` → check for matching opening keyword.
|
|
87
|
+
- **Fix:** Either restore the missing loop or delete the misleading comment.
|
|
88
|
+
|
|
89
|
+
### Schema / performance antipatterns (BLOCKER for new tables, WARN otherwise)
|
|
90
|
+
|
|
91
|
+
6. **New `CREATE TABLE` without a primary key, clustered index, or any index** — heap tables will deadlock-and-table-scan once they grow. This is the exact pattern that caused real production deadlocks.
|
|
92
|
+
- Pattern: `rg -n -A 50 'CREATE TABLE' <file>` → confirm at least one of `PRIMARY KEY`, `CLUSTERED INDEX`, or `CREATE INDEX` exists on that table within the diff or in a sibling file.
|
|
93
|
+
- **Fix:** Add `PRIMARY KEY CLUSTERED` on the natural ID column. If the table is append-mostly with no natural ID, add a clustered index on the column the populating SPs filter by in `WHERE`.
|
|
94
|
+
|
|
95
|
+
7. **Parameter-to-column type-width mismatches** — passing `@x VARCHAR(50)` into a column declared `VARCHAR(3)` causes silent truncation (or runtime error 8152 depending on `ANSI_WARNINGS`).
|
|
96
|
+
- Pattern: cross-reference the SP's `@param VARCHAR(N)` declarations against the target table's column widths. Flag any narrowing.
|
|
97
|
+
- **Fix:** Match widths between parameter and column, or add explicit `LEFT(@x, 3)` truncation at the boundary so it's intentional and visible.
|
|
98
|
+
|
|
99
|
+
8. **Implicit type conversions in `JOIN` / `WHERE`** — `WHERE intCol = @varcharParam` defeats indexes and produces table scans.
|
|
100
|
+
- Pattern: `rg -n -i 'WHERE\s+\w+\s*=\s*@\w+' <file>` → cross-check parameter type vs. column type.
|
|
101
|
+
- **Fix:** Match the parameter type to the column type. If they're authoritatively different, cast the *parameter*, not the column (casting the column kills the index).
|
|
102
|
+
|
|
103
|
+
### Security antipatterns (BLOCKER)
|
|
104
|
+
|
|
105
|
+
9. **Dynamic SQL via string concatenation passed to `EXEC()`** without `sp_executesql @stmt, @params` parameterization — SQL injection.
|
|
106
|
+
- Pattern: `rg -n 'EXEC\s*\(' <file>` → confirm any string-concat dynamic SQL nearby uses `sp_executesql` with parameters, not `EXEC(@strSQL)` with values inlined.
|
|
107
|
+
- **Fix:** Switch to `EXEC sp_executesql @stmt = @strSQL, @params = N'@p1 INT, @p2 VARCHAR(50)', @p1 = @value1, @p2 = @value2;`.
|
|
108
|
+
|
|
109
|
+
10. **Hardcoded environment-specific values** — email addresses, server names, file paths, IP addresses, connection strings.
|
|
110
|
+
- Pattern: `rg -nE "'[\w.-]+@[\w.-]+\.\w+'|@server_name\s*=\s*'\w+'|\\\\\\\\[\\w.-]+\\\\" <file>` (emails, linked servers, UNC paths).
|
|
111
|
+
- **Fix:** Move env-specific values into a configuration table or pass them as parameters from the application layer.
|
|
112
|
+
|
|
113
|
+
11. **Cross-database references** — `msdb.dbo.*`, `master.dbo.*`, linked server `[srv].db.dbo.*`. These require permissions that may not exist in all environments (the canonical example: a SP calling `msdb.dbo.sp_send_dbmail` from a login that lacks EXECUTE on it).
|
|
114
|
+
- Pattern: `rg -n 'msdb\.|master\.dbo\.|\[\w+\]\.\w+\.\w+\.' <file>`.
|
|
115
|
+
- **Fix:** Move the cross-DB call out of operational SPs into infrastructure-owned code. If it must stay, document the required permission in a comment so deployments to new environments include the GRANT.
|
|
116
|
+
|
|
117
|
+
### Correctness / data-safety antipatterns (BLOCKER)
|
|
118
|
+
|
|
119
|
+
12. **`UPDATE` or `DELETE` without a `WHERE` clause** — almost always a bug; will affect every row.
|
|
120
|
+
- Pattern: `rg -n -B 0 -A 20 'UPDATE\s+\w+' <file>` → confirm a `WHERE` clause is present and not commented out.
|
|
121
|
+
- Pattern: `rg -n -B 0 -A 10 'DELETE\s+FROM' <file>` → same.
|
|
122
|
+
- **Fix:** Add the `WHERE` clause. If "every row" really is intended (TRUNCATE-style), use `TRUNCATE TABLE` and add a comment explaining why.
|
|
123
|
+
|
|
124
|
+
13. **`NOLOCK` / `READ UNCOMMITTED` inside transactional write paths** — `WITH (NOLOCK)` is fine for ad-hoc reporting reads, but in an `UPDATE`/`INSERT`/`DELETE` SP it can read uncommitted data and corrupt the write.
|
|
125
|
+
- Pattern: `rg -n -i 'NOLOCK|READ UNCOMMITTED' <file>` → check whether the same SP has `INSERT`, `UPDATE`, or `DELETE` against the same tables.
|
|
126
|
+
- **Fix:** Remove the hint on tables being modified. Consider `SNAPSHOT` isolation if the goal is to avoid blocking.
|
|
127
|
+
|
|
128
|
+
14. **`TRUNCATE TABLE` or `DROP` without `IF EXISTS`** in deploy scripts that are supposed to be idempotent (re-runnable). Many repos use flat idempotent `Deploy_*.sql` files where re-runs must not fail on missing objects.
|
|
129
|
+
- Pattern: `rg -n -i 'DROP (TABLE|PROC|FUNCTION|VIEW)' <file>` → confirm `IF EXISTS` or `OBJECT_ID(...) IS NOT NULL` guard.
|
|
130
|
+
- **Fix:** `IF OBJECT_ID('dbo.X', 'U') IS NOT NULL DROP TABLE dbo.X;` or `DROP TABLE IF EXISTS dbo.X;` (SQL 2016+).
|
|
131
|
+
|
|
132
|
+
### Style / convention (WARN/INFO)
|
|
133
|
+
|
|
134
|
+
15. **Missing `SET NOCOUNT ON`** at the top of a new `CREATE PROC` — causes extra round-trips to the client and breaks some ORM drivers that don't handle row-count messages.
|
|
135
|
+
- Pattern: `rg -n -A 5 'CREATE PROC' <file>` → confirm `SET NOCOUNT ON` appears within the first ~10 lines of the procedure body.
|
|
136
|
+
- **Fix:** Add `SET NOCOUNT ON;` as the first statement after `AS BEGIN`.
|
|
137
|
+
|
|
138
|
+
16. **Cursors without `READ_ONLY FORWARD_ONLY LOCAL`** — default cursors are slow and persist beyond the current batch.
|
|
139
|
+
- Pattern: `rg -n 'DECLARE\s+\w+\s+CURSOR\b' <file>` → confirm `READ_ONLY FORWARD_ONLY LOCAL` (or `FAST_FORWARD`) is specified.
|
|
140
|
+
- **Fix:** `DECLARE c CURSOR READ_ONLY FORWARD_ONLY LOCAL FOR SELECT ...`.
|
|
141
|
+
|
|
142
|
+
17. **Missing `GRANT EXECUTE` at end of `CREATE PROC`** — only flag when the rest of the repo has a consistent `GRANT EXECUTE ON ... TO <role>` pattern at the end of every SP file. Detect the pattern by sampling a few neighboring `.sql` files; if every existing SP grants to the same role and this new SP doesn't, flag it.
|
|
143
|
+
- Pattern: `rg -n 'GRANT\s+EXECUTE' <file>` → confirm present if the repo convention requires it.
|
|
144
|
+
- **Fix:** Append `GRANT EXECUTE ON [dbo].[<proc>] TO [<app_role>] GO` matching the role the rest of the repo grants to.
|
|
145
|
+
|
|
146
|
+
## Output format
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
# SQL review — uncommitted changes
|
|
150
|
+
|
|
151
|
+
**SQL files changed:** <N> (<S new>, <M modified>)
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Verdict
|
|
156
|
+
**Ship / Fix blockers first**
|
|
157
|
+
|
|
158
|
+
<one-paragraph summary of the change and overall state>
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Blockers (<N>)
|
|
163
|
+
|
|
164
|
+
### B1. <short title> — `path/to/file.sql:<line>`
|
|
165
|
+
**Evidence:** `<offending SQL fragment, quoted verbatim>`
|
|
166
|
+
**Why:** <root cause / production consequence — one line>
|
|
167
|
+
**Fix:** <concrete recommendation — one line>
|
|
168
|
+
|
|
169
|
+
### B2. ...
|
|
170
|
+
|
|
171
|
+
## Warnings (<N>)
|
|
172
|
+
|
|
173
|
+
### W1. <short title> — `path/to/file.sql:<line>`
|
|
174
|
+
**Evidence:** `<offending SQL fragment, quoted verbatim>`
|
|
175
|
+
**Why:** ...
|
|
176
|
+
**Fix:** ...
|
|
177
|
+
|
|
178
|
+
## Info (<N>)
|
|
179
|
+
- `path:line` — <one-liner>
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Summary
|
|
184
|
+
- BLOCKER: <count>
|
|
185
|
+
- WARN: <count>
|
|
186
|
+
- INFO: <count>
|
|
187
|
+
|
|
188
|
+
## Fixes I can apply
|
|
189
|
+
|
|
190
|
+
If you want, I can apply any of: B1, B2, W1. Which? (or "all", or "none")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
End with the offer. Wait for the user's choice.
|
|
194
|
+
|
|
195
|
+
## Examples
|
|
196
|
+
|
|
197
|
+
### Example 1: New stored proc with mixed issues
|
|
198
|
+
|
|
199
|
+
**User:** "/sql-review"
|
|
200
|
+
|
|
201
|
+
**Claude:**
|
|
202
|
+
- Runs `git diff --staged -- '*.sql'` and `git diff -- '*.sql'` in parallel.
|
|
203
|
+
- Finds one new file `Stored Procedures/dbo.usp_NewSave.sql`.
|
|
204
|
+
- Spots:
|
|
205
|
+
- `B1` — `BEGIN CATCH` without `;THROW;` (line 88) — swallowing.
|
|
206
|
+
- `B2` — calls `EXEC msdb.dbo.sp_send_dbmail` inside that CATCH (line 92) — masking risk.
|
|
207
|
+
- `W1` — missing `SET NOCOUNT ON` at line 6.
|
|
208
|
+
- `W2` — `WITH (NOLOCK)` on the table being UPDATEd (line 45).
|
|
209
|
+
- `I1` — missing `GRANT EXECUTE` at end of file (the repo's other SPs all end with it).
|
|
210
|
+
- Reports, then asks: "Want me to fix B1, B2, W1, W2, I1?"
|
|
211
|
+
|
|
212
|
+
### Example 2: Adding a new table — the canonical incident pattern
|
|
213
|
+
|
|
214
|
+
**User:** "review the SQL diff before I push"
|
|
215
|
+
|
|
216
|
+
**Claude:**
|
|
217
|
+
- Spots `Tables/dbo.NewAuditLog.sql` (new file) — `CREATE TABLE` declared but **no PRIMARY KEY, no CLUSTERED INDEX, no CREATE INDEX**.
|
|
218
|
+
- Flags as `B1` — "Heap table will table-scan on every read/UPDATE. Once the table grows past a few hundred thousand rows under concurrent writes, SQL Server's optimizer picks parallel plans and the workers deadlock on heap pages — a recurring production incident pattern."
|
|
219
|
+
- Fix: "Add `PRIMARY KEY CLUSTERED (id)` or a clustered index on the lookup column."
|
|
220
|
+
- Also flags the SP that populates the new table (`B2` — implicit type conversion `WHERE recordID = @recordID` where the new table's `recordID` is `INT` but the param is declared `VARCHAR`).
|
|
221
|
+
|
|
222
|
+
### Example 3: Modified existing SP — flag only new badness, not legacy
|
|
223
|
+
|
|
224
|
+
**User:** "lint the SQL — just the staged hunks"
|
|
225
|
+
|
|
226
|
+
**Claude:**
|
|
227
|
+
- The file `usp_UpdateBigOldSP.sql` is modified but huge. The user's diff added a 20-line block that does `UPDATE dbo.Customers SET ...` without a `WHERE` clause.
|
|
228
|
+
- **Only flags the new block's `UPDATE` without `WHERE` as `B1`** — does NOT flag the legacy `sp_send_dbmail` call elsewhere in the file (which was already there before the user's edit).
|
|
229
|
+
- Notes in the report footer: "1 pre-existing antipattern observed in this file but outside the diff — flagging suppressed per scope. Full-file audit available on request."
|
|
230
|
+
|
|
231
|
+
## Anti-patterns
|
|
232
|
+
|
|
233
|
+
- ❌ Trying to parse SQL with a full grammar — use ripgrep on the diff text. The skill is heuristic by design.
|
|
234
|
+
- ❌ Flagging every existing antipattern in a modified file. Stay scoped to the diff for modified files; full-file scan only for new files.
|
|
235
|
+
- ❌ Editing SQL during the review pass. Report first, **always**.
|
|
236
|
+
- ❌ Suggesting autofixes for `BLOCKER` items without asking. SQL deploys to production; user confirmation gates the change.
|
|
237
|
+
- ❌ Listing a `nit` when there's a `BLOCKER` next to it. Lead with severity.
|
|
238
|
+
- ❌ Hand-wavy findings without `file:line`.
|
|
239
|
+
- ❌ Reporting a raw ripgrep hit without reading the surrounding procedure. A pattern match is a candidate; the proc's full context sets the severity (see [Pattern hit ≠ finding](#pattern-hit--finding)).
|
|
240
|
+
- ❌ Paraphrasing the offending SQL instead of quoting it verbatim. `WITH (NOLOCK)` vs `READPAST`, `@retry` vs `@retryCount` — the exact token is the finding.
|
|
241
|
+
- ❌ A `BLOCKER` with no one-sentence production-incident scenario. Can't name the incident? It's a `WARN`.
|
|
242
|
+
- ❌ Padding a clean diff with stretched findings. Zero findings is a valid report.
|
|
243
|
+
- ❌ Inventing antipatterns not in [What to check](#what-to-check). This is a fixed catalog — extending it is a `write-a-skill` change, not a per-run improvisation.
|
|
244
|
+
- ❌ Running on non-SQL files. The skill is `.sql`-scoped; mixed diffs route to `code-review` for the non-SQL parts.
|
|
245
|
+
- ✅ Diff-scoped scan for modified files, full scan for new files, findings categorized + cited + offered as fixes.
|
|
246
|
+
|
|
247
|
+
## Notes
|
|
248
|
+
|
|
249
|
+
- The check catalog is intentionally finite — 17 checks chosen because each one maps to a real production incident class. Don't try to be a full SQL linter (e.g. SQLFluff or sqlcheck) — those exist and run from CI; this skill is the pre-commit human-pass complement focused on bug classes that linters miss.
|
|
250
|
+
- For repos that use flat idempotent `Deploy_*.sql` files (every deploy is a full re-run), check #14 (`DROP` / `TRUNCATE` without `IF EXISTS`) is critical — the deploy will fail on second run otherwise. For repos that use forward-only migrations, this check is lower priority.
|
|
251
|
+
- The "broken retry" pattern (check #2) is sneakier than it sounds: a `DECLARE @retry INT = 5;` followed by `SET @retry = @retry - 1;` in CATCH but no `WHILE` loop *looks* like deadlock handling. It isn't. Multiple SPs in real codebases have this bug from removed-loop refactors.
|
|
252
|
+
- If the user wants the broader cleanup (refactor every legacy SP in the repo with one of these issues), recommend opening a tracked task rather than doing it in the current review — these fixes ripple through deploy pipelines and need coordinated review.
|
|
253
|
+
- This skill composes downstream with [`code-review`](../code-review/SKILL.md) — on a mixed diff (`.cs` + `.sql`), run `code-review` for the application code and `sql-review` for the database changes.
|
|
@@ -125,11 +125,21 @@ If the user pushes back, revise and re-present. Do not partial-implement against
|
|
|
125
125
|
|
|
126
126
|
Walk the plan one step at a time. For each step:
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
0. **Re-anchor.** Re-read the `Goal` section and this step's validation criterion from `Plan` before touching anything. Thirty seconds of re-reading is what keeps turn 20 aligned with turn 1 — drift is silent and this is the only cheap defense.
|
|
129
|
+
1. **Make the smallest change that completes the step.** No drive-by refactors, no "while I'm here" fixes, no batched edits across multiple steps. Done means the step's criterion, nothing more — improvements you notice (missing validation, refactor opportunity, extra config) go into `Risks` as findings, not into the diff.
|
|
129
130
|
2. **Validate immediately.** Run the test, build, type-check, lint, curl the endpoint, or load the page — whichever signal is appropriate for that step. If there's no automated signal at all, say so explicitly in `Progress`; don't pretend there is one.
|
|
130
|
-
3. **Tick the checkbox** with a
|
|
131
|
+
3. **Tick the checkbox** with evidence that is an **observed artifact from this turn** — a pasted output line, an exit code, a status code. A claim is not evidence.
|
|
132
|
+
- ❌ `tests pass` — an assertion; nothing was observed.
|
|
133
|
+
- ✅ `dotnet test → Passed! 42 passed, 0 failed, 0 skipped` — pasted from output you just saw.
|
|
134
|
+
If you didn't run it this turn, you can't tick it.
|
|
131
135
|
4. **Re-emit the full per-turn output** before moving to the next step.
|
|
132
136
|
|
|
137
|
+
When a command fails:
|
|
138
|
+
|
|
139
|
+
- Read the **full** error output — the load-bearing detail is usually in the last lines you'd skim past.
|
|
140
|
+
- Change exactly one thing based on what the error says, then retry once.
|
|
141
|
+
- Two failures on the same step = stop. Add the verbatim error to `Risks` and surface to the user. Never retry verbatim, and never continue as if the command succeeded — a result you didn't observe is not a result.
|
|
142
|
+
|
|
133
143
|
When a validation fails:
|
|
134
144
|
|
|
135
145
|
- Do not move on.
|
|
@@ -160,6 +170,9 @@ When every checkbox is ticked:
|
|
|
160
170
|
- ❌ Convening the inspection council for a 3-file task. Ceremony for its own sake. Inline reads are the default — escalate only when the inspection set genuinely spans multiple layers.
|
|
161
171
|
- ❌ Spawning the inspection sub-agents serially instead of in parallel — one message, N `Agent` calls. Serial defeats the context-protection rationale.
|
|
162
172
|
- ❌ Letting a sub-agent slice overlap with another's. If two slices would re-read the same files, merge them first.
|
|
173
|
+
- ❌ Ticking a checkbox with claimed evidence (`tests pass`) when the command wasn't run this turn. Evidence is pasted observation, not memory or assertion.
|
|
174
|
+
- ❌ Retrying a failed command verbatim, or proceeding as if it succeeded. Read the error, change one thing, retry once; twice failed = `Risks` + stop.
|
|
175
|
+
- ❌ Gold-plating a step: extra config options, defensive layers, speculative hooks the plan didn't call for. Findings go to `Risks`; the diff stays the size of the step.
|
|
163
176
|
- ✅ Same six headers every turn, one step at a time, one validation per step, assumptions tracked explicitly until confirmed or killed.
|
|
164
177
|
|
|
165
178
|
## Examples
|