Litestore · Developer guide

Harness

Learn about the 25 CI checks that run on your fork, and the 8-guide rule set behind them.

The harness is the paid layer around the MIT codebase. The store itself is free and open source; the harness is what keeps your fork of it healthy as it grows.

Every check is a scripts/check-*.ts file that reads the source in your repository. They run locally, over ripgrep and the TypeScript AST — nothing is uploaded and there is nothing to sign in to.

Terminal
bun run verify           # biome, the checks, vitest run, typecheck — sequential
bun run verify:parallel  # the same 15 legs through scripts/verify.ts, concurrent

How it runs

HarnessHow it runs
Commandbun run verify runs the gate in sequence; bun run verify:parallel runs the same checks across 15 legs, capped at one fewer than your core count.
Checks25 scripts in scripts/check-*.ts, of which 24 run inside verify.
Outside the gateOne: bun run db:guarantees:audit. It is excluded because it needs a live DATABASE_URL, which CI does not have.
Rules8 guides in .claude/guides/, about 1,800 lines, hanging off .claude/CLAUDE.md.
BaselinesExisting offenders are ratcheted rather than blocking — a check fails when a count goes up, not when it is above zero.
NetworkNone. Nothing phones home
ScopeYour fork, including modules you add

Existing offenders are baselined, so adopting the harness on a large codebase never stops the build on day one. Ratchets keep the baseline from growing — you can add code, but you cannot add violations. Each check that carries a baseline updates it with --update-baseline, which is the deliberate act the ratchet is designed to make visible.

Eleven checks are named individually in verify; the other thirteen run under the lint:cleanliness aggregate. bun run lint is the shorter working loop — Biome with --write, six named checks and the same lint:cleanliness — so it skips theme boundaries, event completeness and the three visibility checks. It is not the full gate.

The 25 Checks

Cache and Freshness

These checks prevent stale pages after a write.

  • Cache invalidation (check-cache-invalidation.ts): Requires every server file that runs direct Prisma mutations to import an invalidation helper from ~/lib/cache or revalidateTag/revalidatePath from next/cache. The symptom it catches is an admin table serving stale rows until the 60-second TTL expires. BaseCRUD subclasses and write-only activity tracking are allowlisted.
  • Cache tags (check-cache-tags.ts): Rejects a string or template literal as the first argument to revalidateTag, invalidateTag or invalidateTags, and a protected cacheTag = "…" declaration on a CRUD class. Tags must come from CACHE_TAGS in lib/cache.ts, because a typo invalidates nothing and raises no error.
  • Public feed visibility (check-public-feed-visibility.ts): Walks the AST of the sitemap, RSS and llm.txt routes and flags a db.product or db.collection query whose where never mentions isHidden, so hidden content cannot be advertised to search engines and crawlers. A line tagged // visibility-ok opts out.

Database Guarantees

These checks catch a migration that weakens a promise.

  • DB guarantees (check-db-guarantees.ts): Connects to DATABASE_URL and verifies 20 CHECK constraints, 4 triggers, 11 unique indexes and 6 views against the live schema, then runs duplicate probes over the normalized uniqueness keys and fails on any row in the three drift views. This is the one check verify does not run.
  • Guarantee contract (check-guarantees-contract.ts): Reads migration SQL and needs no database. It fails if db:migrate/db:deploy stop being Prisma Migrate, if prisma db push appears in a script, if amountBase loses its non-nullable Decimal(12,2) shape, or if a named constraint, trigger or view disappears from migration source.
  • Nested soft delete (check-nested-soft-delete.ts): The Prisma soft-delete extension only injects deletedAt: null into top-level reads, so this walks the AST for a nested include/select of a soft-deletable to-many relation with no deletedAt filter of its own.
  • Nested hidden (check-nested-hidden.ts): The same walk for isHidden, which nothing injects at all. It scans server/web, components/web and non-admin app routes only — the admin is meant to see hidden rows.

Dead Code

These checks surface work that was built but never wired up.

  • Dead components (check-dead-components.ts): Flags files under components/ that no other file imports, which are usually a parallel implementation of something that already exists.
  • Orphan exports (check-orphan-exports.ts): Export-level rather than file-level — every exported PascalCase component must be rendered as JSX somewhere, which catches a dead export inside a file that is otherwise live.
  • Dead server actions (check-dead-server-actions.ts): Finds exported createServerAction definitions that nothing outside their own file references, so a permission-wrapped action no button ever calls shows up.
  • Dead Prisma fields (check-dead-prisma-fields.ts): Flags models and fields in prisma/schema.prisma whose name appears in no .ts or .tsx file.
  • Duplicate utilities (check-duplicate-utils.ts): Flags an exported function or const name defined in more than one file under lib/ and utils/, which is where two versions of slugify silently drift apart.
  • Unused dependencies (check-unused-deps.ts): Flags runtime dependencies in package.json that nothing imports, with config-only packages allowlisted and pre-existing ones baselined.

