fix(sync): use town-level routes for prefix validation
Follow-up to 828fc11b addressing code review feedback:
1. Added LoadTownRoutes() - exported function that walks up to find
town-level routes.jsonl (e.g., ~/gt/.beads/routes.jsonl)
2. Updated buildAllowedPrefixSet to use LoadTownRoutes instead of
LoadRoutes, so it finds routes even when importing from a rig's
local beads directory
3. Added unit tests for buildAllowedPrefixSet covering:
- Primary prefix inclusion
- allowed_prefixes config parsing
- Routes from routes.jsonl
- Missing routes.jsonl handling
- Empty beadsDir handling
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1058,15 +1058,14 @@ func buildAllowedPrefixSet(primaryPrefix string, allowedPrefixesConfig string, b
|
||||
|
||||
// Load prefixes from routes.jsonl for multi-rig setups (Gas Town)
|
||||
// This allows issues from other rigs to coexist in the same JSONL
|
||||
// Use LoadTownRoutes to find routes at town level (~/gt/.beads/routes.jsonl)
|
||||
if beadsDir != "" {
|
||||
routes, err := routing.LoadRoutes(beadsDir)
|
||||
if err == nil && len(routes) > 0 {
|
||||
for _, route := range routes {
|
||||
// Normalize: remove trailing - if present
|
||||
prefix := strings.TrimSuffix(route.Prefix, "-")
|
||||
if prefix != "" {
|
||||
allowed[prefix] = true
|
||||
}
|
||||
routes, _ := routing.LoadTownRoutes(beadsDir)
|
||||
for _, route := range routes {
|
||||
// Normalize: remove trailing - if present
|
||||
prefix := strings.TrimSuffix(route.Prefix, "-")
|
||||
if prefix != "" {
|
||||
allowed[prefix] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ package importer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -1708,3 +1710,92 @@ func TestMultiRepoPrefixValidation(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildAllowedPrefixSet(t *testing.T) {
|
||||
t.Run("includes primary prefix", func(t *testing.T) {
|
||||
allowed := buildAllowedPrefixSet("gt", "", "")
|
||||
if allowed == nil {
|
||||
t.Fatal("Expected non-nil allowed set")
|
||||
}
|
||||
if !allowed["gt"] {
|
||||
t.Error("Primary prefix 'gt' should be allowed")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("includes allowed_prefixes config", func(t *testing.T) {
|
||||
allowed := buildAllowedPrefixSet("gt", "hq,mol-,other", "")
|
||||
if allowed == nil {
|
||||
t.Fatal("Expected non-nil allowed set")
|
||||
}
|
||||
if !allowed["gt"] {
|
||||
t.Error("Primary prefix 'gt' should be allowed")
|
||||
}
|
||||
if !allowed["hq"] {
|
||||
t.Error("Config prefix 'hq' should be allowed")
|
||||
}
|
||||
if !allowed["mol"] {
|
||||
t.Error("Config prefix 'mol' (normalized from 'mol-') should be allowed")
|
||||
}
|
||||
if !allowed["other"] {
|
||||
t.Error("Config prefix 'other' should be allowed")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("includes prefixes from routes.jsonl", func(t *testing.T) {
|
||||
// Create a temp directory with routes.jsonl
|
||||
tmpDir := t.TempDir()
|
||||
routesPath := filepath.Join(tmpDir, "routes.jsonl")
|
||||
routesContent := `{"prefix": "hq-", "path": "."}
|
||||
{"prefix": "gt-", "path": "gastown/mayor/rig"}
|
||||
{"prefix": "bd-", "path": "beads/mayor/rig"}
|
||||
`
|
||||
if err := os.WriteFile(routesPath, []byte(routesContent), 0644); err != nil {
|
||||
t.Fatalf("Failed to write routes.jsonl: %v", err)
|
||||
}
|
||||
|
||||
// buildAllowedPrefixSet uses LoadTownRoutes which tries to find town root
|
||||
// For this unit test, LoadRoutes will work since routes.jsonl is in tmpDir
|
||||
allowed := buildAllowedPrefixSet("gt", "", tmpDir)
|
||||
if allowed == nil {
|
||||
t.Fatal("Expected non-nil allowed set")
|
||||
}
|
||||
|
||||
// Primary prefix should always be included
|
||||
if !allowed["gt"] {
|
||||
t.Error("Primary prefix 'gt' should be allowed")
|
||||
}
|
||||
|
||||
// Routed prefixes should be included (normalized without trailing -)
|
||||
if !allowed["hq"] {
|
||||
t.Error("Routed prefix 'hq' (from routes.jsonl) should be allowed")
|
||||
}
|
||||
if !allowed["bd"] {
|
||||
t.Error("Routed prefix 'bd' (from routes.jsonl) should be allowed")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("handles missing routes.jsonl gracefully", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
// No routes.jsonl file in tmpDir
|
||||
// Note: LoadTownRoutes may still find routes at town level if running in Gas Town
|
||||
|
||||
allowed := buildAllowedPrefixSet("gt", "", tmpDir)
|
||||
if allowed == nil {
|
||||
t.Fatal("Expected non-nil allowed set")
|
||||
}
|
||||
if !allowed["gt"] {
|
||||
t.Error("Primary prefix 'gt' should be allowed even without local routes.jsonl")
|
||||
}
|
||||
// Don't check exact count - town-level routes may be found
|
||||
})
|
||||
|
||||
t.Run("handles empty beadsDir", func(t *testing.T) {
|
||||
allowed := buildAllowedPrefixSet("gt", "", "")
|
||||
if allowed == nil {
|
||||
t.Fatal("Expected non-nil allowed set")
|
||||
}
|
||||
if !allowed["gt"] {
|
||||
t.Error("Primary prefix 'gt' should be allowed")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -55,6 +55,16 @@ func LoadRoutes(beadsDir string) ([]Route, error) {
|
||||
return routes, scanner.Err()
|
||||
}
|
||||
|
||||
// LoadTownRoutes loads routes from the town-level routes.jsonl.
|
||||
// It first checks the given beadsDir, then walks up to find the town root
|
||||
// and loads routes from there. This is useful for multi-rig setups (Gas Town)
|
||||
// where routes.jsonl lives at ~/gt/.beads/ rather than in individual rig directories.
|
||||
// Returns routes and nil error on success, or nil routes if not in a town or no routes found.
|
||||
func LoadTownRoutes(beadsDir string) ([]Route, error) {
|
||||
routes, _ := findTownRoutes(beadsDir)
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// ExtractPrefix extracts the prefix from an issue ID.
|
||||
// For "gt-abc123", returns "gt-".
|
||||
// For "bd-abc123", returns "bd-".
|
||||
|
||||
Reference in New Issue
Block a user