feat(status): add deferred status for icebox issues (bd-4jr)
Add 'deferred' as a valid issue status for issues that are deliberately put on ice - not blocked by dependencies, just postponed for later. Changes: - Add StatusDeferred constant and update IsValid() validation - Add DeferredIssues to Statistics struct with counting in both SQLite and memory storage - Add 'bd defer' command to set status to deferred - Add 'bd undefer' command to restore status to open - Update help text across list, search, count, dep, stale, and config - Update MCP server models and tools to accept deferred status - Add deferred to blocker status checks (schema, cache, ready, compact) - Add StatusDeferred to public API exports (beads.go, internal/beads) - Add snowflake styling for deferred in dep tree and graph views Semantics: - deferred vs blocked: deferred is a choice, blocked is forced - deferred vs closed: deferred will be revisited, closed is done - Deferred issues excluded from 'bd ready' (already works since default filter only includes open/in_progress) - Deferred issues still block dependents (they are not done!) - Deferred issues visible in 'bd list' and 'bd stale' 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1033,7 +1033,7 @@ func (m *MemoryStorage) GetReadyWork(ctx context.Context, filter types.WorkFilte
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// getOpenBlockers returns the IDs of blockers that are currently open/in_progress/blocked.
|
||||
// getOpenBlockers returns the IDs of blockers that are currently open/in_progress/blocked/deferred.
|
||||
// The caller must hold at least a read lock.
|
||||
func (m *MemoryStorage) getOpenBlockers(issueID string) []string {
|
||||
deps := m.dependencies[issueID]
|
||||
@@ -1053,7 +1053,7 @@ func (m *MemoryStorage) getOpenBlockers(issueID string) []string {
|
||||
continue
|
||||
}
|
||||
switch blocker.Status {
|
||||
case types.StatusOpen, types.StatusInProgress, types.StatusBlocked:
|
||||
case types.StatusOpen, types.StatusInProgress, types.StatusBlocked, types.StatusDeferred:
|
||||
blockers = append(blockers, blocker.ID)
|
||||
}
|
||||
}
|
||||
@@ -1082,7 +1082,8 @@ func (m *MemoryStorage) GetBlockedIssues(ctx context.Context) ([]*types.BlockedI
|
||||
}
|
||||
|
||||
blockers := m.getOpenBlockers(issue.ID)
|
||||
if issue.Status != types.StatusBlocked && len(blockers) == 0 {
|
||||
// Issue is "blocked" if: status is blocked, status is deferred, or has open blockers
|
||||
if issue.Status != types.StatusBlocked && issue.Status != types.StatusDeferred && len(blockers) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1219,13 +1220,17 @@ func (m *MemoryStorage) GetStatistics(ctx context.Context) (*types.Statistics, e
|
||||
stats.InProgressIssues++
|
||||
case types.StatusClosed:
|
||||
stats.ClosedIssues++
|
||||
case types.StatusDeferred:
|
||||
stats.DeferredIssues++
|
||||
case types.StatusTombstone:
|
||||
stats.TombstoneIssues++
|
||||
case types.StatusPinned:
|
||||
stats.PinnedIssues++
|
||||
}
|
||||
}
|
||||
|
||||
// TotalIssues excludes tombstones (matches SQLite behavior)
|
||||
stats.TotalIssues = stats.OpenIssues + stats.InProgressIssues + stats.ClosedIssues
|
||||
stats.TotalIssues = stats.OpenIssues + stats.InProgressIssues + stats.ClosedIssues + stats.DeferredIssues + stats.PinnedIssues
|
||||
|
||||
// Second pass: calculate blocked and ready issues based on dependencies
|
||||
// An issue is blocked if it has open blockers (uses same logic as GetBlockedIssues)
|
||||
|
||||
Reference in New Issue
Block a user