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.
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 gate in sequence; bun run verify:parallel runs the same checks across 15 legs, capped at one fewer than your core count. |
| Checks | 25 scripts in scripts/check-*.ts, of which 24 run inside verify. |
| Outside the gate | One: bun run db:guarantees:audit. It is excluded because it needs a live DATABASE_URL, which CI does not have. |
| Rules | 8 guides in .claude/guides/, about 1,800 lines, hanging off .claude/CLAUDE.md. |
| Baselines | Existing offenders are ratcheted rather than blocking — 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 |
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/cacheorrevalidateTag/revalidatePathfromnext/cache. The symptom it catches is an admin table serving stale rows until the 60-second TTL expires.BaseCRUDsubclasses and write-only activity tracking are allowlisted. - Cache tags (
check-cache-tags.ts): Rejects a string or template literal as the first argument torevalidateTag,invalidateTagorinvalidateTags, and aprotected cacheTag = "…"declaration on a CRUD class. Tags must come fromCACHE_TAGSinlib/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 andllm.txtroutes and flags adb.productordb.collectionquery whosewherenever mentionsisHidden, so hidden content cannot be advertised to search engines and crawlers. A line tagged// visibility-okopts out.
Database Guarantees
These checks catch a migration that weakens a promise.
- DB guarantees (
check-db-guarantees.ts): Connects toDATABASE_URLand 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 checkverifydoes not run. - Guarantee contract (
check-guarantees-contract.ts): Reads migration SQL and needs no database. It fails ifdb:migrate/db:deploystop being Prisma Migrate, ifprisma db pushappears in a script, ifamountBaseloses its non-nullableDecimal(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 injectsdeletedAt: nullinto top-level reads, so this walks the AST for a nestedinclude/selectof a soft-deletable to-many relation with nodeletedAtfilter of its own. - Nested hidden (
check-nested-hidden.ts): The same walk forisHidden, which nothing injects at all. It scansserver/web,components/weband non-adminapproutes 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 undercomponents/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 exportedcreateServerActiondefinitions 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 inprisma/schema.prismawhose name appears in no.tsor.tsxfile. - Duplicate utilities (
check-duplicate-utils.ts): Flags an exported function or const name defined in more than one file underlib/andutils/, which is where two versions ofslugifysilently drift apart. - Unused dependencies (
check-unused-deps.ts): Flags runtimedependenciesinpackage.jsonthat 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 foras anyandas 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 aserver-onlyfile orservices/db. Traversal stops at"use server"files, which are RPC boundaries. It runs in under a second; the same feedback fromnext buildtakes 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.fieldanywhere — 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 underscripts/discipline-baselines/— icons through the sprite, tables through the factory, mutations through the CRUD classes, permission-wrapped actions, noexport *, no broadrevalidatePath, 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 aclassNamewith standaloneflex— a display:flex container — where the sharedStackcomponent belongs.flex-1,flex-col,flex-wrapandinline-flexare modifiers, not containers, and are ignored. - 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 patch 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-erroracross the source and fails when a category rises above the count inscripts/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 ofDOMAIN_EVENT_TYPESto have acreateDomainEventHandlerinfunctions/domain-events.tsand to be registered in thedomainEventFunctionsarray. 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 tocapture(). Names must come fromANALYTICS_EVENTSinlib/analytics/events.ts, so acapture("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.
| Guide | What is in it |
|---|---|
design-review.md | A checklist to run before building anything non-trivial, covering the judgement calls the checks can't make. |
before-building.md | Name the existing sibling you're cloning before writing a new page, model, or module, or stop and ask. |
code-style.md | The conventions the codebase follows, with the reason each one exists. |
ui-components.md | Which shared component to reach for, and when a new one is justified. |
data-api.md | How queries, server actions, and cache tags are structured, and where each belongs. |
new-entity.md | The full path for adding a model, from schema through admin table to activity logging. |
platform-primitives.md | The table of canonical idioms 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 forgotten convention. The guides catch the more expensive mistake: building the wrong thing correctly.
Get the harness
See the harness page.