From d14a18406d2741a202c027f1998379db62cad8e1 Mon Sep 17 00:00:00 2001 From: Matthew Newhook Date: Thu, 29 Jan 2026 10:40:34 -0500 Subject: [PATCH] Fix work creation to include blocked beads from epics When creating work from an epic, beads with a "blocks" dependency type (i.e., beads that depend on the epic) were not being included. Only parent-child relationships were collected. This caused dependent tasks like ac-naao, ac-nn90, ac-woty to be excluded when creating work from their parent epic ac-ynyu. Added collectChildrenAndBlocked helper to recursively collect both parent-child and blocks dependents when expanding an epic for work. Co-Authored-By: Claude Opus 4.5 --- internal/work/beads.go | 78 +++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/internal/work/beads.go b/internal/work/beads.go index c8fba72b..75b52029 100644 --- a/internal/work/beads.go +++ b/internal/work/beads.go @@ -8,8 +8,11 @@ import ( ) // CollectIssueIDsForAutomatedWorkflow collects all issue IDs to include in the workflow. -// For an issue with parent-child dependents, it includes all children recursively. -// For other issues, it includes all transitive dependencies. +// It includes: +// - The issue itself +// - All children recursively (parent-child dependents) +// - All blocked issues recursively (blocks dependents) +// - For issues without children/blocked, all transitive dependencies func CollectIssueIDsForAutomatedWorkflow(ctx context.Context, beadID string, beadsClient *beads.Client) ([]string, error) { if beadsClient == nil { return nil, fmt.Errorf("beads client is nil") @@ -24,30 +27,32 @@ func CollectIssueIDsForAutomatedWorkflow(ctx context.Context, beadID string, bea return nil, fmt.Errorf("bead %s not found", beadID) } - // Check if this issue has children (parent-child relationships) - var hasChildren bool + // Check if this issue has children or blocked issues + var hasChildrenOrBlocked bool for _, dep := range mainIssue.Dependents { - if dep.Type == "parent-child" { - hasChildren = true + if dep.Type == "parent-child" || dep.Type == "blocks" { + hasChildrenOrBlocked = true break } } - if hasChildren { - // Collect all children recursively - allIssues, err := beadsClient.GetBeadWithChildren(ctx, beadID) + if hasChildrenOrBlocked { + // Collect all children and blocked issues recursively + allIssueIDs, err := collectChildrenAndBlocked(ctx, beadID, beadsClient) if err != nil { - return nil, fmt.Errorf("failed to get children for %s: %w", beadID, err) + return nil, fmt.Errorf("failed to collect children and blocked for %s: %w", beadID, err) } - // Include all open issues for tracking + // Filter out closed issues var result []string - for _, issue := range allIssues { - // Skip closed issues - if issue.Status == beads.StatusClosed { - continue + for _, id := range allIssueIDs { + issue, err := beadsClient.GetBead(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to get bead %s: %w", id, err) + } + if issue != nil && issue.Status != beads.StatusClosed { + result = append(result, id) } - result = append(result, issue.ID) } return result, nil } @@ -69,6 +74,47 @@ func CollectIssueIDsForAutomatedWorkflow(ctx context.Context, beadID string, bea return issueIDs, nil } +// collectChildrenAndBlocked recursively collects all children (parent-child) and +// blocked issues (blocks) for a given bead. +func collectChildrenAndBlocked(ctx context.Context, beadID string, beadsClient *beads.Client) ([]string, error) { + visited := make(map[string]bool) + var orderedIDs []string + + var collect func(id string) error + collect = func(id string) error { + if visited[id] { + return nil + } + visited[id] = true + + // Add this bead first + orderedIDs = append(orderedIDs, id) + + // Get this bead to find its dependents + result, err := beadsClient.GetBeadsWithDeps(ctx, []string{id}) + if err != nil { + return err + } + + // Recursively collect all children and blocked issues + for _, dep := range result.Dependents[id] { + if (dep.Type == "parent-child" || dep.Type == "blocks") && !visited[dep.IssueID] { + if err := collect(dep.IssueID); err != nil { + return err + } + } + } + + return nil + } + + if err := collect(beadID); err != nil { + return nil, err + } + + return orderedIDs, nil +} + // CollectIssuesForMultipleIDs collects all issues to include for multiple bead IDs. // It collects transitive dependencies for each bead and deduplicates the results. func CollectIssuesForMultipleIDs(ctx context.Context, beadIDList []string, beadsClient *beads.Client) (*beads.BeadsWithDepsResult, error) {