Add migration inspection tools for AI agents (bd-627d Phase 2)

Implemented:
- bd migrate --inspect --json: Shows migration plan, db state, warnings
- bd info --schema --json: Returns schema details for agents
- Migration invariants: Validates migrations post-execution
- Added ListMigrations() for introspection

Phase 1 (invariants) and Phase 2 (inspection) complete.
Next: Wire up MCP tools in beads-mcp server.

Amp-Thread-ID: https://ampcode.com/threads/T-c4674660-d640-405f-a929-b664e8699a48
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-11-02 14:03:14 -08:00
parent c810a494c6
commit 1abe4e75ad
7 changed files with 796 additions and 89 deletions

View File

@@ -29,13 +29,66 @@ var migrations = []Migration{
{"content_hash_column", migrateContentHashColumn},
}
// RunMigrations executes all registered migrations in order
// MigrationInfo contains metadata about a migration for inspection
type MigrationInfo struct {
Name string `json:"name"`
Description string `json:"description"`
}
// ListMigrations returns list of all registered migrations with descriptions
// Note: This returns ALL registered migrations, not just pending ones (all are idempotent)
func ListMigrations() []MigrationInfo {
result := make([]MigrationInfo, len(migrations))
for i, m := range migrations {
result[i] = MigrationInfo{
Name: m.Name,
Description: getMigrationDescription(m.Name),
}
}
return result
}
// getMigrationDescription returns a human-readable description for a migration
func getMigrationDescription(name string) string {
descriptions := map[string]string{
"dirty_issues_table": "Adds dirty_issues table for auto-export tracking",
"external_ref_column": "Adds external_ref column to issues table",
"composite_indexes": "Adds composite indexes for better query performance",
"closed_at_constraint": "Adds constraint ensuring closed issues have closed_at timestamp",
"compaction_columns": "Adds compaction tracking columns (compacted_at, compacted_at_commit)",
"snapshots_table": "Adds snapshots table for issue history",
"compaction_config": "Adds config entries for compaction",
"compacted_at_commit_column": "Adds compacted_at_commit to snapshots table",
"export_hashes_table": "Adds export_hashes table for idempotent exports",
"content_hash_column": "Adds content_hash column for collision resolution",
}
if desc, ok := descriptions[name]; ok {
return desc
}
return "Unknown migration"
}
// RunMigrations executes all registered migrations in order with invariant checking
func RunMigrations(db *sql.DB) error {
// Capture pre-migration snapshot for validation
snapshot, err := captureSnapshot(db)
if err != nil {
return fmt.Errorf("failed to capture pre-migration snapshot: %w", err)
}
// Run migrations (they are already idempotent)
for _, migration := range migrations {
if err := migration.Func(db); err != nil {
return fmt.Errorf("migration %s failed: %w", migration.Name, err)
}
}
// Verify invariants after migrations complete
if err := verifyInvariants(db, snapshot); err != nil {
return fmt.Errorf("post-migration validation failed: %w", err)
}
return nil
}