feat(ready): add --unassigned filter for bd ready
Add -u/--unassigned flag to bd ready command to show only issues with no assignee. This supports the SCAVENGE protocol where polecats query the 'Salvage Yard' for unassigned ready work. Changes: - Add NoAssignee field to WorkFilter struct - Update SQLite GetReadyWork to filter by empty/null assignee - Add --unassigned/-u flag to ready command - Update RPC protocol and daemon handler - Add test for NoAssignee filter functionality Fixes: gt-3rp 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -39,7 +39,7 @@ var readyCmd = &cobra.Command{
|
||||
priority, _ := cmd.Flags().GetInt("priority")
|
||||
filter.Priority = &priority
|
||||
}
|
||||
if assignee != "" {
|
||||
if assignee != "" && !unassigned {
|
||||
filter.Assignee = &assignee
|
||||
}
|
||||
// Validate sort policy
|
||||
|
||||
@@ -260,3 +260,91 @@ func TestReadyCommandInit(t *testing.T) {
|
||||
t.Error("readyCmd should have Short description")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadyWorkUnassigned(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
testDB := filepath.Join(tmpDir, ".beads", "beads.db")
|
||||
s := newTestStore(t, testDB)
|
||||
ctx := context.Background()
|
||||
|
||||
// Create issues with different assignees
|
||||
issues := []*types.Issue{
|
||||
{
|
||||
ID: "test-unassigned-1",
|
||||
Title: "Unassigned task 1",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 1,
|
||||
IssueType: types.TypeTask,
|
||||
Assignee: "",
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
{
|
||||
ID: "test-unassigned-2",
|
||||
Title: "Unassigned task 2",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 2,
|
||||
IssueType: types.TypeTask,
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
{
|
||||
ID: "test-assigned-alice",
|
||||
Title: "Alice's task",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 1,
|
||||
IssueType: types.TypeTask,
|
||||
Assignee: "alice",
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
{
|
||||
ID: "test-assigned-bob",
|
||||
Title: "Bob's task",
|
||||
Status: types.StatusOpen,
|
||||
Priority: 1,
|
||||
IssueType: types.TypeTask,
|
||||
Assignee: "bob",
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, issue := range issues {
|
||||
if err := s.CreateIssue(ctx, issue, "test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test filtering by --unassigned
|
||||
readyUnassigned, err := s.GetReadyWork(ctx, types.WorkFilter{
|
||||
Unassigned: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("GetReadyWork with Unassigned filter failed: %v", err)
|
||||
}
|
||||
|
||||
// Should only have unassigned issues
|
||||
if len(readyUnassigned) != 2 {
|
||||
t.Errorf("Expected 2 unassigned issues, got %d", len(readyUnassigned))
|
||||
}
|
||||
|
||||
for _, issue := range readyUnassigned {
|
||||
if issue.Assignee != "" {
|
||||
t.Errorf("Expected no assignee, got %q for issue %s", issue.Assignee, issue.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that Unassigned takes precedence over Assignee filter
|
||||
alice := "alice"
|
||||
readyConflict, err := s.GetReadyWork(ctx, types.WorkFilter{
|
||||
Unassigned: true,
|
||||
Assignee: &alice,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("GetReadyWork with conflicting filters failed: %v", err)
|
||||
}
|
||||
|
||||
// Unassigned should win, returning only unassigned issues
|
||||
for _, issue := range readyConflict {
|
||||
if issue.Assignee != "" {
|
||||
t.Errorf("Unassigned should override Assignee filter, got %q for issue %s", issue.Assignee, issue.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user