Installs a 3-layer Cursor-aligned agent pipeline so future agent
sessions can orient quickly and stay inside guard rails:
L1 — context for any agent reading the repo:
- AGENTS.md (top-level orientation, conventions, no-go zones)
- .cursor/rules/ (no-go-zones, api-routes, prisma, prisma-schema-map)
- .cursor/skills/ (add-api-route, add-prisma-model)
- docs/SCHEMA_MAP.md generated from prisma/schema.prisma
- scripts/generate-schema-map.ts (regenerate the map; wired up as
`npm run schema:map`)
L2 — subagent roles for the 9-stage idea-to-feature pipeline:
- .cursor/agents/role-*.md (conductor, architect, ia-architect,
design-system-auditor, implementer, reviewer, ux-reviewer,
a11y-auditor, doc-writer) with explicit multitask annotations.
L3 — pipeline scaffolding:
- .github/CODEOWNERS, PR template, and CI workflows (ci.yml,
preview-smoke.yml, visual-diff.yml, pr-health-rollup.yml).
Test job is intentionally disabled until Playwright is wired up.
- .convoys/ folder for per-feature run notes + scripts/log-convoy-event.sh.
- scripts/wt.sh worktree helper.
- src/lib/flags/index.ts simple env-driven feature flag wrapper.
- tests/smoke/app.smoke.spec.ts (Playwright smoke; excluded from
tsc until @playwright/test is installed — see tsconfig change).
Also writes .agent-context-manifest.yml so the sync-agent-context
skill can detect drift and offer selective updates from upstream.
Follow-ups (not in this commit):
- Install @playwright/test and re-enable the test job in ci.yml.
- Review .cursor/agents/role-*.md and trim any roles that don't
apply to this codebase.
Co-authored-by: Cursor <cursoragent@cursor.com>
6.1 KiB
6.1 KiB
AGENTS.md — AI collaboration (Echo OCR)
Guidance for agents and humans working in this repo. Prefer existing patterns over new abstractions.
1. Project overview
Echo OCR is a Next.js + Prisma app for Echo Life Church. It ingests paper "connect cards" via OCR (scanned PDFs / images / public survey submissions), extracts structured data, stores it in PostgreSQL behind a multi-tenant org model, and serves a filterable review UI with integrations to Planning Center, Monday.com, Airtable, and webhooks.
- Framework: Next.js 16 (App Router) + React 19, TypeScript strict
- Data: Prisma 7 (custom client output at
src/generated/prisma/) + PostgreSQL viapg+@prisma/adapter-pg - Auth: NextAuth 5 beta — session in
src/auth.ts; server-sideauth()wrapped byrequireApiAuthWithOrginsrc/lib/api-auth.ts - UI: Tailwind v4 + shadcn/ui primitives in
src/components/ui/;lucide-reacticons;sonnertoasts - OCR / AI: Ollama vision models (local) and the
aiSDK with OpenAI-compatible gateways - Storage: S3-compatible (MinIO local, anything S3 in prod) via
@aws-sdk/client-s3 - Hosting: Vercel (
vercel.json) primary; Dockerfile +docker-compose.ymlfor Coolify / local
2. Architecture quick reference
| Area | Path | Notes |
|---|---|---|
| App pages | src/app/(dashboard)/, src/app/(auth)/, src/app/(marketing)/ |
Route groups; dashboard is the protected app shell |
| API routes | src/app/api/**/route.ts |
All start with requireApiAuthWithOrg(); errors via handleApiError |
| Prisma client | src/lib/db.ts |
Lazy proxy singleton; import prisma from @/lib/db (never instantiate new PrismaClient()) |
| Auth helpers | src/lib/api-auth.ts, src/auth.ts |
requireApiAuthWithOrg(action?) returns an OrgSession with user.id + user.orgId |
| Permissions | src/lib/permissions.ts |
can(role, action) and Action union; roles: owner > admin > editor > reviewer > viewer |
| UI primitives | src/components/ui/ |
shadcn-style; do not duplicate — extend or compose |
| Feature components | src/components/cards/, src/components/forms/, etc. |
Co-located by feature |
| OCR pipeline | src/lib/ocr.ts, src/lib/ai-ocr.ts, src/lib/ollama.ts |
Background job model in ProcessingJob |
| Integrations | src/lib/integrations/providers/ |
Each provider exports the same shape; fired via fireIntegrationEvent() |
| Schema | prisma/schema.prisma |
21 models, no enums; multi-tenant via organizationId |
| Schema map | docs/SCHEMA_MAP.md |
Regenerate with npm run schema:map |
Generated Prisma client lives at src/generated/prisma/ — do not edit; regenerate with npx prisma generate.
3. Key conventions
- Auth (server):
const session = await requireApiAuthWithOrg(); /* session.user.orgId */. Pass an optionalActionto enforce a permission inline;handleApiErrorreturns the right 401/403/500. - Multi-tenancy: every
ResponseCard,FormTemplate,Person, etc. is scoped byorganizationId. Always includeorganizationId: session.user.orgIdinwhere:filters and create payloads — there is no row-level security in dev. - API errors: wrap handlers in
try { ... } catch (error) { return handleApiError(error); }. Don't hand-rollNextResponse.json({ error }, { status: 500 }). - Validation: body parsing is hand-rolled with defensive defaults (see
src/app/api/cards/[id]/route.tsPUT). Zod is inpackage.jsonbut not widely adopted yet — match the surrounding file's style. - Form templates: field keys that match a top-level
ResponseCardcolumn must beisCore: true(seesrc/lib/form-templates.ts). Non-core values live inResponseCard.fieldDataJSON; the PUT route auto-promotes matching keys to columns. - Toasts:
import { toast } from "sonner"—toast.success,toast.error,toast.info. - Imports:
@/*→src/*(tsconfig.jsonpaths). Generated Prisma at@/generated/prisma/.... - File names: kebab-case for routes (
reprocess-batch/), kebab-case for components (upload-modal.tsx), camelCase for hooks (useUserProfile). - Client vs server: every interactive page starts with
"use client"; API routes never include it.
4. Common gotchas
ResponseCard.nameis a denormalized display string. The PUT route atsrc/app/api/cards/[id]/route.tsrecomputes it fromfirstName + lastNameon edit; the OCR pipeline andsurvey/submitpopulate it on create. If you write a new path that mutates first/last, recomputenametoo.- Prisma client lives at
src/generated/prisma/(custom output, not the default@prisma/client). Import enums and types from there. src/lib/db.tsexports a lazyProxyso importingprismadoesn't open a DB pool at module load — required for Next.js build-time data collection withoutDATABASE_URL. Don't callprismafrom top-level module code.- MinIO/S3 images are served through
GET /api/images/[...path]which presigns and proxies. Don't hand back raw S3 URLs to the client. - The Cloudflare/Vercel route
src/middleware.tsenforces auth before requests hitsrc/app/.... New unauthenticated routes must be allowlisted there. prisma/schema.prismadoes not declare aurl. The connection string comes fromDATABASE_URLat runtime — local dev needs.env.
5. Running locally
- Runtime: Node 18+ (Node 20 LTS recommended).
- Setup: see
README.md(docker compose up -d postgres minio,npm run db:push,npm run dev). Requires Ollama with a vision model (ollama pull llava:7b) and GraphicsMagick for PDF rasterization. - Dev server:
npm run dev. App athttp://localhost:3000, MinIO console at:9001.
6. Testing
- Runner: none configured yet. There are no unit, integration, or E2E tests. Adding tests is welcome — start with
vitestand@playwright/test; gate them in CI before requiring green.
7. Deployment
- Primary target is Vercel (
vercel.json).Dockerfile+docker-compose.ymlare for Coolify / self-host. Build step isnpx prisma generate && next build(seepackage.json). Notes on Coolify / Postgres / MinIO / Traefik wiring are inserver_deploy.mdat the workspace root.