Guidance for AI coding agents working in this repository. CLAUDE.md is an
identical copy for tools that read that filename; keep both in sync.
Serql — Google Search Console analytics. Next.js 16 App Router (Turbopack),
React 19, TypeScript 5 strict, Tailwind v4, shadcn/ui, Drizzle ORM on Postgres
(Neon or any Postgres), Clerk auth, Upstash-compatible Redis (optional),
Google APIs for GSC data. Open core: AGPL-3.0 core plus a commercially licensed
enterprise layer in src/ee/ (Stripe billing, marketing/legal pages,
telemetry, support widget). Read docs/ARCHITECTURE.md before larger changes.
npm run dev # dev server
npm run verify # eslint (zero warnings) + tsc — run before claiming done
npm run test:unit # core unit tests, no credentials needed (node:test via tsx)
npm run test:ee-unit # enterprise-layer unit tests, no credentials needed
npm test # Playwright e2e (needs a running app + Clerk + GSC data; see tests/README.md)
npm run db:generate # Drizzle migration from schema changes
npm run db:migrate # apply migrations
docker compose up -d postgres # local Postgres; add --profile redis for a Redis REST proxysrc/app/(app)/— authenticated shell.layout.tsxcallsrequireAppUser()(Clerk) thengetAppEntitlement()from@/lib/accessas the access gate.src/app/(auth)/,src/app/share/[token]/(public read-only),src/app/api/*.src/app/page.tsxrenderssrc/components/core-home.tsxunlessNEXT_PUBLIC_EE_MARKETING=true, which swaps insrc/ee/marketing.src/lib/access/— the only core module that imports@/ee/billing. WithSTRIPE_SECRET_KEYunset it uses the "unlimited" provider (everyone has access, no seat/property limits). Core code readsentitlement.limitsandentitlement.canSponsorMembers; it never reads billing tiers directly.src/ee/— enterprise layer.src/ee/index.tsholds the env predicates (isBillingEnabled,isMarketingEnabled, …). Route files undersrc/app/that belong to ee are one-line re-export shims.src/config/site.ts— every URL, name, cookie domain, and support address. Never hardcodeserql.io; usesiteConfig,publicUrlFor(),appUrlFor().src/config/csp.ts— CSP built from configured integrations.src/lib/db/schema.ts— single-file Drizzle schema.src/lib/db/index.tspicks the Neon or node-postgres driver fromDATABASE_URL/DB_DRIVER.src/lib/auth/—requireAppUser(),getAccessiblePropertyIds(),authorizeProperty()(all API routes must scope by these).src/lib/gsc/— GSC queries, caching (Redis optional), sync.src/lib/google-oauth/— 3-legged Google OAuth; tokens AES-encrypted at rest withTOKEN_ENCRYPTION_KEY; OAuth state lives in an encrypted cookie.src/hooks/TanStack Query hooks;src/components/ui/shadcn primitives.
- Core (
src/outsidesrc/ee/) must not import@/eeexcept viasrc/lib/access/and the allowlisted shims. ESLint and the CIboundaryjob enforce this. Do not add new allowlist entries without a reason. - Do not edit
src/ee/**unless the task is explicitly about the hosted product; it is under a different license. - API routes: authorize with
getAccessiblePropertyIds()/authorizeProperty(), return errors viahandleApiError(), never leak stack traces. - Zero ESLint warnings. Path alias
@/*→src/*. Unit tests are colocated*.test.ts(x)and must run without env vars or network. - Never commit
.env*, credentials, or Playwright.auth/state. - Env is read from
.env.localin dev; see.env.examplefor every variable.
Maintainers track work with Beads (bd prime, bd ready, bd close). It is
not required for contributors and is not part of the build.