feat(rig): add rig identity bead schema and creation (gt-zmznh)

- Add RigFields struct, CreateRigBead, RigBeadID helpers to beads package
- Modify gt rig add to create rig identity bead after rig creation
- Schema: id=<prefix>-rig-<name>, type=rig, with repo/prefix/state fields

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
furiosa
2026-01-06 18:54:55 -08:00
committed by Steve Yegge
parent 1f44482ad0
commit ec53dfbb40
2 changed files with 131 additions and 0 deletions

View File

@@ -1780,3 +1780,113 @@ func (b *Beads) MergeSlotEnsureExists() (string, error) {
return status.ID, nil
}
// ===== Rig Identity Beads =====
// RigFields contains the fields specific to rig identity beads.
type RigFields struct {
Repo string // Git URL for the rig's repository
Prefix string // Beads prefix for this rig (e.g., "gt", "bd")
State string // Operational state: active, archived, maintenance
}
// FormatRigDescription formats the description field for a rig identity bead.
func FormatRigDescription(name string, fields *RigFields) string {
if fields == nil {
return ""
}
var lines []string
lines = append(lines, fmt.Sprintf("Rig identity bead for %s.", name))
lines = append(lines, "")
if fields.Repo != "" {
lines = append(lines, fmt.Sprintf("repo: %s", fields.Repo))
}
if fields.Prefix != "" {
lines = append(lines, fmt.Sprintf("prefix: %s", fields.Prefix))
}
if fields.State != "" {
lines = append(lines, fmt.Sprintf("state: %s", fields.State))
}
return strings.Join(lines, "\n")
}
// ParseRigFields extracts rig fields from an issue's description.
func ParseRigFields(description string) *RigFields {
fields := &RigFields{}
for _, line := range strings.Split(description, "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
colonIdx := strings.Index(line, ":")
if colonIdx == -1 {
continue
}
key := strings.TrimSpace(line[:colonIdx])
value := strings.TrimSpace(line[colonIdx+1:])
if value == "null" || value == "" {
value = ""
}
switch strings.ToLower(key) {
case "repo":
fields.Repo = value
case "prefix":
fields.Prefix = value
case "state":
fields.State = value
}
}
return fields
}
// CreateRigBead creates a rig identity bead for tracking rig metadata.
// The ID format is: <prefix>-rig-<name> (e.g., gt-rig-gastown)
// Use RigBeadID() helper to generate correct IDs.
// The created_by field is populated from BD_ACTOR env var for provenance tracking.
func (b *Beads) CreateRigBead(id, title string, fields *RigFields) (*Issue, error) {
description := FormatRigDescription(title, fields)
args := []string{"create", "--json",
"--id=" + id,
"--type=rig",
"--title=" + title,
"--description=" + description,
}
// Default actor from BD_ACTOR env var for provenance tracking
if actor := os.Getenv("BD_ACTOR"); actor != "" {
args = append(args, "--actor="+actor)
}
out, err := b.run(args...)
if err != nil {
return nil, err
}
var issue Issue
if err := json.Unmarshal(out, &issue); err != nil {
return nil, fmt.Errorf("parsing bd create output: %w", err)
}
return &issue, nil
}
// RigBeadIDWithPrefix generates a rig identity bead ID using the specified prefix.
// Format: <prefix>-rig-<name> (e.g., gt-rig-gastown)
func RigBeadIDWithPrefix(prefix, name string) string {
return fmt.Sprintf("%s-rig-%s", prefix, name)
}
// RigBeadID generates a rig identity bead ID using "gt" prefix.
// For non-gastown rigs, use RigBeadIDWithPrefix with the rig's configured prefix.
func RigBeadID(name string) string {
return RigBeadIDWithPrefix("gt", name)
}

View File

@@ -366,12 +366,16 @@ func runRigAdd(cmd *cobra.Command, args []string) error {
// - Otherwise route to rig root (where initBeads creates the database)
// The conditional routing is necessary because initBeads creates the database at
// "<rig>/.beads", while repos with tracked beads have their database at mayor/rig/.beads.
var beadsWorkDir string
if newRig.Config.Prefix != "" {
routePath := name
mayorRigBeads := filepath.Join(townRoot, name, "mayor", "rig", ".beads")
if _, err := os.Stat(mayorRigBeads); err == nil {
// Source repo has .beads/ tracked - route to mayor/rig
routePath = name + "/mayor/rig"
beadsWorkDir = filepath.Join(townRoot, name, "mayor", "rig")
} else {
beadsWorkDir = filepath.Join(townRoot, name)
}
route := beads.Route{
Prefix: newRig.Config.Prefix + "-",
@@ -383,6 +387,23 @@ func runRigAdd(cmd *cobra.Command, args []string) error {
}
}
// Create rig identity bead
if newRig.Config.Prefix != "" && beadsWorkDir != "" {
bd := beads.New(beadsWorkDir)
rigBeadID := beads.RigBeadIDWithPrefix(newRig.Config.Prefix, name)
fields := &beads.RigFields{
Repo: gitURL,
Prefix: newRig.Config.Prefix,
State: "active",
}
if _, err := bd.CreateRigBead(rigBeadID, name, fields); err != nil {
// Non-fatal: rig is functional without the identity bead
fmt.Printf(" %s Could not create rig identity bead: %v\n", style.Warning.Render("!"), err)
} else {
fmt.Printf(" Created rig identity bead: %s\n", rigBeadID)
}
}
elapsed := time.Since(startTime)
// Read default branch from rig config