echos-ocr/.cursor/skills/add-prisma-model/SKILL.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

4.5 KiB

name description
add-prisma-model 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:

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:

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

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:

npx prisma migrate dev --name add_widget_model

For prototype-stage edits (no migration yet):

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

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.