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:
Julian Knutsen
2026-01-06 12:59:37 -08:00
committed by GitHub
parent 16fb45bb2a
commit 9d7dcde1e2
11 changed files with 1075 additions and 231 deletions

View File

@@ -159,9 +159,9 @@ func TestDoneBeadsInitBothCodePaths(t *testing.T) {
}
// TestDoneRedirectChain verifies behavior with chained redirects.
// ResolveBeadsDir follows exactly one level of redirect by design - it does NOT
// follow chains transitively. This is intentional: chains typically indicate
// misconfiguration (e.g., a redirect file that shouldn't exist).
// ResolveBeadsDir follows chains up to depth 3 as a safety net for legacy configs.
// SetupRedirect avoids creating chains (bd CLI doesn't support them), but if
// chains exist we follow them to the final destination.
func TestDoneRedirectChain(t *testing.T) {
tmpDir := t.TempDir()
@@ -189,14 +189,15 @@ func TestDoneRedirectChain(t *testing.T) {
t.Fatalf("write worktree redirect: %v", err)
}
// ResolveBeadsDir follows exactly one level - stops at intermediate
// (A warning is printed about the chain, but intermediate is returned)
// ResolveBeadsDir follows chains up to depth 3 as a safety net.
// Note: SetupRedirect avoids creating chains (bd CLI doesn't support them),
// but if chains exist from legacy configs, we follow them to the final destination.
resolved := beads.ResolveBeadsDir(worktreeDir)
// Should resolve to intermediate (one level), NOT canonical (two levels)
if resolved != intermediateBeadsDir {
t.Errorf("ResolveBeadsDir should follow one level only: got %s, want %s",
resolved, intermediateBeadsDir)
// Should resolve to canonical (follows the full chain)
if resolved != canonicalBeadsDir {
t.Errorf("ResolveBeadsDir should follow chain to final destination: got %s, want %s",
resolved, canonicalBeadsDir)
}
}