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>
74 lines
3.4 KiB
Text
74 lines
3.4 KiB
Text
---
|
|
description: Prisma schema and database access conventions
|
|
globs: prisma/**,src/app/api/**/*.ts,src/lib/**/*.ts,scripts/**/*.ts
|
|
---
|
|
|
|
# Prisma Conventions
|
|
|
|
## Client import
|
|
|
|
Always import the singleton:
|
|
|
|
```ts
|
|
import { prisma } from "@/lib/db";
|
|
```
|
|
|
|
`src/lib/db.ts` exposes a lazy `Proxy` over `PrismaClient` — the underlying client (and Postgres pool) is only constructed on first property access. Never `new PrismaClient()` outside of `src/lib/db.ts`. Don't call `prisma` from top-level module code; the lazy proxy depends on `DATABASE_URL` being set at access time, not import time.
|
|
|
|
## Generated client path
|
|
|
|
The Prisma client is generated to `src/generated/prisma/` (custom `output` in `prisma/schema.prisma`), not `@prisma/client`. To import generated types or enums:
|
|
|
|
```ts
|
|
import { Prisma, PrismaClient } from "@/generated/prisma/client";
|
|
```
|
|
|
|
`src/generated/` is a no-go zone — regenerate via `npx prisma generate` instead of editing.
|
|
|
|
## Schema patterns (what this repo actually does)
|
|
|
|
- **IDs:** `String @id @default(cuid())`.
|
|
- **Timestamps:** `createdAt DateTime @default(now())` + `updatedAt DateTime @updatedAt`.
|
|
- **Multi-tenancy:** every business model has `organizationId String` + `@@index([organizationId])`. Auth tables (`User`, `Account`, `Session`) and the `Organization` itself are the exceptions.
|
|
- **JSON columns:** `Json?` is used for `ResponseCard.fieldData`, `ResponseCard.rawOcrResponse`, `Integration.config`, etc. Treat the shape as untrusted on read.
|
|
- **Indexes:** `@@index` on FKs and the columns we sort/filter by. Add an index when you add a `where:` filter.
|
|
- **No `@@map`:** Prisma defaults to PascalCase tables here; do not add `@@map(...)` to existing models.
|
|
|
|
## Multi-tenant scoping (mandatory)
|
|
|
|
Every read or write against a business model MUST scope by `organizationId`:
|
|
|
|
```ts
|
|
// Read
|
|
const items = await prisma.responseCard.findMany({
|
|
where: { organizationId: session.user.orgId, /* ... */ },
|
|
});
|
|
|
|
// Create
|
|
await prisma.responseCard.create({
|
|
data: { organizationId: session.user.orgId, /* ... */ },
|
|
});
|
|
```
|
|
|
|
Cross-org leakage is a security bug. Returning a `404` (not `403`) on accidental cross-org IDs is the convention so we don't reveal existence.
|
|
|
|
## After schema changes
|
|
|
|
```bash
|
|
npx prisma generate # regenerate client at src/generated/prisma/
|
|
npx prisma migrate dev --name descriptive_name # create + apply a migration locally
|
|
npm run schema:map # refresh docs/SCHEMA_MAP.md (see prisma-schema-map.mdc)
|
|
```
|
|
|
|
For prototype-stage edits (no DB structure change you intend to keep), `npm run db:push` is also available.
|
|
|
|
## Query best practices
|
|
|
|
- `findUnique` for ID / unique lookups; `findFirst` when filtering by org.
|
|
- Use `select` when you only need a few fields and the model has heavy relations.
|
|
- Wrap multi-step writes in `prisma.$transaction([...])` or `prisma.$transaction(async (tx) => ...)`.
|
|
- Background jobs go through `ProcessingJob` — see `src/lib/ocr.ts` for the canonical pattern.
|
|
|
|
## Denormalized fields
|
|
|
|
`ResponseCard.name` is a denormalized display string derived from `firstName + lastName`. Three places set it: OCR pipeline (`src/lib/ocr.ts`), the public survey submit handler (`src/app/api/survey/submit/route.ts`), and the PUT route (`src/app/api/cards/[id]/route.ts`, which recomputes on any first/last change). When you write a new path that mutates `firstName` or `lastName`, recompute `name` too — or call into the PUT path so it does it for you.
|