fix: Use BeadsPath() for swarm status to read from git-synced beads (gt-1rxz5)

The swarm status was stale because bd commands were running from the rig
root path instead of the mayor/rig clone which has proper beads sync config.

Changes:
- Add Rig.BeadsPath() method to return mayor/rig path when available
- Update all bd commands in swarm.go to use BeadsPath()
- Update swarm manager to use separate beadsDir and gitDir paths
- beadsDir for bd commands (git-synced location)
- gitDir for git operations and mail (rig root)

This ensures swarm status reflects the latest beads data from the
git-synced beads-sync branch instead of stale local daemon data.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-29 23:43:22 -08:00
parent deb58838ad
commit e362be3c41
5 changed files with 47 additions and 28 deletions

View File

@@ -233,13 +233,15 @@ func runSwarmCreate(cmd *cobra.Command, args []string) error {
// Use beads to create the swarm molecule // Use beads to create the swarm molecule
// First check if the epic already exists (it may be pre-created) // First check if the epic already exists (it may be pre-created)
// Use BeadsPath() to ensure we read from git-synced beads location
beadsPath := r.BeadsPath()
checkCmd := exec.Command("bd", "show", swarmEpic, "--json") checkCmd := exec.Command("bd", "show", swarmEpic, "--json")
checkCmd.Dir = r.Path checkCmd.Dir = beadsPath
if err := checkCmd.Run(); err == nil { if err := checkCmd.Run(); err == nil {
// Epic exists, update it to be a swarm molecule // Epic exists, update it to be a swarm molecule
updateArgs := []string{"update", swarmEpic, "--mol-type=swarm"} updateArgs := []string{"update", swarmEpic, "--mol-type=swarm"}
updateCmd := exec.Command("bd", updateArgs...) updateCmd := exec.Command("bd", updateArgs...)
updateCmd.Dir = r.Path updateCmd.Dir = beadsPath
if err := updateCmd.Run(); err != nil { if err := updateCmd.Run(); err != nil {
return fmt.Errorf("updating epic to swarm molecule: %w", err) return fmt.Errorf("updating epic to swarm molecule: %w", err)
} }
@@ -253,7 +255,7 @@ func runSwarmCreate(cmd *cobra.Command, args []string) error {
"--silent", "--silent",
} }
createCmd := exec.Command("bd", createArgs...) createCmd := exec.Command("bd", createArgs...)
createCmd.Dir = r.Path createCmd.Dir = beadsPath
var stdout bytes.Buffer var stdout bytes.Buffer
createCmd.Stdout = &stdout createCmd.Stdout = &stdout
if err := createCmd.Run(); err != nil { if err := createCmd.Run(); err != nil {
@@ -289,7 +291,7 @@ func runSwarmCreate(cmd *cobra.Command, args []string) error {
if swarmStart { if swarmStart {
// Get swarm status to find ready tasks // Get swarm status to find ready tasks
statusCmd := exec.Command("bd", "swarm", "status", swarmEpic, "--json") statusCmd := exec.Command("bd", "swarm", "status", swarmEpic, "--json")
statusCmd.Dir = r.Path statusCmd.Dir = beadsPath
var statusOut bytes.Buffer var statusOut bytes.Buffer
statusCmd.Stdout = &statusOut statusCmd.Stdout = &statusOut
if err := statusCmd.Run(); err != nil { if err := statusCmd.Run(); err != nil {
@@ -330,8 +332,9 @@ func runSwarmStart(cmd *cobra.Command, args []string) error {
var foundRig *rig.Rig var foundRig *rig.Rig
for _, r := range rigs { for _, r := range rigs {
// Check if swarm exists in this rig by querying beads // Check if swarm exists in this rig by querying beads
// Use BeadsPath() to ensure we read from git-synced location
checkCmd := exec.Command("bd", "show", swarmID, "--json") checkCmd := exec.Command("bd", "show", swarmID, "--json")
checkCmd.Dir = r.Path checkCmd.Dir = r.BeadsPath()
if err := checkCmd.Run(); err == nil { if err := checkCmd.Run(); err == nil {
foundRig = r foundRig = r
break break
@@ -344,7 +347,7 @@ func runSwarmStart(cmd *cobra.Command, args []string) error {
// Get swarm status from beads // Get swarm status from beads
statusCmd := exec.Command("bd", "swarm", "status", swarmID, "--json") statusCmd := exec.Command("bd", "swarm", "status", swarmID, "--json")
statusCmd.Dir = foundRig.Path statusCmd.Dir = foundRig.BeadsPath()
var stdout bytes.Buffer var stdout bytes.Buffer
statusCmd.Stdout = &stdout statusCmd.Stdout = &stdout
@@ -468,8 +471,9 @@ func runSwarmStatus(cmd *cobra.Command, args []string) error {
// Find which rig has this swarm // Find which rig has this swarm
var foundRig *rig.Rig var foundRig *rig.Rig
for _, r := range rigs { for _, r := range rigs {
// Use BeadsPath() to ensure we read from git-synced location
checkCmd := exec.Command("bd", "show", swarmID, "--json") checkCmd := exec.Command("bd", "show", swarmID, "--json")
checkCmd.Dir = r.Path checkCmd.Dir = r.BeadsPath()
if err := checkCmd.Run(); err == nil { if err := checkCmd.Run(); err == nil {
foundRig = r foundRig = r
break break
@@ -487,7 +491,7 @@ func runSwarmStatus(cmd *cobra.Command, args []string) error {
} }
bdCmd := exec.Command("bd", bdArgs...) bdCmd := exec.Command("bd", bdArgs...)
bdCmd.Dir = foundRig.Path bdCmd.Dir = foundRig.BeadsPath()
bdCmd.Stdout = os.Stdout bdCmd.Stdout = os.Stdout
bdCmd.Stderr = os.Stderr bdCmd.Stderr = os.Stderr
@@ -537,7 +541,7 @@ func runSwarmList(cmd *cobra.Command, args []string) error {
for _, r := range rigs { for _, r := range rigs {
bdCmd := exec.Command("bd", bdArgs...) bdCmd := exec.Command("bd", bdArgs...)
bdCmd.Dir = r.Path bdCmd.Dir = r.BeadsPath() // Use BeadsPath() for git-synced beads
var stdout bytes.Buffer var stdout bytes.Buffer
bdCmd.Stdout = &stdout bdCmd.Stdout = &stdout
@@ -615,8 +619,9 @@ func runSwarmLand(cmd *cobra.Command, args []string) error {
var foundRig *rig.Rig var foundRig *rig.Rig
for _, r := range rigs { for _, r := range rigs {
// Use BeadsPath() for git-synced beads
checkCmd := exec.Command("bd", "show", swarmID, "--json") checkCmd := exec.Command("bd", "show", swarmID, "--json")
checkCmd.Dir = r.Path checkCmd.Dir = r.BeadsPath()
if err := checkCmd.Run(); err == nil { if err := checkCmd.Run(); err == nil {
foundRig = r foundRig = r
break break
@@ -629,7 +634,7 @@ func runSwarmLand(cmd *cobra.Command, args []string) error {
// Check swarm status - all children should be closed // Check swarm status - all children should be closed
statusCmd := exec.Command("bd", "swarm", "status", swarmID, "--json") statusCmd := exec.Command("bd", "swarm", "status", swarmID, "--json")
statusCmd.Dir = foundRig.Path statusCmd.Dir = foundRig.BeadsPath()
var stdout bytes.Buffer var stdout bytes.Buffer
statusCmd.Stdout = &stdout statusCmd.Stdout = &stdout
@@ -678,7 +683,7 @@ func runSwarmLand(cmd *cobra.Command, args []string) error {
// Close the swarm epic in beads // Close the swarm epic in beads
closeCmd := exec.Command("bd", "close", swarmID, "--reason", "Swarm landed to main") closeCmd := exec.Command("bd", "close", swarmID, "--reason", "Swarm landed to main")
closeCmd.Dir = foundRig.Path closeCmd.Dir = foundRig.BeadsPath()
if err := closeCmd.Run(); err != nil { if err := closeCmd.Run(); err != nil {
style.PrintWarning("couldn't close swarm epic in beads: %v", err) style.PrintWarning("couldn't close swarm epic in beads: %v", err)
} }
@@ -700,8 +705,9 @@ func runSwarmCancel(cmd *cobra.Command, args []string) error {
var foundRig *rig.Rig var foundRig *rig.Rig
for _, r := range rigs { for _, r := range rigs {
// Use BeadsPath() for git-synced beads
checkCmd := exec.Command("bd", "show", swarmID, "--json") checkCmd := exec.Command("bd", "show", swarmID, "--json")
checkCmd.Dir = r.Path checkCmd.Dir = r.BeadsPath()
if err := checkCmd.Run(); err == nil { if err := checkCmd.Run(); err == nil {
foundRig = r foundRig = r
break break
@@ -714,7 +720,7 @@ func runSwarmCancel(cmd *cobra.Command, args []string) error {
// Check if swarm is already closed // Check if swarm is already closed
checkCmd := exec.Command("bd", "show", swarmID, "--json") checkCmd := exec.Command("bd", "show", swarmID, "--json")
checkCmd.Dir = foundRig.Path checkCmd.Dir = foundRig.BeadsPath()
var stdout bytes.Buffer var stdout bytes.Buffer
checkCmd.Stdout = &stdout checkCmd.Stdout = &stdout
if err := checkCmd.Run(); err != nil { if err := checkCmd.Run(); err != nil {
@@ -732,7 +738,7 @@ func runSwarmCancel(cmd *cobra.Command, args []string) error {
// Close the swarm epic in beads with cancelled reason // Close the swarm epic in beads with cancelled reason
closeCmd := exec.Command("bd", "close", swarmID, "--reason", "Swarm cancelled") closeCmd := exec.Command("bd", "close", swarmID, "--reason", "Swarm cancelled")
closeCmd.Dir = foundRig.Path closeCmd.Dir = foundRig.BeadsPath()
if err := closeCmd.Run(); err != nil { if err := closeCmd.Run(); err != nil {
return fmt.Errorf("closing swarm: %w", err) return fmt.Errorf("closing swarm: %w", err)
} }

View File

@@ -65,3 +65,14 @@ func (r *Rig) Summary() RigSummary {
HasRefinery: r.HasRefinery, HasRefinery: r.HasRefinery,
} }
} }
// BeadsPath returns the path to use for beads operations.
// Returns the mayor/rig clone path if available (has proper sync-branch config),
// otherwise falls back to the rig root path.
// This ensures beads commands read from a location with git-synced beads data.
func (r *Rig) BeadsPath() string {
if r.HasMayor {
return r.Path + "/mayor/rig"
}
return r.Path
}

View File

@@ -172,7 +172,7 @@ func (m *Manager) branchExists(branch string) bool {
// getCurrentBranch returns the current branch name. // getCurrentBranch returns the current branch name.
func (m *Manager) getCurrentBranch() (string, error) { func (m *Manager) getCurrentBranch() (string, error) {
cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD") cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
cmd.Dir = m.workDir cmd.Dir = m.gitDir
var stdout bytes.Buffer var stdout bytes.Buffer
cmd.Stdout = &stdout cmd.Stdout = &stdout
@@ -187,7 +187,7 @@ func (m *Manager) getCurrentBranch() (string, error) {
// gitRun executes a git command. // gitRun executes a git command.
func (m *Manager) gitRun(args ...string) error { func (m *Manager) gitRun(args ...string) error {
cmd := exec.Command("git", args...) cmd := exec.Command("git", args...)
cmd.Dir = m.workDir cmd.Dir = m.gitDir
var stderr bytes.Buffer var stderr bytes.Buffer
cmd.Stderr = &stderr cmd.Stderr = &stderr

View File

@@ -196,7 +196,7 @@ func (m *Manager) gitRunOutput(dir string, args ...string) (string, error) {
// notifyMayorCodeAtRisk sends an alert to Mayor about code at risk. // notifyMayorCodeAtRisk sends an alert to Mayor about code at risk.
func (m *Manager) notifyMayorCodeAtRisk(townRoot, swarmID string, workers []string) { func (m *Manager) notifyMayorCodeAtRisk(townRoot, swarmID string, workers []string) {
router := mail.NewRouter(m.workDir) router := mail.NewRouter(m.gitDir)
msg := &mail.Message{ msg := &mail.Message{
From: fmt.Sprintf("%s/refinery", m.rig.Name), From: fmt.Sprintf("%s/refinery", m.rig.Name),
To: "mayor/", To: "mayor/",
@@ -215,7 +215,7 @@ Manual intervention required.`,
// notifyMayorLanded sends a landing report to Mayor. // notifyMayorLanded sends a landing report to Mayor.
func (m *Manager) notifyMayorLanded(townRoot string, swarm *Swarm, result *LandingResult) { func (m *Manager) notifyMayorLanded(townRoot string, swarm *Swarm, result *LandingResult) {
router := mail.NewRouter(m.workDir) router := mail.NewRouter(m.gitDir)
msg := &mail.Message{ msg := &mail.Message{
From: fmt.Sprintf("%s/refinery", m.rig.Name), From: fmt.Sprintf("%s/refinery", m.rig.Name),
To: "mayor/", To: "mayor/",

View File

@@ -23,15 +23,17 @@ var (
// Manager handles swarm lifecycle operations. // Manager handles swarm lifecycle operations.
// Manager is stateless - all swarm state is discovered from beads. // Manager is stateless - all swarm state is discovered from beads.
type Manager struct { type Manager struct {
rig *rig.Rig rig *rig.Rig
workDir string beadsDir string // Path for beads operations (git-synced)
gitDir string // Path for git operations (rig root)
} }
// NewManager creates a new swarm manager for a rig. // NewManager creates a new swarm manager for a rig.
func NewManager(r *rig.Rig) *Manager { func NewManager(r *rig.Rig) *Manager {
return &Manager{ return &Manager{
rig: r, rig: r,
workDir: r.Path, beadsDir: r.BeadsPath(), // Use BeadsPath() for git-synced beads operations
gitDir: r.Path, // Use rig root for git operations
} }
} }
@@ -40,7 +42,7 @@ func NewManager(r *rig.Rig) *Manager {
func (m *Manager) LoadSwarm(epicID string) (*Swarm, error) { func (m *Manager) LoadSwarm(epicID string) (*Swarm, error) {
// Query beads for the epic // Query beads for the epic
cmd := exec.Command("bd", "show", epicID, "--json") cmd := exec.Command("bd", "show", epicID, "--json")
cmd.Dir = m.workDir cmd.Dir = m.beadsDir
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout cmd.Stdout = &stdout
@@ -126,7 +128,7 @@ func (m *Manager) GetSwarm(id string) (*Swarm, error) {
func (m *Manager) GetReadyTasks(swarmID string) ([]SwarmTask, error) { func (m *Manager) GetReadyTasks(swarmID string) ([]SwarmTask, error) {
// Use bd swarm status to get ready front // Use bd swarm status to get ready front
cmd := exec.Command("bd", "swarm", "status", swarmID, "--json") cmd := exec.Command("bd", "swarm", "status", swarmID, "--json")
cmd.Dir = m.workDir cmd.Dir = m.beadsDir
var stdout bytes.Buffer var stdout bytes.Buffer
cmd.Stdout = &stdout cmd.Stdout = &stdout
@@ -163,7 +165,7 @@ func (m *Manager) GetReadyTasks(swarmID string) ([]SwarmTask, error) {
// IsComplete checks if all tasks are closed by querying beads. // IsComplete checks if all tasks are closed by querying beads.
func (m *Manager) IsComplete(swarmID string) (bool, error) { func (m *Manager) IsComplete(swarmID string) (bool, error) {
cmd := exec.Command("bd", "swarm", "status", swarmID, "--json") cmd := exec.Command("bd", "swarm", "status", swarmID, "--json")
cmd.Dir = m.workDir cmd.Dir = m.beadsDir
var stdout bytes.Buffer var stdout bytes.Buffer
cmd.Stdout = &stdout cmd.Stdout = &stdout
@@ -213,7 +215,7 @@ func isValidTransition(from, to SwarmState) bool {
func (m *Manager) loadTasksFromBeads(epicID string) ([]SwarmTask, error) { func (m *Manager) loadTasksFromBeads(epicID string) ([]SwarmTask, error) {
// Run: bd show <epicID> --json to get epic with children // Run: bd show <epicID> --json to get epic with children
cmd := exec.Command("bd", "show", epicID, "--json") cmd := exec.Command("bd", "show", epicID, "--json")
cmd.Dir = m.workDir cmd.Dir = m.beadsDir
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout cmd.Stdout = &stdout
@@ -275,7 +277,7 @@ func (m *Manager) loadTasksFromBeads(epicID string) ([]SwarmTask, error) {
// getGitHead returns the current HEAD commit. // getGitHead returns the current HEAD commit.
func (m *Manager) getGitHead() (string, error) { func (m *Manager) getGitHead() (string, error) {
cmd := exec.Command("git", "rev-parse", "HEAD") cmd := exec.Command("git", "rev-parse", "HEAD")
cmd.Dir = m.workDir cmd.Dir = m.gitDir
var stdout bytes.Buffer var stdout bytes.Buffer
cmd.Stdout = &stdout cmd.Stdout = &stdout