@kennethsolomon/shipkit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +321 -0
  2. package/bin/shipkit.js +146 -0
  3. package/commands/sk/brainstorm.md +63 -0
  4. package/commands/sk/branch.md +35 -0
  5. package/commands/sk/config.md +96 -0
  6. package/commands/sk/execute-plan.md +85 -0
  7. package/commands/sk/features.md +238 -0
  8. package/commands/sk/finish-feature.md +154 -0
  9. package/commands/sk/help.md +103 -0
  10. package/commands/sk/hotfix.md +61 -0
  11. package/commands/sk/plan.md +30 -0
  12. package/commands/sk/release.md +72 -0
  13. package/commands/sk/security-check.md +188 -0
  14. package/commands/sk/set-profile.md +71 -0
  15. package/commands/sk/status.md +25 -0
  16. package/commands/sk/update-task.md +35 -0
  17. package/commands/sk/write-plan.md +72 -0
  18. package/package.json +23 -0
  19. package/skills/sk:accessibility/LICENSE.txt +177 -0
  20. package/skills/sk:accessibility/SKILL.md +150 -0
  21. package/skills/sk:api-design/LICENSE.txt +177 -0
  22. package/skills/sk:api-design/SKILL.md +158 -0
  23. package/skills/sk:brainstorming/SKILL.md +124 -0
  24. package/skills/sk:debug/SKILL.md +252 -0
  25. package/skills/sk:debug/debug_conductor.py +177 -0
  26. package/skills/sk:debug/lib/__init__.py +1 -0
  27. package/skills/sk:debug/lib/bug_gatherer.py +55 -0
  28. package/skills/sk:debug/lib/context_reader.py +139 -0
  29. package/skills/sk:debug/lib/findings_writer.py +76 -0
  30. package/skills/sk:debug/lib/lessons_writer.py +165 -0
  31. package/skills/sk:debug/lib/step_runner.py +326 -0
  32. package/skills/sk:features/SKILL.md +238 -0
  33. package/skills/sk:frontend-design/LICENSE.txt +177 -0
  34. package/skills/sk:frontend-design/SKILL.md +191 -0
  35. package/skills/sk:laravel-init/SKILL.md +37 -0
  36. package/skills/sk:laravel-new/SKILL.md +68 -0
  37. package/skills/sk:lint/SKILL.md +113 -0
  38. package/skills/sk:perf/LICENSE.txt +177 -0
  39. package/skills/sk:perf/SKILL.md +188 -0
  40. package/skills/sk:release/SKILL.md +113 -0
  41. package/skills/sk:release/references/android-checklist.md +269 -0
  42. package/skills/sk:release/references/ios-checklist.md +339 -0
  43. package/skills/sk:release/release.sh +378 -0
  44. package/skills/sk:review/SKILL.md +346 -0
  45. package/skills/sk:review/references/security-checklist.md +223 -0
  46. package/skills/sk:schema-migrate/SKILL.md +125 -0
  47. package/skills/sk:schema-migrate/orms/drizzle.md +546 -0
  48. package/skills/sk:schema-migrate/orms/laravel.md +367 -0
  49. package/skills/sk:schema-migrate/orms/prisma.md +357 -0
  50. package/skills/sk:schema-migrate/orms/rails.md +351 -0
  51. package/skills/sk:schema-migrate/orms/sqlalchemy.md +385 -0
  52. package/skills/sk:schema-migrate/references/detection.md +110 -0
  53. package/skills/sk:setup-claude/SKILL.md +365 -0
  54. package/skills/sk:setup-claude/references/detection.md +6 -0
  55. package/skills/sk:setup-claude/references/templates.md +11 -0
  56. package/skills/sk:setup-claude/scripts/apply_setup_claude.py +443 -0
  57. package/skills/sk:setup-claude/scripts/detect_arch_changes.py +437 -0
  58. package/skills/sk:setup-claude/templates/.claude/docs/arch-changelog-guide.md.template +6 -0
  59. package/skills/sk:setup-claude/templates/.claude/docs/changelog-guide.md.template +12 -0
  60. package/skills/sk:setup-claude/templates/CHANGELOG.md.template +21 -0
  61. package/skills/sk:setup-claude/templates/CLAUDE.md.template +299 -0
  62. package/skills/sk:setup-claude/templates/arch-changelog-guide.md.template +3 -0
  63. package/skills/sk:setup-claude/templates/changelog-guide.md.template +3 -0
  64. package/skills/sk:setup-claude/templates/commands/brainstorm.md.template +74 -0
  65. package/skills/sk:setup-claude/templates/commands/execute-plan.md.template +57 -0
  66. package/skills/sk:setup-claude/templates/commands/features.md.template +238 -0
  67. package/skills/sk:setup-claude/templates/commands/finish-feature.md.template +155 -0
  68. package/skills/sk:setup-claude/templates/commands/plan.md.template +30 -0
  69. package/skills/sk:setup-claude/templates/commands/re-setup.md.template +38 -0
  70. package/skills/sk:setup-claude/templates/commands/release.md.template +74 -0
  71. package/skills/sk:setup-claude/templates/commands/security-check.md.template +172 -0
  72. package/skills/sk:setup-claude/templates/commands/status.md.template +17 -0
  73. package/skills/sk:setup-claude/templates/commands/write-plan.md.template +34 -0
  74. package/skills/sk:setup-claude/templates/finish-feature.md.template +3 -0
  75. package/skills/sk:setup-claude/templates/plan.md.template +3 -0
  76. package/skills/sk:setup-claude/templates/status.md.template +3 -0
  77. package/skills/sk:setup-claude/templates/tasks/findings.md.template +19 -0
  78. package/skills/sk:setup-claude/templates/tasks/lessons.md.template +26 -0
  79. package/skills/sk:setup-claude/templates/tasks/progress.md.template +20 -0
  80. package/skills/sk:setup-claude/templates/tasks/security-findings.md.template +5 -0
  81. package/skills/sk:setup-claude/templates/tasks/todo.md.template +26 -0
  82. package/skills/sk:setup-claude/templates/tasks/workflow-status.md.template +31 -0
  83. package/skills/sk:setup-claude/templates/tasks-findings.md.template +3 -0
  84. package/skills/sk:setup-claude/templates/tasks-lessons.md.template +3 -0
  85. package/skills/sk:setup-claude/templates/tasks-progress.md.template +3 -0
  86. package/skills/sk:setup-claude/templates/tasks-todo.md.template +3 -0
  87. package/skills/sk:setup-claude/tests/test_apply_setup_claude.py +193 -0
  88. package/skills/sk:setup-optimizer/SKILL.md +184 -0
  89. package/skills/sk:setup-optimizer/lib/__init__.py +24 -0
  90. package/skills/sk:setup-optimizer/lib/detect.py +205 -0
  91. package/skills/sk:setup-optimizer/lib/discover.py +221 -0
  92. package/skills/sk:setup-optimizer/lib/enrich.py +163 -0
  93. package/skills/sk:setup-optimizer/lib/merge.py +277 -0
  94. package/skills/sk:setup-optimizer/lib/sidecar.py +129 -0
  95. package/skills/sk:setup-optimizer/optimize_claude.py +174 -0
  96. package/skills/sk:setup-optimizer/templates/CLAUDE.md.template +105 -0
  97. package/skills/sk:skill-creator/LICENSE.txt +202 -0
  98. package/skills/sk:skill-creator/SKILL.md +479 -0
  99. package/skills/sk:skill-creator/agents/analyzer.md +274 -0
  100. package/skills/sk:skill-creator/agents/comparator.md +202 -0
  101. package/skills/sk:skill-creator/agents/grader.md +223 -0
  102. package/skills/sk:skill-creator/assets/eval_review.html +146 -0
  103. package/skills/sk:skill-creator/eval-viewer/generate_review.py +471 -0
  104. package/skills/sk:skill-creator/eval-viewer/viewer.html +1325 -0
  105. package/skills/sk:skill-creator/references/schemas.md +430 -0
  106. package/skills/sk:skill-creator/scripts/aggregate_benchmark.py +401 -0
  107. package/skills/sk:skill-creator/scripts/generate_report.py +326 -0
  108. package/skills/sk:skill-creator/scripts/improve_description.py +248 -0
  109. package/skills/sk:skill-creator/scripts/package_skill.py +136 -0
  110. package/skills/sk:skill-creator/scripts/quick_validate.py +103 -0
  111. package/skills/sk:skill-creator/scripts/run_eval.py +310 -0
  112. package/skills/sk:skill-creator/scripts/run_loop.py +332 -0
  113. package/skills/sk:skill-creator/scripts/utils.py +47 -0
  114. package/skills/sk:smart-commit/SKILL.md +175 -0
  115. package/skills/sk:test/SKILL.md +171 -0
  116. package/skills/sk:write-tests/SKILL.md +195 -0
  117. package/skills/sk:write-tests/references/patterns.md +209 -0
