diff --git a/internal/routing/routing.go b/internal/routing/routing.go index 144b362c..990be7e7 100644 --- a/internal/routing/routing.go +++ b/internal/routing/routing.go @@ -29,7 +29,7 @@ const ( // Detection strategy: // 1. Check if user has push access to origin (git remote -v shows write URL) // 2. Check git config for beads.role setting (explicit override) -// 3. Fall back to contributor if uncertain +// 3. Fall back to maintainer for local projects (no remote configured) func DetectUserRole(repoPath string) (UserRole, error) { // First check for explicit role in git config output, err := gitCommandRunner(repoPath, "config", "--get", "beads.role") @@ -49,8 +49,8 @@ func DetectUserRole(repoPath string) (UserRole, error) { // Fallback to standard fetch URL if push URL fails (some git versions/configs) output, err = gitCommandRunner(repoPath, "remote", "get-url", "origin") if err != nil { - // No remote or error - default to contributor - return Contributor, nil + // No remote means local project - default to maintainer + return Maintainer, nil } } diff --git a/internal/routing/routing_test.go b/internal/routing/routing_test.go index fdcbf02b..d2afcfae 100644 --- a/internal/routing/routing_test.go +++ b/internal/routing/routing_test.go @@ -83,13 +83,13 @@ func TestDetermineTargetRepo(t *testing.T) { } func TestDetectUserRole_Fallback(t *testing.T) { - // Test fallback behavior when git is not available + // Test fallback behavior when git is not available - local projects default to maintainer role, err := DetectUserRole("/nonexistent/path/that/does/not/exist") if err != nil { t.Fatalf("DetectUserRole() error = %v, want nil", err) } - if role != Contributor { - t.Errorf("DetectUserRole() = %v, want %v (fallback)", role, Contributor) + if role != Maintainer { + t.Errorf("DetectUserRole() = %v, want %v (local project fallback)", role, Maintainer) } } @@ -267,7 +267,7 @@ func TestDetectUserRole_HTTPSCredentialsMaintainer(t *testing.T) { } } -func TestDetectUserRole_DefaultContributor(t *testing.T) { +func TestDetectUserRole_HTTPSNoCredentialsContributor(t *testing.T) { orig := gitCommandRunner stub := &gitStub{t: t, responses: []gitResponse{ {expect: gitCall{"", []string{"config", "--get", "beads.role"}}, err: errors.New("missing")}, @@ -289,6 +289,29 @@ func TestDetectUserRole_DefaultContributor(t *testing.T) { } } +func TestDetectUserRole_NoRemoteMaintainer(t *testing.T) { + // When no git remote is configured, default to maintainer (local project) + orig := gitCommandRunner + stub := &gitStub{t: t, responses: []gitResponse{ + {expect: gitCall{"/local", []string{"config", "--get", "beads.role"}}, err: errors.New("missing")}, + {expect: gitCall{"/local", []string{"remote", "get-url", "--push", "origin"}}, err: errors.New("no remote")}, + {expect: gitCall{"/local", []string{"remote", "get-url", "origin"}}, err: errors.New("no remote")}, + }} + gitCommandRunner = stub.run + t.Cleanup(func() { + gitCommandRunner = orig + stub.verify() + }) + + role, err := DetectUserRole("/local") + if err != nil { + t.Fatalf("DetectUserRole error = %v", err) + } + if role != Maintainer { + t.Fatalf("expected %s for local project with no remote, got %s", Maintainer, role) + } +} + // TestFindTownRoutes_SymlinkedBeadsDir verifies that findTownRoutes correctly // handles symlinked .beads directories by using findTownRootFromCWD() instead of // walking up from the beadsDir path.