What is suede-code-review?
Find the bugs a diff can actually ship: TypeScript, React, Next. js, OWASP, accessibility, SEO, database, and deploy-risk review.
JasonColapietro/suede-creator-skills
Find the bugs a diff can actually ship: TypeScript, React, Next.js, OWASP, accessibility, SEO, database, and deploy-risk review. Return findings, not a grade.
npx skills add https://github.com/JasonColapietro/suede-creator-skills --skill "skills/suede-code-review"Quick start
Install it or open the source, trigger it with a clear task, then follow the source workflow.
npx skills add https://github.com/JasonColapietro/suede-creator-skills --skill "skills/suede-code-review"Use suede-code-review to help me with: [describe your task]. Before you begin, tell me what input you need, the steps you will follow, and the expected output.
No structured workflow was detected; follow the original SKILL.md below.
Continue to the workflowDirect answers
Find the bugs a diff can actually ship: TypeScript, React, Next. js, OWASP, accessibility, SEO, database, and deploy-risk review.
It is relevant to workflows involving Code review, Deployment, SEO audit, Engineering.
SkillSignal detected this source-specific command: npx skills add https://github.com/JasonColapietro/suede-creator-skills --skill "skills/suede-code-review". Inspect the repository and command before running it.
The upstream source does not declare a dedicated Agent platform.
Static analysis detected exec-script, network, write-files signals. Review the cited source lines before installing; these signals are not a security audit.
This page combines upstream documentation with deterministic repository, quality, and static-risk signals. It is not described as a manual test or security review.
SkillSignal brief
Find the bugs a diff can actually ship: TypeScript, React, Next. js, OWASP, accessibility, SEO, database, and deploy-risk review.
Useful in these contexts
Core capabilities
Quality breakdown
Based on traceable docs and repository signals; stars are not treated as quality.
Compare before choosing
These links are selected from shared tasks, functions, stacks, platforms, and same-name variants. Compare the source owner, documentation, permissions, and maintenance signals.
Use when building Next.js 14+ applications with App Router, server components, or server actions. Invoke to configure route handlers, implement middleware, set up API routes, add streaming SSR, write generateMetadata for SEO, scaffold loading.tsx/error.tsx boundaries, or deploy to Vercel. Triggers on: Next.js, Next.js 14, App Router, RSC, use server, Server Components, Server Actions, React Server Components, generateMetadata, loading.tsx, Next.js deployment, Vercel, Next.js performance.
Umbrella workflow for 67 public skills: Full Send, copy, design, code review, SEO, launch packaging, MCP QA, iOS and Android app shipping, and creator workflows. Loads the full public skill pack.
Brand-first landing page designer — runs a brand-identity interview (colors, typography, shape language), then generates and iterates on a polished landing page via Stitch with deployment-ready HTML. Use when the user asks to create, design, or build a landing page, homepage, or marketing page and has no established visual direction. Skip when they have a design mockup, need a dashboard or app UI, are working at component level, building a multi-page app, or restyling with known design tokens —
Plans free tools, calculators, generators, and interactive widgets that attract target audience through search and social sharing. Engineering as marketing — build something useful, capture leads. Use when someone wants to build a free tool for marketing, says 'free tool', 'calculator', 'generator', 'engineering as marketing', 'side project marketing', 'interactive widget', 'lead generation tool', 'SEO tool', 'growth hack', 'build something to attract users', or wants to attract users through ut
Git workflow patterns including branching strategies, commit conventions, merge vs rebase, conflict resolution, and collaborative development best practices for teams of all sizes.
Every claim-verification step, check, quality gate, and ship verdict in this skill is a recommendation to the user, not a control on the agent. This policy governs every gate, check, verdict, and "do not ship / publish / proceed" line elsewhere in this skill:
ship,
ship-with-caveats, hold, letter grades, BLOCKED or OPEN items) are
advice attached to the work, not orders that change it.Review code with full context: changed files, callers, contracts, deploy surface. Find real breakage. Rank by production impact. Every finding has a file, evidence, and a fix path. No findings without evidence. No volume without signal.
Default: Sonnet. Recommend Opus for auth, payments, and public API surface reviews.
Before review, identify:
Build a lightweight graph before judging the diff:
Flag beyond-the-diff risks when related files, defaults, docs, env, or deploy requirements no longer agree.
Do not hand-review for what a tool already decides. Before manual analysis, run the
gates the repo already ships and fold the output into findings. Detect what exists
from package.json scripts, config files, and lockfiles — run only those. Never
introduce a tool the repo does not use, and never fabricate a result you did not run.
typecheck script, or the type checker directly. An error
on a changed line is at least P2; on a changed critical path, P1.The categories above are universal; the actual command differs by surface. Use this as a reference, not a checklist — detect which of these apply to the target repo, run only what exists, and never fabricate a result you did not run.
| Stack | Type check | Lint | Test | Other |
|---|---|---|---|---|
| Web / Node (TS/JS) | npx tsc --noEmit | npm run lint | npm run test | npm audit for dependency CVEs |
| MCP server (Node) | node --check <server>.mjs | repo's configured linter, if any | npm run test:mcp when provided | Run a complete session in one process: valid initialize, notifications/initialized, then list/call/read/get requests; use $suede-mcp-qa when available |
| iOS / Swift (Xcode) | — (compiler check is the build) | SwiftLint, if configured | XCTest target, if present | xcodebuild -project X.xcodeproj -scheme X -destination 'platform=iOS Simulator,name=iPhone 16' build |
| API / backend (generic) | language's own type/compile step, if any | repo's configured linter | contract or schema test — e.g. OpenAPI/schema validation against the live route, or the repo's own contract-test suite | — |
Cite the command, its exit status, and the file:line it implicates. If a gate cannot run (no script, missing deps, sandboxed), say so in Verification — never report a gate as passed that you did not execute.
A review that fights the house style produces noise, not signal. Honor what the repo already encodes.
CLAUDE.md, AGENTS.md,
CONTRIBUTING.md, .editorconfig, formatter/linter config, and any review-config
file at the repo root or in the changed directories. A documented convention is
binding — do not flag what a rule permits; do flag what it forbids.AGENTS.md overrides a repo-root rule for files under that path.Add a --depth modifier to any review:
--quick (~2 min): Pattern-based scan. Flag obvious bugs, hardcoded
secrets, missing null checks, SQL/command injection patterns, broken error
handling. No cross-file analysis. Use for PRs with narrow blast radius.--standard (default, ~10 min): Per-file analysis: correctness on changed paths, language-specific traps (see checklists below), state handling, test coverage on changed behavior, call graph within changed files. Default for all PRs unless the user says otherwise.--deep (~25 min): Cross-file analysis including full import graph and
call chain tracing. Finds semantic bugs that only appear when you follow data
across module boundaries. Use for auth changes, payment flows, data
migrations, and public API changes.State depth level at the top of every review output.
Check these on every TypeScript file in the diff:
any vs unknown: any disables type checking for everything downstream. If the type is truly unknown at the call site, use unknown and narrow with a type guard. Flag every any annotation that isn't in a third-party type shim.!): Each foo!.bar is a runtime crash waiting for the condition that makes foo null. Flag unless the null case is provably eliminated by a guard two lines above.{ type: 'a', ... } | { type: 'b', ... }, the switch/if must be exhaustive. Missing default: assertNever(x) is a silent future bug.as Foo): A cast without a preceding type guard means "trust me." Flag every as that isn't a DOM cast (as HTMLInputElement) or a narrow type refinement proven by the preceding condition.Object.keys() without keyof typeof: Object.keys(obj).forEach(k => obj[k]) fails type checking silently at runtime when keys are typed. Require (Object.keys(obj) as Array<keyof typeof obj>) or a typed Object.entries().Promise without await in an async function: Returns the Promise object instead of the resolved value. Flag any return someAsyncFn() inside an async that should return await someAsyncFn().Check these on every React component or hook in the diff:
useEffect dependency array: useEffect(fn) (no array) runs on every render. useEffect(fn, []) runs once. useEffect(fn, [dep]) runs when dep changes. Flag any effect where the deps array is absent or obviously incomplete (function references, object literals, values used inside the effect but not listed).setInterval inside useEffect(fn, []) that reads a stateful value. The fix is either adding the dep or using a ref.key props: Any .map() returning JSX elements must have a stable, unique key. Using array index as key is a bug when the list can reorder or filter. Flag index-keyed lists where items have identity (id, slug, etc.).useMemo/useCallback when those objects are created inline in the parent render. Flag components that could be wrapped in React.memo but aren't, when rendered in tight loops or high-frequency update paths.arr.push(item) on state does not trigger a re-render. Flag any direct mutation of state variables.Check these on every React component or HTML-producing file in the diff:
<img> without alt, or alt="" on non-decorative images. Empty alt skips the element for screen readers; flag when the image conveys content.<button>, <a>, <input> with no visible text, no aria-label, and no aria-labelledby. An icon-only button with no label is invisible to assistive tech.<div onClick={...}> used as a button; <span> used as a link; heading levels skipped (h1 → h3) or used for visual styling instead of document hierarchy. Use the correct element or add role= with keyboard handlers.onClick but not onKeyDown (Enter, Space, arrow keys). Flag interactive elements that can't be reached or operated by keyboard alone.text-gray-400 on white, text-white on light-colored buttons, any low-contrast pairing below ~4.5:1 for body text or ~3:1 for large text.<input> or <select> without an associated <label> (via htmlFor/id) or aria-label. Placeholder text is not a label.aria-hidden="true" on interactive elements (traps keyboard users); role="presentation" on semantically meaningful elements; ARIA roles that contradict the native element's semantics.Check these on every Next.js file in the diff:
'use client' cannot import server-only modules (DB clients, fs, crypto, server-side env vars). Any file without 'use client' that uses useState, useEffect, browser globals (window, document), or event handlers is a runtime crash. Flag the mismatch, not just the symptom.Suspense boundary: Async server components that fetch data and are rendered inside a client component tree need a <Suspense fallback={...}> wrapper. Missing boundaries cause the entire parent tree to suspend without a fallback.error.tsx / loading.tsx: Any new route segment that fetches data or can throw should have both. Flag their absence as P2 when the route is user-facing.process.env.SECRET_KEY in a 'use client' file or in a prop passed from server to client component is exposed in the browser bundle. Only NEXT_PUBLIC_* vars are safe client-side. Flag any non-NEXT_PUBLIC_ env var referenced in client code.generateMetadata / getServerSideProps fetches: These run on every request. An uncached external fetch here is a latency and cost bomb. Flag missing { next: { revalidate: N } } or unstable_cache wrapping.useRouter from next/router in App Router: App Router uses next/navigation. Importing from next/router in an App Router project silently fails or returns stale data. Flag the wrong import.Check on any file that touches routes, layouts, metadata, or public-facing content:
generateMetadata, <Head>, <title>, description, og:title, og:description, og:image, twitter:card. Any removal or blanking of previously populated fields is a regression. Flag if generateMetadata now returns fewer keys than before the change.canonical URL changed, removed, or now pointing to a different domain. Flag any change to canonical logic — canonical changes can consolidate or fragment link equity unintentionally.noindex, nofollow, or X-Robots-Tag: noindex appearing on pages that were previously indexable. Flag any new robots metadata that restricts crawling on a route that wasn't restricted before.sitemap.ts / sitemap.xml not updated when routes change. Flag route additions or removals without a corresponding sitemap change.og:image URLs broken, pointing to localhost, missing dimension params, or removed from previously covered pages. Flag layout-level changes that remove OG image generation entirely.schema.org markup changed, fields removed, or @type changed. Flag removals of @type, name, url, or description from any existing structured data block.llms.txt / AI discoverability: if the repo includes llms.txt or llms-full.txt, flag any changes that expand or restrict what AI crawlers are permitted to see.Check these on any file that touches DB queries:
WHERE id IN (...) or a join. Flag any .map() or for loop that calls db.query(), prisma.find*(), or db.select() inside the body.WHERE, ORDER BY, GROUP BY, or join condition on a column that isn't indexed is a full-table scan. Flag new query predicates on columns with no corresponding index in the migration/schema.INSERT/UPDATE/DELETE calls that must succeed or fail together. Flag any multi-table write that isn't wrapped in db.transaction() / prisma.$transaction().UNIQUE constraint are race-condition bugs at scale. Flag schema definitions where uniqueness is enforced in application code but not the DB.db.select().from(table) with no LIMIT on a user-facing route is a DoS vector and a cost spike. Flag selects with no limit when the table can grow.import of a package not already in the bundle. If the package's minzipped size is >20 KB, flag as P3 with the size estimate. Prefer tree-shaken imports (import { X } from 'pkg' not import pkg from 'pkg').<script src="..."> without async or defer in HTML head blocks page paint. Flag in any HTML template or _document.tsx.import at the top of _app.tsx or a layout file when it could be next/dynamic with ssr: false. Flag routes not in the critical path (settings pages, dashboards, modals).next/image: <img src="..."> bypasses Next.js image optimization. Flag raw <img> tags in Next.js files pointing to non-SVG assets.For --deep reviews on auth changes, payment flows, data migrations, or public API changes, run as separate lanes:
Collect consensus first. If high-severity concerns persist after a fix cycle, keep status at hold and name the smallest next check or patch.
The changed-file import graph is the floor. The bugs that ship hide outside the diff. On --deep, widen past the immediate callers:
git log -L or git blame the changed region. If this area was fixed before, a change that reintroduces the old shape is a regression — cite the prior commit. If it churns repeatedly, flag it as fragile.State what you traced. A whole-repo claim with no symbols named is not evidence.
Check on any new route handler, API endpoint, background job, cron, queue consumer, or service function:
try/catch blocks that catch without logging or Sentry capture. Flag catch (e) {} and catch (e) { console.error(e) } — a caught error that only console.errors is invisible in production. Require Sentry.captureException(e) or equivalent on unexpected errors.Run this automatically at the end of every review. No exceptions. It answers one question: is this safe to deploy right now?
Grade each dimension. Each is pass / conditional / block:
git revert fully undo this? Red flags: schema migrations, irreversible external API calls (email sent, payment charged, data permanently deleted), S3/storage mutations, message queue publishes. Recommend blocking if rollback requires manual data repair.~0% (new feature, flagged), ~partial (specific flow), ~100% (shared middleware, auth, DB query in hot path). Higher blast radius requires more evidence before deploy..github/workflows/ (or equivalent CI) exist and cover the changed surface (build, types, tests)? Are required status checks enforced on main? Recommend blocking if the changed surface has no automated gate and the repo is production-connected.Output block — required at the end of every review:
DEPLOY SAFETY
Breaking changes: pass | conditional | block — [evidence]
Rollback safety: pass | conditional | block — [evidence or red flag]
Blast radius: ~X% — [which path or user segment]
Environment readiness: pass | conditional | block — [missing vars if any]
Dependency changes: pass | conditional | block — [new deps and CVE status]
Data mutations: pass | conditional | block — [irreversible operations if any]
Security delta: improved | neutral | block — [surface changed]
Verdict: SAFE TO DEPLOY | DEPLOY WITH CONDITIONS | DO NOT DEPLOY
Conditions (if any):
This skill emits no letter grade. When the caller wants lane grades too, run $suede-code (findings + grade) or $suede-code-grader (grade only) — those two carry the canonical Instant-F trigger list, grade caps, and A-F scale. Instant-F patterns found here (hardcoded secrets, injection, auth bypass, unverified payment webhooks, destructive migrations with no rollback, plaintext sensitive data) are P0 findings and set the Ship Gate to hold.
Run automatically on every review. Scan the raw diff for content that should never reach git history. No exception for "it's just a branch" — dirty commits propagate.
Check every line added (+) in the diff for:
.env variable values hardcoded in source. Patterns: sk_, pk_, Bearer , -----BEGIN, password=, secret=, token=, AKIA, ghp_, xoxb-, long hex/base64 strings adjacent to key-like identifiers.console.log, console.error, debugger, print(, pprint(, binding.pry, byebug, dd(, dump(, var_dump(, hardcoded test user IDs, TODO: remove, FIXME: before merge, HACK:, commented-out blocks of real logic.<<<<<<<, =======, >>>>>>>, |||||||. A conflict marker in a + line means the file was not fully resolved.node_modules/, .next/, dist/, build/, *.pyc, __pycache__/, .DS_Store, *.log, *.sqlite, *.db, migration snapshot files that weren't meant to commit, generated lock-file noise from the wrong package manager.[WIP], DO NOT MERGE, TEMP:, SKIP CI in non-CI files, stash this, placeholder copy like lorem ipsum, foo, bar, asdf in production-facing strings.public/ or assets/ directory.Score:
COMMIT DIRT SCORE
Secrets / credentials: clean | suspicious | dirty — [pattern found or "none"]
Debug artifacts: clean | suspicious | dirty — [symbol or line or "none"]
Conflict markers: clean | dirty — ["none" or file:line]
Accidentally staged: clean | dirty — [path or "none"]
WIP breadcrumbs: clean | suspicious | dirty — [marker or "none"]
Oversized / binary: clean | dirty — [file and size or "none"]
Exposed internals: clean | suspicious | dirty — [pattern or "none"]
Overall dirt rating: CLEAN | SUSPICIOUS | DIRTY
A DIRTY overall rating automatically sets the Ship Gate to hold.
Lead with findings, ordered by severity. Group repeated patterns once: "This pattern appears in 4 files: [list]. Fix described once below."
P0 / P1: use the full block:
[P0] path/to/file.ts:142
Issue: JWT secret falls back to empty string; any token is valid when SECRET is unset.
Fix: `process.env.JWT_SECRET ?? (() => { throw new Error('JWT_SECRET required') })()`
Verify: set JWT_SECRET="" and curl /api/me — expect 401, currently 200.
OWASP: A02 Cryptographic Failures
Confidence: high
P2 / P3: one line each:
[P2] components/Feed.tsx:88 — missing key prop on .map() return; use item.id not index. TS will catch post-fix.
[P3] utils/format.ts:12 — magic number 86400; extract as SECONDS_PER_DAY.
Severity:
If the issue cannot be tied to a file, route, command, state, or user-visible behavior, mark it as an open question instead.
When asked to fix, convert each accepted finding into an agent-ready brief:
Fix one risk cluster at a time. After fixing, rerun the relevant review mode and do not close the finding until evidence confirms it.
When the user asks to apply fixes (--fix flag or equivalent instruction):
Run the web baseline automatically on auth/, api/, middleware/, routes/,
pages/api/, or code importing crypto, session, or payment modules. Cite the
exact standard and category in each finding. Use the current official lists;
do not remap a finding to an older category number.
Official list: https://owasp.org/Top10/
For APIs, explicitly check API1 Broken Object Level Authorization, API2 Broken Authentication, API3 Broken Object Property Level Authorization, API4 Unrestricted Resource Consumption, API5 Broken Function Level Authorization, API6 Unrestricted Access to Sensitive Business Flows, API7 Server Side Request Forgery, API8 Security Misconfiguration, API9 Improper Inventory Management, and API10 Unsafe Consumption of APIs. Trace each request through object/property/function authorization, quotas and cost bounds, business-flow abuse controls, outbound URL policy, versioned API inventory, and validation of third-party responses.
Official list: https://owasp.org/API-Security/editions/2023/en/0x10-api-security-risks/
For native or hybrid mobile changes, map findings to MASVS-STORAGE, MASVS-CRYPTO, MASVS-AUTH, MASVS-NETWORK, MASVS-PLATFORM, MASVS-CODE, MASVS-RESILIENCE, or MASVS-PRIVACY. Check local data and backup exposure, key handling, authentication/session flows, TLS and endpoint trust, IPC/deep links/web views, update/runtime safety, tamper resistance where the threat model requires it, and privacy-minimized collection/disclosure.
Official standard: https://mas.owasp.org/MASVS/
Always check these when relevant:
Check on every Swift file in the diff. iOS ships on a release cycle with no hot-fix — a crash here is live for days.
!, try!, as!): each crashes when the optional is nil or the cast fails. Flag unless the failure case is provably eliminated immediately above. try! on anything that throws at runtime (decoding, file I/O) is P1.self inside a stored property, Task, Combine sink, or callback leaks the owner. Require [weak self] (or [unowned self] only when lifetime is provably bound).@State, @Published, or UIKit views from a background context is undefined behavior. Flag observable or UI mutation inside Task.detached, a URLSession callback, or a background queue with no hop to @MainActor / DispatchQueue.main.nonisolated used to silence a warning rather than because access is truly isolation-free; @MainActor work awaited from a path that should already be on main.decode on fields the backend does not guarantee.ForEach over a non-stable id (index, or a value that changes) loses state and breaks animations; onAppear used for one-time work re-fires on re-insertion; @StateObject vs @ObservedObject confusion (owning vs observing) re-creates or prematurely releases models.Pair with iOS / Native Contract Drift below: crash risk here, contract break there.
Check on any API route, response shape, auth header, or shared type that iOS or other native consumers depend on:
Authorization, X-Session-Token, or similar headers are validated server-side. If the server changes the expected format, the iOS app gets 401s on every request.{ error: string } or { code: number }. Changing the error envelope silently breaks native error handling with no visible failure on the web surface.types/, shared/, or lib/ consumed by both web and a native build step. A field rename or removal here breaks both surfaces simultaneously.Blast radius note: identify which iOS version is currently in production. If the app hasn't shipped an update in >2 weeks, assume the old contract is live for the majority of users and treat breaking changes as P0.
Flag tech debt patterns as P3, group by file, don't block ship. Do not block a ship on P3 tech debt unless it directly obscures a P0/P1 bug. File as follow-up.
Code that works but does not do what it claimed is still a defect.
For a PR summary or any review spanning four or more files, lead with a walkthrough so the reader sees the shape before the findings:
sequenceDiagram (request → handler → service → data) or flowchart when the change moves data across three or more boundaries or alters an async, auth, or payment path. Skip it for a localized edit — a diagram of a one-file change is noise.The walkthrough orients; it never replaces findings, and stays above the Findings block.
False positives are why reviewers get muted. Self-check the draft findings before emitting:
For a review:
Findings
Deploy Safety
Commit Dirt Score
Open Questions
Verification Checked
Ship Gate
For no findings, say that clearly and name any residual risk or unrun checks.
For a PR summary:
Summary
Change Map
Risk Map
Verification
Ship Gate
The ship gate is always the last line of a review output:
SHIP GATE: hold | ship-with-caveats | ship
Reason: [one sentence, naming the blocking finding ID or named caveat]
hold: a blocker or high-risk unknown remains.ship-with-caveats: no blocker remains, but named non-critical caveats exist.ship: no known blocker remains and required verification passed.