feat: show redirect info in bd prime output (bd-kblo)
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 <noreply@anthropic.com>
This commit is contained in:
9
beads.go
9
beads.go
@@ -50,6 +50,15 @@ func FindAllDatabases() []DatabaseInfo {
|
|||||||
return beads.FindAllDatabases()
|
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
|
// Core types from internal/types
|
||||||
type (
|
type (
|
||||||
Issue = types.Issue
|
Issue = types.Issue
|
||||||
|
|||||||
@@ -126,6 +126,22 @@ var isEphemeralBranch = func() bool {
|
|||||||
return err != nil
|
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
|
// outputPrimeContext outputs workflow context in markdown format
|
||||||
func outputPrimeContext(w io.Writer, mcpMode bool, stealthMode bool) error {
|
func outputPrimeContext(w io.Writer, mcpMode bool, stealthMode bool) error {
|
||||||
if mcpMode {
|
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"
|
closeProtocol = "Before saying \"done\": git status → git add → bd sync → git commit → bd sync → git push"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
redirectNotice := getRedirectNotice(false)
|
||||||
|
|
||||||
context := `# Beads Issue Tracker Active
|
context := `# Beads Issue Tracker Active
|
||||||
|
|
||||||
# 🚨 SESSION CLOSE PROTOCOL 🚨
|
` + redirectNotice + `# 🚨 SESSION CLOSE PROTOCOL 🚨
|
||||||
|
|
||||||
` + closeProtocol + `
|
` + closeProtocol + `
|
||||||
|
|
||||||
@@ -238,12 +256,14 @@ bd sync # Push to remote
|
|||||||
` + "```"
|
` + "```"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
redirectNotice := getRedirectNotice(true)
|
||||||
|
|
||||||
context := `# Beads Workflow Context
|
context := `# Beads Workflow Context
|
||||||
|
|
||||||
> **Context Recovery**: Run ` + "`bd prime`" + ` after compaction, clear, or new session
|
> **Context Recovery**: Run ` + "`bd prime`" + ` after compaction, clear, or new session
|
||||||
> Hooks auto-call this in Claude Code when .beads/ detected
|
> 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:
|
**CRITICAL**: Before saying "done" or "complete", you MUST run this checklist:
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,81 @@ func followRedirect(beadsDir string) string {
|
|||||||
return target
|
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.
|
// findDatabaseInBeadsDir searches for a database file within a .beads directory.
|
||||||
// It implements the standard search order:
|
// It implements the standard search order:
|
||||||
// 1. Check config.json first (single source of truth)
|
// 1. Check config.json first (single source of truth)
|
||||||
|
|||||||
Reference in New Issue
Block a user