@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,357 @@
1
+ # /schema-migrate — Prisma Analysis (Phases 2–5)
2
+
3
+ > This file is loaded by `SKILL.md` when Prisma 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
+ ### Files to Scan (read in parallel)
11
+
12
+ 1. **`prisma/schema.prisma`** — full schema definition
13
+ - Parse all `model`, `enum`, `datasource`, `generator` blocks
14
+ - Track: models, fields, types, attributes (`@id`, `@unique`, `@@index`, `@relation`, `?`)
15
+
16
+ 2. **`prisma/migrations/`** — migration history directory
17
+ - List all migration folders (timestamp-named)
18
+ - Read latest migration SQL file
19
+
20
+ 3. **`migration_lock.toml`** — migration lock file
21
+ - Verify provider matches `schema.prisma` datasource
22
+
23
+ 4. **`package.json`** — scripts and dependencies
24
+ - Detect: `prisma migrate dev`, `prisma migrate deploy`, `prisma db push`
25
+ - Confirm `@prisma/client` and `prisma` in devDependencies
26
+
27
+ 5. **`.env`** — environment config
28
+ - Extract `DATABASE_URL` → parse dialect from connection string prefix
29
+
30
+ 6. **`git diff HEAD -- prisma/schema.prisma`** — uncommitted changes (what's pending)
31
+
32
+ 7. **`git log --oneline -5 -- prisma/schema.prisma`** — recent schema commit history
33
+
34
+ ### Dialect Detection
35
+
36
+ From `datasource db { provider = "..." }` in `prisma/schema.prisma`:
37
+
38
+ | Provider | Dialect |
39
+ |----------|---------|
40
+ | `postgresql` | PostgreSQL |
41
+ | `mysql` | MySQL |
42
+ | `sqlite` | SQLite |
43
+ | `sqlserver` | SQL Server |
44
+ | `mongodb` | MongoDB ⚠️ (no migrations — flag to user) |
45
+
46
+ **MongoDB note:** Prisma does not generate migration files for MongoDB. If detected, report: "Prisma + MongoDB detected — migration analysis not applicable. Prisma uses `prisma db push` only for MongoDB."
47
+
48
+ ### Risk Matrix
49
+
50
+ | Change | Risk | Notes |
51
+ |--------|------|-------|
52
+ | Add new model | 🟢 Safe | Additive — new table created |
53
+ | Add optional field (`field?` or with default) | 🟢 Safe | No impact on existing rows |
54
+ | Add `@@index` | 🟢 Safe | Non-blocking index creation |
55
+ | Add enum value | 🟡 Careful | PostgreSQL: irreversible `ALTER TYPE ADD VALUE` — cannot be in a transaction |
56
+ | Add required field with default | 🟡 Careful | All existing rows get default value — verify acceptability |
57
+ | Add unique constraint | 🟡 Careful | Fails if duplicate values exist in current data |
58
+ | Add `@relation` to existing field | 🟡 Careful | Orphan row check required; SQLite recreates full table |
59
+ | Remove `?` (make field required) | 🔴 Breaking | Any NULL rows will fail migration |
60
+ | Rename field (no `@map`) | 🔴 Breaking | Prisma sees as drop + add → data loss |
61
+ | Change field type | 🔴 Breaking | Review generated SQL; SQLite requires full table recreation |
62
+ | Remove field | 🔴 Breaking | Data permanently lost — check all code usages first |
63
+ | Remove model | 🔴 Breaking | Destructive — check all code and FK references |
64
+ | Remove enum value | 🔴 Breaking | PostgreSQL: cannot drop a value that rows reference |
65
+
66
+ ### Also Detect
67
+
68
+ - ⚠️ **Migration drift**: Run `prisma migrate status` — flags migrations applied to DB but not in `prisma/migrations/`
69
+ - ⚠️ **Missing baseline migration**: Database exists but no `_prisma_migrations` table — needs `prisma migrate baseline`
70
+ - ⚠️ **Shadow DB not configured**: MySQL and SQL Server require a shadow database URL for `prisma migrate dev`
71
+ - ⚠️ **`migration_lock.toml` provider mismatch**: Lock file provider differs from schema datasource provider
72
+
73
+ ---
74
+
75
+ ## Phase 3: Present Risk Report
76
+
77
+ ### Report Format
78
+
79
+ ```
80
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
81
+ /schema-migrate — Schema Change Analysis
82
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
83
+
84
+ ORM: Prisma
85
+ Dialect: PostgreSQL
86
+ Workflow: prisma migrate dev (versioned migrations)
87
+ Schema: prisma/schema.prisma
88
+ Migrations dir: prisma/migrations/
89
+ Last migration: 20250228120000_add_profiles_table
90
+
91
+ Status:
92
+ 📁 Migration history: IN SYNC
93
+ 📝 Schema file: MODIFIED (uncommitted)
94
+ ✅ Lock file: provider matches
95
+
96
+ Detected Changes (since last commit):
97
+ ──────────────────────────────────────────
98
+
99
+ 🟢 SAFE (2 changes)
100
+ • Job model: Added optional field `appliedNotes` (String?)
101
+ • Job model: Added @@index on [status]
102
+
103
+ 🟡 CAREFUL (1 change — review before migrating)
104
+ • Profile model: Added required field `email String @default("")`
105
+ ↳ All existing rows will get empty string value
106
+ ↳ Is "" an acceptable initial value? Consider String? first.
107
+
108
+ 🔴 BREAKING (1 change — stop and read migration plan)
109
+ • Job model: Field `jobType` changed String → Int
110
+ ↳ Prisma will generate SQL to ALTER column type
111
+ ↳ Review generated migration SQL before applying
112
+ ↳ SQLite: full table recreation required
113
+
114
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
115
+ ```
116
+
117
+ ### Dialect-Specific Notes
118
+
119
+ #### PostgreSQL
120
+ - ✅ Full ALTER TABLE support — most changes are online
121
+ - ⚠️ `ALTER TYPE ADD VALUE` for enums cannot be rolled back
122
+ - ⚠️ Long-running ALTER may briefly lock large tables
123
+ - ✅ Prisma generates versioned migration SQL — review before applying
124
+
125
+ #### MySQL
126
+ - ⚠️ Requires shadow database URL for `prisma migrate dev`
127
+ - ⚠️ Rename field: check if `doctrine/dbal` equivalent needed (Prisma handles natively)
128
+ - ✅ Online DDL available for MySQL 8.0+
129
+ - ⚠️ Type casts may fail silently — verify generated SQL
130
+
131
+ #### SQLite
132
+ - 🚨 Prisma recreates entire table for most structural changes (ALTER limitation)
133
+ - ✅ Table recreation is safe but slower for large tables
134
+ - ⚠️ Foreign key changes always trigger table recreation
135
+
136
+ #### SQL Server
137
+ - ⚠️ Requires shadow database configured in `.env` as `SHADOW_DATABASE_URL`
138
+ - ⚠️ Some ALTER operations not supported — review generated SQL
139
+
140
+ ---
141
+
142
+ ## Phase 4: Per-Change Migration Plans
143
+
144
+ ### Scenario: Rename Field (No `@map`)
145
+
146
+ ```
147
+ Problem: Without @map, Prisma sees rename as drop old + add new → data loss!
148
+
149
+ Safe path via @map (zero data loss):
150
+ 1. Add @map annotation — keep DB column name, rename Prisma field only:
151
+ oldName String @map("old_name") → newName String @map("old_name")
152
+ 2. prisma migrate dev --name rename_field_to_new_name
153
+ 3. Review generated SQL — should only be a comment change (no ALTER)
154
+ 4. Apply migration
155
+ 5. Update all code references from oldName → newName
156
+ 6. Later (optional): rename underlying column with separate migration
157
+
158
+ Safe path via add/backfill/drop (if column must also be renamed in DB):
159
+ 1. Add new field: newName String? (nullable)
160
+ 2. prisma migrate dev --name add_new_name
161
+ 3. Backfill: UPDATE table SET new_name = old_name
162
+ 4. Remove old field from schema, make newName required
163
+ 5. prisma migrate dev --name remove_old_name
164
+ 6. Data preserved ✅
165
+ ```
166
+
167
+ ---
168
+
169
+ ### Scenario: Remove `?` (Make Required)
170
+
171
+ ```
172
+ Problem: Any NULL values in existing rows will cause migration to fail.
173
+
174
+ Pre-check SQL:
175
+ SELECT COUNT(*) FROM [table] WHERE [field] IS NULL;
176
+
177
+ If NULLs found:
178
+ Option A (backfill then migrate):
179
+ 1. Run UPDATE [table] SET [field] = [value] WHERE [field] IS NULL
180
+ 2. Remove ? from schema
181
+ 3. prisma migrate dev
182
+
183
+ Option B (add default then migrate):
184
+ 1. Add default: field String @default("value")
185
+ 2. prisma migrate dev → existing NULLs get default
186
+ 3. Optionally remove default later if not needed
187
+
188
+ Option C (keep optional):
189
+ - Reconsider requirement — keep field nullable if data is legitimately absent
190
+ ```
191
+
192
+ ---
193
+
194
+ ### Scenario: Add Relation FK to Existing Field
195
+
196
+ ```
197
+ Pre-check for orphan rows:
198
+ SELECT COUNT(*) FROM [table]
199
+ WHERE [foreignKey] NOT IN (SELECT id FROM [referencedTable]);
200
+
201
+ If orphans found:
202
+ Option A: Delete orphan rows (data loss — review first)
203
+ Option B: Set orphans to NULL (if FK field is nullable)
204
+ Option C: Update orphans to valid reference
205
+
206
+ After orphans cleaned:
207
+ Add @relation to schema
208
+ prisma migrate dev
209
+
210
+ SQLite note:
211
+ - Prisma will recreate the full table (SQLite limitation)
212
+ - All data preserved, but takes longer for large tables
213
+ - Test on a copy of data if concerned about recreation time
214
+ ```
215
+
216
+ ---
217
+
218
+ ### Scenario: Change Field Type
219
+
220
+ ```
221
+ Pre-review generated migration SQL:
222
+ prisma migrate dev --create-only --name change_type
223
+ # Review prisma/migrations/[timestamp]_change_type/migration.sql
224
+
225
+ PostgreSQL:
226
+ - Review if CAST is present in generated SQL
227
+ - If no CAST: Prisma expects types to be compatible
228
+ - If data incompatible: manually add USING clause in migration SQL
229
+ - Apply: prisma migrate dev (after editing migration.sql)
230
+
231
+ SQLite:
232
+ - Prisma will generate a table recreation script (shadow table approach)
233
+ - Verify generated SQL includes data copy: INSERT INTO new SELECT ... FROM old
234
+ - Apply: prisma migrate dev
235
+
236
+ MySQL:
237
+ - Review ALTER TABLE ... MODIFY COLUMN ... in migration.sql
238
+ - Check for silent truncation (e.g., TEXT → VARCHAR(100))
239
+ ```
240
+
241
+ ---
242
+
243
+ ### Scenario: Add Enum Value on PostgreSQL
244
+
245
+ ```
246
+ ⚠️ PostgreSQL: ALTER TYPE ADD VALUE cannot be run inside a transaction.
247
+ Prisma wraps migrations in transactions by default — this will FAIL.
248
+
249
+ Workaround:
250
+ 1. Create migration manually:
251
+ prisma migrate dev --create-only --name add_enum_value
252
+ 2. Edit migration.sql — add at the TOP:
253
+ -- prisma-client-js
254
+ -- This migration is run outside of a transaction because of ALTER TYPE ADD VALUE
255
+ 3. In migration.sql, use: ALTER TYPE "MyEnum" ADD VALUE 'NEW_VALUE';
256
+ 4. Mark migration as non-transactional in schema.prisma:
257
+ generator client {
258
+ provider = "prisma-client-js"
259
+ previewFeatures = ["postgresqlExtensions"]
260
+ }
261
+ 5. Apply: prisma migrate dev
262
+ 6. Note: This enum value addition is IRREVERSIBLE — cannot be removed later
263
+ ```
264
+
265
+ ---
266
+
267
+ ## Phase 5: Command Recommendations
268
+
269
+ ### Core Commands
270
+
271
+ ```bash
272
+ # Check migration status
273
+ npx prisma migrate status
274
+
275
+ # Generate and apply migration (development)
276
+ npx prisma migrate dev --name [describe_change]
277
+
278
+ # Create migration SQL without applying (for review)
279
+ npx prisma migrate dev --create-only --name [describe_change]
280
+
281
+ # Apply pending migrations (production — no interactive prompts)
282
+ npx prisma migrate deploy
283
+
284
+ # Reset database and reapply all migrations (dev only — DESTRUCTIVE)
285
+ npx prisma migrate reset
286
+
287
+ # Push schema changes without migration files (prototyping/SQLite dev)
288
+ npx prisma db push
289
+
290
+ # Regenerate Prisma Client after schema changes
291
+ npx prisma generate
292
+
293
+ # Pull current DB schema into schema.prisma (for baseline)
294
+ npx prisma db pull
295
+
296
+ # Baseline an existing database (first-time migration setup)
297
+ npx prisma migrate resolve --applied [migration_name]
298
+ ```
299
+
300
+ ### Workflow: Development (with migration files)
301
+
302
+ ```bash
303
+ # 1. Review changes
304
+ /schema-migrate
305
+
306
+ # 2. Generate migration (review SQL before applying)
307
+ npx prisma migrate dev --create-only --name [describe_change]
308
+ cat prisma/migrations/[latest]/migration.sql
309
+
310
+ # 3. If SQL looks correct, apply
311
+ npx prisma migrate dev --name [describe_change]
312
+
313
+ # 4. Commit
314
+ git add prisma/schema.prisma prisma/migrations/
315
+ git commit -m "feat(schema): [describe change]"
316
+ ```
317
+
318
+ ### Workflow: Production Deployment
319
+
320
+ ```bash
321
+ # 1. Migrations committed and reviewed
322
+ # 2. Deploy application
323
+ npx prisma migrate deploy # applies all pending migrations
324
+ npx prisma generate # regenerate client if needed
325
+ ```
326
+
327
+ ### Supabase + Prisma Workflow
328
+
329
+ ```bash
330
+ # 1. Generate migration
331
+ npx prisma migrate dev --create-only --name [describe_change]
332
+
333
+ # 2. Review generated SQL
334
+ cat prisma/migrations/[timestamp]_[describe_change]/migration.sql
335
+
336
+ # 3. Create Supabase migration
337
+ supabase migration new [describe_change]
338
+ # Copy SQL from prisma migration into supabase/migrations/[timestamp]_[describe_change].sql
339
+
340
+ # 4. Test locally
341
+ supabase db reset
342
+
343
+ # 5. Apply prisma migration (links Prisma state)
344
+ npx prisma migrate dev
345
+
346
+ # 6. Push to staging
347
+ supabase link --project-ref=[staging-ref]
348
+ supabase push --linked
349
+
350
+ # 7. Promote to production
351
+ supabase link --project-ref=[prod-ref]
352
+ supabase push --linked
353
+
354
+ # 8. Commit
355
+ git add prisma/ supabase/migrations/
356
+ git commit -m "feat(schema): [describe change]"
357
+ ```
@@ -0,0 +1,351 @@
1
+ # /schema-migrate — Rails + ActiveRecord Analysis (Phases 2–5)
2
+
3
+ > This file is loaded by `SKILL.md` when Rails + ActiveRecord 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
+ ### Files to Scan (read in parallel)
11
+
12
+ 1. **`Gemfile`** — dependencies
13
+ - Confirm `rails` or `activerecord` gem version
14
+ - Check for `strong_migrations` gem (adds safety checks)
15
+ - Check for database adapter gem: `pg`, `mysql2`, `sqlite3`
16
+
17
+ 2. **`Gemfile.lock`** — locked gem versions for exact version info
18
+
19
+ 3. **`config/database.yml`** — database configuration
20
+ - Extract `adapter:` under `development:` entry
21
+ - Values: `postgresql` | `mysql2` | `sqlite3`
22
+
23
+ 4. **`db/schema.rb`** or **`db/structure.sql`** — current schema state
24
+ - Track: table definitions, column types, indexes, constraints
25
+ - Note: `structure.sql` used when `config.active_record.schema_format = :sql`
26
+
27
+ 5. **`db/migrate/`** — migration history
28
+ - List all migration files sorted by timestamp
29
+ - Read the most recent migration file(s)
30
+ - Compare latest timestamp with `db/schema.rb` version line
31
+
32
+ 6. **`git diff HEAD -- db/migrate/ db/schema.rb`** — uncommitted migration changes
33
+
34
+ 7. **`git log --oneline -5 -- db/migrate/ db/schema.rb`** — recent migration commit history
35
+
36
+ ### Dialect Detection
37
+
38
+ From `config/database.yml` → `adapter:` under `development:`:
39
+
40
+ | Adapter | Dialect |
41
+ |---------|---------|
42
+ | `postgresql` | PostgreSQL |
43
+ | `mysql2` | MySQL |
44
+ | `sqlite3` | SQLite |
45
+ | `trilogy` | MySQL (alternative adapter) |
46
+
47
+ ### Risk Matrix
48
+
49
+ | Change | Risk | Notes |
50
+ |--------|------|-------|
51
+ | `create_table` | 🟢 Safe | Additive — no existing data affected |
52
+ | `add_column` (nullable, no default) | 🟢 Safe | Standard additive change |
53
+ | `add_index` | 🟢 Safe | Non-blocking (unless large table — see PostgreSQL note) |
54
+ | `add_column` with default | 🟡 Careful | Existing rows get default — verify acceptability |
55
+ | `add_index unique: true` | 🟡 Careful | Fails if duplicate values exist |
56
+ | `add_reference` / `add_foreign_key` | 🟡 Careful | Orphan row check; MySQL online DDL |
57
+ | `add_column` NOT NULL without default | 🔴 Breaking | Existing rows need a value — migration fails |
58
+ | `change_column` (type change) | 🔴 Breaking | SQLite: no ALTER; PostgreSQL: CAST required |
59
+ | `remove_column` | 🔴 Breaking | Check model usage — `$casts`, queries, serializers |
60
+ | `drop_table` | 🔴 Breaking | Destructive — check all model and FK references |
61
+ | `rename_column` | 🟡 Careful | Safe in modern Rails; update model attribute too |
62
+ | `rename_table` | 🟡 Careful | Update `self.table_name` in model + all references |
63
+
64
+ ### Also Detect
65
+
66
+ - ⚠️ **`schema.rb` version drift**: `ActiveRecord::Schema[X.Y].define(version: TIMESTAMP)` version doesn't match latest migration timestamp
67
+ - ⚠️ **Missing `down` on `def up` migrations**: Old-style `def up` / `def down` with empty `down` — rollback does nothing
68
+ - ⚠️ **MySQL strict mode warnings**: Adding column with large default on a table with many rows → metadata lock risk
69
+ - ⚠️ **`strong_migrations` gem detected**: Certain operations require `safety_assured { }` wrapper — flag which operations need it
70
+ - ⚠️ **PostgreSQL large table index**: Adding `add_index` without `algorithm: :concurrently` will lock the table
71
+
72
+ ---
73
+
74
+ ## Phase 3: Present Risk Report
75
+
76
+ ### Report Format
77
+
78
+ ```
79
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
80
+ /schema-migrate — Schema Change Analysis
81
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
82
+
83
+ ORM: Rails + ActiveRecord
84
+ Dialect: PostgreSQL
85
+ Workflow: rails db:migrate (versioned migrations)
86
+ Migrations dir: db/migrate/
87
+ Schema file: db/schema.rb (version: 20250228120000)
88
+
89
+ Status:
90
+ 📁 Migration history: IN SYNC with schema.rb
91
+ 📝 Pending: 1 uncommitted migration
92
+ 🔧 strong_migrations: NOT DETECTED
93
+
94
+ Detected Changes (since last commit):
95
+ ──────────────────────────────────────────
96
+
97
+ 🟢 SAFE (1 change)
98
+ • jobs: add_column :applied_notes, :text (nullable)
99
+
100
+ 🟡 CAREFUL (1 change — review before migrating)
101
+ • profile: add_column :email, :string, default: '', null: false
102
+ ↳ All existing rows will get empty string value
103
+ ↳ Is '' an acceptable default? Consider nullable: true first.
104
+
105
+ 🔴 BREAKING (1 change — stop and read migration plan)
106
+ • jobs: change_column :job_type, :string → :integer
107
+ ↳ PostgreSQL: requires USING cast
108
+ ↳ SQLite: no ALTER support — table recreation required
109
+ ↳ Review migration SQL before applying
110
+
111
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
112
+ ```
113
+
114
+ ### Dialect-Specific Notes
115
+
116
+ #### PostgreSQL
117
+ - ✅ Full ALTER TABLE support — most changes are online
118
+ - ⚠️ `change_column` for type changes may need explicit USING cast
119
+ - ⚠️ Adding `add_index` on large tables without `algorithm: :concurrently` will lock table
120
+ - ✅ `rename_column` is safe and fast (metadata-only change)
121
+
122
+ #### MySQL
123
+ - ⚠️ Online DDL available for MySQL 8.0+ (ALGORITHM=INPLACE for many operations)
124
+ - ⚠️ Adding column with large default on large tables may hold metadata lock
125
+ - ⚠️ MySQL strict mode: invalid data rejected rather than silently truncated
126
+
127
+ #### SQLite
128
+ - 🚨 No `change_column` support — requires table recreation workaround
129
+ - 🚨 No `rename_column` support in older SQLite (< 3.25) — Rails handles via `pragma_columns`
130
+ - ✅ Rails handles SQLite table recreation via `change_table` where possible
131
+
132
+ ---
133
+
134
+ ## Phase 4: Per-Change Migration Plans
135
+
136
+ ### Scenario: `rename_column` — Zero-Downtime
137
+
138
+ ```
139
+ Modern Rails (6.1+): rename_column is safe — atomic metadata rename.
140
+
141
+ Zero-downtime pattern for live systems:
142
+ Step 1: Add alias (ignore_columns + new attribute)
143
+ - app/models/user.rb: alias_attribute :new_name, :old_name
144
+ - Deploy code that uses new_name (reads from old_name via alias)
145
+
146
+ Step 2: Rename in migration
147
+ rename_column :users, :old_name, :new_name
148
+
149
+ Step 3: Remove alias after rename is stable
150
+ - Remove alias_attribute line
151
+ - Update any remaining references to old_name
152
+
153
+ After rename:
154
+ ✅ Check model: attr_accessor, validates, scopes using old name
155
+ ✅ Check serializers, API resources
156
+ ✅ Check named scopes: where(old_name: ...)
157
+ ✅ Run full test suite
158
+ ```
159
+
160
+ ---
161
+
162
+ ### Scenario: `add_column` NOT NULL Without Default
163
+
164
+ ```
165
+ Problem: Existing rows have no value → migration fails with NOT NULL constraint.
166
+
167
+ Safe path (nullable-first):
168
+ Step 1: Add nullable column
169
+ add_column :users, :email, :string # nullable by default
170
+
171
+ Step 2: Backfill data
172
+ User.in_batches.update_all(email: '')
173
+ # Or in migration: execute("UPDATE users SET email = '' WHERE email IS NULL")
174
+
175
+ Step 3: Add NOT NULL constraint
176
+ change_column_null :users, :email, false
177
+
178
+ Alternative (with default — single migration):
179
+ add_column :users, :email, :string, default: '', null: false
180
+ ⚠️ All existing rows get '' — verify this is acceptable
181
+
182
+ With strong_migrations gem:
183
+ safety_assured do
184
+ add_column :users, :email, :string, null: false, default: ''
185
+ end
186
+ ```
187
+
188
+ ---
189
+
190
+ ### Scenario: `change_column` Type Change
191
+
192
+ ```
193
+ PostgreSQL — Review CAST:
194
+ # Rails generates: ALTER TABLE jobs ALTER COLUMN job_type TYPE integer
195
+ # May fail if data contains non-integer strings
196
+
197
+ # Add explicit CAST in migration:
198
+ execute <<~SQL
199
+ ALTER TABLE jobs
200
+ ALTER COLUMN job_type TYPE integer
201
+ USING job_type::integer
202
+ SQL
203
+
204
+ # Pre-check for non-castable data:
205
+ # SELECT job_type FROM jobs WHERE job_type !~ '^[0-9]+$';
206
+
207
+ SQLite — No ALTER support, use workaround:
208
+ # Create new column, copy data, drop old, rename
209
+ def up
210
+ add_column :jobs, :job_type_int, :integer
211
+ execute("UPDATE jobs SET job_type_int = CAST(job_type AS INTEGER)")
212
+ remove_column :jobs, :job_type
213
+ rename_column :jobs, :job_type_int, :job_type
214
+ end
215
+
216
+ def down
217
+ add_column :jobs, :job_type_str, :string
218
+ execute("UPDATE jobs SET job_type_str = CAST(job_type AS TEXT)")
219
+ remove_column :jobs, :job_type
220
+ rename_column :jobs, :job_type_str, :job_type
221
+ end
222
+ ```
223
+
224
+ ---
225
+
226
+ ### Scenario: Large Table Index on PostgreSQL
227
+
228
+ ```
229
+ Problem: add_index on a large PostgreSQL table locks the table for writes
230
+ during index creation.
231
+
232
+ Fix: Use concurrent index creation
233
+
234
+ def change
235
+ disable_ddl_transaction! # ← required for concurrent indexes
236
+ add_index :jobs, :status, algorithm: :concurrently
237
+ end
238
+
239
+ Rules:
240
+ ✅ disable_ddl_transaction! must be the first line in change/up
241
+ ✅ algorithm: :concurrently only works outside of transactions
242
+ ✅ Cannot be combined with other migration operations in same file
243
+ ⚠️ May leave invalid index if cancelled — check with \di in psql
244
+
245
+ Cleanup invalid index:
246
+ REINDEX INDEX CONCURRENTLY jobs_status_idx;
247
+ # or
248
+ DROP INDEX CONCURRENTLY jobs_status_idx;
249
+ ```
250
+
251
+ ---
252
+
253
+ ### Scenario: `strong_migrations` Gem Detected
254
+
255
+ ```
256
+ If strong_migrations gem is present in Gemfile:
257
+
258
+ Unsafe operations are blocked by default. To bypass:
259
+ safety_assured do
260
+ # your unsafe operation here
261
+ end
262
+
263
+ Operations that require safety_assured:
264
+ - add_column with NOT NULL and no default (use nullable-first instead)
265
+ - change_column (use separate steps)
266
+ - add_index without algorithm: :concurrently (on large tables)
267
+ - rename_column / rename_table (use alias pattern)
268
+ - remove_column (ensure ActiveRecord ignores column before removing)
269
+
270
+ Best practice: Fix the underlying issue rather than using safety_assured.
271
+ strong_migrations provides the correct safe pattern in its error message.
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Phase 5: Command Recommendations
277
+
278
+ ### Core Commands
279
+
280
+ ```bash
281
+ # Check migration status
282
+ rails db:migrate:status
283
+
284
+ # Run pending migrations
285
+ rails db:migrate
286
+
287
+ # Rollback last migration
288
+ rails db:rollback STEP=1
289
+
290
+ # Rollback to specific version
291
+ rails db:migrate:down VERSION=[timestamp]
292
+
293
+ # Check current schema version
294
+ rails db:version
295
+
296
+ # Generate new migration
297
+ rails generate migration [MigrationName] [optional_columns]
298
+ rails generate migration AddEmailToUsers email:string:uniq
299
+ rails generate migration CreateJobs title:string status:string
300
+
301
+ # Reset database (dev only — DESTRUCTIVE)
302
+ rails db:reset # drop + create + schema + seed
303
+ rails db:drop && rails db:create && rails db:migrate && rails db:seed
304
+ ```
305
+
306
+ ### Workflow: Development
307
+
308
+ ```bash
309
+ # 1. Review pending migrations
310
+ /schema-migrate
311
+
312
+ # 2. Preview migration (check db/schema.rb after migrate on test db)
313
+ RAILS_ENV=test rails db:migrate
314
+
315
+ # 3. Apply to development
316
+ rails db:migrate
317
+
318
+ # 4. Verify status
319
+ rails db:migrate:status
320
+
321
+ # 5. Run tests
322
+ bundle exec rspec # or: rails test
323
+
324
+ # 6. Commit
325
+ git add db/migrate/ db/schema.rb
326
+ git commit -m "feat(schema): [describe change]"
327
+ ```
328
+
329
+ ### Workflow: PostgreSQL — Concurrent Indexes
330
+
331
+ ```bash
332
+ # Migration file must have disable_ddl_transaction! + algorithm: :concurrently
333
+ # See Scenario: Large Table Index above
334
+
335
+ rails db:migrate
336
+ # Monitor with: SELECT * FROM pg_stat_progress_create_index;
337
+ ```
338
+
339
+ ### Workflow: Production Deployment
340
+
341
+ ```bash
342
+ # 1. Migrations committed and reviewed
343
+ # 2. Check status before deploying
344
+ rails db:migrate:status
345
+
346
+ # 3. Apply (in deployment script or Capistrano task)
347
+ rails db:migrate RAILS_ENV=production
348
+
349
+ # 4. Verify
350
+ rails db:version RAILS_ENV=production
351
+ ```