Code reviewer
Review changes for correctness, security, and adherence to this project's
conventions. Report findings; do not silently rewrite code unless asked.
This skill is the review rubric. Several concerns are owned by other skills —
load them instead of re-deriving their rules:
- Svelte 5 idioms →
svelte-core-bestpractices
.svelte / .svelte.ts analysis → svelte-code-writer (run svelte-autofixer)
- Test quality →
test-writer
- Module/architecture design →
deep-modules
How to review
- Get the diff scope (
git diff, a PR, or the named files). Review only what
changed plus what it directly touches.
- Read the changed files in full for context — never review from the diff alone
when the surrounding code matters.
- Walk each checklist section below; for
.svelte/.svelte.ts files also apply
svelte-core-bestpractices and svelte-code-writer.
- Report findings grouped by severity (see Output). Reference
file:line.
- Confirm the verification gates pass (or call out that they must).
Be direct and specific. Prefer pointing at the exact line and the concrete fix
over general advice. Flag real problems only — do not pad the review.
Severity
- Blocker — security, data-loss, broken trust boundary, failing gate, user-visible breakage.
- Should-fix — correctness/maintainability issue, convention violation, missing test.
- Nit — style/readability, optional improvement.
SvelteKit checklist
- Server-only code lives in
$lib/server/. Anything touching secrets, DB,
owned services, or private env MUST be under $lib/server/ (or a +server.ts
/ +*.server.ts) so SvelteKit forbids client import. A client-reachable module
importing such code is a blocker.
- Env access is segregated.
$env/static/private and $env/dynamic/private
must never be reachable from client code; use $env/*/public (and the
PUBLIC_ prefix) for anything shipped to the browser. Hard-coded secrets/keys
are a blocker.
load choice is deliberate. +page.server.ts load for anything needing
secrets, private env, DB, or large/cacheable server data; universal load
only when it must also run on the client. Don't leak server-only data through a
universal load.
- No load waterfalls. Independent fetches inside one
load should run
concurrently (Promise.all / parallel awaits), not sequentially.
- Returned data is serializable from server
load/actions (no class
instances/functions that won't survive devalue), and not over-fetched.
- Form actions over ad-hoc endpoints for mutations from
<form>; validate
and re-check authorization server-side; use fail() for validation errors and
redirect() (thrown) for post-success navigation.
- Invalidation is correct. After mutations, rely on action return + automatic
invalidation or
invalidate/depends; don't hand-roll stale client state.
+server.ts endpoints set correct status codes, Content-Type, and
caching headers; validate input; never trust the request body for
authorization or pricing.
- Errors use SvelteKit primitives — throw
error(status, ...) for expected
failures, +error.svelte for route errors, <svelte:boundary> for async
component errors. Don't swallow errors or leak stack traces/internal messages
to the client.
SSR / state checklist
- No request state in module scope. State shared via a top-level
let/$state in a .svelte.ts module is shared across all users on the
server and leaks between requests — a blocker. Use createContext (see
svelte-core-bestpractices) or pass through load data.
- No
if (browser) guards inside $effect (effects don't run on the server
anyway); SSR-unsafe browser API access belongs in onMount/effects, not in
component/module top-level that runs during SSR.
Storefront checklist
- The client is never the source of truth. Information must be
(re)computed/validated on the server from trusted sources.
- Auth/ownership checked on every protected load and action — don't rely on
hidden UI; verify the session owns the cart/order/account being acted on.
- SEO essentials per listing page — unique
<title> and meta
description via <svelte:head>, canonical URL, and product structured data
where relevant; avoid indexing cart/checkout/account pages.
- Accessibility — images have meaningful
alt (empty alt="" only for
decorative); interactive elements are real buttons/links and keyboard-operable;
forms have labels; query/test by ARIA role (matches existing test style).
- Performance — keyed
{#each} for product lists (never index keys); avoid
shipping large server data to the client; sized/lazy images; sensible cache
headers / preload on navigations; no N+1 fetches.
Conventions & gates
- Runes mode is enforced. Flag any legacy syntax:
export let, $:,
on:click, <slot>, $$props/$$restProps, svelte:component, stores used
where $state classes/context fit. (Full list in svelte-core-bestpractices.)
- Formatting/lint follows
@canonical/biome-config: 2-space indent, double
quotes, organized imports. Don't flag things npm run fix auto-resolves —
note them as "run npm run fix".
- Inclusive naming is a hard CI gate; flag non-inclusive terms.
- Before approving, the change must pass:
npm run svelte-check,
npm run check, npm test, and npm run build. Call out any not yet run.
Output
## Review summary
<1–3 sentences: overall assessment + go/no-go>
### Blockers
- `path:line` — <issue> → <fix>
### Should-fix
- `path:line` — <issue> → <fix>
### Nits
- `path:line` — <note>
Omit empty sections. If clean, say so plainly and list which gates still need to
run.
1---2name: code-reviewer3description: Use when reviewing code changes. Checks Svelte 5 runes usage, SvelteKit data-loading/endpoints, server-only and env/secret boundaries, SSR state leakage, storefront pitfalls (money, cart/price/stock trust boundaries, payments, SEO, a11y, caching), tests, and the repo's verification gates. Use ONLY for reviewing/critiquing existing code, not for writing new features.4---56# Code reviewer78Review changes for correctness, security, and adherence to this project's9conventions. Report findings; do not silently rewrite code unless asked.1011This skill is the **review rubric**. Several concerns are owned by other skills —12load them instead of re-deriving their rules:1314- **Svelte 5 idioms** → `svelte-core-bestpractices`15- **`.svelte` / `.svelte.ts` analysis** → `svelte-code-writer` (run `svelte-autofixer`)16- **Test quality** → `test-writer`17- **Module/architecture design** → `deep-modules`1819## How to review20211. Get the diff scope (`git diff`, a PR, or the named files). Review only what22 changed plus what it directly touches.232. Read the changed files in full for context — never review from the diff alone24 when the surrounding code matters.253. Walk each checklist section below; for `.svelte`/`.svelte.ts` files also apply26 `svelte-core-bestpractices` and `svelte-code-writer`.274. Report findings grouped by severity (see Output). Reference `file:line`.285. Confirm the verification gates pass (or call out that they must).2930Be direct and specific. Prefer pointing at the exact line and the concrete fix31over general advice. Flag real problems only — do not pad the review.3233## Severity3435- **Blocker** — security, data-loss, broken trust boundary, failing gate, user-visible breakage.36- **Should-fix** — correctness/maintainability issue, convention violation, missing test.37- **Nit** — style/readability, optional improvement.3839## SvelteKit checklist4041- **Server-only code lives in `$lib/server/`.** Anything touching secrets, DB,42 owned services, or private env MUST be under `$lib/server/` (or a `+server.ts`43 / `+*.server.ts`) so SvelteKit forbids client import. A client-reachable module44 importing such code is a **blocker**.45- **Env access is segregated.** `$env/static/private` and `$env/dynamic/private`46 must never be reachable from client code; use `$env/*/public` (and the47 `PUBLIC_` prefix) for anything shipped to the browser. Hard-coded secrets/keys48 are a blocker.49- **`load` choice is deliberate.** `+page.server.ts` `load` for anything needing50 secrets, private env, DB, or large/cacheable server data; universal `load`51 only when it must also run on the client. Don't leak server-only data through a52 universal `load`.53- **No load waterfalls.** Independent fetches inside one `load` should run54 concurrently (`Promise.all` / parallel awaits), not sequentially.55- **Returned data is serializable** from server `load`/actions (no class56 instances/functions that won't survive devalue), and not over-fetched.57- **Form actions over ad-hoc endpoints** for mutations from `<form>`; validate58 and re-check authorization server-side; use `fail()` for validation errors and59 `redirect()` (thrown) for post-success navigation.60- **Invalidation is correct.** After mutations, rely on action return + automatic61 invalidation or `invalidate`/`depends`; don't hand-roll stale client state.62- **`+server.ts` endpoints** set correct status codes, `Content-Type`, and63 caching headers; validate input; never trust the request body for64 authorization or pricing.65- **Errors use SvelteKit primitives** — throw `error(status, ...)` for expected66 failures, `+error.svelte` for route errors, `<svelte:boundary>` for async67 component errors. Don't swallow errors or leak stack traces/internal messages68 to the client.6970## SSR / state checklist7172- **No request state in module scope.** State shared via a top-level73 `let`/`$state` in a `.svelte.ts` module is shared across all users on the74 server and **leaks between requests** — a blocker. Use `createContext` (see75 `svelte-core-bestpractices`) or pass through `load` data.76- **No `if (browser)` guards inside `$effect`** (effects don't run on the server77 anyway); SSR-unsafe browser API access belongs in `onMount`/effects, not in78 component/module top-level that runs during SSR.7980## Storefront checklist8182- **The client is never the source of truth.** Information must be 83 (re)computed/validated on the server from trusted sources.84- **Auth/ownership checked on every protected load and action** — don't rely on85 hidden UI; verify the session owns the cart/order/account being acted on.86- **SEO essentials per listing page** — unique `<title>` and meta87 description via `<svelte:head>`, canonical URL, and product structured data88 where relevant; avoid indexing cart/checkout/account pages.89- **Accessibility** — images have meaningful `alt` (empty `alt=""` only for90 decorative); interactive elements are real buttons/links and keyboard-operable;91 forms have labels; query/test by ARIA role (matches existing test style).92- **Performance** — keyed `{#each}` for product lists (never index keys); avoid93 shipping large server data to the client; sized/lazy images; sensible cache94 headers / `preload` on navigations; no N+1 fetches.9596## Conventions & gates9798- **Runes mode is enforced.** Flag any legacy syntax: `export let`, `$:`,99 `on:click`, `<slot>`, `$$props`/`$$restProps`, `svelte:component`, stores used100 where `$state` classes/context fit. (Full list in `svelte-core-bestpractices`.)101- **Formatting/lint** follows `@canonical/biome-config`: 2-space indent, double102 quotes, organized imports. Don't flag things `npm run fix` auto-resolves —103 note them as "run `npm run fix`".104- **Inclusive naming** is a hard CI gate; flag non-inclusive terms.105- Before approving, the change must pass: `npm run svelte-check`,106 `npm run check`, `npm test`, and `npm run build`. Call out any not yet run.107108## Output109110```111## Review summary112<1–3 sentences: overall assessment + go/no-go>113114### Blockers115- `path:line` — <issue> → <fix>116117### Should-fix118- `path:line` — <issue> → <fix>119120### Nits121- `path:line` — <note>122```123124Omit empty sections. If clean, say so plainly and list which gates still need to125run.