diff --git a/internal/rig/manager.go b/internal/rig/manager.go index 28536219..fbaf783b 100644 --- a/internal/rig/manager.go +++ b/internal/rig/manager.go @@ -418,8 +418,9 @@ func (m *Manager) initBeads(rigPath, prefix string) error { // Run bd init if available, with --no-agents to skip AGENTS.md creation cmd := exec.Command("bd", "init", "--prefix", prefix, "--no-agents") cmd.Dir = rigPath - if err := cmd.Run(); err != nil { - // bd might not be installed or --no-agents not supported, create minimal structure + _, err := cmd.CombinedOutput() + if err != nil { + // bd might not be installed or failed, create minimal structure // Note: beads currently expects YAML format for config configPath := filepath.Join(beadsDir, "config.yaml") configContent := fmt.Sprintf("prefix: %s\n", prefix) @@ -438,6 +439,18 @@ func (m *Manager) initBeads(rigPath, prefix string) error { func (m *Manager) initAgentBeads(rigPath, rigName, prefix string, isFirstRig bool) error { // Run bd commands from mayor/rig which has the beads database mayorRigPath := filepath.Join(rigPath, "mayor", "rig") + beadsDir := filepath.Join(rigPath, ".beads") + prevBeadsDir, hadBeadsDir := os.LookupEnv("BEADS_DIR") + if err := os.Setenv("BEADS_DIR", beadsDir); err != nil { + return fmt.Errorf("setting BEADS_DIR: %w", err) + } + defer func() { + if hadBeadsDir { + _ = os.Setenv("BEADS_DIR", prevBeadsDir) + } else { + _ = os.Unsetenv("BEADS_DIR") + } + }() bd := beads.New(mayorRigPath) // Define agents to create diff --git a/internal/rig/manager_test.go b/internal/rig/manager_test.go index eaf3908f..6868ac0e 100644 --- a/internal/rig/manager_test.go +++ b/internal/rig/manager_test.go @@ -21,6 +21,16 @@ func setupTestTown(t *testing.T) (string, *config.RigsConfig) { return root, rigsConfig } +func writeFakeBD(t *testing.T, script string) string { + t.Helper() + binDir := t.TempDir() + scriptPath := filepath.Join(binDir, "bd") + if err := os.WriteFile(scriptPath, []byte(script), 0755); err != nil { + t.Fatalf("write fake bd: %v", err) + } + return binDir +} + func createTestRig(t *testing.T, root, name string) { t.Helper() @@ -251,3 +261,104 @@ func TestEnsureGitignoreEntry_AppendsToExisting(t *testing.T) { t.Errorf("content = %q, want %q", string(content), expected) } } + +func TestInitBeadsWritesConfigOnFailure(t *testing.T) { + rigPath := t.TempDir() + beadsDir := filepath.Join(rigPath, ".beads") + + script := `#!/usr/bin/env bash +set -e +cmd="$1" +shift +if [[ "$cmd" == "init" ]]; then + echo "bd init failed" >&2 + exit 1 +fi +echo "unexpected command: $cmd" >&2 +exit 1 +` + + binDir := writeFakeBD(t, script) + t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH")) + t.Setenv("EXPECT_BEADS_DIR", beadsDir) + + manager := &Manager{} + if err := manager.initBeads(rigPath, "gt"); err != nil { + t.Fatalf("initBeads: %v", err) + } + + configPath := filepath.Join(beadsDir, "config.yaml") + config, err := os.ReadFile(configPath) + if err != nil { + t.Fatalf("reading config.yaml: %v", err) + } + if string(config) != "prefix: gt\n" { + t.Fatalf("config.yaml = %q, want %q", string(config), "prefix: gt\n") + } +} + +func TestInitAgentBeadsUsesRigBeadsDir(t *testing.T) { + rigPath := t.TempDir() + beadsDir := filepath.Join(rigPath, ".beads") + mayorRigPath := filepath.Join(rigPath, "mayor", "rig") + + if err := os.MkdirAll(beadsDir, 0755); err != nil { + t.Fatalf("mkdir beads dir: %v", err) + } + if err := os.MkdirAll(mayorRigPath, 0755); err != nil { + t.Fatalf("mkdir mayor rig: %v", err) + } + + script := `#!/usr/bin/env bash +set -e +if [[ "$1" == "--no-daemon" ]]; then + shift +fi +cmd="$1" +shift +case "$cmd" in + show) + if [[ "$BEADS_DIR" != "$EXPECT_BEADS_DIR" ]]; then + echo "BEADS_DIR mismatch" >&2 + exit 1 + fi + echo "[]" + ;; + create) + if [[ "$BEADS_DIR" != "$EXPECT_BEADS_DIR" ]]; then + echo "BEADS_DIR mismatch" >&2 + exit 1 + fi + id="" + title="" + for arg in "$@"; do + case "$arg" in + --id=*) id="${arg#--id=}" ;; + --title=*) title="${arg#--title=}" ;; + esac + done + printf '{"id":"%s","title":"%s","description":"","issue_type":"agent"}' "$id" "$title" + ;; + slot) + if [[ "$BEADS_DIR" != "$EXPECT_BEADS_DIR" ]]; then + echo "BEADS_DIR mismatch" >&2 + exit 1 + fi + ;; + *) + echo "unexpected command: $cmd" >&2 + exit 1 + ;; +esac +` + + binDir := writeFakeBD(t, script) + t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH")) + t.Setenv("EXPECT_BEADS_DIR", beadsDir) + t.Setenv("BEADS_DIR", "") + + manager := &Manager{} + if err := manager.initAgentBeads(rigPath, "demo", "gt", false); err != nil { + t.Fatalf("initAgentBeads: %v", err) + } +}