From ba1c9d6b17284647d5d947536bcec2d461151c7f Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Sat, 27 Dec 2025 21:33:01 -0800 Subject: [PATCH] feat: show redirect info in bd prime output (bd-kblo) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When beads is redirected (e.g., in Gas Town crew clones), bd prime now shows a notice about the redirect. This helps agents understand why they share issues with other clones. CLI mode shows: > ⚠️ **Redirected**: Local .beads → /path/to/target/.beads > You share issues with other clones using this redirect. MCP mode shows: **Note**: Beads redirected to /path/to/target/.beads (shared with other clones) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- beads.go | 9 +++++ cmd/bd/prime.go | 24 +++++++++++-- internal/beads/beads.go | 75 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/beads.go b/beads.go index d95f2d5f..81aaf7a7 100644 --- a/beads.go +++ b/beads.go @@ -50,6 +50,15 @@ func FindAllDatabases() []DatabaseInfo { return beads.FindAllDatabases() } +// RedirectInfo contains information about a beads directory redirect +type RedirectInfo = beads.RedirectInfo + +// GetRedirectInfo checks if the current beads directory is redirected. +// Returns RedirectInfo with IsRedirected=true if a redirect is active. +func GetRedirectInfo() RedirectInfo { + return beads.GetRedirectInfo() +} + // Core types from internal/types type ( Issue = types.Issue diff --git a/cmd/bd/prime.go b/cmd/bd/prime.go index 8fb2f2d9..d4a7cf8b 100644 --- a/cmd/bd/prime.go +++ b/cmd/bd/prime.go @@ -126,6 +126,22 @@ var isEphemeralBranch = func() bool { return err != nil } +// getRedirectNotice returns a notice string if beads is redirected +func getRedirectNotice(verbose bool) string { + redirectInfo := beads.GetRedirectInfo() + if !redirectInfo.IsRedirected { + return "" + } + + if verbose { + return fmt.Sprintf(`> ⚠️ **Redirected**: Local .beads → %s +> You share issues with other clones using this redirect. + +`, redirectInfo.TargetDir) + } + return fmt.Sprintf("**Note**: Beads redirected to %s (shared with other clones)\n\n", redirectInfo.TargetDir) +} + // outputPrimeContext outputs workflow context in markdown format func outputPrimeContext(w io.Writer, mcpMode bool, stealthMode bool) error { if mcpMode { @@ -151,9 +167,11 @@ func outputMCPContext(w io.Writer, stealthMode bool) error { closeProtocol = "Before saying \"done\": git status → git add → bd sync → git commit → bd sync → git push" } + redirectNotice := getRedirectNotice(false) + context := `# Beads Issue Tracker Active -# 🚨 SESSION CLOSE PROTOCOL 🚨 +` + redirectNotice + `# 🚨 SESSION CLOSE PROTOCOL 🚨 ` + closeProtocol + ` @@ -238,12 +256,14 @@ bd sync # Push to remote ` + "```" } + redirectNotice := getRedirectNotice(true) + context := `# Beads Workflow Context > **Context Recovery**: Run ` + "`bd prime`" + ` after compaction, clear, or new session > Hooks auto-call this in Claude Code when .beads/ detected -# 🚨 SESSION CLOSE PROTOCOL 🚨 +` + redirectNotice + `# 🚨 SESSION CLOSE PROTOCOL 🚨 **CRITICAL**: Before saying "done" or "complete", you MUST run this checklist: diff --git a/internal/beads/beads.go b/internal/beads/beads.go index 15878d65..dfec54d3 100644 --- a/internal/beads/beads.go +++ b/internal/beads/beads.go @@ -93,6 +93,81 @@ func followRedirect(beadsDir string) string { return target } +// RedirectInfo contains information about a beads directory redirect. +type RedirectInfo struct { + // IsRedirected is true if the local .beads has a redirect file + IsRedirected bool + // LocalDir is the local .beads directory (the one with the redirect file) + LocalDir string + // TargetDir is the actual .beads directory being used (after following redirect) + TargetDir string +} + +// GetRedirectInfo checks if the current beads directory is redirected. +// It searches for the local .beads/ directory and checks if it contains a redirect file. +// Returns RedirectInfo with IsRedirected=true if a redirect is active. +func GetRedirectInfo() RedirectInfo { + info := RedirectInfo{} + + // Find the local .beads directory without following redirects + localBeadsDir := findLocalBeadsDir() + if localBeadsDir == "" { + return info + } + info.LocalDir = localBeadsDir + + // Check if this directory has a redirect file + redirectFile := filepath.Join(localBeadsDir, RedirectFileName) + if _, err := os.Stat(redirectFile); err != nil { + // No redirect file + return info + } + + // There's a redirect - find the target + targetDir := followRedirect(localBeadsDir) + if targetDir == localBeadsDir { + // Redirect file exists but failed to resolve (invalid target) + return info + } + + info.IsRedirected = true + info.TargetDir = targetDir + return info +} + +// findLocalBeadsDir finds the local .beads directory without following redirects. +// This is used to detect if a redirect is configured. +func findLocalBeadsDir() string { + // Check BEADS_DIR environment variable first + if beadsDir := os.Getenv("BEADS_DIR"); beadsDir != "" { + return utils.CanonicalizePath(beadsDir) + } + + // Check for worktree - use main repo's .beads + mainRepoRoot, err := git.GetMainRepoRoot() + if err == nil && mainRepoRoot != "" { + beadsDir := filepath.Join(mainRepoRoot, ".beads") + if info, err := os.Stat(beadsDir); err == nil && info.IsDir() { + return beadsDir + } + } + + // Walk up directory tree + cwd, err := os.Getwd() + if err != nil { + return "" + } + + for dir := cwd; dir != "/" && dir != "."; dir = filepath.Dir(dir) { + beadsDir := filepath.Join(dir, ".beads") + if info, err := os.Stat(beadsDir); err == nil && info.IsDir() { + return beadsDir + } + } + + return "" +} + // findDatabaseInBeadsDir searches for a database file within a .beads directory. // It implements the standard search order: // 1. Check config.json first (single source of truth)