Harness
This page explains the checks that keep your fork of Litestore healthy and how to run them.
The harness is a set of checks and guides that keeps your fork of Litestore healthy as it grows. The store itself is free and open source under the MIT license. The harness is the paid layer around it.
Each check is a scripts/check-*.ts file that reads the source code in your repository. The checks run on your machine using ripgrep and the TypeScript AST. Nothing is uploaded, and you don't sign in to anything.
bun run verify # biome, the checks, vitest run, typecheck — sequential
bun run verify:parallel # the same 15 legs through scripts/verify.ts, concurrentHow it runs
| Harness | How it runs |
|---|---|
| Command | bun run verify runs the steps one after another. bun run verify:parallel runs the same 15 legs at the same time, up to one fewer than your number of CPU cores. |
| Checks | There are 25 scripts in scripts/check-*.ts. 24 of them run inside verify. |
| Outside the gate | One check, bun run db:guarantees:audit, is left out because it needs a live DATABASE_URL, and CI doesn't have one. |
| Rules | 8 guides in .claude/guides/, about 1,800 lines in total, linked from .claude/CLAUDE.md. |
| Baselines | Problems that already exist are recorded in a baseline instead of blocking you. A check fails when a count goes up, not when it is above zero. |
| Network | None. Nothing phones home. |
| Scope | Your fork, including modules you add. |
Because existing problems go into a baseline, you can adopt the harness on a large codebase without the build failing on day one. The baseline can't grow. You can add code, but you can't add new violations.
Each check with a baseline can update it with --update-baseline. Running that flag is a deliberate step, and it shows up in your diff where reviewers can see it.
verify names eleven checks one by one and runs the other thirteen through the lint:cleanliness aggregate.
bun run lint is a shorter loop for everyday work. It runs Biome with --write, six of the named checks and the same lint:cleanliness. It skips the theme boundaries, event completeness and three visibility checks, so it doesn't replace the full verify.
The 25 Checks
Cache and Freshness
These checks stop pages from showing old data after a write.
- Cache invalidation (
check-cache-invalidation.ts): Every server file that runs Prisma mutations directly must import an invalidation helper from~/lib/cache, orrevalidateTag/revalidatePathfromnext/cache. Without this, an admin table can show old rows until the 60-second TTL runs out.BaseCRUDsubclasses and write-only activity tracking are allowlisted. - Cache tags (
check-cache-tags.ts): Fails when the first argument torevalidateTag,invalidateTagorinvalidateTagsis a string or template literal. It also fails on aprotected cacheTag = "…"declaration on a CRUD class. Tags must come fromCACHE_TAGSinlib/cache.ts, because a mistyped tag invalidates nothing and raises no error. - Public feed visibility (
check-public-feed-visibility.ts): Reads the sitemap, RSS andllm.txtroutes and flags anydb.productordb.collectionquery whosewherenever mentionsisHidden. This keeps hidden content out of what search engines and crawlers see. You can mark a line with// visibility-okto skip it.
Database Guarantees
These checks catch a migration that weakens one of the database's promises.
- DB guarantees (
check-db-guarantees.ts): Connects toDATABASE_URLand confirms that 20 CHECK constraints, 4 triggers, 11 unique indexes and 6 views exist in the live schema. It then looks for duplicates in the normalized uniqueness keys, and fails if any of the three drift views returns a row. This is the one checkverifydoesn't run. - Guarantee contract (
check-guarantees-contract.ts): Reads the migration SQL and needs no database. It fails ifdb:migrateordb:deploystop using Prisma Migrate, if a script usesprisma db push, ifamountBasestops being a non-nullableDecimal(12,2), or if a named constraint, trigger or view disappears from the migrations. - Nested soft delete (
check-nested-soft-delete.ts): The Prisma soft-delete extension only addsdeletedAt: nullto top-level reads. This check looks for a nestedincludeorselectof a soft-deletable to-many relation that has nodeletedAtfilter of its own. - Nested hidden (
check-nested-hidden.ts): Does the same forisHidden, which nothing adds automatically. It only scansserver/web,components/weband the non-adminapproutes, because the admin is supposed to see hidden rows.
Dead Code
These checks find work that was built but never connected.
- Dead components (
check-dead-components.ts): Flags files undercomponents/that no other file imports. These are usually a second version of something that already exists. - Orphan exports (
check-orphan-exports.ts): Works on single exports instead of whole files. Every exported PascalCase component must be rendered as JSX somewhere, so it catches a dead export inside a file that is otherwise in use. - Dead server actions (
check-dead-server-actions.ts): Finds exportedcreateServerActiondefinitions that no code outside their own file uses. That shows you a permission-wrapped action that no button ever calls. - Dead Prisma fields (
check-dead-prisma-fields.ts): Flags models and fields inprisma/schema.prismawhose names appear in no.tsor.tsxfile. - Duplicate utilities (
check-duplicate-utils.ts): Flags an exported function or const name that is defined in more than one file underlib/andutils/. That's how two versions ofslugifydrift apart without anyone noticing. - Unused dependencies (
check-unused-deps.ts): Flags runtimedependenciesinpackage.jsonthat nothing imports. Packages used only in config are allowlisted, and ones that were already unused are in the baseline.
Type and Boundary Safety
These checks catch runtime crashes that the compiler should have caught.
- No as any (
check-no-as-any.ts): Scans the whole source tree foras anyandas unknown as. It records today's count and fails on any new one. These casts are most dangerous in money, order and inventory code. - Client and server boundary (
check-client-server-boundary.ts): Catches a server file that imports and calls a plain lowercase function exported from a"use client"module. Next registers that export as a client reference, so the call throws when a request comes in instead of failing the build. - Client bundle isolation (
check-client-bundle-server-only.ts): Follows the static imports from every"use client"module and fails if any path reaches aserver-onlyfile orservices/db. It stops at"use server"files, because those are RPC boundaries. It takes under a second, whilenext buildtakes minutes to tell you the same thing. - Prisma field balance (
check-prisma-field-balance.ts): Catches a scalar field that the code writes or selects as an object key but never reads as.field. Dead columns usually start out this way, stored but never used.
Convention Discipline
These checks keep one standard way of doing each job.
- Discipline (
check-discipline.ts): Runs 69 named structural rules, each with its own baseline file underscripts/discipline-baselines/. Examples include using the sprite for icons, the factory for tables and the CRUD classes for mutations. Others require permission-wrapped actions and forbidexport *, broadrevalidatePathcalls and money on a major-unit field. - Discipline hygiene (
check-discipline-hygiene.ts): Checks the harness itself. It fails on a baseline entry whose file no longer exists, and on an allowlist regex that matches nothing. Both kinds of leftover only pile up, and each dead exemption stays a hole forever. This check has no baseline of its own. - Raw flex (
check-raw-flex.ts): Flags aclassNamewith a standaloneflex, which makes a display:flex container, where the sharedStackcomponent should be used. It ignoresflex-1,flex-col,flex-wrapandinline-flex, because those modify a container rather than create one. - Theme boundaries (
check-theme-boundaries.ts): Keeps theme-specific CSS selectors and theme ids insideconfig/theme.tsandconfig/themes/. Global CSS, admin code and shared components must not change a theme by name. They usetheme.targetand the helper APIs instead. - Hack budget (
check-hacks.ts): CountssetTimeout, inlinestyle={{,!important, hardcoded highz-[…],as anyand@ts-ignore/@ts-expect-errorin the source. It fails when any of these counts rises above the number inscripts/hacks-baseline.json.
Events and Tests
These checks protect the code where a bug costs real money.
- Critical tests (
check-critical-tests.ts): Every file in the money and inventory paths must be imported by at least one*.test.ts. That covers checkout, payments, pricing, cart, tax, orders, inventory, shipping, refunds and returns. The check looks at imports, not file names, so it catches a test that only checks its own stub. - Event completeness (
check-event-completeness.ts): Every member ofDOMAIN_EVENT_TYPESmust have acreateDomainEventHandlerinfunctions/domain-events.tsand be registered in thedomainEventFunctionsarray. Without a registered handler, an event goes to Inngest and nothing picks it up, so subscribed webhooks silently get nothing. - Analytics events (
check-events.ts): Fails when the first argument tocapture()is a string literal. Event names must come fromANALYTICS_EVENTSinlib/analytics/events.ts, so acapture("search")can't drift away from the"search_executed"that the funnel looks for.
The Rule Set
The rule set is eight guides in .claude/guides/, about 1,800 lines in total, linked from the root file .claude/CLAUDE.md. They are written for an AI agent working in the repository, so that Claude Code makes changes that follow the conventions already in place.
| Guide | What is in it |
|---|---|
design-review.md | A checklist to go through before you build anything non-trivial. It covers the judgement calls the checks can't make. |
before-building.md | Before you write a new page, model or module, name the existing one you're copying, or stop and ask. |
code-style.md | The conventions the codebase follows, with the reason for each one. |
ui-components.md | Which shared component to use, and when a new one is justified. |
data-api.md | How queries, server actions and cache tags are structured, and where each one goes. |
new-entity.md | Every step for adding a model, from the schema through the admin table to activity logging. |
platform-primitives.md | A table of the standard patterns for lifecycle, queues, publish gates, registries and alerting. |
project-specific.md | The rules that only apply to Litestore, including the money and inventory constraints. |
The checks catch a convention someone forgot. The guides catch a more expensive mistake, which is building the wrong thing correctly.
Get the harness
See the harness page.