From e362be3c419a9cae99cf33462f390166f7c08c7d Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Mon, 29 Dec 2025 23:43:22 -0800 Subject: [PATCH] fix: Use BeadsPath() for swarm status to read from git-synced beads (gt-1rxz5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- internal/cmd/swarm.go | 36 ++++++++++++++++++++--------------- internal/rig/types.go | 11 +++++++++++ internal/swarm/integration.go | 4 ++-- internal/swarm/landing.go | 4 ++-- internal/swarm/manager.go | 20 ++++++++++--------- 5 files changed, 47 insertions(+), 28 deletions(-) diff --git a/internal/cmd/swarm.go b/internal/cmd/swarm.go index 57dc82ab..e5766bb6 100644 --- a/internal/cmd/swarm.go +++ b/internal/cmd/swarm.go @@ -233,13 +233,15 @@ func runSwarmCreate(cmd *cobra.Command, args []string) error { // Use beads to create the swarm molecule // 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.Dir = r.Path + checkCmd.Dir = beadsPath if err := checkCmd.Run(); err == nil { // Epic exists, update it to be a swarm molecule updateArgs := []string{"update", swarmEpic, "--mol-type=swarm"} updateCmd := exec.Command("bd", updateArgs...) - updateCmd.Dir = r.Path + updateCmd.Dir = beadsPath if err := updateCmd.Run(); err != nil { return fmt.Errorf("updating epic to swarm molecule: %w", err) } @@ -253,7 +255,7 @@ func runSwarmCreate(cmd *cobra.Command, args []string) error { "--silent", } createCmd := exec.Command("bd", createArgs...) - createCmd.Dir = r.Path + createCmd.Dir = beadsPath var stdout bytes.Buffer createCmd.Stdout = &stdout if err := createCmd.Run(); err != nil { @@ -289,7 +291,7 @@ func runSwarmCreate(cmd *cobra.Command, args []string) error { if swarmStart { // Get swarm status to find ready tasks statusCmd := exec.Command("bd", "swarm", "status", swarmEpic, "--json") - statusCmd.Dir = r.Path + statusCmd.Dir = beadsPath var statusOut bytes.Buffer statusCmd.Stdout = &statusOut if err := statusCmd.Run(); err != nil { @@ -330,8 +332,9 @@ func runSwarmStart(cmd *cobra.Command, args []string) error { var foundRig *rig.Rig for _, r := range rigs { // 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.Dir = r.Path + checkCmd.Dir = r.BeadsPath() if err := checkCmd.Run(); err == nil { foundRig = r break @@ -344,7 +347,7 @@ func runSwarmStart(cmd *cobra.Command, args []string) error { // Get swarm status from beads statusCmd := exec.Command("bd", "swarm", "status", swarmID, "--json") - statusCmd.Dir = foundRig.Path + statusCmd.Dir = foundRig.BeadsPath() var stdout bytes.Buffer statusCmd.Stdout = &stdout @@ -468,8 +471,9 @@ func runSwarmStatus(cmd *cobra.Command, args []string) error { // Find which rig has this swarm var foundRig *rig.Rig for _, r := range rigs { + // Use BeadsPath() to ensure we read from git-synced location checkCmd := exec.Command("bd", "show", swarmID, "--json") - checkCmd.Dir = r.Path + checkCmd.Dir = r.BeadsPath() if err := checkCmd.Run(); err == nil { foundRig = r break @@ -487,7 +491,7 @@ func runSwarmStatus(cmd *cobra.Command, args []string) error { } bdCmd := exec.Command("bd", bdArgs...) - bdCmd.Dir = foundRig.Path + bdCmd.Dir = foundRig.BeadsPath() bdCmd.Stdout = os.Stdout bdCmd.Stderr = os.Stderr @@ -537,7 +541,7 @@ func runSwarmList(cmd *cobra.Command, args []string) error { for _, r := range rigs { bdCmd := exec.Command("bd", bdArgs...) - bdCmd.Dir = r.Path + bdCmd.Dir = r.BeadsPath() // Use BeadsPath() for git-synced beads var stdout bytes.Buffer bdCmd.Stdout = &stdout @@ -615,8 +619,9 @@ func runSwarmLand(cmd *cobra.Command, args []string) error { var foundRig *rig.Rig for _, r := range rigs { + // Use BeadsPath() for git-synced beads checkCmd := exec.Command("bd", "show", swarmID, "--json") - checkCmd.Dir = r.Path + checkCmd.Dir = r.BeadsPath() if err := checkCmd.Run(); err == nil { foundRig = r break @@ -629,7 +634,7 @@ func runSwarmLand(cmd *cobra.Command, args []string) error { // Check swarm status - all children should be closed statusCmd := exec.Command("bd", "swarm", "status", swarmID, "--json") - statusCmd.Dir = foundRig.Path + statusCmd.Dir = foundRig.BeadsPath() var stdout bytes.Buffer statusCmd.Stdout = &stdout @@ -678,7 +683,7 @@ func runSwarmLand(cmd *cobra.Command, args []string) error { // Close the swarm epic in beads 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 { 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 for _, r := range rigs { + // Use BeadsPath() for git-synced beads checkCmd := exec.Command("bd", "show", swarmID, "--json") - checkCmd.Dir = r.Path + checkCmd.Dir = r.BeadsPath() if err := checkCmd.Run(); err == nil { foundRig = r break @@ -714,7 +720,7 @@ func runSwarmCancel(cmd *cobra.Command, args []string) error { // Check if swarm is already closed checkCmd := exec.Command("bd", "show", swarmID, "--json") - checkCmd.Dir = foundRig.Path + checkCmd.Dir = foundRig.BeadsPath() var stdout bytes.Buffer checkCmd.Stdout = &stdout 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 closeCmd := exec.Command("bd", "close", swarmID, "--reason", "Swarm cancelled") - closeCmd.Dir = foundRig.Path + closeCmd.Dir = foundRig.BeadsPath() if err := closeCmd.Run(); err != nil { return fmt.Errorf("closing swarm: %w", err) } diff --git a/internal/rig/types.go b/internal/rig/types.go index 0edc9d5b..2d1a4e74 100644 --- a/internal/rig/types.go +++ b/internal/rig/types.go @@ -65,3 +65,14 @@ func (r *Rig) Summary() RigSummary { 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 +} diff --git a/internal/swarm/integration.go b/internal/swarm/integration.go index 262dd0dc..f84ea765 100644 --- a/internal/swarm/integration.go +++ b/internal/swarm/integration.go @@ -172,7 +172,7 @@ func (m *Manager) branchExists(branch string) bool { // getCurrentBranch returns the current branch name. func (m *Manager) getCurrentBranch() (string, error) { cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD") - cmd.Dir = m.workDir + cmd.Dir = m.gitDir var stdout bytes.Buffer cmd.Stdout = &stdout @@ -187,7 +187,7 @@ func (m *Manager) getCurrentBranch() (string, error) { // gitRun executes a git command. func (m *Manager) gitRun(args ...string) error { cmd := exec.Command("git", args...) - cmd.Dir = m.workDir + cmd.Dir = m.gitDir var stderr bytes.Buffer cmd.Stderr = &stderr diff --git a/internal/swarm/landing.go b/internal/swarm/landing.go index 24e65072..e5e9dc69 100644 --- a/internal/swarm/landing.go +++ b/internal/swarm/landing.go @@ -196,7 +196,7 @@ func (m *Manager) gitRunOutput(dir string, args ...string) (string, error) { // notifyMayorCodeAtRisk sends an alert to Mayor about code at risk. func (m *Manager) notifyMayorCodeAtRisk(townRoot, swarmID string, workers []string) { - router := mail.NewRouter(m.workDir) + router := mail.NewRouter(m.gitDir) msg := &mail.Message{ From: fmt.Sprintf("%s/refinery", m.rig.Name), To: "mayor/", @@ -215,7 +215,7 @@ Manual intervention required.`, // notifyMayorLanded sends a landing report to Mayor. func (m *Manager) notifyMayorLanded(townRoot string, swarm *Swarm, result *LandingResult) { - router := mail.NewRouter(m.workDir) + router := mail.NewRouter(m.gitDir) msg := &mail.Message{ From: fmt.Sprintf("%s/refinery", m.rig.Name), To: "mayor/", diff --git a/internal/swarm/manager.go b/internal/swarm/manager.go index af106a9f..342fcde9 100644 --- a/internal/swarm/manager.go +++ b/internal/swarm/manager.go @@ -23,15 +23,17 @@ var ( // Manager handles swarm lifecycle operations. // Manager is stateless - all swarm state is discovered from beads. type Manager struct { - rig *rig.Rig - workDir string + rig *rig.Rig + 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. func NewManager(r *rig.Rig) *Manager { return &Manager{ - rig: r, - workDir: r.Path, + rig: r, + 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) { // Query beads for the epic cmd := exec.Command("bd", "show", epicID, "--json") - cmd.Dir = m.workDir + cmd.Dir = m.beadsDir var stdout, stderr bytes.Buffer cmd.Stdout = &stdout @@ -126,7 +128,7 @@ func (m *Manager) GetSwarm(id string) (*Swarm, error) { func (m *Manager) GetReadyTasks(swarmID string) ([]SwarmTask, error) { // Use bd swarm status to get ready front cmd := exec.Command("bd", "swarm", "status", swarmID, "--json") - cmd.Dir = m.workDir + cmd.Dir = m.beadsDir var stdout bytes.Buffer 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. func (m *Manager) IsComplete(swarmID string) (bool, error) { cmd := exec.Command("bd", "swarm", "status", swarmID, "--json") - cmd.Dir = m.workDir + cmd.Dir = m.beadsDir var stdout bytes.Buffer cmd.Stdout = &stdout @@ -213,7 +215,7 @@ func isValidTransition(from, to SwarmState) bool { func (m *Manager) loadTasksFromBeads(epicID string) ([]SwarmTask, error) { // Run: bd show --json to get epic with children cmd := exec.Command("bd", "show", epicID, "--json") - cmd.Dir = m.workDir + cmd.Dir = m.beadsDir var stdout, stderr bytes.Buffer cmd.Stdout = &stdout @@ -275,7 +277,7 @@ func (m *Manager) loadTasksFromBeads(epicID string) ([]SwarmTask, error) { // getGitHead returns the current HEAD commit. func (m *Manager) getGitHead() (string, error) { cmd := exec.Command("git", "rev-parse", "HEAD") - cmd.Dir = m.workDir + cmd.Dir = m.gitDir var stdout bytes.Buffer cmd.Stdout = &stdout