feat: crew attach auto-detection, worktree polecats, beads mail

- gt crew at: auto-detect crew from cwd, run gt prime after launch
- Polecats now use git worktrees from refinery (faster than clones)
- Updated architecture.md for two-tier beads mail model
- Town beads (gm-*) for Mayor mail/coordination
- Rig .beads/ symlinks to refinery/rig/.beads

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-17 19:51:36 -08:00
parent cb04302e7b
commit 691971a16a
18 changed files with 343 additions and 160 deletions

View File

@@ -14,11 +14,8 @@ var ErrNotFound = errors.New("not in a Gas Town workspace")
// Markers used to detect a Gas Town workspace.
const (
// PrimaryMarker is the main config file that identifies a workspace.
PrimaryMarker = "config/town.json"
// AlternativePrimaryMarker is the town-level mayor config file.
// This distinguishes a town mayor from a rig-level mayor clone.
AlternativePrimaryMarker = "mayor/config.json"
// The town.json file lives in mayor/ along with other mayor config.
PrimaryMarker = "mayor/town.json"
// SecondaryMarker is an alternative indicator at the town level.
// Note: This can match rig-level mayors too, so we continue searching
@@ -27,8 +24,7 @@ const (
)
// Find locates the town root by walking up from the given directory.
// It looks for config/town.json or mayor/config.json (primary markers)
// or mayor/ directory (secondary marker).
// It looks for mayor/town.json (primary marker) or mayor/ directory (secondary marker).
//
// To avoid matching rig-level mayor directories, we continue searching
// upward after finding a secondary marker, preferring primary matches.
@@ -50,19 +46,12 @@ func Find(startDir string) (string, error) {
// Walk up the directory tree
current := absDir
for {
// Check for primary marker (config/town.json)
// Check for primary marker (mayor/town.json)
primaryPath := filepath.Join(current, PrimaryMarker)
if _, err := os.Stat(primaryPath); err == nil {
return current, nil
}
// Check for alternative primary marker (mayor/config.json)
// This distinguishes a town-level mayor from a rig-level mayor clone
altPrimaryPath := filepath.Join(current, AlternativePrimaryMarker)
if _, err := os.Stat(altPrimaryPath); err == nil {
return current, nil
}
// Check for secondary marker (mayor/ directory)
// Don't return immediately - continue searching for primary markers
if secondaryMatch == "" {
@@ -114,27 +103,21 @@ func FindFromCwdOrError() (string, error) {
}
// IsWorkspace checks if the given directory is a Gas Town workspace root.
// A directory is a workspace if it has primary markers (config/town.json
// or mayor/config.json) or a secondary marker (mayor/ directory).
// A directory is a workspace if it has a primary marker (mayor/town.json)
// or a secondary marker (mayor/ directory).
func IsWorkspace(dir string) (bool, error) {
absDir, err := filepath.Abs(dir)
if err != nil {
return false, fmt.Errorf("resolving path: %w", err)
}
// Check for primary marker
// Check for primary marker (mayor/town.json)
primaryPath := filepath.Join(absDir, PrimaryMarker)
if _, err := os.Stat(primaryPath); err == nil {
return true, nil
}
// Check for alternative primary marker
altPrimaryPath := filepath.Join(absDir, AlternativePrimaryMarker)
if _, err := os.Stat(altPrimaryPath); err == nil {
return true, nil
}
// Check for secondary marker
// Check for secondary marker (mayor/ directory)
secondaryPath := filepath.Join(absDir, SecondaryMarker)
info, err := os.Stat(secondaryPath)
if err == nil && info.IsDir() {

View File

@@ -18,11 +18,11 @@ func realPath(t *testing.T, path string) string {
func TestFindWithPrimaryMarker(t *testing.T) {
// Create temp workspace structure
root := realPath(t, t.TempDir())
configDir := filepath.Join(root, "config")
if err := os.MkdirAll(configDir, 0755); err != nil {
mayorDir := filepath.Join(root, "mayor")
if err := os.MkdirAll(mayorDir, 0755); err != nil {
t.Fatalf("mkdir: %v", err)
}
townFile := filepath.Join(configDir, "town.json")
townFile := filepath.Join(mayorDir, "town.json")
if err := os.WriteFile(townFile, []byte(`{"type":"town"}`), 0644); err != nil {
t.Fatalf("write: %v", err)
}
@@ -92,11 +92,11 @@ func TestFindOrErrorNotFound(t *testing.T) {
func TestFindAtRoot(t *testing.T) {
// Create workspace at temp root level
root := realPath(t, t.TempDir())
configDir := filepath.Join(root, "config")
if err := os.MkdirAll(configDir, 0755); err != nil {
mayorDir := filepath.Join(root, "mayor")
if err := os.MkdirAll(mayorDir, 0755); err != nil {
t.Fatalf("mkdir: %v", err)
}
townFile := filepath.Join(configDir, "town.json")
townFile := filepath.Join(mayorDir, "town.json")
if err := os.WriteFile(townFile, []byte(`{"type":"town"}`), 0644); err != nil {
t.Fatalf("write: %v", err)
}
@@ -123,12 +123,12 @@ func TestIsWorkspace(t *testing.T) {
t.Error("expected not a workspace initially")
}
// Add primary marker
configDir := filepath.Join(root, "config")
if err := os.MkdirAll(configDir, 0755); err != nil {
// Add primary marker (mayor/town.json)
mayorDir := filepath.Join(root, "mayor")
if err := os.MkdirAll(mayorDir, 0755); err != nil {
t.Fatalf("mkdir: %v", err)
}
townFile := filepath.Join(configDir, "town.json")
townFile := filepath.Join(mayorDir, "town.json")
if err := os.WriteFile(townFile, []byte(`{"type":"town"}`), 0644); err != nil {
t.Fatalf("write: %v", err)
}
@@ -146,11 +146,11 @@ func TestIsWorkspace(t *testing.T) {
func TestFindFollowsSymlinks(t *testing.T) {
// Create workspace
root := realPath(t, t.TempDir())
configDir := filepath.Join(root, "config")
if err := os.MkdirAll(configDir, 0755); err != nil {
mayorDir := filepath.Join(root, "mayor")
if err := os.MkdirAll(mayorDir, 0755); err != nil {
t.Fatalf("mkdir: %v", err)
}
townFile := filepath.Join(configDir, "town.json")
townFile := filepath.Join(mayorDir, "town.json")
if err := os.WriteFile(townFile, []byte(`{"type":"town"}`), 0644); err != nil {
t.Fatalf("write: %v", err)
}