feat: add cross-project dependency support - config and external: prefix (bd-66w1, bd-om4a)
Config (bd-66w1): - Add external_projects config for mapping project names to paths - Add GetExternalProjects() and ResolveExternalProjectPath() functions - Add config documentation and tests External deps (bd-om4a): - bd dep add accepts external:project:capability syntax - External refs stored as-is in dependencies table - GetBlockedIssues includes external deps in blocked_by list - blocked_issues_cache includes external dependencies - Add validation and parsing helpers for external refs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -274,12 +274,17 @@ func (s *SQLiteStorage) GetStaleIssues(ctx context.Context, filter types.StaleFi
|
||||
|
||||
// GetBlockedIssues returns issues that are blocked by dependencies or have status=blocked
|
||||
// Note: Pinned issues are excluded from the output (beads-ei4)
|
||||
// Note: Includes external: references in blocked_by list (bd-om4a)
|
||||
func (s *SQLiteStorage) GetBlockedIssues(ctx context.Context) ([]*types.BlockedIssue, error) {
|
||||
// Use UNION to combine:
|
||||
// 1. Issues with open/in_progress/blocked status that have dependency blockers
|
||||
// 2. Issues with status=blocked (even if they have no dependency blockers)
|
||||
// Use GROUP_CONCAT to get all blocker IDs in a single query (no N+1)
|
||||
// Exclude pinned issues (beads-ei4)
|
||||
//
|
||||
// For blocked_by_count and blocker_ids:
|
||||
// - Count local blockers (open issues) + external refs (external:*)
|
||||
// - External refs are always considered "open" until resolved (bd-om4a)
|
||||
rows, err := s.db.QueryContext(ctx, `
|
||||
SELECT
|
||||
i.id, i.title, i.description, i.design, i.acceptance_criteria, i.notes,
|
||||
@@ -290,16 +295,22 @@ func (s *SQLiteStorage) GetBlockedIssues(ctx context.Context) ([]*types.BlockedI
|
||||
FROM issues i
|
||||
LEFT JOIN dependencies d ON i.id = d.issue_id
|
||||
AND d.type = 'blocks'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM issues blocker
|
||||
WHERE blocker.id = d.depends_on_id
|
||||
AND blocker.status IN ('open', 'in_progress', 'blocked', 'deferred')
|
||||
AND (
|
||||
-- Local blockers: must be open/in_progress/blocked/deferred
|
||||
EXISTS (
|
||||
SELECT 1 FROM issues blocker
|
||||
WHERE blocker.id = d.depends_on_id
|
||||
AND blocker.status IN ('open', 'in_progress', 'blocked', 'deferred')
|
||||
)
|
||||
-- External refs: always included (resolution happens at query time)
|
||||
OR d.depends_on_id LIKE 'external:%'
|
||||
)
|
||||
WHERE i.status IN ('open', 'in_progress', 'blocked', 'deferred')
|
||||
AND i.pinned = 0
|
||||
AND (
|
||||
i.status = 'blocked'
|
||||
OR i.status = 'deferred'
|
||||
-- Has local open blockers
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM dependencies d2
|
||||
JOIN issues blocker ON d2.depends_on_id = blocker.id
|
||||
@@ -307,6 +318,13 @@ func (s *SQLiteStorage) GetBlockedIssues(ctx context.Context) ([]*types.BlockedI
|
||||
AND d2.type = 'blocks'
|
||||
AND blocker.status IN ('open', 'in_progress', 'blocked', 'deferred')
|
||||
)
|
||||
-- Has external blockers (always considered blocking until resolved)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM dependencies d3
|
||||
WHERE d3.issue_id = i.id
|
||||
AND d3.type = 'blocks'
|
||||
AND d3.depends_on_id LIKE 'external:%'
|
||||
)
|
||||
)
|
||||
GROUP BY i.id
|
||||
ORDER BY i.priority ASC
|
||||
|
||||
Reference in New Issue
Block a user