Skip to content

Commit 7f9ee3d

Browse files
steveyeggeclaude
andcommitted
fix(graph): traverse all dependency types, not just parent-child
The graph command now shows issues connected via any dependency type (blocks, parent-child, etc.), not just parent-child relationships. This makes bd graph useful for epics where children are connected via blocks dependencies rather than hierarchical parent-child. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a89de1a commit 7f9ee3d

1 file changed

Lines changed: 66 additions & 2 deletions

File tree

cmd/bd/graph.go

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,73 @@ func init() {
108108
}
109109

110110
// loadGraphSubgraph loads an issue and its subgraph for visualization
111-
// Reuses template subgraph loading logic
111+
// Unlike template loading, this includes ALL dependency types (not just parent-child)
112112
func loadGraphSubgraph(ctx context.Context, s storage.Storage, issueID string) (*TemplateSubgraph, error) {
113-
return loadTemplateSubgraph(ctx, s, issueID)
113+
if s == nil {
114+
return nil, fmt.Errorf("no database connection")
115+
}
116+
117+
// Get the root issue
118+
root, err := s.GetIssue(ctx, issueID)
119+
if err != nil {
120+
return nil, fmt.Errorf("failed to get issue: %w", err)
121+
}
122+
if root == nil {
123+
return nil, fmt.Errorf("issue %s not found", issueID)
124+
}
125+
126+
subgraph := &TemplateSubgraph{
127+
Root: root,
128+
Issues: []*types.Issue{root},
129+
IssueMap: map[string]*types.Issue{root.ID: root},
130+
}
131+
132+
// BFS to find all connected issues (via any dependency type)
133+
// We traverse both directions: dependents and dependencies
134+
queue := []string{root.ID}
135+
visited := map[string]bool{root.ID: true}
136+
137+
for len(queue) > 0 {
138+
currentID := queue[0]
139+
queue = queue[1:]
140+
141+
// Get issues that depend on this one (dependents)
142+
dependents, err := s.GetDependents(ctx, currentID)
143+
if err != nil {
144+
continue
145+
}
146+
for _, dep := range dependents {
147+
if !visited[dep.ID] {
148+
visited[dep.ID] = true
149+
subgraph.Issues = append(subgraph.Issues, dep)
150+
subgraph.IssueMap[dep.ID] = dep
151+
queue = append(queue, dep.ID)
152+
}
153+
}
154+
155+
// Get issues this one depends on (dependencies) - but only for non-root
156+
// to avoid pulling in unrelated upstream issues
157+
if currentID == root.ID {
158+
// For root, we might want to include direct dependencies too
159+
// Skip for now to keep graph focused on "what this issue encompasses"
160+
}
161+
}
162+
163+
// Load all dependencies within the subgraph
164+
for _, issue := range subgraph.Issues {
165+
deps, err := s.GetDependencyRecords(ctx, issue.ID)
166+
if err != nil {
167+
continue
168+
}
169+
for _, dep := range deps {
170+
// Only include dependencies where both ends are in the subgraph
171+
if _, ok := subgraph.IssueMap[dep.DependsOnID]; ok {
172+
subgraph.Dependencies = append(subgraph.Dependencies, dep)
173+
}
174+
}
175+
}
176+
177+
return subgraph, nil
114178
}
115179

116180
// computeLayout assigns layers to nodes using topological sort

0 commit comments

Comments
 (0)