oh-my-customcode 0.44.4 → 0.44.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  **[한국어 문서 (Korean)](./README_ko.md)**
15
15
 
16
- 44 agents. 76 skills. 21 rules. One command.
16
+ 45 agents. 77 skills. 21 rules. One command.
17
17
 
18
18
  ```bash
19
19
  npm install -g oh-my-customcode && cd your-project && omcustom init
@@ -117,7 +117,7 @@ Agent(arch-documenter):haiku ┘
117
117
 
118
118
  ---
119
119
 
120
- ### Agents (44)
120
+ ### Agents (45)
121
121
 
122
122
  | Category | Count | Agents |
123
123
  |----------|-------|--------|
@@ -125,7 +125,7 @@ Agent(arch-documenter):haiku ┘
125
125
  | Backend | 6 | be-fastapi, be-springboot, be-go-backend, be-express, be-nestjs, be-django |
126
126
  | Frontend | 4 | fe-vercel, fe-vuejs, fe-svelte, fe-flutter |
127
127
  | Data Engineering | 6 | de-airflow, de-dbt, de-spark, de-kafka, de-snowflake, de-pipeline |
128
- | Database | 3 | db-supabase, db-postgres, db-redis |
128
+ | Database | 4 | db-supabase, db-postgres, db-redis, db-alembic |
129
129
  | Tooling | 3 | tool-npm, tool-optimizer, tool-bun |
130
130
  | Architecture | 2 | arch-documenter, arch-speckit |
131
131
  | Infrastructure | 2 | infra-docker, infra-aws |
@@ -138,11 +138,11 @@ Each agent declares its tools, model, memory scope, and limitations in YAML fron
138
138
 
139
139
  ---
140
140
 
141
- ### Skills (76)
141
+ ### Skills (77)
142
142
 
143
143
  | Category | Count | Includes |
144
144
  |----------|-------|----------|
145
- | Best Practices | 23 | Go, Python, TypeScript, Kotlin, Rust, React, FastAPI, Spring Boot, Django, Flutter, Docker, AWS, Postgres, Redis, Kafka, dbt, Spark, Snowflake, Airflow, pipeline-architecture-patterns, and more |
145
+ | Best Practices | 24 | Go, Python, TypeScript, Kotlin, Rust, React, FastAPI, Spring Boot, Django, Flutter, Docker, AWS, Postgres, Redis, Kafka, dbt, Spark, Snowflake, Airflow, pipeline-architecture-patterns, alembic, and more |
146
146
  | Routing | 4 | secretary, dev-lead, de-lead, qa-lead |
147
147
  | Workflow | 12 | structured-dev-cycle, deep-plan, research, evaluator-optimizer, dag-orchestration, worker-reviewer-pipeline, reasoning-sandwich, and more |
148
148
  | Development | 7 | dev-review, dev-refactor, analysis, create-agent, intent-detection, web-design-guidelines, omcustom-takeover |
@@ -215,7 +215,7 @@ Key rules: R010 (orchestrator never writes files), R009 (parallel execution mand
215
215
 
216
216
  ---
217
217
 
218
- ### Guides (25)
218
+ ### Guides (26)
219
219
 
220
220
  Reference documentation covering best practices, architecture decisions, and integration patterns. Located in `guides/` at project root, covering topics from agent design to CI/CD to observability.
221
221
 
@@ -256,15 +256,15 @@ omcustom security # Scan for security issues
256
256
  your-project/
257
257
  ├── CLAUDE.md # Entry point
258
258
  ├── .claude/
259
- │ ├── agents/ # 44 agent definitions
260
- │ ├── skills/ # 75 skill modules
259
+ │ ├── agents/ # 45 agent definitions
260
+ │ ├── skills/ # 77 skill modules
261
261
  │ ├── rules/ # 21 governance rules (R000-R021)
262
262
  │ ├── hooks/ # 15 lifecycle hook scripts
263
263
  │ ├── schemas/ # Tool input validation schemas
264
264
  │ ├── specs/ # Extracted canonical specs
265
265
  │ ├── contexts/ # 4 shared context files
266
266
  │ └── ontology/ # Knowledge graph for RAG
267
- └── guides/ # 25 reference documents
267
+ └── guides/ # 26 reference documents
268
268
  ```
269
269
 
270
270
  ---
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "oh-my-customcode",
3
3
  "workspaces": ["packages/*"],
4
- "version": "0.44.4",
4
+ "version": "0.44.6",
5
5
  "description": "Batteries-included agent harness for Claude Code",
6
6
  "type": "module",
