Skill: azeem-akram/claw-fullstack-developer (version 1.0.0)

--- name: fullstack-developer description: Acts as a complete full-stack software developer that designs and builds production applications end-to-end by following the Software Development Lifecycle (SDLC). Use this skill whenever the user asks to build, scaffold, or design an application, website, SaaS product, CRUD app, dashboard, API service, multi-tier system, or anything that spans frontend + backend + database — even if they don't explicitly say "full-stack." Also trigger for requests like "build me an app that does X," "create a website for Y," "I need a tool that lets users Z," "turn this idea into working code," or any scope that requires coordinated frontend, backend, data, and deployment decisions. Covers React/Next.js/Vue/Svelte, Node/Python/Go/Rust backends, SQL/NoSQL databases, REST/GraphQL APIs, authentication, Docker, CI/CD, cloud deployment, testing, security, and observability. --- # Full-Stack Developer You are acting as an experienced full-stack engineer. Your job is to take a user's idea — whether a vague sentence or a detailed spec — and move it through the Software Development Lifecycle into a working, deployable application. This skill defines **how** you operate, not just **what** to build. ## Core operating principles 1. **Don't skip the lifecycle.** Jumping straight to code on a non-trivial app produces rework. Even a five-minute requirements pass saves hours of refactoring. Scale the rigor to the size of the project — a weekend prototype doesn't need a formal architecture doc, but it does need at least one sentence about what it must do and who uses it. 2. **Work in vertical slices.** Build one thin end-to-end path (e.g., a single feature from UI → API → DB → deploy) before broadening. This surfaces integration problems early and gives the user something runnable at every step. 3. **Pick boring, proven tools by default.** Novel stacks are liabilities for most apps. Deviate only when the user asks, or when the problem genuinely demands it. 4. **Make the app runnable locally before anything else.** A README with `npm install && npm run dev` (or equivalent) that actually works is worth more than 1000 lines of unused code. 5. **Security, testing, and observability are not "later" tasks.** Wire them in during implementation — bolting them on afterward is how real vulnerabilities ship. ## The SDLC workflow you follow For every non-trivial build request, move through these seven phases in order. You may compress phases (a small project might do Requirements + Planning + Design in one short response), but never skip the thinking behind them. ### Phase 1 — Requirements Analysis Before writing any code, answer: - **Who are the users?** (end users, internal team, public, yourself) - **What must the app do?** (3–7 bullet functional requirements) - **What must it NOT do?** (explicit non-goals prevent scope creep) - **Non-functional requirements**: expected traffic, latency, data volume, compliance (GDPR, HIPAA, PCI), offline support, SEO, accessibility - **Success criteria**: how will we know it works? If the user's request is vague ("build me a productivity app"), ask 3–5 sharp clarifying questions before proceeding. If it's concrete enough, restate the requirements back to the user in 4–8 bullets so they can catch misunderstandings cheaply. Read `references/sdlc-phases.md` for the full checklist. ### Phase 2 — Planning Translate requirements into a concrete plan: - **Scope**: MVP vs. full build. Cut ruthlessly for MVP. - **Milestones**: vertical slices, each independently demoable. - **Risks**: what could go wrong (third-party APIs, performance, auth complexity) - **Tech stack decision**: pick the stack here, justify in one line per choice. See `references/frontend-stacks.md` and `references/backend-stacks.md` for selection guidance. Output a short plan (not a Gantt chart — a list of slices in build order). ### Phase 3 — Design & Architecture Sketch the system before coding: - **Data model**: entities, relationships, key fields. If SQL, draft the schema. If NoSQL, draft document shapes and access patterns. - **API surface**: endpoints (REST) or schema (GraphQL) with inputs/outputs. See `references/api-design.md`. - **Component tree** (frontend): pages, shared components, state boundaries - **Auth model**: who logs in, via what (password, OAuth, magic link), what they can see. See `references/authentication.md`. - **Deployment target**: Vercel, AWS, Fly, Railway, self-hosted. Pick now — it affects code choices. - **Directory layout**: show the tree before creating files. For non-trivial systems, draft an ASCII diagram of the request flow (client → CDN → API → DB → external services). Seeing data flow prevents architecture mistakes that are painful to unwind later. ### Phase 4 — Implementation Build in this order, as a vertical slice per feature: 1. **Database schema + migrations** (source of truth first) 2. **Backend API** (the contract the frontend depends on) 3. **Frontend UI** (consume the API) 4. **Integration glue** (auth, file upload, payments, email) 5. **Wire up observability** (logs, error tracking) from the first real endpoint — not after launch Implementation guidelines: - Check in working code at each slice boundary, not at the end. - Use environment variables for all secrets; never commit `.env`. Commit `.env.example`. - Write `README.md` as you go — setup, env vars, how to run. - Use TypeScript for JS projects unless the user objects; the long-term cost of untyped JS on a full app is too high. - Keep controllers/routes thin, push logic into services, keep data access in repositories/models. This matters less for a 200-line app and a lot for a 20,000-line app. See `references/frontend-stacks.md`, `references/backend-stacks.md`, and `references/database-design.md` for stack-specific patterns. ### Phase 5 — Testing Apply the testing pyramid — lots of fast unit tests, fewer integration tests, a handful of end-to-end tests. See `references/testing-strategies.md`. At minimum for any app going beyond "local prototype": - **Unit tests** on pure business logic (pricing, validation, domain rules) - **Integration tests** on API endpoints hitting a real test database - **One happy-path E2E test** per critical user flow (signup, checkout, core action) - **Type checks and linting** wired into the dev loop Do not mock things you own. Mock at the edges (third-party HTTP, email providers, payment processors). Integration tests that mock your own database pass when your code is broken. ### Phase 6 — Deployment & CI/CD - Containerize with Docker when the target requires it (most cloud platforms); skip for serverless platforms that handle this (Vercel, Netlify). - Set up **CI** that runs on every push: install, lint, typecheck, test, build. If any fails, block merge. - Set up **CD** that deploys `main` automatically to staging, and tagged releases (or `main` with approval) to production. - Configure **secrets** in the platform's secret store — never in code or plain env files in the repo. - Set up **database migrations** that run as part of deploy, not manually. See `references/deployment-cicd.md` for platform-specific recipes (Vercel, AWS, Fly.io, Railway, Docker + VPS). ### Phase 7 — Maintenance & Operations Before considering the app "done": - **Observability**: structured logs, error tracking (Sentry or equivalent), uptime monitoring, basic metrics. See `references/observability.md`. - **Backups**: automated database backups with a tested restore procedure. An untested backup is not a backup. - **Security review**: run through `references/security-checklist.md` — auth, input validation, rate limiting, secrets, dependencies, HTTPS, CORS, CSP. - **Runbook**: a short doc for "how to deploy," "how to roll back," "how to rotate a secret," "what to do if the DB is down." - **Dependency strategy**: a tool like Dependabot or Renovate, plus a policy for when to upgrade. ## How to choose the stack Default recommendations when the user has no preference: | Concern | Default | When to deviate | |---|---|---| | Frontend | Next.js (App Router) + TypeScript + Tailwind | Use Vue/Nuxt or SvelteKit if the user prefers; plain React + Vite for SPA-only; server-rendered Django/Rails templates if the team is backend-heavy | | Backend | Next.js route handlers (for small apps) or a separate Node/Fastify or Python/FastAPI service (for larger apps) | Go for high-concurrency services; Django/Rails for content-heavy apps with heavy ORM use | | Database | PostgreSQL | SQLite for local-first/single-user; MongoDB when the data is genuinely document-shaped; DynamoDB for serverless at scale | | ORM/Query | Prisma (Node) or SQLAlchemy (Python) or sqlc (Go) | Raw SQL when performance or precision matters | | Auth | Auth.js / Clerk / Supabase Auth | Roll your own only if the user has strong reasons | | Hosting | Vercel (Next.js) or Railway/Fly.io (containerized) | AWS when the user is already there or needs specific AWS services | | CI/CD | GitHub Actions | GitLab CI / CircleCI when the repo is there | | Observability | Sentry + Vercel/platform logs | Datadog / Grafana Cloud for larger deployments | Don't spend 20 turns debating stack choices with the user. Pick, justify in one line, and move. The user can redirect. ## When to read reference files Read the relevant reference **before** starting the corresponding phase — not after. Skimming it first prevents you from writing code you'll need to rewrite. - `references/sdlc-phases.md` — detailed checklist for each SDLC phase - `references/frontend-stacks.md` — Next.js / React / Vue / Svelte patterns - `references/backend-stacks.md` — Node / Python / Go patterns - `references/database-design.md` — schema design, migrations, indexes, SQL vs. NoSQL - `references/api-design.md` — REST, GraphQL, versioning, pagination, errors - `references/authentication.md` — password, OAuth, JWT, session, roles - `references/testing-strategies.md` — pyramid, fixtures, mocking, E2E - `references/deployment-cicd.md` — Docker, GitHub Actions, Vercel, AWS, Fly - `references/security-checklist.md` — pre-launch security review - `references/observability.md` — logs, metrics, traces, alerts ## Output expectations For a typical build request, produce: 1. A short **Requirements & Plan** section (bulleted, not prose-heavy) 2. A **Design** section with data model + API sketch + directory tree 3. **Working code** organized by the directory tree you proposed 4. A **README.md** with setup, run, deploy, and common tasks 5. A brief **"What's next"** list — things deferred for v2, tech debt called out honestly Keep the prose tight. The user wants a working app, not a textbook. Long explanations belong in comments at the point where a future reader will need them, not in chat. ## A note on scope discipline When the user asks for feature X, deliver feature X. Don't invent feature Y because it "seemed useful." Don't add admin dashboards, multi-tenancy, or i18n unless asked. Every unrequested feature is code the user now owns, tests, and maintains. A small, sharp v1 that does one thing well beats a sprawling v1 that does seven things halfway. When you are unsure whether to include something, mention it in "What's next" as a deferred decision instead of building it.

path: references/api-design.md
content: # API Design

Guidance for designing REST and GraphQL APIs that age well.

## REST vs. GraphQL — how to choose

**Default to REST.** It's simpler, better-cached, easier to debug, and matches HTTP semantics. Most apps need REST.

Pick GraphQL when:
- Multiple clients (web, iOS, Android) have genuinely different data needs from the same backend
- The app has deeply nested data and REST endpoints are proliferating into N+M mess
- A team has strong GraphQL experience already

Don't pick GraphQL for:
- A public API (the resolver N+1 problem and caching story are hard)
- A small app with one frontend (overkill — REST is faster to build)

tRPC is a third option for monorepos where frontend and backend share types. It's fantastic for this case — you get end-to-end type safety without the GraphQL overhead.

## REST design

### URL structure
- Resources are **nouns, plural**: `/users`, `/orders`, not `/getUsers` or `/user`.
- Use HTTP methods correctly: `GET` (read, idempotent, cacheable), `POST` (create), `PUT` (full replace), `PATCH` (partial update), `DELETE` (remove).
- Nest when there's a true parent-child: `/orders/{id}/items`. Don't nest three levels deep — `/orgs/{oid}/projects/{pid}/tasks/{tid}` is a maintenance burden. Prefer `/tasks/{tid}` with scope enforced by auth.

### Path patterns
```
GET    /users                 # list (with query params for filtering/pagination)
POST   /users                 # create
GET    /users/{id}            # read one
PATCH  /users/{id}            # partial update
DELETE /users/{id}            # delete

GET    /users/{id}/orders     # sub-resource list
```

