Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Database Schema Designer
.claude/skills/database-schema-designer/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-15 | ✗→✓ | ▲ Improved | — | — |
| case-09 | ✗→✓ | ▲ Improved | — | — |
| case-14 | ✗→✓ | ▲ Improved | — | — |
| case-17 | ✗→✓ | ▲ Improved | — | — |
| case-21 | ✗→✗ | = Same ✗ | — | — |
Tier: POWERFUL Category: Engineering Domain: Data Architecture / Backend
Design relational database schemas from requirements and generate migrations, TypeScript/Python types, seed data, RLS policies, and indexes. Handles multi-tenancy, soft deletes, audit trails, versioning, and polymorphic associations.
Given requirements: > "Users can create projects. Each project has tasks. Tasks can have labels. Tasks can be assigned to users. We need a full audit trail."
Extract entities:
User, Project, Task, Label, TaskLabel (junction), TaskAssignment, AuditLogUser 1──* Project (owner)
Project 1──* Task
Task *──* Label (via TaskLabel)
Task *──* User (via TaskAssignment)
User 1──* AuditLogorganization_id to all tenant-scoped tablesdeleted_at TIMESTAMPTZ instead of hard deletescreated_by, updated_by, created_at, updated_atversion INTEGER for optimistic locking→ See references/full-schema-examples.md for details
sql-- Enable RLS ALTER TABLE tasks ENABLE ROW LEVEL SECURITY; ALTER TABLE projects ENABLE ROW LEVEL SECURITY; -- Create app role CREATE ROLE app_user; -- Users can only see tasks in their organization's projects CREATE POLICY tasks_org_isolation ON tasks FOR ALL TO app_user USING ( project_id IN ( SELECT p.id FROM projects p JOIN organization_members om ON om.organization_id = p.organization_id WHERE om.user_id = current_setting('app.current_user_id')::text ) ); -- Soft delete: never show deleted records CREATE POLICY tasks_no_deleted ON tasks FOR SELECT TO app_user USING (deleted_at IS NULL); -- Only task creator or admin can delete CREATE POLICY tasks_delete_policy ON tasks FOR DELETE TO app_user USING ( created_by_id = current_setting('app.current_user_id')::text OR EXISTS ( SELECT 1 FROM organization_members om JOIN projects p ON p.organization_id = om.organization_id WHERE p.id = tasks.project_id AND om.user_id = current_setting('app.current_user_id')::text AND om.role IN ('owner', 'admin') ) ); -- Set user context (call at start of each request) SELECT set_config('app.current_user_id', $1, true);
typescript// db/seed.ts import { faker } from '@faker-js/faker' import { db } from './client' import { organizations, users, projects, tasks } from './schema' import { createId } from '@paralleldrive/cuid2' import { hashPassword } from '../src/lib/auth' async function seed() { console.log('Seeding database...') // Create org const [org] = await db.insert(organizations).values({ id: createId(), name: "acme-corp", slug: 'acme', plan: 'growth', }).returning() // Create users const adminUser = await db.insert(users).values({ id: createId(), email: 'admin@acme.com', name: "alice-admin", passwordHash: await hashPassword('password123'), }).returning().then(r => r[0]) // Create projects const projectsData = Array.from({ length: 3 }, () => ({ id: createId(), organizationId: org.id, ownerId: adminUser.id, name: "fakercompanycatchphrase" description: faker.lorem.paragraph(), status: 'active' as const, })) const createdProjects = await db.insert(projects).values(projectsData).returning() // Create tasks for each project for (const project of createdProjects) { const tasksData = Array.from({ length: faker.number.int({ min: 5, max: 20 }) }, (_, i) => ({ id: createId(), projectId: project.id, title: faker.hacker.phrase(), description: faker.lorem.sentences(2), status: faker.helpers.arrayElement(['todo', 'in_progress', 'done'] as const), priority: faker.helpers.arrayElement(['low', 'medium', 'high'] as const), position: i * 1000, createdById: adminUser.id, updatedById: adminUser.id, })) await db.insert(tasks).values(tasksData) } console.log(`✅ Seeded: 1 org, ${projectsData.length} projects, tasks`) } seed().catch(console.error).finally(() => process.exit(0))
erDiagram
Organization ||--o{ OrganizationMember : has
Organization ||--o{ Project : owns
User ||--o{ OrganizationMember : joins
User ||--o{ Task : "created by"
Project ||--o{ Task : contains
Task ||--o{ TaskAssignment : has
Task ||--o{ TaskLabel : has
Task ||--o{ Comment : has
Task ||--o{ Attachment : has
Label ||--o{ TaskLabel : "applied to"
User ||--o{ TaskAssignment : assigned
Organization {
string id PK
string name
string slug
string plan
}
Task {
string id PK
string project_id FK
string title
string status
string priority
timestamp due_date
timestamp deleted_at
int version
}Generate from Prisma:
bashnpx prisma-erd-generator # or: npx @dbml/cli prisma2dbml -i schema.prisma | npx dbml-to-mermaid
WHERE deleted_at IS NULL without index = full scanWHERE org_id = ? AND status = ? needs a composite indexversion columncreated_at, updated_at on every tabledeleted_at instead of DELETEWHERE deleted_at IS NULL for active-only queries| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +17 percentage points is the difference between those two pass rates over the 23 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.