fix: filter satisfied external deps from GetBlockedIssues (bd-396j)

GetBlockedIssues was showing external deps as blocking even when they
were satisfied (had a closed issue with provides:capability label).

Added filterBlockedByExternalDeps() which:
- Collects all external refs from blocked issues
- Checks each with CheckExternalDeps() in batch
- Filters satisfied refs from BlockedBy lists
- Updates BlockedByCount accordingly
- Removes issues with no remaining blockers (unless status=blocked/deferred)

This matches the behavior of GetReadyWork which already filters by
external deps correctly.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-22 21:48:55 -08:00
parent 25061ea9a7
commit 340417f393
2 changed files with 341 additions and 0 deletions

View File

@@ -490,9 +490,82 @@ func (s *SQLiteStorage) GetBlockedIssues(ctx context.Context) ([]*types.BlockedI
blocked = append(blocked, &issue)
}
// Filter out satisfied external dependencies from BlockedBy lists (bd-396j)
// Only check if external_projects are configured
if len(config.GetExternalProjects()) > 0 && len(blocked) > 0 {
blocked = filterBlockedByExternalDeps(ctx, blocked)
}
return blocked, nil
}
// filterBlockedByExternalDeps removes satisfied external deps from BlockedBy lists.
// Issues with no remaining blockers are removed unless they have status=blocked/deferred.
func filterBlockedByExternalDeps(ctx context.Context, blocked []*types.BlockedIssue) []*types.BlockedIssue {
if len(blocked) == 0 {
return blocked
}
// Collect all unique external refs across all blocked issues
externalRefs := make(map[string]bool)
for _, issue := range blocked {
for _, ref := range issue.BlockedBy {
if strings.HasPrefix(ref, "external:") {
externalRefs[ref] = true
}
}
}
// If no external refs, return as-is
if len(externalRefs) == 0 {
return blocked
}
// Check all external refs in batch
refList := make([]string, 0, len(externalRefs))
for ref := range externalRefs {
refList = append(refList, ref)
}
statuses := CheckExternalDeps(ctx, refList)
// Build set of satisfied refs
satisfiedRefs := make(map[string]bool)
for ref, status := range statuses {
if status.Satisfied {
satisfiedRefs[ref] = true
}
}
// If nothing is satisfied, return as-is
if len(satisfiedRefs) == 0 {
return blocked
}
// Filter each issue's BlockedBy list
result := make([]*types.BlockedIssue, 0, len(blocked))
for _, issue := range blocked {
// Filter out satisfied external deps
var filteredBlockers []string
for _, ref := range issue.BlockedBy {
if !satisfiedRefs[ref] {
filteredBlockers = append(filteredBlockers, ref)
}
}
// Update issue with filtered blockers
issue.BlockedBy = filteredBlockers
issue.BlockedByCount = len(filteredBlockers)
// Keep issue if it has remaining blockers OR has blocked/deferred status
// (status=blocked/deferred issues always show even with no dep blockers)
if len(filteredBlockers) > 0 || issue.Status == "blocked" || issue.Status == "deferred" {
result = append(result, issue)
}
}
return result
}
// buildOrderByClause generates the ORDER BY clause based on sort policy
func buildOrderByClause(policy types.SortPolicy) string {
switch policy {