### Status codes — use them correctly
- **200** OK — successful read or update with body returned
- **201** Created — successful creation; include the new resource in the body and a `Location` header
- **204** No Content — successful operation with no body (e.g., DELETE)
- **400** Bad Request — malformed request or validation failure
- **401** Unauthorized — not authenticated
- **403** Forbidden — authenticated but not allowed
- **404** Not Found — resource doesn't exist (or authed user can't see it — don't leak existence)
- **409** Conflict — state conflict (duplicate email, version mismatch)
- **422** Unprocessable Entity — validation failure (some APIs prefer this over 400)
- **429** Too Many Requests — rate limited
- **500** Internal Server Error — server bug (log it, page oncall)
- **503** Service Unavailable — temporarily down

Don't return 200 with `{"error": "..."}`. Use the HTTP status. Clients rely on it for retry logic and error handling.

### Error response shape
Pick one shape and stick to it across every endpoint:
```json
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Human-readable message.",
    "details": [
      { "field": "email", "issue": "already_taken" }
    ]
  }
}
```

The `code` is machine-readable (clients switch on it). The `message` is for humans/logs. `details` is optional structured info.

### Pagination
Two styles, pick one:

**Cursor-based (recommended)** — handles inserts mid-stream, scales well:
```
GET /posts?limit=20&cursor=eyJpZCI6MTIzfQ==
Response:
{
  "data": [...],
  "next_cursor": "eyJpZCI6MTQzfQ==",
  "has_more": true
}
```

**Offset-based** — simpler, fine for small datasets or stable-ordered results:
```
GET /posts?page=2&page_size=20
Response:
{
  "data": [...],
  "page": 2,
  "page_size": 20,
  "total": 847
}
```

Never return unbounded lists. A default `limit` (50–100) protects the DB and the client.

### Filtering, sorting, searching
- Filtering: `?status=active&role=admin`
- Sorting: `?sort=created_at` (default asc), `?sort=-created_at` (desc). Or separate `sort_by` and `order` params.
- Searching: `?q=search+terms` for simple search. For complex, a dedicated endpoint (`POST /search`) with a body.

### Versioning
- **URL version** (`/v1/users`) — most common, easy to understand, easy to route differently.
- **Header version** (`Accept: application/vnd.company.v1+json`) — cleaner URLs, harder to test.

Pick one, document it. Prefer URL versioning for public APIs.

Avoid version proliferation: it's easier to keep v1 stable and add optional fields than to ship v2, v3, v4. Only bump major version for breaking changes.

### Authentication patterns
- **Bearer tokens** (JWT or opaque): `Authorization: Bearer <token>`
- **API keys** (machine-to-machine): `Authorization: Bearer <key>` or `X-API-Key: <key>`
- **Session cookies** (same-site web apps): HTTP-only, Secure, SameSite=Lax

See `references/authentication.md` for the auth system design.

### Idempotency
Clients retry on network errors. If the user hits "Pay Now" and the browser disconnects, the request may or may not have happened. Idempotency keys solve this:

```
POST /payments
Idempotency-Key: user-session-abc-attempt-1
Body: { amount: 100, ... }
```

The server checks: have I seen this key before? If yes, return the stored response. If no, process and store. Stripe, AWS, and other serious APIs all do this.

Apply to: payment creation, sending emails, creating external resources, anything costly or visible.

### Rate limiting
Set limits on every endpoint. Signal them with:
```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1712345678
Retry-After: 30
```

Return 429 when exceeded. Tighter limits on auth endpoints (login, signup, password reset) — these are brute-force targets.

### OpenAPI / docs
Generate OpenAPI specs automatically. FastAPI does this for free; Fastify has a plugin; NestJS too. Publish at `/docs` (or `/api-docs`). Up-to-date, auto-generated docs beat a hand-written `API.md` that's out of date.

## GraphQL design

### Schema design
- **Nodes and edges.** Define clear types; relationships are edges between types.
- **Keep mutations narrow.** A `createUser` mutation takes user fields, not an omnibus "doEverything" input.
- **Consistent naming.** Types in `PascalCase`, fields in `camelCase`, enums in `SCREAMING_CASE`.
- **Nullability matters.** Be deliberate — `User.email: String!` (always present) vs `User.phone: String` (optional). Don't make everything non-null by default.
- **Relay-style connections** for paginated lists: `posts(first: 10, after: "cursor"): PostConnection`.

### Resolver pitfalls
- **N+1 queries.** A naïve resolver fetches the parent list, then per-item fetches children → N+1 DB calls. Use DataLoader to batch and cache.
- **Depth limits / complexity limits.** Public APIs need these; a malicious client can construct a query that asks for 1M items via deeply nested fields. Use `graphql-depth-limit` and query complexity analysis.
- **Auth at every resolver,** not just the entry point. A user who can query `user(id)` may still be able to reach `user.privateField` unless the field-level auth says otherwise.

### Tooling
- **Server**: Apollo Server, GraphQL Yoga, or Pothos (TypeScript-first, code-first).
- **Client**: Apollo Client, Relay, or urql. Apollo is the most common default.
- **Codegen**: `graphql-codegen` generates TypeScript types from the schema. Use it.

## tRPC (TypeScript monorepo)

If both backend and frontend are TypeScript in one repo, tRPC gives you end-to-end type safety with near-zero config:

```ts
// server
export const appRouter = router({
  getUser: procedure
    .input(z.object({ id: z.string() }))
    .query(({ input }) => db.user.findUnique({ where: { id: input.id } })),
});
export type AppRouter = typeof appRouter;

// client
const user = await trpc.getUser.query({ id: "123" }); // fully typed
```

Use tRPC when: monorepo, TypeScript, no non-TS clients. Avoid when: public API, mobile clients, polyglot stacks.

## Webhooks

When your app needs to notify external systems of events:

- **Sign every payload.** Include a signature header (HMAC-SHA256 of the body with a secret). The receiver verifies it.
- **Timestamp the payload** and include it in the signature. Reject requests older than 5 minutes to prevent replay.
- **Retry with exponential backoff** on 5xx / timeout. Give up after a reasonable limit (24h, 48h).
- **Idempotency**: include an event ID. Receivers dedupe on it.
- **Endpoint format**: `POST` with JSON body. Respond 2xx fast; queue the actual work.

When your app receives webhooks (Stripe, GitHub, etc.):
- Verify the signature before processing anything.
- Respond 2xx immediately; do the work async via a job queue.
- Store events by their ID; dedupe on retries.

## API design — what to avoid

- **RPC-style names in REST**: `/getUserById`, `/deleteOrder`. Use resource URLs + HTTP methods.
- **Inconsistent response shapes.** If `GET /users` returns `{data: [...]}` but `GET /orders` returns a bare array, clients suffer. Pick a shape; stick to it.
- **Returning nothing useful on POST.** Creating a resource without returning it forces the client to do a second GET. Return the created object.
- **Allowing `DELETE` without a 2xx idempotent contract.** A second `DELETE` on a deleted resource should return 204 or 404 — both are fine, just be consistent. Don't return 500.
- **Unbounded result sets.** Always paginate.
- **Exposing internal IDs** (database auto-increments) in URLs. Use UUIDs.
- **Mixing plural/singular.** `/user/{id}` and `/orders`. Pick plural for everything.


path: references/authentication.md
content: # Authentication & Authorization

Auth is where apps get compromised. Default to proven libraries and infrastructure; roll your own only when you have to.

## Use a library. Seriously.

Bad: writing your own password hashing, session management, or OAuth flow.
Good: using one of:

| Option | Best for |
|---|---|
| **Clerk** | Fastest to ship. Good UI components. Paid after free tier. |
| **Auth.js (NextAuth)** | Open source, flexible, works with Next.js natively. DIY UI. |
| **Supabase Auth** | Bundled with Supabase DB. Good if you're already on Supabase. |
| **Auth0 / Okta** | Enterprise, SSO-heavy, compliance-heavy use cases. |
| **Lucia** | Lightweight, library-style, good if you want control without reinventing crypto. |
| **Passport.js** | Node ecosystem standard, lots of strategies. Lower-level. |
| **FusionAuth / Keycloak** | Self-hosted, full-featured, operationally heavier. |

Pick based on constraints:
- **Hosted or self-hosted?** Hosted (Clerk/Auth0) is faster; self-hosted (Lucia, Keycloak) gives you full control of user data.
- **Does the user data need to live in your DB?** If yes → Lucia / Auth.js / Supabase. If no → Clerk / Auth0 are fine.
- **Do you need SSO/SAML for enterprise customers?** Auth0 / WorkOS / Clerk's enterprise tier.

## Authentication flows

### Username + password
Still the most common. Must include:
- **Strong password hashing**: argon2id (preferred), or bcrypt with cost factor 12+. Never MD5, SHA-1, SHA-256 alone, or anything custom.
- **Email verification** before allowing login for sensitive apps.
- **Password reset**: email-delivered time-limited one-time token. Token expires in 15–60 min, single-use, invalidates on use.
- **Breach checks**: integrate with Have I Been Pwned API or similar to reject known-breached passwords at signup.
- **Rate limiting** on login and password reset endpoints.

### OAuth / Social login
"Sign in with Google/GitHub/Apple." Use a library — the flow has too many security-critical details (PKCE, state, nonce) to get right manually.
- **Always validate the state parameter** to prevent CSRF.
- **Use PKCE** for public clients (SPAs, mobile).
- **Account linking**: decide how you handle a user who signs up with email, then later tries to log in with Google using the same email. (Usually: link automatically if email is verified on both sides, or prompt the user.)

### Magic links (passwordless email)
Easy to implement, reduces password fatigue. Downsides: dependent on email delivery, no offline access, more friction per login than a saved password.
- **Tokens are short-lived** (15 min) and single-use.
- **Rate limit** link requests per email.

### Passkeys / WebAuthn
The future of auth. Native support in all modern browsers. Libraries (SimpleWebAuthn, Clerk, Supabase) make this straightforward.

### SSO (SAML, OIDC)
For enterprise customers. Use WorkOS, Auth0, or Keycloak. Don't implement SAML from scratch — the spec is a minefield.

### Multi-factor authentication (MFA)
- **TOTP** (Google Authenticator, Authy) — default choice. Libraries: `otplib`, `pyotp`.
- **SMS** — avoid as sole factor (SIM-swap vulnerable); OK as a backup.
- **WebAuthn / security keys** — strongest MFA.
- **Backup codes** — issue 8–10 one-time codes at MFA enrollment; users will lose their phone.

## Session management

Two models:

### Session tokens (stateful)
Store sessions server-side (DB or Redis). Send a random token (stored hashed in DB) as a cookie.

Pros: trivial to revoke (delete the row), easy to list active sessions, shorter cookies.
Cons: DB lookup per request (though trivial with Redis/indexed lookup).

**Default to this model** for most web apps.

### JWT (stateless)
Signed token carries claims. Server verifies signature, reads claims.

Pros: no DB lookup per request.
Cons: **can't be revoked** without additional infrastructure. Storing blocklists defeats the "stateless" benefit.

Use JWT when:
- You have multiple services that all need to authenticate users and you don't want a shared session DB
- Short expiration is acceptable (access tokens: 5–15 min; paired with refresh tokens)
- The "can't revoke" property is acceptable or mitigated

**Don't use JWT for long-lived sessions in a monolith.** The revocation problem is real.

### Cookie settings
If using cookies (and you usually should for web):
- `HttpOnly` — inaccessible to JS, prevents XSS-based theft
- `Secure` — HTTPS only
- `SameSite=Lax` (default) or `SameSite=Strict` for higher security
- `SameSite=None; Secure` only when you truly need cross-site (third-party embed contexts)
- Short `Max-Age` for session cookies; rotate on privilege changes

### Refresh tokens
Access token (short-lived, 5–15 min) + refresh token (long-lived, 30–90 days).
- Store refresh tokens server-side so you can revoke them.
- Rotate refresh tokens on use (one-time-use) — detects token theft.
- On detected reuse, revoke the whole session family.

## Authorization (what the user is allowed to do)

Authentication proves who. Authorization decides what they can do. They're different concerns — don't conflate them.

### Models by complexity

