@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,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` |