@smicolon/ai-kit 0.1.0 → 0.2.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/.claude-plugin/CLAUDE.md +7 -0
- package/.claude-plugin/marketplace.json +373 -0
- package/README.md +26 -16
- package/dist/index.js +146 -38
- package/package.json +4 -3
- package/packs/architect/CHANGELOG.md +17 -0
- package/packs/architect/README.md +58 -0
- package/packs/architect/agents/system-architect.md +768 -0
- package/packs/architect/commands/diagram-create.md +300 -0
- package/packs/better-auth/.claude-plugin/plugin.json +14 -0
- package/packs/better-auth/.mcp.json +14 -0
- package/packs/better-auth/CHANGELOG.md +26 -0
- package/packs/better-auth/README.md +125 -0
- package/packs/better-auth/agents/auth-architect.md +278 -0
- package/packs/better-auth/commands/auth-provider-add.md +265 -0
- package/packs/better-auth/commands/auth-setup.md +298 -0
- package/packs/better-auth/skills/auth-security/SKILL.md +425 -0
- package/packs/better-auth/skills/better-auth-patterns/SKILL.md +455 -0
- package/packs/dev-loop/.claude-plugin/plugin.json +10 -0
- package/packs/dev-loop/CHANGELOG.md +69 -0
- package/packs/dev-loop/README.md +155 -0
- package/packs/dev-loop/commands/cancel-dev.md +21 -0
- package/packs/dev-loop/commands/dev-loop.md +72 -0
- package/packs/dev-loop/commands/dev-plan.md +351 -0
- package/packs/dev-loop/hooks/hooks.json +15 -0
- package/packs/dev-loop/hooks/stop-hook.sh +178 -0
- package/packs/dev-loop/scripts/setup-dev-loop.sh +194 -0
- package/packs/dev-loop/skills/tdd-planner/SKILL.md +249 -0
- package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +874 -0
- package/packs/dev-loop/skills/tdd-planner/references/good-example.md +260 -0
- package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +275 -0
- package/packs/django/CHANGELOG.md +39 -0
- package/packs/django/README.md +92 -0
- package/packs/django/agents/django-architect.md +182 -0
- package/packs/django/agents/django-builder.md +250 -0
- package/packs/django/agents/django-feature-based.md +420 -0
- package/packs/django/agents/django-reviewer.md +253 -0
- package/packs/django/agents/django-tester.md +230 -0
- package/packs/django/commands/api-endpoint.md +285 -0
- package/packs/django/commands/model-create.md +178 -0
- package/packs/django/commands/test-generate.md +325 -0
- package/packs/django/rules/migrations.md +138 -0
- package/packs/django/rules/models.md +167 -0
- package/packs/django/rules/serializers.md +126 -0
- package/packs/django/rules/services.md +131 -0
- package/packs/django/rules/tests.md +140 -0
- package/packs/django/rules/views.md +102 -0
- package/packs/django/skills/import-convention-enforcer/SKILL.md +226 -0
- package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +343 -0
- package/packs/django/skills/migration-safety-checker/SKILL.md +375 -0
- package/packs/django/skills/model-entity-validator/SKILL.md +298 -0
- package/packs/django/skills/performance-optimizer/SKILL.md +447 -0
- package/packs/django/skills/red-phase-verifier/SKILL.md +180 -0
- package/packs/django/skills/security-first-validator/SKILL.md +435 -0
- package/packs/django/skills/test-coverage-advisor/SKILL.md +394 -0
- package/packs/django/skills/test-validity-checker/SKILL.md +194 -0
- package/packs/failure-log/.claude-plugin/plugin.json +14 -0
- package/packs/failure-log/CHANGELOG.md +20 -0
- package/packs/failure-log/README.md +168 -0
- package/packs/failure-log/commands/failure-add.md +106 -0
- package/packs/failure-log/commands/failure-list.md +89 -0
- package/packs/failure-log/hooks/hooks.json +16 -0
- package/packs/failure-log/hooks/scripts/inject-failures.sh +64 -0
- package/packs/failure-log/skills/failure-log-manager/SKILL.md +164 -0
- package/packs/flutter/.claude-plugin/plugin.json +10 -0
- package/packs/flutter/CHANGELOG.md +19 -0
- package/packs/flutter/README.md +170 -0
- package/packs/flutter/agents/flutter-architect.md +166 -0
- package/packs/flutter/agents/flutter-builder.md +303 -0
- package/packs/flutter/agents/release-manager.md +355 -0
- package/packs/flutter/commands/fastlane-setup.md +188 -0
- package/packs/flutter/commands/flutter-build.md +90 -0
- package/packs/flutter/commands/flutter-deploy.md +133 -0
- package/packs/flutter/commands/flutter-test.md +117 -0
- package/packs/flutter/commands/signing-setup.md +209 -0
- package/packs/flutter/hooks/hooks.json +17 -0
- package/packs/flutter/skills/fastlane-knowledge/SKILL.md +193 -0
- package/packs/flutter/skills/flutter-architecture/SKILL.md +127 -0
- package/packs/flutter/skills/store-publishing/SKILL.md +163 -0
- package/packs/hono/.claude-plugin/plugin.json +19 -0
- package/packs/hono/CHANGELOG.md +19 -0
- package/packs/hono/README.md +143 -0
- package/packs/hono/agents/hono-architect.md +240 -0
- package/packs/hono/agents/hono-builder.md +285 -0
- package/packs/hono/agents/hono-reviewer.md +279 -0
- package/packs/hono/agents/hono-tester.md +346 -0
- package/packs/hono/commands/middleware-create.md +223 -0
- package/packs/hono/commands/project-init.md +306 -0
- package/packs/hono/commands/route-create.md +153 -0
- package/packs/hono/commands/rpc-client.md +263 -0
- package/packs/hono/hooks/hooks.json +4 -0
- package/packs/hono/skills/cloudflare-bindings/SKILL.md +408 -0
- package/packs/hono/skills/hono-patterns/SKILL.md +309 -0
- package/packs/hono/skills/rpc-typesafe/SKILL.md +388 -0
- package/packs/hono/skills/zod-validation/SKILL.md +332 -0
- package/packs/nestjs/CHANGELOG.md +29 -0
- package/packs/nestjs/README.md +75 -0
- package/packs/nestjs/agents/nestjs-architect.md +402 -0
- package/packs/nestjs/agents/nestjs-builder.md +301 -0
- package/packs/nestjs/agents/nestjs-tester.md +437 -0
- package/packs/nestjs/commands/module-create.md +369 -0
- package/packs/nestjs/rules/controllers.md +92 -0
- package/packs/nestjs/rules/dto.md +124 -0
- package/packs/nestjs/rules/entities.md +102 -0
- package/packs/nestjs/rules/services.md +106 -0
- package/packs/nestjs/skills/barrel-export-manager/SKILL.md +389 -0
- package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +365 -0
- package/packs/nextjs/CHANGELOG.md +36 -0
- package/packs/nextjs/README.md +76 -0
- package/packs/nextjs/agents/frontend-tester.md +680 -0
- package/packs/nextjs/agents/frontend-visual.md +820 -0
- package/packs/nextjs/agents/nextjs-architect.md +331 -0
- package/packs/nextjs/agents/nextjs-modular.md +433 -0
- package/packs/nextjs/commands/component-create.md +398 -0
- package/packs/nextjs/rules/api-routes.md +129 -0
- package/packs/nextjs/rules/components.md +106 -0
- package/packs/nextjs/rules/hooks.md +132 -0
- package/packs/nextjs/skills/accessibility-validator/SKILL.md +445 -0
- package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +399 -0
- package/packs/nextjs/skills/react-form-validator/SKILL.md +569 -0
- package/packs/nuxtjs/CHANGELOG.md +30 -0
- package/packs/nuxtjs/README.md +56 -0
- package/packs/nuxtjs/agents/frontend-tester.md +680 -0
- package/packs/nuxtjs/agents/frontend-visual.md +820 -0
- package/packs/nuxtjs/agents/nuxtjs-architect.md +537 -0
- package/packs/nuxtjs/commands/component-create.md +223 -0
- package/packs/nuxtjs/rules/components.md +101 -0
- package/packs/nuxtjs/rules/composables.md +118 -0
- package/packs/nuxtjs/rules/server-routes.md +127 -0
- package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +183 -0
- package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +196 -0
- package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +190 -0
- package/packs/onboard/CHANGELOG.md +22 -0
- package/packs/onboard/README.md +103 -0
- package/packs/onboard/agents/onboard-guide.md +118 -0
- package/packs/onboard/commands/onboard.md +313 -0
- package/packs/onboard/skills/onboard-context-provider/SKILL.md +98 -0
- package/packs/tanstack-router/.claude-plugin/plugin.json +14 -0
- package/packs/tanstack-router/CHANGELOG.md +30 -0
- package/packs/tanstack-router/README.md +113 -0
- package/packs/tanstack-router/agents/tanstack-architect.md +173 -0
- package/packs/tanstack-router/agents/tanstack-builder.md +360 -0
- package/packs/tanstack-router/agents/tanstack-tester.md +454 -0
- package/packs/tanstack-router/commands/form-create.md +313 -0
- package/packs/tanstack-router/commands/query-create.md +263 -0
- package/packs/tanstack-router/commands/route-create.md +190 -0
- package/packs/tanstack-router/commands/table-create.md +413 -0
- package/packs/tanstack-router/skills/ai-patterns/SKILL.md +370 -0
- package/packs/tanstack-router/skills/db-patterns/SKILL.md +346 -0
- package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +415 -0
- package/packs/tanstack-router/skills/form-patterns/SKILL.md +425 -0
- package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +341 -0
- package/packs/tanstack-router/skills/query-patterns/SKILL.md +359 -0
- package/packs/tanstack-router/skills/router-patterns/SKILL.md +285 -0
- package/packs/tanstack-router/skills/store-patterns/SKILL.md +351 -0
- package/packs/tanstack-router/skills/table-patterns/SKILL.md +531 -0
- package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +428 -0
- package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +490 -0
- package/packs/worktree/.claude-plugin/plugin.json +19 -0
- package/packs/worktree/CHANGELOG.md +24 -0
- package/packs/worktree/README.md +110 -0
- package/packs/worktree/commands/wt.md +73 -0
- package/packs/worktree/scripts/wt.sh +396 -0
- package/packs/worktree/skills/worktree-manager/SKILL.md +68 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: migration-safety-checker
|
|
3
|
+
description: This skill should be used when the user asks to "create a migration", "run makemigrations", "modify a model field", "rename a column", or mentions "schema change", "alter table", "database migration". Validates migrations are production-safe.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Migration Safety Checker
|
|
7
|
+
|
|
8
|
+
Validates Django migrations are production-safe before they cause data loss.
|
|
9
|
+
|
|
10
|
+
## Activation Triggers
|
|
11
|
+
|
|
12
|
+
This skill activates when:
|
|
13
|
+
- Running `makemigrations`
|
|
14
|
+
- Modifying model fields
|
|
15
|
+
- Mentioning "migration", "schema", "alter table"
|
|
16
|
+
- Adding/removing/renaming fields
|
|
17
|
+
- Changing field types
|
|
18
|
+
- Creating or modifying migration files
|
|
19
|
+
|
|
20
|
+
## Safety Requirements
|
|
21
|
+
|
|
22
|
+
All migrations MUST be:
|
|
23
|
+
- ✅ **Reversible** - Can roll back safely
|
|
24
|
+
- ✅ **No data loss** - Preserve existing data
|
|
25
|
+
- ✅ **Tested** - Run on staging first
|
|
26
|
+
- ✅ **Safe for production** - No downtime operations
|
|
27
|
+
- ✅ **Documented** - Explain what changes and why
|
|
28
|
+
|
|
29
|
+
## High-Risk Operations
|
|
30
|
+
|
|
31
|
+
These operations require special attention:
|
|
32
|
+
|
|
33
|
+
### 🔴 CRITICAL RISK - Data Loss Possible
|
|
34
|
+
|
|
35
|
+
1. **Dropping columns** without data migration
|
|
36
|
+
2. **Renaming fields** without data migration
|
|
37
|
+
3. **Changing field types** (data conversion issues)
|
|
38
|
+
4. **Adding non-nullable fields** without default
|
|
39
|
+
5. **Removing tables** with data
|
|
40
|
+
|
|
41
|
+
### 🟡 MEDIUM RISK - Requires Careful Planning
|
|
42
|
+
|
|
43
|
+
1. **Adding indexes** on large tables (use `CONCURRENTLY`)
|
|
44
|
+
2. **Adding unique constraints** (check for duplicates first)
|
|
45
|
+
3. **Changing `max_length`** (data truncation risk)
|
|
46
|
+
|
|
47
|
+
### 🟢 LOW RISK - Generally Safe
|
|
48
|
+
|
|
49
|
+
1. **Adding nullable fields**
|
|
50
|
+
2. **Adding fields with defaults**
|
|
51
|
+
3. **Creating new tables**
|
|
52
|
+
4. **Adding non-unique indexes**
|
|
53
|
+
|
|
54
|
+
## Validation Process
|
|
55
|
+
|
|
56
|
+
### Step 1: Analyze Migration File
|
|
57
|
+
|
|
58
|
+
When migration is created:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
# Generated migration
|
|
62
|
+
class Migration(migrations.Migration):
|
|
63
|
+
operations = [
|
|
64
|
+
migrations.RemoveField(
|
|
65
|
+
model_name='user',
|
|
66
|
+
name='old_email', # 🔴 DATA LOSS RISK!
|
|
67
|
+
),
|
|
68
|
+
]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Step 2: Identify Risks
|
|
72
|
+
|
|
73
|
+
Detect:
|
|
74
|
+
- Operation type: `RemoveField`
|
|
75
|
+
- Field: `old_email`
|
|
76
|
+
- **Risk:** 🔴 CRITICAL - Dropping column loses data!
|
|
77
|
+
|
|
78
|
+
### Step 3: Block Unsafe Migration
|
|
79
|
+
|
|
80
|
+
> **⛔ UNSAFE MIGRATION DETECTED**
|
|
81
|
+
>
|
|
82
|
+
> **Operation:** `RemoveField(model_name='user', name='old_email')`
|
|
83
|
+
>
|
|
84
|
+
> **Risk:** 🔴 CRITICAL - Data loss!
|
|
85
|
+
>
|
|
86
|
+
> **Problem:**
|
|
87
|
+
> - Drops `old_email` column immediately
|
|
88
|
+
> - All existing email data will be PERMANENTLY LOST
|
|
89
|
+
> - Cannot be rolled back after data is gone
|
|
90
|
+
>
|
|
91
|
+
> **Safe Approach:**
|
|
92
|
+
> Use 3-step migration pattern:
|
|
93
|
+
> 1. Data migration: Archive `old_email` data
|
|
94
|
+
> 2. Wait for deployment
|
|
95
|
+
> 3. Drop column in separate migration
|
|
96
|
+
>
|
|
97
|
+
> Should I generate the safe migration sequence?
|
|
98
|
+
|
|
99
|
+
### Step 4: Generate Safe Migration
|
|
100
|
+
|
|
101
|
+
If user approves, create:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# Migration 1: Archive data
|
|
105
|
+
class Migration(migrations.Migration):
|
|
106
|
+
dependencies = [('users', '0001_initial')]
|
|
107
|
+
|
|
108
|
+
operations = [
|
|
109
|
+
migrations.RunPython(archive_old_emails, reverse_code=restore_old_emails),
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
def archive_old_emails(apps, schema_editor):
|
|
113
|
+
"""Archive old_email data before dropping."""
|
|
114
|
+
User = apps.get_model('users', 'User')
|
|
115
|
+
EmailArchive = apps.get_model('users', 'EmailArchive')
|
|
116
|
+
|
|
117
|
+
for user in User.objects.all():
|
|
118
|
+
if user.old_email:
|
|
119
|
+
EmailArchive.objects.create(
|
|
120
|
+
user_id=user.id,
|
|
121
|
+
email=user.old_email
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def restore_old_emails(apps, schema_editor):
|
|
125
|
+
"""Restore old_email data on rollback."""
|
|
126
|
+
User = apps.get_model('users', 'User')
|
|
127
|
+
EmailArchive = apps.get_model('users', 'EmailArchive')
|
|
128
|
+
|
|
129
|
+
for archive in EmailArchive.objects.all():
|
|
130
|
+
User.objects.filter(id=archive.user_id).update(
|
|
131
|
+
old_email=archive.email
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Migration 2: Drop column (run after deployment)
|
|
135
|
+
class Migration(migrations.Migration):
|
|
136
|
+
dependencies = [('users', '0002_archive_emails')]
|
|
137
|
+
|
|
138
|
+
operations = [
|
|
139
|
+
migrations.RemoveField(
|
|
140
|
+
model_name='user',
|
|
141
|
+
name='old_email',
|
|
142
|
+
),
|
|
143
|
+
]
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Safe Migration Patterns
|
|
147
|
+
|
|
148
|
+
### Pattern 1: Renaming Field (3 Steps)
|
|
149
|
+
|
|
150
|
+
**❌ UNSAFE:**
|
|
151
|
+
```python
|
|
152
|
+
operations = [
|
|
153
|
+
migrations.RenameField('user', 'email', 'email_address'), # ❌ Risky!
|
|
154
|
+
]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**✅ SAFE (3-Step):**
|
|
158
|
+
```python
|
|
159
|
+
# Step 1: Add new field
|
|
160
|
+
operations = [
|
|
161
|
+
migrations.AddField('user', 'email_address',
|
|
162
|
+
models.EmailField(null=True))
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
# Step 2: Data migration (copy old → new)
|
|
166
|
+
operations = [
|
|
167
|
+
migrations.RunPython(copy_email_to_email_address)
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
# Step 3: Drop old field (separate deployment)
|
|
171
|
+
operations = [
|
|
172
|
+
migrations.RemoveField('user', 'email')
|
|
173
|
+
]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Pattern 2: Changing Field Type (3 Steps)
|
|
177
|
+
|
|
178
|
+
**❌ UNSAFE:**
|
|
179
|
+
```python
|
|
180
|
+
operations = [
|
|
181
|
+
migrations.AlterField('product', 'price',
|
|
182
|
+
models.DecimalField(max_digits=10, decimal_places=2))
|
|
183
|
+
]
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**✅ SAFE:**
|
|
187
|
+
```python
|
|
188
|
+
# Step 1: Add new field with new type
|
|
189
|
+
operations = [
|
|
190
|
+
migrations.AddField('product', 'price_decimal',
|
|
191
|
+
models.DecimalField(max_digits=10, decimal_places=2, null=True))
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
# Step 2: Data migration (convert & copy)
|
|
195
|
+
operations = [
|
|
196
|
+
migrations.RunPython(convert_price_to_decimal)
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
# Step 3: Drop old field, rename new field
|
|
200
|
+
operations = [
|
|
201
|
+
migrations.RemoveField('product', 'price'),
|
|
202
|
+
migrations.RenameField('product', 'price_decimal', 'price')
|
|
203
|
+
]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Pattern 3: Adding Non-Nullable Field (2 Steps)
|
|
207
|
+
|
|
208
|
+
**❌ UNSAFE:**
|
|
209
|
+
```python
|
|
210
|
+
operations = [
|
|
211
|
+
migrations.AddField('user', 'organization',
|
|
212
|
+
models.ForeignKey('Organization')) # ❌ No default!
|
|
213
|
+
]
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**✅ SAFE:**
|
|
217
|
+
```python
|
|
218
|
+
# Step 1: Add as nullable
|
|
219
|
+
operations = [
|
|
220
|
+
migrations.AddField('user', 'organization',
|
|
221
|
+
models.ForeignKey('Organization', null=True))
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
# Step 2: Populate + make non-nullable
|
|
225
|
+
operations = [
|
|
226
|
+
migrations.RunPython(assign_default_organizations),
|
|
227
|
+
migrations.AlterField('user', 'organization',
|
|
228
|
+
models.ForeignKey('Organization', null=False))
|
|
229
|
+
]
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Pattern 4: Adding Unique Constraint (Check First)
|
|
233
|
+
|
|
234
|
+
**❌ UNSAFE:**
|
|
235
|
+
```python
|
|
236
|
+
operations = [
|
|
237
|
+
migrations.AlterField('user', 'email',
|
|
238
|
+
models.EmailField(unique=True)) # ❌ Fails if duplicates!
|
|
239
|
+
]
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**✅ SAFE:**
|
|
243
|
+
```python
|
|
244
|
+
# Step 1: Check for duplicates
|
|
245
|
+
operations = [
|
|
246
|
+
migrations.RunPython(check_and_fix_duplicate_emails)
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
# Step 2: Add unique constraint
|
|
250
|
+
operations = [
|
|
251
|
+
migrations.AlterField('user', 'email',
|
|
252
|
+
models.EmailField(unique=True))
|
|
253
|
+
]
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Production-Safe Practices
|
|
257
|
+
|
|
258
|
+
### Use CONCURRENT Index Creation
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
from django.db import migrations
|
|
262
|
+
from django.contrib.postgres.operations import AddIndexConcurrently
|
|
263
|
+
|
|
264
|
+
class Migration(migrations.Migration):
|
|
265
|
+
atomic = False # Required for CONCURRENT operations
|
|
266
|
+
|
|
267
|
+
operations = [
|
|
268
|
+
AddIndexConcurrently(
|
|
269
|
+
model_name='user',
|
|
270
|
+
index=models.Index(fields=['email'], name='user_email_idx')
|
|
271
|
+
)
|
|
272
|
+
]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Data Migration Template
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
def forward_migration(apps, schema_editor):
|
|
279
|
+
"""
|
|
280
|
+
Safe data migration with batching.
|
|
281
|
+
"""
|
|
282
|
+
Model = apps.get_model('app', 'Model')
|
|
283
|
+
batch_size = 1000
|
|
284
|
+
|
|
285
|
+
total = Model.objects.count()
|
|
286
|
+
for offset in range(0, total, batch_size):
|
|
287
|
+
batch = Model.objects.all()[offset:offset + batch_size]
|
|
288
|
+
for obj in batch:
|
|
289
|
+
# Transform data
|
|
290
|
+
obj.new_field = transform(obj.old_field)
|
|
291
|
+
obj.save(update_fields=['new_field'])
|
|
292
|
+
|
|
293
|
+
def reverse_migration(apps, schema_editor):
|
|
294
|
+
"""
|
|
295
|
+
Reverse the migration.
|
|
296
|
+
"""
|
|
297
|
+
Model = apps.get_model('app', 'Model')
|
|
298
|
+
Model.objects.all().update(new_field=None)
|
|
299
|
+
|
|
300
|
+
class Migration(migrations.Migration):
|
|
301
|
+
operations = [
|
|
302
|
+
migrations.RunPython(forward_migration, reverse_migration)
|
|
303
|
+
]
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Migration Testing Checklist
|
|
307
|
+
|
|
308
|
+
Before applying migration:
|
|
309
|
+
|
|
310
|
+
- ✅ Run on copy of production database
|
|
311
|
+
- ✅ Verify data integrity after migration
|
|
312
|
+
- ✅ Test rollback works
|
|
313
|
+
- ✅ Check migration duration (< 5 minutes ideal)
|
|
314
|
+
- ✅ Review SQL with `sqlmigrate`
|
|
315
|
+
- ✅ Confirm no downtime required
|
|
316
|
+
- ✅ Have rollback plan ready
|
|
317
|
+
|
|
318
|
+
## Validation Commands
|
|
319
|
+
|
|
320
|
+
Suggest running:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
# Review SQL before applying
|
|
324
|
+
python manage.py sqlmigrate users 0002
|
|
325
|
+
|
|
326
|
+
# Check for issues
|
|
327
|
+
python manage.py makemigrations --check
|
|
328
|
+
|
|
329
|
+
# Dry run
|
|
330
|
+
python manage.py migrate --plan
|
|
331
|
+
|
|
332
|
+
# Apply to staging first
|
|
333
|
+
python manage.py migrate --database=staging
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Integration with CI/CD
|
|
337
|
+
|
|
338
|
+
```yaml
|
|
339
|
+
# .github/workflows/migrations.yml
|
|
340
|
+
- name: Check migrations safety
|
|
341
|
+
run: |
|
|
342
|
+
python manage.py makemigrations --check --dry-run --no-input
|
|
343
|
+
python manage.py migrate --plan
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Success Criteria
|
|
347
|
+
|
|
348
|
+
✅ No data loss migrations
|
|
349
|
+
✅ All migrations tested on staging
|
|
350
|
+
✅ Rollback plan exists
|
|
351
|
+
✅ Large table migrations use CONCURRENT
|
|
352
|
+
✅ Data migrations properly batched
|
|
353
|
+
✅ Reversible migrations
|
|
354
|
+
|
|
355
|
+
## Behavior
|
|
356
|
+
|
|
357
|
+
**Proactive enforcement:**
|
|
358
|
+
- Analyze migrations without being asked
|
|
359
|
+
- Block unsafe migrations immediately
|
|
360
|
+
- Suggest safe 3-step patterns
|
|
361
|
+
- Generate data migration code
|
|
362
|
+
- Explain WHY the migration is risky
|
|
363
|
+
|
|
364
|
+
**Never:**
|
|
365
|
+
- Allow data loss migrations
|
|
366
|
+
- Let users skip safety checks
|
|
367
|
+
- Apply migrations without review
|
|
368
|
+
- Just warn without blocking unsafe operations
|
|
369
|
+
|
|
370
|
+
**When migration is unsafe:**
|
|
371
|
+
- Stop the process
|
|
372
|
+
- Explain the risk
|
|
373
|
+
- Provide safe alternative
|
|
374
|
+
- Generate corrected migration code
|
|
375
|
+
- User must acknowledge before proceeding
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: model-entity-validator
|
|
3
|
+
description: This skill should be used when the user asks to "create a model", "add a Django model", "create database table", "add entity", "define schema", or when writing class definitions inheriting from models.Model. Validates BaseModel inheritance pattern.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Model/Entity Validator
|
|
7
|
+
|
|
8
|
+
Enforces Smicolon's BaseModel inheritance pattern for all Django models.
|
|
9
|
+
|
|
10
|
+
## Activation Triggers
|
|
11
|
+
|
|
12
|
+
This skill activates when:
|
|
13
|
+
- Creating new model files
|
|
14
|
+
- Modifying existing models
|
|
15
|
+
- Mentioning "model", "database", "schema", "table"
|
|
16
|
+
- Writing class inheriting from `models.Model`
|
|
17
|
+
- Running migrations
|
|
18
|
+
- Discussing data structure
|
|
19
|
+
|
|
20
|
+
## Core Principle: BaseModel Inheritance
|
|
21
|
+
|
|
22
|
+
**NEVER repeat UUID/timestamp fields.** All models inherit from BaseModel.
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
# ❌ WRONG - Repeating fields
|
|
26
|
+
class User(models.Model):
|
|
27
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
28
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
29
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
30
|
+
is_deleted = models.BooleanField(default=False)
|
|
31
|
+
email = models.EmailField()
|
|
32
|
+
|
|
33
|
+
# ✅ CORRECT - Inherit from BaseModel
|
|
34
|
+
import core.models as _core_models
|
|
35
|
+
|
|
36
|
+
class User(_core_models.BaseModel):
|
|
37
|
+
email = models.EmailField(unique=True)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Validation Process
|
|
41
|
+
|
|
42
|
+
### Step 1: Check if BaseModel Exists
|
|
43
|
+
|
|
44
|
+
Before any action, check if the project has a BaseModel:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# Search for BaseModel in (in order):
|
|
48
|
+
# 1. core/models.py
|
|
49
|
+
# 2. shared/models.py
|
|
50
|
+
# 3. common/models.py
|
|
51
|
+
# 4. {app}/base.py
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**If BaseModel found:** Suggest inheritance (Step 2a)
|
|
55
|
+
**If BaseModel NOT found:** Create BaseModel first (Step 2b)
|
|
56
|
+
|
|
57
|
+
### Step 2a: BaseModel Exists - Suggest Inheritance
|
|
58
|
+
|
|
59
|
+
When seeing:
|
|
60
|
+
```python
|
|
61
|
+
class Product(models.Model):
|
|
62
|
+
name = models.CharField(max_length=255)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Fix to:
|
|
66
|
+
```python
|
|
67
|
+
import core.models as _core_models
|
|
68
|
+
|
|
69
|
+
class Product(_core_models.BaseModel):
|
|
70
|
+
"""Product model - inherits id, timestamps, soft delete from BaseModel."""
|
|
71
|
+
|
|
72
|
+
name = models.CharField(max_length=255)
|
|
73
|
+
price = models.DecimalField(max_digits=10, decimal_places=2)
|
|
74
|
+
|
|
75
|
+
class Meta:
|
|
76
|
+
db_table = 'products'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Step 2b: BaseModel NOT Found - Create It First
|
|
80
|
+
|
|
81
|
+
If no BaseModel exists, create it:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# core/models.py
|
|
85
|
+
import uuid
|
|
86
|
+
from django.db import models
|
|
87
|
+
|
|
88
|
+
class BaseModel(models.Model):
|
|
89
|
+
"""
|
|
90
|
+
Abstract base model providing:
|
|
91
|
+
- UUID primary key
|
|
92
|
+
- Automatic timestamps (created_at, updated_at)
|
|
93
|
+
- Soft delete support (is_deleted)
|
|
94
|
+
|
|
95
|
+
All models MUST inherit from this class.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
id = models.UUIDField(
|
|
99
|
+
primary_key=True,
|
|
100
|
+
default=uuid.uuid4,
|
|
101
|
+
editable=False
|
|
102
|
+
)
|
|
103
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
104
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
105
|
+
is_deleted = models.BooleanField(default=False)
|
|
106
|
+
|
|
107
|
+
class Meta:
|
|
108
|
+
abstract = True
|
|
109
|
+
ordering = ['-created_at']
|
|
110
|
+
|
|
111
|
+
def soft_delete(self) -> None:
|
|
112
|
+
"""Soft delete the record."""
|
|
113
|
+
self.is_deleted = True
|
|
114
|
+
self.save(update_fields=['is_deleted', 'updated_at'])
|
|
115
|
+
|
|
116
|
+
def restore(self) -> None:
|
|
117
|
+
"""Restore a soft-deleted record."""
|
|
118
|
+
self.is_deleted = False
|
|
119
|
+
self.save(update_fields=['is_deleted', 'updated_at'])
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ActiveManager(models.Manager):
|
|
123
|
+
"""Manager that excludes soft-deleted records."""
|
|
124
|
+
|
|
125
|
+
def get_queryset(self):
|
|
126
|
+
return super().get_queryset().filter(is_deleted=False)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Report to developer:
|
|
130
|
+
> **BaseModel Created**
|
|
131
|
+
>
|
|
132
|
+
> Created `core/models.py` with BaseModel. All models should now inherit from it:
|
|
133
|
+
> ```python
|
|
134
|
+
> import core.models as _core_models
|
|
135
|
+
>
|
|
136
|
+
> class YourModel(_core_models.BaseModel):
|
|
137
|
+
> # Your fields here - DO NOT add id, created_at, updated_at, is_deleted
|
|
138
|
+
> ```
|
|
139
|
+
|
|
140
|
+
### Step 3: Detect Duplicate Fields
|
|
141
|
+
|
|
142
|
+
If seeing a model with explicit id/timestamp fields that inherits from BaseModel:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
# ❌ WRONG - Duplicate fields
|
|
146
|
+
class User(_core_models.BaseModel):
|
|
147
|
+
id = models.UUIDField(...) # Already in BaseModel!
|
|
148
|
+
created_at = models.DateTimeField(...) # Already in BaseModel!
|
|
149
|
+
email = models.EmailField()
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Remove them:
|
|
153
|
+
```python
|
|
154
|
+
# ✅ CORRECT - Only custom fields
|
|
155
|
+
class User(_core_models.BaseModel):
|
|
156
|
+
email = models.EmailField(unique=True)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Step 4: Validate Indexes
|
|
160
|
+
|
|
161
|
+
Check if appropriate indexes exist:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
class Product(_core_models.BaseModel):
|
|
165
|
+
name = models.CharField(max_length=255)
|
|
166
|
+
sku = models.CharField(max_length=100, unique=True)
|
|
167
|
+
|
|
168
|
+
class Meta:
|
|
169
|
+
db_table = 'products'
|
|
170
|
+
indexes = [
|
|
171
|
+
models.Index(fields=['sku']), # Suggest for unique lookups
|
|
172
|
+
models.Index(fields=['name']), # Suggest for search
|
|
173
|
+
]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Complete Model Examples
|
|
177
|
+
|
|
178
|
+
### Standard Model
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
# products/models.py
|
|
182
|
+
from django.db import models
|
|
183
|
+
import core.models as _core_models
|
|
184
|
+
|
|
185
|
+
class Product(_core_models.BaseModel):
|
|
186
|
+
"""Product model."""
|
|
187
|
+
|
|
188
|
+
name = models.CharField(max_length=255)
|
|
189
|
+
description = models.TextField(blank=True)
|
|
190
|
+
price = models.DecimalField(max_digits=10, decimal_places=2)
|
|
191
|
+
sku = models.CharField(max_length=100, unique=True, db_index=True)
|
|
192
|
+
is_active = models.BooleanField(default=True)
|
|
193
|
+
|
|
194
|
+
class Meta:
|
|
195
|
+
db_table = 'products'
|
|
196
|
+
verbose_name = 'Product'
|
|
197
|
+
verbose_name_plural = 'Products'
|
|
198
|
+
indexes = [
|
|
199
|
+
models.Index(fields=['name']),
|
|
200
|
+
models.Index(fields=['is_active', 'is_deleted']),
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
def __str__(self):
|
|
204
|
+
return self.name
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### User Model (with AbstractUser)
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
# users/models.py
|
|
211
|
+
import uuid
|
|
212
|
+
from django.db import models
|
|
213
|
+
from django.contrib.auth.models import AbstractUser
|
|
214
|
+
|
|
215
|
+
class User(AbstractUser):
|
|
216
|
+
"""
|
|
217
|
+
Custom User model with UUID and timestamps.
|
|
218
|
+
|
|
219
|
+
Note: For User, override AbstractUser directly since it has its own
|
|
220
|
+
ID handling. Add the standard fields manually.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
224
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
225
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
226
|
+
is_deleted = models.BooleanField(default=False)
|
|
227
|
+
|
|
228
|
+
email = models.EmailField(unique=True)
|
|
229
|
+
|
|
230
|
+
USERNAME_FIELD = 'email'
|
|
231
|
+
REQUIRED_FIELDS = []
|
|
232
|
+
|
|
233
|
+
class Meta:
|
|
234
|
+
db_table = 'users'
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Through Model (Many-to-Many)
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
# users/models.py
|
|
241
|
+
import core.models as _core_models
|
|
242
|
+
|
|
243
|
+
class UserOrganization(_core_models.BaseModel):
|
|
244
|
+
"""Through model for user-organization relationship."""
|
|
245
|
+
|
|
246
|
+
user = models.ForeignKey('User', on_delete=models.CASCADE)
|
|
247
|
+
organization = models.ForeignKey('Organization', on_delete=models.CASCADE)
|
|
248
|
+
role = models.CharField(max_length=50)
|
|
249
|
+
|
|
250
|
+
class Meta:
|
|
251
|
+
db_table = 'user_organizations'
|
|
252
|
+
unique_together = [['user', 'organization']]
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Custom Managers
|
|
256
|
+
|
|
257
|
+
BaseModel provides soft delete. Add a custom manager for convenience:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
import core.models as _core_models
|
|
261
|
+
|
|
262
|
+
class Product(_core_models.BaseModel):
|
|
263
|
+
name = models.CharField(max_length=255)
|
|
264
|
+
|
|
265
|
+
objects = models.Manager() # All records
|
|
266
|
+
active = _core_models.ActiveManager() # Excludes soft-deleted
|
|
267
|
+
|
|
268
|
+
# Usage
|
|
269
|
+
Product.active.all() # Only non-deleted
|
|
270
|
+
Product.objects.all() # All including deleted
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Validation Checklist
|
|
274
|
+
|
|
275
|
+
When reviewing a model, check:
|
|
276
|
+
|
|
277
|
+
1. ✅ Inherits from `BaseModel` (or has explicit required fields for special cases)
|
|
278
|
+
2. ✅ Does NOT duplicate fields from BaseModel
|
|
279
|
+
3. ✅ Uses absolute import: `import core.models as _core_models`
|
|
280
|
+
4. ✅ Has appropriate `db_table` name (singular or plural, snake_case)
|
|
281
|
+
5. ✅ Has indexes on frequently queried fields
|
|
282
|
+
6. ✅ Foreign keys have `on_delete` specified
|
|
283
|
+
7. ✅ Has docstring explaining the model
|
|
284
|
+
8. ✅ Has `__str__` method for admin display
|
|
285
|
+
|
|
286
|
+
## Behavior
|
|
287
|
+
|
|
288
|
+
**Proactive enforcement:**
|
|
289
|
+
- Check if BaseModel exists FIRST
|
|
290
|
+
- Suggest inheritance instead of field duplication
|
|
291
|
+
- Remove duplicate fields automatically
|
|
292
|
+
- Create BaseModel if missing
|
|
293
|
+
- Explain WHY inheritance is better
|
|
294
|
+
|
|
295
|
+
**Never:**
|
|
296
|
+
- Add duplicate id/timestamp fields to models
|
|
297
|
+
- Let developers repeat base fields
|
|
298
|
+
- Ignore existing BaseModel in the project
|