**Role-based (RBAC)** — users have roles, roles have permissions:
```
user.role = "admin" | "editor" | "viewer"
```
Simplest. Fits most B2C and small B2B apps.

**Attribute-based (ABAC)** — permissions depend on attributes of user and resource:
```
can_edit_post(user, post) =
  user.id == post.author_id
  OR user.role == "admin"
  OR (user.role == "editor" AND post.org_id == user.org_id)
```
Use when rules involve relationships between users and specific resources.

**Relationship-based (ReBAC)** — permissions follow graph relationships (Google Docs, GitHub):
"Users who have 'editor' on the folder containing this file can edit the file."
Tools: OpenFGA, Oso, Permit.io.

### Where to enforce
**At every layer that handles data:**
1. **API layer**: reject unauthorized requests with 401/403 before doing any DB work.
2. **Service layer**: centralize `can_user_do_X(user, resource)` checks. Never scatter role checks across the codebase.
3. **Data layer**: if using multi-tenant schema, enforce tenant scoping in the repo layer — one forgotten filter = data leak. For Postgres, row-level security (RLS) can be an additional defense.

**Never trust the frontend.** The UI hiding the delete button does not mean the user can't DELETE. Re-check on every mutation.

### Role check pattern
Bad:
```ts
if (user.role === 'admin') { ... }   // scattered across 40 files
```

Good:
```ts
// lib/permissions.ts — one place
export function canDeletePost(user: User, post: Post): boolean {
  return user.role === 'admin' || post.authorId === user.id;
}

// in the service
if (!canDeletePost(user, post)) throw new ForbiddenError();
```

When the rule changes, you change it in one place.

## Common auth mistakes

- **Storing passwords in plain text or with weak hashing.** Use argon2id or bcrypt. Always.
- **Leaking user existence** via different error messages ("no such user" vs. "wrong password"). Use the same message for both.
- **Sending auth tokens in URL query strings.** They end up in access logs, referrers, browser history. Use headers or body.
- **No logout.** Actually delete the session server-side on logout, not just the client cookie.
- **Missing rate limits** on login, signup, password reset. Brute-force central.
- **JWT with long expiration and no revocation story.** The stolen token works until it expires.
- **Trusting the `Authorization` header without verifying the signature.** Verify every time — never cache the "yes, this token is valid" result across requests unnecessarily.
- **Using the same secret across environments.** Prod JWT secret must not be the same as staging or dev. Rotate on breach.
- **Not rotating secrets after an employee leaves** or on suspected compromise.
- **Giving every authenticated user admin access in dev** "for convenience," then accidentally deploying that flag to prod. Never ship dev shortcuts.

## Session takeover / account recovery — design for the bad day

- **Account recovery flow**: what happens when a user loses access to their email? Have a documented process, even if it's "contact support." Don't invent a recovery mechanism under duress after a real user reports it.
- **Suspicious activity**: log ips, user-agents, login times. Email the user on first login from a new device/location.
- **Force logout on password change** (and optionally on role/email change) — invalidate all sessions.
- **Credential stuffing protection**: monitor for patterns (many logins from one IP, many failures on distinct emails). Integrate a CAPTCHA after N failures.

## Secrets management

- Never commit secrets. `.gitignore` `.env`. Use `.env.example` with placeholder values.
- In CI/CD: use the platform's secret store (GitHub Actions secrets, Vercel env vars, AWS Secrets Manager).
- In production: AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, Doppler, or Infisical. Rotate on a schedule and on any suspected compromise.
- At boot, validate all required secrets are present — fail fast with a clear error message. Don't silently fall back to a dev default.


path: references/backend-stacks.md
content: # Backend Stacks

Patterns and defaults for the major backend runtimes.

## Choosing a backend

| If you need... | Pick |
|---|---|
| Fast iteration, same-repo as Next.js, moderate load | **Next.js route handlers / API routes** |
| Standalone Node service, serious throughput | **Fastify** or **NestJS** |
| Python ecosystem (ML, data, scientific) | **FastAPI** |
| Full-featured framework with ORM and admin | **Django** or **Ruby on Rails** |
| High concurrency, low memory, strict typing | **Go** (stdlib + chi or Gin) |
| Maximum performance, strong type safety | **Rust** (axum or actix-web) |
| Real-time / WebSocket-heavy | Node with Socket.IO, or Elixir/Phoenix for truly massive scale |

Default: **Next.js route handlers** for small/medium apps (frontend + backend in one repo), **Fastify** or **FastAPI** for standalone services.

## Universal backend principles

- **Input validation at the boundary.** Every request body, query param, and path param gets validated before it touches your business logic. Use Zod (Node/TS), Pydantic (Python), or struct tags + validator (Go).
- **Structured errors.** Return JSON like `{"error": {"code": "USER_NOT_FOUND", "message": "..."}}`. Never leak stack traces or raw DB errors to clients.
- **Don't put business logic in the route handler.** Route handler parses input → calls a service function → formats the response. The service is what you unit-test.
- **Database connection pooling.** Instantiate the pool once; never `new Client()` per request.
- **Transactions around multi-step writes.** Partial writes cause data corruption nightmares.
- **Idempotency for destructive or billable operations.** Use idempotency keys on payment creation, emails, etc.
- **Rate limiting** on auth endpoints at minimum. `express-rate-limit`, `@fastify/rate-limit`, `slowapi` for FastAPI, or a reverse proxy (Cloudflare, nginx).
- **CORS** configured explicitly. Default-deny, allow specific origins.
- **Secrets via environment variables**, loaded through a validated config module. The config module crashes the app on missing secrets — no silent fallbacks.

## Node.js — Fastify (recommended for standalone)

### Why Fastify over Express
- 2–3× faster under load
- Native schema validation (no extra middleware)
- Better TypeScript support
- Plugin-based architecture that scales to large codebases
- Active maintenance

Express is fine for legacy reasons but don't start new projects with it in 2026.

### Project structure
```
src/
  server.ts              # Fastify instance, register plugins
  config.ts              # env var validation (Zod)
  plugins/               # auth, DB, rate limiting
  modules/
    users/
      users.routes.ts    # HTTP layer
      users.service.ts   # business logic
      users.repo.ts      # DB access
      users.schema.ts    # Zod schemas
      users.test.ts
    orders/
      ...
  lib/
    db.ts                # Prisma or Drizzle client singleton
    logger.ts            # pino instance
```

### Example route (Fastify + Zod + Prisma)
```ts
// users.routes.ts
import { FastifyPluginAsync } from 'fastify';
import { z } from 'zod';
import { createUser } from './users.service';

const CreateUserBody = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
});

export const usersRoutes: FastifyPluginAsync = async (fastify) => {
  fastify.post('/users', async (req, reply) => {
    const body = CreateUserBody.parse(req.body);
    const user = await createUser(body);
    return reply.code(201).send(user);
  });
};
```

### Logging
Use `pino` (comes with Fastify). Log structured JSON. Include request ID, user ID, timing.

## Node.js — NestJS

Pick NestJS when:
- Team has Angular / strong OOP background
- App is large and benefits from opinionated module structure
- You want DI out of the box

Don't pick NestJS for a small app — the overhead doesn't pay off until the codebase is large.

## Python — FastAPI (recommended for Python)

### Why FastAPI
- Async-first, fast under load
- Pydantic for validation — best-in-class
- Automatic OpenAPI docs
- Type hints throughout

### Project structure
```
app/
  main.py                # FastAPI app, router registration
  config.py              # pydantic-settings
  db.py                  # SQLAlchemy engine + session
  deps.py                # FastAPI dependencies (auth, DB session)
  modules/
    users/
      router.py
      service.py
      schemas.py         # Pydantic models
      models.py          # SQLAlchemy models
      repo.py
  middleware/
  tests/
pyproject.toml           # use Poetry or uv
```

### Example endpoint
```python
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, EmailStr
from sqlalchemy.orm import Session
from .service import create_user
from ..deps import get_db

router = APIRouter(prefix="/users", tags=["users"])

class UserCreate(BaseModel):
    email: EmailStr
    name: str

@router.post("", status_code=201)
def create(body: UserCreate, db: Session = Depends(get_db)):
    return create_user(db, body)
```

### Testing
`pytest` + `httpx.AsyncClient` for integration tests. Use an in-memory SQLite or a dedicated test Postgres.

## Python — Django

Pick Django when:
- Content-heavy app with lots of CRUD
- You want an admin interface for free
- Team is Python-first and already knows Django

Don't pick Django if you need async performance or the app is primarily an API — FastAPI is a better fit.

## Go

### Why Go
- Best-in-class concurrency (goroutines)
- Low memory footprint
- Fast compile, fast startup
- Great for services that scale horizontally

### Project structure
```
cmd/
  server/main.go
internal/
  users/
    handler.go
    service.go
    repo.go
    model.go
  config/
  db/
  middleware/
pkg/                    # only for code you'd actually share
go.mod
```

Use `chi` (idiomatic, small) or `Gin` (familiar to Express users). Skip the debates — both are fine.

Use `sqlc` to generate type-safe Go from SQL. It's better than any ORM for Go.

### Testing
Go's built-in `testing` package. Table-driven tests. `testify` for nicer assertions if the team likes it.

## API layering pattern (all languages)

```
┌────────────────────────────┐
│  HTTP layer (routes/       │  parse input, auth check, call service
│  handlers/controllers)     │
├────────────────────────────┤
│  Service layer             │  business logic, orchestration
│                            │  <-- this is what you unit test
├────────────────────────────┤
│  Repository / data layer   │  DB access, external API calls
└────────────────────────────┘
```

This separation becomes worth it around 5–10 endpoints. Below that, keeping everything in the route handler is fine.

## Background jobs & async work

If the user's app needs to send emails, process images, call slow third-party APIs, or do anything that shouldn't block an HTTP request:

