Merge pull request #106 from danshapiro/fix/install-role-slots-pr

CI failures unrelated to this PR (formula embed issue)
This commit is contained in:
Steve Yegge
2026-01-04 12:56:33 -08:00
committed by GitHub
2 changed files with 103 additions and 38 deletions

View File

@@ -344,44 +344,6 @@ func ensureRepoFingerprint(beadsPath string) error {
func initTownAgentBeads(townPath string) error {
bd := beads.New(townPath)
// Town-level agent beads
agentDefs := []struct {
id string
roleType string
title string
}{
{
id: beads.MayorBeadIDTown(),
roleType: "mayor",
title: "Mayor - global coordinator, handles cross-rig communication and escalations.",
},
{
id: beads.DeaconBeadIDTown(),
roleType: "deacon",
title: "Deacon (daemon beacon) - receives mechanical heartbeats, runs town plugins and monitoring.",
},
}
for _, agent := range agentDefs {
// Check if already exists
if _, err := bd.Show(agent.id); err == nil {
continue // Already exists
}
fields := &beads.AgentFields{
RoleType: agent.roleType,
Rig: "", // Town-level agents have no rig
AgentState: "idle",
HookBead: "",
RoleBead: beads.RoleBeadIDTown(agent.roleType),
}
if _, err := bd.CreateAgentBead(agent.id, agent.title, fields); err != nil {
return fmt.Errorf("creating %s: %w", agent.id, err)
}
fmt.Printf(" ✓ Created agent bead: %s\n", agent.id)
}
// Role beads (global templates)
roleDefs := []struct {
id string
@@ -448,5 +410,55 @@ func initTownAgentBeads(townPath string) error {
fmt.Printf(" ✓ Created role bead: %s\n", role.id)
}
// Town-level agent beads
agentDefs := []struct {
id string
roleType string
title string
}{
{
id: beads.MayorBeadIDTown(),
roleType: "mayor",
title: "Mayor - global coordinator, handles cross-rig communication and escalations.",
},
{
id: beads.DeaconBeadIDTown(),
roleType: "deacon",
title: "Deacon (daemon beacon) - receives mechanical heartbeats, runs town plugins and monitoring.",
},
}
existingAgents, err := bd.List(beads.ListOptions{
Status: "all",
Type: "agent",
Priority: -1,
})
if err != nil {
return fmt.Errorf("listing existing agent beads: %w", err)
}
existingAgentIDs := make(map[string]struct{}, len(existingAgents))
for _, issue := range existingAgents {
existingAgentIDs[issue.ID] = struct{}{}
}
for _, agent := range agentDefs {
if _, ok := existingAgentIDs[agent.id]; ok {
continue
}
fields := &beads.AgentFields{
RoleType: agent.roleType,
Rig: "", // Town-level agents have no rig
AgentState: "idle",
HookBead: "",
RoleBead: beads.RoleBeadIDTown(agent.roleType),
}
if _, err := bd.CreateAgentBead(agent.id, agent.title, fields); err != nil {
return fmt.Errorf("creating %s: %w", agent.id, err)
}
fmt.Printf(" ✓ Created agent bead: %s\n", agent.id)
}
return nil
}

View File

@@ -112,6 +112,31 @@ func TestInstallBeadsHasCorrectPrefix(t *testing.T) {
}
}
// TestInstallTownRoleSlots validates that town-level agent beads
// have their role slot set after install.
func TestInstallTownRoleSlots(t *testing.T) {
// Skip if bd is not available
if _, err := exec.LookPath("bd"); err != nil {
t.Skip("bd not installed, skipping role slot test")
}
tmpDir := t.TempDir()
hqPath := filepath.Join(tmpDir, "test-hq")
gtBinary := buildGT(t)
// Run gt install (includes beads init by default)
cmd := exec.Command(gtBinary, "install", hqPath)
cmd.Env = append(os.Environ(), "HOME="+tmpDir)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("gt install failed: %v\nOutput: %s", err, output)
}
assertSlotValue(t, hqPath, "hq-mayor", "role", "hq-mayor-role")
assertSlotValue(t, hqPath, "hq-deacon", "role", "hq-deacon-role")
}
// TestInstallIdempotent validates that running gt install twice
// on the same directory fails without --force flag.
func TestInstallIdempotent(t *testing.T) {
@@ -295,3 +320,31 @@ func assertFileExists(t *testing.T, path, name string) {
t.Errorf("%s is a directory, expected file", name)
}
}
func assertSlotValue(t *testing.T, townRoot, issueID, slot, want string) {
t.Helper()
cmd := exec.Command("bd", "--no-daemon", "--json", "slot", "show", issueID)
cmd.Dir = townRoot
output, err := cmd.Output()
if err != nil {
debugCmd := exec.Command("bd", "--no-daemon", "--json", "slot", "show", issueID)
debugCmd.Dir = townRoot
combined, _ := debugCmd.CombinedOutput()
t.Fatalf("bd slot show %s failed: %v\nOutput: %s", issueID, err, combined)
}
var parsed struct {
Slots map[string]*string `json:"slots"`
}
if err := json.Unmarshal(output, &parsed); err != nil {
t.Fatalf("parsing slot show output failed: %v\nOutput: %s", err, output)
}
var got string
if value, ok := parsed.Slots[slot]; ok && value != nil {
got = *value
}
if got != want {
t.Fatalf("slot %s for %s = %q, want %q", slot, issueID, got, want)
}
}