@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.
- package/README.md +321 -0
- package/bin/shipkit.js +146 -0
- package/commands/sk/brainstorm.md +63 -0
- package/commands/sk/branch.md +35 -0
- package/commands/sk/config.md +96 -0
- package/commands/sk/execute-plan.md +85 -0
- package/commands/sk/features.md +238 -0
- package/commands/sk/finish-feature.md +154 -0
- package/commands/sk/help.md +103 -0
- package/commands/sk/hotfix.md +61 -0
- package/commands/sk/plan.md +30 -0
- package/commands/sk/release.md +72 -0
- package/commands/sk/security-check.md +188 -0
- package/commands/sk/set-profile.md +71 -0
- package/commands/sk/status.md +25 -0
- package/commands/sk/update-task.md +35 -0
- package/commands/sk/write-plan.md +72 -0
- package/package.json +23 -0
- package/skills/sk:accessibility/LICENSE.txt +177 -0
- package/skills/sk:accessibility/SKILL.md +150 -0
- package/skills/sk:api-design/LICENSE.txt +177 -0
- package/skills/sk:api-design/SKILL.md +158 -0
- package/skills/sk:brainstorming/SKILL.md +124 -0
- package/skills/sk:debug/SKILL.md +252 -0
- package/skills/sk:debug/debug_conductor.py +177 -0
- package/skills/sk:debug/lib/__init__.py +1 -0
- package/skills/sk:debug/lib/bug_gatherer.py +55 -0
- package/skills/sk:debug/lib/context_reader.py +139 -0
- package/skills/sk:debug/lib/findings_writer.py +76 -0
- package/skills/sk:debug/lib/lessons_writer.py +165 -0
- package/skills/sk:debug/lib/step_runner.py +326 -0
- package/skills/sk:features/SKILL.md +238 -0
- package/skills/sk:frontend-design/LICENSE.txt +177 -0
- package/skills/sk:frontend-design/SKILL.md +191 -0
- package/skills/sk:laravel-init/SKILL.md +37 -0
- package/skills/sk:laravel-new/SKILL.md +68 -0
- package/skills/sk:lint/SKILL.md +113 -0
- package/skills/sk:perf/LICENSE.txt +177 -0
- package/skills/sk:perf/SKILL.md +188 -0
- package/skills/sk:release/SKILL.md +113 -0
- package/skills/sk:release/references/android-checklist.md +269 -0
- package/skills/sk:release/references/ios-checklist.md +339 -0
- package/skills/sk:release/release.sh +378 -0
- package/skills/sk:review/SKILL.md +346 -0
- package/skills/sk:review/references/security-checklist.md +223 -0
- package/skills/sk:schema-migrate/SKILL.md +125 -0
- package/skills/sk:schema-migrate/orms/drizzle.md +546 -0
- package/skills/sk:schema-migrate/orms/laravel.md +367 -0
- package/skills/sk:schema-migrate/orms/prisma.md +357 -0
- package/skills/sk:schema-migrate/orms/rails.md +351 -0
- package/skills/sk:schema-migrate/orms/sqlalchemy.md +385 -0
- package/skills/sk:schema-migrate/references/detection.md +110 -0
- package/skills/sk:setup-claude/SKILL.md +365 -0
- package/skills/sk:setup-claude/references/detection.md +6 -0
- package/skills/sk:setup-claude/references/templates.md +11 -0
- package/skills/sk:setup-claude/scripts/apply_setup_claude.py +443 -0
- package/skills/sk:setup-claude/scripts/detect_arch_changes.py +437 -0
- package/skills/sk:setup-claude/templates/.claude/docs/arch-changelog-guide.md.template +6 -0
- package/skills/sk:setup-claude/templates/.claude/docs/changelog-guide.md.template +12 -0
- package/skills/sk:setup-claude/templates/CHANGELOG.md.template +21 -0
- package/skills/sk:setup-claude/templates/CLAUDE.md.template +299 -0
- package/skills/sk:setup-claude/templates/arch-changelog-guide.md.template +3 -0
- package/skills/sk:setup-claude/templates/changelog-guide.md.template +3 -0
- package/skills/sk:setup-claude/templates/commands/brainstorm.md.template +74 -0
- package/skills/sk:setup-claude/templates/commands/execute-plan.md.template +57 -0
- package/skills/sk:setup-claude/templates/commands/features.md.template +238 -0
- package/skills/sk:setup-claude/templates/commands/finish-feature.md.template +155 -0
- package/skills/sk:setup-claude/templates/commands/plan.md.template +30 -0
- package/skills/sk:setup-claude/templates/commands/re-setup.md.template +38 -0
- package/skills/sk:setup-claude/templates/commands/release.md.template +74 -0
- package/skills/sk:setup-claude/templates/commands/security-check.md.template +172 -0
- package/skills/sk:setup-claude/templates/commands/status.md.template +17 -0
- package/skills/sk:setup-claude/templates/commands/write-plan.md.template +34 -0
- package/skills/sk:setup-claude/templates/finish-feature.md.template +3 -0
- package/skills/sk:setup-claude/templates/plan.md.template +3 -0
- package/skills/sk:setup-claude/templates/status.md.template +3 -0
- package/skills/sk:setup-claude/templates/tasks/findings.md.template +19 -0
- package/skills/sk:setup-claude/templates/tasks/lessons.md.template +26 -0
- package/skills/sk:setup-claude/templates/tasks/progress.md.template +20 -0
- package/skills/sk:setup-claude/templates/tasks/security-findings.md.template +5 -0
- package/skills/sk:setup-claude/templates/tasks/todo.md.template +26 -0
- package/skills/sk:setup-claude/templates/tasks/workflow-status.md.template +31 -0
- package/skills/sk:setup-claude/templates/tasks-findings.md.template +3 -0
- package/skills/sk:setup-claude/templates/tasks-lessons.md.template +3 -0
- package/skills/sk:setup-claude/templates/tasks-progress.md.template +3 -0
- package/skills/sk:setup-claude/templates/tasks-todo.md.template +3 -0
- package/skills/sk:setup-claude/tests/test_apply_setup_claude.py +193 -0
- package/skills/sk:setup-optimizer/SKILL.md +184 -0
- package/skills/sk:setup-optimizer/lib/__init__.py +24 -0
- package/skills/sk:setup-optimizer/lib/detect.py +205 -0
- package/skills/sk:setup-optimizer/lib/discover.py +221 -0
- package/skills/sk:setup-optimizer/lib/enrich.py +163 -0
- package/skills/sk:setup-optimizer/lib/merge.py +277 -0
- package/skills/sk:setup-optimizer/lib/sidecar.py +129 -0
- package/skills/sk:setup-optimizer/optimize_claude.py +174 -0
- package/skills/sk:setup-optimizer/templates/CLAUDE.md.template +105 -0
- package/skills/sk:skill-creator/LICENSE.txt +202 -0
- package/skills/sk:skill-creator/SKILL.md +479 -0
- package/skills/sk:skill-creator/agents/analyzer.md +274 -0
- package/skills/sk:skill-creator/agents/comparator.md +202 -0
- package/skills/sk:skill-creator/agents/grader.md +223 -0
- package/skills/sk:skill-creator/assets/eval_review.html +146 -0
- package/skills/sk:skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/sk:skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/sk:skill-creator/references/schemas.md +430 -0
- package/skills/sk:skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/sk:skill-creator/scripts/generate_report.py +326 -0
- package/skills/sk:skill-creator/scripts/improve_description.py +248 -0
- package/skills/sk:skill-creator/scripts/package_skill.py +136 -0
- package/skills/sk:skill-creator/scripts/quick_validate.py +103 -0
- package/skills/sk:skill-creator/scripts/run_eval.py +310 -0
- package/skills/sk:skill-creator/scripts/run_loop.py +332 -0
- package/skills/sk:skill-creator/scripts/utils.py +47 -0
- package/skills/sk:smart-commit/SKILL.md +175 -0
- package/skills/sk:test/SKILL.md +171 -0
- package/skills/sk:write-tests/SKILL.md +195 -0
- 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
|
+
```
|