slopproof
AGENTS.md2 starsChanged 20 days agoMITView on GitHub
Slopproof is a full-stack template built around a verifiable API contract. src/contract generates openapi.json, which generates src/sdk; the running API and the tests must agree with all three. A change is done when pnpm check passes, plus pnpm check:drift and pnpm build && pnpm verify:app for changes to the contract, database, auth, or UI flows. - UI and page routes reach data only through the SDK (#/sdk/...) or a server function in #/lib/*.functions.ts. src/server, drizzle-orm, pg and better-auth (its client…
- Reads credentials
- Installs packages
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,542 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository10 other files it gives its agents.
- tests/AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- tests/CLAUDE.mdCLAUDE.md
- add-featureSkill
- add-pageSkill
- api-changeSkill
- auth-changeSkill
- database-changeSkill
- shadcnSkill
- upgrade-prerelease-depsSkill
The file
We split this AGENTS.md into the rules it gives an agent
10 rules. About 0 are common, 1 is distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 42
Import the file that defines a symbol.
- Very specific: line 91
First setup: Run pnpm install && pnpm bootstrap.
- Distinctive: line 96
Read tests/AGENTS.md before you write or change a test: it says which layer a behavior belongs in and how each layer's harness works.
- Very specific: line 111
Every gate's failure message says how to fix it.
- Very specific: line 120
Keep node_modules/, .output/, and .repos/ out of every tool's scope.
- Very specific: line 124
Run heavy commands one at a time (pnpm build, type-aware lint, verify:app, lighthouse).
- Very specific: line 126
Read upstream code with gh api or a sparse clone of the listed paths into .repos/ (dependency-sources.md), never a full clone.
- Very specific: line 158
CSP: No policy allows 'unsafe-inline' (ADR 0010): style with classes, and put head scripts and styles through the route's head() so the …
- Very specific: line 160
Tailwind: Keep @import "tailwindcss" source("../") in src/styles/app.css.
- Very specific: line 171
TanStack Start and Router: run pnpm dlx @tanstack/intent@0.4.0 list, then pnpm dlx @tanstack/intent@0.4.0 load <package>#<skill> for the …
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AGENTS.md
Slopproof is a full-stack template built around a verifiable API contract. src/contract generates
openapi.json, which generates src/sdk; the running API and the tests must agree with all three. A
change is done when pnpm check passes, plus pnpm check:drift and pnpm build && pnpm verify:app for
changes to the contract, database, auth, or UI flows.
Architecture map
| Path | Role | May import |
src/contract/ | Effect HttpApi contract: schemas, endpoints, tagged errors, middleware tags | effect and other src/contract modules only |
src/server/ | Server-only: Effect handlers (api/), Better Auth (auth.ts), Drizzle (db/), repositories, env.ts, shutdown lifecycle.ts. Read src/server/AGENTS.md before changing it | contract, sdk |
src/sdk/ | Hey API client and TanStack Query options (generated) | nothing |
src/lib/ | Shared plumbing: api-client.ts (isomorphic SDK client), api-error.ts, utils.ts. *.functions.ts are server functions (createServerFn): session.functions.ts (route guards), auth.functions.ts (every account action) | contract, sdk. The server adapters (*.functions.ts, api-client.ts) also import server and lib |
src/components/ | Shared UI that knows no feature: ui/ (shadcn primitives, Radix, style radix-nova), errors/ (router error and not-found states, SectionErrorBoundary, ApiErrorAlert), layouts/ (site header, pending state), form/, lists/, time/, confirm-delete/ | lib, contract, sdk |
src/features/<name>/ | One feature each (posts, auth): api/ (query options and mutation hooks), components/, utils/ | components, lib, server adapters, contract, sdk. Never another feature |
src/routes/, router.tsx, start.ts, styles/ | The app layer. Page routes define the route (loader, guards, head) and compose features. api/$.ts hands requests to the Effect API; api/auth/$.ts to Better Auth | Page routes: features, components, lib, server adapters, contract, sdk. api/** is a server adapter: server, contract, sdk, lib |
- UI and page routes reach data only through the SDK (
#/sdk/...) or a server function in#/lib/*.functions.ts.src/server,drizzle-orm,pgandbetter-auth(its client included) stay behind those: account actions are server functions that run Better Auth's router in-process (callAuthEndpoint), so rate limits and the endpoint allowlist apply. Authorization happens in the EffectAuthenticationmiddleware; the_authedroute guard is only a UX redirect. - Boundaries are enforced by Fallow zones in
.fallowrc.jsonand byno-restricted-importsin.oxlintrc.json. A new top-levelsrc/directory needs a zone before it can be used. - Imports use the
#/alias (package.json#imports) with explicit.tsor.tsxextensions.
Where code goes
The UI follows Bulletproof React, with src/routes/ as the app
layer. Imports flow one way: components → features → app (routes/).
- Feature code:
src/features/<name>/. Read src/features/AGENTS.md before adding or changing one: it holds the data-access and mutation-hook conventions. - UI shared by several features and free of feature knowledge:
src/components/(table above); shadcn primitives go incomponents/ui/throughpnpm exec shadcn add. - Route files hold the route definition and a small page component that composes features (skill
add-page). - Naming: files and folders are kebab-case (Oxlint
unicorn/filename-case; folders bytests/unit/repo-policy.test.ts). TanStack route names keep their prefixes (__root.tsx,_authed.tsx,$.ts,-private/,(group)/).src/sdk/is generated and exempt. - Import the file that defines a symbol. Barrel files (
index.tsre-exporting a folder) fail Oxlintoxc/no-barrel-file; the only one issrc/server/db/schema/index.ts, Drizzle's schema entry.
Code style
Code is read by agents one file at a time: a module fits one read, a name greps to its definition, and a
failure says what was wrong. Oxlint (.oxlintrc.json) enforces the numbers; the rest is for review. Tests:
tests/AGENTS.md; dependencies of the Effect API: src/server/AGENTS.md.
- Size. Functions up to 20 lines, components (
.tsx) up to 80, files up to 300, blank and comment lines not counted (max-lines-per-function,max-lines). Over a limit, split by responsibility, not by moving lines: a component extracts a child component, a Layer moves its methods into functions that take the service (listPublic(db, page)), a switch that maps codes to text becomes a table. - Nesting. At most two nested blocks per function (
max-depth), three nested callbacks (max-nested-callbacks), JSX four elements deep (react/jsx-max-depth), three parameters, more go in an options object (max-params). Return early; no nested ternaries (no-nested-ternary). - Names. A name says what the value is, specifically enough that
rg -w <name>finds its definition and its uses:outcome,listed,lintRun, neverresult,info,item,tmp(id-denylist).dataandhandlerare keys TanStack and Effect impose: keep them as keys, destructure into a specific name (({ data: credentials })), and never declare your own. Name a module after what it does, neverutils.tsorhelpers.ts(src/lib/utils.tsis shadcn's). - Types. No
any(no-explicit-any,no-unsafe-*): read an untyped library value asunknownand narrow it. Exports ofsrc/server,src/lib,src/contractandscriptsdeclare their return type (explicit-module-boundary-types); components and hooks infer theirs. - Errors. A message names the offending value and the shape expected:
`${name} must be an integer between ${min} and ${max} (got "${raw}")`(unicorn/error-messageonly rejects an empty one). Never echo a secret, a password or user content: give its length, scheme or path (envholds credentials asRedacted, which print as<redacted>). Across a trust boundary the client gets a generic message andloggets the cause (src/lib/server-function-errors.ts). - Comments. Say why, not what. Keep them when you refactor, update them when the reason changes. A line that
exists because of an upstream bug or limit links the issue (
better-auth#9920) and says when to remove it. Exports ofsrc/server,src/libandfeatures/*/{api,utils}get a JSDoc sentence on intent, plus a one-line example when the types do not make the call obvious. - Logging. Server code logs through
log(src/server/log.ts), one JSON object per line;consoleis for scripts (no-console).
Workflows
Each multi-step workflow is a project skill in .agents/skills/ (linked into .claude/skills/). Load it first:
| Task | Skill |
Endpoint, schema or typed error in src/contract | api-change |
| Table, column, index or migration | database-change |
| A feature across database, API and UI | add-feature |
| Account action, Better Auth config, creating users | auth-change |
| New page, form or UI state | add-page |
| Any dependency bump, above all the pre-release pins | upgrade-prerelease-deps |
First setup. Run pnpm install && pnpm bootstrap. This creates .env with a secret, starts Postgres and
Mailpit (pnpm mail:up, the local inbox for account emails), and applies migrations.
Verify
Read tests/AGENTS.md before you write or change a test: it says which layer a behavior belongs in and how each layer's harness works.
| Command | Covers |
pnpm check | Every gate that needs no database and no build (format, type-aware lint, typecheck, Effect diagnostics, dead code, complexity, duplication, security sinks, unit/api/component tests with coverage, drift, migration lint, licenses), about 16 s. Run it before every hand-off; the lefthook pre-commit hook runs the jobs your staged files touch |
pnpm test:unit|test:api|test:component [filter ...] | One fast layer; pnpm test:fast runs all three with coverage |
pnpm test:db [filter ...] | The db layer on a throwaway Postgres database. Needs Postgres, no build |
pnpm format, pnpm lint:fix | Autofixes |
pnpm check:drift [contract|migrations|auth|database] | Generated files match their sources, and the auth schema holds what Better Auth writes; database needs Postgres |
pnpm test [filter ...], pnpm test:e2e [filter ...] | The integration or E2E layer against the built app; each starts its own servers on a throwaway database. Needs pnpm build, Postgres and Mailpit (pnpm mail:up) |
pnpm build && pnpm verify:app | The db, integration and E2E layers and contract coverage. Run it, and test:e2e, through the verifier subagent, and wait for its report before reporting the work |
pnpm build && pnpm lighthouse [--page=<name>] | The Lighthouse gate behind the Caddy edge (needs Docker) |
pnpm ci:local [job ...] | The CI jobs in the Playwright Ubuntu container (needs Docker) |
Every gate's failure message says how to fix it. What each gate covers, how to make a reviewed exception, the
Lighthouse policy and the less common commands (audit:check, sbom:release) are in
docs/agents/gates.md.
Memory safety
A misconfigured lint once used 17 GB of memory.
- To check a few files, pass explicit paths:
pnpm lint src/x.ts,pnpm exec oxfmt --check src/x.ts. - Keep
node_modules/**,.output/**, and.repos/**out of every tool's scope..oxlintrc.jsonand.oxfmtrc.jsonlist all three inignorePatterns,tsconfig.jsonincludes onlysrc,scripts,tests, and*.config.ts(and excludesnode_modules,.output, and.repos), and.fallowrc.jsonlists all three inignorePatterns. A new tool or config needs the same exclusions. - Run heavy commands one at a time (
pnpm build, type-aware lint,verify:app,lighthouse); on Linux under a memory cap, see docs/operations.md. - Read upstream code with
gh apior a sparse clone of the listed paths into.repos/(dependency-sources.md), never a full clone.
Generated and vendored files
Regenerate these files; edit their sources instead. Claude Code refuses edits to them (.claude/settings.json):
a refused edit means change the source and run the command.
src/sdk/**andopenapi.json:pnpm codegensrc/routeTree.gen.ts:pnpm devorpnpm builddrizzle/**:pnpm db:generate(only the new SQL file may be adjusted by hand, before it is committed;tests/unit/repo-policy.test.tsrejects edits to migrations already in the journal).agents/skills/shadcn/**andskills-lock.json: theskillsCLI (docs/agents/skills.md). The other skills in.agents/skills/are this repo's own and are edited by hand..repos/**: the sparse clones in dependency-sources.md
Version policy
Dependencies are pinned exactly, fresh releases are quarantined for a day, and the pre-release packages
(nitro, @tanstack/react-start, oxfmt, @hey-api/openapi-ts) are upgraded one
per PR with check, check:drift and verify:app passing. The upgrade-prerelease-deps skill holds the full
policy; load it before changing package.json or pnpm-workspace.yaml.
Sharp edges
- Effect v4. v4 became stable in October 2026, so most examples on the web still use the v3 API. Before
writing Effect code, read
node_modules/effect/AGENTS.mdin full; look up APIs innode_modules/effect/src. - TypeScript. The project has one TypeScript, 7.0.2 (no JS compiler API). Hey API stays on its
nextsnapshot; 0.99.0 crashes on TS 7 (ADR 0002).scripts/*.tsrun through Node's type stripping, so use erasable syntax only (no enums, namespaces, or constructor parameter properties). The TanStack lint plugins run without type information under Oxlint (pnpm-workspace.yamlexplains the peer rule). - CSP. No policy allows
'unsafe-inline'(ADR 0010): style with classes, and put head scripts and styles through the route'shead()so the router adds the nonce. - Tailwind. Keep
@import "tailwindcss" source("../")insrc/styles/app.css. Without it, Tailwind scans.output, SSR and client CSS hashes diverge, and the CSS returns 404 in production (guarded bytests/integration/assets.test.tsandtests/unit/repo-policy.test.ts). - Origin.
APP_URLmust be the public origin. SSR uses it as the SDK base URL, and a mismatch changes TanStack Query keys and causes a refetch after hydration.src/start.tsrejects state-changing requests from any other origin. - Prerender. Nitro, the deployment layer, prerenders the routes in
vite.config.tsand writes each page's hash-based CSP in itsprerender:generatehook (ADR 0004).
Library docs and skills
- TanStack Start and Router: run
pnpm dlx @tanstack/intent@0.4.0 list, thenpnpm dlx @tanstack/intent@0.4.0 load <package>#<skill>for the matching skill (for example,@tanstack/start-client-core#start-core/execution-model). TheLoad:lines thatintent listprints use@tanstack/intent@latest; replace@latestwith@0.4.0. - shadcn/ui: the skill in
.agents/skills/shadcnloads automatically. Run the pinned CLI withpnpm exec shadcn …wherever the skill saysnpx shadcn@latest. - Better Auth, Fallow, and Playwright traces: see docs/agents/skills.md.
- Upstream docs or source not in
node_modules: see docs/agents/dependency-sources.md. Snapshots in.repos/are read-only reference material.
Decisions
Architecture decisions are recorded in docs/decisions/. Read the relevant ADR before reversing a choice, for example adding tRPC or Zod, moving prerendering, or upgrading Drizzle to 1.0.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