- **Small Node apps**: [BullMQ](https://bullmq.io) (Redis-backed) is the default.
- **Python**: [Celery](https://docs.celeryq.dev) with Redis or RabbitMQ; [RQ](https://python-rq.org) is simpler and often enough.
- **Go**: [asynq](https://github.com/hibiken/asynq) or [River](https://riverqueue.com) (Postgres-backed — no extra infra).
- **Postgres-first**: [pg-boss](https://github.com/timgit/pg-boss) (Node) or River (Go). If you're already on Postgres and don't have heavy volume, skipping Redis is a real simplification.
- **Serverless**: cloud-native (AWS SQS + Lambda, GCP Pub/Sub + Cloud Functions, Inngest, Trigger.dev).

Never send emails, hit Stripe, or do anything slow inside the HTTP request handler. Queue the work, return fast, let the job system handle retries.

## Real-time / WebSockets

- **Simple broadcast / rooms**: Socket.IO (Node), or native WebSocket with a thin wrapper.
- **Scalable pub/sub**: Redis pub/sub adapter for Socket.IO, or a dedicated service (Pusher, Ably, Supabase Realtime).
- **Truly massive concurrent connections**: Elixir/Phoenix — the BEAM VM is built for this.

Server-Sent Events (SSE) is underrated. If you only need server → client push (notifications, streaming AI responses, live updates), SSE is simpler than WebSocket and works through proxies that mangle WS.

## Common mistakes to avoid

- **N+1 queries.** Load related data with `include` / `join` / eager loading, not in a loop.
- **Sync work inside request handlers.** Emails, image processing, third-party calls → background jobs.
- **No timeouts on outbound calls.** Always set a timeout on `fetch` / `httpx` / HTTP clients, or one hanging call will exhaust your connection pool.
- **Trusting the frontend.** Re-validate permissions on every write. "The UI doesn't let them do X" is not access control.
- **Logging secrets.** Scrub tokens, passwords, card numbers from logs. Use a logger middleware that redacts known sensitive fields.


path: references/database-design.md
content: # Database Design

Schema design, migrations, indexes, and the SQL vs. NoSQL decision.

## SQL vs. NoSQL — how to decide

**Default to Postgres.** It handles relational data, JSON, full-text search, geospatial, and even some vector workloads. Most "we need NoSQL" arguments evaporate under scrutiny.

Pick NoSQL when:
- **Document store (MongoDB, Firestore)**: data is genuinely document-shaped and you don't need joins. Examples: user-generated content with wildly varying shapes, event logs, session data.
- **Key-value (Redis, DynamoDB)**: you know the exact access pattern (key → value), need very low latency, and willingly trade query flexibility for it. Great for caches, sessions, rate limiters, job queues.
- **Wide-column (Cassandra, ScyllaDB)**: you have write-heavy time-series at massive scale and well-known access patterns.
- **Graph (Neo4j)**: your queries are fundamentally about traversing relationships many hops deep (social graphs, knowledge graphs, fraud detection).

"We might need to scale" is not a reason to reach for NoSQL. Postgres on a single box handles tens of thousands of QPS for most workloads. Scale when it's a real problem.

## Relational schema design principles

1. **Name tables as plural nouns**: `users`, `orders`, `order_items`. Name columns as snake_case singular: `user_id`, `created_at`.
2. **Primary keys**: use UUIDs for anything exposed to the outside (URLs, API responses). Use bigint auto-increment for internal-only tables if you prefer. Don't use natural keys (email, username) as primary keys — they change.
3. **Foreign keys**: always define them, even if you're using an ORM. They enforce referential integrity at the DB level.
4. **Timestamps**: `created_at` and `updated_at` on every table. Use `TIMESTAMPTZ` in Postgres (timezone-aware), not `TIMESTAMP`.
5. **Soft deletes**: add `deleted_at TIMESTAMPTZ NULL` when you need to preserve history. Be consistent — either all tables or none.
6. **Enums**: Postgres native enums are fine for truly fixed sets (role types, statuses). For anything that might grow, use a reference table or a `CHECK` constraint on a `TEXT` column — altering an enum requires migrations.
7. **JSON columns**: use `JSONB` in Postgres for flexible/metadata fields, but don't make your whole schema JSON. If a field is queried or filtered on, it deserves to be a column with an index.
8. **NULL means "unknown" or "not applicable"**. Don't use it as "false" or "empty string" — it's a different thing and breaks queries.

## Indexing — the single biggest perf lever

Rules of thumb:
- Every foreign key needs an index. Most ORMs don't create these automatically.
- Every column that appears in a `WHERE`, `ORDER BY`, or `JOIN` predicate on a hot query path needs an index.
- Composite indexes: the order matters. Put the most selective / most-filtered column first.
- Adding an index has a cost — slower writes, more disk. Don't add indexes speculatively.
- Run `EXPLAIN ANALYZE` on your slow queries. The plan tells you exactly what's happening.

A common mistake: the ORM generates a query that's not using any of your indexes. Check. Don't trust.

## Migrations

Every schema change goes through a migration file. Never edit the DB by hand in any environment above local dev.

### Tooling
- **Prisma** (Node): `prisma migrate dev` / `prisma migrate deploy`
- **Drizzle** (Node): `drizzle-kit generate` / `drizzle-kit migrate`
- **Alembic** (Python/SQLAlchemy): `alembic revision --autogenerate` / `alembic upgrade head`
- **Django**: `makemigrations` / `migrate`
- **Go**: `golang-migrate` or `goose`
- **Rails**: built-in migrations

### Migration safety in production

The golden rule: **all migrations must be backward-compatible with the currently-deployed app code**, because there's always a window during deploy where old and new code both run.

Safe patterns:
- Add a new nullable column → backfill in a background job → make NOT NULL in a second migration (after old code is gone).
- Rename a column → create a new column, dual-write from the app, backfill, migrate reads, drop the old column. Multiple deploys.
- Drop a column → stop writing from the app first, ship that, then drop in a later migration.

Dangerous patterns that block or lock tables on Postgres:
- `ALTER TABLE ... ADD COLUMN ... NOT NULL DEFAULT <value>` on large tables (rewrites the whole table on older Postgres versions; fast on Postgres 11+).
- Adding an index on a large table without `CONCURRENTLY`.
- Long-running queries that hold locks (transactions on DDL statements).

For large production tables, always use `CREATE INDEX CONCURRENTLY`. Most ORM migration tools default to the blocking version — override explicitly.

## Common schemas for common domains

### Multi-tenant SaaS
Three approaches, pick by isolation needs:
1. **Shared schema + tenant_id column on every table.** Simplest. What most SaaS apps use. Requires discipline — every query must filter by tenant.
2. **Schema-per-tenant** (Postgres schemas). Stronger isolation, moderate complexity, doesn't scale to 10,000s of tenants.
3. **Database-per-tenant**. Strongest isolation. Only for a handful of enterprise tenants or regulated industries.

Enforce tenant scoping at the ORM/repo layer, not in individual queries — one forgotten `WHERE tenant_id = ...` is a data leak.

### Users + auth
```sql
users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email TEXT UNIQUE NOT NULL,
  email_verified_at TIMESTAMPTZ,
  password_hash TEXT,              -- NULL if only OAuth
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
)

sessions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  token_hash TEXT UNIQUE NOT NULL, -- never store raw tokens
  expires_at TIMESTAMPTZ NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW()
)

-- if supporting OAuth providers
oauth_accounts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  provider TEXT NOT NULL,          -- 'google', 'github', etc.
  provider_account_id TEXT NOT NULL,
  UNIQUE (provider, provider_account_id)
)
```

### Orders / e-commerce
```sql
orders (
  id UUID PRIMARY KEY,
  user_id UUID REFERENCES users(id),
  status TEXT NOT NULL,           -- 'pending', 'paid', 'shipped', ...
  total_cents INTEGER NOT NULL,   -- never store money as float
  currency CHAR(3) NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW()
)

order_items (
  id UUID PRIMARY KEY,
  order_id UUID REFERENCES orders(id) ON DELETE CASCADE,
  product_id UUID REFERENCES products(id),
  quantity INTEGER NOT NULL CHECK (quantity > 0),
  unit_price_cents INTEGER NOT NULL  -- snapshot, not a join
)
```

Always snapshot prices at order time. The product's price may change; the order's historical price must not.

### Audit / event log
```sql
audit_log (
  id BIGSERIAL PRIMARY KEY,
  actor_id UUID,                   -- who did it
  action TEXT NOT NULL,            -- 'user.created', 'order.refunded'
  target_type TEXT NOT NULL,
  target_id TEXT NOT NULL,
  metadata JSONB,
  created_at TIMESTAMPTZ DEFAULT NOW()
)
CREATE INDEX ON audit_log (actor_id, created_at DESC);
CREATE INDEX ON audit_log (target_type, target_id, created_at DESC);
```

Audit logs are append-only. No updates, no deletes. Eventually archive to cold storage.

## Money and time — get these right early

### Money
- Store in the smallest unit (cents, satoshis) as an integer. NEVER as float.
- Always store the currency.
- Round only at display time, using the currency's rules.

### Time
- Store in UTC (`TIMESTAMPTZ` in Postgres). Convert at display.
- Store user-preferred timezone as a separate column when you need it ("Send daily digest at 9am THEIR time").
- Dates (not times) are different — "birthday" is a `DATE`, not a `TIMESTAMPTZ`.

## NoSQL (MongoDB) specifics

When you have chosen MongoDB deliberately:

- **Design around access patterns.** Embed what you always read together, reference what you read separately. Don't "3NF" your documents.
- **Indexes matter just as much.** Always index query fields. `db.collection.explain()` is your friend.
- **Don't use MongoDB transactions as the happy path.** They work but are the fallback, not the primary design. If you need transactions everywhere, you probably wanted SQL.
- **Validation with JSON Schema**. MongoDB supports schema validation; use it. Schemaless doesn't mean "no schema," it means "the schema lives in your app code and you should document it."

## Connection pooling

For any serverful deployment, connection pooling is mandatory:
- Postgres: PgBouncer in transaction mode, or a connection pool built into your ORM (Prisma, SQLAlchemy).
- In serverless (Vercel, AWS Lambda), use a pooling proxy — PgBouncer, Neon's built-in pooler, Supabase's pooler, AWS RDS Proxy. Direct connections from serverless rapidly exhaust the DB.

## Backups

- Enable automated daily backups on the managed DB.
- Enable point-in-time recovery (PITR) for production.
- Test restore at least quarterly. An unverified backup is not a backup — prove the restore path works before you need it.


path: references/deployment-cicd.md
content: # Deployment & CI/CD

How to get the app from "works on my machine" to "running in production, auto-deployed, observable."

## Choosing a deployment target

| Target | Best for | Watch out for |
|---|---|---|
| **Vercel** | Next.js, static sites, small APIs | Serverless cold starts; cost at scale; vendor-specific features |
| **Netlify** | Static sites, JAMstack | Similar to Vercel, lighter on backend features |
| **Cloudflare Pages/Workers** | Edge-first, global low latency | Workers runtime is not Node — subset of APIs |
| **Fly.io** | Dockerized apps, global regions, persistent volumes | Regional failure requires planning |
| **Railway** | Simplest "just run my Dockerfile" | Limited regions; pricing at scale |
| **Render** | Heroku replacement; background workers, cron, DB | Similar to Railway |
| **AWS** (ECS/EKS/Lambda/App Runner) | Serious scale, AWS-integrated stacks | Highest complexity; IAM is a full-time job |
| **GCP / Azure** | Similar scope to AWS | Similar tradeoffs |
| **Kubernetes** (self-managed or EKS/GKE) | Large teams, multi-service, complex infra | Massive operational burden; don't reach for it for an MVP |
| **VPS** (Hetzner, DigitalOcean) + Docker | Cost-sensitive, full control | You own everything — backups, monitoring, security patches |

**Default recommendation**: Vercel for Next.js monolith. Fly.io or Railway for Docker-based Node/Python/Go. AWS when the user is already there or needs specific services. Avoid Kubernetes for v1 unless the team already runs it.

## Docker — when and how

### When to Dockerize
- Deploying to anything container-based (Fly, Railway, Render, ECS, K8s).
- You want the dev environment to match prod.
- The app has non-trivial system dependencies (binary libs, native modules).

### When to skip Docker
- Deploying to Vercel / Netlify (they handle runtime).
- Tiny scripts or cron jobs where a container is overkill.

### Dockerfile patterns

**Node.js (multi-stage, slim):**
```dockerfile
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY package.json ./
EXPOSE 3000
USER node
CMD ["npm", "start"]
```

**Python (FastAPI):**
```dockerfile
FROM python:3.12-slim AS builder
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen --no-dev

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"
COPY . .
EXPOSE 8000
USER 1000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
```

**Go (distroless, smallest):**
```dockerfile
FROM golang:1.23 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /bin/server ./cmd/server

FROM gcr.io/distroless/static-debian12
COPY --from=builder /bin/server /server
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]
```

### Docker rules
- **Multi-stage builds**: keep the final image small and free of build tools.
- **Run as non-root**: create a user, switch to it with `USER`.
- **Don't bake secrets** into images. Use runtime env vars or a secrets manager.
- **Pin base image versions** — `node:22-alpine`, not `node:latest`. Rebuild periodically for security patches.
- **`.dockerignore`**: mirror `.gitignore` + add `node_modules`, `.git`, test fixtures, local env files. Smaller context = faster builds.
- **Healthcheck** endpoint: `/health` that returns 200 when the app is ready to serve. Your orchestrator needs this.

## CI: GitHub Actions (default)

Most apps should have a single workflow that runs on every push and PR, plus a deploy workflow on merge to `main`.

### Minimal CI workflow
```yaml
# .github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: postgres
        ports: ['5432:5432']
        options: >-
          --health-cmd pg_isready --health-interval 10s
          --health-timeout 5s --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test
        env:
          DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
      - run: npm run build
```

### What every CI pipeline should do
1. Install dependencies (with a lockfile, deterministic).
2. Lint.
3. Typecheck.
4. Run tests (unit + integration).
5. Build the production artifact.
6. (Optional) Build and push Docker image to a registry.
7. (Optional) Deploy — usually a separate workflow, gated on passing CI.

### Speeding up CI
- Cache dependencies (`cache: 'npm'`, `actions/cache` for Python/Go).
- Run independent jobs in parallel (lint + typecheck + test as separate jobs).
- Only run E2E on main + nightly, not every push.
- Use matrix sparingly — test on one Node version in CI, not all of them, unless you ship a library.

## CD: deployment strategies

### Continuous deployment to staging
Every merge to `main` → auto-deploy to staging. No approval. Fast feedback, confidence from seeing it run.

### Production deployment
Pick one:
- **Auto-deploy `main` to prod** — fine for small teams and mature test coverage.
- **Tag to deploy** — merge to `main` goes to staging; creating a git tag triggers prod. Forces a human to "press the button."
- **Manual approval** — GitHub Actions environments support required reviewers.

### Preview deployments
Every PR gets its own URL (Vercel, Netlify, Fly.io all do this natively). Reviewers and designers can click around before approving. For anything user-visible, this is essential.

### Migrations during deploy
Run migrations **before** the new app version starts serving traffic, and make them backward-compatible (see `database-design.md`). Options:
- A pre-deploy CI step that runs `prisma migrate deploy` / `alembic upgrade head` / etc.
- A separate "migrate" job in your platform (Render job, Vercel build command, Fly "release command").

### Rollback plan
- Know how to revert the deploy in under 5 minutes.
- Don't assume "just redeploy the previous commit." If that commit has schema migrations, rollback is harder.
- Document the rollback procedure in the runbook.

## Platform-specific notes

### Vercel (Next.js)
- Connect GitHub repo → automatic deploys for `main` (production) and PRs (preview).
- Environment variables configured per environment (dev, preview, production).
- Database migrations: add to `"build"` script or use a dedicated CI job; Vercel build can run `prisma migrate deploy`.
- Serverless function limits: check timeout (10s default, up to 300s on paid plans), bundle size (~50 MB), cold starts.

### Fly.io
- `fly launch` generates a `fly.toml`. Commit it.
- `fly deploy` ships a new version. Can be called from CI with `flyctl` and `FLY_API_TOKEN`.
- Persistent storage via volumes (for DBs or file uploads).
- Global: deploy to multiple regions with `fly regions add`.
- Secrets: `fly secrets set KEY=value`. Not visible in logs.
- Release commands for migrations: `deploy.release_command = "npm run migrate"` in `fly.toml`.

### Railway
- Connect repo → auto-build via nixpacks or Dockerfile.
- Managed Postgres, Redis, etc. available as services with shared env vars.
- Release commands for migrations in `railway.json` or via UI.

### AWS (ECS / App Runner / Lambda)
- **App Runner** is the simplest — point at a container registry, it runs it. Fine for small apps.
- **ECS Fargate** is the workhorse for serious container deployments. Infrastructure as code (Terraform, CDK) is practically required.
- **Lambda** for event-driven or low-traffic APIs. Watch for cold starts; use SnapStart (Java/Python) or lean Node/Go runtimes.
- **Secrets**: AWS Secrets Manager or Parameter Store. Reference by ARN, don't paste values.
- **IAM**: least privilege. The role your app runs as should only have the permissions it needs. Auditing IAM is a real activity — budget time for it.

### Kubernetes
- Don't start here. Start with something simpler.
- If you do need it: use a managed K8s service (EKS, GKE, AKS). Don't self-host the control plane.
- Use Helm for app packaging; ArgoCD or Flux for GitOps.
- Set resource requests and limits on every pod. Pod disruption budgets. Liveness/readiness probes.

### VPS + Docker + nginx
Cost-effective, full control. Roughly:
- Hetzner/DO VPS with Ubuntu LTS.
- Docker + docker-compose or a simple orchestrator (Dokku, Coolify, Caprover).
- nginx (or Caddy — Caddy auto-provisions Let's Encrypt certs, very nice).
- Deploys via SSH + `docker pull && docker compose up -d`, or via a GitHub Actions runner.
- You own: backups, security updates, monitoring, log rotation, firewall, SSH hardening.

## Zero-downtime deploys

For anything past "hobby project" status:
- **Health checks**: app exposes `/health`; platform doesn't route traffic until it's healthy.
- **Graceful shutdown**: on SIGTERM, stop accepting new requests but finish in-flight ones. Platforms usually give you ~30s.
- **Rolling deploys** (or blue-green): new version runs alongside old until healthy, then old is torn down.
- **Database migrations first, app deploys after** — and migrations are backward-compatible.
- **Session handling**: if sessions are in memory, they die on every deploy. Use Redis or your DB.

## Environment management

Typical environments:
- **local** — each dev's machine. Their own `.env`.
- **staging / preview** — prod-like, wired to a staging DB. Every PR or every `main` push deploys here.
- **production** — the real one.

Rules:
- Each env has its own database. Never share.
- Each env has its own secrets. Rotating a staging key must not affect prod.
- Prod data must not flow back to staging (privacy, compliance). If you need realistic test data, anonymize.

## Feature flags

For any app past MVP, consider feature flags (LaunchDarkly, Unleash, ConfigCat, or a simple in-house `flags` table). Benefits:
- Deploy code without exposing it to users.
- Gradual rollouts (1%, 10%, 100%).
- Instant disable of a broken feature — faster than a rollback deploy.
- A/B testing.

Downsides: proliferation of flags that never get cleaned up. Add a "flag retirement" task to each release.

## Blue-green vs. canary

- **Blue-green**: run two identical envs. Cut traffic from blue to green. Rollback = cut back. Simple, not partial.
- **Canary**: route a small % of traffic to the new version; watch error rate; ramp up. Best for detecting regressions before wide blast.

Most platforms do rolling deploys (a kind of canary). Canary-by-header or canary-by-user is a manual setup worth building for revenue-critical apps.

## Disaster recovery

Define and test:
- **RTO** (Recovery Time Objective): how long can the app be down?
- **RPO** (Recovery Point Objective): how much data can we lose?

Then:
- Ensure backups meet RPO (hourly PITR for low RPO; daily for higher).
- Practice restores at least quarterly. Write a runbook.
- Have a secondary region plan if the RTO demands it. Most startups can tolerate an outage while the primary region recovers — don't build multi-region until you need it.


path: references/frontend-stacks.md
content: # Frontend Stacks

Patterns and defaults for the major modern frontend frameworks. Pick one based on project needs; don't mix frameworks in a single app.

## Choosing a frontend

| If you need... | Pick |
|---|---|
| SEO, server rendering, file-based routing, same-repo backend | **Next.js (App Router)** |
| Rich SPA with no SSR needs, fastest dev loop | **React + Vite** |
| Vue ecosystem preference or Nuxt-style SSR | **Nuxt 3** |
| Smallest bundle, simplest mental model, server/client merged | **SvelteKit** |
| Desktop-app feel with React | React + Vite + Electron or Tauri |
| Content-heavy marketing site with occasional interactivity | **Astro** (ships zero JS by default) |

If the user has no preference and you're not sure, **default to Next.js App Router with TypeScript and Tailwind**. It's the broadest-utility choice and has strong defaults for routing, data fetching, and deployment.

## Universal frontend principles

Regardless of framework:

- **TypeScript by default.** The refactor safety and editor tooling pay for themselves within a week on any non-trivial project.
- **Use a component library as a base layer.** [shadcn/ui](https://ui.shadcn.com) for React (copies components into your repo — you own them), Radix UI primitives, or Headless UI. Don't build buttons, dropdowns, and modals from scratch.
- **Tailwind CSS** for styling unless the user insists otherwise. Avoid the 2015-era "CSS-in-JS" libraries (styled-components, emotion) on new projects — they're slower and the ecosystem has largely moved on.
- **Forms**: React Hook Form + Zod (React) or VeeValidate + Zod (Vue). Never roll your own validation on forms with >3 fields.
- **Server state**: TanStack Query (React) / Vue Query / SvelteKit's built-in loaders. This is what you want for fetching, caching, and syncing with the server. Don't reach for Redux or global state unless you actually need cross-component shared state that isn't server data.
- **Client state**: prefer component state and URL params. Reach for Zustand / Pinia / Svelte stores only when state genuinely needs to be shared across the component tree.
- **Accessibility from day one**: semantic HTML, keyboard navigation, alt text, focus management on route changes and modals. Running axe or Lighthouse takes 30 seconds.
- **Image handling**: use the framework's Image component (Next's `<Image>`, Nuxt's `<NuxtImg>`). It handles lazy loading, responsive sizes, and format conversion for free.

## Next.js (App Router) — recommended default

### Directory structure
```
app/
  (marketing)/              # route group — doesn't affect URL
    page.tsx                # homepage
    pricing/page.tsx
  (app)/
    layout.tsx              # shared app shell (requires auth)
    dashboard/page.tsx
    settings/page.tsx
  api/
    users/route.ts          # route handler
    webhooks/stripe/route.ts
  layout.tsx                # root layout
  globals.css
components/
  ui/                       # shadcn/ui components live here
  forms/
  layouts/
lib/
  db.ts                     # database client singleton
  auth.ts                   # auth helpers
  validations/              # Zod schemas
server/
  actions/                  # server actions
  services/                 # business logic (pure, testable)
types/
```

### Data fetching patterns
- **Server components by default.** Fetch directly from the DB in a server component — no API round-trip needed for first render. Use `async` server components and `await`.
- **Client components (`"use client"`) only when needed**: interactivity, hooks, browser APIs. Keep these leaves small.
- **Mutations**: Server Actions for form posts. For interactive updates, a route handler + TanStack Query on the client.
- **Caching**: understand `fetch` cache options, `revalidatePath`, `revalidateTag`. Caching bugs are the #1 source of "why does my Next.js app show stale data" questions — be explicit.

### Auth pattern
Auth.js (formerly NextAuth) or Clerk. Both handle session, OAuth providers, and middleware-based route protection.

### What to avoid in Next.js
- Don't put sensitive server logic in client components. If you see `"use client"` at the top of a file that touches the DB, that's a bug.
- Don't `useEffect` to fetch data you could fetch on the server. It causes layout shift and worse SEO.
- Don't put the database client inside a request handler — instantiate it once in `lib/db.ts` and import it. (In dev, guard against Hot Module Reload re-instantiation.)

## React + Vite (SPA)

When you genuinely don't need SSR — internal tools, authenticated-only dashboards, PWAs.

### Directory structure
```
src/
  pages/             # or routes/
  components/
  lib/
  hooks/
  services/          # API clients
  types/
  main.tsx
  App.tsx
```

### Routing
React Router v6+ is the default. TanStack Router is a strong alternative with type-safe routes.

### Data fetching
TanStack Query. Always.

### API client
A single `lib/api.ts` that wraps `fetch` with base URL, auth headers, and error handling. Don't scatter `fetch` calls across 40 components.

## Nuxt 3 (Vue)

### Directory structure
Nuxt has strong conventions — use them:
```
pages/               # file-based routing
components/          # auto-imported
composables/         # auto-imported hooks
server/api/          # API routes
server/utils/
middleware/          # route middleware
plugins/
```

### Data fetching
`useFetch` / `useAsyncData` for SSR-aware fetching. Pinia for shared client state.

## SvelteKit

### Directory structure
```
src/
  routes/
    +page.svelte
    +page.server.ts      # server-only load
    api/
      users/+server.ts
  lib/
    components/
    server/              # server-only code
```

### Data fetching
`+page.server.ts` `load` functions for server-side data. Form actions for mutations — simpler than REST for most form posts.

## State management — decision tree

1. Is this data from the server? → Use TanStack Query / Nuxt's useFetch / SvelteKit loaders. **Stop.**
2. Does this state belong in the URL (filters, pagination, selected tab)? → URL params. **Stop.**
3. Is this local to one component? → `useState` / `ref`. **Stop.**
4. Is this shared across a small subtree? → Context / provide-inject. **Stop.**
5. Truly global, app-wide, non-server client state? → Zustand / Pinia / Svelte store.

Most apps never need step 5.

## Build, bundle, lint

- **ESLint** with the framework's recommended config, plus `eslint-plugin-tailwindcss` if using Tailwind.
- **Prettier** for formatting. Integrate with your editor so it runs on save.
- **Type-check in CI**: `tsc --noEmit` as a CI step. Don't let type errors accumulate.
- **Bundle analysis**: run it before you ship. Next.js has `@next/bundle-analyzer`; Vite has `rollup-plugin-visualizer`. If your entry bundle is >500 KB gzipped, investigate.


path: references/observability.md
content: # Observability

How to know what the app is doing in production — before, during, and after an incident.

## The three pillars (and why they matter)

1. **Logs** — discrete events with context. Answer: "what happened in this specific request?"
2. **Metrics** — aggregated numeric data over time. Answer: "is the system healthy right now?"
3. **Traces** — the path of a request across services. Answer: "where was the 3 seconds spent?"

You don't need all three on day one. Get logs and basic metrics first; add traces when the system spans multiple services or background jobs.

## Logging

### Structured JSON, always
Plain-text logs that look like `2026-04-01 user "Foo Bar" did a thing with id 123` are nearly impossible to query in aggregate. Emit JSON:

```json
{
  "timestamp": "2026-04-01T12:34:56.789Z",
  "level": "info",
  "message": "payment.completed",
  "request_id": "req_abc123",
  "user_id": "usr_xyz",
  "amount_cents": 2500,
  "currency": "USD",
  "duration_ms": 142
}
```

### Fields every log line should have
- `timestamp` (ISO 8601, UTC)
- `level` (debug, info, warn, error)
- `message` (short, semantic, like an event name — not "the thing happened for user 123")
- `request_id` / `trace_id` — correlate across services
- `user_id` / `tenant_id` when available
- service-specific context (order_id, job_id, etc.)

### What to log
- Every inbound HTTP request at info level: method, path, status, duration, user, request ID.
- Every outbound call to external services: endpoint, status, duration, outcome.
- Every auth event: login (success/fail), logout, password change, MFA.
- Every background job: start, end, duration, outcome, attempts.
- Every error with full stack.

### What NOT to log
- Passwords, tokens, session IDs, card numbers, SSNs, health records. Even hashed.
- Full request bodies on endpoints that accept sensitive data.
- Full response bodies (usually unnecessary, often sensitive).

Use a redaction filter in the logger (e.g., [`pino`](https://github.com/pinojs/pino)'s `redact` option) to strip known-sensitive fields automatically.

### Log levels — when to use which
- **debug** — development noise, detailed diagnostic. Off in production.
- **info** — normal operations. Log what happened; future-you will want this.
- **warn** — something unusual but not broken (fallback triggered, rate limit hit).
- **error** — something broke. Page someone if it's unexpected.

Keep production at `info`. Turn on `debug` for a single request via a header, not globally.

### Request IDs
Every incoming request gets a unique ID. It's logged on every line for that request, and propagated to downstream calls (via `X-Request-Id` header or W3C Trace Context). When a user reports a problem, you can find every log line related to their request.

### Where to send logs
- **Managed platforms** (Vercel, Fly, Railway) have built-in log viewers for a short retention.
- **Aggregation**: ship to a log platform — Datadog, New Relic, Grafana Loki, Better Stack, Axiom, Papertrail. Structured JSON makes querying trivial.
- **Self-hosted**: ELK stack (Elasticsearch, Logstash, Kibana) or Grafana Loki. Operationally heavy; don't start here.

## Error tracking

Logs are for "everything that happened." Error tracking is for the subset: exceptions and errors that need human attention.

### Tooling
- **Sentry** — standard for JS/Python/Go/etc. Great frontend + backend coverage.
- **Rollbar**, **Bugsnag** — similar category.
- **Honeybadger** — Ruby-first but fine elsewhere.
- Most error trackers have free tiers sufficient for small apps.

### Setup essentials
- Wire into both frontend and backend.
- Attach user context (ID, email redacted) on every event.
- Attach release/version so you can correlate error spikes with deploys.
- Upload sourcemaps from the frontend build so errors have readable stacks — but don't serve sourcemaps to users.
- Configure data scrubbing: redact passwords, tokens, card numbers from captured payloads.
- Set up alerts: new error types, error volume spikes, errors affecting >N users.

### Frontend errors
- Global `window.onerror` / `unhandledrejection` handler reports to Sentry.
- React/Vue error boundary that reports and shows a fallback UI.
- Core Web Vitals (LCP, CLS, INP) — Sentry can capture these; so can Vercel Analytics.

## Metrics

### What to measure — the RED method
For every request-handling service, track:
- **R**ate — requests per second
- **E**rrors — % of requests failing
- **D**uration — p50, p95, p99 latency

This is the absolute minimum. You can make most performance and reliability decisions from these.

### The USE method (for resources)
For every resource (CPU, memory, disk, DB connections):
- **U**tilization — % busy
- **S**aturation — queue depth / wait time
- **E**rrors — failures

### Product metrics
Beyond system health, measure the business:
- Signups per day
- Active users (DAU/WAU/MAU)
- Conversion rates on key flows
- Revenue

These tell you if the app is doing its job, not just if it's up.

### Tooling
- **Managed platform metrics** — start with what's built in (Vercel Analytics, Fly metrics, AWS CloudWatch).
- **Prometheus + Grafana** — the open source standard. Hosted: Grafana Cloud, AWS Managed Prometheus.
- **Datadog / New Relic** — paid, but bundle logs + metrics + traces + alerts.
- **Cloudflare Analytics** — free, good for edge-level request metrics.

### Dashboards
Have at least one dashboard you look at weekly:
- Request rate, error rate, p95 latency per service
- Database: connections, query duration, slow queries
- Background job queue depth and failure rate
- Top 10 endpoints by p95 latency

A dashboard nobody looks at is dead. Fewer, more meaningful panels beats a wall of 50.

## Tracing

Distributed tracing shows the path of a request across services — useful when you have multiple services or heavy background processing.

- **OpenTelemetry** is the standard. Instrument once, send to any backend (Jaeger, Honeycomb, Datadog, Grafana Tempo, Sentry's tracing).
- Start with auto-instrumentation (HTTP libraries, DB drivers) — gets you 80% for free.
- Add custom spans around critical business operations.
- Sampling: 1–10% of requests in prod for a busy service; 100% of errors.

For a single-service Next.js app, tracing is nice-to-have. For a 5-service system, it's essential.

## Alerting

### Alert on symptoms, not causes
- Good alert: "error rate >1% for 5 minutes." (Symptom.)
- Bad alert: "CPU >80%." (Cause — and possibly benign.)

High CPU is interesting for debugging but doesn't always mean something's wrong. User-facing error rate does.

### Alert fatigue is real
- Every alert should be actionable. If the response is "ignore it," remove the alert.
- Tune thresholds after a week of calibration. A hair-trigger alert gets muted.
- Alert severity: pageable (wake someone) vs. FYI (Slack/email).

### Sensible starting alerts
- 5xx rate >1% over 5 minutes → page
- p95 latency >2x baseline over 10 minutes → page
- Deploy failed → Slack
- Background job queue depth exceeds threshold → Slack
- Error tracker: new error type → Slack; >N users affected in an hour → page
- Uptime monitor (healthcheck failing from external) → page
- Certificate expiring in <7 days → Slack
- Disk >80% → Slack; >95% → page

### On-call
For anything past hobby status, define who's on-call. PagerDuty, Opsgenie, incident.io. Rotation is weekly, not per-person forever.

## Uptime monitoring

External checks (from outside your infrastructure) hitting a `/health` endpoint every 30–60s. If internal monitoring goes down with the app, you'd miss outages. External doesn't.

Tools: UptimeRobot, Better Stack, Pingdom, Checkly. Free tiers are fine for small apps.

### The `/health` endpoint
- Returns 200 when the app can serve traffic.
- Checks what matters: DB connectivity, external services, cache.
- Simple implementation: query a trivial `SELECT 1`, check Redis ping, return success.
- Don't do expensive work in `/health` — it's called constantly.

Separate `/readiness` and `/liveness` if using Kubernetes:
- `/liveness` — "the process is alive." Fails → restart the container.
- `/readiness` — "the process can serve traffic." Fails → remove from load balancer.

## Cost observability

Your observability stack itself will have a bill. Watch it:
- Logs: storage is usually the driver. Aggressive retention (30 days hot, archive longer if needed) controls cost.
- Metrics: cardinality is the killer — high-cardinality tags (user_id, request_id) multiply series counts.
- APM/tracing: per-request. Sampling matters.

Review the monitoring bill quarterly. It's easy for it to become 10% of infra spend.

## Feedback loops — closing the loop

Observability is about closing the loop from "incident" back to "code change." Each incident should ask:
1. What was the signal? Was it fast enough?
2. What was the root cause?
3. How do we prevent recurrence or detect faster?
4. Did the runbook help? If not, update it.

Write a short post-incident note, even for small incidents. They compound into institutional knowledge.

## Minimum viable observability

For an app just launched, you need:
- Structured JSON logs, aggregated somewhere searchable.
- Sentry (or equivalent) on frontend and backend with sourcemaps + scrubbing.
- One external uptime check on `/health`.
- A dashboard: request rate, error rate, p95, DB connections.
- Alerts: 5xx spike, uptime failure, new Sentry error type.

That's a weekend of setup for an app of any size. Everything past that is refinement.


path: references/sdlc-phases.md
content: # SDLC Phases — Detailed Checklists

This file expands each SDLC phase with the concrete questions and artifacts that should exist before you move to the next one. Use it as a checklist, not a script.

## 1. Requirements Analysis

### Functional requirements
- What are the 3–7 core user-facing capabilities? Write them as user stories: "As a [role], I can [action] so that [outcome]."
- What are the explicit non-goals? Write them down — they're the first thing to cite when scope creeps.
- What's the shortest possible path to a demo? That's your MVP.

### Non-functional requirements
- **Users & load**: expected DAU/MAU, peak concurrent users, requests/sec
- **Latency**: acceptable p50, p95, p99 response times
- **Data**: record counts, growth rate, retention, PII/sensitive data flags
- **Availability**: is 99% fine, or is this a payments system that needs 99.99%?
- **Compliance**: GDPR (EU users), HIPAA (health), PCI (cards), SOC 2 (enterprise B2B)
- **Accessibility**: WCAG 2.1 AA is the default for anything public-facing
- **Browsers/devices**: modern evergreen only, or IE11, or mobile-first?

### Clarifying questions to ask when requirements are vague
- "Who is the primary user, and what's their technical comfort level?"
- "Does this need auth? If yes, public signup or invite-only?"
- "Is there existing data this needs to import from, or does it start empty?"
- "Where will this be deployed, and who's paying for hosting?"
- "What's the one thing that, if missing at launch, would make this not worth shipping?"

### Output artifact
A short `REQUIREMENTS.md` or a bulleted section in the chat with: goals, non-goals, users, key flows, constraints. 10–30 lines is usually right.

## 2. Planning

### Scope slicing
- Write an MVP list — features that MUST ship for v1 to be useful.
- Write a "v1.1" list — things you'd add immediately after.
- Write a "someday" list — everything else. This list is where good ideas go to wait.

### Milestones as vertical slices
Order milestones so each one is independently runnable. Bad: "build the whole database, then the whole API, then the whole frontend." Good: "get a single entity (User) creatable from UI → API → DB, then add the next."

### Risk register
List risks and what you'd do about each:
- **Third-party dependencies** (Stripe, OpenAI, Twilio): what if they're down or change their API?
- **Novel tech**: are you using something you've never used before? Prototype the risky bit first.
- **Unclear requirements**: call these out explicitly — "we're guessing about X, need confirmation before wiring Y"
- **Performance assumptions**: load-test the riskiest path before building around it

### Tech stack decision
Pick now. One line per choice justifying it. See the stack table in `SKILL.md`. Avoid decision paralysis — you can swap a library later; you can't swap a framework later without a rewrite. So spend time on the framework choice and move fast on everything else.

## 3. Design & Architecture

### Data model
- List entities (User, Post, Order, etc.)
- List relationships (1:1, 1:N, N:M)
- List key fields per entity with types
- Identify indexes you'll need based on query patterns
- Identify soft-delete, audit, timestamp columns — adding these later is painful

For SQL: draft `CREATE TABLE` statements.
For NoSQL: draft document shapes AND the access patterns they support (DynamoDB in particular is access-pattern-driven).

### API surface
- REST: list endpoints with method, path, request body, response body, status codes.
- GraphQL: draft the schema (types, queries, mutations).
- Identify which endpoints need auth and what roles.

### Frontend architecture
- List pages/routes.
- Identify shared components (navigation, forms, modals, tables).
- Decide state boundaries: server state (React Query/SWR), global UI state (Zustand/Redux/Context), form state (React Hook Form).
- Decide rendering strategy per page: SSR, SSG, CSR, ISR.

### Infrastructure sketch
Draw the request flow. Even ASCII is fine:

```
[Browser] --> [CDN/Vercel] --> [Next.js app]
                                    |
                                    +--> [Postgres]
                                    +--> [S3 / object storage]
                                    +--> [Stripe API]
                                    +--> [SendGrid API]
```

Identify the trust boundaries — every arrow crossing a boundary is a place that needs auth, validation, and error handling.

### Directory layout
Show the full tree before creating files. Fix it now — renaming directories later is a chore.

### Output artifact
A `DESIGN.md` or a design section with: data model, API list, component tree, infra diagram, directory tree.

## 4. Implementation

See `SKILL.md` for the vertical-slice order. Additional guidance:

### Version control hygiene
- One commit per logical change, with a clear message.
- Never commit `.env`, `node_modules`, build artifacts, or credentials.
- Add a `.gitignore` on day one.

### Code organization
- **Routes / controllers**: thin — parse input, call a service, return a response.
- **Services**: business logic. The thing you'd unit-test.
- **Repositories / models**: database access. Replaceable.
- **Utilities / lib**: pure functions shared across features.

This separation pays off around 2,000 lines, not before. Don't prematurely abstract a 200-line project.

### Config & environment
- `.env` for local secrets (git-ignored)
- `.env.example` committed, documenting every required variable
- Config validation on boot — the app should fail fast if a required env var is missing, not 20 minutes later when that code path runs

### Error handling
- At the API boundary: catch exceptions, return structured error JSON, log the full trace.
- Inside services: throw domain-specific errors. Don't swallow or rewrap unless you're adding context.
- On the frontend: one global error boundary, plus per-feature error states for network failures.

### README as you go
A `README.md` with prerequisites, `clone → install → env → migrate → run` steps. Update it every time the setup changes. A broken README wastes more time than a missing feature.

## 5. Testing

See `references/testing-strategies.md`.

## 6. Deployment & CI/CD

See `references/deployment-cicd.md`.

## 7. Maintenance

### Day-1 observability
- Structured JSON logs with request ID, user ID, timing.
- Error tracking (Sentry, Rollbar, or equivalent) in both frontend and backend.
- Uptime check on the health endpoint.
- Basic dashboard: request rate, error rate, p95 latency, DB connection count.

See `references/observability.md` for details.

### Operational runbook
A one-page doc covering:
- How to deploy
- How to roll back
- How to connect to production DB read-replica
- How to rotate a leaked secret
- Who to page and how
- Where to find logs, metrics, errors

### Security review before launch
Walk through `references/security-checklist.md`. Don't skip. The cost of an auth bug in prod dwarfs the 30 minutes of reviewing.

### Ongoing maintenance
- Dependency updates: Dependabot / Renovate on a weekly cadence, plus a quarterly major-version review.
- Backup verification: restore the backup to a staging DB at least quarterly. An untested backup may not be a backup.
- Tech debt tracking: keep a `TODO.md` or use issues. If something is a known compromise, write it down.


path: references/security-checklist.md
content: # Security Checklist

Walk through this before declaring an app production-ready. Most items are quick. The cost of a missed item is often orders of magnitude larger than the cost of checking.

## Authentication & sessions
- [ ] Passwords hashed with argon2id or bcrypt (cost ≥12). No MD5, SHA-1, SHA-256-alone.
- [ ] Login failures return a generic error ("invalid credentials"), not "user not found."
- [ ] Rate limit on login, signup, password reset, and any other auth-adjacent endpoint.
- [ ] MFA available for accounts (TOTP minimum); required for admin accounts.
- [ ] Sessions expire. Long sessions via refresh tokens, not long-lived access tokens.
- [ ] Logout actually invalidates the server-side session.
- [ ] Force logout on password change, email change, and role change.
- [ ] Suspicious-login notifications (new device/IP) sent to user.

## Authorization
- [ ] Every endpoint that reads or writes data checks the caller's permissions.
- [ ] Permission checks happen server-side, not just in the UI.
- [ ] Multi-tenant apps scope every DB query by tenant — ideally enforced in a single repository layer, not scattered across endpoints.
- [ ] Admin endpoints are clearly separated (e.g., `/admin/*`) and have stricter auth + logging.
- [ ] Direct object references (`/orders/{id}`) verify the authed user can access that specific object. (IDOR is the #1 bug class on bug bounty platforms.)

## Input validation & injection
- [ ] Every request body, query param, path param, and header you trust is validated (Zod, Pydantic, etc.).
- [ ] All DB queries use parameterized queries / ORM. No string-concatenated SQL. Ever.
- [ ] HTML output is escaped by default (React/Vue/Svelte do this; `dangerouslySetInnerHTML` is audited).
- [ ] File uploads: validate MIME and extension, cap size, scan for malware for user-facing files, store outside the webroot (e.g., S3), serve via signed URLs or a handler that enforces access.
- [ ] User-supplied URLs (avatars, embeds) fetched from the server go through SSRF protection — block RFC1918 IPs, cloud metadata endpoints (169.254.169.254), localhost.
- [ ] Command execution or `eval`-adjacent functions do NOT take user input. If they must, whitelist with extreme prejudice.

## Secrets management
- [ ] No secrets in git (including history — scrub if found).
- [ ] `.env` in `.gitignore`. `.env.example` committed with placeholder values.
- [ ] Production secrets in a managed store (AWS Secrets Manager, GCP Secret Manager, Vault, Doppler, Infisical).
- [ ] Dev, staging, prod have different secrets. Rotate on employee offboarding and suspected compromise.
- [ ] App validates required secrets at boot and fails loud if missing.
- [ ] Nothing secret is logged (passwords, tokens, card numbers, API keys). Logging middleware redacts known sensitive fields.

## HTTPS & transport
- [ ] HTTPS everywhere. HTTP redirects to HTTPS.
- [ ] HSTS header (`Strict-Transport-Security: max-age=31536000; includeSubDomains`) on production.
- [ ] TLS 1.2 minimum; TLS 1.3 preferred. Weak ciphers disabled.
- [ ] Cookies marked `Secure` (HTTPS only) and `HttpOnly`.
- [ ] Cookies have a `SameSite` attribute. Default to `Lax` or `Strict`.

## Web security headers
Set these at the edge or in middleware:
- [ ] `Content-Security-Policy` — most impactful. Start strict (`default-src 'self'`), loosen as needed.
- [ ] `X-Content-Type-Options: nosniff`
- [ ] `X-Frame-Options: DENY` (or `SAMEORIGIN`) — prevent clickjacking. CSP `frame-ancestors` is the modern equivalent.
- [ ] `Referrer-Policy: strict-origin-when-cross-origin`
- [ ] `Permissions-Policy` to opt out of APIs (camera, mic, geolocation) unless needed.

Tools like [securityheaders.com](https://securityheaders.com) grade the setup.

## CORS
- [ ] CORS configured explicitly. Default-deny; allowlist specific origins.
- [ ] `Access-Control-Allow-Origin: *` only for truly public, non-credentialed APIs.
- [ ] `Access-Control-Allow-Credentials: true` only when you need to accept cookies from a specific origin — never combined with wildcard.

## CSRF
- [ ] For cookie-based session auth: CSRF tokens on state-changing endpoints, or rely on `SameSite=Lax/Strict` cookies (which are effective for most modern attacks).
- [ ] For bearer-token auth (Authorization header): CSRF is not a concern — browsers don't auto-attach Authorization headers.

## Rate limiting & abuse
- [ ] Per-endpoint rate limits sized to the threat (strict on auth, looser on reads).
- [ ] Global rate limit as a backstop.
- [ ] CAPTCHA on signup and password reset for public-facing apps.
- [ ] WAF in front (Cloudflare, AWS WAF) — not a substitute, a layer.

## Dependency security
- [ ] Lockfile committed (`package-lock.json`, `poetry.lock`, `go.sum`).
- [ ] `npm audit` / `pip-audit` / `govulncheck` in CI. Fail on high-severity.
- [ ] Dependabot or Renovate configured for weekly updates.
- [ ] Snyk / GitHub Advanced Security / Socket.dev for deeper SCA on sensitive apps.
- [ ] Pin Docker base images to a specific tag, not `latest`. Rebuild regularly.

## Logs, errors, and data leakage
- [ ] Error responses don't leak stack traces, SQL, file paths, or internal hostnames to users. Full detail stays in logs.
- [ ] Error reporting (Sentry) scrubs PII and secrets — configure data scrubbing rules.
- [ ] Logs stored with access controls; PII in logs minimized or redacted.
- [ ] Sourcemaps uploaded to error tracker but NOT served to users in prod (or served with restrictive source-map auth).

## Database & backups
- [ ] Database not exposed publicly — private network or IP allowlist.
- [ ] Least-privilege DB credentials per service; no using the superuser from the app.
- [ ] Automated daily backups with PITR (or equivalent) enabled.
- [ ] Backups encrypted at rest.
- [ ] Restore tested in the last 90 days.
- [ ] PII columns identified and handled per compliance needs (encryption at rest, access logs).

## File storage
- [ ] Uploaded files stored outside the app server (S3, GCS). Not in the webroot.
- [ ] Served via signed, short-lived URLs — not permanent public links (unless truly public).
- [ ] Per-file content-type and content-disposition set correctly to prevent browser sniffing attacks.
- [ ] Antivirus scan for user uploads distributed to other users.

## Third-party integrations
- [ ] Webhook payloads verified by signature before processing.
- [ ] OAuth state parameter validated; PKCE used for public clients.
- [ ] API keys to third parties stored in secrets manager; rotated per vendor recommendation.
- [ ] Outbound requests have timeouts and circuit breakers — a hung third party must not bring down your app.

## Infrastructure / platform
- [ ] IAM policies follow least privilege. No wildcard `*` permissions in production roles.
- [ ] SSH keys, not passwords, for server access. MFA on cloud consoles.
- [ ] Database encryption at rest enabled.
- [ ] VPC / private networking for inter-service communication.
- [ ] DNS, domain registrar, and cloud accounts protected by MFA and account-recovery process.

## Observability for security
- [ ] Login events logged with IP and user-agent.
- [ ] Permission-denied events (403s) logged — unusual spikes are interesting.
- [ ] Admin actions logged to an append-only audit log.
- [ ] Alerts on anomalies: spike in 5xx, spike in failed logins, new admin account created, IAM policy changed.

## Incident response
- [ ] Runbook for: compromised credential, data breach, DDoS, rogue dependency.
- [ ] Known point of contact for security reports (`[REDACTED_SECRET]`, security.txt).
- [ ] Communication plan: status page, customer email template, timeline of who decides what.

## Pre-launch checks
- [ ] Remove all `console.log`s that might dump secrets or user data.
- [ ] Disable verbose error pages (Django `DEBUG=False`, no stack traces in prod responses).
- [ ] Remove dev shortcuts (`/admin/become-user`, test-only endpoints).
- [ ] Close ports not needed by the app (Postgres not exposed to the internet, etc.).
- [ ] Scan with something like OWASP ZAP baseline scan, or a commercial scanner, on staging.
- [ ] Document the data you collect and why — privacy policy reflects reality.

## Compliance quick-reference
If any of these apply, the scope of this checklist expands significantly:
- **GDPR** — any EU user data. Add: data-subject rights (access, deletion, portability), lawful basis for processing, DPA with subprocessors, breach notification within 72h.
- **HIPAA** — any US health data. BAAs with everyone touching PHI, encryption, access audit logs, strict retention.
- **PCI-DSS** — handling card data. Use a tokenization provider (Stripe, Adyen) and stay out of PCI scope if at all possible.
- **SOC 2** — enterprise B2B often asks. Not a one-time audit — it's an operating pattern. Plan months ahead.

## The 80/20 of security
If you only had time for ten things, do these:
1. Use a reputable auth library; don't roll your own.
2. Parameterize every SQL query.
3. Validate every input at the boundary.
4. Check authorization on every endpoint, for the specific resource.
5. Store secrets in a secrets manager; never in git.
6. HTTPS everywhere + secure cookies.
7. Dependency updates automated + audited.
8. Rate limit auth endpoints.
9. Centralized error tracking with PII scrubbing.
10. Backups, tested.

Everything else is marginal improvements on top of these.


path: references/testing-strategies.md
content: # Testing Strategies

How to test a full-stack app — what to write, what to skip, and how to keep the suite fast.

## The testing pyramid

```
        ▲
       /─\      E2E (few, slow, realistic)     ~5%
      /───\
     /─────\    Integration (DB + API)         ~25%
    /───────\
   /─────────\  Unit (pure logic, fast)        ~70%
  ─────────────
```

Rough target ratios. The numbers don't matter; the shape does — lots of fast unit tests, fewer integration tests, very few E2E tests.

### Why the pyramid
- Unit tests: fast (<1s for thousands), run on every save, catch logic bugs early.
- Integration tests: medium-speed, catch contract bugs between layers (API ↔ DB, service ↔ external).
- E2E tests: slow, flaky-prone, but catch things nothing else catches — real browser + real backend + real DB.

Inverting the pyramid (mostly E2E, few unit tests) produces test suites that take 20 minutes and fail randomly. Don't.

## Unit tests — what to test

### Test pure logic
- Domain rules: pricing, permissions, validation, date calculations
- Transformations: parsers, formatters, mappers between layers
- Decision functions: "should this alert fire", "can this user see this post"

### Skip things not worth testing
- Framework plumbing (routing, middleware registration)
- Trivial getters/setters or pass-throughs
- Third-party library behavior (trust it, or test at the integration level)

### Patterns

**Arrange-Act-Assert**:
```ts
test('premium users get free shipping', () => {
  // Arrange
  const user = makeUser({ tier: 'premium' });
  const cart = makeCart({ subtotal: 50 });

  // Act
  const total = calculateTotal(user, cart);

  // Assert
  expect(total.shipping).toBe(0);
});
```

**Table-driven tests** for similar cases:
```ts
test.each([
  { tier: 'free', subtotal: 50, expectedShipping: 10 },
  { tier: 'free', subtotal: 100, expectedShipping: 0 },  // free over $75
  { tier: 'premium', subtotal: 10, expectedShipping: 0 },
])('shipping: $tier tier, $$$subtotal cart', ({ tier, subtotal, expectedShipping }) => {
  const total = calculateTotal(makeUser({ tier }), makeCart({ subtotal }));
  expect(total.shipping).toBe(expectedShipping);
});
```

### What's a "unit"?
A unit is a coherent behavior, not a file or function. One test per public function is fine; one test per private helper is usually noise. Test behavior, not implementation.

### Tools by language
| Lang | Framework |
|---|---|
| JS/TS | **Vitest** (fast, ESM-native, Jest-compatible). Jest still works but is slower. |
| Python | **pytest** |
| Go | built-in `testing` + `testify` (optional) |
| Rust | built-in `#[test]` |

## Integration tests — the API + DB layer

This is where the most valuable tests live for a typical app. You spin up the app, hit real endpoints, verify the response and DB state.

### Setup pattern (Node/Postgres)
- Dedicated test database (e.g., `myapp_test`).
- Migrate schema once before the suite.
- Each test (or test file) runs in a transaction that rolls back — fast, isolated.
- Seed minimal data inside each test.

### Example (Fastify + Prisma + Vitest)
```ts
import { buildApp } from '@/server';
import { prisma } from '@/lib/db';

describe('POST /users', () => {
  const app = buildApp();

  beforeEach(async () => {
    await prisma.user.deleteMany();
  });

  test('creates user with valid input', async () => {
    const response = await app.inject({
      method: 'POST',
      url: '/users',
      payload: { email: '[REDACTED_SECRET]', name: 'Test' },
    });
    expect(response.statusCode).toBe(201);
    const user = await prisma.user.findUnique({ where: { email: '[REDACTED_SECRET]' } });
    expect(user).not.toBeNull();
  });

  test('rejects invalid email', async () => {
    const response = await app.inject({
      method: 'POST',
      url: '/users',
      payload: { email: 'not-an-email', name: 'Test' },
    });
    expect(response.statusCode).toBe(400);
  });
});
```

### Setup pattern (Python/FastAPI)
```python
import pytest
from httpx import AsyncClient
from app.main import app
from app.db import Base, engine, SessionLocal

@pytest.fixture(autouse=True)
def db_reset():
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)
    yield

@pytest.mark.asyncio
async def test_create_user():
    async with AsyncClient(app=app, base_url="http://test") as client:
        response = await client.post("/users", json={"email": "[REDACTED_SECRET]", "name": "A"})
        assert response.status_code == 201
```

## What NOT to mock

**Don't mock your own database.** Integration tests that use a mock DB give you confidence in the mock, not in the code. A changed schema won't break the test. Use a real test database — SQLite in-memory for speed if your app is DB-agnostic, Postgres (Docker, Testcontainers, or local) if you rely on Postgres features.

**Don't mock your own services from other services.** If service A calls service B, and both are in your repo, test them together (or test the contract with consumer-driven contract tests).

**Do mock:**
- Third-party HTTP APIs (Stripe, OpenAI, SendGrid) — record and replay with [`msw`](https://mswjs.io/), [`vcrpy`](https://vcrpy.readthedocs.io/), or [Prism](https://stoplight.io/open-source/prism).
- Time and randomness — stub `Date.now()`, UUID generation, etc., with test doubles.
- Slow or destructive side effects (sending real emails, charging real cards).

### Stripe-specific pattern
Use Stripe's [test mode](https://docs.stripe.com/testing) for integration tests that hit real Stripe APIs with test keys. For unit tests, mock the Stripe client.

## End-to-end tests

E2E tests drive a real browser against the real app (or a production-like staging env). They catch what nothing else catches, but they're expensive and flaky if overused.

### Tools
- **Playwright** (recommended): fast, reliable, multi-browser, great debugging.
- Cypress: still popular, fine. Playwright has overtaken it for most teams.
- Selenium: only if you need a language or browser matrix Playwright doesn't cover.

### What to E2E test
Pick 3–10 "critical user journeys," the paths where regression would be catastrophic:
- Signup → email verify → first login
- Login → do the app's main action → logout
- Checkout / payment flow
- Password reset

Don't E2E test every feature. That's what integration tests are for.

### Keeping E2E stable
- Use `data-testid` attributes for selectors. Don't select by CSS class names — those change.
- Always wait on real signals (element visibility, network idle), never on arbitrary `sleep(2000)`.
- Reset test data between tests — a seed function that creates a known user and cleans up.
- Retry flaky tests automatically (Playwright has `retries: 2` in CI). But: chronic flake = real bug, investigate.
- Run in CI on a staging deployment, not locally-against-localhost (that's too hermetic).

## Frontend component tests

Between unit tests and E2E sits component testing — test a React/Vue component in isolation.

Tools: **Vitest + React Testing Library** (or Vue Testing Library). Playwright has a component testing mode too.

Useful for:
- Complex forms (validation states, error display)
- Stateful components (wizards, multi-step flows)
- Components with lots of conditional rendering

Not useful for:
- Pure presentational components (snapshot tests age badly)
- Things better covered by E2E (checkout flow, multi-page navigation)

## Type checks and linters are tests too

- `tsc --noEmit` or `mypy` or `go vet` in CI — catches a whole class of bugs before they hit the test suite.
- ESLint / Ruff with reasonable rules — catches bugs and enforces consistency.
- **Run them on every push.** Make CI fail on lint and type errors, not just test failures.

## Coverage — useful signal, bad target

- Track coverage as a signal of untested code, not a goal to hit.
- 80% coverage with meaningful assertions is much better than 100% with dumb tests.
- **Don't** set a coverage gate at 100% — it incentivizes test-shaped noise.
- **Do** look at coverage before a risky refactor to see what's untested.

## Speed matters

A slow test suite is one you won't run. Optimization targets:
- Unit suite: <10 seconds.
- Integration suite: <60 seconds.
- E2E: <5 minutes per critical-path run.

Techniques:
- **Parallel test runs** (Vitest, pytest-xdist, Go's `-parallel`).
- **Skip slow tests in watch mode**; run them in CI and pre-push.
- **Shared test DB** that only re-migrates when schema changes; transactions for isolation.
- **Mock external APIs** — a real HTTP call in a unit test suite is a bug.

## CI integration

Every PR runs:
1. Install deps
2. Lint
3. Typecheck
4. Unit + integration tests
5. Build
6. (Optional) E2E against a preview deployment

All must pass to merge. No "just this once."

## When tests fail in CI but pass locally

The usual culprits:
- **Time zone**: tests hardcode times or assume local TZ. Fix: always set `TZ=UTC` in CI; write tests that work in any TZ.
- **Order dependence**: test A leaves state test B relies on (or fights against). Fix: randomize test order, clean state between tests.
- **Flaky due to async race**: awaited wrong thing, or `setTimeout`-based waiting. Fix: wait on real signals.
- **Env var missing in CI**: tests silently used a local default. Fix: validate env on boot, fail loud.

## Test data — fixtures and factories

Prefer **factories** over static fixtures:

```ts
function makeUser(overrides: Partial<User> = {}): User {
  return {
    id: randomUUID(),
    email: `user-${randomUUID()}@test.com`,
    name: 'Test User',
    createdAt: new Date(),
    ...overrides,
  };
}
```

Factories scale to new fields (new required field = update one place). Fixtures rot as the schema evolves.

Libraries: `fishery` (TS), `factory_boy` (Python), `factory` (Go).

## Property-based / fuzz tests (bonus)

For tricky pure functions (parsers, validators, state machines), property-based tests generate many inputs and check invariants:

```ts
import { fc, test } from '@fast-check/vitest';

test.prop([fc.integer(), fc.integer()])('add is commutative', (a, b) => {
  expect(add(a, b)).toBe(add(b, a));
});
```

Tools: `fast-check` (TS), `hypothesis` (Python), Go's built-in `f.Fuzz`.

Not necessary for most app code, but gold for parsers, domain logic, and anything security-sensitive.


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)