Add dependency and dependent counts to bd list JSON output (#198)

When using `bd list --json`, each issue now includes:
- `dependency_count`: Number of issues this issue depends on
- `dependent_count`: Number of issues that depend on this issue

This provides quick access to dependency relationship counts without
needing to fetch full dependency lists or run multiple bd show commands.

Performance:
- Uses single bulk query (GetDependencyCounts) instead of N individual queries
- Overhead: ~26% for 500 issues (24ms vs 19ms baseline)
- Avoids N+1 query problem that would have caused 2.2x slowdown

Implementation:
- Added GetDependencyCounts() to Storage interface for bulk counting
- Efficient SQLite query using UNION ALL + GROUP BY
- Memory storage implementation for testing
- Moved IssueWithCounts to types package to avoid duplication
- Both RPC and direct modes use optimized bulk query

Tests:
- Added comprehensive tests for GetDependencyCounts
- Tests cover: normal operation, empty list, nonexistent IDs
- All existing tests continue to pass

Backwards compatible: JSON structure is additive, all original fields preserved.

Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Nikolai Prokoschenko
2025-11-02 03:59:15 +01:00
committed by GitHub
parent 21ab565819
commit c65cfa1ebd
8 changed files with 300 additions and 16 deletions

View File

@@ -119,27 +119,34 @@ var listCmd = &cobra.Command{
os.Exit(1)
}
if jsonOutput {
// For JSON output, preserve the full response with counts
var issuesWithCounts []*types.IssueWithCounts
if err := json.Unmarshal(resp.Data, &issuesWithCounts); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err)
os.Exit(1)
}
outputJSON(issuesWithCounts)
return
}
var issues []*types.Issue
if err := json.Unmarshal(resp.Data, &issues); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err)
os.Exit(1)
}
if jsonOutput {
outputJSON(issues)
} else {
fmt.Printf("\nFound %d issues:\n\n", len(issues))
for _, issue := range issues {
fmt.Printf("%s [P%d] [%s] %s\n", issue.ID, issue.Priority, issue.IssueType, issue.Status)
fmt.Printf(" %s\n", issue.Title)
if issue.Assignee != "" {
fmt.Printf(" Assignee: %s\n", issue.Assignee)
}
if len(issue.Labels) > 0 {
fmt.Printf(" Labels: %v\n", issue.Labels)
}
fmt.Println()
fmt.Printf("\nFound %d issues:\n\n", len(issues))
for _, issue := range issues {
fmt.Printf("%s [P%d] [%s] %s\n", issue.ID, issue.Priority, issue.IssueType, issue.Status)
fmt.Printf(" %s\n", issue.Title)
if issue.Assignee != "" {
fmt.Printf(" Assignee: %s\n", issue.Assignee)
}
if len(issue.Labels) > 0 {
fmt.Printf(" Labels: %v\n", issue.Labels)
}
fmt.Println()
}
return
}
@@ -178,7 +185,28 @@ var listCmd = &cobra.Command{
for _, issue := range issues {
issue.Labels, _ = store.GetLabels(ctx, issue.ID)
}
outputJSON(issues)
// Get dependency counts in bulk (single query instead of N queries)
issueIDs := make([]string, len(issues))
for i, issue := range issues {
issueIDs[i] = issue.ID
}
depCounts, _ := store.GetDependencyCounts(ctx, issueIDs)
// Build response with counts
issuesWithCounts := make([]*types.IssueWithCounts, len(issues))
for i, issue := range issues {
counts := depCounts[issue.ID]
if counts == nil {
counts = &types.DependencyCounts{DependencyCount: 0, DependentCount: 0}
}
issuesWithCounts[i] = &types.IssueWithCounts{
Issue: issue,
DependencyCount: counts.DependencyCount,
DependentCount: counts.DependentCount,
}
}
outputJSON(issuesWithCounts)
return
}