@@ -0,0 +1,546 @@
1
+ # /schema-migrate — Drizzle ORM Analysis (Phases 2–5)
2
+
3
+ > This file is loaded by `SKILL.md` when Drizzle ORM is detected.
4
+ > Execute Phase 2 through Phase 5 below, then return the final report.
5
+
6
+ ---
7
+
8
+ ## Phase 2: Classify Changes
9
+
10
+ Parse `git diff HEAD -- [schema_file]` and classify each change:
11
+
12
+ ### Files to Scan (read in parallel)
13
+
14
+ 1. **`drizzle.config.ts` / `drizzle.config.js`**
15
+ - Extract: `dialect` (sqlite | postgres | mysql), `schema`, `out`, `dbCredentials`
16
+ - Detect Supabase: `dialect === 'postgres'` + `dbCredentials.url` contains `supabase` or `supabaseUrl` is set
17
+
18
+ 2. **Schema file** (from config or default `src/db/schema.ts`)
19
+ - Parse all `export const [table] = ...` definitions
20
+ - Track: table names, columns, constraints, indexes, FKs, defaults
21
+
22
+ 3. **`drizzle/meta/_journal.json`**
23
+ - List migration entries + timestamps
24
+ - Track: last migration name and date
25
+
26
+ 4. **`drizzle/meta/` latest snapshot** (e.g., `0000_colorful_nebula.json`)
27
+ - Last recorded schema state from migrations
28
+ - Used to detect "out of sync" condition (pushed changes without `db:generate`)
29
+
30
+ 5. **`package.json`** scripts
31
+ - Detect: `db:push`, `db:migrate`, `db:generate` commands
32
+ - Detect: `supabase push`, `supabase migration` scripts (if Supabase)
33
+
34
+ 6. **`git diff HEAD -- [schema_file]`**
35
+ - Uncommitted schema changes (what's pending)
36
+
37
+ 7. **`git log --oneline -5 -- [schema_file]`**
38
+ - Recent schema commit history
39
+
40
+ 8. **`supabase/config.toml`** (if Supabase project detected)
41
+ - Project ID, region, API settings
42
+ - Check if local migrations exist (`supabase/migrations/`)
43
+
44
+ 9. **Schema version on remote** (Supabase only)
45
+ - If Supabase: fetch schema introspection from `{project_url}/rest/v1/?apikey={key}`
46
+ - Compare local schema against remote to detect sync issues
47
+
48
+ ### Risk Matrix
49
+
50
+ | Change Type | Risk | SQLite | Postgres | MySQL | Supabase | Notes |
51
+ |---|---|---|---|---|---|---|
52
+ | Add new table | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | Unless FK targets are missing |
53
+ | Add nullable column | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | Standard additive change |
54
+ | Add index | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | Non-breaking |
55
+ | Add comment | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | Metadata only |
56
+ | Change default value | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | Only affects new rows |
57
+ | Remove `.notNull()` | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | Relaxing constraint |
58
+ | Add `.primaryKey()` to new col | 🟢 Safe | ✅ | ✅ | ✅ | ✅ | New table schema only |
59
+ | Add NOT NULL col + default | 🟡 Careful | ✅ | ✅ | ✅ | ✅ | Existing rows get default |
60
+ | Add unique constraint | 🟡 Careful | ✅ | ✅ | ✅ | ✅ | Fails if duplicates exist |
61
+ | Add FK to existing col | 🟡 Careful | ⚠️ Table recreation | ✅ | ⚠️ Online DDL | ✅ | Check for orphan rows first |
62
+ | Add/change CASCADE behavior | 🟡 Careful | ⚠️ Table recreation | ✅ | ✅ | ✅ | Behavioral change, data safety |
63
+ | Add check constraint | 🟡 Careful | ⚠️ Table recreation | ✅ | ✅ | ✅ | SQLite requires table recreation |
64
+ | Add RLS policy (Supabase only) | 🟡 Careful | N/A | N/A | N/A | ⚠️ Auth-dependent | Review security implications |
65
+ | Add NOT NULL col (no default) | 🔴 Breaking | ❌ | ❌ | ❌ | ❌ | Fails on non-empty tables |
66
+ | Rename column | 🔴 Breaking | ❌ → data loss | ✅ | ✅ | ✅ | Drizzle sees as drop+add |
67
+ | Change column type | 🔴 Breaking | ⚠️ Table recreation | ⚠️ May fail | ⚠️ May fail | ⚠️ May fail | Cast failures, data loss risk |
68
+ | Drop column | 🔴 Breaking | ⚠️ Data loss | ❌ Data loss | ❌ Data loss | ❌ Data loss | Check usages in code first |
69
+ | Drop table | 🔴 Breaking | ❌ Data loss | ❌ Data loss | ❌ Data loss | ❌ Data loss | Check usages in code first |
70
+ | Add edge function (Supabase) | 🟢 Safe | N/A | N/A | N/A | ✅ | Deployment via Supabase CLI |
71
+ | Modify RLS policy (Supabase) | 🟡 Careful | N/A | N/A | N/A | ⚠️ Auth-dependent | Test auth flows after change |
72
+ | Enable realtime (Supabase) | 🟡 Careful | N/A | N/A | N/A | ⚠️ Performance | Large tables may impact realtime |
73
+
74
+ ### Also Detect
75
+
76
+ - ⚠️ **Migration snapshot out of sync**: Latest snapshot JSON doesn't match schema file
77
+ - Indicates: schema changes have been pushed without `db:generate`
78
+
79
+ - ⚠️ **Uncommitted schema changes**: Diff includes non-schema files (e.g., seed.ts modified)
80
+
81
+ - ⚠️ **Supabase sync issue**: Remote schema differs from local schema
82
+ - Run `supabase db pull` to sync
83
+
84
+ - 🔴 **RLS policies in schema without auth context**: Supabase security issue
85
+ - Policies must reference `auth.uid()` correctly or deny all access
86
+
87
+ ---
88
+
89
+ ## Phase 3: Present Risk Report
90
+
91
+ ### Report Format
92
+
93
+ ```
94
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
95
+ /schema-migrate — Schema Change Analysis
96
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
97
+
98
+ ORM: Drizzle ORM
99
+ Dialect: PostgreSQL (Supabase)
100
+ Project: my-project (region: us-east-1)
101
+ Workflow: db:migrate (versioned migrations)
102
+ Schema: src/db/schema.ts
103
+ Migrations dir: drizzle/
104
+ Last migration: 2025-02-28 (0003_add_profiles_table.sql)
105
+ Supabase local: supabase/migrations/
106
+
107
+ Status:
108
+ 📁 Migration snapshot: IN SYNC
109
+ 📝 Schema file: COMMITTED
110
+ 🌐 Remote schema: IN SYNC with local
111
+ ✅ All systems ready for push
112
+
113
+ Detected Changes (since last commit):
114
+ ──────────────────────────────────────────
115
+
116
+ 🟢 SAFE (2 changes)
117
+ • jobs: Added nullable column `applied_notes` (text)
118
+ • jobs: Added index on `status`
119
+
120
+ 🟡 CAREFUL (1 change — review before pushing)
121
+ • profile: Added `email text NOT NULL DEFAULT ''`
122
+ ↳ All existing rows will get empty string value
123
+ ↳ Is '' an acceptable initial value? Consider nullable first.
124
+ ↳ Supabase: Run seed/trigger to populate meaningful defaults
125
+
126
+ 🔴 BREAKING (1 change — stop and read migration plan)
127
+ • jobs: Column `job_type` changed text → integer
128
+ ↳ PostgreSQL can auto-cast, but verify casts don't lose data
129
+ ↳ See safe migration path below
130
+ ↳ Supabase: Test on staging env first
131
+
132
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
133
+ ```
134
+
135
+ ### Dialect-Specific Notes
136
+
137
+ #### SQLite
138
+ - 🚨 **No ALTER COLUMN TYPE** — must recreate table
139
+ - 🚨 **No ADD FOREIGN KEY to existing column** — must recreate table
140
+ - ✅ Table recreation is safe, but slower for large tables
141
+ - ✅ Foreign key constraints enabled by default (Drizzle handles this)
142
+
143
+ #### PostgreSQL
144
+ - ✅ Full `ALTER TABLE` support — changes are online
145
+ - ✅ Type casts usually safe (explicit `CAST` syntax supported)
146
+ - ⚠️ Long-running `ALTER` may lock table briefly
147
+ - ✅ Migrations are versioned and reversible
148
+
149
+ #### MySQL
150
+ - ⚠️ Partial `ALTER TABLE` — some operations trigger table recreation
151
+ - ⚠️ Type casts may fail silently (use `CAST` explicitly)
152
+ - ⚠️ Foreign key constraints must be managed carefully
153
+ - ✅ Online DDL available in MySQL 8.0+
154
+
155
+ #### Supabase (PostgreSQL + managed features)
156
+ - ✅ All PostgreSQL features available
157
+ - ⚠️ RLS policies require careful auth context review
158
+ - ⚠️ Realtime subscriptions on large tables may impact performance
159
+ - ✅ Remote schema introspection available via Supabase API
160
+ - ✅ Local migrations sync with `supabase push` / `supabase pull`
161
+ - ⚠️ Edge functions deployed via `supabase functions deploy`
162
+ - ✅ Can test changes locally before pushing to production
163
+
164
+ ---
165
+
166
+ ## Phase 4: Per-Change Migration Plans
167
+
168
+ ### Scenario: NOT NULL Column Without Default
169
+
170
+ #### Safe Path (db:push / PostgreSQL / Supabase)
171
+ ```
172
+ Option A (nullable first — most conservative):
173
+ 1. Schema: text('col') [no .notNull()]
174
+ 2. db:push
175
+ 3. Data: backfill values with SQL or migration script
176
+ 4. Schema: text('col').notNull()
177
+ 5. db:push
178
+
179
+ Option B (with default — immediate):
180
+ 1. Schema: text('col').notNull().default('')
181
+ 2. db:push
182
+ 3. All existing rows get '' (or your default)
183
+ ⚠️ Verify '' is acceptable for your use case
184
+
185
+ Option C (pre-populate — scripted):
186
+ 1. Create a migration script that backfills values
187
+ 2. Apply script
188
+ 3. Schema: text('col').notNull()
189
+ 4. db:push
190
+ ```
191
+
192
+ #### Supabase-Specific Notes
193
+ - Use `supabase migration new [name]` to create custom migration with backfill logic
194
+ - Test on staging env: `supabase push --linked` (push to remote staging)
195
+ - Then promote to production
196
+
197
+ ---
198
+
199
+ ### Scenario: Column Type Change (PostgreSQL / Supabase)
200
+
201
+ #### Safe Path (PostgreSQL can auto-cast)
202
+ ```
203
+ PostgreSQL/Supabase:
204
+ 1. Schema: Change column type from text to integer
205
+ 2. db:push (generates migration)
206
+ 3. Review generated SQL — PostgreSQL will attempt CAST
207
+ 4. If migration succeeds: done ✅
208
+ 5. If migration fails: review data for non-numeric values, fix, retry
209
+
210
+ Supabase:
211
+ 1. Test on local/staging first:
212
+ supabase migration new change_type_to_integer
213
+ [Edit migration to include: ALTER TABLE ... ALTER COLUMN ... USING CAST(...) ]
214
+ 2. Test locally: supabase db reset (or supabase push --linked)
215
+ 3. Verify data integrity
216
+ 4. Promote to production: supabase push
217
+ ```
218
+
219
+ #### SQLite (Requires Table Recreation)
220
+
221
+ ```
222
+ SQLite (zero data loss — table recreation):
223
+ 1. Add new column with new type: col_new: integer('col_new')
224
+ 2. db:push → column added, existing data untouched
225
+ 3. Write conversion script: UPDATE table SET col_new = CAST(col AS INTEGER)
226
+ 4. Remove original column from schema: delete text('col')
227
+ 5. db:push → original column dropped
228
+ 6. Rename in schema: col_new → col
229
+ 7. db:push → final shape
230
+
231
+ Note: Step 3 may need custom conversion logic for non-trivial casts
232
+ (e.g., "full-time" → cannot cast to integer → must map manually)
233
+ ```
234
+
235
+ ---
236
+
237
+ ### Scenario: Rename Column
238
+
239
+ #### Safe Path (All Dialects via Drizzle)
240
+
241
+ ```
242
+ Problem: Drizzle sees rename as DROP old + ADD new → data loss!
243
+
244
+ Safe path (add column, backfill, drop old):
245
+ 1. Schema: Add new_name: text('new_name') [nullable]
246
+ 2. db:push
247
+ 3. Backfill: UPDATE table SET new_name = old_name
248
+ 4. Remove old_name from schema
249
+ 5. db:push → old column drops, data preserved ✅
250
+ 6. (Optional) Rename in schema: new_name → desired_name
251
+ 7. db:push
252
+
253
+ Supabase note:
254
+ - Wrap steps 1-3 in a single migration: supabase migration new rename_column
255
+ - Test on staging: supabase db reset
256
+ - Then push to production
257
+
258
+ Alternative for Postgres/Supabase (direct migration):
259
+ - Create manual migration file with: ALTER TABLE table RENAME COLUMN old TO new
260
+ - BUT: also update Drizzle schema to match, or next db:generate will conflict
261
+ - Not recommended — add/backfill/drop is safer and Drizzle-friendly
262
+ ```
263
+
264
+ ---
265
+
266
+ ### Scenario: Unique Constraint on Existing Data
267
+
268
+ #### Pre-Check Before Pushing
269
+
270
+ ```
271
+ SQL to check for duplicates:
272
+ SELECT [col], COUNT(*) FROM [table]
273
+ GROUP BY [col] HAVING COUNT(*) > 1;
274
+
275
+ If duplicates found:
276
+ Option A: Remove duplicates (keep latest/earliest)
277
+ Option B: Add unique constraint only to new data (not retroactive)
278
+ Option C: Use nullable column (allow NULL to bypass unique)
279
+
280
+ After duplicates removed:
281
+ Schema: text('col').unique()
282
+ db:push → constraint added
283
+ ```
284
+
285
+ #### Supabase-Specific
286
+ - If using managed auth, check `auth.users` table for unique email — already enforced
287
+ - For custom tables, test constraint on staging first
288
+
289
+ ---
290
+
291
+ ### Scenario: Add Foreign Key to Existing Column (Supabase / PostgreSQL)
292
+
293
+ #### Pre-Check for Orphan Rows
294
+
295
+ ```
296
+ SQL to check for orphan rows:
297
+ SELECT COUNT(*) FROM [table]
298
+ WHERE [col] NOT IN (SELECT id FROM [referenced_table]);
299
+
300
+ If orphans found:
301
+ Option A: Delete orphan rows (data loss — review first)
302
+ Option B: Set orphans to NULL (if col is nullable)
303
+ Option C: Update orphans to valid reference
304
+ Option D: Don't add FK constraint (allow data quality drift)
305
+
306
+ After orphans cleaned:
307
+ Schema: int('col').references(() => other_table.id)
308
+ db:push → FK constraint added
309
+
310
+ Supabase:
311
+ - RLS policies must permit the JOIN for app code
312
+ - Test ON DELETE / ON UPDATE behavior (CASCADE, SET NULL, RESTRICT)
313
+ ```
314
+
315
+ #### SQLite (Requires Table Recreation)
316
+ ```
317
+ SQLite cannot add FK to existing column directly.
318
+ Follow same orphan-check process, then:
319
+ 1. Schema: int('col').references(() => other_table.id)
320
+ 2. db:push → Drizzle recreates table with FK
321
+ 3. All data preserved ✅
322
+
323
+ ⚠️ Large tables will be slow during recreation (single-threaded)
324
+ ```
325
+
326
+ ---
327
+
328
+ ### Scenario: Add/Change CASCADE Behavior (Supabase / PostgreSQL)
329
+
330
+ #### Before Changing
331
+
332
+ ```
333
+ Review current ON DELETE / ON UPDATE clauses:
334
+ - CASCADE: delete child rows when parent deleted
335
+ - SET NULL: set foreign key to NULL
336
+ - RESTRICT: prevent parent deletion if children exist
337
+ - NO ACTION: (default) similar to RESTRICT
338
+
339
+ Behavioral change example:
340
+ Before: ON DELETE RESTRICT (prevent deletion if children exist)
341
+ After: ON DELETE CASCADE (delete all children too)
342
+
343
+ Impact: If parent record deleted, child records silently deleted
344
+
345
+ Safety checklist:
346
+ ✅ Is cascade the intended behavior?
347
+ ✅ Are there dependent triggers/views that rely on old behavior?
348
+ ✅ Will data loss be acceptable?
349
+ ✅ Test on staging: attempt parent deletion, verify children deleted
350
+
351
+ After approval:
352
+ Schema: .references(() => parent.id, { onDelete: 'cascade' })
353
+ db:push / supabase push
354
+ ```
355
+
356
+ ---
357
+
358
+ ### Scenario: Enable Realtime Subscriptions (Supabase)
359
+
360
+ #### Performance Considerations
361
+
362
+ ```
363
+ ⚠️ Realtime subscriptions on large tables (1M+ rows) may:
364
+ - Increase latency on INSERT/UPDATE/DELETE
365
+ - Consume more server resources
366
+ - Send unnecessary updates to many clients
367
+
368
+ Before enabling:
369
+ 1. Estimate table size and update frequency
370
+ 2. Test on staging with realistic load
371
+ 3. Consider filtering subscriptions (WHERE clauses)
372
+ 4. Use `realtimeEnabled: true` selectively on tables that benefit
373
+
374
+ Schema example:
375
+ export const jobs = pgTable('jobs', { ... }, (table) => ({
376
+ realtimeEnabled: true,
377
+ }))
378
+
379
+ Supabase dashboard:
380
+ - Verify in Supabase project: Database > Replication > Enable for [table]
381
+ - Confirm clients can subscribe: .on('*', callback)
382
+ ```
383
+
384
+ ---
385
+
386
+ ### Scenario: Add RLS Policy (Supabase)
387
+
388
+ #### Auth Context Review
389
+
390
+ ```
391
+ ⚠️ RLS policies require careful auth setup. If policy is wrong, table is inaccessible.
392
+
393
+ Policy template:
394
+ CREATE POLICY "Users can read own jobs"
395
+ ON jobs FOR SELECT
396
+ USING (auth.uid() = user_id);
397
+
398
+ Before adding policy:
399
+ ✅ Is `user_id` column present and populated?
400
+ ✅ Is `auth.uid()` available (Supabase Auth enabled)?
401
+ ✅ Will SELECT/INSERT/UPDATE/DELETE still work for expected users?
402
+ ✅ Are service roles (admin) exempted?
403
+
404
+ Test on staging:
405
+ 1. Set up test user in Supabase Auth
406
+ 2. Query table as test user (use user's JWT token)
407
+ 3. Verify policy allows expected rows, blocks others
408
+ 4. Retry as different user — verify isolation
409
+ 5. Retry as service role — verify bypass
410
+
411
+ If policy blocks all access:
412
+ - Disable policy temporarily: ALTER POLICY ... DISABLE
413
+ - Review policy logic
414
+ - Re-enable after fix
415
+ ```
416
+
417
+ ---
418
+
419
+ ## Phase 5: Command Recommendations
420
+
421
+ ### SQLite (db:push workflow)
422
+
423
+ ```bash
424
+ # 1. View pending changes
425
+ git diff HEAD -- src/db/schema.ts
426
+
427
+ # 2. Review with /schema-migrate
428
+ /schema-migrate
429
+
430
+ # 3. If safe changes only:
431
+ npm run db:push
432
+
433
+ # 4. After pushing, update snapshot
434
+ npm run db:generate
435
+ git add drizzle/
436
+ git commit -m "chore(schema): sync migration snapshot"
437
+
438
+ # 5. If breaking changes:
439
+ # — Fix schema first (follow per-change migration plan)
440
+ # — Then db:push when ready
441
+ ```
442
+
443
+ ### PostgreSQL (db:migrate workflow)
444
+
445
+ ```bash
446
+ # 1. Review changes
447
+ /schema-migrate
448
+
449
+ # 2. Generate migration from schema diff
450
+ npm run db:generate
451
+
452
+ # 3. Review generated SQL
453
+ cat drizzle/[latest-file].sql
454
+
455
+ # 4. Test migration locally (if possible)
456
+ npm run db:migrate
457
+
458
+ # 5. If migration succeeds:
459
+ git add src/db/schema.ts drizzle/
460
+ git commit -m "feat(schema): [describe change]"
461
+
462
+ # 6. Deploy to production (via CI/CD or manual migration)
463
+ ```
464
+
465
+ ### MySQL (db:migrate workflow + online DDL)
466
+
467
+ ```bash
468
+ # 1. Check MySQL version (8.0+ for online DDL)
469
+ mysql --version
470
+
471
+ # 2. Review changes
472
+ /schema-migrate
473
+
474
+ # 3. For large tables, enable online DDL (MySQL 8.0+)
475
+ # — Drizzle will use ALGORITHM=INSTANT where possible
476
+ # — Some operations still require table rebuild
477
+
478
+ npm run db:generate
479
+
480
+ # 4. Review and test
481
+ cat drizzle/[latest-file].sql
482
+ npm run db:migrate
483
+
484
+ # 5. Commit
485
+ git add src/db/schema.ts drizzle/
486
+ git commit -m "feat(schema): [describe change]"
487
+ ```
488
+
489
+ ### Supabase (db:migrate + supabase CLI workflow)
490
+
491
+ ```bash
492
+ # 1. Review changes
493
+ /schema-migrate
494
+
495
+ # 2. Generate migration from schema diff
496
+ npm run db:generate
497
+
498
+ # 3. Review generated migration SQL
499
+ cat drizzle/[latest-file].sql
500
+
501
+ # 4. Create Supabase migration with same changes
502
+ supabase migration new [describe_change]
503
+ # Edit supabase/migrations/[timestamp]_[describe_change].sql
504
+ # Copy SQL from drizzle/[latest-file].sql
505
+
506
+ # 5. Test on local environment
507
+ supabase start
508
+ supabase db reset # or supabase push (to local)
509
+
510
+ # 6. Test on linked staging environment
511
+ supabase link --project-ref=[staging-project]
512
+ supabase push --linked
513
+
514
+ # 7. Verify on staging (Supabase dashboard or API tests)
515
+
516
+ # 8. Promote to production
517
+ supabase link --project-ref=[prod-project]
518
+ supabase push --linked
519
+
520
+ # 9. Commit both schema and migration
521
+ git add src/db/schema.ts drizzle/ supabase/migrations/
522
+ git commit -m "feat(schema): [describe change]"
523
+ ```
524
+
525
+ #### Supabase-Specific Commands
526
+
527
+ ```bash
528
+ # Pull remote schema changes (if someone edited via dashboard)
529
+ supabase db pull
530
+
531
+ # Create a custom migration (e.g., backfill data)
532
+ supabase migration new backfill_user_emails
533
+
534
+ # Test migrations locally
535
+ supabase db reset
536
+ supabase db push # or supabase push --local
537
+
538
+ # Check status
539
+ supabase status
540
+
541
+ # Reset to clean state
542
+ supabase db reset
543
+
544
+ # View migration history
545
+ ls supabase/migrations/
546
+ ```