@baselane/packs 0.1.4 → 0.1.5

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 (97) hide show
  1. package/dist/aliases.js +1 -0
  2. package/package.json +2 -2
  3. package/packs/database-review/pack.json +75 -9
  4. package/packs/database-review/rendered/.claude/agents/database-reviewer.md +75 -2
  5. package/packs/database-review/rendered/.claude/commands/migration-review.md +6 -0
  6. package/packs/database-review/rendered/.claude/settings.json +11 -0
  7. package/packs/database-review/rendered/.claude/skills/database-migrations/SKILL.md +431 -0
  8. package/packs/database-review/rendered/.claude/skills/mysql-patterns/SKILL.md +414 -0
  9. package/packs/database-review/rendered/.claude/skills/postgres-patterns/SKILL.md +149 -0
  10. package/packs/database-review/rendered/.github/copilot-instructions.md +1096 -14
  11. package/packs/database-review/rendered/AGENTS.md +45 -16
  12. package/packs/database-review/rendered/CLAUDE.md +4 -0
  13. package/packs/database-review/rendered/GEMINI.md +987 -0
  14. package/packs/go-rules/pack.json +65 -9
  15. package/packs/go-rules/rendered/.claude/agents/go-build-resolver.md +90 -0
  16. package/packs/go-rules/rendered/.claude/agents/go-reviewer.md +72 -2
  17. package/packs/go-rules/rendered/.claude/commands/go-build.md +6 -0
  18. package/packs/go-rules/rendered/.claude/commands/go-review.md +1 -1
  19. package/packs/go-rules/rendered/.claude/commands/go-test.md +6 -0
  20. package/packs/go-rules/rendered/.claude/settings.json +9 -0
  21. package/packs/go-rules/rendered/.claude/skills/golang-patterns/SKILL.md +673 -0
  22. package/packs/go-rules/rendered/.claude/skills/golang-testing/SKILL.md +719 -0
  23. package/packs/go-rules/rendered/.github/copilot-instructions.md +1783 -7
  24. package/packs/go-rules/rendered/AGENTS.md +244 -8
  25. package/packs/go-rules/rendered/CLAUDE.md +5 -0
  26. package/packs/go-rules/rendered/GEMINI.md +1394 -0
  27. package/packs/python-rules/pack.json +87 -10
  28. package/packs/python-rules/rendered/.claude/agents/django-reviewer.md +159 -0
  29. package/packs/python-rules/rendered/.claude/agents/fastapi-reviewer.md +70 -0
  30. package/packs/python-rules/rendered/.claude/agents/python-reviewer.md +92 -2
  31. package/packs/python-rules/rendered/.claude/commands/django-review.md +6 -0
  32. package/packs/python-rules/rendered/.claude/commands/fastapi-review.md +6 -0
  33. package/packs/python-rules/rendered/.claude/commands/python-review.md +1 -1
  34. package/packs/python-rules/rendered/.claude/settings.json +1 -1
  35. package/packs/python-rules/rendered/.claude/skills/django-security/SKILL.md +642 -0
  36. package/packs/python-rules/rendered/.claude/skills/django-tdd/SKILL.md +728 -0
  37. package/packs/python-rules/rendered/.claude/skills/fastapi-patterns/SKILL.md +512 -0
  38. package/packs/python-rules/rendered/.claude/skills/python-patterns/SKILL.md +749 -0
  39. package/packs/python-rules/rendered/.claude/skills/python-testing/SKILL.md +815 -0
  40. package/packs/python-rules/rendered/.github/copilot-instructions.md +3815 -12
  41. package/packs/python-rules/rendered/AGENTS.md +73 -13
  42. package/packs/python-rules/rendered/CLAUDE.md +9 -0
  43. package/packs/python-rules/rendered/GEMINI.md +3448 -0
  44. package/packs/security-review/pack.json +51 -7
  45. package/packs/security-review/rendered/.claude/agents/security-reviewer.md +29 -2
  46. package/packs/security-review/rendered/.claude/settings.json +11 -0
  47. package/packs/security-review/rendered/.claude/skills/security-review/SKILL.md +302 -0
  48. package/packs/security-review/rendered/.claude/skills/security-review/cloud-infrastructure-security.md +171 -0
  49. package/packs/security-review/rendered/.claude/skills/security-scan-config/SKILL.md +56 -0
  50. package/packs/security-review/rendered/.github/copilot-instructions.md +658 -15
  51. package/packs/security-review/rendered/AGENTS.md +283 -17
  52. package/packs/security-review/rendered/CLAUDE.md +2 -0
  53. package/packs/security-review/rendered/GEMINI.md +352 -0
  54. package/packs/software-engineer-harness/pack.json +249 -13
  55. package/packs/software-engineer-harness/rendered/.claude/agents/code-explorer.md +67 -0
  56. package/packs/software-engineer-harness/rendered/.claude/agents/performance-optimizer.md +444 -0
  57. package/packs/software-engineer-harness/rendered/.claude/agents/pr-test-analyzer.md +43 -0
  58. package/packs/software-engineer-harness/rendered/.claude/commands/feature-dev.md +6 -0
  59. package/packs/software-engineer-harness/rendered/.claude/settings.json +29 -0
  60. package/packs/software-engineer-harness/rendered/.claude/skills/api-design/SKILL.md +522 -0
  61. package/packs/software-engineer-harness/rendered/.claude/skills/architecture-decision-records/SKILL.md +178 -0
  62. package/packs/software-engineer-harness/rendered/.claude/skills/backend-patterns/SKILL.md +560 -0
  63. package/packs/software-engineer-harness/rendered/.claude/skills/codebase-onboarding/SKILL.md +232 -0
  64. package/packs/software-engineer-harness/rendered/.claude/skills/deployment-patterns/SKILL.md +426 -0
  65. package/packs/software-engineer-harness/rendered/.claude/skills/docker-patterns/SKILL.md +363 -0
  66. package/packs/software-engineer-harness/rendered/.claude/skills/e2e-testing/SKILL.md +325 -0
  67. package/packs/software-engineer-harness/rendered/.claude/skills/tdd-workflow/SKILL.md +580 -0
  68. package/packs/software-engineer-harness/rendered/.github/copilot-instructions.md +3948 -2
  69. package/packs/software-engineer-harness/rendered/AGENTS.md +194 -4
  70. package/packs/software-engineer-harness/rendered/CLAUDE.md +20 -0
  71. package/packs/software-engineer-harness/rendered/GEMINI.md +3208 -0
  72. package/packs/typescript-rules/pack.json +68 -5
  73. package/packs/typescript-rules/rendered/.claude/agents/react-reviewer.md +165 -0
  74. package/packs/typescript-rules/rendered/.claude/agents/type-design-analyzer.md +48 -0
  75. package/packs/typescript-rules/rendered/.claude/commands/react-review.md +6 -0
  76. package/packs/typescript-rules/rendered/.claude/settings.json +9 -0
  77. package/packs/typescript-rules/rendered/.claude/skills/react-patterns/SKILL.md +340 -0
  78. package/packs/typescript-rules/rendered/.claude/skills/react-performance/SKILL.md +573 -0
  79. package/packs/typescript-rules/rendered/.claude/skills/react-testing/SKILL.md +422 -0
  80. package/packs/typescript-rules/rendered/.github/copilot-instructions.md +2459 -53
  81. package/packs/typescript-rules/rendered/AGENTS.md +893 -17
  82. package/packs/typescript-rules/rendered/CLAUDE.md +6 -0
  83. package/packs/typescript-rules/rendered/GEMINI.md +1335 -0
  84. package/packs/ecc-essentials/pack.json +0 -93
  85. package/packs/ecc-essentials/rendered/.claude/settings.json +0 -37
  86. package/packs/ecc-essentials/rendered/.github/copilot-instructions.md +0 -110
  87. package/packs/ecc-essentials/rendered/AGENTS.md +0 -74
  88. package/packs/ecc-essentials/rendered/CLAUDE.md +0 -14
  89. package/packs/ecc-essentials/rendered/GEMINI.md +0 -25
  90. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/agents/build-error-resolver.md +0 -0
  91. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/agents/refactor-cleaner.md +0 -0
  92. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/agents/silent-failure-hunter.md +0 -0
  93. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/agents/tdd-guide.md +0 -0
  94. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/commands/fix-build.md +0 -0
  95. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/commands/hunt-silent-failures.md +0 -0
  96. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/commands/tdd.md +0 -0
  97. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/skills/tdd-red-green-refactor/SKILL.md +0 -0
