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
|
-
|
|
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 (
|
|
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 |
|
|
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 (
|
|
141
|
+
### Skills (77)
|
|
142
142
|
|
|
143
143
|
| Category | Count | Includes |
|
|
144
144
|
|----------|-------|----------|
|
|
145
|
-
| Best Practices |
|
|
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 (
|
|
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/ #
|
|
260
|
-
│ ├── skills/ #
|
|
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/ #
|
|
267
|
+
└── guides/ # 26 reference documents
|
|
268
268
|
```
|
|
269
269
|
|
|
270
270
|
---
|
package/package.json
CHANGED
|
@@ -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
|
+
```
|
package/templates/manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.44.
|
|
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":
|
|
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":
|
|
21
|
+
"files": 77
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"name": "guides",
|
|
25
25
|
"path": "guides",
|
|
26
26
|
"description": "Reference documentation",
|
|
27
|
-
"files":
|
|
27
|
+
"files": 26
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"name": "hooks",
|