echos-ocr/AGENTS.md
Randall Stillwell 9c1aaaa61f Bootstrap agent-context pipeline (L1 + L2 + L3)
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>
2026-05-23 14:55:18 -05:00

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 via pg + @prisma/adapter-pg
  • Auth: NextAuth 5 beta — session in src/auth.ts; server-side auth() wrapped by requireApiAuthWithOrg in src/lib/api-auth.ts
  • UI: Tailwind v4 + shadcn/ui primitives in src/components/ui/; lucide-react icons; sonner toasts
  • OCR / AI: Ollama vision models (local) and the ai SDK 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.yml for 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 optional Action to enforce a permission inline; handleApiError returns the right 401/403/500.
  • Multi-tenancy: every ResponseCard, FormTemplate, Person, etc. is scoped by organizationId. Always include organizationId: session.user.orgId in where: 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-roll NextResponse.json({ error }, { status: 500 }).
  • Validation: body parsing is hand-rolled with defensive defaults (see src/app/api/cards/[id]/route.ts PUT). Zod is in package.json but not widely adopted yet — match the surrounding file's style.
  • Form templates: field keys that match a top-level ResponseCard column must be isCore: true (see src/lib/form-templates.ts). Non-core values live in ResponseCard.fieldData JSON; the PUT route auto-promotes matching keys to columns.
  • Toasts: import { toast } from "sonner"toast.success, toast.error, toast.info.
  • Imports: @/*src/* (tsconfig.json paths). 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.name is a denormalized display string. The PUT route at src/app/api/cards/[id]/route.ts recomputes it from firstName + lastName on edit; the OCR pipeline and survey/submit populate it on create. If you write a new path that mutates first/last, recompute name too.
  • Prisma client lives at src/generated/prisma/ (custom output, not the default @prisma/client). Import enums and types from there.
  • src/lib/db.ts exports a lazy Proxy so importing prisma doesn't open a DB pool at module load — required for Next.js build-time data collection without DATABASE_URL. Don't call prisma from 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.ts enforces auth before requests hit src/app/.... New unauthenticated routes must be allowlisted there.
  • prisma/schema.prisma does not declare a url. The connection string comes from DATABASE_URL at 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 at http://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 vitest and @playwright/test; gate them in CI before requiring green.

7. Deployment

  • Primary target is Vercel (vercel.json). Dockerfile + docker-compose.yml are for Coolify / self-host. Build step is npx prisma generate && next build (see package.json). Notes on Coolify / Postgres / MinIO / Traefik wiring are in server_deploy.md at the workspace root.