@@ -1,3 +1,990 @@
1
1
  # GEMINI.md
2
2
 
3
3
  @AGENTS.md
4
+
5
+ ## Skills
6
+
7
+ ### postgres-patterns
8
+
9
+ PostgreSQL indexing, schema, RLS, pagination, and configuration patterns. Use when writing SQL queries or migrations, designing schemas, troubleshooting slow queries, implementing Row Level Security, or setting up connection pooling.
10
+
11
+ # PostgreSQL Patterns
12
+
13
+ Quick reference for PostgreSQL best practices. For detailed guidance, use the `database-reviewer` agent.
14
+
15
+ ## When to Activate
16
+
17
+ - Writing SQL queries or migrations
18
+ - Designing database schemas
19
+ - Troubleshooting slow queries
20
+ - Implementing Row Level Security
21
+ - Setting up connection pooling
22
+
23
+ ## Quick Reference
24
+
25
+ ### Index Cheat Sheet
26
+
27
+ | Query Pattern | Index Type | Example |
28
+ |--------------|------------|---------|
29
+ | `WHERE col = value` | B-tree (default) | `CREATE INDEX idx ON t (col)` |
30
+ | `WHERE col > value` | B-tree | `CREATE INDEX idx ON t (col)` |
31
+ | `WHERE a = x AND b > y` | Composite | `CREATE INDEX idx ON t (a, b)` |
32
+ | `WHERE jsonb @> '{}'` | GIN | `CREATE INDEX idx ON t USING gin (col)` |
33
+ | `WHERE tsv @@ query` | GIN | `CREATE INDEX idx ON t USING gin (col)` |
34
+ | Time-series ranges | BRIN | `CREATE INDEX idx ON t USING brin (col)` |
35
+
36
+ ### Data Type Quick Reference
37
+
38
+ | Use Case | Correct Type | Avoid |
39
+ |----------|-------------|-------|
40
+ | IDs | `bigint` | `int`, random UUID |
41
+ | Strings | `text` | `varchar(255)` |
42
+ | Timestamps | `timestamptz` | `timestamp` |
43
+ | Money | `numeric(10,2)` | `float` |
44
+ | Flags | `boolean` | `varchar`, `int` |
45
+
46
+ ### Common Patterns
47
+
48
+ **Composite Index Order:**
49
+ ```sql
50
+ -- Equality columns first, then range columns
51
+ CREATE INDEX idx ON orders (status, created_at);
52
+ -- Works for: WHERE status = 'pending' AND created_at > '2024-01-01'
53
+ ```
54
+
55
+ **Covering Index:**
56
+ ```sql
57
+ CREATE INDEX idx ON users (email) INCLUDE (name, created_at);
58
+ -- Avoids table lookup for SELECT email, name, created_at
59
+ ```
60
+
61
+ **Partial Index:**
62
+ ```sql
63
+ CREATE INDEX idx ON users (email) WHERE deleted_at IS NULL;
64
+ -- Smaller index, only includes active users
65
+ ```
66
+
67
+ **RLS Policy (Optimized):**
68
+ ```sql
69
+ CREATE POLICY policy ON orders
70
+ USING ((SELECT auth.uid()) = user_id); -- Wrap in SELECT!
71
+ ```
72
+
73
+ **UPSERT:**
74
+ ```sql
75
+ INSERT INTO settings (user_id, key, value)
76
+ VALUES (123, 'theme', 'dark')
77
+ ON CONFLICT (user_id, key)
78
+ DO UPDATE SET value = EXCLUDED.value;
79
+ ```
80
+
81
+ **Cursor Pagination:**
82
+ ```sql
83
+ SELECT * FROM products WHERE id > $last_id ORDER BY id LIMIT 20;
84
+ -- O(1) vs OFFSET which is O(n)
85
+ ```
86
+
87
+ **Queue Processing:**
88
+ ```sql
89
+ UPDATE jobs SET status = 'processing'
90
+ WHERE id = (
91
+ SELECT id FROM jobs WHERE status = 'pending'
92
+ ORDER BY created_at LIMIT 1
93
+ FOR UPDATE SKIP LOCKED
94
+ ) RETURNING *;
95
+ ```
96
+
97
+ ### Anti-Pattern Detection
98
+
99
+ ```sql
100
+ -- Find unindexed foreign keys
101
+ SELECT conrelid::regclass, a.attname
102
+ FROM pg_constraint c
103
+ JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey)
104
+ WHERE c.contype = 'f'
105
+ AND NOT EXISTS (
106
+ SELECT 1 FROM pg_index i
107
+ WHERE i.indrelid = c.conrelid AND a.attnum = ANY(i.indkey)
108
+ );
109
+
110
+ -- Find slow queries
111
+ SELECT query, mean_exec_time, calls
112
+ FROM pg_stat_statements
113
+ WHERE mean_exec_time > 100
114
+ ORDER BY mean_exec_time DESC;
115
+
116
+ -- Check table bloat
117
+ SELECT relname, n_dead_tup, last_vacuum
118
+ FROM pg_stat_user_tables
119
+ WHERE n_dead_tup > 1000
120
+ ORDER BY n_dead_tup DESC;
121
+ ```
122
+
123
+ ### Configuration Template
124
+
125
+ ```sql
126
+ -- Connection limits (adjust for RAM)
127
+ ALTER SYSTEM SET max_connections = 100;
128
+ ALTER SYSTEM SET work_mem = '8MB';
129
+
130
+ -- Timeouts
131
+ ALTER SYSTEM SET idle_in_transaction_session_timeout = '30s';
132
+ ALTER SYSTEM SET statement_timeout = '30s';
133
+
134
+ -- Monitoring
135
+ CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
136
+
137
+ -- Security defaults
138
+ REVOKE ALL ON SCHEMA public FROM public;
139
+
140
+ SELECT pg_reload_conf();
141
+ ```
142
+
143
+ ## Related
144
+
145
+ - Agent: `database-reviewer` - Full database review workflow
146
+ - Skill: `clickhouse-io` - ClickHouse analytics patterns
147
+ - Skill: `backend-patterns` - API and backend patterns
148
+
149
+ ---
150
+
151
+ *Based on Supabase Agent Skills (credit: Supabase team) (MIT License)*
152
+
153
+ ### mysql-patterns
154
+
155
+ MySQL and MariaDB schema, query, indexing, transaction, replication, and connection-pool patterns for production backends. Use when designing MySQL/MariaDB tables and indexes, reviewing migrations before they run on large tables, debugging slow queries/lock waits/deadlocks/connection exhaustion, or adding keyset pagination, upserts, full-text search, JSON columns, or queues.
156
+
157
+ # MySQL Patterns
158
+
159
+ Use this skill when working on MySQL or MariaDB schema design, migrations,
160
+ slow-query investigation, queue-style transactions, connection pools, or
161
+ production database configuration. Prefer exact version checks before applying a
162
+ feature-specific pattern because MySQL and MariaDB have diverged in several SQL
163
+ details.
164
+
165
+ ## Activation
166
+
167
+ - Designing MySQL or MariaDB tables, indexes, and constraints
168
+ - Reviewing migrations before they run on large production tables
169
+ - Debugging slow queries, lock waits, deadlocks, or connection exhaustion
170
+ - Adding keyset pagination, upserts, full-text search, JSON columns, or queues
171
+ - Configuring application connection pools, read replicas, TLS, or slow logs
172
+
173
+ ## Version Check
174
+
175
+ Start by identifying the engine and version:
176
+
177
+ ```sql
178
+ SELECT VERSION();
179
+ SHOW VARIABLES LIKE 'version_comment';
180
+ ```
181
+
182
+ Keep MySQL and MariaDB guidance separate when syntax differs:
183
+
184
+ - MySQL documents row aliases as the replacement for `VALUES(col)` in
185
+ `ON DUPLICATE KEY UPDATE`; `VALUES(col)` is deprecated there.
186
+ - MariaDB documents `VALUES(col)` as the supported way to reference inserted
187
+ values in `ON DUPLICATE KEY UPDATE`; use it for cross-engine compatibility.
188
+ - `SKIP LOCKED` is appropriate for queue-like work only. It skips locked rows
189
+ and can return an inconsistent view, so do not use it for general accounting
190
+ or integrity-sensitive reads.
191
+
192
+ ## Schema Defaults
193
+
194
+ ```sql
195
+ CREATE TABLE orders (
196
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
197
+ account_id BIGINT UNSIGNED NOT NULL,
198
+ status VARCHAR(32) NOT NULL,
199
+ total DECIMAL(15, 2) NOT NULL,
200
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
201
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
202
+ deleted_at DATETIME NULL,
203
+ PRIMARY KEY (id),
204
+ KEY idx_orders_account_status_created (account_id, status, created_at),
205
+ KEY idx_orders_active (account_id, deleted_at)
206
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
207
+ ```
208
+
209
+ Default choices:
210
+
211
+ | Use Case | Prefer | Avoid |
212
+ | --- | --- | --- |
213
+ | Surrogate primary keys | `BIGINT UNSIGNED AUTO_INCREMENT` | `INT` for tables that can grow beyond 2B rows |
214
+ | UUID lookup keys | `BINARY(16)` with conversion helpers | `VARCHAR(36)` primary keys on hot tables |
215
+ | Money and exact quantities | `DECIMAL(p, s)` | `FLOAT` or `DOUBLE` |
216
+ | User-facing text | `utf8mb4` tables and indexes | MySQL `utf8` / `utf8mb3` defaults |
217
+ | Application timestamps | `DATETIME` with UTC managed by the app | Assuming `DATETIME` stores time zone metadata |
218
+ | Soft deletes | `deleted_at DATETIME NULL` plus scoped indexes | Filtering soft-deleted rows without an index |
219
+ | Extensible status values | lookup table or constrained `VARCHAR` | `ENUM` when values change often |
220
+
221
+ ## Indexing
222
+
223
+ Composite index order usually follows equality predicates first, then range or
224
+ sort columns:
225
+
226
+ ```sql
227
+ CREATE INDEX idx_orders_account_status_created
228
+ ON orders (account_id, status, created_at);
229
+
230
+ SELECT id, total
231
+ FROM orders
232
+ WHERE account_id = ?
233
+ AND status = 'pending'
234
+ AND created_at >= ?
235
+ ORDER BY created_at DESC
236
+ LIMIT 50;
237
+ ```
238
+
239
+ Use `EXPLAIN` before adding or changing an index:
240
+
241
+ ```sql
242
+ EXPLAIN
243
+ SELECT id, total
244
+ FROM orders
245
+ WHERE account_id = 123 AND status = 'pending'
246
+ ORDER BY created_at DESC
247
+ LIMIT 50;
248
+ ```
249
+
250
+ Signals to investigate:
251
+
252
+ | Field | Risk Signal |
253
+ | --- | --- |
254
+ | `type` | `ALL` on a large table |
255
+ | `key` | `NULL` when a selective predicate exists |
256
+ | `rows` | Very high row estimate for an interactive path |
257
+ | `Extra` | `Using temporary`, `Using filesort`, or broad `Using where` |
258
+
259
+ Avoid adding indexes blindly. Each index increases write cost, migration time,
260
+ backup size, and buffer-pool pressure.
261
+
262
+ ## Query Patterns
263
+
264
+ ### Upsert
265
+
266
+ Cross-engine-compatible form:
267
+
268
+ ```sql
269
+ INSERT INTO user_settings (user_id, setting_key, setting_value)
270
+ VALUES (?, ?, ?)
271
+ ON DUPLICATE KEY UPDATE
272
+ setting_value = VALUES(setting_value),
273
+ updated_at = CURRENT_TIMESTAMP;
274
+ ```
275
+
276
+ MySQL row-alias form:
277
+
278
+ ```sql
279
+ INSERT INTO user_settings (user_id, setting_key, setting_value)
280
+ VALUES (?, ?, ?) AS new
281
+ ON DUPLICATE KEY UPDATE
282
+ setting_value = new.setting_value,
283
+ updated_at = CURRENT_TIMESTAMP;
284
+ ```
285
+
286
+ Use the row-alias form only after confirming the target is MySQL. Use
287
+ `VALUES(col)` for MariaDB or mixed MySQL/MariaDB fleets.
288
+
289
+ ### Keyset Pagination
290
+
291
+ ```sql
292
+ SELECT id, name, created_at
293
+ FROM products
294
+ WHERE (created_at, id) < (?, ?)
295
+ ORDER BY created_at DESC, id DESC
296
+ LIMIT 50;
297
+ ```
298
+
299
+ Back it with an index that matches the cursor:
300
+
301
+ ```sql
302
+ CREATE INDEX idx_products_created_id ON products (created_at, id);
303
+ ```
304
+
305
+ Do not use deep `OFFSET` pagination on large tables; it makes the server scan
306
+ and discard rows before returning the page.
307
+
308
+ ### JSON Fields
309
+
310
+ Use JSON columns for extension data, not for fields that need heavy relational
311
+ filtering or constraints.
312
+
313
+ ```sql
314
+ CREATE TABLE events (
315
+ id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
316
+ payload JSON NOT NULL,
317
+ event_type VARCHAR(64)
318
+ GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(payload, '$.type'))) STORED,
319
+ KEY idx_events_type (event_type)
320
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
321
+ ```
322
+
323
+ For frequently queried JSON paths, expose a generated column and index that
324
+ column. Keep foreign keys, ownership, tenancy, and lifecycle fields relational.
325
+
326
+ ### Full-Text Search
327
+
328
+ ```sql
329
+ ALTER TABLE articles ADD FULLTEXT KEY ft_articles_title_body (title, body);
330
+
331
+ SELECT id, title, MATCH(title, body) AGAINST (? IN NATURAL LANGUAGE MODE) AS score
332
+ FROM articles
333
+ WHERE MATCH(title, body) AGAINST (? IN NATURAL LANGUAGE MODE)
334
+ ORDER BY score DESC
335
+ LIMIT 20;
336
+ ```
337
+
338
+ Use external search when you need typo tolerance, complex ranking, cross-table
339
+ facets, or language-specific analysis beyond built-in full-text behavior.
340
+
341
+ ## Transactions
342
+
343
+ Keep transactions short and lock rows in a consistent order:
344
+
345
+ ```sql
346
+ START TRANSACTION;
347
+
348
+ SELECT id, balance
349
+ FROM accounts
350
+ WHERE id IN (?, ?)
351
+ ORDER BY id
352
+ FOR UPDATE;
353
+
354
+ UPDATE accounts SET balance = balance - ? WHERE id = ?;
355
+ UPDATE accounts SET balance = balance + ? WHERE id = ?;
356
+
357
+ COMMIT;
358
+ ```
359
+
360
+ Deadlock and lock-wait checklist:
361
+
362
+ - Lock rows in a deterministic order across code paths.
363
+ - Do external API calls before opening the transaction, not inside it.
364
+ - Add indexes for predicates used in `UPDATE`, `DELETE`, and locking reads.
365
+ - On deadlock, roll back and retry the whole transaction with a bounded retry
366
+ budget.
367
+ - Capture `SHOW ENGINE INNODB STATUS\G` soon after a deadlock; it is overwritten
368
+ by later events.
369
+
370
+ Queue-style worker claim:
371
+
372
+ ```sql
373
+ START TRANSACTION;
374
+
375
+ SELECT id
376
+ FROM jobs
377
+ WHERE status = 'pending'
378
+ ORDER BY created_at
379
+ LIMIT 1
380
+ FOR UPDATE SKIP LOCKED;
381
+
382
+ UPDATE jobs
383
+ SET status = 'processing', started_at = CURRENT_TIMESTAMP
384
+ WHERE id = ?;
385
+
386
+ COMMIT;
387
+ ```
388
+
389
+ Use `SKIP LOCKED` only for queue-like workloads where skipping a locked row is
390
+ acceptable. It is not a replacement for normal transactional consistency.
391
+
392
+ ## Connection Pools
393
+
394
+ SQLAlchemy example:
395
+
396
+ ```python
397
+ from sqlalchemy import create_engine
398
+
399
+ engine = create_engine(
400
+ "mysql+mysqlconnector://app:secret@db.internal/app",
401
+ pool_size=10,
402
+ max_overflow=5,
403
+ pool_timeout=30,
404
+ pool_recycle=240,
405
+ pool_pre_ping=True,
406
+ connect_args={"connect_timeout": 5},
407
+ )
408
+ ```
409
+
410
+ Node.js `mysql2` example:
411
+
412
+ ```javascript
413
+ import mysql from 'mysql2/promise';
414
+
415
+ const pool = mysql.createPool({
416
+ host: process.env.DB_HOST,
417
+ user: process.env.DB_USER,
418
+ password: process.env.DB_PASSWORD,
419
+ database: process.env.DB_NAME,
420
+ waitForConnections: true,
421
+ connectionLimit: 10,
422
+ queueLimit: 0,
423
+ enableKeepAlive: true,
424
+ keepAliveInitialDelay: 30000,
425
+ });
426
+
427
+ const [rows] = await pool.execute(
428
+ 'SELECT id, total FROM orders WHERE account_id = ? LIMIT 50',
429
+ [accountId],
430
+ );
431
+ ```
432
+
433
+ Keep application pool recycling below the server `wait_timeout`. If the server
434
+ uses `wait_timeout = 300`, a `pool_recycle` around 240 seconds is coherent;
435
+ `pool_pre_ping` still helps recover from network and failover events.
436
+
437
+ ## Diagnostics
438
+
439
+ Useful first-pass commands:
440
+
441
+ ```sql
442
+ SHOW FULL PROCESSLIST;
443
+ SHOW ENGINE INNODB STATUS\G;
444
+ SHOW VARIABLES LIKE 'slow_query_log';
445
+ SHOW VARIABLES LIKE 'long_query_time';
446
+ ```
447
+
448
+ Enable the slow log in a controlled environment:
449
+
450
+ ```sql
451
+ SET GLOBAL slow_query_log = 'ON';
452
+ SET GLOBAL long_query_time = 1;
453
+ SET GLOBAL log_queries_not_using_indexes = 'ON';
454
+ ```
455
+
456
+ Use `EXPLAIN ANALYZE` only when it is safe to execute the query. It runs the
457
+ statement and can be expensive on production-sized data.
458
+
459
+ ## Replication
460
+
461
+ Read replicas can lag. Do not route read-your-own-write paths, checkout flows,
462
+ permission checks, or idempotency-key reads to a replica immediately after a
463
+ write.
464
+
465
+ ```sql
466
+ -- MySQL legacy terminology, still common in existing fleets
467
+ SHOW SLAVE STATUS\G;
468
+
469
+ -- Newer terminology where supported
470
+ SHOW REPLICA STATUS\G;
471
+ ```
472
+
473
+ Check the engine/version before standardizing on one command. Monitor replica
474
+ SQL thread health, IO thread health, and lag, not just whether the TCP
475
+ connection is alive.
476
+
477
+ ## Security
478
+
479
+ ```sql
480
+ CREATE USER 'app'@'%' IDENTIFIED BY 'use-a-secret-manager';
481
+ GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'%';
482
+
483
+ ALTER USER 'app'@'%' REQUIRE SSL;
484
+
485
+ SELECT user, host
486
+ FROM mysql.user
487
+ WHERE user = '';
488
+
489
+ DROP USER IF EXISTS ''@'localhost';
490
+ DROP USER IF EXISTS ''@'%';
491
+ ```
492
+
493
+ Security review points:
494
+
495
+ - Do not grant `ALL PRIVILEGES` or `*.*` to application users.
496
+ - Require TLS for application users when traffic crosses hosts or networks.
497
+ - Store credentials in the platform secret manager, not in examples, scripts, or
498
+ repository files.
499
+ - Separate migration/admin users from runtime application users.
500
+ - Audit public network exposure and bind addresses before tuning performance.
501
+
502
+ ## Configuration
503
+
504
+ Example starting point for a dedicated database host:
505
+
506
+ ```ini
507
+ [mysqld]
508
+ innodb_buffer_pool_size = 4G
509
+ innodb_flush_log_at_trx_commit = 1
510
+ sync_binlog = 1
511
+
512
+ max_connections = 300
513
+ thread_cache_size = 50
514
+
515
+ wait_timeout = 300
516
+ interactive_timeout = 300
517
+ innodb_lock_wait_timeout = 10
518
+
519
+ slow_query_log = ON
520
+ long_query_time = 1
521
+ log_queries_not_using_indexes = ON
522
+
523
+ log_bin = mysql-bin
524
+ binlog_format = ROW
525
+ binlog_expire_logs_seconds = 604800
526
+ ```
527
+
528
+ Treat configuration values as a prompt for review, not a universal preset. Size
529
+ memory, connections, log retention, and durability settings from workload,
530
+ hardware, backup policy, and recovery objectives.
531
+
532
+ ## Anti-Patterns
533
+
534
+ | Anti-Pattern | Risk | Better Pattern |
535
+ | --- | --- | --- |
536
+ | `SELECT *` in hot paths | Over-fetching and brittle clients | Select explicit columns |
537
+ | Deep `OFFSET` pagination | Linear scans and slow pages | Keyset pagination |
538
+ | No index on foreign-key joins | Slow joins and lock-heavy deletes | Index FK columns intentionally |
539
+ | Long transactions | Lock waits and large undo history | Commit small units of work |
540
+ | Direct DML against `mysql.user` | Grant-table corruption risk | Use `CREATE USER`, `ALTER USER`, `DROP USER` |
541
+ | Application user with admin grants | High blast radius | Least-privilege runtime user |
542
+ | Pool recycle above `wait_timeout` | Stale pooled connections | Recycle below timeout and pre-ping |
543
+ | Replica reads after writes | Stale user-facing state | Pin read-after-write flows to primary |
544
+
545
+ ## Output Expectations
546
+
547
+ When this skill is used for review, return:
548
+
549
+ 1. Engine/version assumptions.
550
+ 2. Highest-risk correctness, lock, security, and migration issues.
551
+ 3. Exact SQL or code changes for the safe path.
552
+ 4. Validation plan: `EXPLAIN`, migration dry run, lock/deadlock check, and
553
+ rollback criteria.
554
+ 5. Any MySQL/MariaDB syntax differences that affect the recommendation.
555
+
556
+ ## Related
557
+
558
+ - Skill: `postgres-patterns` - PostgreSQL-specific schema and query patterns
559
+ - Skill: `database-migrations` - migration planning and rollout safety
560
+ - Skill: `backend-patterns` - API and service-layer patterns
561
+ - Skill: `security-review` - secret handling, auth, and least privilege
562
+ - Agent: `database-reviewer` - broader database review workflow
563
+
564
+ ### database-migrations
565
+
566
+ Safe, reversible database schema-change patterns across PostgreSQL, MySQL, and common ORMs (Prisma, Drizzle, Kysely, Django, golang-migrate). Use when creating or altering tables, adding/removing columns or indexes, running data migrations, or planning zero-downtime schema changes.
567
+
568
+ # Database Migration Patterns
569
+
570
+ Safe, reversible database schema changes for production systems.
571
+
572
+ ## When to Activate
573
+
574
+ - Creating or altering database tables
575
+ - Adding/removing columns or indexes
576
+ - Running data migrations (backfill, transform)
577
+ - Planning zero-downtime schema changes
578
+ - Setting up migration tooling for a new project
579
+
580
+ ## Core Principles
581
+
582
+ 1. **Every change is a migration** — never alter production databases manually
583
+ 2. **Migrations are forward-only in production** — rollbacks use new forward migrations
584
+ 3. **Schema and data migrations are separate** — never mix DDL and DML in one migration
585
+ 4. **Test migrations against production-sized data** — a migration that works on 100 rows may lock on 10M
586
+ 5. **Migrations are immutable once deployed** — never edit a migration that has run in production
587
+
588
+ ## Migration Safety Checklist
589
+
590
+ Before applying any migration:
591
+
592
+ - [ ] Migration has both UP and DOWN (or is explicitly marked irreversible)
593
+ - [ ] No full table locks on large tables (use concurrent operations)
594
+ - [ ] New columns have defaults or are nullable (never add NOT NULL without default)
595
+ - [ ] Indexes created concurrently (not inline with CREATE TABLE for existing tables)
596
+ - [ ] Data backfill is a separate migration from schema change
597
+ - [ ] Tested against a copy of production data
598
+ - [ ] Rollback plan documented
599
+
600
+ ## PostgreSQL Patterns
601
+
602
+ ### Adding a Column Safely
603
+
604
+ ```sql
605
+ -- GOOD: Nullable column, no lock
606
+ ALTER TABLE users ADD COLUMN avatar_url TEXT;
607
+
608
+ -- GOOD: Column with default (Postgres 11+ is instant, no rewrite)
609
+ ALTER TABLE users ADD COLUMN is_active BOOLEAN NOT NULL DEFAULT true;
610
+
611
+ -- BAD: NOT NULL without default on existing table (requires full rewrite)
612
+ ALTER TABLE users ADD COLUMN role TEXT NOT NULL;
613
+ -- This locks the table and rewrites every row
614
+ ```
615
+
616
+ ### Adding an Index Without Downtime
617
+
618
+ ```sql
619
+ -- BAD: Blocks writes on large tables
620
+ CREATE INDEX idx_users_email ON users (email);
621
+
622
+ -- GOOD: Non-blocking, allows concurrent writes
623
+ CREATE INDEX CONCURRENTLY idx_users_email ON users (email);
624
+
625
+ -- Note: CONCURRENTLY cannot run inside a transaction block
626
+ -- Most migration tools need special handling for this
627
+ ```
628
+
629
+ ### Renaming a Column (Zero-Downtime)
630
+
631
+ Never rename directly in production. Use the expand-contract pattern:
632
+
633
+ ```sql
634
+ -- Step 1: Add new column (migration 001)
635
+ ALTER TABLE users ADD COLUMN display_name TEXT;
636
+
637
+ -- Step 2: Backfill data (migration 002, data migration)
638
+ UPDATE users SET display_name = username WHERE display_name IS NULL;
639
+
640
+ -- Step 3: Update application code to read/write both columns
641
+ -- Deploy application changes
642
+
643
+ -- Step 4: Stop writing to old column, drop it (migration 003)
644
+ ALTER TABLE users DROP COLUMN username;
645
+ ```
646
+
647
+ ### Removing a Column Safely
648
+
649
+ ```sql
650
+ -- Step 1: Remove all application references to the column
651
+ -- Step 2: Deploy application without the column reference
652
+ -- Step 3: Drop column in next migration
653
+ ALTER TABLE orders DROP COLUMN legacy_status;
654
+
655
+ -- For Django: use SeparateDatabaseAndState to remove from model
656
+ -- without generating DROP COLUMN (then drop in next migration)
657
+ ```
658
+
659
+ ### Large Data Migrations
660
+
661
+ ```sql
662
+ -- BAD: Updates all rows in one transaction (locks table)
663
+ UPDATE users SET normalized_email = LOWER(email);
664
+
665
+ -- GOOD: Batch update with progress
666
+ DO $$
667
+ DECLARE
668
+ batch_size INT := 10000;
669
+ rows_updated INT;
670
+ BEGIN
671
+ LOOP
672
+ UPDATE users
673
+ SET normalized_email = LOWER(email)
674
+ WHERE id IN (
675
+ SELECT id FROM users
676
+ WHERE normalized_email IS NULL
677
+ LIMIT batch_size
678
+ FOR UPDATE SKIP LOCKED
679
+ );
680
+ GET DIAGNOSTICS rows_updated = ROW_COUNT;
681
+ RAISE NOTICE 'Updated % rows', rows_updated;
682
+ EXIT WHEN rows_updated = 0;
683
+ COMMIT;
684
+ END LOOP;
685
+ END $$;
686
+ ```
687
+
688
+ ## Prisma (TypeScript/Node.js)
689
+
690
+ ### Workflow
691
+
692
+ ```bash
693
+ # Create migration from schema changes
694
+ npx prisma migrate dev --name add_user_avatar
695
+
696
+ # Apply pending migrations in production
697
+ npx prisma migrate deploy
698
+
699
+ # Reset database (dev only)
700
+ npx prisma migrate reset
701
+
702
+ # Generate client after schema changes
703
+ npx prisma generate
704
+ ```
705
+
706
+ ### Schema Example
707
+
708
+ ```prisma
709
+ model User {
710
+ id String @id @default(cuid())
711
+ email String @unique
712
+ name String?
713
+ avatarUrl String? @map("avatar_url")
714
+ createdAt DateTime @default(now()) @map("created_at")
715
+ updatedAt DateTime @updatedAt @map("updated_at")
716
+ orders Order[]
717
+
718
+ @@map("users")
719
+ @@index([email])
720
+ }
721
+ ```
722
+
723
+ ### Custom SQL Migration
724
+
725
+ For operations Prisma cannot express (concurrent indexes, data backfills):
726
+
727
+ ```bash
728
+ # Create empty migration, then edit the SQL manually
729
+ npx prisma migrate dev --create-only --name add_email_index
730
+ ```
731
+
732
+ ```sql
733
+ -- migrations/20240115_add_email_index/migration.sql
734
+ -- Prisma cannot generate CONCURRENTLY, so we write it manually
735
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_email ON users (email);
736
+ ```
737
+
738
+ ## Drizzle (TypeScript/Node.js)
739
+
740
+ ### Workflow
741
+
742
+ ```bash
743
+ # Generate migration from schema changes
744
+ npx drizzle-kit generate
745
+
746
+ # Apply migrations
747
+ npx drizzle-kit migrate
748
+
749
+ # Push schema directly (dev only, no migration file)
750
+ npx drizzle-kit push
751
+ ```
752
+
753
+ ### Schema Example
754
+
755
+ ```typescript
756
+ import { pgTable, text, timestamp, uuid, boolean } from "drizzle-orm/pg-core";
757
+
758
+ export const users = pgTable("users", {
759
+ id: uuid("id").primaryKey().defaultRandom(),
760
+ email: text("email").notNull().unique(),
761
+ name: text("name"),
762
+ isActive: boolean("is_active").notNull().default(true),
763
+ createdAt: timestamp("created_at").notNull().defaultNow(),
764
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
765
+ });
766
+ ```
767
+
768
+ ## Kysely (TypeScript/Node.js)
769
+
770
+ ### Workflow (kysely-ctl)
771
+
772
+ ```bash
773
+ # Initialize config file (kysely.config.ts)
774
+ kysely init
775
+
776
+ # Create a new migration file
777
+ kysely migrate make add_user_avatar
778
+
779
+ # Apply all pending migrations
780
+ kysely migrate latest
781
+
782
+ # Rollback last migration
783
+ kysely migrate down
784
+
785
+ # Show migration status
786
+ kysely migrate list
787
+ ```
788
+
789
+ ### Migration File
790
+
791
+ ```typescript
792
+ // migrations/2024_01_15_001_create_user_profile.ts
793
+ import { type Kysely, sql } from 'kysely'
794
+
795
+ // IMPORTANT: Always use Kysely<any>, not your typed DB interface.
796
+ // Migrations are frozen in time and must not depend on current schema types.
797
+ export async function up(db: Kysely<any>): Promise<void> {
798
+ await db.schema
799
+ .createTable('user_profile')
800
+ .addColumn('id', 'serial', (col) => col.primaryKey())
801
+ .addColumn('email', 'varchar(255)', (col) => col.notNull().unique())
802
+ .addColumn('avatar_url', 'text')
803
+ .addColumn('created_at', 'timestamp', (col) =>
804
+ col.defaultTo(sql`now()`).notNull()
805
+ )
806
+ .execute()
807
+
808
+ await db.schema
809
+ .createIndex('idx_user_profile_avatar')
810
+ .on('user_profile')
811
+ .column('avatar_url')
812
+ .execute()
813
+ }
814
+
815
+ export async function down(db: Kysely<any>): Promise<void> {
816
+ await db.schema.dropTable('user_profile').execute()
817
+ }
818
+ ```
819
+
820
+ ### Programmatic Migrator
821
+
822
+ ```typescript
823
+ import { Migrator, FileMigrationProvider } from 'kysely'
824
+ import { promises as fs } from 'fs'
825
+ import * as path from 'path'
826
+ // ESM only — CJS can use __dirname directly
827
+ import { fileURLToPath } from 'url'
828
+ const migrationFolder = path.join(
829
+ path.dirname(fileURLToPath(import.meta.url)),
830
+ './migrations',
831
+ )
832
+
833
+ // `db` is your Kysely<any> database instance
834
+ const migrator = new Migrator({
835
+ db,
836
+ provider: new FileMigrationProvider({
837
+ fs,
838
+ path,
839
+ migrationFolder,
840
+ }),
841
+ // WARNING: Only enable in development. Disables timestamp-ordering
842
+ // validation, which can cause schema drift between environments.
843
+ // allowUnorderedMigrations: true,
844
+ })
845
+
846
+ const { error, results } = await migrator.migrateToLatest()
847
+
848
+ results?.forEach((it) => {
849
+ if (it.status === 'Success') {
850
+ console.log(`migration "${it.migrationName}" executed successfully`)
851
+ } else if (it.status === 'Error') {
852
+ console.error(`failed to execute migration "${it.migrationName}"`)
853
+ }
854
+ })
855
+
856
+ if (error) {
857
+ console.error('migration failed', error)
858
+ process.exit(1)
859
+ }
860
+ ```
861
+
862
+ ## Django (Python)
863
+
864
+ ### Workflow
865
+
866
+ ```bash
867
+ # Generate migration from model changes
868
+ python manage.py makemigrations
869
+
870
+ # Apply migrations
871
+ python manage.py migrate
872
+
873
+ # Show migration status
874
+ python manage.py showmigrations
875
+
876
+ # Generate empty migration for custom SQL
877
+ python manage.py makemigrations --empty app_name -n description
878
+ ```
879
+
880
+ ### Data Migration
881
+
882
+ ```python
883
+ from django.db import migrations
884
+
885
+ def backfill_display_names(apps, schema_editor):
886
+ User = apps.get_model("accounts", "User")
887
+ batch_size = 5000
888
+ users = User.objects.filter(display_name="")
889
+ while users.exists():
890
+ batch = list(users[:batch_size])
891
+ for user in batch:
892
+ user.display_name = user.username
893
+ User.objects.bulk_update(batch, ["display_name"], batch_size=batch_size)
894
+
895
+ def reverse_backfill(apps, schema_editor):
896
+ pass # Data migration, no reverse needed
897
+
898
+ class Migration(migrations.Migration):
899
+ dependencies = [("accounts", "0015_add_display_name")]
900
+
901
+ operations = [
902
+ migrations.RunPython(backfill_display_names, reverse_backfill),
903
+ ]
904
+ ```
905
+
906
+ ### SeparateDatabaseAndState
907
+
908
+ Remove a column from the Django model without dropping it from the database immediately:
909
+
910
+ ```python
911
+ class Migration(migrations.Migration):
912
+ operations = [
913
+ migrations.SeparateDatabaseAndState(
914
+ state_operations=[
915
+ migrations.RemoveField(model_name="user", name="legacy_field"),
916
+ ],
917
+ database_operations=[], # Don't touch the DB yet
918
+ ),
919
+ ]
920
+ ```
921
+
922
+ ## golang-migrate (Go)
923
+
924
+ ### Workflow
925
+
926
+ ```bash
927
+ # Create migration pair
928
+ migrate create -ext sql -dir migrations -seq add_user_avatar
929
+
930
+ # Apply all pending migrations
931
+ migrate -path migrations -database "$DATABASE_URL" up
932
+
933
+ # Rollback last migration
934
+ migrate -path migrations -database "$DATABASE_URL" down 1
935
+
936
+ # Force version (fix dirty state)
937
+ migrate -path migrations -database "$DATABASE_URL" force VERSION
938
+ ```
939
+
940
+ ### Migration Files
941
+
942
+ ```sql
943
+ -- migrations/000003_add_user_avatar.up.sql
944
+ ALTER TABLE users ADD COLUMN avatar_url TEXT;
945
+ CREATE INDEX CONCURRENTLY idx_users_avatar ON users (avatar_url) WHERE avatar_url IS NOT NULL;
946
+
947
+ -- migrations/000003_add_user_avatar.down.sql
948
+ DROP INDEX IF EXISTS idx_users_avatar;
949
+ ALTER TABLE users DROP COLUMN IF EXISTS avatar_url;
950
+ ```
951
+
952
+ ## Zero-Downtime Migration Strategy
953
+
954
+ For critical production changes, follow the expand-contract pattern:
955
+
956
+ ```
957
+ Phase 1: EXPAND
958
+ - Add new column/table (nullable or with default)
959
+ - Deploy: app writes to BOTH old and new
960
+ - Backfill existing data
961
+
962
+ Phase 2: MIGRATE
963
+ - Deploy: app reads from NEW, writes to BOTH
964
+ - Verify data consistency
965
+
966
+ Phase 3: CONTRACT
967
+ - Deploy: app only uses NEW
968
+ - Drop old column/table in separate migration
969
+ ```
970
+
971
+ ### Timeline Example
972
+
973
+ ```
974
+ Day 1: Migration adds new_status column (nullable)
975
+ Day 1: Deploy app v2 — writes to both status and new_status
976
+ Day 2: Run backfill migration for existing rows
977
+ Day 3: Deploy app v3 — reads from new_status only
978
+ Day 7: Migration drops old status column
979
+ ```
980
+
981
+ ## Anti-Patterns
982
+
983
+ | Anti-Pattern | Why It Fails | Better Approach |
984
+ |-------------|-------------|-----------------|
985
+ | Manual SQL in production | No audit trail, unrepeatable | Always use migration files |
986
+ | Editing deployed migrations | Causes drift between environments | Create new migration instead |
987
+ | NOT NULL without default | Locks table, rewrites all rows | Add nullable, backfill, then add constraint |
988
+ | Inline index on large table | Blocks writes during build | CREATE INDEX CONCURRENTLY |
989
+ | Schema + data in one migration | Hard to rollback, long transactions | Separate migrations |
990
+ | Dropping column before removing code | Application errors on missing column | Remove code first, drop column next deploy |