7
7
  "bin": {
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: db-alembic-expert
3
+ description: Alembic migration specialist for generating, reviewing, fixing, and advising on SQLAlchemy database migrations
4
+ model: sonnet
5
+ domain: backend
6
+ memory: project
7
+ effort: high
8
+ tools:
9
+ - Read
10
+ - Write
11
+ - Edit
12
+ - Grep
13
+ - Glob
14
+ - Bash
15
+ skills:
16
+ - alembic-best-practices
17
+ - postgres-best-practices
18
+ escalation:
19
+ enabled: true
20
+ path: sonnet → opus
21
+ threshold: 2
22
+ limitations:
23
+ - "cannot apply migrations directly to production databases"
24
+ - "cannot resolve application-level data backfill logic without domain context"
25
+ - "cannot detect rename intent without git diff context or explicit user instruction"
26
+ ---
27
+
28
+ # db-alembic-expert
29
+
30
+ Alembic migration lifecycle specialist. Generates, reviews, fixes, and advises on SQLAlchemy database migrations with a focus on safety, zero-downtime deployment, and PostgreSQL best practices.
31
+
32
+ ## Purpose
33
+
34
+ Manage the complete Alembic migration lifecycle: autogenerate from SQLAlchemy models, review generated scripts for dangerous patterns, enforce naming conventions, configure env.py for async and multi-tenant setups, and integrate migrations into CI pipelines.
35
+
36
+ ## Key Capabilities
37
+
38
+ 1. **Migration Generation** — Run `alembic revision --autogenerate` and perform a post-generation safety review before any migration is committed
39
+ 2. **Dangerous Pattern Detection** — Identify rename-as-drop+add sequences, anonymous constraints, lock-risky operations (non-concurrent index creation, NOT NULL without server default on large tables), and missing downgrade paths
40
+ 3. **Expand-Contract Pattern** — Design and implement zero-downtime migrations across three phases: Expand (add nullable column), Migrate (backfill data), Contract (enforce NOT NULL, drop old column)
41
+ 4. **env.py Configuration** — Set up sync, async (asyncpg/asyncio), multi-tenant, and multi-database environments with proper connection URL handling and credential sourcing from environment variables
42
+ 5. **pytest-alembic Testing** — Configure built-in tests (single_head_revision, upgrade, model_match, up_down_consistency) and custom pre/post migration data checks
43
+ 6. **alembic-utils Integration** — Manage PostgreSQL-specific objects (views, functions, triggers, row-level security policies) as Replaceable Objects with proper dependency ordering
44
+ 7. **CI Integration** — Configure `alembic check` for pending-migration detection and Squawk linter for lock-risk DDL analysis in GitHub Actions or similar pipelines
45
+
46
+ ## Workflow
47
+
48
+ ```
49
+ 1. Read SQLAlchemy models and existing migration history
50
+ 2. Run autogenerate (or inspect provided migration script)
51
+ 3. Review generated ops against dangerous pattern checklist (alembic-best-practices)
52
+ 4. Flag any CRITICAL risks with explanation and safer alternatives
53
+ 5. Fix naming conventions, add missing downgrade logic, restructure if needed
54
+ 6. Advise on expand-contract phasing for breaking schema changes
55
+ 7. Recommend test coverage via pytest-alembic
56
+ ```
57
+
58
+ ## Safety Rules
59
+
60
+ - **Never auto-fix column renames** without explicit user confirmation — autogenerate cannot distinguish rename from drop+add
61
+ - **Always flag downgrade gaps** — `pass` in `downgrade()` is acceptable only when explicitly justified
62
+ - **Never embed credentials** in `alembic.ini` or `env.py` — always source from `os.environ`
63
+ - **Require CONCURRENTLY** for index operations on large tables to avoid table-level locks
64
+ - **Validate naming_convention** is set on MetaData before generating constraint-related migrations
65
+
66
+ ## Collaboration
67
+
68
+ | Agent | When to involve |
69
+ |-------|----------------|
70
+ | db-postgres-expert | PostgreSQL-specific DDL nuances, partitioning, JSONB patterns |
71
+ | be-fastapi-expert | Async engine configuration, lifespan integration, dependency injection |
72
+ | qa-engineer | Migration test strategy, rollback testing, data integrity checks |
@@ -0,0 +1,294 @@
1
+ ---
2
+ name: alembic-best-practices
3
+ description: Alembic migration patterns for naming conventions, safety checks, expand-contract, env.py configuration, and CI integration
4
+ scope: core
5
+ version: 1.0.0
6
+ ---
7
+
8
+ # Alembic Best Practices
9
+
10
+ Reference patterns for safe, maintainable Alembic database migrations.
11
+
12
+ ## 1. Naming Convention
13
+
14
+ Always set `naming_convention` on `MetaData` before autogenerate runs. Without it, constraint names are database-generated and differ across engines, causing migration drift.
15
+
16
+ ```python
17
+ from sqlalchemy import MetaData
18
+
19
+ convention = {
20
+ "ix": "ix_%(column_0_label)s",
21
+ "uq": "uq_%(table_name)s_%(column_0_N_name)s",
22
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
23
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
24
+ "pk": "pk_%(table_name)s",
25
+ }
26
+
27
+ metadata = MetaData(naming_convention=convention)
28
+ ```
29
+
30
+ Set `file_template` in `alembic.ini` for timestamp-prefixed filenames:
31
+
32
+ ```ini
33
+ file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(rev)s_%%(slug)s
34
+ ```
35
+
36
+ ## 2. Credential Management
37
+
38
+ **NEVER** store database credentials in `alembic.ini` or commit them to version control.
39
+
40
+ Override `sqlalchemy.url` in `env.py` from environment variables:
41
+
42
+ ```python
43
+ # env.py — override alembic.ini URL with environment variable
44
+ import os
45
+ from alembic import context
46
+
47
+ config = context.config
48
+ db_url = os.environ.get("DATABASE_URL")
49
+ if db_url:
50
+ config.set_main_option("sqlalchemy.url", db_url)
51
+ ```
52
+
53
+ For PostgreSQL + asyncpg, ensure the sync URL uses `postgresql+psycopg2` (or `postgresql`) for offline/sync contexts and `postgresql+asyncpg` only for async contexts.
54
+
55
+ ## 3. Autogenerate Trust Matrix
56
+
57
+ Autogenerate is a starting point, not a final answer. Always review generated scripts.
58
+
59
+ | Object / Change | Autogenerate Detects | Notes |
60
+ |-----------------|----------------------|-------|
61
+ | Table add/drop | Yes | Reliable |
62
+ | Column add/drop | Yes | Reliable |
63
+ | Column type change | Partial | Type equivalence varies by backend |
64
+ | Column rename | **Never** | Generates drop+add — will destroy data |
65
+ | Index add/drop | Yes | Only if reflected or declared |
66
+ | Named constraint add/drop | Yes | Requires `naming_convention` on MetaData |
67
+ | Anonymous constraint | **No** | No name = no detection |
68
+ | Default value change | Partial | Server defaults vs client defaults differ |
69
+ | PostgreSQL views / functions | **No** | Use alembic-utils |
70
+ | PostgreSQL enum add value | Partial | Requires `alembic-postgresql-enum` |
71
+ | Sequence changes | No | Manual op required |
72
+
73
+ ## 4. Dangerous Pattern Detection
74
+
75
+ Review every generated migration against this checklist before committing:
76
+
77
+ **CRITICAL — Review Required:**
78
+
79
+ - [ ] `op.drop_column` + `op.add_column` on the same column name → likely unintended rename; confirm with user
80
+ - [ ] `op.create_foreign_key(None, ...)` → anonymous FK; must have an explicit name
81
+ - [ ] `op.add_column` with `nullable=False` and no `server_default` on a non-empty table → full-table rewrite, lock risk
82
+ - [ ] `op.create_index` without `postgresql_concurrently=True` on a large table → table-level lock
83
+ - [ ] `op.drop_table` or `op.drop_column` → confirm there are no application references
84
+ - [ ] Empty `def downgrade(): pass` → document justification or implement rollback
85
+ - [ ] `op.alter_column` type change across incompatible types (e.g., `VARCHAR` → `INTEGER`) → data loss risk
86
+
87
+ **WARNING — Verify Intent:**
88
+
89
+ - [ ] Multiple heads detected (`alembic heads` shows 2+) → merge before deploying
90
+ - [ ] `batch_alter_table` missing for SQLite → required for constraint modifications on SQLite
91
+ - [ ] `render_as_batch=True` not set in `env.py` for SQLite projects
92
+
93
+ ## 5. Expand-Contract Pattern
94
+
95
+ For zero-downtime schema changes on live tables, use three separate migration phases:
96
+
97
+ **Phase 1 — Expand** (deploy without application changes):
98
+ ```python
99
+ def upgrade():
100
+ op.add_column("users", sa.Column("email_new", sa.String(255), nullable=True))
101
+
102
+ def downgrade():
103
+ op.drop_column("users", "email_new")
104
+ ```
105
+
106
+ **Phase 2 — Migrate** (data backfill, can run during deploy):
107
+ ```python
108
+ def upgrade():
109
+ op.execute("""
110
+ UPDATE users SET email_new = email WHERE email_new IS NULL
111
+ """)
112
+
113
+ def downgrade():
114
+ pass # Data loss acceptable; backfill was additive
115
+ ```
116
+
117
+ **Phase 3 — Contract** (after all application nodes use new column):
118
+ ```python
119
+ def upgrade():
120
+ with op.batch_alter_table("users") as batch_op:
121
+ batch_op.alter_column("email_new", nullable=False)
122
+ op.drop_column("users", "email")
123
+ op.alter_column("users", "email_new", new_column_name="email")
124
+
125
+ def downgrade():
126
+ op.add_column("users", sa.Column("email", sa.String(255), nullable=True))
127
+ op.execute("UPDATE users SET email = email_new WHERE email IS NULL")
128
+ op.drop_column("users", "email_new")
129
+ ```
130
+
131
+ ## 6. Async env.py
132
+
133
+ Canonical pattern for async SQLAlchemy (asyncpg) with Alembic:
134
+
135
+ ```python
136
+ # env.py — async configuration
137
+ import asyncio
138
+ from logging.config import fileConfig
139
+
140
+ from sqlalchemy import pool
141
+ from sqlalchemy.ext.asyncio import async_engine_from_config
142
+
143
+ from alembic import context
144
+ from myapp.models import Base # Import all models here
145
+
146
+ config = context.config
147
+ fileConfig(config.config_file_name)
148
+ target_metadata = Base.metadata
149
+
150
+
151
+ def run_migrations_offline() -> None:
152
+ url = config.get_main_option("sqlalchemy.url")
153
+ context.configure(
154
+ url=url,
155
+ target_metadata=target_metadata,
156
+ literal_binds=True,
157
+ dialect_opts={"paramstyle": "named"},
158
+ )
159
+ with context.begin_transaction():
160
+ context.run_migrations()
161
+
162
+
163
+ def do_run_migrations(connection):
164
+ context.configure(connection=connection, target_metadata=target_metadata)
165
+ with context.begin_transaction():
166
+ context.run_migrations()
167
+
168
+
169
+ async def run_migrations_online() -> None:
170
+ connectable = async_engine_from_config(
171
+ config.get_section(config.config_ini_section, {}),
172
+ prefix="sqlalchemy.",
173
+ poolclass=pool.NullPool, # Required: avoids pool issues during migration
174
+ )
175
+ async with connectable.connect() as connection:
176
+ await connection.run_sync(do_run_migrations)
177
+ await connectable.dispose()
178
+
179
+
180
+ if context.is_offline_mode():
181
+ run_migrations_offline()
182
+ else:
183
+ asyncio.run(run_migrations_online())
184
+ ```
185
+
186
+ Key points:
187
+ - Use `NullPool` — migration scripts are one-shot; pooling causes connection leaks
188
+ - Import ALL models in `env.py` (directly or via a central `models/__init__.py`) so autogenerate sees every table
189
+ - `run_sync` bridges the async connection back to Alembic's sync API
190
+
191
+ ## 7. Testing with pytest-alembic
192
+
193
+ Install: `pip install pytest-alembic`
194
+
195
+ Built-in tests (all enabled by default):
196
+
197
+ ```python
198
+ # conftest.py
199
+ import pytest
200
+ from sqlalchemy import create_engine
201
+ from alembic.config import Config
202
+
203
+ @pytest.fixture
204
+ def alembic_config():
205
+ return Config("alembic.ini")
206
+
207
+ @pytest.fixture
208
+ def alembic_engine():
209
+ return create_engine("postgresql://test_user:test_pass@localhost/test_db")
210
+ ```
211
+
212
+ Built-in test assertions:
213
+ - `test_single_head_revision` — exactly one head revision exists
214
+ - `test_upgrade` — all upgrades apply without error
215
+ - `test_model_definitions_match_ddl` — SQLAlchemy models match the migrated schema
216
+ - `test_up_down_consistency` — every upgrade can be cleanly downgraded
217
+
218
+ Custom data migration test:
219
+ ```python
220
+ @pytest.mark.alembic
221
+ def test_user_email_backfill(alembic_runner):
222
+ # Insert data before migration
223
+ alembic_runner.migrate_up_before("abc123def456")
224
+ alembic_runner.insert_into("users", [{"id": 1, "email": "test@example.com"}])
225
+
226
+ # Apply the migration
227
+ alembic_runner.migrate_up_one()
228
+
229
+ # Assert post-migration state
230
+ result = alembic_runner.execute("SELECT email_new FROM users WHERE id = 1")
231
+ assert result.scalar() == "test@example.com"
232
+ ```
233
+
234
+ ## 8. CI Integration
235
+
236
+ **Detect uncommitted migrations** — fail CI if models changed but no migration was generated:
237
+
238
+ ```yaml
239
+ # .github/workflows/migrations.yml
240
+ - name: Check for pending migrations
241
+ run: alembic check
242
+ env:
243
+ DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
244
+ ```
245
+
246
+ **Squawk** — static analysis for lock-risk DDL (no live DB required):
247
+
248
+ ```yaml
249
+ - name: Install Squawk
250
+ run: pip install squawk-cli # or: brew install squawk
251
+
252
+ - name: Lint migrations for lock risks
253
+ run: |
254
+ alembic upgrade head --sql > migration.sql
255
+ squawk migration.sql
256
+ ```
257
+
258
+ Squawk detects: non-concurrent index creation, adding NOT NULL without default, renaming columns, dropping constraints without cascade, and other patterns that cause long locks.
259
+
260
+ ## 9. Extensions
261
+
262
+ | Package | Purpose | Install |
263
+ |---------|---------|---------|
264
+ | `alembic-utils` | Replaceable PG objects: views, functions, triggers, RLS policies | `pip install alembic-utils` |
265
+ | `alembic-postgresql-enum` | Safe enum value additions without full table rewrites | `pip install alembic-postgresql-enum` |
266
+ | `audit-alembic` | Attach migration metadata to audit log tables | `pip install audit-alembic` |
267
+ | `sqla-utils` | Additional SQLAlchemy model utilities complementing alembic-utils | `pip install sqla-utils` |
268
+
269
+ ### alembic-utils example (PostgreSQL view):
270
+
271
+ ```python
272
+ from alembic_utils.pg_view import PGView
273
+
274
+ user_summary_view = PGView(
275
+ schema="public",
276
+ signature="user_summary",
277
+ definition="SELECT id, email, created_at FROM users WHERE active = true",
278
+ )
279
+
280
+ # In env.py — register with autogenerate
281
+ from alembic_utils.replaceable_entity import register_entities
282
+ register_entities([user_summary_view])
283
+ ```
284
+
285
+ ## 10. Common Pitfalls
286
+
287
+ | Pitfall | Symptom | Fix |
288
+ |---------|---------|-----|
289
+ | Empty `target_metadata` | Autogenerate produces empty migration | Import all models in `env.py` before `Base.metadata` is referenced |
290
+ | Multiple heads | `alembic upgrade head` fails with merge conflict error | Run `alembic merge heads -m "merge"` |
291
+ | SQLite constraint modification | `NotImplementedError` on `op.alter_column` | Use `op.batch_alter_table` context manager |
292
+ | asyncpg URL in offline mode | `Can't load plugin: sqlalchemy.dialects:postgresql+asyncpg` | Use sync URL (`postgresql://`) for offline mode; override only for async online mode |
293
+ | Missing model imports | Tables not detected by autogenerate | Add `from myapp import models` to `env.py` (not just `Base`) |
294
+ | `server_default` vs `default` | `server_default` needed for NOT NULL on existing rows | Use `server_default=sa.text("''")`; remove it in the Contract phase |
@@ -0,0 +1,438 @@
1
+ # Alembic Guide
2
+
3
+ Database migration framework for SQLAlchemy. Current stable: **1.18.x** (2025).
4
+
5
+ ## Overview
6
+
7
+ Alembic is the de-facto migration tool for SQLAlchemy projects. It manages the evolution of a relational database schema over time through versioned migration scripts organized as a directed acyclic graph (DAG). Each migration is a Python file with `upgrade()` and `downgrade()` functions that emit SQLAlchemy Core operations or raw SQL.
8
+
9
+ **Relationship to SQLAlchemy**: Alembic depends on SQLAlchemy but operates at the DDL (schema) level, not the DML (data query) level. It uses SQLAlchemy's introspection API to compare the current database state against declared models, then generates the difference as migration ops.
10
+
11
+ **Philosophy**: Unlike Rails migrations or Django migrations, Alembic generates migration code that developers are expected to review and edit. Autogenerate is a starting point, not a final answer.
12
+
13
+ ---
14
+
15
+ ## Core Concepts
16
+
17
+ ### Revision Chain (DAG)
18
+
19
+ Each migration file has:
20
+ - `revision`: unique identifier (e.g., `a1b2c3d4e5f6`)
21
+ - `down_revision`: parent revision(s) — `None` for the first migration, a tuple for merge points
22
+
23
+ The chain forms a DAG. `alembic upgrade head` walks the graph from the current DB revision to the tip(s). Branches can exist and are resolved with merge migrations.
24
+
25
+ ```
26
+ None → a1b2 → c3d4 → e5f6 (head)
27
+ ↘ g7h8 → merge(e5f6, g7h8) (merged head)
28
+ ```
29
+
30
+ ### env.py
31
+
32
+ The bridge between Alembic and the application. Responsibilities:
33
+ 1. Configure the SQLAlchemy engine/connection URL
34
+ 2. Set `target_metadata` (SQLAlchemy `MetaData` with all models imported)
35
+ 3. Define `run_migrations_offline()` and `run_migrations_online()` functions
36
+ 4. Optionally override `alembic.ini` settings (e.g., inject `DATABASE_URL` from env vars)
37
+
38
+ ### alembic.ini
39
+
40
+ Main configuration file. Key settings:
41
+ - `script_location` — path to `alembic/` directory
42
+ - `sqlalchemy.url` — database URL (should be overridden in `env.py` from env vars)
43
+ - `file_template` — naming pattern for generated migration files
44
+ - `prepend_sys_path` — adds project root to `sys.path` so `env.py` can import models
45
+
46
+ ### script.py.mako
47
+
48
+ Mako template used to generate new migration files. Default template includes the `revision`, `down_revision`, `branch_labels`, `depends_on` header and empty `upgrade()`/`downgrade()` stubs.
49
+
50
+ ---
51
+
52
+ ## CLI Reference
53
+
54
+ | Command | Description |
55
+ |---------|-------------|
56
+ | `alembic init <dir>` | Initialize a new Alembic environment in `<dir>` |
57
+ | `alembic revision -m "message"` | Create a new empty migration |
58
+ | `alembic revision --autogenerate -m "message"` | Generate migration from model diff |
59
+ | `alembic upgrade head` | Apply all pending migrations |
60
+ | `alembic upgrade +1` | Apply the next one migration |
61
+ | `alembic upgrade <rev>` | Apply migrations up to `<rev>` |
62
+ | `alembic downgrade -1` | Revert the most recent migration |
63
+ | `alembic downgrade base` | Revert all migrations |
64
+ | `alembic downgrade <rev>` | Revert to `<rev>` |
65
+ | `alembic current` | Show current revision(s) applied to the DB |
66
+ | `alembic history` | Show full revision history |
67
+ | `alembic heads` | Show all head revisions (should be 1 in linear projects) |
68
+ | `alembic branches` | Show branch points |
69
+ | `alembic merge <rev1> <rev2> -m "merge"` | Create a merge migration |
70
+ | `alembic check` | Exit non-zero if models have changes not yet reflected in migrations |
71
+ | `alembic upgrade head --sql` | Print SQL statements without executing (offline mode) |
72
+
73
+ ---
74
+
75
+ ## Operations Reference
76
+
77
+ ### Table Operations
78
+ | Op | Description |
79
+ |----|-------------|
80
+ | `op.create_table(name, *cols)` | Create table |
81
+ | `op.drop_table(name)` | Drop table |
82
+ | `op.rename_table(old, new)` | Rename table |
83
+
84
+ ### Column Operations
85
+ | Op | Description |
86
+ |----|-------------|
87
+ | `op.add_column(table, column)` | Add column |
88
+ | `op.drop_column(table, col_name)` | Drop column |
89
+ | `op.alter_column(table, col_name, ...)` | Change type, nullable, default, name |
90
+
91
+ ### Index Operations
92
+ | Op | Description |
93
+ |----|-------------|
94
+ | `op.create_index(name, table, cols)` | Create index |
95
+ | `op.drop_index(name, table)` | Drop index |
96
+ | `op.create_index(..., postgresql_concurrently=True)` | Non-locking index (PG only) |
97
+
98
+ ### Constraint Operations
99
+ | Op | Description |
100
+ |----|-------------|
101
+ | `op.create_unique_constraint(name, table, cols)` | Unique constraint |
102
+ | `op.drop_constraint(name, table)` | Drop constraint |
103
+ | `op.create_foreign_key(name, src_table, ref_table, lcols, rcols)` | FK constraint |
104
+ | `op.create_check_constraint(name, table, condition)` | Check constraint |
105
+
106
+ ### Data & Raw SQL
107
+ | Op | Description |
108
+ |----|-------------|
109
+ | `op.execute(sql)` | Run raw SQL string or `text()` |
110
+ | `op.bulk_insert(table, rows)` | Insert list of dicts |
111
+ | `op.get_bind()` | Get the active connection for custom queries |
112
+
113
+ ### Batch (SQLite / locked tables)
114
+ ```python
115
+ with op.batch_alter_table("users") as batch_op:
116
+ batch_op.add_column(sa.Column("age", sa.Integer()))
117
+ batch_op.alter_column("name", nullable=False)
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Configuration Patterns
123
+
124
+ ### Sync (Default)
125
+
126
+ ```python
127
+ # env.py
128
+ import os
129
+ from sqlalchemy import engine_from_config, pool
130
+ from alembic import context
131
+ from myapp.models import Base
132
+
133
+ config = context.config
134
+ config.set_main_option("sqlalchemy.url", os.environ["DATABASE_URL"])
135
+ target_metadata = Base.metadata
136
+
137
+ def run_migrations_online():
138
+ connectable = engine_from_config(
139
+ config.get_section(config.config_ini_section, {}),
140
+ prefix="sqlalchemy.",
141
+ poolclass=pool.NullPool,
142
+ )
143
+ with connectable.connect() as connection:
144
+ context.configure(connection=connection, target_metadata=target_metadata)
145
+ with context.begin_transaction():
146
+ context.run_migrations()
147
+ ```
148
+
149
+ ### Async (asyncpg)
150
+
151
+ ```python
152
+ # env.py — async pattern
153
+ import asyncio, os
154
+ from sqlalchemy import pool
155
+ from sqlalchemy.ext.asyncio import async_engine_from_config
156
+ from alembic import context
157
+ from myapp.models import Base
158
+
159
+ config = context.config
160
+ config.set_main_option("sqlalchemy.url", os.environ["DATABASE_URL"])
161
+ target_metadata = Base.metadata
162
+
163
+ async def run_migrations_online():
164
+ connectable = async_engine_from_config(
165
+ config.get_section(config.config_ini_section, {}),
166
+ prefix="sqlalchemy.",
167
+ poolclass=pool.NullPool,
168
+ )
169
+ async with connectable.connect() as connection:
170
+ await connection.run_sync(
171
+ lambda conn: context.configure(conn, target_metadata=target_metadata)
172
+ )
173
+ async with connectable.begin() as trans:
174
+ await connection.run_sync(lambda _: context.run_migrations())
175
+ await connectable.dispose()
176
+
177
+ asyncio.run(run_migrations_online())
178
+ ```
179
+
180
+ ### Multi-Tenant (Schema Per Tenant)
181
+
182
+ ```python
183
+ # env.py — schema-per-tenant pattern
184
+ import os
185
+ from alembic import context
186
+
187
+ TENANT_SCHEMAS = os.environ.get("TENANT_SCHEMAS", "public").split(",")
188
+
189
+ def run_migrations_online():
190
+ connectable = engine_from_config(...)
191
+ with connectable.connect() as connection:
192
+ for schema in TENANT_SCHEMAS:
193
+ connection.execute(text(f"SET search_path TO {schema}"))
194
+ context.configure(
195
+ connection=connection,
196
+ target_metadata=target_metadata,
197
+ version_table_schema=schema,
198
+ include_schemas=True,
199
+ )
200
+ with context.begin_transaction():
201
+ context.run_migrations()
202
+ ```
203
+
204
+ ### Multi-Database
205
+
206
+ Use separate `alembic/` directories (one per database), each with its own `alembic.ini` and `env.py`. Invoke as:
207
+
208
+ ```bash
209
+ alembic -c alembic_orders.ini upgrade head
210
+ alembic -c alembic_users.ini upgrade head
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Integration
216
+
217
+ ### FastAPI
218
+
219
+ Use lifespan events to run migrations on startup in development:
220
+
221
+ ```python
222
+ from contextlib import asynccontextmanager
223
+ from fastapi import FastAPI
224
+ from alembic.config import Config
225
+ from alembic import command
226
+
227
+ @asynccontextmanager
228
+ async def lifespan(app: FastAPI):
229
+ # Run migrations on startup (dev/test only — use CI in production)
230
+ alembic_cfg = Config("alembic.ini")
231
+ command.upgrade(alembic_cfg, "head")
232
+ yield
233
+
234
+ app = FastAPI(lifespan=lifespan)
235
+ ```
236
+
237
+ For production, run migrations as a separate step before the application starts (init container, pre-deploy hook, or CI step).
238
+
239
+ ### Flask-Migrate
240
+
241
+ Flask-Migrate wraps Alembic for Flask applications. It adds `flask db init`, `flask db migrate`, `flask db upgrade` commands. Under the hood it calls the same Alembic Python API. Alembic knowledge transfers directly; only the CLI surface differs.
242
+
243
+ ### Docker / Kubernetes
244
+
245
+ Run migrations as a Kubernetes init container or Docker Compose `depends_on` service:
246
+
247
+ ```yaml
248
+ # docker-compose.yml
249
+ services:
250
+ migrate:
251
+ image: myapp:latest
252
+ command: alembic upgrade head
253
+ environment:
254
+ DATABASE_URL: postgresql://user:pass@db:5432/mydb
255
+ depends_on:
256
+ db:
257
+ condition: service_healthy
258
+
259
+ app:
260
+ image: myapp:latest
261
+ depends_on:
262
+ migrate:
263
+ condition: service_completed_successfully
264
+ ```
265
+
266
+ ### pytest-alembic
267
+
268
+ ```bash
269
+ pip install pytest-alembic
270
+ ```
271
+
272
+ Provides fixtures (`alembic_config`, `alembic_engine`, `alembic_runner`) and built-in tests. Add `--test-alembic` flag or run `pytest tests/test_migrations.py`. See `alembic-best-practices` skill for full setup.
273
+
274
+ ---
275
+
276
+ ## Comparison
277
+
278
+ | Feature | Alembic | Atlas (HashiCorp) | Django Migrations | Flyway | Liquibase |
279
+ |---------|---------|-------------------|-------------------|--------|-----------|
280
+ | Language | Python | Go / HCL | Python | Java | Java / XML |
281
+ | Migration format | Python scripts | HCL / SQL | Python auto-generated | SQL files | XML / YAML / SQL |
282
+ | Autogenerate | Yes (SQLAlchemy) | Yes (schema diff) | Yes (ORM diff) | No | No |
283
+ | Downgrade support | Yes | Partial | Yes | Yes (Undo) | Yes (Rollback) |
284
+ | Multi-DB | Manual | Built-in | Manual | Built-in | Built-in |
285
+ | Async support | Yes (v1.11+) | N/A | No | N/A | N/A |
286
+ | PG objects (views, etc.) | Via alembic-utils | Native HCL | No | SQL only | SQL only |
287
+ | Lock analysis | Via Squawk | Built-in lint | None | None | None |
288
+ | Best for | SQLAlchemy / Python projects | Infra-as-code DB schemas | Django projects | JVM projects | Enterprise / multi-engine |
289
+
290
+ ---
291
+
292
+ ## Security
293
+
294
+ ### Credential Management
295
+
296
+ Never embed database credentials in `alembic.ini`. Always source from environment variables in `env.py`:
297
+
298
+ ```python
299
+ import os
300
+ config.set_main_option("sqlalchemy.url", os.environ["DATABASE_URL"])
301
+ ```
302
+
303
+ Use secret management (AWS Secrets Manager, HashiCorp Vault, Kubernetes Secrets) to inject `DATABASE_URL` at runtime.
304
+
305
+ ### Migration Script Integrity
306
+
307
+ - Commit migration files to version control — do not generate them on the fly in CI
308
+ - Use `alembic check` in CI to verify no uncommitted model changes exist
309
+ - Review all autogenerated migrations before merging — they are code, not config
310
+
311
+ ### Database Permission Separation
312
+
313
+ | Role | Permissions | Used by |
314
+ |------|------------|---------|
315
+ | Migration role | `CREATE`, `ALTER`, `DROP`, `INSERT`, `UPDATE`, `DELETE` on schema | Alembic only |
316
+ | Application role | `SELECT`, `INSERT`, `UPDATE`, `DELETE` on tables | Application at runtime |
317
+ | Read-only role | `SELECT` only | Reporting, analytics |
318
+
319
+ Run `alembic upgrade head` with the migration role, not the application role. The application should never have `DROP TABLE` or `ALTER TABLE` permissions.
320
+
321
+ ---
322
+
323
+ ## Advanced Topics
324
+
325
+ ### Batch Operations (SQLite)
326
+
327
+ SQLite does not support `ALTER TABLE` for most operations. Use `batch_alter_table` which recreates the table:
328
+
329
+ ```python
330
+ with op.batch_alter_table("users", schema=None) as batch_op:
331
+ batch_op.alter_column("email", nullable=False)
332
+ batch_op.drop_constraint("uq_users_username", type_="unique")
333
+ ```
334
+
335
+ Set `render_as_batch=True` in `env.py` to make autogenerate always emit batch operations for SQLite projects.
336
+
337
+ ### Offline Mode
338
+
339
+ Generate SQL without a live database:
340
+
341
+ ```bash
342
+ alembic upgrade head --sql > migration.sql
343
+ ```
344
+
345
+ Useful for: production deployments (run SQL through DBA review), Squawk lint analysis, audit trails.
346
+
347
+ ### Branching and Merging
348
+
349
+ When two developers create migrations from the same base revision, a branch forms. Resolve with:
350
+
351
+ ```bash
352
+ alembic merge <rev1> <rev2> -m "merge parallel migrations"
353
+ ```
354
+
355
+ The merge migration has both revisions in `down_revision` as a tuple:
356
+
357
+ ```python
358
+ down_revision = ("a1b2c3d4", "e5f6g7h8")
359
+ ```
360
+
361
+ ### Custom Operations
362
+
363
+ Extend Alembic with project-specific ops:
364
+
365
+ ```python
366
+ # migrations/ops.py
367
+ from alembic.operations import Operations, MigrateOperation
368
+
369
+ @Operations.register_operation("create_sequence")
370
+ class CreateSequenceOp(MigrateOperation):
371
+ def __init__(self, sequence_name, schema=None):
372
+ self.sequence_name = sequence_name
373
+ self.schema = schema
374
+
375
+ @classmethod
376
+ def create_sequence(cls, operations, sequence_name, **kw):
377
+ op = CreateSequenceOp(sequence_name, **kw)
378
+ return operations.invoke(op)
379
+
380
+ @Operations.implementation_for(CreateSequenceOp)
381
+ def create_sequence(operations, operation):
382
+ operations.execute(f"CREATE SEQUENCE {operation.sequence_name}")
383
+ ```
384
+
385
+ ### Replaceable Objects (alembic-utils)
386
+
387
+ For PostgreSQL objects that are replaced entirely on change (views, functions, triggers, RLS policies), use `alembic-utils` to manage them as Replaceable Objects. Register with autogenerate so diffs are automatically detected:
388
+
389
+ ```python
390
+ # env.py
391
+ from alembic_utils.replaceable_entity import register_entities
392
+ from myapp.db.views import user_summary_view, active_orders_view
393
+
394
+ register_entities([user_summary_view, active_orders_view])
395
+ ```
396
+
397
+ ---
398
+
399
+ ## Ecosystem
400
+
401
+ | Package | Purpose | Maturity |
402
+ |---------|---------|---------|
403
+ | `alembic-utils` | PostgreSQL Replaceable Objects: views, functions, triggers, RLS | Stable |
404
+ | `alembic-postgresql-enum` | Add enum values without full table recreate | Stable |
405
+ | `pytest-alembic` | Migration test fixtures and built-in test cases | Stable |
406
+ | `audit-alembic` | Attach migration context to audit log | Beta |
407
+ | `Squawk` | Static DDL linter for lock-risk patterns | Stable |
408
+ | `pgai Vectorizer` | AI-enabled column vectorization via migrations | Experimental |
409
+ | `Atlas` | Alternative migration engine with HCL-based schema | Stable (separate tool) |
410
+
411
+ ---
412
+
413
+ ## Quick Reference
414
+
415
+ ```bash
416
+ # Initial setup
417
+ pip install alembic sqlalchemy
418
+ alembic init alembic
419
+
420
+ # Daily workflow
421
+ alembic revision --autogenerate -m "add user preferences table"
422
+ # → Review generated file in alembic/versions/
423
+ alembic upgrade head
424
+
425
+ # Check / verify
426
+ alembic current
427
+ alembic history --verbose
428
+ alembic check # fails if models have unmigrated changes
429
+
430
+ # Rollback
431
+ alembic downgrade -1
432
+
433
+ # Branching
434
+ alembic merge rev1 rev2 -m "merge branches"
435
+
436
+ # Offline SQL
437
+ alembic upgrade head --sql | squawk # lint before applying
438
+ ```
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.44.4",
2
+ "version": "0.44.6",
3
3
  "lastUpdated": "2026-03-16T00:00:00.000Z",
4
4
  "components": [
5
5
  {
@@ -12,19 +12,19 @@
12
12
  "name": "agents",
13
13
  "path": ".claude/agents",
14
14
  "description": "AI agent definitions (flat .md files with prefixes)",
15
- "files": 44
15
+ "files": 45
16
16
  },
17
17
  {
18
18
  "name": "skills",
19
19
  "path": ".claude/skills",
20
20
  "description": "Reusable skill modules (includes slash commands)",
21
- "files": 76
21
+ "files": 77
22
22
  },
23
23
  {
24
24
  "name": "guides",
25
25
  "path": "guides",
26
26
  "description": "Reference documentation",
27
- "files": 25
27
+ "files": 26
28
28
  },
29
29
  {
30
30
  "name": "hooks",