feat: Unified beads redirect for tracked and local beads (#222)
* feat: Beads redirect architecture for tracked and local beads This change implements proper redirect handling so that all rig agents (Witness, Refinery, Crew, Polecats) can work with both: - Tracked beads: .beads/ checked into git at mayor/rig/.beads - Local beads: .beads/ created at rig root during gt rig add Key changes: 1. SetupRedirect now handles tracked beads by skipping redirect chains. The bd CLI doesn't support chains (A→B→C), so worktrees redirect directly to the final destination (mayor/rig/.beads for tracked). 2. ResolveBeadsDir is now used consistently in polecat and refinery managers instead of hardcoded mayor/rig paths. 3. Rig-level agents (witness, refinery) now use rig beads with rig prefix instead of town beads. This follows the architecture where town beads are only for Mayor/Deacon. 4. prime.go simplified to always use ../../.beads for crew redirects, letting rig-level redirect handle tracked vs local routing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(doctor): Add beads-redirect check for tracked beads When a repo has .beads/ tracked in git (at mayor/rig/.beads), the rig root needs a redirect file pointing to that location. This check: - Detects missing rig-level redirect for tracked beads - Verifies redirect points to correct location (mayor/rig/.beads) - Auto-fixes with 'gt doctor --fix' 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Handle fileLock.Unlock error in daemon Wrap fileLock.Unlock() return value to satisfy errcheck linter. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -367,6 +367,14 @@ func (m *Manager) AddRig(opts AddRigOptions) (*Rig, error) {
|
||||
return nil, fmt.Errorf("creating mayor CLAUDE.md: %w", err)
|
||||
}
|
||||
|
||||
// Initialize beads at rig level BEFORE creating worktrees.
|
||||
// This ensures rig/.beads exists so worktree redirects can point to it.
|
||||
fmt.Printf(" Initializing beads database...\n")
|
||||
if err := m.initBeads(rigPath, opts.BeadsPrefix); err != nil {
|
||||
return nil, fmt.Errorf("initializing beads: %w", err)
|
||||
}
|
||||
fmt.Printf(" ✓ Initialized beads (prefix: %s)\n", opts.BeadsPrefix)
|
||||
|
||||
// Create refinery as worktree from bare repo on default branch.
|
||||
// Refinery needs to see polecat branches (shared .repo.git) and merges them.
|
||||
// Being on the default branch allows direct merge workflow.
|
||||
@@ -379,6 +387,10 @@ func (m *Manager) AddRig(opts AddRigOptions) (*Rig, error) {
|
||||
return nil, fmt.Errorf("creating refinery worktree: %w", err)
|
||||
}
|
||||
fmt.Printf(" ✓ Created refinery worktree\n")
|
||||
// Set up beads redirect for refinery (points to rig-level .beads)
|
||||
if err := beads.SetupRedirect(m.townRoot, refineryRigPath); err != nil {
|
||||
fmt.Printf(" Warning: Could not set up refinery beads redirect: %v\n", err)
|
||||
}
|
||||
// Create refinery CLAUDE.md (overrides any from cloned repo)
|
||||
if err := m.createRoleCLAUDEmd(refineryRigPath, "refinery", opts.Name, ""); err != nil {
|
||||
return nil, fmt.Errorf("creating refinery CLAUDE.md: %w", err)
|
||||
@@ -433,13 +445,6 @@ Use crew for your own workspace. Polecats are for batch work dispatch.
|
||||
return nil, fmt.Errorf("creating polecats dir: %w", err)
|
||||
}
|
||||
|
||||
// Initialize beads at rig level
|
||||
fmt.Printf(" Initializing beads database...\n")
|
||||
if err := m.initBeads(rigPath, opts.BeadsPrefix); err != nil {
|
||||
return nil, fmt.Errorf("initializing beads: %w", err)
|
||||
}
|
||||
fmt.Printf(" ✓ Initialized beads (prefix: %s)\n", opts.BeadsPrefix)
|
||||
|
||||
// Create rig-level agent beads (witness, refinery) in rig beads.
|
||||
// Town-level agents (mayor, deacon) are created by gt install in town beads.
|
||||
if err := m.initAgentBeads(rigPath, opts.Name, opts.BeadsPrefix); err != nil {
|
||||
@@ -508,6 +513,23 @@ func (m *Manager) initBeads(rigPath, prefix string) error {
|
||||
}
|
||||
|
||||
beadsDir := filepath.Join(rigPath, ".beads")
|
||||
mayorRigBeads := filepath.Join(rigPath, "mayor", "rig", ".beads")
|
||||
|
||||
// Check if source repo has tracked .beads/ (cloned into mayor/rig).
|
||||
// If so, create a redirect file instead of a new database.
|
||||
if _, err := os.Stat(mayorRigBeads); err == nil {
|
||||
// Tracked beads exist - create redirect to mayor/rig/.beads
|
||||
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
redirectPath := filepath.Join(beadsDir, "redirect")
|
||||
if err := os.WriteFile(redirectPath, []byte("mayor/rig/.beads\n"), 0644); err != nil {
|
||||
return fmt.Errorf("creating redirect file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// No tracked beads - create local database
|
||||
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -572,7 +594,8 @@ func (m *Manager) initBeads(rigPath, prefix string) error {
|
||||
func (m *Manager) initAgentBeads(rigPath, rigName, prefix string) error {
|
||||
// Rig-level agents go in rig beads with rig prefix (per docs/architecture.md).
|
||||
// Town-level agents (Mayor, Deacon) are created by gt install in town beads.
|
||||
rigBeadsDir := filepath.Join(rigPath, ".beads")
|
||||
// Use ResolveBeadsDir to follow redirect files for tracked beads.
|
||||
rigBeadsDir := beads.ResolveBeadsDir(rigPath)
|
||||
bd := beads.NewWithBeadsDir(rigPath, rigBeadsDir)
|
||||
|
||||
// Define rig-level agents to create
|
||||
|
||||
Reference in New Issue
Block a user