active

SkillSlap Migration Writer

Safe
System VerifiedSafe

Generate production-ready Supabase migration SQL following SkillSlap conventions: RLS policies, audit triggers, idempotent guards, and rollback safety. Give it a feature description and get a migration file back.

@system/skillslap-migration-writer

supabase
sql
migration
rls
postgres
skillslap

SkillSlap Migration Writer

Purpose: Generate a complete, production-ready Supabase migration SQL file following SkillSlap's exact conventions. No hand-editing the output — it should be paste-and-push quality.


Invocation

code
/migration <feature-description>

Examples:

  • /migration Add a skill_reports table so users can flag inappropriate skills
  • /migration Add a focus_pool_history table tracking daily focus_pool snapshots per skill
  • /migration Add a skill_collections table for users to curate skill lists

SkillSlap Migration Conventions

File naming

YYYYMMDDHHMMSS_snake_case_description.sql Use the current timestamp. Keep description to 3–5 words max.

Required header

sql
-- Migration: [description]
-- Created: [date]
-- Purpose: [one sentence]

Idempotency — always guard with IF NOT EXISTS

sql
-- Tables
CREATE TABLE IF NOT EXISTS public.[table] (...);

-- Columns
ALTER TABLE public.[table]
  ADD COLUMN IF NOT EXISTS [col] [type];

-- Indexes
CREATE INDEX IF NOT EXISTS idx_[table]_[col] ON public.[table]([col]);

Column conventions

sql
id          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
created_at  timestamptz NOT NULL DEFAULT now(),
updated_at  timestamptz NOT NULL DEFAULT now()

Always add an updated_at trigger:

sql
CREATE TRIGGER [table]_updated_at
  BEFORE UPDATE ON public.[table]
  FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Note: update_updated_at_column() already exists in SkillSlap schema

Foreign keys

sql
skill_id    uuid NOT NULL REFERENCES public.skills(id) ON DELETE CASCADE,
user_id     uuid NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,

Always use ON DELETE CASCADE for child records unless there's a specific reason for SET NULL or RESTRICT.

RLS — every user-data table must have it

sql
ALTER TABLE public.[table] ENABLE ROW LEVEL SECURITY;

-- SELECT: authenticated users see their own rows
CREATE POLICY "[table]_select_own" ON public.[table]
  FOR SELECT TO authenticated
  USING (auth.uid() = user_id);

-- SELECT: public data (active status)
CREATE POLICY "[table]_select_public" ON public.[table]
  FOR SELECT USING (status = 'active');

-- INSERT: user can only insert their own rows
CREATE POLICY "[table]_insert_own" ON public.[table]
  FOR INSERT TO authenticated
  WITH CHECK (auth.uid() = user_id);

-- UPDATE: user can only update their own rows
CREATE POLICY "[table]_update_own" ON public.[table]
  FOR UPDATE TO authenticated
  USING (auth.uid() = user_id)
  WITH CHECK (auth.uid() = user_id);

-- DELETE: user can only delete their own rows
CREATE POLICY "[table]_delete_own" ON public.[table]
  FOR DELETE TO authenticated
  USING (auth.uid() = user_id);

-- Service role bypass (only if needed for server-side writes)
-- Document WHY this is needed
CREATE POLICY "[table]_service_role" ON public.[table]
  USING (auth.role() = 'service_role');

Enum types

sql
CREATE TYPE public.[name]_status AS ENUM ('pending', 'active', 'archived');
-- Add IF NOT EXISTS guard using a named block for types
DO $block$ BEGIN
  CREATE TYPE public.[name]_status AS ENUM ('pending', 'active', 'archived');
EXCEPTION WHEN duplicate_object THEN NULL;
END $block$;

Indexes — add for every foreign key and common query pattern

sql
CREATE INDEX IF NOT EXISTS idx_[table]_user_id ON public.[table](user_id);
CREATE INDEX IF NOT EXISTS idx_[table]_skill_id ON public.[table](skill_id);
CREATE INDEX IF NOT EXISTS idx_[table]_created_at ON public.[table](created_at DESC);
-- Partial index for active rows only (common pattern)
CREATE INDEX IF NOT EXISTS idx_[table]_active ON public.[table](created_at DESC)
  WHERE status = 'active';

GRANTs

sql
GRANT SELECT, INSERT, UPDATE, DELETE ON public.[table] TO authenticated;
GRANT SELECT ON public.[table] TO anon;  -- only if table has public data

Output Format

  1. Complete migration file — ready to save as supabase/migrations/[timestamp]_[name].sql
  2. Down migration — save as supabase/migrations/down/[same-filename].sql
  3. Summary — bullet list of what the migration does (for commit message and PR)

Down migration pattern

sql
-- Down: [description]
DROP TABLE IF EXISTS public.[table] CASCADE;
DROP TYPE IF EXISTS public.[type];

Checklist Before Outputting

  • All tables have id uuid PRIMARY KEY DEFAULT gen_random_uuid()
  • All tables have created_at + updated_at with trigger
  • All foreign keys have ON DELETE CASCADE or explicit reason for alternative
  • RLS enabled and all CRUD policies present for tables with user data
  • Indexes on all foreign keys
  • Idempotent (IF NOT EXISTS on all DDL)
  • Down migration included
  • GRANTs match data sensitivity (no anon grant on private tables)
Dormant$0/mo

$20 more to next tier

Info

Created February 24, 2026
Version 1.0.0
Agent-invoked
Terminal output

Embed

Add this skill card to any webpage.

<iframe src="https://skillslap.com/skill/68871c4f-f65b-4812-b37c-003d2477561b/embed"
        width="400" height="200"
        style="border:none;border-radius:12px;"
        title="SkillSlap Skill: SkillSlap Migration Writer">
</iframe>