—— Start here
Three products. One stack. Agents write the code.
I build with AI coding agents now, mostly Devin, mostly from my phone. This is the stack and the workflow I use across three products, pulled from the actual repos. The skills at the bottom are copy-paste ready, so you can give the same workflow to your own agent.
A fan app for a touring band
iOS, Android, Apple TV, Android TV, Roku, CarPlay and web, with live video, an audio archive and an admin CMS.
Deb
A money agent you text. An SMS and iMessage thread with secure web links for sensitive steps. No app to download.
SlowTV
A calm, curated TV for kids: channel-based video on a kiosk-mode phone or TV, configured by a parent from the web.
The loop
Every change, in every repo, goes through the same six steps. Most of them happen without me.
- 01
Prompt
An idea becomes a Devin session, usually typed from my phone. No ticket, no spec doc. The repo's AGENTS.md and skills carry the context.
- 02
Build
Devin plans, writes the code and its tests, and runs the repo gate (pnpm lint, typecheck, test through Turborepo) before it opens a PR.
- 03
Preview
Every PR gets its own Convex preview deployment, a Vercel preview wired to it, and an EAS Update preview I can open on my phone.
- 04
Prove
Devin drives the change like a user: Playwright for web, Android emulator, iOS and Apple TV simulators and cloud devices for mobile. It sends back a screen recording.
- 05
Ship
Merge to main. Convex deploys from CI, Vercel ships the web apps and EAS Update pushes JS over the air. Native changes go through EAS Build to TestFlight.
- 06
Watch
New UX ships behind PostHog flags (on in dev, off in prod) with the old version kept as a variant. Sentry and Expo Observe catch what breaks.
The four things I'd pick again
The engineering team
Devin
- Writes nearly every line across all three products. Each idea is a session, and many run in parallel.
- Blueprints snapshot each repo's environment (dependencies, Android emulator, iOS and tvOS simulators on Mac machines), so a session boots ready to build and test.
- Reads the same AGENTS.md and .agents/skills that Claude Code, Codex and Cursor read, so the knowledge lives in the repo.
- A testing agent drives the running app and records video. Devin Review comments on the PR.
- MCP servers for Convex, Vercel, Expo, PostHog, Sentry, Linear, Figma and Mobbin let it check real data and real deploys.
- Scheduled automations run unattended overnight. I merge in the morning.
The backend
Convex
- One reactive TypeScript backend per product: schema, queries, mutations, actions, crons and HTTP routes. No REST layer, no ORM, no SQL migrations.
- Agents work well with it because everything is typed TypeScript in one folder, with validators on every public function.
- convex-test plus Vitest runs the whole backend suite in CI with no live deployment.
- CI creates a preview deployment per PR, deploys production from main, and cleans previews up when the PR closes.
- Better Auth runs inside Convex through @convex-dev/better-auth. @convex-dev/agent handles AI threads.
Web hosting and AI plumbing
Vercel
- Hosts the Next.js apps: marketing sites, web apps and admin consoles.
- Every PR gets a preview URL pointed at that PR's Convex preview, so the whole stack is testable before merge.
- An ignore-build script skips deploys for apps a PR didn't touch, which matters in a monorepo.
- The AI SDK (ai) and AI Gateway are how every product talks to models.
- The skills CLI (npx skills) installs and pins third-party agent skills.
Every native surface
Expo
- One TypeScript codebase for iPhone, Android, Apple TV and Android TV (via react-native-tvos), plus CarPlay and Live Activities through config plugins and local Expo modules.
- Latest SDK, React Compiler on, and Expo Router for navigation.
- EAS Update ships JS over the air and publishes a preview per PR. EAS Build and Workflows handle simulator builds, TestFlight and the stores.
- Expo Observe and Sentry report performance and crashes from real devices.
- Native folders are generated by prebuild and never committed.
The whole stack
Tags show which products use each piece today. Deb has no native app, so it skips the mobile group.
Foundation
- pnpm workspacesPinned version, frozen lockfile in CIFan appDebSlowTV
- TurborepoOne lint, typecheck and test gate across appsFan appDebSlowTV
- TypeScriptEverywhere, strictFan appDebSlowTV
- ZodValidation at the edgesFan appDebSlowTV
Web
- Next.js 16 + React 19Sites, web apps, admin consolesFan appDebSlowTV
- Tailwind CSS v4Theme tokens shared with mobileFan appDebSlowTV
- shadcn/ui patterncva + clsx + tailwind-merge primitives in a shared ui packageFan appDebSlowTV
- LucideIconsFan appDebSlowTV
Backend and AI
- ConvexDatabase, functions, crons, HTTP, file storageFan appDebSlowTV
- Better AuthAuth via @convex-dev/better-auth, @better-auth/expo on mobileFan appDebSlowTV
- Vercel AI SDKModel calls, tools, streamingFan appDebSlowTV
- @convex-dev/agentPersistent agent threads in ConvexFan appSlowTV
- convex-helpersCustom functions, relationshipsFan appSlowTV
- ResendTransactional emailFan appSlowTV
- Cloudflare Workers + R2Media pipelines and edge jobs Convex shouldn't runFan appDeb
Mobile and TV
- Expo SDK + Expo RouterLatest SDK, file-based routesFan appSlowTV
- React CompilerOn by default, no manual memoFan appSlowTV
- Reanimated 4 + WorkletsAll animation on the UI threadFan appSlowTV
- React Native SkiaCustom drawing and shadersFan appSlowTV
- expo-video + expo-updatesPlayback, OTA updatesFan appSlowTV
- react-native-tvosApple TV and Android TV from the same codeFan appSlowTV
- Uniwind (Pro)Tailwind v4 classNames in React NativeFan app
- Gesture HandlerNative gesturesFan app
- FlashListEvery long listFan app
- Keyboard ControllerKeyboard-aware sheets and formsFan app
- expo-hapticsBehind one shared haptics helperFan appSlowTV
Quality
- Vitest + convex-testUnit and backend tests, no live deployFan appDebSlowTV
- PlaywrightWeb E2E against Convex previewsFan appSlowTV
- ESLint 9 + @shadcn/lintDesign-system lint: no raw colors or off-scale valuesFan appDeb
- react-doctorBlocking React perf and correctness check in CIFan app
- RevylCloud iOS and Android devices for agent-driven testsFan appSlowTV
Observe
- PostHogAnalytics, flags, A/B variants, session replayFan appDebSlowTV
- SentryErrors on web, mobile and backendFan appDebSlowTV
- Expo ObserveReal-device performanceFan appSlowTV
House rules
- Every repo has a short root AGENTS.md: what lives where, how to run it, how to validate it, what never to do. Deep knowledge goes in skills.
- Product invariants are written down as hard rules (for example, "never take an action without explicit approval"), so the agent can say no to me.
- New non-trivial logic ships with unit tests in the same PR. Bugs go red, then green.
- Shared rules live in a pure packages/domain module that the web, mobile and backend all import.
- Lint enforces the design system: theme tokens only, one haptics helper, reduced-motion checks.
- Mobile clients can run old code against a new backend, so a contract test lists every function the app calls, and breaking changes need a shim or a forced update.
- No secrets in code, no PII in analytics, never point local commands at production.
—— Copy this
Give your agent our workflow
Save each file at the path shown. Devin, Claude Code, Codex and Cursor all read AGENTS.md and .agents/skills, so the knowledge lives in the repo, not in one tool.
AGENTS.mdAGENTS.md
# AGENTS.md
Repo-wide policy for coding agents. Keep it short. Deep guidance lives in
`.agents/skills/*/SKILL.md`.
## Precedence
1. This file. 2. The nearest nested AGENTS.md. 3. The relevant skill.
4. Task instructions. The more specific file wins.
## Layout
pnpm workspaces + Turborepo.
- apps/web Next.js (latest) + React 19 + Tailwind v4
- apps/mobile Expo (latest SDK) + Expo Router, iOS / Android / TV
- apps/convex Convex backend (schema, functions, crons, HTTP)
- packages/domain Pure shared logic + types. Change rules here, once.
- packages/ui Shared shadcn-style primitives. Check here before writing raw controls.
## Product invariants
<List the rules the product must never break. Agents should refuse
changes that violate them.>
## Validate before you finish
`pnpm install --frozen-lockfile && pnpm lint && pnpm typecheck && pnpm test`
Run the narrowest package filter while iterating, the full gate before a PR.
## Change rules
- Non-trivial logic ships with unit tests in the same PR. Extract it from
components into plain modules and test those.
- Bugs: reproduce, add a failing test, fix, re-run.
- UI changes: verify in a real browser or simulator and attach screenshots
of every state (loading, empty, error).
- New user journeys leave a repeatable E2E test behind.
- New UX ships behind a PostHog flag: on in dev, off in prod, old version
kept as a variant.
## Deploys
- Convex deploys to production from CI on push to main. Every PR gets a
Convex preview deployment, a Vercel preview and an EAS Update preview.
- Never run local commands against production. Never seed production.
## Never
- Commit secrets, .env files or generated native folders (ios/, android/).
- Put email, phone numbers, OTPs or other PII in analytics or logs.
Convex.agents/skills/convex/SKILL.md
---
name: convex
description: Convex backend rules. Use for any work in apps/convex or code that
calls it - schema, queries, mutations, actions, indexes, validators, crons,
HTTP routes, auth, convex-test tests, codegen and deploys.
---
# Convex
## Mental model
- Queries are reactive TypeScript functions running in the database.
- Each mutation is one ACID transaction.
- Actions do side effects (fetch, Node libs) and have no ctx.db. They use
ctx.runQuery / ctx.runMutation.
- Schema is enforced on deploy. There are no SQL migrations: widen, backfill,
then tighten.
## Deploy-breaking mistakes
1. Never make a schema change that existing documents don't satisfy. Add new
fields as v.optional, backfill, then tighten.
2. Never pass undefined into a write. Omit the key.
3. Never edit convex/_generated. Run codegen.
4. "use node" files export actions only, no queries or mutations.
5. Every public function has args (and ideally returns) validators.
6. New args on an existing public function deploy before any client sends
them. Ship the validator change in its own PR.
7. Query indexes in field order. Use .withIndex(), not .filter(), for reads.
8. Await every ctx.db.* and ctx.scheduler.* call.
9. Use internal.* for scheduled and server-to-server calls.
10. Shared helpers live in convex/lib/ and are not functions.
## Clients
Import a typed api from a shared types-only package (for example
packages/convex-api re-exporting convex/_generated/api) so web and mobile
get full types.
## Validate
`pnpm --filter <convex-pkg> codegen && pnpm --filter <convex-pkg> typecheck \
&& pnpm --filter <convex-pkg> test && pnpm --filter <convex-pkg> lint`
Tests use convex-test + Vitest and need no live deployment.
## Previews
CI creates a Convex preview deployment per PR, seeds it with demo data, and
deletes it when the PR closes. Production deploys only from main.
Mobile UI.agents/skills/mobile-ui/SKILL.md
---
name: mobile-ui
description: Conventions for the Expo app. Use when building or changing
screens, styling, animation, gestures, lists, keyboard handling or haptics
in apps/mobile.
---
# Mobile UI
## Styling
- Uniwind (Tailwind v4 for React Native) is the default. Use className on
core components and keep class strings static.
- Use theme tokens from global.css (bg-surface, text-text-secondary...), never
raw palette colors or arbitrary values that have an on-scale equivalent.
- useResolveClassNames for APIs that only take style objects. withUniwind only
for third-party components without className.
## Motion
- Reanimated only (never Animated from react-native), and keep work on the UI
thread with worklets.
- Every entering/exiting animation respects useReducedMotion().
- Every Pressable has a visible pressed state (active: class or pressed style).
## Lists, keyboard, haptics
- FlashList for any list that can grow.
- react-native-keyboard-controller: KeyboardAwareScrollView with
keyboardShouldPersistTaps="handled", never KeyboardAvoidingView.
- Haptics only through src/lib/haptics.ts (selection, tap, press, destructive,
confirm, warn, error), never expo-haptics directly.
- No hardcoded bottom padding. Use safe-area utilities or useSafeAreaInsets.
## Platform
- React Compiler is on. Don't add useMemo/useCallback by reflex.
- ios/ and android/ come from expo prebuild and are never committed. Native
code lives in local Expo modules and config plugins.
## Verify
Run it on a simulator or emulator, screenshot every state (loading, empty,
error, long text), and check reduced motion and dark mode.
Enforce the rules above with ESLint (no-restricted-imports plus a small
custom plugin) so the agent gets told the fix, not just the error.
App versioning.agents/skills/app-versioning/SKILL.md
---
name: app-versioning
description: Keep installed mobile and TV apps working against a newer
backend. Use when removing, renaming or tightening a backend function the
app calls, or when making any native change.
---
# App versioning and forced updates
Installed apps run old JS against your newest backend ("drift").
## Two layers
1. OTA (JS-only): expo-updates checks on launch and on foreground, then
reloads at a safe moment (never mid-playback or mid-form). Additive
changes heal themselves.
2. Min-version gate (native): a reactive query compares the app's native
version to a staff-set minimum and shows a blocking "Update" screen
below it. It fails open, so missing config never bricks the app.
## Breaking changes
- Removing or renaming a function the app calls.
- Tightening an arg or return validator, or changing what a function means.
- Anything that bumps the native runtimeVersion.
## When you must break
1. Prefer a back-compat shim: keep the old name or shape working.
2. Otherwise bump the app version and ship a native build.
3. Raise MIN_SUPPORTED_APP_VERSION in packages/domain and prune the contract.
4. After the build is live, raise the floor in the admin console.
## Guardrail
Keep a MOBILE_BACKEND_CONTRACT list of every function the app calls. A CI test
fails if the app calls something not in the list, if a listed function no
longer exists, or if the minimum version exceeds the shipped version. It
can't catch semantic changes, so use judgment too.
Ship.agents/skills/ship/SKILL.md
---
name: ship
description: How a change gets from prompt to production. Use before opening
or updating a PR, and when asked to test, deploy or verify a change.
---
# Ship
1. Branch from latest main. One PR per change. Consolidate parallel work into
one integration PR with conflicts resolved.
2. Run the full root gate before opening the PR:
`pnpm install --frozen-lockfile && pnpm lint && pnpm typecheck && pnpm test`
3. Open the PR with: what changed, why, tests run, risks, and screenshots or a
recording for anything visual.
4. Wait for previews: Convex preview deployment, Vercel preview URL, EAS Update
preview. Test against those, not localhost, when you can.
5. Prove the user outcome on the real target:
- Web: Playwright against the preview.
- Mobile: emulator, simulator or cloud device. Record it.
- Replay a new E2E test 3 times in a row before calling it stable.
6. Keep the PR mergeable. When main moves, merge it in and resolve conflicts
so both sides survive.
7. After merge, confirm the deploy (Convex from CI, Vercel, EAS Update) and
check Sentry/PostHog for regressions.
Report status plainly: what is merged, what is deployed, what is live, and
what the human still has to do.
Public skills we install
Official skills from the teams that make the tools. Add -a claude-code (or your agent's name) to target one tool.
npx skills add get-convex/agent-skillsConvex's official skillsnpx skills add expo/skillsExpo's official skills: EAS Update, Router, native UI, upgradesnpx skills add vercel-labs/agent-skillsVercel's skills: React and React Native best practices, deploysnpx skills add uni-stack/uniwind --skill uniwindUniwind styling for React Nativenpx skills add shadcn/ui --skill shadcnshadcn components and compositionnpx skills add pbakaus/impeccableDesign critique and polish for any frontendnpx skills add cloudflare/skills --skill wrangler --skill workers-best-practicesCloudflare Workers, if you run edge jobsnpx skills add safaiyeh/app-store-review-skillCheck an app against Apple's review guidelines before you submit
Or hand it all over at once
Paste this into Devin or any coding agent pointed at your repo.
Read https://stevemartocci.com/start.md and set this repo up the same way.
1. Compare our stack to it and list the gaps. Don't change frameworks
without asking me.
2. Write a root AGENTS.md from the template, filled in with our real
layout, commands and product invariants.
3. Add the skills from that page under .agents/skills/, adapted to our
paths and package names.
4. Install the public skills that match our stack with `npx skills add`.
5. Make sure CI runs pnpm lint, typecheck and test, and tell me which
preview environments (Convex, Vercel, EAS Update) are missing.
Open one PR with all of it.