From db0c6fbd3fd67591166230bd6834e45167fd1b16 Mon Sep 17 00:00:00 2001 From: beads/crew/emma Date: Fri, 23 Jan 2026 22:24:55 -0800 Subject: [PATCH] fix(dolt): enable server mode for Gas Town integration (bd-nnjik) - Skip Bootstrap when ServerMode is true (bootstrap is for embedded cold-start) - Fix doltExists() to follow symlinks using os.Stat - Pass database name from metadata.json to DoltStore config Co-Authored-By: Claude Opus 4.5 --- internal/configfile/configfile.go | 13 +++++++ internal/storage/dolt/bootstrap.go | 12 +++++-- internal/storage/factory/factory.go | 4 +++ internal/storage/factory/factory_dolt.go | 46 +++++++++++++----------- 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/internal/configfile/configfile.go b/internal/configfile/configfile.go index 94dc0501..218ba03e 100644 --- a/internal/configfile/configfile.go +++ b/internal/configfile/configfile.go @@ -262,3 +262,16 @@ func (c *Config) GetDoltServerUser() string { } return c.DoltServerUser } + +// GetDoltDatabase returns the database name for Dolt server mode. +// This is different from DatabasePath which returns the on-disk path. +// For server mode, Database field contains the database name on the server +// (e.g., "hq", "gastown", "beads"). Defaults to "beads". +func (c *Config) GetDoltDatabase() string { + db := strings.TrimSpace(c.Database) + if db == "" || db == "beads.db" || db == "dolt" { + return "beads" + } + // Strip any path components - just want the database name + return filepath.Base(db) +} diff --git a/internal/storage/dolt/bootstrap.go b/internal/storage/dolt/bootstrap.go index 9d655139..7b68beb4 100644 --- a/internal/storage/dolt/bootstrap.go +++ b/internal/storage/dolt/bootstrap.go @@ -101,9 +101,15 @@ func doltExists(doltPath string) bool { } for _, entry := range entries { - if entry.IsDir() { - doltDir := filepath.Join(doltPath, entry.Name(), ".dolt") - if info, err := os.Stat(doltDir); err == nil && info.IsDir() { + // Use os.Stat to follow symlinks - entry.IsDir() returns false for symlinks + fullPath := filepath.Join(doltPath, entry.Name()) + info, err := os.Stat(fullPath) + if err != nil { + continue + } + if info.IsDir() { + doltDir := filepath.Join(fullPath, ".dolt") + if doltInfo, err := os.Stat(doltDir); err == nil && doltInfo.IsDir() { return true } } diff --git a/internal/storage/factory/factory.go b/internal/storage/factory/factory.go index 6fbde06d..4bc37870 100644 --- a/internal/storage/factory/factory.go +++ b/internal/storage/factory/factory.go @@ -32,6 +32,7 @@ type Options struct { ServerHost string // Server host (default: 127.0.0.1) ServerPort int // Server port (default: 3306) ServerUser string // MySQL user (default: root) + Database string // Database name for Dolt server mode (default: beads) } // New creates a storage backend based on the backend type. @@ -101,6 +102,9 @@ func NewFromConfigWithOptions(ctx context.Context, beadsDir string, opts Options if opts.ServerUser == "" { opts.ServerUser = cfg.GetDoltServerUser() } + if opts.Database == "" { + opts.Database = cfg.GetDoltDatabase() + } } return NewWithOptions(ctx, backend, cfg.DatabasePath(beadsDir), opts) default: diff --git a/internal/storage/factory/factory_dolt.go b/internal/storage/factory/factory_dolt.go index e73cd193..ddd96c8a 100644 --- a/internal/storage/factory/factory_dolt.go +++ b/internal/storage/factory/factory_dolt.go @@ -15,34 +15,38 @@ import ( func init() { RegisterBackend(configfile.BackendDolt, func(ctx context.Context, path string, opts Options) (storage.Storage, error) { - // Check if bootstrap is needed (JSONL exists but Dolt doesn't) - // Path is the dolt subdirectory, parent is .beads directory - beadsDir := filepath.Dir(path) + // Only bootstrap in embedded mode - server mode has database on server + if !opts.ServerMode { + // Check if bootstrap is needed (JSONL exists but Dolt doesn't) + // Path is the dolt subdirectory, parent is .beads directory + beadsDir := filepath.Dir(path) - bootstrapped, result, err := dolt.Bootstrap(ctx, dolt.BootstrapConfig{ - BeadsDir: beadsDir, - DoltPath: path, - LockTimeout: opts.LockTimeout, - }) - if err != nil { - return nil, fmt.Errorf("bootstrap failed: %w", err) - } + bootstrapped, result, err := dolt.Bootstrap(ctx, dolt.BootstrapConfig{ + BeadsDir: beadsDir, + DoltPath: path, + LockTimeout: opts.LockTimeout, + }) + if err != nil { + return nil, fmt.Errorf("bootstrap failed: %w", err) + } - if bootstrapped && result != nil { - // Report bootstrap results - fmt.Fprintf(os.Stderr, "Bootstrapping Dolt from JSONL...\n") - if len(result.ParseErrors) > 0 { - fmt.Fprintf(os.Stderr, " Skipped %d malformed lines (see above for details)\n", len(result.ParseErrors)) + if bootstrapped && result != nil { + // Report bootstrap results + fmt.Fprintf(os.Stderr, "Bootstrapping Dolt from JSONL...\n") + if len(result.ParseErrors) > 0 { + fmt.Fprintf(os.Stderr, " Skipped %d malformed lines (see above for details)\n", len(result.ParseErrors)) + } + fmt.Fprintf(os.Stderr, " Imported %d issues", result.IssuesImported) + if result.IssuesSkipped > 0 { + fmt.Fprintf(os.Stderr, ", skipped %d duplicates", result.IssuesSkipped) + } + fmt.Fprintf(os.Stderr, "\n Dolt database ready\n") } - fmt.Fprintf(os.Stderr, " Imported %d issues", result.IssuesImported) - if result.IssuesSkipped > 0 { - fmt.Fprintf(os.Stderr, ", skipped %d duplicates", result.IssuesSkipped) - } - fmt.Fprintf(os.Stderr, "\n Dolt database ready\n") } return dolt.New(ctx, &dolt.Config{ Path: path, + Database: opts.Database, ReadOnly: opts.ReadOnly, ServerMode: opts.ServerMode, ServerHost: opts.ServerHost,