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>
113 lines
4.5 KiB
Markdown
113 lines
4.5 KiB
Markdown
---
|
|
name: add-prisma-model
|
|
description: Add a new Prisma model to prisma/schema.prisma following the repo's multi-tenant conventions, then regenerate the client, create a migration, and refresh the schema map. Use when the user asks to add a model, table, entity, schema, or new database object.
|
|
---
|
|
|
|
# Add a new Prisma model
|
|
|
|
Every business model in this repo is multi-tenant (scoped by `organizationId`), uses `cuid()` IDs, has `createdAt` / `updatedAt`, and gets indexed on its FK columns. The schema map (`docs/SCHEMA_MAP.md`) is a generated artifact — refresh it after schema edits.
|
|
|
|
## Recipe
|
|
|
|
### 1. Edit `prisma/schema.prisma`
|
|
|
|
Add the model near related models (keep the section comment dividers intact). Template for a typical business model:
|
|
|
|
```prisma
|
|
model Widget {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
name String
|
|
description String?
|
|
config Json?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([organizationId])
|
|
@@index([organizationId, name])
|
|
}
|
|
```
|
|
|
|
Then add the back-relation on `Organization`:
|
|
|
|
```prisma
|
|
model Organization {
|
|
// ...existing fields...
|
|
widgets Widget[]
|
|
}
|
|
```
|
|
|
|
If the new model is owned by a user (e.g. a comment), add `userId` + `@relation` + `onDelete: Cascade` (or `SetNull`, depending) and the back-relation on `User`.
|
|
|
|
Auth tables (`User`, `Account`, `Session`) and `Organization` itself are the only models without `organizationId`.
|
|
|
|
### 2. Regenerate the Prisma client
|
|
|
|
```bash
|
|
npx prisma generate
|
|
```
|
|
|
|
This rewrites `src/generated/prisma/`. The client is consumed via `import { prisma } from "@/lib/db"` — no other file should change.
|
|
|
|
### 3. Create a migration
|
|
|
|
For DB changes you intend to keep:
|
|
|
|
```bash
|
|
npx prisma migrate dev --name add_widget_model
|
|
```
|
|
|
|
For prototype-stage edits (no migration yet):
|
|
|
|
```bash
|
|
npm run db:push
|
|
```
|
|
|
|
Once you're settling on a shape, switch to migrations — `prisma/migrations/` is append-only and authoritative for production.
|
|
|
|
### 4. Refresh the schema map
|
|
|
|
```bash
|
|
npm run schema:map
|
|
```
|
|
|
|
This regenerates `docs/SCHEMA_MAP.md` from `prisma/schema.prisma`. New models land in the **Other** group by default. To categorize:
|
|
|
|
1. Open `scripts/generate-schema-map.ts`.
|
|
2. Add the model to the right `MODEL_GROUPS` bucket (or add a new bucket).
|
|
3. Re-run `npm run schema:map`.
|
|
|
|
### 5. Wire it into API + UI
|
|
|
|
- API routes: follow the `add-api-route` skill. Every query MUST scope by `organizationId`.
|
|
- Activity log: if the model represents user-meaningful state, fire `logActivity(...)` from `src/lib/activity-log.ts` on create / update / delete.
|
|
- Integrations: if the model should propagate to Planning Center / Monday / etc., add an event in `src/lib/integrations.ts` and a handler in the relevant provider under `src/lib/integrations/providers/`.
|
|
|
|
## Common patterns by model shape
|
|
|
|
### Soft delete / archive
|
|
|
|
Don't introduce a `deletedAt` column unless there's a real use case — this repo uses hard delete + `ActivityLog` for audit trail. If you do need soft delete, add `archivedAt DateTime?` (not `deletedAt`) and remember to filter `archivedAt: null` in every list query.
|
|
|
|
### JSON config column
|
|
|
|
Lots of models have a `Json?` column (`Integration.config`, `ResponseCard.fieldData`, etc.) — perfect for variable shapes. Treat as untrusted on read; validate / coerce before use. Don't try to query inside JSON with raw SQL.
|
|
|
|
### Denormalized display field
|
|
|
|
If you add a model with a denormalized display string (like `ResponseCard.name`), establish the recompute path in the SAME PR — both server-side (any write path) and any UI that reads it. See the cautionary tale in `AGENTS.md` § 4.
|
|
|
|
### Lookup table for enums
|
|
|
|
Prisma enums exist but this repo doesn't use any. Convention here: `String` column with documented allowed values + a TypeScript union in `src/lib/<area>.ts`. Stay consistent.
|
|
|
|
## Anti-patterns
|
|
|
|
- Forgetting `organizationId` → cross-tenant data leak waiting to happen.
|
|
- Hand-editing a previous migration → `prisma/migrations/` is append-only; create a new migration.
|
|
- Editing files under `src/generated/prisma/` → regenerate instead.
|
|
- Adding `@@map("...")` to a single model → none of the existing models use it; keep the schema consistent.
|
|
- Skipping `npm run schema:map` after adding the model → `docs/SCHEMA_MAP.md` drifts immediately.
|
|
- Adding a back-relation on `Organization` without thinking about cascade behavior → `onDelete: Cascade` is usually right; `SetNull` is sometimes right; `Restrict` almost never.
|