Working with AI coding agents
ShipVeryFast ships first-class guidance for AI coding agents (Claude Code, Cursor, Codex, and friends) so they edit the codebase the way a senior maintainer would. This page explains the three guidance files, the conventions agents must honor, and the hard rules that keep the repo healthy.
The guidance lives in version control, not in a chat history. Point your agent at the repo root and it reads CLAUDE.md, AGENTS.md, or .cursorrules automatically, no prompt engineering required.
Three guidance files kept in sync
The same conventions are written for three different agent ecosystems. They are deliberately redundant so that whichever file your tool reads, it gets the same rules.
| File | Audience | Role |
|---|---|---|
CLAUDE.md | Claude Code | The canonical, longest guide: architecture, commands, patterns. |
AGENTS.md | Codex, Cursor, Claude Code, others | A tool-agnostic mirror of CLAUDE.md, commands, layout, conventions, gotchas. |
.cursorrules | Cursor | A tight, scannable rules digest that links back to the other two. |
AGENTS.md states the contract explicitly: "This mirrors CLAUDE.md, keep the two in sync." When you change a convention, update all three so no agent gets stale instructions.
Repo conventions agents must follow
Every API route in the boilerplate follows the same four-part shape. Agents adding or editing route handlers under app/api/ are expected to reproduce it. The reference implementations are app/api/security/alerts/route.ts and app/api/ai/chat/route.ts.
- Validate input with Zod, parse the request body against a schema before doing anything else.
- Gate with the session, call
getServerSession(authOptions)from@/libs/authand reject unauthenticated callers. - Rate-limit, use a limiter from
@/libs/rateLimiter. The module exports purpose-built limiters:apiRateLimiter,authRateLimiter,sensitiveOpRateLimiter,magicLinkRateLimiter, andaiRateLimiter. - Log with
securityLoggerand returnNextResponse.json({ error, details }, { status })on failure.
For environment variables, agents must add each new key to the Zod schema in libs/config.ts and to .env.example. Integration keys are marked .optional() so the app still boots without them (graceful degradation). See Environment variables for the full list.
AI / LLM rules: never mix SDKs
The boilerplate has a strict policy for LLM code, repeated verbatim across all three guidance files. The Claude path must use the official @anthropic-ai/sdk with model claude-opus-4-8; the OpenAI path uses the official openai SDK. No OpenAI-compatible shims for Claude, and never mix the two SDKs in one code path.
Both sit behind the provider-agnostic interface in libs/ai/. Agents call the shared surface rather than instantiating a vendor client inline:
// libs/ai/index.ts
export const DEFAULT_PROVIDER: ProviderId = 'anthropic';
export function getProvider(id: ProviderId): Provider { /* ... */ }
export function availableProviders(): Provider[] { /* ... */ }
export function providerCatalogue() { /* ... */ }
// Every provider implements Provider.streamChat(...)See The AI assistant for how the streaming chat endpoint and dashboard UI are wired up on top of this layer.
Styling and structure rules
Visual changes should stay inside the existing design system so the UI remains coherent. The guidance files spell out the conventions agents should respect:
- Palette: Tailwind with a slate / indigo palette,
font-displayfor headings, androunded-2xlcards bordered withborder-slate-200 dark:border-slate-800. - Theming:
components/theme/theme-provider.tsxinjects a global stylesheet of!importantoverrides. Be careful when adding header buttons or gradient backgrounds, legacy selectors can catch them. Toggles useinline-flex+translate-x, not an absolute thumb. - Imports: components are exposed through barrel exports; prefer the established import surfaces over deep relative paths.
One concrete charting gotcha from AGENTS.md: recharts gradient <defs> ids must be unique per instance, so generate them with useId.
The pre-commit coverage ratchet
A Husky pre-commit hook (.husky/pre-commit) runs lint, unit tests, and a coverage check on every commit:
# .husky/pre-commit (paraphrased)
npm run lint
npm run test:unit
npm run test:coverage:unit -- --passWithNoTestsThe thresholds live in jest.config.js under coverageThreshold.global:
| Metric | Minimum |
|---|---|
branches | 10 |
functions | 5 |
lines | 10 |
statements | 10 |
These are intentionally low, a ratchet, not a target. The rule for agents: do not add untested logic under collected paths without either covering it with a test or excluding it from collectCoverageFrom in jest.config.js. Dropping a chunk of untestable presentational or SDK code into a measured path lowers the percentages and fails the commit. Exclude such code in config rather than letting coverage slide.
Hard rules
A few non-negotiables appear in every guidance file. Agents that ignore them tend to corrupt local state or break the build.
- Never run
npm run buildwhile a dev server is running, concurrent writes corrupt the.nextdirectory. - Keep keys optional. New integration env vars go into the Zod schema as
.optional()so the app boots without them. - Know the safe-to-ignore warning.
libs/securityLogger.tsimportsfs/pathand gets pulled into the Edge middleware bundle, producing a dev-only "1 Issue" warning. It is guarded byNEXT_RUNTIME==='edge'and does not throw at runtime, pre-existing and safe to ignore.
The agent commands block
For convenience, here is the canonical commands block from AGENTS.md that agents lean on:
npm run dev # dev server (http://localhost:3000)
npm run build # production build (also runs ESLint)
npm run lint # ESLint
npm run test # unit tests (Jest)
npm run test:e2e # Playwright end-to-end testsMCP-aware agents: copy .mcp.json.example to .mcp.json to grant a filesystem server scoped to the project, then add more servers (GitHub, Postgres) as needed. See MCP setup.
