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

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/steveyegge/beads/internal/types"
@@ -21,11 +22,15 @@ or daemon connection. It shows:
- Daemon connection status (daemon or direct mode)
- If using daemon: socket path, health status, version
- Database statistics (issue count)
- Schema information (with --schema flag)
Examples:
bd info
bd info --json`,
bd info --json
bd info --schema --json`,
Run: func(cmd *cobra.Command, args []string) {
schemaFlag, _ := cmd.Flags().GetBool("schema")
// Get database path (absolute)
absDBPath, err := filepath.Abs(dbPath)
if err != nil {
@@ -81,6 +86,55 @@ Examples:
}
}
// Add schema information if requested
if schemaFlag && store != nil {
ctx := context.Background()
// Get schema version
schemaVersion, err := store.GetMetadata(ctx, "bd_version")
if err != nil {
schemaVersion = "unknown"
}
// Get tables
tables := []string{"issues", "dependencies", "labels", "config", "metadata"}
// Get config
configMap := make(map[string]string)
prefix, _ := store.GetConfig(ctx, "issue_prefix")
if prefix != "" {
configMap["issue_prefix"] = prefix
}
// Get sample issue IDs
filter := types.IssueFilter{}
issues, err := store.SearchIssues(ctx, "", filter)
sampleIDs := []string{}
detectedPrefix := ""
if err == nil && len(issues) > 0 {
// Get first 3 issue IDs as samples
maxSamples := 3
if len(issues) < maxSamples {
maxSamples = len(issues)
}
for i := 0; i < maxSamples; i++ {
sampleIDs = append(sampleIDs, issues[i].ID)
}
// Detect prefix from first issue
if len(issues) > 0 {
detectedPrefix = extractPrefix(issues[0].ID)
}
}
info["schema"] = map[string]interface{}{
"tables": tables,
"schema_version": schemaVersion,
"config": configMap,
"sample_issue_ids": sampleIDs,
"detected_prefix": detectedPrefix,
}
}
// JSON output
if jsonOutput {
outputJSON(info)
@@ -125,10 +179,38 @@ Examples:
fmt.Printf("\nIssue Count: %d\n", count)
}
// Show schema information if requested
if schemaFlag {
if schemaInfo, ok := info["schema"].(map[string]interface{}); ok {
fmt.Println("\nSchema Information:")
fmt.Printf(" Tables: %v\n", schemaInfo["tables"])
if version, ok := schemaInfo["schema_version"].(string); ok {
fmt.Printf(" Schema Version: %s\n", version)
}
if prefix, ok := schemaInfo["detected_prefix"].(string); ok && prefix != "" {
fmt.Printf(" Detected Prefix: %s\n", prefix)
}
if samples, ok := schemaInfo["sample_issue_ids"].([]string); ok && len(samples) > 0 {
fmt.Printf(" Sample Issues: %v\n", samples)
}
}
}
fmt.Println()
},
}
// extractPrefix extracts the prefix from an issue ID (e.g., "bd-123" -> "bd")
func extractPrefix(issueID string) string {
parts := strings.Split(issueID, "-")
if len(parts) > 0 {
return parts[0]
}
return ""
}
func init() {
infoCmd.Flags().Bool("schema", false, "Include schema information in output")
infoCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
rootCmd.AddCommand(infoCmd)
}