Type and Boundary Safety

These checks catch a runtime crash the compiler should have caught.

  • No as any (check-no-as-any.ts): Scans the whole source tree for as any and as unknown as, baselines today's count and fails on any new occurrence. The casts matter most on money, order and inventory code.
  • Client and server boundary (check-client-server-boundary.ts): Catches a server file importing and invoking a plain lowercase function exported from a "use client" module. Next registers that export as a client reference, so the call throws at request time rather than failing the build.
  • Client bundle isolation (check-client-bundle-server-only.ts): Traverses the static import graph from every "use client" module and fails if a path reaches a server-only file or services/db. Traversal stops at "use server" files, which are RPC boundaries. It runs in under a second; the same feedback from next build takes minutes.
  • Prisma field balance (check-prisma-field-balance.ts): Catches a scalar field that is written or selected as an object key but whose value is never read as .field anywhere — the stored-but-never-consumed shape that dead columns accumulate as.

Convention Discipline

These checks keep one canonical way of doing each job.

  • Discipline (check-discipline.ts): 69 named structural rules, each with its own baseline file under scripts/discipline-baselines/ — icons through the sprite, tables through the factory, mutations through the CRUD classes, permission-wrapped actions, no export *, no broad revalidatePath, no money on a major-unit field.
  • Discipline hygiene (check-discipline-hygiene.ts): A meta-check on the harness itself. It fails on a baseline entry whose file no longer exists and on an allowlist regex that matches nothing, because both only accumulate and each dead exemption is a permanent hole. It has no baseline of its own.
  • Raw flex (check-raw-flex.ts): Flags a className with standalone flex — a display:flex container — where the shared Stack component belongs. flex-1, flex-col, flex-wrap and inline-flex are modifiers, not containers, and are ignored.
  • Theme boundaries (check-theme-boundaries.ts): Keeps theme-specific CSS selectors and theme ids inside config/theme.ts and config/themes/. Global CSS, admin code and shared components must not patch a theme by name; they use theme.target and the helper APIs instead.
  • Hack budget (check-hacks.ts): Counts setTimeout, inline style={{, !important, hardcoded high z-[…], as any and @ts-ignore/@ts-expect-error across the source and fails when a category rises above the count in scripts/hacks-baseline.json.

Events and Tests

These checks protect the code paths where a bug costs real money.

  • Critical tests (check-critical-tests.ts): Requires every file under the money and inventory globs — checkout, payments, pricing, cart, tax, orders, inventory, shipping, refunds, returns — to be imported by at least one *.test.ts. Checking the import, not the filename, is what catches a test asserting on its own stub.
  • Event completeness (check-event-completeness.ts): Requires every member of DOMAIN_EVENT_TYPES to have a createDomainEventHandler in functions/domain-events.ts and to be registered in the domainEventFunctions array. An emitted type with no registered handler publishes into Inngest where nothing consumes it, so subscribed webhooks receive nothing, silently.
  • Analytics events (check-events.ts): Rejects a string literal as the first argument to capture(). Names must come from ANALYTICS_EVENTS in lib/analytics/events.ts, so a capture("search") cannot drift from the "search_executed" the funnel queries.

The Rule Set

The rule set is eight guides in .claude/guides/, about 1,800 lines, hanging off the .claude/CLAUDE.md root file. They are written for an AI agent working in the repository, so Claude Code writes changes that match the conventions already in place.

GuideWhat is in it
design-review.mdA checklist to run before building anything non-trivial, covering the judgement calls the checks can't make.
before-building.mdName the existing sibling you're cloning before writing a new page, model, or module, or stop and ask.
code-style.mdThe conventions the codebase follows, with the reason each one exists.
ui-components.mdWhich shared component to reach for, and when a new one is justified.
data-api.mdHow queries, server actions, and cache tags are structured, and where each belongs.
new-entity.mdThe full path for adding a model, from schema through admin table to activity logging.
platform-primitives.mdThe table of canonical idioms for lifecycle, queues, publish gates, registries, and alerting.
project-specific.mdThe rules that only apply to Litestore, including the money and inventory constraints.

The checks catch a forgotten convention. The guides catch the more expensive mistake: building the wrong thing correctly.

Get the harness

See the harness page.

On this page