@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,385 @@
|
|
|
1
|
+
# /schema-migrate — SQLAlchemy + Alembic Analysis (Phases 2–5)
|
|
2
|
+
|
|
3
|
+
> This file is loaded by `SKILL.md` when SQLAlchemy + Alembic 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. **`alembic.ini`** — Alembic configuration
|
|
13
|
+
- Extract `sqlalchemy.url` → determine dialect
|
|
14
|
+
- Extract `script_location` (default: `alembic/`)
|
|
15
|
+
|
|
16
|
+
2. **`alembic/env.py`** — environment configuration
|
|
17
|
+
- Identify `target_metadata` import path → find model files
|
|
18
|
+
- Check `render_as_batch` setting (required for SQLite)
|
|
19
|
+
- Check if `include_schemas` or `compare_type` options are set
|
|
20
|
+
|
|
21
|
+
3. **`alembic/versions/`** — migration history
|
|
22
|
+
- List all revision files sorted by `down_revision` chain
|
|
23
|
+
- Read the most recent revision file(s)
|
|
24
|
+
- Check for multiple heads (`# head revision` comments or `alembic heads` output)
|
|
25
|
+
|
|
26
|
+
4. **Model files** (path from `env.py` import → `target_metadata`)
|
|
27
|
+
- Parse `Column()` definitions, types, constraints, indexes, FKs
|
|
28
|
+
- Track: table names, column types, nullable flags, defaults
|
|
29
|
+
|
|
30
|
+
5. **`.env`** — environment config
|
|
31
|
+
- Fallback for `DATABASE_URL` if `alembic.ini` uses environment variable interpolation
|
|
32
|
+
|
|
33
|
+
6. **`git diff HEAD -- [models_path]`** — uncommitted model changes
|
|
34
|
+
|
|
35
|
+
7. **`git log --oneline -5 -- [models_path]`** — recent model commit history
|
|
36
|
+
|
|
37
|
+
### Dialect Detection
|
|
38
|
+
|
|
39
|
+
From `sqlalchemy.url` in `alembic.ini`:
|
|
40
|
+
|
|
41
|
+
| URL Prefix | Dialect |
|
|
42
|
+
|------------|---------|
|
|
43
|
+
| `postgresql://` or `postgresql+psycopg2://` | PostgreSQL |
|
|
44
|
+
| `mysql://` or `mysql+pymysql://` | MySQL |
|
|
45
|
+
| `sqlite:///` | SQLite |
|
|
46
|
+
| `mssql+pyodbc://` | SQL Server |
|
|
47
|
+
|
|
48
|
+
### Risk Matrix
|
|
49
|
+
|
|
50
|
+
| Change | Risk | Notes |
|
|
51
|
+
|--------|------|-------|
|
|
52
|
+
| Add new table (`op.create_table`) | 🟢 Safe | Additive — no existing data affected |
|
|
53
|
+
| Add nullable column | 🟢 Safe | `nullable=True` or `server_default` present |
|
|
54
|
+
| Add index (`op.create_index`) | 🟢 Safe | Non-blocking for most engines |
|
|
55
|
+
| Add column with `server_default` | 🟡 Careful | Existing rows get default — verify acceptability |
|
|
56
|
+
| Add unique constraint | 🟡 Careful | Fails if duplicate values exist in current data |
|
|
57
|
+
| Add FK (`op.create_foreign_key`) | 🟡 Careful | Orphan row check required; SQLite: limited support |
|
|
58
|
+
| Add NOT NULL column without `server_default` | 🔴 Breaking | Fails on any non-empty table |
|
|
59
|
+
| Change column type (`op.alter_column`) | 🔴 Breaking | Use `postgresql_using` for casts; SQLite: table recreation |
|
|
60
|
+
| Drop column (`op.drop_column`) | 🔴 Breaking | SQLite < 3.35: not supported natively |
|
|
61
|
+
| Drop table (`op.drop_table`) | 🔴 Breaking | Destructive — check all references |
|
|
62
|
+
| Rename column | 🔴 Breaking | Autogenerate blindspot: detected as drop+add, not rename |
|
|
63
|
+
| Change nullable (`nullable=False`) | 🟡 Careful | Pre-check for NULL values before applying |
|
|
64
|
+
|
|
65
|
+
### Also Detect
|
|
66
|
+
|
|
67
|
+
- ⚠️ **Broken revision chain**: A revision file has a `down_revision` that doesn't exist → migration chain broken
|
|
68
|
+
- ⚠️ **Multiple heads**: Two or more revisions with no `down_revision` pointing to them → `alembic heads` will show multiple
|
|
69
|
+
- ⚠️ **`render_as_batch` missing**: SQLite env.py without `render_as_batch=True` → ALTER operations will fail
|
|
70
|
+
- ⚠️ **Autogenerate blindspots**: Alembic autogenerate does NOT detect:
|
|
71
|
+
- Column server defaults (only `default=` in Python, not DB-level)
|
|
72
|
+
- Check constraints (`CheckConstraint`)
|
|
73
|
+
- Collation changes
|
|
74
|
+
- Sequence changes
|
|
75
|
+
- Column renames (shows as drop+add)
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Phase 3: Present Risk Report
|
|
80
|
+
|
|
81
|
+
### Report Format
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
85
|
+
/schema-migrate — Schema Change Analysis
|
|
86
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
87
|
+
|
|
88
|
+
ORM: SQLAlchemy + Alembic
|
|
89
|
+
Dialect: PostgreSQL
|
|
90
|
+
Workflow: alembic upgrade head (versioned migrations)
|
|
91
|
+
Script location: alembic/
|
|
92
|
+
Config: alembic.ini
|
|
93
|
+
Current head: a3f2b1c9d8e4
|
|
94
|
+
|
|
95
|
+
Status:
|
|
96
|
+
📁 Revision chain: INTACT (no broken links)
|
|
97
|
+
🔀 Heads: 1 (no merge needed)
|
|
98
|
+
📝 Models: MODIFIED (uncommitted changes)
|
|
99
|
+
🔧 render_as_batch: N/A (PostgreSQL)
|
|
100
|
+
|
|
101
|
+
Detected Changes (since last commit):
|
|
102
|
+
──────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
🟢 SAFE (1 change)
|
|
105
|
+
• jobs: Added nullable column `applied_notes` (Text, nullable=True)
|
|
106
|
+
|
|
107
|
+
🟡 CAREFUL (1 change — review before migrating)
|
|
108
|
+
• profile: Added column `email` (String(255), NOT NULL, server_default='')
|
|
109
|
+
↳ All existing rows will get empty string value
|
|
110
|
+
↳ Is '' an acceptable default? Consider nullable first.
|
|
111
|
+
|
|
112
|
+
🔴 BREAKING (1 change — stop and read migration plan)
|
|
113
|
+
• jobs: Column `job_type` changed String → Integer
|
|
114
|
+
↳ PostgreSQL: requires USING cast in ALTER COLUMN
|
|
115
|
+
↳ Review autogenerated migration — add postgresql_using if missing
|
|
116
|
+
↳ SQLite: full table recreation via batch operations
|
|
117
|
+
|
|
118
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Dialect-Specific Notes
|
|
122
|
+
|
|
123
|
+
#### PostgreSQL
|
|
124
|
+
- ✅ Full ALTER TABLE support — most changes are online
|
|
125
|
+
- ⚠️ Column type changes require explicit `USING` clause for non-trivial casts
|
|
126
|
+
- ⚠️ `op.alter_column` without `postgresql_using` will fail if data is incompatible
|
|
127
|
+
- ✅ Migrations are versioned and fully reversible
|
|
128
|
+
|
|
129
|
+
#### MySQL
|
|
130
|
+
- ⚠️ Online DDL available in MySQL 8.0+
|
|
131
|
+
- ⚠️ Some ALTER operations trigger full table reconstruction
|
|
132
|
+
- ⚠️ Implicit type casts may fail silently
|
|
133
|
+
|
|
134
|
+
#### SQLite
|
|
135
|
+
- 🚨 `render_as_batch=True` MUST be set in `env.py` for any column modifications
|
|
136
|
+
- 🚨 `DROP COLUMN` not supported in SQLite < 3.35 (Python 3.11 / SQLite 3.35+)
|
|
137
|
+
- ✅ Batch operations recreate the table safely (data preserved)
|
|
138
|
+
- ⚠️ FK support in SQLite is off by default — check `PRAGMA foreign_keys = ON`
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Phase 4: Per-Change Migration Plans
|
|
143
|
+
|
|
144
|
+
### Scenario: Multiple Heads
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
Detection: alembic heads returns more than one revision
|
|
148
|
+
|
|
149
|
+
Problem: Branched revision history — alembic upgrade head is ambiguous.
|
|
150
|
+
|
|
151
|
+
Fix: Merge heads with a merge revision
|
|
152
|
+
|
|
153
|
+
# See current heads
|
|
154
|
+
alembic heads
|
|
155
|
+
|
|
156
|
+
# Merge two heads (replace rev1 and rev2 with actual IDs)
|
|
157
|
+
alembic merge -m "merge heads" rev1 rev2
|
|
158
|
+
|
|
159
|
+
# This creates a new migration file with both heads as down_revision:
|
|
160
|
+
# down_revision = ('rev1', 'rev2')
|
|
161
|
+
|
|
162
|
+
# Apply the merge
|
|
163
|
+
alembic upgrade head
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### Scenario: Add NOT NULL Column Without `server_default`
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
Problem: Non-empty table will fail — existing rows have no value for new column.
|
|
172
|
+
|
|
173
|
+
Three-step safe migration:
|
|
174
|
+
|
|
175
|
+
Step 1: Add column as nullable
|
|
176
|
+
op.add_column('users', sa.Column('email', sa.String(255), nullable=True))
|
|
177
|
+
|
|
178
|
+
Step 2: Backfill existing rows (in same or separate migration)
|
|
179
|
+
op.execute("UPDATE users SET email = '' WHERE email IS NULL")
|
|
180
|
+
|
|
181
|
+
Step 3: Alter column to NOT NULL
|
|
182
|
+
op.alter_column('users', 'email', nullable=False)
|
|
183
|
+
|
|
184
|
+
All three steps can be in a single revision file:
|
|
185
|
+
|
|
186
|
+
def upgrade():
|
|
187
|
+
op.add_column('users', sa.Column('email', sa.String(255), nullable=True))
|
|
188
|
+
op.execute("UPDATE users SET email = '' WHERE email IS NULL")
|
|
189
|
+
op.alter_column('users', 'email', nullable=False)
|
|
190
|
+
|
|
191
|
+
def downgrade():
|
|
192
|
+
op.alter_column('users', 'email', nullable=True)
|
|
193
|
+
op.drop_column('users', 'email')
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
### Scenario: SQLite — `render_as_batch` Missing
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
Problem: Alembic cannot ALTER columns in SQLite without batch operations.
|
|
202
|
+
|
|
203
|
+
Fix: Edit alembic/env.py to add render_as_batch=True
|
|
204
|
+
|
|
205
|
+
# In env.py, find the run_migrations_online() function:
|
|
206
|
+
# BEFORE:
|
|
207
|
+
context.configure(
|
|
208
|
+
connection=connection,
|
|
209
|
+
target_metadata=target_metadata,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# AFTER:
|
|
213
|
+
context.configure(
|
|
214
|
+
connection=connection,
|
|
215
|
+
target_metadata=target_metadata,
|
|
216
|
+
render_as_batch=True, # ← add this line
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Also update run_migrations_offline() if present:
|
|
220
|
+
context.configure(
|
|
221
|
+
url=url,
|
|
222
|
+
target_metadata=target_metadata,
|
|
223
|
+
render_as_batch=True, # ← add here too
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
After this change, all ALTER operations will use SQLite's table recreation pattern:
|
|
227
|
+
with op.batch_alter_table('users') as batch_op:
|
|
228
|
+
batch_op.alter_column('email', nullable=False)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
### Scenario: Change Column Type on PostgreSQL
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
Problem: op.alter_column without USING clause fails if data is incompatible.
|
|
237
|
+
|
|
238
|
+
Autogenerated migration (may be missing USING):
|
|
239
|
+
op.alter_column('jobs', 'job_type',
|
|
240
|
+
existing_type=sa.VARCHAR(),
|
|
241
|
+
type_=sa.INTEGER(),
|
|
242
|
+
existing_nullable=True)
|
|
243
|
+
|
|
244
|
+
Fix: Add postgresql_using to the alter_column call:
|
|
245
|
+
op.alter_column('jobs', 'job_type',
|
|
246
|
+
existing_type=sa.VARCHAR(),
|
|
247
|
+
type_=sa.INTEGER(),
|
|
248
|
+
postgresql_using='job_type::integer', # ← explicit CAST
|
|
249
|
+
existing_nullable=True)
|
|
250
|
+
|
|
251
|
+
Pre-check for non-castable data:
|
|
252
|
+
SELECT job_type FROM jobs WHERE job_type !~ '^[0-9]+$';
|
|
253
|
+
# If rows found: these cannot cast to integer — fix data first
|
|
254
|
+
|
|
255
|
+
For complex type changes (text → custom type, etc.):
|
|
256
|
+
# Use a temporary column approach:
|
|
257
|
+
# 1. Add new column with target type
|
|
258
|
+
# 2. Copy with explicit cast: UPDATE jobs SET job_type_new = CAST(job_type AS INT)
|
|
259
|
+
# 3. Drop old column
|
|
260
|
+
# 4. Rename new column
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
### Scenario: Rename Column — Autogenerate Blindspot
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
Problem: Alembic autogenerate cannot detect renames — shows as drop+add.
|
|
269
|
+
|
|
270
|
+
Detection from git diff:
|
|
271
|
+
- Model file: renamed attribute "old_name" → "new_name"
|
|
272
|
+
- Autogenerated migration: op.drop_column + op.add_column (DATA LOSS!)
|
|
273
|
+
|
|
274
|
+
Fix: Write rename manually using op.alter_column:
|
|
275
|
+
|
|
276
|
+
def upgrade():
|
|
277
|
+
op.alter_column('users', 'old_name', new_column_name='new_name')
|
|
278
|
+
|
|
279
|
+
def downgrade():
|
|
280
|
+
op.alter_column('users', 'new_name', new_column_name='old_name')
|
|
281
|
+
|
|
282
|
+
# op.alter_column for rename is supported in PostgreSQL, MySQL, SQLite (batch)
|
|
283
|
+
# For SQLite, use batch operations:
|
|
284
|
+
with op.batch_alter_table('users') as batch_op:
|
|
285
|
+
batch_op.alter_column('old_name', new_column_name='new_name')
|
|
286
|
+
|
|
287
|
+
Workflow:
|
|
288
|
+
1. alembic revision --autogenerate -m "rename_column"
|
|
289
|
+
2. Open generated file — replace drop+add with alter_column rename
|
|
290
|
+
3. alembic upgrade head
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Phase 5: Command Recommendations
|
|
296
|
+
|
|
297
|
+
### Core Commands
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
# Check current migration state
|
|
301
|
+
alembic current
|
|
302
|
+
|
|
303
|
+
# List all heads (should be 1)
|
|
304
|
+
alembic heads
|
|
305
|
+
|
|
306
|
+
# Show full migration history
|
|
307
|
+
alembic history --verbose
|
|
308
|
+
|
|
309
|
+
# Generate migration from model changes (autogenerate)
|
|
310
|
+
alembic revision --autogenerate -m "[describe_change]"
|
|
311
|
+
|
|
312
|
+
# Generate empty migration (for manual SQL)
|
|
313
|
+
alembic revision -m "[describe_change]"
|
|
314
|
+
|
|
315
|
+
# Apply all pending migrations
|
|
316
|
+
alembic upgrade head
|
|
317
|
+
|
|
318
|
+
# Apply one migration at a time
|
|
319
|
+
alembic upgrade +1
|
|
320
|
+
|
|
321
|
+
# Roll back one migration
|
|
322
|
+
alembic downgrade -1
|
|
323
|
+
|
|
324
|
+
# Roll back to specific revision
|
|
325
|
+
alembic downgrade [revision_id]
|
|
326
|
+
|
|
327
|
+
# Merge diverged heads
|
|
328
|
+
alembic merge -m "merge heads" [rev1] [rev2]
|
|
329
|
+
|
|
330
|
+
# Mark current DB state as head (without running migrations)
|
|
331
|
+
alembic stamp head
|
|
332
|
+
|
|
333
|
+
# Mark specific revision as applied
|
|
334
|
+
alembic stamp [revision_id]
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Workflow: Development
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
# 1. Review pending model changes
|
|
341
|
+
/schema-migrate
|
|
342
|
+
|
|
343
|
+
# 2. Generate migration
|
|
344
|
+
alembic revision --autogenerate -m "[describe_change]"
|
|
345
|
+
|
|
346
|
+
# 3. Review generated migration file (ALWAYS review before applying!)
|
|
347
|
+
cat alembic/versions/[latest_revision].py
|
|
348
|
+
|
|
349
|
+
# 4. Edit if needed (add USING clauses, fix renames, etc.)
|
|
350
|
+
|
|
351
|
+
# 5. Apply
|
|
352
|
+
alembic upgrade head
|
|
353
|
+
|
|
354
|
+
# 6. Verify state
|
|
355
|
+
alembic current
|
|
356
|
+
|
|
357
|
+
# 7. Commit
|
|
358
|
+
git add alembic/versions/
|
|
359
|
+
git commit -m "feat(schema): [describe change]"
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Workflow: Production Deployment
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
# 1. Check current state
|
|
366
|
+
alembic current
|
|
367
|
+
|
|
368
|
+
# 2. Preview pending migrations
|
|
369
|
+
alembic history --verbose
|
|
370
|
+
|
|
371
|
+
# 3. Apply
|
|
372
|
+
alembic upgrade head
|
|
373
|
+
|
|
374
|
+
# 4. Verify
|
|
375
|
+
alembic current
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### SQLite — Enabling Batch Operations
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
# After adding render_as_batch=True to env.py:
|
|
382
|
+
alembic revision --autogenerate -m "column_change"
|
|
383
|
+
# Verify generated migration uses batch_alter_table context manager
|
|
384
|
+
alembic upgrade head
|
|
385
|
+
```
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# /schema-migrate — ORM Detection Heuristics
|
|
2
|
+
|
|
3
|
+
Used by `SKILL.md` Phase 1 to detect the active ORM and route to the correct `orms/[orm].md` file.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Detection Priority Order
|
|
8
|
+
|
|
9
|
+
First match wins. Check signals in this order:
|
|
10
|
+
|
|
11
|
+
| Priority | ORM | Definitive Signal | Secondary Check |
|
|
12
|
+
|----------|-----|-------------------|-----------------|
|
|
13
|
+
| 1 | Drizzle | `drizzle.config.ts` or `drizzle.config.js` exists | `package.json` → `drizzle-orm` dep |
|
|
14
|
+
| 2 | Prisma | `prisma/schema.prisma` exists | `package.json` → `@prisma/client` |
|
|
15
|
+
| 3 | Laravel | `composer.json` → `laravel/framework` | `database/migrations/` exists |
|
|
16
|
+
| 4 | SQLAlchemy | `alembic.ini` exists | `alembic/versions/` exists |
|
|
17
|
+
| 5 | Rails | `Gemfile` → `rails` or `activerecord` gem | `db/migrate/` + `db/schema.rb` |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Supabase Overlay
|
|
22
|
+
|
|
23
|
+
Supabase is detected **alongside** the ORM, not instead of it.
|
|
24
|
+
|
|
25
|
+
**Signal:** `supabase/config.toml` exists → set `isSupabase = true`
|
|
26
|
+
|
|
27
|
+
Effect: The detected ORM file's Phase 5 commands will include Supabase CLI workflow in addition to the standard ORM commands. Supabase overlay applies primarily to Drizzle and Prisma projects (both use PostgreSQL under Supabase).
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Ambiguity Handling
|
|
32
|
+
|
|
33
|
+
If two definitive signals match (e.g., monorepo with both `drizzle.config.ts` and `composer.json`), ask the user:
|
|
34
|
+
|
|
35
|
+
> "Found both `drizzle.config.ts` (Drizzle ORM) and `composer.json` (Laravel). Which ORM should I analyze?"
|
|
36
|
+
|
|
37
|
+
Do not guess — wait for explicit confirmation.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Detection Flow (Phase 1 Steps)
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Step 1: Read in parallel —
|
|
45
|
+
drizzle.config.ts, drizzle.config.js,
|
|
46
|
+
prisma/schema.prisma,
|
|
47
|
+
composer.json,
|
|
48
|
+
alembic.ini,
|
|
49
|
+
Gemfile,
|
|
50
|
+
package.json,
|
|
51
|
+
supabase/config.toml
|
|
52
|
+
|
|
53
|
+
Step 2: Apply priority rules →
|
|
54
|
+
- Does drizzle.config.ts/js exist? → ORM = Drizzle
|
|
55
|
+
- Else does prisma/schema.prisma exist? → ORM = Prisma
|
|
56
|
+
- Else does composer.json have laravel/framework? → ORM = Laravel
|
|
57
|
+
- Else does alembic.ini exist? → ORM = SQLAlchemy
|
|
58
|
+
- Else does Gemfile have rails or activerecord? → ORM = Rails
|
|
59
|
+
- Else: Unknown — report error and ask user to specify
|
|
60
|
+
|
|
61
|
+
Supabase check (independent):
|
|
62
|
+
- Does supabase/config.toml exist? → isSupabase = true
|
|
63
|
+
|
|
64
|
+
Step 3: Report detection result:
|
|
65
|
+
"Detected: [ORM] ([dialect]) — loading orms/[orm].md"
|
|
66
|
+
e.g., "Detected: Drizzle ORM (SQLite) — loading orms/drizzle.md"
|
|
67
|
+
e.g., "Detected: Prisma (PostgreSQL + Supabase) — loading orms/prisma.md"
|
|
68
|
+
|
|
69
|
+
Step 4: Load orms/[orm].md and execute its Phase 2 through Phase 5
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Dialect Detection (per ORM)
|
|
75
|
+
|
|
76
|
+
After the ORM is identified, detect the database dialect:
|
|
77
|
+
|
|
78
|
+
### Drizzle
|
|
79
|
+
- Read `drizzle.config.ts` → `dialect` field: `sqlite` | `postgres` | `mysql`
|
|
80
|
+
- Supabase: `dialect === 'postgres'` + URL contains `supabase`
|
|
81
|
+
|
|
82
|
+
### Prisma
|
|
83
|
+
- Read `prisma/schema.prisma` → `datasource db { provider = "..." }`
|
|
84
|
+
- Values: `postgresql` | `mysql` | `sqlite` | `sqlserver` | `mongodb`
|
|
85
|
+
- Note: MongoDB does not support migrations — flag this to the user
|
|
86
|
+
|
|
87
|
+
### Laravel
|
|
88
|
+
- Read `.env` → `DB_CONNECTION` value: `mysql` | `pgsql` | `sqlite`
|
|
89
|
+
- Fallback: read `config/database.php` → `default` key
|
|
90
|
+
- Default if undetectable: `mysql`
|
|
91
|
+
|
|
92
|
+
### SQLAlchemy
|
|
93
|
+
- Read `alembic.ini` → `sqlalchemy.url` value
|
|
94
|
+
- Prefix: `postgresql` | `mysql` | `sqlite` | `mssql`
|
|
95
|
+
|
|
96
|
+
### Rails
|
|
97
|
+
- Read `config/database.yml` → `adapter:` value under `development:`
|
|
98
|
+
- Values: `postgresql` | `mysql2` | `sqlite3`
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## ORM → File Map
|
|
103
|
+
|
|
104
|
+
| Detected ORM | File to Load |
|
|
105
|
+
|--------------|-------------|
|
|
106
|
+
| Drizzle | `orms/drizzle.md` |
|
|
107
|
+
| Prisma | `orms/prisma.md` |
|
|
108
|
+
| Laravel | `orms/laravel.md` |
|
|
109
|
+
| SQLAlchemy (Alembic) | `orms/sqlalchemy.md` |
|
|
110
|
+
| Rails (ActiveRecord) | `orms/rails.md` |
|